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