1use crate::egress::EgressPolicy;
30use crate::queues::WakePipe;
31use crate::virtio_net_log;
32use smoltcp::iface::{Interface, SocketHandle, SocketSet};
33use smoltcp::socket::tcp;
34use smoltcp::wire::IpListenEndpoint;
35use std::collections::{HashMap, HashSet};
36use std::io::{self, Read, Write};
37use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, TcpStream};
38use std::sync::atomic::{AtomicU8, Ordering};
39use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError};
40use std::sync::Arc;
41use std::thread;
42use std::time::Duration;
43
44const TCP_RX_BUFFER_BYTES: usize = 64 * 1024;
45const TCP_TX_BUFFER_BYTES: usize = 64 * 1024;
46const MAX_CONNECTIONS: usize = 256;
47const CHANNEL_CAPACITY: usize = 32;
48const RELAY_BUFFER_BYTES: usize = 16 * 1024;
49const CLOSE_RETRY_LIMIT: u16 = 64;
50const PROXY_IDLE_SLEEP: Duration = Duration::from_millis(10);
51const PUBLISHED_PORT_START: u16 = 49_152;
52const PUBLISHED_PORT_END: u16 = 65_535;
53
54pub struct TcpRelayTable {
59 connections: HashMap<SocketHandle, TrackedConnection>,
60 connection_keys: HashSet<(SocketAddr, SocketAddr)>,
61 used_published_ports: HashSet<u16>,
62 next_published_port: u16,
63 max_connections: usize,
64 egress: EgressPolicy,
67 gateway_ips: Vec<IpAddr>,
72}
73
74pub struct NewTcpConnection {
80 pub destination: SocketAddr,
82 pub relay_target: RelayTarget,
84 pub from_smoltcp: Receiver<Vec<u8>>,
86 pub to_smoltcp: SyncSender<Vec<u8>>,
88 pub exit_state: RelayExitState,
90}
91
92#[derive(Debug)]
93struct TrackedConnection {
94 source: SocketAddr,
96 destination: SocketAddr,
97 to_proxy: SyncSender<Vec<u8>>,
99 from_proxy: Receiver<Vec<u8>>,
101 pending_proxy_endpoints: Option<PendingProxyEndpoints>,
103 relay_spawned: bool,
105 buffered_guest_data: Option<Vec<u8>>,
108 buffered_proxy_data: Option<(Vec<u8>, usize)>,
110 close_attempts: u16,
112 guest_send_closed: bool,
115 exit_state: RelayExitState,
117 reserved_published_port: Option<u16>,
119}
120
121#[derive(Debug)]
122struct PendingProxyEndpoints {
123 from_smoltcp: Receiver<Vec<u8>>,
124 to_smoltcp: SyncSender<Vec<u8>>,
125 relay_target: RelayTarget,
126}
127
128#[derive(Debug)]
130pub enum RelayTarget {
131 Connect(SocketAddr),
133 Attached(TcpStream),
135}
136
137#[derive(Clone, Debug)]
147pub struct RelayExitState {
148 inner: Arc<AtomicU8>,
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153#[repr(u8)]
154pub enum RelayExitMode {
155 Running = 0,
157 Graceful = 1,
159 Abort = 2,
161 HalfClosed = 3,
167}
168
169impl RelayExitState {
170 fn new() -> Self {
171 Self {
172 inner: Arc::new(AtomicU8::new(RelayExitMode::Running as u8)),
173 }
174 }
175
176 fn load(&self) -> RelayExitMode {
177 match self.inner.load(Ordering::Relaxed) {
178 1 => RelayExitMode::Graceful,
179 2 => RelayExitMode::Abort,
180 3 => RelayExitMode::HalfClosed,
181 _ => RelayExitMode::Running,
182 }
183 }
184
185 fn store(&self, mode: RelayExitMode) {
186 self.inner.store(mode as u8, Ordering::Relaxed);
187 }
188}
189
190impl TcpRelayTable {
191 pub fn new(
193 max_connections: Option<usize>,
194 egress: EgressPolicy,
195 gateway_ips: Vec<IpAddr>,
196 ) -> Self {
197 Self {
198 connections: HashMap::new(),
199 connection_keys: HashSet::new(),
200 used_published_ports: HashSet::new(),
201 next_published_port: PUBLISHED_PORT_START,
202 max_connections: max_connections.unwrap_or(MAX_CONNECTIONS),
203 egress,
204 gateway_ips,
205 }
206 }
207
208 fn host_connect_addr(&self, destination: SocketAddr) -> SocketAddr {
222 if self.gateway_ips.contains(&destination.ip()) {
223 let loopback = if destination.is_ipv4() {
224 IpAddr::V4(Ipv4Addr::LOCALHOST)
225 } else {
226 IpAddr::V6(Ipv6Addr::LOCALHOST)
227 };
228 return SocketAddr::new(loopback, destination.port());
229 }
230 destination
231 }
232
233 pub fn has_socket_for(&self, source: &SocketAddr, destination: &SocketAddr) -> bool {
235 self.connection_keys.contains(&(*source, *destination))
236 }
237
238 pub fn create_tcp_socket(
255 &mut self,
256 source: SocketAddr,
257 destination: SocketAddr,
258 sockets: &mut SocketSet<'_>,
259 ) -> bool {
260 if self.connections.len() >= self.max_connections {
261 tracing::warn!("dropping TCP connection because the relay table is full");
262 return false;
263 }
264
265 if !self.egress.allows(destination.ip()) {
269 tracing::debug!(
270 %destination,
271 "virtio-net: blocking outbound connection by egress policy"
272 );
273 return false;
274 }
275
276 let rx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_RX_BUFFER_BYTES]);
277 let tx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_TX_BUFFER_BYTES]);
278 let mut socket = tcp::Socket::new(rx_buffer, tx_buffer);
279
280 let listen_endpoint = IpListenEndpoint {
281 addr: Some(destination.ip().into()),
282 port: destination.port(),
283 };
284 if socket.listen(listen_endpoint).is_err() {
285 return false;
286 }
287
288 let handle = sockets.add(socket);
289
290 let (to_proxy_tx, to_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
291 let (from_proxy_tx, from_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
292 let exit_state = RelayExitState::new();
293
294 self.connection_keys.insert((source, destination));
295 self.connections.insert(
296 handle,
297 TrackedConnection {
298 source,
299 destination,
300 to_proxy: to_proxy_tx,
301 from_proxy: from_proxy_rx,
302 pending_proxy_endpoints: Some(PendingProxyEndpoints {
303 from_smoltcp: to_proxy_rx,
304 to_smoltcp: from_proxy_tx,
305 relay_target: RelayTarget::Connect(self.host_connect_addr(destination)),
306 }),
307 relay_spawned: false,
308 buffered_guest_data: None,
309 buffered_proxy_data: None,
310 close_attempts: 0,
311 guest_send_closed: false,
312 exit_state,
313 reserved_published_port: None,
314 },
315 );
316
317 true
318 }
319
320 pub fn create_published_socket(
336 &mut self,
337 interface: &mut Interface,
338 gateway_ip: Ipv4Addr,
339 destination: SocketAddr,
340 host_stream: TcpStream,
341 sockets: &mut SocketSet<'_>,
342 ) -> bool {
343 if self.connections.len() >= self.max_connections {
344 tracing::warn!("dropping published TCP connection because the relay table is full");
345 return false;
346 }
347
348 let Some(local_port) = self.allocate_published_port() else {
349 tracing::warn!(
350 "dropping published TCP connection because no gateway source port is available"
351 );
352 return false;
353 };
354
355 let std::net::IpAddr::V4(destination_ip) = destination.ip() else {
358 self.used_published_ports.remove(&local_port);
359 return false;
360 };
361
362 let rx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_RX_BUFFER_BYTES]);
363 let tx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_TX_BUFFER_BYTES]);
364 let mut socket = tcp::Socket::new(rx_buffer, tx_buffer);
365 let local_endpoint = IpListenEndpoint {
366 addr: Some(gateway_ip.into()),
367 port: local_port,
368 };
369 if socket
370 .connect(
371 interface.context(),
372 (destination_ip, destination.port()),
373 local_endpoint,
374 )
375 .is_err()
376 {
377 self.used_published_ports.remove(&local_port);
378 return false;
379 }
380
381 let handle = sockets.add(socket);
382 let source = SocketAddr::new(std::net::IpAddr::V4(gateway_ip), local_port);
383
384 let (to_proxy_tx, to_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
385 let (from_proxy_tx, from_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
386 let exit_state = RelayExitState::new();
387
388 self.connection_keys.insert((source, destination));
389 self.connections.insert(
390 handle,
391 TrackedConnection {
392 source,
393 destination,
394 to_proxy: to_proxy_tx,
395 from_proxy: from_proxy_rx,
396 pending_proxy_endpoints: Some(PendingProxyEndpoints {
397 from_smoltcp: to_proxy_rx,
398 to_smoltcp: from_proxy_tx,
399 relay_target: RelayTarget::Attached(host_stream),
400 }),
401 relay_spawned: false,
402 buffered_guest_data: None,
403 buffered_proxy_data: None,
404 close_attempts: 0,
405 guest_send_closed: false,
406 exit_state,
407 reserved_published_port: Some(local_port),
408 },
409 );
410
411 true
412 }
413
414 pub fn relay_data(&mut self, sockets: &mut SocketSet<'_>) {
423 let mut read_buffer = [0u8; RELAY_BUFFER_BYTES];
424
425 for (&handle, connection) in &mut self.connections {
426 if !connection.relay_spawned {
427 continue;
428 }
429
430 let socket = sockets.get_mut::<tcp::Socket>(handle);
431
432 match connection.exit_state.load() {
433 RelayExitMode::Abort => {
434 socket.abort();
435 continue;
436 }
437 RelayExitMode::Graceful => {
438 flush_proxy_data(socket, connection);
439 if connection.buffered_proxy_data.is_none() {
440 socket.close();
441 } else {
442 connection.close_attempts += 1;
443 if connection.close_attempts >= CLOSE_RETRY_LIMIT {
444 socket.abort();
445 }
446 }
447 continue;
448 }
449 RelayExitMode::HalfClosed => {
450 flush_proxy_data(socket, connection);
457 if connection.buffered_proxy_data.is_none() && !connection.guest_send_closed {
458 socket.close();
459 connection.guest_send_closed = true;
460 }
461 }
462 RelayExitMode::Running => {}
463 }
464
465 flush_guest_data(connection);
466 while connection.buffered_guest_data.is_none() && socket.can_recv() {
467 match socket.recv_slice(&mut read_buffer) {
468 Ok(bytes_read) if bytes_read > 0 => {
469 let payload = read_buffer[..bytes_read].to_vec();
470 if !send_guest_payload(connection, payload) {
471 break;
472 }
473 }
474 _ => break,
475 }
476 }
477
478 flush_proxy_data(socket, connection);
479 }
480 }
481
482 pub fn take_new_connections(&mut self, sockets: &mut SocketSet<'_>) -> Vec<NewTcpConnection> {
488 let mut new_connections = Vec::new();
489
490 for (&handle, connection) in &mut self.connections {
491 if connection.relay_spawned {
492 continue;
493 }
494
495 let socket = sockets.get::<tcp::Socket>(handle);
496 if socket.state() == tcp::State::Established {
497 connection.relay_spawned = true;
498
499 if let Some(endpoints) = connection.pending_proxy_endpoints.take() {
500 new_connections.push(NewTcpConnection {
501 destination: connection.destination,
502 relay_target: endpoints.relay_target,
503 from_smoltcp: endpoints.from_smoltcp,
504 to_smoltcp: endpoints.to_smoltcp,
505 exit_state: connection.exit_state.clone(),
506 });
507 }
508 }
509 }
510
511 new_connections
512 }
513
514 pub fn cleanup_closed(&mut self, sockets: &mut SocketSet<'_>) {
518 let keys = &mut self.connection_keys;
519 let published_ports = &mut self.used_published_ports;
520 self.connections.retain(|&handle, connection| {
521 let socket = sockets.get::<tcp::Socket>(handle);
522 if socket.state() == tcp::State::Closed {
523 keys.remove(&(connection.source, connection.destination));
524 if let Some(port) = connection.reserved_published_port {
525 published_ports.remove(&port);
526 }
527 sockets.remove(handle);
528 false
529 } else {
530 true
531 }
532 });
533 }
534
535 fn allocate_published_port(&mut self) -> Option<u16> {
536 let start = self.next_published_port;
537
538 loop {
539 let candidate = self.next_published_port;
540 self.next_published_port = if candidate == PUBLISHED_PORT_END {
541 PUBLISHED_PORT_START
542 } else {
543 candidate + 1
544 };
545
546 if self.used_published_ports.insert(candidate) {
547 return Some(candidate);
548 }
549
550 if self.next_published_port == start {
551 return None;
552 }
553 }
554 }
555}
556
557pub fn spawn_tcp_relay(
566 destination: SocketAddr,
567 relay_target: RelayTarget,
568 from_smoltcp: Receiver<Vec<u8>>,
569 to_smoltcp: SyncSender<Vec<u8>>,
570 relay_wake: Arc<WakePipe>,
571 exit_state: RelayExitState,
572) {
573 let thread_name = format!("smolvm-tcp-{}", destination.port());
574 virtio_net_log!(
575 "virtio-net: spawning host TCP relay thread destination={} thread={}",
576 destination,
577 thread_name
578 );
579 let _ = thread::Builder::new().name(thread_name).spawn(move || {
580 run_tcp_relay(
581 destination,
582 relay_target,
583 from_smoltcp,
584 to_smoltcp,
585 relay_wake,
586 exit_state,
587 )
588 });
589}
590
591fn run_tcp_relay(
592 destination: SocketAddr,
593 relay_target: RelayTarget,
594 from_smoltcp: Receiver<Vec<u8>>,
595 to_smoltcp: SyncSender<Vec<u8>>,
596 relay_wake: Arc<WakePipe>,
597 exit_state: RelayExitState,
598) {
599 virtio_net_log!(
602 "virtio-net: host TCP relay thread started destination={}",
603 destination
604 );
605 match tcp_relay_loop(
606 destination,
607 relay_target,
608 from_smoltcp,
609 to_smoltcp,
610 relay_wake,
611 &exit_state,
612 ) {
613 Ok(mode) => {
614 virtio_net_log!(
615 "virtio-net: host TCP relay thread exited destination={} mode={:?}",
616 destination,
617 mode
618 );
619 exit_state.store(mode)
620 }
621 Err(err) => {
622 virtio_net_log!(
623 "virtio-net: host TCP relay failed destination={} error={}",
624 destination,
625 err
626 );
627 exit_state.store(RelayExitMode::Abort);
628 }
629 }
630}
631
632fn tcp_relay_loop(
633 destination: SocketAddr,
634 relay_target: RelayTarget,
635 from_smoltcp: Receiver<Vec<u8>>,
636 to_smoltcp: SyncSender<Vec<u8>>,
637 relay_wake: Arc<WakePipe>,
638 exit_state: &RelayExitState,
639) -> io::Result<RelayExitMode> {
640 let mut stream = match relay_target {
647 RelayTarget::Connect(destination) => {
648 virtio_net_log!(
649 "virtio-net: connecting host TCP relay socket destination={}",
650 destination
651 );
652 let stream = TcpStream::connect(destination)?;
653 virtio_net_log!(
654 "virtio-net: host TCP relay socket connected destination={}",
655 destination
656 );
657 stream
658 }
659 RelayTarget::Attached(stream) => {
660 virtio_net_log!(
661 "virtio-net: using accepted host TCP socket for published port guest_destination={} peer_addr={:?} local_addr={:?}",
662 destination,
663 stream.peer_addr().ok(),
664 stream.local_addr().ok()
665 );
666 stream
667 }
668 };
669 stream.set_nonblocking(true)?;
670
671 let mut guest_write_closed = false;
672 let mut guest_channel_closed = false;
673 let mut host_read_closed = false;
674 let mut pending_guest_data: Option<(Vec<u8>, usize)> = None;
675 let mut read_buffer = [0u8; RELAY_BUFFER_BYTES];
676
677 loop {
678 let mut did_work = false;
679
680 if pending_guest_data.is_none() && !guest_channel_closed {
681 match from_smoltcp.try_recv() {
682 Ok(payload) => {
683 pending_guest_data = Some((payload, 0));
684 relay_wake.wake();
689 did_work = true;
690 }
691 Err(TryRecvError::Empty) => {}
692 Err(TryRecvError::Disconnected) => {
693 guest_channel_closed = true;
694 }
695 }
696 }
697
698 if let Some((payload, offset)) = &mut pending_guest_data {
699 while *offset < payload.len() {
700 match stream.write(&payload[*offset..]) {
701 Ok(0) => {
702 return Err(io::Error::new(
703 io::ErrorKind::WriteZero,
704 "host TCP relay wrote zero bytes",
705 ));
706 }
707 Ok(bytes_written) => {
708 *offset += bytes_written;
709 did_work = true;
710 }
711 Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
712 Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
713 Err(err) => return Err(err),
714 }
715 }
716
717 if *offset >= payload.len() {
718 pending_guest_data = None;
719 }
720 }
721
722 if guest_channel_closed && pending_guest_data.is_none() && !guest_write_closed {
723 let _ = stream.shutdown(Shutdown::Write);
726 guest_write_closed = true;
727 }
728
729 if host_read_closed && guest_channel_closed && pending_guest_data.is_none() {
733 return Ok(RelayExitMode::Graceful);
734 }
735
736 if !host_read_closed {
742 match stream.read(&mut read_buffer) {
743 Ok(0) => {
744 host_read_closed = true;
745 exit_state.store(RelayExitMode::HalfClosed);
746 relay_wake.wake();
747 did_work = true;
748 }
749 Ok(bytes_read) => {
750 if to_smoltcp.send(read_buffer[..bytes_read].to_vec()).is_err() {
751 return Ok(RelayExitMode::Graceful);
752 }
753 relay_wake.wake();
754 did_work = true;
755 }
756 Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
757 Err(err) => return Err(err),
758 }
759 }
760
761 if !did_work {
762 thread::sleep(PROXY_IDLE_SLEEP);
763 }
764 }
765}
766
767fn flush_guest_data(connection: &mut TrackedConnection) {
768 let Some(payload) = connection.buffered_guest_data.take() else {
769 return;
770 };
771 send_guest_payload(connection, payload);
772}
773
774fn send_guest_payload(connection: &mut TrackedConnection, payload: Vec<u8>) -> bool {
775 match connection.to_proxy.try_send(payload) {
776 Ok(()) => true,
777 Err(TrySendError::Full(payload)) => {
778 connection.buffered_guest_data = Some(payload);
779 false
780 }
781 Err(TrySendError::Disconnected(_)) => false,
782 }
783}
784
785fn flush_proxy_data(socket: &mut tcp::Socket<'_>, connection: &mut TrackedConnection) {
786 if let Some((data, offset)) = &mut connection.buffered_proxy_data {
790 if socket.can_send() {
791 match socket.send_slice(&data[*offset..]) {
792 Ok(written) => {
793 *offset += written;
794 if *offset >= data.len() {
795 connection.buffered_proxy_data = None;
796 }
797 }
798 Err(_) => return,
799 }
800 } else {
801 return;
802 }
803 }
804
805 while connection.buffered_proxy_data.is_none() {
806 match connection.from_proxy.try_recv() {
807 Ok(payload) => {
808 if socket.can_send() {
809 match socket.send_slice(&payload) {
810 Ok(written) if written < payload.len() => {
811 connection.buffered_proxy_data = Some((payload, written));
812 }
813 Err(_) => {
814 connection.buffered_proxy_data = Some((payload, 0));
815 }
816 _ => {}
817 }
818 } else {
819 connection.buffered_proxy_data = Some((payload, 0));
820 }
821 }
822 Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
823 }
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830
831 fn test_connection(to_proxy: SyncSender<Vec<u8>>) -> TrackedConnection {
832 let (_from_proxy_tx, from_proxy) = mpsc::sync_channel(CHANNEL_CAPACITY);
833 TrackedConnection {
834 source: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 12_345),
835 destination: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 80),
836 to_proxy,
837 from_proxy,
838 pending_proxy_endpoints: None,
839 relay_spawned: true,
840 buffered_guest_data: None,
841 buffered_proxy_data: None,
842 close_attempts: 0,
843 guest_send_closed: false,
844 exit_state: RelayExitState::new(),
845 reserved_published_port: None,
846 }
847 }
848
849 #[test]
850 fn guest_payload_is_buffered_when_relay_channel_is_full() {
851 let (to_proxy, from_smoltcp) = mpsc::sync_channel(1);
852 to_proxy.send(vec![1]).unwrap();
853 let mut connection = test_connection(to_proxy);
854
855 assert!(!send_guest_payload(&mut connection, vec![2]));
856 assert_eq!(connection.buffered_guest_data.as_deref(), Some(&[2][..]));
857
858 assert_eq!(from_smoltcp.recv().unwrap(), vec![1]);
859 flush_guest_data(&mut connection);
860
861 assert!(connection.buffered_guest_data.is_none());
862 assert_eq!(from_smoltcp.recv().unwrap(), vec![2]);
863 }
864
865 #[test]
866 fn gateway_ip_destination_dials_the_host_over_loopback() {
867 let gw4: IpAddr = "100.96.0.1".parse().unwrap();
868 let gw6: IpAddr = "fd00::1".parse().unwrap();
869 let table = TcpRelayTable::new(None, EgressPolicy::unrestricted(), vec![gw4, gw6]);
870
871 assert_eq!(
875 table.host_connect_addr(SocketAddr::new(gw4, 19997)),
876 SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 19997),
877 );
878 assert_eq!(
879 table.host_connect_addr(SocketAddr::new(gw6, 6379)),
880 SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 6379),
881 );
882
883 let lan: IpAddr = "192.168.1.5".parse().unwrap();
886 assert_eq!(
887 table.host_connect_addr(SocketAddr::new(lan, 3306)),
888 SocketAddr::new(lan, 3306),
889 );
890 }
891}