| use crate::*; |
| |
| /// A slice containing an ICMPv6 network package. |
| /// |
| /// Struct allows the selective read of fields in the ICMPv6 |
| /// packet. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct Icmpv6Slice<'a> { |
| pub(crate) slice: &'a [u8], |
| } |
| |
| impl<'a> Icmpv6Slice<'a> { |
| /// Creates a slice containing an ICMPv6 packet. |
| /// |
| /// # Errors |
| /// |
| /// The function will return an `Err` [`err::LenError`] |
| /// if the given slice is too small (smaller then [`Icmpv6Header::MIN_LEN`]) or |
| /// too large (bigger then [`icmpv6::MAX_ICMPV6_BYTE_LEN`]). |
| #[inline] |
| pub fn from_slice(slice: &'a [u8]) -> Result<Icmpv6Slice<'a>, err::LenError> { |
| //check length |
| if slice.len() < Icmpv6Header::MIN_LEN { |
| return Err(err::LenError { |
| required_len: Icmpv6Header::MIN_LEN, |
| len: slice.len(), |
| len_source: LenSource::Slice, |
| layer: err::Layer::Icmpv6, |
| layer_start_offset: 0, |
| }); |
| } |
| if slice.len() > icmpv6::MAX_ICMPV6_BYTE_LEN { |
| return Err(err::LenError { |
| required_len: icmpv6::MAX_ICMPV6_BYTE_LEN, |
| len: slice.len(), |
| len_source: LenSource::Slice, |
| layer: err::Layer::Icmpv6, |
| layer_start_offset: 0, |
| }); |
| } |
| |
| //done |
| Ok(Icmpv6Slice { slice }) |
| } |
| |
| /// Decode the header fields and copy the results to a [`Icmpv6Header`] struct. |
| #[inline] |
| pub fn header(&self) -> Icmpv6Header { |
| Icmpv6Header { |
| icmp_type: self.icmp_type(), |
| checksum: self.checksum(), |
| } |
| } |
| |
| /// Number of bytes/octets that will be converted into a |
| /// [`Icmpv6Header`] when [`Icmpv6Slice::header`] gets called. |
| #[inline] |
| pub fn header_len(&self) -> usize { |
| 8 |
| } |
| |
| /// Decode the header values (excluding the checksum) into an [`Icmpv6Type`] enum. |
| pub fn icmp_type(&self) -> Icmpv6Type { |
| use crate::{icmpv6::*, Icmpv6Type::*}; |
| |
| match self.type_u8() { |
| TYPE_DST_UNREACH => { |
| if let Some(code) = DestUnreachableCode::from_u8(self.code_u8()) { |
| return DestinationUnreachable(code); |
| } |
| } |
| TYPE_PACKET_TOO_BIG => { |
| if 0 == self.code_u8() { |
| return PacketTooBig { |
| mtu: u32::from_be_bytes(self.bytes5to8()), |
| }; |
| } |
| } |
| TYPE_TIME_EXCEEDED => { |
| if let Some(code) = TimeExceededCode::from_u8(self.code_u8()) { |
| return TimeExceeded(code); |
| } |
| } |
| TYPE_PARAMETER_PROBLEM => { |
| if let Some(code) = ParameterProblemCode::from_u8(self.code_u8()) { |
| return ParameterProblem(ParameterProblemHeader { |
| code, |
| pointer: u32::from_be_bytes(self.bytes5to8()), |
| }); |
| } |
| } |
| TYPE_ECHO_REQUEST => { |
| if 0 == self.code_u8() { |
| return EchoRequest(IcmpEchoHeader::from_bytes(self.bytes5to8())); |
| } |
| } |
| TYPE_ECHO_REPLY => { |
| if 0 == self.code_u8() { |
| return EchoReply(IcmpEchoHeader::from_bytes(self.bytes5to8())); |
| } |
| } |
| _ => {} |
| } |
| Unknown { |
| type_u8: self.type_u8(), |
| code_u8: self.code_u8(), |
| bytes5to8: self.bytes5to8(), |
| } |
| } |
| |
| /// Returns "type" value in the ICMPv6 header. |
| #[inline] |
| pub fn type_u8(&self) -> u8 { |
| // SAFETY: |
| // Safe as the contructor checks that the slice has |
| // at least the length of Icmpv6Header::MIN_LEN (8). |
| unsafe { *self.slice.get_unchecked(0) } |
| } |
| |
| /// Returns "code" value in the ICMPv6 header. |
| #[inline] |
| pub fn code_u8(&self) -> u8 { |
| // SAFETY: |
| // Safe as the contructor checks that the slice has |
| // at least the length of Icmpv6Header::MIN_LEN (8). |
| unsafe { *self.slice.get_unchecked(1) } |
| } |
| |
| /// Returns "checksum" value in the ICMPv6 header. |
| #[inline] |
| pub fn checksum(&self) -> u16 { |
| // SAFETY: |
| // Safe as the contructor checks that the slice has |
| // at least the length of Icmpv6Header::MIN_LEN (8). |
| unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } |
| } |
| |
| /// Returns if the checksum in the slice is correct. |
| pub fn is_checksum_valid(&self, source_ip: [u8; 16], destination_ip: [u8; 16]) -> bool { |
| // NOTE: rfc4443 section 2.3 - Icmp6 *does* use a pseudoheader, |
| // unlike Icmp4 |
| checksum::Sum16BitWords::new() |
| .add_16bytes(source_ip) |
| .add_16bytes(destination_ip) |
| .add_4bytes((self.slice().len() as u32).to_be_bytes()) |
| .add_2bytes([0, ip_number::IPV6_ICMP.0]) |
| // NOTE: From RFC 1071 |
| // To check a checksum, the 1's complement sum is computed over the |
| // same set of octets, including the checksum field. If the result |
| // is all 1 bits (-0 in 1's complement arithmetic), the check |
| // succeeds. |
| .add_slice(self.slice) |
| .ones_complement() |
| == 0 |
| } |
| |
| /// Returns the bytes from position 4 till and including the 8th position |
| /// in the ICMPv6 header. |
| /// |
| /// These bytes located at th 5th, 6th, 7th and 8th position of the ICMP |
| /// packet can depending on the ICMPv6 type and code contain additional data. |
| #[inline] |
| pub fn bytes5to8(&self) -> [u8; 4] { |
| // SAFETY: |
| // Safe as the contructor checks that the slice has |
| // at least the length of Icmpv6Header::MIN_LEN (8). |
| unsafe { |
| [ |
| *self.slice.get_unchecked(4), |
| *self.slice.get_unchecked(5), |
| *self.slice.get_unchecked(6), |
| *self.slice.get_unchecked(7), |
| ] |
| } |
| } |
| |
| /// Returns the slice containing the ICMPv6 packet. |
| #[inline] |
| pub fn slice(&self) -> &'a [u8] { |
| self.slice |
| } |
| |
| /// Returns a slice to the bytes not covered by `.header()`. |
| #[inline] |
| pub fn payload(&self) -> &'a [u8] { |
| // SAFETY: |
| // Safe as the contructor checks that the slice has |
| // at least the length of Icmpv6Header::MIN_LEN(8). |
| unsafe { core::slice::from_raw_parts(self.slice.as_ptr().add(8), self.slice.len() - 8) } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use crate::{icmpv6::*, test_gens::*, Icmpv6Type::*, *}; |
| use alloc::{format, vec::Vec}; |
| use proptest::prelude::*; |
| |
| proptest! { |
| #[test] |
| fn from_slice(slice in proptest::collection::vec(any::<u8>(), 8..1024)) { |
| // ok case |
| assert_eq!(Icmpv6Slice::from_slice(&slice[..]).unwrap().slice(), &slice[..]); |
| |
| // too small size error case |
| for len in 0..8 { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&slice[..len]).unwrap_err(), |
| err::LenError{ |
| required_len: Icmpv6Header::MIN_LEN, |
| len: len, |
| len_source: LenSource::Slice, |
| layer: err::Layer::Icmpv6, |
| layer_start_offset: 0, |
| } |
| ); |
| } |
| } |
| } |
| |
| proptest! { |
| /// This error can only occur on systems with a pointer size |
| /// bigger then 64 bits. |
| #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] |
| #[test] |
| fn from_slice_too_big_error( |
| bad_len in ((core::u32::MAX as usize) + 1)..=(core::isize::MAX as usize), |
| ) { |
| // too large packet error case |
| { |
| // SAFETY: In case the error is not triggered |
| // a segmentation fault will be triggered. |
| let too_big_slice = unsafe { |
| //NOTE: The pointer must be initialized with a non null value |
| // otherwise a key constraint of slices is not fulfilled |
| // which can lead to crashes in release mode. |
| use core::ptr::NonNull; |
| core::slice::from_raw_parts( |
| NonNull::<u8>::dangling().as_ptr(), |
| bad_len |
| ) |
| }; |
| assert_eq!( |
| Icmpv6Slice::from_slice(too_big_slice).unwrap_err(), |
| err::LenError{ |
| required_len: u32::MAX as usize, |
| len: bad_len, |
| len_source: LenSource::Slice, |
| layer: err::Layer::Icmpv6, |
| layer_start_offset: 0 |
| } |
| ); |
| } |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn header( |
| icmp_type in icmpv6_type_any(), |
| checksum in any::<u16>() |
| ) { |
| let expected = Icmpv6Header { |
| icmp_type, |
| checksum |
| }; |
| assert_eq!( |
| Icmpv6Slice::from_slice(&expected.to_bytes()).unwrap().header(), |
| expected |
| ); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn icmp_type( |
| checksum in any::<[u8;2]>(), |
| bytes5to8 in any::<[u8;4]>() |
| ) { |
| use Icmpv6Type::*; |
| |
| let gen_bytes = |type_u8: u8, code_u8: u8| -> [u8;8] { |
| [ |
| type_u8, code_u8, checksum[0], checksum[1], |
| bytes5to8[0], bytes5to8[1], bytes5to8[2], bytes5to8[3] |
| ] |
| }; |
| |
| let assert_unknown = |type_u8: u8, code_u8: u8| { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&gen_bytes(type_u8, code_u8)).unwrap().icmp_type(), |
| Unknown{ |
| type_u8, |
| code_u8, |
| bytes5to8, |
| } |
| ); |
| }; |
| |
| // destination unreachable |
| { |
| // known codes |
| for (code, code_u8) in dest_unreachable_code_test_consts::VALID_VALUES { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&gen_bytes(TYPE_DST_UNREACH, code_u8)).unwrap().icmp_type(), |
| DestinationUnreachable(code) |
| ); |
| } |
| |
| // unknown codes |
| for code_u8 in 7..=u8::MAX { |
| assert_unknown(TYPE_DST_UNREACH, code_u8); |
| } |
| } |
| |
| // packet too big |
| { |
| // known code |
| assert_eq!( |
| Icmpv6Slice::from_slice(&gen_bytes(TYPE_PACKET_TOO_BIG, 0)).unwrap().icmp_type(), |
| PacketTooBig { |
| mtu: u32::from_be_bytes(bytes5to8) |
| } |
| ); |
| |
| // unknown code |
| for code_u8 in 1..=u8::MAX { |
| assert_unknown(TYPE_PACKET_TOO_BIG, code_u8); |
| } |
| } |
| |
| // time exceeded |
| { |
| // known codes |
| for (code, code_u8) in time_exceeded_code_test_consts::VALID_VALUES { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&gen_bytes(TYPE_TIME_EXCEEDED, code_u8)).unwrap().icmp_type(), |
| TimeExceeded(code) |
| ); |
| } |
| |
| // unknown codes |
| for code_u8 in 2..=u8::MAX { |
| assert_unknown(TYPE_TIME_EXCEEDED, code_u8); |
| } |
| } |
| |
| // parameter problem |
| { |
| // known codes |
| for (code, code_u8) in parameter_problem_code_test_consts::VALID_VALUES { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&gen_bytes(TYPE_PARAMETER_PROBLEM, code_u8)).unwrap().icmp_type(), |
| ParameterProblem(ParameterProblemHeader{ |
| code, |
| pointer: u32::from_be_bytes(bytes5to8), |
| }) |
| ); |
| } |
| |
| // unknown codes |
| for code_u8 in 11..=u8::MAX { |
| assert_unknown(TYPE_PARAMETER_PROBLEM, code_u8); |
| } |
| } |
| |
| // echo request |
| { |
| // known code |
| assert_eq!( |
| Icmpv6Slice::from_slice(&gen_bytes(TYPE_ECHO_REQUEST, 0)).unwrap().icmp_type(), |
| EchoRequest(IcmpEchoHeader::from_bytes(bytes5to8)) |
| ); |
| |
| // unknown codes |
| for code_u8 in 1..=u8::MAX { |
| assert_unknown(TYPE_ECHO_REPLY, code_u8); |
| } |
| } |
| |
| // echo reply |
| { |
| // known code |
| assert_eq!( |
| Icmpv6Slice::from_slice(&gen_bytes(TYPE_ECHO_REPLY, 0)).unwrap().icmp_type(), |
| EchoReply(IcmpEchoHeader::from_bytes(bytes5to8)) |
| ); |
| |
| // unknown codes |
| for code_u8 in 1..=u8::MAX { |
| assert_unknown(TYPE_ECHO_REPLY, code_u8); |
| } |
| } |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn header_len( |
| code_u8 in any::<u8>(), |
| bytes5to8 in any::<[u8;4]>(), |
| ) { |
| let len_8_types = [ |
| DestinationUnreachable(DestUnreachableCode::Prohibited), |
| PacketTooBig{ mtu: u32::from_be_bytes(bytes5to8), }, |
| TimeExceeded(TimeExceededCode::HopLimitExceeded), |
| ParameterProblem( |
| ParameterProblemHeader{ |
| code: ParameterProblemCode::OptionTooBig, |
| pointer: u32::from_be_bytes(bytes5to8), |
| } |
| ), |
| EchoRequest(IcmpEchoHeader::from_bytes(bytes5to8)), |
| EchoReply(IcmpEchoHeader::from_bytes(bytes5to8)), |
| ]; |
| |
| for t in len_8_types { |
| assert_eq!( |
| t.header_len(), |
| Icmpv6Slice::from_slice( |
| &Icmpv6Header::new(t).to_bytes() |
| ).unwrap().header_len() |
| ); |
| } |
| |
| for t in 0..=u8::MAX { |
| let header = Icmpv6Header::new( |
| Unknown{ |
| type_u8: t, |
| code_u8, |
| bytes5to8, |
| } |
| ); |
| assert_eq!( |
| 8, |
| Icmpv6Slice::from_slice( |
| &header.to_bytes() |
| ).unwrap().header_len() |
| ); |
| } |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn type_u8(slice in proptest::collection::vec(any::<u8>(), 8..16)) { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&slice[..]).unwrap().type_u8(), |
| slice[0] |
| ); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn code_u8(slice in proptest::collection::vec(any::<u8>(), 8..16)) { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&slice[..]).unwrap().code_u8(), |
| slice[1] |
| ); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn checksum(slice in proptest::collection::vec(any::<u8>(), 8..16)) { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&slice[..]).unwrap().checksum(), |
| u16::from_be_bytes([slice[2], slice[3]]) |
| ); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn is_checksum_valid( |
| ip_header in ipv6_any(), |
| icmp_type in icmpv6_type_any(), |
| payload in proptest::collection::vec(any::<u8>(), 0..1024), |
| flip_byte in 0usize..1032, |
| ) { |
| // generate slice with a correct checksum |
| let header = Icmpv6Header::with_checksum(icmp_type, ip_header.source, ip_header.destination, &payload).unwrap(); |
| let bytes = { |
| let mut bytes = Vec::with_capacity(header.header_len() + payload.len()); |
| header.write(&mut bytes).unwrap(); |
| bytes.extend_from_slice(&payload); |
| bytes |
| }; |
| |
| // check that the checksum gets reported as ok |
| assert!( |
| Icmpv6Slice::from_slice(&bytes).unwrap().is_checksum_valid(ip_header.source, ip_header.destination) |
| ); |
| |
| // corrupt icmp packet |
| { |
| let mut corrupted_bytes = bytes.clone(); |
| let i = flip_byte % corrupted_bytes.len(); |
| corrupted_bytes[i] = !corrupted_bytes[i]; |
| |
| assert_eq!( |
| false, |
| Icmpv6Slice::from_slice(&corrupted_bytes).unwrap().is_checksum_valid(ip_header.source, ip_header.destination) |
| ); |
| } |
| |
| // corrupt ip source |
| { |
| let mut corrupted_source = ip_header.source; |
| let i = flip_byte % corrupted_source.len(); |
| corrupted_source[i] = !corrupted_source[i]; |
| |
| assert_eq!( |
| false, |
| Icmpv6Slice::from_slice(&bytes).unwrap().is_checksum_valid(corrupted_source, ip_header.destination) |
| ); |
| } |
| |
| // corrupt ip destination |
| { |
| let mut corrupted_dest = ip_header.destination; |
| let i = flip_byte % corrupted_dest.len(); |
| corrupted_dest[i] = !corrupted_dest[i]; |
| |
| assert_eq!( |
| false, |
| Icmpv6Slice::from_slice(&bytes).unwrap().is_checksum_valid(ip_header.source, corrupted_dest) |
| ); |
| } |
| |
| // corrupt length |
| { |
| let mut larger_bytes = bytes.clone(); |
| larger_bytes.push(0); |
| larger_bytes.push(0); |
| |
| assert_eq!( |
| false, |
| Icmpv6Slice::from_slice(&larger_bytes).unwrap().is_checksum_valid(ip_header.source, ip_header.destination) |
| ); |
| } |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn bytes5to8(slice in proptest::collection::vec(any::<u8>(), 8..16)) { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&slice[..]).unwrap().bytes5to8(), |
| [slice[4], slice[5], slice[6], slice[7]] |
| ); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn slice(slice in proptest::collection::vec(any::<u8>(), 8..16)) { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&slice[..]).unwrap().slice(), |
| &slice[..] |
| ); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn payload( |
| type_u8 in any::<u8>(), |
| code_u8 in any::<u8>(), |
| bytes5to8 in any::<[u8;4]>(), |
| payload in proptest::collection::vec(any::<u8>(), 8..16) |
| ) { |
| let len_8_types = [ |
| Unknown{ |
| type_u8, |
| code_u8, |
| bytes5to8, |
| }, |
| DestinationUnreachable(DestUnreachableCode::Prohibited), |
| PacketTooBig{ mtu: u32::from_be_bytes(bytes5to8), }, |
| TimeExceeded(TimeExceededCode::HopLimitExceeded), |
| ParameterProblem( |
| ParameterProblemHeader{ |
| code: ParameterProblemCode::ExtensionHeaderChainTooLong, |
| pointer: u32::from_be_bytes(bytes5to8), |
| } |
| ), |
| EchoRequest(IcmpEchoHeader::from_bytes(bytes5to8)), |
| EchoReply(IcmpEchoHeader::from_bytes(bytes5to8)), |
| ]; |
| |
| for t in len_8_types { |
| let mut bytes = Vec::with_capacity(t.header_len() + payload.len()); |
| Icmpv6Header::new(t.clone()).write(&mut bytes).unwrap(); |
| bytes.extend_from_slice(&payload); |
| |
| assert_eq!( |
| Icmpv6Slice::from_slice(&bytes[..]).unwrap().payload(), |
| &payload[..] |
| ); |
| } |
| } |
| } |
| |
| #[test] |
| fn debug() { |
| let data = [0u8; 8]; |
| assert_eq!( |
| format!("{:?}", Icmpv6Slice::from_slice(&data).unwrap()), |
| format!("Icmpv6Slice {{ slice: {:?} }}", &data) |
| ); |
| } |
| |
| proptest! { |
| #[test] |
| fn clone_eq(slice in proptest::collection::vec(any::<u8>(), 8..16)) { |
| assert_eq!( |
| Icmpv6Slice::from_slice(&slice).unwrap().clone(), |
| Icmpv6Slice::from_slice(&slice).unwrap() |
| ); |
| } |
| } |
| } |