⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/net/
vlan.rs

1//! IEEE 802.1Q VLAN tagging support
2//!
3//! Provides VLAN tag insertion/stripping and interface management
4//! for 802.1Q virtual LAN segmentation.
5
6#![allow(dead_code)]
7
8use alloc::{string::String, vec::Vec};
9
10use spin::Mutex;
11
12use crate::sync::once_lock::OnceLock;
13
14/// IEEE 802.1Q Tag Protocol Identifier
15pub const TPID_8021Q: u16 = 0x8100;
16
17/// Minimum Ethernet frame size (dst MAC + src MAC + EtherType)
18const ETH_HEADER_MIN: usize = 14;
19
20/// Size of an 802.1Q tag in bytes (TPID + TCI)
21const VLAN_TAG_SIZE: usize = 4;
22
23/// Maximum valid VLAN ID
24const VLAN_ID_MAX: u16 = 4094;
25
26// ── 802.1Q Tag ──────────────────────────────────────────────────────────────
27
28/// IEEE 802.1Q VLAN tag
29///
30/// Layout (network byte order):
31///   TPID: 16 bits (0x8100 for 802.1Q)
32///   TCI:  16 bits = PCP (3) + DEI (1) + VID (12)
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct VlanTag {
35    /// Tag Protocol Identifier (always 0x8100 for 802.1Q)
36    pub tpid: u16,
37    /// Tag Control Information (PCP + DEI + VID)
38    pub tci: u16,
39}
40
41impl VlanTag {
42    /// Create a new VLAN tag with the given parameters.
43    ///
44    /// - `vid`: VLAN Identifier (0-4094, 12 bits)
45    /// - `pcp`: Priority Code Point (0-7, 3 bits)
46    /// - `dei`: Drop Eligible Indicator
47    pub fn new(vid: u16, pcp: u8, dei: bool) -> Self {
48        let tci = ((pcp as u16 & 0x07) << 13) | (if dei { 1u16 << 12 } else { 0 }) | (vid & 0x0FFF);
49        Self {
50            tpid: TPID_8021Q,
51            tci,
52        }
53    }
54
55    /// Extract the 12-bit VLAN Identifier from TCI.
56    pub fn vid(&self) -> u16 {
57        self.tci & 0x0FFF
58    }
59
60    /// Extract the 3-bit Priority Code Point from TCI.
61    pub fn pcp(&self) -> u8 {
62        ((self.tci >> 13) & 0x07) as u8
63    }
64
65    /// Extract the Drop Eligible Indicator bit from TCI.
66    pub fn dei(&self) -> bool {
67        (self.tci >> 12) & 0x01 != 0
68    }
69
70    /// Serialize the tag to 4 bytes in network byte order.
71    pub fn to_bytes(&self) -> [u8; VLAN_TAG_SIZE] {
72        let tpid = self.tpid.to_be_bytes();
73        let tci = self.tci.to_be_bytes();
74        [tpid[0], tpid[1], tci[0], tci[1]]
75    }
76
77    /// Parse a VLAN tag from 4 bytes in network byte order.
78    pub fn from_bytes(bytes: &[u8; VLAN_TAG_SIZE]) -> Self {
79        let tpid = u16::from_be_bytes([bytes[0], bytes[1]]);
80        let tci = u16::from_be_bytes([bytes[2], bytes[3]]);
81        Self { tpid, tci }
82    }
83}
84
85// ── Tag insertion / stripping ───────────────────────────────────────────────
86
87/// Check whether an Ethernet frame carries an 802.1Q tag.
88///
89/// Inspects the EtherType field at byte offset 12.
90pub fn has_vlan_tag(frame: &[u8]) -> bool {
91    if frame.len() < ETH_HEADER_MIN {
92        return false;
93    }
94    let ethertype = u16::from_be_bytes([frame[12], frame[13]]);
95    ethertype == TPID_8021Q
96}
97
98/// Insert an 802.1Q tag into an Ethernet frame after the source MAC address.
99///
100/// Returns a new frame with the 4-byte tag inserted at offset 12.
101pub fn insert_tag(frame: &[u8], tag: VlanTag) -> Vec<u8> {
102    if frame.len() < ETH_HEADER_MIN {
103        return frame.to_vec();
104    }
105    let mut out = Vec::with_capacity(frame.len() + VLAN_TAG_SIZE);
106    // dst MAC (6) + src MAC (6)
107    out.extend_from_slice(&frame[..12]);
108    // 802.1Q tag (4)
109    out.extend_from_slice(&tag.to_bytes());
110    // original EtherType + payload
111    out.extend_from_slice(&frame[12..]);
112    out
113}
114
115/// Strip an 802.1Q tag from an Ethernet frame.
116///
117/// Returns `(Some(tag), inner_frame)` if the frame is tagged, or
118/// `(None, original_frame)` if not.
119pub fn strip_tag(frame: &[u8]) -> (Option<VlanTag>, Vec<u8>) {
120    if !has_vlan_tag(frame) || frame.len() < ETH_HEADER_MIN + VLAN_TAG_SIZE {
121        return (None, frame.to_vec());
122    }
123    let tag_bytes: [u8; VLAN_TAG_SIZE] = [frame[12], frame[13], frame[14], frame[15]];
124    let tag = VlanTag::from_bytes(&tag_bytes);
125
126    let mut out = Vec::with_capacity(frame.len() - VLAN_TAG_SIZE);
127    // dst MAC (6) + src MAC (6)
128    out.extend_from_slice(&frame[..12]);
129    // skip 4-byte tag, copy remaining (original EtherType + payload)
130    out.extend_from_slice(&frame[16..]);
131    (Some(tag), out)
132}
133
134// ── VLAN interface / mode ───────────────────────────────────────────────────
135
136/// VLAN operating mode for a port/interface.
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub enum VlanMode {
139    /// Access port: all frames belong to a single VLAN.
140    /// Tags outgoing frames, strips incoming tags.
141    Access(u16),
142    /// Trunk port: carries multiple VLANs.
143    /// Passes tagged frames for allowed VIDs.
144    Trunk(Vec<u16>),
145}
146
147/// A VLAN interface bound to a parent network device.
148#[derive(Debug, Clone)]
149pub struct VlanInterface {
150    /// Name of the underlying physical NIC.
151    pub parent_device: String,
152    /// VLAN ID (1-4094).
153    pub vid: u16,
154    /// Port mode (access or trunk).
155    pub mode: VlanMode,
156}
157
158// ── Errors ──────────────────────────────────────────────────────────────────
159
160/// Errors from VLAN operations.
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub enum VlanError {
163    /// VLAN ID out of valid range (1-4094).
164    InvalidVid(u16),
165    /// A VLAN with this (parent, vid) already exists.
166    AlreadyExists,
167    /// No VLAN found for the given (parent, vid).
168    NotFound,
169}
170
171// ── VLAN manager ────────────────────────────────────────────────────────────
172
173/// Manages VLAN interfaces across all network devices.
174#[derive(Debug)]
175pub struct VlanManager {
176    interfaces: Vec<VlanInterface>,
177}
178
179impl VlanManager {
180    /// Create a new empty VLAN manager.
181    pub fn new() -> Self {
182        Self {
183            interfaces: Vec::new(),
184        }
185    }
186
187    /// Create a VLAN interface on the given parent device.
188    pub fn create_vlan(&mut self, parent: &str, vid: u16, mode: VlanMode) -> Result<(), VlanError> {
189        if vid == 0 || vid > VLAN_ID_MAX {
190            return Err(VlanError::InvalidVid(vid));
191        }
192        // Check for duplicates
193        let exists = self
194            .interfaces
195            .iter()
196            .any(|i| i.parent_device == parent && i.vid == vid);
197        if exists {
198            return Err(VlanError::AlreadyExists);
199        }
200        self.interfaces.push(VlanInterface {
201            parent_device: String::from(parent),
202            vid,
203            mode,
204        });
205        Ok(())
206    }
207
208    /// Delete a VLAN interface.
209    pub fn delete_vlan(&mut self, parent: &str, vid: u16) -> Result<(), VlanError> {
210        let pos = self
211            .interfaces
212            .iter()
213            .position(|i| i.parent_device == parent && i.vid == vid);
214        match pos {
215            Some(idx) => {
216                self.interfaces.remove(idx);
217                Ok(())
218            }
219            None => Err(VlanError::NotFound),
220        }
221    }
222
223    /// List all configured VLAN interfaces.
224    pub fn list_vlans(&self) -> Vec<VlanInterface> {
225        self.interfaces.clone()
226    }
227
228    /// Process an incoming (ingress) frame on a parent device.
229    ///
230    /// Returns `Some((vid, untagged_frame))` if the frame should be accepted,
231    /// or `None` if it should be dropped.
232    pub fn process_ingress(&self, parent: &str, frame: &[u8]) -> Option<(u16, Vec<u8>)> {
233        let matching: Vec<&VlanInterface> = self
234            .interfaces
235            .iter()
236            .filter(|i| i.parent_device == parent)
237            .collect();
238
239        if matching.is_empty() {
240            return None;
241        }
242
243        if has_vlan_tag(frame) {
244            // Tagged frame -- extract VID and check trunk ports
245            let (tag_opt, inner) = strip_tag(frame);
246            let tag = tag_opt?;
247            let vid = tag.vid();
248
249            for iface in &matching {
250                match &iface.mode {
251                    VlanMode::Access(access_vid) => {
252                        if vid == *access_vid {
253                            return Some((vid, inner));
254                        }
255                    }
256                    VlanMode::Trunk(allowed) => {
257                        if allowed.contains(&vid) {
258                            return Some((vid, inner));
259                        }
260                    }
261                }
262            }
263            None
264        } else {
265            // Untagged frame -- assign to the access VLAN if one exists
266            for iface in &matching {
267                if let VlanMode::Access(access_vid) = &iface.mode {
268                    return Some((*access_vid, frame.to_vec()));
269                }
270            }
271            None
272        }
273    }
274
275    /// Process an outgoing (egress) frame for a given VLAN on a parent device.
276    ///
277    /// For access ports the frame is sent untagged (already stripped).
278    /// For trunk ports the frame is sent with an 802.1Q tag.
279    pub fn process_egress(&self, parent: &str, vid: u16, frame: &[u8]) -> Vec<u8> {
280        let iface = self
281            .interfaces
282            .iter()
283            .find(|i| i.parent_device == parent && i.vid == vid);
284
285        match iface {
286            Some(i) => match &i.mode {
287                VlanMode::Access(_) => {
288                    // Access port: send untagged
289                    frame.to_vec()
290                }
291                VlanMode::Trunk(_) => {
292                    // Trunk port: insert 802.1Q tag
293                    let tag = VlanTag::new(vid, 0, false);
294                    insert_tag(frame, tag)
295                }
296            },
297            None => {
298                // No matching interface -- pass through unmodified
299                frame.to_vec()
300            }
301        }
302    }
303}
304
305impl Default for VlanManager {
306    fn default() -> Self {
307        Self::new()
308    }
309}
310
311// ── Global manager ──────────────────────────────────────────────────────────
312
313static VLAN_MANAGER: OnceLock<Mutex<VlanManager>> = OnceLock::new();
314
315/// Initialize the global VLAN manager.
316pub fn init() {
317    let _ = VLAN_MANAGER.set(Mutex::new(VlanManager::new()));
318}
319
320/// Access the global VLAN manager under a lock.
321pub fn with_manager<R, F: FnOnce(&mut VlanManager) -> R>(f: F) -> Option<R> {
322    VLAN_MANAGER.get().map(|m| {
323        let mut guard = m.lock();
324        f(&mut guard)
325    })
326}
327
328// ── Tests ───────────────────────────────────────────────────────────────────
329
330#[cfg(test)]
331mod tests {
332    #[allow(unused_imports)]
333    use alloc::vec;
334
335    use super::*;
336
337    #[test]
338    fn test_vlan_tag_new() {
339        let tag = VlanTag::new(100, 5, true);
340        assert_eq!(tag.vid(), 100);
341        assert_eq!(tag.pcp(), 5);
342        assert!(tag.dei());
343        assert_eq!(tag.tpid, TPID_8021Q);
344    }
345
346    #[test]
347    fn test_vlan_tag_fields_zero() {
348        let tag = VlanTag::new(0, 0, false);
349        assert_eq!(tag.vid(), 0);
350        assert_eq!(tag.pcp(), 0);
351        assert!(!tag.dei());
352    }
353
354    #[test]
355    fn test_vlan_tag_max_vid() {
356        let tag = VlanTag::new(4095, 7, true);
357        assert_eq!(tag.vid(), 4095);
358        assert_eq!(tag.pcp(), 7);
359        assert!(tag.dei());
360    }
361
362    #[test]
363    fn test_vlan_tag_roundtrip_bytes() {
364        let original = VlanTag::new(42, 3, false);
365        let bytes = original.to_bytes();
366        let parsed = VlanTag::from_bytes(&bytes);
367        assert_eq!(original, parsed);
368    }
369
370    #[test]
371    fn test_has_vlan_tag_true() {
372        // 12 bytes MACs + 0x8100 EtherType
373        let mut frame = vec![0u8; 14];
374        frame[12] = 0x81;
375        frame[13] = 0x00;
376        assert!(has_vlan_tag(&frame));
377    }
378
379    #[test]
380    fn test_has_vlan_tag_false() {
381        // Normal IPv4 EtherType (0x0800)
382        let mut frame = vec![0u8; 14];
383        frame[12] = 0x08;
384        frame[13] = 0x00;
385        assert!(!has_vlan_tag(&frame));
386    }
387
388    #[test]
389    fn test_has_vlan_tag_short_frame() {
390        let frame = vec![0u8; 10];
391        assert!(!has_vlan_tag(&frame));
392    }
393
394    #[test]
395    fn test_insert_and_strip_tag() {
396        // Build a minimal Ethernet frame: 6 dst + 6 src + 2 EtherType + 4 payload
397        let mut frame = vec![0xAA; 6]; // dst
398        frame.extend_from_slice(&[0xBB; 6]); // src
399        frame.extend_from_slice(&[0x08, 0x00]); // IPv4
400        frame.extend_from_slice(&[1, 2, 3, 4]); // payload
401
402        let tag = VlanTag::new(200, 2, false);
403        let tagged = insert_tag(&frame, tag);
404
405        // Tagged frame should be 4 bytes longer
406        assert_eq!(tagged.len(), frame.len() + 4);
407        assert!(has_vlan_tag(&tagged));
408
409        // Strip and verify round-trip
410        let (stripped_tag, inner) = strip_tag(&tagged);
411        assert_eq!(stripped_tag, Some(tag));
412        assert_eq!(inner, frame);
413    }
414
415    #[test]
416    fn test_strip_untagged_frame() {
417        let frame = vec![0u8; 20];
418        let (tag, inner) = strip_tag(&frame);
419        assert!(tag.is_none());
420        assert_eq!(inner, frame);
421    }
422
423    #[test]
424    fn test_manager_create_and_list() {
425        let mut mgr = VlanManager::new();
426        mgr.create_vlan("eth0", 10, VlanMode::Access(10)).unwrap();
427        mgr.create_vlan("eth0", 20, VlanMode::Trunk(vec![20, 30]))
428            .unwrap();
429
430        let vlans = mgr.list_vlans();
431        assert_eq!(vlans.len(), 2);
432        assert_eq!(vlans[0].vid, 10);
433        assert_eq!(vlans[1].vid, 20);
434    }
435
436    #[test]
437    fn test_manager_duplicate_error() {
438        let mut mgr = VlanManager::new();
439        mgr.create_vlan("eth0", 10, VlanMode::Access(10)).unwrap();
440        let err = mgr
441            .create_vlan("eth0", 10, VlanMode::Access(10))
442            .unwrap_err();
443        assert_eq!(err, VlanError::AlreadyExists);
444    }
445
446    #[test]
447    fn test_manager_invalid_vid() {
448        let mut mgr = VlanManager::new();
449        assert_eq!(
450            mgr.create_vlan("eth0", 0, VlanMode::Access(0)),
451            Err(VlanError::InvalidVid(0))
452        );
453        assert_eq!(
454            mgr.create_vlan("eth0", 4095, VlanMode::Access(4095)),
455            Err(VlanError::InvalidVid(4095))
456        );
457    }
458
459    #[test]
460    fn test_manager_delete() {
461        let mut mgr = VlanManager::new();
462        mgr.create_vlan("eth0", 10, VlanMode::Access(10)).unwrap();
463        mgr.delete_vlan("eth0", 10).unwrap();
464        assert!(mgr.list_vlans().is_empty());
465        assert_eq!(mgr.delete_vlan("eth0", 10), Err(VlanError::NotFound));
466    }
467
468    #[test]
469    fn test_ingress_access_untagged() {
470        let mut mgr = VlanManager::new();
471        mgr.create_vlan("eth0", 10, VlanMode::Access(10)).unwrap();
472
473        // Untagged frame on access port -> assigned to VLAN 10
474        let mut frame = vec![0u8; 18]; // 14 header + 4 payload
475        frame[12] = 0x08;
476        frame[13] = 0x00;
477        let result = mgr.process_ingress("eth0", &frame);
478        assert!(result.is_some());
479        let (vid, inner) = result.unwrap();
480        assert_eq!(vid, 10);
481        assert_eq!(inner, frame);
482    }
483
484    #[test]
485    fn test_ingress_trunk_tagged() {
486        let mut mgr = VlanManager::new();
487        mgr.create_vlan("eth0", 20, VlanMode::Trunk(vec![20, 30]))
488            .unwrap();
489
490        // Build tagged frame with VID=20
491        let mut frame = vec![0u8; 18];
492        frame[12] = 0x08;
493        frame[13] = 0x00;
494        let tag = VlanTag::new(20, 0, false);
495        let tagged = insert_tag(&frame, tag);
496
497        let result = mgr.process_ingress("eth0", &tagged);
498        assert!(result.is_some());
499        let (vid, inner) = result.unwrap();
500        assert_eq!(vid, 20);
501        assert_eq!(inner, frame);
502    }
503
504    #[test]
505    fn test_ingress_trunk_disallowed_vid() {
506        let mut mgr = VlanManager::new();
507        mgr.create_vlan("eth0", 20, VlanMode::Trunk(vec![20, 30]))
508            .unwrap();
509
510        // Build tagged frame with VID=99 (not in allowed list)
511        let mut frame = vec![0u8; 18];
512        frame[12] = 0x08;
513        frame[13] = 0x00;
514        let tag = VlanTag::new(99, 0, false);
515        let tagged = insert_tag(&frame, tag);
516
517        let result = mgr.process_ingress("eth0", &tagged);
518        assert!(result.is_none());
519    }
520
521    #[test]
522    fn test_egress_access_untagged() {
523        let mut mgr = VlanManager::new();
524        mgr.create_vlan("eth0", 10, VlanMode::Access(10)).unwrap();
525
526        let frame = vec![0u8; 18];
527        let out = mgr.process_egress("eth0", 10, &frame);
528        // Access port: output is untagged
529        assert_eq!(out, frame);
530    }
531
532    #[test]
533    fn test_egress_trunk_tagged() {
534        let mut mgr = VlanManager::new();
535        mgr.create_vlan("eth0", 20, VlanMode::Trunk(vec![20, 30]))
536            .unwrap();
537
538        let mut frame = vec![0u8; 18];
539        frame[12] = 0x08;
540        frame[13] = 0x00;
541        let out = mgr.process_egress("eth0", 20, &frame);
542        // Trunk port: output is tagged
543        assert!(has_vlan_tag(&out));
544        assert_eq!(out.len(), frame.len() + 4);
545    }
546}