1use std::collections::HashMap;
27use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
28use std::time::Instant;
29
30use std::sync::Arc;
31
32use socket2::SockRef;
33use tokio::net::{TcpListener, UdpSocket};
34use tokio::sync::mpsc;
35use tokio::task::JoinHandle;
36use tokio_util::sync::CancellationToken;
37
38use arcbox_packet::ethernet::{ETH_HEADER_LEN, build_udp_ip_ethernet};
39
40const INBOUND_TCP_BUF_SIZE: usize = 4 * 1024 * 1024;
50
51const EPHEMERAL_START: u16 = 61000;
57const EPHEMERAL_END: u16 = 65535;
59
60pub(crate) struct EphemeralPorts {
62 next: u16,
63}
64
65impl EphemeralPorts {
66 pub(crate) fn new() -> Self {
67 Self {
68 next: EPHEMERAL_START,
69 }
70 }
71
72 pub(crate) fn allocate(&mut self) -> u16 {
74 let port = self.next;
75 self.next = if self.next == EPHEMERAL_END {
76 EPHEMERAL_START
77 } else {
78 self.next + 1
79 };
80 port
81 }
82
83 #[inline]
85 pub(crate) fn in_range(port: u16) -> bool {
86 (EPHEMERAL_START..=EPHEMERAL_END).contains(&port)
87 }
88}
89
90pub enum InboundCommand {
96 TcpAccepted {
98 host_port: u16,
99 container_port: u16,
100 stream: tokio::net::TcpStream,
101 },
102 UdpReceived {
104 host_port: u16,
105 container_port: u16,
106 data: Vec<u8>,
107 reply_tx: mpsc::Sender<Vec<u8>>,
109 client_addr: SocketAddr,
110 },
111}
112
113struct InboundUdpFlow {
119 client_tx: mpsc::Sender<Vec<u8>>,
121 last_active: Instant,
123}
124
125pub(crate) struct InboundRelay {
132 udp_flows: HashMap<(Ipv4Addr, u16, Ipv4Addr, u16), InboundUdpFlow>,
134 reply_tx: mpsc::Sender<Vec<u8>>,
136 gateway_mac: [u8; 6],
137 gateway_ip: Ipv4Addr,
138 guest_ip: Ipv4Addr,
139 ephemeral_ports: EphemeralPorts,
140}
141
142impl InboundRelay {
143 pub(crate) fn new(
144 reply_tx: mpsc::Sender<Vec<u8>>,
145 gateway_mac: [u8; 6],
146 gateway_ip: Ipv4Addr,
147 guest_ip: Ipv4Addr,
148 ) -> Self {
149 Self {
150 udp_flows: HashMap::new(),
151 reply_tx,
152 gateway_mac,
153 gateway_ip,
154 guest_ip,
155 ephemeral_ports: EphemeralPorts::new(),
156 }
157 }
158
159 pub(crate) fn try_handle_reply(&mut self, frame: &[u8], _guest_mac: [u8; 6]) -> bool {
169 if frame.len() < ETH_HEADER_LEN + 20 {
170 return false;
171 }
172
173 let ip_start = ETH_HEADER_LEN;
174 let protocol = frame[ip_start + 9];
175
176 let ihl = ((frame[ip_start] & 0x0F) as usize) * 4;
177 let l4_start = ip_start + ihl;
178
179 match protocol {
180 6 => false, 17 => self.try_handle_udp_reply(frame, ip_start, l4_start),
182 _ => false,
183 }
184 }
185
186 fn try_handle_udp_reply(&mut self, frame: &[u8], ip_start: usize, udp_start: usize) -> bool {
188 if frame.len() < udp_start + 8 {
189 return false;
190 }
191
192 let dst_port = u16::from_be_bytes([frame[udp_start + 2], frame[udp_start + 3]]);
193 if !EphemeralPorts::in_range(dst_port) {
194 return false;
195 }
196
197 let src_ip = Ipv4Addr::new(
198 frame[ip_start + 12],
199 frame[ip_start + 13],
200 frame[ip_start + 14],
201 frame[ip_start + 15],
202 );
203 let dst_ip = Ipv4Addr::new(
204 frame[ip_start + 16],
205 frame[ip_start + 17],
206 frame[ip_start + 18],
207 frame[ip_start + 19],
208 );
209 let src_port = u16::from_be_bytes([frame[udp_start], frame[udp_start + 1]]);
210
211 let key = (dst_ip, dst_port, src_ip, src_port);
212
213 if let Some(flow) = self.udp_flows.get_mut(&key) {
214 let udp_len = u16::from_be_bytes([frame[udp_start + 4], frame[udp_start + 5]]) as usize;
215 if udp_len >= 8 && udp_start + udp_len <= frame.len() {
216 let payload = frame[udp_start + 8..udp_start + udp_len].to_vec();
217 flow.last_active = Instant::now();
218 let _ = flow.client_tx.try_send(payload);
219 }
220 return true;
221 }
222
223 false
224 }
225
226 pub(crate) fn inject_udp(
232 &mut self,
233 container_port: u16,
234 data: &[u8],
235 client_tx: mpsc::Sender<Vec<u8>>,
236 guest_mac: [u8; 6],
237 ) {
238 let ephemeral_port = self.ephemeral_ports.allocate();
239 let key = (
240 self.gateway_ip,
241 ephemeral_port,
242 self.guest_ip,
243 container_port,
244 );
245
246 self.udp_flows.insert(
247 key,
248 InboundUdpFlow {
249 client_tx,
250 last_active: Instant::now(),
251 },
252 );
253
254 let frame = build_udp_ip_ethernet(
255 self.gateway_ip,
256 self.guest_ip,
257 ephemeral_port,
258 container_port,
259 data,
260 self.gateway_mac,
261 guest_mac,
262 );
263
264 let _ = self.reply_tx.try_send(frame);
265
266 tracing::debug!(
267 "Inbound UDP: injected {} bytes gw:{} → guest:{}",
268 data.len(),
269 ephemeral_port,
270 container_port,
271 );
272 }
273
274 pub(crate) fn cleanup(&mut self) {
280 let now = Instant::now();
281 self.udp_flows
282 .retain(|_, flow| now.duration_since(flow.last_active).as_secs() < 60);
283 }
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
292pub enum InboundProtocol {
293 Tcp,
294 Udp,
295}
296
297pub struct InboundListenerManager {
300 cmd_tx: mpsc::Sender<InboundCommand>,
301 listeners: HashMap<(Ipv4Addr, u16, InboundProtocol), (JoinHandle<()>, CancellationToken)>,
302}
303
304impl InboundListenerManager {
305 #[must_use]
307 pub fn new(cmd_tx: mpsc::Sender<InboundCommand>) -> Self {
308 Self {
309 cmd_tx,
310 listeners: HashMap::new(),
311 }
312 }
313
314 pub async fn add_rule(
320 &mut self,
321 host_ip: Ipv4Addr,
322 host_port: u16,
323 container_port: u16,
324 protocol: InboundProtocol,
325 ) -> std::io::Result<()> {
326 let key = (host_ip, host_port, protocol);
327 if self.listeners.contains_key(&key) {
328 return Ok(()); }
330
331 let cancel = CancellationToken::new();
332 let cmd_tx = self.cmd_tx.clone();
333
334 let handle = match protocol {
335 InboundProtocol::Tcp => {
336 let listener =
337 TcpListener::bind(SocketAddr::V4(SocketAddrV4::new(host_ip, host_port)))
338 .await?;
339 tracing::info!(
340 "Inbound listener: TCP {}:{} → container :{}",
341 host_ip,
342 host_port,
343 container_port,
344 );
345 let cancel_clone = cancel.clone();
346 tokio::spawn(async move {
347 tcp_listener_task(listener, container_port, cmd_tx, cancel_clone).await;
348 })
349 }
350 InboundProtocol::Udp => {
351 let socket =
352 UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(host_ip, host_port))).await?;
353 tracing::info!(
354 "Inbound listener: UDP {}:{} → container :{}",
355 host_ip,
356 host_port,
357 container_port,
358 );
359 let cancel_clone = cancel.clone();
360 tokio::spawn(async move {
361 udp_listener_task(socket, container_port, cmd_tx, cancel_clone).await;
362 })
363 }
364 };
365
366 self.listeners.insert(key, (handle, cancel));
367 Ok(())
368 }
369
370 pub fn remove_rule(&mut self, host_ip: Ipv4Addr, host_port: u16, protocol: InboundProtocol) {
372 let key = (host_ip, host_port, protocol);
373 if let Some((handle, cancel)) = self.listeners.remove(&key) {
374 cancel.cancel();
375 handle.abort();
376 tracing::debug!(
377 "Inbound listener removed: {:?} {}:{}",
378 protocol,
379 host_ip,
380 host_port
381 );
382 }
383 }
384
385 pub fn stop_all(&mut self) {
387 for ((ip, port, proto), (handle, cancel)) in self.listeners.drain() {
388 cancel.cancel();
389 handle.abort();
390 tracing::debug!("Inbound listener stopped: {:?} {}:{}", proto, ip, port);
391 }
392 }
393}
394
395async fn tcp_listener_task(
401 listener: TcpListener,
402 container_port: u16,
403 cmd_tx: mpsc::Sender<InboundCommand>,
404 cancel: CancellationToken,
405) {
406 let host_port = listener.local_addr().map_or(0, |a| a.port());
407 loop {
408 tokio::select! {
409 biased;
410 () = cancel.cancelled() => break,
411 result = listener.accept() => {
412 match result {
413 Ok((stream, peer)) => {
414 tracing::debug!(
415 "Inbound TCP accept: {} → host:{} → container:{}",
416 peer, host_port, container_port,
417 );
418 let sock = SockRef::from(&stream);
422 if let Err(e) = sock.set_recv_buffer_size(INBOUND_TCP_BUF_SIZE) {
423 tracing::warn!("Failed to set SO_RCVBUF on inbound stream: {e}");
424 }
425 if let Err(e) = sock.set_send_buffer_size(INBOUND_TCP_BUF_SIZE) {
426 tracing::warn!("Failed to set SO_SNDBUF on inbound stream: {e}");
427 }
428 let cmd = InboundCommand::TcpAccepted {
429 host_port,
430 container_port,
431 stream,
432 };
433 if cmd_tx.send(cmd).await.is_err() {
434 break;
435 }
436 }
437 Err(e) => {
438 tracing::warn!("Inbound TCP accept error on :{}: {}", host_port, e);
439 }
440 }
441 }
442 }
443 }
444}
445
446async fn udp_listener_task(
448 socket: UdpSocket,
449 container_port: u16,
450 cmd_tx: mpsc::Sender<InboundCommand>,
451 cancel: CancellationToken,
452) {
453 let host_port = socket.local_addr().map_or(0, |a| a.port());
454 let socket = Arc::new(socket);
455 let mut reply_flows: HashMap<SocketAddr, mpsc::Sender<Vec<u8>>> = HashMap::new();
456 let mut buf = vec![0u8; 65535];
457
458 loop {
459 tokio::select! {
460 biased;
461 () = cancel.cancelled() => break,
462 result = socket.recv_from(&mut buf) => {
463 match result {
464 Ok((n, client_addr)) => {
465 let reply_tx = if let Some(tx) = reply_flows.get(&client_addr) {
466 if tx.is_closed() {
467 reply_flows.remove(&client_addr);
468 create_udp_reply_flow(client_addr, &socket, &cancel, &mut reply_flows)
469 } else {
470 tx.clone()
471 }
472 } else {
473 create_udp_reply_flow(client_addr, &socket, &cancel, &mut reply_flows)
474 };
475
476 let cmd = InboundCommand::UdpReceived {
477 host_port,
478 container_port,
479 data: buf[..n].to_vec(),
480 reply_tx,
481 client_addr,
482 };
483 if cmd_tx.send(cmd).await.is_err() {
484 break;
485 }
486 }
487 Err(e) => {
488 tracing::warn!("Inbound UDP recv error on :{}: {}", host_port, e);
489 }
490 }
491 }
492 }
493 }
494}
495
496fn create_udp_reply_flow(
497 client_addr: SocketAddr,
498 socket: &Arc<UdpSocket>,
499 cancel: &CancellationToken,
500 reply_flows: &mut HashMap<SocketAddr, mpsc::Sender<Vec<u8>>>,
501) -> mpsc::Sender<Vec<u8>> {
502 let (reply_tx, mut reply_rx) = mpsc::channel::<Vec<u8>>(16);
503 let reply_sock = Arc::clone(socket);
504 let flow_cancel = cancel.clone();
505 tokio::spawn(async move {
506 loop {
507 tokio::select! {
508 biased;
509 () = flow_cancel.cancelled() => break,
510 maybe_data = reply_rx.recv() => {
511 let Some(data) = maybe_data else {
512 break;
513 };
514 let _ = reply_sock.send_to(&data, client_addr).await;
515 }
516 }
517 }
518 });
519 reply_flows.insert(client_addr, reply_tx.clone());
520 reply_tx
521}
522
523#[cfg(test)]
528mod tests {
529 use super::*;
530
531 const GW_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 64, 1);
532 const GUEST_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 64, 2);
533 const GW_MAC: [u8; 6] = [0x02, 0xAB, 0xCD, 0x00, 0x00, 0x01];
534 const GUEST_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x99];
535
536 #[test]
537 fn ephemeral_ports_allocation() {
538 let mut ep = EphemeralPorts::new();
539 assert_eq!(ep.allocate(), 61000);
540 assert_eq!(ep.allocate(), 61001);
541 }
542
543 #[test]
544 fn ephemeral_ports_wrap_around() {
545 let mut ep = EphemeralPorts::new();
546 ep.next = EPHEMERAL_END;
547 assert_eq!(ep.allocate(), EPHEMERAL_END);
548 assert_eq!(ep.allocate(), EPHEMERAL_START);
549 }
550
551 #[test]
552 fn ephemeral_ports_in_range() {
553 assert!(EphemeralPorts::in_range(61000));
554 assert!(EphemeralPorts::in_range(65535));
555 assert!(EphemeralPorts::in_range(63000));
556 assert!(!EphemeralPorts::in_range(60999));
557 assert!(!EphemeralPorts::in_range(32768));
558 assert!(!EphemeralPorts::in_range(80));
559 }
560
561 #[test]
562 fn inbound_relay_rejects_non_ephemeral() {
563 let (tx, _rx) = mpsc::channel(16);
564 let mut relay = InboundRelay::new(tx, GW_MAC, GW_IP, GUEST_IP);
565
566 let mut frame = vec![0u8; ETH_HEADER_LEN + 40];
568 frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
569 let ip = &mut frame[ETH_HEADER_LEN..];
570 ip[0] = 0x45;
571 ip[9] = 6; ip[12..16].copy_from_slice(&GUEST_IP.octets());
573 ip[16..20].copy_from_slice(&GW_IP.octets());
574 let tcp = &mut frame[ETH_HEADER_LEN + 20..];
576 tcp[0..2].copy_from_slice(&8080u16.to_be_bytes());
577 tcp[2..4].copy_from_slice(&80u16.to_be_bytes());
578 tcp[12] = 0x50; assert!(!relay.try_handle_reply(&frame, GUEST_MAC));
581 }
582
583 #[tokio::test]
584 async fn inject_udp_sends_frame_and_tracks_flow() {
585 let (tx, mut rx) = mpsc::channel(16);
586 let mut relay = InboundRelay::new(tx, GW_MAC, GW_IP, GUEST_IP);
587
588 let (client_tx, _client_rx) = mpsc::channel(16);
589 relay.inject_udp(53, b"dns query", client_tx, GUEST_MAC);
590
591 let key = (GW_IP, EPHEMERAL_START, GUEST_IP, 53);
593 assert!(relay.udp_flows.contains_key(&key));
594
595 let frame = rx.recv().await.expect("should receive UDP frame");
597 assert!(frame.len() >= ETH_HEADER_LEN + 28, "UDP frame too short");
598
599 assert_eq!(frame[ETH_HEADER_LEN + 9], 17);
601
602 let udp_start = ETH_HEADER_LEN + 20;
604 let src_port = u16::from_be_bytes([frame[udp_start], frame[udp_start + 1]]);
605 let dst_port = u16::from_be_bytes([frame[udp_start + 2], frame[udp_start + 3]]);
606 assert_eq!(src_port, EPHEMERAL_START);
607 assert_eq!(dst_port, 53);
608 }
609
610 #[test]
611 fn cleanup_removes_expired_udp_flows() {
612 let (tx, _rx) = mpsc::channel(16);
613 let mut relay = InboundRelay::new(tx, GW_MAC, GW_IP, GUEST_IP);
614
615 let (client_tx, _client_rx) = mpsc::channel(16);
616 let key = (GW_IP, 61000, GUEST_IP, 53);
617 relay.udp_flows.insert(
618 key,
619 InboundUdpFlow {
620 client_tx,
621 last_active: Instant::now()
622 .checked_sub(std::time::Duration::from_secs(120))
623 .unwrap(),
624 },
625 );
626 assert_eq!(relay.udp_flows.len(), 1);
627
628 relay.cleanup();
629 assert_eq!(
630 relay.udp_flows.len(),
631 0,
632 "expired UDP flow should be removed"
633 );
634 }
635
636 #[tokio::test]
637 async fn listener_manager_add_and_remove_rule() {
638 let (cmd_tx, mut cmd_rx) = mpsc::channel(16);
639 let mut manager = InboundListenerManager::new(cmd_tx);
640
641 manager
643 .add_rule(Ipv4Addr::LOCALHOST, 0, 80, InboundProtocol::Tcp)
644 .await
645 .expect("should bind to port 0 (OS-assigned)");
646
647 manager.remove_rule(Ipv4Addr::LOCALHOST, 0, InboundProtocol::Tcp);
649
650 assert!(cmd_rx.try_recv().is_err(), "no commands expected yet");
652 }
653
654 #[tokio::test]
655 async fn listener_manager_stop_all() {
656 let (cmd_tx, _cmd_rx) = mpsc::channel(16);
657 let mut manager = InboundListenerManager::new(cmd_tx);
658
659 manager
660 .add_rule(Ipv4Addr::LOCALHOST, 0, 80, InboundProtocol::Tcp)
661 .await
662 .unwrap();
663 manager
664 .add_rule(Ipv4Addr::LOCALHOST, 0, 53, InboundProtocol::Udp)
665 .await
666 .unwrap();
667
668 manager.stop_all();
669 manager
673 .add_rule(Ipv4Addr::LOCALHOST, 0, 80, InboundProtocol::Tcp)
674 .await
675 .unwrap();
676 }
677
678 #[tokio::test]
679 async fn same_port_different_ip_coexist() {
680 let (cmd_tx, _cmd_rx) = mpsc::channel(16);
681 let mut manager = InboundListenerManager::new(cmd_tx);
682
683 manager
685 .add_rule(Ipv4Addr::LOCALHOST, 0, 80, InboundProtocol::Tcp)
686 .await
687 .unwrap();
688 manager
689 .add_rule(Ipv4Addr::UNSPECIFIED, 0, 80, InboundProtocol::Tcp)
690 .await
691 .unwrap();
692
693 manager.remove_rule(Ipv4Addr::LOCALHOST, 0, InboundProtocol::Tcp);
695 manager
696 .add_rule(Ipv4Addr::LOCALHOST, 0, 80, InboundProtocol::Tcp)
697 .await
698 .unwrap();
699 }
700
701 #[test]
702 fn invalid_host_ip_is_rejected() {
703 assert!(
706 "::1".parse::<Ipv4Addr>().is_err(),
707 "IPv6 should fail Ipv4Addr parse"
708 );
709 assert!("not-an-ip".parse::<Ipv4Addr>().is_err());
710 assert!("".parse::<Ipv4Addr>().is_err());
711 assert!("127.0.0.1".parse::<Ipv4Addr>().is_ok());
713 assert!("0.0.0.0".parse::<Ipv4Addr>().is_ok());
714 }
715}