1use std::collections::HashSet;
9use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
10use std::num::NonZeroUsize;
11use std::sync::Arc;
12use std::sync::atomic::Ordering;
13
14#[cfg(windows)]
15use msb_krun_utils::event::{EventSet, EventSource, WaitContext, WaitEvent};
16use smoltcp::iface::{Config, Interface, SocketSet};
17use smoltcp::time::Instant;
18
19use smoltcp::wire::{
20 EthernetAddress, EthernetFrame, EthernetProtocol, HardwareAddress, Icmpv4Packet, Icmpv4Repr,
21 Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr, IpProtocol, Ipv4Packet, Ipv4Repr, Ipv6Packet,
22 Ipv6Repr, TcpPacket, UdpPacket,
23};
24
25use crate::config::{ConnectionLimit, DnsConfig, PublishedPort};
26use crate::engine::dns::common::ports::DnsPortType;
27use crate::engine::dns::{
28 interceptor::DnsInterceptor,
29 proxies::{dot::DotProxy, tcp::DnsTcpProxy},
30};
31use crate::engine::tls::{proxy::TlsProxy, state::TlsState};
32use crate::icmp::relay::IcmpRelay;
33use crate::policy::{EgressEvaluation, HostnameSource, NetworkPolicy, Protocol};
34use crate::ports::PortPublisher;
35use crate::proxy::ResolvedOutboundProxy;
36use crate::secrets::handle::SecretsHandle;
37use crate::tcp::{connection::TcpConnectionTracker, proxy::TcpProxy, upstream::UpstreamTcpTarget};
38use crate::udp::fragments::{
39 Ipv4UdpFragmentReassembler, Ipv6UdpFragmentReassembler, ReassembledUdpDatagram,
40 is_ipv4_udp_fragment, is_ipv6_fragment, is_ipv6_udp_fragment,
41};
42use crate::udp::relay::UdpRelay;
43
44use super::{device::SmoltcpDevice, shared::SharedState};
45
46#[cfg(windows)]
51const TX_WAKE_TOKEN: u64 = 1;
52
53#[cfg(windows)]
54const PROXY_WAKE_TOKEN: u64 = 2;
55
56pub enum FrameAction {
67 TcpSyn { src: SocketAddr, dst: SocketAddr },
70
71 UdpRelay { src: SocketAddr, dst: SocketAddr },
74
75 Dns,
77
78 Ipv4UdpFragment,
80
81 Ipv6UdpFragment,
83
84 Ipv6UnsupportedFragment,
86
87 Passthrough,
90}
91
92struct GatewayIcmpReply {
94 dst: IpAddr,
95 protocol: Protocol,
96 frame: Vec<u8>,
97}
98
99pub struct PollLoopConfig {
102 pub gateway_mac: [u8; 6],
104 pub guest_mac: [u8; 6],
106 pub gateway: GatewayIps,
110 pub guest_ipv4: Option<Ipv4Addr>,
112 pub guest_ipv6: Option<Ipv6Addr>,
114 pub mtu: usize,
116}
117
118#[derive(Debug, Clone, Copy)]
123pub struct GatewayIps {
124 pub ipv4: Option<Ipv4Addr>,
126 pub ipv6: Option<Ipv6Addr>,
128}
129
130pub fn classify_frame(frame: &[u8]) -> FrameAction {
140 let Ok(eth) = EthernetFrame::new_checked(frame) else {
141 return FrameAction::Passthrough;
142 };
143
144 match eth.ethertype() {
145 EthernetProtocol::Ipv4 => classify_ipv4(eth.payload()),
146 EthernetProtocol::Ipv6 => classify_ipv6(eth.payload()),
147 _ => FrameAction::Passthrough, }
149}
150
151pub fn create_interface(device: &mut SmoltcpDevice, config: &PollLoopConfig) -> Interface {
158 let hw_addr = HardwareAddress::Ethernet(EthernetAddress(config.gateway_mac));
159 let iface_config = Config::new(hw_addr);
160 let mut iface = Interface::new(iface_config, device, smoltcp_now());
161
162 iface.update_ip_addrs(|addrs| {
164 if let Some(ipv4) = config.gateway.ipv4 {
165 addrs
166 .push(IpCidr::new(IpAddress::Ipv4(ipv4), 30)) .expect("failed to add gateway IPv4 address");
168 }
169 if let Some(ipv6) = config.gateway.ipv6 {
170 addrs
171 .push(IpCidr::new(IpAddress::Ipv6(ipv6), 64))
172 .expect("failed to add gateway IPv6 address");
173 }
174 });
175
176 if let Some(ipv4) = config.gateway.ipv4 {
178 iface
179 .routes_mut()
180 .add_default_ipv4_route(ipv4)
181 .expect("failed to add default IPv4 route");
182 }
183 if let Some(ipv6) = config.gateway.ipv6 {
184 iface
185 .routes_mut()
186 .add_default_ipv6_route(ipv6)
187 .expect("failed to add default IPv6 route");
188 }
189
190 iface.set_any_ip(true);
192
193 iface
194}
195
196#[allow(clippy::too_many_arguments)]
228pub fn smoltcp_poll_loop(
229 shared: Arc<SharedState>,
230 config: PollLoopConfig,
231 network_policy: NetworkPolicy,
232 platform_policy: Option<NetworkPolicy>,
233 dns_config: DnsConfig,
234 tls_state: Option<Arc<TlsState>>,
235 published_ports: Vec<PublishedPort>,
236 strict: bool,
237 max_tcp_connections: Option<NonZeroUsize>,
238 max_udp_connections: Option<ConnectionLimit>,
239 tokio_handle: tokio::runtime::Handle,
240 secrets: SecretsHandle,
241 outbound_proxy: Option<Arc<ResolvedOutboundProxy>>,
242) {
243 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
244 let mut iface = create_interface(&mut device, &config);
245 let mut sockets = SocketSet::new(vec![]);
246 let mut conn_tracker = TcpConnectionTracker::new(max_tcp_connections);
247
248 let gateway_ips: Arc<HashSet<IpAddr>> = Arc::new(
254 config
255 .gateway
256 .ipv4
257 .map(IpAddr::V4)
258 .into_iter()
259 .chain(config.gateway.ipv6.map(IpAddr::V6))
260 .collect(),
261 );
262 shared.set_gateway_ips(config.gateway.ipv4, config.gateway.ipv6);
265 let network_policy = Arc::new(network_policy);
266 let platform_policy = platform_policy.map(Arc::new);
267
268 let (mut dns_interceptor, dns_forwarder_handle) = DnsInterceptor::new(
269 &mut sockets,
270 dns_config,
271 shared.clone(),
272 &tokio_handle,
273 gateway_ips,
274 network_policy.clone(),
275 platform_policy.clone(),
276 config.gateway,
277 config.gateway_mac,
278 config.guest_mac,
279 );
280 let mut port_publisher = PortPublisher::new(
281 &published_ports,
282 config.guest_ipv4,
283 config.guest_ipv6,
284 config.gateway.ipv4,
285 config.gateway.ipv6,
286 config.gateway_mac,
287 config.guest_mac,
288 network_policy.clone(),
289 shared.clone(),
290 &tokio_handle,
291 );
292 let mut udp_relay = UdpRelay::new(
293 shared.clone(),
294 config.gateway_mac,
295 config.guest_mac,
296 config.mtu,
297 tokio_handle.clone(),
298 outbound_proxy.clone(),
299 );
300 udp_relay.set_max_sessions(max_udp_connections.and_then(ConnectionLimit::cap));
301 udp_relay.attach_dns_forwarder(dns_forwarder_handle.clone());
302 let mut udp_fragments = Ipv4UdpFragmentReassembler::new();
303 let mut ipv6_udp_fragments = Ipv6UdpFragmentReassembler::new();
304 let icmp_relay = IcmpRelay::new(
305 shared.clone(),
306 config.gateway_mac,
307 config.guest_mac,
308 tokio_handle.clone(),
309 );
310
311 let mut last_cleanup = std::time::Instant::now();
313
314 #[cfg(unix)]
316 let mut poll_fds = [
317 libc::pollfd {
318 fd: shared.tx_wake.as_raw_fd(),
319 events: libc::POLLIN,
320 revents: 0,
321 },
322 libc::pollfd {
323 fd: shared.proxy_wake.as_raw_fd(),
324 events: libc::POLLIN,
325 revents: 0,
326 },
327 ];
328 #[cfg(windows)]
329 let wait_context = match windows_stack_wait_context(&shared) {
330 Ok(context) => context,
331 Err(err) => {
332 tracing::error!(error = %err, "network poll loop: failed to create wait context");
333 return;
334 }
335 };
336
337 loop {
338 let now = smoltcp_now();
339
340 while let Some(frame) = device.stage_next_frame() {
342 if handle_gateway_icmp_echo(
343 frame,
344 &config,
345 &shared,
346 &network_policy,
347 platform_policy.as_deref(),
348 ) {
349 device.drop_staged_frame();
350 continue;
351 }
352
353 if icmp_relay.relay_outbound_if_echo(
354 frame,
355 &config,
356 &network_policy,
357 platform_policy.as_deref(),
358 ) {
359 device.drop_staged_frame();
360 continue;
361 }
362
363 match classify_frame(frame) {
364 FrameAction::TcpSyn { src, dst } => {
365 let allow = match DnsPortType::from_tcp(dst.port()) {
366 DnsPortType::Dns => true,
370 DnsPortType::EncryptedDns => {
379 if tls_state.is_some() {
380 true
381 } else {
382 tracing::debug!(%dst, "DoT port refused (TLS interception not configured); stub should fall back to TCP/53");
383 false
384 }
385 }
386 DnsPortType::AlternativeDns => {
392 tracing::debug!(%dst, "alternative-DNS TCP port refused; stub should fall back to TCP/53");
393 false
394 }
395 DnsPortType::Other => {
398 let platform_allows = platform_policy.as_deref().is_none_or(|policy| {
399 policy
400 .evaluate_egress(dst, Protocol::Tcp, &shared)
401 .is_allow()
402 });
403 platform_allows
404 && matches!(
405 network_policy.evaluate_egress_with_source(
406 dst,
407 Protocol::Tcp,
408 &shared,
409 HostnameSource::Deferred,
410 ),
411 EgressEvaluation::Allow | EgressEvaluation::DeferUntilHostname
412 )
413 }
414 };
415 if allow && !conn_tracker.has_socket_for(&src, &dst) {
416 conn_tracker.create_tcp_socket(src, dst, &mut sockets);
417 }
418 iface.poll_ingress_single(now, &mut device, &mut sockets);
421 }
422
423 FrameAction::UdpRelay { src, dst } => {
424 relay_udp_frame(
425 frame,
426 src,
427 dst,
428 &config,
429 &network_policy,
430 platform_policy.as_deref(),
431 &shared,
432 &mut port_publisher,
433 tls_state.as_deref(),
434 &mut udp_relay,
435 );
436 device.drop_staged_frame();
437 }
438
439 FrameAction::Ipv4UdpFragment => {
440 if let Some(datagram) = udp_fragments.push(frame) {
441 handle_reassembled_udp_datagram(
442 datagram,
443 &mut device,
444 &mut iface,
445 now,
446 &mut sockets,
447 &config,
448 &network_policy,
449 platform_policy.as_deref(),
450 &shared,
451 &mut port_publisher,
452 tls_state.as_deref(),
453 &mut udp_relay,
454 );
455 } else {
456 device.drop_staged_frame();
457 }
458 }
459
460 FrameAction::Ipv6UdpFragment => {
461 if let Some(datagram) = ipv6_udp_fragments.push(frame) {
462 handle_reassembled_udp_datagram(
463 datagram,
464 &mut device,
465 &mut iface,
466 now,
467 &mut sockets,
468 &config,
469 &network_policy,
470 platform_policy.as_deref(),
471 &shared,
472 &mut port_publisher,
473 tls_state.as_deref(),
474 &mut udp_relay,
475 );
476 } else {
477 device.drop_staged_frame();
478 }
479 }
480
481 FrameAction::Ipv6UnsupportedFragment => {
482 device.drop_staged_frame();
486 }
487
488 FrameAction::Dns | FrameAction::Passthrough => {
489 iface.poll_ingress_single(now, &mut device, &mut sockets);
491 }
492 }
493 }
494
495 loop {
499 let result = iface.poll_egress(now, &mut device, &mut sockets);
500 if matches!(result, smoltcp::iface::PollResult::None) {
501 break;
502 }
503 }
504 iface.poll_maintenance(now);
505
506 if device.frames_emitted.swap(false, Ordering::Relaxed) {
509 shared.rx_wake.wake();
510 }
511
512 conn_tracker.relay_data(&mut sockets);
517 dns_interceptor.process(&mut sockets);
518
519 port_publisher.accept_inbound(&mut iface, &mut sockets, &shared, &tokio_handle);
521 port_publisher.relay_data(&mut sockets);
522
523 let new_conns = conn_tracker.take_new_connections(&mut sockets);
525 for conn in new_conns {
526 if let Some(ref tls_state) = tls_state
527 && tls_state
528 .config
529 .intercepted_ports
530 .contains(&conn.dst.port())
531 {
532 let connect_target = resolve_tcp_host_target(conn.dst, config.gateway);
534 let connection_outbound_proxy = ResolvedOutboundProxy::select_for_destination(
535 &outbound_proxy,
536 conn.dst,
537 connect_target.primary(),
538 );
539 let proxy = TlsProxy::new(
540 conn.dst,
541 connect_target,
542 conn.from_smoltcp,
543 conn.to_smoltcp,
544 shared.clone(),
545 tls_state.clone(),
546 network_policy.clone(),
547 strict,
548 conn.proxy_connect,
549 connection_outbound_proxy,
550 );
551 tokio_handle.spawn(proxy.run());
552 continue;
553 }
554 if conn.dst.port() == 53 {
555 conn.proxy_connect.mark_connected();
562
563 let proxy = DnsTcpProxy::new(
573 conn.dst,
574 conn.from_smoltcp,
575 conn.to_smoltcp,
576 dns_forwarder_handle.clone(),
577 shared.clone(),
578 );
579 tokio_handle.spawn(proxy.run());
580 continue;
581 }
582 if conn.dst.port() == 853
583 && let Some(ref tls_state) = tls_state
584 {
585 conn.proxy_connect.mark_connected();
587
588 let proxy = DotProxy::new(
594 conn.dst,
595 conn.from_smoltcp,
596 conn.to_smoltcp,
597 dns_forwarder_handle.clone(),
598 tls_state.clone(),
599 shared.clone(),
600 );
601 tokio_handle.spawn(proxy.run());
602 continue;
603 }
604 let connect_target = resolve_tcp_host_target(conn.dst, config.gateway);
606 let connection_outbound_proxy = ResolvedOutboundProxy::select_for_destination(
607 &outbound_proxy,
608 conn.dst,
609 connect_target.primary(),
610 );
611 let proxy = TcpProxy::new(
612 conn.dst,
613 connect_target,
614 conn.from_smoltcp,
615 conn.to_smoltcp,
616 shared.clone(),
617 network_policy.clone(),
618 secrets.load(),
621 tls_state.clone(),
622 strict,
623 conn.proxy_connect,
624 connection_outbound_proxy,
625 );
626 tokio_handle.spawn(proxy.run());
627 }
628
629 if last_cleanup.elapsed() >= std::time::Duration::from_secs(1) {
632 conn_tracker.cleanup_closed(&mut sockets);
633 conn_tracker.trace_stats(&sockets);
634 port_publisher.cleanup_closed(&mut sockets);
635 udp_relay.cleanup_expired();
636 udp_fragments.cleanup_expired();
637 ipv6_udp_fragments.cleanup_expired();
638 shared.cleanup_resolved_hostnames();
639 last_cleanup = std::time::Instant::now();
640 }
641
642 loop {
645 let result = iface.poll_egress(now, &mut device, &mut sockets);
646 if matches!(result, smoltcp::iface::PollResult::None) {
647 break;
648 }
649 }
650
651 if device.frames_emitted.swap(false, Ordering::Relaxed) {
653 shared.rx_wake.wake();
654 }
655
656 let timeout_ms = iface
657 .poll_delay(now, &sockets)
658 .map(|d| d.total_millis().min(i32::MAX as u64) as i32)
659 .unwrap_or(100); #[cfg(unix)]
662 sleep_until_stack_wake(&shared, timeout_ms, &mut poll_fds);
663 #[cfg(windows)]
664 sleep_until_stack_wake_windows(&shared, timeout_ms, &wait_context);
665 }
666}
667
668#[cfg(unix)]
673fn sleep_until_stack_wake(shared: &SharedState, timeout_ms: i32, poll_fds: &mut [libc::pollfd; 2]) {
674 unsafe {
676 libc::poll(
677 poll_fds.as_mut_ptr(),
678 poll_fds.len() as libc::nfds_t,
679 timeout_ms,
680 );
681 }
682
683 if poll_fds[0].revents & libc::POLLIN != 0 {
684 shared.tx_wake.drain();
685 }
686 if poll_fds[1].revents & libc::POLLIN != 0 {
687 shared.proxy_wake.drain();
688 }
689}
690
691#[cfg(windows)]
692fn windows_stack_wait_context(shared: &SharedState) -> std::io::Result<WaitContext> {
693 let mut context = WaitContext::new();
694 context.add(
695 EventSource::waitable_handle(shared.tx_wake.as_raw_handle(), TX_WAKE_TOKEN),
696 EventSet::IN,
697 )?;
698 context.add(
699 EventSource::waitable_handle(shared.proxy_wake.as_raw_handle(), PROXY_WAKE_TOKEN),
700 EventSet::IN,
701 )?;
702 Ok(context)
703}
704
705#[cfg(windows)]
706fn sleep_until_stack_wake_windows(
707 shared: &SharedState,
708 timeout_ms: i32,
709 wait_context: &WaitContext,
710) {
711 let mut events = [WaitEvent::default(); 2];
712 let count = match wait_context.wait(timeout_ms, &mut events) {
713 Ok(count) => count,
714 Err(err) => {
715 tracing::warn!(error = %err, "network poll loop: wait failed");
716 return;
717 }
718 };
719
720 for event in events.iter().take(count) {
721 match event.token() {
722 TX_WAKE_TOKEN => shared.tx_wake.drain(),
723 PROXY_WAKE_TOKEN => shared.proxy_wake.drain(),
724 token => tracing::warn!(token, "network poll loop: unknown wake token"),
725 }
726 }
727}
728
729#[allow(clippy::too_many_arguments)]
731fn relay_udp_frame(
732 frame: &[u8],
733 src: SocketAddr,
734 dst: SocketAddr,
735 config: &PollLoopConfig,
736 network_policy: &NetworkPolicy,
737 platform_policy: Option<&NetworkPolicy>,
738 shared: &Arc<SharedState>,
739 port_publisher: &mut PortPublisher,
740 tls_state: Option<&TlsState>,
741 udp_relay: &mut UdpRelay,
742) {
743 if port_publisher.relay_udp_outbound(frame, src, dst) {
744 return;
745 }
746
747 if let Some(tls) = tls_state
749 && tls.config.intercepted_ports.contains(&dst.port())
750 && tls.config.block_quic_on_intercept
751 {
752 return;
753 }
754
755 match DnsPortType::from_udp(dst.port()) {
756 DnsPortType::Dns | DnsPortType::EncryptedDns => return,
759 DnsPortType::AlternativeDns => {
762 tracing::debug!(%dst, "alternative-DNS UDP port dropped; stub should fall back to UDP/53");
763 return;
764 }
765 DnsPortType::Other => {}
766 }
767
768 if platform_policy
770 .is_some_and(|policy| policy.evaluate_egress(dst, Protocol::Udp, shared).is_deny())
771 || network_policy
772 .evaluate_egress(dst, Protocol::Udp, shared)
773 .is_deny()
774 {
775 return;
776 }
777
778 let host_dst = resolve_host_dst(dst, config.gateway);
781 udp_relay.relay_outbound(frame, src, dst, host_dst);
782}
783
784#[allow(clippy::too_many_arguments)]
786fn handle_reassembled_udp_datagram(
787 datagram: ReassembledUdpDatagram,
788 device: &mut SmoltcpDevice,
789 iface: &mut Interface,
790 now: Instant,
791 sockets: &mut SocketSet<'_>,
792 config: &PollLoopConfig,
793 network_policy: &NetworkPolicy,
794 platform_policy: Option<&NetworkPolicy>,
795 shared: &Arc<SharedState>,
796 port_publisher: &mut PortPublisher,
797 tls_state: Option<&TlsState>,
798 udp_relay: &mut UdpRelay,
799) {
800 if DnsPortType::from_udp(datagram.dst.port()) == DnsPortType::Dns {
801 device.replace_staged_frame(datagram.frame);
802 iface.poll_ingress_single(now, device, sockets);
803 return;
804 }
805
806 relay_udp_frame(
807 &datagram.frame,
808 datagram.src,
809 datagram.dst,
810 config,
811 network_policy,
812 platform_policy,
813 shared,
814 port_publisher,
815 tls_state,
816 udp_relay,
817 );
818 device.drop_staged_frame();
819}
820
821fn resolve_tcp_host_target(dst: SocketAddr, gateway: GatewayIps) -> UpstreamTcpTarget {
827 let port = dst.port();
828 match dst.ip() {
829 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => UpstreamTcpTarget::with_fallback(
830 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port),
831 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), port),
832 ),
833 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => UpstreamTcpTarget::with_fallback(
834 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), port),
835 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port),
836 ),
837 _ => UpstreamTcpTarget::direct(dst),
838 }
839}
840
841pub(crate) fn resolve_host_dst(dst: SocketAddr, gateway: GatewayIps) -> SocketAddr {
851 match dst.ip() {
852 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => {
853 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst.port())
854 }
855 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => {
856 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), dst.port())
857 }
858 _ => dst,
859 }
860}
861
862fn smoltcp_now() -> Instant {
868 static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
869 let epoch = EPOCH.get_or_init(std::time::Instant::now);
870 let elapsed = epoch.elapsed();
871 Instant::from_millis(elapsed.as_millis() as i64)
872}
873
874fn handle_gateway_icmp_echo(
881 frame: &[u8],
882 config: &PollLoopConfig,
883 shared: &SharedState,
884 network_policy: &NetworkPolicy,
885 platform_policy: Option<&NetworkPolicy>,
886) -> bool {
887 let Ok(eth) = EthernetFrame::new_checked(frame) else {
888 return false;
889 };
890
891 let reply = match eth.ethertype() {
892 EthernetProtocol::Ipv4 => gateway_icmpv4_echo_reply(ð, config),
893 EthernetProtocol::Ipv6 => gateway_icmpv6_echo_reply(ð, config),
894 _ => None,
895 };
896 let Some(reply) = reply else {
897 return false;
898 };
899
900 let tenant_denied = network_policy
903 .evaluate_egress_ip(reply.dst, reply.protocol, shared)
904 .is_deny();
905 let platform_denied = platform_policy.is_some_and(|policy| {
906 policy
907 .evaluate_egress_ip(reply.dst, reply.protocol, shared)
908 .is_deny()
909 });
910 if tenant_denied || platform_denied {
911 tracing::debug!(
912 dst = %reply.dst,
913 tenant_denied,
914 platform_denied,
915 "gateway ICMP echo denied by policy",
916 );
917 return true;
918 }
919
920 shared.push_rx_frame_and_wake(reply.frame);
921
922 true
923}
924
925fn gateway_icmpv4_echo_reply(
927 eth: &EthernetFrame<&[u8]>,
928 config: &PollLoopConfig,
929) -> Option<GatewayIcmpReply> {
930 let gateway_ipv4 = config.gateway.ipv4?;
931 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
932 if ipv4.dst_addr() != gateway_ipv4 || ipv4.next_header() != IpProtocol::Icmp {
933 return None;
934 }
935
936 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).ok()?;
937 let Icmpv4Repr::EchoRequest {
938 ident,
939 seq_no,
940 data,
941 } = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default()).ok()?
942 else {
943 return None;
944 };
945
946 let ipv4_repr = Ipv4Repr {
947 src_addr: gateway_ipv4,
948 dst_addr: ipv4.src_addr(),
949 next_header: IpProtocol::Icmp,
950 payload_len: 8 + data.len(),
951 hop_limit: 64,
952 };
953 let icmp_repr = Icmpv4Repr::EchoReply {
954 ident,
955 seq_no,
956 data,
957 };
958 let mut reply = vec![0u8; 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len()];
959
960 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
961 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
962 reply_eth.set_dst_addr(eth.src_addr());
963 reply_eth.set_ethertype(EthernetProtocol::Ipv4);
964
965 ipv4_repr.emit(
966 &mut Ipv4Packet::new_unchecked(&mut reply[14..34]),
967 &smoltcp::phy::ChecksumCapabilities::default(),
968 );
969 icmp_repr.emit(
970 &mut Icmpv4Packet::new_unchecked(&mut reply[34..]),
971 &smoltcp::phy::ChecksumCapabilities::default(),
972 );
973
974 Some(GatewayIcmpReply {
975 dst: IpAddr::V4(gateway_ipv4),
976 protocol: Protocol::Icmpv4,
977 frame: reply,
978 })
979}
980
981fn gateway_icmpv6_echo_reply(
983 eth: &EthernetFrame<&[u8]>,
984 config: &PollLoopConfig,
985) -> Option<GatewayIcmpReply> {
986 let gateway_ipv6 = config.gateway.ipv6?;
987 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
988 if ipv6.dst_addr() != gateway_ipv6 || ipv6.next_header() != IpProtocol::Icmpv6 {
989 return None;
990 }
991
992 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).ok()?;
993 let Icmpv6Repr::EchoRequest {
994 ident,
995 seq_no,
996 data,
997 } = Icmpv6Repr::parse(
998 &ipv6.src_addr(),
999 &ipv6.dst_addr(),
1000 &icmp,
1001 &smoltcp::phy::ChecksumCapabilities::default(),
1002 )
1003 .ok()?
1004 else {
1005 return None;
1006 };
1007
1008 let ipv6_repr = Ipv6Repr {
1009 src_addr: gateway_ipv6,
1010 dst_addr: ipv6.src_addr(),
1011 next_header: IpProtocol::Icmpv6,
1012 payload_len: icmp_repr_buffer_len_v6(data),
1013 hop_limit: 64,
1014 };
1015 let icmp_repr = Icmpv6Repr::EchoReply {
1016 ident,
1017 seq_no,
1018 data,
1019 };
1020 let ipv6_hdr_len = 40;
1021 let mut reply = vec![0u8; 14 + ipv6_hdr_len + icmp_repr.buffer_len()];
1022
1023 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
1024 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
1025 reply_eth.set_dst_addr(eth.src_addr());
1026 reply_eth.set_ethertype(EthernetProtocol::Ipv6);
1027
1028 ipv6_repr.emit(&mut Ipv6Packet::new_unchecked(&mut reply[14..54]));
1029 icmp_repr.emit(
1030 &gateway_ipv6,
1031 &ipv6.src_addr(),
1032 &mut Icmpv6Packet::new_unchecked(&mut reply[54..]),
1033 &smoltcp::phy::ChecksumCapabilities::default(),
1034 );
1035
1036 Some(GatewayIcmpReply {
1037 dst: IpAddr::V6(gateway_ipv6),
1038 protocol: Protocol::Icmpv6,
1039 frame: reply,
1040 })
1041}
1042
1043fn icmp_repr_buffer_len_v6(data: &[u8]) -> usize {
1044 Icmpv6Repr::EchoReply {
1045 ident: 0,
1046 seq_no: 0,
1047 data,
1048 }
1049 .buffer_len()
1050}
1051
1052fn classify_ipv4(payload: &[u8]) -> FrameAction {
1054 let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
1055 return FrameAction::Passthrough;
1056 };
1057 if is_ipv4_udp_fragment(&ipv4) {
1058 return FrameAction::Ipv4UdpFragment;
1059 }
1060 classify_transport(
1061 ipv4.next_header(),
1062 ipv4.src_addr().into(),
1063 ipv4.dst_addr().into(),
1064 ipv4.payload(),
1065 )
1066}
1067
1068fn classify_ipv6(payload: &[u8]) -> FrameAction {
1070 let Ok(ipv6) = Ipv6Packet::new_checked(payload) else {
1071 return FrameAction::Passthrough;
1072 };
1073 if is_ipv6_udp_fragment(&ipv6) {
1074 return FrameAction::Ipv6UdpFragment;
1075 }
1076 if is_ipv6_fragment(&ipv6) {
1077 return FrameAction::Ipv6UnsupportedFragment;
1078 }
1079 classify_transport(
1080 ipv6.next_header(),
1081 ipv6.src_addr().into(),
1082 ipv6.dst_addr().into(),
1083 ipv6.payload(),
1084 )
1085}
1086
1087fn classify_transport(
1089 protocol: IpProtocol,
1090 src_ip: std::net::IpAddr,
1091 dst_ip: std::net::IpAddr,
1092 transport_payload: &[u8],
1093) -> FrameAction {
1094 match protocol {
1095 IpProtocol::Tcp => {
1096 let Ok(tcp) = TcpPacket::new_checked(transport_payload) else {
1097 return FrameAction::Passthrough;
1098 };
1099 if tcp.syn() && !tcp.ack() {
1100 FrameAction::TcpSyn {
1101 src: SocketAddr::new(src_ip, tcp.src_port()),
1102 dst: SocketAddr::new(dst_ip, tcp.dst_port()),
1103 }
1104 } else {
1105 FrameAction::Passthrough
1106 }
1107 }
1108 IpProtocol::Udp => {
1109 let Ok(udp) = UdpPacket::new_checked(transport_payload) else {
1110 return FrameAction::Passthrough;
1111 };
1112 if DnsPortType::from_udp(udp.dst_port()) == DnsPortType::Dns {
1116 FrameAction::Dns
1117 } else {
1118 FrameAction::UdpRelay {
1119 src: SocketAddr::new(src_ip, udp.src_port()),
1120 dst: SocketAddr::new(dst_ip, udp.dst_port()),
1121 }
1122 }
1123 }
1124 _ => FrameAction::Passthrough, }
1126}
1127
1128#[cfg(test)]
1133mod tests {
1134 use super::*;
1135 use std::sync::Arc;
1136
1137 use smoltcp::phy::ChecksumCapabilities;
1138 use smoltcp::wire::{
1139 ArpOperation, ArpPacket, ArpRepr, EthernetRepr, Icmpv4Packet, Icmpv4Repr, Ipv4Repr,
1140 };
1141
1142 use super::super::{device::SmoltcpDevice, shared::SharedState};
1143 use crate::tcp::connection::NewConnection;
1144
1145 fn build_tcp_syn_frame(
1147 src_ip: [u8; 4],
1148 dst_ip: [u8; 4],
1149 src_port: u16,
1150 dst_port: u16,
1151 ) -> Vec<u8> {
1152 let mut frame = vec![0u8; 14 + 20 + 20]; frame[12] = 0x08; frame[13] = 0x00;
1157
1158 let ip = &mut frame[14..34];
1160 ip[0] = 0x45; let total_len = 40u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
1163 ip[6] = 0x40; ip[8] = 64; ip[9] = 6; ip[12..16].copy_from_slice(&src_ip);
1167 ip[16..20].copy_from_slice(&dst_ip);
1168
1169 let tcp = &mut frame[34..54];
1171 tcp[0..2].copy_from_slice(&src_port.to_be_bytes());
1172 tcp[2..4].copy_from_slice(&dst_port.to_be_bytes());
1173 tcp[12] = 0x50; tcp[13] = 0x02; frame
1177 }
1178
1179 fn build_udp_frame(src_ip: [u8; 4], dst_ip: [u8; 4], src_port: u16, dst_port: u16) -> Vec<u8> {
1181 let mut frame = vec![0u8; 14 + 20 + 8]; frame[12] = 0x08;
1185 frame[13] = 0x00;
1186
1187 let ip = &mut frame[14..34];
1189 ip[0] = 0x45;
1190 let total_len = 28u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
1192 ip[8] = 64;
1193 ip[9] = 17; ip[12..16].copy_from_slice(&src_ip);
1195 ip[16..20].copy_from_slice(&dst_ip);
1196
1197 let udp = &mut frame[34..42];
1199 udp[0..2].copy_from_slice(&src_port.to_be_bytes());
1200 udp[2..4].copy_from_slice(&dst_port.to_be_bytes());
1201 let udp_len = 8u16;
1202 udp[4..6].copy_from_slice(&udp_len.to_be_bytes());
1203
1204 frame
1205 }
1206
1207 fn build_icmpv4_echo_frame(
1209 src_mac: [u8; 6],
1210 dst_mac: [u8; 6],
1211 src_ip: [u8; 4],
1212 dst_ip: [u8; 4],
1213 ident: u16,
1214 seq_no: u16,
1215 data: &[u8],
1216 ) -> Vec<u8> {
1217 let ipv4_repr = Ipv4Repr {
1218 src_addr: Ipv4Addr::from(src_ip),
1219 dst_addr: Ipv4Addr::from(dst_ip),
1220 next_header: IpProtocol::Icmp,
1221 payload_len: 8 + data.len(),
1222 hop_limit: 64,
1223 };
1224 let icmp_repr = Icmpv4Repr::EchoRequest {
1225 ident,
1226 seq_no,
1227 data,
1228 };
1229 let frame_len = 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
1230 let mut frame = vec![0u8; frame_len];
1231
1232 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1233 EthernetRepr {
1234 src_addr: EthernetAddress(src_mac),
1235 dst_addr: EthernetAddress(dst_mac),
1236 ethertype: EthernetProtocol::Ipv4,
1237 }
1238 .emit(&mut eth_frame);
1239
1240 ipv4_repr.emit(
1241 &mut Ipv4Packet::new_unchecked(&mut frame[14..34]),
1242 &ChecksumCapabilities::default(),
1243 );
1244 icmp_repr.emit(
1245 &mut Icmpv4Packet::new_unchecked(&mut frame[34..]),
1246 &ChecksumCapabilities::default(),
1247 );
1248
1249 frame
1250 }
1251
1252 fn build_arp_request_frame(src_mac: [u8; 6], src_ip: [u8; 4], target_ip: [u8; 4]) -> Vec<u8> {
1254 let mut frame = vec![0u8; 14 + 28];
1255
1256 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1257 EthernetRepr {
1258 src_addr: EthernetAddress(src_mac),
1259 dst_addr: EthernetAddress([0xff; 6]),
1260 ethertype: EthernetProtocol::Arp,
1261 }
1262 .emit(&mut eth_frame);
1263
1264 ArpRepr::EthernetIpv4 {
1265 operation: ArpOperation::Request,
1266 source_hardware_addr: EthernetAddress(src_mac),
1267 source_protocol_addr: Ipv4Addr::from(src_ip),
1268 target_hardware_addr: EthernetAddress([0x00; 6]),
1269 target_protocol_addr: Ipv4Addr::from(target_ip),
1270 }
1271 .emit(&mut ArpPacket::new_unchecked(&mut frame[14..]));
1272
1273 frame
1274 }
1275
1276 #[test]
1277 fn classify_tcp_syn() {
1278 let frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1279 match classify_frame(&frame) {
1280 FrameAction::TcpSyn { src, dst } => {
1281 assert_eq!(
1282 src,
1283 SocketAddr::new(Ipv4Addr::new(10, 0, 0, 2).into(), 54321)
1284 );
1285 assert_eq!(
1286 dst,
1287 SocketAddr::new(Ipv4Addr::new(93, 184, 216, 34).into(), 443)
1288 );
1289 }
1290 _ => panic!("expected TcpSyn"),
1291 }
1292 }
1293
1294 #[test]
1295 fn classify_tcp_ack_is_passthrough() {
1296 let mut frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1297 frame[34 + 13] = 0x10; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1300 }
1301
1302 #[test]
1303 fn classify_udp_dns() {
1304 let frame = build_udp_frame([10, 0, 0, 2], [10, 0, 0, 1], 12345, 53);
1305 assert!(matches!(classify_frame(&frame), FrameAction::Dns));
1306 }
1307
1308 #[test]
1309 fn classify_udp_non_dns() {
1310 let frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1311 match classify_frame(&frame) {
1312 FrameAction::UdpRelay { src, dst } => {
1313 assert_eq!(src.port(), 12345);
1314 assert_eq!(dst.port(), 443);
1315 }
1316 _ => panic!("expected UdpRelay"),
1317 }
1318 }
1319
1320 #[test]
1321 fn classify_ipv4_udp_fragment() {
1322 let mut frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1323 frame[14 + 6] = 0x20; assert!(matches!(
1325 classify_frame(&frame),
1326 FrameAction::Ipv4UdpFragment
1327 ));
1328 }
1329
1330 #[test]
1331 fn classify_ipv6_udp_fragment() {
1332 let mut frame = vec![0u8; 14 + 40 + 8];
1333
1334 frame[12] = 0x86;
1335 frame[13] = 0xdd;
1336
1337 let ip = &mut frame[14..54];
1338 ip[0] = 0x60;
1339 ip[4..6].copy_from_slice(&8u16.to_be_bytes());
1340 ip[6] = u8::from(IpProtocol::Ipv6Frag);
1341 ip[7] = 64;
1342 ip[8..24].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1343 ip[24..40].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1344
1345 let fragment = &mut frame[54..62];
1346 fragment[0] = u8::from(IpProtocol::Udp);
1347 fragment[3] = 1; assert!(matches!(
1350 classify_frame(&frame),
1351 FrameAction::Ipv6UdpFragment
1352 ));
1353 }
1354
1355 #[test]
1356 fn classify_ipv6_non_udp_fragment_is_unsupported() {
1357 let mut frame = vec![0u8; 14 + 40 + 8];
1358
1359 frame[12] = 0x86;
1360 frame[13] = 0xdd;
1361
1362 let ip = &mut frame[14..54];
1363 ip[0] = 0x60;
1364 ip[4..6].copy_from_slice(&8u16.to_be_bytes());
1365 ip[6] = u8::from(IpProtocol::Ipv6Frag);
1366 ip[7] = 64;
1367 ip[8..24].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1368 ip[24..40].copy_from_slice(&Ipv6Addr::LOCALHOST.octets());
1369
1370 let fragment = &mut frame[54..62];
1371 fragment[0] = u8::from(IpProtocol::Tcp);
1372 fragment[3] = 1; assert!(matches!(
1375 classify_frame(&frame),
1376 FrameAction::Ipv6UnsupportedFragment
1377 ));
1378 }
1379
1380 #[test]
1381 fn classify_arp_is_passthrough() {
1382 let mut frame = vec![0u8; 42]; frame[12] = 0x08;
1384 frame[13] = 0x06; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1386 }
1387
1388 #[test]
1389 fn classify_garbage_is_passthrough() {
1390 assert!(matches!(classify_frame(&[]), FrameAction::Passthrough));
1391 assert!(matches!(classify_frame(&[0; 5]), FrameAction::Passthrough));
1392 }
1393
1394 #[test]
1395 fn gateway_replies_to_icmp_echo_requests() {
1396 fn drive_one_frame(
1397 device: &mut SmoltcpDevice,
1398 iface: &mut Interface,
1399 sockets: &mut SocketSet<'_>,
1400 shared: &Arc<SharedState>,
1401 poll_config: &PollLoopConfig,
1402 now: Instant,
1403 ) {
1404 let frame = device.stage_next_frame().expect("expected staged frame");
1405 if handle_gateway_icmp_echo(
1406 frame,
1407 poll_config,
1408 shared,
1409 &NetworkPolicy::allow_all(),
1410 None,
1411 ) {
1412 device.drop_staged_frame();
1413 return;
1414 }
1415 let _ = iface.poll_ingress_single(now, device, sockets);
1416 let _ = iface.poll_egress(now, device, sockets);
1417 }
1418
1419 let shared = Arc::new(SharedState::new(4));
1420 let poll_config = PollLoopConfig {
1421 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1422 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1423 gateway: GatewayIps {
1424 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1425 ipv6: Some(Ipv6Addr::LOCALHOST),
1426 },
1427 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1428 guest_ipv6: None,
1429 mtu: 1500,
1430 };
1431 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1432 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1433 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1434 let mut iface = create_interface(&mut device, &poll_config);
1435 let mut sockets = SocketSet::new(vec![]);
1436 let now = smoltcp_now();
1437
1438 shared
1441 .tx_ring
1442 .push(build_arp_request_frame(
1443 poll_config.guest_mac,
1444 guest_ipv4.octets(),
1445 gateway_ipv4.octets(),
1446 ))
1447 .unwrap();
1448 shared
1449 .tx_ring
1450 .push(build_icmpv4_echo_frame(
1451 poll_config.guest_mac,
1452 poll_config.gateway_mac,
1453 guest_ipv4.octets(),
1454 gateway_ipv4.octets(),
1455 0x1234,
1456 0xABCD,
1457 b"ping",
1458 ))
1459 .unwrap();
1460
1461 drive_one_frame(
1462 &mut device,
1463 &mut iface,
1464 &mut sockets,
1465 &shared,
1466 &poll_config,
1467 now,
1468 );
1469 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1470
1471 drive_one_frame(
1472 &mut device,
1473 &mut iface,
1474 &mut sockets,
1475 &shared,
1476 &poll_config,
1477 now,
1478 );
1479
1480 let reply = shared.rx_ring.pop().expect("expected ICMP echo reply");
1481 let eth = EthernetFrame::new_checked(&reply).expect("valid ethernet frame");
1482 assert_eq!(eth.src_addr(), EthernetAddress(poll_config.gateway_mac));
1483 assert_eq!(eth.dst_addr(), EthernetAddress(poll_config.guest_mac));
1484 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1485
1486 let ipv4 = Ipv4Packet::new_checked(eth.payload()).expect("valid IPv4 packet");
1487 assert_eq!(ipv4.src_addr(), gateway_ipv4);
1488 assert_eq!(ipv4.dst_addr(), guest_ipv4);
1489 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
1490
1491 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).expect("valid ICMP packet");
1492 let icmp_repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default())
1493 .expect("valid ICMP echo reply");
1494 assert_eq!(
1495 icmp_repr,
1496 Icmpv4Repr::EchoReply {
1497 ident: 0x1234,
1498 seq_no: 0xABCD,
1499 data: b"ping",
1500 }
1501 );
1502 }
1503
1504 #[test]
1505 fn gateway_icmp_echo_respects_deny_policy() {
1506 let shared = SharedState::new(4);
1507 let poll_config = PollLoopConfig {
1508 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1509 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1510 gateway: GatewayIps {
1511 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1512 ipv6: None,
1513 },
1514 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1515 guest_ipv6: None,
1516 mtu: 1500,
1517 };
1518 let policy = NetworkPolicy::builder().default_deny().build().unwrap();
1519 let frame = build_icmpv4_echo_frame(
1520 poll_config.guest_mac,
1521 poll_config.gateway_mac,
1522 poll_config.guest_ipv4.unwrap().octets(),
1523 poll_config.gateway.ipv4.unwrap().octets(),
1524 0x1234,
1525 0xABCD,
1526 b"ping",
1527 );
1528
1529 assert!(handle_gateway_icmp_echo(
1530 &frame,
1531 &poll_config,
1532 &shared,
1533 &policy,
1534 None,
1535 ));
1536 assert!(
1537 shared.rx_ring.pop().is_none(),
1538 "denied gateway ICMP should not queue a reply"
1539 );
1540 }
1541
1542 #[test]
1543 fn platform_public_floor_consumes_gateway_echo_without_replying() {
1544 let shared = SharedState::new(4);
1545 let gateway = Ipv4Addr::new(100, 96, 0, 1);
1546 let guest = Ipv4Addr::new(100, 96, 0, 2);
1547 shared.set_gateway_ips(Some(gateway), None);
1548 let config = PollLoopConfig {
1549 gateway_mac: [0x02, 0, 0, 0, 0, 1],
1550 guest_mac: [0x02, 0, 0, 0, 0, 2],
1551 gateway: GatewayIps {
1552 ipv4: Some(gateway),
1553 ipv6: None,
1554 },
1555 guest_ipv4: Some(guest),
1556 guest_ipv6: None,
1557 mtu: 1500,
1558 };
1559 let frame = build_icmpv4_echo_frame(
1560 config.guest_mac,
1561 config.gateway_mac,
1562 guest.octets(),
1563 gateway.octets(),
1564 1,
1565 1,
1566 b"ping",
1567 );
1568 let platform = NetworkPolicy::from_profiles([crate::policy::NetworkProfile::Public]);
1569
1570 assert!(handle_gateway_icmp_echo(
1571 &frame,
1572 &config,
1573 &shared,
1574 &NetworkPolicy::allow_all(),
1575 Some(&platform),
1576 ));
1577 assert!(shared.rx_ring.pop().is_none());
1578 }
1579
1580 fn test_gateway() -> GatewayIps {
1581 GatewayIps {
1582 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1583 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1584 }
1585 }
1586
1587 #[test]
1588 fn resolve_tcp_host_target_ipv4_can_fall_back_to_ipv6() {
1589 let gw = test_gateway();
1590 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1591
1592 assert_eq!(
1593 resolve_tcp_host_target(dst, gw),
1594 UpstreamTcpTarget::with_fallback(
1595 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
1596 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
1597 )
1598 );
1599 }
1600
1601 #[test]
1602 fn resolve_tcp_host_target_ipv6_can_fall_back_to_ipv4() {
1603 let gw = test_gateway();
1604 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1605
1606 assert_eq!(
1607 resolve_tcp_host_target(dst, gw),
1608 UpstreamTcpTarget::with_fallback(
1609 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
1610 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
1611 )
1612 );
1613 }
1614
1615 #[test]
1616 fn resolve_tcp_host_target_external_has_no_fallback() {
1617 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1618
1619 assert_eq!(
1620 resolve_tcp_host_target(dst, test_gateway()),
1621 UpstreamTcpTarget::direct(dst)
1622 );
1623 }
1624
1625 #[test]
1626 fn resolve_host_dst_matches_ipv4() {
1627 let gw = test_gateway();
1628 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1629 assert_eq!(
1630 resolve_host_dst(dst, gw),
1631 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
1632 );
1633 }
1634
1635 #[test]
1636 fn resolve_host_dst_matches_ipv6() {
1637 let gw = test_gateway();
1638 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1639 assert_eq!(
1640 resolve_host_dst(dst, gw),
1641 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
1642 );
1643 }
1644
1645 #[test]
1646 fn resolve_host_dst_passes_through_when_family_absent() {
1647 let gw = GatewayIps {
1648 ipv4: None,
1649 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1650 };
1651 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 1)), 8080);
1653 assert_eq!(resolve_host_dst(dst, gw), dst);
1654 }
1655
1656 #[test]
1657 fn resolve_host_dst_passes_through_non_gateway() {
1658 let gw = test_gateway();
1659 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1660 assert_eq!(resolve_host_dst(dst, gw), dst);
1661 }
1662
1663 #[test]
1664 fn outbound_proxy_is_skipped_for_host_destination() {
1665 let gw = test_gateway();
1666 let guest_dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1667 let connect_target = resolve_tcp_host_target(guest_dst, gw);
1668 let proxy = Some(Arc::new(ResolvedOutboundProxy::Socks5 {
1669 address: "192.0.2.1:1080".parse().unwrap(),
1670 credentials: None,
1671 }));
1672
1673 assert!(
1674 ResolvedOutboundProxy::select_for_destination(
1675 &proxy,
1676 guest_dst,
1677 connect_target.primary(),
1678 )
1679 .is_none()
1680 );
1681 }
1682
1683 #[test]
1684 fn outbound_proxy_is_preserved_for_external_destination() {
1685 let gw = test_gateway();
1686 let guest_dst = "198.51.100.10:443".parse().unwrap();
1687 let connect_target = resolve_tcp_host_target(guest_dst, gw);
1688 let proxy = Some(Arc::new(ResolvedOutboundProxy::Socks5 {
1689 address: "192.0.2.1:1080".parse().unwrap(),
1690 credentials: None,
1691 }));
1692
1693 assert!(
1694 ResolvedOutboundProxy::select_for_destination(
1695 &proxy,
1696 guest_dst,
1697 connect_target.primary(),
1698 )
1699 .is_some()
1700 );
1701 }
1702
1703 #[test]
1704 fn external_icmp_echo_requests_are_not_answered_locally() {
1705 fn drive_one_frame(
1706 device: &mut SmoltcpDevice,
1707 iface: &mut Interface,
1708 sockets: &mut SocketSet<'_>,
1709 shared: &Arc<SharedState>,
1710 poll_config: &PollLoopConfig,
1711 now: Instant,
1712 ) {
1713 let frame = device.stage_next_frame().expect("expected staged frame");
1714 if handle_gateway_icmp_echo(
1715 frame,
1716 poll_config,
1717 shared,
1718 &NetworkPolicy::allow_all(),
1719 None,
1720 ) {
1721 device.drop_staged_frame();
1722 return;
1723 }
1724 let _ = iface.poll_ingress_single(now, device, sockets);
1725 let _ = iface.poll_egress(now, device, sockets);
1726 }
1727
1728 let shared = Arc::new(SharedState::new(4));
1729 let poll_config = PollLoopConfig {
1730 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1731 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1732 gateway: GatewayIps {
1733 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1734 ipv6: Some(Ipv6Addr::LOCALHOST),
1735 },
1736 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1737 guest_ipv6: None,
1738 mtu: 1500,
1739 };
1740 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1741 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1742 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1743 let mut iface = create_interface(&mut device, &poll_config);
1744 let mut sockets = SocketSet::new(vec![]);
1745 let now = smoltcp_now();
1746
1747 shared
1748 .tx_ring
1749 .push(build_arp_request_frame(
1750 poll_config.guest_mac,
1751 guest_ipv4.octets(),
1752 gateway_ipv4.octets(),
1753 ))
1754 .unwrap();
1755 shared
1756 .tx_ring
1757 .push(build_icmpv4_echo_frame(
1758 poll_config.guest_mac,
1759 poll_config.gateway_mac,
1760 guest_ipv4.octets(),
1761 [142, 251, 216, 46],
1762 0x1234,
1763 0xABCD,
1764 b"ping",
1765 ))
1766 .unwrap();
1767
1768 drive_one_frame(
1769 &mut device,
1770 &mut iface,
1771 &mut sockets,
1772 &shared,
1773 &poll_config,
1774 now,
1775 );
1776 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1777
1778 drive_one_frame(
1779 &mut device,
1780 &mut iface,
1781 &mut sockets,
1782 &shared,
1783 &poll_config,
1784 now,
1785 );
1786 assert!(
1787 shared.rx_ring.pop().is_none(),
1788 "external ICMP should not be answered locally"
1789 );
1790 }
1791
1792 use smoltcp::socket::tcp;
1808 use smoltcp::wire::{TcpControl, TcpPacket, TcpRepr, TcpSeqNumber};
1809
1810 const GUEST_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
1811 const GATEWAY_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
1812 const GUEST_IP: [u8; 4] = [100, 96, 0, 2];
1813 const GATEWAY_IP: [u8; 4] = [100, 96, 0, 1];
1814 const SERVER_IP: [u8; 4] = [93, 184, 216, 34];
1816
1817 fn leak_poll_config() -> PollLoopConfig {
1818 PollLoopConfig {
1819 gateway_mac: GATEWAY_MAC,
1820 guest_mac: GUEST_MAC,
1821 gateway: GatewayIps {
1822 ipv4: Some(Ipv4Addr::from(GATEWAY_IP)),
1823 ipv6: None,
1824 },
1825 guest_ipv4: Some(Ipv4Addr::from(GUEST_IP)),
1826 guest_ipv6: None,
1827 mtu: 1500,
1828 }
1829 }
1830
1831 #[allow(clippy::too_many_arguments)]
1833 fn build_tcp_frame(
1834 src_port: u16,
1835 dst_port: u16,
1836 control: TcpControl,
1837 seq: i32,
1838 ack: Option<i32>,
1839 payload: &[u8],
1840 ) -> Vec<u8> {
1841 let src_ip = Ipv4Addr::from(GUEST_IP);
1842 let dst_ip = Ipv4Addr::from(SERVER_IP);
1843
1844 let tcp_repr = TcpRepr {
1845 src_port,
1846 dst_port,
1847 control,
1848 seq_number: TcpSeqNumber(seq),
1849 ack_number: ack.map(TcpSeqNumber),
1850 window_len: 65535,
1851 window_scale: None,
1852 max_seg_size: None,
1853 sack_permitted: false,
1854 sack_ranges: [None, None, None],
1855 timestamp: None,
1856 payload,
1857 };
1858 let ipv4_repr = Ipv4Repr {
1859 src_addr: src_ip,
1860 dst_addr: dst_ip,
1861 next_header: IpProtocol::Tcp,
1862 payload_len: tcp_repr.buffer_len(),
1863 hop_limit: 64,
1864 };
1865
1866 let frame_len = 14 + ipv4_repr.buffer_len() + tcp_repr.buffer_len();
1867 let mut frame = vec![0u8; frame_len];
1868
1869 let mut eth = EthernetFrame::new_unchecked(&mut frame);
1870 EthernetRepr {
1871 src_addr: EthernetAddress(GUEST_MAC),
1872 dst_addr: EthernetAddress(GATEWAY_MAC),
1873 ethertype: EthernetProtocol::Ipv4,
1874 }
1875 .emit(&mut eth);
1876
1877 let ip_end = 14 + ipv4_repr.buffer_len();
1878 ipv4_repr.emit(
1879 &mut Ipv4Packet::new_unchecked(&mut frame[14..ip_end]),
1880 &ChecksumCapabilities::default(),
1881 );
1882 tcp_repr.emit(
1883 &mut TcpPacket::new_unchecked(&mut frame[ip_end..]),
1884 &IpAddress::Ipv4(src_ip),
1885 &IpAddress::Ipv4(dst_ip),
1886 &ChecksumCapabilities::default(),
1887 );
1888
1889 frame
1890 }
1891
1892 fn ingress(
1894 frame: Vec<u8>,
1895 device: &mut SmoltcpDevice,
1896 iface: &mut Interface,
1897 sockets: &mut SocketSet<'_>,
1898 shared: &Arc<SharedState>,
1899 now: Instant,
1900 ) {
1901 shared.tx_ring.push(frame).unwrap();
1902 device.stage_next_frame().expect("frame should stage");
1903 iface.poll_ingress_single(now, device, sockets);
1904 loop {
1905 let r = iface.poll_egress(now, device, sockets);
1906 if matches!(r, smoltcp::iface::PollResult::None) {
1907 break;
1908 }
1909 }
1910 }
1911
1912 fn last_tcp_reply(shared: &Arc<SharedState>) -> Option<(i32, i32, bool, bool, bool)> {
1915 let mut out = None;
1916 while let Some(frame) = shared.rx_ring.pop() {
1917 if frame.len() < 34 {
1918 continue;
1919 }
1920 if frame[23] != 6 {
1922 continue; }
1924 let tcp = match TcpPacket::new_checked(&frame[34..]) {
1925 Ok(p) => p,
1926 Err(_) => continue,
1927 };
1928 out = Some((
1929 tcp.seq_number().0,
1930 tcp.ack_number().0,
1931 tcp.syn(),
1932 tcp.fin(),
1933 tcp.rst(),
1934 ));
1935 }
1936 out
1937 }
1938
1939 fn only_tcp_state(sockets: &SocketSet<'_>) -> Option<tcp::State> {
1941 for (_h, sock) in sockets.iter() {
1942 if let smoltcp::socket::Socket::Tcp(s) = sock {
1943 return Some(s.state());
1944 }
1945 }
1946 None
1947 }
1948
1949 fn handshake(
1952 tracker: &mut TcpConnectionTracker,
1953 device: &mut SmoltcpDevice,
1954 iface: &mut Interface,
1955 sockets: &mut SocketSet<'_>,
1956 shared: &Arc<SharedState>,
1957 now: Instant,
1958 guest_port: u16,
1959 ) -> (i32, i32) {
1960 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), guest_port);
1961 let dst = SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443);
1962
1963 ingress(
1966 build_arp_request_frame(GUEST_MAC, GUEST_IP, GATEWAY_IP),
1967 device,
1968 iface,
1969 sockets,
1970 shared,
1971 now,
1972 );
1973 let _ = shared.rx_ring.pop(); let guest_isn = 1000i32;
1976
1977 assert!(
1980 tracker.create_tcp_socket(src, dst, sockets),
1981 "socket creation should succeed under the limit"
1982 );
1983 ingress(
1984 build_tcp_frame(guest_port, 443, TcpControl::Syn, guest_isn, None, &[]),
1985 device,
1986 iface,
1987 sockets,
1988 shared,
1989 now,
1990 );
1991 let (server_isn, ack, is_syn, _, _) =
1992 last_tcp_reply(shared).expect("expected SYN-ACK from smoltcp");
1993 assert!(is_syn, "expected SYN flag on handshake reply");
1994 assert_eq!(ack, guest_isn + 1, "SYN-ACK should ack guest ISN+1");
1995
1996 ingress(
1998 build_tcp_frame(
1999 guest_port,
2000 443,
2001 TcpControl::None,
2002 guest_isn + 1,
2003 Some(server_isn + 1),
2004 &[],
2005 ),
2006 device,
2007 iface,
2008 sockets,
2009 shared,
2010 now,
2011 );
2012 assert_eq!(
2013 only_tcp_state(sockets),
2014 Some(tcp::State::Established),
2015 "socket should be ESTABLISHED after handshake",
2016 );
2017
2018 (server_isn, guest_isn + 1)
2019 }
2020
2021 fn establish(
2023 tracker: &mut TcpConnectionTracker,
2024 device: &mut SmoltcpDevice,
2025 iface: &mut Interface,
2026 sockets: &mut SocketSet<'_>,
2027 shared: &Arc<SharedState>,
2028 now: Instant,
2029 guest_port: u16,
2030 ) -> (i32, i32, Vec<NewConnection>) {
2031 let (server_isn, guest_seq) =
2032 handshake(tracker, device, iface, sockets, shared, now, guest_port);
2033
2034 let new_conns = tracker.take_new_connections(sockets);
2036 assert_eq!(
2037 new_conns.len(),
2038 1,
2039 "one new connection should be handed off"
2040 );
2041
2042 (server_isn, guest_seq, new_conns)
2043 }
2044
2045 #[test]
2046 fn guest_fin_propagates_half_close_without_killing_the_connection() {
2047 let shared = Arc::new(SharedState::new(64));
2048 let poll_config = leak_poll_config();
2049 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
2050 let mut iface = create_interface(&mut device, &poll_config);
2051 let mut sockets = SocketSet::new(vec![]);
2052 let mut tracker = TcpConnectionTracker::new(None);
2053 let now = smoltcp_now();
2054
2055 let (server_isn, guest_seq, mut new_conns) = establish(
2056 &mut tracker,
2057 &mut device,
2058 &mut iface,
2059 &mut sockets,
2060 &shared,
2061 now,
2062 54321,
2063 );
2064 let conn = new_conns.remove(0);
2067 let mut from_smoltcp = conn.from_smoltcp;
2068 let to_smoltcp = conn.to_smoltcp;
2069
2070 ingress(
2073 build_tcp_frame(
2074 54321,
2075 443,
2076 TcpControl::Fin,
2077 guest_seq,
2078 Some(server_isn + 1),
2079 &[],
2080 ),
2081 &mut device,
2082 &mut iface,
2083 &mut sockets,
2084 &shared,
2085 now,
2086 );
2087 assert_eq!(
2088 only_tcp_state(&sockets),
2089 Some(tcp::State::CloseWait),
2090 "guest FIN should move the smoltcp socket to CLOSE_WAIT",
2091 );
2092
2093 tracker.relay_data(&mut sockets);
2097 assert!(
2098 matches!(
2099 from_smoltcp.try_recv(),
2100 Err(tokio::sync::mpsc::error::TryRecvError::Disconnected)
2101 ),
2102 "guest FIN must propagate EOF to the proxy task",
2103 );
2104
2105 assert!(
2108 tracker.has_socket_for(
2109 &SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 54321),
2110 &SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443),
2111 ),
2112 "half-closed connection must stay tracked while the proxy runs",
2113 );
2114 let payload = b"pending server response";
2115 to_smoltcp
2116 .try_send(bytes::Bytes::from_static(payload))
2117 .expect("server → guest channel should accept data");
2118 while shared.rx_ring.pop().is_some() {} tracker.relay_data(&mut sockets);
2120 loop {
2121 let r = iface.poll_egress(now, &mut device, &mut sockets);
2122 if matches!(r, smoltcp::iface::PollResult::None) {
2123 break;
2124 }
2125 }
2126 let mut delivered = false;
2127 while let Some(frame) = shared.rx_ring.pop() {
2128 if frame.windows(payload.len()).any(|w| w == payload) {
2129 delivered = true;
2130 }
2131 }
2132 assert!(
2133 delivered,
2134 "server data must still reach a half-closed guest",
2135 );
2136 assert_eq!(
2137 only_tcp_state(&sockets),
2138 Some(tcp::State::CloseWait),
2139 "socket must stay open (CLOSE_WAIT) while the proxy is alive",
2140 );
2141 }
2142
2143 #[test]
2144 fn guest_fin_connection_is_reaped_after_proxy_exit() {
2145 let shared = Arc::new(SharedState::new(64));
2146 let poll_config = leak_poll_config();
2147 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
2148 let mut iface = create_interface(&mut device, &poll_config);
2149 let mut sockets = SocketSet::new(vec![]);
2150 let mut tracker = TcpConnectionTracker::new(None);
2151 let now = smoltcp_now();
2152
2153 let (server_isn, guest_seq, mut new_conns) = establish(
2154 &mut tracker,
2155 &mut device,
2156 &mut iface,
2157 &mut sockets,
2158 &shared,
2159 now,
2160 54321,
2161 );
2162 let conn = new_conns.remove(0);
2163 let from_smoltcp = conn.from_smoltcp;
2164 let to_smoltcp = conn.to_smoltcp;
2165
2166 ingress(
2168 build_tcp_frame(
2169 54321,
2170 443,
2171 TcpControl::Fin,
2172 guest_seq,
2173 Some(server_isn + 1),
2174 &[],
2175 ),
2176 &mut device,
2177 &mut iface,
2178 &mut sockets,
2179 &shared,
2180 now,
2181 );
2182 tracker.relay_data(&mut sockets);
2183
2184 drop(from_smoltcp);
2187 drop(to_smoltcp);
2188
2189 while shared.rx_ring.pop().is_some() {} tracker.relay_data(&mut sockets);
2193 loop {
2194 let r = iface.poll_egress(now, &mut device, &mut sockets);
2195 if matches!(r, smoltcp::iface::PollResult::None) {
2196 break;
2197 }
2198 }
2199 let (fin_seq, _, _, is_fin, _) =
2200 last_tcp_reply(&shared).expect("expected FIN toward the guest");
2201 assert!(is_fin, "proxy exit after guest FIN must FIN the guest side");
2202
2203 ingress(
2205 build_tcp_frame(
2206 54321,
2207 443,
2208 TcpControl::None,
2209 guest_seq + 1,
2210 Some(fin_seq + 1),
2211 &[],
2212 ),
2213 &mut device,
2214 &mut iface,
2215 &mut sockets,
2216 &shared,
2217 now,
2218 );
2219 tracker.relay_data(&mut sockets);
2220 tracker.cleanup_closed(&mut sockets);
2221
2222 assert!(
2225 !tracker.has_socket_for(
2226 &SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 54321),
2227 &SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443),
2228 ),
2229 "connection must be evicted after FIN + proxy exit",
2230 );
2231 assert_eq!(
2232 only_tcp_state(&sockets),
2233 None,
2234 "socket must be removed from the socket set",
2235 );
2236 }
2237
2238 #[test]
2239 fn guest_rst_is_cleaned_up() {
2240 let shared = Arc::new(SharedState::new(64));
2241 let poll_config = leak_poll_config();
2242 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
2243 let mut iface = create_interface(&mut device, &poll_config);
2244 let mut sockets = SocketSet::new(vec![]);
2245 let mut tracker = TcpConnectionTracker::new(None);
2246 let now = smoltcp_now();
2247
2248 let (server_isn, guest_seq, mut new_conns) = establish(
2249 &mut tracker,
2250 &mut device,
2251 &mut iface,
2252 &mut sockets,
2253 &shared,
2254 now,
2255 54322,
2256 );
2257 let conn = new_conns.remove(0);
2258 let mut from_smoltcp = conn.from_smoltcp;
2259 let _to_smoltcp = conn.to_smoltcp;
2260
2261 ingress(
2263 build_tcp_frame(
2264 54322,
2265 443,
2266 TcpControl::Rst,
2267 guest_seq,
2268 Some(server_isn + 1),
2269 &[],
2270 ),
2271 &mut device,
2272 &mut iface,
2273 &mut sockets,
2274 &shared,
2275 now,
2276 );
2277
2278 for _ in 0..8 {
2280 tracker.relay_data(&mut sockets);
2281 tracker.cleanup_closed(&mut sockets);
2282 let _ = iface.poll_egress(now, &mut device, &mut sockets);
2283 }
2284
2285 assert!(
2286 !tracker.has_socket_for(
2287 &SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 54322),
2288 &SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443),
2289 ),
2290 "RST connection should be evicted from the tracker",
2291 );
2292 assert!(
2295 matches!(
2296 from_smoltcp.try_recv(),
2297 Err(tokio::sync::mpsc::error::TryRecvError::Disconnected)
2298 ),
2299 "RST teardown must close the proxy channel (clean, no orphan)",
2300 );
2301 }
2302
2303 #[test]
2304 fn closed_slot_is_reused_under_pressure_but_pending_reset_is_sent_first() {
2305 let shared = Arc::new(SharedState::new(64));
2306 let config = leak_poll_config();
2307 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
2308 let mut iface = create_interface(&mut device, &config);
2309 let mut sockets = SocketSet::new(vec![]);
2310 let mut tracker = TcpConnectionTracker::new(NonZeroUsize::new(1));
2311 let now = smoltcp_now();
2312 handshake(
2313 &mut tracker,
2314 &mut device,
2315 &mut iface,
2316 &mut sockets,
2317 &shared,
2318 now,
2319 40000,
2320 );
2321 let handle = sockets.iter().next().unwrap().0;
2322 sockets.get_mut::<tcp::Socket>(handle).abort();
2323 tracker.cleanup_closed(&mut sockets);
2324 assert_eq!(
2325 only_tcp_state(&sockets),
2326 Some(tcp::State::Closed),
2327 "pending RST must not be lost during cleanup"
2328 );
2329 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 40001);
2330 let dst = SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443);
2331 assert!(
2332 !tracker.create_tcp_socket(src, dst, &mut sockets),
2333 "the pending reset still owns its socket budget"
2334 );
2335 loop {
2336 if matches!(
2337 iface.poll_egress(now, &mut device, &mut sockets),
2338 smoltcp::iface::PollResult::None
2339 ) {
2340 break;
2341 }
2342 }
2343 let (_, _, _, _, rst) = last_tcp_reply(&shared).expect("RST must reach guest");
2344 assert!(rst);
2345 assert!(tracker.create_tcp_socket(src, dst, &mut sockets));
2347 assert!(!tracker.has_socket_for(
2348 &SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 40000),
2349 &dst
2350 ));
2351 }
2352
2353 #[test]
2354 fn reset_during_handshake_does_not_leave_an_idle_listener_at_capacity() {
2355 let shared = Arc::new(SharedState::new(64));
2356 let config = leak_poll_config();
2357 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
2358 let mut iface = create_interface(&mut device, &config);
2359 let mut sockets = SocketSet::new(vec![]);
2360 let mut tracker = TcpConnectionTracker::new(NonZeroUsize::new(1));
2361 let now = smoltcp_now();
2362 ingress(
2363 build_arp_request_frame(GUEST_MAC, GUEST_IP, GATEWAY_IP),
2364 &mut device,
2365 &mut iface,
2366 &mut sockets,
2367 &shared,
2368 now,
2369 );
2370 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 40000);
2371 let dst = SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443);
2372 assert!(tracker.create_tcp_socket(src, dst, &mut sockets));
2373 ingress(
2374 build_tcp_frame(40000, 443, TcpControl::Syn, 1000, None, &[]),
2375 &mut device,
2376 &mut iface,
2377 &mut sockets,
2378 &shared,
2379 now,
2380 );
2381 let (server_seq, _, syn, _, _) = last_tcp_reply(&shared).unwrap();
2382 assert!(syn);
2383 assert_eq!(only_tcp_state(&sockets), Some(tcp::State::SynReceived));
2384 ingress(
2385 build_tcp_frame(40000, 443, TcpControl::Rst, 1001, Some(server_seq + 1), &[]),
2386 &mut device,
2387 &mut iface,
2388 &mut sockets,
2389 &shared,
2390 now,
2391 );
2392 assert_eq!(only_tcp_state(&sockets), Some(tcp::State::Listen));
2393 let next_src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 40001);
2394 assert!(tracker.create_tcp_socket(next_src, dst, &mut sockets));
2395 assert!(!tracker.has_socket_for(&src, &dst));
2396 }
2397
2398 #[test]
2399 fn connection_pressure_preserves_time_wait_protection() {
2400 let shared = Arc::new(SharedState::new(64));
2401 let config = leak_poll_config();
2402 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
2403 let mut iface = create_interface(&mut device, &config);
2404 let mut sockets = SocketSet::new(vec![]);
2405 let mut tracker = TcpConnectionTracker::new(NonZeroUsize::new(1));
2406 let now = smoltcp_now();
2407 let (_, guest_seq) = handshake(
2408 &mut tracker,
2409 &mut device,
2410 &mut iface,
2411 &mut sockets,
2412 &shared,
2413 now,
2414 40000,
2415 );
2416 let handle = sockets.iter().next().unwrap().0;
2417 sockets.get_mut::<tcp::Socket>(handle).close();
2418 loop {
2419 if matches!(
2420 iface.poll_egress(now, &mut device, &mut sockets),
2421 smoltcp::iface::PollResult::None
2422 ) {
2423 break;
2424 }
2425 }
2426 let (fin_seq, _, _, fin, _) = last_tcp_reply(&shared).unwrap();
2427 assert!(fin);
2428 ingress(
2429 build_tcp_frame(
2430 40000,
2431 443,
2432 TcpControl::None,
2433 guest_seq,
2434 Some(fin_seq + 1),
2435 &[],
2436 ),
2437 &mut device,
2438 &mut iface,
2439 &mut sockets,
2440 &shared,
2441 now,
2442 );
2443 ingress(
2444 build_tcp_frame(
2445 40000,
2446 443,
2447 TcpControl::Fin,
2448 guest_seq,
2449 Some(fin_seq + 1),
2450 &[],
2451 ),
2452 &mut device,
2453 &mut iface,
2454 &mut sockets,
2455 &shared,
2456 now,
2457 );
2458 assert_eq!(only_tcp_state(&sockets), Some(tcp::State::TimeWait));
2459 tracker.cleanup_closed(&mut sockets);
2460 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 40001);
2461 let dst = SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443);
2462 assert!(!tracker.create_tcp_socket(src, dst, &mut sockets));
2463 assert_eq!(only_tcp_state(&sockets), Some(tcp::State::TimeWait));
2464 }
2465
2466 #[test]
2467 fn full_connection_table_refuses_new_sockets() {
2468 let mut tracker = TcpConnectionTracker::new(NonZeroUsize::new(4));
2472 let mut sockets = SocketSet::new(vec![]);
2473 let shared = Arc::new(SharedState::new(64));
2474 let config = leak_poll_config();
2475 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
2476 let mut iface = create_interface(&mut device, &config);
2477 let now = smoltcp_now();
2478 let dst = SocketAddr::new(Ipv4Addr::from(SERVER_IP).into(), 443);
2479
2480 for port in 40000u16..40004 {
2481 handshake(
2482 &mut tracker,
2483 &mut device,
2484 &mut iface,
2485 &mut sockets,
2486 &shared,
2487 now,
2488 port,
2489 );
2490 }
2491 let src = SocketAddr::new(Ipv4Addr::from(GUEST_IP).into(), 40004);
2495 assert!(
2496 !tracker.create_tcp_socket(src, dst, &mut sockets),
2497 "creation at the limit must be refused",
2498 );
2499 }
2500
2501 #[test]
2502 fn guest_fin_before_proxy_spawn_is_handed_off() {
2503 let shared = Arc::new(SharedState::new(64));
2504 let poll_config = leak_poll_config();
2505 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
2506 let mut iface = create_interface(&mut device, &poll_config);
2507 let mut sockets = SocketSet::new(vec![]);
2508 let mut tracker = TcpConnectionTracker::new(None);
2509 let now = smoltcp_now();
2510 let guest_port = 54323;
2511 let (server_isn, guest_seq) = handshake(
2512 &mut tracker,
2513 &mut device,
2514 &mut iface,
2515 &mut sockets,
2516 &shared,
2517 now,
2518 guest_port,
2519 );
2520
2521 ingress(
2525 build_tcp_frame(
2526 guest_port,
2527 443,
2528 TcpControl::Fin,
2529 guest_seq,
2530 Some(server_isn + 1),
2531 &[],
2532 ),
2533 &mut device,
2534 &mut iface,
2535 &mut sockets,
2536 &shared,
2537 now,
2538 );
2539 assert_eq!(
2540 only_tcp_state(&sockets),
2541 Some(tcp::State::CloseWait),
2542 "guest FIN should arrive before the proxy handoff",
2543 );
2544
2545 let new_conns = tracker.take_new_connections(&mut sockets);
2546 assert_eq!(
2547 new_conns.len(),
2548 1,
2549 "a connection that reached CLOSE_WAIT still needs a proxy task",
2550 );
2551 }
2552}