use crate::*; use core::slice::from_raw_parts; /// Slice containing the IPv6 extension headers present after the ip header. /// /// Currently supported: /// * Authentication Header /// * Hop by Hop Options Header /// * Destination Options Header (before and after routing headers) /// * Routing Header /// * Fragment /// * Authentication Header /// /// Currently not supported: /// * Encapsulating Security Payload Header (ESP) /// * Host Identity Protocol (HIP) /// * IP Mobility /// * Site Multihoming by IPv6 Intermediation (SHIM6) #[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct Ipv6ExtensionsSlice<'a> { /// IP protocol number of the first header present in the slice. first_header: Option, /// True if a fragment header is present in the ipv6 header extensions that causes the payload to be fragmented. fragmented: bool, /// Slice containing ipv6 extension headers. slice: &'a [u8], } impl<'a> Ipv6ExtensionsSlice<'a> { /// Collects all ipv6 extension headers in a slice & checks if /// a fragmentation header that fragments the packet is present. pub fn from_slice( start_ip_number: IpNumber, start_slice: &'a [u8], ) -> Result<(Ipv6ExtensionsSlice<'a>, IpNumber, &'a [u8]), err::ipv6_exts::HeaderSliceError> { let mut rest = start_slice; let mut next_header = start_ip_number; let mut fragmented = false; use err::ipv6_exts::{HeaderError::*, HeaderSliceError::*}; use ip_number::*; // the hop by hop header is required to occur directly after the ipv6 header if IPV6_HOP_BY_HOP == next_header { let slice = Ipv6RawExtHeaderSlice::from_slice(rest).map_err(Len)?; rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); } loop { match next_header { IPV6_HOP_BY_HOP => { return Err(Content(HopByHopNotAtStart)); } IPV6_DEST_OPTIONS | IPV6_ROUTE => { let slice = Ipv6RawExtHeaderSlice::from_slice(rest) .map_err(|err| Len(err.add_offset(start_slice.len() - rest.len())))?; // SAFETY: // Ipv6RawExtHeaderSlice::from_slice always generates // a subslice from the given slice rest. Therefor it is guaranteed // that len is always greater or equal the len of rest. rest = unsafe { let len = slice.slice().len(); from_raw_parts(rest.as_ptr().add(len), rest.len() - len) }; next_header = slice.next_header(); } IPV6_FRAG => { let slice = Ipv6FragmentHeaderSlice::from_slice(rest) .map_err(|err| Len(err.add_offset(start_slice.len() - rest.len())))?; // SAFETY: // Ipv6FragmentHeaderSlice::from_slice always generates // a subslice from the given slice rest. Therefor it is guaranteed // that len is always greater or equal the len of rest. rest = unsafe { let len = slice.slice().len(); from_raw_parts(rest.as_ptr().add(len), rest.len() - len) }; next_header = slice.next_header(); // check if the fragment header actually causes fragmentation fragmented = fragmented || slice.is_fragmenting_payload(); } AUTH => { let slice = IpAuthHeaderSlice::from_slice(rest).map_err(|err| { use err::ip_auth::HeaderSliceError as I; match err { I::Len(err) => Len(err.add_offset(start_slice.len() - rest.len())), I::Content(err) => Content(IpAuth(err)), } })?; // SAFETY: // IpAuthHeaderSlice::from_slice always generates // a subslice from the given slice rest. Therefor it is guaranteed // that len is always greater or equal the len of rest. rest = unsafe { let len = slice.slice().len(); from_raw_parts(rest.as_ptr().add(len), rest.len() - len) }; next_header = slice.next_header(); } // done parsing, the next header is not a known/supported header extension _ => break, } } Ok(( Ipv6ExtensionsSlice { first_header: if rest.len() != start_slice.len() { Some(start_ip_number) } else { None }, fragmented, slice: &start_slice[..start_slice.len() - rest.len()], }, next_header, rest, )) } /// Collects all ipv6 extension headers in a slice until an error /// is encountered or a "non IP extension header" is found and /// returns the successfully parsed parts (+ the unparsed slice /// it's `IpNumber` and the error if one occurred). /// /// The returned values are /// /// * [`Ipv6ExtensionsSlice`] containing the successfully parsed IPv6 extension headers /// * [`IpNumber`] of unparsed data /// * Slice with unparsed data /// * Optional with error if there was an error wich stoped the parsing. pub fn from_slice_lax( start_ip_number: IpNumber, start_slice: &'a [u8], ) -> ( Ipv6ExtensionsSlice<'a>, IpNumber, &'a [u8], Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, ) { let mut rest = start_slice; let mut next_header = start_ip_number; let mut error = None; let mut fragmented = false; use err::ipv6_exts::{HeaderError::*, HeaderSliceError::*}; use ip_number::*; // the hop by hop header is required to occur directly after the ipv6 header if IPV6_HOP_BY_HOP == next_header { match Ipv6RawExtHeaderSlice::from_slice(rest) { Ok(slice) => { rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); } Err(err) => { error = Some((Len(err), err::Layer::Ipv6HopByHopHeader)); } } } while error.is_none() { match next_header { IPV6_HOP_BY_HOP => { error = Some((Content(HopByHopNotAtStart), err::Layer::Ipv6HopByHopHeader)); break; } IPV6_DEST_OPTIONS | IPV6_ROUTE => { let slice = match Ipv6RawExtHeaderSlice::from_slice(rest) { Ok(s) => s, Err(err) => { error = Some(( Len(err.add_offset(start_slice.len() - rest.len())), if next_header == IPV6_DEST_OPTIONS { err::Layer::Ipv6DestOptionsHeader } else { err::Layer::Ipv6RouteHeader }, )); break; } }; // SAFETY: // Ipv6RawExtHeaderSlice::from_slice always generates // a subslice from the given slice rest. Therefor it is guranteed // that len is always greater or equal the len of rest. rest = unsafe { let len = slice.slice().len(); from_raw_parts(rest.as_ptr().add(len), rest.len() - len) }; next_header = slice.next_header(); } IPV6_FRAG => { let slice = match Ipv6FragmentHeaderSlice::from_slice(rest) { Ok(s) => s, Err(err) => { error = Some(( Len(err.add_offset(start_slice.len() - rest.len())), err::Layer::Ipv6FragHeader, )); break; } }; // SAFETY: // Ipv6FragmentHeaderSlice::from_slice always generates // a subslice from the given slice rest. Therefor it is guranteed // that len is always greater or equal the len of rest. rest = unsafe { let len = slice.slice().len(); from_raw_parts(rest.as_ptr().add(len), rest.len() - len) }; next_header = slice.next_header(); // check if the fragment header actually causes fragmentation fragmented = fragmented || slice.is_fragmenting_payload(); } AUTH => { use err::ip_auth::HeaderSliceError as I; let slice = match IpAuthHeaderSlice::from_slice(rest) { Ok(s) => s, Err(err) => { error = Some(( match err { I::Len(err) => { Len(err.add_offset(start_slice.len() - rest.len())) } I::Content(err) => Content(IpAuth(err)), }, err::Layer::IpAuthHeader, )); break; } }; // SAFETY: // IpAuthHeaderSlice::from_slice always generates // a subslice from the given slice rest. Therefor it is guranteed // that len is always greater or equal the len of rest. rest = unsafe { let len = slice.slice().len(); from_raw_parts(rest.as_ptr().add(len), rest.len() - len) }; next_header = slice.next_header(); } // done parsing, the next header is not a known/supported header extension _ => break, } } ( Ipv6ExtensionsSlice { first_header: if rest.len() != start_slice.len() { Some(start_ip_number) } else { None }, fragmented, slice: &start_slice[..start_slice.len() - rest.len()], }, next_header, rest, error, ) } /// Returns true if a fragmentation header is present in /// the extensions that fragments the payload. /// /// Note: A fragmentation header can still be present /// even if the return value is false in case the fragmentation /// headers don't fragment the payload. This is the case if /// the offset of all fragmentation header is 0 and the /// more fragment bit is not set. #[inline] pub fn is_fragmenting_payload(&self) -> bool { self.fragmented } /// Returns the ip protocol number of the first header in the slice /// if the slice contains an ipv6 extension header. If no ipv6 header /// is present None is returned. /// /// None is only returned if the slice length of this struct is 0. #[inline] pub fn first_header(&self) -> Option { self.first_header } /// Slice containing the ipv6 extension headers. #[inline] pub fn slice(&self) -> &'a [u8] { self.slice } /// Returns true if no IPv6 extension header is present (slice is empty). #[inline] pub fn is_empty(&self) -> bool { self.slice.is_empty() } } impl<'a> IntoIterator for Ipv6ExtensionsSlice<'a> { type Item = Ipv6ExtensionSlice<'a>; type IntoIter = Ipv6ExtensionSliceIter<'a>; fn into_iter(self) -> Self::IntoIter { Ipv6ExtensionSliceIter { // map the next header None value to some non ipv6 ext header // value. next_header: self.first_header.unwrap_or(ip_number::UDP), rest: self.slice, } } } #[cfg(test)] mod test { use super::ipv6_exts_test_helpers::*; use super::*; use crate::ip_number::*; use crate::test_gens::*; use alloc::{borrow::ToOwned, vec::Vec}; use proptest::prelude::*; proptest! { #[test] fn from_slice( header_size in any::(), post_header in ip_number_any() .prop_filter("Must be a non ipv6 header relevant ip number".to_owned(), |v| !EXTENSION_KNOWN_IP_NUMBERS.iter().any(|&x| v == &x) ) ) { use err::ipv6_exts::{HeaderError::*, HeaderSliceError::*}; // no extension headers filled { let some_data = [1,2,3,4]; let actual = Ipv6ExtensionsSlice::from_slice(UDP, &some_data).unwrap(); assert_eq!(actual.0.is_fragmenting_payload(), false); assert_eq!(actual.0.first_header(), None); assert_eq!(actual.0.slice().len(), 0); assert_eq!(actual.1, UDP); assert_eq!(actual.2, &some_data); } /// Run a test with the given ip numbers fn run_test(ip_numbers: &[IpNumber], header_sizes: &[u8]) { // setup test payload let e = ExtensionTestPayload::new( ip_numbers, header_sizes ); if e.ip_numbers[1..].iter().any(|&x| x == IPV6_HOP_BY_HOP) { // a hop by hop header that is not at the start triggers an error assert_eq!( Ipv6ExtensionsSlice::from_slice(ip_numbers[0], e.slice()).unwrap_err(), Content(HopByHopNotAtStart) ); } else { // normal read let (header, next, rest) = Ipv6ExtensionsSlice::from_slice(ip_numbers[0], e.slice()).unwrap(); assert_eq!(header.first_header(), Some(ip_numbers[0])); assert_eq!(header.slice(), e.slice()); assert_eq!(next, *ip_numbers.last().unwrap()); assert_eq!(rest, &e.slice()[e.slice().len()..]); // unexpected end of slice { let offset: usize = e.lengths[..e.lengths.len() - 1].into_iter().sum(); assert_eq!( Ipv6ExtensionsSlice::from_slice(ip_numbers[0], &e.slice()[..e.slice().len() - 1]).unwrap_err(), Len(err::LenError { required_len: e.slice().len() - offset, len: e.slice().len() - offset - 1, len_source: LenSource::Slice, layer: match ip_numbers[ip_numbers.len() - 2] { AUTH => err::Layer::IpAuthHeader, IPV6_FRAG => err::Layer::Ipv6FragHeader, _ => err::Layer::Ipv6ExtHeader }, layer_start_offset: offset, }) ); } } } // test the parsing of different extension header combinations for first_header in &EXTENSION_KNOWN_IP_NUMBERS { // single header parsing run_test( &[*first_header, post_header], &[header_size], ); for second_header in &EXTENSION_KNOWN_IP_NUMBERS { // double header parsing run_test( &[*first_header, *second_header, post_header], &[header_size], ); for third_header in &EXTENSION_KNOWN_IP_NUMBERS { // tripple header parsing run_test( &[*first_header, *second_header, *third_header, post_header], &[header_size], ); } } } } } proptest! { #[test] fn from_slice_lax( header_size in any::(), post_header in ip_number_any() .prop_filter("Must be a non ipv6 header relevant ip number".to_owned(), |v| !EXTENSION_KNOWN_IP_NUMBERS.iter().any(|&x| v == &x) ) ) { use err::ipv6_exts::{HeaderError::*, HeaderSliceError::*}; // no extension headers filled { let some_data = [1,2,3,4]; let actual = Ipv6ExtensionsSlice::from_slice_lax(UDP, &some_data); assert_eq!(actual.0.is_fragmenting_payload(), false); assert_eq!(actual.0.first_header(), None); assert_eq!(actual.0.slice().len(), 0); assert_eq!(actual.1, UDP); assert_eq!(actual.2, &some_data); } /// Run a test with the given ip numbers fn run_test(ip_numbers: &[IpNumber], header_sizes: &[u8]) { // setup test payload let e = ExtensionTestPayload::new( ip_numbers, header_sizes ); if e.ip_numbers[1..].iter().any(|&x| x == IPV6_HOP_BY_HOP) { // a hop by hop header that is not at the start triggers an error assert_eq!( Ipv6ExtensionsSlice::from_slice_lax(ip_numbers[0], e.slice()).3.unwrap(), (Content(HopByHopNotAtStart), err::Layer::Ipv6HopByHopHeader) ); } else { // normal read let actual_normal = Ipv6ExtensionsSlice::from_slice_lax(ip_numbers[0], e.slice()); assert_eq!(actual_normal.0.first_header(), Some(ip_numbers[0])); assert_eq!(actual_normal.0.slice(), e.slice()); assert_eq!(actual_normal.1, *ip_numbers.last().unwrap()); assert_eq!(actual_normal.2, &[]); // unexpected end of slice { let offset: usize = e.lengths[..e.lengths.len() - 1].into_iter().sum(); let actual = Ipv6ExtensionsSlice::from_slice_lax( ip_numbers[0], &e.slice()[..e.slice().len() - 1] ); assert_eq!(&e.slice()[offset..e.slice().len() - 1], actual.2); assert_eq!( actual.3.unwrap().0, Len(err::LenError { required_len: e.slice().len() - offset, len: e.slice().len() - offset - 1, len_source: LenSource::Slice, layer: match ip_numbers[ip_numbers.len() - 2] { AUTH => err::Layer::IpAuthHeader, IPV6_FRAG => err::Layer::Ipv6FragHeader, _ => err::Layer::Ipv6ExtHeader }, layer_start_offset: offset, }) ); } } } // test the parsing of different extension header combinations for first_header in &EXTENSION_KNOWN_IP_NUMBERS { // single header parsing run_test( &[*first_header, post_header], &[header_size], ); for second_header in &EXTENSION_KNOWN_IP_NUMBERS { // double header parsing run_test( &[*first_header, *second_header, post_header], &[header_size], ); for third_header in &EXTENSION_KNOWN_IP_NUMBERS { // tripple header parsing run_test( &[*first_header, *second_header, *third_header, post_header], &[header_size], ); } } } // test that the auth content error gets forwarded { let auth = IpAuthHeader::new(post_header, 0, 0, &[]).unwrap(); let mut bytes = auth.to_bytes(); // inject an invalid len value bytes[1] = 0; let actual = Ipv6ExtensionsSlice::from_slice_lax(AUTH, &bytes); use err::ipv6_exts::HeaderError::IpAuth; use err::ip_auth::HeaderError::ZeroPayloadLen; assert_eq!(actual.0.slice(), &[]); assert_eq!(actual.1, AUTH); assert_eq!(actual.2, &bytes[..]); assert_eq!(actual.3.unwrap().0.content().unwrap(), &IpAuth(ZeroPayloadLen)); } } } proptest! { #[test] fn is_fragmenting_payload( hop_by_hop_options in ipv6_raw_ext_any(), destination_options in ipv6_raw_ext_any(), routing in ipv6_raw_ext_any(), auth in ip_auth_any(), final_destination_options in ipv6_raw_ext_any() ) { // no fragment header { let mut exts = Ipv6Extensions{ hop_by_hop_options: Some(hop_by_hop_options), destination_options: Some(destination_options), routing: Some( Ipv6RoutingExtensions { routing, final_destination_options: Some(final_destination_options), } ), fragment: None, auth: Some(auth), }; let first_ip_number = exts.set_next_headers(UDP); let mut bytes = Vec::with_capacity(exts.header_len()); exts.write(&mut bytes, first_ip_number).unwrap(); let (header, _, _) = Ipv6ExtensionsSlice::from_slice(first_ip_number, &bytes).unwrap(); assert_eq!(false, header.is_fragmenting_payload()); } // different variants of the fragment header with // variants that fragment and variants that don't fragment let frag_variants : [(bool, Ipv6FragmentHeader);4] = [ (false, Ipv6FragmentHeader::new(UDP, 0.try_into().unwrap(), false, 123)), (true, Ipv6FragmentHeader::new(UDP, 2.try_into().unwrap(), false, 123)), (true, Ipv6FragmentHeader::new(UDP, 0.try_into().unwrap(), true, 123)), (true, Ipv6FragmentHeader::new(UDP, 3.try_into().unwrap(), true, 123)), ]; for (first_expected, first_header) in frag_variants.iter() { // single fragment header { let bytes = first_header.to_bytes(); let (header, _, _) = Ipv6ExtensionsSlice::from_slice(IPV6_FRAG, &bytes).unwrap(); assert_eq!(*first_expected, header.is_fragmenting_payload()); } // two fragment headers for (second_expected, second_header) in frag_variants.iter() { let mut first_mod = first_header.clone(); first_mod.next_header = IPV6_FRAG; let mut bytes = Vec::with_capacity(first_mod.header_len() + second_header.header_len()); bytes.extend_from_slice(&first_mod.to_bytes()); bytes.extend_from_slice(&second_header.to_bytes()); let (header, _, _) = Ipv6ExtensionsSlice::from_slice(IPV6_FRAG, &bytes).unwrap(); assert_eq!( *first_expected || *second_expected, header.is_fragmenting_payload() ); } } } } #[test] fn is_empty() { // empty { let slice = Ipv6ExtensionsSlice::from_slice(ip_number::UDP, &[]) .unwrap() .0; assert!(slice.is_empty()); } // fragment { let bytes = Ipv6FragmentHeader::new(ip_number::UDP, IpFragOffset::ZERO, true, 0).to_bytes(); let slice = Ipv6ExtensionsSlice::from_slice(ip_number::IPV6_FRAG, &bytes) .unwrap() .0; assert_eq!(false, slice.is_empty()); } } #[test] fn debug() { use alloc::format; let a: Ipv6ExtensionsSlice = Default::default(); assert_eq!( "Ipv6ExtensionsSlice { first_header: None, fragmented: false, slice: [] }", &format!("{:?}", a) ); } #[test] fn clone_eq() { let a: Ipv6ExtensionsSlice = Default::default(); assert_eq!(a, a.clone()); } #[test] fn default() { let a: Ipv6ExtensionsSlice = Default::default(); assert_eq!(a.is_fragmenting_payload(), false); assert_eq!(a.first_header(), None); assert_eq!(a.slice().len(), 0); } }