blob: e22a566a41cea4710713b75d656819cdcbb14ee7 [file] [log] [blame]
use crate::{
err::{ipv4::SliceError, Layer, LenError},
*,
};
/// Slice containing the IPv4 headers & payload.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Ipv4Slice<'a> {
pub(crate) header: Ipv4HeaderSlice<'a>,
pub(crate) exts: Ipv4ExtensionsSlice<'a>,
pub(crate) payload: IpPayloadSlice<'a>,
}
impl<'a> Ipv4Slice<'a> {
/// Separates and validates IPv4 headers (including extension headers)
/// in the given slice and determine the sub-slice containing the payload
/// of the IPv4 packet.
///
/// Note that his function returns an [`crate::err::LenError`] if the given slice
/// contains less data then the `total_len` field in the IPv4 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 [`crate::LaxIpv4Slice::from_slice`] instead.
pub fn from_slice(slice: &[u8]) -> Result<Ipv4Slice, SliceError> {
use crate::ip_number::AUTH;
// decode the header
let header = Ipv4HeaderSlice::from_slice(slice).map_err(|err| {
use crate::err::ipv4::HeaderSliceError::*;
match err {
Len(err) => SliceError::Len(err),
Content(err) => SliceError::Header(err),
}
})?;
// validate total_len at least contains the header
let header_total_len: usize = header.total_len().into();
if header_total_len < header.slice().len() {
return Err(SliceError::Len(LenError {
required_len: header.slice().len(),
len: header_total_len,
len_source: LenSource::Ipv4HeaderTotalLen,
layer: Layer::Ipv4Packet,
layer_start_offset: 0,
}));
}
// check slice length based on the total length
let header_payload = if slice.len() < header_total_len {
return Err(SliceError::Len(LenError {
required_len: header_total_len,
len: slice.len(),
len_source: LenSource::Slice,
layer: Layer::Ipv4Packet,
layer_start_offset: 0,
}));
} else {
unsafe {
core::slice::from_raw_parts(
slice.as_ptr().add(header.slice().len()),
header_total_len - header.slice().len(),
)
}
};
// 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
let auth = match IpAuthHeaderSlice::from_slice(header_payload) {
Ok(s) => s,
Err(err) => match err {
E::Len(mut l) => {
// change the length source to the ipv4 header
l.len_source = LenSource::Ipv4HeaderTotalLen;
l.layer_start_offset += header.slice().len();
return Err(SliceError::Len(l));
}
E::Content(err) => return Err(SliceError::Exts(err)),
},
};
// 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(Ipv4Slice {
header,
exts: Ipv4ExtensionsSlice { auth: Some(auth) },
payload: IpPayloadSlice {
ip_number,
fragmented,
len_source: LenSource::Ipv4HeaderTotalLen,
payload,
},
})
}
ip_number => Ok(Ipv4Slice {
header,
exts: Ipv4ExtensionsSlice { auth: None },
payload: IpPayloadSlice {
ip_number,
fragmented,
len_source: LenSource::Ipv4HeaderTotalLen,
payload: header_payload,
},
}),
}
}
/// 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) -> &IpPayloadSlice<'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::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 = Ipv4Slice::from_slice(&data).unwrap();
// check debug output
prop_assert_eq!(
format!("{:?}", slice),
format!(
"Ipv4Slice {{ header: {:?}, exts: {:?}, payload: {:?} }}",
slice.header(),
slice.extensions(),
slice.payload()
)
);
prop_assert_eq!(slice.clone(), slice);
}
}
proptest! {
#[test]
fn from_slice(
ipv4_base in ipv4_any(),
auth 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(
ipv4_base.header_len() +
payload.len() +
4
);
let mut ipv4 = ipv4_base.clone();
ipv4.set_payload_len(payload.len()).unwrap();
ipv4.protocol = crate::ip_number::UDP;
data.extend_from_slice(&ipv4.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(
ipv4_base.header_len() +
auth.header_len() +
payload.len() +
4
);
let mut ipv4 = ipv4_base.clone();
ipv4.set_payload_len(auth.header_len() + payload.len()).unwrap();
ipv4.protocol = crate::ip_number::AUTH;
data.extend_from_slice(&ipv4.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
{
let actual = Ipv4Slice::from_slice(&data_without_ext).unwrap();
prop_assert_eq!(actual.header().slice(), &data_without_ext[..ipv4_base.header_len()]);
prop_assert!(actual.extensions().auth.is_none());
prop_assert_eq!(
&actual.payload,
&IpPayloadSlice{
ip_number: ip_number::UDP.into(),
fragmented: ipv4_base.is_fragmenting_payload(),
len_source: LenSource::Ipv4HeaderTotalLen,
payload: &payload
}
);
prop_assert_eq!(actual.payload_ip_number(), ip_number::UDP.into());
}
// parsing with extensions
{
let actual = Ipv4Slice::from_slice(&data_with_ext).unwrap();
prop_assert_eq!(actual.header().slice(), &data_with_ext[..ipv4_base.header_len()]);
prop_assert_eq!(
actual.extensions().auth.unwrap(),
IpAuthHeaderSlice::from_slice(&data_with_ext[ipv4_base.header_len()..]).unwrap()
);
prop_assert_eq!(
&actual.payload,
&IpPayloadSlice{
ip_number: auth.next_header.into(),
fragmented: ipv4_base.is_fragmenting_payload(),
len_source: LenSource::Ipv4HeaderTotalLen,
payload: &payload
}
);
prop_assert_eq!(actual.payload_ip_number(), auth.next_header.into());
}
// header length error
for len in 0..Ipv4Header::MIN_LEN {
prop_assert_eq!(
Ipv4Slice::from_slice(&data_without_ext[..len]).unwrap_err(),
SliceError::Len(
LenError{
required_len: Ipv4Header::MIN_LEN,
len,
len_source: LenSource::Slice,
layer: Layer::Ipv4Header,
layer_start_offset: 0,
}
)
);
}
// header content error
{
use crate::err::ipv4::HeaderError;
// inject invalid icv
let mut data = data_without_ext.clone();
data[0] = data[0] & 0xf0; // icv 0
prop_assert_eq!(
Ipv4Slice::from_slice(&data).unwrap_err(),
SliceError::Header(
HeaderError::HeaderLengthSmallerThanHeader { ihl: 0 }
)
);
}
// payload length error without auth header
{
use crate::err::{LenError, Layer};
let required_len = ipv4_base.header_len() + payload.len();
prop_assert_eq!(
Ipv4Slice::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::Ipv4Packet,
layer_start_offset: 0,
})
);
}
// payload length error auth header
{
use crate::err::{LenError, Layer};
let required_len = ipv4_base.header_len() + auth.header_len() + payload.len();
prop_assert_eq!(
Ipv4Slice::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::Ipv4Packet,
layer_start_offset: 0,
})
);
}
// auth length error
{
use crate::err::{LenError, Layer};
// inject a total_length that is smaller then the auth header
let mut data = data_with_ext.clone();
let total_len_too_small = ipv4_base.header_len() + auth.header_len() - 1;
{
let tltsm = (total_len_too_small as u16).to_be_bytes();
data[2] = tltsm[0];
data[3] = tltsm[1];
}
prop_assert_eq!(
Ipv4Slice::from_slice(&data).unwrap_err(),
SliceError::Len(
LenError{
required_len: auth.header_len(),
len: auth.header_len() - 1,
len_source: LenSource::Ipv4HeaderTotalLen,
layer: Layer::IpAuthHeader,
layer_start_offset: ipv4_base.header_len(),
}
)
);
}
// auth content error
{
use crate::err::ip_auth::HeaderError;
// inject zero as auth header length
let mut data = data_with_ext.clone();
data[ipv4_base.header_len() + 1] = 0;
prop_assert_eq!(
Ipv4Slice::from_slice(&data).unwrap_err(),
SliceError::Exts(
HeaderError::ZeroPayloadLen
)
);
}
}
}
#[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 = Ipv4Slice::from_slice(&data).unwrap();
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 = Ipv4Slice::from_slice(&data).unwrap();
assert!(slice.is_payload_fragmented());
}
}
}