pcapsql_core/protocol/
ethernet.rs

1//! Ethernet II protocol parser.
2
3use smallvec::SmallVec;
4
5use etherparse::Ethernet2HeaderSlice;
6
7use super::{FieldValue, ParseContext, ParseResult, Protocol};
8use crate::schema::{DataKind, FieldDescriptor};
9
10/// Link type constant for Ethernet.
11pub const LINKTYPE_ETHERNET: u16 = 1;
12
13/// Well-known EtherType values (IEEE 802).
14#[allow(dead_code)]
15pub mod ethertype {
16    // Common protocols
17    pub const IPV4: u16 = 0x0800;
18    pub const ARP: u16 = 0x0806;
19    pub const WAKE_ON_LAN: u16 = 0x0842;
20    pub const RARP: u16 = 0x8035;
21    pub const VLAN: u16 = 0x8100;
22    pub const IPV6: u16 = 0x86DD;
23    pub const QINQ: u16 = 0x88A8;
24
25    // Streaming/AV protocols
26    pub const AVTP: u16 = 0x22F0;
27    pub const SRP: u16 = 0x22EA;
28    pub const TRILL: u16 = 0x22F3;
29
30    // Legacy protocols
31    pub const DEC_MOP_RC: u16 = 0x6002;
32    pub const DECNET: u16 = 0x6003;
33    pub const DEC_LAT: u16 = 0x6004;
34    pub const APPLETALK: u16 = 0x809B;
35    pub const AARP: u16 = 0x80F3;
36    pub const IPX: u16 = 0x8137;
37    pub const QNX_QNET: u16 = 0x8204;
38
39    // Link layer protocols
40    pub const SLPP: u16 = 0x8102;
41    pub const VLACP: u16 = 0x8103;
42    pub const FLOW_CONTROL: u16 = 0x8808;
43    pub const LACP: u16 = 0x8809;
44    pub const LLDP: u16 = 0x88CC;
45
46    // MPLS
47    pub const MPLS: u16 = 0x8847;
48    pub const MPLS_MULTICAST: u16 = 0x8848;
49
50    // PPPoE
51    pub const PPPOE_DISCOVERY: u16 = 0x8863;
52    pub const PPPOE_SESSION: u16 = 0x8864;
53
54    // Industrial protocols
55    pub const COBRANET: u16 = 0x8819;
56    pub const PROFINET: u16 = 0x8892;
57    pub const HYPERSCSI: u16 = 0x889A;
58    pub const ATA_OVER_ETHERNET: u16 = 0x88A2;
59    pub const ETHERCAT: u16 = 0x88A4;
60    pub const POWERLINK: u16 = 0x88AB;
61    pub const GOOSE: u16 = 0x88B8;
62    pub const GSE: u16 = 0x88B9;
63    pub const SV: u16 = 0x88BA;
64    pub const SERCOS_III: u16 = 0x88CD;
65    pub const MRP: u16 = 0x88E3;
66    pub const PRP: u16 = 0x88FB;
67    pub const HSR: u16 = 0x892F;
68
69    // Security protocols
70    pub const EAP_OVER_LAN: u16 = 0x888E;
71    pub const MACSEC: u16 = 0x88E5;
72
73    // Provider bridging
74    pub const PBB: u16 = 0x88E7;
75
76    // Time protocols
77    pub const PTP: u16 = 0x88F7;
78
79    // Network management
80    pub const HOMEPLUG_MME: u16 = 0x887B;
81    pub const HOMEPLUG_AV_MME: u16 = 0x88E1;
82    pub const MIKROTIK_ROMON: u16 = 0x88BF;
83    pub const NC_SI: u16 = 0x88F8;
84    pub const CFM_OAM: u16 = 0x8902;
85
86    // Storage protocols
87    pub const FCOE: u16 = 0x8906;
88    pub const FCOE_INIT: u16 = 0x8914;
89    pub const ROCE: u16 = 0x8915;
90
91    // Other
92    pub const WSMP: u16 = 0x88DC;
93    pub const TTE: u16 = 0x891D;
94    pub const ECTP: u16 = 0x9000;
95    pub const QINQ_OLD: u16 = 0x9100;
96    pub const VERITAS_LLT: u16 = 0xCAFE;
97}
98
99/// Ethernet II protocol parser.
100#[derive(Debug, Clone, Copy)]
101pub struct EthernetProtocol;
102
103impl Protocol for EthernetProtocol {
104    fn name(&self) -> &'static str {
105        "ethernet"
106    }
107
108    fn display_name(&self) -> &'static str {
109        "Ethernet II"
110    }
111
112    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
113        // Parse Ethernet at the root level for Ethernet link type
114        if context.is_root() && context.link_type == LINKTYPE_ETHERNET {
115            return Some(100);
116        }
117
118        // Also parse inside tunnels when link_type hint is set to Ethernet
119        // This allows parsing inner Ethernet frames inside VXLAN, etc.
120        if let Some(link_type) = context.hint("link_type") {
121            if link_type == LINKTYPE_ETHERNET as u64 {
122                return Some(100);
123            }
124        }
125
126        None
127    }
128
129    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
130        match Ethernet2HeaderSlice::from_slice(data) {
131            Ok(eth) => {
132                let mut fields = SmallVec::new();
133
134                fields.push(("src_mac", FieldValue::mac(&eth.source())));
135                fields.push(("dst_mac", FieldValue::mac(&eth.destination())));
136                fields.push(("ethertype", FieldValue::UInt16(eth.ether_type().0)));
137
138                let mut child_hints = SmallVec::new();
139                child_hints.push(("ethertype", eth.ether_type().0 as u64));
140
141                let header_len = eth.slice().len();
142                ParseResult::success(fields, &data[header_len..], child_hints)
143            }
144            Err(e) => ParseResult::error(format!("Ethernet parse error: {e}"), data),
145        }
146    }
147
148    fn schema_fields(&self) -> Vec<FieldDescriptor> {
149        vec![
150            FieldDescriptor::mac_field("eth.src_mac").set_nullable(true),
151            FieldDescriptor::mac_field("eth.dst_mac").set_nullable(true),
152            FieldDescriptor::new("eth.ethertype", DataKind::UInt16).set_nullable(true),
153        ]
154    }
155
156    fn child_protocols(&self) -> &[&'static str] {
157        &["ipv4", "ipv6", "arp", "vlan"]
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn test_parse_ethernet() {
167        // Sample Ethernet frame: dst MAC, src MAC, ethertype (0x0800 = IPv4)
168        let frame = [
169            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // dst: broadcast
170            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // src
171            0x08, 0x00, // ethertype: IPv4
172            0x45, 0x00, // IPv4 header start (payload)
173        ];
174
175        let parser = EthernetProtocol;
176        let context = ParseContext::new(LINKTYPE_ETHERNET);
177        let result = parser.parse(&frame, &context);
178
179        assert!(result.is_ok());
180        assert_eq!(
181            result.get("ethertype"),
182            Some(&FieldValue::UInt16(ethertype::IPV4))
183        );
184        assert_eq!(result.remaining.len(), 2); // IPv4 header bytes
185        assert_eq!(result.hint("ethertype"), Some(ethertype::IPV4 as u64));
186    }
187
188    #[test]
189    fn test_parse_ethernet_ipv6() {
190        let frame = [
191            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // dst
192            0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // src
193            0x86, 0xdd, // ethertype: IPv6
194        ];
195
196        let parser = EthernetProtocol;
197        let context = ParseContext::new(LINKTYPE_ETHERNET);
198        let result = parser.parse(&frame, &context);
199
200        assert!(result.is_ok());
201        assert_eq!(
202            result.get("ethertype"),
203            Some(&FieldValue::UInt16(ethertype::IPV6))
204        );
205        assert_eq!(result.hint("ethertype"), Some(ethertype::IPV6 as u64));
206    }
207
208    #[test]
209    fn test_parse_ethernet_arp() {
210        let frame = [
211            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // dst: broadcast
212            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // src
213            0x08, 0x06, // ethertype: ARP
214            0x00, 0x01, // ARP start
215        ];
216
217        let parser = EthernetProtocol;
218        let context = ParseContext::new(LINKTYPE_ETHERNET);
219        let result = parser.parse(&frame, &context);
220
221        assert!(result.is_ok());
222        assert_eq!(
223            result.get("ethertype"),
224            Some(&FieldValue::UInt16(ethertype::ARP))
225        );
226    }
227
228    #[test]
229    fn test_can_parse_only_at_root() {
230        let parser = EthernetProtocol;
231
232        // At root with Ethernet link type
233        let root_ctx = ParseContext::new(LINKTYPE_ETHERNET);
234        assert!(parser.can_parse(&root_ctx).is_some());
235
236        // At root with non-Ethernet link type
237        let other_ctx = ParseContext::new(113); // Linux cooked capture
238        assert!(parser.can_parse(&other_ctx).is_none());
239
240        // Not at root
241        let mut child_ctx = ParseContext::new(LINKTYPE_ETHERNET);
242        child_ctx.parent_protocol = Some("something");
243        assert!(parser.can_parse(&child_ctx).is_none());
244    }
245
246    #[test]
247    fn test_parse_ethernet_too_short() {
248        let short_frame = [0xff, 0xff, 0xff, 0xff, 0xff]; // Only 5 bytes
249
250        let parser = EthernetProtocol;
251        let context = ParseContext::new(LINKTYPE_ETHERNET);
252        let result = parser.parse(&short_frame, &context);
253
254        assert!(!result.is_ok());
255        assert!(result.error.is_some());
256    }
257
258    #[test]
259    fn test_mac_address_extraction() {
260        let frame = [
261            0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, // dst
262            0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // src
263            0x08, 0x00, // ethertype
264        ];
265
266        let parser = EthernetProtocol;
267        let context = ParseContext::new(LINKTYPE_ETHERNET);
268        let result = parser.parse(&frame, &context);
269
270        assert!(result.is_ok());
271
272        // Check MAC addresses are properly extracted
273        let src_mac = result.get("src_mac").unwrap();
274        let dst_mac = result.get("dst_mac").unwrap();
275
276        assert!(src_mac.as_string().is_some());
277        assert!(dst_mac.as_string().is_some());
278    }
279}