Skip to main content

packet_dissector_ethernet/
lib.rs

1//! Ethernet II frame dissector.
2//!
3//! Parses classic Ethernet II frames (DIX v2) as well as IEEE 802.3 frames
4//! with IEEE 802.2 LLC encapsulation. IEEE 802.1Q (C-Tag) and IEEE 802.1ad
5//! (S-Tag / QinQ) VLAN tags stacked in any number are accepted; each tag
6//! is parsed in a loop until a non-VLAN EtherType or a length value is
7//! reached.
8//!
9//! ## References
10//! - IEEE 802.3-2022 (Ethernet): <https://standards.ieee.org/ieee/802.3/10422/>
11//! - IEEE 802.1Q-2022 (VLAN tagging, incorporates IEEE 802.1ad QinQ):
12//!   <https://standards.ieee.org/ieee/802.1Q/10323/>
13//! - IEEE 802.2-1998 (LLC): <https://standards.ieee.org/ieee/802.2/1048/>
14//! - IANA EtherType registry: <https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml>
15
16#![deny(missing_docs)]
17
18use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
19use packet_dissector_core::error::PacketError;
20use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue, MacAddr};
21use packet_dissector_core::packet::DissectBuffer;
22use packet_dissector_core::util::read_be_u16;
23
24/// Minimum Ethernet II header size (dst MAC + src MAC + EtherType).
25/// IEEE 802.3-2022, clause 3.2.3.
26const HEADER_SIZE: usize = 14;
27
28/// 802.1Q Customer VLAN TPID value (C-Tag).
29/// IEEE 802.1Q-2022, clause 9.6 (VLAN Tag Protocol Identifier).
30const TPID_8021Q: u16 = 0x8100;
31
32/// 802.1ad Service VLAN TPID value (S-Tag / QinQ outer tag).
33/// IEEE 802.1Q-2022, clause 9.6 (originally introduced by IEEE 802.1ad-2005
34/// and rolled into IEEE 802.1Q-2011 and later).
35const TPID_8021AD: u16 = 0x88A8;
36
37/// Minimum value of a valid EtherType field in an Ethernet II frame.
38/// IEEE 802.3-2022, clause 3.2.6: values less than 0x0600 indicate a length field
39/// (IEEE 802.3 LLC frame), not an EtherType.
40const ETHERTYPE_MIN: u16 = 0x0600;
41
42/// Maximum valid IEEE 802.3 length field value (1500 octets).
43/// IEEE 802.3-2022, clause 3.2.6.
44const LENGTH_MAX: u16 = 0x05DC;
45
46/// Minimum size of an IEEE 802.2 LLC header (DSAP + SSAP + Control for UI frames).
47/// IEEE 802.2-1998, Section 3.
48const LLC_HEADER_SIZE: usize = 3;
49
50/// Field descriptor indices for [`FIELD_DESCRIPTORS`].
51const FD_DST: usize = 0;
52const FD_SRC: usize = 1;
53const FD_VLAN_TPID: usize = 2;
54const FD_VLAN_PCP: usize = 3;
55const FD_VLAN_DEI: usize = 4;
56const FD_VLAN_ID: usize = 5;
57const FD_ETHERTYPE: usize = 6;
58const FD_LENGTH: usize = 7;
59const FD_LLC_DSAP: usize = 8;
60const FD_LLC_SSAP: usize = 9;
61const FD_LLC_CONTROL: usize = 10;
62
63static FIELD_DESCRIPTORS: &[FieldDescriptor] = &[
64    FieldDescriptor::new("dst", "Destination", FieldType::MacAddr),
65    FieldDescriptor::new("src", "Source", FieldType::MacAddr),
66    FieldDescriptor::new("vlan_tpid", "VLAN TPID", FieldType::U16).optional(),
67    FieldDescriptor::new("vlan_pcp", "VLAN PCP", FieldType::U8).optional(),
68    FieldDescriptor::new("vlan_dei", "VLAN DEI", FieldType::U8).optional(),
69    FieldDescriptor::new("vlan_id", "VLAN ID", FieldType::U16).optional(),
70    FieldDescriptor {
71        name: "ethertype",
72        display_name: "EtherType",
73        field_type: FieldType::U16,
74        optional: true,
75        children: None,
76        display_fn: Some(|v, _siblings| match v {
77            FieldValue::U16(v) => ethertype_name(*v),
78            _ => None,
79        }),
80        format_fn: None,
81    },
82    FieldDescriptor::new("length", "Length", FieldType::U16).optional(),
83    FieldDescriptor::new("llc_dsap", "LLC DSAP", FieldType::U8).optional(),
84    FieldDescriptor::new("llc_ssap", "LLC SSAP", FieldType::U8).optional(),
85    FieldDescriptor::new("llc_control", "LLC Control", FieldType::U8).optional(),
86];
87
88/// Returns a human-readable name for well-known EtherType values.
89fn ethertype_name(v: u16) -> Option<&'static str> {
90    match v {
91        0x0800 => Some("IPv4"),
92        0x0806 => Some("ARP"),
93        0x8100 => Some("802.1Q"),
94        0x88A8 => Some("802.1ad"),
95        0x86DD => Some("IPv6"),
96        0x8847 => Some("MPLS"),
97        0x8848 => Some("MPLS_MC"),
98        0x8809 => Some("Slow Protocols"),
99        0x88CC => Some("LLDP"),
100        _ => None,
101    }
102}
103
104/// Ethernet II frame dissector.
105pub struct EthernetDissector;
106
107impl Dissector for EthernetDissector {
108    fn name(&self) -> &'static str {
109        "Ethernet II"
110    }
111
112    fn short_name(&self) -> &'static str {
113        "Ethernet"
114    }
115
116    fn field_descriptors(&self) -> &'static [FieldDescriptor] {
117        FIELD_DESCRIPTORS
118    }
119
120    fn dissect<'pkt>(
121        &self,
122        data: &'pkt [u8],
123        buf: &mut DissectBuffer<'pkt>,
124        offset: usize,
125    ) -> Result<DissectResult, PacketError> {
126        // IEEE 802.3-2022, clause 3.2.3: minimum frame header is dst (6) + src (6) + type/length (2).
127        if data.len() < HEADER_SIZE {
128            return Err(PacketError::Truncated {
129                expected: HEADER_SIZE,
130                actual: data.len(),
131            });
132        }
133
134        // IEEE 802.3-2022, clause 3.2.3: Destination Address (6 octets).
135        let dst = MacAddr([data[0], data[1], data[2], data[3], data[4], data[5]]);
136        // IEEE 802.3-2022, clause 3.2.3: Source Address (6 octets).
137        let src = MacAddr([data[6], data[7], data[8], data[9], data[10], data[11]]);
138        // IEEE 802.3-2022, clause 3.2.6: Length/Type field (2 octets, big-endian).
139        let ethertype_or_tpid = read_be_u16(data, 12)?;
140
141        // We defer begin_layer until we know the header length (VLAN tags vary).
142        // Push MAC fields first; they're always present.
143        let layer_field_start = buf.field_count();
144
145        buf.push_field(
146            &FIELD_DESCRIPTORS[FD_DST],
147            FieldValue::MacAddr(dst),
148            offset..offset + 6,
149        );
150        buf.push_field(
151            &FIELD_DESCRIPTORS[FD_SRC],
152            FieldValue::MacAddr(src),
153            offset + 6..offset + 12,
154        );
155
156        let mut header_len = HEADER_SIZE;
157        let mut current_type = ethertype_or_tpid;
158
159        // IEEE 802.1Q-2022, clause 9.6: stacked VLAN tags may appear back-to-back
160        // (e.g. QinQ S-Tag + C-Tag). Parse tags until `current_type` is no longer
161        // a VLAN TPID. If a VLAN TPID is present but the remaining data cannot
162        // hold the required 4-byte tag, the frame is truncated.
163        while current_type == TPID_8021Q || current_type == TPID_8021AD {
164            let vlan_end = header_len + 4;
165            if data.len() < vlan_end {
166                return Err(PacketError::Truncated {
167                    expected: vlan_end,
168                    actual: data.len(),
169                });
170            }
171
172            // IEEE 802.1Q-2022, clause 9.6: Tag Control Information (TCI), 2 octets.
173            // Bit layout (MSB first): PCP[3] | DEI[1] | VID[12].
174            let tci = read_be_u16(data, header_len)?;
175            let pcp = (tci >> 13) & 0x07;
176            let dei = (tci >> 12) & 0x01;
177            let vlan_id = tci & 0x0FFF;
178            let inner_type = read_be_u16(data, header_len + 2)?;
179
180            buf.push_field(
181                &FIELD_DESCRIPTORS[FD_VLAN_TPID],
182                FieldValue::U16(current_type),
183                offset + header_len - 2..offset + header_len,
184            );
185            buf.push_field(
186                &FIELD_DESCRIPTORS[FD_VLAN_PCP],
187                FieldValue::U8(pcp as u8),
188                offset + header_len..offset + header_len + 2,
189            );
190            buf.push_field(
191                &FIELD_DESCRIPTORS[FD_VLAN_DEI],
192                FieldValue::U8(dei as u8),
193                offset + header_len..offset + header_len + 2,
194            );
195            buf.push_field(
196                &FIELD_DESCRIPTORS[FD_VLAN_ID],
197                FieldValue::U16(vlan_id),
198                offset + header_len..offset + header_len + 2,
199            );
200
201            header_len = vlan_end;
202            current_type = inner_type;
203        }
204
205        let dispatch_hint = if current_type <= LENGTH_MAX {
206            // IEEE 802.3-2022, clause 3.2.6: values ≤ 1500 indicate a length field
207            // (IEEE 802.3 frame with LLC encapsulation).
208            let llc_start = header_len;
209            let llc_end = llc_start + LLC_HEADER_SIZE;
210            if data.len() < llc_end {
211                return Err(PacketError::Truncated {
212                    expected: llc_end,
213                    actual: data.len(),
214                });
215            }
216
217            let dsap = data[llc_start];
218            let ssap = data[llc_start + 1];
219            let control = data[llc_start + 2];
220
221            buf.push_field(
222                &FIELD_DESCRIPTORS[FD_LENGTH],
223                FieldValue::U16(current_type),
224                offset + header_len - 2..offset + header_len,
225            );
226            buf.push_field(
227                &FIELD_DESCRIPTORS[FD_LLC_DSAP],
228                FieldValue::U8(dsap),
229                offset + llc_start..offset + llc_start + 1,
230            );
231            buf.push_field(
232                &FIELD_DESCRIPTORS[FD_LLC_SSAP],
233                FieldValue::U8(ssap),
234                offset + llc_start + 1..offset + llc_start + 2,
235            );
236            buf.push_field(
237                &FIELD_DESCRIPTORS[FD_LLC_CONTROL],
238                FieldValue::U8(control),
239                offset + llc_start + 2..offset + llc_end,
240            );
241
242            header_len = llc_end;
243            DispatchHint::ByLlcSap(dsap)
244        } else if current_type < ETHERTYPE_MIN {
245            // IEEE 802.3-2022, clause 3.2.6: values 1501–1535 are undefined/reserved.
246            return Err(PacketError::InvalidFieldValue {
247                field: "type_length",
248                value: current_type as u32,
249            });
250        } else {
251            // Valid Ethernet II EtherType (≥ 0x0600).
252            buf.push_field(
253                &FIELD_DESCRIPTORS[FD_ETHERTYPE],
254                FieldValue::U16(current_type),
255                offset + header_len - 2..offset + header_len,
256            );
257
258            DispatchHint::ByEtherType(current_type)
259        };
260
261        // Now that we know the header length, add the layer with the correct field range.
262        let layer_field_end = buf.field_count();
263        buf.push_layer(packet_dissector_core::packet::Layer {
264            name: self.short_name(),
265            display_name: None,
266            field_descriptors: FIELD_DESCRIPTORS,
267            range: offset..offset + header_len,
268            field_range: layer_field_start..layer_field_end,
269        });
270
271        Ok(DissectResult::new(header_len, dispatch_hint))
272    }
273}