1use std::collections::HashSet;
9use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
10use std::sync::Arc;
11use std::sync::atomic::Ordering;
12
13#[cfg(windows)]
14use msb_krun_utils::event::{EventSet, EventSource, WaitContext, WaitEvent};
15use smoltcp::iface::{Config, Interface, SocketSet};
16use smoltcp::time::Instant;
17
18use smoltcp::wire::{
19 EthernetAddress, EthernetFrame, EthernetProtocol, HardwareAddress, Icmpv4Packet, Icmpv4Repr,
20 Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr, IpProtocol, Ipv4Packet, Ipv4Repr, Ipv6Packet,
21 Ipv6Repr, TcpPacket, UdpPacket,
22};
23
24use crate::config::{DnsConfig, PublishedPort};
25use crate::conn::ConnectionTracker;
26use crate::device::SmoltcpDevice;
27use crate::dns::common::ports::DnsPortType;
28use crate::dns::{
29 interceptor::DnsInterceptor,
30 proxies::{dot::DotProxy, tcp::DnsTcpProxy},
31};
32use crate::icmp_relay::IcmpRelay;
33use crate::policy::{EgressEvaluation, HostnameSource, NetworkPolicy, Protocol};
34use crate::proxy;
35use crate::publisher::PortPublisher;
36use crate::secrets::config::SecretsConfig;
37use crate::shared::SharedState;
38use crate::tls::{proxy as tls_proxy, state::TlsState};
39use crate::udp_relay::UdpRelay;
40
41#[cfg(windows)]
46const TX_WAKE_TOKEN: u64 = 1;
47
48#[cfg(windows)]
49const PROXY_WAKE_TOKEN: u64 = 2;
50
51pub enum FrameAction {
62 TcpSyn { src: SocketAddr, dst: SocketAddr },
65
66 UdpRelay { src: SocketAddr, dst: SocketAddr },
69
70 Dns,
72
73 Passthrough,
76}
77
78pub struct PollLoopConfig {
81 pub gateway_mac: [u8; 6],
83 pub guest_mac: [u8; 6],
85 pub gateway: GatewayIps,
89 pub guest_ipv4: Option<Ipv4Addr>,
91 pub guest_ipv6: Option<Ipv6Addr>,
93 pub mtu: usize,
95}
96
97#[derive(Debug, Clone, Copy)]
102pub struct GatewayIps {
103 pub ipv4: Option<Ipv4Addr>,
105 pub ipv6: Option<Ipv6Addr>,
107}
108
109pub fn classify_frame(frame: &[u8]) -> FrameAction {
119 let Ok(eth) = EthernetFrame::new_checked(frame) else {
120 return FrameAction::Passthrough;
121 };
122
123 match eth.ethertype() {
124 EthernetProtocol::Ipv4 => classify_ipv4(eth.payload()),
125 EthernetProtocol::Ipv6 => classify_ipv6(eth.payload()),
126 _ => FrameAction::Passthrough, }
128}
129
130pub fn create_interface(device: &mut SmoltcpDevice, config: &PollLoopConfig) -> Interface {
137 let hw_addr = HardwareAddress::Ethernet(EthernetAddress(config.gateway_mac));
138 let iface_config = Config::new(hw_addr);
139 let mut iface = Interface::new(iface_config, device, smoltcp_now());
140
141 iface.update_ip_addrs(|addrs| {
143 if let Some(ipv4) = config.gateway.ipv4 {
144 addrs
145 .push(IpCidr::new(IpAddress::Ipv4(ipv4), 30)) .expect("failed to add gateway IPv4 address");
147 }
148 if let Some(ipv6) = config.gateway.ipv6 {
149 addrs
150 .push(IpCidr::new(IpAddress::Ipv6(ipv6), 64))
151 .expect("failed to add gateway IPv6 address");
152 }
153 });
154
155 if let Some(ipv4) = config.gateway.ipv4 {
157 iface
158 .routes_mut()
159 .add_default_ipv4_route(ipv4)
160 .expect("failed to add default IPv4 route");
161 }
162 if let Some(ipv6) = config.gateway.ipv6 {
163 iface
164 .routes_mut()
165 .add_default_ipv6_route(ipv6)
166 .expect("failed to add default IPv6 route");
167 }
168
169 iface.set_any_ip(true);
171
172 iface
173}
174
175#[allow(clippy::too_many_arguments)]
206pub fn smoltcp_poll_loop(
207 shared: Arc<SharedState>,
208 config: PollLoopConfig,
209 network_policy: NetworkPolicy,
210 dns_config: DnsConfig,
211 tls_state: Option<Arc<TlsState>>,
212 published_ports: Vec<PublishedPort>,
213 max_connections: Option<usize>,
214 tokio_handle: tokio::runtime::Handle,
215 secrets: Arc<SecretsConfig>,
216) {
217 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
218 let mut iface = create_interface(&mut device, &config);
219 let mut sockets = SocketSet::new(vec![]);
220 let mut conn_tracker = ConnectionTracker::new(max_connections);
221
222 let gateway_ips: Arc<HashSet<IpAddr>> = Arc::new(
228 config
229 .gateway
230 .ipv4
231 .map(IpAddr::V4)
232 .into_iter()
233 .chain(config.gateway.ipv6.map(IpAddr::V6))
234 .collect(),
235 );
236 shared.set_gateway_ips(config.gateway.ipv4, config.gateway.ipv6);
239 let network_policy = Arc::new(network_policy);
240
241 let (mut dns_interceptor, dns_forwarder_handle) = DnsInterceptor::new(
242 &mut sockets,
243 dns_config,
244 shared.clone(),
245 &tokio_handle,
246 gateway_ips,
247 network_policy.clone(),
248 config.gateway,
249 config.gateway_mac,
250 config.guest_mac,
251 );
252 let mut port_publisher = PortPublisher::new(
253 &published_ports,
254 config.guest_ipv4,
255 config.guest_ipv6,
256 config.gateway.ipv4,
257 config.gateway.ipv6,
258 config.gateway_mac,
259 config.guest_mac,
260 network_policy.clone(),
261 shared.clone(),
262 &tokio_handle,
263 );
264 let mut udp_relay = UdpRelay::new(
265 shared.clone(),
266 config.gateway_mac,
267 config.guest_mac,
268 tokio_handle.clone(),
269 );
270 let icmp_relay = IcmpRelay::new(
271 shared.clone(),
272 config.gateway_mac,
273 config.guest_mac,
274 tokio_handle.clone(),
275 );
276
277 let mut last_cleanup = std::time::Instant::now();
279
280 #[cfg(unix)]
282 let mut poll_fds = [
283 libc::pollfd {
284 fd: shared.tx_wake.as_raw_fd(),
285 events: libc::POLLIN,
286 revents: 0,
287 },
288 libc::pollfd {
289 fd: shared.proxy_wake.as_raw_fd(),
290 events: libc::POLLIN,
291 revents: 0,
292 },
293 ];
294 #[cfg(windows)]
295 let wait_context = match windows_stack_wait_context(&shared) {
296 Ok(context) => context,
297 Err(err) => {
298 tracing::error!(error = %err, "network poll loop: failed to create wait context");
299 return;
300 }
301 };
302
303 loop {
304 let now = smoltcp_now();
305
306 while let Some(frame) = device.stage_next_frame() {
308 if handle_gateway_icmp_echo(frame, &config, &shared) {
309 device.drop_staged_frame();
310 continue;
311 }
312
313 if icmp_relay.relay_outbound_if_echo(frame, &config, &network_policy) {
314 device.drop_staged_frame();
315 continue;
316 }
317
318 match classify_frame(frame) {
319 FrameAction::TcpSyn { src, dst } => {
320 let allow = match DnsPortType::from_tcp(dst.port()) {
321 DnsPortType::Dns => true,
325 DnsPortType::EncryptedDns => {
334 if tls_state.is_some() {
335 true
336 } else {
337 tracing::debug!(%dst, "DoT port refused (TLS interception not configured); stub should fall back to TCP/53");
338 false
339 }
340 }
341 DnsPortType::AlternativeDns => {
347 tracing::debug!(%dst, "alternative-DNS TCP port refused; stub should fall back to TCP/53");
348 false
349 }
350 DnsPortType::Other => match network_policy.evaluate_egress_with_source(
353 dst,
354 Protocol::Tcp,
355 &shared,
356 HostnameSource::Deferred,
357 ) {
358 EgressEvaluation::Allow | EgressEvaluation::DeferUntilHostname => true,
359 EgressEvaluation::Deny => false,
360 },
361 };
362 if allow && !conn_tracker.has_socket_for(&src, &dst) {
363 conn_tracker.create_tcp_socket(src, dst, &mut sockets);
364 }
365 iface.poll_ingress_single(now, &mut device, &mut sockets);
368 }
369
370 FrameAction::UdpRelay { src, dst } => {
371 if port_publisher.relay_udp_outbound(frame, src, dst) {
372 device.drop_staged_frame();
373 continue;
374 }
375
376 if let Some(ref tls) = tls_state
379 && tls.config.intercepted_ports.contains(&dst.port())
380 && tls.config.block_quic_on_intercept
381 {
382 device.drop_staged_frame();
383 continue;
384 }
385
386 match DnsPortType::from_udp(dst.port()) {
387 DnsPortType::Dns => {
391 device.drop_staged_frame();
392 continue;
393 }
394 DnsPortType::EncryptedDns => {
399 device.drop_staged_frame();
400 continue;
401 }
402 DnsPortType::AlternativeDns => {
405 tracing::debug!(%dst, "alternative-DNS UDP port dropped; stub should fall back to UDP/53");
406 device.drop_staged_frame();
407 continue;
408 }
409 DnsPortType::Other => {}
410 }
411
412 if network_policy
414 .evaluate_egress(dst, Protocol::Udp, &shared)
415 .is_deny()
416 {
417 device.drop_staged_frame();
418 continue;
419 }
420
421 let host_dst = resolve_host_dst(dst, config.gateway);
425 udp_relay.relay_outbound(frame, src, dst, host_dst);
426 device.drop_staged_frame();
427 }
428
429 FrameAction::Dns | FrameAction::Passthrough => {
430 iface.poll_ingress_single(now, &mut device, &mut sockets);
432 }
433 }
434 }
435
436 loop {
440 let result = iface.poll_egress(now, &mut device, &mut sockets);
441 if matches!(result, smoltcp::iface::PollResult::None) {
442 break;
443 }
444 }
445 iface.poll_maintenance(now);
446
447 if device.frames_emitted.swap(false, Ordering::Relaxed) {
450 shared.rx_wake.wake();
451 }
452
453 conn_tracker.relay_data(&mut sockets);
458 dns_interceptor.process(&mut sockets);
459
460 port_publisher.accept_inbound(&mut iface, &mut sockets, &shared, &tokio_handle);
462 port_publisher.relay_data(&mut sockets);
463
464 let new_conns = conn_tracker.take_new_connections(&mut sockets);
466 for conn in new_conns {
467 if let Some(ref tls_state) = tls_state
468 && tls_state
469 .config
470 .intercepted_ports
471 .contains(&conn.dst.port())
472 {
473 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
475 tls_proxy::spawn_tls_proxy(
476 &tokio_handle,
477 conn.dst,
478 connect_dst,
479 conn.from_smoltcp,
480 conn.to_smoltcp,
481 shared.clone(),
482 tls_state.clone(),
483 network_policy.clone(),
484 conn.proxy_connect,
485 );
486 continue;
487 }
488 if conn.dst.port() == 53 {
489 conn.proxy_connect.mark_connected();
496
497 DnsTcpProxy::spawn(
507 &tokio_handle,
508 conn.dst,
509 conn.from_smoltcp,
510 conn.to_smoltcp,
511 dns_forwarder_handle.clone(),
512 shared.clone(),
513 );
514 continue;
515 }
516 if conn.dst.port() == 853
517 && let Some(ref tls_state) = tls_state
518 {
519 conn.proxy_connect.mark_connected();
521
522 DotProxy::spawn(
528 &tokio_handle,
529 conn.dst,
530 conn.from_smoltcp,
531 conn.to_smoltcp,
532 dns_forwarder_handle.clone(),
533 tls_state.clone(),
534 shared.clone(),
535 );
536 continue;
537 }
538 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
540 proxy::spawn_tcp_proxy(
541 &tokio_handle,
542 conn.dst,
543 connect_dst,
544 conn.from_smoltcp,
545 conn.to_smoltcp,
546 shared.clone(),
547 network_policy.clone(),
548 secrets.clone(),
549 tls_state.clone(),
550 conn.proxy_connect,
551 );
552 }
553
554 if last_cleanup.elapsed() >= std::time::Duration::from_secs(1) {
557 conn_tracker.cleanup_closed(&mut sockets);
558 port_publisher.cleanup_closed(&mut sockets);
559 udp_relay.cleanup_expired();
560 shared.cleanup_resolved_hostnames();
561 last_cleanup = std::time::Instant::now();
562 }
563
564 loop {
567 let result = iface.poll_egress(now, &mut device, &mut sockets);
568 if matches!(result, smoltcp::iface::PollResult::None) {
569 break;
570 }
571 }
572
573 if device.frames_emitted.swap(false, Ordering::Relaxed) {
575 shared.rx_wake.wake();
576 }
577
578 let timeout_ms = iface
579 .poll_delay(now, &sockets)
580 .map(|d| d.total_millis().min(i32::MAX as u64) as i32)
581 .unwrap_or(100); #[cfg(unix)]
584 sleep_until_stack_wake(&shared, timeout_ms, &mut poll_fds);
585 #[cfg(windows)]
586 sleep_until_stack_wake_windows(&shared, timeout_ms, &wait_context);
587 }
588}
589
590#[cfg(unix)]
595fn sleep_until_stack_wake(shared: &SharedState, timeout_ms: i32, poll_fds: &mut [libc::pollfd; 2]) {
596 unsafe {
598 libc::poll(
599 poll_fds.as_mut_ptr(),
600 poll_fds.len() as libc::nfds_t,
601 timeout_ms,
602 );
603 }
604
605 if poll_fds[0].revents & libc::POLLIN != 0 {
606 shared.tx_wake.drain();
607 }
608 if poll_fds[1].revents & libc::POLLIN != 0 {
609 shared.proxy_wake.drain();
610 }
611}
612
613#[cfg(windows)]
614fn windows_stack_wait_context(shared: &SharedState) -> std::io::Result<WaitContext> {
615 let mut context = WaitContext::new();
616 context.add(
617 EventSource::waitable_handle(shared.tx_wake.as_raw_handle(), TX_WAKE_TOKEN),
618 EventSet::IN,
619 )?;
620 context.add(
621 EventSource::waitable_handle(shared.proxy_wake.as_raw_handle(), PROXY_WAKE_TOKEN),
622 EventSet::IN,
623 )?;
624 Ok(context)
625}
626
627#[cfg(windows)]
628fn sleep_until_stack_wake_windows(
629 shared: &SharedState,
630 timeout_ms: i32,
631 wait_context: &WaitContext,
632) {
633 let mut events = [WaitEvent::default(); 2];
634 let count = match wait_context.wait(timeout_ms, &mut events) {
635 Ok(count) => count,
636 Err(err) => {
637 tracing::warn!(error = %err, "network poll loop: wait failed");
638 return;
639 }
640 };
641
642 for event in events.iter().take(count) {
643 match event.token() {
644 TX_WAKE_TOKEN => shared.tx_wake.drain(),
645 PROXY_WAKE_TOKEN => shared.proxy_wake.drain(),
646 token => tracing::warn!(token, "network poll loop: unknown wake token"),
647 }
648 }
649}
650
651pub(crate) fn resolve_host_dst(dst: SocketAddr, gateway: GatewayIps) -> SocketAddr {
661 match dst.ip() {
662 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => {
663 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst.port())
664 }
665 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => {
666 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), dst.port())
667 }
668 _ => dst,
669 }
670}
671
672fn smoltcp_now() -> Instant {
678 static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
679 let epoch = EPOCH.get_or_init(std::time::Instant::now);
680 let elapsed = epoch.elapsed();
681 Instant::from_millis(elapsed.as_millis() as i64)
682}
683
684fn handle_gateway_icmp_echo(frame: &[u8], config: &PollLoopConfig, shared: &SharedState) -> bool {
691 let Ok(eth) = EthernetFrame::new_checked(frame) else {
692 return false;
693 };
694
695 let reply = match eth.ethertype() {
696 EthernetProtocol::Ipv4 => gateway_icmpv4_echo_reply(ð, config),
697 EthernetProtocol::Ipv6 => gateway_icmpv6_echo_reply(ð, config),
698 _ => None,
699 };
700 let Some(reply) = reply else {
701 return false;
702 };
703
704 shared.push_rx_frame_and_wake(reply);
705
706 true
707}
708
709fn gateway_icmpv4_echo_reply(
711 eth: &EthernetFrame<&[u8]>,
712 config: &PollLoopConfig,
713) -> Option<Vec<u8>> {
714 let gateway_ipv4 = config.gateway.ipv4?;
715 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
716 if ipv4.dst_addr() != gateway_ipv4 || ipv4.next_header() != IpProtocol::Icmp {
717 return None;
718 }
719
720 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).ok()?;
721 let Icmpv4Repr::EchoRequest {
722 ident,
723 seq_no,
724 data,
725 } = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default()).ok()?
726 else {
727 return None;
728 };
729
730 let ipv4_repr = Ipv4Repr {
731 src_addr: gateway_ipv4,
732 dst_addr: ipv4.src_addr(),
733 next_header: IpProtocol::Icmp,
734 payload_len: 8 + data.len(),
735 hop_limit: 64,
736 };
737 let icmp_repr = Icmpv4Repr::EchoReply {
738 ident,
739 seq_no,
740 data,
741 };
742 let mut reply = vec![0u8; 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len()];
743
744 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
745 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
746 reply_eth.set_dst_addr(eth.src_addr());
747 reply_eth.set_ethertype(EthernetProtocol::Ipv4);
748
749 ipv4_repr.emit(
750 &mut Ipv4Packet::new_unchecked(&mut reply[14..34]),
751 &smoltcp::phy::ChecksumCapabilities::default(),
752 );
753 icmp_repr.emit(
754 &mut Icmpv4Packet::new_unchecked(&mut reply[34..]),
755 &smoltcp::phy::ChecksumCapabilities::default(),
756 );
757
758 Some(reply)
759}
760
761fn gateway_icmpv6_echo_reply(
763 eth: &EthernetFrame<&[u8]>,
764 config: &PollLoopConfig,
765) -> Option<Vec<u8>> {
766 let gateway_ipv6 = config.gateway.ipv6?;
767 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
768 if ipv6.dst_addr() != gateway_ipv6 || ipv6.next_header() != IpProtocol::Icmpv6 {
769 return None;
770 }
771
772 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).ok()?;
773 let Icmpv6Repr::EchoRequest {
774 ident,
775 seq_no,
776 data,
777 } = Icmpv6Repr::parse(
778 &ipv6.src_addr(),
779 &ipv6.dst_addr(),
780 &icmp,
781 &smoltcp::phy::ChecksumCapabilities::default(),
782 )
783 .ok()?
784 else {
785 return None;
786 };
787
788 let ipv6_repr = Ipv6Repr {
789 src_addr: gateway_ipv6,
790 dst_addr: ipv6.src_addr(),
791 next_header: IpProtocol::Icmpv6,
792 payload_len: icmp_repr_buffer_len_v6(data),
793 hop_limit: 64,
794 };
795 let icmp_repr = Icmpv6Repr::EchoReply {
796 ident,
797 seq_no,
798 data,
799 };
800 let ipv6_hdr_len = 40;
801 let mut reply = vec![0u8; 14 + ipv6_hdr_len + icmp_repr.buffer_len()];
802
803 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
804 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
805 reply_eth.set_dst_addr(eth.src_addr());
806 reply_eth.set_ethertype(EthernetProtocol::Ipv6);
807
808 ipv6_repr.emit(&mut Ipv6Packet::new_unchecked(&mut reply[14..54]));
809 icmp_repr.emit(
810 &gateway_ipv6,
811 &ipv6.src_addr(),
812 &mut Icmpv6Packet::new_unchecked(&mut reply[54..]),
813 &smoltcp::phy::ChecksumCapabilities::default(),
814 );
815
816 Some(reply)
817}
818
819fn icmp_repr_buffer_len_v6(data: &[u8]) -> usize {
820 Icmpv6Repr::EchoReply {
821 ident: 0,
822 seq_no: 0,
823 data,
824 }
825 .buffer_len()
826}
827
828fn classify_ipv4(payload: &[u8]) -> FrameAction {
830 let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
831 return FrameAction::Passthrough;
832 };
833 classify_transport(
834 ipv4.next_header(),
835 ipv4.src_addr().into(),
836 ipv4.dst_addr().into(),
837 ipv4.payload(),
838 )
839}
840
841fn classify_ipv6(payload: &[u8]) -> FrameAction {
843 let Ok(ipv6) = Ipv6Packet::new_checked(payload) else {
844 return FrameAction::Passthrough;
845 };
846 classify_transport(
847 ipv6.next_header(),
848 ipv6.src_addr().into(),
849 ipv6.dst_addr().into(),
850 ipv6.payload(),
851 )
852}
853
854fn classify_transport(
856 protocol: IpProtocol,
857 src_ip: std::net::IpAddr,
858 dst_ip: std::net::IpAddr,
859 transport_payload: &[u8],
860) -> FrameAction {
861 match protocol {
862 IpProtocol::Tcp => {
863 let Ok(tcp) = TcpPacket::new_checked(transport_payload) else {
864 return FrameAction::Passthrough;
865 };
866 if tcp.syn() && !tcp.ack() {
867 FrameAction::TcpSyn {
868 src: SocketAddr::new(src_ip, tcp.src_port()),
869 dst: SocketAddr::new(dst_ip, tcp.dst_port()),
870 }
871 } else {
872 FrameAction::Passthrough
873 }
874 }
875 IpProtocol::Udp => {
876 let Ok(udp) = UdpPacket::new_checked(transport_payload) else {
877 return FrameAction::Passthrough;
878 };
879 if DnsPortType::from_udp(udp.dst_port()) == DnsPortType::Dns {
883 FrameAction::Dns
884 } else {
885 FrameAction::UdpRelay {
886 src: SocketAddr::new(src_ip, udp.src_port()),
887 dst: SocketAddr::new(dst_ip, udp.dst_port()),
888 }
889 }
890 }
891 _ => FrameAction::Passthrough, }
893}
894
895#[cfg(test)]
900mod tests {
901 use super::*;
902 use std::sync::Arc;
903
904 use smoltcp::phy::ChecksumCapabilities;
905 use smoltcp::wire::{
906 ArpOperation, ArpPacket, ArpRepr, EthernetRepr, Icmpv4Packet, Icmpv4Repr, Ipv4Repr,
907 };
908
909 use crate::device::SmoltcpDevice;
910 use crate::shared::SharedState;
911
912 fn build_tcp_syn_frame(
914 src_ip: [u8; 4],
915 dst_ip: [u8; 4],
916 src_port: u16,
917 dst_port: u16,
918 ) -> Vec<u8> {
919 let mut frame = vec![0u8; 14 + 20 + 20]; frame[12] = 0x08; frame[13] = 0x00;
924
925 let ip = &mut frame[14..34];
927 ip[0] = 0x45; let total_len = 40u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
930 ip[6] = 0x40; ip[8] = 64; ip[9] = 6; ip[12..16].copy_from_slice(&src_ip);
934 ip[16..20].copy_from_slice(&dst_ip);
935
936 let tcp = &mut frame[34..54];
938 tcp[0..2].copy_from_slice(&src_port.to_be_bytes());
939 tcp[2..4].copy_from_slice(&dst_port.to_be_bytes());
940 tcp[12] = 0x50; tcp[13] = 0x02; frame
944 }
945
946 fn build_udp_frame(src_ip: [u8; 4], dst_ip: [u8; 4], src_port: u16, dst_port: u16) -> Vec<u8> {
948 let mut frame = vec![0u8; 14 + 20 + 8]; frame[12] = 0x08;
952 frame[13] = 0x00;
953
954 let ip = &mut frame[14..34];
956 ip[0] = 0x45;
957 let total_len = 28u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
959 ip[8] = 64;
960 ip[9] = 17; ip[12..16].copy_from_slice(&src_ip);
962 ip[16..20].copy_from_slice(&dst_ip);
963
964 let udp = &mut frame[34..42];
966 udp[0..2].copy_from_slice(&src_port.to_be_bytes());
967 udp[2..4].copy_from_slice(&dst_port.to_be_bytes());
968 let udp_len = 8u16;
969 udp[4..6].copy_from_slice(&udp_len.to_be_bytes());
970
971 frame
972 }
973
974 fn build_icmpv4_echo_frame(
976 src_mac: [u8; 6],
977 dst_mac: [u8; 6],
978 src_ip: [u8; 4],
979 dst_ip: [u8; 4],
980 ident: u16,
981 seq_no: u16,
982 data: &[u8],
983 ) -> Vec<u8> {
984 let ipv4_repr = Ipv4Repr {
985 src_addr: Ipv4Addr::from(src_ip),
986 dst_addr: Ipv4Addr::from(dst_ip),
987 next_header: IpProtocol::Icmp,
988 payload_len: 8 + data.len(),
989 hop_limit: 64,
990 };
991 let icmp_repr = Icmpv4Repr::EchoRequest {
992 ident,
993 seq_no,
994 data,
995 };
996 let frame_len = 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
997 let mut frame = vec![0u8; frame_len];
998
999 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1000 EthernetRepr {
1001 src_addr: EthernetAddress(src_mac),
1002 dst_addr: EthernetAddress(dst_mac),
1003 ethertype: EthernetProtocol::Ipv4,
1004 }
1005 .emit(&mut eth_frame);
1006
1007 ipv4_repr.emit(
1008 &mut Ipv4Packet::new_unchecked(&mut frame[14..34]),
1009 &ChecksumCapabilities::default(),
1010 );
1011 icmp_repr.emit(
1012 &mut Icmpv4Packet::new_unchecked(&mut frame[34..]),
1013 &ChecksumCapabilities::default(),
1014 );
1015
1016 frame
1017 }
1018
1019 fn build_arp_request_frame(src_mac: [u8; 6], src_ip: [u8; 4], target_ip: [u8; 4]) -> Vec<u8> {
1021 let mut frame = vec![0u8; 14 + 28];
1022
1023 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
1024 EthernetRepr {
1025 src_addr: EthernetAddress(src_mac),
1026 dst_addr: EthernetAddress([0xff; 6]),
1027 ethertype: EthernetProtocol::Arp,
1028 }
1029 .emit(&mut eth_frame);
1030
1031 ArpRepr::EthernetIpv4 {
1032 operation: ArpOperation::Request,
1033 source_hardware_addr: EthernetAddress(src_mac),
1034 source_protocol_addr: Ipv4Addr::from(src_ip),
1035 target_hardware_addr: EthernetAddress([0x00; 6]),
1036 target_protocol_addr: Ipv4Addr::from(target_ip),
1037 }
1038 .emit(&mut ArpPacket::new_unchecked(&mut frame[14..]));
1039
1040 frame
1041 }
1042
1043 #[test]
1044 fn classify_tcp_syn() {
1045 let frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1046 match classify_frame(&frame) {
1047 FrameAction::TcpSyn { src, dst } => {
1048 assert_eq!(
1049 src,
1050 SocketAddr::new(Ipv4Addr::new(10, 0, 0, 2).into(), 54321)
1051 );
1052 assert_eq!(
1053 dst,
1054 SocketAddr::new(Ipv4Addr::new(93, 184, 216, 34).into(), 443)
1055 );
1056 }
1057 _ => panic!("expected TcpSyn"),
1058 }
1059 }
1060
1061 #[test]
1062 fn classify_tcp_ack_is_passthrough() {
1063 let mut frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
1064 frame[34 + 13] = 0x10; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1067 }
1068
1069 #[test]
1070 fn classify_udp_dns() {
1071 let frame = build_udp_frame([10, 0, 0, 2], [10, 0, 0, 1], 12345, 53);
1072 assert!(matches!(classify_frame(&frame), FrameAction::Dns));
1073 }
1074
1075 #[test]
1076 fn classify_udp_non_dns() {
1077 let frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1078 match classify_frame(&frame) {
1079 FrameAction::UdpRelay { src, dst } => {
1080 assert_eq!(src.port(), 12345);
1081 assert_eq!(dst.port(), 443);
1082 }
1083 _ => panic!("expected UdpRelay"),
1084 }
1085 }
1086
1087 #[test]
1088 fn classify_arp_is_passthrough() {
1089 let mut frame = vec![0u8; 42]; frame[12] = 0x08;
1091 frame[13] = 0x06; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1093 }
1094
1095 #[test]
1096 fn classify_garbage_is_passthrough() {
1097 assert!(matches!(classify_frame(&[]), FrameAction::Passthrough));
1098 assert!(matches!(classify_frame(&[0; 5]), FrameAction::Passthrough));
1099 }
1100
1101 #[test]
1102 fn gateway_replies_to_icmp_echo_requests() {
1103 fn drive_one_frame(
1104 device: &mut SmoltcpDevice,
1105 iface: &mut Interface,
1106 sockets: &mut SocketSet<'_>,
1107 shared: &Arc<SharedState>,
1108 poll_config: &PollLoopConfig,
1109 now: Instant,
1110 ) {
1111 let frame = device.stage_next_frame().expect("expected staged frame");
1112 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1113 device.drop_staged_frame();
1114 return;
1115 }
1116 let _ = iface.poll_ingress_single(now, device, sockets);
1117 let _ = iface.poll_egress(now, device, sockets);
1118 }
1119
1120 let shared = Arc::new(SharedState::new(4));
1121 let poll_config = PollLoopConfig {
1122 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1123 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1124 gateway: GatewayIps {
1125 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1126 ipv6: Some(Ipv6Addr::LOCALHOST),
1127 },
1128 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1129 guest_ipv6: None,
1130 mtu: 1500,
1131 };
1132 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1133 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1134 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1135 let mut iface = create_interface(&mut device, &poll_config);
1136 let mut sockets = SocketSet::new(vec![]);
1137 let now = smoltcp_now();
1138
1139 shared
1142 .tx_ring
1143 .push(build_arp_request_frame(
1144 poll_config.guest_mac,
1145 guest_ipv4.octets(),
1146 gateway_ipv4.octets(),
1147 ))
1148 .unwrap();
1149 shared
1150 .tx_ring
1151 .push(build_icmpv4_echo_frame(
1152 poll_config.guest_mac,
1153 poll_config.gateway_mac,
1154 guest_ipv4.octets(),
1155 gateway_ipv4.octets(),
1156 0x1234,
1157 0xABCD,
1158 b"ping",
1159 ))
1160 .unwrap();
1161
1162 drive_one_frame(
1163 &mut device,
1164 &mut iface,
1165 &mut sockets,
1166 &shared,
1167 &poll_config,
1168 now,
1169 );
1170 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1171
1172 drive_one_frame(
1173 &mut device,
1174 &mut iface,
1175 &mut sockets,
1176 &shared,
1177 &poll_config,
1178 now,
1179 );
1180
1181 let reply = shared.rx_ring.pop().expect("expected ICMP echo reply");
1182 let eth = EthernetFrame::new_checked(&reply).expect("valid ethernet frame");
1183 assert_eq!(eth.src_addr(), EthernetAddress(poll_config.gateway_mac));
1184 assert_eq!(eth.dst_addr(), EthernetAddress(poll_config.guest_mac));
1185 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1186
1187 let ipv4 = Ipv4Packet::new_checked(eth.payload()).expect("valid IPv4 packet");
1188 assert_eq!(ipv4.src_addr(), gateway_ipv4);
1189 assert_eq!(ipv4.dst_addr(), guest_ipv4);
1190 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
1191
1192 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).expect("valid ICMP packet");
1193 let icmp_repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default())
1194 .expect("valid ICMP echo reply");
1195 assert_eq!(
1196 icmp_repr,
1197 Icmpv4Repr::EchoReply {
1198 ident: 0x1234,
1199 seq_no: 0xABCD,
1200 data: b"ping",
1201 }
1202 );
1203 }
1204
1205 fn test_gateway() -> GatewayIps {
1206 GatewayIps {
1207 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1208 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1209 }
1210 }
1211
1212 #[test]
1213 fn resolve_host_dst_matches_ipv4() {
1214 let gw = test_gateway();
1215 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1216 assert_eq!(
1217 resolve_host_dst(dst, gw),
1218 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
1219 );
1220 }
1221
1222 #[test]
1223 fn resolve_host_dst_matches_ipv6() {
1224 let gw = test_gateway();
1225 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1226 assert_eq!(
1227 resolve_host_dst(dst, gw),
1228 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
1229 );
1230 }
1231
1232 #[test]
1233 fn resolve_host_dst_passes_through_when_family_absent() {
1234 let gw = GatewayIps {
1235 ipv4: None,
1236 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1237 };
1238 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 1)), 8080);
1240 assert_eq!(resolve_host_dst(dst, gw), dst);
1241 }
1242
1243 #[test]
1244 fn resolve_host_dst_passes_through_non_gateway() {
1245 let gw = test_gateway();
1246 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1247 assert_eq!(resolve_host_dst(dst, gw), dst);
1248 }
1249
1250 #[test]
1251 fn external_icmp_echo_requests_are_not_answered_locally() {
1252 fn drive_one_frame(
1253 device: &mut SmoltcpDevice,
1254 iface: &mut Interface,
1255 sockets: &mut SocketSet<'_>,
1256 shared: &Arc<SharedState>,
1257 poll_config: &PollLoopConfig,
1258 now: Instant,
1259 ) {
1260 let frame = device.stage_next_frame().expect("expected staged frame");
1261 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1262 device.drop_staged_frame();
1263 return;
1264 }
1265 let _ = iface.poll_ingress_single(now, device, sockets);
1266 let _ = iface.poll_egress(now, device, sockets);
1267 }
1268
1269 let shared = Arc::new(SharedState::new(4));
1270 let poll_config = PollLoopConfig {
1271 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1272 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1273 gateway: GatewayIps {
1274 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1275 ipv6: Some(Ipv6Addr::LOCALHOST),
1276 },
1277 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1278 guest_ipv6: None,
1279 mtu: 1500,
1280 };
1281 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1282 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1283 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1284 let mut iface = create_interface(&mut device, &poll_config);
1285 let mut sockets = SocketSet::new(vec![]);
1286 let now = smoltcp_now();
1287
1288 shared
1289 .tx_ring
1290 .push(build_arp_request_frame(
1291 poll_config.guest_mac,
1292 guest_ipv4.octets(),
1293 gateway_ipv4.octets(),
1294 ))
1295 .unwrap();
1296 shared
1297 .tx_ring
1298 .push(build_icmpv4_echo_frame(
1299 poll_config.guest_mac,
1300 poll_config.gateway_mac,
1301 guest_ipv4.octets(),
1302 [142, 251, 216, 46],
1303 0x1234,
1304 0xABCD,
1305 b"ping",
1306 ))
1307 .unwrap();
1308
1309 drive_one_frame(
1310 &mut device,
1311 &mut iface,
1312 &mut sockets,
1313 &shared,
1314 &poll_config,
1315 now,
1316 );
1317 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1318
1319 drive_one_frame(
1320 &mut device,
1321 &mut iface,
1322 &mut sockets,
1323 &shared,
1324 &poll_config,
1325 now,
1326 );
1327 assert!(
1328 shared.rx_ring.pop().is_none(),
1329 "external ICMP should not be answered locally"
1330 );
1331 }
1332}