Skip to main content

sniffnet_packet_parser/
headers.rs

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