packet_strata/packet/
null.rs

1//! BSD Null/Loopback encapsulation header implementation
2//!
3//! The Null/Loopback link type is used by BSD systems (FreeBSD, macOS, etc.)
4//! for the loopback interface. It consists of a 4-byte protocol family field
5//! that indicates the network layer protocol.
6//!
7//! # Header Format (4 bytes)
8//!
9//! ```text
10//!  0                   1                   2                   3
11//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
12//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
13//! |                       Protocol Family                        |
14//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15//! ```
16//!
17//! The protocol family field uses BSD `AF_*` constants in **host byte order**
18//! (little-endian on most systems, but can be big-endian on some).
19//!
20//! # Common Protocol Family Values
21//!
22//! | Value | Protocol |
23//! |-------|----------|
24//! | 2     | AF_INET (IPv4) |
25//! | 24/28/30 | AF_INET6 (IPv6) - varies by OS |
26//!
27//! # Examples
28//!
29//! ## Basic Null header parsing
30//!
31//! ```
32//! use packet_strata::packet::null::NullHeader;
33//! use packet_strata::packet::protocol::EtherProto;
34//! use packet_strata::packet::HeaderParser;
35//!
36//! // Null header with IPv4 payload (little-endian AF_INET = 2)
37//! let packet = vec![
38//!     0x02, 0x00, 0x00, 0x00,  // Protocol family: AF_INET (IPv4)
39//!     // IPv4 payload follows...
40//!     0x45, 0x00, 0x00, 0x28,  // IPv4 header start
41//! ];
42//!
43//! let (header, payload) = NullHeader::from_bytes(&packet).unwrap();
44//! assert_eq!(header.protocol(), EtherProto::IPV4);
45//! assert_eq!(payload.len(), 4);
46//! ```
47//!
48//! ## IPv6 over loopback
49//!
50//! ```
51//! use packet_strata::packet::null::NullHeader;
52//! use packet_strata::packet::protocol::EtherProto;
53//! use packet_strata::packet::HeaderParser;
54//!
55//! // Null header with IPv6 payload (little-endian AF_INET6 = 30 on macOS/FreeBSD)
56//! let packet = vec![
57//!     0x1e, 0x00, 0x00, 0x00,  // Protocol family: AF_INET6 (IPv6) on macOS
58//!     // IPv6 payload follows...
59//! ];
60//!
61//! let (header, payload) = NullHeader::from_bytes(&packet).unwrap();
62//! assert_eq!(header.protocol(), EtherProto::IPV6);
63//! ```
64//!
65//! # References
66//!
67//! - https://www.tcpdump.org/linktypes/LINKTYPE_NULL.html
68//! - BSD socket.h AF_* constants
69
70use std::fmt::{Display, Formatter};
71
72use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
73
74use crate::packet::protocol::EtherProto;
75use crate::packet::{HeaderParser, PacketHeader};
76
77/// BSD Address Family values
78///
79/// These are the `AF_*` constants from BSD systems. Note that the values
80/// can differ between operating systems, particularly for IPv6.
81#[repr(u32)]
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum AddressFamily {
84    /// AF_INET - IPv4
85    Inet = 2,
86    /// AF_INET6 - IPv6 (Linux value)
87    Inet6Linux = 10,
88    /// AF_INET6 - IPv6 (macOS/iOS value)
89    Inet6Darwin = 30,
90    /// AF_INET6 - IPv6 (FreeBSD value)
91    Inet6FreeBsd = 28,
92    /// AF_INET6 - IPv6 (OpenBSD value)
93    Inet6OpenBsd = 24,
94    /// Unknown address family
95    Unknown(u32),
96}
97
98impl From<u32> for AddressFamily {
99    fn from(value: u32) -> Self {
100        match value {
101            2 => AddressFamily::Inet,
102            10 => AddressFamily::Inet6Linux,
103            24 => AddressFamily::Inet6OpenBsd,
104            28 => AddressFamily::Inet6FreeBsd,
105            30 => AddressFamily::Inet6Darwin,
106            other => AddressFamily::Unknown(other),
107        }
108    }
109}
110
111impl AddressFamily {
112    /// Check if this address family represents IPv6
113    pub fn is_ipv6(&self) -> bool {
114        matches!(
115            self,
116            AddressFamily::Inet6Linux
117                | AddressFamily::Inet6Darwin
118                | AddressFamily::Inet6FreeBsd
119                | AddressFamily::Inet6OpenBsd
120        )
121    }
122
123    /// Convert to EtherProto for compatibility with the rest of the stack
124    pub fn to_ether_proto(&self) -> EtherProto {
125        match self {
126            AddressFamily::Inet => EtherProto::IPV4,
127            AddressFamily::Inet6Linux
128            | AddressFamily::Inet6Darwin
129            | AddressFamily::Inet6FreeBsd
130            | AddressFamily::Inet6OpenBsd => EtherProto::IPV6,
131            AddressFamily::Unknown(_) => EtherProto::from(0u16),
132        }
133    }
134}
135
136impl Display for AddressFamily {
137    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
138        match self {
139            AddressFamily::Inet => write!(f, "AF_INET"),
140            AddressFamily::Inet6Linux => write!(f, "AF_INET6 (Linux)"),
141            AddressFamily::Inet6Darwin => write!(f, "AF_INET6 (Darwin)"),
142            AddressFamily::Inet6FreeBsd => write!(f, "AF_INET6 (FreeBSD)"),
143            AddressFamily::Inet6OpenBsd => write!(f, "AF_INET6 (OpenBSD)"),
144            AddressFamily::Unknown(v) => write!(f, "Unknown({})", v),
145        }
146    }
147}
148
149/// BSD Null/Loopback header
150///
151/// This is the link-layer header used on BSD loopback interfaces.
152/// It consists of a single 4-byte field containing the address family
153/// in host byte order.
154///
155/// # Wire Format
156///
157/// The header is 4 bytes containing the protocol family in host byte order.
158/// Since most systems are little-endian, we parse it as little-endian by default,
159/// but also check for big-endian encoding for portability.
160#[repr(C)]
161#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
162pub struct NullHeader {
163    /// Protocol family in host byte order (typically little-endian)
164    family: [u8; 4],
165}
166
167impl NullHeader {
168    /// Header size in bytes
169    pub const SIZE: usize = 4;
170
171    /// Get the raw protocol family value (little-endian interpretation)
172    #[inline]
173    pub fn family_raw_le(&self) -> u32 {
174        u32::from_le_bytes(self.family)
175    }
176
177    /// Get the raw protocol family value (big-endian interpretation)
178    #[inline]
179    pub fn family_raw_be(&self) -> u32 {
180        u32::from_be_bytes(self.family)
181    }
182
183    /// Get the address family
184    ///
185    /// This method tries to detect the byte order by checking if the value
186    /// makes sense as a known address family. It first tries little-endian
187    /// (most common), then big-endian.
188    #[inline]
189    pub fn address_family(&self) -> AddressFamily {
190        let le_value = self.family_raw_le();
191        let be_value = self.family_raw_be();
192
193        // Try little-endian first (most common)
194        match le_value {
195            2 | 10 | 24 | 28 | 30 => AddressFamily::from(le_value),
196            _ => {
197                // Try big-endian
198                match be_value {
199                    2 | 10 | 24 | 28 | 30 => AddressFamily::from(be_value),
200                    // Default to little-endian interpretation
201                    _ => AddressFamily::from(le_value),
202                }
203            }
204        }
205    }
206
207    /// Get the protocol as EtherProto for compatibility with the iterator
208    #[inline]
209    pub fn protocol(&self) -> EtherProto {
210        self.address_family().to_ether_proto()
211    }
212}
213
214impl PacketHeader for NullHeader {
215    const NAME: &'static str = "Null/Loopback";
216    type InnerType = EtherProto;
217
218    #[inline]
219    fn inner_type(&self) -> Self::InnerType {
220        self.protocol()
221    }
222}
223
224impl HeaderParser for NullHeader {
225    type Output<'a> = &'a NullHeader;
226
227    fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
228        header
229    }
230}
231
232impl Display for NullHeader {
233    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
234        write!(
235            f,
236            "Null family={} (0x{:08x})",
237            self.address_family(),
238            self.family_raw_le()
239        )
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_null_header_size() {
249        assert_eq!(std::mem::size_of::<NullHeader>(), 4);
250        assert_eq!(NullHeader::SIZE, 4);
251    }
252
253    #[test]
254    fn test_null_header_ipv4_le() {
255        // Little-endian AF_INET = 2
256        let packet = vec![0x02, 0x00, 0x00, 0x00, 0x45, 0x00]; // + some IPv4 data
257
258        let (header, payload) = NullHeader::from_bytes(&packet).unwrap();
259
260        assert_eq!(header.family_raw_le(), 2);
261        assert_eq!(header.address_family(), AddressFamily::Inet);
262        assert_eq!(header.protocol(), EtherProto::IPV4);
263        assert_eq!(payload.len(), 2);
264    }
265
266    #[test]
267    fn test_null_header_ipv6_darwin() {
268        // Little-endian AF_INET6 on macOS = 30
269        let packet = vec![0x1e, 0x00, 0x00, 0x00, 0x60, 0x00]; // + some IPv6 data
270
271        let (header, payload) = NullHeader::from_bytes(&packet).unwrap();
272
273        assert_eq!(header.family_raw_le(), 30);
274        assert_eq!(header.address_family(), AddressFamily::Inet6Darwin);
275        assert!(header.address_family().is_ipv6());
276        assert_eq!(header.protocol(), EtherProto::IPV6);
277        assert_eq!(payload.len(), 2);
278    }
279
280    #[test]
281    fn test_null_header_ipv6_linux() {
282        // Little-endian AF_INET6 on Linux = 10
283        let packet = vec![0x0a, 0x00, 0x00, 0x00];
284
285        let (header, _payload) = NullHeader::from_bytes(&packet).unwrap();
286
287        assert_eq!(header.family_raw_le(), 10);
288        assert_eq!(header.address_family(), AddressFamily::Inet6Linux);
289        assert!(header.address_family().is_ipv6());
290        assert_eq!(header.protocol(), EtherProto::IPV6);
291    }
292
293    #[test]
294    fn test_null_header_ipv6_freebsd() {
295        // Little-endian AF_INET6 on FreeBSD = 28
296        let packet = vec![0x1c, 0x00, 0x00, 0x00];
297
298        let (header, _payload) = NullHeader::from_bytes(&packet).unwrap();
299
300        assert_eq!(header.family_raw_le(), 28);
301        assert_eq!(header.address_family(), AddressFamily::Inet6FreeBsd);
302        assert!(header.address_family().is_ipv6());
303    }
304
305    #[test]
306    fn test_null_header_ipv6_openbsd() {
307        // Little-endian AF_INET6 on OpenBSD = 24
308        let packet = vec![0x18, 0x00, 0x00, 0x00];
309
310        let (header, _payload) = NullHeader::from_bytes(&packet).unwrap();
311
312        assert_eq!(header.family_raw_le(), 24);
313        assert_eq!(header.address_family(), AddressFamily::Inet6OpenBsd);
314        assert!(header.address_family().is_ipv6());
315    }
316
317    #[test]
318    fn test_null_header_ipv4_be() {
319        // Big-endian AF_INET = 2 (some old captures might have this)
320        let packet = vec![0x00, 0x00, 0x00, 0x02];
321
322        let (header, _payload) = NullHeader::from_bytes(&packet).unwrap();
323
324        // Should detect big-endian and return correct family
325        assert_eq!(header.address_family(), AddressFamily::Inet);
326        assert_eq!(header.protocol(), EtherProto::IPV4);
327    }
328
329    #[test]
330    fn test_null_header_unknown() {
331        // Unknown address family
332        let packet = vec![0xff, 0x00, 0x00, 0x00];
333
334        let (header, _payload) = NullHeader::from_bytes(&packet).unwrap();
335
336        assert!(matches!(
337            header.address_family(),
338            AddressFamily::Unknown(255)
339        ));
340        assert_eq!(header.protocol(), EtherProto::from(0u16));
341    }
342
343    #[test]
344    fn test_null_header_too_short() {
345        let packet = vec![0x02, 0x00, 0x00]; // Only 3 bytes
346
347        let result = NullHeader::from_bytes(&packet);
348        assert!(result.is_err());
349    }
350
351    #[test]
352    fn test_null_header_display() {
353        let packet = vec![0x02, 0x00, 0x00, 0x00];
354        let (header, _) = NullHeader::from_bytes(&packet).unwrap();
355
356        let display = format!("{}", header);
357        assert!(display.contains("Null"));
358        assert!(display.contains("family="));
359        assert!(display.contains("AF_INET"));
360    }
361
362    #[test]
363    fn test_address_family_display() {
364        assert_eq!(format!("{}", AddressFamily::Inet), "AF_INET");
365        assert_eq!(format!("{}", AddressFamily::Inet6Linux), "AF_INET6 (Linux)");
366        assert_eq!(
367            format!("{}", AddressFamily::Inet6Darwin),
368            "AF_INET6 (Darwin)"
369        );
370        assert_eq!(format!("{}", AddressFamily::Unknown(99)), "Unknown(99)");
371    }
372
373    #[test]
374    fn test_packet_header_trait() {
375        let packet = vec![0x02, 0x00, 0x00, 0x00];
376        let (header, _) = NullHeader::from_bytes(&packet).unwrap();
377
378        assert_eq!(NullHeader::NAME, "Null/Loopback");
379        assert_eq!(header.inner_type(), EtherProto::IPV4);
380    }
381}