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
91struct GatewayIcmpReply {
93 dst: IpAddr,
94 protocol: Protocol,
95 frame: Vec<u8>,
96}
97
98pub struct PollLoopConfig {
101 pub gateway_mac: [u8; 6],
103 pub guest_mac: [u8; 6],
105 pub gateway: GatewayIps,
109 pub guest_ipv4: Option<Ipv4Addr>,
111 pub guest_ipv6: Option<Ipv6Addr>,
113 pub mtu: usize,
115}
116
117#[derive(Debug, Clone, Copy)]
122pub struct GatewayIps {
123 pub ipv4: Option<Ipv4Addr>,
125 pub ipv6: Option<Ipv6Addr>,
127}
128
129pub fn classify_frame(frame: &[u8]) -> FrameAction {
139 let Ok(eth) = EthernetFrame::new_checked(frame) else {
140 return FrameAction::Passthrough;
141 };
142
143 match eth.ethertype() {
144 EthernetProtocol::Ipv4 => classify_ipv4(eth.payload()),
145 EthernetProtocol::Ipv6 => classify_ipv6(eth.payload()),
146 _ => FrameAction::Passthrough, }
148}
149
150pub fn create_interface(device: &mut SmoltcpDevice, config: &PollLoopConfig) -> Interface {
157 let hw_addr = HardwareAddress::Ethernet(EthernetAddress(config.gateway_mac));
158 let iface_config = Config::new(hw_addr);
159 let mut iface = Interface::new(iface_config, device, smoltcp_now());
160
161 iface.update_ip_addrs(|addrs| {
163 if let Some(ipv4) = config.gateway.ipv4 {
164 addrs
165 .push(IpCidr::new(IpAddress::Ipv4(ipv4), 30)) .expect("failed to add gateway IPv4 address");
167 }
168 if let Some(ipv6) = config.gateway.ipv6 {
169 addrs
170 .push(IpCidr::new(IpAddress::Ipv6(ipv6), 64))
171 .expect("failed to add gateway IPv6 address");
172 }
173 });
174
175 if let Some(ipv4) = config.gateway.ipv4 {
177 iface
178 .routes_mut()
179 .add_default_ipv4_route(ipv4)
180 .expect("failed to add default IPv4 route");
181 }
182 if let Some(ipv6) = config.gateway.ipv6 {
183 iface
184 .routes_mut()
185 .add_default_ipv6_route(ipv6)
186 .expect("failed to add default IPv6 route");
187 }
188
189 iface.set_any_ip(true);
191
192 iface
193}
194
195#[allow(clippy::too_many_arguments)]
227pub fn smoltcp_poll_loop(
228 shared: Arc<SharedState>,
229 config: PollLoopConfig,
230 network_policy: NetworkPolicy,
231 platform_policy: Option<NetworkPolicy>,
232 dns_config: DnsConfig,
233 tls_state: Option<Arc<TlsState>>,
234 published_ports: Vec<PublishedPort>,
235 max_connections: Option<usize>,
236 tokio_handle: tokio::runtime::Handle,
237 secrets: SecretsHandle,
238) {
239 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
240 let mut iface = create_interface(&mut device, &config);
241 let mut sockets = SocketSet::new(vec![]);
242 let mut conn_tracker = ConnectionTracker::new(max_connections);
243
244 let gateway_ips: Arc<HashSet<IpAddr>> = Arc::new(
250 config
251 .gateway
252 .ipv4
253 .map(IpAddr::V4)
254 .into_iter()
255 .chain(config.gateway.ipv6.map(IpAddr::V6))
256 .collect(),
257 );
258 shared.set_gateway_ips(config.gateway.ipv4, config.gateway.ipv6);
261 let network_policy = Arc::new(network_policy);
262 let platform_policy = platform_policy.map(Arc::new);
263
264 let (mut dns_interceptor, dns_forwarder_handle) = DnsInterceptor::new(
265 &mut sockets,
266 dns_config,
267 shared.clone(),
268 &tokio_handle,
269 gateway_ips,
270 network_policy.clone(),
271 platform_policy.clone(),
272 config.gateway,
273 config.gateway_mac,
274 config.guest_mac,
275 );
276 let mut port_publisher = PortPublisher::new(
277 &published_ports,
278 config.guest_ipv4,
279 config.guest_ipv6,
280 config.gateway.ipv4,
281 config.gateway.ipv6,
282 config.gateway_mac,
283 config.guest_mac,
284 network_policy.clone(),
285 shared.clone(),
286 &tokio_handle,
287 );
288 let mut udp_relay = UdpRelay::new(
289 shared.clone(),
290 config.gateway_mac,
291 config.guest_mac,
292 config.mtu,
293 tokio_handle.clone(),
294 );
295 let mut udp_fragments = Ipv4UdpFragmentReassembler::new();
296 let mut ipv6_udp_fragments = Ipv6UdpFragmentReassembler::new();
297 let icmp_relay = IcmpRelay::new(
298 shared.clone(),
299 config.gateway_mac,
300 config.guest_mac,
301 tokio_handle.clone(),
302 );
303
304 let mut last_cleanup = std::time::Instant::now();
306
307 #[cfg(unix)]
309 let mut poll_fds = [
310 libc::pollfd {
311 fd: shared.tx_wake.as_raw_fd(),
312 events: libc::POLLIN,
313 revents: 0,
314 },
315 libc::pollfd {
316 fd: shared.proxy_wake.as_raw_fd(),
317 events: libc::POLLIN,
318 revents: 0,
319 },
320 ];
321 #[cfg(windows)]
322 let wait_context = match windows_stack_wait_context(&shared) {
323 Ok(context) => context,
324 Err(err) => {
325 tracing::error!(error = %err, "network poll loop: failed to create wait context");
326 return;
327 }
328 };
329
330 loop {
331 let now = smoltcp_now();
332
333 while let Some(frame) = device.stage_next_frame() {
335 if handle_gateway_icmp_echo(
336 frame,
337 &config,
338 &shared,
339 &network_policy,
340 platform_policy.as_deref(),
341 ) {
342 device.drop_staged_frame();
343 continue;
344 }
345
346 if icmp_relay.relay_outbound_if_echo(
347 frame,
348 &config,
349 &network_policy,
350 platform_policy.as_deref(),
351 ) {
352 device.drop_staged_frame();
353 continue;
354 }
355
356 match classify_frame(frame) {
357 FrameAction::TcpSyn { src, dst } => {
358 let allow = match DnsPortType::from_tcp(dst.port()) {
359 DnsPortType::Dns => true,
363 DnsPortType::EncryptedDns => {
372 if tls_state.is_some() {
373 true
374 } else {
375 tracing::debug!(%dst, "DoT port refused (TLS interception not configured); stub should fall back to TCP/53");
376 false
377 }
378 }
379 DnsPortType::AlternativeDns => {
385 tracing::debug!(%dst, "alternative-DNS TCP port refused; stub should fall back to TCP/53");
386 false
387 }
388 DnsPortType::Other => {
391 let platform_allows = platform_policy.as_deref().is_none_or(|policy| {
392 policy
393 .evaluate_egress(dst, Protocol::Tcp, &shared)
394 .is_allow()
395 });
396 platform_allows
397 && matches!(
398 network_policy.evaluate_egress_with_source(
399 dst,
400 Protocol::Tcp,
401 &shared,
402 HostnameSource::Deferred,
403 ),
404 EgressEvaluation::Allow | EgressEvaluation::DeferUntilHostname
405 )
406 }
407 };
408 if allow && !conn_tracker.has_socket_for(&src, &dst) {
409 conn_tracker.create_tcp_socket(src, dst, &mut sockets);
410 }
411 iface.poll_ingress_single(now, &mut device, &mut sockets);
414 }
415
416 FrameAction::UdpRelay { src, dst } => {
417 relay_udp_frame(
418 frame,
419 src,
420 dst,
421 &config,
422 &network_policy,
423 platform_policy.as_deref(),
424 &shared,
425 &mut port_publisher,
426 tls_state.as_deref(),
427 &mut udp_relay,
428 );
429 device.drop_staged_frame();
430 }
431
432 FrameAction::Ipv4UdpFragment => {
433 if let Some(datagram) = udp_fragments.push(frame) {
434 handle_reassembled_udp_datagram(
435 datagram,
436 &mut device,
437 &mut iface,
438 now,
439 &mut sockets,
440 &config,
441 &network_policy,
442 platform_policy.as_deref(),
443 &shared,
444 &mut port_publisher,
445 tls_state.as_deref(),
446 &mut udp_relay,
447 );
448 } else {
449 device.drop_staged_frame();
450 }
451 }
452
453 FrameAction::Ipv6UdpFragment => {
454 if let Some(datagram) = ipv6_udp_fragments.push(frame) {
455 handle_reassembled_udp_datagram(
456 datagram,
457 &mut device,
458 &mut iface,
459 now,
460 &mut sockets,
461 &config,
462 &network_policy,
463 platform_policy.as_deref(),
464 &shared,
465 &mut port_publisher,
466 tls_state.as_deref(),
467 &mut udp_relay,
468 );
469 } else {
470 device.drop_staged_frame();
471 }
472 }
473
474 FrameAction::Ipv6UnsupportedFragment => {
475 device.drop_staged_frame();
479 }
480
481 FrameAction::Dns | FrameAction::Passthrough => {
482 iface.poll_ingress_single(now, &mut device, &mut sockets);
484 }
485 }
486 }
487
488 loop {
492 let result = iface.poll_egress(now, &mut device, &mut sockets);
493 if matches!(result, smoltcp::iface::PollResult::None) {
494 break;
495 }
496 }
497 iface.poll_maintenance(now);
498
499 if device.frames_emitted.swap(false, Ordering::Relaxed) {
502 shared.rx_wake.wake();
503 }
504
505 conn_tracker.relay_data(&mut sockets);
510 dns_interceptor.process(&mut sockets);
511
512 port_publisher.accept_inbound(&mut iface, &mut sockets, &shared, &tokio_handle);
514 port_publisher.relay_data(&mut sockets);
515
516 let new_conns = conn_tracker.take_new_connections(&mut sockets);
518 for conn in new_conns {
519 if let Some(ref tls_state) = tls_state
520 && tls_state
521 .config
522 .intercepted_ports
523 .contains(&conn.dst.port())
524 {
525 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
527 tls_proxy::spawn_tls_proxy(
528 &tokio_handle,
529 conn.dst,
530 connect_dst,
531 conn.from_smoltcp,
532 conn.to_smoltcp,
533 shared.clone(),
534 tls_state.clone(),
535 network_policy.clone(),
536 conn.proxy_connect,
537 );
538 continue;
539 }
540 if conn.dst.port() == 53 {
541 conn.proxy_connect.mark_connected();
548
549 DnsTcpProxy::spawn(
559 &tokio_handle,
560 conn.dst,
561 conn.from_smoltcp,
562 conn.to_smoltcp,
563 dns_forwarder_handle.clone(),
564 shared.clone(),
565 );
566 continue;
567 }
568 if conn.dst.port() == 853
569 && let Some(ref tls_state) = tls_state
570 {
571 conn.proxy_connect.mark_connected();
573
574 DotProxy::spawn(
580 &tokio_handle,
581 conn.dst,
582 conn.from_smoltcp,
583 conn.to_smoltcp,
584 dns_forwarder_handle.clone(),
585 tls_state.clone(),
586 shared.clone(),
587 );
588 continue;
589 }
590 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
592 proxy::spawn_tcp_proxy(
593 &tokio_handle,
594 conn.dst,
595 connect_dst,
596 conn.from_smoltcp,
597 conn.to_smoltcp,
598 shared.clone(),
599 network_policy.clone(),
600 secrets.load(),
603 tls_state.clone(),
604 conn.proxy_connect,
605 );
606 }
607
608 if last_cleanup.elapsed() >= std::time::Duration::from_secs(1) {
611 conn_tracker.cleanup_closed(&mut sockets);
612 port_publisher.cleanup_closed(&mut sockets);
613 udp_relay.cleanup_expired();
614 udp_fragments.cleanup_expired();
615 ipv6_udp_fragments.cleanup_expired();
616 shared.cleanup_resolved_hostnames();
617 last_cleanup = std::time::Instant::now();
618 }
619
620 loop {
623 let result = iface.poll_egress(now, &mut device, &mut sockets);
624 if matches!(result, smoltcp::iface::PollResult::None) {
625 break;
626 }
627 }
628
629 if device.frames_emitted.swap(false, Ordering::Relaxed) {
631 shared.rx_wake.wake();
632 }
633
634 let timeout_ms = iface
635 .poll_delay(now, &sockets)
636 .map(|d| d.total_millis().min(i32::MAX as u64) as i32)
637 .unwrap_or(100); #[cfg(unix)]
640 sleep_until_stack_wake(&shared, timeout_ms, &mut poll_fds);
641 #[cfg(windows)]
642 sleep_until_stack_wake_windows(&shared, timeout_ms, &wait_context);
643 }
644}
645
646#[cfg(unix)]
651fn sleep_until_stack_wake(shared: &SharedState, timeout_ms: i32, poll_fds: &mut [libc::pollfd; 2]) {
652 unsafe {
654 libc::poll(
655 poll_fds.as_mut_ptr(),
656 poll_fds.len() as libc::nfds_t,
657 timeout_ms,
658 );
659 }
660
661 if poll_fds[0].revents & libc::POLLIN != 0 {
662 shared.tx_wake.drain();
663 }
664 if poll_fds[1].revents & libc::POLLIN != 0 {
665 shared.proxy_wake.drain();
666 }
667}
668
669#[cfg(windows)]
670fn windows_stack_wait_context(shared: &SharedState) -> std::io::Result<WaitContext> {
671 let mut context = WaitContext::new();
672 context.add(
673 EventSource::waitable_handle(shared.tx_wake.as_raw_handle(), TX_WAKE_TOKEN),
674 EventSet::IN,
675 )?;
676 context.add(
677 EventSource::waitable_handle(shared.proxy_wake.as_raw_handle(), PROXY_WAKE_TOKEN),
678 EventSet::IN,
679 )?;
680 Ok(context)
681}
682
683#[cfg(windows)]
684fn sleep_until_stack_wake_windows(
685 shared: &SharedState,
686 timeout_ms: i32,
687 wait_context: &WaitContext,
688) {
689 let mut events = [WaitEvent::default(); 2];
690 let count = match wait_context.wait(timeout_ms, &mut events) {
691 Ok(count) => count,
692 Err(err) => {
693 tracing::warn!(error = %err, "network poll loop: wait failed");
694 return;
695 }
696 };
697
698 for event in events.iter().take(count) {
699 match event.token() {
700 TX_WAKE_TOKEN => shared.tx_wake.drain(),
701 PROXY_WAKE_TOKEN => shared.proxy_wake.drain(),
702 token => tracing::warn!(token, "network poll loop: unknown wake token"),
703 }
704 }
705}
706
707#[allow(clippy::too_many_arguments)]
709fn relay_udp_frame(
710 frame: &[u8],
711 src: SocketAddr,
712 dst: SocketAddr,
713 config: &PollLoopConfig,
714 network_policy: &NetworkPolicy,
715 platform_policy: Option<&NetworkPolicy>,
716 shared: &Arc<SharedState>,
717 port_publisher: &mut PortPublisher,
718 tls_state: Option<&TlsState>,
719 udp_relay: &mut UdpRelay,
720) {
721 if port_publisher.relay_udp_outbound(frame, src, dst) {
722 return;
723 }
724
725 if let Some(tls) = tls_state
727 && tls.config.intercepted_ports.contains(&dst.port())
728 && tls.config.block_quic_on_intercept
729 {
730 return;
731 }
732
733 match DnsPortType::from_udp(dst.port()) {
734 DnsPortType::Dns | DnsPortType::EncryptedDns => return,
737 DnsPortType::AlternativeDns => {
740 tracing::debug!(%dst, "alternative-DNS UDP port dropped; stub should fall back to UDP/53");
741 return;
742 }
743 DnsPortType::Other => {}
744 }
745
746 if platform_policy
748 .is_some_and(|policy| policy.evaluate_egress(dst, Protocol::Udp, shared).is_deny())
749 || network_policy
750 .evaluate_egress(dst, Protocol::Udp, shared)
751 .is_deny()
752 {
753 return;
754 }
755
756 let host_dst = resolve_host_dst(dst, config.gateway);
759 udp_relay.relay_outbound(frame, src, dst, host_dst);
760}
761
762#[allow(clippy::too_many_arguments)]
764fn handle_reassembled_udp_datagram(
765 datagram: ReassembledUdpDatagram,
766 device: &mut SmoltcpDevice,
767 iface: &mut Interface,
768 now: Instant,
769 sockets: &mut SocketSet<'_>,
770 config: &PollLoopConfig,
771 network_policy: &NetworkPolicy,
772 platform_policy: Option<&NetworkPolicy>,
773 shared: &Arc<SharedState>,
774 port_publisher: &mut PortPublisher,
775 tls_state: Option<&TlsState>,
776 udp_relay: &mut UdpRelay,
777) {
778 if DnsPortType::from_udp(datagram.dst.port()) == DnsPortType::Dns {
779 device.replace_staged_frame(datagram.frame);
780 iface.poll_ingress_single(now, device, sockets);
781 return;
782 }
783
784 relay_udp_frame(
785 &datagram.frame,
786 datagram.src,
787 datagram.dst,
788 config,
789 network_policy,
790 platform_policy,
791 shared,
792 port_publisher,
793 tls_state,
794 udp_relay,
795 );
796 device.drop_staged_frame();
797}
798
799pub(crate) fn resolve_host_dst(dst: SocketAddr, gateway: GatewayIps) -> SocketAddr {
809 match dst.ip() {
810 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => {
811 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst.port())
812 }
813 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => {
814 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), dst.port())
815 }
816 _ => dst,
817 }
818}
819
820fn smoltcp_now() -> Instant {
826 static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
827 let epoch = EPOCH.get_or_init(std::time::Instant::now);
828 let elapsed = epoch.elapsed();
829 Instant::from_millis(elapsed.as_millis() as i64)
830}
831
832fn handle_gateway_icmp_echo(
839 frame: &[u8],
840 config: &PollLoopConfig,
841 shared: &SharedState,
842 network_policy: &NetworkPolicy,
843 platform_policy: Option<&NetworkPolicy>,
844) -> bool {
845 let Ok(eth) = EthernetFrame::new_checked(frame) else {
846 return false;
847 };
848
849 let reply = match eth.ethertype() {
850 EthernetProtocol::Ipv4 => gateway_icmpv4_echo_reply(ð, config),
851 EthernetProtocol::Ipv6 => gateway_icmpv6_echo_reply(ð, config),
852 _ => None,
853 };
854 let Some(reply) = reply else {
855 return false;
856 };
857
858 let tenant_denied = network_policy
861 .evaluate_egress_ip(reply.dst, reply.protocol, shared)
862 .is_deny();
863 let platform_denied = platform_policy.is_some_and(|policy| {
864 policy
865 .evaluate_egress_ip(reply.dst, reply.protocol, shared)
866 .is_deny()
867 });
868 if tenant_denied || platform_denied {
869 tracing::debug!(
870 dst = %reply.dst,
871 tenant_denied,
872 platform_denied,
873 "gateway ICMP echo denied by policy",
874 );
875 return true;
876 }
877
878 shared.push_rx_frame_and_wake(reply.frame);
879
880 true
881}
882
883fn gateway_icmpv4_echo_reply(
885 eth: &EthernetFrame<&[u8]>,
886 config: &PollLoopConfig,
887) -> Option<GatewayIcmpReply> {
888 let gateway_ipv4 = config.gateway.ipv4?;
889 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
890 if ipv4.dst_addr() != gateway_ipv4 || ipv4.next_header() != IpProtocol::Icmp {
891 return None;
892 }
893
894 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).ok()?;
895 let Icmpv4Repr::EchoRequest {
896 ident,
897 seq_no,
898 data,
899 } = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default()).ok()?
900 else {
901 return None;
902 };
903
904 let ipv4_repr = Ipv4Repr {
905 src_addr: gateway_ipv4,
906 dst_addr: ipv4.src_addr(),
907 next_header: IpProtocol::Icmp,
908 payload_len: 8 + data.len(),
909 hop_limit: 64,
910 };
911 let icmp_repr = Icmpv4Repr::EchoReply {
912 ident,
913 seq_no,
914 data,
915 };
916 let mut reply = vec![0u8; 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len()];
917
918 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
919 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
920 reply_eth.set_dst_addr(eth.src_addr());
921 reply_eth.set_ethertype(EthernetProtocol::Ipv4);
922
923 ipv4_repr.emit(
924 &mut Ipv4Packet::new_unchecked(&mut reply[14..34]),
925 &smoltcp::phy::ChecksumCapabilities::default(),
926 );
927 icmp_repr.emit(
928 &mut Icmpv4Packet::new_unchecked(&mut reply[34..]),
929 &smoltcp::phy::ChecksumCapabilities::default(),
930 );
931
932 Some(GatewayIcmpReply {
933 dst: IpAddr::V4(gateway_ipv4),
934 protocol: Protocol::Icmpv4,
935 frame: reply,
936 })
937}
938
939fn gateway_icmpv6_echo_reply(
941 eth: &EthernetFrame<&[u8]>,
942 config: &PollLoopConfig,
943) -> Option<GatewayIcmpReply> {
944 let gateway_ipv6 = config.gateway.ipv6?;
945 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
946 if ipv6.dst_addr() != gateway_ipv6 || ipv6.next_header() != IpProtocol::Icmpv6 {
947 return None;
948 }
949
950 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).ok()?;
951 let Icmpv6Repr::EchoRequest {
952 ident,
953 seq_no,
954 data,
955 } = Icmpv6Repr::parse(
956 &ipv6.src_addr(),
957 &ipv6.dst_addr(),
958 &icmp,
959 &smoltcp::phy::ChecksumCapabilities::default(),
960 )
961 .ok()?
962 else {
963 return None;
964 };
965
966 let ipv6_repr = Ipv6Repr {
967 src_addr: gateway_ipv6,
968 dst_addr: ipv6.src_addr(),
969 next_header: IpProtocol::Icmpv6,
970 payload_len: icmp_repr_buffer_len_v6(data),
971 hop_limit: 64,
972 };
973 let icmp_repr = Icmpv6Repr::EchoReply {
974 ident,
975 seq_no,
976 data,
977 };
978 let ipv6_hdr_len = 40;
979 let mut reply = vec![0u8; 14 + ipv6_hdr_len + icmp_repr.buffer_len()];
980
981 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
982 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
983 reply_eth.set_dst_addr(eth.src_addr());
984 reply_eth.set_ethertype(EthernetProtocol::Ipv6);
985
986 ipv6_repr.emit(&mut Ipv6Packet::new_unchecked(&mut reply[14..54]));
987 icmp_repr.emit(
988 &gateway_ipv6,
989 &ipv6.src_addr(),
990 &mut Icmpv6Packet::new_unchecked(&mut reply[54..]),
991 &smoltcp::phy::ChecksumCapabilities::default(),
992 );
993
994 Some(GatewayIcmpReply {
995 dst: IpAddr::V6(gateway_ipv6),
996 protocol: Protocol::Icmpv6,
997 frame: reply,
998 })
999}
1000
1001fn icmp_repr_buffer_len_v6(data: &[u8]) -> usize {
1002 Icmpv6Repr::EchoReply {
1003 ident: 0,
1004 seq_no: 0,
1005 data,
1006 }
1007 .buffer_len()
1008}
1009
1010fn classify_ipv4(payload: &[u8]) -> FrameAction {
1012 let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
1013 return FrameAction::Passthrough;
1014 };
1015 if is_ipv4_udp_fragment(&ipv4) {
1016 return FrameAction::Ipv4UdpFragment;
1017 }
1018 classify_transport(
1019 ipv4.next_header(),
1020 ipv4.src_addr().into(),
1021 ipv4.dst_addr().into(),
1022 ipv4.payload(),
1023 )
1024}
1025
1026fn classify_ipv6(payload: &[u8]) -> FrameAction {
1028 let Ok(ipv6) = Ipv6Packet::new_checked(payload) else {
1029 return FrameAction::Passthrough;
1030 };
1031 if is_ipv6_udp_fragment(&ipv6) {
1032 return FrameAction::Ipv6UdpFragment;
1033 }
1034 if is_ipv6_fragment(&ipv6) {
1035 return FrameAction::Ipv6UnsupportedFragment;
1036 }
1037 classify_transport(
1038 ipv6.next_header(),
1039 ipv6.src_addr().into(),
1040 ipv6.dst_addr().into(),
1041 ipv6.payload(),
1042 )
1043}
1044
1045fn classify_transport(
1047 protocol: IpProtocol,
1048 src_ip: std::net::IpAddr,
1049 dst_ip: std::net::IpAddr,
1050 transport_payload: &[u8],
1051) -> FrameAction {
1052 match protocol {
1053 IpProtocol::Tcp => {
1054 let Ok(tcp) = TcpPacket::new_checked(transport_payload) else {
1055 return FrameAction::Passthrough;
1056 };
1057 if tcp.syn() && !tcp.ack() {
1058 FrameAction::TcpSyn {
1059 src: SocketAddr::new(src_ip, tcp.src_port()),
1060 dst: SocketAddr::new(dst_ip, tcp.dst_port()),
1061 }
1062 } else {
1063 FrameAction::Passthrough
1064 }
1065 }
1066 IpProtocol::Udp => {
1067 let Ok(udp) = UdpPacket::new_checked(transport_payload) else {
1068 return FrameAction::Passthrough;
1069 };
1070 if DnsPortType::from_udp(udp.dst_port()) == DnsPortType::Dns {
1074 FrameAction::Dns
1075 } else {
1076 FrameAction::UdpRelay {
1077 src: SocketAddr::new(src_ip, udp.src_port()),
1078 dst: SocketAddr::new(dst_ip, udp.dst_port()),
1079 }
1080 }
1081 }
1082 _ => FrameAction::Passthrough, }
1084}
1085
1086#[cfg(test)]
1091mod tests {
1092 use super::*;
1093 use std::sync::Arc;
1094
1095 use smoltcp::phy::ChecksumCapabilities;
1096 use smoltcp::wire::{
1097 ArpOperation, ArpPacket, ArpRepr, EthernetRepr, Icmpv4Packet, Icmpv4Repr, Ipv4Repr,
1098 };
1099
1100 use crate::device::SmoltcpDevice;
1101 use crate::shared::SharedState;
1102
1103 fn build_tcp_syn_frame(
1105 src_ip: [u8; 4],
1106 dst_ip: [u8; 4],
1107 src_port: u16,
1108 dst_port: u16,
1109 ) -> Vec<u8> {
1110 let mut frame = vec![0u8; 14 + 20 + 20]; frame[12] = 0x08; frame[13] = 0x00;
1115
1116 let ip = &mut frame[14..34];
1118 ip[0] = 0x45; let total_len = 40u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
1121 ip[6] = 0x40; ip[8] = 64; ip[9] = 6; ip[12..16].copy_from_slice(&src_ip);
1125 ip[16..20].copy_from_slice(&dst_ip);
1126
1127 let tcp = &mut frame[34..54];
1129 tcp[0..2].copy_from_slice(&src_port.to_be_bytes());
1130 tcp[2..4].copy_from_slice(&dst_port.to_be_bytes());
1131 tcp[12] = 0x50; tcp[13] = 0x02; frame
1135 }
1136
1137 fn build_udp_frame(src_ip: [u8; 4], dst_ip: [u8; 4], src_port: u16, dst_port: u16) -> Vec<u8> {
1139 let mut frame = vec![0u8; 14 + 20 + 8]; frame[12] = 0x08;
1143 frame[13] = 0x00;
1144
1145 let ip = &mut frame[14..34];
1147 ip[0] = 0x45;
1148 let total_len = 28u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
1150 ip[8] = 64;
1151 ip[9] = 17; ip[12..16].copy_from_slice(&src_ip);
1153 ip[16..20].copy_from_slice(&dst_ip);
1154
1155 let udp = &mut frame[34..42];
1157 udp[0..2].copy_from_slice(&src_port.to_be_bytes());
1158 udp[2..4].copy_from_slice(&dst_port.to_be_bytes());
1159 let udp_len = 8u16;
1160 udp[4..6].copy_from_slice(&udp_len.to_be_bytes());
1161
1162 frame
1163 }
1164
1165 fn build_icmpv4_echo_frame(
1167 src_mac: [u8; 6],
1168 dst_mac: [u8; 6],
1169 src_ip: [u8; 4],
1170 dst_ip: [u8; 4],
1171 ident: u16,
1172 seq_no: u16,
1173 data: &[u8],
1174 ) -> Vec<u8> {
1175 let ipv4_repr = Ipv4Repr {
1176 src_addr: Ipv4Addr::from(src_ip),
1177 dst_addr: Ipv4Addr::from(dst_ip),
1178 next_header: IpProtocol::Icmp,
1179 payload_len: 8 + data.len(),
1180 hop_limit: 64,
1181 };
1182 let icmp_repr = Icmpv4Repr::EchoRequest {
1183 ident,
1184 seq_no,
1185 data,
1186 };
1187 let frame_len = 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
1188 let mut frame = vec![0u8; frame_len];
1189
1190 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1191 EthernetRepr {
1192 src_addr: EthernetAddress(src_mac),
1193 dst_addr: EthernetAddress(dst_mac),
1194 ethertype: EthernetProtocol::Ipv4,
1195 }
1196 .emit(&mut eth_frame);
1197
1198 ipv4_repr.emit(
1199 &mut Ipv4Packet::new_unchecked(&mut frame[14..34]),
1200 &ChecksumCapabilities::default(),
1201 );
1202 icmp_repr.emit(
1203 &mut Icmpv4Packet::new_unchecked(&mut frame[34..]),
1204 &ChecksumCapabilities::default(),
1205 );
1206
1207 frame
1208 }
1209
1210 fn build_arp_request_frame(src_mac: [u8; 6], src_ip: [u8; 4], target_ip: [u8; 4]) -> Vec<u8> {
1212 let mut frame = vec![0u8; 14 + 28];
1213
1214 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1215 EthernetRepr {
1216 src_addr: EthernetAddress(src_mac),
1217 dst_addr: EthernetAddress([0xff; 6]),
1218 ethertype: EthernetProtocol::Arp,
1219 }
1220 .emit(&mut eth_frame);
1221
1222 ArpRepr::EthernetIpv4 {
1223 operation: ArpOperation::Request,
1224 source_hardware_addr: EthernetAddress(src_mac),
1225 source_protocol_addr: Ipv4Addr::from(src_ip),
1226 target_hardware_addr: EthernetAddress([0x00; 6]),
1227 target_protocol_addr: Ipv4Addr::from(target_ip),
1228 }
1229 .emit(&mut ArpPacket::new_unchecked(&mut frame[14..]));
1230
1231 frame
1232 }
1233
1234 #[test]
1235 fn classify_tcp_syn() {
1236 let frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1237 match classify_frame(&frame) {
1238 FrameAction::TcpSyn { src, dst } => {
1239 assert_eq!(
1240 src,
1241 SocketAddr::new(Ipv4Addr::new(10, 0, 0, 2).into(), 54321)
1242 );
1243 assert_eq!(
1244 dst,
1245 SocketAddr::new(Ipv4Addr::new(93, 184, 216, 34).into(), 443)
1246 );
1247 }
1248 _ => panic!("expected TcpSyn"),
1249 }
1250 }
1251
1252 #[test]
1253 fn classify_tcp_ack_is_passthrough() {
1254 let mut frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1255 frame[34 + 13] = 0x10; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1258 }
1259
1260 #[test]
1261 fn classify_udp_dns() {
1262 let frame = build_udp_frame([10, 0, 0, 2], [10, 0, 0, 1], 12345, 53);
1263 assert!(matches!(classify_frame(&frame), FrameAction::Dns));
1264 }
1265
1266 #[test]
1267 fn classify_udp_non_dns() {
1268 let frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1269 match classify_frame(&frame) {
1270 FrameAction::UdpRelay { src, dst } => {
1271 assert_eq!(src.port(), 12345);
1272 assert_eq!(dst.port(), 443);
1273 }
1274 _ => panic!("expected UdpRelay"),
1275 }
1276 }
1277
1278 #[test]
1279 fn classify_ipv4_udp_fragment() {
1280 let mut frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1281 frame[14 + 6] = 0x20; assert!(matches!(
1283 classify_frame(&frame),
1284 FrameAction::Ipv4UdpFragment
1285 ));
1286 }
1287
1288 #[test]
1289 fn classify_ipv6_udp_fragment() {
1290 let mut frame = vec![0u8; 14 + 40 + 8];
1291
1292 frame[12] = 0x86;
1293 frame[13] = 0xdd;
1294
1295 let ip = &mut frame[14..54];
1296 ip[0] = 0x60;
1297 ip[4..6].copy_from_slice(&8u16.to_be_bytes());
1298 ip[6] = u8::from(IpProtocol::Ipv6Frag);
1299 ip[7] = 64;
1300 ip[8..24].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1301 ip[24..40].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1302
1303 let fragment = &mut frame[54..62];
1304 fragment[0] = u8::from(IpProtocol::Udp);
1305 fragment[3] = 1; assert!(matches!(
1308 classify_frame(&frame),
1309 FrameAction::Ipv6UdpFragment
1310 ));
1311 }
1312
1313 #[test]
1314 fn classify_ipv6_non_udp_fragment_is_unsupported() {
1315 let mut frame = vec![0u8; 14 + 40 + 8];
1316
1317 frame[12] = 0x86;
1318 frame[13] = 0xdd;
1319
1320 let ip = &mut frame[14..54];
1321 ip[0] = 0x60;
1322 ip[4..6].copy_from_slice(&8u16.to_be_bytes());
1323 ip[6] = u8::from(IpProtocol::Ipv6Frag);
1324 ip[7] = 64;
1325 ip[8..24].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1326 ip[24..40].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1327
1328 let fragment = &mut frame[54..62];
1329 fragment[0] = u8::from(IpProtocol::Tcp);
1330 fragment[3] = 1; assert!(matches!(
1333 classify_frame(&frame),
1334 FrameAction::Ipv6UnsupportedFragment
1335 ));
1336 }
1337
1338 #[test]
1339 fn classify_arp_is_passthrough() {
1340 let mut frame = vec![0u8; 42]; frame[12] = 0x08;
1342 frame[13] = 0x06; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1344 }
1345
1346 #[test]
1347 fn classify_garbage_is_passthrough() {
1348 assert!(matches!(classify_frame(&[]), FrameAction::Passthrough));
1349 assert!(matches!(classify_frame(&[0; 5]), FrameAction::Passthrough));
1350 }
1351
1352 #[test]
1353 fn gateway_replies_to_icmp_echo_requests() {
1354 fn drive_one_frame(
1355 device: &mut SmoltcpDevice,
1356 iface: &mut Interface,
1357 sockets: &mut SocketSet<'_>,
1358 shared: &Arc<SharedState>,
1359 poll_config: &PollLoopConfig,
1360 now: Instant,
1361 ) {
1362 let frame = device.stage_next_frame().expect("expected staged frame");
1363 if handle_gateway_icmp_echo(
1364 frame,
1365 poll_config,
1366 shared,
1367 &NetworkPolicy::allow_all(),
1368 None,
1369 ) {
1370 device.drop_staged_frame();
1371 return;
1372 }
1373 let _ = iface.poll_ingress_single(now, device, sockets);
1374 let _ = iface.poll_egress(now, device, sockets);
1375 }
1376
1377 let shared = Arc::new(SharedState::new(4));
1378 let poll_config = PollLoopConfig {
1379 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1380 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1381 gateway: GatewayIps {
1382 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1383 ipv6: Some(Ipv6Addr::LOCALHOST),
1384 },
1385 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1386 guest_ipv6: None,
1387 mtu: 1500,
1388 };
1389 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1390 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1391 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1392 let mut iface = create_interface(&mut device, &poll_config);
1393 let mut sockets = SocketSet::new(vec![]);
1394 let now = smoltcp_now();
1395
1396 shared
1399 .tx_ring
1400 .push(build_arp_request_frame(
1401 poll_config.guest_mac,
1402 guest_ipv4.octets(),
1403 gateway_ipv4.octets(),
1404 ))
1405 .unwrap();
1406 shared
1407 .tx_ring
1408 .push(build_icmpv4_echo_frame(
1409 poll_config.guest_mac,
1410 poll_config.gateway_mac,
1411 guest_ipv4.octets(),
1412 gateway_ipv4.octets(),
1413 0x1234,
1414 0xABCD,
1415 b"ping",
1416 ))
1417 .unwrap();
1418
1419 drive_one_frame(
1420 &mut device,
1421 &mut iface,
1422 &mut sockets,
1423 &shared,
1424 &poll_config,
1425 now,
1426 );
1427 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1428
1429 drive_one_frame(
1430 &mut device,
1431 &mut iface,
1432 &mut sockets,
1433 &shared,
1434 &poll_config,
1435 now,
1436 );
1437
1438 let reply = shared.rx_ring.pop().expect("expected ICMP echo reply");
1439 let eth = EthernetFrame::new_checked(&reply).expect("valid ethernet frame");
1440 assert_eq!(eth.src_addr(), EthernetAddress(poll_config.gateway_mac));
1441 assert_eq!(eth.dst_addr(), EthernetAddress(poll_config.guest_mac));
1442 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1443
1444 let ipv4 = Ipv4Packet::new_checked(eth.payload()).expect("valid IPv4 packet");
1445 assert_eq!(ipv4.src_addr(), gateway_ipv4);
1446 assert_eq!(ipv4.dst_addr(), guest_ipv4);
1447 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
1448
1449 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).expect("valid ICMP packet");
1450 let icmp_repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default())
1451 .expect("valid ICMP echo reply");
1452 assert_eq!(
1453 icmp_repr,
1454 Icmpv4Repr::EchoReply {
1455 ident: 0x1234,
1456 seq_no: 0xABCD,
1457 data: b"ping",
1458 }
1459 );
1460 }
1461
1462 #[test]
1463 fn gateway_icmp_echo_respects_deny_policy() {
1464 let shared = SharedState::new(4);
1465 let poll_config = PollLoopConfig {
1466 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1467 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1468 gateway: GatewayIps {
1469 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1470 ipv6: None,
1471 },
1472 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1473 guest_ipv6: None,
1474 mtu: 1500,
1475 };
1476 let policy = NetworkPolicy::builder().default_deny().build().unwrap();
1477 let frame = build_icmpv4_echo_frame(
1478 poll_config.guest_mac,
1479 poll_config.gateway_mac,
1480 poll_config.guest_ipv4.unwrap().octets(),
1481 poll_config.gateway.ipv4.unwrap().octets(),
1482 0x1234,
1483 0xABCD,
1484 b"ping",
1485 );
1486
1487 assert!(handle_gateway_icmp_echo(
1488 &frame,
1489 &poll_config,
1490 &shared,
1491 &policy,
1492 None,
1493 ));
1494 assert!(
1495 shared.rx_ring.pop().is_none(),
1496 "denied gateway ICMP should not queue a reply"
1497 );
1498 }
1499
1500 #[test]
1501 fn platform_public_floor_consumes_gateway_echo_without_replying() {
1502 let shared = SharedState::new(4);
1503 let gateway = Ipv4Addr::new(100, 96, 0, 1);
1504 let guest = Ipv4Addr::new(100, 96, 0, 2);
1505 shared.set_gateway_ips(Some(gateway), None);
1506 let config = PollLoopConfig {
1507 gateway_mac: [0x02, 0, 0, 0, 0, 1],
1508 guest_mac: [0x02, 0, 0, 0, 0, 2],
1509 gateway: GatewayIps {
1510 ipv4: Some(gateway),
1511 ipv6: None,
1512 },
1513 guest_ipv4: Some(guest),
1514 guest_ipv6: None,
1515 mtu: 1500,
1516 };
1517 let frame = build_icmpv4_echo_frame(
1518 config.guest_mac,
1519 config.gateway_mac,
1520 guest.octets(),
1521 gateway.octets(),
1522 1,
1523 1,
1524 b"ping",
1525 );
1526 let platform = NetworkPolicy::from_profiles([crate::policy::NetworkProfile::Public]);
1527
1528 assert!(handle_gateway_icmp_echo(
1529 &frame,
1530 &config,
1531 &shared,
1532 &NetworkPolicy::allow_all(),
1533 Some(&platform),
1534 ));
1535 assert!(shared.rx_ring.pop().is_none());
1536 }
1537
1538 fn test_gateway() -> GatewayIps {
1539 GatewayIps {
1540 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1541 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1542 }
1543 }
1544
1545 #[test]
1546 fn resolve_host_dst_matches_ipv4() {
1547 let gw = test_gateway();
1548 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1549 assert_eq!(
1550 resolve_host_dst(dst, gw),
1551 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
1552 );
1553 }
1554
1555 #[test]
1556 fn resolve_host_dst_matches_ipv6() {
1557 let gw = test_gateway();
1558 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1559 assert_eq!(
1560 resolve_host_dst(dst, gw),
1561 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
1562 );
1563 }
1564
1565 #[test]
1566 fn resolve_host_dst_passes_through_when_family_absent() {
1567 let gw = GatewayIps {
1568 ipv4: None,
1569 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1570 };
1571 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 1)), 8080);
1573 assert_eq!(resolve_host_dst(dst, gw), dst);
1574 }
1575
1576 #[test]
1577 fn resolve_host_dst_passes_through_non_gateway() {
1578 let gw = test_gateway();
1579 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1580 assert_eq!(resolve_host_dst(dst, gw), dst);
1581 }
1582
1583 #[test]
1584 fn external_icmp_echo_requests_are_not_answered_locally() {
1585 fn drive_one_frame(
1586 device: &mut SmoltcpDevice,
1587 iface: &mut Interface,
1588 sockets: &mut SocketSet<'_>,
1589 shared: &Arc<SharedState>,
1590 poll_config: &PollLoopConfig,
1591 now: Instant,
1592 ) {
1593 let frame = device.stage_next_frame().expect("expected staged frame");
1594 if handle_gateway_icmp_echo(
1595 frame,
1596 poll_config,
1597 shared,
1598 &NetworkPolicy::allow_all(),
1599 None,
1600 ) {
1601 device.drop_staged_frame();
1602 return;
1603 }
1604 let _ = iface.poll_ingress_single(now, device, sockets);
1605 let _ = iface.poll_egress(now, device, sockets);
1606 }
1607
1608 let shared = Arc::new(SharedState::new(4));
1609 let poll_config = PollLoopConfig {
1610 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1611 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1612 gateway: GatewayIps {
1613 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1614 ipv6: Some(Ipv6Addr::LOCALHOST),
1615 },
1616 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1617 guest_ipv6: None,
1618 mtu: 1500,
1619 };
1620 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1621 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1622 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1623 let mut iface = create_interface(&mut device, &poll_config);
1624 let mut sockets = SocketSet::new(vec![]);
1625 let now = smoltcp_now();
1626
1627 shared
1628 .tx_ring
1629 .push(build_arp_request_frame(
1630 poll_config.guest_mac,
1631 guest_ipv4.octets(),
1632 gateway_ipv4.octets(),
1633 ))
1634 .unwrap();
1635 shared
1636 .tx_ring
1637 .push(build_icmpv4_echo_frame(
1638 poll_config.guest_mac,
1639 poll_config.gateway_mac,
1640 guest_ipv4.octets(),
1641 [142, 251, 216, 46],
1642 0x1234,
1643 0xABCD,
1644 b"ping",
1645 ))
1646 .unwrap();
1647
1648 drive_one_frame(
1649 &mut device,
1650 &mut iface,
1651 &mut sockets,
1652 &shared,
1653 &poll_config,
1654 now,
1655 );
1656 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1657
1658 drive_one_frame(
1659 &mut device,
1660 &mut iface,
1661 &mut sockets,
1662 &shared,
1663 &poll_config,
1664 now,
1665 );
1666 assert!(
1667 shared.rx_ring.pop().is_none(),
1668 "external ICMP should not be answered locally"
1669 );
1670 }
1671
1672 use smoltcp::socket::tcp;
1688 use smoltcp::wire::{TcpControl, TcpPacket, TcpRepr, TcpSeqNumber};
1689
1690 const GUEST_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
1691 const GATEWAY_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
1692 const GUEST_IP: [u8; 4] = [100, 96, 0, 2];
1693 const GATEWAY_IP: [u8; 4] = [100, 96, 0, 1];
1694 const SERVER_IP: [u8; 4] = [93, 184, 216, 34];
1696
1697 fn leak_poll_config() -> PollLoopConfig {
1698 PollLoopConfig {
1699 gateway_mac: GATEWAY_MAC,
1700 guest_mac: GUEST_MAC,
1701 gateway: GatewayIps {
1702 ipv4: Some(Ipv4Addr::from(GATEWAY_IP)),
1703 ipv6: None,
1704 },
1705 guest_ipv4: Some(Ipv4Addr::from(GUEST_IP)),
1706 guest_ipv6: None,
1707 mtu: 1500,
1708 }
1709 }
1710
1711 #[allow(clippy::too_many_arguments)]
1713 fn build_tcp_frame(
1714 src_port: u16,
1715 dst_port: u16,
1716 control: TcpControl,
1717 seq: i32,
1718 ack: Option<i32>,
1719 payload: &[u8],
1720 ) -> Vec<u8> {
1721 let src_ip = Ipv4Addr::from(GUEST_IP);
1722 let dst_ip = Ipv4Addr::from(SERVER_IP);
1723
1724 let tcp_repr = TcpRepr {
1725 src_port,
1726 dst_port,
1727 control,
1728 seq_number: TcpSeqNumber(seq),
1729 ack_number: ack.map(TcpSeqNumber),
1730 window_len: 65535,
1731 window_scale: None,
1732 max_seg_size: None,
1733 sack_permitted: false,
1734 sack_ranges: [None, None, None],
1735 timestamp: None,
1736 payload,
1737 };
1738 let ipv4_repr = Ipv4Repr {
1739 src_addr: src_ip,
1740 dst_addr: dst_ip,
1741 next_header: IpProtocol::Tcp,
1742 payload_len: tcp_repr.buffer_len(),
1743 hop_limit: 64,
1744 };
1745
1746 let frame_len = 14 + ipv4_repr.buffer_len() + tcp_repr.buffer_len();
1747 let mut frame = vec![0u8; frame_len];
1748
1749 let mut eth = EthernetFrame::new_unchecked(&mut frame);
1750 EthernetRepr {
1751 src_addr: EthernetAddress(GUEST_MAC),
1752 dst_addr: EthernetAddress(GATEWAY_MAC),
1753 ethertype: EthernetProtocol::Ipv4,
1754 }
1755 .emit(&mut eth);
1756
1757 let ip_end = 14 + ipv4_repr.buffer_len();
1758 ipv4_repr.emit(
1759 &mut Ipv4Packet::new_unchecked(&mut frame[14..ip_end]),
1760 &ChecksumCapabilities::default(),
1761 );
1762 tcp_repr.emit(
1763 &mut TcpPacket::new_unchecked(&mut frame[ip_end..]),
1764 &IpAddress::Ipv4(src_ip),
1765 &IpAddress::Ipv4(dst_ip),
1766 &ChecksumCapabilities::default(),
1767 );
1768
1769 frame
1770 }
1771
1772 fn ingress(
1774 frame: Vec<u8>,
1775 device: &mut SmoltcpDevice,
1776 iface: &mut Interface,
1777 sockets: &mut SocketSet<'_>,
1778 shared: &Arc<SharedState>,
1779 now: Instant,
1780 ) {
1781 shared.tx_ring.push(frame).unwrap();
1782 device.stage_next_frame().expect("frame should stage");
1783 iface.poll_ingress_single(now, device, sockets);
1784 loop {
1785 let r = iface.poll_egress(now, device, sockets);
1786 if matches!(r, smoltcp::iface::PollResult::None) {
1787 break;
1788 }
1789 }
1790 }
1791
1792 fn last_tcp_reply(shared: &Arc<SharedState>) -> Option<(i32, i32, bool, bool, bool)> {
1795 let mut out = None;
1796 while let Some(frame) = shared.rx_ring.pop() {
1797 if frame.len() < 34 {
1798 continue;
1799 }
1800 if frame[23] != 6 {
1802 continue; }
1804 let tcp = match TcpPacket::new_checked(&frame[34..]) {
1805 Ok(p) => p,
1806 Err(_) => continue,
1807 };
1808 out = Some((
1809 tcp.seq_number().0,
1810 tcp.ack_number().0,
1811 tcp.syn(),
1812 tcp.fin(),
1813 tcp.rst(),
1814 ));
1815 }
1816 out
1817 }
1818
1819 fn only_tcp_state(sockets: &SocketSet<'_>) -> Option<tcp::State> {
1821 for (_h, sock) in sockets.iter() {
1822 if let smoltcp::socket::Socket::Tcp(s) = sock {
1823 return Some(s.state());
1824 }
1825 }
1826 None
1827 }
1828
1829 fn handshake(
1832 tracker: &mut ConnectionTracker,
1833 device: &mut SmoltcpDevice,
1834 iface: &mut Interface,
1835 sockets: &mut SocketSet<'_>,
1836 shared: &Arc<SharedState>,
1837 now: Instant,
1838 guest_port: u16,
1839 ) -> (i32, i32) {
1840 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), guest_port);
1841 let dst = SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443);
1842
1843 ingress(
1846 build_arp_request_frame(GUEST_MAC, GUEST_IP, GATEWAY_IP),
1847 device,
1848 iface,
1849 sockets,
1850 shared,
1851 now,
1852 );
1853 let _ = shared.rx_ring.pop(); let guest_isn = 1000i32;
1856
1857 assert!(
1860 tracker.create_tcp_socket(src, dst, sockets),
1861 "socket creation should succeed under the limit"
1862 );
1863 ingress(
1864 build_tcp_frame(guest_port, 443, TcpControl::Syn, guest_isn, None, &[]),
1865 device,
1866 iface,
1867 sockets,
1868 shared,
1869 now,
1870 );
1871 let (server_isn, ack, is_syn, _, _) =
1872 last_tcp_reply(shared).expect("expected SYN-ACK from smoltcp");
1873 assert!(is_syn, "expected SYN flag on handshake reply");
1874 assert_eq!(ack, guest_isn + 1, "SYN-ACK should ack guest ISN+1");
1875
1876 ingress(
1878 build_tcp_frame(
1879 guest_port,
1880 443,
1881 TcpControl::None,
1882 guest_isn + 1,
1883 Some(server_isn + 1),
1884 &[],
1885 ),
1886 device,
1887 iface,
1888 sockets,
1889 shared,
1890 now,
1891 );
1892 assert_eq!(
1893 only_tcp_state(sockets),
1894 Some(tcp::State::Established),
1895 "socket should be ESTABLISHED after handshake",
1896 );
1897
1898 (server_isn, guest_isn + 1)
1899 }
1900
1901 fn establish(
1903 tracker: &mut ConnectionTracker,
1904 device: &mut SmoltcpDevice,
1905 iface: &mut Interface,
1906 sockets: &mut SocketSet<'_>,
1907 shared: &Arc<SharedState>,
1908 now: Instant,
1909 guest_port: u16,
1910 ) -> (i32, i32, Vec<crate::conn::NewConnection>) {
1911 let (server_isn, guest_seq) =
1912 handshake(tracker, device, iface, sockets, shared, now, guest_port);
1913
1914 let new_conns = tracker.take_new_connections(sockets);
1916 assert_eq!(
1917 new_conns.len(),
1918 1,
1919 "one new connection should be handed off"
1920 );
1921
1922 (server_isn, guest_seq, new_conns)
1923 }
1924
1925 #[test]
1926 fn guest_fin_propagates_half_close_without_killing_the_connection() {
1927 let shared = Arc::new(SharedState::new(64));
1928 let poll_config = leak_poll_config();
1929 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1930 let mut iface = create_interface(&mut device, &poll_config);
1931 let mut sockets = SocketSet::new(vec![]);
1932 let mut tracker = ConnectionTracker::new(None);
1933 let now = smoltcp_now();
1934
1935 let (server_isn, guest_seq, mut new_conns) = establish(
1936 &mut tracker,
1937 &mut device,
1938 &mut iface,
1939 &mut sockets,
1940 &shared,
1941 now,
1942 54321,
1943 );
1944 let conn = new_conns.remove(0);
1947 let mut from_smoltcp = conn.from_smoltcp;
1948 let to_smoltcp = conn.to_smoltcp;
1949
1950 ingress(
1953 build_tcp_frame(
1954 54321,
1955 443,
1956 TcpControl::Fin,
1957 guest_seq,
1958 Some(server_isn + 1),
1959 &[],
1960 ),
1961 &mut device,
1962 &mut iface,
1963 &mut sockets,
1964 &shared,
1965 now,
1966 );
1967 assert_eq!(
1968 only_tcp_state(&sockets),
1969 Some(tcp::State::CloseWait),
1970 "guest FIN should move the smoltcp socket to CLOSE_WAIT",
1971 );
1972
1973 tracker.relay_data(&mut sockets);
1977 assert!(
1978 matches!(
1979 from_smoltcp.try_recv(),
1980 Err(tokio::sync::mpsc::error::TryRecvError::Disconnected)
1981 ),
1982 "guest FIN must propagate EOF to the proxy task",
1983 );
1984
1985 assert!(
1988 tracker.has_socket_for(
1989 &SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 54321),
1990 &SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443),
1991 ),
1992 "half-closed connection must stay tracked while the proxy runs",
1993 );
1994 let payload = b"pending server response";
1995 to_smoltcp
1996 .try_send(bytes::Bytes::from_static(payload))
1997 .expect("server → guest channel should accept data");
1998 while shared.rx_ring.pop().is_some() {} tracker.relay_data(&mut sockets);
2000 loop {
2001 let r = iface.poll_egress(now, &mut device, &mut sockets);
2002 if matches!(r, smoltcp::iface::PollResult::None) {
2003 break;
2004 }
2005 }
2006 let mut delivered = false;
2007 while let Some(frame) = shared.rx_ring.pop() {
2008 if frame.windows(payload.len()).any(|w| w == payload) {
2009 delivered = true;
2010 }
2011 }
2012 assert!(
2013 delivered,
2014 "server data must still reach a half-closed guest",
2015 );
2016 assert_eq!(
2017 only_tcp_state(&sockets),
2018 Some(tcp::State::CloseWait),
2019 "socket must stay open (CLOSE_WAIT) while the proxy is alive",
2020 );
2021 }
2022
2023 #[test]
2024 fn guest_fin_connection_is_reaped_after_proxy_exit() {
2025 let shared = Arc::new(SharedState::new(64));
2026 let poll_config = leak_poll_config();
2027 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
2028 let mut iface = create_interface(&mut device, &poll_config);
2029 let mut sockets = SocketSet::new(vec![]);
2030 let mut tracker = ConnectionTracker::new(None);
2031 let now = smoltcp_now();
2032
2033 let (server_isn, guest_seq, mut new_conns) = establish(
2034 &mut tracker,
2035 &mut device,
2036 &mut iface,
2037 &mut sockets,
2038 &shared,
2039 now,
2040 54321,
2041 );
2042 let conn = new_conns.remove(0);
2043 let from_smoltcp = conn.from_smoltcp;
2044 let to_smoltcp = conn.to_smoltcp;
2045
2046 ingress(
2048 build_tcp_frame(
2049 54321,
2050 443,
2051 TcpControl::Fin,
2052 guest_seq,
2053 Some(server_isn + 1),
2054 &[],
2055 ),
2056 &mut device,
2057 &mut iface,
2058 &mut sockets,
2059 &shared,
2060 now,
2061 );
2062 tracker.relay_data(&mut sockets);
2063
2064 drop(from_smoltcp);
2067 drop(to_smoltcp);
2068
2069 while shared.rx_ring.pop().is_some() {} tracker.relay_data(&mut sockets);
2073 loop {
2074 let r = iface.poll_egress(now, &mut device, &mut sockets);
2075 if matches!(r, smoltcp::iface::PollResult::None) {
2076 break;
2077 }
2078 }
2079 let (fin_seq, _, _, is_fin, _) =
2080 last_tcp_reply(&shared).expect("expected FIN toward the guest");
2081 assert!(is_fin, "proxy exit after guest FIN must FIN the guest side");
2082
2083 ingress(
2085 build_tcp_frame(
2086 54321,
2087 443,
2088 TcpControl::None,
2089 guest_seq + 1,
2090 Some(fin_seq + 1),
2091 &[],
2092 ),
2093 &mut device,
2094 &mut iface,
2095 &mut sockets,
2096 &shared,
2097 now,
2098 );
2099 tracker.relay_data(&mut sockets);
2100 tracker.cleanup_closed(&mut sockets);
2101
2102 assert!(
2105 !tracker.has_socket_for(
2106 &SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 54321),
2107 &SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443),
2108 ),
2109 "connection must be evicted after FIN + proxy exit",
2110 );
2111 assert_eq!(
2112 only_tcp_state(&sockets),
2113 None,
2114 "socket must be removed from the socket set",
2115 );
2116 }
2117
2118 #[test]
2119 fn guest_rst_is_cleaned_up() {
2120 let shared = Arc::new(SharedState::new(64));
2121 let poll_config = leak_poll_config();
2122 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
2123 let mut iface = create_interface(&mut device, &poll_config);
2124 let mut sockets = SocketSet::new(vec![]);
2125 let mut tracker = ConnectionTracker::new(None);
2126 let now = smoltcp_now();
2127
2128 let (server_isn, guest_seq, mut new_conns) = establish(
2129 &mut tracker,
2130 &mut device,
2131 &mut iface,
2132 &mut sockets,
2133 &shared,
2134 now,
2135 54322,
2136 );
2137 let conn = new_conns.remove(0);
2138 let mut from_smoltcp = conn.from_smoltcp;
2139 let _to_smoltcp = conn.to_smoltcp;
2140
2141 ingress(
2143 build_tcp_frame(
2144 54322,
2145 443,
2146 TcpControl::Rst,
2147 guest_seq,
2148 Some(server_isn + 1),
2149 &[],
2150 ),
2151 &mut device,
2152 &mut iface,
2153 &mut sockets,
2154 &shared,
2155 now,
2156 );
2157
2158 for _ in 0..8 {
2160 tracker.relay_data(&mut sockets);
2161 tracker.cleanup_closed(&mut sockets);
2162 let _ = iface.poll_egress(now, &mut device, &mut sockets);
2163 }
2164
2165 assert!(
2166 !tracker.has_socket_for(
2167 &SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 54322),
2168 &SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443),
2169 ),
2170 "RST connection should be evicted from the tracker",
2171 );
2172 assert!(
2175 matches!(
2176 from_smoltcp.try_recv(),
2177 Err(tokio::sync::mpsc::error::TryRecvError::Disconnected)
2178 ),
2179 "RST teardown must close the proxy channel (clean, no orphan)",
2180 );
2181 }
2182
2183 #[test]
2184 fn full_connection_table_refuses_new_sockets() {
2185 let mut tracker = ConnectionTracker::new(Some(4));
2189 let mut sockets = SocketSet::new(vec![]);
2190 let dst = SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443);
2191
2192 for port in 40000u16..40004 {
2193 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), port);
2194 assert!(
2195 tracker.create_tcp_socket(src, dst, &mut sockets),
2196 "creation under the limit must succeed",
2197 );
2198 }
2199 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 40004);
2203 assert!(
2204 !tracker.create_tcp_socket(src, dst, &mut sockets),
2205 "creation at the limit must be refused",
2206 );
2207 }
2208
2209 #[test]
2210 fn guest_fin_before_proxy_spawn_is_handed_off() {
2211 let shared = Arc::new(SharedState::new(64));
2212 let poll_config = leak_poll_config();
2213 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
2214 let mut iface = create_interface(&mut device, &poll_config);
2215 let mut sockets = SocketSet::new(vec![]);
2216 let mut tracker = ConnectionTracker::new(None);
2217 let now = smoltcp_now();
2218 let guest_port = 54323;
2219 let (server_isn, guest_seq) = handshake(
2220 &mut tracker,
2221 &mut device,
2222 &mut iface,
2223 &mut sockets,
2224 &shared,
2225 now,
2226 guest_port,
2227 );
2228
2229 ingress(
2233 build_tcp_frame(
2234 guest_port,
2235 443,
2236 TcpControl::Fin,
2237 guest_seq,
2238 Some(server_isn + 1),
2239 &[],
2240 ),
2241 &mut device,
2242 &mut iface,
2243 &mut sockets,
2244 &shared,
2245 now,
2246 );
2247 assert_eq!(
2248 only_tcp_state(&sockets),
2249 Some(tcp::State::CloseWait),
2250 "guest FIN should arrive before the proxy handoff",
2251 );
2252
2253 let new_conns = tracker.take_new_connections(&mut sockets);
2254 assert_eq!(
2255 new_conns.len(),
2256 1,
2257 "a connection that reached CLOSE_WAIT still needs a proxy task",
2258 );
2259 }
2260}