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