| use crate::{err::*, *}; |
| |
| /// Slice containing laxly separated IPv4 or IPv6 headers & payload. |
| /// |
| /// Compared to the normal [`IpSlice`] 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) or `payload_len` (for IPv6) |
| /// 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 enum LaxIpSlice<'a> { |
| /// The ipv4 header & the decoded extension headers. |
| Ipv4(LaxIpv4Slice<'a>), |
| /// The ipv6 header & the decoded extension headers. |
| Ipv6(LaxIpv6Slice<'a>), |
| } |
| |
| impl<'a> LaxIpSlice<'a> { |
| /// Returns a reference to the `Ipv4Slice` if `self` is a `IpSlice::Ipv4`. |
| pub fn ipv4(&self) -> Option<&LaxIpv4Slice> { |
| use LaxIpSlice::*; |
| match self { |
| Ipv4(slice) => Some(slice), |
| Ipv6(_) => None, |
| } |
| } |
| |
| /// Returns a reference to the `Ipv6Slice` if `self` is a `IpSlice::Ipv6`. |
| pub fn ipv6(&self) -> Option<&LaxIpv6Slice> { |
| use LaxIpSlice::*; |
| match self { |
| Ipv4(_) => None, |
| Ipv6(slice) => Some(slice), |
| } |
| } |
| |
| /// Returns true if the payload is fragmented. |
| pub fn is_fragmenting_payload(&self) -> bool { |
| match self { |
| LaxIpSlice::Ipv4(s) => s.is_payload_fragmented(), |
| LaxIpSlice::Ipv6(s) => s.is_payload_fragmented(), |
| } |
| } |
| |
| /// Return the source address as an std::net::Ipvddr (requires |
| /// crate feature `std`). |
| #[cfg(feature = "std")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
| pub fn source_addr(&self) -> std::net::IpAddr { |
| match self { |
| LaxIpSlice::Ipv4(s) => s.header().source_addr().into(), |
| LaxIpSlice::Ipv6(s) => s.header().source_addr().into(), |
| } |
| } |
| |
| /// Return the destination address as an std::net::IpAddr (requires |
| /// crate feature `std`). |
| #[cfg(feature = "std")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
| pub fn destination_addr(&self) -> std::net::IpAddr { |
| match self { |
| LaxIpSlice::Ipv4(s) => s.header().destination_addr().into(), |
| LaxIpSlice::Ipv6(s) => s.header().destination_addr().into(), |
| } |
| } |
| |
| /// Returns a slice containing the data after the IP header |
| /// and IP extensions headers. |
| #[inline] |
| pub fn payload(&self) -> &LaxIpPayloadSlice<'a> { |
| use LaxIpSlice::*; |
| match self { |
| Ipv4(ipv4) => ipv4.payload(), |
| Ipv6(ipv6) => ipv6.payload(), |
| } |
| } |
| |
| /// Returns the ip number the type of payload of the IP packet. |
| /// |
| /// This function returns the ip number stored in the last |
| /// IP header or extension header. |
| #[inline] |
| pub fn payload_ip_number(&self) -> IpNumber { |
| use LaxIpSlice::*; |
| match self { |
| Ipv4(ipv4) => ipv4.payload().ip_number, |
| Ipv6(ipv6) => ipv6.payload().ip_number, |
| } |
| } |
| |
| /// Separates IP headers (include extension headers) & the IP payload from the given slice |
| /// as far as possible without encountering an error and with less strict length checks. |
| /// This function is usefull for cut off packet or for packets with unset length fields. |
| /// |
| /// If you want to only receive correct IpPayloads use [`IpSlice::from_ip_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) or `payload_length` (for IPv6) |
| /// 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 `IpSlice::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 `from_slice` function an `Err` is returned if an error is |
| /// encountered in an exteions header. |
| /// * `from_slice_lax` ignores inconsistent `total_len` (in IPv4 headers) and |
| /// inconsistent `payload_length` (in IPv6 headers) values. When these length |
| /// values in the IP header are 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 `result.payload().len_source` is set to [`LenSource::Slice`]. |
| /// If a substitution was not needed `len_source` is set to |
| /// [`LenSource::Ipv4HeaderTotalLen`] or [`LenSource::Ipv6HeaderPayloadLen`]. |
| /// |
| /// # When is the slice length used as a fallback? |
| /// |
| /// For IPv4 packets the slice length is used as a fallback/substitude |
| /// 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. |
| /// |
| /// For IPv6 packet the slice length is used as a fallback/substitude |
| /// if the `payload_length` is |
| /// |
| /// * Bigger then the given slice (payload cannot fully be seperated). |
| /// * The value `0`. |
| pub fn from_slice( |
| slice: &[u8], |
| ) -> Result< |
| ( |
| LaxIpSlice, |
| Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, |
| ), |
| err::ip::LaxHeaderSliceError, |
| > { |
| use crate::ip_number::AUTH; |
| use err::ip::HeaderError::*; |
| use err::ip::LaxHeaderSliceError as E; |
| use err::ipv6_exts::HeaderError as SH; |
| use err::ipv6_exts::HeaderSliceError as S; |
| use LaxIpSlice::*; |
| |
| if slice.is_empty() { |
| Err(E::Len(err::LenError { |
| required_len: 1, |
| len: slice.len(), |
| len_source: LenSource::Slice, |
| layer: err::Layer::IpHeader, |
| layer_start_offset: 0, |
| })) |
| } else { |
| // SAFETY: Safe as slice is not empty. |
| let first_byte = unsafe { slice.get_unchecked(0) }; |
| match first_byte >> 4 { |
| 4 => { |
| let ihl = first_byte & 0xf; |
| |
| // check that the ihl has at least the length of the base IPv4 header |
| if ihl < 5 { |
| return Err(E::Content(Ipv4HeaderLengthSmallerThanHeader { ihl })); |
| } |
| |
| // check there is enough data for the header |
| let header_len = (usize::from(ihl)) * 4; |
| if slice.len() < header_len { |
| return Err(E::Len(LenError { |
| required_len: header_len, |
| len: slice.len(), |
| len_source: LenSource::Slice, |
| layer: Layer::Ipv4Header, |
| layer_start_offset: 0, |
| })); |
| } |
| |
| // SAFETY: |
| // Safe as the slice length is checked to be at least |
| // header_len or greater above. |
| let header = unsafe { |
| Ipv4HeaderSlice::from_slice_unchecked(core::slice::from_raw_parts( |
| slice.as_ptr(), |
| header_len, |
| )) |
| }; |
| |
| // check the total_lenat least contains the header |
| let total_len = usize::from(header.total_len()); |
| |
| let (header_payload, len_source, incomplete) = if total_len < header_len { |
| // fallback to slice len |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| // SAFETY: Safe as slice.len() >= header_len was validated |
| // in a if statement above. |
| slice.as_ptr().add(header_len), |
| // SAFETY: Safe as slice.len() >= header_len was validated |
| // in a if statement above. |
| slice.len() - header_len, |
| ) |
| }, |
| LenSource::Slice, |
| false, |
| ) |
| } else if slice.len() < total_len { |
| // fallback to slice len |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| // SAFETY: Safe as slice.len() >= header_len was validated |
| // in a if statement above. |
| slice.as_ptr().add(header_len), |
| // SAFETY: Safe as slice.len() >= header_len was validated |
| // in a if statement above. |
| slice.len() - header_len, |
| ) |
| }, |
| LenSource::Slice, |
| true, // flag payload as incomplete |
| ) |
| } else { |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| // SAFETY: Safe as slice.len() >= header_len was validated |
| // in a if statement above. |
| slice.as_ptr().add(header_len), |
| // SAFETY: Safe as total_length >= header_len was verfied in an |
| // if statement above as well as that slice.len() >= total_length_usize. |
| total_len - header_len, |
| ) |
| }, |
| LenSource::Ipv4HeaderTotalLen, |
| false, |
| ) |
| }; |
| |
| // slice extension headers |
| // decode the authentication header if needed |
| let fragmented = header.is_fragmenting_payload(); |
| match header.protocol() { |
| AUTH => { |
| use crate::err::ip_auth::HeaderSliceError as A; |
| |
| // 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(), |
| ) |
| }; |
| Ok(( |
| Ipv4(LaxIpv4Slice { |
| header, |
| exts: Ipv4ExtensionsSlice { auth: Some(auth) }, |
| payload: LaxIpPayloadSlice { |
| incomplete, |
| ip_number: auth.next_header(), |
| fragmented, |
| len_source, |
| payload, |
| }, |
| }), |
| None, |
| )) |
| } |
| Err(err) => { |
| let ip_number = header.protocol(); |
| Ok(( |
| Ipv4(LaxIpv4Slice { |
| header, |
| exts: Ipv4ExtensionsSlice { auth: None }, |
| payload: LaxIpPayloadSlice { |
| incomplete, |
| ip_number, |
| fragmented, |
| len_source, |
| payload: header_payload, |
| }, |
| }), |
| match err { |
| A::Len(mut l) => Some(( |
| S::Len({ |
| l.len_source = len_source; |
| l.add_offset(header.slice().len()) |
| }), |
| err::Layer::IpAuthHeader, |
| )), |
| A::Content(l) => Some(( |
| S::Content(SH::IpAuth(l)), |
| err::Layer::IpAuthHeader, |
| )), |
| }, |
| )) |
| } |
| } |
| } |
| ip_number => Ok(( |
| Ipv4(LaxIpv4Slice { |
| header, |
| exts: Ipv4ExtensionsSlice { auth: None }, |
| payload: LaxIpPayloadSlice { |
| incomplete, |
| ip_number, |
| fragmented, |
| len_source, |
| payload: header_payload, |
| }, |
| }), |
| None, |
| )), |
| } |
| } |
| 6 => { |
| // check length |
| if slice.len() < Ipv6Header::LEN { |
| return Err(E::Len(LenError { |
| required_len: Ipv6Header::LEN, |
| len: slice.len(), |
| len_source: LenSource::Slice, |
| layer: Layer::Ipv6Header, |
| layer_start_offset: 0, |
| })); |
| } |
| |
| let header = unsafe { |
| Ipv6HeaderSlice::from_slice_unchecked(core::slice::from_raw_parts( |
| slice.as_ptr(), |
| Ipv6Header::LEN, |
| )) |
| }; |
| |
| // restrict slice by the length specified in the header (if possible) |
| let payload_len = usize::from(header.payload_length()); |
| let (header_payload, len_source, incomplete) = |
| if 0 == payload_len && slice.len() > Ipv6Header::LEN { |
| // zero set as payload len, assume jumbograms or unitialized |
| // length and use the slice length as a fallback value |
| // TODO: Add payload length parsing from the jumbogram for the zero case |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| slice.as_ptr().add(Ipv6Header::LEN), |
| slice.len() - Ipv6Header::LEN, |
| ) |
| }, |
| LenSource::Slice, |
| false, |
| ) |
| } else if slice.len() - Ipv6Header::LEN < payload_len { |
| // slice is smaller then the assumed payload length |
| ( |
| unsafe { |
| core::slice::from_raw_parts( |
| slice.as_ptr().add(Ipv6Header::LEN), |
| slice.len() - Ipv6Header::LEN, |
| ) |
| }, |
| LenSource::Slice, |
| true, // incomplete |
| ) |
| } else { |
| // all good, all data should be here |
| ( |
| 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); |
| |
| // add len offset |
| if let Some((S::Len(l), _)) = ext_stop_err.as_mut() { |
| l.len_source = len_source; |
| l.layer_start_offset += header.header_len(); |
| } |
| let fragmented = exts.is_fragmenting_payload(); |
| Ok(( |
| Ipv6(LaxIpv6Slice { |
| header, |
| exts, |
| payload: LaxIpPayloadSlice { |
| incomplete, |
| ip_number: payload_ip_number, |
| fragmented, |
| len_source, |
| payload, |
| }, |
| }), |
| ext_stop_err, |
| )) |
| } |
| version_number => Err(E::Content(UnsupportedIpVersion { version_number })), |
| } |
| } |
| } |
| } |
| |
| impl<'a> From<LaxIpv4Slice<'a>> for LaxIpSlice<'a> { |
| fn from(value: LaxIpv4Slice<'a>) -> Self { |
| LaxIpSlice::Ipv4(value) |
| } |
| } |
| |
| impl<'a> From<LaxIpv6Slice<'a>> for LaxIpSlice<'a> { |
| fn from(value: LaxIpv6Slice<'a>) -> Self { |
| LaxIpSlice::Ipv6(value) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::test_gens::*; |
| use alloc::{format, vec::Vec}; |
| use proptest::prelude::*; |
| use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; |
| |
| #[test] |
| fn debug_clone_eq() { |
| // ipv4 |
| { |
| let mut header: Ipv4Header = Default::default(); |
| header.protocol = ip_number::UDP; |
| header.set_payload_len(0).unwrap(); |
| let buffer = header.to_bytes(); |
| |
| let ipv4 = LaxIpv4Slice::from_slice(&buffer).unwrap().0; |
| let slice = LaxIpSlice::Ipv4(ipv4.clone()); |
| |
| // clone & eq |
| assert_eq!(slice.clone(), slice); |
| |
| // debug |
| assert_eq!(format!("{:?}", slice), format!("Ipv4({:?})", ipv4)); |
| } |
| // ipv6 |
| { |
| let header = Ipv6Header { |
| payload_length: 0, |
| next_header: ip_number::UDP, |
| ..Default::default() |
| }; |
| let buffer = header.to_bytes(); |
| let ipv6 = LaxIpv6Slice::from_slice(&buffer).unwrap().0; |
| let slice = LaxIpSlice::Ipv6(ipv6.clone()); |
| |
| // clone & eq |
| assert_eq!(slice.clone(), slice); |
| |
| // debug |
| assert_eq!(format!("{:?}", slice), format!("Ipv6({:?})", ipv6)); |
| } |
| } |
| |
| #[test] |
| fn is_fragmenting_payload() { |
| for fragment in [false, true] { |
| use ip_number::UDP; |
| // ipv4 |
| { |
| let mut ipv4 = Ipv4Header::new(0, 1, UDP, [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); |
| if fragment { |
| ipv4.fragment_offset = 123.try_into().unwrap(); |
| } |
| |
| let data = ipv4.to_bytes(); |
| let ipv4_slice = LaxIpv4Slice::from_slice(&data).unwrap().0; |
| assert_eq!( |
| fragment, |
| LaxIpSlice::Ipv4(ipv4_slice).is_fragmenting_payload() |
| ); |
| } |
| |
| // ipv6 |
| { |
| let ipv6_frag = Ipv6FragmentHeader { |
| next_header: UDP, |
| fragment_offset: IpFragOffset::ZERO, |
| more_fragments: fragment, |
| identification: 0, |
| }; |
| let ipv6 = Ipv6Header { |
| traffic_class: 0, |
| flow_label: 1.try_into().unwrap(), |
| payload_length: ipv6_frag.header_len() as u16, |
| next_header: ip_number::IPV6_FRAG, |
| hop_limit: 4, |
| source: [1; 16], |
| destination: [2; 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_eq!( |
| fragment, |
| LaxIpSlice::Ipv6(LaxIpv6Slice::from_slice(&data).unwrap().0) |
| .is_fragmenting_payload() |
| ); |
| } |
| } |
| } |
| |
| #[cfg(feature = "std")] |
| #[test] |
| fn source_addr() { |
| // ipv4 |
| { |
| let data = Ipv4Header::new(0, 1, 2.into(), [3, 4, 5, 6], [7, 8, 9, 10]) |
| .unwrap() |
| .to_bytes(); |
| assert_eq!( |
| IpAddr::V4(Ipv4Addr::from([3, 4, 5, 6])), |
| LaxIpSlice::Ipv4(LaxIpv4Slice::from_slice(&data[..]).unwrap().0).source_addr() |
| ); |
| } |
| |
| // ipv6 |
| { |
| let data = Ipv6Header { |
| traffic_class: 0, |
| flow_label: 1.try_into().unwrap(), |
| payload_length: 0, |
| next_header: ip_number::IGMP, |
| hop_limit: 4, |
| source: [1; 16], |
| destination: [2; 16], |
| } |
| .to_bytes(); |
| |
| assert_eq!( |
| IpAddr::V6(Ipv6Addr::from([1; 16])), |
| LaxIpSlice::Ipv6(LaxIpv6Slice::from_slice(&data[..]).unwrap().0).source_addr() |
| ); |
| } |
| } |
| |
| #[cfg(feature = "std")] |
| #[test] |
| fn destination_addr() { |
| use crate::ip_number::UDP; |
| |
| // ipv4 |
| { |
| let data = Ipv4Header::new(0, 1, UDP, [3, 4, 5, 6], [7, 8, 9, 10]) |
| .unwrap() |
| .to_bytes(); |
| |
| assert_eq!( |
| IpAddr::V4(Ipv4Addr::from([7, 8, 9, 10])), |
| LaxIpSlice::Ipv4(LaxIpv4Slice::from_slice(&data[..]).unwrap().0).destination_addr() |
| ); |
| } |
| |
| // ipv6 |
| { |
| let data = Ipv6Header { |
| traffic_class: 0, |
| flow_label: 1.try_into().unwrap(), |
| payload_length: 0, |
| next_header: ip_number::IGMP, |
| hop_limit: 4, |
| source: [1; 16], |
| destination: [2; 16], |
| } |
| .to_bytes(); |
| |
| assert_eq!( |
| IpAddr::V6(Ipv6Addr::from([2; 16])), |
| LaxIpSlice::Ipv6(LaxIpv6Slice::from_slice(&data).unwrap().0).destination_addr() |
| ); |
| } |
| } |
| |
| #[test] |
| fn ip_payload() { |
| let payload: [u8; 4] = [1, 2, 3, 4]; |
| // ipv4 |
| { |
| let header = Ipv4Header::new( |
| payload.len() as u16, |
| 1, |
| ip_number::UDP, |
| [3, 4, 5, 6], |
| [7, 8, 9, 10], |
| ) |
| .unwrap(); |
| let mut data = Vec::with_capacity(header.header_len() + payload.len()); |
| data.extend_from_slice(&header.to_bytes()); |
| data.extend_from_slice(&payload); |
| assert_eq!( |
| LaxIpSlice::Ipv4(LaxIpv4Slice::from_slice(&data[..]).unwrap().0).payload(), |
| &LaxIpPayloadSlice { |
| incomplete: false, |
| ip_number: ip_number::UDP.into(), |
| fragmented: header.is_fragmenting_payload(), |
| len_source: LenSource::Ipv4HeaderTotalLen, |
| payload: &payload, |
| } |
| ); |
| } |
| |
| // ipv6 |
| { |
| let header = Ipv6Header { |
| traffic_class: 0, |
| flow_label: 1.try_into().unwrap(), |
| payload_length: payload.len() as u16, |
| next_header: ip_number::UDP, |
| hop_limit: 4, |
| source: [1; 16], |
| destination: [2; 16], |
| }; |
| let mut data = Vec::with_capacity(header.header_len() + payload.len()); |
| data.extend_from_slice(&header.to_bytes()); |
| data.extend_from_slice(&payload); |
| assert_eq!( |
| LaxIpSlice::Ipv6(LaxIpv6Slice::from_slice(&data[..]).unwrap().0).payload(), |
| &LaxIpPayloadSlice { |
| incomplete: false, |
| ip_number: ip_number::UDP.into(), |
| fragmented: false, |
| len_source: LenSource::Ipv6HeaderPayloadLen, |
| payload: &payload, |
| } |
| ); |
| } |
| } |
| |
| #[test] |
| fn payload_ip_number() { |
| use crate::ip_number::{IGMP, UDP}; |
| |
| // ipv4 |
| { |
| let data = Ipv4Header::new(0, 1, UDP, [3, 4, 5, 6], [7, 8, 9, 10]) |
| .unwrap() |
| .to_bytes(); |
| assert_eq!( |
| UDP, |
| LaxIpSlice::Ipv4(LaxIpv4Slice::from_slice(&data[..]).unwrap().0) |
| .payload_ip_number() |
| ); |
| } |
| |
| // ipv6 |
| { |
| let data = Ipv6Header { |
| traffic_class: 0, |
| flow_label: 1.try_into().unwrap(), |
| payload_length: 0, |
| next_header: IGMP, |
| hop_limit: 4, |
| source: [1; 16], |
| destination: [2; 16], |
| } |
| .to_bytes(); |
| |
| assert_eq!( |
| IGMP, |
| LaxIpSlice::Ipv6(LaxIpv6Slice::from_slice(&data).unwrap().0).payload_ip_number() |
| ); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn from_ip_slice( |
| ipv4_header in ipv4_any(), |
| ipv4_exts in ipv4_extensions_with(ip_number::UDP), |
| ipv6_header in ipv6_any(), |
| mut ipv6_exts in ipv6_extensions_with(ip_number::UDP) |
| ) { |
| use err::ip::HeaderError::*; |
| use err::ip::LaxHeaderSliceError as E; |
| use err::ipv6_exts::HeaderSliceError as S; |
| use err::ip_auth::HeaderError::*; |
| use crate::IpHeaders; |
| |
| // zero payload |
| assert_eq!( |
| LaxIpSlice::from_slice(&[]), |
| Err(E::Len(LenError{ |
| required_len: 1, |
| len: 0, |
| len_source: LenSource::Slice, |
| layer: Layer::IpHeader, |
| layer_start_offset: 0, |
| })) |
| ); |
| |
| // unknown version number |
| for bad_version in 0..0xfu8 { |
| if bad_version != 4 && bad_version != 6 { |
| assert_eq!( |
| LaxIpSlice::from_slice(&[bad_version << 4]), |
| Err(E::Content(UnsupportedIpVersion { |
| version_number: bad_version, |
| })) |
| ); |
| } |
| } |
| |
| let payload = [1,2,3,4]; |
| |
| // IPv4 |
| { |
| // setup header length & fields |
| let ipv4_header = { |
| let mut header = ipv4_header; |
| header.protocol = if ipv4_exts.auth.is_some() { |
| ip_number::AUTH |
| } else { |
| ip_number::UDP |
| }; |
| header.total_len = (header.header_len() + ipv4_exts.header_len() + payload.len()) as u16; |
| header.header_checksum = header.calc_header_checksum(); |
| header |
| }; |
| |
| let ipv4 = IpHeaders::Ipv4( |
| ipv4_header.clone(), |
| ipv4_exts.clone() |
| ); |
| |
| // build packet |
| let mut buffer = Vec::with_capacity(ipv4.header_len() + payload.len()); |
| ipv4.write(&mut buffer).unwrap(); |
| buffer.extend_from_slice(&payload); |
| |
| // happy path v4 |
| { |
| // run test |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| assert_eq!(None, actual_stop_err); |
| assert!(actual.ipv6().is_none()); |
| let actual = actual.ipv4().unwrap().clone(); |
| assert_eq!(actual.header.to_header(), ipv4_header); |
| assert_eq!(actual.extensions().to_header(), ipv4_exts); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: ip_number::UDP.into(), |
| fragmented: ipv4_header.is_fragmenting_payload(), |
| len_source: LenSource::Ipv4HeaderTotalLen, |
| payload: &payload |
| } |
| ); |
| } |
| |
| // ihl smaller then 5 error |
| for bad_ihl in 0..5u8 { |
| let mut buffer = buffer.clone(); |
| |
| // inject bad IHL |
| buffer[0] = (buffer[0] & 0xf0u8) | bad_ihl; |
| |
| assert_eq!( |
| LaxIpSlice::from_slice(&buffer), |
| Err(E::Content(Ipv4HeaderLengthSmallerThanHeader { ihl: bad_ihl })) |
| ); |
| } |
| |
| // slice smaller then header error |
| for bad_len in 1..ipv4_header.header_len() { |
| assert_eq!( |
| LaxIpSlice::from_slice(&buffer[..bad_len]), |
| Err(E::Len(LenError{ |
| required_len: ipv4_header.header_len(), |
| len: bad_len, |
| len_source: LenSource::Slice, |
| layer: Layer::Ipv4Header, |
| layer_start_offset: 0, |
| })) |
| ); |
| } |
| |
| // total len smaller then header |
| for bad_len in 1..ipv4_header.header_len() { |
| let mut buffer = buffer.clone(); |
| |
| // inject bad total length |
| let bad_len_be = (bad_len as u16).to_be_bytes(); |
| buffer[2] = bad_len_be[0]; |
| buffer[3] = bad_len_be[1]; |
| |
| // expect a valid parse with length source "slice" |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| assert_eq!(None, actual_stop_err); |
| let actual = actual.ipv4().unwrap().clone(); |
| let mut expected_header = ipv4_header.clone(); |
| expected_header.total_len = bad_len as u16; |
| assert_eq!(actual.header.to_header(), expected_header); |
| assert_eq!(actual.extensions().to_header(), ipv4_exts); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: ip_number::UDP.into(), |
| fragmented: ipv4_header.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &payload |
| } |
| ); |
| } |
| |
| // total len bigger then slice |
| { |
| let bad_len = (buffer.len() + 1) as u16; |
| let mut buffer = buffer.clone(); |
| |
| // inject bad total length |
| let bad_len_be = (bad_len as u16).to_be_bytes(); |
| buffer[2] = bad_len_be[0]; |
| buffer[3] = bad_len_be[1]; |
| |
| // expect a valid parse with length source "slice" & incomplete set |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| assert_eq!(None, actual_stop_err); |
| let actual = actual.ipv4().unwrap().clone(); |
| let mut expected_header = ipv4_header.clone(); |
| expected_header.total_len = bad_len as u16; |
| assert_eq!(actual.header.to_header(), expected_header); |
| assert_eq!(actual.extensions().to_header(), ipv4_exts); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: true, |
| ip_number: ip_number::UDP.into(), |
| fragmented: ipv4_header.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &payload |
| } |
| ); |
| } |
| |
| // auth ext header len error |
| if ipv4_exts.auth.is_some() { |
| let bad_len = ipv4_header.header_len() + ipv4_exts.header_len() - 1; |
| |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer[..bad_len]).unwrap(); |
| assert_eq!( |
| actual_stop_err, |
| Some(( |
| S::Len(LenError{ |
| required_len: ipv4_exts.header_len(), |
| len: bad_len - ipv4_header.header_len(), |
| len_source: LenSource::Slice, |
| layer: Layer::IpAuthHeader, |
| layer_start_offset: ipv4_header.header_len(), |
| }), |
| Layer::IpAuthHeader |
| )) |
| ); |
| assert_eq!(actual.ipv4().unwrap().clone().header().to_header(), ipv4_header); |
| assert_eq!( |
| actual.payload(), |
| &LaxIpPayloadSlice{ |
| incomplete: true, |
| ip_number: ip_number::AUTH, |
| fragmented: ipv4_header.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &buffer[ipv4_header.header_len()..bad_len], |
| } |
| ); |
| } |
| |
| // auth ext header content error |
| if ipv4_exts.auth.is_some() { |
| let mut buffer = buffer.clone(); |
| buffer[ipv4_header.header_len() + 1] = 0; |
| |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| |
| assert_eq!( |
| actual_stop_err, |
| Some(( |
| S::Content(ipv6_exts::HeaderError::IpAuth(ZeroPayloadLen)), |
| Layer::IpAuthHeader |
| )) |
| ); |
| assert_eq!(actual.ipv4().unwrap().clone().header().to_header(), ipv4_header); |
| assert_eq!( |
| actual.payload(), |
| &LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: ip_number::AUTH, |
| fragmented: ipv4_header.is_fragmenting_payload(), |
| len_source: LenSource::Ipv4HeaderTotalLen, |
| payload: &buffer[ipv4_header.header_len()..], |
| } |
| ); |
| } |
| } |
| |
| // IPv6 |
| { |
| let ipv6_header = { |
| let mut header = ipv6_header; |
| header.next_header = ipv6_exts.set_next_headers(ip_number::UDP); |
| header.payload_length = (ipv6_exts.header_len() + payload.len()) as u16; |
| header |
| }; |
| |
| let ipv6 = IpHeaders::Ipv6( |
| ipv6_header.clone(), |
| ipv6_exts.clone() |
| ); |
| |
| // build packet |
| let mut buffer = Vec::with_capacity(ipv6.header_len() + payload.len()); |
| ipv6.write(&mut buffer).unwrap(); |
| buffer.extend_from_slice(&payload); |
| |
| // happy path v6 |
| { |
| // run test |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| assert_eq!(None, actual_stop_err); |
| assert!(actual.ipv4().is_none()); |
| let actual = actual.ipv6().unwrap().clone(); |
| assert_eq!(actual.header.to_header(), ipv6_header); |
| assert_eq!( |
| Ipv6Extensions::from_slice( |
| ipv6_header.next_header, |
| actual.extensions().slice() |
| ).unwrap().0, |
| ipv6_exts |
| ); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: ip_number::UDP.into(), |
| fragmented: ipv6_exts.is_fragmenting_payload(), |
| len_source: LenSource::Ipv6HeaderPayloadLen, |
| payload: &payload |
| } |
| ); |
| } |
| |
| // len error when parsing header |
| for bad_len in 1..ipv6_header.header_len() { |
| assert_eq!( |
| LaxIpSlice::from_slice(&buffer[..bad_len]), |
| Err(E::Len(LenError{ |
| required_len: ipv6_header.header_len(), |
| len: bad_len, |
| len_source: LenSource::Slice, |
| layer: Layer::Ipv6Header, |
| layer_start_offset: 0, |
| })) |
| ); |
| } |
| |
| // ipv6 with zero payload length (should fallback to the slice length) |
| { |
| let mut buffer = buffer.clone(); |
| |
| // inject 0 as payload len |
| buffer[4] = 0; |
| buffer[5] = 0; |
| |
| // run test |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| assert_eq!(None, actual_stop_err); |
| let actual = actual.ipv6().unwrap().clone(); |
| let mut expected_header = ipv6_header.clone(); |
| expected_header.payload_length = 0; |
| assert_eq!(actual.header.to_header(), expected_header); |
| assert_eq!( |
| Ipv6Extensions::from_slice( |
| ipv6_header.next_header, |
| actual.extensions().slice() |
| ).unwrap().0, |
| ipv6_exts |
| ); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: false, |
| ip_number: ip_number::UDP.into(), |
| fragmented: ipv6_exts.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &payload |
| } |
| ); |
| } |
| |
| // payload len bigger then slice |
| { |
| let mut buffer = buffer.clone(); |
| |
| // inject 0 as payload len |
| let bad_payload_len = (buffer.len() - ipv6_header.header_len() + 1) as u16; |
| let bad_payload_len_be = bad_payload_len.to_be_bytes(); |
| buffer[4] = bad_payload_len_be[0]; |
| buffer[5] = bad_payload_len_be[1]; |
| |
| // run test |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| assert_eq!(None, actual_stop_err); |
| let actual = actual.ipv6().unwrap().clone(); |
| let mut expected_header = ipv6_header.clone(); |
| expected_header.payload_length = bad_payload_len; |
| |
| assert_eq!(actual.header.to_header(), expected_header); |
| assert_eq!( |
| Ipv6Extensions::from_slice( |
| ipv6_header.next_header, |
| actual.extensions().slice() |
| ).unwrap().0, |
| ipv6_exts |
| ); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: true, |
| ip_number: ip_number::UDP.into(), |
| fragmented: ipv6_exts.is_fragmenting_payload(), |
| len_source: LenSource::Slice, |
| payload: &payload |
| } |
| ); |
| } |
| |
| // extension length error |
| if ipv6_exts.hop_by_hop_options.is_some() { |
| let bad_len = Ipv6Header::LEN + 1; |
| |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer[..bad_len]).unwrap(); |
| |
| let actual = actual.ipv6().unwrap().clone(); |
| assert_eq!(actual.header.to_header(), ipv6_header); |
| assert_eq!( |
| actual_stop_err, |
| Some(( |
| S::Len(LenError{ |
| required_len: 8, |
| len: bad_len - ipv6_header.header_len(), |
| len_source: LenSource::Slice, |
| layer: Layer::Ipv6ExtHeader, |
| layer_start_offset: ipv6_header.header_len(), |
| }), |
| Layer::Ipv6HopByHopHeader |
| )) |
| ); |
| assert_eq!( |
| actual.payload, |
| LaxIpPayloadSlice{ |
| incomplete: true, |
| ip_number: ip_number::IPV6_HOP_BY_HOP, |
| fragmented: false, // fragment header will not be able to be read |
| len_source: LenSource::Slice, |
| payload: &buffer[ipv6_header.header_len()..bad_len] |
| } |
| ); |
| } |
| |
| // extension content error |
| if ipv6_exts.auth.is_some() { |
| |
| // introduce a auth header zero payload error |
| let mut buffer = buffer.clone(); |
| let auth_offset = ipv6_header.header_len() + |
| ipv6_exts.hop_by_hop_options.as_ref().map(|h| h.header_len()).unwrap_or(0) + |
| ipv6_exts.destination_options.as_ref().map(|h| h.header_len()).unwrap_or(0) + |
| ipv6_exts.routing.as_ref().map(|h| h.routing.header_len()).unwrap_or(0) + |
| // routing.final_destination_options skiped, as after auth |
| ipv6_exts.fragment.as_ref().map(|h| h.header_len()).unwrap_or(0); |
| |
| // inject length zero into auth header (not valid, will |
| // trigger a content error) |
| buffer[auth_offset + 1] = 0; |
| |
| let (actual, actual_stop_err) = LaxIpSlice::from_slice(&buffer).unwrap(); |
| let actual = actual.ipv6().unwrap().clone(); |
| assert_eq!(actual.header.to_header(), ipv6_header); |
| assert_eq!( |
| actual_stop_err, |
| Some(( |
| S::Content(ipv6_exts::HeaderError::IpAuth(ZeroPayloadLen)), |
| Layer::IpAuthHeader, |
| )) |
| ); |
| } |
| } |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn from_ipv4_slice( |
| ipv4_header in ipv4_unknown() |
| ) { |
| let mut header = ipv4_header.clone(); |
| header.total_len = (header.header_len() + 4) as u16; |
| |
| let mut buffer = Vec::with_capacity(header.total_len.into()); |
| buffer.extend_from_slice(&header.to_bytes()[..]); |
| buffer.extend_from_slice(&[1,2,3,4]); |
| let s = LaxIpv4Slice::from_slice(&buffer).unwrap().0; |
| let actual: LaxIpSlice = s.clone().into(); |
| assert_eq!(LaxIpSlice::Ipv4(s), actual); |
| } |
| } |
| |
| proptest! { |
| #[test] |
| fn from_ipv6_slice( |
| ipv6_header in ipv6_unknown() |
| ) { |
| let mut header = ipv6_header.clone(); |
| header.payload_length = 4; |
| |
| let mut buffer = Vec::with_capacity(header.header_len() + 4); |
| buffer.extend_from_slice(&header.to_bytes()[..]); |
| buffer.extend_from_slice(&[1,2,3,4]); |
| let s = LaxIpv6Slice::from_slice(&buffer).unwrap().0; |
| let actual: LaxIpSlice = s.clone().into(); |
| assert_eq!(LaxIpSlice::Ipv6(s), actual); |
| } |
| } |
| } |