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, 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::(), 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::::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::() ) { 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::(), 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::(), 8..16)) { assert_eq!( Icmpv6Slice::from_slice(&slice[..]).unwrap().type_u8(), slice[0] ); } } proptest! { #[test] fn code_u8(slice in proptest::collection::vec(any::(), 8..16)) { assert_eq!( Icmpv6Slice::from_slice(&slice[..]).unwrap().code_u8(), slice[1] ); } } proptest! { #[test] fn checksum(slice in proptest::collection::vec(any::(), 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::(), 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::(), 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::(), 8..16)) { assert_eq!( Icmpv6Slice::from_slice(&slice[..]).unwrap().slice(), &slice[..] ); } } proptest! { #[test] fn payload( type_u8 in any::(), code_u8 in any::(), bytes5to8 in any::<[u8;4]>(), payload in proptest::collection::vec(any::(), 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::(), 8..16)) { assert_eq!( Icmpv6Slice::from_slice(&slice).unwrap().clone(), Icmpv6Slice::from_slice(&slice).unwrap() ); } } }