1use std::collections::HashSet;
9use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
10use std::sync::Arc;
11use std::sync::atomic::Ordering;
12
13#[cfg(windows)]
14use msb_krun_utils::event::{EventSet, EventSource, WaitContext, WaitEvent};
15use smoltcp::iface::{Config, Interface, SocketSet};
16use smoltcp::time::Instant;
17
18use smoltcp::wire::{
19 EthernetAddress, EthernetFrame, EthernetProtocol, HardwareAddress, Icmpv4Packet, Icmpv4Repr,
20 Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr, IpProtocol, Ipv4Packet, Ipv4Repr, Ipv6Packet,
21 Ipv6Repr, TcpPacket, UdpPacket,
22};
23
24use crate::config::{DnsConfig, PublishedPort};
25use crate::conn::ConnectionTracker;
26use crate::device::SmoltcpDevice;
27use crate::dns::common::ports::DnsPortType;
28use crate::dns::{
29 interceptor::DnsInterceptor,
30 proxies::{dot::DotProxy, tcp::DnsTcpProxy},
31};
32use crate::icmp_relay::IcmpRelay;
33use crate::policy::{EgressEvaluation, HostnameSource, NetworkPolicy, Protocol};
34use crate::proxy;
35use crate::publisher::PortPublisher;
36use crate::secrets::handle::SecretsHandle;
37use crate::shared::SharedState;
38use crate::tls::{proxy as tls_proxy, state::TlsState};
39use crate::udp_fragments::{
40 Ipv4UdpFragmentReassembler, Ipv6UdpFragmentReassembler, ReassembledUdpDatagram,
41 is_ipv4_udp_fragment, is_ipv6_fragment, is_ipv6_udp_fragment,
42};
43use crate::udp_relay::UdpRelay;
44
45#[cfg(windows)]
50const TX_WAKE_TOKEN: u64 = 1;
51
52#[cfg(windows)]
53const PROXY_WAKE_TOKEN: u64 = 2;
54
55pub enum FrameAction {
66 TcpSyn { src: SocketAddr, dst: SocketAddr },
69
70 UdpRelay { src: SocketAddr, dst: SocketAddr },
73
74 Dns,
76
77 Ipv4UdpFragment,
79
80 Ipv6UdpFragment,
82
83 Ipv6UnsupportedFragment,
85
86 Passthrough,
89}
90
91pub struct PollLoopConfig {
94 pub gateway_mac: [u8; 6],
96 pub guest_mac: [u8; 6],
98 pub gateway: GatewayIps,
102 pub guest_ipv4: Option<Ipv4Addr>,
104 pub guest_ipv6: Option<Ipv6Addr>,
106 pub mtu: usize,
108}
109
110#[derive(Debug, Clone, Copy)]
115pub struct GatewayIps {
116 pub ipv4: Option<Ipv4Addr>,
118 pub ipv6: Option<Ipv6Addr>,
120}
121
122pub fn classify_frame(frame: &[u8]) -> FrameAction {
132 let Ok(eth) = EthernetFrame::new_checked(frame) else {
133 return FrameAction::Passthrough;
134 };
135
136 match eth.ethertype() {
137 EthernetProtocol::Ipv4 => classify_ipv4(eth.payload()),
138 EthernetProtocol::Ipv6 => classify_ipv6(eth.payload()),
139 _ => FrameAction::Passthrough, }
141}
142
143pub fn create_interface(device: &mut SmoltcpDevice, config: &PollLoopConfig) -> Interface {
150 let hw_addr = HardwareAddress::Ethernet(EthernetAddress(config.gateway_mac));
151 let iface_config = Config::new(hw_addr);
152 let mut iface = Interface::new(iface_config, device, smoltcp_now());
153
154 iface.update_ip_addrs(|addrs| {
156 if let Some(ipv4) = config.gateway.ipv4 {
157 addrs
158 .push(IpCidr::new(IpAddress::Ipv4(ipv4), 30)) .expect("failed to add gateway IPv4 address");
160 }
161 if let Some(ipv6) = config.gateway.ipv6 {
162 addrs
163 .push(IpCidr::new(IpAddress::Ipv6(ipv6), 64))
164 .expect("failed to add gateway IPv6 address");
165 }
166 });
167
168 if let Some(ipv4) = config.gateway.ipv4 {
170 iface
171 .routes_mut()
172 .add_default_ipv4_route(ipv4)
173 .expect("failed to add default IPv4 route");
174 }
175 if let Some(ipv6) = config.gateway.ipv6 {
176 iface
177 .routes_mut()
178 .add_default_ipv6_route(ipv6)
179 .expect("failed to add default IPv6 route");
180 }
181
182 iface.set_any_ip(true);
184
185 iface
186}
187
188#[allow(clippy::too_many_arguments)]
219pub fn smoltcp_poll_loop(
220 shared: Arc<SharedState>,
221 config: PollLoopConfig,
222 network_policy: NetworkPolicy,
223 dns_config: DnsConfig,
224 tls_state: Option<Arc<TlsState>>,
225 published_ports: Vec<PublishedPort>,
226 max_connections: Option<usize>,
227 tokio_handle: tokio::runtime::Handle,
228 secrets: SecretsHandle,
229) {
230 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
231 let mut iface = create_interface(&mut device, &config);
232 let mut sockets = SocketSet::new(vec![]);
233 let mut conn_tracker = ConnectionTracker::new(max_connections);
234
235 let gateway_ips: Arc<HashSet<IpAddr>> = Arc::new(
241 config
242 .gateway
243 .ipv4
244 .map(IpAddr::V4)
245 .into_iter()
246 .chain(config.gateway.ipv6.map(IpAddr::V6))
247 .collect(),
248 );
249 shared.set_gateway_ips(config.gateway.ipv4, config.gateway.ipv6);
252 let network_policy = Arc::new(network_policy);
253
254 let (mut dns_interceptor, dns_forwarder_handle) = DnsInterceptor::new(
255 &mut sockets,
256 dns_config,
257 shared.clone(),
258 &tokio_handle,
259 gateway_ips,
260 network_policy.clone(),
261 config.gateway,
262 config.gateway_mac,
263 config.guest_mac,
264 );
265 let mut port_publisher = PortPublisher::new(
266 &published_ports,
267 config.guest_ipv4,
268 config.guest_ipv6,
269 config.gateway.ipv4,
270 config.gateway.ipv6,
271 config.gateway_mac,
272 config.guest_mac,
273 network_policy.clone(),
274 shared.clone(),
275 &tokio_handle,
276 );
277 let mut udp_relay = UdpRelay::new(
278 shared.clone(),
279 config.gateway_mac,
280 config.guest_mac,
281 config.mtu,
282 tokio_handle.clone(),
283 );
284 let mut udp_fragments = Ipv4UdpFragmentReassembler::new();
285 let mut ipv6_udp_fragments = Ipv6UdpFragmentReassembler::new();
286 let icmp_relay = IcmpRelay::new(
287 shared.clone(),
288 config.gateway_mac,
289 config.guest_mac,
290 tokio_handle.clone(),
291 );
292
293 let mut last_cleanup = std::time::Instant::now();
295
296 #[cfg(unix)]
298 let mut poll_fds = [
299 libc::pollfd {
300 fd: shared.tx_wake.as_raw_fd(),
301 events: libc::POLLIN,
302 revents: 0,
303 },
304 libc::pollfd {
305 fd: shared.proxy_wake.as_raw_fd(),
306 events: libc::POLLIN,
307 revents: 0,
308 },
309 ];
310 #[cfg(windows)]
311 let wait_context = match windows_stack_wait_context(&shared) {
312 Ok(context) => context,
313 Err(err) => {
314 tracing::error!(error = %err, "network poll loop: failed to create wait context");
315 return;
316 }
317 };
318
319 loop {
320 let now = smoltcp_now();
321
322 while let Some(frame) = device.stage_next_frame() {
324 if handle_gateway_icmp_echo(frame, &config, &shared) {
325 device.drop_staged_frame();
326 continue;
327 }
328
329 if icmp_relay.relay_outbound_if_echo(frame, &config, &network_policy) {
330 device.drop_staged_frame();
331 continue;
332 }
333
334 match classify_frame(frame) {
335 FrameAction::TcpSyn { src, dst } => {
336 let allow = match DnsPortType::from_tcp(dst.port()) {
337 DnsPortType::Dns => true,
341 DnsPortType::EncryptedDns => {
350 if tls_state.is_some() {
351 true
352 } else {
353 tracing::debug!(%dst, "DoT port refused (TLS interception not configured); stub should fall back to TCP/53");
354 false
355 }
356 }
357 DnsPortType::AlternativeDns => {
363 tracing::debug!(%dst, "alternative-DNS TCP port refused; stub should fall back to TCP/53");
364 false
365 }
366 DnsPortType::Other => match network_policy.evaluate_egress_with_source(
369 dst,
370 Protocol::Tcp,
371 &shared,
372 HostnameSource::Deferred,
373 ) {
374 EgressEvaluation::Allow | EgressEvaluation::DeferUntilHostname => true,
375 EgressEvaluation::Deny => false,
376 },
377 };
378 if allow && !conn_tracker.has_socket_for(&src, &dst) {
379 conn_tracker.create_tcp_socket(src, dst, &mut sockets);
380 }
381 iface.poll_ingress_single(now, &mut device, &mut sockets);
384 }
385
386 FrameAction::UdpRelay { src, dst } => {
387 relay_udp_frame(
388 frame,
389 src,
390 dst,
391 &config,
392 &network_policy,
393 &shared,
394 &mut port_publisher,
395 tls_state.as_deref(),
396 &mut udp_relay,
397 );
398 device.drop_staged_frame();
399 }
400
401 FrameAction::Ipv4UdpFragment => {
402 if let Some(datagram) = udp_fragments.push(frame) {
403 handle_reassembled_udp_datagram(
404 datagram,
405 &mut device,
406 &mut iface,
407 now,
408 &mut sockets,
409 &config,
410 &network_policy,
411 &shared,
412 &mut port_publisher,
413 tls_state.as_deref(),
414 &mut udp_relay,
415 );
416 } else {
417 device.drop_staged_frame();
418 }
419 }
420
421 FrameAction::Ipv6UdpFragment => {
422 if let Some(datagram) = ipv6_udp_fragments.push(frame) {
423 handle_reassembled_udp_datagram(
424 datagram,
425 &mut device,
426 &mut iface,
427 now,
428 &mut sockets,
429 &config,
430 &network_policy,
431 &shared,
432 &mut port_publisher,
433 tls_state.as_deref(),
434 &mut udp_relay,
435 );
436 } else {
437 device.drop_staged_frame();
438 }
439 }
440
441 FrameAction::Ipv6UnsupportedFragment => {
442 device.drop_staged_frame();
446 }
447
448 FrameAction::Dns | FrameAction::Passthrough => {
449 iface.poll_ingress_single(now, &mut device, &mut sockets);
451 }
452 }
453 }
454
455 loop {
459 let result = iface.poll_egress(now, &mut device, &mut sockets);
460 if matches!(result, smoltcp::iface::PollResult::None) {
461 break;
462 }
463 }
464 iface.poll_maintenance(now);
465
466 if device.frames_emitted.swap(false, Ordering::Relaxed) {
469 shared.rx_wake.wake();
470 }
471
472 conn_tracker.relay_data(&mut sockets);
477 dns_interceptor.process(&mut sockets);
478
479 port_publisher.accept_inbound(&mut iface, &mut sockets, &shared, &tokio_handle);
481 port_publisher.relay_data(&mut sockets);
482
483 let new_conns = conn_tracker.take_new_connections(&mut sockets);
485 for conn in new_conns {
486 if let Some(ref tls_state) = tls_state
487 && tls_state
488 .config
489 .intercepted_ports
490 .contains(&conn.dst.port())
491 {
492 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
494 tls_proxy::spawn_tls_proxy(
495 &tokio_handle,
496 conn.dst,
497 connect_dst,
498 conn.from_smoltcp,
499 conn.to_smoltcp,
500 shared.clone(),
501 tls_state.clone(),
502 network_policy.clone(),
503 conn.proxy_connect,
504 );
505 continue;
506 }
507 if conn.dst.port() == 53 {
508 conn.proxy_connect.mark_connected();
515
516 DnsTcpProxy::spawn(
526 &tokio_handle,
527 conn.dst,
528 conn.from_smoltcp,
529 conn.to_smoltcp,
530 dns_forwarder_handle.clone(),
531 shared.clone(),
532 );
533 continue;
534 }
535 if conn.dst.port() == 853
536 && let Some(ref tls_state) = tls_state
537 {
538 conn.proxy_connect.mark_connected();
540
541 DotProxy::spawn(
547 &tokio_handle,
548 conn.dst,
549 conn.from_smoltcp,
550 conn.to_smoltcp,
551 dns_forwarder_handle.clone(),
552 tls_state.clone(),
553 shared.clone(),
554 );
555 continue;
556 }
557 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
559 proxy::spawn_tcp_proxy(
560 &tokio_handle,
561 conn.dst,
562 connect_dst,
563 conn.from_smoltcp,
564 conn.to_smoltcp,
565 shared.clone(),
566 network_policy.clone(),
567 secrets.load(),
570 tls_state.clone(),
571 conn.proxy_connect,
572 );
573 }
574
575 if last_cleanup.elapsed() >= std::time::Duration::from_secs(1) {
578 conn_tracker.cleanup_closed(&mut sockets);
579 port_publisher.cleanup_closed(&mut sockets);
580 udp_relay.cleanup_expired();
581 udp_fragments.cleanup_expired();
582 ipv6_udp_fragments.cleanup_expired();
583 shared.cleanup_resolved_hostnames();
584 last_cleanup = std::time::Instant::now();
585 }
586
587 loop {
590 let result = iface.poll_egress(now, &mut device, &mut sockets);
591 if matches!(result, smoltcp::iface::PollResult::None) {
592 break;
593 }
594 }
595
596 if device.frames_emitted.swap(false, Ordering::Relaxed) {
598 shared.rx_wake.wake();
599 }
600
601 let timeout_ms = iface
602 .poll_delay(now, &sockets)
603 .map(|d| d.total_millis().min(i32::MAX as u64) as i32)
604 .unwrap_or(100); #[cfg(unix)]
607 sleep_until_stack_wake(&shared, timeout_ms, &mut poll_fds);
608 #[cfg(windows)]
609 sleep_until_stack_wake_windows(&shared, timeout_ms, &wait_context);
610 }
611}
612
613#[cfg(unix)]
618fn sleep_until_stack_wake(shared: &SharedState, timeout_ms: i32, poll_fds: &mut [libc::pollfd; 2]) {
619 unsafe {
621 libc::poll(
622 poll_fds.as_mut_ptr(),
623 poll_fds.len() as libc::nfds_t,
624 timeout_ms,
625 );
626 }
627
628 if poll_fds[0].revents & libc::POLLIN != 0 {
629 shared.tx_wake.drain();
630 }
631 if poll_fds[1].revents & libc::POLLIN != 0 {
632 shared.proxy_wake.drain();
633 }
634}
635
636#[cfg(windows)]
637fn windows_stack_wait_context(shared: &SharedState) -> std::io::Result<WaitContext> {
638 let mut context = WaitContext::new();
639 context.add(
640 EventSource::waitable_handle(shared.tx_wake.as_raw_handle(), TX_WAKE_TOKEN),
641 EventSet::IN,
642 )?;
643 context.add(
644 EventSource::waitable_handle(shared.proxy_wake.as_raw_handle(), PROXY_WAKE_TOKEN),
645 EventSet::IN,
646 )?;
647 Ok(context)
648}
649
650#[cfg(windows)]
651fn sleep_until_stack_wake_windows(
652 shared: &SharedState,
653 timeout_ms: i32,
654 wait_context: &WaitContext,
655) {
656 let mut events = [WaitEvent::default(); 2];
657 let count = match wait_context.wait(timeout_ms, &mut events) {
658 Ok(count) => count,
659 Err(err) => {
660 tracing::warn!(error = %err, "network poll loop: wait failed");
661 return;
662 }
663 };
664
665 for event in events.iter().take(count) {
666 match event.token() {
667 TX_WAKE_TOKEN => shared.tx_wake.drain(),
668 PROXY_WAKE_TOKEN => shared.proxy_wake.drain(),
669 token => tracing::warn!(token, "network poll loop: unknown wake token"),
670 }
671 }
672}
673
674#[allow(clippy::too_many_arguments)]
676fn relay_udp_frame(
677 frame: &[u8],
678 src: SocketAddr,
679 dst: SocketAddr,
680 config: &PollLoopConfig,
681 network_policy: &NetworkPolicy,
682 shared: &Arc<SharedState>,
683 port_publisher: &mut PortPublisher,
684 tls_state: Option<&TlsState>,
685 udp_relay: &mut UdpRelay,
686) {
687 if port_publisher.relay_udp_outbound(frame, src, dst) {
688 return;
689 }
690
691 if let Some(tls) = tls_state
693 && tls.config.intercepted_ports.contains(&dst.port())
694 && tls.config.block_quic_on_intercept
695 {
696 return;
697 }
698
699 match DnsPortType::from_udp(dst.port()) {
700 DnsPortType::Dns | DnsPortType::EncryptedDns => return,
703 DnsPortType::AlternativeDns => {
706 tracing::debug!(%dst, "alternative-DNS UDP port dropped; stub should fall back to UDP/53");
707 return;
708 }
709 DnsPortType::Other => {}
710 }
711
712 if network_policy
714 .evaluate_egress(dst, Protocol::Udp, shared)
715 .is_deny()
716 {
717 return;
718 }
719
720 let host_dst = resolve_host_dst(dst, config.gateway);
723 udp_relay.relay_outbound(frame, src, dst, host_dst);
724}
725
726#[allow(clippy::too_many_arguments)]
728fn handle_reassembled_udp_datagram(
729 datagram: ReassembledUdpDatagram,
730 device: &mut SmoltcpDevice,
731 iface: &mut Interface,
732 now: Instant,
733 sockets: &mut SocketSet<'_>,
734 config: &PollLoopConfig,
735 network_policy: &NetworkPolicy,
736 shared: &Arc<SharedState>,
737 port_publisher: &mut PortPublisher,
738 tls_state: Option<&TlsState>,
739 udp_relay: &mut UdpRelay,
740) {
741 if DnsPortType::from_udp(datagram.dst.port()) == DnsPortType::Dns {
742 device.replace_staged_frame(datagram.frame);
743 iface.poll_ingress_single(now, device, sockets);
744 return;
745 }
746
747 relay_udp_frame(
748 &datagram.frame,
749 datagram.src,
750 datagram.dst,
751 config,
752 network_policy,
753 shared,
754 port_publisher,
755 tls_state,
756 udp_relay,
757 );
758 device.drop_staged_frame();
759}
760
761pub(crate) fn resolve_host_dst(dst: SocketAddr, gateway: GatewayIps) -> SocketAddr {
771 match dst.ip() {
772 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => {
773 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst.port())
774 }
775 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => {
776 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), dst.port())
777 }
778 _ => dst,
779 }
780}
781
782fn smoltcp_now() -> Instant {
788 static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
789 let epoch = EPOCH.get_or_init(std::time::Instant::now);
790 let elapsed = epoch.elapsed();
791 Instant::from_millis(elapsed.as_millis() as i64)
792}
793
794fn handle_gateway_icmp_echo(frame: &[u8], config: &PollLoopConfig, shared: &SharedState) -> bool {
801 let Ok(eth) = EthernetFrame::new_checked(frame) else {
802 return false;
803 };
804
805 let reply = match eth.ethertype() {
806 EthernetProtocol::Ipv4 => gateway_icmpv4_echo_reply(ð, config),
807 EthernetProtocol::Ipv6 => gateway_icmpv6_echo_reply(ð, config),
808 _ => None,
809 };
810 let Some(reply) = reply else {
811 return false;
812 };
813
814 shared.push_rx_frame_and_wake(reply);
815
816 true
817}
818
819fn gateway_icmpv4_echo_reply(
821 eth: &EthernetFrame<&[u8]>,
822 config: &PollLoopConfig,
823) -> Option<Vec<u8>> {
824 let gateway_ipv4 = config.gateway.ipv4?;
825 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
826 if ipv4.dst_addr() != gateway_ipv4 || ipv4.next_header() != IpProtocol::Icmp {
827 return None;
828 }
829
830 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).ok()?;
831 let Icmpv4Repr::EchoRequest {
832 ident,
833 seq_no,
834 data,
835 } = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default()).ok()?
836 else {
837 return None;
838 };
839
840 let ipv4_repr = Ipv4Repr {
841 src_addr: gateway_ipv4,
842 dst_addr: ipv4.src_addr(),
843 next_header: IpProtocol::Icmp,
844 payload_len: 8 + data.len(),
845 hop_limit: 64,
846 };
847 let icmp_repr = Icmpv4Repr::EchoReply {
848 ident,
849 seq_no,
850 data,
851 };
852 let mut reply = vec![0u8; 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len()];
853
854 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
855 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
856 reply_eth.set_dst_addr(eth.src_addr());
857 reply_eth.set_ethertype(EthernetProtocol::Ipv4);
858
859 ipv4_repr.emit(
860 &mut Ipv4Packet::new_unchecked(&mut reply[14..34]),
861 &smoltcp::phy::ChecksumCapabilities::default(),
862 );
863 icmp_repr.emit(
864 &mut Icmpv4Packet::new_unchecked(&mut reply[34..]),
865 &smoltcp::phy::ChecksumCapabilities::default(),
866 );
867
868 Some(reply)
869}
870
871fn gateway_icmpv6_echo_reply(
873 eth: &EthernetFrame<&[u8]>,
874 config: &PollLoopConfig,
875) -> Option<Vec<u8>> {
876 let gateway_ipv6 = config.gateway.ipv6?;
877 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
878 if ipv6.dst_addr() != gateway_ipv6 || ipv6.next_header() != IpProtocol::Icmpv6 {
879 return None;
880 }
881
882 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).ok()?;
883 let Icmpv6Repr::EchoRequest {
884 ident,
885 seq_no,
886 data,
887 } = Icmpv6Repr::parse(
888 &ipv6.src_addr(),
889 &ipv6.dst_addr(),
890 &icmp,
891 &smoltcp::phy::ChecksumCapabilities::default(),
892 )
893 .ok()?
894 else {
895 return None;
896 };
897
898 let ipv6_repr = Ipv6Repr {
899 src_addr: gateway_ipv6,
900 dst_addr: ipv6.src_addr(),
901 next_header: IpProtocol::Icmpv6,
902 payload_len: icmp_repr_buffer_len_v6(data),
903 hop_limit: 64,
904 };
905 let icmp_repr = Icmpv6Repr::EchoReply {
906 ident,
907 seq_no,
908 data,
909 };
910 let ipv6_hdr_len = 40;
911 let mut reply = vec![0u8; 14 + ipv6_hdr_len + icmp_repr.buffer_len()];
912
913 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
914 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
915 reply_eth.set_dst_addr(eth.src_addr());
916 reply_eth.set_ethertype(EthernetProtocol::Ipv6);
917
918 ipv6_repr.emit(&mut Ipv6Packet::new_unchecked(&mut reply[14..54]));
919 icmp_repr.emit(
920 &gateway_ipv6,
921 &ipv6.src_addr(),
922 &mut Icmpv6Packet::new_unchecked(&mut reply[54..]),
923 &smoltcp::phy::ChecksumCapabilities::default(),
924 );
925
926 Some(reply)
927}
928
929fn icmp_repr_buffer_len_v6(data: &[u8]) -> usize {
930 Icmpv6Repr::EchoReply {
931 ident: 0,
932 seq_no: 0,
933 data,
934 }
935 .buffer_len()
936}
937
938fn classify_ipv4(payload: &[u8]) -> FrameAction {
940 let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
941 return FrameAction::Passthrough;
942 };
943 if is_ipv4_udp_fragment(&ipv4) {
944 return FrameAction::Ipv4UdpFragment;
945 }
946 classify_transport(
947 ipv4.next_header(),
948 ipv4.src_addr().into(),
949 ipv4.dst_addr().into(),
950 ipv4.payload(),
951 )
952}
953
954fn classify_ipv6(payload: &[u8]) -> FrameAction {
956 let Ok(ipv6) = Ipv6Packet::new_checked(payload) else {
957 return FrameAction::Passthrough;
958 };
959 if is_ipv6_udp_fragment(&ipv6) {
960 return FrameAction::Ipv6UdpFragment;
961 }
962 if is_ipv6_fragment(&ipv6) {
963 return FrameAction::Ipv6UnsupportedFragment;
964 }
965 classify_transport(
966 ipv6.next_header(),
967 ipv6.src_addr().into(),
968 ipv6.dst_addr().into(),
969 ipv6.payload(),
970 )
971}
972
973fn classify_transport(
975 protocol: IpProtocol,
976 src_ip: std::net::IpAddr,
977 dst_ip: std::net::IpAddr,
978 transport_payload: &[u8],
979) -> FrameAction {
980 match protocol {
981 IpProtocol::Tcp => {
982 let Ok(tcp) = TcpPacket::new_checked(transport_payload) else {
983 return FrameAction::Passthrough;
984 };
985 if tcp.syn() && !tcp.ack() {
986 FrameAction::TcpSyn {
987 src: SocketAddr::new(src_ip, tcp.src_port()),
988 dst: SocketAddr::new(dst_ip, tcp.dst_port()),
989 }
990 } else {
991 FrameAction::Passthrough
992 }
993 }
994 IpProtocol::Udp => {
995 let Ok(udp) = UdpPacket::new_checked(transport_payload) else {
996 return FrameAction::Passthrough;
997 };
998 if DnsPortType::from_udp(udp.dst_port()) == DnsPortType::Dns {
1002 FrameAction::Dns
1003 } else {
1004 FrameAction::UdpRelay {
1005 src: SocketAddr::new(src_ip, udp.src_port()),
1006 dst: SocketAddr::new(dst_ip, udp.dst_port()),
1007 }
1008 }
1009 }
1010 _ => FrameAction::Passthrough, }
1012}
1013
1014#[cfg(test)]
1019mod tests {
1020 use super::*;
1021 use std::sync::Arc;
1022
1023 use smoltcp::phy::ChecksumCapabilities;
1024 use smoltcp::wire::{
1025 ArpOperation, ArpPacket, ArpRepr, EthernetRepr, Icmpv4Packet, Icmpv4Repr, Ipv4Repr,
1026 };
1027
1028 use crate::device::SmoltcpDevice;
1029 use crate::shared::SharedState;
1030
1031 fn build_tcp_syn_frame(
1033 src_ip: [u8; 4],
1034 dst_ip: [u8; 4],
1035 src_port: u16,
1036 dst_port: u16,
1037 ) -> Vec<u8> {
1038 let mut frame = vec![0u8; 14 + 20 + 20]; frame[12] = 0x08; frame[13] = 0x00;
1043
1044 let ip = &mut frame[14..34];
1046 ip[0] = 0x45; let total_len = 40u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
1049 ip[6] = 0x40; ip[8] = 64; ip[9] = 6; ip[12..16].copy_from_slice(&src_ip);
1053 ip[16..20].copy_from_slice(&dst_ip);
1054
1055 let tcp = &mut frame[34..54];
1057 tcp[0..2].copy_from_slice(&src_port.to_be_bytes());
1058 tcp[2..4].copy_from_slice(&dst_port.to_be_bytes());
1059 tcp[12] = 0x50; tcp[13] = 0x02; frame
1063 }
1064
1065 fn build_udp_frame(src_ip: [u8; 4], dst_ip: [u8; 4], src_port: u16, dst_port: u16) -> Vec<u8> {
1067 let mut frame = vec![0u8; 14 + 20 + 8]; frame[12] = 0x08;
1071 frame[13] = 0x00;
1072
1073 let ip = &mut frame[14..34];
1075 ip[0] = 0x45;
1076 let total_len = 28u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
1078 ip[8] = 64;
1079 ip[9] = 17; ip[12..16].copy_from_slice(&src_ip);
1081 ip[16..20].copy_from_slice(&dst_ip);
1082
1083 let udp = &mut frame[34..42];
1085 udp[0..2].copy_from_slice(&src_port.to_be_bytes());
1086 udp[2..4].copy_from_slice(&dst_port.to_be_bytes());
1087 let udp_len = 8u16;
1088 udp[4..6].copy_from_slice(&udp_len.to_be_bytes());
1089
1090 frame
1091 }
1092
1093 fn build_icmpv4_echo_frame(
1095 src_mac: [u8; 6],
1096 dst_mac: [u8; 6],
1097 src_ip: [u8; 4],
1098 dst_ip: [u8; 4],
1099 ident: u16,
1100 seq_no: u16,
1101 data: &[u8],
1102 ) -> Vec<u8> {
1103 let ipv4_repr = Ipv4Repr {
1104 src_addr: Ipv4Addr::from(src_ip),
1105 dst_addr: Ipv4Addr::from(dst_ip),
1106 next_header: IpProtocol::Icmp,
1107 payload_len: 8 + data.len(),
1108 hop_limit: 64,
1109 };
1110 let icmp_repr = Icmpv4Repr::EchoRequest {
1111 ident,
1112 seq_no,
1113 data,
1114 };
1115 let frame_len = 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
1116 let mut frame = vec![0u8; frame_len];
1117
1118 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1119 EthernetRepr {
1120 src_addr: EthernetAddress(src_mac),
1121 dst_addr: EthernetAddress(dst_mac),
1122 ethertype: EthernetProtocol::Ipv4,
1123 }
1124 .emit(&mut eth_frame);
1125
1126 ipv4_repr.emit(
1127 &mut Ipv4Packet::new_unchecked(&mut frame[14..34]),
1128 &ChecksumCapabilities::default(),
1129 );
1130 icmp_repr.emit(
1131 &mut Icmpv4Packet::new_unchecked(&mut frame[34..]),
1132 &ChecksumCapabilities::default(),
1133 );
1134
1135 frame
1136 }
1137
1138 fn build_arp_request_frame(src_mac: [u8; 6], src_ip: [u8; 4], target_ip: [u8; 4]) -> Vec<u8> {
1140 let mut frame = vec![0u8; 14 + 28];
1141
1142 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1143 EthernetRepr {
1144 src_addr: EthernetAddress(src_mac),
1145 dst_addr: EthernetAddress([0xff; 6]),
1146 ethertype: EthernetProtocol::Arp,
1147 }
1148 .emit(&mut eth_frame);
1149
1150 ArpRepr::EthernetIpv4 {
1151 operation: ArpOperation::Request,
1152 source_hardware_addr: EthernetAddress(src_mac),
1153 source_protocol_addr: Ipv4Addr::from(src_ip),
1154 target_hardware_addr: EthernetAddress([0x00; 6]),
1155 target_protocol_addr: Ipv4Addr::from(target_ip),
1156 }
1157 .emit(&mut ArpPacket::new_unchecked(&mut frame[14..]));
1158
1159 frame
1160 }
1161
1162 #[test]
1163 fn classify_tcp_syn() {
1164 let frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1165 match classify_frame(&frame) {
1166 FrameAction::TcpSyn { src, dst } => {
1167 assert_eq!(
1168 src,
1169 SocketAddr::new(Ipv4Addr::new(10, 0, 0, 2).into(), 54321)
1170 );
1171 assert_eq!(
1172 dst,
1173 SocketAddr::new(Ipv4Addr::new(93, 184, 216, 34).into(), 443)
1174 );
1175 }
1176 _ => panic!("expected TcpSyn"),
1177 }
1178 }
1179
1180 #[test]
1181 fn classify_tcp_ack_is_passthrough() {
1182 let mut frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1183 frame[34 + 13] = 0x10; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1186 }
1187
1188 #[test]
1189 fn classify_udp_dns() {
1190 let frame = build_udp_frame([10, 0, 0, 2], [10, 0, 0, 1], 12345, 53);
1191 assert!(matches!(classify_frame(&frame), FrameAction::Dns));
1192 }
1193
1194 #[test]
1195 fn classify_udp_non_dns() {
1196 let frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1197 match classify_frame(&frame) {
1198 FrameAction::UdpRelay { src, dst } => {
1199 assert_eq!(src.port(), 12345);
1200 assert_eq!(dst.port(), 443);
1201 }
1202 _ => panic!("expected UdpRelay"),
1203 }
1204 }
1205
1206 #[test]
1207 fn classify_ipv4_udp_fragment() {
1208 let mut frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1209 frame[14 + 6] = 0x20; assert!(matches!(
1211 classify_frame(&frame),
1212 FrameAction::Ipv4UdpFragment
1213 ));
1214 }
1215
1216 #[test]
1217 fn classify_ipv6_udp_fragment() {
1218 let mut frame = vec![0u8; 14 + 40 + 8];
1219
1220 frame[12] = 0x86;
1221 frame[13] = 0xdd;
1222
1223 let ip = &mut frame[14..54];
1224 ip[0] = 0x60;
1225 ip[4..6].copy_from_slice(&8u16.to_be_bytes());
1226 ip[6] = u8::from(IpProtocol::Ipv6Frag);
1227 ip[7] = 64;
1228 ip[8..24].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1229 ip[24..40].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1230
1231 let fragment = &mut frame[54..62];
1232 fragment[0] = u8::from(IpProtocol::Udp);
1233 fragment[3] = 1; assert!(matches!(
1236 classify_frame(&frame),
1237 FrameAction::Ipv6UdpFragment
1238 ));
1239 }
1240
1241 #[test]
1242 fn classify_ipv6_non_udp_fragment_is_unsupported() {
1243 let mut frame = vec![0u8; 14 + 40 + 8];
1244
1245 frame[12] = 0x86;
1246 frame[13] = 0xdd;
1247
1248 let ip = &mut frame[14..54];
1249 ip[0] = 0x60;
1250 ip[4..6].copy_from_slice(&8u16.to_be_bytes());
1251 ip[6] = u8::from(IpProtocol::Ipv6Frag);
1252 ip[7] = 64;
1253 ip[8..24].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1254 ip[24..40].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1255
1256 let fragment = &mut frame[54..62];
1257 fragment[0] = u8::from(IpProtocol::Tcp);
1258 fragment[3] = 1; assert!(matches!(
1261 classify_frame(&frame),
1262 FrameAction::Ipv6UnsupportedFragment
1263 ));
1264 }
1265
1266 #[test]
1267 fn classify_arp_is_passthrough() {
1268 let mut frame = vec![0u8; 42]; frame[12] = 0x08;
1270 frame[13] = 0x06; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1272 }
1273
1274 #[test]
1275 fn classify_garbage_is_passthrough() {
1276 assert!(matches!(classify_frame(&[]), FrameAction::Passthrough));
1277 assert!(matches!(classify_frame(&[0; 5]), FrameAction::Passthrough));
1278 }
1279
1280 #[test]
1281 fn gateway_replies_to_icmp_echo_requests() {
1282 fn drive_one_frame(
1283 device: &mut SmoltcpDevice,
1284 iface: &mut Interface,
1285 sockets: &mut SocketSet<'_>,
1286 shared: &Arc<SharedState>,
1287 poll_config: &PollLoopConfig,
1288 now: Instant,
1289 ) {
1290 let frame = device.stage_next_frame().expect("expected staged frame");
1291 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1292 device.drop_staged_frame();
1293 return;
1294 }
1295 let _ = iface.poll_ingress_single(now, device, sockets);
1296 let _ = iface.poll_egress(now, device, sockets);
1297 }
1298
1299 let shared = Arc::new(SharedState::new(4));
1300 let poll_config = PollLoopConfig {
1301 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1302 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1303 gateway: GatewayIps {
1304 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1305 ipv6: Some(Ipv6Addr::LOCALHOST),
1306 },
1307 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1308 guest_ipv6: None,
1309 mtu: 1500,
1310 };
1311 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1312 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1313 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1314 let mut iface = create_interface(&mut device, &poll_config);
1315 let mut sockets = SocketSet::new(vec![]);
1316 let now = smoltcp_now();
1317
1318 shared
1321 .tx_ring
1322 .push(build_arp_request_frame(
1323 poll_config.guest_mac,
1324 guest_ipv4.octets(),
1325 gateway_ipv4.octets(),
1326 ))
1327 .unwrap();
1328 shared
1329 .tx_ring
1330 .push(build_icmpv4_echo_frame(
1331 poll_config.guest_mac,
1332 poll_config.gateway_mac,
1333 guest_ipv4.octets(),
1334 gateway_ipv4.octets(),
1335 0x1234,
1336 0xABCD,
1337 b"ping",
1338 ))
1339 .unwrap();
1340
1341 drive_one_frame(
1342 &mut device,
1343 &mut iface,
1344 &mut sockets,
1345 &shared,
1346 &poll_config,
1347 now,
1348 );
1349 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1350
1351 drive_one_frame(
1352 &mut device,
1353 &mut iface,
1354 &mut sockets,
1355 &shared,
1356 &poll_config,
1357 now,
1358 );
1359
1360 let reply = shared.rx_ring.pop().expect("expected ICMP echo reply");
1361 let eth = EthernetFrame::new_checked(&reply).expect("valid ethernet frame");
1362 assert_eq!(eth.src_addr(), EthernetAddress(poll_config.gateway_mac));
1363 assert_eq!(eth.dst_addr(), EthernetAddress(poll_config.guest_mac));
1364 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1365
1366 let ipv4 = Ipv4Packet::new_checked(eth.payload()).expect("valid IPv4 packet");
1367 assert_eq!(ipv4.src_addr(), gateway_ipv4);
1368 assert_eq!(ipv4.dst_addr(), guest_ipv4);
1369 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
1370
1371 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).expect("valid ICMP packet");
1372 let icmp_repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default())
1373 .expect("valid ICMP echo reply");
1374 assert_eq!(
1375 icmp_repr,
1376 Icmpv4Repr::EchoReply {
1377 ident: 0x1234,
1378 seq_no: 0xABCD,
1379 data: b"ping",
1380 }
1381 );
1382 }
1383
1384 fn test_gateway() -> GatewayIps {
1385 GatewayIps {
1386 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1387 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1388 }
1389 }
1390
1391 #[test]
1392 fn resolve_host_dst_matches_ipv4() {
1393 let gw = test_gateway();
1394 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1395 assert_eq!(
1396 resolve_host_dst(dst, gw),
1397 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
1398 );
1399 }
1400
1401 #[test]
1402 fn resolve_host_dst_matches_ipv6() {
1403 let gw = test_gateway();
1404 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1405 assert_eq!(
1406 resolve_host_dst(dst, gw),
1407 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
1408 );
1409 }
1410
1411 #[test]
1412 fn resolve_host_dst_passes_through_when_family_absent() {
1413 let gw = GatewayIps {
1414 ipv4: None,
1415 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1416 };
1417 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 1)), 8080);
1419 assert_eq!(resolve_host_dst(dst, gw), dst);
1420 }
1421
1422 #[test]
1423 fn resolve_host_dst_passes_through_non_gateway() {
1424 let gw = test_gateway();
1425 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1426 assert_eq!(resolve_host_dst(dst, gw), dst);
1427 }
1428
1429 #[test]
1430 fn external_icmp_echo_requests_are_not_answered_locally() {
1431 fn drive_one_frame(
1432 device: &mut SmoltcpDevice,
1433 iface: &mut Interface,
1434 sockets: &mut SocketSet<'_>,
1435 shared: &Arc<SharedState>,
1436 poll_config: &PollLoopConfig,
1437 now: Instant,
1438 ) {
1439 let frame = device.stage_next_frame().expect("expected staged frame");
1440 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1441 device.drop_staged_frame();
1442 return;
1443 }
1444 let _ = iface.poll_ingress_single(now, device, sockets);
1445 let _ = iface.poll_egress(now, device, sockets);
1446 }
1447
1448 let shared = Arc::new(SharedState::new(4));
1449 let poll_config = PollLoopConfig {
1450 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1451 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1452 gateway: GatewayIps {
1453 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1454 ipv6: Some(Ipv6Addr::LOCALHOST),
1455 },
1456 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1457 guest_ipv6: None,
1458 mtu: 1500,
1459 };
1460 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1461 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1462 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1463 let mut iface = create_interface(&mut device, &poll_config);
1464 let mut sockets = SocketSet::new(vec![]);
1465 let now = smoltcp_now();
1466
1467 shared
1468 .tx_ring
1469 .push(build_arp_request_frame(
1470 poll_config.guest_mac,
1471 guest_ipv4.octets(),
1472 gateway_ipv4.octets(),
1473 ))
1474 .unwrap();
1475 shared
1476 .tx_ring
1477 .push(build_icmpv4_echo_frame(
1478 poll_config.guest_mac,
1479 poll_config.gateway_mac,
1480 guest_ipv4.octets(),
1481 [142, 251, 216, 46],
1482 0x1234,
1483 0xABCD,
1484 b"ping",
1485 ))
1486 .unwrap();
1487
1488 drive_one_frame(
1489 &mut device,
1490 &mut iface,
1491 &mut sockets,
1492 &shared,
1493 &poll_config,
1494 now,
1495 );
1496 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1497
1498 drive_one_frame(
1499 &mut device,
1500 &mut iface,
1501 &mut sockets,
1502 &shared,
1503 &poll_config,
1504 now,
1505 );
1506 assert!(
1507 shared.rx_ring.pop().is_none(),
1508 "external ICMP should not be answered locally"
1509 );
1510 }
1511}