use crate::{err::ValueTooBigError, *}; use arrayvec::ArrayVec; /// The statically sized data at the start of an ICMPv6 packet (at least the first 8 bytes of an ICMPv6 packet). #[derive(Clone, Debug, PartialEq, Eq)] pub struct Icmpv6Header { /// Type & type specific values & code. pub icmp_type: Icmpv6Type, /// Checksum in the ICMPv6 header. pub checksum: u16, } impl Icmpv6Header { /// Minimum number of bytes an ICMP header needs to have. /// /// Note that minimum size can be larger depending on /// the type and code. pub const MIN_LEN: usize = 8; /// Deprecated, use [`Icmpv6Header::MIN_LEN`] instead. #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MIN_LEN instead")] pub const MIN_SERIALIZED_SIZE: usize = Icmpv6Header::MIN_LEN; /// Maximum number of bytes/octets an Icmpv6Header takes up /// in serialized form. /// /// Currently this number is determined by the biggest /// planned ICMPv6 header type, which is currently the /// "Neighbor Discovery Protocol" "Redirect" message. pub const MAX_LEN: usize = 8 + 16 + 16; /// Deprecated, use [`Icmpv6Header::MAX_LEN`] instead. #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MAX_LEN instead")] pub const MAX_SERIALIZED_SIZE: usize = Icmpv6Header::MAX_LEN; /// Setups a new header with the checksum being set to 0. #[inline] pub fn new(icmp_type: Icmpv6Type) -> Icmpv6Header { Icmpv6Header { icmp_type, checksum: 0, // will be filled in later } } /// Creates a [`Icmpv6Header`] with a checksum calculated based /// on the given payload & ip addresses from the IPv6 header. pub fn with_checksum( icmp_type: Icmpv6Type, source_ip: [u8; 16], destination_ip: [u8; 16], payload: &[u8], ) -> Result> { let checksum = icmp_type.calc_checksum(source_ip, destination_ip, payload)?; Ok(Icmpv6Header { icmp_type, checksum, }) } /// Reads an icmp6 header from a slice directly and returns a tuple /// containing the resulting header & unused part of the slice. #[inline] pub fn from_slice(slice: &[u8]) -> Result<(Icmpv6Header, &[u8]), err::LenError> { let header = Icmpv6Slice::from_slice(slice)?.header(); let len = header.header_len(); Ok((header, &slice[len..])) } /// Read a ICMPv6 header from the given reader #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn read(reader: &mut T) -> Result { // read the initial 8 bytes let mut start = [0u8; 8]; reader.read_exact(&mut start)?; Ok(Icmpv6Slice { slice: &start }.header()) } /// Write the ICMPv6 header to the given writer. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn write(&self, writer: &mut T) -> Result<(), std::io::Error> { writer.write_all(&self.to_bytes()) } /// Serialized length of the header in bytes/octets. /// /// Note that this size is not the size of the entire /// ICMPv6 packet but only the header. #[inline] pub fn header_len(&self) -> usize { self.icmp_type.header_len() } /// If the ICMP type has a fixed size returns the number of /// bytes that should be present after the header of this type. #[inline] pub fn fixed_payload_size(&self) -> Option { self.icmp_type.fixed_payload_size() } /// Updates the checksum of the header. pub fn update_checksum( &mut self, source_ip: [u8; 16], destination_ip: [u8; 16], payload: &[u8], ) -> Result<(), ValueTooBigError> { self.checksum = self .icmp_type .calc_checksum(source_ip, destination_ip, payload)?; Ok(()) } /// Returns the header on the wire bytes. #[inline] pub fn to_bytes(&self) -> ArrayVec { let checksum_be = self.checksum.to_be_bytes(); let return_trivial = |type_u8: u8, code_u8: u8| -> ArrayVec { #[rustfmt::skip] let mut re = ArrayVec::from([ type_u8, code_u8, checksum_be[0], checksum_be[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); // SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20. unsafe { re.set_len(8); } re }; let return_4u8 = |type_u8: u8, code_u8: u8, bytes5to8: [u8; 4]| -> ArrayVec { #[rustfmt::skip] let mut re = ArrayVec::from([ type_u8, code_u8, checksum_be[0], checksum_be[1], bytes5to8[0], bytes5to8[1], bytes5to8[2], bytes5to8[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); // SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20. unsafe { re.set_len(8); } re }; use crate::{icmpv6::*, Icmpv6Type::*}; match self.icmp_type { Unknown { type_u8, code_u8, bytes5to8, } => return_4u8(type_u8, code_u8, bytes5to8), DestinationUnreachable(header) => return_trivial(TYPE_DST_UNREACH, header.code_u8()), PacketTooBig { mtu } => return_4u8(TYPE_PACKET_TOO_BIG, 0, mtu.to_be_bytes()), TimeExceeded(code) => return_trivial(TYPE_TIME_EXCEEDED, code.code_u8()), ParameterProblem(header) => return_4u8( TYPE_PARAMETER_PROBLEM, header.code.code_u8(), header.pointer.to_be_bytes(), ), EchoRequest(echo) => return_4u8(TYPE_ECHO_REQUEST, 0, echo.to_bytes()), EchoReply(echo) => return_4u8(TYPE_ECHO_REPLY, 0, echo.to_bytes()), } } } #[cfg(test)] mod test { use crate::{ err::{ValueTooBigError, ValueType}, icmpv6::*, test_gens::*, *, }; use alloc::{format, vec::Vec}; use arrayvec::ArrayVec; use proptest::prelude::*; proptest! { #[test] fn new(icmp_type in icmpv6_type_any()) { assert_eq!( Icmpv6Header::new(icmp_type.clone()), Icmpv6Header { icmp_type, checksum: 0, } ); } } proptest! { #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] #[test] fn with_checksum( ip_header in ipv6_any(), icmp_type in icmpv6_type_any(), // max length is u32::MAX - header_len (7) bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize), payload in proptest::collection::vec(any::(), 0..1024) ) { // 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!( Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, too_big_slice), Err(ValueTooBigError{ actual: bad_len, max_allowed: (core::u32::MAX - 8) as usize, value_type: ValueType::Icmpv6PayloadLength, }) ); } // non error case assert_eq!( Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, &payload).unwrap(), Icmpv6Header { icmp_type, checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(), } ); } } proptest! { #[test] fn from_slice( icmp_type in icmpv6_type_any(), checksum in any::(), ) { let bytes = { Icmpv6Header { icmp_type: icmp_type.clone(), checksum, }.to_bytes() }; // ok case { let result = Icmpv6Header::from_slice(&bytes).unwrap(); assert_eq!( Icmpv6Header{ icmp_type, checksum, }, result.0, ); assert_eq!(&bytes[8..], result.1); } // size error case for length in 0..8 { assert_eq!( Icmpv6Header::from_slice(&bytes[..length]).unwrap_err(), err::LenError{ required_len: bytes.len(), len: length, len_source: LenSource::Slice, layer: err::Layer::Icmpv6, layer_start_offset: 0 } ); } } } proptest! { #[test] fn read( icmp_type in icmpv6_type_any(), checksum in any::(), ) { let header = Icmpv6Header { icmp_type: icmp_type.clone(), checksum, }; let bytes = header.to_bytes(); // ok case { let mut cursor = std::io::Cursor::new(&bytes); let result = Icmpv6Header::read(&mut cursor).unwrap(); assert_eq!(header, result,); assert_eq!(header.header_len() as u64, cursor.position()); } // size error case for length in 0..header.header_len() { let mut cursor = std::io::Cursor::new(&bytes[..length]); assert!(Icmpv6Header::read(&mut cursor).is_err()); } } } proptest! { #[test] fn write( icmp_type in icmpv6_type_any(), checksum in any::(), bad_len in 0..8usize ) { // normal case { let mut buffer = Vec::with_capacity(icmp_type.header_len()); let header = Icmpv6Header { icmp_type, checksum, }; header.write(&mut buffer).unwrap(); assert_eq!( &header.to_bytes(), &buffer[..] ); } // error case { let mut buffer = [0u8;Icmpv6Header::MAX_LEN]; let mut writer = std::io::Cursor::new(&mut buffer[..bad_len]); Icmpv6Header { icmp_type, checksum, }.write(&mut writer).unwrap_err(); } } } proptest! { #[test] fn header_len(icmp_type in icmpv6_type_any(), checksum in any::()) { assert_eq!( icmp_type.header_len(), Icmpv6Header{ icmp_type, checksum }.header_len() ); } } proptest! { #[test] fn fixed_payload_size(icmp_type in icmpv6_type_any(), checksum in any::()) { assert_eq!( icmp_type.fixed_payload_size(), Icmpv6Header{ icmp_type, checksum }.fixed_payload_size() ); } } proptest! { #[test] #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] fn update_checksum( ip_header in ipv6_any(), icmp_type in icmpv6_type_any(), start_checksum in any::(), // max length is u32::MAX - header_len (7) bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize), payload in proptest::collection::vec(any::(), 0..1024) ) { // 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!( Icmpv6Header{ icmp_type, checksum: 0 }.update_checksum(ip_header.source, ip_header.destination, too_big_slice), Err(ValueTooBigError{ actual: bad_len, max_allowed: (u32::MAX - 8) as usize, value_type: ValueType::Icmpv6PayloadLength }) ); } // normal case assert_eq!( { let mut header = Icmpv6Header{ icmp_type, checksum: start_checksum, }; header.update_checksum(ip_header.source, ip_header.destination, &payload).unwrap(); header }, Icmpv6Header{ icmp_type, checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(), } ); } } proptest! { #[test] fn to_bytes( checksum in any::(), rand_u32 in any::(), rand_4bytes in any::<[u8;4]>(), ) { use Icmpv6Type::*; let with_5to8_bytes = |type_u8: u8, code_u8: u8, bytes5to8: [u8;4]| -> ArrayVec { let mut bytes = ArrayVec::::new(); bytes.push(type_u8); bytes.push(code_u8); bytes.try_extend_from_slice(&checksum.to_be_bytes()).unwrap(); bytes.try_extend_from_slice(&bytes5to8).unwrap(); bytes }; let simple_bytes = |type_u8: u8, code_u8: u8| -> ArrayVec { with_5to8_bytes(type_u8, code_u8, [0;4]) }; // destination unreachable for (code, code_u8) in dest_unreachable_code_test_consts::VALID_VALUES { assert_eq!( Icmpv6Header{ icmp_type: DestinationUnreachable(code), checksum }.to_bytes(), simple_bytes(TYPE_DST_UNREACH, code_u8) ); } // packet too big assert_eq!( Icmpv6Header{ icmp_type: PacketTooBig{ mtu: rand_u32 }, checksum }.to_bytes(), with_5to8_bytes(TYPE_PACKET_TOO_BIG, 0, rand_u32.to_be_bytes()) ); // time exceeded for (code, code_u8) in time_exceeded_code_test_consts::VALID_VALUES { assert_eq!( Icmpv6Header{ icmp_type: TimeExceeded(code), checksum }.to_bytes(), simple_bytes(TYPE_TIME_EXCEEDED, code_u8) ); } // parameter problem for (code, code_u8) in parameter_problem_code_test_consts::VALID_VALUES { assert_eq!( Icmpv6Header{ icmp_type: ParameterProblem( ParameterProblemHeader{ code, pointer: rand_u32, } ), checksum }.to_bytes(), with_5to8_bytes(TYPE_PARAMETER_PROBLEM, code_u8, rand_u32.to_be_bytes()) ); } // echo request assert_eq!( Icmpv6Header{ icmp_type: EchoRequest(IcmpEchoHeader { id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]), seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]), }), checksum }.to_bytes(), with_5to8_bytes(TYPE_ECHO_REQUEST, 0, rand_4bytes) ); // echo reply assert_eq!( Icmpv6Header{ icmp_type: EchoReply(IcmpEchoHeader { id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]), seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]), }), checksum }.to_bytes(), with_5to8_bytes(TYPE_ECHO_REPLY, 0, rand_4bytes) ); // unknown for type_u8 in 0..=u8::MAX { for code_u8 in 0..=u8::MAX { assert_eq!( Icmpv6Header{ icmp_type: Unknown { type_u8, code_u8, bytes5to8: rand_4bytes, }, checksum }.to_bytes(), with_5to8_bytes(type_u8, code_u8, rand_4bytes) ); } } } } #[test] fn debug() { let t = Icmpv6Type::Unknown { type_u8: 0, code_u8: 1, bytes5to8: [2, 3, 4, 5], }; assert_eq!( format!( "{:?}", Icmpv6Header { icmp_type: t.clone(), checksum: 7 } ), format!("Icmpv6Header {{ icmp_type: {:?}, checksum: {:?} }}", t, 7) ); } proptest! { #[test] fn clone_eq(icmp_type in icmpv6_type_any(), checksum in any::()) { let header = Icmpv6Header{ icmp_type, checksum }; assert_eq!(header, header.clone()); } } }