1use crate::device::VirtioNetworkDevice;
54use crate::dns;
55use crate::egress::EgressPolicy;
56use crate::queues::NetworkFrameQueues;
57use crate::tcp_listeners::AcceptedTcpConnection;
58use crate::tcp_relay::{spawn_tcp_relay, TcpRelayTable};
59use crate::{virtio_net_log, DEFAULT_DNS_ADDR};
60use smoltcp::iface::{
61 Config, Interface, PollIngressSingleResult, PollResult, SocketHandle, SocketSet,
62};
63use smoltcp::socket::udp::{PacketBuffer, PacketMetadata, Socket as UdpSocket, UdpMetadata};
64use smoltcp::time::Instant;
65use smoltcp::wire::{
66 EthernetAddress, EthernetFrame, EthernetProtocol, HardwareAddress, IpAddress, IpCidr,
67 Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket,
68};
69use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket as HostUdpSocket};
70use std::sync::atomic::Ordering;
71use std::sync::mpsc::{Receiver, TryRecvError};
72use std::sync::Arc;
73use std::thread::{self, JoinHandle};
74use std::time::{Duration, Instant as StdInstant};
75
76const DNS_SOCKET_PORT: u16 = 53;
77const DNS_PACKET_SLOTS: usize = 8;
78const DNS_BUFFER_BYTES: usize = 2048;
79const DEFAULT_IDLE_TIMEOUT_MS: i32 = 100;
80
81#[derive(Debug, Clone, Copy)]
87pub struct VirtioPollConfig {
88 pub gateway_mac: [u8; 6],
90 pub guest_mac: [u8; 6],
92 pub gateway_ipv4: Ipv4Addr,
94 pub guest_ipv4: Ipv4Addr,
96 pub gateway_ipv6: Ipv6Addr,
98 pub guest_ipv6: Ipv6Addr,
100 pub prefix_len6: u8,
102 pub mtu: usize,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107enum FrameAction {
108 TcpSyn {
109 source: SocketAddr,
110 destination: SocketAddr,
111 },
112 DnsQuery,
113 UnsupportedUdp,
114 Passthrough,
115}
116
117pub fn start_network_stack(
127 queues: Arc<NetworkFrameQueues>,
128 config: VirtioPollConfig,
129 tcp_receiver: Option<Receiver<AcceptedTcpConnection>>,
130 egress: EgressPolicy,
131) -> std::io::Result<JoinHandle<()>> {
132 virtio_net_log!(
133 "virtio-net: spawning poll thread guest_ip={} gateway_ip={} mtu={}",
134 config.guest_ipv4,
135 config.gateway_ipv4,
136 config.mtu
137 );
138 thread::Builder::new()
139 .name("smolvm-net-poll".into())
140 .spawn(move || run_network_stack(queues, config, tcp_receiver, egress))
141}
142
143fn run_network_stack(
144 queues: Arc<NetworkFrameQueues>,
145 config: VirtioPollConfig,
146 mut tcp_receiver: Option<Receiver<AcceptedTcpConnection>>,
147 egress: EgressPolicy,
148) {
149 virtio_net_log!(
163 "virtio-net: poll loop started guest_ip={} gateway_ip={}",
164 config.guest_ipv4,
165 config.gateway_ipv4
166 );
167 let clock = StdInstant::now();
168 let mut device = VirtioNetworkDevice::new(queues.clone(), config.mtu);
169 let mut interface = create_interface(&mut device, &config);
170 let mut sockets = SocketSet::new(vec![]);
171 let dns_socket_handle = add_dns_socket(&mut sockets);
172 let relay_wake = Arc::new(queues.relay_wake.clone());
173 let mut relays = TcpRelayTable::new(None, egress.clone());
174
175 let mut poll_fds = [
179 libc::pollfd {
180 fd: queues.guest_wake.as_raw_fd(),
181 events: libc::POLLIN,
182 revents: 0,
183 },
184 libc::pollfd {
185 fd: queues.relay_wake.as_raw_fd(),
186 events: libc::POLLIN,
187 revents: 0,
188 },
189 ];
190
191 loop {
192 if queues.is_shutting_down() {
193 return;
194 }
195 let now = smoltcp_now(clock);
196
197 while let Some(frame) = device.stage_next_frame() {
198 match classify_guest_frame(frame) {
204 FrameAction::TcpSyn {
205 source,
206 destination,
207 } => {
208 virtio_net_log!(
209 "virtio-net: guest TCP SYN source={} destination={}",
210 source,
211 destination
212 );
213 if !relays.has_socket_for(&source, &destination) {
214 relays.create_tcp_socket(source, destination, &mut sockets);
215 }
216 if matches!(
217 interface.poll_ingress_single(now, &mut device, &mut sockets),
218 PollIngressSingleResult::None
219 ) {
220 device.drop_staged_frame();
221 }
222 }
223 FrameAction::DnsQuery | FrameAction::Passthrough => {
224 if matches!(
225 interface.poll_ingress_single(now, &mut device, &mut sockets),
226 PollIngressSingleResult::None
227 ) {
228 device.drop_staged_frame();
229 }
230 }
231 FrameAction::UnsupportedUdp => {
232 virtio_net_log!("virtio-net: dropping unsupported guest UDP datagram");
235 device.drop_staged_frame();
236 }
237 }
238 }
239
240 relay_accepted_tcp_connection(
241 &mut tcp_receiver,
242 &mut relays,
243 &mut interface,
244 &mut sockets,
245 config.gateway_ipv4,
246 config.guest_ipv4,
247 );
248
249 flush_interface_egress(&mut interface, &mut device, &mut sockets, now);
252 interface.poll_maintenance(now);
253 wake_guest_if_needed(&queues, &device);
254
255 relays.relay_data(&mut sockets);
258 process_dns_queries(dns_socket_handle, &mut sockets, &egress);
259
260 for connection in relays.take_new_connections(&mut sockets) {
263 spawn_tcp_relay(
264 connection.destination,
265 connection.relay_target,
266 connection.from_smoltcp,
267 connection.to_smoltcp,
268 relay_wake.clone(),
269 connection.exit_state,
270 );
271 }
272
273 relays.cleanup_closed(&mut sockets);
274
275 flush_interface_egress(&mut interface, &mut device, &mut sockets, now);
278 wake_guest_if_needed(&queues, &device);
279
280 let timeout_ms = interface
281 .poll_delay(now, &sockets)
282 .map(|duration| duration.total_millis().min(i32::MAX as u64) as i32)
283 .unwrap_or(DEFAULT_IDLE_TIMEOUT_MS);
284
285 unsafe {
287 libc::poll(
288 poll_fds.as_mut_ptr(),
289 poll_fds.len() as libc::nfds_t,
290 timeout_ms,
291 );
292 }
293
294 if poll_fds[0].revents & libc::POLLIN != 0 {
295 queues.guest_wake.drain();
296 }
297 if poll_fds[1].revents & libc::POLLIN != 0 {
298 queues.relay_wake.drain();
299 }
300 }
301}
302
303fn create_interface(device: &mut VirtioNetworkDevice, config: &VirtioPollConfig) -> Interface {
304 let mut interface = Interface::new(
314 Config::new(HardwareAddress::Ethernet(EthernetAddress(
315 config.gateway_mac,
316 ))),
317 device,
318 Instant::ZERO,
319 );
320 interface.update_ip_addrs(|addresses| {
321 addresses
322 .push(IpCidr::new(IpAddress::Ipv4(config.gateway_ipv4), 30))
323 .expect("failed to add gateway IPv4 address");
324 addresses
325 .push(IpCidr::new(
326 IpAddress::Ipv6(config.gateway_ipv6),
327 config.prefix_len6,
328 ))
329 .expect("failed to add gateway IPv6 address");
330 addresses
334 .push(IpCidr::new(
335 IpAddress::Ipv6(link_local_from_mac(config.gateway_mac)),
336 64,
337 ))
338 .expect("failed to add gateway IPv6 link-local address");
339 });
340 interface
344 .routes_mut()
345 .add_default_ipv4_route(config.gateway_ipv4)
346 .expect("failed to add default IPv4 route");
347 interface
348 .routes_mut()
349 .add_default_ipv6_route(config.gateway_ipv6)
350 .expect("failed to add default IPv6 route");
351 interface.set_any_ip(true);
352 interface
353}
354
355fn link_local_from_mac(mac: [u8; 6]) -> Ipv6Addr {
358 Ipv6Addr::new(
359 0xfe80,
360 0,
361 0,
362 0,
363 u16::from_be_bytes([mac[0] ^ 0x02, mac[1]]),
364 u16::from_be_bytes([mac[2], 0xff]),
365 u16::from_be_bytes([0xfe, mac[3]]),
366 u16::from_be_bytes([mac[4], mac[5]]),
367 )
368}
369
370fn add_dns_socket(sockets: &mut SocketSet<'_>) -> SocketHandle {
381 let rx_meta = vec![PacketMetadata::EMPTY; DNS_PACKET_SLOTS];
382 let tx_meta = vec![PacketMetadata::EMPTY; DNS_PACKET_SLOTS];
383 let rx_buffer = PacketBuffer::new(rx_meta, vec![0u8; DNS_BUFFER_BYTES]);
384 let tx_buffer = PacketBuffer::new(tx_meta, vec![0u8; DNS_BUFFER_BYTES]);
385 let mut socket = UdpSocket::new(rx_buffer, tx_buffer);
386 socket
387 .bind(smoltcp::wire::IpListenEndpoint {
388 addr: None,
389 port: DNS_SOCKET_PORT,
390 })
391 .expect("failed to bind gateway DNS socket");
392 sockets.add(socket)
393}
394
395fn relay_accepted_tcp_connection(
398 tcp_receiver: &mut Option<Receiver<AcceptedTcpConnection>>,
399 relays: &mut TcpRelayTable,
400 interface: &mut Interface,
401 sockets: &mut SocketSet<'_>,
402 gateway_ipv4: Ipv4Addr,
403 guest_ipv4: Ipv4Addr,
404) {
405 let mut disconnected = false;
415
416 if let Some(receiver) = tcp_receiver.as_mut() {
417 loop {
418 match receiver.try_recv() {
419 Ok(connection) => {
420 let guest_destination =
421 SocketAddr::new(std::net::IpAddr::V4(guest_ipv4), connection.guest_port);
422 virtio_net_log!(
423 "virtio-net: accepted published TCP connection peer={} host_port={} guest_destination={}",
424 connection.peer_addr,
425 connection.host_port,
426 guest_destination
427 );
428 if !relays.create_published_socket(
429 interface,
430 gateway_ipv4,
431 guest_destination,
432 connection.stream,
433 sockets,
434 ) {
435 tracing::warn!(
436 host_port = connection.host_port,
437 guest_port = connection.guest_port,
438 peer_addr = %connection.peer_addr,
439 "dropping published TCP connection because the guest relay path could not be created"
440 );
441 }
442 }
443 Err(TryRecvError::Empty) => break,
444 Err(TryRecvError::Disconnected) => {
445 disconnected = true;
446 break;
447 }
448 }
449 }
450 }
451
452 if disconnected {
453 *tcp_receiver = None;
454 }
455}
456
457fn process_dns_queries(
458 dns_socket_handle: SocketHandle,
459 sockets: &mut SocketSet<'_>,
460 egress: &EgressPolicy,
461) {
462 let upstream_dns = match DEFAULT_DNS_ADDR {
466 std::net::IpAddr::V4(ip) => ip,
467 std::net::IpAddr::V6(_) => return,
468 };
469
470 let socket = sockets.get_mut::<UdpSocket>(dns_socket_handle);
471 while socket.can_recv() {
472 let (query, metadata) = match socket.recv() {
473 Ok(result) => result,
474 Err(_) => break,
475 };
476 virtio_net_log!(
477 "virtio-net: forwarding guest DNS query guest={} local_address={:?} query_len={} upstream_dns={}",
478 metadata.endpoint,
479 metadata.local_address,
480 query.len(),
481 upstream_dns
482 );
483 let response = if egress.dns_filter_active() {
488 match dns::question_name(query) {
489 Some(name) if egress.hostname_allowed(&name) => {
490 match forward_dns_query(upstream_dns, query) {
491 Ok(response) => {
492 egress.learn_ip_records(&dns::answer_ip_records(&response));
493 response
494 }
495 Err(err) => {
496 virtio_net_log!("virtio-net: host DNS forwarding failed error={}", err);
497 continue;
498 }
499 }
500 }
501 Some(name) => {
502 virtio_net_log!(
503 "virtio-net: blocking DNS query by allow-host policy name={}",
504 name
505 );
506 dns::error_response(query, dns::DNS_RCODE_NXDOMAIN)
507 }
508 None => dns::error_response(query, dns::DNS_RCODE_SERVFAIL),
509 }
510 } else {
511 match forward_dns_query(upstream_dns, query) {
512 Ok(response) => response,
513 Err(err) => {
514 virtio_net_log!("virtio-net: host DNS forwarding failed error={}", err);
515 continue;
516 }
517 }
518 };
519 virtio_net_log!(
520 "virtio-net: forwarded DNS response back to guest guest={} response_len={}",
521 metadata.endpoint,
522 response.len()
523 );
524
525 let response_meta = UdpMetadata {
526 endpoint: metadata.endpoint,
527 local_address: metadata.local_address,
528 meta: Default::default(),
529 };
530 let _ = socket.send_slice(&response, response_meta);
531 }
532}
533
534fn forward_dns_query(upstream_dns: Ipv4Addr, query: &[u8]) -> std::io::Result<Vec<u8>> {
535 let socket = HostUdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
543 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
544 let local_addr = socket.local_addr()?;
545 virtio_net_log!(
546 "virtio-net: sending DNS query to upstream resolver local_addr={} upstream_dns={} query_len={}",
547 local_addr,
548 upstream_dns,
549 query.len()
550 );
551 socket.send_to(query, (upstream_dns, DNS_SOCKET_PORT))?;
552
553 let mut buffer = vec![0u8; DNS_BUFFER_BYTES];
554 let (bytes_read, _) = socket.recv_from(&mut buffer)?;
555 buffer.truncate(bytes_read);
556 virtio_net_log!(
557 "virtio-net: received DNS response from upstream resolver upstream_dns={} response_len={}",
558 upstream_dns,
559 buffer.len()
560 );
561 Ok(buffer)
562}
563
564fn flush_interface_egress(
565 interface: &mut Interface,
566 device: &mut VirtioNetworkDevice,
567 sockets: &mut SocketSet<'_>,
568 now: Instant,
569) {
570 loop {
574 let result = interface.poll_egress(now, device, sockets);
575 if matches!(result, PollResult::None) {
576 break;
577 }
578 }
579}
580
581fn wake_guest_if_needed(queues: &NetworkFrameQueues, device: &VirtioNetworkDevice) {
582 if device.frames_emitted.swap(false, Ordering::Relaxed) {
586 queues.host_wake.wake();
587 }
588}
589
590fn smoltcp_now(clock: StdInstant) -> Instant {
591 let elapsed = clock.elapsed();
592 Instant::from_millis(elapsed.as_millis() as i64)
593}
594
595fn classify_guest_frame(frame: &[u8]) -> FrameAction {
596 let ethernet = match EthernetFrame::new_checked(frame) {
597 Ok(frame) => frame,
598 Err(_) => return FrameAction::Passthrough,
599 };
600
601 let (src_ip, dst_ip, protocol, transport): (IpAddr, IpAddr, _, _) = match ethernet.ethertype() {
606 EthernetProtocol::Ipv4 => {
607 let ipv4 = match Ipv4Packet::new_checked(ethernet.payload()) {
608 Ok(packet) => packet,
609 Err(_) => return FrameAction::Passthrough,
610 };
611 (
612 IpAddr::V4(ipv4.src_addr()),
613 IpAddr::V4(ipv4.dst_addr()),
614 ipv4.next_header(),
615 ipv4.payload(),
616 )
617 }
618 EthernetProtocol::Ipv6 => {
619 let ipv6 = match Ipv6Packet::new_checked(ethernet.payload()) {
620 Ok(packet) => packet,
621 Err(_) => return FrameAction::Passthrough,
622 };
623 (
624 IpAddr::V6(ipv6.src_addr()),
625 IpAddr::V6(ipv6.dst_addr()),
626 ipv6.next_header(),
627 ipv6.payload(),
628 )
629 }
630 _ => return FrameAction::Passthrough,
631 };
632
633 match protocol {
634 smoltcp::wire::IpProtocol::Tcp => {
635 let tcp = match TcpPacket::new_checked(transport) {
636 Ok(packet) => packet,
637 Err(_) => return FrameAction::Passthrough,
638 };
639
640 if tcp.syn() && !tcp.ack() {
641 FrameAction::TcpSyn {
642 source: SocketAddr::new(src_ip, tcp.src_port()),
643 destination: SocketAddr::new(dst_ip, tcp.dst_port()),
644 }
645 } else {
646 FrameAction::Passthrough
647 }
648 }
649 smoltcp::wire::IpProtocol::Udp => {
650 let udp = match UdpPacket::new_checked(transport) {
651 Ok(packet) => packet,
652 Err(_) => return FrameAction::Passthrough,
653 };
654
655 if udp.dst_port() == DNS_SOCKET_PORT {
656 FrameAction::DnsQuery
657 } else {
658 FrameAction::UnsupportedUdp
659 }
660 }
661 _ => FrameAction::Passthrough,
662 }
663}