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 struct SharedState {
48 pub tx_ring: ArrayQueue<Vec<u8>>,
50
51 pub rx_ring: ArrayQueue<Vec<u8>>,
53
54 pub rx_wake: WakePipe,
58
59 pub tx_wake: WakePipe,
63
64 pub proxy_wake: WakePipe,
68
69 termination_hook: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
71
72 resolved_hostnames: RwLock<TtlReverseIndex<ResolvedHostnameKey, IpAddr>>,
74
75 gateway_ipv4: OnceLock<Ipv4Addr>,
79
80 gateway_ipv6: OnceLock<Ipv6Addr>,
82
83 nat64_prefixes: OnceLock<Vec<Ipv6Network>>,
85
86 metrics: NetworkMetrics,
88
89 http: OnceLock<HttpConfig>,
91}
92
93pub struct NetworkMetrics {
95 tx_bytes: AtomicU64,
96 rx_bytes: AtomicU64,
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
101pub enum ResolvedHostnameFamily {
102 Ipv4,
103 Ipv6,
104}
105
106#[derive(Clone, Debug, PartialEq, Eq, Hash)]
111struct ResolvedHostnameKey {
112 hostname: String,
113 family: ResolvedHostnameFamily,
114}
115
116impl SharedState {
121 pub fn new(queue_capacity: usize) -> Self {
123 Self {
124 tx_ring: ArrayQueue::new(queue_capacity),
125 rx_ring: ArrayQueue::new(queue_capacity),
126 rx_wake: WakePipe::new(),
127 tx_wake: WakePipe::new(),
128 proxy_wake: WakePipe::new(),
129 termination_hook: Mutex::new(None),
130 resolved_hostnames: RwLock::new(TtlReverseIndex::default()),
131 gateway_ipv4: OnceLock::new(),
132 gateway_ipv6: OnceLock::new(),
133 nat64_prefixes: OnceLock::new(),
134 metrics: NetworkMetrics::default(),
135 http: OnceLock::new(),
136 }
137 }
138
139 pub fn set_gateway_ips(&self, ipv4: Option<Ipv4Addr>, ipv6: Option<Ipv6Addr>) {
142 if let Some(ipv4) = ipv4 {
143 let _ = self.gateway_ipv4.set(ipv4);
144 }
145 if let Some(ipv6) = ipv6 {
146 let _ = self.gateway_ipv6.set(ipv6);
147 }
148 }
149
150 pub fn gateway_ipv4(&self) -> Option<Ipv4Addr> {
152 self.gateway_ipv4.get().copied()
153 }
154
155 pub fn gateway_ipv6(&self) -> Option<Ipv6Addr> {
157 self.gateway_ipv6.get().copied()
158 }
159
160 pub fn set_http_config(&self, config: HttpConfig) {
162 let _ = self.http.set(config);
163 }
164
165 pub fn http_deny_response_enabled(&self) -> bool {
167 self.http.get().is_some_and(|http| http.deny_response)
168 }
169
170 pub fn http_deny_body(&self, host: &str) -> String {
172 let template = self
173 .http
174 .get()
175 .and_then(|http| http.deny_message.as_deref())
176 .unwrap_or(DEFAULT_HTTP_DENY_MESSAGE);
177 http_deny::render_http_deny_message(template, host)
178 }
179
180 pub fn set_nat64_prefixes(&self, prefixes: Vec<Ipv6Network>) {
182 let _ = self.nat64_prefixes.set(prefixes);
183 }
184
185 pub fn nat64_prefixes(&self) -> &[Ipv6Network] {
187 self.nat64_prefixes.get().map(Vec::as_slice).unwrap_or(&[])
188 }
189
190 pub fn set_termination_hook(&self, hook: Arc<dyn Fn() + Send + Sync>) {
192 *self.termination_hook.lock().unwrap() = Some(hook);
193 }
194
195 pub fn trigger_termination(&self) {
197 let hook = self.termination_hook.lock().unwrap().clone();
198 if let Some(hook) = hook {
199 hook();
200 }
201 }
202
203 pub fn cache_resolved_hostname(
205 &self,
206 domain: &str,
207 family: ResolvedHostnameFamily,
208 addrs: impl IntoIterator<Item = IpAddr>,
209 ttl: Duration,
210 ) {
211 let hostname = normalize_hostname(domain);
212 let key = ResolvedHostnameKey { hostname, family };
213 let addrs = addrs.into_iter().map(normalize_ip_addr);
214 self.resolved_hostnames
215 .write()
216 .insert(key, addrs, ttl, Instant::now());
217 }
218
219 pub fn clear_resolved_hostname(&self, domain: &str, family: ResolvedHostnameFamily) {
221 let hostname = normalize_hostname(domain);
222 let key = ResolvedHostnameKey { hostname, family };
223 self.resolved_hostnames.write().remove(&key, Instant::now());
224 }
225
226 pub fn any_resolved_hostname(
228 &self,
229 addr: IpAddr,
230 mut predicate: impl FnMut(&str) -> bool,
231 ) -> bool {
232 let addr = normalize_ip_addr(addr);
233
234 self.resolved_hostnames
235 .read()
236 .member_matches(&addr, Instant::now(), |key| predicate(&key.hostname))
237 }
238
239 pub fn cleanup_resolved_hostnames(&self) {
244 if let Some(mut idx) = self.resolved_hostnames.try_write() {
245 idx.evict_expired(Instant::now());
246 }
247 }
248
249 pub fn add_tx_bytes(&self, bytes: usize) {
251 self.metrics
252 .tx_bytes
253 .fetch_add(bytes as u64, Ordering::Relaxed);
254 }
255
256 pub fn add_rx_bytes(&self, bytes: usize) {
258 self.metrics
259 .rx_bytes
260 .fetch_add(bytes as u64, Ordering::Relaxed);
261 }
262
263 pub(crate) fn push_rx_frame(&self, frame: Vec<u8>) -> bool {
265 let frame_len = frame.len();
266 if self.rx_ring.push(frame).is_err() {
267 return false;
268 }
269
270 self.add_rx_bytes(frame_len);
271 true
272 }
273
274 pub(crate) fn push_rx_frame_and_wake(&self, frame: Vec<u8>) -> bool {
276 if !self.push_rx_frame(frame) {
277 return false;
278 }
279
280 self.rx_wake.wake();
281 true
282 }
283
284 pub fn tx_bytes(&self) -> u64 {
286 self.metrics.tx_bytes.load(Ordering::Relaxed)
287 }
288
289 pub fn rx_bytes(&self) -> u64 {
291 self.metrics.rx_bytes.load(Ordering::Relaxed)
292 }
293}
294
295impl Default for NetworkMetrics {
296 fn default() -> Self {
297 Self {
298 tx_bytes: AtomicU64::new(0),
299 rx_bytes: AtomicU64::new(0),
300 }
301 }
302}
303
304pub(crate) fn normalize_hostname(domain: &str) -> String {
305 domain.trim_end_matches('.').to_ascii_lowercase()
306}
307
308#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn shared_state_queue_push_pop() {
318 let state = SharedState::new(4);
319
320 state.tx_ring.push(vec![1, 2, 3]).unwrap();
322 state.tx_ring.push(vec![4, 5, 6]).unwrap();
323
324 assert_eq!(state.tx_ring.pop(), Some(vec![1, 2, 3]));
326 assert_eq!(state.tx_ring.pop(), Some(vec![4, 5, 6]));
327 assert_eq!(state.tx_ring.pop(), None);
328 }
329
330 #[test]
331 fn shared_state_queue_full() {
332 let state = SharedState::new(2);
333
334 state.rx_ring.push(vec![1]).unwrap();
335 state.rx_ring.push(vec![2]).unwrap();
336 assert!(state.rx_ring.push(vec![3]).is_err());
338 }
339
340 #[test]
341 fn push_rx_frame_counts_only_successful_pushes() {
342 let state = SharedState::new(1);
343
344 assert!(state.push_rx_frame(vec![1, 2, 3]));
345 assert_eq!(state.rx_bytes(), 3);
346
347 assert!(!state.push_rx_frame(vec![4, 5]));
348 assert_eq!(state.rx_bytes(), 3);
349 }
350
351 #[test]
352 fn resolved_hostnames_are_isolated_per_family() {
353 let state = SharedState::new(4);
354 let v4: IpAddr = "1.1.1.1".parse().unwrap();
355 let v6: IpAddr = "2606:4700:4700::1111".parse().unwrap();
356
357 state.cache_resolved_hostname(
358 "Example.com.",
359 ResolvedHostnameFamily::Ipv4,
360 [v4],
361 Duration::from_secs(30),
362 );
363 state.cache_resolved_hostname(
364 "example.com",
365 ResolvedHostnameFamily::Ipv6,
366 [v6],
367 Duration::from_secs(30),
368 );
369
370 assert!(state.any_resolved_hostname(v4, |h| h == "example.com"));
371 assert!(state.any_resolved_hostname(v6, |h| h == "example.com"));
372 assert!(!state.any_resolved_hostname(v4, |h| h == "other.example"));
373 }
374
375 #[test]
376 fn resolved_hostnames_normalize_ipv4_mapped_ipv6() {
377 let state = SharedState::new(4);
378 let mapped: IpAddr = "::ffff:169.254.169.254".parse().unwrap();
379 let embedded: IpAddr = "169.254.169.254".parse().unwrap();
380
381 state.cache_resolved_hostname(
382 "metadata.example",
383 ResolvedHostnameFamily::Ipv6,
384 [mapped],
385 Duration::from_secs(30),
386 );
387
388 assert!(state.any_resolved_hostname(embedded, |h| h == "metadata.example"));
389 assert!(state.any_resolved_hostname(mapped, |h| h == "metadata.example"));
390 }
391}