Skip to main content

arcbox_virtio_net/
config.rs

1//! Network device configuration, link status, and host-side `NetPort`.
2
3use std::os::unix::io::RawFd;
4use std::sync::atomic::AtomicU16;
5
6/// Network device configuration.
7#[derive(Debug, Clone)]
8pub struct NetConfig {
9    /// MAC address.
10    pub mac: [u8; 6],
11    /// MTU size.
12    pub mtu: u16,
13    /// TAP device name (if applicable).
14    pub tap_name: Option<String>,
15    /// Number of queue pairs.
16    pub num_queues: u16,
17}
18
19impl Default for NetConfig {
20    fn default() -> Self {
21        Self {
22            mac: [0x52, 0x54, 0x00, 0x12, 0x34, 0x56], // Default QEMU MAC prefix
23            mtu: 1500,
24            tap_name: None,
25            num_queues: 1,
26        }
27    }
28}
29
30impl NetConfig {
31    /// Generates a random MAC address.
32    ///
33    /// The first three bytes are the locally-administered ArcBox OUI
34    /// (`52:54:AB`). The remaining three bytes come from `getrandom`, giving
35    /// 24 bits of true entropy — 1-in-16M collision odds. Time-derived
36    /// fallbacks were rejected because concurrent VM creation within the
37    /// same nanosecond tick would otherwise produce identical MACs and
38    /// poison the host ARP cache.
39    #[must_use]
40    pub fn random_mac() -> [u8; 6] {
41        let mut mac = [0u8; 6];
42        mac[0] = 0x52;
43        mac[1] = 0x54;
44        mac[2] = 0xAB;
45        let mut suffix = [0u8; 3];
46        if getrandom::getrandom(&mut suffix).is_err() {
47            // Extremely unlikely on supported platforms; fall back to the
48            // process-id XOR monotonic counter so we still avoid the
49            // all-zeroes collision pattern.
50            static COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
51            let n = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
52            let pid = std::process::id();
53            let mixed = pid ^ n ^ 0x9E37_79B9;
54            suffix[0] = (mixed >> 16) as u8;
55            suffix[1] = (mixed >> 8) as u8;
56            suffix[2] = mixed as u8;
57        }
58        mac[3..6].copy_from_slice(&suffix);
59        mac
60    }
61}
62
63/// Network device status.
64#[repr(u16)]
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum NetStatus {
67    /// Link is up.
68    LinkUp = 1,
69    /// Announce required.
70    Announce = 2,
71}
72
73/// Host-side plumbing for a `VirtioNet` instance running under the custom VMM.
74///
75/// Holds the raw socket fd used to exchange L2 frames with the datapath
76/// (primary NIC) or the vmnet relay (bridge NIC), plus the TX cursor.
77///
78/// A `NetPort` is bound once via `VirtioNet::bind_port` after the host
79/// fd is known (which is *after* device registration, when the socketpair
80/// has been created). It is kept in an `OnceLock` on the device so hot-path
81/// methods can read both fields without mutex overhead.
82#[derive(Debug)]
83pub struct NetPort {
84    /// Raw fd of the host-side socketpair peer. Reads pull inbound frames
85    /// from the datapath; writes push guest TX frames to the datapath.
86    pub host_fd: RawFd,
87    /// Cursor into the TX avail ring — the last avail index the device
88    /// has already drained. Wrapping u16 matches the VirtIO spec.
89    pub last_avail_tx: AtomicU16,
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_net_config_default() {
98        let config = NetConfig::default();
99        assert_eq!(config.mac, [0x52, 0x54, 0x00, 0x12, 0x34, 0x56]);
100        assert_eq!(config.mtu, 1500);
101        assert!(config.tap_name.is_none());
102        assert_eq!(config.num_queues, 1);
103    }
104
105    #[test]
106    fn test_net_config_custom() {
107        let config = NetConfig {
108            mac: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55],
109            mtu: 9000, // Jumbo frames
110            tap_name: Some("tap0".to_string()),
111            num_queues: 4,
112        };
113        assert_eq!(config.mac[0], 0x00);
114        assert_eq!(config.mtu, 9000);
115        assert_eq!(config.tap_name.as_deref(), Some("tap0"));
116        assert_eq!(config.num_queues, 4);
117    }
118
119    #[test]
120    fn test_random_mac() {
121        let mac1 = NetConfig::random_mac();
122        let mac2 = NetConfig::random_mac();
123
124        assert_eq!(mac1[0], 0x52);
125        assert_eq!(mac1[1], 0x54);
126        assert_eq!(mac1[2], 0xAB);
127
128        assert_eq!(mac2[0], 0x52);
129        assert_eq!(mac2[1], 0x54);
130        assert_eq!(mac2[2], 0xAB);
131    }
132
133    #[test]
134    fn test_net_status_values() {
135        assert_eq!(NetStatus::LinkUp as u16, 1);
136        assert_eq!(NetStatus::Announce as u16, 2);
137    }
138
139    #[test]
140    fn test_config_clone() {
141        let config = NetConfig {
142            mac: [1, 2, 3, 4, 5, 6],
143            mtu: 1234,
144            tap_name: Some("test".to_string()),
145            num_queues: 2,
146        };
147
148        let cloned = config.clone();
149        assert_eq!(cloned.mac, config.mac);
150        assert_eq!(cloned.mtu, config.mtu);
151        assert_eq!(cloned.tap_name, config.tap_name);
152        assert_eq!(cloned.num_queues, config.num_queues);
153    }
154}