| 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()); |
| } |
| } |
| } |