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

veridian_kernel/services/cni/
overlay.rs

1//! VXLAN Overlay Network
2//!
3//! Provides VXLAN tunnel encapsulation/decapsulation, forwarding database
4//! (FDB) MAC learning, and ARP proxy for cross-host container networking.
5
6#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, vec::Vec};
9
10// ---------------------------------------------------------------------------
11// VXLAN Types
12// ---------------------------------------------------------------------------
13
14/// VXLAN tunnel configuration.
15#[derive(Debug, Clone)]
16pub struct VxlanTunnel {
17    /// VXLAN Network Identifier (24-bit, 0..16777215).
18    pub vni: u32,
19    /// Local tunnel endpoint IP.
20    pub local_ip: u32,
21    /// Remote tunnel endpoint IP.
22    pub remote_ip: u32,
23    /// UDP destination port (default 4789).
24    pub port: u16,
25    /// MTU for the tunnel interface.
26    pub mtu: u16,
27}
28
29impl VxlanTunnel {
30    /// Default VXLAN UDP port.
31    pub const DEFAULT_PORT: u16 = 4789;
32    /// Default tunnel MTU (1500 - 50 bytes VXLAN overhead).
33    pub const DEFAULT_MTU: u16 = 1450;
34
35    /// Create a new VXLAN tunnel.
36    pub fn new(vni: u32, local_ip: u32, remote_ip: u32) -> Self {
37        VxlanTunnel {
38            vni: vni & 0x00FF_FFFF, // mask to 24 bits
39            local_ip,
40            remote_ip,
41            port: Self::DEFAULT_PORT,
42            mtu: Self::DEFAULT_MTU,
43        }
44    }
45}
46
47/// VXLAN header (8 bytes).
48///
49/// Wire format:
50///   [flags(1)][reserved(3)][vni(3)][reserved(1)]
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct VxlanHeader {
53    /// Flags (bit 3 = VNI valid).
54    pub flags: u8,
55    /// VXLAN Network Identifier (24-bit).
56    pub vni: u32,
57}
58
59impl VxlanHeader {
60    /// VXLAN header size in bytes.
61    pub const SIZE: usize = 8;
62    /// Flag indicating VNI is valid.
63    pub const FLAG_VNI_VALID: u8 = 0x08;
64
65    /// Create a new VXLAN header.
66    pub fn new(vni: u32) -> Self {
67        VxlanHeader {
68            flags: Self::FLAG_VNI_VALID,
69            vni: vni & 0x00FF_FFFF,
70        }
71    }
72
73    /// Serialize to bytes.
74    pub fn to_bytes(&self) -> [u8; 8] {
75        let mut buf = [0u8; 8];
76        buf[0] = self.flags;
77        // bytes 1-3 reserved
78        buf[4] = ((self.vni >> 16) & 0xFF) as u8;
79        buf[5] = ((self.vni >> 8) & 0xFF) as u8;
80        buf[6] = (self.vni & 0xFF) as u8;
81        // byte 7 reserved
82        buf
83    }
84
85    /// Parse from bytes.
86    pub fn from_bytes(data: &[u8]) -> Option<Self> {
87        if data.len() < Self::SIZE {
88            return None;
89        }
90        let flags = data[0];
91        let vni = ((data[4] as u32) << 16) | ((data[5] as u32) << 8) | (data[6] as u32);
92        Some(VxlanHeader { flags, vni })
93    }
94}
95
96// ---------------------------------------------------------------------------
97// Forwarding Database
98// ---------------------------------------------------------------------------
99
100/// MAC address (6 bytes).
101pub type MacAddress = [u8; 6];
102
103/// FDB entry: maps MAC to remote VTEP IP.
104#[derive(Debug, Clone)]
105pub struct FdbEntry {
106    /// Destination MAC address.
107    pub mac: MacAddress,
108    /// Remote VTEP IP address.
109    pub vtep_ip: u32,
110    /// Last seen tick (for aging).
111    pub last_seen: u64,
112    /// Whether this is a static (non-aging) entry.
113    pub is_static: bool,
114}
115
116// ---------------------------------------------------------------------------
117// ARP Proxy Entry
118// ---------------------------------------------------------------------------
119
120/// ARP proxy entry: maps IP to MAC for remote containers.
121#[derive(Debug, Clone)]
122pub struct ArpProxyEntry {
123    /// Container IP address.
124    pub ip: u32,
125    /// Container MAC address.
126    pub mac: MacAddress,
127    /// Remote VTEP IP.
128    pub vtep_ip: u32,
129}
130
131// ---------------------------------------------------------------------------
132// VXLAN Overlay
133// ---------------------------------------------------------------------------
134
135/// VXLAN overlay error.
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub enum VxlanError {
138    /// Tunnel not found.
139    TunnelNotFound(u32),
140    /// Tunnel already exists.
141    TunnelExists(u32),
142    /// Packet too small.
143    PacketTooSmall,
144    /// Invalid VXLAN header.
145    InvalidHeader,
146    /// VNI mismatch.
147    VniMismatch { expected: u32, got: u32 },
148    /// FDB lookup miss (no entry for MAC).
149    FdbMiss,
150}
151
152/// VXLAN overlay network manager.
153#[derive(Debug)]
154pub struct VxlanOverlay {
155    /// Active tunnels keyed by VNI.
156    tunnels: BTreeMap<u32, VxlanTunnel>,
157    /// Forwarding database: MAC -> FDB entry.
158    fdb: BTreeMap<MacAddress, FdbEntry>,
159    /// ARP proxy table: IP -> ARP entry.
160    arp_proxy_table: BTreeMap<u32, ArpProxyEntry>,
161    /// FDB aging timeout in ticks.
162    fdb_age_timeout: u64,
163}
164
165impl Default for VxlanOverlay {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171impl VxlanOverlay {
172    /// Default FDB aging timeout: 300 ticks (5 minutes at 1 tick/sec).
173    pub const DEFAULT_FDB_AGE: u64 = 300;
174
175    /// Create a new VXLAN overlay manager.
176    pub fn new() -> Self {
177        VxlanOverlay {
178            tunnels: BTreeMap::new(),
179            fdb: BTreeMap::new(),
180            arp_proxy_table: BTreeMap::new(),
181            fdb_age_timeout: Self::DEFAULT_FDB_AGE,
182        }
183    }
184
185    /// Add a tunnel.
186    pub fn add_tunnel(&mut self, tunnel: VxlanTunnel) -> Result<(), VxlanError> {
187        if self.tunnels.contains_key(&tunnel.vni) {
188            return Err(VxlanError::TunnelExists(tunnel.vni));
189        }
190        self.tunnels.insert(tunnel.vni, tunnel);
191        Ok(())
192    }
193
194    /// Remove a tunnel.
195    pub fn remove_tunnel(&mut self, vni: u32) -> Result<(), VxlanError> {
196        self.tunnels
197            .remove(&vni)
198            .map(|_| ())
199            .ok_or(VxlanError::TunnelNotFound(vni))
200    }
201
202    /// Get a tunnel by VNI.
203    pub fn get_tunnel(&self, vni: u32) -> Option<&VxlanTunnel> {
204        self.tunnels.get(&vni)
205    }
206
207    /// Encapsulate a frame in a VXLAN header.
208    ///
209    /// Returns the VXLAN-encapsulated packet (header + original frame).
210    pub fn encapsulate(&self, vni: u32, inner_frame: &[u8]) -> Result<Vec<u8>, VxlanError> {
211        if !self.tunnels.contains_key(&vni) {
212            return Err(VxlanError::TunnelNotFound(vni));
213        }
214
215        let header = VxlanHeader::new(vni);
216        let header_bytes = header.to_bytes();
217
218        let mut packet = Vec::with_capacity(VxlanHeader::SIZE + inner_frame.len());
219        packet.extend_from_slice(&header_bytes);
220        packet.extend_from_slice(inner_frame);
221        Ok(packet)
222    }
223
224    /// Decapsulate a VXLAN packet.
225    ///
226    /// Returns (VNI, inner frame).
227    pub fn decapsulate(&self, packet: &[u8]) -> Result<(u32, Vec<u8>), VxlanError> {
228        let header = VxlanHeader::from_bytes(packet).ok_or(VxlanError::PacketTooSmall)?;
229
230        if header.flags & VxlanHeader::FLAG_VNI_VALID == 0 {
231            return Err(VxlanError::InvalidHeader);
232        }
233
234        if !self.tunnels.contains_key(&header.vni) {
235            return Err(VxlanError::TunnelNotFound(header.vni));
236        }
237
238        let inner = packet[VxlanHeader::SIZE..].to_vec();
239        Ok((header.vni, inner))
240    }
241
242    /// Learn a MAC address from an incoming frame.
243    pub fn fdb_learn(&mut self, mac: MacAddress, vtep_ip: u32, current_tick: u64) {
244        let entry = self.fdb.entry(mac).or_insert(FdbEntry {
245            mac,
246            vtep_ip,
247            last_seen: current_tick,
248            is_static: false,
249        });
250        entry.vtep_ip = vtep_ip;
251        entry.last_seen = current_tick;
252    }
253
254    /// Add a static FDB entry.
255    pub fn fdb_add_static(&mut self, mac: MacAddress, vtep_ip: u32) {
256        self.fdb.insert(
257            mac,
258            FdbEntry {
259                mac,
260                vtep_ip,
261                last_seen: 0,
262                is_static: true,
263            },
264        );
265    }
266
267    /// Look up a MAC address in the FDB.
268    pub fn fdb_lookup(&self, mac: &MacAddress) -> Option<&FdbEntry> {
269        self.fdb.get(mac)
270    }
271
272    /// Remove aged-out FDB entries.
273    pub fn fdb_age(&mut self, current_tick: u64) -> usize {
274        let timeout = self.fdb_age_timeout;
275        let before = self.fdb.len();
276        self.fdb.retain(|_, entry| {
277            entry.is_static || current_tick.saturating_sub(entry.last_seen) < timeout
278        });
279        before - self.fdb.len()
280    }
281
282    /// Add an ARP proxy entry.
283    pub fn arp_proxy_add(&mut self, ip: u32, mac: MacAddress, vtep_ip: u32) {
284        self.arp_proxy_table
285            .insert(ip, ArpProxyEntry { ip, mac, vtep_ip });
286    }
287
288    /// Respond to an ARP request for a remote container.
289    ///
290    /// Returns the MAC address if we can proxy-respond.
291    pub fn arp_proxy(&self, target_ip: u32) -> Option<MacAddress> {
292        self.arp_proxy_table.get(&target_ip).map(|e| e.mac)
293    }
294
295    /// Get tunnel count.
296    pub fn tunnel_count(&self) -> usize {
297        self.tunnels.len()
298    }
299
300    /// Get FDB entry count.
301    pub fn fdb_count(&self) -> usize {
302        self.fdb.len()
303    }
304}
305
306// ---------------------------------------------------------------------------
307// Tests
308// ---------------------------------------------------------------------------
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    fn ip(a: u8, b: u8, c: u8, d: u8) -> u32 {
315        ((a as u32) << 24) | ((b as u32) << 16) | ((c as u32) << 8) | (d as u32)
316    }
317
318    #[test]
319    fn test_vxlan_header_roundtrip() {
320        let header = VxlanHeader::new(12345);
321        let bytes = header.to_bytes();
322        let parsed = VxlanHeader::from_bytes(&bytes).unwrap();
323        assert_eq!(parsed.vni, 12345);
324        assert_eq!(parsed.flags, VxlanHeader::FLAG_VNI_VALID);
325    }
326
327    #[test]
328    fn test_vxlan_header_too_small() {
329        assert!(VxlanHeader::from_bytes(&[0u8; 4]).is_none());
330    }
331
332    #[test]
333    fn test_add_remove_tunnel() {
334        let mut overlay = VxlanOverlay::new();
335        let tunnel = VxlanTunnel::new(100, ip(10, 0, 0, 1), ip(10, 0, 0, 2));
336        overlay.add_tunnel(tunnel).unwrap();
337        assert_eq!(overlay.tunnel_count(), 1);
338        overlay.remove_tunnel(100).unwrap();
339        assert_eq!(overlay.tunnel_count(), 0);
340    }
341
342    #[test]
343    fn test_duplicate_tunnel() {
344        let mut overlay = VxlanOverlay::new();
345        let t1 = VxlanTunnel::new(100, ip(10, 0, 0, 1), ip(10, 0, 0, 2));
346        let t2 = VxlanTunnel::new(100, ip(10, 0, 0, 1), ip(10, 0, 0, 3));
347        overlay.add_tunnel(t1).unwrap();
348        assert_eq!(overlay.add_tunnel(t2), Err(VxlanError::TunnelExists(100)));
349    }
350
351    #[test]
352    fn test_encap_decap() {
353        let mut overlay = VxlanOverlay::new();
354        overlay
355            .add_tunnel(VxlanTunnel::new(42, ip(10, 0, 0, 1), ip(10, 0, 0, 2)))
356            .unwrap();
357
358        let frame = [0xDE, 0xAD, 0xBE, 0xEF];
359        let packet = overlay.encapsulate(42, &frame).unwrap();
360        assert_eq!(packet.len(), 8 + 4);
361
362        let (vni, inner) = overlay.decapsulate(&packet).unwrap();
363        assert_eq!(vni, 42);
364        assert_eq!(inner, frame);
365    }
366
367    #[test]
368    fn test_encap_unknown_vni() {
369        let overlay = VxlanOverlay::new();
370        assert_eq!(
371            overlay.encapsulate(999, &[1, 2, 3]),
372            Err(VxlanError::TunnelNotFound(999))
373        );
374    }
375
376    #[test]
377    fn test_fdb_learn_and_lookup() {
378        let mut overlay = VxlanOverlay::new();
379        let mac = [0x02, 0x42, 0xAC, 0x11, 0x00, 0x02];
380        overlay.fdb_learn(mac, ip(10, 0, 0, 5), 100);
381        let entry = overlay.fdb_lookup(&mac).unwrap();
382        assert_eq!(entry.vtep_ip, ip(10, 0, 0, 5));
383        assert!(!entry.is_static);
384    }
385
386    #[test]
387    fn test_fdb_aging() {
388        let mut overlay = VxlanOverlay::new();
389        let mac = [0x02, 0x42, 0xAC, 0x11, 0x00, 0x03];
390        overlay.fdb_learn(mac, ip(10, 0, 0, 5), 100);
391        // Not aged yet
392        assert_eq!(overlay.fdb_age(200), 0);
393        // Aged out
394        assert_eq!(overlay.fdb_age(500), 1);
395        assert_eq!(overlay.fdb_count(), 0);
396    }
397
398    #[test]
399    fn test_arp_proxy() {
400        let mut overlay = VxlanOverlay::new();
401        let mac = [0x02, 0x42, 0xAC, 0x11, 0x00, 0x04];
402        let container_ip = ip(10, 244, 1, 5);
403        overlay.arp_proxy_add(container_ip, mac, ip(10, 0, 0, 2));
404        let resolved = overlay.arp_proxy(container_ip).unwrap();
405        assert_eq!(resolved, mac);
406        assert!(overlay.arp_proxy(ip(10, 244, 1, 99)).is_none());
407    }
408}