use crate::error::{NetError, Result};
pub const ETH_HEADER_LEN: usize = 14;
pub const VLAN_TAG_LEN: usize = 4;
pub const IPV4_MIN_HEADER_LEN: usize = 20;
pub const IPV4_MAX_HEADER_LEN: usize = 60;
pub const IPV6_HEADER_LEN: usize = 40;
pub const TCP_MIN_HEADER_LEN: usize = 20;
pub const UDP_HEADER_LEN: usize = 8;
pub const ARP_PACKET_LEN: usize = 28;
pub const ICMP_MIN_HEADER_LEN: usize = 8;
pub const ICMPV6_MIN_HEADER_LEN: usize = 4;
pub const MAX_PARSE_STEPS: u32 = 20;
#[allow(unsafe_code)]
pub(crate) mod cast {
use crate::error::{NetError, Result};
pub trait PacketHeader: Sized {
const MIN_LEN: usize;
}
#[inline]
pub fn from_bytes<T: PacketHeader>(data: &[u8]) -> Result<&T> {
if data.len() < T::MIN_LEN {
return Err(NetError::PacketTooShort {
need: T::MIN_LEN,
got: data.len(),
});
}
Ok(unsafe { &*(data.as_ptr() as *const T) })
}
}
impl cast::PacketHeader for EthHeader {
const MIN_LEN: usize = ETH_HEADER_LEN;
}
impl cast::PacketHeader for VlanTag {
const MIN_LEN: usize = VLAN_TAG_LEN;
}
impl cast::PacketHeader for Ipv4Header {
const MIN_LEN: usize = IPV4_MIN_HEADER_LEN;
}
impl cast::PacketHeader for Ipv6Header {
const MIN_LEN: usize = IPV6_HEADER_LEN;
}
impl cast::PacketHeader for TcpHeader {
const MIN_LEN: usize = TCP_MIN_HEADER_LEN;
}
impl cast::PacketHeader for UdpHeader {
const MIN_LEN: usize = UDP_HEADER_LEN;
}
impl cast::PacketHeader for ArpPacket {
const MIN_LEN: usize = ARP_PACKET_LEN;
}
impl cast::PacketHeader for IcmpHeader {
const MIN_LEN: usize = ICMP_MIN_HEADER_LEN;
}
impl cast::PacketHeader for IcmpV6Header {
const MIN_LEN: usize = ICMPV6_MIN_HEADER_LEN;
}
pub mod ethertype {
pub const IPV4: u16 = 0x0800;
pub const ARP: u16 = 0x0806;
pub const IPV6: u16 = 0x86DD;
pub const VLAN: u16 = 0x8100;
}
pub mod ip_proto {
pub const ICMP: u8 = 1;
pub const TCP: u8 = 6;
pub const UDP: u8 = 17;
pub const ICMPV6: u8 = 58;
}
pub mod icmp_type {
pub const ECHO_REPLY: u8 = 0;
pub const DEST_UNREACH: u8 = 3;
pub const REDIRECT: u8 = 5;
pub const ECHO_REQUEST: u8 = 8;
pub const ROUTER_ADV: u8 = 9;
pub const ROUTER_SOL: u8 = 10;
pub const TIME_EXCEEDED: u8 = 11;
pub const PARAM_PROBLEM: u8 = 12;
pub const TIMESTAMP_REQUEST: u8 = 13;
pub const TIMESTAMP_REPLY: u8 = 14;
}
pub mod icmpv6_type {
pub const DEST_UNREACH: u8 = 1;
pub const PKT_TOO_BIG: u8 = 2;
pub const TIME_EXCEEDED: u8 = 3;
pub const PARAM_PROBLEM: u8 = 4;
pub const ECHO_REQUEST: u8 = 128;
pub const ECHO_REPLY: u8 = 129;
pub const ROUTER_SOL: u8 = 133;
pub const ROUTER_ADV: u8 = 134;
pub const NEIGHBOR_SOL: u8 = 135;
pub const NEIGHBOR_ADV: u8 = 136;
pub const REDIRECT: u8 = 137;
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct EthHeader {
pub(crate) dst_mac: [u8; 6],
pub(crate) src_mac: [u8; 6],
pub(crate) ethertype: u16,
}
impl EthHeader {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
cast::from_bytes(data)
}
#[inline]
pub fn ethertype(&self) -> u16 {
u16::from_be(self.ethertype)
}
#[inline]
pub fn src_mac(&self) -> [u8; 6] {
self.src_mac
}
#[inline]
pub fn dst_mac(&self) -> [u8; 6] {
self.dst_mac
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct VlanTag {
pub(crate) tci: u16,
pub(crate) ethertype: u16,
}
impl VlanTag {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
cast::from_bytes(data)
}
#[inline]
pub fn ethertype(&self) -> u16 {
u16::from_be(self.ethertype)
}
#[inline]
pub fn vlan_id(&self) -> u16 {
u16::from_be(self.tci) & 0x0FFF
}
#[inline]
pub fn pcp(&self) -> u8 {
(u16::from_be(self.tci) >> 13) as u8
}
}
#[repr(C, packed)]
#[derive(Debug, Clone)]
pub struct Ipv4Header {
pub(crate) version_ihl: u8,
pub(crate) tos: u8,
pub(crate) total_length: u16,
pub(crate) identification: u16,
pub(crate) flags_fragment: u16,
pub(crate) ttl: u8,
pub(crate) protocol: u8,
pub(crate) checksum: u16,
pub(crate) src_ip: [u8; 4],
pub(crate) dst_ip: [u8; 4],
}
impl Ipv4Header {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
let hdr = cast::from_bytes::<Self>(data)?;
let version = hdr.version_ihl >> 4;
if version != 4 {
return Err(NetError::InvalidIpVersion(version));
}
let ihl = hdr.version_ihl & 0x0F;
if ihl < 5 {
return Err(NetError::InvalidPacket {
reason: "IPv4 IHL < 5 (header too short)",
});
}
let hdr_len = (ihl as usize) << 2;
if hdr_len > data.len() {
return Err(NetError::InvalidPacket {
reason: "IPv4 header length exceeds packet size",
});
}
let total_len = u16::from_be(hdr.total_length) as usize;
if total_len < hdr_len {
return Err(NetError::InvalidPacket {
reason: "IPv4 total_length < header length (RFC 791 violation)",
});
}
Ok(hdr)
}
#[inline]
pub fn version(&self) -> u8 {
self.version_ihl >> 4
}
#[inline]
pub fn ihl(&self) -> u8 {
self.version_ihl & 0x0F
}
#[inline]
pub fn total_length(&self) -> u16 {
u16::from_be(self.total_length)
}
#[inline]
pub fn protocol(&self) -> u8 {
self.protocol
}
#[inline]
pub fn ttl(&self) -> u8 {
self.ttl
}
#[inline]
pub fn src_ip(&self) -> [u8; 4] {
self.src_ip
}
#[inline]
pub fn dst_ip(&self) -> [u8; 4] {
self.dst_ip
}
#[inline]
pub fn checksum(&self) -> u16 {
u16::from_be(self.checksum)
}
#[inline]
pub fn flags(&self) -> u8 {
(u16::from_be(self.flags_fragment) >> 13) as u8
}
#[inline]
pub fn dont_fragment(&self) -> bool {
self.flags() & 0x02 != 0
}
#[inline]
pub fn more_fragments(&self) -> bool {
self.flags() & 0x01 != 0
}
#[inline]
pub fn header_length(&self) -> usize {
(self.ihl() as usize) << 2
}
#[inline]
pub fn payload_offset(&self) -> usize {
self.header_length()
}
#[inline]
pub fn identification(&self) -> u16 {
u16::from_be(self.identification)
}
#[inline]
pub fn tos(&self) -> u8 {
self.tos
}
#[inline]
pub fn fragment_offset(&self) -> u16 {
u16::from_be(self.flags_fragment) & 0x1FFF
}
}
#[inline]
pub fn verify_ipv4_checksum(data: &[u8]) -> Option<bool> {
if data.len() < IPV4_MIN_HEADER_LEN {
return None;
}
let ihl = (data[0] & 0x0F) as usize;
let hdr_len = ihl << 2;
if !(IPV4_MIN_HEADER_LEN..=IPV4_MAX_HEADER_LEN).contains(&hdr_len)
|| !hdr_len.is_multiple_of(2)
|| data.len() < hdr_len
{
return None;
}
let mut tmp = [0u8; IPV4_MAX_HEADER_LEN];
tmp[..hdr_len].copy_from_slice(&data[..hdr_len]);
tmp[10] = 0;
tmp[11] = 0;
let stored = u16::from_be_bytes([data[10], data[11]]);
Some(compute_ipv4_checksum(&tmp[..hdr_len]) == stored)
}
#[inline]
pub fn recompute_ipv4_checksum(data: &mut [u8]) -> bool {
if data.len() < IPV4_MIN_HEADER_LEN {
return false;
}
let ihl = (data[0] & 0x0F) as usize;
let hdr_len = ihl << 2;
if !(IPV4_MIN_HEADER_LEN..=IPV4_MAX_HEADER_LEN).contains(&hdr_len)
|| !hdr_len.is_multiple_of(2)
|| data.len() < hdr_len
{
return false;
}
data[10] = 0;
data[11] = 0;
let computed = compute_ipv4_checksum(&data[..hdr_len]);
data[10..12].copy_from_slice(&computed.to_be_bytes());
true
}
#[inline]
pub fn compute_ipv4_checksum(header: &[u8]) -> u16 {
debug_assert!(header.len().is_multiple_of(2));
let mut sum: u32 = 0;
for pair in header.chunks_exact(2) {
sum = sum.saturating_add(u32::from(u16::from_be_bytes([pair[0], pair[1]])));
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct Ipv6Header {
pub(crate) version_traffic_flow: u32,
pub(crate) payload_length: u16,
pub(crate) next_header: u8,
pub(crate) hop_limit: u8,
pub(crate) src_ip: [u8; 16],
pub(crate) dst_ip: [u8; 16],
}
impl Ipv6Header {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
let hdr = cast::from_bytes::<Self>(data)?;
let version = (u32::from_be(hdr.version_traffic_flow) >> 28) as u8;
if version != 6 {
return Err(NetError::InvalidIpVersion(version));
}
Ok(hdr)
}
#[inline]
pub fn version(&self) -> u8 {
(u32::from_be(self.version_traffic_flow) >> 28) as u8
}
#[inline]
pub fn traffic_class(&self) -> u8 {
(u32::from_be(self.version_traffic_flow) >> 20) as u8
}
#[inline]
pub fn flow_label(&self) -> u32 {
u32::from_be(self.version_traffic_flow) & 0x000FFFFF
}
#[inline]
pub fn payload_length(&self) -> u16 {
u16::from_be(self.payload_length)
}
#[inline]
pub fn next_header(&self) -> u8 {
self.next_header
}
#[inline]
pub fn hop_limit(&self) -> u8 {
self.hop_limit
}
#[inline]
pub fn src_ip(&self) -> [u8; 16] {
self.src_ip
}
#[inline]
pub fn dst_ip(&self) -> [u8; 16] {
self.dst_ip
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct TcpHeader {
pub(crate) src_port: u16,
pub(crate) dst_port: u16,
pub(crate) seq_num: u32,
pub(crate) ack_num: u32,
pub(crate) data_offset_flags: u16,
pub(crate) window_size: u16,
pub(crate) checksum: u16,
pub(crate) urgent_ptr: u16,
}
impl TcpHeader {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
let hdr = cast::from_bytes::<Self>(data)?;
let data_offset = (u16::from_be(hdr.data_offset_flags) >> 12) as u8;
if data_offset < 5 {
return Err(NetError::InvalidPacket {
reason: "TCP data_offset < 5 (header too short)",
});
}
let hdr_len = (data_offset as usize) << 2;
if hdr_len > data.len() {
return Err(NetError::InvalidPacket {
reason: "TCP header length exceeds packet size",
});
}
Ok(hdr)
}
#[inline]
pub fn src_port(&self) -> u16 {
u16::from_be(self.src_port)
}
#[inline]
pub fn dst_port(&self) -> u16 {
u16::from_be(self.dst_port)
}
#[inline]
pub fn seq_num(&self) -> u32 {
u32::from_be(self.seq_num)
}
#[inline]
pub fn ack_num(&self) -> u32 {
u32::from_be(self.ack_num)
}
#[inline]
pub fn data_offset(&self) -> u8 {
(u16::from_be(self.data_offset_flags) >> 12) as u8
}
#[inline]
pub fn flags(&self) -> u16 {
u16::from_be(self.data_offset_flags) & 0x01FF
}
#[inline]
pub fn syn(&self) -> bool {
self.flags() & 0x02 != 0
}
#[inline]
pub fn ack(&self) -> bool {
self.flags() & 0x10 != 0
}
#[inline]
pub fn fin(&self) -> bool {
self.flags() & 0x01 != 0
}
#[inline]
pub fn rst(&self) -> bool {
self.flags() & 0x04 != 0
}
#[inline]
pub fn psh(&self) -> bool {
self.flags() & 0x08 != 0
}
#[inline]
pub fn window_size(&self) -> u16 {
u16::from_be(self.window_size)
}
#[inline]
pub fn checksum(&self) -> u16 {
u16::from_be(self.checksum)
}
#[inline]
pub fn header_length(&self) -> usize {
(self.data_offset() as usize) << 2
}
#[inline]
pub fn payload_offset(&self) -> usize {
self.header_length()
}
#[inline]
pub fn validate_flags(&self) -> bool {
let flags = self.flags();
flags != 0 && flags != 0x1FF
}
#[inline]
pub fn options_bytes<'a>(&self, data: &'a [u8]) -> Option<&'a [u8]> {
let data_offset = (self.data_offset() as usize) * 4;
if data_offset > 20 && data.len() >= data_offset {
Some(&data[20..data_offset])
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct TcpOptionsInfo {
pub option_types: Vec<u8>,
pub mss: Option<u16>,
pub window_scale: Option<u8>,
pub sack_permitted: bool,
pub tsval: Option<u32>,
pub tsecr: Option<u32>,
}
pub fn parse_tcp_options(options: &[u8]) -> TcpOptionsInfo {
let mut info = TcpOptionsInfo {
option_types: Vec::new(),
mss: None,
window_scale: None,
sack_permitted: false,
tsval: None,
tsecr: None,
};
let mut i = 0;
while i < options.len() {
let kind = options[i];
match kind {
0 => break, 1 => { info.option_types.push(1);
i += 1;
continue;
}
_ => {
info.option_types.push(kind);
if i + 1 >= options.len() {
break;
}
let len = options[i + 1] as usize;
if len < 2 || i + len > options.len() {
break;
}
match kind {
2 => { if len >= 4 {
info.mss = Some(u16::from_be_bytes([options[i + 2], options[i + 3]]));
}
}
3 => { if len >= 3 {
info.window_scale = Some(options[i + 2]);
}
}
4 => { info.sack_permitted = true;
}
8 => { if len >= 10 {
info.tsval = Some(u32::from_be_bytes([
options[i + 2], options[i + 3], options[i + 4], options[i + 5]
]));
info.tsecr = Some(u32::from_be_bytes([
options[i + 6], options[i + 7], options[i + 8], options[i + 9]
]));
}
}
_ => {}
}
i += len;
}
}
}
info
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct UdpHeader {
pub(crate) src_port: u16,
pub(crate) dst_port: u16,
pub(crate) length: u16,
pub(crate) checksum: u16,
}
impl UdpHeader {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
let hdr = cast::from_bytes::<Self>(data)?;
let length = u16::from_be(hdr.length);
if (length as usize) < UDP_HEADER_LEN {
return Err(NetError::InvalidPacket {
reason: "UDP length field < 8 (smaller than header)",
});
}
if (length as usize) > data.len() {
return Err(NetError::InvalidPacket {
reason: "UDP length field exceeds buffer size",
});
}
Ok(hdr)
}
#[inline]
pub fn src_port(&self) -> u16 {
u16::from_be(self.src_port)
}
#[inline]
pub fn dst_port(&self) -> u16 {
u16::from_be(self.dst_port)
}
#[inline]
pub fn length(&self) -> u16 {
u16::from_be(self.length)
}
#[inline]
pub fn checksum(&self) -> u16 {
u16::from_be(self.checksum)
}
#[inline]
pub fn payload_len(&self) -> u16 {
let total = self.length();
total.saturating_sub(UDP_HEADER_LEN as u16)
}
#[inline]
pub fn validate_length(&self, actual_data_len: usize) -> bool {
let declared = self.length() as usize;
declared >= UDP_HEADER_LEN && declared <= actual_data_len
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct ArpPacket {
pub(crate) hw_type: u16,
pub(crate) proto_type: u16,
pub(crate) hw_addr_len: u8,
pub(crate) proto_addr_len: u8,
pub(crate) opcode: u16,
pub(crate) sender_hw_addr: [u8; 6],
pub(crate) sender_proto_addr: [u8; 4],
pub(crate) target_hw_addr: [u8; 6],
pub(crate) target_proto_addr: [u8; 4],
}
impl ArpPacket {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
cast::from_bytes(data)
}
#[inline]
pub fn hw_type(&self) -> u16 {
u16::from_be(self.hw_type)
}
#[inline]
pub fn proto_type(&self) -> u16 {
u16::from_be(self.proto_type)
}
#[inline]
pub fn opcode(&self) -> u16 {
u16::from_be(self.opcode)
}
#[inline]
pub fn is_request(&self) -> bool {
self.opcode() == 1
}
#[inline]
pub fn is_reply(&self) -> bool {
self.opcode() == 2
}
#[inline]
pub fn sender_mac(&self) -> [u8; 6] {
self.sender_hw_addr
}
#[inline]
pub fn sender_ip(&self) -> [u8; 4] {
self.sender_proto_addr
}
#[inline]
pub fn target_mac(&self) -> [u8; 6] {
self.target_hw_addr
}
#[inline]
pub fn target_ip(&self) -> [u8; 4] {
self.target_proto_addr
}
#[inline]
pub fn validate(&self) -> bool {
self.hw_type() == 1
&& self.proto_type() == ethertype::IPV4
&& self.hw_addr_len == 6
&& self.proto_addr_len == 4
&& (self.opcode() == 1 || self.opcode() == 2)
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct IcmpHeader {
pub(crate) icmp_type: u8,
pub(crate) code: u8,
pub(crate) checksum: u16,
pub(crate) rest: u32,
}
impl IcmpHeader {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
cast::from_bytes(data)
}
#[inline]
pub fn icmp_type(&self) -> u8 {
self.icmp_type
}
#[inline]
pub fn code(&self) -> u8 {
self.code
}
#[inline]
pub fn checksum(&self) -> u16 {
u16::from_be(self.checksum)
}
#[inline]
pub fn is_echo_reply(&self) -> bool {
self.icmp_type == icmp_type::ECHO_REPLY
}
#[inline]
pub fn is_echo_request(&self) -> bool {
self.icmp_type == icmp_type::ECHO_REQUEST
}
#[inline]
pub fn is_dest_unreachable(&self) -> bool {
self.icmp_type == icmp_type::DEST_UNREACH
}
#[inline]
pub fn is_time_exceeded(&self) -> bool {
self.icmp_type == icmp_type::TIME_EXCEEDED
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct IcmpV6Header {
pub(crate) icmp_type: u8,
pub(crate) code: u8,
pub(crate) checksum: u16,
}
impl IcmpV6Header {
#[inline]
pub fn parse(data: &[u8]) -> Result<&Self> {
cast::from_bytes(data)
}
#[inline]
pub fn icmp_type(&self) -> u8 {
self.icmp_type
}
#[inline]
pub fn code(&self) -> u8 {
self.code
}
#[inline]
pub fn checksum(&self) -> u16 {
u16::from_be(self.checksum)
}
#[inline]
pub fn is_echo_request(&self) -> bool {
self.icmp_type == icmpv6_type::ECHO_REQUEST
}
#[inline]
pub fn is_echo_reply(&self) -> bool {
self.icmp_type == icmpv6_type::ECHO_REPLY
}
#[inline]
pub fn is_dest_unreachable(&self) -> bool {
self.icmp_type == icmpv6_type::DEST_UNREACH
}
#[inline]
pub fn is_neighbor_sol(&self) -> bool {
self.icmp_type == icmpv6_type::NEIGHBOR_SOL
}
#[inline]
pub fn is_neighbor_adv(&self) -> bool {
self.icmp_type == icmpv6_type::NEIGHBOR_ADV
}
#[inline]
pub fn is_router_adv(&self) -> bool {
self.icmp_type == icmpv6_type::ROUTER_ADV
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ipv6ExtType {
HopByHop,
DestOptions,
Routing,
Fragment,
Auth,
Esp,
Mobility,
Hip,
Shim6,
NoNext,
}
impl Ipv6ExtType {
#[inline]
pub fn from_next_header(next: u8) -> Option<Self> {
match next {
0 => Some(Self::HopByHop),
43 => Some(Self::Routing),
44 => Some(Self::Fragment),
50 => Some(Self::Esp),
51 => Some(Self::Auth),
60 => Some(Self::DestOptions),
135 => Some(Self::Mobility),
139 => Some(Self::Hip),
140 => Some(Self::Shim6),
59 => Some(Self::NoNext),
_ => None,
}
}
#[inline]
pub fn is_parseable(next: u8) -> bool {
Self::from_next_header(next).is_some()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ParsedPacket<'a> {
pub eth: &'a EthHeader,
pub vlan: Option<&'a VlanTag>,
pub ip_version: IpVersion,
pub ipv4: Option<&'a Ipv4Header>,
pub ipv6: Option<&'a Ipv6Header>,
pub arp: Option<&'a ArpPacket>,
pub l4_proto: L4Protocol,
pub tcp: Option<&'a TcpHeader>,
pub udp: Option<&'a UdpHeader>,
pub icmp: Option<&'a IcmpHeader>,
pub icmpv6: Option<&'a IcmpV6Header>,
pub raw: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IpVersion {
V4,
V6,
Unknown,
}
#[inline]
pub fn parse_packet(data: &[u8]) -> Result<ParsedPacket<'_>> {
parse_packet_with_budget(data, MAX_PARSE_STEPS)
}
#[inline]
pub fn parse_packet_with_budget(data: &[u8], max_steps: u32) -> Result<ParsedPacket<'_>> {
let budget = if max_steps == 0 { MAX_PARSE_STEPS } else { max_steps };
let steps = std::cell::Cell::new(0);
parse_packet_with_budget_inner(data, &steps, budget)
}
#[inline]
fn parse_packet_with_budget_inner<'a>(
data: &'a [u8],
steps: &std::cell::Cell<u32>,
budget: u32,
) -> Result<ParsedPacket<'a>> {
steps.set(steps.get() + 1);
if steps.get() > budget {
return Err(NetError::InvalidPacket {
reason: "parse step budget exceeded",
});
}
let eth = EthHeader::parse(data)?;
let mut offset = ETH_HEADER_LEN;
let mut vlan = None;
steps.set(steps.get() + 1);
if steps.get() > budget {
return Err(NetError::InvalidPacket {
reason: "parse step budget exceeded",
});
}
let mut ethertype = eth.ethertype();
if ethertype == ethertype::VLAN {
if data.len() < offset + VLAN_TAG_LEN {
return Err(NetError::PacketTooShort {
need: offset + VLAN_TAG_LEN,
got: data.len(),
});
}
let vlan_tag = VlanTag::parse(&data[offset..])?;
vlan = Some(vlan_tag);
ethertype = vlan_tag.ethertype();
offset += VLAN_TAG_LEN;
}
steps.set(steps.get() + 1);
if steps.get() > budget {
return Err(NetError::InvalidPacket {
reason: "parse step budget exceeded",
});
}
match ethertype {
ethertype::IPV4 => {
let result = parse_ipv4(&data[offset..], steps, budget)?;
Ok(ParsedPacket {
eth,
vlan,
ip_version: result.0,
ipv4: result.1,
ipv6: result.2,
arp: None,
l4_proto: result.3,
tcp: result.4,
udp: result.5,
icmp: result.6,
icmpv6: result.7,
raw: data,
})
}
ethertype::IPV6 => {
let result = parse_ipv6(&data[offset..], steps, budget)?;
Ok(ParsedPacket {
eth,
vlan,
ip_version: result.0,
ipv4: result.1,
ipv6: result.2,
arp: None,
l4_proto: result.3,
tcp: result.4,
udp: result.5,
icmp: result.6,
icmpv6: result.7,
raw: data,
})
}
ethertype::ARP => {
if data.len() < offset + ARP_PACKET_LEN {
return Err(NetError::PacketTooShort {
need: offset + ARP_PACKET_LEN,
got: data.len(),
});
}
let arp = ArpPacket::parse(&data[offset..])?;
Ok(ParsedPacket {
eth,
vlan,
ip_version: IpVersion::V4,
ipv4: None,
ipv6: None,
arp: Some(arp),
l4_proto: L4Protocol::Arp,
tcp: None,
udp: None,
icmp: None,
icmpv6: None,
raw: data,
})
}
other => Err(NetError::InvalidEtherType(other)),
}
}
type L4Result<'a> = (
IpVersion,
Option<&'a Ipv4Header>,
Option<&'a Ipv6Header>,
L4Protocol,
Option<&'a TcpHeader>,
Option<&'a UdpHeader>,
Option<&'a IcmpHeader>,
Option<&'a IcmpV6Header>,
);
#[inline]
fn parse_ipv4<'a>(
data: &'a [u8],
steps: &std::cell::Cell<u32>,
budget: u32,
) -> Result<L4Result<'a>> {
steps.set(steps.get() + 1);
if steps.get() > budget {
return Err(NetError::InvalidPacket {
reason: "parse step budget exceeded",
});
}
let ipv4 = Ipv4Header::parse(data)?;
let ip_hdr_len = ipv4.header_length();
let l4_data_start = ip_hdr_len;
if data.len() < l4_data_start {
return Err(NetError::PacketTooShort {
need: l4_data_start,
got: data.len(),
});
}
let total_len = ipv4.total_length() as usize;
if total_len > data.len() {
return Err(NetError::InvalidPacket {
reason: "IPv4 total_length exceeds buffer size (RFC 791 violation)",
});
}
let (l4_proto, tcp_hdr, udp_hdr, icmp_hdr, icmpv6_hdr) = match ipv4.protocol() {
ip_proto::TCP => {
let need = l4_data_start.checked_add(TCP_MIN_HEADER_LEN).ok_or({
NetError::InvalidPacket {
reason: "IPv4 L4 offset + TCP header length overflow",
}
})?;
if total_len >= need {
let tcp = TcpHeader::parse(&data[l4_data_start..total_len]).ok();
(L4Protocol::Tcp, tcp, None, None, None)
} else {
(L4Protocol::Tcp, None, None, None, None)
}
}
ip_proto::UDP => {
let need = l4_data_start.checked_add(UDP_HEADER_LEN).ok_or({
NetError::InvalidPacket {
reason: "IPv4 L4 offset + UDP header length overflow",
}
})?;
if total_len >= need {
let udp = UdpHeader::parse(&data[l4_data_start..total_len]).ok();
(L4Protocol::Udp, None, udp, None, None)
} else {
(L4Protocol::Udp, None, None, None, None)
}
}
ip_proto::ICMP => {
let need = l4_data_start.checked_add(ICMP_MIN_HEADER_LEN).ok_or({
NetError::InvalidPacket {
reason: "IPv4 L4 offset + ICMP header length overflow",
}
})?;
if total_len >= need {
let icmp = IcmpHeader::parse(&data[l4_data_start..total_len]).ok();
(L4Protocol::Icmp, None, None, icmp, None)
} else {
(L4Protocol::Icmp, None, None, None, None)
}
}
other => (L4Protocol::Other(other), None, None, None, None),
};
Ok((IpVersion::V4, Some(ipv4), None, l4_proto, tcp_hdr, udp_hdr, icmp_hdr, icmpv6_hdr))
}
#[inline]
fn parse_ipv6<'a>(
data: &'a [u8],
steps: &std::cell::Cell<u32>,
budget: u32,
) -> Result<L4Result<'a>> {
steps.set(steps.get() + 1);
if steps.get() > budget {
return Err(NetError::InvalidPacket {
reason: "parse step budget exceeded",
});
}
let ipv6 = Ipv6Header::parse(data)?;
let l4_data_start = IPV6_HEADER_LEN;
if data.len() < l4_data_start {
return Err(NetError::PacketTooShort {
need: l4_data_start,
got: data.len(),
});
}
let payload_len = ipv6.payload_length() as usize;
let ip_total_len = IPV6_HEADER_LEN.checked_add(payload_len).ok_or({
NetError::InvalidPacket {
reason: "IPv6 header length + payload_length overflow",
}
})?;
if ip_total_len > data.len() {
return Err(NetError::InvalidPacket {
reason: "IPv6 payload_length exceeds buffer size (RFC 8200 violation)",
});
}
let mut next_header = ipv6.next_header();
let mut effective_offset = l4_data_start;
loop {
steps.set(steps.get() + 1);
if steps.get() > budget {
return Err(NetError::InvalidPacket {
reason: "IPv6 extension header chain exceeds parse step budget",
});
}
let ext_type = Ipv6ExtType::from_next_header(next_header);
match ext_type {
Some(Ipv6ExtType::NoNext) => {
return Ok((
IpVersion::V6,
None,
Some(ipv6),
L4Protocol::Other(next_header),
None,
None,
None,
None,
));
}
Some(Ipv6ExtType::HopByHop) | Some(Ipv6ExtType::DestOptions) | Some(Ipv6ExtType::Routing) => {
let need = effective_offset.checked_add(2).ok_or({
NetError::InvalidPacket { reason: "IPv6 ext header: offset+2 overflow" }
})?;
if ip_total_len < need {
return Err(NetError::PacketTooShort {
need,
got: ip_total_len,
});
}
let len_field = data[effective_offset + 1] as usize;
let ext_bytes = len_field.checked_add(1).and_then(|v| v.checked_mul(8)).ok_or({
NetError::InvalidPacket { reason: "IPv6 ext header length overflow" }
})?;
effective_offset = effective_offset.checked_add(ext_bytes).ok_or({
NetError::InvalidPacket { reason: "IPv6 ext header skip offset overflow" }
})?;
if ip_total_len < effective_offset {
return Err(NetError::InvalidPacket {
reason: "IPv6 ext header exceeds declared packet length",
});
}
next_header = data[effective_offset - ext_bytes];
}
Some(Ipv6ExtType::Fragment) => {
let need = effective_offset.checked_add(8).ok_or({
NetError::InvalidPacket { reason: "IPv6 Fragment header offset+8 overflow" }
})?;
if ip_total_len < need {
return Err(NetError::PacketTooShort {
need,
got: ip_total_len,
});
}
next_header = data[effective_offset];
effective_offset = need;
}
Some(Ipv6ExtType::Auth) => {
let need = effective_offset.checked_add(2).ok_or({
NetError::InvalidPacket { reason: "IPv6 Auth header offset+2 overflow" }
})?;
if ip_total_len < need {
return Err(NetError::PacketTooShort {
need,
got: ip_total_len,
});
}
let len_field = data[effective_offset + 1] as usize;
let auth_bytes = len_field.checked_mul(4).and_then(|v| v.checked_add(12)).ok_or({
NetError::InvalidPacket { reason: "IPv6 Auth header length overflow" }
})?;
effective_offset = effective_offset.checked_add(auth_bytes).ok_or({
NetError::InvalidPacket { reason: "IPv6 Auth header skip offset overflow" }
})?;
if ip_total_len < effective_offset {
return Err(NetError::InvalidPacket {
reason: "IPv6 Auth header exceeds declared packet length",
});
}
next_header = data[effective_offset - auth_bytes];
}
Some(Ipv6ExtType::Esp) => {
break;
}
Some(Ipv6ExtType::Mobility) | Some(Ipv6ExtType::Hip) | Some(Ipv6ExtType::Shim6) => {
let need = effective_offset.checked_add(2).ok_or({
NetError::InvalidPacket { reason: "IPv6 Mobility/HIP/Shim6 header offset+2 overflow" }
})?;
if ip_total_len < need {
return Err(NetError::PacketTooShort {
need,
got: ip_total_len,
});
}
let len_field = data[effective_offset + 1] as usize;
let ext_bytes = len_field.checked_add(1).and_then(|v| v.checked_mul(8)).ok_or({
NetError::InvalidPacket { reason: "IPv6 Mobility/HIP/Shim6 header length overflow" }
})?;
effective_offset = effective_offset.checked_add(ext_bytes).ok_or({
NetError::InvalidPacket { reason: "IPv6 Mobility/HIP/Shim6 skip offset overflow" }
})?;
if ip_total_len < effective_offset {
return Err(NetError::InvalidPacket {
reason: "IPv6 Mobility/HIP/Shim6 exceeds declared packet length",
});
}
next_header = data[effective_offset - ext_bytes];
}
None => break,
}
}
if ip_total_len < effective_offset {
return Err(NetError::InvalidPacket {
reason: "IPv6 effective offset exceeds declared packet length",
});
}
let (l4_proto, tcp_hdr, udp_hdr, icmp_hdr, icmpv6_hdr) = match next_header {
ip_proto::TCP => {
let need = effective_offset.checked_add(TCP_MIN_HEADER_LEN).ok_or({
NetError::InvalidPacket {
reason: "IPv6 L4 offset + TCP header length overflow",
}
})?;
if ip_total_len >= need {
let tcp = TcpHeader::parse(&data[effective_offset..ip_total_len]).ok();
(L4Protocol::Tcp, tcp, None, None, None)
} else {
(L4Protocol::Tcp, None, None, None, None)
}
}
ip_proto::UDP => {
let need = effective_offset.checked_add(UDP_HEADER_LEN).ok_or({
NetError::InvalidPacket {
reason: "IPv6 L4 offset + UDP header length overflow",
}
})?;
if ip_total_len >= need {
let udp = UdpHeader::parse(&data[effective_offset..ip_total_len]).ok();
(L4Protocol::Udp, None, udp, None, None)
} else {
(L4Protocol::Udp, None, None, None, None)
}
}
ip_proto::ICMPV6 => {
let need = effective_offset.checked_add(ICMPV6_MIN_HEADER_LEN).ok_or({
NetError::InvalidPacket {
reason: "IPv6 L4 offset + ICMPv6 header length overflow",
}
})?;
if ip_total_len >= need {
let icmpv6 = IcmpV6Header::parse(&data[effective_offset..ip_total_len]).ok();
(L4Protocol::IcmpV6, None, None, None, icmpv6)
} else {
(L4Protocol::IcmpV6, None, None, None, None)
}
}
other => (L4Protocol::Other(other), None, None, None, None),
};
Ok((IpVersion::V6, None, Some(ipv6), l4_proto, tcp_hdr, udp_hdr, icmp_hdr, icmpv6_hdr))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum L4Protocol {
Tcp,
Udp,
Icmp,
IcmpV6,
Arp,
Other(u8),
}
impl L4Protocol {
#[inline]
pub fn is_tcp(&self) -> bool {
matches!(self, Self::Tcp)
}
#[inline]
pub fn is_udp(&self) -> bool {
matches!(self, Self::Udp)
}
#[inline]
pub fn is_icmp(&self) -> bool {
matches!(self, Self::Icmp)
}
#[inline]
pub fn is_icmpv6(&self) -> bool {
matches!(self, Self::IcmpV6)
}
#[inline]
pub fn is_arp(&self) -> bool {
matches!(self, Self::Arp)
}
#[inline]
pub fn is_supported(&self) -> bool {
matches!(self, Self::Tcp | Self::Udp)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eth_header_parse() {
let data = [
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x08, 0x00, ];
let eth = EthHeader::parse(&data).unwrap();
assert_eq!(eth.ethertype(), ethertype::IPV4);
assert_eq!(eth.dst_mac(), [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
}
#[test]
fn test_eth_header_too_short() {
let data = [0x00; 10];
let result = EthHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv4_header_parse() {
let mut data = [0u8; 20];
data[0] = 0x45; data[1] = 0x00; data[2] = 0x00;
data[3] = 0x28; data[8] = 64; data[9] = ip_proto::TCP; data[12] = 10;
data[13] = 0;
data[14] = 0;
data[15] = 1; data[16] = 10;
data[17] = 0;
data[18] = 0;
data[19] = 2;
let ipv4 = Ipv4Header::parse(&data).unwrap();
assert_eq!(ipv4.version(), 4);
assert_eq!(ipv4.protocol(), ip_proto::TCP);
assert_eq!(ipv4.ttl(), 64);
assert_eq!(ipv4.total_length(), 40);
assert_eq!(ipv4.src_ip(), [10, 0, 0, 1]);
assert_eq!(ipv4.dst_ip(), [10, 0, 0, 2]);
}
#[test]
fn test_ipv4_checksum_verify() {
let mut data = [0u8; 20];
data[0] = 0x45;
data[1] = 0x00;
data[2] = 0x00;
data[3] = 0x28;
data[8] = 64;
data[9] = ip_proto::TCP;
data[12] = 10;
data[13] = 0;
data[14] = 0;
data[15] = 1;
data[16] = 10;
data[17] = 0;
data[18] = 0;
data[19] = 2;
let checksum_val: u16 = 0x66CE;
data[10] = (checksum_val >> 8) as u8;
data[11] = (checksum_val & 0xFF) as u8;
let ipv4 = Ipv4Header::parse(&data).unwrap();
assert!(verify_ipv4_checksum(&data).unwrap());
assert_eq!(ipv4.ihl(), 5);
}
#[test]
fn test_tcp_header_parse() {
let mut data = [0u8; 20];
data[0] = 0x30;
data[1] = 0x39;
data[2] = 0x00;
data[3] = 0x50;
data[4] = 0x00;
data[5] = 0x00;
data[6] = 0x00;
data[7] = 0x01;
data[12] = 0x50;
data[13] = 0x02;
let tcp = TcpHeader::parse(&data).unwrap();
assert_eq!(tcp.src_port(), 12345);
assert_eq!(tcp.dst_port(), 80);
assert!(tcp.syn());
assert!(!tcp.ack());
assert_eq!(tcp.data_offset(), 5);
assert!(tcp.validate_flags());
}
#[test]
fn test_tcp_flags_validation() {
let mut data = [0u8; 20];
data[12] = 0x50;
data[13] = 0x00;
let tcp = TcpHeader::parse(&data).unwrap();
assert!(!tcp.validate_flags());
data[12] = 0x51; data[13] = 0xFF; let tcp = TcpHeader::parse(&data).unwrap();
assert!(!tcp.validate_flags());
data[12] = 0x50;
data[13] = 0x12;
let tcp = TcpHeader::parse(&data).unwrap();
assert!(tcp.validate_flags());
}
#[test]
fn test_udp_header_parse() {
let mut data = [0u8; 16];
data[0] = 0x00;
data[1] = 0x35;
data[2] = 0x30;
data[3] = 0x39;
data[4] = 0x00;
data[5] = 0x10;
let udp = UdpHeader::parse(&data).unwrap();
assert_eq!(udp.src_port(), 53);
assert_eq!(udp.dst_port(), 12345);
assert_eq!(udp.length(), 16);
assert_eq!(udp.payload_len(), 8);
assert!(udp.validate_length(100));
}
#[test]
fn test_arp_packet_parse() {
let mut data = [0u8; 28];
data[0] = 0x00;
data[1] = 0x01;
data[2] = 0x08;
data[3] = 0x00;
data[4] = 0x06;
data[5] = 0x04;
data[6] = 0x00;
data[7] = 0x01;
data[8..14].copy_from_slice(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
data[14] = 192;
data[15] = 168;
data[16] = 1;
data[17] = 1;
data[18..24].copy_from_slice(&[0x00; 6]);
data[24] = 192;
data[25] = 168;
data[26] = 1;
data[27] = 2;
let arp = ArpPacket::parse(&data).unwrap();
assert_eq!(arp.hw_type(), 1);
assert_eq!(arp.proto_type(), ethertype::IPV4);
assert_eq!(arp.opcode(), 1);
assert!(arp.is_request());
assert!(!arp.is_reply());
assert!(arp.validate());
}
#[test]
fn test_arp_reply() {
let mut data = [0u8; 28];
data[0] = 0x00;
data[1] = 0x01;
data[2] = 0x08;
data[3] = 0x00;
data[4] = 0x06;
data[5] = 0x04;
data[6] = 0x00;
data[7] = 0x02;
data[8..14].copy_from_slice(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
data[14] = 10;
data[15] = 0;
data[16] = 0;
data[17] = 1;
data[18..24].copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
data[24] = 10;
data[25] = 0;
data[26] = 0;
data[27] = 2;
let arp = ArpPacket::parse(&data).unwrap();
assert!(arp.is_reply());
assert!(arp.validate());
}
#[test]
fn test_icmp_header_parse() {
let mut data = [0u8; 8];
data[0] = icmp_type::ECHO_REQUEST;
data[1] = 0;
data[2] = 0;
data[3] = 0;
data[4] = 0x00;
data[5] = 0x01;
data[6] = 0x00;
data[7] = 0x01;
let icmp = IcmpHeader::parse(&data).unwrap();
assert_eq!(icmp.icmp_type(), icmp_type::ECHO_REQUEST);
assert_eq!(icmp.code(), 0);
assert!(icmp.is_echo_request());
assert!(!icmp.is_echo_reply());
}
#[test]
fn test_icmpv6_header_parse() {
let mut data = [0u8; 4];
data[0] = icmpv6_type::ECHO_REQUEST;
data[1] = 0;
data[2] = 0;
data[3] = 0;
let icmpv6 = IcmpV6Header::parse(&data).unwrap();
assert_eq!(icmpv6.icmp_type(), icmpv6_type::ECHO_REQUEST);
assert!(icmpv6.is_echo_request());
assert!(!icmpv6.is_echo_reply());
}
#[test]
fn test_vlan_detection() {
let mut data = [0u8; 18]; data[12] = 0x81;
data[13] = 0x00; data[14] = 0x00;
data[15] = 0x64; data[16] = 0x08;
data[17] = 0x00;
let vlan = VlanTag::parse(&data[14..]).unwrap();
assert_eq!(vlan.vlan_id(), 100);
assert_eq!(vlan.ethertype(), ethertype::IPV4);
}
#[test]
fn test_full_packet_parse_udp() {
let mut data = vec![0u8; 14 + 20 + 8 + 16];
data[12] = 0x08;
data[13] = 0x00;
data[14] = 0x45; data[14 + 9] = ip_proto::UDP; data[14 + 2] = 0x00;
data[14 + 3] = (20 + 8 + 16) as u8; let udp_offset = 14 + 20;
data[udp_offset] = 0x00;
data[udp_offset + 1] = 0x35; data[udp_offset + 2] = 0x00;
data[udp_offset + 3] = 0x35; data[udp_offset + 4] = 0x00;
data[udp_offset + 5] = 24;
let packet = parse_packet(&data).unwrap();
assert_eq!(packet.ip_version, IpVersion::V4);
assert!(packet.l4_proto.is_udp());
assert!(packet.udp.is_some());
}
#[test]
fn test_full_packet_parse_arp() {
let mut data = vec![0u8; 14 + 28];
data[12] = 0x08;
data[13] = 0x06;
let arp_offset = 14;
data[arp_offset] = 0x00;
data[arp_offset + 1] = 0x01;
data[arp_offset + 2] = 0x08;
data[arp_offset + 3] = 0x00;
data[arp_offset + 4] = 0x06;
data[arp_offset + 5] = 0x04;
data[arp_offset + 6] = 0x00;
data[arp_offset + 7] = 0x01;
let packet = parse_packet(&data).unwrap();
assert!(packet.arp.is_some());
let arp = packet.arp.unwrap();
assert!(arp.is_request());
assert_eq!(arp.hw_addr_len, 6);
assert_eq!(arp.proto_addr_len, 4);
}
#[test]
fn test_full_packet_parse_icmp() {
let mut data = vec![0u8; 14 + 20 + 8];
data[12] = 0x08;
data[13] = 0x00;
data[14] = 0x45;
data[14 + 9] = ip_proto::ICMP;
data[14 + 2] = 0x00;
data[14 + 3] = 28; let icmp_offset = 14 + 20;
data[icmp_offset] = icmp_type::ECHO_REQUEST;
data[icmp_offset + 1] = 0;
let packet = parse_packet(&data).unwrap();
assert_eq!(packet.ip_version, IpVersion::V4);
assert!(packet.l4_proto.is_icmp());
assert!(packet.icmp.is_some());
}
#[test]
fn test_packet_too_short() {
let data = [0u8; 5];
let result = parse_packet(&data);
assert!(result.is_err());
}
#[test]
fn test_unknown_ethertype() {
let mut data = [0u8; 14];
data[12] = 0x08;
data[13] = 0x99; let result = parse_packet(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv4_parse_rejects_ihl_too_small() {
let mut data = [0u8; 20];
data[0] = 0x43; let result = Ipv4Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv4_parse_rejects_ihl_zero() {
let mut data = [0u8; 20];
data[0] = 0x40; let result = Ipv4Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv4_parse_rejects_header_exceeds_data() {
let mut data = [0u8; 30];
data[0] = 0x4F; let result = Ipv4Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv4_parse_accepts_min_ihl() {
let mut data = [0u8; 20];
data[0] = 0x45; data[2] = 0x00;
data[3] = 0x14; let result = Ipv4Header::parse(&data);
assert!(result.is_ok());
}
#[test]
fn test_ipv4_parse_accepts_max_ihl() {
let mut data = [0u8; 60];
data[0] = 0x4F; data[2] = 0x00;
data[3] = 0x3C; let result = Ipv4Header::parse(&data);
assert!(result.is_ok());
}
#[test]
fn test_tcp_parse_rejects_data_offset_too_small() {
let mut data = [0u8; 20];
data[12] = 0x30; let result = TcpHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_tcp_parse_rejects_data_offset_zero() {
let mut data = [0u8; 20];
data[12] = 0x00; data[13] = 0x02; let result = TcpHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_tcp_parse_rejects_header_exceeds_data() {
let mut data = [0u8; 20];
data[12] = 0xF0; let result = TcpHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_tcp_parse_accepts_min_data_offset() {
let mut data = [0u8; 20];
data[12] = 0x50; data[13] = 0x02; let result = TcpHeader::parse(&data);
assert!(result.is_ok());
}
#[test]
fn test_udp_parse_rejects_length_too_small() {
let mut data = [0u8; 8];
data[4] = 0x00;
data[5] = 0x04; let result = UdpHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_udp_parse_rejects_length_zero() {
let mut data = [0u8; 8];
data[4] = 0x00;
data[5] = 0x00; let result = UdpHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_udp_parse_accepts_min_length() {
let mut data = [0u8; 8];
data[4] = 0x00;
data[5] = 0x08; let result = UdpHeader::parse(&data);
assert!(result.is_ok());
}
#[test]
fn test_ipv4_parse_rejects_total_length_zero() {
let mut data = [0u8; 20];
data[0] = 0x45; data[2] = 0x00;
data[3] = 0x00; let result = Ipv4Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv4_parse_rejects_total_length_less_than_header() {
let mut data = [0u8; 20];
data[0] = 0x45; data[2] = 0x00;
data[3] = 0x13; let result = Ipv4Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv4_parse_accepts_total_length_equals_header() {
let mut data = [0u8; 20];
data[0] = 0x45;
data[2] = 0x00;
data[3] = 0x14; let result = Ipv4Header::parse(&data);
assert!(result.is_ok());
}
#[test]
fn test_ipv4_parse_accepts_max_ihl_with_matching_total_length() {
let mut data = [0u8; 60];
data[0] = 0x4F; data[2] = 0x00;
data[3] = 0x3C; let result = Ipv4Header::parse(&data);
assert!(result.is_ok());
}
#[test]
fn test_parse_ipv4_rejects_total_length_exceeds_buffer() {
let mut data = [0u8; 30];
data[0] = 0x45; data[2] = 0x00;
data[3] = 0x64; data[9] = ip_proto::TCP;
let steps = std::cell::Cell::new(0);
let result = parse_ipv4(&data, &steps, MAX_PARSE_STEPS);
assert!(result.is_err());
}
#[test]
fn test_parse_ipv4_l4_bounded_by_total_length() {
let mut data = [0u8; 100];
data[0] = 0x45; data[2] = 0x00;
data[3] = 0x1E; data[9] = ip_proto::TCP;
data[20 + 12] = 0x50; data[20 + 13] = 0x02;
let steps = std::cell::Cell::new(0);
let result = parse_ipv4(&data, &steps, MAX_PARSE_STEPS).unwrap();
assert_eq!(result.3, L4Protocol::Tcp);
assert!(result.4.is_none(), "TCP header should not be parsed when total_length < hdr_len + TCP_MIN_HEADER_LEN");
}
#[test]
fn test_parse_ipv4_l4_parsed_when_total_length_sufficient() {
let mut data = [0u8; 100];
data[0] = 0x45;
data[2] = 0x00;
data[3] = 0x28; data[9] = ip_proto::TCP;
data[20 + 12] = 0x50; data[20 + 13] = 0x02;
let steps = std::cell::Cell::new(0);
let result = parse_ipv4(&data, &steps, MAX_PARSE_STEPS).unwrap();
assert_eq!(result.3, L4Protocol::Tcp);
assert!(result.4.is_some(), "TCP header should be parsed when total_length is sufficient");
}
#[test]
fn test_parse_ipv4_total_length_less_than_buffer_with_valid_tcp() {
let mut data = [0u8; 80];
data[0] = 0x45;
data[2] = 0x00;
data[3] = 0x28; data[9] = ip_proto::TCP;
data[20 + 12] = 0x50;
data[20 + 13] = 0x02;
let steps = std::cell::Cell::new(0);
let result = parse_ipv4(&data, &steps, MAX_PARSE_STEPS).unwrap();
assert_eq!(result.3, L4Protocol::Tcp);
assert!(result.4.is_some());
}
#[test]
fn test_parse_ipv6_rejects_payload_length_exceeds_buffer() {
let mut data = [0u8; 60];
data[0] = 0x60; data[4] = 0x00;
data[5] = 0x64; data[6] = ip_proto::TCP; data[7] = 64;
let steps = std::cell::Cell::new(0);
let result = parse_ipv6(&data, &steps, MAX_PARSE_STEPS);
assert!(result.is_err());
}
#[test]
fn test_parse_ipv6_accepts_zero_payload_length() {
let mut data = [0u8; 40];
data[0] = 0x60; data[4] = 0x00;
data[5] = 0x00; data[6] = ip_proto::TCP;
let steps = std::cell::Cell::new(0);
let result = parse_ipv6(&data, &steps, MAX_PARSE_STEPS).unwrap();
assert_eq!(result.0, IpVersion::V6);
assert_eq!(result.3, L4Protocol::Tcp);
assert!(result.4.is_none());
}
#[test]
fn test_parse_ipv6_l4_bounded_by_payload_length() {
let mut data = [0u8; 60];
data[0] = 0x60;
data[4] = 0x00;
data[5] = 0x0A; data[6] = ip_proto::TCP;
data[7] = 64;
data[40 + 12] = 0x50;
data[40 + 13] = 0x02;
let steps = std::cell::Cell::new(0);
let result = parse_ipv6(&data, &steps, MAX_PARSE_STEPS).unwrap();
assert_eq!(result.3, L4Protocol::Tcp);
assert!(result.4.is_none(), "TCP header should not be parsed when payload_length < TCP_MIN_HEADER_LEN");
}
#[test]
fn test_parse_ipv6_l4_parsed_when_payload_length_sufficient() {
let mut data = [0u8; 80];
data[0] = 0x60;
data[4] = 0x00;
data[5] = 0x14; data[6] = ip_proto::TCP;
data[7] = 64;
data[40 + 12] = 0x50;
data[40 + 13] = 0x02;
let steps = std::cell::Cell::new(0);
let result = parse_ipv6(&data, &steps, MAX_PARSE_STEPS).unwrap();
assert_eq!(result.3, L4Protocol::Tcp);
assert!(result.4.is_some());
}
#[test]
fn test_parse_packet_with_ethernet_padding() {
let mut data = [0u8; 60];
data[12] = 0x08;
data[13] = 0x00; data[14] = 0x45; data[14 + 2] = 0x00;
data[14 + 3] = 0x1C; data[14 + 9] = ip_proto::ICMP;
let icmp_offset = 14 + 20;
data[icmp_offset] = icmp_type::ECHO_REQUEST;
data[icmp_offset + 1] = 0;
data[42] = 0xFF;
data[43] = 0xFF;
data[59] = 0xFF;
let packet = parse_packet(&data).unwrap();
assert_eq!(packet.ip_version, IpVersion::V4);
assert!(packet.l4_proto.is_icmp());
assert!(packet.icmp.is_some(), "ICMP header should be parsed within total_length bounds");
}
#[test]
fn test_parse_packet_rejects_oversized_total_length() {
let mut data = [0u8; 50];
data[12] = 0x08;
data[13] = 0x00; data[14] = 0x45;
data[14 + 2] = 0x00;
data[14 + 3] = 0x64; data[14 + 9] = ip_proto::TCP;
let result = parse_packet(&data);
assert!(result.is_err(), "parse_packet should reject packet with total_length > buffer size");
}
#[test]
fn test_parse_packet_rejects_oversized_ipv6_payload_length() {
let mut data = [0u8; 60];
data[12] = 0x86;
data[13] = 0xDD; data[14] = 0x60; data[14 + 4] = 0x00;
data[14 + 5] = 0x64; data[14 + 6] = ip_proto::TCP;
let result = parse_packet(&data);
assert!(result.is_err());
}
#[test]
fn test_parse_packet_vlan_ipv4_respects_total_length() {
let mut data = [0u8; 80];
data[12] = 0x81;
data[13] = 0x00; data[14] = 0x00;
data[15] = 0x64; data[16] = 0x08;
data[17] = 0x00; data[18] = 0x45;
data[18 + 2] = 0x00;
data[18 + 3] = 0x28; data[18 + 9] = ip_proto::TCP;
data[38 + 12] = 0x50; data[38 + 13] = 0x02;
let packet = parse_packet(&data).unwrap();
assert_eq!(packet.ip_version, IpVersion::V4);
assert!(packet.vlan.is_some());
assert_eq!(packet.vlan.unwrap().vlan_id(), 100);
assert!(packet.l4_proto.is_tcp());
assert!(packet.tcp.is_some(), "TCP header should be parsed within total_length bounds");
}
#[test]
fn test_ipv4_checksum_all_zeros() {
let mut data = [0u8; 20];
data[0] = 0x45;
data[2] = 0x00;
data[3] = 0x14;
data[8] = 64;
data[9] = ip_proto::TCP;
let ipv4 = Ipv4Header::parse(&data).unwrap();
assert!(!verify_ipv4_checksum(&data).unwrap());
assert_eq!(ipv4.ihl(), 5);
}
#[test]
fn test_ipv4_checksum_all_ones() {
let mut data = [0xFFu8; 20];
data[0] = 0x45;
data[10] = 0x00;
data[11] = 0x00;
let ipv4 = Ipv4Header::parse(&data).unwrap();
assert!(!verify_ipv4_checksum(&data).unwrap());
assert_eq!(ipv4.ihl(), 5);
}
#[test]
fn test_ipv4_header_too_short() {
let data = [0u8; 10];
let result = Ipv4Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv6_header_too_short() {
let data = [0u8; 20];
let result = Ipv6Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_tcp_header_too_short() {
let data = [0u8; 10];
let result = TcpHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_udp_header_too_short() {
let data = [0u8; 4];
let result = UdpHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_arp_packet_too_short() {
let data = [0u8; 10];
let result = ArpPacket::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_tcp_flags_all_combinations() {
let flags_tests = vec![
(0x02, true, false, false, false, false), (0x10, false, true, false, false, false), (0x01, false, false, true, false, false), (0x04, false, false, false, true, false), (0x08, false, false, false, false, true), (0x12, true, true, false, false, false), (0x11, false, true, true, false, false), (0x18, false, true, false, false, true), ];
for (flags_byte, syn, ack, fin, rst, psh) in flags_tests {
let mut data = [0u8; 20];
data[12] = 0x50;
data[13] = flags_byte;
let tcp = TcpHeader::parse(&data).unwrap();
assert_eq!(tcp.syn(), syn, "SYN flag mismatch for 0x{:02x}", flags_byte);
assert_eq!(tcp.ack(), ack, "ACK flag mismatch for 0x{:02x}", flags_byte);
assert_eq!(tcp.fin(), fin, "FIN flag mismatch for 0x{:02x}", flags_byte);
assert_eq!(tcp.rst(), rst, "RST flag mismatch for 0x{:02x}", flags_byte);
assert_eq!(tcp.psh(), psh, "PSH flag mismatch for 0x{:02x}", flags_byte);
}
}
#[test]
fn test_arp_opcodes() {
let opcodes = vec![1, 2, 3, 4];
for opcode in opcodes {
let mut data = [0u8; 28];
data[0] = 0x00;
data[1] = 0x01;
data[2] = 0x08;
data[3] = 0x00;
data[4] = 0x06;
data[5] = 0x04;
data[6] = (opcode >> 8) as u8;
data[7] = opcode as u8;
let arp = ArpPacket::parse(&data).unwrap();
assert_eq!(arp.opcode(), opcode);
if opcode == 1 {
assert!(arp.is_request());
assert!(!arp.is_reply());
} else if opcode == 2 {
assert!(!arp.is_request());
assert!(arp.is_reply());
} else {
assert!(!arp.is_request());
assert!(!arp.is_reply());
}
}
}
#[test]
fn test_icmp_types() {
let types = vec![0, 3, 5, 8, 11, 13];
for icmp_type in types {
let mut data = [0u8; 8];
data[0] = icmp_type;
let icmp = IcmpHeader::parse(&data).unwrap();
assert_eq!(icmp.icmp_type(), icmp_type);
}
}
#[test]
fn test_icmpv6_types() {
let types = vec![1, 2, 3, 4, 128, 129, 135, 136];
for icmp_type in types {
let mut data = [0u8; 8];
data[0] = icmp_type;
let icmp = IcmpV6Header::parse(&data).unwrap();
assert_eq!(icmp.icmp_type(), icmp_type);
}
}
#[test]
fn test_eth_header_various_ethertypes() {
let ethertypes = vec![
(0x0800, "IPv4"),
(0x86DD, "IPv6"),
(0x0806, "ARP"),
(0x8100, "VLAN"),
(0xFFFF, "Unknown"),
];
for (ethertype, _name) in ethertypes {
let mut data = [0u8; 14];
data[12] = (ethertype >> 8) as u8;
data[13] = (ethertype & 0xFF) as u8;
let eth = EthHeader::parse(&data).unwrap();
assert_eq!(eth.ethertype(), ethertype);
}
}
#[test]
fn test_ip_version_variants() {
let v4 = IpVersion::V4;
let v6 = IpVersion::V6;
let unknown = IpVersion::Unknown;
assert_ne!(v4, v6);
assert_ne!(v4, unknown);
assert_ne!(v6, unknown);
}
#[test]
fn test_l4_protocol_variants() {
let proto = L4Protocol::Tcp;
assert!(proto.is_tcp());
assert!(!proto.is_udp());
assert!(!proto.is_icmp());
let proto = L4Protocol::Udp;
assert!(!proto.is_tcp());
assert!(proto.is_udp());
assert!(!proto.is_icmp());
let proto = L4Protocol::Icmp;
assert!(!proto.is_tcp());
assert!(!proto.is_udp());
assert!(proto.is_icmp());
}
#[test]
fn test_udp_length_validation() {
let mut data = [0u8; 32];
data[4] = 0x00;
data[5] = 0x20;
let udp = UdpHeader::parse(&data).unwrap();
assert_eq!(udp.length(), 32);
assert!(udp.validate_length(100));
assert!(!udp.validate_length(20));
}
#[test]
fn test_ipv4_invalid_version() {
let mut data = [0u8; 20];
data[0] = 0x65;
let result = Ipv4Header::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_ipv6_version_field() {
let mut data = [0u8; 40];
data[0] = 0x60;
let ipv6 = Ipv6Header::parse(&data).unwrap();
assert_eq!(ipv6.version(), 6);
}
#[test]
fn test_truncated_ethernet_header() {
let data = [0u8; 10];
let result = EthHeader::parse(&data);
assert!(result.is_err());
}
#[test]
fn test_parsed_packet_debug() {
let mut data = [0u8; 54];
data[12] = 0x08;
data[13] = 0x00;
data[14] = 0x45;
data[16] = 0x00;
data[17] = 0x28;
data[23] = 6;
data[34] = 0x50;
data[35] = 0x02;
let packet = parse_packet(&data).unwrap();
let debug = format!("{:?}", packet);
assert!(debug.contains("ParsedPacket"));
}
}