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::{Ipv4Addr, 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}
68
69pub struct NewTcpConnection {
75 pub destination: SocketAddr,
77 pub relay_target: RelayTarget,
79 pub from_smoltcp: Receiver<Vec<u8>>,
81 pub to_smoltcp: SyncSender<Vec<u8>>,
83 pub exit_state: RelayExitState,
85}
86
87#[derive(Debug)]
88struct TrackedConnection {
89 source: SocketAddr,
91 destination: SocketAddr,
92 to_proxy: SyncSender<Vec<u8>>,
94 from_proxy: Receiver<Vec<u8>>,
96 pending_proxy_endpoints: Option<PendingProxyEndpoints>,
98 relay_spawned: bool,
100 buffered_guest_data: Option<Vec<u8>>,
103 buffered_proxy_data: Option<(Vec<u8>, usize)>,
105 close_attempts: u16,
107 guest_send_closed: bool,
110 exit_state: RelayExitState,
112 reserved_published_port: Option<u16>,
114}
115
116#[derive(Debug)]
117struct PendingProxyEndpoints {
118 from_smoltcp: Receiver<Vec<u8>>,
119 to_smoltcp: SyncSender<Vec<u8>>,
120 relay_target: RelayTarget,
121}
122
123#[derive(Debug)]
125pub enum RelayTarget {
126 Connect(SocketAddr),
128 Attached(TcpStream),
130}
131
132#[derive(Clone, Debug)]
142pub struct RelayExitState {
143 inner: Arc<AtomicU8>,
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148#[repr(u8)]
149pub enum RelayExitMode {
150 Running = 0,
152 Graceful = 1,
154 Abort = 2,
156 HalfClosed = 3,
162}
163
164impl RelayExitState {
165 fn new() -> Self {
166 Self {
167 inner: Arc::new(AtomicU8::new(RelayExitMode::Running as u8)),
168 }
169 }
170
171 fn load(&self) -> RelayExitMode {
172 match self.inner.load(Ordering::Relaxed) {
173 1 => RelayExitMode::Graceful,
174 2 => RelayExitMode::Abort,
175 3 => RelayExitMode::HalfClosed,
176 _ => RelayExitMode::Running,
177 }
178 }
179
180 fn store(&self, mode: RelayExitMode) {
181 self.inner.store(mode as u8, Ordering::Relaxed);
182 }
183}
184
185impl TcpRelayTable {
186 pub fn new(max_connections: Option<usize>, egress: EgressPolicy) -> Self {
188 Self {
189 connections: HashMap::new(),
190 connection_keys: HashSet::new(),
191 used_published_ports: HashSet::new(),
192 next_published_port: PUBLISHED_PORT_START,
193 max_connections: max_connections.unwrap_or(MAX_CONNECTIONS),
194 egress,
195 }
196 }
197
198 pub fn has_socket_for(&self, source: &SocketAddr, destination: &SocketAddr) -> bool {
200 self.connection_keys.contains(&(*source, *destination))
201 }
202
203 pub fn create_tcp_socket(
220 &mut self,
221 source: SocketAddr,
222 destination: SocketAddr,
223 sockets: &mut SocketSet<'_>,
224 ) -> bool {
225 if self.connections.len() >= self.max_connections {
226 tracing::warn!("dropping TCP connection because the relay table is full");
227 return false;
228 }
229
230 if !self.egress.allows(destination.ip()) {
234 tracing::debug!(
235 %destination,
236 "virtio-net: blocking outbound connection by egress policy"
237 );
238 return false;
239 }
240
241 let rx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_RX_BUFFER_BYTES]);
242 let tx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_TX_BUFFER_BYTES]);
243 let mut socket = tcp::Socket::new(rx_buffer, tx_buffer);
244
245 let listen_endpoint = IpListenEndpoint {
246 addr: Some(destination.ip().into()),
247 port: destination.port(),
248 };
249 if socket.listen(listen_endpoint).is_err() {
250 return false;
251 }
252
253 let handle = sockets.add(socket);
254
255 let (to_proxy_tx, to_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
256 let (from_proxy_tx, from_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
257 let exit_state = RelayExitState::new();
258
259 self.connection_keys.insert((source, destination));
260 self.connections.insert(
261 handle,
262 TrackedConnection {
263 source,
264 destination,
265 to_proxy: to_proxy_tx,
266 from_proxy: from_proxy_rx,
267 pending_proxy_endpoints: Some(PendingProxyEndpoints {
268 from_smoltcp: to_proxy_rx,
269 to_smoltcp: from_proxy_tx,
270 relay_target: RelayTarget::Connect(destination),
271 }),
272 relay_spawned: false,
273 buffered_guest_data: None,
274 buffered_proxy_data: None,
275 close_attempts: 0,
276 guest_send_closed: false,
277 exit_state,
278 reserved_published_port: None,
279 },
280 );
281
282 true
283 }
284
285 pub fn create_published_socket(
301 &mut self,
302 interface: &mut Interface,
303 gateway_ip: Ipv4Addr,
304 destination: SocketAddr,
305 host_stream: TcpStream,
306 sockets: &mut SocketSet<'_>,
307 ) -> bool {
308 if self.connections.len() >= self.max_connections {
309 tracing::warn!("dropping published TCP connection because the relay table is full");
310 return false;
311 }
312
313 let Some(local_port) = self.allocate_published_port() else {
314 tracing::warn!(
315 "dropping published TCP connection because no gateway source port is available"
316 );
317 return false;
318 };
319
320 let std::net::IpAddr::V4(destination_ip) = destination.ip() else {
323 self.used_published_ports.remove(&local_port);
324 return false;
325 };
326
327 let rx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_RX_BUFFER_BYTES]);
328 let tx_buffer = tcp::SocketBuffer::new(vec![0u8; TCP_TX_BUFFER_BYTES]);
329 let mut socket = tcp::Socket::new(rx_buffer, tx_buffer);
330 let local_endpoint = IpListenEndpoint {
331 addr: Some(gateway_ip.into()),
332 port: local_port,
333 };
334 if socket
335 .connect(
336 interface.context(),
337 (destination_ip, destination.port()),
338 local_endpoint,
339 )
340 .is_err()
341 {
342 self.used_published_ports.remove(&local_port);
343 return false;
344 }
345
346 let handle = sockets.add(socket);
347 let source = SocketAddr::new(std::net::IpAddr::V4(gateway_ip), local_port);
348
349 let (to_proxy_tx, to_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
350 let (from_proxy_tx, from_proxy_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
351 let exit_state = RelayExitState::new();
352
353 self.connection_keys.insert((source, destination));
354 self.connections.insert(
355 handle,
356 TrackedConnection {
357 source,
358 destination,
359 to_proxy: to_proxy_tx,
360 from_proxy: from_proxy_rx,
361 pending_proxy_endpoints: Some(PendingProxyEndpoints {
362 from_smoltcp: to_proxy_rx,
363 to_smoltcp: from_proxy_tx,
364 relay_target: RelayTarget::Attached(host_stream),
365 }),
366 relay_spawned: false,
367 buffered_guest_data: None,
368 buffered_proxy_data: None,
369 close_attempts: 0,
370 guest_send_closed: false,
371 exit_state,
372 reserved_published_port: Some(local_port),
373 },
374 );
375
376 true
377 }
378
379 pub fn relay_data(&mut self, sockets: &mut SocketSet<'_>) {
388 let mut read_buffer = [0u8; RELAY_BUFFER_BYTES];
389
390 for (&handle, connection) in &mut self.connections {
391 if !connection.relay_spawned {
392 continue;
393 }
394
395 let socket = sockets.get_mut::<tcp::Socket>(handle);
396
397 match connection.exit_state.load() {
398 RelayExitMode::Abort => {
399 socket.abort();
400 continue;
401 }
402 RelayExitMode::Graceful => {
403 flush_proxy_data(socket, connection);
404 if connection.buffered_proxy_data.is_none() {
405 socket.close();
406 } else {
407 connection.close_attempts += 1;
408 if connection.close_attempts >= CLOSE_RETRY_LIMIT {
409 socket.abort();
410 }
411 }
412 continue;
413 }
414 RelayExitMode::HalfClosed => {
415 flush_proxy_data(socket, connection);
422 if connection.buffered_proxy_data.is_none() && !connection.guest_send_closed {
423 socket.close();
424 connection.guest_send_closed = true;
425 }
426 }
427 RelayExitMode::Running => {}
428 }
429
430 flush_guest_data(connection);
431 while connection.buffered_guest_data.is_none() && socket.can_recv() {
432 match socket.recv_slice(&mut read_buffer) {
433 Ok(bytes_read) if bytes_read > 0 => {
434 let payload = read_buffer[..bytes_read].to_vec();
435 if !send_guest_payload(connection, payload) {
436 break;
437 }
438 }
439 _ => break,
440 }
441 }
442
443 flush_proxy_data(socket, connection);
444 }
445 }
446
447 pub fn take_new_connections(&mut self, sockets: &mut SocketSet<'_>) -> Vec<NewTcpConnection> {
453 let mut new_connections = Vec::new();
454
455 for (&handle, connection) in &mut self.connections {
456 if connection.relay_spawned {
457 continue;
458 }
459
460 let socket = sockets.get::<tcp::Socket>(handle);
461 if socket.state() == tcp::State::Established {
462 connection.relay_spawned = true;
463
464 if let Some(endpoints) = connection.pending_proxy_endpoints.take() {
465 new_connections.push(NewTcpConnection {
466 destination: connection.destination,
467 relay_target: endpoints.relay_target,
468 from_smoltcp: endpoints.from_smoltcp,
469 to_smoltcp: endpoints.to_smoltcp,
470 exit_state: connection.exit_state.clone(),
471 });
472 }
473 }
474 }
475
476 new_connections
477 }
478
479 pub fn cleanup_closed(&mut self, sockets: &mut SocketSet<'_>) {
483 let keys = &mut self.connection_keys;
484 let published_ports = &mut self.used_published_ports;
485 self.connections.retain(|&handle, connection| {
486 let socket = sockets.get::<tcp::Socket>(handle);
487 if socket.state() == tcp::State::Closed {
488 keys.remove(&(connection.source, connection.destination));
489 if let Some(port) = connection.reserved_published_port {
490 published_ports.remove(&port);
491 }
492 sockets.remove(handle);
493 false
494 } else {
495 true
496 }
497 });
498 }
499
500 fn allocate_published_port(&mut self) -> Option<u16> {
501 let start = self.next_published_port;
502
503 loop {
504 let candidate = self.next_published_port;
505 self.next_published_port = if candidate == PUBLISHED_PORT_END {
506 PUBLISHED_PORT_START
507 } else {
508 candidate + 1
509 };
510
511 if self.used_published_ports.insert(candidate) {
512 return Some(candidate);
513 }
514
515 if self.next_published_port == start {
516 return None;
517 }
518 }
519 }
520}
521
522pub fn spawn_tcp_relay(
531 destination: SocketAddr,
532 relay_target: RelayTarget,
533 from_smoltcp: Receiver<Vec<u8>>,
534 to_smoltcp: SyncSender<Vec<u8>>,
535 relay_wake: Arc<WakePipe>,
536 exit_state: RelayExitState,
537) {
538 let thread_name = format!("smolvm-tcp-{}", destination.port());
539 virtio_net_log!(
540 "virtio-net: spawning host TCP relay thread destination={} thread={}",
541 destination,
542 thread_name
543 );
544 let _ = thread::Builder::new().name(thread_name).spawn(move || {
545 run_tcp_relay(
546 destination,
547 relay_target,
548 from_smoltcp,
549 to_smoltcp,
550 relay_wake,
551 exit_state,
552 )
553 });
554}
555
556fn run_tcp_relay(
557 destination: SocketAddr,
558 relay_target: RelayTarget,
559 from_smoltcp: Receiver<Vec<u8>>,
560 to_smoltcp: SyncSender<Vec<u8>>,
561 relay_wake: Arc<WakePipe>,
562 exit_state: RelayExitState,
563) {
564 virtio_net_log!(
567 "virtio-net: host TCP relay thread started destination={}",
568 destination
569 );
570 match tcp_relay_loop(
571 destination,
572 relay_target,
573 from_smoltcp,
574 to_smoltcp,
575 relay_wake,
576 &exit_state,
577 ) {
578 Ok(mode) => {
579 virtio_net_log!(
580 "virtio-net: host TCP relay thread exited destination={} mode={:?}",
581 destination,
582 mode
583 );
584 exit_state.store(mode)
585 }
586 Err(err) => {
587 virtio_net_log!(
588 "virtio-net: host TCP relay failed destination={} error={}",
589 destination,
590 err
591 );
592 exit_state.store(RelayExitMode::Abort);
593 }
594 }
595}
596
597fn tcp_relay_loop(
598 destination: SocketAddr,
599 relay_target: RelayTarget,
600 from_smoltcp: Receiver<Vec<u8>>,
601 to_smoltcp: SyncSender<Vec<u8>>,
602 relay_wake: Arc<WakePipe>,
603 exit_state: &RelayExitState,
604) -> io::Result<RelayExitMode> {
605 let mut stream = match relay_target {
612 RelayTarget::Connect(destination) => {
613 virtio_net_log!(
614 "virtio-net: connecting host TCP relay socket destination={}",
615 destination
616 );
617 let stream = TcpStream::connect(destination)?;
618 virtio_net_log!(
619 "virtio-net: host TCP relay socket connected destination={}",
620 destination
621 );
622 stream
623 }
624 RelayTarget::Attached(stream) => {
625 virtio_net_log!(
626 "virtio-net: using accepted host TCP socket for published port guest_destination={} peer_addr={:?} local_addr={:?}",
627 destination,
628 stream.peer_addr().ok(),
629 stream.local_addr().ok()
630 );
631 stream
632 }
633 };
634 stream.set_nonblocking(true)?;
635
636 let mut guest_write_closed = false;
637 let mut guest_channel_closed = false;
638 let mut host_read_closed = false;
639 let mut pending_guest_data: Option<(Vec<u8>, usize)> = None;
640 let mut read_buffer = [0u8; RELAY_BUFFER_BYTES];
641
642 loop {
643 let mut did_work = false;
644
645 if pending_guest_data.is_none() && !guest_channel_closed {
646 match from_smoltcp.try_recv() {
647 Ok(payload) => {
648 pending_guest_data = Some((payload, 0));
649 relay_wake.wake();
654 did_work = true;
655 }
656 Err(TryRecvError::Empty) => {}
657 Err(TryRecvError::Disconnected) => {
658 guest_channel_closed = true;
659 }
660 }
661 }
662
663 if let Some((payload, offset)) = &mut pending_guest_data {
664 while *offset < payload.len() {
665 match stream.write(&payload[*offset..]) {
666 Ok(0) => {
667 return Err(io::Error::new(
668 io::ErrorKind::WriteZero,
669 "host TCP relay wrote zero bytes",
670 ));
671 }
672 Ok(bytes_written) => {
673 *offset += bytes_written;
674 did_work = true;
675 }
676 Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
677 Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
678 Err(err) => return Err(err),
679 }
680 }
681
682 if *offset >= payload.len() {
683 pending_guest_data = None;
684 }
685 }
686
687 if guest_channel_closed && pending_guest_data.is_none() && !guest_write_closed {
688 let _ = stream.shutdown(Shutdown::Write);
691 guest_write_closed = true;
692 }
693
694 if host_read_closed && guest_channel_closed && pending_guest_data.is_none() {
698 return Ok(RelayExitMode::Graceful);
699 }
700
701 if !host_read_closed {
707 match stream.read(&mut read_buffer) {
708 Ok(0) => {
709 host_read_closed = true;
710 exit_state.store(RelayExitMode::HalfClosed);
711 relay_wake.wake();
712 did_work = true;
713 }
714 Ok(bytes_read) => {
715 if to_smoltcp.send(read_buffer[..bytes_read].to_vec()).is_err() {
716 return Ok(RelayExitMode::Graceful);
717 }
718 relay_wake.wake();
719 did_work = true;
720 }
721 Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
722 Err(err) => return Err(err),
723 }
724 }
725
726 if !did_work {
727 thread::sleep(PROXY_IDLE_SLEEP);
728 }
729 }
730}
731
732fn flush_guest_data(connection: &mut TrackedConnection) {
733 let Some(payload) = connection.buffered_guest_data.take() else {
734 return;
735 };
736 send_guest_payload(connection, payload);
737}
738
739fn send_guest_payload(connection: &mut TrackedConnection, payload: Vec<u8>) -> bool {
740 match connection.to_proxy.try_send(payload) {
741 Ok(()) => true,
742 Err(TrySendError::Full(payload)) => {
743 connection.buffered_guest_data = Some(payload);
744 false
745 }
746 Err(TrySendError::Disconnected(_)) => false,
747 }
748}
749
750fn flush_proxy_data(socket: &mut tcp::Socket<'_>, connection: &mut TrackedConnection) {
751 if let Some((data, offset)) = &mut connection.buffered_proxy_data {
755 if socket.can_send() {
756 match socket.send_slice(&data[*offset..]) {
757 Ok(written) => {
758 *offset += written;
759 if *offset >= data.len() {
760 connection.buffered_proxy_data = None;
761 }
762 }
763 Err(_) => return,
764 }
765 } else {
766 return;
767 }
768 }
769
770 while connection.buffered_proxy_data.is_none() {
771 match connection.from_proxy.try_recv() {
772 Ok(payload) => {
773 if socket.can_send() {
774 match socket.send_slice(&payload) {
775 Ok(written) if written < payload.len() => {
776 connection.buffered_proxy_data = Some((payload, written));
777 }
778 Err(_) => {
779 connection.buffered_proxy_data = Some((payload, 0));
780 }
781 _ => {}
782 }
783 } else {
784 connection.buffered_proxy_data = Some((payload, 0));
785 }
786 }
787 Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
788 }
789 }
790}
791
792#[cfg(test)]
793mod tests {
794 use super::*;
795
796 fn test_connection(to_proxy: SyncSender<Vec<u8>>) -> TrackedConnection {
797 let (_from_proxy_tx, from_proxy) = mpsc::sync_channel(CHANNEL_CAPACITY);
798 TrackedConnection {
799 source: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 12_345),
800 destination: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 80),
801 to_proxy,
802 from_proxy,
803 pending_proxy_endpoints: None,
804 relay_spawned: true,
805 buffered_guest_data: None,
806 buffered_proxy_data: None,
807 close_attempts: 0,
808 guest_send_closed: false,
809 exit_state: RelayExitState::new(),
810 reserved_published_port: None,
811 }
812 }
813
814 #[test]
815 fn guest_payload_is_buffered_when_relay_channel_is_full() {
816 let (to_proxy, from_smoltcp) = mpsc::sync_channel(1);
817 to_proxy.send(vec![1]).unwrap();
818 let mut connection = test_connection(to_proxy);
819
820 assert!(!send_guest_payload(&mut connection, vec![2]));
821 assert_eq!(connection.buffered_guest_data.as_deref(), Some(&[2][..]));
822
823 assert_eq!(from_smoltcp.recv().unwrap(), vec![1]);
824 flush_guest_data(&mut connection);
825
826 assert!(connection.buffered_guest_data.is_none());
827 assert_eq!(from_smoltcp.recv().unwrap(), vec![2]);
828 }
829}