use crate::error::{CrafterError, Result};
use crate::packet::{Layer, LayerContext};
use crate::protocols::ipv4::Ipv4;
use crate::protocols::ipv6::Ipv6;
const IPV4_MIN_HEADER_LEN: usize = 20;
const IPV6_BASE_HEADER_LEN: usize = 40;
const IPV6_EXT_MIN_LEN: usize = 8;
const IPV6_HOP_LIMIT_OFFSET: usize = 7;
const IPV6_VERSION_NIBBLE_MASK: u32 = 0xF000_0000;
const IPV6_OPTION_MUTABLE_MASK: u8 = 0x20;
const IPV6_OPTION_PAD1: u8 = 0x00;
const IPV4_TOS_OFFSET: usize = 1;
const IPV4_FLAGS_FRAGMENT_OFFSET: usize = 6;
const IPV4_TTL_OFFSET: usize = 8;
const IPV4_CHECKSUM_OFFSET: usize = 10;
pub(crate) fn canonical_ipv4_for_ah(ipv4_header_bytes: &[u8]) -> Result<Vec<u8>> {
if ipv4_header_bytes.len() < IPV4_MIN_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"ah.icv.canonical_ipv4",
IPV4_MIN_HEADER_LEN,
ipv4_header_bytes.len(),
));
}
let mut canonical = ipv4_header_bytes.to_vec();
canonical[IPV4_TOS_OFFSET] = 0;
canonical[IPV4_FLAGS_FRAGMENT_OFFSET] = 0;
canonical[IPV4_FLAGS_FRAGMENT_OFFSET + 1] = 0;
canonical[IPV4_TTL_OFFSET] = 0;
canonical[IPV4_CHECKSUM_OFFSET] = 0;
canonical[IPV4_CHECKSUM_OFFSET + 1] = 0;
if canonical.len() > IPV4_MIN_HEADER_LEN {
for byte in &mut canonical[IPV4_MIN_HEADER_LEN..] {
*byte = 0;
}
}
Ok(canonical)
}
pub(crate) fn preceding_ipv4_header_bytes(ctx: &LayerContext<'_>) -> Result<Vec<u8>> {
let previous = ctx.previous().ok_or_else(|| {
CrafterError::invalid_field_value(
"ah.icv.previous",
"AH requires a preceding IP header to authenticate",
)
})?;
let ipv4 = previous.as_any().downcast_ref::<Ipv4>().ok_or_else(|| {
CrafterError::invalid_field_value(
"ah.icv.previous",
"preceding layer is not an IPv4 header",
)
})?;
let previous_index = ctx.index().checked_sub(1).ok_or_else(|| {
CrafterError::invalid_field_value(
"ah.icv.previous",
"AH requires a preceding IP header to authenticate",
)
})?;
let previous_ctx = LayerContext::new(ctx.packet(), previous_index);
let mut header = Vec::new();
ipv4.compile(&previous_ctx, &mut header)?;
Ok(header)
}
pub(crate) fn canonical_ipv6_for_ah(
ipv6_header_bytes: &[u8],
preceding_ext_headers: &[u8],
) -> Result<Vec<u8>> {
if ipv6_header_bytes.len() < IPV6_BASE_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"ah.icv.canonical_ipv6",
IPV6_BASE_HEADER_LEN,
ipv6_header_bytes.len(),
));
}
let mut canonical = ipv6_header_bytes[..IPV6_BASE_HEADER_LEN].to_vec();
let first_word = u32::from_be_bytes([canonical[0], canonical[1], canonical[2], canonical[3]]);
let version_only = first_word & IPV6_VERSION_NIBBLE_MASK;
canonical[..4].copy_from_slice(&version_only.to_be_bytes());
canonical[IPV6_HOP_LIMIT_OFFSET] = 0;
let next_header = canonical[6];
let extensions = canonical_ipv6_extension_headers(next_header, preceding_ext_headers)?;
canonical.extend_from_slice(&extensions);
Ok(canonical)
}
fn canonical_ipv6_extension_headers(first_next_header: u8, ext_bytes: &[u8]) -> Result<Vec<u8>> {
use crate::protocols::ip::shared::{IPPROTO_DSTOPTS, IPPROTO_FRAGMENT, IPPROTO_HOPOPTS};
let mut canonical = ext_bytes.to_vec();
let mut offset = 0usize;
let mut next_header = first_next_header;
while offset < canonical.len() {
if canonical.len() - offset < IPV6_EXT_MIN_LEN {
return Err(CrafterError::buffer_too_short(
"ah.icv.canonical_ipv6.ext",
offset + IPV6_EXT_MIN_LEN,
canonical.len(),
));
}
let this_next_header = canonical[offset];
let header_len = if next_header == IPPROTO_FRAGMENT {
IPV6_EXT_MIN_LEN
} else {
IPV6_EXT_MIN_LEN + canonical[offset + 1] as usize * 8
};
if canonical.len() - offset < header_len {
return Err(CrafterError::buffer_too_short(
"ah.icv.canonical_ipv6.ext",
offset + header_len,
canonical.len(),
));
}
if next_header == IPPROTO_HOPOPTS || next_header == IPPROTO_DSTOPTS {
canonicalize_ipv6_options(&mut canonical[offset + 2..offset + header_len]);
}
next_header = this_next_header;
offset += header_len;
}
Ok(canonical)
}
fn canonicalize_ipv6_options(options: &mut [u8]) {
let mut index = 0usize;
while index < options.len() {
let option_type = options[index];
if option_type == IPV6_OPTION_PAD1 {
index += 1;
continue;
}
if index + 2 > options.len() {
break;
}
let data_len = options[index + 1] as usize;
let data_start = index + 2;
let data_end = data_start + data_len;
if data_end > options.len() {
break;
}
if option_type & IPV6_OPTION_MUTABLE_MASK != 0 {
for byte in &mut options[data_start..data_end] {
*byte = 0;
}
}
index = data_end;
}
}
pub(crate) fn preceding_ipv6_header_bytes(ctx: &LayerContext<'_>) -> Result<(Vec<u8>, Vec<u8>)> {
let ah_index = ctx.index();
if ah_index == 0 {
return Err(CrafterError::invalid_field_value(
"ah.icv.previous",
"AH requires a preceding IP header to authenticate",
));
}
let mut ext_headers: Vec<Vec<u8>> = Vec::new();
let mut base_header: Option<Vec<u8>> = None;
for index in (0..ah_index).rev() {
let layer = ctx.packet().iter().nth(index).ok_or_else(|| {
CrafterError::invalid_field_value(
"ah.icv.previous",
"AH requires a preceding IP header to authenticate",
)
})?;
let layer_ctx = LayerContext::new(ctx.packet(), index);
if let Some(ipv6) = layer.as_any().downcast_ref::<Ipv6>() {
let mut header = Vec::new();
ipv6.compile(&layer_ctx, &mut header)?;
base_header = Some(header);
break;
}
if is_ipv6_extension_layer(layer) {
let mut header = Vec::new();
layer.compile(&layer_ctx, &mut header)?;
ext_headers.push(header);
continue;
}
return Err(CrafterError::invalid_field_value(
"ah.icv.previous",
"AH requires the preceding layers to be an IPv6 header and its extension headers",
));
}
let base_header = base_header.ok_or_else(|| {
CrafterError::invalid_field_value(
"ah.icv.previous",
"preceding layer is not an IPv6 header",
)
})?;
ext_headers.reverse();
let preceding_ext: Vec<u8> = ext_headers.into_iter().flatten().collect();
Ok((base_header, preceding_ext))
}
fn is_ipv6_extension_layer(layer: &dyn Layer) -> bool {
use crate::protocols::ipv6::{
Ipv6DestinationOptionsHeader, Ipv6FragmentHeader, Ipv6HopByHopOptionsHeader,
Ipv6MobileRoutingHeader, Ipv6RoutingHeader, Ipv6SegmentRoutingHeader,
};
let any = layer.as_any();
any.is::<Ipv6HopByHopOptionsHeader>()
|| any.is::<Ipv6DestinationOptionsHeader>()
|| any.is::<Ipv6RoutingHeader>()
|| any.is::<Ipv6MobileRoutingHeader>()
|| any.is::<Ipv6SegmentRoutingHeader>()
|| any.is::<Ipv6FragmentHeader>()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{Packet, Raw};
use crate::protocols::ipv4::Ipv4;
use core::net::Ipv4Addr;
fn sample_ipv4_header() -> Vec<u8> {
vec![
0x45, 0xB8, 0x00, 0x54, 0x12, 0x34, 0x40, 0x00, 0x33, 0x33, 0xAB, 0xCD, 192, 0, 2, 1, 192, 0, 2, 2, ]
}
#[test]
fn canonicalizes_exactly_the_mutable_fields() {
let header = sample_ipv4_header();
let canonical = canonical_ipv4_for_ah(&header).expect("20-byte header canonicalizes");
assert_eq!(canonical[IPV4_TOS_OFFSET], 0, "TOS/DSCP+ECN zeroed");
assert_eq!(canonical[IPV4_FLAGS_FRAGMENT_OFFSET], 0, "Flags zeroed");
assert_eq!(
canonical[IPV4_FLAGS_FRAGMENT_OFFSET + 1],
0,
"Fragment Offset zeroed"
);
assert_eq!(canonical[IPV4_TTL_OFFSET], 0, "TTL zeroed");
assert_eq!(canonical[IPV4_CHECKSUM_OFFSET], 0, "Checksum high zeroed");
assert_eq!(
canonical[IPV4_CHECKSUM_OFFSET + 1],
0,
"Checksum low zeroed"
);
let expected: Vec<u8> = vec![
0x45, 0x00, 0x00, 0x54, 0x12, 0x34, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 192, 0, 2, 1, 192, 0, 2, 2, ];
assert_eq!(canonical, expected);
assert_eq!(canonical.len(), header.len());
}
#[test]
fn protocol_and_total_length_are_preserved() {
let header = sample_ipv4_header();
let canonical = canonical_ipv4_for_ah(&header).expect("canonicalizes");
assert_eq!(canonical[9], 0x33);
assert_eq!(&canonical[2..4], &[0x00, 0x54]);
}
#[test]
fn options_are_zeroed_but_base_header_canonicalizes_normally() {
let mut header = sample_ipv4_header();
header[0] = 0x46;
header.extend_from_slice(&[0x94, 0x04, 0x00, 0x00]);
let canonical = canonical_ipv4_for_ah(&header).expect("canonicalizes with options");
assert_eq!(&canonical[IPV4_MIN_HEADER_LEN..], &[0u8; 4]);
assert_eq!(canonical[IPV4_TOS_OFFSET], 0);
assert_eq!(canonical[IPV4_TTL_OFFSET], 0);
assert_eq!(canonical[0], 0x46);
}
#[test]
fn rejects_a_truncated_header() {
let err = canonical_ipv4_for_ah(&[0x45, 0x00, 0x00])
.expect_err("a 3-byte buffer is too short for an IPv4 header");
let _ = err;
}
#[test]
fn preceding_ipv4_header_is_extracted_and_compiled() {
let ipv4 = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
.ttl(51);
let packet: Packet = ipv4 / Raw::from_bytes([0u8; 4]);
let ctx = LayerContext::new(&packet, 1);
let header = preceding_ipv4_header_bytes(&ctx).expect("preceding IPv4 header extracted");
assert!(header.len() >= IPV4_MIN_HEADER_LEN);
assert_eq!(header[0] >> 4, 4);
assert_eq!(&header[12..16], &[192, 0, 2, 1]);
assert_eq!(&header[16..20], &[192, 0, 2, 2]);
let canonical = canonical_ipv4_for_ah(&header).expect("canonicalizes");
assert_eq!(canonical[IPV4_TTL_OFFSET], 0);
}
#[test]
fn preceding_layer_must_be_ipv4() {
let packet: Packet = Packet::from_layer(Raw::from_bytes([0u8; 4]));
let ctx = LayerContext::new(&packet, 0);
let err = preceding_ipv4_header_bytes(&ctx).expect_err("no preceding IP header");
let _ = err;
}
#[test]
fn preceding_non_ipv4_layer_is_rejected() {
let packet: Packet =
Packet::from_layer(Raw::from_bytes([0xDEu8; 8])).push(Raw::from_bytes([0u8; 4]));
let ctx = LayerContext::new(&packet, 1);
let err = preceding_ipv4_header_bytes(&ctx).expect_err("preceding layer is not IPv4");
let _ = err;
}
fn sample_ipv6_header() -> Vec<u8> {
let mut header = Vec::with_capacity(IPV6_BASE_HEADER_LEN);
header.extend_from_slice(&0x6B81_2345u32.to_be_bytes());
header.extend_from_slice(&0x0010u16.to_be_bytes()); header.push(51); header.push(0x40); header.extend_from_slice(&[
0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01,
]);
header.extend_from_slice(&[
0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02,
]);
assert_eq!(header.len(), IPV6_BASE_HEADER_LEN);
header
}
#[test]
fn canonicalizes_ipv6_traffic_class_flow_label_and_hop_limit() {
let header = sample_ipv6_header();
let canonical =
canonical_ipv6_for_ah(&header, &[]).expect("40-byte IPv6 header canonicalizes");
assert_eq!(&canonical[..4], &0x6000_0000u32.to_be_bytes());
assert_eq!(canonical[0] >> 4, 6, "Version nibble preserved");
assert_eq!(canonical[IPV6_HOP_LIMIT_OFFSET], 0, "Hop Limit zeroed");
assert_eq!(&canonical[4..6], &0x0010u16.to_be_bytes());
assert_eq!(canonical[6], 51, "Next Header preserved");
assert_eq!(&canonical[8..24], &header[8..24], "Source preserved");
assert_eq!(&canonical[24..40], &header[24..40], "Destination preserved");
assert_eq!(canonical.len(), IPV6_BASE_HEADER_LEN);
}
#[test]
fn ipv6_canonicalization_zeroes_only_the_mutable_bits() {
let header = sample_ipv6_header();
let canonical = canonical_ipv6_for_ah(&header, &[]).expect("canonicalizes");
let mut expected = header.clone();
expected[0] = 0x60;
expected[1] = 0x00;
expected[2] = 0x00;
expected[3] = 0x00;
expected[IPV6_HOP_LIMIT_OFFSET] = 0;
assert_eq!(canonical, expected);
}
#[test]
fn ipv6_rejects_a_truncated_base_header() {
let err = canonical_ipv6_for_ah(&[0x60, 0, 0, 0], &[])
.expect_err("a 4-byte buffer is too short for an IPv6 header");
let _ = err;
}
#[test]
fn ipv6_destination_options_mutable_data_is_zeroed() {
use crate::protocols::ip::shared::IPPROTO_DSTOPTS;
let mut header = sample_ipv6_header();
header[6] = IPPROTO_DSTOPTS;
let ext = vec![
51, 0, 0xE0, 0x04, 0xDE, 0xAD, 0xBE, 0xEF,
];
let canonical =
canonical_ipv6_for_ah(&header, &ext).expect("canonicalizes with ext header");
assert_eq!(&canonical[..4], &0x6000_0000u32.to_be_bytes());
assert_eq!(canonical[IPV6_HOP_LIMIT_OFFSET], 0);
let ext_region = &canonical[IPV6_BASE_HEADER_LEN..];
assert_eq!(ext_region[0], 51, "ext Next Header preserved");
assert_eq!(ext_region[1], 0, "ext Hdr Ext Len preserved");
assert_eq!(ext_region[2], 0xE0, "option type preserved");
assert_eq!(ext_region[3], 0x04, "option length preserved");
assert_eq!(
&ext_region[4..8],
&[0, 0, 0, 0],
"mutable option data zeroed"
);
}
#[test]
fn ipv6_immutable_option_data_is_preserved() {
use crate::protocols::ip::shared::IPPROTO_DSTOPTS;
let mut header = sample_ipv6_header();
header[6] = IPPROTO_DSTOPTS;
let ext = vec![51, 0, 0x04, 0x04, 0x11, 0x22, 0x33, 0x44];
let canonical = canonical_ipv6_for_ah(&header, &ext).expect("canonicalizes");
let ext_region = &canonical[IPV6_BASE_HEADER_LEN..];
assert_eq!(
&ext_region[4..8],
&[0x11, 0x22, 0x33, 0x44],
"immutable option data preserved"
);
}
#[test]
fn ipv6_routing_header_is_covered_as_is() {
use crate::protocols::ip::shared::IPPROTO_ROUTE;
let mut header = sample_ipv6_header();
header[6] = IPPROTO_ROUTE;
let ext = vec![51, 0, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD];
let canonical = canonical_ipv6_for_ah(&header, &ext).expect("canonicalizes");
assert_eq!(&canonical[IPV6_BASE_HEADER_LEN..], &ext[..]);
}
#[test]
fn ipv6_extension_header_truncation_is_a_structured_error() {
use crate::protocols::ip::shared::IPPROTO_DSTOPTS;
let mut header = sample_ipv6_header();
header[6] = IPPROTO_DSTOPTS;
let ext = vec![51, 1, 0x01, 0x00, 0, 0, 0, 0];
let err = canonical_ipv6_for_ah(&header, &ext)
.expect_err("extension header runs off the supplied bytes");
let _ = err;
}
#[test]
fn preceding_ipv6_header_is_extracted_and_canonicalizes() {
use crate::protocols::ipv6::Ipv6;
use core::net::Ipv6Addr;
let ipv6 = Ipv6::new()
.src(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1))
.dst(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 2))
.traffic_class(0xB8)
.flow_label(0x12345)
.hop_limit(51);
let packet: Packet = ipv6 / Raw::from_bytes([0u8; 4]);
let ctx = LayerContext::new(&packet, 1);
let (base, ext) =
preceding_ipv6_header_bytes(&ctx).expect("preceding IPv6 header extracted");
assert_eq!(base.len(), IPV6_BASE_HEADER_LEN);
assert_eq!(base[0] >> 4, 6, "version nibble is 6");
assert!(ext.is_empty(), "no preceding extension headers");
assert_eq!(
&base[8..24],
&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
);
let canonical = canonical_ipv6_for_ah(&base, &ext).expect("canonicalizes");
assert_eq!(&canonical[..4], &0x6000_0000u32.to_be_bytes());
assert_eq!(canonical[IPV6_HOP_LIMIT_OFFSET], 0);
}
#[test]
fn preceding_ipv6_layer_must_be_ipv6() {
let packet: Packet = Packet::from_layer(Raw::from_bytes([0u8; 4]));
let ctx = LayerContext::new(&packet, 0);
let err = preceding_ipv6_header_bytes(&ctx).expect_err("no preceding IP header");
let _ = err;
}
}