blob: afbd104dd1a7dfb2d7e4c8c0ce4211727ff9bc07 [file] [log] [blame]
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<Ipv6RawExtHeader>,
pub destination_options: Option<Ipv6RawExtHeader>,
pub routing: Option<Ipv6RoutingExtensions>,
pub fragment: Option<Ipv6FragmentHeader>,
pub auth: Option<IpAuthHeader>,
}
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<T: std::io::Read + std::io::Seek + Sized>(
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<T: std::io::Read + std::io::Seek + Sized>(
reader: &mut crate::io::LimitedReader<T>,
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<T: std::io::Write + Sized>(
&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<IpNumber, ExtsWalkError> {
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<IpNumber>,
pub lengths: Vec<usize>,
pub data: Vec<u8>,
}
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>, 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<IpNumber>) {
// 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<IpNumber>,
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::<u8>(),
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::<u8>(),
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::<u8>(),
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::<u8>(),
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::<u8>(),
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::<u8>(),
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);
}
}