| use crate::*; |
| |
| /// Slice containing laxly separated IPv4 headers & payload. |
| /// |
| /// Compared to the normal [`Ipv4Slice`] this slice allows the |
| /// payload to be 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 LaxIpv4Slice<'a> { |
| pub(crate) header: Ipv4HeaderSlice<'a>, |
| pub(crate) exts: Ipv4ExtensionsSlice<'a>, |
| pub(crate) payload: LaxIpPayloadSlice<'a>, |
| } |
| |
| impl<'a> LaxIpv4Slice<'a> { |
| /// Separates and validates IPv4 headers (including extension headers) & |
| /// 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 [`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 `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). |
| /// |
| /// # Differences to `Ipv4Slice::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 `total_len` values. When the `total_len` |
| /// value in the IPv4 header are inconsistant the length of the given slice is |
| /// used as a substitute. |
| /// |
| /// ## What happens in the `total_len` value is inconsistent? |
| /// |
| /// When the total_length value in the IPv4 header is inconsistent the |
| /// length of the given slice is used as a substitute. This can happen |
| /// if the `total_length` field in the IPv4 header is: |
| /// |
| /// * Bigger then the given slice (payload cannot fully be seperated). |
| /// * Too small to contain at least the IPv4 header. |
| /// |
| /// Additionally you can check if more data was expected based on the |
| /// `total_len` but the given slice was too small by checking if `incomplete` |
| /// is set to `true` in the returned [`LaxIpPayloadSlice`]. |
| /// |
| /// You can check if the slice length was used as a substitude by checking |
| /// if the `len_source` value in the returned [`LaxIpPayloadSlice`] is set to |
| /// [`LenSource::Slice`]. If a substitution was not needed `len_source` |
| /// is set to [`LenSource::Ipv4HeaderTotalLen`]. |
| |
| pub fn from_slice( |
| slice: &[u8], |
| ) -> Result<(LaxIpv4Slice, Option<err::ip_auth::HeaderSliceError>), err::ipv4::HeaderSliceError> |
| { |
| use crate::ip_number::AUTH; |
| |
| // decode the header |
| let header = Ipv4HeaderSlice::from_slice(slice)?; |
| |
| // validate total_len at least contains the header |
| let header_total_len: usize = header.total_len().into(); |
| let (header_payload, len_source, incomplete) = if header_total_len < header.slice().len() { |
| // total_length is smaller then the header itself |
| // fall back to the slice for the length |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| slice.as_ptr().add(header.slice().len()), |
| slice.len() - header.slice().len(), |
| ) |
| }, |
| LenSource::Slice, |
| // note that we have no indication that the packet is incomplete |
| false, |
| ) |
| } else if header_total_len > slice.len() { |
| // more data was expected, fallback to slice and report payload as "incomplete" |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| slice.as_ptr().add(header.slice().len()), |
| slice.len() - header.slice().len(), |
| ) |
| }, |
| LenSource::Slice, |
| true, // incomplete |
| ) |
| } else { |
| // all good the packet seems to be complete |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| slice.as_ptr().add(header.slice().len()), |
| header_total_len - header.slice().len(), |
| ) |
| }, |
| LenSource::Ipv4HeaderTotalLen, |
| false, |
| ) |
| }; |
| |
| // decode the authentication header if needed |
| let fragmented = header.is_fragmenting_payload(); |
| match header.protocol() { |
| AUTH => { |
| use crate::err::ip_auth::HeaderSliceError as E; |
| |
| // parse extension headers |
| match IpAuthHeaderSlice::from_slice(header_payload) { |
| Ok(auth) => { |
| // remove the extension header from the payload |
| let payload = unsafe { |
| core::slice::from_raw_parts( |
| header_payload.as_ptr().add(auth.slice().len()), |
| header_payload.len() - auth.slice().len(), |
| ) |
| }; |
| let ip_number = auth.next_header(); |
| Ok(( |
| LaxIpv4Slice { |
| header, |
| exts: Ipv4ExtensionsSlice { auth: Some(auth) }, |
| payload: LaxIpPayloadSlice { |
| incomplete, |
| ip_number, |
| fragmented, |
| len_source, |
| payload, |
| }, |
| }, |
| None, |
| )) |
| } |
| Err(err) => { |
| let err = match err { |
| E::Len(mut l) => { |
| // change the length source to the ipv4 header |
| l.len_source = len_source; |
| l.layer_start_offset += header.slice().len(); |
| E::Len(l) |
| } |
| E::Content(err) => E::Content(err), |
| }; |
| Ok(( |
| LaxIpv4Slice { |
| header, |
| exts: Ipv4ExtensionsSlice { auth: None }, |
| payload: LaxIpPayloadSlice { |
| incomplete, |
| ip_number: AUTH, |
| fragmented, |
| len_source, |
| payload: header_payload, |
| }, |
| }, |
| Some(err), |
| )) |
| } |
| } |
| } |
| ip_number => Ok(( |
| LaxIpv4Slice { |
| header, |
| exts: Ipv4ExtensionsSlice { auth: None }, |
| payload: LaxIpPayloadSlice { |
| incomplete, |
| ip_number, |
| fragmented, |
| len_source, |
| payload: header_payload, |
| }, |
| }, |
| None, |
| )), |
| } |
| } |
| |
| /// Returns a slice containing the IPv4 header. |
| #[inline] |
| pub fn header(&self) -> Ipv4HeaderSlice { |
| self.header |
| } |
| |
| /// Returns a slice containing the IPv4 extension headers. |
| #[inline] |
| pub fn extensions(&self) -> Ipv4ExtensionsSlice { |
| self.exts |
| } |
| |
| /// Returns a slice containing the data after the IPv4 header |
| /// and IPv4 extensions headers. |
| #[inline] |
| pub fn payload(&self) -> &LaxIpPayloadSlice<'a> { |
| &self.payload |
| } |
| |
| /// Returns the ip number the type of payload of the IPv4 packet. |
| /// |
| /// This function returns the ip number stored in the last |
| /// IPv4 header or extension header. |
| #[inline] |
| pub fn payload_ip_number(&self) -> IpNumber { |
| self.payload.ip_number |
| } |
| |
| /// Returns true if the payload is flagged as being fragmented. |
| #[inline] |
| pub fn is_payload_fragmented(&self) -> bool { |
| self.header.is_fragmenting_payload() |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::{err::LenError, ip_number::AUTH, test_gens::*}; |
| use alloc::{format, vec::Vec}; |
| use proptest::prelude::*; |
| |
| proptest! { |
| #[test] |
| fn debug_clone_eq( |
| ipv4_base in ipv4_any(), |
| auth in ip_auth_any() |
| ) { |
| let payload: [u8;4] = [1,2,3,4]; |
| let mut data = Vec::with_capacity( |
| ipv4_base.header_len() + |
| auth.header_len() + |
| payload.len() |
| ); |
| let mut ipv4 = ipv4_base.clone(); |
| ipv4.protocol = crate::ip_number::AUTH; |
| ipv4.set_payload_len(auth.header_len() + payload.len()).unwrap(); |
| data.extend_from_slice(&ipv4.to_bytes()); |
| data.extend_from_slice(&auth.to_bytes()); |
| data.extend_from_slice(&payload); |
| |
| // decode packet |
| let (slice, _) = LaxIpv4Slice::from_slice(&data).unwrap(); |
| |
| // check debug output |
| prop_assert_eq!( |
| format!("{:?}", slice), |
| format!( |
| "LaxIpv4Slice {{ header: {:?}, exts: {:?}, payload: {:?} }}", |
| slice.header(), |
| slice.extensions(), |
| slice.payload() |
| ) |
| ); |
| prop_assert_eq!(slice.clone(), slice); |
| } |
| } |
| |
| fn combine_v4( |
| v4: &Ipv4Header, |
| ext: &crate::Ipv4Extensions, |
| payload: &[u8], |
| ) -> crate::IpHeaders { |
| use crate::ip_number::UDP; |
| crate::IpHeaders::Ipv4( |
| { |
| let mut v4 = v4.clone(); |
| v4.protocol = if ext.auth.is_some() { AUTH } else { UDP }; |
| v4.total_len = (v4.header_len() + ext.header_len() + payload.len()) as u16; |
| v4.header_checksum = v4.calc_header_checksum(); |
| v4 |
| }, |
| ext.clone(), |
| ) |
| } |
| |
| proptest! { |
| #[test] |
| fn from_slice( |
| v4 in ipv4_any(), |
| v4_exts in ipv4_extensions_any() |
| ) { |
| use crate::err::{self, ipv4::HeaderError::*}; |
| use crate::err::ipv4::HeaderSliceError as E; |
| use err::ip_auth::HeaderSliceError as A; |
| |
| let payload = [1,2,3,4]; |
| |
| // empty error |
| assert_eq!( |
| LaxIpv4Slice::from_slice(&[]), |
| Err(E::Len(err::LenError { |
| required_len: 20, |
| len: 0, |
| len_source: LenSource::Slice, |
| layer: err::Layer::Ipv4Header, |
| layer_start_offset: 0, |
| })) |
| ); |
| |
| // build a buffer with a valid packet |
| let header = combine_v4(&v4, &v4_exts, &payload); |
| let mut buffer = Vec::with_capacity(header.header_len() + payload.len() + 1); |
| header.write(&mut buffer).unwrap(); |
| buffer.extend_from_slice(&payload); |
| buffer.push(1); // add some value to check the return slice |
| |
| // normal read |
| { |
| let (actual, actual_stop_err) = LaxIpv4Slice::from_slice(&buffer).unwrap(); |
| assert_eq!(None, actual_stop_err); |
| assert_eq!(&actual.header.to_header(), header.ipv4().unwrap().0); |
| assert_eq!(&actual.extensions().to_header(), header.ipv4().unwrap().1); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: header.next_header().unwrap(), |
| fragmented: header.is_fragmenting_payload(), |
| len_source: LenSource::Ipv4HeaderTotalLen, |
| payload: &payload |
| } |
| ); |
| } |
| |
| // error len smaller then min header len |
| for len in 1..Ipv4Header::MIN_LEN { |
| assert_eq!( |
| LaxIpv4Slice::from_slice(&buffer[..len]), |
| Err(E::Len(err::LenError { |
| required_len: Ipv4Header::MIN_LEN, |
| len, |
| len_source: LenSource::Slice, |
| layer: err::Layer::Ipv4Header, |
| layer_start_offset: 0, |
| })) |
| ); |
| } |
| |
| // ihl value error |
| { |
| let mut bad_ihl_buffer = buffer.clone(); |
| for bad_ihl in 0..5 { |
| bad_ihl_buffer[0] = (bad_ihl_buffer[0] & 0xf0) | bad_ihl; |
| assert_eq!( |
| LaxIpv4Slice::from_slice(&bad_ihl_buffer), |
| Err(E::Content(HeaderLengthSmallerThanHeader { ihl: bad_ihl })) |
| ); |
| } |
| } |
| |
| // ihl len error |
| for short_ihl in 5..usize::from(v4.ihl()) { |
| assert_eq!( |
| LaxIpv4Slice::from_slice(&buffer[..4*short_ihl]), |
| Err(E::Len(err::LenError { |
| required_len: usize::from(v4.ihl())*4, |
| len: 4*short_ihl, |
| len_source: LenSource::Slice, |
| layer: err::Layer::Ipv4Header, |
| layer_start_offset: 0, |
| })) |
| ); |
| } |
| |
| // total_len bigger then slice len (fallback to slice len) |
| for payload_len in 0..payload.len(){ |
| let (actual, stop_err) = LaxIpv4Slice::from_slice(&buffer[..v4.header_len() + v4_exts.header_len() + payload_len]).unwrap(); |
| assert_eq!(stop_err, None); |
| assert_eq!(&actual.header().to_header(), header.ipv4().unwrap().0); |
| assert_eq!( |
| actual.payload(), |
| &LaxIpPayloadSlice{ |
| incomplete: true, |
| ip_number: header.next_header().unwrap(), |
| fragmented: header.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &payload[..payload_len] |
| } |
| ); |
| } |
| |
| // len error ipv4 extensions |
| if v4_exts.header_len() > 0 { |
| let (actual, stop_err) = LaxIpv4Slice::from_slice(&buffer[..v4.header_len() + 1]).unwrap(); |
| assert_eq!(&actual.header().to_header(), header.ipv4().unwrap().0); |
| assert_eq!( |
| actual.payload(), |
| &LaxIpPayloadSlice{ |
| incomplete: true, |
| ip_number: AUTH, |
| fragmented: header.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &buffer[v4.header_len()..v4.header_len() + 1] |
| } |
| ); |
| assert_eq!(stop_err, Some(A::Len(LenError{ |
| required_len: IpAuthHeader::MIN_LEN, |
| len: 1, |
| len_source: LenSource::Slice, |
| layer: err::Layer::IpAuthHeader, |
| layer_start_offset: header.ipv4().unwrap().0.header_len() |
| }))); |
| } |
| |
| // content error ipv4 extensions |
| if v4_exts.auth.is_some() { |
| use err::ip_auth::HeaderError::ZeroPayloadLen; |
| |
| |
| // introduce a auth header zero payload error |
| let mut errored_buffer = buffer.clone(); |
| // inject length zero into auth header (not valid, will |
| // trigger a content error) |
| errored_buffer[v4.header_len() + 1] = 0; |
| |
| let (actual, stop_err) = LaxIpv4Slice::from_slice(&errored_buffer).unwrap(); |
| assert_eq!(&actual.header().to_header(), header.ipv4().unwrap().0); |
| assert!(actual.extensions().is_empty()); |
| let auth_offset = header.ipv4().unwrap().0.header_len(); |
| let payload_end = auth_offset + v4_exts.auth.map(|v| v.header_len()).unwrap() + payload.len(); |
| assert_eq!( |
| actual.payload(), |
| &LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: AUTH, |
| fragmented: header.is_fragmenting_payload(), |
| len_source: LenSource::Ipv4HeaderTotalLen, |
| payload: &errored_buffer[auth_offset..payload_end] |
| } |
| ); |
| assert_eq!(stop_err, Some(A::Content(ZeroPayloadLen))); |
| } |
| |
| // total length smaller the header (fallback to slice len) |
| { |
| let bad_total_len = (v4.header_len() - 1) as u16; |
| |
| let mut buffer = buffer.clone(); |
| // inject bad total_len |
| let bad_total_len_be = bad_total_len.to_be_bytes(); |
| buffer[2] = bad_total_len_be[0]; |
| buffer[3] = bad_total_len_be[1]; |
| |
| let (actual, actual_stop_error) = LaxIpv4Slice::from_slice(&buffer[..]).unwrap(); |
| assert_eq!(actual_stop_error, None); |
| |
| let (v4_header, v4_exts) = header.ipv4().unwrap(); |
| let expected_headers = IpHeaders::Ipv4( |
| { |
| let mut expected_v4 = v4_header.clone(); |
| expected_v4.total_len = bad_total_len; |
| expected_v4 |
| }, |
| v4_exts.clone() |
| ); |
| assert_eq!(expected_headers.ipv4().unwrap().0, &actual.header().to_header()); |
| assert_eq!( |
| actual.payload(), |
| &LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: header.next_header().unwrap(), |
| fragmented: header.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &buffer[v4_header.header_len() + v4_exts.header_len()..], |
| } |
| ); |
| } |
| } |
| } |
| |
| #[test] |
| fn is_payload_fragmented() { |
| use crate::ip_number::UDP; |
| // non-fragmented |
| { |
| let payload: [u8; 6] = [1, 2, 3, 4, 5, 6]; |
| let ipv4 = |
| Ipv4Header::new(payload.len() as u16, 1, UDP, [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); |
| let data = { |
| let mut data = Vec::with_capacity(ipv4.header_len() + payload.len()); |
| data.extend_from_slice(&ipv4.to_bytes()); |
| data.extend_from_slice(&payload); |
| data |
| }; |
| |
| let (slice, stop_err) = LaxIpv4Slice::from_slice(&data).unwrap(); |
| assert_eq!(None, stop_err); |
| assert!(false == slice.is_payload_fragmented()); |
| } |
| // fragmented |
| { |
| let payload: [u8; 6] = [1, 2, 3, 4, 5, 6]; |
| let mut ipv4 = |
| Ipv4Header::new(payload.len() as u16, 1, UDP, [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); |
| ipv4.fragment_offset = 123.try_into().unwrap(); |
| let data = { |
| let mut data = Vec::with_capacity(ipv4.header_len() + payload.len()); |
| data.extend_from_slice(&ipv4.to_bytes()); |
| data.extend_from_slice(&payload); |
| data |
| }; |
| |
| let (slice, stop_err) = LaxIpv4Slice::from_slice(&data).unwrap(); |
| assert_eq!(None, stop_err); |
| assert!(slice.is_payload_fragmented()); |
| } |
| } |
| } |