use crate::{ err::{ipv6, ipv6_exts}, *, }; /// Slice containing laxly separated IPv6 headers & payload. /// /// Compared to the normal [`Ipv6Slice`] this slice allows the /// payload to incomplete/cut off and errors to be present in /// the IpPayload. /// /// The main usecases for "laxly" parsed slices are are: /// /// * Parsing packets that have been cut off. This is, for example, useful to /// parse packets returned via ICMP as these usually only contain the start. /// * Parsing packets where the `total_len` (for IPv4) have not yet been set. /// This can be useful when parsing packets which have been recorded in a /// layer before the length field was set (e.g. before the operating /// system set the length fields). #[derive(Clone, Debug, Eq, PartialEq)] pub struct LaxIpv6Slice<'a> { pub(crate) header: Ipv6HeaderSlice<'a>, pub(crate) exts: Ipv6ExtensionsSlice<'a>, pub(crate) payload: LaxIpPayloadSlice<'a>, } impl<'a> LaxIpv6Slice<'a> { /// Seperate an IPv6 header (+ extensions) & the payload from the given slice with /// less strict length checks (useful for cut off packet or for packets with /// unset length fields). /// /// If you want to only receive correct IpPayloads use [`crate::Ipv4Slice::from_slice`] /// instead. /// /// The main usecases for this functions are: /// /// * Parsing packets that have been cut off. This is, for example, useful to /// parse packets returned via ICMP as these usually only contain the start. /// * Parsing packets where the `payload_length` (in the IPv6 header) has not /// yet been set. This can be useful when parsing packets which have been /// recorded in a layer before the length field was set (e.g. before the operating /// system set the length fields). /// /// # Differences to `from_slice`: /// /// There are two main differences: /// /// * Errors in the expansion headers will only stop the parsing and return an `Ok` /// with the successfully parsed parts and the error as optional. Only if an /// unrecoverable error is encountered in the IP header itself an `Err` is returned. /// In the normal `Ipv4Slice::from_slice` function an `Err` is returned if an error is /// encountered in an exteions header. /// * `LaxIpv4Slice::from_slice` ignores inconsistent `payload_length` values. When the /// `payload_length` value in the IPv6 header is inconsistant the length of /// the given slice is used as a substitute. /// /// You can check if the slice length was used as a substitude by checking /// if the `len_source` value in the returned [`IpPayloadSlice`] is set to /// [`LenSource::Slice`]. If a substitution was not needed `len_source` /// is set to [`LenSource::Ipv6HeaderPayloadLen`]. /// /// # When is the slice length used as a fallback? /// /// The slice length is used as a fallback/substitude if the `payload_length` /// field in the IPv6 header is /// /// * Bigger then the given slice (payload cannot fully be seperated). /// * The value `0`. pub fn from_slice( slice: &'a [u8], ) -> Result< ( LaxIpv6Slice<'a>, Option<(ipv6_exts::HeaderSliceError, err::Layer)>, ), ipv6::HeaderSliceError, > { // try reading the header let header = Ipv6HeaderSlice::from_slice(slice)?; // restrict slice by the length specified in the header let (header_payload, len_source, incomplete) = if 0 == header.payload_length() && slice.len() > Ipv6Header::LEN { // In case the payload_length is 0 assume that the entire // rest of the slice is part of the packet until the jumbogram // parameters can be parsed. // TODO: Add payload length parsing from the jumbogram ( unsafe { core::slice::from_raw_parts( slice.as_ptr().add(Ipv6Header::LEN), slice.len() - Ipv6Header::LEN, ) }, LenSource::Slice, false, ) } else { let payload_len = usize::from(header.payload_length()); let expected_len = Ipv6Header::LEN + payload_len; if slice.len() < expected_len { ( unsafe { core::slice::from_raw_parts( slice.as_ptr().add(Ipv6Header::LEN), slice.len() - Ipv6Header::LEN, ) }, LenSource::Slice, true, ) } else { ( unsafe { core::slice::from_raw_parts( slice.as_ptr().add(Ipv6Header::LEN), payload_len, ) }, LenSource::Ipv6HeaderPayloadLen, false, ) } }; // parse extension headers let (exts, payload_ip_number, payload, mut ext_stop_err) = Ipv6ExtensionsSlice::from_slice_lax(header.next_header(), header_payload); // modify length errors if let Some((ipv6_exts::HeaderSliceError::Len(err), _)) = &mut ext_stop_err { err.len_source = len_source; err.layer_start_offset += Ipv6Header::LEN; }; let fragmented = exts.is_fragmenting_payload(); Ok(( LaxIpv6Slice { header, exts, payload: LaxIpPayloadSlice { incomplete, ip_number: payload_ip_number, fragmented, len_source, payload, }, }, ext_stop_err, )) } /// Returns a slice containing the IPv6 header. #[inline] pub fn header(&self) -> Ipv6HeaderSlice<'a> { self.header } /// Returns a slice containing the IPv6 extension headers. #[inline] pub fn extensions(&self) -> &Ipv6ExtensionsSlice<'a> { &self.exts } /// Returns a slice containing the data after the IPv6 header /// and IPv6 extensions headers. #[inline] pub fn payload(&self) -> &LaxIpPayloadSlice<'a> { &self.payload } /// Returns true if the payload is flagged as being fragmented. #[inline] pub fn is_payload_fragmented(&self) -> bool { self.payload.fragmented } } #[cfg(test)] mod test { use super::*; use crate::{ err::{Layer, LenError}, ip_number::{AUTH, IGMP, UDP}, test_gens::*, }; use alloc::{format, vec::Vec}; use proptest::prelude::*; proptest! { #[test] fn debug_clone_eq( ipv6_base in ipv6_any(), auth_base in ip_auth_any() ) { let mut auth = auth_base.clone(); auth.next_header = IGMP; let payload: [u8;4] = [1,2,3,4]; let mut data = Vec::with_capacity( ipv6_base.header_len() + auth.header_len() + payload.len() ); let mut ipv6 = ipv6_base.clone(); ipv6.next_header = AUTH; ipv6.payload_length = (auth.header_len() + payload.len()) as u16; data.extend_from_slice(&ipv6.to_bytes()); data.extend_from_slice(&auth.to_bytes()); data.extend_from_slice(&payload); // decode packet let (slice, _) = LaxIpv6Slice::from_slice(&data).unwrap(); // check debug output prop_assert_eq!( format!("{:?}", slice), format!( "LaxIpv6Slice {{ header: {:?}, exts: {:?}, payload: {:?} }}", slice.header(), slice.extensions(), slice.payload() ) ); prop_assert_eq!(slice.clone(), slice); } } proptest! { #[test] fn from_slice( ipv6_base in ipv6_any(), auth_base in ip_auth_any() ) { let payload: [u8;6] = [1,2,3,4,5,6]; // build packets let data_without_ext = { let mut data = Vec::with_capacity( ipv6_base.header_len() + payload.len() + 4 ); let mut ipv6 = ipv6_base.clone(); ipv6.payload_length = (payload.len()) as u16; ipv6.next_header = UDP; data.extend_from_slice(&ipv6.to_bytes()); data.extend_from_slice(&payload); data.extend_from_slice(&[0,0,0,0]); data }; let data_with_ext = { let payload: [u8;6] = [1,2,3,4,5,6]; let mut data = Vec::with_capacity( ipv6_base.header_len() + auth_base.header_len() + payload.len() + 4 ); let mut ipv6 = ipv6_base.clone(); ipv6.payload_length = (auth_base.header_len() + payload.len()) as u16; ipv6.next_header = AUTH; let mut auth = auth_base.clone(); auth.next_header = UDP; data.extend_from_slice(&ipv6.to_bytes()); data.extend_from_slice(&auth.to_bytes()); data.extend_from_slice(&payload); data.extend_from_slice(&[0,0,0,0]); data }; // parsing without extensions (normal length) { let (actual, actual_stop_err) = LaxIpv6Slice::from_slice(&data_without_ext).unwrap(); prop_assert_eq!(None, actual_stop_err); prop_assert_eq!(actual.header().slice(), &data_without_ext[..ipv6_base.header_len()]); prop_assert!(actual.extensions().first_header().is_none()); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: false, ip_number: UDP.into(), fragmented: false, len_source: LenSource::Ipv6HeaderPayloadLen, payload: &payload, } ); } // parsing with extensions (normal length) { let (actual, actual_stop_err) = LaxIpv6Slice::from_slice(&data_with_ext).unwrap(); prop_assert_eq!(None, actual_stop_err); prop_assert_eq!(actual.header().slice(), &data_with_ext[..ipv6_base.header_len()]); let (expected, _, _) = Ipv6ExtensionsSlice::from_slice(AUTH, &data_with_ext[ipv6_base.header_len()..]).unwrap(); prop_assert_eq!( actual.extensions(), &expected ); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: false, ip_number: UDP.into(), fragmented: false, len_source: LenSource::Ipv6HeaderPayloadLen, payload: &payload, } ); } // parsing without extensions (zero length, fallback to slice length) { // inject zero as payload length let mut data = data_without_ext.clone(); data[4] = 0; data[5] = 0; let (actual, actual_stop_err) = LaxIpv6Slice::from_slice(&data).unwrap(); prop_assert_eq!(None, actual_stop_err); prop_assert_eq!(actual.header().slice(), &data[..ipv6_base.header_len()]); prop_assert!(actual.extensions().first_header().is_none()); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: false, ip_number: UDP.into(), fragmented: false, len_source: LenSource::Slice, payload: &data[ipv6_base.header_len()..], } ); } // parsing with extensions (zero length, fallback to slice length) { // inject zero as payload length let mut data = data_with_ext.clone(); data[4] = 0; data[5] = 0; let (actual, actual_stop_err) = LaxIpv6Slice::from_slice(&data).unwrap(); prop_assert_eq!(None, actual_stop_err); prop_assert_eq!(actual.header().slice(), &data[..ipv6_base.header_len()]); let (expected, _, _) = Ipv6ExtensionsSlice::from_slice(AUTH, &data[ipv6_base.header_len()..]).unwrap(); prop_assert_eq!( actual.extensions(), &expected ); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: false, ip_number: UDP.into(), fragmented: false, len_source: LenSource::Slice, payload: &data[ipv6_base.header_len() + auth_base.header_len()..], } ); } // header content error { use crate::err::ipv6::HeaderError; // inject invalid ip version let mut data = data_without_ext.clone(); data[0] = data[0] & 0x0f; // version 0 prop_assert_eq!( LaxIpv6Slice::from_slice(&data).unwrap_err(), ipv6::HeaderSliceError::Content( HeaderError::UnexpectedVersion{ version_number: 0 } ) ); } // header length error for len in 0..Ipv6Header::LEN { prop_assert_eq!( LaxIpv6Slice::from_slice(&data_without_ext[..len]).unwrap_err(), ipv6::HeaderSliceError::Len( LenError{ required_len: Ipv6Header::LEN, len, len_source: LenSource::Slice, layer: Layer::Ipv6Header, layer_start_offset: 0 } ) ); } // payload length larger then slice (fallback to slice length) { let len = ipv6_base.header_len() + payload.len() - 1; let (actual , actual_stop_err) = LaxIpv6Slice::from_slice(&data_without_ext[..len]).unwrap(); prop_assert_eq!(actual_stop_err, None); prop_assert_eq!(actual.header().slice(), &data_without_ext[..ipv6_base.header_len()]); prop_assert_eq!( 0, actual.extensions().slice().len() ); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: true, ip_number: UDP.into(), fragmented: false, len_source: LenSource::Slice, payload: &data_without_ext[ipv6_base.header_len()..len], } ); } // payload length error auth header { use crate::err::{LenError, Layer}; let required_len = ipv6_base.header_len() + auth_base.header_len(); let (actual, actual_stop_err) = LaxIpv6Slice::from_slice(&data_with_ext[..required_len - 1]).unwrap(); prop_assert_eq!( actual_stop_err.unwrap(), ( ipv6_exts::HeaderSliceError::Len(LenError{ required_len: required_len - Ipv6Header::LEN, len: required_len - Ipv6Header::LEN - 1, len_source: LenSource::Slice, layer: Layer::IpAuthHeader, layer_start_offset: Ipv6Header::LEN, }), err::Layer::IpAuthHeader ) ); prop_assert_eq!(actual.header().slice(), &data_with_ext[..ipv6_base.header_len()]); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: true, ip_number: AUTH, fragmented: false, len_source: LenSource::Slice, payload: &data_with_ext[ipv6_base.header_len()..required_len - 1], } ); } // auth length error { use crate::err::{LenError, Layer}; // inject payload length that is smaller then the auth header let mut data = data_with_ext.clone(); let payload_len_too_small = auth_base.header_len() - 1; { let plts = (payload_len_too_small as u16).to_be_bytes(); data[4] = plts[0]; data[5] = plts[1]; } let (actual, actual_stop_err) = LaxIpv6Slice::from_slice(&data).unwrap(); prop_assert_eq!( actual_stop_err.unwrap(), ( ipv6_exts::HeaderSliceError::Len( LenError{ required_len: auth_base.header_len(), len: auth_base.header_len() - 1, len_source: LenSource::Ipv6HeaderPayloadLen, layer: Layer::IpAuthHeader, layer_start_offset: ipv6_base.header_len(), } ), err::Layer::IpAuthHeader ) ); prop_assert_eq!(actual.header().slice(), &data[..ipv6_base.header_len()]); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: false, ip_number: AUTH, fragmented: false, len_source: LenSource::Ipv6HeaderPayloadLen, payload: &data[ipv6_base.header_len()..ipv6_base.header_len() + payload_len_too_small], } ); } // auth content error { use crate::err::{ip_auth, ipv6_exts}; // inject zero as auth header length let mut data = data_with_ext.clone(); data[ipv6_base.header_len() + 1] = 0; let (actual, actual_stop_error) = LaxIpv6Slice::from_slice(&data).unwrap(); prop_assert_eq!( actual_stop_error.unwrap(), ( ipv6_exts::HeaderSliceError::Content(ipv6_exts::HeaderError::IpAuth( ip_auth::HeaderError::ZeroPayloadLen )), err::Layer::IpAuthHeader ) ); prop_assert_eq!( actual.header().slice(), &data[..ipv6_base.header_len()] ); prop_assert_eq!( actual.payload(), &LaxIpPayloadSlice{ incomplete: false, ip_number: AUTH, fragmented: false, len_source: LenSource::Ipv6HeaderPayloadLen, payload: &data[ipv6_base.header_len()..ipv6_base.header_len() + auth_base.header_len() + payload.len()], } ); } } } #[test] fn is_payload_fragmented() { use crate::ip_number::{IPV6_FRAG, UDP}; // not fragmented { let data = Ipv6Header { traffic_class: 0, flow_label: 1.try_into().unwrap(), payload_length: 0, next_header: UDP, hop_limit: 4, source: [0; 16], destination: [0; 16], } .to_bytes(); assert_eq!( false, LaxIpv6Slice::from_slice(&data) .unwrap() .0 .is_payload_fragmented() ); } // fragmented { let ipv6_frag = Ipv6FragmentHeader { next_header: UDP, fragment_offset: 0.try_into().unwrap(), more_fragments: true, identification: 0, }; let ipv6 = Ipv6Header { traffic_class: 0, flow_label: 1.try_into().unwrap(), payload_length: ipv6_frag.header_len() as u16, next_header: IPV6_FRAG, hop_limit: 4, source: [0; 16], destination: [0; 16], }; let mut data = Vec::with_capacity(ipv6.header_len() + ipv6_frag.header_len()); data.extend_from_slice(&ipv6.to_bytes()); data.extend_from_slice(&ipv6_frag.to_bytes()); assert!(Ipv6Slice::from_slice(&data) .unwrap() .is_payload_fragmented()); } } }