use crate::{ err::{ipv6_exts::*, Layer}, *, }; /// 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 Ipv6Extensions { pub hop_by_hop_options: Option, pub destination_options: Option, pub routing: Option, pub fragment: Option, pub auth: Option, } impl Ipv6Extensions { /// Minimum length required for extension header in bytes/octets. /// Which is zero as no extension headers are required. pub const MIN_LEN: usize = 0; /// Maximum summed up length of all extension headers in bytes/octets. pub const MAX_LEN: usize = Ipv6RawExtHeader::MAX_LEN * 2 + Ipv6RoutingExtensions::MAX_LEN + Ipv6FragmentHeader::LEN + IpAuthHeader::MAX_LEN; /// Reads as many extension headers as possible from the slice. /// /// Returns the found ipv6 extension headers, the next header ip number after the read /// headers and a slice containing the rest of the packet after the read headers. /// /// Note that this function can only handle ipv6 extensions if each extension header does /// occur at most once, except for destination options headers which are allowed to /// exist once in front of a routing header and once after a routing header. /// /// In case that more extension headers then can fit into a `Ipv6Extensions` struct are /// encountered, the parsing is stoped at the point where the data would no longer fit into /// the struct. In such a scenario a struct with the data that could be parsed is returned /// together with the next header ip number and slice containing the unparsed data. /// /// It is in the responsibility of the caller to handle a scenario like this. /// /// The reason that no error is generated, is that even though according to RFC 8200 packets /// "should" not contain more then one occurence of an extension header the RFC also specifies /// that "IPv6 nodes must accept and attempt to process extension headers in any order and /// occurring any number of times in the same packet". So packets with multiple headers "should" /// not exist, but are still valid IPv6 packets. As such this function does not generate a /// parsing error, as it is not an invalid packet, but if packets like these are encountered /// the user of this function has to themself decide how to handle packets like these. /// /// The only exception is if an hop by hop header is located somewhere else then directly at /// the start. In this case an `ReadError::Ipv6HopByHopHeaderNotAtStart` error is generated as /// the hop by hop header is required to be located directly after the IPv6 header according /// to RFC 8200. pub fn from_slice( start_ip_number: IpNumber, slice: &[u8], ) -> Result<(Ipv6Extensions, IpNumber, &[u8]), err::ipv6_exts::HeaderSliceError> { let mut result: Ipv6Extensions = Default::default(); let mut rest = slice; let mut next_header = start_ip_number; 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(); result.hop_by_hop_options = Some(slice.to_header()); } loop { match next_header { IPV6_HOP_BY_HOP => { return Err(Content(HopByHopNotAtStart)); } IPV6_DEST_OPTIONS => { if let Some(ref mut routing) = result.routing { // if the routing header is already present // this this a "final destination options" header if routing.final_destination_options.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_header, rest)); } else { let slice = Ipv6RawExtHeaderSlice::from_slice(rest) .map_err(|err| Len(err.add_offset(slice.len() - rest.len())))?; rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); routing.final_destination_options = Some(slice.to_header()); } } else if result.destination_options.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_header, rest)); } else { let slice = Ipv6RawExtHeaderSlice::from_slice(rest) .map_err(|err| Len(err.add_offset(slice.len() - rest.len())))?; rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.destination_options = Some(slice.to_header()); } } IPV6_ROUTE => { if result.routing.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_header, rest)); } else { let slice = Ipv6RawExtHeaderSlice::from_slice(rest) .map_err(|err| Len(err.add_offset(slice.len() - rest.len())))?; rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.routing = Some(Ipv6RoutingExtensions { routing: slice.to_header(), final_destination_options: None, }); } } IPV6_FRAG => { if result.fragment.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_header, rest)); } else { let slice = Ipv6FragmentHeaderSlice::from_slice(rest) .map_err(|err| Len(err.add_offset(slice.len() - rest.len())))?; rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.fragment = Some(slice.to_header()); } } AUTH => { if result.auth.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_header, rest)); } else { let slice = IpAuthHeaderSlice::from_slice(rest).map_err(|err| { use err::ip_auth::HeaderSliceError as I; use err::ipv6_exts::HeaderError as O; match err { I::Len(err) => Len(err.add_offset(slice.len() - rest.len())), I::Content(err) => Content(O::IpAuth(err)), } })?; rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.auth = Some(slice.to_header()); } } _ => { // done parsing, the next header is not a known header extension return Ok((result, next_header, rest)); } } } //should not be hit } /// Reads as many extension headers as possible from the slice until a non IPv6 extension /// header or an error gets encountered. /// /// This function differs from [`Ipv6Extensions::from_slice`] in that it returns the successfully /// parsed parts together with the error. While [`Ipv6Extensions::from_slice`] only returns an /// error. /// /// Note that this function (same as [`Ipv6Extensions::from_slice`]) will stop parsing as soon /// as more headers then can be stored in [`Ipv6Extensions`] are encountered. E.g. if there is /// more then one "auth" header the function returns as soon as the second "auth" header is /// encountered. pub fn from_slice_lax( start_ip_number: IpNumber, slice: &[u8], ) -> ( Ipv6Extensions, IpNumber, &[u8], Option<(err::ipv6_exts::HeaderSliceError, err::Layer)>, ) { let mut result: Ipv6Extensions = Default::default(); let mut rest = slice; let mut next_header = start_ip_number; 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(); result.hop_by_hop_options = Some(slice.to_header()); } Err(error) => { return ( result, next_header, rest, Some((Len(error), Layer::Ipv6HopByHopHeader)), ); } } } loop { match next_header { IPV6_HOP_BY_HOP => { return ( result, next_header, rest, Some((Content(HopByHopNotAtStart), Layer::Ipv6HopByHopHeader)), ); } IPV6_DEST_OPTIONS => { if let Some(ref mut routing) = result.routing { // if the routing header is already present // this this a "final destination options" header if routing.final_destination_options.is_some() { // more then one header of this type found -> abort parsing return (result, next_header, rest, None); } else { match Ipv6RawExtHeaderSlice::from_slice(rest) { Ok(slice) => { rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); routing.final_destination_options = Some(slice.to_header()); } Err(err) => { return ( result, next_header, rest, Some(( Len(err.add_offset(slice.len() - rest.len())), Layer::Ipv6DestOptionsHeader, )), ); } } } } else if result.destination_options.is_some() { // more then one header of this type found -> abort parsing return (result, next_header, rest, None); } else { match Ipv6RawExtHeaderSlice::from_slice(rest) { Ok(slice) => { rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.destination_options = Some(slice.to_header()); } Err(err) => { return ( result, next_header, rest, Some(( Len(err.add_offset(slice.len() - rest.len())), Layer::Ipv6DestOptionsHeader, )), ); } } } } IPV6_ROUTE => { if result.routing.is_some() { // more then one header of this type found -> abort parsing return (result, next_header, rest, None); } else { match Ipv6RawExtHeaderSlice::from_slice(rest) { Ok(slice) => { rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.routing = Some(Ipv6RoutingExtensions { routing: slice.to_header(), final_destination_options: None, }); } Err(err) => { return ( result, next_header, rest, Some(( Len(err.add_offset(slice.len() - rest.len())), Layer::Ipv6RouteHeader, )), ); } } } } IPV6_FRAG => { if result.fragment.is_some() { // more then one header of this type found -> abort parsing return (result, next_header, rest, None); } else { match Ipv6FragmentHeaderSlice::from_slice(rest) { Ok(slice) => { rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.fragment = Some(slice.to_header()); } Err(err) => { return ( result, next_header, rest, Some(( Len(err.add_offset(slice.len() - rest.len())), Layer::Ipv6FragHeader, )), ); } } } } AUTH => { if result.auth.is_some() { // more then one header of this type found -> abort parsing return (result, next_header, rest, None); } else { match IpAuthHeaderSlice::from_slice(rest) { Ok(slice) => { rest = &rest[slice.slice().len()..]; next_header = slice.next_header(); result.auth = Some(slice.to_header()); } Err(err) => { use err::ip_auth::HeaderSliceError as I; use err::ipv6_exts::HeaderError as O; return ( result, next_header, rest, Some(( match err { I::Len(err) => { Len(err.add_offset(slice.len() - rest.len())) } I::Content(err) => Content(O::IpAuth(err)), }, Layer::IpAuthHeader, )), ); } } } } _ => { // done parsing, the next header is not a known header extension return (result, next_header, rest, None); } } } //should not be hit } /// Reads as many extension headers as possible from the reader and returns the found ipv6 /// extension headers and the next header ip number. /// /// If no extension headers are present an unfilled struct and the original `first_header` /// ip number is returned. /// /// Note that this function can only handle ipv6 extensions if each extension header does /// occur at most once, except for destination options headers which are allowed to /// exist once in front of a routing header and once after a routing header. /// /// In case that more extension headers then can fit into a `Ipv6Extensions` struct are /// encountered, the parsing is stoped at the point where the data would no longer fit into /// the struct. In such a scenario a struct with the data that could be parsed is returned /// together with the next header ip number that identfies which header could be read next. /// /// It is in the responsibility of the caller to handle a scenario like this. /// /// The reason that no error is generated, is that even though according to RFC 8200, packets /// "should" not contain more then one occurence of an extension header, the RFC also specifies /// that "IPv6 nodes must accept and attempt to process extension headers in any order and /// occurring any number of times in the same packet". So packets with multiple headers "should" /// not exist, but are still valid IPv6 packets. As such this function does not generate a /// parsing error, as it is not an invalid packet, but if packets like these are encountered /// the user of this function has to themself decide how to handle packets like these. /// /// The only exception is if an hop by hop header is located somewhere else then directly at /// the start. In this case an `ReadError::Ipv6HopByHopHeaderNotAtStart` error is generated as /// the hop by hop header is required to be located directly after the IPv6 header according /// to RFC 8200. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn read( reader: &mut T, start_ip_number: IpNumber, ) -> Result<(Ipv6Extensions, IpNumber), err::ipv6_exts::HeaderReadError> { let mut result: Ipv6Extensions = Default::default(); let mut next_protocol = start_ip_number; use err::ipv6_exts::{HeaderError::*, HeaderReadError::*}; use ip_number::*; // the hop by hop header is required to occur directly after the ipv6 header if IPV6_HOP_BY_HOP == next_protocol { let header = Ipv6RawExtHeader::read(reader).map_err(Io)?; next_protocol = header.next_header; result.hop_by_hop_options = Some(header); } loop { match next_protocol { IPV6_HOP_BY_HOP => { return Err(Content(HopByHopNotAtStart)); } IPV6_DEST_OPTIONS => { if let Some(ref mut routing) = result.routing { // if the routing header is already present // asume this is a "final destination options" header if routing.final_destination_options.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6RawExtHeader::read(reader).map_err(Io)?; next_protocol = header.next_header; routing.final_destination_options = Some(header); } } else if result.destination_options.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6RawExtHeader::read(reader).map_err(Io)?; next_protocol = header.next_header; result.destination_options = Some(header); } } IPV6_ROUTE => { if result.routing.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6RawExtHeader::read(reader).map_err(Io)?; next_protocol = header.next_header; result.routing = Some(Ipv6RoutingExtensions { routing: header, final_destination_options: None, }); } } IPV6_FRAG => { if result.fragment.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6FragmentHeader::read(reader).map_err(Io)?; next_protocol = header.next_header; result.fragment = Some(header); } } AUTH => { if result.auth.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = IpAuthHeader::read(reader).map_err(|err| { use err::ip_auth::HeaderReadError as I; match err { I::Io(err) => Io(err), I::Content(err) => Content(IpAuth(err)), } })?; next_protocol = header.next_header; result.auth = Some(header); } } _ => { // done parsing, the next header is not a known header extension return Ok((result, next_protocol)); } } } //should not be hit } /// Reads as many extension headers as possible from the limited reader and returns the found ipv6 /// extension headers and the next header ip number. /// /// If no extension headers are present an unfilled struct and the original `first_header` /// ip number is returned. /// /// Note that this function can only handle ipv6 extensions if each extension header does /// occur at most once, except for destination options headers which are allowed to /// exist once in front of a routing header and once after a routing header. /// /// In case that more extension headers then can fit into a `Ipv6Extensions` struct are /// encountered, the parsing is stoped at the point where the data would no longer fit into /// the struct. In such a scenario a struct with the data that could be parsed is returned /// together with the next header ip number that identfies which header could be read next. /// /// It is in the responsibility of the caller to handle a scenario like this. /// /// The reason that no error is generated, is that even though according to RFC 8200, packets /// "should" not contain more then one occurence of an extension header, the RFC also specifies /// that "IPv6 nodes must accept and attempt to process extension headers in any order and /// occurring any number of times in the same packet". So packets with multiple headers "should" /// not exist, but are still valid IPv6 packets. As such this function does not generate a /// parsing error, as it is not an invalid packet, but if packets like these are encountered /// the user of this function has to themself decide how to handle packets like these. /// /// The only exception is if an hop by hop header is located somewhere else then directly at /// the start. In this case an `ReadError::Ipv6HopByHopHeaderNotAtStart` error is generated as /// the hop by hop header is required to be located directly after the IPv6 header according /// to RFC 8200. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn read_limited( reader: &mut crate::io::LimitedReader, start_ip_number: IpNumber, ) -> Result<(Ipv6Extensions, IpNumber), HeaderLimitedReadError> { use ip_number::*; use HeaderError::*; use HeaderLimitedReadError::*; fn map_limited_err(err: err::io::LimitedReadError) -> HeaderLimitedReadError { use crate::err::io::LimitedReadError as I; match err { I::Io(err) => Io(err), I::Len(err) => Len(err), } } // start decoding let mut result: Ipv6Extensions = Default::default(); let mut next_protocol = start_ip_number; // the hop by hop header is required to occur directly after the ipv6 header if IPV6_HOP_BY_HOP == next_protocol { let header = Ipv6RawExtHeader::read_limited(reader).map_err(map_limited_err)?; next_protocol = header.next_header; result.hop_by_hop_options = Some(header); } loop { match next_protocol { IPV6_HOP_BY_HOP => { return Err(Content(HopByHopNotAtStart)); } IPV6_DEST_OPTIONS => { if let Some(ref mut routing) = result.routing { // if the routing header is already present // asume this is a "final destination options" header if routing.final_destination_options.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6RawExtHeader::read_limited(reader).map_err(map_limited_err)?; next_protocol = header.next_header; routing.final_destination_options = Some(header); } } else if result.destination_options.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6RawExtHeader::read_limited(reader).map_err(map_limited_err)?; next_protocol = header.next_header; result.destination_options = Some(header); } } IPV6_ROUTE => { if result.routing.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6RawExtHeader::read_limited(reader).map_err(map_limited_err)?; next_protocol = header.next_header; result.routing = Some(Ipv6RoutingExtensions { routing: header, final_destination_options: None, }); } } IPV6_FRAG => { if result.fragment.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = Ipv6FragmentHeader::read_limited(reader).map_err(map_limited_err)?; next_protocol = header.next_header; result.fragment = Some(header); } } AUTH => { if result.auth.is_some() { // more then one header of this type found -> abort parsing return Ok((result, next_protocol)); } else { let header = IpAuthHeader::read_limited(reader).map_err(|err| { use err::ip_auth::HeaderLimitedReadError as I; match err { I::Io(err) => Io(err), I::Len(err) => Len(err), I::Content(err) => Content(IpAuth(err)), } })?; next_protocol = header.next_header; result.auth = Some(header); } } _ => { // done parsing, the next header is not a known header extension return Ok((result, next_protocol)); } } } //should not be hit } /// Writes the given headers to a writer based on the order defined in /// the next_header fields of the headers and the first header_id /// passed to this function. /// /// It is required that all next header are correctly set in the headers /// and no other ipv6 header extensions follow this header. If this is not /// the case an [`err::ipv6_exts::HeaderWriteError::Content`] error is /// returned. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn write( &self, writer: &mut T, first_header: IpNumber, ) -> Result<(), err::ipv6_exts::HeaderWriteError> { use err::ipv6_exts::ExtsWalkError::*; use err::ipv6_exts::HeaderWriteError::*; use ip_number::*; /// Struct flagging if a header needs to be written. struct NeedsWrite { pub hop_by_hop_options: bool, pub destination_options: bool, pub routing: bool, pub fragment: bool, pub auth: bool, pub final_destination_options: bool, } let mut needs_write = NeedsWrite { hop_by_hop_options: self.hop_by_hop_options.is_some(), destination_options: self.destination_options.is_some(), routing: self.routing.is_some(), fragment: self.fragment.is_some(), auth: self.auth.is_some(), final_destination_options: if let Some(ref routing) = self.routing { routing.final_destination_options.is_some() } else { false }, }; let mut next_header = first_header; let mut route_written = false; // check if hop by hop header should be written first if IPV6_HOP_BY_HOP == next_header { let header = &self.hop_by_hop_options.as_ref().unwrap(); header.write(writer).map_err(Io)?; next_header = header.next_header; needs_write.hop_by_hop_options = false; } loop { match next_header { IPV6_HOP_BY_HOP => { // Only trigger a "hop by hop not at start" error // if we actually still have to write a hop by hop header. // // The ip number for hop by hop is 0, which could be used // as a placeholder by user and later replaced. So let's // not be overzealous and allow a next header with hop // by hop if it is not part of this extensions struct. if needs_write.hop_by_hop_options { // the hop by hop header is only allowed at the start return Err(Content(HopByHopNotAtStart)); } else { break; } } IPV6_DEST_OPTIONS => { // the destination options are allowed to be written twice // once before a routing header and once after. if route_written { if needs_write.final_destination_options { let header = &self .routing .as_ref() .unwrap() .final_destination_options .as_ref() .unwrap(); header.write(writer).map_err(Io)?; next_header = header.next_header; needs_write.final_destination_options = false; } else { break; } } else if needs_write.destination_options { let header = &self.destination_options.as_ref().unwrap(); header.write(writer).map_err(Io)?; next_header = header.next_header; needs_write.destination_options = false; } else { break; } } IPV6_ROUTE => { if needs_write.routing { let header = &self.routing.as_ref().unwrap().routing; header.write(writer).map_err(Io)?; next_header = header.next_header; needs_write.routing = false; // for destination options route_written = true; } else { break; } } IPV6_FRAG => { if needs_write.fragment { let header = &self.fragment.as_ref().unwrap(); header.write(writer).map_err(Io)?; next_header = header.next_header; needs_write.fragment = false; } else { break; } } AUTH => { if needs_write.auth { let header = &self.auth.as_ref().unwrap(); header.write(writer).map_err(Io)?; next_header = header.next_header; needs_write.auth = false; } else { break; } } _ => { // reached an unknown next_header id, proceed to check if everything was written break; } } } // check that all header have been written if needs_write.hop_by_hop_options { Err(Content(ExtNotReferenced { missing_ext: IpNumber::IPV6_HEADER_HOP_BY_HOP, })) } else if needs_write.destination_options { Err(Content(ExtNotReferenced { missing_ext: IpNumber::IPV6_DESTINATION_OPTIONS, })) } else if needs_write.routing { Err(Content(ExtNotReferenced { missing_ext: IpNumber::IPV6_ROUTE_HEADER, })) } else if needs_write.fragment { Err(Content(ExtNotReferenced { missing_ext: IpNumber::IPV6_FRAGMENTATION_HEADER, })) } else if needs_write.auth { Err(Content(ExtNotReferenced { missing_ext: IpNumber::AUTHENTICATION_HEADER, })) } else if needs_write.final_destination_options { Err(Content(ExtNotReferenced { missing_ext: IpNumber::IPV6_DESTINATION_OPTIONS, })) } else { Ok(()) } } /// Length of the all present headers in bytes. pub fn header_len(&self) -> usize { let mut result = 0; if let Some(ref header) = self.hop_by_hop_options { result += header.header_len(); } if let Some(ref header) = self.destination_options { result += header.header_len(); } if let Some(ref header) = self.routing { result += header.routing.header_len(); if let Some(ref header) = header.final_destination_options { result += header.header_len(); } } if let Some(ref header) = self.fragment { result += header.header_len(); } if let Some(ref header) = self.auth { result += header.header_len(); } result } /// Sets all the next_header fields of the headers based on the adviced default order /// with the given protocol number as last "next header" value. The return value is the protocol /// number of the first existing extension header that should be entered in the ipv6 header as /// next_header. /// /// If no extension headers are present the value of the argument is returned. pub fn set_next_headers(&mut self, last_protocol_number: IpNumber) -> IpNumber { use ip_number::*; let mut next = last_protocol_number; // go through the proposed order of extension headers from // RFC 8200 backwards. The header order defined in RFC8200 is: // // * IPv6 header // * Hop-by-Hop Options header // * Destination Options header // * Routing header // * Fragment header // * Authentication header // * Encapsulating Security Payload header // * Destination Options header // * Upper-Layer header // if let Some(ref mut routing) = self.routing { if let Some(ref mut header) = routing.final_destination_options { header.next_header = next; next = IPV6_DEST_OPTIONS; } } if let Some(ref mut header) = self.auth { header.next_header = next; next = AUTH; } if let Some(ref mut header) = self.fragment { header.next_header = next; next = IPV6_FRAG; } if let Some(ref mut routing) = self.routing { routing.routing.next_header = next; next = IPV6_ROUTE; } if let Some(ref mut header) = self.destination_options { header.next_header = next; next = IPV6_DEST_OPTIONS; } if let Some(ref mut header) = self.hop_by_hop_options { header.next_header = next; next = IPV6_HOP_BY_HOP; } next } /// Return next header based on the extension headers and /// the first ip protocol number. pub fn next_header(&self, first_next_header: IpNumber) -> Result { use ip_number::*; use ExtsWalkError::*; /// Struct flagging if a header needs to be referenced. struct OutstandingRef { pub hop_by_hop_options: bool, pub destination_options: bool, pub routing: bool, pub fragment: bool, pub auth: bool, pub final_destination_options: bool, } let mut outstanding_refs = OutstandingRef { hop_by_hop_options: self.hop_by_hop_options.is_some(), destination_options: self.destination_options.is_some(), routing: self.routing.is_some(), fragment: self.fragment.is_some(), auth: self.auth.is_some(), final_destination_options: if let Some(ref routing) = self.routing { routing.final_destination_options.is_some() } else { false }, }; let mut next = first_next_header; let mut route_refed = false; // check if hop by hop header should be written first if IPV6_HOP_BY_HOP == next { if let Some(ref header) = self.hop_by_hop_options { next = header.next_header; outstanding_refs.hop_by_hop_options = false; } } loop { match next { IPV6_HOP_BY_HOP => { // Only trigger a "hop by hop not at start" error // if we actually still have to write a hop by hop header. // // The ip number for hop by hop is 0, which could be used // as a placeholder by user and later replaced. So let's // not be overzealous and allow a next header with hop // by hop if it is not part of this extensions struct. if outstanding_refs.hop_by_hop_options { // the hop by hop header is only allowed at the start return Err(HopByHopNotAtStart); } else { break; } } IPV6_DEST_OPTIONS => { // the destination options are allowed to be written twice // once before a routing header and once after. if route_refed { if outstanding_refs.final_destination_options { let header = &self .routing .as_ref() .unwrap() .final_destination_options .as_ref() .unwrap(); next = header.next_header; outstanding_refs.final_destination_options = false; } else { break; } } else if outstanding_refs.destination_options { let header = &self.destination_options.as_ref().unwrap(); next = header.next_header; outstanding_refs.destination_options = false; } else { break; } } IPV6_ROUTE => { if outstanding_refs.routing { let header = &self.routing.as_ref().unwrap().routing; next = header.next_header; outstanding_refs.routing = false; // for destination options route_refed = true; } else { break; } } IPV6_FRAG => { if outstanding_refs.fragment { let header = &self.fragment.as_ref().unwrap(); next = header.next_header; outstanding_refs.fragment = false; } else { break; } } AUTH => { if outstanding_refs.auth { let header = &self.auth.as_ref().unwrap(); next = header.next_header; outstanding_refs.auth = false; } else { break; } } _ => break, } } // assume all done if outstanding_refs.hop_by_hop_options { return Err(ExtNotReferenced { missing_ext: IpNumber::IPV6_HEADER_HOP_BY_HOP, }); } if outstanding_refs.destination_options { return Err(ExtNotReferenced { missing_ext: IpNumber::IPV6_DESTINATION_OPTIONS, }); } if outstanding_refs.routing { return Err(ExtNotReferenced { missing_ext: IpNumber::IPV6_ROUTE_HEADER, }); } if outstanding_refs.fragment { return Err(ExtNotReferenced { missing_ext: IpNumber::IPV6_FRAGMENTATION_HEADER, }); } if outstanding_refs.auth { return Err(ExtNotReferenced { missing_ext: IpNumber::AUTHENTICATION_HEADER, }); } if outstanding_refs.final_destination_options { return Err(ExtNotReferenced { missing_ext: IpNumber::IPV6_DESTINATION_OPTIONS, }); } Ok(next) } /// 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 { if let Some(frag) = self.fragment.as_ref() { frag.is_fragmenting_payload() } else { false } } /// Returns true if no IPv6 extension header is present (all fields `None`). #[inline] pub fn is_empty(&self) -> bool { self.hop_by_hop_options.is_none() && self.destination_options.is_none() && self.routing.is_none() && self.fragment.is_none() && self.auth.is_none() } } #[cfg(test)] pub mod ipv6_exts_test_helpers { use super::*; use crate::ip_number::*; use alloc::vec::Vec; /// IP numbers that are assigned ipv6 header extensions. pub const EXTENSION_KNOWN_IP_NUMBERS: [IpNumber; 5] = [ AUTH, IPV6_DEST_OPTIONS, IPV6_HOP_BY_HOP, IPV6_FRAG, IPV6_ROUTE, ]; /// Helper struct that generates test data with dummy /// extension header data. pub struct ExtensionTestPayload { pub ip_numbers: Vec, pub lengths: Vec, pub data: Vec, } impl ExtensionTestPayload { pub fn new(ip_numbers: &[IpNumber], header_sizes: &[u8]) -> ExtensionTestPayload { assert!(ip_numbers.len() > 1); assert!(header_sizes.len() > 0); let mut result = ExtensionTestPayload { ip_numbers: ip_numbers.to_vec(), lengths: Vec::with_capacity(ip_numbers.len() - 1), data: Vec::with_capacity((ip_numbers.len() - 1) * (0xff * 8 + 8)), }; for i in 0..ip_numbers.len() - 1 { result.add_payload( ip_numbers[i], ip_numbers[i + 1], header_sizes[i % header_sizes.len()], ) } result } pub fn slice(&self) -> &[u8] { &self.data } fn add_payload(&mut self, ip_number: IpNumber, next_header: IpNumber, header_ext_len: u8) { match ip_number { IPV6_HOP_BY_HOP | IPV6_ROUTE | IPV6_DEST_OPTIONS => { // insert next header & size let mut raw: [u8; 0xff * 8 + 8] = [0; 0xff * 8 + 8]; raw[0] = next_header.0; raw[1] = header_ext_len; // insert payload self.data .extend_from_slice(&raw[..8 + usize::from(header_ext_len) * 8]); self.lengths.push(8 + usize::from(header_ext_len) * 8); } IPV6_FRAG => { // generate payload let mut raw: [u8; 8] = [0; 8]; raw[0] = next_header.0; raw[1] = 0; // insert payload self.data.extend_from_slice(&raw[..8]); self.lengths.push(8); } AUTH => { let mut raw: [u8; 0xff * 4 + 8] = [0; 0xff * 4 + 8]; raw[0] = next_header.0; // authentfication header len is defined as // '32-bit words (4-byteunits), minus "2"' let len = if header_ext_len > 0 { raw[1] = header_ext_len; usize::from(header_ext_len) * 4 } else { // auth has a minimum size of 1 raw[1] = 1; 4 } + 8; self.data.extend_from_slice(&raw[..len]); self.lengths.push(len); } _ => unreachable!(), } } /// Returns true of the payload will trigger a "hop by hop not /// at start" error which is not ignored because of an early /// parsing abort. pub fn exts_hop_by_hop_error(&self) -> bool { struct ReadState { dest_opt: bool, routing: bool, final_dest_opt: bool, frag: bool, auth: bool, } // state if a header type has already been read let mut read = ReadState { dest_opt: false, routing: false, final_dest_opt: false, frag: false, auth: false, }; for i in 0..self.ip_numbers.len() { match self.ip_numbers[i] { IPV6_HOP_BY_HOP => { if i != 0 { return true; } } IPV6_ROUTE => { if read.routing { return false; } else { read.routing = true; } } IPV6_DEST_OPTIONS => { // check the kind of destination options (aka is it before or after the routing header) if read.routing { // final dest opt if read.final_dest_opt { return false; } else { read.final_dest_opt = true; } } else { // dst opt if read.dest_opt { return false; } else { read.dest_opt = true; } } } IPV6_FRAG => { if read.frag { return false; } else { read.frag = true; } } AUTH => { if read.auth { return false; } else { read.auth = true; } } _ => return false, } } return false; } /// Checks the if the extensions match the expected values based /// on this test payload. pub fn assert_extensions( &self, exts: &Ipv6Extensions, ) -> (usize, Option, IpNumber) { struct ReadState { hop_by_hop: bool, dest_opt: bool, routing: bool, final_dest_opt: bool, frag: bool, auth: bool, } // state if a header type has already been read let mut read = ReadState { hop_by_hop: false, dest_opt: false, routing: false, final_dest_opt: false, frag: false, auth: false, }; let mut slice = &self.data[..]; let mut last_decoded = None; let mut post_header = self.ip_numbers[0]; for i in 0..self.ip_numbers.len() - 1 { let mut stop = false; match self.ip_numbers[i] { IPV6_HOP_BY_HOP => { assert!(false == read.hop_by_hop); let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); assert_eq!(&header, exts.hop_by_hop_options.as_ref().unwrap()); slice = rest; read.hop_by_hop = true; last_decoded = Some(IPV6_HOP_BY_HOP); } IPV6_ROUTE => { if read.routing { stop = true; } else { let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); assert_eq!(&header, &exts.routing.as_ref().unwrap().routing); slice = rest; read.routing = true; last_decoded = Some(IPV6_ROUTE); } } IPV6_DEST_OPTIONS => { // check the kind of destination options (aka is it before or after the routing header) if read.routing { // final dest opt if read.final_dest_opt { stop = true; } else { let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); assert_eq!( &header, exts.routing .as_ref() .unwrap() .final_destination_options .as_ref() .unwrap() ); slice = rest; read.final_dest_opt = true; last_decoded = Some(IPV6_DEST_OPTIONS); } } else { // dst opt if read.dest_opt { stop = true; } else { let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); assert_eq!(&header, exts.destination_options.as_ref().unwrap()); slice = rest; read.dest_opt = true; last_decoded = Some(IPV6_DEST_OPTIONS); } } } IPV6_FRAG => { if read.frag { // duplicate header -> stop stop = true; } else { let (header, rest) = Ipv6FragmentHeader::from_slice(slice).unwrap(); assert_eq!(&header, exts.fragment.as_ref().unwrap()); slice = rest; read.frag = true; last_decoded = Some(IPV6_FRAG); } } AUTH => { if read.auth { // duplicate header -> stop stop = true; } else { let (header, rest) = IpAuthHeader::from_slice(slice).unwrap(); assert_eq!(&header, exts.auth.as_ref().unwrap()); slice = rest; read.auth = true; last_decoded = Some(AUTH); } } _ => { // non extension header -> stop stop = true; } } if stop { post_header = self.ip_numbers[i]; break; } else { post_header = self.ip_numbers[i + 1]; } } // check the non parsed headers are not present if false == read.hop_by_hop { assert!(exts.hop_by_hop_options.is_none()); } if false == read.dest_opt { assert!(exts.destination_options.is_none()); } if false == read.routing { assert!(exts.routing.is_none()); } else { if false == read.final_dest_opt { assert!(exts .routing .as_ref() .unwrap() .final_destination_options .is_none()); } } if false == read.frag { assert!(exts.fragment.is_none()); } if false == read.auth { assert!(exts.auth.is_none()); } (self.data.len() - slice.len(), last_decoded, post_header) } /// Return the expected lax from slice result and ipnumber which caused /// an error. pub fn lax_extensions_for_len( &self, limiting_len: usize, ) -> (Ipv6Extensions, IpNumber, &[u8], Option) { // state if a header type has already been read let mut exts: Ipv6Extensions = Default::default(); let mut post_header = *self.ip_numbers.first().unwrap(); let mut slice = &self.data[..]; for i in 0..self.ip_numbers.len() - 1 { // check if the limiting size gets hit if self.slice().len() - slice.len() + self.lengths[i] > limiting_len { return ( exts, self.ip_numbers[i], &self.slice()[self.slice().len() - slice.len()..limiting_len], Some(self.ip_numbers[i]), ); } let mut stop = false; match self.ip_numbers[i] { IPV6_HOP_BY_HOP => { assert!(exts.hop_by_hop_options.is_none()); let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); exts.hop_by_hop_options = Some(header); slice = rest; } IPV6_ROUTE => { if exts.routing.is_some() { stop = true; } else { let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); exts.routing = Some(Ipv6RoutingExtensions { routing: header, final_destination_options: None, }); slice = rest; } } IPV6_DEST_OPTIONS => { // check the kind of destination options (aka is it before or after the routing header) if let Some(routing) = exts.routing.as_mut() { // final dest opt if routing.final_destination_options.is_some() { stop = true; } else { let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); routing.final_destination_options = Some(header); slice = rest; } } else { // dst opt if exts.destination_options.is_some() { stop = true; } else { let (header, rest) = Ipv6RawExtHeader::from_slice(slice).unwrap(); exts.destination_options = Some(header); slice = rest; } } } IPV6_FRAG => { if exts.fragment.is_some() { // duplicate header -> stop stop = true; } else { let (header, rest) = Ipv6FragmentHeader::from_slice(slice).unwrap(); exts.fragment = Some(header); slice = rest; } } AUTH => { if exts.auth.is_some() { // duplicate header -> stop stop = true; } else { let (header, rest) = IpAuthHeader::from_slice(slice).unwrap(); exts.auth = Some(header); slice = rest; } } _ => { // non extension header -> stop stop = true; } } if stop { post_header = self.ip_numbers[i]; break; } else { post_header = self.ip_numbers[i + 1]; } } ( exts, post_header, &self.slice()[self.slice().len() - slice.len()..limiting_len], None, ) } } /// extension header data. #[derive(Clone)] pub struct ExtensionTestHeaders { pub ip_numbers: Vec, pub data: Ipv6Extensions, } impl ExtensionTestHeaders { pub fn new(ip_numbers: &[IpNumber], header_sizes: &[u8]) -> ExtensionTestHeaders { assert!(ip_numbers.len() > 1); assert!(header_sizes.len() > 0); let mut result = ExtensionTestHeaders { ip_numbers: ip_numbers.to_vec(), data: Default::default(), }; for i in 0..ip_numbers.len() - 1 { let succ = result.add_payload( ip_numbers[i], ip_numbers[i + 1], header_sizes[i % header_sizes.len()], ); if false == succ { // write was not possible (duplicate) // reduce the list so the current ip number // is the final one result.ip_numbers.truncate(i + 1); break; } } result } pub fn introduce_missing_ref(&mut self, new_header: IpNumber) -> IpNumber { assert!(self.ip_numbers.len() >= 2); // set the next_header of the last extension header and return the id if self.ip_numbers.len() >= 3 { match self.ip_numbers[self.ip_numbers.len() - 3] { IPV6_HOP_BY_HOP => { self.data.hop_by_hop_options.as_mut().unwrap().next_header = new_header; } IPV6_DEST_OPTIONS => { if self.ip_numbers[..self.ip_numbers.len() - 3] .iter() .any(|&x| x == IPV6_ROUTE) { self.data .routing .as_mut() .unwrap() .final_destination_options .as_mut() .unwrap() .next_header = new_header; } else { self.data.destination_options.as_mut().unwrap().next_header = new_header; } } IPV6_ROUTE => { self.data.routing.as_mut().unwrap().routing.next_header = new_header; } IPV6_FRAG => { self.data.fragment.as_mut().unwrap().next_header = new_header; } AUTH => { self.data.auth.as_mut().unwrap().next_header = new_header; } _ => unreachable!(), } match self.ip_numbers[self.ip_numbers.len() - 2] { IPV6_HOP_BY_HOP => IpNumber::IPV6_HEADER_HOP_BY_HOP, IPV6_DEST_OPTIONS => IpNumber::IPV6_DESTINATION_OPTIONS, IPV6_ROUTE => IpNumber::IPV6_ROUTE_HEADER, IPV6_FRAG => IpNumber::IPV6_FRAGMENTATION_HEADER, AUTH => IpNumber::AUTHENTICATION_HEADER, _ => unreachable!(), } } else { // rewrite start number in case it is just one extension header let missing = self.ip_numbers[0]; self.ip_numbers[0] = new_header; match missing { IPV6_HOP_BY_HOP => IpNumber::IPV6_HEADER_HOP_BY_HOP, IPV6_DEST_OPTIONS => IpNumber::IPV6_DESTINATION_OPTIONS, IPV6_ROUTE => IpNumber::IPV6_ROUTE_HEADER, IPV6_FRAG => IpNumber::IPV6_FRAGMENTATION_HEADER, AUTH => IpNumber::AUTHENTICATION_HEADER, _ => unreachable!(), } } } fn add_payload( &mut self, ip_number: IpNumber, next_header: IpNumber, header_ext_len: u8, ) -> bool { match ip_number { IPV6_HOP_BY_HOP | IPV6_ROUTE | IPV6_DEST_OPTIONS => { use Ipv6RawExtHeader as R; let payload: [u8; R::MAX_PAYLOAD_LEN] = [0; R::MAX_PAYLOAD_LEN]; let len = usize::from(header_ext_len) * 8 + 6; let raw = Ipv6RawExtHeader::new_raw(next_header, &payload[..len]).unwrap(); match ip_number { IPV6_HOP_BY_HOP => { if self.data.hop_by_hop_options.is_none() { self.data.hop_by_hop_options = Some(raw); true } else { false } } IPV6_ROUTE => { if self.data.routing.is_none() { self.data.routing = Some(Ipv6RoutingExtensions { routing: raw, final_destination_options: None, }); true } else { false } } IPV6_DEST_OPTIONS => { if let Some(ref mut route) = self.data.routing { if route.final_destination_options.is_none() { route.final_destination_options = Some(raw); true } else { false } } else { // dest option if self.data.destination_options.is_none() { self.data.destination_options = Some(raw); true } else { false } } } _ => unreachable!(), } } IPV6_FRAG => { if self.data.fragment.is_none() { self.data.fragment = Some(Ipv6FragmentHeader::new( next_header, IpFragOffset::ZERO, true, 123, )); true } else { false } } AUTH => { if self.data.auth.is_none() { use IpAuthHeader as A; let mut len = usize::from(header_ext_len) * 4; if len > A::MAX_ICV_LEN { len = A::MAX_ICV_LEN; } let raw_icv: [u8; A::MAX_ICV_LEN] = [0; A::MAX_ICV_LEN]; self.data.auth = Some( IpAuthHeader::new(next_header, 123, 234, &raw_icv[..len]).unwrap(), ); true } else { false } } _ => unreachable!(), } } } } #[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 = Ipv6Extensions::from_slice(post_header, &some_data).unwrap(); assert_eq!(actual.0, Default::default()); assert_eq!(actual.1, post_header); 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.exts_hop_by_hop_error() { // a hop by hop header that is not at the start triggers an error assert_eq!( Ipv6Extensions::from_slice(ip_numbers[0], e.slice()).unwrap_err(), Content(HopByHopNotAtStart) ); } else { // normal read let (header, next, rest) = Ipv6Extensions::from_slice(ip_numbers[0], e.slice()).unwrap(); let (read_len, last_header, expected_post_header) = e.assert_extensions(&header); assert_eq!(next, expected_post_header); assert_eq!(rest, &e.slice()[read_len..]); // unexpected end of slice { let mut offset: usize = 0; for l in &e.lengths { if offset + l >= read_len { break; } offset += l; } assert_eq!( Ipv6Extensions::from_slice(ip_numbers[0], &e.slice()[..read_len - 1]).unwrap_err(), Len(err::LenError { required_len: read_len - offset, len: read_len - offset - 1, len_source: LenSource::Slice, layer: match last_header.unwrap() { 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 = Ipv6Extensions::from_slice(AUTH, &bytes).unwrap_err(); use err::ipv6_exts::HeaderError::IpAuth; use err::ip_auth::HeaderError::ZeroPayloadLen; assert_eq!(actual, Content(IpAuth(ZeroPayloadLen))); } } } 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 = Ipv6Extensions::from_slice_lax(post_header, &some_data); assert_eq!(actual.0, Default::default()); assert_eq!(actual.1, post_header); assert_eq!(actual.2, &some_data); assert!(actual.3.is_none()); } /// 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.exts_hop_by_hop_error() { // a hop by hop header that is not at the start triggers an error let actual = Ipv6Extensions::from_slice_lax(ip_numbers[0], e.slice()); assert_eq!(actual.3.unwrap(), (Content(HopByHopNotAtStart), Layer::Ipv6HopByHopHeader)); } else { // normal read let norm_actual = Ipv6Extensions::from_slice_lax(ip_numbers[0], e.slice()); let norm_expected = e.lax_extensions_for_len(e.slice().len()); assert_eq!(norm_actual.0, norm_expected.0); assert_eq!(norm_actual.1, norm_expected.1); assert_eq!(norm_actual.2, norm_expected.2); assert!(norm_actual.3.is_none()); // unexpected end of slice if norm_actual.0.header_len() > 0 { let norm_len = norm_actual.0.header_len(); let actual = Ipv6Extensions::from_slice_lax(ip_numbers[0], &e.slice()[..norm_len - 1]); let expected = e.lax_extensions_for_len(norm_len - 1); assert_eq!(actual.0, expected.0); assert_eq!(actual.1, expected.1); assert_eq!(actual.2, expected.2); let len_err = actual.3.unwrap().0.len_error().unwrap().clone(); assert_eq!(len_err.len, norm_len - 1 - expected.0.header_len()); assert_eq!(len_err.len_source, LenSource::Slice); assert_eq!( len_err.layer, match expected.3.unwrap() { AUTH => err::Layer::IpAuthHeader, IPV6_FRAG => err::Layer::Ipv6FragHeader, _ => err::Layer::Ipv6ExtHeader } ); assert_eq!(len_err.layer_start_offset, expected.0.header_len()); } } } // 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 = Ipv6Extensions::from_slice_lax(AUTH, &bytes); assert_eq!(0, actual.0.header_len()); assert_eq!(AUTH, actual.1); assert_eq!(&bytes[..], actual.2); use err::ipv6_exts::HeaderError::IpAuth; use err::ip_auth::HeaderError::ZeroPayloadLen; assert_eq!(actual.3.unwrap(), (Content(IpAuth(ZeroPayloadLen)), Layer::IpAuthHeader)); } } } proptest! { #[test] fn read( 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::*; use std::io::Cursor; // no extension headers filled { let mut cursor = Cursor::new(&[]); let actual = Ipv6Extensions::read(&mut cursor, post_header).unwrap(); assert_eq!(actual.0, Default::default()); assert_eq!(actual.1, post_header); assert_eq!(0, cursor.position()); } /// 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 ); let mut cursor = Cursor::new(e.slice()); if e.exts_hop_by_hop_error() { // a hop by hop header that is not at the start triggers an error assert_eq!( Ipv6Extensions::read(&mut cursor, ip_numbers[0]).unwrap_err().content_error().unwrap(), HopByHopNotAtStart ); } else { // normal read let (header, next) = Ipv6Extensions::read(&mut cursor, ip_numbers[0]).unwrap(); let (read_len, _, expected_post_header) = e.assert_extensions(&header); assert_eq!(next, expected_post_header); assert_eq!(cursor.position() as usize, read_len); // unexpected end of slice { let mut short_cursor = Cursor::new(&e.slice()[..read_len - 1]); assert!( Ipv6Extensions::read(&mut short_cursor, ip_numbers[0]) .unwrap_err() .io_error() .is_some() ); } } } // 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 mut cursor = Cursor::new(&bytes[..]); let actual = Ipv6Extensions::read(&mut cursor, AUTH).unwrap_err(); use err::ipv6_exts::HeaderError::IpAuth; use err::ip_auth::HeaderError::ZeroPayloadLen; assert_eq!(actual.content_error().unwrap(), IpAuth(ZeroPayloadLen)); } } } proptest! { #[test] fn read_limited( 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::*; use err::Layer; use std::io::Cursor; use crate::io::LimitedReader; // no extension headers filled { let mut reader = LimitedReader::new( Cursor::new(&[]), 0, LenSource::Slice, 0, Layer::Ipv6Header ); let actual = Ipv6Extensions::read_limited(&mut reader, post_header).unwrap(); assert_eq!(actual.0, Default::default()); assert_eq!(actual.1, post_header); assert_eq!(0, reader.read_len()); } /// 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 ); let mut reader = LimitedReader::new( Cursor::new(e.slice()), e.slice().len(), LenSource::Slice, 0, Layer::Ipv6Header ); if e.exts_hop_by_hop_error() { // a hop by hop header that is not at the start triggers an error assert_eq!( Ipv6Extensions::read_limited(&mut reader, ip_numbers[0]).unwrap_err().content().unwrap(), HopByHopNotAtStart ); } else { // normal read let (header, next) = Ipv6Extensions::read_limited(&mut reader, ip_numbers[0]).unwrap(); let (read_len, _, expected_post_header) = e.assert_extensions(&header); assert_eq!(next, expected_post_header); assert_eq!(reader.read_len() + reader.layer_offset(), read_len); // io error unexpected end { let mut short_reader = LimitedReader::new( Cursor::new(&e.slice()[..read_len - 1]), read_len, LenSource::Slice, 0, Layer::Ipv6Header ); assert!( Ipv6Extensions::read_limited(&mut short_reader, ip_numbers[0]) .unwrap_err() .io() .is_some() ); } // len error { let mut short_reader = LimitedReader::new( Cursor::new(e.slice()), read_len - 1, LenSource::Slice, 0, Layer::Ipv6Header ); assert!( Ipv6Extensions::read_limited(&mut short_reader, ip_numbers[0]) .unwrap_err() .len() .is_some() ); } } } // 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 mut reader = LimitedReader::new( Cursor::new(&bytes[..]), bytes.len(), LenSource::Slice, 0, Layer::Ipv6Header ); let actual = Ipv6Extensions::read_limited(&mut reader, AUTH).unwrap_err(); use err::ipv6_exts::HeaderError::IpAuth; use err::ip_auth::HeaderError::ZeroPayloadLen; assert_eq!(actual.content().unwrap(), IpAuth(ZeroPayloadLen)); } } } proptest! { #[test] fn write( 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) ) ) { // no extension headers filled { let exts : Ipv6Extensions = Default::default(); let mut buffer = Vec::new(); exts.write(&mut buffer, post_header).unwrap(); assert_eq!(0, buffer.len()); } /// Run a test with the given ip numbers fn run_test(ip_numbers: &[IpNumber], header_sizes: &[u8], post_header: IpNumber) { use std::io::Cursor; use crate::err::ipv6_exts::ExtsWalkError::*; // setup test header let e = ExtensionTestHeaders::new( ip_numbers, header_sizes ); if e.ip_numbers[1..e.ip_numbers.len()-1].iter().any(|&x| x == IPV6_HOP_BY_HOP) { // a hop by hop header that is not at the start triggers an error let mut writer = Vec::with_capacity(e.data.header_len()); assert_eq!( e.data.write(&mut writer, e.ip_numbers[0]).unwrap_err().content().unwrap(), &HopByHopNotAtStart ); } else { // normal write { let mut writer = Vec::with_capacity(e.data.header_len()); e.data.write(&mut writer, e.ip_numbers[0]).unwrap(); if *e.ip_numbers.last().unwrap() != IPV6_HOP_BY_HOP { // decoding if there will be no duplicate hop by hop error // will be triggered let (read, read_next, _) = Ipv6Extensions::from_slice( e.ip_numbers[0], &writer ).unwrap(); assert_eq!(e.data, read); assert_eq!(*e.ip_numbers.last().unwrap(), read_next); } } // write error { let mut buffer = Vec::with_capacity(e.data.header_len() - 1); buffer.resize(e.data.header_len() - 1, 0); let mut cursor = Cursor::new(&mut buffer[..]); let err = e.data.write( &mut cursor, e.ip_numbers[0] ).unwrap_err(); assert!(err.io().is_some()); } // missing reference (skip the last header) { use crate::err::ipv6_exts::ExtsWalkError::ExtNotReferenced; let mut missing_ref = e.clone(); let missing_ext = missing_ref.introduce_missing_ref(post_header); let mut writer = Vec::with_capacity(e.data.header_len()); let err = missing_ref.data.write( &mut writer, missing_ref.ip_numbers[0] ).unwrap_err(); assert_eq!( err.content().unwrap(), &ExtNotReferenced{ missing_ext } ); } } } // 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], post_header, ); for second_header in &EXTENSION_KNOWN_IP_NUMBERS { // double header parsing run_test( &[*first_header, *second_header, post_header], &[header_size], post_header, ); for third_header in &EXTENSION_KNOWN_IP_NUMBERS { // tripple header parsing run_test( &[*first_header, *second_header, *third_header, post_header], &[header_size], post_header, ); } } } } } proptest! { #[test] fn header_len( hop_by_hop_options in ipv6_raw_ext_any(), destination_options in ipv6_raw_ext_any(), routing in ipv6_raw_ext_any(), fragment in ipv6_fragment_any(), auth in ip_auth_any(), final_destination_options in ipv6_raw_ext_any(), ) { // None { let exts : Ipv6Extensions = Default::default(); assert_eq!(0, exts.header_len()); } // All filled { let exts = Ipv6Extensions{ hop_by_hop_options: Some(hop_by_hop_options.clone()), destination_options: Some(destination_options.clone()), routing: Some( Ipv6RoutingExtensions{ routing: routing.clone(), final_destination_options: Some(final_destination_options.clone()), } ), fragment: Some(fragment.clone()), auth: Some(auth.clone()), }; assert_eq!( exts.header_len(), ( hop_by_hop_options.header_len() + destination_options.header_len() + routing.header_len() + final_destination_options.header_len() + fragment.header_len() + auth.header_len() ) ); } // Routing without final destination options { let exts = Ipv6Extensions{ hop_by_hop_options: Some(hop_by_hop_options.clone()), destination_options: Some(destination_options.clone()), routing: Some( Ipv6RoutingExtensions{ routing: routing.clone(), final_destination_options: None, } ), fragment: Some(fragment.clone()), auth: Some(auth.clone()), }; assert_eq!( exts.header_len(), ( hop_by_hop_options.header_len() + destination_options.header_len() + routing.header_len() + fragment.header_len() + auth.header_len() ) ); } } } proptest! { #[test] fn set_next_headers( hop_by_hop_options in ipv6_raw_ext_any(), destination_options in ipv6_raw_ext_any(), routing in ipv6_raw_ext_any(), fragment in ipv6_fragment_any(), auth in ip_auth_any(), final_destination_options in ipv6_raw_ext_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) ), ) { // none filled { let mut exts : Ipv6Extensions = Default::default(); assert_eq!(post_header, exts.set_next_headers(post_header)); assert!(exts.hop_by_hop_options.is_none()); assert!(exts.destination_options.is_none()); assert!(exts.routing.is_none()); assert!(exts.fragment.is_none()); assert!(exts.auth.is_none()); } // all filled { let mut exts = Ipv6Extensions{ hop_by_hop_options: Some(hop_by_hop_options.clone()), destination_options: Some(destination_options.clone()), routing: Some( Ipv6RoutingExtensions{ routing: routing.clone(), final_destination_options: Some(final_destination_options.clone()), } ), fragment: Some(fragment.clone()), auth: Some(auth.clone()), }; assert_eq!(IPV6_HOP_BY_HOP, exts.set_next_headers(post_header)); assert_eq!(IPV6_DEST_OPTIONS, exts.hop_by_hop_options.as_ref().unwrap().next_header); assert_eq!(IPV6_ROUTE, exts.destination_options.as_ref().unwrap().next_header); assert_eq!(IPV6_FRAG, exts.routing.as_ref().unwrap().routing.next_header); assert_eq!(AUTH, exts.fragment.as_ref().unwrap().next_header); assert_eq!(IPV6_DEST_OPTIONS, exts.auth.as_ref().unwrap().next_header); assert_eq!(post_header, exts.routing.as_ref().unwrap().final_destination_options.as_ref().unwrap().next_header); } } } proptest! { #[test] fn next_header( 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) ),) { // test empty { let exts : Ipv6Extensions = Default::default(); assert_eq!(post_header, exts.next_header(post_header).unwrap()); } /// Run a test with the given ip numbers fn run_test(ip_numbers: &[IpNumber], header_sizes: &[u8], post_header: IpNumber) { // setup test header let e = ExtensionTestHeaders::new( ip_numbers, header_sizes ); if e.ip_numbers[1..e.ip_numbers.len()-1].iter().any(|&x| x == IPV6_HOP_BY_HOP) { // a hop by hop header that is not at the start triggers an error use crate::err::ipv6_exts::ExtsWalkError::HopByHopNotAtStart; assert_eq!( e.data.next_header(e.ip_numbers[0]).unwrap_err(), HopByHopNotAtStart ); } else { // normal header assert_eq!( *e.ip_numbers.last().unwrap(), e.data.next_header(e.ip_numbers[0]).unwrap() ); // missing reference (skip the last header) { use crate::err::ipv6_exts::ExtsWalkError::ExtNotReferenced; let mut missing_ref = e.clone(); let missing_ext = missing_ref.introduce_missing_ref(post_header); assert_eq!( missing_ref.data.next_header(missing_ref.ip_numbers[0]).unwrap_err(), ExtNotReferenced{ missing_ext } ); } } } // 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], post_header, ); for second_header in &EXTENSION_KNOWN_IP_NUMBERS { // double header parsing run_test( &[*first_header, *second_header, post_header], &[header_size], post_header, ); for third_header in &EXTENSION_KNOWN_IP_NUMBERS { // tripple header parsing run_test( &[*first_header, *second_header, *third_header, post_header], &[header_size], post_header, ); } } } } } #[test] fn is_fragmenting_payload() { // empty assert_eq!( false, Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: None, fragment: None, auth: None, } .is_fragmenting_payload() ); // non fragmenting frag header assert_eq!( false, Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: None, fragment: Some(Ipv6FragmentHeader::new( ip_number::UDP, IpFragOffset::ZERO, false, 0 )), auth: None, } .is_fragmenting_payload() ); // fragmenting frag header assert!(Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: None, fragment: Some(Ipv6FragmentHeader::new( ip_number::UDP, IpFragOffset::ZERO, true, 0 )), auth: None, } .is_fragmenting_payload()); } #[test] fn is_empty() { // empty assert!(Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: None, fragment: None, auth: None, } .is_empty()); // hop_by_hop_options assert_eq!( false, Ipv6Extensions { hop_by_hop_options: Some( Ipv6RawExtHeader::new_raw(ip_number::UDP, &[1, 2, 3, 4, 5, 6]).unwrap() ), destination_options: None, routing: None, fragment: None, auth: None, } .is_empty() ); // destination_options assert_eq!( false, Ipv6Extensions { hop_by_hop_options: None, destination_options: Some( Ipv6RawExtHeader::new_raw(ip_number::UDP, &[1, 2, 3, 4, 5, 6]).unwrap() ), routing: None, fragment: None, auth: None, } .is_empty() ); // routing assert_eq!( false, Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: Some(Ipv6RoutingExtensions { routing: Ipv6RawExtHeader::new_raw(ip_number::UDP, &[1, 2, 3, 4, 5, 6]) .unwrap(), final_destination_options: None, }), fragment: None, auth: None, } .is_empty() ); // fragment assert_eq!( false, Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: None, fragment: Some(Ipv6FragmentHeader::new( ip_number::UDP, IpFragOffset::ZERO, true, 0 )), auth: None, } .is_empty() ); // auth assert_eq!( false, Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: None, fragment: None, auth: Some(IpAuthHeader::new(ip_number::UDP, 0, 0, &[]).unwrap()), } .is_empty() ); } #[test] fn debug() { use alloc::format; let a: Ipv6Extensions = Default::default(); assert_eq!( &format!( "Ipv6Extensions {{ hop_by_hop_options: {:?}, destination_options: {:?}, routing: {:?}, fragment: {:?}, auth: {:?} }}", a.hop_by_hop_options, a.destination_options, a.routing, a.fragment, a.auth, ), &format!("{:?}", a) ); } #[test] fn clone_eq() { let a: Ipv6Extensions = Default::default(); assert_eq!(a, a.clone()); } #[test] fn default() { let a: Ipv6Extensions = Default::default(); assert_eq!(a.hop_by_hop_options, None); assert_eq!(a.destination_options, None); assert_eq!(a.routing, None); assert_eq!(a.fragment, None); assert_eq!(a.auth, None); } }