microsandbox_network/engine/netstack/
shared.rs1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
8use std::sync::{
9 Arc, Mutex, OnceLock,
10 atomic::{AtomicU64, Ordering},
11};
12use std::time::{Duration, Instant};
13
14use crossbeam_queue::ArrayQueue;
15use ipnetwork::Ipv6Network;
16use microsandbox_types::HttpConfig;
17use microsandbox_utils::ttl_reverse_index::TtlReverseIndex;
18pub use microsandbox_utils::wake_pipe::WakePipe;
19use parking_lot::RwLock;
20
21use crate::addr::normalize_ip_addr;
22use crate::engine::http_deny::{self, DEFAULT_HTTP_DENY_MESSAGE};
23
24pub const DEFAULT_QUEUE_CAPACITY: usize = 1024;
30
31pub(crate) const MAX_RESOLVED_HOSTNAME_BINDINGS: usize = 16_384;
33
34pub(crate) const MAX_BINDINGS_PER_HOSTNAME: usize = 1_024;
36
37pub struct SharedState {
54 pub tx_ring: ArrayQueue<Vec<u8>>,
56
57 pub rx_ring: ArrayQueue<Vec<u8>>,
59
60 pub rx_wake: WakePipe,
64
65 pub tx_wake: WakePipe,
69
70 pub proxy_wake: WakePipe,
74
75 termination_hook: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
77
78 resolved_hostnames: RwLock<TtlReverseIndex<ResolvedHostnameKey, IpAddr>>,
80
81 gateway_ipv4: OnceLock<Ipv4Addr>,
85
86 gateway_ipv6: OnceLock<Ipv6Addr>,
88
89 nat64_prefixes: OnceLock<Vec<Ipv6Network>>,
91
92 metrics: NetworkMetrics,
94
95 http: OnceLock<HttpConfig>,
97}
98
99pub struct NetworkMetrics {
101 tx_bytes: AtomicU64,
102 rx_bytes: AtomicU64,
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
107pub enum ResolvedHostnameFamily {
108 Ipv4,
109 Ipv6,
110}
111
112#[derive(Clone, Debug, PartialEq, Eq, Hash)]
117struct ResolvedHostnameKey {
118 hostname: String,
119 family: ResolvedHostnameFamily,
120}
121
122impl SharedState {
127 pub fn new(queue_capacity: usize) -> Self {
129 Self {
130 tx_ring: ArrayQueue::new(queue_capacity),
131 rx_ring: ArrayQueue::new(queue_capacity),
132 rx_wake: WakePipe::new(),
133 tx_wake: WakePipe::new(),
134 proxy_wake: WakePipe::new(),
135 termination_hook: Mutex::new(None),
136 resolved_hostnames: RwLock::new(TtlReverseIndex::default()),
137 gateway_ipv4: OnceLock::new(),
138 gateway_ipv6: OnceLock::new(),
139 nat64_prefixes: OnceLock::new(),
140 metrics: NetworkMetrics::default(),
141 http: OnceLock::new(),
142 }
143 }
144
145 pub fn set_gateway_ips(&self, ipv4: Option<Ipv4Addr>, ipv6: Option<Ipv6Addr>) {
148 if let Some(ipv4) = ipv4 {
149 let _ = self.gateway_ipv4.set(ipv4);
150 }
151 if let Some(ipv6) = ipv6 {
152 let _ = self.gateway_ipv6.set(ipv6);
153 }
154 }
155
156 pub fn gateway_ipv4(&self) -> Option<Ipv4Addr> {
158 self.gateway_ipv4.get().copied()
159 }
160
161 pub fn gateway_ipv6(&self) -> Option<Ipv6Addr> {
163 self.gateway_ipv6.get().copied()
164 }
165
166 pub fn set_http_config(&self, config: HttpConfig) {
168 let _ = self.http.set(config);
169 }
170
171 pub fn http_deny_response_enabled(&self) -> bool {
173 self.http.get().is_some_and(|http| http.deny_response)
174 }
175
176 pub fn http_deny_body(&self, host: &str) -> String {
178 let template = self
179 .http
180 .get()
181 .and_then(|http| http.deny_message.as_deref())
182 .unwrap_or(DEFAULT_HTTP_DENY_MESSAGE);
183 http_deny::render_http_deny_message(template, host)
184 }
185
186 pub fn set_nat64_prefixes(&self, prefixes: Vec<Ipv6Network>) {
188 let _ = self.nat64_prefixes.set(prefixes);
189 }
190
191 pub fn nat64_prefixes(&self) -> &[Ipv6Network] {
193 self.nat64_prefixes.get().map(Vec::as_slice).unwrap_or(&[])
194 }
195
196 pub fn set_termination_hook(&self, hook: Arc<dyn Fn() + Send + Sync>) {
198 *self.termination_hook.lock().unwrap() = Some(hook);
199 }
200
201 pub fn trigger_termination(&self) {
203 let hook = self.termination_hook.lock().unwrap().clone();
204 if let Some(hook) = hook {
205 hook();
206 }
207 }
208
209 pub fn cache_resolved_hostname(
220 &self,
221 domain: &str,
222 family: ResolvedHostnameFamily,
223 addrs: impl IntoIterator<Item = IpAddr>,
224 ttl: Duration,
225 ) -> bool {
226 let hostname = normalize_hostname(domain);
227 let key = ResolvedHostnameKey { hostname, family };
228 let addrs = addrs.into_iter().map(normalize_ip_addr);
229
230 let now = Instant::now();
231 let mut index = self.resolved_hostnames.write();
232 index.evict_expired(now);
233
234 let bindings_for = |family| {
235 index.member_count(&ResolvedHostnameKey {
236 hostname: key.hostname.clone(),
237 family,
238 })
239 };
240
241 let ipv4_bindings = bindings_for(ResolvedHostnameFamily::Ipv4);
242 let ipv6_bindings = bindings_for(ResolvedHostnameFamily::Ipv6);
243 let hostname_bindings = ipv4_bindings + ipv6_bindings;
244
245 let total_bindings = index.binding_count();
246 let hostname_slots = MAX_BINDINGS_PER_HOSTNAME.saturating_sub(hostname_bindings);
247 let sandbox_slots = MAX_RESOLVED_HOSTNAME_BINDINGS.saturating_sub(total_bindings);
248 let available_slots = hostname_slots.min(sandbox_slots);
249
250 let capacity = total_bindings + available_slots;
252 index.try_extend(key, addrs, ttl, now, capacity)
253 }
254
255 pub fn clear_resolved_hostname(&self, domain: &str, family: ResolvedHostnameFamily) {
257 let hostname = normalize_hostname(domain);
258 let key = ResolvedHostnameKey { hostname, family };
259 self.resolved_hostnames.write().remove(&key, Instant::now());
260 }
261
262 pub fn any_resolved_hostname(
264 &self,
265 addr: IpAddr,
266 mut predicate: impl FnMut(&str) -> bool,
267 ) -> bool {
268 let addr = normalize_ip_addr(addr);
269
270 self.resolved_hostnames
271 .read()
272 .member_matches(&addr, Instant::now(), |key| predicate(&key.hostname))
273 }
274
275 pub fn cleanup_resolved_hostnames(&self) {
280 if let Some(mut idx) = self.resolved_hostnames.try_write() {
281 idx.evict_expired(Instant::now());
282 }
283 }
284
285 pub fn add_tx_bytes(&self, bytes: usize) {
287 self.metrics
288 .tx_bytes
289 .fetch_add(bytes as u64, Ordering::Relaxed);
290 }
291
292 pub fn add_rx_bytes(&self, bytes: usize) {
294 self.metrics
295 .rx_bytes
296 .fetch_add(bytes as u64, Ordering::Relaxed);
297 }
298
299 pub(crate) fn push_rx_frame(&self, frame: Vec<u8>) -> bool {
301 let frame_len = frame.len();
302 if self.rx_ring.push(frame).is_err() {
303 return false;
304 }
305
306 self.add_rx_bytes(frame_len);
307 true
308 }
309
310 pub(crate) fn push_rx_frame_and_wake(&self, frame: Vec<u8>) -> bool {
312 if !self.push_rx_frame(frame) {
313 return false;
314 }
315
316 self.rx_wake.wake();
317 true
318 }
319
320 pub fn tx_bytes(&self) -> u64 {
322 self.metrics.tx_bytes.load(Ordering::Relaxed)
323 }
324
325 pub fn rx_bytes(&self) -> u64 {
327 self.metrics.rx_bytes.load(Ordering::Relaxed)
328 }
329}
330
331impl Default for NetworkMetrics {
332 fn default() -> Self {
333 Self {
334 tx_bytes: AtomicU64::new(0),
335 rx_bytes: AtomicU64::new(0),
336 }
337 }
338}
339
340pub(crate) fn normalize_hostname(domain: &str) -> String {
341 domain.trim_end_matches('.').to_ascii_lowercase()
342}
343
344#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn shared_state_queue_push_pop() {
354 let state = SharedState::new(4);
355
356 state.tx_ring.push(vec![1, 2, 3]).unwrap();
358 state.tx_ring.push(vec![4, 5, 6]).unwrap();
359
360 assert_eq!(state.tx_ring.pop(), Some(vec![1, 2, 3]));
362 assert_eq!(state.tx_ring.pop(), Some(vec![4, 5, 6]));
363 assert_eq!(state.tx_ring.pop(), None);
364 }
365
366 #[test]
367 fn shared_state_queue_full() {
368 let state = SharedState::new(2);
369
370 state.rx_ring.push(vec![1]).unwrap();
371 state.rx_ring.push(vec![2]).unwrap();
372 assert!(state.rx_ring.push(vec![3]).is_err());
374 }
375
376 #[test]
377 fn push_rx_frame_counts_only_successful_pushes() {
378 let state = SharedState::new(1);
379
380 assert!(state.push_rx_frame(vec![1, 2, 3]));
381 assert_eq!(state.rx_bytes(), 3);
382
383 assert!(!state.push_rx_frame(vec![4, 5]));
384 assert_eq!(state.rx_bytes(), 3);
385 }
386
387 #[test]
388 fn resolved_hostnames_are_isolated_per_family() {
389 let state = SharedState::new(4);
390 let v4: IpAddr = "1.1.1.1".parse().unwrap();
391 let v6: IpAddr = "2606:4700:4700::1111".parse().unwrap();
392
393 state.cache_resolved_hostname(
394 "Example.com.",
395 ResolvedHostnameFamily::Ipv4,
396 [v4],
397 Duration::from_secs(30),
398 );
399 state.cache_resolved_hostname(
400 "example.com",
401 ResolvedHostnameFamily::Ipv6,
402 [v6],
403 Duration::from_secs(30),
404 );
405
406 assert!(state.any_resolved_hostname(v4, |h| h == "example.com"));
407 assert!(state.any_resolved_hostname(v6, |h| h == "example.com"));
408 assert!(!state.any_resolved_hostname(v4, |h| h == "other.example"));
409 }
410
411 #[test]
412 fn resolved_hostnames_keep_earlier_answers() {
413 let state = SharedState::new(4);
414 let first: IpAddr = "142.250.0.1".parse().unwrap();
415 let second: IpAddr = "142.250.0.2".parse().unwrap();
416
417 for n in 1..=65 {
420 let addr = IpAddr::V4(Ipv4Addr::new(142, 250, 0, n));
421 state.cache_resolved_hostname(
422 "fonts.example",
423 ResolvedHostnameFamily::Ipv4,
424 [addr],
425 Duration::from_secs(30),
426 );
427 }
428
429 for n in 1..=65 {
430 let addr = IpAddr::V4(Ipv4Addr::new(142, 250, 0, n));
431 assert!(state.any_resolved_hostname(addr, |h| h == "fonts.example"));
432 }
433
434 state.clear_resolved_hostname("fonts.example", ResolvedHostnameFamily::Ipv4);
435 assert!(!state.any_resolved_hostname(first, |h| h == "fonts.example"));
436 assert!(!state.any_resolved_hostname(second, |h| h == "fonts.example"));
437 }
438
439 #[test]
440 fn resolved_hostnames_normalize_ipv4_mapped_ipv6() {
441 let state = SharedState::new(4);
442 let mapped: IpAddr = "::ffff:169.254.169.254".parse().unwrap();
443 let embedded: IpAddr = "169.254.169.254".parse().unwrap();
444
445 state.cache_resolved_hostname(
446 "metadata.example",
447 ResolvedHostnameFamily::Ipv6,
448 [mapped],
449 Duration::from_secs(30),
450 );
451
452 assert!(state.any_resolved_hostname(embedded, |h| h == "metadata.example"));
453 assert!(state.any_resolved_hostname(mapped, |h| h == "metadata.example"));
454 }
455}