huginn_net/
packet_parser.rs

1/// Packet parsing utilities for different network packet formats
2///
3/// This module provides unified parsing for various network packet formats
4/// from both live network capture and PCAP files:
5/// - Ethernet frames (most common in network interfaces)
6/// - Raw IP packets (tunnels, loopback interfaces)
7/// - NULL datalink packets (specialized capture tools)
8/// - Future packet formats can be added here
9use pnet::packet::ethernet::{EtherTypes, EthernetPacket};
10use pnet::packet::ipv4::Ipv4Packet;
11use pnet::packet::ipv6::Ipv6Packet;
12use tracing::debug;
13
14/// Represents the result of IP packet parsing
15#[derive(Debug)]
16pub enum IpPacket<'a> {
17    /// IPv4 packet data (slice of original packet)
18    Ipv4(&'a [u8]),
19    /// IPv6 packet data (slice of original packet)
20    Ipv6(&'a [u8]),
21    /// No valid IP packet found
22    None,
23}
24
25/// Datalink format types supported
26#[derive(Debug, Clone, Copy, PartialEq)]
27pub enum DatalinkFormat {
28    /// Standard Ethernet frame (14-byte header)
29    Ethernet,
30    /// Raw IP packet (no datalink header)
31    RawIp,
32    /// NULL datalink with 4-byte header (0x1e 0x00 ...)
33    Null,
34}
35
36/// Parse a network packet using multiple format strategies
37///
38/// Tries different parsing strategies in order of likelihood:
39/// 1. Ethernet (most common in network interfaces and PCAPs)
40/// 2. Raw IP (tunnels, loopback interfaces, some PCAPs)
41/// 3. NULL datalink (specialized capture tools)
42///
43/// Works with packets from both live network capture and PCAP files.
44///
45/// # Arguments
46/// * `packet` - Raw packet bytes from network interface or PCAP file
47///
48/// # Returns
49/// * `IpPacket` - The parsed IP packet or None if no valid format found
50pub fn parse_packet(packet: &[u8]) -> IpPacket<'_> {
51    // Strategy 1: Try Ethernet first (most common)
52    if let Some(parsed) = try_ethernet_format(packet) {
53        return parsed;
54    }
55
56    // Strategy 2: Try Raw IP (no Ethernet header)
57    if let Some(parsed) = try_raw_ip_format(packet) {
58        return parsed;
59    }
60
61    // Strategy 3: Try NULL datalink (skip 4-byte header)
62    if let Some(parsed) = try_null_datalink_format(packet) {
63        return parsed;
64    }
65
66    IpPacket::None
67}
68
69/// Try parsing as Ethernet frame
70fn try_ethernet_format(packet: &[u8]) -> Option<IpPacket<'_>> {
71    // Ethernet header is 14 bytes: [6B dst][6B src][2B ethertype]
72    if packet.len() < 14 {
73        return None;
74    }
75
76    let ethernet = EthernetPacket::new(packet)?;
77    let ip_data = &packet[14..]; // Skip 14-byte Ethernet header
78
79    match ethernet.get_ethertype() {
80        EtherTypes::Ipv4 => {
81            if Ipv4Packet::new(ip_data).is_some() {
82                debug!("Parsed Ethernet IPv4 packet");
83                return Some(IpPacket::Ipv4(ip_data));
84            }
85        }
86        EtherTypes::Ipv6 => {
87            if Ipv6Packet::new(ip_data).is_some() {
88                debug!("Parsed Ethernet IPv6 packet");
89                return Some(IpPacket::Ipv6(ip_data));
90            }
91        }
92        _ => {}
93    }
94
95    None
96}
97
98/// Try parsing as Raw IP (no datalink header)
99fn try_raw_ip_format(packet: &[u8]) -> Option<IpPacket<'_>> {
100    if packet.len() < 20 {
101        return None;
102    }
103
104    // Check IP version in first 4 bits
105    let version = (packet[0] & 0xF0) >> 4;
106    match version {
107        4 => {
108            if Ipv4Packet::new(packet).is_some() {
109                debug!("Parsed Raw IPv4 packet");
110                return Some(IpPacket::Ipv4(packet));
111            }
112        }
113        6 => {
114            if Ipv6Packet::new(packet).is_some() {
115                debug!("Parsed Raw IPv6 packet");
116                return Some(IpPacket::Ipv6(packet));
117            }
118        }
119        _ => {}
120    }
121
122    None
123}
124
125/// Try parsing as NULL datalink format (4-byte header)
126fn try_null_datalink_format(packet: &[u8]) -> Option<IpPacket<'_>> {
127    // Check for NULL datalink signature and minimum size
128    if packet.len() < 24 || packet[0] != 0x1e || packet[1] != 0x00 {
129        return None;
130    }
131
132    let ip_data = &packet[4..]; // Skip 4-byte NULL header
133    let version = (ip_data[0] & 0xF0) >> 4;
134
135    match version {
136        4 => {
137            if Ipv4Packet::new(ip_data).is_some() {
138                debug!("Parsed NULL datalink IPv4 packet");
139                return Some(IpPacket::Ipv4(ip_data));
140            }
141        }
142        6 => {
143            if Ipv6Packet::new(ip_data).is_some() {
144                debug!("Parsed NULL datalink IPv6 packet");
145                return Some(IpPacket::Ipv6(ip_data));
146            }
147        }
148        _ => {}
149    }
150
151    None
152}
153
154/// Detect the datalink format of a packet without full parsing
155///
156/// Useful for statistics or format validation
157pub fn detect_datalink_format(packet: &[u8]) -> Option<DatalinkFormat> {
158    // Check NULL datalink first (most specific signature)
159    if packet.len() >= 24 && packet[0] == 0x1e && packet[1] == 0x00 {
160        let ip_data = &packet[4..];
161        let version = (ip_data[0] & 0xF0) >> 4;
162        if version == 4 || version == 6 {
163            return Some(DatalinkFormat::Null);
164        }
165    }
166
167    // Check Raw IP (check if it starts with valid IP version)
168    if packet.len() >= 20 {
169        let version = (packet[0] & 0xF0) >> 4;
170        if version == 4 || version == 6 {
171            // Additional validation for IPv4
172            if version == 4 {
173                let ihl = (packet[0] & 0x0F).saturating_mul(4);
174                if ihl >= 20 && packet.len() >= usize::from(ihl) {
175                    return Some(DatalinkFormat::RawIp);
176                }
177            }
178            // Additional validation for IPv6
179            else if version == 6 && packet.len() >= 40 {
180                return Some(DatalinkFormat::RawIp);
181            }
182        }
183    }
184
185    // Check Ethernet (least specific - needs valid EtherType)
186    if packet.len() >= 14 {
187        if let Some(ethernet) = EthernetPacket::new(packet) {
188            let ethertype = ethernet.get_ethertype();
189            // Only consider it Ethernet if it has a valid IP EtherType
190            if ethertype == EtherTypes::Ipv4 || ethertype == EtherTypes::Ipv6 {
191                let ip_data = &packet[14..];
192                if !ip_data.is_empty() {
193                    let version = (ip_data[0] & 0xF0) >> 4;
194                    if (ethertype == EtherTypes::Ipv4 && version == 4)
195                        || (ethertype == EtherTypes::Ipv6 && version == 6)
196                    {
197                        return Some(DatalinkFormat::Ethernet);
198                    }
199                }
200            }
201        }
202    }
203
204    None
205}