Skip to main content

sniffnet_packet_parser/
headers.rs

1use crate::igmp_type::IgmpType;
2use crate::link_type::LinkType;
3use crate::{ArpType, IcmpType, Protocol};
4use etherparse::{EtherType, LaxPacketHeaders};
5use std::net::IpAddr;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8/// Info extracted from the data link layer header.
9pub struct LinkInfo {
10    /// Source MAC address, if available.
11    pub src_mac: Option<[u8; 6]>,
12    /// Destination MAC address, if available.
13    pub dst_mac: Option<[u8; 6]>,
14    /// Outermost VLAN ID, if the packet is VLAN-tagged.
15    pub vlan_id: Option<u16>,
16    pub(crate) bytes: usize,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20/// Info extracted from the network layer header.
21pub struct NetInfo {
22    /// Source IP address.
23    pub src_ip: IpAddr,
24    /// Destination IP address.
25    pub dst_ip: IpAddr,
26    /// ARP message type, if the packet is an ARP packet.
27    pub arp_type: Option<ArpType>,
28    /// Ethernet type identifying the network layer protocol (IPv4, IPv6, or ARP).
29    pub ether_type: u16,
30    pub(crate) bytes: usize,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34/// Info extracted from the transport layer header.
35pub struct TransportInfo {
36    /// Source port, if the packet is a TCP or UDP packet.
37    pub src_port: Option<u16>,
38    /// Destination port, if the packet is a TCP or UDP packet.
39    pub dst_port: Option<u16>,
40    /// Protocol carried by the packet.
41    pub protocol: Protocol,
42    /// ICMP message type, if the packet is an ICMP packet.
43    pub icmp_type: Option<IcmpType>,
44    /// IGMP message type, if the packet is an IGMP packet.
45    pub igmp_type: Option<IgmpType>,
46}
47
48#[must_use]
49pub(crate) fn get_sniffable_headers(
50    packet: &[u8],
51    link_type: LinkType,
52) -> Option<LaxPacketHeaders<'_>> {
53    match link_type {
54        LinkType::Ethernet(_) | LinkType::Unsupported(_) => {
55            LaxPacketHeaders::from_ethernet(packet).ok()
56        }
57        LinkType::RawIp(_) | LinkType::IPv4(_) | LinkType::IPv6(_) => {
58            LaxPacketHeaders::from_ip(packet).ok()
59        }
60        LinkType::LinuxSll(_) => from_linux_sll(packet, true),
61        LinkType::LinuxSll2(_) => from_linux_sll(packet, false),
62        LinkType::Null(_) | LinkType::Loop(_) => from_null(packet),
63    }
64}
65
66fn from_null(packet: &[u8]) -> Option<LaxPacketHeaders<'_>> {
67    if packet.len() <= 4 {
68        return None;
69    }
70
71    let is_valid_af_inet = {
72        // based on https://wiki.wireshark.org/NullLoopback.md (2023-12-31)
73        fn matches(value: u32) -> bool {
74            match value {
75                // 2 = IPv4 on all platforms
76                // 24, 28, or 30 = IPv6 depending on platform
77                2 | 24 | 28 | 30 => true,
78                _ => false,
79            }
80        }
81        let h = &packet[..4];
82        let b = [h[0], h[1], h[2], h[3]];
83        // check both big endian and little endian representations
84        // as some OS'es use native endianness and others use big endian
85        matches(u32::from_le_bytes(b)) || matches(u32::from_be_bytes(b))
86    };
87
88    if is_valid_af_inet {
89        LaxPacketHeaders::from_ip(&packet[4..]).ok()
90    } else {
91        None
92    }
93}
94
95// TODO: do this with etherparse once they support Linux SLL2
96fn from_linux_sll(packet: &[u8], is_v1: bool) -> Option<LaxPacketHeaders<'_>> {
97    let header_len = if is_v1 { 16 } else { 20 };
98    if packet.len() <= header_len {
99        return None;
100    }
101
102    let protocol_type = u16::from_be_bytes(if is_v1 {
103        [packet[14], packet[15]]
104    } else {
105        [packet[0], packet[1]]
106    });
107    let payload = &packet[header_len..];
108
109    Some(LaxPacketHeaders::from_ether_type(
110        EtherType(protocol_type),
111        payload,
112    ))
113}