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