pcapsql_core/protocol/
linux_sll.rs

1//! Linux SLL (Sockaddr Link Layer) protocol parser.
2//!
3//! Parses Linux cooked capture headers (LINKTYPE_LINUX_SLL = 113).
4//! This format is used when capturing on the "any" interface or for
5//! protocols that don't have a native link-layer header.
6
7use smallvec::SmallVec;
8
9use super::{FieldValue, ParseContext, ParseResult, Protocol};
10use crate::schema::{DataKind, FieldDescriptor};
11
12/// PCAP link type for Linux SLL captures.
13pub const LINKTYPE_LINUX_SLL: u16 = 113;
14
15/// Linux SLL header length in bytes.
16pub const LINUX_SLL_HEADER_LEN: usize = 16;
17
18/// ARPHRD types (link layer hardware types).
19pub mod arphrd {
20    pub const ETHER: u16 = 1;
21    pub const LOOPBACK: u16 = 772;
22    pub const FRAD: u16 = 770;
23    pub const IPGRE: u16 = 778;
24    pub const IEEE80211_RADIOTAP: u16 = 803;
25    pub const IP6GRE: u16 = 823;
26    pub const NETLINK: u16 = 824;
27}
28
29/// Packet type values.
30pub mod packet_type {
31    pub const HOST: u16 = 0; // Packet was sent to us
32    pub const BROADCAST: u16 = 1; // Broadcast by another host
33    pub const MULTICAST: u16 = 2; // Multicast by another host
34    pub const OTHERHOST: u16 = 3; // Sent to someone else
35    pub const OUTGOING: u16 = 4; // Sent by us
36}
37
38/// Get the name of a packet type.
39fn packet_type_name(pkt_type: u16) -> &'static str {
40    match pkt_type {
41        packet_type::HOST => "HOST",
42        packet_type::BROADCAST => "BROADCAST",
43        packet_type::MULTICAST => "MULTICAST",
44        packet_type::OTHERHOST => "OTHERHOST",
45        packet_type::OUTGOING => "OUTGOING",
46        _ => "UNKNOWN",
47    }
48}
49
50/// Get the name of an ARPHRD type.
51fn arphrd_name(arphrd: u16) -> &'static str {
52    match arphrd {
53        arphrd::ETHER => "ETHER",
54        arphrd::LOOPBACK => "LOOPBACK",
55        arphrd::FRAD => "FRAD",
56        arphrd::IPGRE => "IPGRE",
57        arphrd::IEEE80211_RADIOTAP => "IEEE80211_RADIOTAP",
58        arphrd::IP6GRE => "IP6GRE",
59        arphrd::NETLINK => "NETLINK",
60        _ => "UNKNOWN",
61    }
62}
63
64/// Linux SLL protocol parser.
65///
66/// Parses the 16-byte Linux cooked capture header and routes to
67/// appropriate child protocols based on the ARPHRD type and protocol field.
68#[derive(Debug, Clone, Copy)]
69pub struct LinuxSllProtocol;
70
71impl Protocol for LinuxSllProtocol {
72    fn name(&self) -> &'static str {
73        "linux_sll"
74    }
75
76    fn display_name(&self) -> &'static str {
77        "Linux SLL"
78    }
79
80    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
81        // Only parse at root level with LINKTYPE_LINUX_SLL
82        if context.is_root() && context.link_type == LINKTYPE_LINUX_SLL {
83            return Some(100);
84        }
85        None
86    }
87
88    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
89        // Linux SLL header is 16 bytes minimum
90        if data.len() < LINUX_SLL_HEADER_LEN {
91            return ParseResult::error(
92                format!("Linux SLL header too short: {} bytes", data.len()),
93                data,
94            );
95        }
96
97        let mut fields = SmallVec::new();
98
99        // Parse header fields (all big-endian)
100        let pkt_type = u16::from_be_bytes([data[0], data[1]]);
101        let arphrd_type = u16::from_be_bytes([data[2], data[3]]);
102        let addr_len = u16::from_be_bytes([data[4], data[5]]);
103        // addr is bytes 6-13 (8 bytes, but only addr_len are valid)
104        let protocol = u16::from_be_bytes([data[14], data[15]]);
105
106        fields.push(("packet_type", FieldValue::UInt16(pkt_type)));
107        fields.push((
108            "packet_type_name",
109            FieldValue::Str(packet_type_name(pkt_type)),
110        ));
111        fields.push(("arphrd_type", FieldValue::UInt16(arphrd_type)));
112        fields.push(("arphrd_name", FieldValue::Str(arphrd_name(arphrd_type))));
113        fields.push(("addr_len", FieldValue::UInt16(addr_len)));
114
115        // Extract the valid portion of the link-layer address
116        let valid_addr_len = (addr_len as usize).min(8);
117        if valid_addr_len > 0 {
118            let addr_slice = &data[6..6 + valid_addr_len];
119            fields.push(("addr", FieldValue::Bytes(addr_slice)));
120        }
121
122        fields.push(("protocol", FieldValue::UInt16(protocol)));
123
124        // Calculate remaining payload
125        let remaining = &data[LINUX_SLL_HEADER_LEN..];
126
127        // Set child hints based on ARPHRD type
128        let mut child_hints = SmallVec::new();
129
130        match arphrd_type {
131            arphrd::NETLINK => {
132                // For netlink, protocol field is the netlink family
133                child_hints.push(("sll_arphrd", arphrd::NETLINK as u64));
134                child_hints.push(("netlink_family", protocol as u64));
135                child_hints.push(("is_netlink", 1u64));
136            }
137            arphrd::ETHER | arphrd::LOOPBACK => {
138                // For ethernet-like, protocol is the ethertype
139                child_hints.push(("sll_arphrd", arphrd_type as u64));
140                child_hints.push(("ethertype", protocol as u64));
141            }
142            _ => {
143                child_hints.push(("sll_arphrd", arphrd_type as u64));
144                child_hints.push(("sll_protocol", protocol as u64));
145            }
146        }
147
148        ParseResult::success(fields, remaining, child_hints)
149    }
150
151    fn schema_fields(&self) -> Vec<FieldDescriptor> {
152        vec![
153            FieldDescriptor::new("linux_sll.packet_type", DataKind::UInt16).set_nullable(true),
154            FieldDescriptor::new("linux_sll.packet_type_name", DataKind::String).set_nullable(true),
155            FieldDescriptor::new("linux_sll.arphrd_type", DataKind::UInt16).set_nullable(true),
156            FieldDescriptor::new("linux_sll.arphrd_name", DataKind::String).set_nullable(true),
157            FieldDescriptor::new("linux_sll.addr_len", DataKind::UInt16).set_nullable(true),
158            FieldDescriptor::new("linux_sll.addr", DataKind::Binary).set_nullable(true),
159            FieldDescriptor::new("linux_sll.protocol", DataKind::UInt16).set_nullable(true),
160        ]
161    }
162
163    fn child_protocols(&self) -> &[&'static str] {
164        &["netlink", "ethernet", "ipv4", "ipv6"]
165    }
166
167    fn dependencies(&self) -> &'static [&'static str] {
168        &[] // Root protocol - no dependencies
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    /// Helper to create a Linux SLL header (big-endian).
177    fn create_sll_header(
178        pkt_type: u16,
179        arphrd: u16,
180        addr_len: u16,
181        addr: [u8; 8],
182        protocol: u16,
183    ) -> Vec<u8> {
184        let mut header = Vec::with_capacity(16);
185        header.extend_from_slice(&pkt_type.to_be_bytes());
186        header.extend_from_slice(&arphrd.to_be_bytes());
187        header.extend_from_slice(&addr_len.to_be_bytes());
188        header.extend_from_slice(&addr);
189        header.extend_from_slice(&protocol.to_be_bytes());
190        header
191    }
192
193    // ==========================================================================
194    // Test 1: can_parse returns Some for root context with LINKTYPE_LINUX_SLL
195    // ==========================================================================
196    #[test]
197    fn test_can_parse_linux_sll_at_root() {
198        let parser = LinuxSllProtocol;
199        let ctx = ParseContext::new(LINKTYPE_LINUX_SLL);
200
201        assert!(parser.can_parse(&ctx).is_some());
202        assert_eq!(parser.can_parse(&ctx), Some(100));
203    }
204
205    // ==========================================================================
206    // Test 2: can_parse returns None for other link types
207    // ==========================================================================
208    #[test]
209    fn test_cannot_parse_ethernet() {
210        let parser = LinuxSllProtocol;
211        let ctx = ParseContext::new(1); // Ethernet LINKTYPE
212
213        assert!(parser.can_parse(&ctx).is_none());
214    }
215
216    // ==========================================================================
217    // Test 3: can_parse returns None when not at root
218    // ==========================================================================
219    #[test]
220    fn test_cannot_parse_when_not_root() {
221        let parser = LinuxSllProtocol;
222        let mut ctx = ParseContext::new(LINKTYPE_LINUX_SLL);
223        ctx.parent_protocol = Some("something");
224
225        assert!(parser.can_parse(&ctx).is_none());
226    }
227
228    // ==========================================================================
229    // Test 4: Parse basic SLL header with ethernet
230    // ==========================================================================
231    #[test]
232    fn test_parse_sll_ethernet() {
233        let header = create_sll_header(
234            packet_type::HOST,
235            arphrd::ETHER,
236            6,
237            [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x00, 0x00],
238            0x0800, // IPv4
239        );
240        let parser = LinuxSllProtocol;
241        let ctx = ParseContext::new(LINKTYPE_LINUX_SLL);
242
243        let result = parser.parse(&header, &ctx);
244
245        assert!(result.is_ok());
246        assert_eq!(result.get("packet_type"), Some(&FieldValue::UInt16(0)));
247        assert_eq!(
248            result.get("packet_type_name"),
249            Some(&FieldValue::Str("HOST"))
250        );
251        assert_eq!(
252            result.get("arphrd_type"),
253            Some(&FieldValue::UInt16(arphrd::ETHER))
254        );
255        assert_eq!(result.get("arphrd_name"), Some(&FieldValue::Str("ETHER")));
256        assert_eq!(result.get("protocol"), Some(&FieldValue::UInt16(0x0800)));
257    }
258
259    // ==========================================================================
260    // Test 5: Parse SLL header with netlink
261    // ==========================================================================
262    #[test]
263    fn test_parse_sll_netlink() {
264        let header = create_sll_header(
265            packet_type::OUTGOING,
266            arphrd::NETLINK,
267            0,
268            [0; 8],
269            0, // NETLINK_ROUTE
270        );
271        let parser = LinuxSllProtocol;
272        let ctx = ParseContext::new(LINKTYPE_LINUX_SLL);
273
274        let result = parser.parse(&header, &ctx);
275
276        assert!(result.is_ok());
277        assert_eq!(
278            result.get("arphrd_type"),
279            Some(&FieldValue::UInt16(arphrd::NETLINK))
280        );
281        assert_eq!(result.get("arphrd_name"), Some(&FieldValue::Str("NETLINK")));
282
283        // Check child hints for netlink
284        assert!(result
285            .child_hints
286            .iter()
287            .any(|(k, v)| *k == "is_netlink" && *v == 1));
288        assert!(result
289            .child_hints
290            .iter()
291            .any(|(k, v)| *k == "netlink_family" && *v == 0));
292    }
293
294    // ==========================================================================
295    // Test 6: Header too short
296    // ==========================================================================
297    #[test]
298    fn test_parse_sll_header_too_short() {
299        let short_data = vec![0u8; 10]; // Less than 16 bytes
300        let parser = LinuxSllProtocol;
301        let ctx = ParseContext::new(LINKTYPE_LINUX_SLL);
302
303        let result = parser.parse(&short_data, &ctx);
304
305        assert!(!result.is_ok());
306        assert!(result.error.is_some());
307    }
308
309    // ==========================================================================
310    // Test 7: Packet type name resolution
311    // ==========================================================================
312    #[test]
313    fn test_packet_type_names() {
314        assert_eq!(packet_type_name(packet_type::HOST), "HOST");
315        assert_eq!(packet_type_name(packet_type::BROADCAST), "BROADCAST");
316        assert_eq!(packet_type_name(packet_type::MULTICAST), "MULTICAST");
317        assert_eq!(packet_type_name(packet_type::OTHERHOST), "OTHERHOST");
318        assert_eq!(packet_type_name(packet_type::OUTGOING), "OUTGOING");
319        assert_eq!(packet_type_name(99), "UNKNOWN");
320    }
321
322    // ==========================================================================
323    // Test 8: ARPHRD name resolution
324    // ==========================================================================
325    #[test]
326    fn test_arphrd_names() {
327        assert_eq!(arphrd_name(arphrd::ETHER), "ETHER");
328        assert_eq!(arphrd_name(arphrd::NETLINK), "NETLINK");
329        assert_eq!(arphrd_name(arphrd::LOOPBACK), "LOOPBACK");
330        assert_eq!(arphrd_name(999), "UNKNOWN");
331    }
332
333    // ==========================================================================
334    // Test 9: Schema fields are complete
335    // ==========================================================================
336    #[test]
337    fn test_linux_sll_schema_fields() {
338        let parser = LinuxSllProtocol;
339        let fields = parser.schema_fields();
340
341        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
342
343        assert!(field_names.contains(&"linux_sll.packet_type"));
344        assert!(field_names.contains(&"linux_sll.packet_type_name"));
345        assert!(field_names.contains(&"linux_sll.arphrd_type"));
346        assert!(field_names.contains(&"linux_sll.arphrd_name"));
347        assert!(field_names.contains(&"linux_sll.addr_len"));
348        assert!(field_names.contains(&"linux_sll.addr"));
349        assert!(field_names.contains(&"linux_sll.protocol"));
350    }
351
352    // ==========================================================================
353    // Test 10: Child protocols declaration
354    // ==========================================================================
355    #[test]
356    fn test_linux_sll_child_protocols() {
357        let parser = LinuxSllProtocol;
358        let children = parser.child_protocols();
359
360        assert!(children.contains(&"netlink"));
361        assert!(children.contains(&"ethernet"));
362    }
363
364    // ==========================================================================
365    // Test 11: No dependencies (root protocol)
366    // ==========================================================================
367    #[test]
368    fn test_linux_sll_no_dependencies() {
369        let parser = LinuxSllProtocol;
370        let deps = parser.dependencies();
371
372        assert!(deps.is_empty());
373    }
374
375    // ==========================================================================
376    // Test 12: Remaining data after header
377    // ==========================================================================
378    #[test]
379    fn test_remaining_data() {
380        let mut data = create_sll_header(packet_type::HOST, arphrd::NETLINK, 0, [0; 8], 0);
381        // Add some payload
382        data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
383
384        let parser = LinuxSllProtocol;
385        let ctx = ParseContext::new(LINKTYPE_LINUX_SLL);
386
387        let result = parser.parse(&data, &ctx);
388
389        assert!(result.is_ok());
390        assert_eq!(result.remaining.len(), 4);
391        assert_eq!(result.remaining, &[0x01, 0x02, 0x03, 0x04]);
392    }
393}