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

veridian_kernel/virt/containers/
networking.rs

1//! Container Networking - virtual Ethernet pairs, bridge, NAT masquerade, ARP
2//! proxy.
3
4#[cfg(feature = "alloc")]
5use alloc::{string::String, vec::Vec};
6use core::sync::atomic::{AtomicU64, Ordering};
7
8use crate::error::KernelError;
9
10/// Virtual Ethernet interface state.
11#[cfg(feature = "alloc")]
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct VethEndpoint {
14    /// Interface name.
15    pub name: String,
16    /// Peer interface name.
17    pub peer_name: String,
18    /// MAC address (6 bytes).
19    pub mac: [u8; 6],
20    /// IPv4 address (network byte order).
21    pub ipv4_addr: u32,
22    /// IPv4 subnet mask (network byte order).
23    pub ipv4_mask: u32,
24    /// MTU in bytes (default 1500).
25    pub mtu: u16,
26    /// Whether the interface is up.
27    pub is_up: bool,
28    /// Namespace ID this endpoint belongs to (0 = host).
29    pub namespace_id: u64,
30}
31
32/// A virtual Ethernet pair.
33#[cfg(feature = "alloc")]
34#[derive(Debug, Clone)]
35pub struct VethPair {
36    /// Host-side endpoint.
37    pub host: VethEndpoint,
38    /// Container-side endpoint.
39    pub container: VethEndpoint,
40}
41
42/// NAT port mapping entry.
43#[cfg(feature = "alloc")]
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct NatPortMapping {
46    /// External (host) port.
47    pub external_port: u16,
48    /// Internal (container) port.
49    pub internal_port: u16,
50    /// Protocol: 6=TCP, 17=UDP.
51    pub protocol: u8,
52    /// Container IPv4 address.
53    pub container_ip: u32,
54}
55
56/// NAT masquerade table for outbound SNAT and inbound port forwarding.
57#[cfg(feature = "alloc")]
58#[derive(Debug, Clone)]
59pub struct NatTable {
60    /// Host external IP address.
61    pub host_ip: u32,
62    /// Port mappings for inbound (DNAT).
63    pub port_mappings: Vec<NatPortMapping>,
64    /// Whether SNAT masquerade is enabled.
65    pub masquerade_enabled: bool,
66}
67
68#[cfg(feature = "alloc")]
69impl NatTable {
70    pub fn new(host_ip: u32) -> Self {
71        Self {
72            host_ip,
73            port_mappings: Vec::new(),
74            masquerade_enabled: false,
75        }
76    }
77
78    /// Enable SNAT masquerade for outbound traffic.
79    pub fn enable_masquerade(&mut self) {
80        self.masquerade_enabled = true;
81    }
82
83    /// Add a port mapping for inbound traffic.
84    pub fn add_port_mapping(&mut self, mapping: NatPortMapping) -> Result<(), KernelError> {
85        // Check for duplicate external port + protocol
86        for existing in &self.port_mappings {
87            if existing.external_port == mapping.external_port
88                && existing.protocol == mapping.protocol
89            {
90                return Err(KernelError::AlreadyExists {
91                    resource: "nat port mapping",
92                    id: mapping.external_port as u64,
93                });
94            }
95        }
96        self.port_mappings.push(mapping);
97        Ok(())
98    }
99
100    /// Remove a port mapping.
101    pub fn remove_port_mapping(&mut self, external_port: u16, protocol: u8) -> bool {
102        let before = self.port_mappings.len();
103        self.port_mappings
104            .retain(|m| !(m.external_port == external_port && m.protocol == protocol));
105        self.port_mappings.len() < before
106    }
107
108    /// Look up a port mapping for inbound traffic.
109    pub fn lookup_inbound(&self, external_port: u16, protocol: u8) -> Option<&NatPortMapping> {
110        self.port_mappings
111            .iter()
112            .find(|m| m.external_port == external_port && m.protocol == protocol)
113    }
114
115    /// Apply SNAT: rewrite source IP to host IP for outbound packets.
116    pub fn snat_rewrite(&self, _src_ip: u32) -> Option<u32> {
117        if self.masquerade_enabled {
118            Some(self.host_ip)
119        } else {
120            None
121        }
122    }
123}
124
125/// ARP proxy entry for container IPs.
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct ArpProxyEntry {
128    /// IPv4 address to proxy.
129    pub ip: u32,
130    /// MAC address to respond with.
131    pub mac: [u8; 6],
132}
133
134/// Bridge configuration for container networking.
135#[cfg(feature = "alloc")]
136#[derive(Debug, Clone)]
137pub struct VethBridge {
138    /// Bridge name.
139    pub name: String,
140    /// Bridge IPv4 address (gateway).
141    pub bridge_ip: u32,
142    /// Bridge subnet mask.
143    pub subnet_mask: u32,
144    /// Attached veth host-side endpoint names.
145    pub attached_interfaces: Vec<String>,
146    /// ARP proxy entries.
147    pub arp_proxy_entries: Vec<ArpProxyEntry>,
148    /// NAT table.
149    pub nat: NatTable,
150}
151
152#[cfg(feature = "alloc")]
153impl VethBridge {
154    pub fn new(name: &str, bridge_ip: u32, subnet_mask: u32) -> Self {
155        Self {
156            name: String::from(name),
157            bridge_ip,
158            subnet_mask,
159            attached_interfaces: Vec::new(),
160            arp_proxy_entries: Vec::new(),
161            nat: NatTable::new(bridge_ip),
162        }
163    }
164
165    /// Attach a host-side veth endpoint to the bridge.
166    pub fn attach(&mut self, interface_name: &str) {
167        if !self.attached_interfaces.iter().any(|n| n == interface_name) {
168            self.attached_interfaces.push(String::from(interface_name));
169        }
170    }
171
172    /// Detach an interface from the bridge.
173    pub fn detach(&mut self, interface_name: &str) {
174        self.attached_interfaces.retain(|n| n != interface_name);
175    }
176
177    /// Add an ARP proxy entry.
178    pub fn add_arp_proxy(&mut self, entry: ArpProxyEntry) {
179        self.arp_proxy_entries.push(entry);
180    }
181
182    /// Look up an ARP proxy entry by IP.
183    pub fn arp_lookup(&self, ip: u32) -> Option<&ArpProxyEntry> {
184        self.arp_proxy_entries.iter().find(|e| e.ip == ip)
185    }
186
187    /// Check if an IP is within the bridge subnet.
188    pub fn in_subnet(&self, ip: u32) -> bool {
189        (ip & self.subnet_mask) == (self.bridge_ip & self.subnet_mask)
190    }
191
192    pub fn attached_count(&self) -> usize {
193        self.attached_interfaces.len()
194    }
195}
196
197static NEXT_VETH_ID: AtomicU64 = AtomicU64::new(1);
198
199/// Generate a deterministic MAC address from a veth pair ID.
200pub fn generate_veth_mac(veth_id: u64) -> [u8; 6] {
201    [
202        0x02, // locally administered
203        0x42,
204        ((veth_id >> 24) & 0xff) as u8,
205        ((veth_id >> 16) & 0xff) as u8,
206        ((veth_id >> 8) & 0xff) as u8,
207        (veth_id & 0xff) as u8,
208    ]
209}
210
211/// Create a veth pair with generated MACs.
212#[cfg(feature = "alloc")]
213pub fn create_veth_pair(host_name: &str, container_name: &str, namespace_id: u64) -> VethPair {
214    let id = NEXT_VETH_ID.fetch_add(1, Ordering::Relaxed);
215    let host_mac = generate_veth_mac(id);
216    // Container MAC: flip one bit to differentiate
217    let mut container_mac = generate_veth_mac(id);
218    container_mac[5] ^= 0x01;
219
220    VethPair {
221        host: VethEndpoint {
222            name: String::from(host_name),
223            peer_name: String::from(container_name),
224            mac: host_mac,
225            ipv4_addr: 0,
226            ipv4_mask: 0,
227            mtu: 1500,
228            is_up: false,
229            namespace_id: 0,
230        },
231        container: VethEndpoint {
232            name: String::from(container_name),
233            peer_name: String::from(host_name),
234            mac: container_mac,
235            ipv4_addr: 0,
236            ipv4_mask: 0,
237            mtu: 1500,
238            is_up: false,
239            namespace_id,
240        },
241    }
242}