1use std::net::{Ipv4Addr, Ipv6Addr};
9use std::sync::Arc;
10use std::thread::JoinHandle;
11
12use msb_krun::backends::net::NetBackend;
13
14use crate::backend::SmoltcpBackend;
15use crate::config::NetworkConfig;
16use crate::shared::{DEFAULT_QUEUE_CAPACITY, SharedState};
17use crate::stack::{self, PollLoopConfig};
18use crate::tls::state::TlsState;
19
20const MAX_SLOT: u64 = u16::MAX as u64;
29
30pub struct SmoltcpNetwork {
41 config: NetworkConfig,
42 shared: Arc<SharedState>,
43 backend: Option<SmoltcpBackend>,
44 poll_handle: Option<JoinHandle<()>>,
45
46 guest_mac: [u8; 6],
48 gateway_mac: [u8; 6],
49 mtu: u16,
50 guest_ipv4: Ipv4Addr,
51 gateway_ipv4: Ipv4Addr,
52 guest_ipv6: Ipv6Addr,
53 gateway_ipv6: Ipv6Addr,
54
55 tls_state: Option<Arc<TlsState>>,
57}
58
59#[derive(Clone)]
61pub struct TerminationHandle {
62 shared: Arc<SharedState>,
63}
64
65#[derive(Clone)]
67pub struct MetricsHandle {
68 shared: Arc<SharedState>,
69}
70
71impl SmoltcpNetwork {
76 pub fn new(config: NetworkConfig, slot: u64) -> Self {
83 assert!(
84 slot <= MAX_SLOT,
85 "sandbox slot {slot} exceeds address pool capacity (max {MAX_SLOT})"
86 );
87
88 let guest_mac = config
89 .interface
90 .mac
91 .unwrap_or_else(|| derive_guest_mac(slot));
92 let gateway_mac = derive_gateway_mac(slot);
93 let mtu = config.interface.mtu.unwrap_or(1500);
94 let guest_ipv4 = config
95 .interface
96 .ipv4_address
97 .unwrap_or_else(|| derive_guest_ipv4(slot));
98 let gateway_ipv4 = gateway_from_guest_ipv4(guest_ipv4);
99 let guest_ipv6 = config
100 .interface
101 .ipv6_address
102 .unwrap_or_else(|| derive_guest_ipv6(slot));
103 let gateway_ipv6 = gateway_from_guest_ipv6(guest_ipv6);
104
105 let queue_capacity = config
106 .max_connections
107 .unwrap_or(DEFAULT_QUEUE_CAPACITY)
108 .max(DEFAULT_QUEUE_CAPACITY);
109 let shared = Arc::new(SharedState::new(queue_capacity));
110 let backend = SmoltcpBackend::new(shared.clone());
111
112 let tls_state = if config.tls.enabled {
113 Some(Arc::new(TlsState::new(
114 config.tls.clone(),
115 config.secrets.clone(),
116 )))
117 } else {
118 None
119 };
120
121 Self {
122 config,
123 shared,
124 backend: Some(backend),
125 poll_handle: None,
126 guest_mac,
127 gateway_mac,
128 mtu,
129 guest_ipv4,
130 gateway_ipv4,
131 guest_ipv6,
132 gateway_ipv6,
133 tls_state,
134 }
135 }
136
137 pub fn start(&mut self, tokio_handle: tokio::runtime::Handle) {
142 let shared = self.shared.clone();
143 let poll_config = PollLoopConfig {
144 gateway_mac: self.gateway_mac,
145 guest_mac: self.guest_mac,
146 gateway_ipv4: self.gateway_ipv4,
147 guest_ipv4: self.guest_ipv4,
148 gateway_ipv6: self.gateway_ipv6,
149 mtu: self.mtu as usize,
150 };
151 let network_policy = self.config.policy.clone();
152 let dns_config = self.config.dns.clone();
153 let tls_state = self.tls_state.clone();
154 let published_ports = self.config.ports.clone();
155 let max_connections = self.config.max_connections;
156
157 self.poll_handle = Some(
158 std::thread::Builder::new()
159 .name("smoltcp-poll".into())
160 .spawn(move || {
161 stack::smoltcp_poll_loop(
162 shared,
163 poll_config,
164 network_policy,
165 dns_config,
166 tls_state,
167 published_ports,
168 max_connections,
169 tokio_handle,
170 );
171 })
172 .expect("failed to spawn smoltcp poll thread"),
173 );
174 }
175
176 pub fn take_backend(&mut self) -> Box<dyn NetBackend + Send> {
178 Box::new(self.backend.take().expect("backend already taken"))
179 }
180
181 pub fn guest_mac(&self) -> [u8; 6] {
183 self.guest_mac
184 }
185
186 pub fn guest_env_vars(&self) -> Vec<(String, String)> {
191 let mut vars = vec![
192 (
193 "MSB_NET".into(),
194 format!(
195 "iface=eth0,mac={},mtu={}",
196 format_mac(self.guest_mac),
197 self.mtu,
198 ),
199 ),
200 (
201 "MSB_NET_IPV4".into(),
202 format!(
203 "addr={}/30,gw={},dns={}",
204 self.guest_ipv4, self.gateway_ipv4, self.gateway_ipv4,
205 ),
206 ),
207 (
208 "MSB_NET_IPV6".into(),
209 format!(
210 "addr={}/64,gw={},dns={}",
211 self.guest_ipv6, self.gateway_ipv6, self.gateway_ipv6,
212 ),
213 ),
214 ];
215
216 for secret in &self.config.secrets.secrets {
218 vars.push((secret.env_var.clone(), secret.placeholder.clone()));
219 }
220
221 vars
222 }
223
224 pub fn ca_cert_pem(&self) -> Option<Vec<u8>> {
228 self.tls_state.as_ref().map(|s| s.ca_cert_pem())
229 }
230
231 pub fn termination_handle(&self) -> TerminationHandle {
233 TerminationHandle {
234 shared: self.shared.clone(),
235 }
236 }
237
238 pub fn metrics_handle(&self) -> MetricsHandle {
240 MetricsHandle {
241 shared: self.shared.clone(),
242 }
243 }
244}
245
246impl TerminationHandle {
247 pub fn set_hook(&self, hook: Arc<dyn Fn() + Send + Sync>) {
249 self.shared.set_termination_hook(hook);
250 }
251}
252
253impl MetricsHandle {
254 pub fn tx_bytes(&self) -> u64 {
256 self.shared.tx_bytes()
257 }
258
259 pub fn rx_bytes(&self) -> u64 {
261 self.shared.rx_bytes()
262 }
263}
264
265fn derive_guest_mac(slot: u64) -> [u8; 6] {
273 let s = slot.to_be_bytes();
274 [0x02, 0x6d, 0x73, s[6], s[7], 0x02]
275}
276
277fn derive_gateway_mac(slot: u64) -> [u8; 6] {
281 let s = slot.to_be_bytes();
282 [0x02, 0x6d, 0x73, s[6], s[7], 0x01]
283}
284
285fn derive_guest_ipv4(slot: u64) -> Ipv4Addr {
290 let base: u32 = u32::from(Ipv4Addr::new(100, 96, 0, 0));
291 let offset = (slot as u32) * 4 + 2; Ipv4Addr::from(base + offset)
293}
294
295fn gateway_from_guest_ipv4(guest: Ipv4Addr) -> Ipv4Addr {
297 Ipv4Addr::from(u32::from(guest) - 1)
298}
299
300fn derive_guest_ipv6(slot: u64) -> Ipv6Addr {
305 Ipv6Addr::new(0xfd42, 0x6d73, 0x0062, slot as u16, 0, 0, 0, 2)
306}
307
308fn gateway_from_guest_ipv6(guest: Ipv6Addr) -> Ipv6Addr {
310 let segs = guest.segments();
311 Ipv6Addr::new(segs[0], segs[1], segs[2], segs[3], 0, 0, 0, 1)
312}
313
314fn format_mac(mac: [u8; 6]) -> String {
316 format!(
317 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
318 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
319 )
320}
321
322#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn derive_addresses_slot_0() {
332 assert_eq!(derive_guest_mac(0), [0x02, 0x6d, 0x73, 0x00, 0x00, 0x02]);
333 assert_eq!(derive_gateway_mac(0), [0x02, 0x6d, 0x73, 0x00, 0x00, 0x01]);
334 assert_eq!(derive_guest_ipv4(0), Ipv4Addr::new(100, 96, 0, 2));
335 assert_eq!(
336 gateway_from_guest_ipv4(Ipv4Addr::new(100, 96, 0, 2)),
337 Ipv4Addr::new(100, 96, 0, 1)
338 );
339 }
340
341 #[test]
342 fn derive_addresses_slot_1() {
343 assert_eq!(derive_guest_ipv4(1), Ipv4Addr::new(100, 96, 0, 6));
344 assert_eq!(
345 gateway_from_guest_ipv4(Ipv4Addr::new(100, 96, 0, 6)),
346 Ipv4Addr::new(100, 96, 0, 5)
347 );
348 }
349
350 #[test]
351 fn derive_ipv6_slot_0() {
352 assert_eq!(
353 derive_guest_ipv6(0),
354 "fd42:6d73:62:0::2".parse::<Ipv6Addr>().unwrap()
355 );
356 assert_eq!(
357 gateway_from_guest_ipv6(derive_guest_ipv6(0)),
358 "fd42:6d73:62:0::1".parse::<Ipv6Addr>().unwrap()
359 );
360 }
361
362 #[test]
363 fn format_mac_address() {
364 assert_eq!(
365 format_mac([0x02, 0x6d, 0x73, 0x00, 0x00, 0x01]),
366 "02:6d:73:00:00:01"
367 );
368 }
369
370 #[test]
371 fn guest_env_vars_format() {
372 let config = NetworkConfig::default();
373 let net = SmoltcpNetwork::new(config, 0);
374 let vars = net.guest_env_vars();
375
376 assert_eq!(vars.len(), 3);
377 assert_eq!(vars[0].0, "MSB_NET");
378 assert!(vars[0].1.contains("iface=eth0"));
379 assert_eq!(vars[1].0, "MSB_NET_IPV4");
380 assert!(vars[1].1.contains("/30"));
381 assert_eq!(vars[2].0, "MSB_NET_IPV6");
382 assert!(vars[2].1.contains("/64"));
383 }
384}