1use std::net::{Ipv4Addr, Ipv6Addr};
9use std::sync::Arc;
10use std::thread::JoinHandle;
11
12use microsandbox_protocol::{ENV_HOST_ALIAS, ENV_NET, ENV_NET_IPV4, ENV_NET_IPV6};
13use msb_krun::backends::net::NetBackend;
14
15use crate::backend::SmoltcpBackend;
16use crate::config::NetworkConfig;
17use crate::shared::{DEFAULT_QUEUE_CAPACITY, SharedState};
18use crate::stack::{self, GatewayIps, PollLoopConfig};
19use crate::tls::state::TlsState;
20
21const MAX_SLOT: u64 = u16::MAX as u64;
30
31pub struct SmoltcpNetwork {
42 config: NetworkConfig,
43 shared: Arc<SharedState>,
44 backend: Option<SmoltcpBackend>,
45 poll_handle: Option<JoinHandle<()>>,
46
47 guest_mac: [u8; 6],
49 gateway_mac: [u8; 6],
50 mtu: u16,
51 guest_ipv4: Ipv4Addr,
52 gateway_ipv4: Ipv4Addr,
53 guest_ipv6: Ipv6Addr,
54 gateway_ipv6: Ipv6Addr,
55
56 tls_state: Option<Arc<TlsState>>,
58}
59
60#[derive(Clone)]
62pub struct TerminationHandle {
63 shared: Arc<SharedState>,
64}
65
66#[derive(Clone)]
68pub struct MetricsHandle {
69 shared: Arc<SharedState>,
70}
71
72impl SmoltcpNetwork {
77 pub fn new(config: NetworkConfig, slot: u64) -> Self {
84 assert!(
85 slot <= MAX_SLOT,
86 "sandbox slot {slot} exceeds address pool capacity (max {MAX_SLOT})"
87 );
88
89 let guest_mac = config
90 .interface
91 .mac
92 .unwrap_or_else(|| derive_guest_mac(slot));
93 let gateway_mac = derive_gateway_mac(slot);
94 let mtu = config.interface.mtu.unwrap_or(1500);
95 let guest_ipv4 = config
96 .interface
97 .ipv4_address
98 .unwrap_or_else(|| derive_guest_ipv4(slot));
99 let gateway_ipv4 = gateway_from_guest_ipv4(guest_ipv4);
100 let guest_ipv6 = config
101 .interface
102 .ipv6_address
103 .unwrap_or_else(|| derive_guest_ipv6(slot));
104 let gateway_ipv6 = gateway_from_guest_ipv6(guest_ipv6);
105
106 let queue_capacity = config
107 .max_connections
108 .unwrap_or(DEFAULT_QUEUE_CAPACITY)
109 .max(DEFAULT_QUEUE_CAPACITY);
110 let shared = Arc::new(SharedState::new(queue_capacity));
111 let backend = SmoltcpBackend::new(shared.clone());
112
113 let tls_state = if config.tls.enabled {
114 Some(Arc::new(TlsState::new(
115 config.tls.clone(),
116 config.secrets.clone(),
117 )))
118 } else {
119 None
120 };
121
122 Self {
123 config,
124 shared,
125 backend: Some(backend),
126 poll_handle: None,
127 guest_mac,
128 gateway_mac,
129 mtu,
130 guest_ipv4,
131 gateway_ipv4,
132 guest_ipv6,
133 gateway_ipv6,
134 tls_state,
135 }
136 }
137
138 fn gateway_ips(&self) -> GatewayIps {
140 GatewayIps {
141 ipv4: self.gateway_ipv4,
142 ipv6: self.gateway_ipv6,
143 }
144 }
145
146 pub fn start(&mut self, tokio_handle: tokio::runtime::Handle) {
151 let shared = self.shared.clone();
152 let poll_config = PollLoopConfig {
153 gateway_mac: self.gateway_mac,
154 guest_mac: self.guest_mac,
155 gateway: self.gateway_ips(),
156 guest_ipv4: self.guest_ipv4,
157 mtu: self.mtu as usize,
158 };
159 let network_policy = self.config.policy.clone();
160 let dns_config = self.config.dns.clone();
161 let tls_state = self.tls_state.clone();
162 let published_ports = self.config.ports.clone();
163 let max_connections = self.config.max_connections;
164
165 self.poll_handle = Some(
166 std::thread::Builder::new()
167 .name("smoltcp-poll".into())
168 .spawn(move || {
169 stack::smoltcp_poll_loop(
170 shared,
171 poll_config,
172 network_policy,
173 dns_config,
174 tls_state,
175 published_ports,
176 max_connections,
177 tokio_handle,
178 );
179 })
180 .expect("failed to spawn smoltcp poll thread"),
181 );
182 }
183
184 pub fn take_backend(&mut self) -> Box<dyn NetBackend + Send> {
186 Box::new(self.backend.take().expect("backend already taken"))
187 }
188
189 pub fn guest_mac(&self) -> [u8; 6] {
191 self.guest_mac
192 }
193
194 pub fn guest_env_vars(&self) -> Vec<(String, String)> {
199 let mut vars = vec![
200 (
201 ENV_NET.into(),
202 format!(
203 "iface=eth0,mac={},mtu={}",
204 format_mac(self.guest_mac),
205 self.mtu,
206 ),
207 ),
208 (
209 ENV_NET_IPV4.into(),
210 format!(
211 "addr={}/30,gw={},dns={}",
212 self.guest_ipv4, self.gateway_ipv4, self.gateway_ipv4,
213 ),
214 ),
215 (
216 ENV_NET_IPV6.into(),
217 format!(
218 "addr={}/64,gw={},dns={}",
219 self.guest_ipv6, self.gateway_ipv6, self.gateway_ipv6,
220 ),
221 ),
222 (ENV_HOST_ALIAS.into(), crate::HOST_ALIAS.into()),
223 ];
224
225 for secret in &self.config.secrets.secrets {
227 vars.push((secret.env_var.clone(), secret.placeholder.clone()));
228 }
229
230 vars
231 }
232
233 pub fn ca_cert_pem(&self) -> Option<Vec<u8>> {
237 self.tls_state.as_ref().map(|s| s.ca_cert_pem())
238 }
239
240 pub fn host_cas_cert_pem(&self) -> Option<Vec<u8>> {
248 if !self.config.trust_host_cas {
249 return None;
250 }
251 crate::tls::host_cas::collect_host_cas()
252 }
253
254 pub fn termination_handle(&self) -> TerminationHandle {
256 TerminationHandle {
257 shared: self.shared.clone(),
258 }
259 }
260
261 pub fn metrics_handle(&self) -> MetricsHandle {
263 MetricsHandle {
264 shared: self.shared.clone(),
265 }
266 }
267}
268
269impl TerminationHandle {
270 pub fn set_hook(&self, hook: Arc<dyn Fn() + Send + Sync>) {
272 self.shared.set_termination_hook(hook);
273 }
274}
275
276impl MetricsHandle {
277 pub fn tx_bytes(&self) -> u64 {
279 self.shared.tx_bytes()
280 }
281
282 pub fn rx_bytes(&self) -> u64 {
284 self.shared.rx_bytes()
285 }
286}
287
288fn derive_guest_mac(slot: u64) -> [u8; 6] {
296 let s = slot.to_be_bytes();
297 [0x02, 0x6d, 0x73, s[6], s[7], 0x02]
298}
299
300fn derive_gateway_mac(slot: u64) -> [u8; 6] {
304 let s = slot.to_be_bytes();
305 [0x02, 0x6d, 0x73, s[6], s[7], 0x01]
306}
307
308fn derive_guest_ipv4(slot: u64) -> Ipv4Addr {
313 let base: u32 = u32::from(Ipv4Addr::new(100, 96, 0, 0));
314 let offset = (slot as u32) * 4 + 2; Ipv4Addr::from(base + offset)
316}
317
318fn gateway_from_guest_ipv4(guest: Ipv4Addr) -> Ipv4Addr {
320 Ipv4Addr::from(u32::from(guest) - 1)
321}
322
323fn derive_guest_ipv6(slot: u64) -> Ipv6Addr {
328 Ipv6Addr::new(0xfd42, 0x6d73, 0x0062, slot as u16, 0, 0, 0, 2)
329}
330
331fn gateway_from_guest_ipv6(guest: Ipv6Addr) -> Ipv6Addr {
333 let segs = guest.segments();
334 Ipv6Addr::new(segs[0], segs[1], segs[2], segs[3], 0, 0, 0, 1)
335}
336
337fn format_mac(mac: [u8; 6]) -> String {
339 format!(
340 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
341 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
342 )
343}
344
345#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn derive_addresses_slot_0() {
355 assert_eq!(derive_guest_mac(0), [0x02, 0x6d, 0x73, 0x00, 0x00, 0x02]);
356 assert_eq!(derive_gateway_mac(0), [0x02, 0x6d, 0x73, 0x00, 0x00, 0x01]);
357 assert_eq!(derive_guest_ipv4(0), Ipv4Addr::new(100, 96, 0, 2));
358 assert_eq!(
359 gateway_from_guest_ipv4(Ipv4Addr::new(100, 96, 0, 2)),
360 Ipv4Addr::new(100, 96, 0, 1)
361 );
362 }
363
364 #[test]
365 fn derive_addresses_slot_1() {
366 assert_eq!(derive_guest_ipv4(1), Ipv4Addr::new(100, 96, 0, 6));
367 assert_eq!(
368 gateway_from_guest_ipv4(Ipv4Addr::new(100, 96, 0, 6)),
369 Ipv4Addr::new(100, 96, 0, 5)
370 );
371 }
372
373 #[test]
374 fn derive_ipv6_slot_0() {
375 assert_eq!(
376 derive_guest_ipv6(0),
377 "fd42:6d73:62:0::2".parse::<Ipv6Addr>().unwrap()
378 );
379 assert_eq!(
380 gateway_from_guest_ipv6(derive_guest_ipv6(0)),
381 "fd42:6d73:62:0::1".parse::<Ipv6Addr>().unwrap()
382 );
383 }
384
385 #[test]
386 fn format_mac_address() {
387 assert_eq!(
388 format_mac([0x02, 0x6d, 0x73, 0x00, 0x00, 0x01]),
389 "02:6d:73:00:00:01"
390 );
391 }
392
393 #[test]
394 fn guest_env_vars_format() {
395 let config = NetworkConfig::default();
396 let net = SmoltcpNetwork::new(config, 0);
397 let vars = net.guest_env_vars();
398
399 assert_eq!(vars.len(), 4);
400 assert_eq!(vars[0].0, ENV_NET);
401 assert!(vars[0].1.contains("iface=eth0"));
402 assert_eq!(vars[1].0, ENV_NET_IPV4);
403 assert!(vars[1].1.contains("/30"));
404 assert_eq!(vars[2].0, ENV_NET_IPV6);
405 assert!(vars[2].1.contains("/64"));
406 assert_eq!(vars[3].0, ENV_HOST_ALIAS);
407 assert_eq!(vars[3].1, crate::HOST_ALIAS);
408 }
409}