blob: 13d431f8fd87b121ae4772069fa3d451abd1e4c0 [file] [log] [blame]
use crate::err::{ipv6::SliceError, Layer, LenError};
use crate::*;
/// Slice containing the IPv6 headers & payload.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Ipv6Slice<'a> {
pub(crate) header: Ipv6HeaderSlice<'a>,
pub(crate) exts: Ipv6ExtensionsSlice<'a>,
pub(crate) payload: IpPayloadSlice<'a>,
}
impl<'a> Ipv6Slice<'a> {
/// Separates and validates IPv6 headers (including extension headers)
/// in the given slice and determine the sub-slice containing the payload
/// of the IPv6 packet (based on the payload length value in the header).
///
/// Note that his function returns an [`crate::err::LenError`] if the given slice
/// contains less data then the `payload_len` field in the IPv6 header indicates
/// should be present.
///
/// If you want to ignore these kind of length errors based on the length
/// fields in the IP headers use [`Ipv6Slice::from_slice_lax`] instead.
pub fn from_slice(slice: &'a [u8]) -> Result<Ipv6Slice<'a>, SliceError> {
// try reading the header
let header = Ipv6HeaderSlice::from_slice(slice).map_err(|err| {
use crate::err::ipv6::HeaderSliceError::*;
match err {
Len(err) => SliceError::Len(err),
Content(err) => SliceError::Header(err),
}
})?;
// restrict slice by the length specified in the header
let (header_payload, len_source) =
if 0 == header.payload_length() && slice.len() > Ipv6Header::LEN {
// In case the payload_length is 0 assume that the entire
// rest of the slice is part of the packet until the jumbogram
// parameters can be parsed.
// TODO: Add payload length parsing from the jumbogram
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(Ipv6Header::LEN),
slice.len() - Ipv6Header::LEN,
)
},
LenSource::Slice,
)
} else {
let payload_len = usize::from(header.payload_length());
let expected_len = Ipv6Header::LEN + payload_len;
if slice.len() < expected_len {
return Err(SliceError::Len(LenError {
required_len: expected_len,
len: slice.len(),
len_source: LenSource::Slice,
layer: Layer::Ipv6Packet,
layer_start_offset: 0,
}));
} else {
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(Ipv6Header::LEN),
payload_len,
)
},
LenSource::Ipv6HeaderPayloadLen,
)
}
};
// parse extension headers
let (exts, payload_ip_number, payload) =
Ipv6ExtensionsSlice::from_slice(header.next_header(), header_payload).map_err(
|err| {
// modify length errors
use crate::err::ipv6_exts::HeaderSliceError::*;
match err {
Len(mut err) => {
err.len_source = LenSource::Ipv6HeaderPayloadLen;
err.layer_start_offset += Ipv6Header::LEN;
SliceError::Len(err)
}
Content(err) => SliceError::Exts(err),
}
},
)?;
let fragmented = exts.is_fragmenting_payload();
Ok(Ipv6Slice {
header,
exts,
payload: IpPayloadSlice {
ip_number: payload_ip_number,
fragmented,
len_source,
payload,
},
})
}
/// Seperate an IPv6 header (+ extensions) & the payload from the given slice with
/// less strict length checks (useful for cut off packet or for packets with
/// unset length fields).
///
/// If you want to only receive correct IpPayloads use [`crate::Ipv4Slice::from_slice`]
/// instead.
///
/// The main usecases for this functions are:
///
/// * Parsing packets that have been cut off. This is, for example, useful to
/// parse packets returned via ICMP as these usually only contain the start.
/// * Parsing packets where the `payload_length` (in the IPv6 header) has not
/// yet been set. This can be useful when parsing packets which have been
/// recorded in a layer before the length field was set (e.g. before the operating
/// system set the length fields).
///
/// # Differences to `from_slice`:
///
/// The main differences is that the function ignores inconsistent
/// `payload_length` values (in IPv6 headers). When these length values
/// in the IP header are inconsistant the length of the given slice is
/// used as a substitute.
///
/// You can check if the slice length was used as a substitude by checking
/// if the `len_source` value in the returned [`IpPayloadSlice`] is set to
/// [`LenSource::Slice`]. If a substitution was not needed `len_source`
/// is set to [`LenSource::Ipv6HeaderPayloadLen`].
///
/// # When is the slice length used as a fallback?
///
/// The slice length is used as a fallback/substitude if the `payload_length`
/// field in the IPv6 header is
///
/// * Bigger then the given slice (payload cannot fully be seperated).
/// * The value `0`.
pub fn from_slice_lax(slice: &'a [u8]) -> Result<Ipv6Slice<'a>, SliceError> {
// try reading the header
let header = Ipv6HeaderSlice::from_slice(slice).map_err(|err| {
use crate::err::ipv6::HeaderSliceError::*;
match err {
Len(err) => SliceError::Len(err),
Content(err) => SliceError::Header(err),
}
})?;
// restrict slice by the length specified in the header
let (header_payload, len_source) =
if 0 == header.payload_length() && slice.len() > Ipv6Header::LEN {
// In case the payload_length is 0 assume that the entire
// rest of the slice is part of the packet until the jumbogram
// parameters can be parsed.
// TODO: Add payload length parsing from the jumbogram
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(Ipv6Header::LEN),
slice.len() - Ipv6Header::LEN,
)
},
LenSource::Slice,
)
} else {
let payload_len = usize::from(header.payload_length());
let expected_len = Ipv6Header::LEN + payload_len;
if slice.len() < expected_len {
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(Ipv6Header::LEN),
slice.len() - Ipv6Header::LEN,
)
},
LenSource::Slice,
)
} else {
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(Ipv6Header::LEN),
payload_len,
)
},
LenSource::Ipv6HeaderPayloadLen,
)
}
};
// parse extension headers
let (exts, payload_ip_number, payload) =
Ipv6ExtensionsSlice::from_slice(header.next_header(), header_payload).map_err(
|err| {
// modify length errors
use crate::err::ipv6_exts::HeaderSliceError::*;
match err {
Len(mut err) => {
err.len_source = len_source;
err.layer_start_offset += Ipv6Header::LEN;
SliceError::Len(err)
}
Content(err) => SliceError::Exts(err),
}
},
)?;
let fragmented = exts.is_fragmenting_payload();
Ok(Ipv6Slice {
header,
exts,
payload: IpPayloadSlice {
ip_number: payload_ip_number,
fragmented,
len_source,
payload,
},
})
}
/// Returns a slice containing the IPv6 header.
#[inline]
pub fn header(&self) -> Ipv6HeaderSlice<'a> {
self.header
}
/// Returns a slice containing the IPv6 extension headers.
#[inline]
pub fn extensions(&self) -> &Ipv6ExtensionsSlice<'a> {
&self.exts
}
/// Returns a slice containing the data after the IPv6 header
/// and IPv6 extensions headers.
#[inline]
pub fn payload(&self) -> &IpPayloadSlice<'a> {
&self.payload
}
/// Returns true if the payload is flagged as being fragmented.
#[inline]
pub fn is_payload_fragmented(&self) -> bool {
self.payload.fragmented
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
ip_number::{AUTH, IGMP, UDP},
test_gens::*,
};
use alloc::{format, vec::Vec};
use proptest::prelude::*;
proptest! {
#[test]
fn debug_clone_eq(
ipv6_base in ipv6_any(),
auth_base in ip_auth_any()
) {
let mut auth = auth_base.clone();
auth.next_header = IGMP;
let payload: [u8;4] = [1,2,3,4];
let mut data = Vec::with_capacity(
ipv6_base.header_len() +
auth.header_len() +
payload.len()
);
let mut ipv6 = ipv6_base.clone();
ipv6.next_header = AUTH;
ipv6.payload_length = (auth.header_len() + payload.len()) as u16;
data.extend_from_slice(&ipv6.to_bytes());
data.extend_from_slice(&auth.to_bytes());
data.extend_from_slice(&payload);
// decode packet
let slice = Ipv6Slice::from_slice(&data).unwrap();
// check debug output
prop_assert_eq!(
format!("{:?}", slice),
format!(
"Ipv6Slice {{ header: {:?}, exts: {:?}, payload: {:?} }}",
slice.header(),
slice.extensions(),
slice.payload()
)
);
prop_assert_eq!(slice.clone(), slice);
}
}
proptest! {
#[test]
fn from_slice(
ipv6_base in ipv6_any(),
auth_base in ip_auth_any()
) {
let payload: [u8;6] = [1,2,3,4,5,6];
// build packets
let data_without_ext = {
let mut data = Vec::with_capacity(
ipv6_base.header_len() +
payload.len() +
4
);
let mut ipv6 = ipv6_base.clone();
ipv6.payload_length = (payload.len()) as u16;
ipv6.next_header = UDP;
data.extend_from_slice(&ipv6.to_bytes());
data.extend_from_slice(&payload);
data.extend_from_slice(&[0,0,0,0]);
data
};
let data_with_ext = {
let payload: [u8;6] = [1,2,3,4,5,6];
let mut data = Vec::with_capacity(
ipv6_base.header_len() +
auth_base.header_len() +
payload.len() +
4
);
let mut ipv6 = ipv6_base.clone();
ipv6.payload_length = (auth_base.header_len() + payload.len()) as u16;
ipv6.next_header = AUTH;
let mut auth = auth_base.clone();
auth.next_header = UDP;
data.extend_from_slice(&ipv6.to_bytes());
data.extend_from_slice(&auth.to_bytes());
data.extend_from_slice(&payload);
data.extend_from_slice(&[0,0,0,0]);
data
};
// parsing without extensions (normal length)
{
let actual = Ipv6Slice::from_slice(&data_without_ext).unwrap();
prop_assert_eq!(actual.header().slice(), &data_without_ext[..ipv6_base.header_len()]);
prop_assert!(actual.extensions().first_header().is_none());
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Ipv6HeaderPayloadLen,
payload: &payload,
}
);
}
// parsing with extensions (normal length)
{
let actual = Ipv6Slice::from_slice(&data_with_ext).unwrap();
prop_assert_eq!(actual.header().slice(), &data_with_ext[..ipv6_base.header_len()]);
let (expected, _, _) = Ipv6ExtensionsSlice::from_slice(AUTH, &data_with_ext[ipv6_base.header_len()..]).unwrap();
prop_assert_eq!(
actual.extensions(),
&expected
);
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Ipv6HeaderPayloadLen,
payload: &payload,
}
);
}
// parsing without extensions (zero length, fallback to slice length)
{
// inject zero as payload length
let mut data = data_without_ext.clone();
data[4] = 0;
data[5] = 0;
let actual = Ipv6Slice::from_slice(&data).unwrap();
prop_assert_eq!(actual.header().slice(), &data[..ipv6_base.header_len()]);
prop_assert!(actual.extensions().first_header().is_none());
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Slice,
payload: &data[ipv6_base.header_len()..],
}
);
}
// parsing with extensions (zero length, fallback to slice length)
{
// inject zero as payload length
let mut data = data_with_ext.clone();
data[4] = 0;
data[5] = 0;
let actual = Ipv6Slice::from_slice(&data).unwrap();
prop_assert_eq!(actual.header().slice(), &data[..ipv6_base.header_len()]);
let (expected, _, _) = Ipv6ExtensionsSlice::from_slice(AUTH, &data[ipv6_base.header_len()..]).unwrap();
prop_assert_eq!(
actual.extensions(),
&expected
);
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Slice,
payload: &data[ipv6_base.header_len() + auth_base.header_len()..],
}
);
}
// header content error
{
use crate::err::ipv6::HeaderError;
// inject invalid ip version
let mut data = data_without_ext.clone();
data[0] = data[0] & 0x0f; // version 0
prop_assert_eq!(
Ipv6Slice::from_slice(&data).unwrap_err(),
SliceError::Header(
HeaderError::UnexpectedVersion{ version_number: 0 }
)
);
}
// header length error
for len in 0..Ipv6Header::LEN {
prop_assert_eq!(
Ipv6Slice::from_slice(&data_without_ext[..len]).unwrap_err(),
SliceError::Len(
LenError{
required_len: Ipv6Header::LEN,
len,
len_source: LenSource::Slice,
layer: Layer::Ipv6Header,
layer_start_offset: 0
}
)
);
}
// payload length error without auth header
{
use crate::err::{LenError, Layer};
let required_len = ipv6_base.header_len() + payload.len();
prop_assert_eq!(
Ipv6Slice::from_slice(&data_without_ext[..required_len - 1]).unwrap_err(),
SliceError::Len(LenError{
required_len: required_len,
len: required_len - 1,
len_source: LenSource::Slice,
layer: Layer::Ipv6Packet,
layer_start_offset: 0,
})
);
}
// payload length error auth header
{
use crate::err::{LenError, Layer};
let required_len = ipv6_base.header_len() + auth_base.header_len() + payload.len();
prop_assert_eq!(
Ipv6Slice::from_slice(&data_with_ext[..required_len - 1]).unwrap_err(),
SliceError::Len(LenError{
required_len: required_len,
len: required_len - 1,
len_source: LenSource::Slice,
layer: Layer::Ipv6Packet,
layer_start_offset: 0,
})
);
}
// auth length error
{
use crate::err::{LenError, Layer};
// inject payload length that is smaller then the auth header
let mut data = data_with_ext.clone();
let payload_len_too_small = auth_base.header_len() - 1;
{
let plts = (payload_len_too_small as u16).to_be_bytes();
data[4] = plts[0];
data[5] = plts[1];
}
prop_assert_eq!(
Ipv6Slice::from_slice(&data).unwrap_err(),
SliceError::Len(
LenError{
required_len: auth_base.header_len(),
len: auth_base.header_len() - 1,
len_source: LenSource::Ipv6HeaderPayloadLen,
layer: Layer::IpAuthHeader,
layer_start_offset: ipv6_base.header_len(),
}
)
);
}
// auth content error
{
use crate::err::{ip_auth, ipv6_exts};
// inject zero as auth header length
let mut data = data_with_ext.clone();
data[ipv6_base.header_len() + 1] = 0;
prop_assert_eq!(
Ipv6Slice::from_slice(&data).unwrap_err(),
SliceError::Exts(ipv6_exts::HeaderError::IpAuth(
ip_auth::HeaderError::ZeroPayloadLen
))
);
}
}
}
proptest! {
#[test]
fn from_slice_lax(
ipv6_base in ipv6_any(),
auth_base in ip_auth_any()
) {
let payload: [u8;6] = [1,2,3,4,5,6];
// build packets
let data_without_ext = {
let mut data = Vec::with_capacity(
ipv6_base.header_len() +
payload.len() +
4
);
let mut ipv6 = ipv6_base.clone();
ipv6.payload_length = (payload.len()) as u16;
ipv6.next_header = UDP;
data.extend_from_slice(&ipv6.to_bytes());
data.extend_from_slice(&payload);
data.extend_from_slice(&[0,0,0,0]);
data
};
let data_with_ext = {
let payload: [u8;6] = [1,2,3,4,5,6];
let mut data = Vec::with_capacity(
ipv6_base.header_len() +
auth_base.header_len() +
payload.len() +
4
);
let mut ipv6 = ipv6_base.clone();
ipv6.payload_length = (auth_base.header_len() + payload.len()) as u16;
ipv6.next_header = AUTH;
let mut auth = auth_base.clone();
auth.next_header = UDP;
data.extend_from_slice(&ipv6.to_bytes());
data.extend_from_slice(&auth.to_bytes());
data.extend_from_slice(&payload);
data.extend_from_slice(&[0,0,0,0]);
data
};
// parsing without extensions (normal length)
{
let actual = Ipv6Slice::from_slice_lax(&data_without_ext).unwrap();
prop_assert_eq!(actual.header().slice(), &data_without_ext[..ipv6_base.header_len()]);
prop_assert!(actual.extensions().first_header().is_none());
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Ipv6HeaderPayloadLen,
payload: &payload,
}
);
}
// parsing with extensions (normal length)
{
let actual = Ipv6Slice::from_slice_lax(&data_with_ext).unwrap();
prop_assert_eq!(actual.header().slice(), &data_with_ext[..ipv6_base.header_len()]);
let (expected, _, _) = Ipv6ExtensionsSlice::from_slice(AUTH, &data_with_ext[ipv6_base.header_len()..]).unwrap();
prop_assert_eq!(
actual.extensions(),
&expected
);
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Ipv6HeaderPayloadLen,
payload: &payload,
}
);
}
// parsing without extensions (zero length, fallback to slice length)
{
// inject zero as payload length
let mut data = data_without_ext.clone();
data[4] = 0;
data[5] = 0;
let actual = Ipv6Slice::from_slice_lax(&data).unwrap();
prop_assert_eq!(actual.header().slice(), &data[..ipv6_base.header_len()]);
prop_assert!(actual.extensions().first_header().is_none());
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Slice,
payload: &data[ipv6_base.header_len()..],
}
);
}
// parsing with extensions (zero length, fallback to slice length)
{
// inject zero as payload length
let mut data = data_with_ext.clone();
data[4] = 0;
data[5] = 0;
let actual = Ipv6Slice::from_slice_lax(&data).unwrap();
prop_assert_eq!(actual.header().slice(), &data[..ipv6_base.header_len()]);
let (expected, _, _) = Ipv6ExtensionsSlice::from_slice(AUTH, &data[ipv6_base.header_len()..]).unwrap();
prop_assert_eq!(
actual.extensions(),
&expected
);
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Slice,
payload: &data[ipv6_base.header_len() + auth_base.header_len()..],
}
);
}
// header content error
{
use crate::err::ipv6::HeaderError;
// inject invalid ip version
let mut data = data_without_ext.clone();
data[0] = data[0] & 0x0f; // version 0
prop_assert_eq!(
Ipv6Slice::from_slice_lax(&data).unwrap_err(),
SliceError::Header(
HeaderError::UnexpectedVersion{ version_number: 0 }
)
);
}
// header length error
for len in 0..Ipv6Header::LEN {
prop_assert_eq!(
Ipv6Slice::from_slice_lax(&data_without_ext[..len]).unwrap_err(),
SliceError::Len(
LenError{
required_len: Ipv6Header::LEN,
len,
len_source: LenSource::Slice,
layer: Layer::Ipv6Header,
layer_start_offset: 0
}
)
);
}
// payload length larger then slice (fallback to slice length)
{
let len = ipv6_base.header_len() + payload.len() - 1;
let actual = Ipv6Slice::from_slice_lax(&data_without_ext[..len]).unwrap();
prop_assert_eq!(actual.header().slice(), &data_without_ext[..ipv6_base.header_len()]);
prop_assert_eq!(
0,
actual.extensions().slice().len()
);
prop_assert_eq!(
actual.payload(),
&IpPayloadSlice{
ip_number: UDP.into(),
fragmented: false,
len_source: LenSource::Slice,
payload: &data_without_ext[ipv6_base.header_len()..len],
}
);
}
// payload length error auth header
{
use crate::err::{LenError, Layer};
let required_len = ipv6_base.header_len() + auth_base.header_len();
prop_assert_eq!(
Ipv6Slice::from_slice_lax(&data_with_ext[..required_len - 1]).unwrap_err(),
SliceError::Len(LenError{
required_len: required_len - Ipv6Header::LEN,
len: required_len - Ipv6Header::LEN - 1,
len_source: LenSource::Slice,
layer: Layer::IpAuthHeader,
layer_start_offset: Ipv6Header::LEN,
})
);
}
// auth length error
{
use crate::err::{LenError, Layer};
// inject payload length that is smaller then the auth header
let mut data = data_with_ext.clone();
let payload_len_too_small = auth_base.header_len() - 1;
{
let plts = (payload_len_too_small as u16).to_be_bytes();
data[4] = plts[0];
data[5] = plts[1];
}
prop_assert_eq!(
Ipv6Slice::from_slice_lax(&data).unwrap_err(),
SliceError::Len(
LenError{
required_len: auth_base.header_len(),
len: auth_base.header_len() - 1,
len_source: LenSource::Ipv6HeaderPayloadLen,
layer: Layer::IpAuthHeader,
layer_start_offset: ipv6_base.header_len(),
}
)
);
}
// auth content error
{
use crate::err::{ip_auth, ipv6_exts};
// inject zero as auth header length
let mut data = data_with_ext.clone();
data[ipv6_base.header_len() + 1] = 0;
prop_assert_eq!(
Ipv6Slice::from_slice_lax(&data).unwrap_err(),
SliceError::Exts(ipv6_exts::HeaderError::IpAuth(
ip_auth::HeaderError::ZeroPayloadLen
))
);
}
}
}
#[test]
fn is_payload_fragmented() {
use crate::ip_number::{IPV6_FRAG, UDP};
// not fragmented
{
let data = Ipv6Header {
traffic_class: 0,
flow_label: 1.try_into().unwrap(),
payload_length: 0,
next_header: UDP,
hop_limit: 4,
source: [0; 16],
destination: [0; 16],
}
.to_bytes();
assert_eq!(
false,
Ipv6Slice::from_slice(&data)
.unwrap()
.is_payload_fragmented()
);
}
// fragmented
{
let ipv6_frag = Ipv6FragmentHeader {
next_header: UDP,
fragment_offset: 0.try_into().unwrap(),
more_fragments: true,
identification: 0,
};
let ipv6 = Ipv6Header {
traffic_class: 0,
flow_label: 1.try_into().unwrap(),
payload_length: ipv6_frag.header_len() as u16,
next_header: IPV6_FRAG,
hop_limit: 4,
source: [0; 16],
destination: [0; 16],
};
let mut data = Vec::with_capacity(ipv6.header_len() + ipv6_frag.header_len());
data.extend_from_slice(&ipv6.to_bytes());
data.extend_from_slice(&ipv6_frag.to_bytes());
assert!(Ipv6Slice::from_slice(&data)
.unwrap()
.is_payload_fragmented());
}
}
}