blob: 2c73f58cef4d7cf4a575a2ec29c732925b68c0ce [file] [log] [blame]
use crate::*;
/// Slice containing laxly separated IPv4 headers & payload.
///
/// Compared to the normal [`Ipv4Slice`] this slice allows the
/// payload to be incomplete/cut off and errors to be present in
/// the IpPayload.
///
/// The main usecases for "laxly" parsed slices are 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 `total_len` (for IPv4) have 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).
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LaxIpv4Slice<'a> {
pub(crate) header: Ipv4HeaderSlice<'a>,
pub(crate) exts: Ipv4ExtensionsSlice<'a>,
pub(crate) payload: LaxIpPayloadSlice<'a>,
}
impl<'a> LaxIpv4Slice<'a> {
/// Separates and validates IPv4 headers (including extension headers) &
/// 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 [`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 `total_len` (for IPv4) have 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 `Ipv4Slice::from_slice`:
///
/// There are two main differences:
///
/// * Errors in the expansion headers will only stop the parsing and return an `Ok`
/// with the successfully parsed parts and the error as optional. Only if an
/// unrecoverable error is encountered in the IP header itself an `Err` is returned.
/// In the normal `Ipv4Slice::from_slice` function an `Err` is returned if an error is
/// encountered in an exteions header.
/// * `LaxIpv4Slice::from_slice` ignores inconsistent `total_len` values. When the `total_len`
/// value in the IPv4 header are inconsistant the length of the given slice is
/// used as a substitute.
///
/// ## What happens in the `total_len` value is inconsistent?
///
/// When the total_length value in the IPv4 header is inconsistent the
/// length of the given slice is used as a substitute. This can happen
/// if the `total_length` field in the IPv4 header is:
///
/// * Bigger then the given slice (payload cannot fully be seperated).
/// * Too small to contain at least the IPv4 header.
///
/// Additionally you can check if more data was expected based on the
/// `total_len` but the given slice was too small by checking if `incomplete`
/// is set to `true` in the returned [`LaxIpPayloadSlice`].
///
/// You can check if the slice length was used as a substitude by checking
/// if the `len_source` value in the returned [`LaxIpPayloadSlice`] is set to
/// [`LenSource::Slice`]. If a substitution was not needed `len_source`
/// is set to [`LenSource::Ipv4HeaderTotalLen`].
pub fn from_slice(
slice: &[u8],
) -> Result<(LaxIpv4Slice, Option<err::ip_auth::HeaderSliceError>), err::ipv4::HeaderSliceError>
{
use crate::ip_number::AUTH;
// decode the header
let header = Ipv4HeaderSlice::from_slice(slice)?;
// validate total_len at least contains the header
let header_total_len: usize = header.total_len().into();
let (header_payload, len_source, incomplete) = if header_total_len < header.slice().len() {
// total_length is smaller then the header itself
// fall back to the slice for the length
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(header.slice().len()),
slice.len() - header.slice().len(),
)
},
LenSource::Slice,
// note that we have no indication that the packet is incomplete
false,
)
} else if header_total_len > slice.len() {
// more data was expected, fallback to slice and report payload as "incomplete"
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(header.slice().len()),
slice.len() - header.slice().len(),
)
},
LenSource::Slice,
true, // incomplete
)
} else {
// all good the packet seems to be complete
(
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(header.slice().len()),
header_total_len - header.slice().len(),
)
},
LenSource::Ipv4HeaderTotalLen,
false,
)
};
// decode the authentication header if needed
let fragmented = header.is_fragmenting_payload();
match header.protocol() {
AUTH => {
use crate::err::ip_auth::HeaderSliceError as E;
// parse extension headers
match IpAuthHeaderSlice::from_slice(header_payload) {
Ok(auth) => {
// remove the extension header from the payload
let payload = unsafe {
core::slice::from_raw_parts(
header_payload.as_ptr().add(auth.slice().len()),
header_payload.len() - auth.slice().len(),
)
};
let ip_number = auth.next_header();
Ok((
LaxIpv4Slice {
header,
exts: Ipv4ExtensionsSlice { auth: Some(auth) },
payload: LaxIpPayloadSlice {
incomplete,
ip_number,
fragmented,
len_source,
payload,
},
},
None,
))
}
Err(err) => {
let err = match err {
E::Len(mut l) => {
// change the length source to the ipv4 header
l.len_source = len_source;
l.layer_start_offset += header.slice().len();
E::Len(l)
}
E::Content(err) => E::Content(err),
};
Ok((
LaxIpv4Slice {
header,
exts: Ipv4ExtensionsSlice { auth: None },
payload: LaxIpPayloadSlice {
incomplete,
ip_number: AUTH,
fragmented,
len_source,
payload: header_payload,
},
},
Some(err),
))
}
}
}
ip_number => Ok((
LaxIpv4Slice {
header,
exts: Ipv4ExtensionsSlice { auth: None },
payload: LaxIpPayloadSlice {
incomplete,
ip_number,
fragmented,
len_source,
payload: header_payload,
},
},
None,
)),
}
}
/// Returns a slice containing the IPv4 header.
#[inline]
pub fn header(&self) -> Ipv4HeaderSlice {
self.header
}
/// Returns a slice containing the IPv4 extension headers.
#[inline]
pub fn extensions(&self) -> Ipv4ExtensionsSlice {
self.exts
}
/// Returns a slice containing the data after the IPv4 header
/// and IPv4 extensions headers.
#[inline]
pub fn payload(&self) -> &LaxIpPayloadSlice<'a> {
&self.payload
}
/// Returns the ip number the type of payload of the IPv4 packet.
///
/// This function returns the ip number stored in the last
/// IPv4 header or extension header.
#[inline]
pub fn payload_ip_number(&self) -> IpNumber {
self.payload.ip_number
}
/// Returns true if the payload is flagged as being fragmented.
#[inline]
pub fn is_payload_fragmented(&self) -> bool {
self.header.is_fragmenting_payload()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{err::LenError, ip_number::AUTH, test_gens::*};
use alloc::{format, vec::Vec};
use proptest::prelude::*;
proptest! {
#[test]
fn debug_clone_eq(
ipv4_base in ipv4_any(),
auth in ip_auth_any()
) {
let payload: [u8;4] = [1,2,3,4];
let mut data = Vec::with_capacity(
ipv4_base.header_len() +
auth.header_len() +
payload.len()
);
let mut ipv4 = ipv4_base.clone();
ipv4.protocol = crate::ip_number::AUTH;
ipv4.set_payload_len(auth.header_len() + payload.len()).unwrap();
data.extend_from_slice(&ipv4.to_bytes());
data.extend_from_slice(&auth.to_bytes());
data.extend_from_slice(&payload);
// decode packet
let (slice, _) = LaxIpv4Slice::from_slice(&data).unwrap();
// check debug output
prop_assert_eq!(
format!("{:?}", slice),
format!(
"LaxIpv4Slice {{ header: {:?}, exts: {:?}, payload: {:?} }}",
slice.header(),
slice.extensions(),
slice.payload()
)
);
prop_assert_eq!(slice.clone(), slice);
}
}
fn combine_v4(
v4: &Ipv4Header,
ext: &crate::Ipv4Extensions,
payload: &[u8],
) -> crate::IpHeaders {
use crate::ip_number::UDP;
crate::IpHeaders::Ipv4(
{
let mut v4 = v4.clone();
v4.protocol = if ext.auth.is_some() { AUTH } else { UDP };
v4.total_len = (v4.header_len() + ext.header_len() + payload.len()) as u16;
v4.header_checksum = v4.calc_header_checksum();
v4
},
ext.clone(),
)
}
proptest! {
#[test]
fn from_slice(
v4 in ipv4_any(),
v4_exts in ipv4_extensions_any()
) {
use crate::err::{self, ipv4::HeaderError::*};
use crate::err::ipv4::HeaderSliceError as E;
use err::ip_auth::HeaderSliceError as A;
let payload = [1,2,3,4];
// empty error
assert_eq!(
LaxIpv4Slice::from_slice(&[]),
Err(E::Len(err::LenError {
required_len: 20,
len: 0,
len_source: LenSource::Slice,
layer: err::Layer::Ipv4Header,
layer_start_offset: 0,
}))
);
// build a buffer with a valid packet
let header = combine_v4(&v4, &v4_exts, &payload);
let mut buffer = Vec::with_capacity(header.header_len() + payload.len() + 1);
header.write(&mut buffer).unwrap();
buffer.extend_from_slice(&payload);
buffer.push(1); // add some value to check the return slice
// normal read
{
let (actual, actual_stop_err) = LaxIpv4Slice::from_slice(&buffer).unwrap();
assert_eq!(None, actual_stop_err);
assert_eq!(&actual.header.to_header(), header.ipv4().unwrap().0);
assert_eq!(&actual.extensions().to_header(), header.ipv4().unwrap().1);
assert_eq!(
actual.payload,
LaxIpPayloadSlice{
incomplete: false,
ip_number: header.next_header().unwrap(),
fragmented: header.is_fragmenting_payload(),
len_source: LenSource::Ipv4HeaderTotalLen,
payload: &payload
}
);
}
// error len smaller then min header len
for len in 1..Ipv4Header::MIN_LEN {
assert_eq!(
LaxIpv4Slice::from_slice(&buffer[..len]),
Err(E::Len(err::LenError {
required_len: Ipv4Header::MIN_LEN,
len,
len_source: LenSource::Slice,
layer: err::Layer::Ipv4Header,
layer_start_offset: 0,
}))
);
}
// ihl value error
{
let mut bad_ihl_buffer = buffer.clone();
for bad_ihl in 0..5 {
bad_ihl_buffer[0] = (bad_ihl_buffer[0] & 0xf0) | bad_ihl;
assert_eq!(
LaxIpv4Slice::from_slice(&bad_ihl_buffer),
Err(E::Content(HeaderLengthSmallerThanHeader { ihl: bad_ihl }))
);
}
}
// ihl len error
for short_ihl in 5..usize::from(v4.ihl()) {
assert_eq!(
LaxIpv4Slice::from_slice(&buffer[..4*short_ihl]),
Err(E::Len(err::LenError {
required_len: usize::from(v4.ihl())*4,
len: 4*short_ihl,
len_source: LenSource::Slice,
layer: err::Layer::Ipv4Header,
layer_start_offset: 0,
}))
);
}
// total_len bigger then slice len (fallback to slice len)
for payload_len in 0..payload.len(){
let (actual, stop_err) = LaxIpv4Slice::from_slice(&buffer[..v4.header_len() + v4_exts.header_len() + payload_len]).unwrap();
assert_eq!(stop_err, None);
assert_eq!(&actual.header().to_header(), header.ipv4().unwrap().0);
assert_eq!(
actual.payload(),
&LaxIpPayloadSlice{
incomplete: true,
ip_number: header.next_header().unwrap(),
fragmented: header.is_fragmenting_payload(),
len_source: LenSource::Slice,
payload: &payload[..payload_len]
}
);
}
// len error ipv4 extensions
if v4_exts.header_len() > 0 {
let (actual, stop_err) = LaxIpv4Slice::from_slice(&buffer[..v4.header_len() + 1]).unwrap();
assert_eq!(&actual.header().to_header(), header.ipv4().unwrap().0);
assert_eq!(
actual.payload(),
&LaxIpPayloadSlice{
incomplete: true,
ip_number: AUTH,
fragmented: header.is_fragmenting_payload(),
len_source: LenSource::Slice,
payload: &buffer[v4.header_len()..v4.header_len() + 1]
}
);
assert_eq!(stop_err, Some(A::Len(LenError{
required_len: IpAuthHeader::MIN_LEN,
len: 1,
len_source: LenSource::Slice,
layer: err::Layer::IpAuthHeader,
layer_start_offset: header.ipv4().unwrap().0.header_len()
})));
}
// content error ipv4 extensions
if v4_exts.auth.is_some() {
use err::ip_auth::HeaderError::ZeroPayloadLen;
// introduce a auth header zero payload error
let mut errored_buffer = buffer.clone();
// inject length zero into auth header (not valid, will
// trigger a content error)
errored_buffer[v4.header_len() + 1] = 0;
let (actual, stop_err) = LaxIpv4Slice::from_slice(&errored_buffer).unwrap();
assert_eq!(&actual.header().to_header(), header.ipv4().unwrap().0);
assert!(actual.extensions().is_empty());
let auth_offset = header.ipv4().unwrap().0.header_len();
let payload_end = auth_offset + v4_exts.auth.map(|v| v.header_len()).unwrap() + payload.len();
assert_eq!(
actual.payload(),
&LaxIpPayloadSlice{
incomplete: false,
ip_number: AUTH,
fragmented: header.is_fragmenting_payload(),
len_source: LenSource::Ipv4HeaderTotalLen,
payload: &errored_buffer[auth_offset..payload_end]
}
);
assert_eq!(stop_err, Some(A::Content(ZeroPayloadLen)));
}
// total length smaller the header (fallback to slice len)
{
let bad_total_len = (v4.header_len() - 1) as u16;
let mut buffer = buffer.clone();
// inject bad total_len
let bad_total_len_be = bad_total_len.to_be_bytes();
buffer[2] = bad_total_len_be[0];
buffer[3] = bad_total_len_be[1];
let (actual, actual_stop_error) = LaxIpv4Slice::from_slice(&buffer[..]).unwrap();
assert_eq!(actual_stop_error, None);
let (v4_header, v4_exts) = header.ipv4().unwrap();
let expected_headers = IpHeaders::Ipv4(
{
let mut expected_v4 = v4_header.clone();
expected_v4.total_len = bad_total_len;
expected_v4
},
v4_exts.clone()
);
assert_eq!(expected_headers.ipv4().unwrap().0, &actual.header().to_header());
assert_eq!(
actual.payload(),
&LaxIpPayloadSlice{
incomplete: false,
ip_number: header.next_header().unwrap(),
fragmented: header.is_fragmenting_payload(),
len_source: LenSource::Slice,
payload: &buffer[v4_header.header_len() + v4_exts.header_len()..],
}
);
}
}
}
#[test]
fn is_payload_fragmented() {
use crate::ip_number::UDP;
// non-fragmented
{
let payload: [u8; 6] = [1, 2, 3, 4, 5, 6];
let ipv4 =
Ipv4Header::new(payload.len() as u16, 1, UDP, [3, 4, 5, 6], [7, 8, 9, 10]).unwrap();
let data = {
let mut data = Vec::with_capacity(ipv4.header_len() + payload.len());
data.extend_from_slice(&ipv4.to_bytes());
data.extend_from_slice(&payload);
data
};
let (slice, stop_err) = LaxIpv4Slice::from_slice(&data).unwrap();
assert_eq!(None, stop_err);
assert!(false == slice.is_payload_fragmented());
}
// fragmented
{
let payload: [u8; 6] = [1, 2, 3, 4, 5, 6];
let mut ipv4 =
Ipv4Header::new(payload.len() as u16, 1, UDP, [3, 4, 5, 6], [7, 8, 9, 10]).unwrap();
ipv4.fragment_offset = 123.try_into().unwrap();
let data = {
let mut data = Vec::with_capacity(ipv4.header_len() + payload.len());
data.extend_from_slice(&ipv4.to_bytes());
data.extend_from_slice(&payload);
data
};
let (slice, stop_err) = LaxIpv4Slice::from_slice(&data).unwrap();
assert_eq!(None, stop_err);
assert!(slice.is_payload_fragmented());
}
}
}