1use crate::device::VirtioNetworkDevice;
54use crate::dns;
55use crate::egress::EgressPolicy;
56use crate::icmp_relay;
57use crate::queues::NetworkFrameQueues;
58use crate::tcp_listeners::AcceptedTcpConnection;
59use crate::tcp_relay::{spawn_tcp_relay, TcpRelayTable};
60use crate::udp_relay;
61use crate::{virtio_net_log, DEFAULT_DNS_ADDR};
62use smoltcp::iface::{
63 Config, Interface, PollIngressSingleResult, PollResult, SocketHandle, SocketSet,
64};
65use smoltcp::socket::raw::{
66 PacketBuffer as RawPacketBuffer, PacketMetadata as RawPacketMetadata, Socket as RawSocket,
67};
68use smoltcp::socket::udp::{PacketBuffer, PacketMetadata, Socket as UdpSocket, UdpMetadata};
69use smoltcp::time::Instant;
70use smoltcp::wire::{
71 EthernetAddress, EthernetFrame, EthernetProtocol, HardwareAddress, IpAddress, IpCidr,
72 IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket,
73};
74use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket as HostUdpSocket};
75use std::sync::atomic::Ordering;
76use std::sync::mpsc::{Receiver, SyncSender, TryRecvError, TrySendError};
77use std::sync::Arc;
78use std::thread::{self, JoinHandle};
79use std::time::{Duration, Instant as StdInstant};
80
81const DNS_SOCKET_PORT: u16 = 53;
82const DNS_PACKET_SLOTS: usize = 8;
83const DNS_BUFFER_BYTES: usize = 2048;
84const DEFAULT_IDLE_TIMEOUT_MS: i32 = 100;
85const ICMP_PACKET_SLOTS: usize = 16;
87const ICMP_BUFFER_BYTES: usize = 32 * 1024;
89
90#[derive(Debug, Clone, Copy)]
96pub struct VirtioPollConfig {
97 pub gateway_mac: [u8; 6],
99 pub guest_mac: [u8; 6],
101 pub gateway_ipv4: Ipv4Addr,
103 pub guest_ipv4: Ipv4Addr,
105 pub gateway_ipv6: Ipv6Addr,
107 pub guest_ipv6: Ipv6Addr,
109 pub prefix_len6: u8,
111 pub mtu: usize,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116enum FrameAction {
117 TcpSyn {
118 source: SocketAddr,
119 destination: SocketAddr,
120 },
121 DnsQuery,
122 UdpFlow {
124 destination: SocketAddr,
125 },
126 Passthrough,
127}
128
129pub fn start_network_stack(
139 queues: Arc<NetworkFrameQueues>,
140 config: VirtioPollConfig,
141 tcp_receiver: Option<Receiver<AcceptedTcpConnection>>,
142 egress: EgressPolicy,
143) -> std::io::Result<JoinHandle<()>> {
144 virtio_net_log!(
145 "virtio-net: spawning poll thread guest_ip={} gateway_ip={} mtu={}",
146 config.guest_ipv4,
147 config.gateway_ipv4,
148 config.mtu
149 );
150 thread::Builder::new()
151 .name("smolvm-net-poll".into())
152 .spawn(move || run_network_stack(queues, config, tcp_receiver, egress))
153}
154
155fn run_network_stack(
156 queues: Arc<NetworkFrameQueues>,
157 config: VirtioPollConfig,
158 mut tcp_receiver: Option<Receiver<AcceptedTcpConnection>>,
159 egress: EgressPolicy,
160) {
161 virtio_net_log!(
175 "virtio-net: poll loop started guest_ip={} gateway_ip={}",
176 config.guest_ipv4,
177 config.gateway_ipv4
178 );
179 let clock = StdInstant::now();
180 let mut device = VirtioNetworkDevice::new(queues.clone(), config.mtu);
181 let mut interface = create_interface(&mut device, &config);
182 let mut sockets = SocketSet::new(vec![]);
183 let dns_socket_handle = add_dns_socket(&mut sockets);
184 let (icmp4_handle, icmp6_handle) = add_icmp_raw_sockets(&mut sockets);
185 let gateway_addrs = [
188 IpAddr::V4(config.gateway_ipv4),
189 IpAddr::V6(config.gateway_ipv6),
190 IpAddr::V6(link_local_from_mac(config.gateway_mac)),
191 ];
192 let relay_wake = Arc::new(queues.relay_wake.clone());
193 let mut relays = TcpRelayTable::new(None, egress.clone());
194 let mut udp_sockets = udp_relay::UdpSocketTable::new();
195 let udp_channels = {
196 let shutdown_queues = queues.clone();
197 udp_relay::start_udp_relay(
198 relay_wake.clone(),
199 Arc::new(move || shutdown_queues.is_shutting_down()),
200 )
201 };
202 let icmp_channels = {
203 let shutdown_queues = queues.clone();
204 icmp_relay::start_icmp_relay(
205 relay_wake.clone(),
206 Arc::new(move || shutdown_queues.is_shutting_down()),
207 )
208 };
209
210 let mut poll_fds = [
214 libc::pollfd {
215 fd: queues.guest_wake.as_raw_fd(),
216 events: libc::POLLIN,
217 revents: 0,
218 },
219 libc::pollfd {
220 fd: queues.relay_wake.as_raw_fd(),
221 events: libc::POLLIN,
222 revents: 0,
223 },
224 ];
225
226 loop {
227 if queues.is_shutting_down() {
228 return;
229 }
230 let now = smoltcp_now(clock);
231
232 while let Some(frame) = device.stage_next_frame() {
233 match classify_guest_frame(frame) {
239 FrameAction::TcpSyn {
240 source,
241 destination,
242 } => {
243 virtio_net_log!(
244 "virtio-net: guest TCP SYN source={} destination={}",
245 source,
246 destination
247 );
248 if !relays.has_socket_for(&source, &destination) {
249 relays.create_tcp_socket(source, destination, &mut sockets);
250 }
251 if matches!(
252 interface.poll_ingress_single(now, &mut device, &mut sockets),
253 PollIngressSingleResult::None
254 ) {
255 device.drop_staged_frame();
256 }
257 }
258 FrameAction::DnsQuery | FrameAction::Passthrough => {
259 if matches!(
260 interface.poll_ingress_single(now, &mut device, &mut sockets),
261 PollIngressSingleResult::None
262 ) {
263 device.drop_staged_frame();
264 }
265 }
266 FrameAction::UdpFlow { destination } => {
267 if udp_relay::should_relay_udp(destination, &egress)
270 && udp_sockets.ensure_socket(destination, &mut sockets)
271 {
272 if matches!(
273 interface.poll_ingress_single(now, &mut device, &mut sockets),
274 PollIngressSingleResult::None
275 ) {
276 device.drop_staged_frame();
277 }
278 } else {
279 device.drop_staged_frame();
280 }
281 }
282 }
283 }
284
285 relay_accepted_tcp_connection(
286 &mut tcp_receiver,
287 &mut relays,
288 &mut interface,
289 &mut sockets,
290 config.gateway_ipv4,
291 config.guest_ipv4,
292 );
293
294 flush_interface_egress(&mut interface, &mut device, &mut sockets, now);
297 interface.poll_maintenance(now);
298 wake_guest_if_needed(&queues, &device);
299
300 relays.relay_data(&mut sockets);
303 process_dns_queries(dns_socket_handle, &mut sockets, &egress);
304
305 if udp_sockets.drain_to_relay(&mut sockets, &udp_channels.to_relay) {
308 udp_channels.relay_thread_wake.wake();
309 }
310 udp_sockets.deliver_replies(&mut sockets, &udp_channels.from_relay);
311 udp_sockets.expire_idle(&mut sockets);
312
313 let mut woke_icmp = false;
317 woke_icmp |= drain_icmp_echo(
318 &mut sockets,
319 icmp4_handle,
320 false,
321 &egress,
322 &gateway_addrs,
323 &icmp_channels.to_relay,
324 );
325 woke_icmp |= drain_icmp_echo(
326 &mut sockets,
327 icmp6_handle,
328 true,
329 &egress,
330 &gateway_addrs,
331 &icmp_channels.to_relay,
332 );
333 if woke_icmp {
334 icmp_channels.relay_thread_wake.wake();
335 }
336 deliver_icmp_replies(
337 &mut sockets,
338 icmp4_handle,
339 icmp6_handle,
340 &icmp_channels.from_relay,
341 );
342
343 for connection in relays.take_new_connections(&mut sockets) {
346 spawn_tcp_relay(
347 connection.destination,
348 connection.relay_target,
349 connection.from_smoltcp,
350 connection.to_smoltcp,
351 relay_wake.clone(),
352 connection.exit_state,
353 );
354 }
355
356 relays.cleanup_closed(&mut sockets);
357
358 flush_interface_egress(&mut interface, &mut device, &mut sockets, now);
361 wake_guest_if_needed(&queues, &device);
362
363 let timeout_ms = interface
364 .poll_delay(now, &sockets)
365 .map(|duration| duration.total_millis().min(i32::MAX as u64) as i32)
366 .unwrap_or(DEFAULT_IDLE_TIMEOUT_MS);
367
368 unsafe {
370 libc::poll(
371 poll_fds.as_mut_ptr(),
372 poll_fds.len() as libc::nfds_t,
373 timeout_ms,
374 );
375 }
376
377 if poll_fds[0].revents & libc::POLLIN != 0 {
378 queues.guest_wake.drain();
379 }
380 if poll_fds[1].revents & libc::POLLIN != 0 {
381 queues.relay_wake.drain();
382 }
383 }
384}
385
386fn create_interface(device: &mut VirtioNetworkDevice, config: &VirtioPollConfig) -> Interface {
387 let mut interface = Interface::new(
397 Config::new(HardwareAddress::Ethernet(EthernetAddress(
398 config.gateway_mac,
399 ))),
400 device,
401 Instant::ZERO,
402 );
403 interface.update_ip_addrs(|addresses| {
404 addresses
405 .push(IpCidr::new(IpAddress::Ipv4(config.gateway_ipv4), 30))
406 .expect("failed to add gateway IPv4 address");
407 addresses
408 .push(IpCidr::new(
409 IpAddress::Ipv6(config.gateway_ipv6),
410 config.prefix_len6,
411 ))
412 .expect("failed to add gateway IPv6 address");
413 addresses
417 .push(IpCidr::new(
418 IpAddress::Ipv6(link_local_from_mac(config.gateway_mac)),
419 64,
420 ))
421 .expect("failed to add gateway IPv6 link-local address");
422 });
423 interface
427 .routes_mut()
428 .add_default_ipv4_route(config.gateway_ipv4)
429 .expect("failed to add default IPv4 route");
430 interface
431 .routes_mut()
432 .add_default_ipv6_route(config.gateway_ipv6)
433 .expect("failed to add default IPv6 route");
434 interface.set_any_ip(true);
435 interface
436}
437
438fn link_local_from_mac(mac: [u8; 6]) -> Ipv6Addr {
441 Ipv6Addr::new(
442 0xfe80,
443 0,
444 0,
445 0,
446 u16::from_be_bytes([mac[0] ^ 0x02, mac[1]]),
447 u16::from_be_bytes([mac[2], 0xff]),
448 u16::from_be_bytes([0xfe, mac[3]]),
449 u16::from_be_bytes([mac[4], mac[5]]),
450 )
451}
452
453fn add_dns_socket(sockets: &mut SocketSet<'_>) -> SocketHandle {
464 let rx_meta = vec![PacketMetadata::EMPTY; DNS_PACKET_SLOTS];
465 let tx_meta = vec![PacketMetadata::EMPTY; DNS_PACKET_SLOTS];
466 let rx_buffer = PacketBuffer::new(rx_meta, vec![0u8; DNS_BUFFER_BYTES]);
467 let tx_buffer = PacketBuffer::new(tx_meta, vec![0u8; DNS_BUFFER_BYTES]);
468 let mut socket = UdpSocket::new(rx_buffer, tx_buffer);
469 socket
470 .bind(smoltcp::wire::IpListenEndpoint {
471 addr: None,
472 port: DNS_SOCKET_PORT,
473 })
474 .expect("failed to bind gateway DNS socket");
475 sockets.add(socket)
476}
477
478fn add_icmp_raw_sockets(sockets: &mut SocketSet<'_>) -> (SocketHandle, SocketHandle) {
486 fn raw_socket(version: IpVersion, protocol: IpProtocol) -> RawSocket<'static> {
487 let rx = RawPacketBuffer::new(
488 vec![RawPacketMetadata::EMPTY; ICMP_PACKET_SLOTS],
489 vec![0u8; ICMP_BUFFER_BYTES],
490 );
491 let tx = RawPacketBuffer::new(
492 vec![RawPacketMetadata::EMPTY; ICMP_PACKET_SLOTS],
493 vec![0u8; ICMP_BUFFER_BYTES],
494 );
495 RawSocket::new(Some(version), Some(protocol), rx, tx)
496 }
497
498 let v4 = sockets.add(raw_socket(IpVersion::Ipv4, IpProtocol::Icmp));
499 let v6 = sockets.add(raw_socket(IpVersion::Ipv6, IpProtocol::Icmpv6));
500 (v4, v6)
501}
502
503fn drain_icmp_echo(
508 sockets: &mut SocketSet<'_>,
509 handle: SocketHandle,
510 is_ipv6: bool,
511 egress: &EgressPolicy,
512 gateway_addrs: &[IpAddr],
513 to_relay: &SyncSender<icmp_relay::IcmpEcho>,
514) -> bool {
515 let mut echoes = Vec::new();
518 {
519 let socket = sockets.get_mut::<RawSocket>(handle);
520 while socket.can_recv() {
521 let Ok(packet) = socket.recv() else {
522 break;
523 };
524 let parsed = if is_ipv6 {
525 icmp_relay::parse_guest_echo_v6(packet)
526 } else {
527 icmp_relay::parse_guest_echo_v4(packet)
528 };
529 if let Some(echo) = parsed {
530 echoes.push(echo);
531 }
532 }
533 }
534
535 let mut woke = false;
537 let mut local_replies = Vec::new();
538 for echo in echoes {
539 if gateway_addrs.contains(&echo.destination) {
540 local_replies.push(echo);
541 } else if icmp_relay::should_relay_icmp(echo.destination, egress) {
542 match to_relay.try_send(echo) {
543 Ok(()) => woke = true,
544 Err(TrySendError::Full(_)) => {
545 virtio_net_log!("virtio-net: dropping guest ICMP echo (relay queue full)");
546 }
547 Err(TrySendError::Disconnected(_)) => return woke,
548 }
549 }
550 }
552
553 if !local_replies.is_empty() {
555 let socket = sockets.get_mut::<RawSocket>(handle);
556 for reply in local_replies {
557 let frame = if is_ipv6 {
558 icmp_relay::build_echo_reply_v6(&reply)
559 } else {
560 icmp_relay::build_echo_reply_v4(&reply)
561 };
562 if let Some(frame) = frame {
563 let _ = socket.send_slice(&frame);
564 }
565 }
566 }
567 woke
568}
569
570fn deliver_icmp_replies(
574 sockets: &mut SocketSet<'_>,
575 icmp4_handle: SocketHandle,
576 icmp6_handle: SocketHandle,
577 from_relay: &Receiver<icmp_relay::IcmpEcho>,
578) {
579 while let Ok(reply) = from_relay.try_recv() {
580 let (handle, frame) = match reply.guest {
581 IpAddr::V4(_) => (icmp4_handle, icmp_relay::build_echo_reply_v4(&reply)),
582 IpAddr::V6(_) => (icmp6_handle, icmp_relay::build_echo_reply_v6(&reply)),
583 };
584 let Some(frame) = frame else {
585 continue;
586 };
587 let socket = sockets.get_mut::<RawSocket>(handle);
588 if socket.send_slice(&frame).is_err() {
589 virtio_net_log!(
590 "virtio-net: dropping ICMP reply to {} (raw socket buffer full)",
591 reply.guest
592 );
593 }
594 }
595}
596
597fn relay_accepted_tcp_connection(
600 tcp_receiver: &mut Option<Receiver<AcceptedTcpConnection>>,
601 relays: &mut TcpRelayTable,
602 interface: &mut Interface,
603 sockets: &mut SocketSet<'_>,
604 gateway_ipv4: Ipv4Addr,
605 guest_ipv4: Ipv4Addr,
606) {
607 let mut disconnected = false;
617
618 if let Some(receiver) = tcp_receiver.as_mut() {
619 loop {
620 match receiver.try_recv() {
621 Ok(connection) => {
622 let guest_destination =
623 SocketAddr::new(std::net::IpAddr::V4(guest_ipv4), connection.guest_port);
624 virtio_net_log!(
625 "virtio-net: accepted published TCP connection peer={} host_port={} guest_destination={}",
626 connection.peer_addr,
627 connection.host_port,
628 guest_destination
629 );
630 if !relays.create_published_socket(
631 interface,
632 gateway_ipv4,
633 guest_destination,
634 connection.stream,
635 sockets,
636 ) {
637 tracing::warn!(
638 host_port = connection.host_port,
639 guest_port = connection.guest_port,
640 peer_addr = %connection.peer_addr,
641 "dropping published TCP connection because the guest relay path could not be created"
642 );
643 }
644 }
645 Err(TryRecvError::Empty) => break,
646 Err(TryRecvError::Disconnected) => {
647 disconnected = true;
648 break;
649 }
650 }
651 }
652 }
653
654 if disconnected {
655 *tcp_receiver = None;
656 }
657}
658
659fn process_dns_queries(
660 dns_socket_handle: SocketHandle,
661 sockets: &mut SocketSet<'_>,
662 egress: &EgressPolicy,
663) {
664 let upstream_dns = match DEFAULT_DNS_ADDR {
668 std::net::IpAddr::V4(ip) => ip,
669 std::net::IpAddr::V6(_) => return,
670 };
671
672 let socket = sockets.get_mut::<UdpSocket>(dns_socket_handle);
673 while socket.can_recv() {
674 let (query, metadata) = match socket.recv() {
675 Ok(result) => result,
676 Err(_) => break,
677 };
678 virtio_net_log!(
679 "virtio-net: forwarding guest DNS query guest={} local_address={:?} query_len={} upstream_dns={}",
680 metadata.endpoint,
681 metadata.local_address,
682 query.len(),
683 upstream_dns
684 );
685 let response = if egress.dns_filter_active() {
690 match dns::question_name(query) {
691 Some(name) if egress.hostname_allowed(&name) => {
692 match forward_dns_query(upstream_dns, query) {
693 Ok(response) => {
694 egress.learn_ip_records(&dns::answer_ip_records(&response));
695 response
696 }
697 Err(err) => {
698 virtio_net_log!("virtio-net: host DNS forwarding failed error={}", err);
699 continue;
700 }
701 }
702 }
703 Some(name) => {
704 virtio_net_log!(
705 "virtio-net: blocking DNS query by allow-host policy name={}",
706 name
707 );
708 dns::error_response(query, dns::DNS_RCODE_NXDOMAIN)
709 }
710 None => dns::error_response(query, dns::DNS_RCODE_SERVFAIL),
711 }
712 } else {
713 match forward_dns_query(upstream_dns, query) {
714 Ok(response) => response,
715 Err(err) => {
716 virtio_net_log!("virtio-net: host DNS forwarding failed error={}", err);
717 continue;
718 }
719 }
720 };
721 virtio_net_log!(
722 "virtio-net: forwarded DNS response back to guest guest={} response_len={}",
723 metadata.endpoint,
724 response.len()
725 );
726
727 let response_meta = UdpMetadata {
728 endpoint: metadata.endpoint,
729 local_address: metadata.local_address,
730 meta: Default::default(),
731 };
732 let _ = socket.send_slice(&response, response_meta);
733 }
734}
735
736fn forward_dns_query(upstream_dns: Ipv4Addr, query: &[u8]) -> std::io::Result<Vec<u8>> {
737 let socket = HostUdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
745 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
746 let local_addr = socket.local_addr()?;
747 virtio_net_log!(
748 "virtio-net: sending DNS query to upstream resolver local_addr={} upstream_dns={} query_len={}",
749 local_addr,
750 upstream_dns,
751 query.len()
752 );
753 socket.send_to(query, (upstream_dns, DNS_SOCKET_PORT))?;
754
755 let mut buffer = vec![0u8; DNS_BUFFER_BYTES];
756 let (bytes_read, _) = socket.recv_from(&mut buffer)?;
757 buffer.truncate(bytes_read);
758 virtio_net_log!(
759 "virtio-net: received DNS response from upstream resolver upstream_dns={} response_len={}",
760 upstream_dns,
761 buffer.len()
762 );
763 Ok(buffer)
764}
765
766fn flush_interface_egress(
767 interface: &mut Interface,
768 device: &mut VirtioNetworkDevice,
769 sockets: &mut SocketSet<'_>,
770 now: Instant,
771) {
772 loop {
776 let result = interface.poll_egress(now, device, sockets);
777 if matches!(result, PollResult::None) {
778 break;
779 }
780 }
781}
782
783fn wake_guest_if_needed(queues: &NetworkFrameQueues, device: &VirtioNetworkDevice) {
784 if device.frames_emitted.swap(false, Ordering::Relaxed) {
788 queues.host_wake.wake();
789 }
790}
791
792fn smoltcp_now(clock: StdInstant) -> Instant {
793 let elapsed = clock.elapsed();
794 Instant::from_millis(elapsed.as_millis() as i64)
795}
796
797fn classify_guest_frame(frame: &[u8]) -> FrameAction {
798 let ethernet = match EthernetFrame::new_checked(frame) {
799 Ok(frame) => frame,
800 Err(_) => return FrameAction::Passthrough,
801 };
802
803 let (src_ip, dst_ip, protocol, transport): (IpAddr, IpAddr, _, _) = match ethernet.ethertype() {
808 EthernetProtocol::Ipv4 => {
809 let ipv4 = match Ipv4Packet::new_checked(ethernet.payload()) {
810 Ok(packet) => packet,
811 Err(_) => return FrameAction::Passthrough,
812 };
813 (
814 IpAddr::V4(ipv4.src_addr()),
815 IpAddr::V4(ipv4.dst_addr()),
816 ipv4.next_header(),
817 ipv4.payload(),
818 )
819 }
820 EthernetProtocol::Ipv6 => {
821 let ipv6 = match Ipv6Packet::new_checked(ethernet.payload()) {
822 Ok(packet) => packet,
823 Err(_) => return FrameAction::Passthrough,
824 };
825 (
826 IpAddr::V6(ipv6.src_addr()),
827 IpAddr::V6(ipv6.dst_addr()),
828 ipv6.next_header(),
829 ipv6.payload(),
830 )
831 }
832 _ => return FrameAction::Passthrough,
833 };
834
835 match protocol {
836 smoltcp::wire::IpProtocol::Tcp => {
837 let tcp = match TcpPacket::new_checked(transport) {
838 Ok(packet) => packet,
839 Err(_) => return FrameAction::Passthrough,
840 };
841
842 if tcp.syn() && !tcp.ack() {
843 FrameAction::TcpSyn {
844 source: SocketAddr::new(src_ip, tcp.src_port()),
845 destination: SocketAddr::new(dst_ip, tcp.dst_port()),
846 }
847 } else {
848 FrameAction::Passthrough
849 }
850 }
851 smoltcp::wire::IpProtocol::Udp => {
852 let udp = match UdpPacket::new_checked(transport) {
853 Ok(packet) => packet,
854 Err(_) => return FrameAction::Passthrough,
855 };
856
857 if udp.dst_port() == DNS_SOCKET_PORT {
858 FrameAction::DnsQuery
859 } else {
860 FrameAction::UdpFlow {
861 destination: SocketAddr::new(dst_ip, udp.dst_port()),
862 }
863 }
864 }
865 _ => FrameAction::Passthrough,
866 }
867}
868
869#[cfg(feature = "fuzzing")]
875pub fn fuzz_classify_guest_frame(frame: &[u8]) {
876 let _ = classify_guest_frame(frame);
877}