1use std::collections::HashSet;
9use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
10use std::sync::Arc;
11use std::sync::atomic::Ordering;
12
13use smoltcp::iface::{Config, Interface, SocketSet};
14use smoltcp::time::Instant;
15
16use smoltcp::wire::{
17 EthernetAddress, EthernetFrame, EthernetProtocol, HardwareAddress, Icmpv4Packet, Icmpv4Repr,
18 Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr, IpProtocol, Ipv4Packet, Ipv4Repr, Ipv6Packet,
19 Ipv6Repr, TcpPacket, UdpPacket,
20};
21
22use crate::config::{DnsConfig, PublishedPort};
23use crate::conn::ConnectionTracker;
24use crate::device::SmoltcpDevice;
25use crate::dns::common::ports::DnsPortType;
26use crate::dns::{
27 interceptor::DnsInterceptor,
28 proxies::{dot::DotProxy, tcp::DnsTcpProxy},
29};
30use crate::icmp_relay::IcmpRelay;
31use crate::policy::{EgressEvaluation, HostnameSource, NetworkPolicy, Protocol};
32use crate::proxy;
33use crate::publisher::PortPublisher;
34use crate::shared::SharedState;
35use crate::tls::{proxy as tls_proxy, state::TlsState};
36use crate::udp_relay::UdpRelay;
37
38pub enum FrameAction {
49 TcpSyn { src: SocketAddr, dst: SocketAddr },
52
53 UdpRelay { src: SocketAddr, dst: SocketAddr },
56
57 Dns,
59
60 Passthrough,
63}
64
65pub struct PollLoopConfig {
68 pub gateway_mac: [u8; 6],
70 pub guest_mac: [u8; 6],
72 pub gateway: GatewayIps,
76 pub guest_ipv4: Option<Ipv4Addr>,
78 pub guest_ipv6: Option<Ipv6Addr>,
80 pub mtu: usize,
82}
83
84#[derive(Debug, Clone, Copy)]
89pub struct GatewayIps {
90 pub ipv4: Option<Ipv4Addr>,
92 pub ipv6: Option<Ipv6Addr>,
94}
95
96pub fn classify_frame(frame: &[u8]) -> FrameAction {
106 let Ok(eth) = EthernetFrame::new_checked(frame) else {
107 return FrameAction::Passthrough;
108 };
109
110 match eth.ethertype() {
111 EthernetProtocol::Ipv4 => classify_ipv4(eth.payload()),
112 EthernetProtocol::Ipv6 => classify_ipv6(eth.payload()),
113 _ => FrameAction::Passthrough, }
115}
116
117pub fn create_interface(device: &mut SmoltcpDevice, config: &PollLoopConfig) -> Interface {
124 let hw_addr = HardwareAddress::Ethernet(EthernetAddress(config.gateway_mac));
125 let iface_config = Config::new(hw_addr);
126 let mut iface = Interface::new(iface_config, device, smoltcp_now());
127
128 iface.update_ip_addrs(|addrs| {
130 if let Some(ipv4) = config.gateway.ipv4 {
131 addrs
132 .push(IpCidr::new(IpAddress::Ipv4(ipv4), 30)) .expect("failed to add gateway IPv4 address");
134 }
135 if let Some(ipv6) = config.gateway.ipv6 {
136 addrs
137 .push(IpCidr::new(IpAddress::Ipv6(ipv6), 64))
138 .expect("failed to add gateway IPv6 address");
139 }
140 });
141
142 if let Some(ipv4) = config.gateway.ipv4 {
144 iface
145 .routes_mut()
146 .add_default_ipv4_route(ipv4)
147 .expect("failed to add default IPv4 route");
148 }
149 if let Some(ipv6) = config.gateway.ipv6 {
150 iface
151 .routes_mut()
152 .add_default_ipv6_route(ipv6)
153 .expect("failed to add default IPv6 route");
154 }
155
156 iface.set_any_ip(true);
158
159 iface
160}
161
162#[allow(clippy::too_many_arguments)]
193pub fn smoltcp_poll_loop(
194 shared: Arc<SharedState>,
195 config: PollLoopConfig,
196 network_policy: NetworkPolicy,
197 dns_config: DnsConfig,
198 tls_state: Option<Arc<TlsState>>,
199 published_ports: Vec<PublishedPort>,
200 max_connections: Option<usize>,
201 tokio_handle: tokio::runtime::Handle,
202) {
203 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
204 let mut iface = create_interface(&mut device, &config);
205 let mut sockets = SocketSet::new(vec![]);
206 let mut conn_tracker = ConnectionTracker::new(max_connections);
207
208 let gateway_ips: Arc<HashSet<IpAddr>> = Arc::new(
214 config
215 .gateway
216 .ipv4
217 .map(IpAddr::V4)
218 .into_iter()
219 .chain(config.gateway.ipv6.map(IpAddr::V6))
220 .collect(),
221 );
222 shared.set_gateway_ips(config.gateway.ipv4, config.gateway.ipv6);
225 let network_policy = Arc::new(network_policy);
226
227 let (mut dns_interceptor, dns_forwarder_handle) = DnsInterceptor::new(
228 &mut sockets,
229 dns_config,
230 shared.clone(),
231 &tokio_handle,
232 gateway_ips,
233 network_policy.clone(),
234 config.gateway,
235 );
236 let mut port_publisher = PortPublisher::new(
237 &published_ports,
238 config.guest_ipv4,
239 config.guest_ipv6,
240 &tokio_handle,
241 );
242 let mut udp_relay = UdpRelay::new(
243 shared.clone(),
244 config.gateway_mac,
245 config.guest_mac,
246 tokio_handle.clone(),
247 );
248 let icmp_relay = IcmpRelay::new(
249 shared.clone(),
250 config.gateway_mac,
251 config.guest_mac,
252 tokio_handle.clone(),
253 );
254
255 let mut last_cleanup = std::time::Instant::now();
257
258 let mut poll_fds = [
260 libc::pollfd {
261 fd: shared.tx_wake.as_raw_fd(),
262 events: libc::POLLIN,
263 revents: 0,
264 },
265 libc::pollfd {
266 fd: shared.proxy_wake.as_raw_fd(),
267 events: libc::POLLIN,
268 revents: 0,
269 },
270 ];
271
272 loop {
273 let now = smoltcp_now();
274
275 while let Some(frame) = device.stage_next_frame() {
277 if handle_gateway_icmp_echo(frame, &config, &shared) {
278 device.drop_staged_frame();
279 continue;
280 }
281
282 if icmp_relay.relay_outbound_if_echo(frame, &config, &network_policy) {
283 device.drop_staged_frame();
284 continue;
285 }
286
287 match classify_frame(frame) {
288 FrameAction::TcpSyn { src, dst } => {
289 let allow = match DnsPortType::from_tcp(dst.port()) {
290 DnsPortType::Dns => true,
294 DnsPortType::EncryptedDns => {
303 if tls_state.is_some() {
304 true
305 } else {
306 tracing::debug!(%dst, "DoT port refused (TLS interception not configured); stub should fall back to TCP/53");
307 false
308 }
309 }
310 DnsPortType::AlternativeDns => {
316 tracing::debug!(%dst, "alternative-DNS TCP port refused; stub should fall back to TCP/53");
317 false
318 }
319 DnsPortType::Other => match network_policy.evaluate_egress_with_source(
322 dst,
323 Protocol::Tcp,
324 &shared,
325 HostnameSource::Deferred,
326 ) {
327 EgressEvaluation::Allow | EgressEvaluation::DeferUntilHostname => true,
328 EgressEvaluation::Deny => false,
329 },
330 };
331 if allow && !conn_tracker.has_socket_for(&src, &dst) {
332 conn_tracker.create_tcp_socket(src, dst, &mut sockets);
333 }
334 iface.poll_ingress_single(now, &mut device, &mut sockets);
337 }
338
339 FrameAction::UdpRelay { src, dst } => {
340 if let Some(ref tls) = tls_state
343 && tls.config.intercepted_ports.contains(&dst.port())
344 && tls.config.block_quic_on_intercept
345 {
346 device.drop_staged_frame();
347 continue;
348 }
349
350 match DnsPortType::from_udp(dst.port()) {
351 DnsPortType::Dns => {
355 device.drop_staged_frame();
356 continue;
357 }
358 DnsPortType::EncryptedDns => {
363 device.drop_staged_frame();
364 continue;
365 }
366 DnsPortType::AlternativeDns => {
369 tracing::debug!(%dst, "alternative-DNS UDP port dropped; stub should fall back to UDP/53");
370 device.drop_staged_frame();
371 continue;
372 }
373 DnsPortType::Other => {}
374 }
375
376 if network_policy
378 .evaluate_egress(dst, Protocol::Udp, &shared)
379 .is_deny()
380 {
381 device.drop_staged_frame();
382 continue;
383 }
384
385 let host_dst = resolve_host_dst(dst, config.gateway);
389 udp_relay.relay_outbound(frame, src, dst, host_dst);
390 device.drop_staged_frame();
391 }
392
393 FrameAction::Dns | FrameAction::Passthrough => {
394 iface.poll_ingress_single(now, &mut device, &mut sockets);
396 }
397 }
398 }
399
400 loop {
404 let result = iface.poll_egress(now, &mut device, &mut sockets);
405 if matches!(result, smoltcp::iface::PollResult::None) {
406 break;
407 }
408 }
409 iface.poll_maintenance(now);
410
411 if device.frames_emitted.swap(false, Ordering::Relaxed) {
414 shared.rx_wake.wake();
415 }
416
417 conn_tracker.relay_data(&mut sockets);
422 dns_interceptor.process(&mut sockets);
423
424 port_publisher.accept_inbound(&mut iface, &mut sockets, &shared, &tokio_handle);
426 port_publisher.relay_data(&mut sockets);
427
428 let new_conns = conn_tracker.take_new_connections(&mut sockets);
430 for conn in new_conns {
431 if let Some(ref tls_state) = tls_state
432 && tls_state
433 .config
434 .intercepted_ports
435 .contains(&conn.dst.port())
436 {
437 let conn_dst = resolve_host_dst(conn.dst, config.gateway);
439 tls_proxy::spawn_tls_proxy(
440 &tokio_handle,
441 conn_dst,
442 conn.from_smoltcp,
443 conn.to_smoltcp,
444 shared.clone(),
445 tls_state.clone(),
446 network_policy.clone(),
447 );
448 continue;
449 }
450 if conn.dst.port() == 53 {
451 DnsTcpProxy::spawn(
461 &tokio_handle,
462 conn.dst,
463 conn.from_smoltcp,
464 conn.to_smoltcp,
465 dns_forwarder_handle.clone(),
466 shared.clone(),
467 );
468 continue;
469 }
470 if conn.dst.port() == 853
471 && let Some(ref tls_state) = tls_state
472 {
473 DotProxy::spawn(
479 &tokio_handle,
480 conn.dst,
481 conn.from_smoltcp,
482 conn.to_smoltcp,
483 dns_forwarder_handle.clone(),
484 tls_state.clone(),
485 shared.clone(),
486 );
487 continue;
488 }
489 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
491 proxy::spawn_tcp_proxy(
492 &tokio_handle,
493 conn.dst,
494 connect_dst,
495 conn.from_smoltcp,
496 conn.to_smoltcp,
497 shared.clone(),
498 network_policy.clone(),
499 );
500 }
501
502 if last_cleanup.elapsed() >= std::time::Duration::from_secs(1) {
505 conn_tracker.cleanup_closed(&mut sockets);
506 port_publisher.cleanup_closed(&mut sockets);
507 udp_relay.cleanup_expired();
508 shared.cleanup_resolved_hostnames();
509 last_cleanup = std::time::Instant::now();
510 }
511
512 loop {
515 let result = iface.poll_egress(now, &mut device, &mut sockets);
516 if matches!(result, smoltcp::iface::PollResult::None) {
517 break;
518 }
519 }
520
521 if device.frames_emitted.swap(false, Ordering::Relaxed) {
523 shared.rx_wake.wake();
524 }
525
526 let timeout_ms = iface
527 .poll_delay(now, &sockets)
528 .map(|d| d.total_millis().min(i32::MAX as u64) as i32)
529 .unwrap_or(100); unsafe {
533 libc::poll(
534 poll_fds.as_mut_ptr(),
535 poll_fds.len() as libc::nfds_t,
536 timeout_ms,
537 );
538 }
539
540 if poll_fds[0].revents & libc::POLLIN != 0 {
542 shared.tx_wake.drain();
543 }
544 if poll_fds[1].revents & libc::POLLIN != 0 {
545 shared.proxy_wake.drain();
546 }
547 }
548}
549
550pub(crate) fn resolve_host_dst(dst: SocketAddr, gateway: GatewayIps) -> SocketAddr {
564 match dst.ip() {
565 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => {
566 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst.port())
567 }
568 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => {
569 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), dst.port())
570 }
571 _ => dst,
572 }
573}
574
575fn smoltcp_now() -> Instant {
581 static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
582 let epoch = EPOCH.get_or_init(std::time::Instant::now);
583 let elapsed = epoch.elapsed();
584 Instant::from_millis(elapsed.as_millis() as i64)
585}
586
587fn handle_gateway_icmp_echo(frame: &[u8], config: &PollLoopConfig, shared: &SharedState) -> bool {
594 let Ok(eth) = EthernetFrame::new_checked(frame) else {
595 return false;
596 };
597
598 let reply = match eth.ethertype() {
599 EthernetProtocol::Ipv4 => gateway_icmpv4_echo_reply(ð, config),
600 EthernetProtocol::Ipv6 => gateway_icmpv6_echo_reply(ð, config),
601 _ => None,
602 };
603 let Some(reply) = reply else {
604 return false;
605 };
606
607 let reply_len = reply.len();
608 if shared.rx_ring.push(reply).is_ok() {
609 shared.add_rx_bytes(reply_len);
610 shared.rx_wake.wake();
611 }
612
613 true
614}
615
616fn gateway_icmpv4_echo_reply(
618 eth: &EthernetFrame<&[u8]>,
619 config: &PollLoopConfig,
620) -> Option<Vec<u8>> {
621 let gateway_ipv4 = config.gateway.ipv4?;
622 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
623 if ipv4.dst_addr() != gateway_ipv4 || ipv4.next_header() != IpProtocol::Icmp {
624 return None;
625 }
626
627 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).ok()?;
628 let Icmpv4Repr::EchoRequest {
629 ident,
630 seq_no,
631 data,
632 } = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default()).ok()?
633 else {
634 return None;
635 };
636
637 let ipv4_repr = Ipv4Repr {
638 src_addr: gateway_ipv4,
639 dst_addr: ipv4.src_addr(),
640 next_header: IpProtocol::Icmp,
641 payload_len: 8 + data.len(),
642 hop_limit: 64,
643 };
644 let icmp_repr = Icmpv4Repr::EchoReply {
645 ident,
646 seq_no,
647 data,
648 };
649 let mut reply = vec![0u8; 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len()];
650
651 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
652 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
653 reply_eth.set_dst_addr(eth.src_addr());
654 reply_eth.set_ethertype(EthernetProtocol::Ipv4);
655
656 ipv4_repr.emit(
657 &mut Ipv4Packet::new_unchecked(&mut reply[14..34]),
658 &smoltcp::phy::ChecksumCapabilities::default(),
659 );
660 icmp_repr.emit(
661 &mut Icmpv4Packet::new_unchecked(&mut reply[34..]),
662 &smoltcp::phy::ChecksumCapabilities::default(),
663 );
664
665 Some(reply)
666}
667
668fn gateway_icmpv6_echo_reply(
670 eth: &EthernetFrame<&[u8]>,
671 config: &PollLoopConfig,
672) -> Option<Vec<u8>> {
673 let gateway_ipv6 = config.gateway.ipv6?;
674 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
675 if ipv6.dst_addr() != gateway_ipv6 || ipv6.next_header() != IpProtocol::Icmpv6 {
676 return None;
677 }
678
679 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).ok()?;
680 let Icmpv6Repr::EchoRequest {
681 ident,
682 seq_no,
683 data,
684 } = Icmpv6Repr::parse(
685 &ipv6.src_addr(),
686 &ipv6.dst_addr(),
687 &icmp,
688 &smoltcp::phy::ChecksumCapabilities::default(),
689 )
690 .ok()?
691 else {
692 return None;
693 };
694
695 let ipv6_repr = Ipv6Repr {
696 src_addr: gateway_ipv6,
697 dst_addr: ipv6.src_addr(),
698 next_header: IpProtocol::Icmpv6,
699 payload_len: icmp_repr_buffer_len_v6(data),
700 hop_limit: 64,
701 };
702 let icmp_repr = Icmpv6Repr::EchoReply {
703 ident,
704 seq_no,
705 data,
706 };
707 let ipv6_hdr_len = 40;
708 let mut reply = vec![0u8; 14 + ipv6_hdr_len + icmp_repr.buffer_len()];
709
710 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
711 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
712 reply_eth.set_dst_addr(eth.src_addr());
713 reply_eth.set_ethertype(EthernetProtocol::Ipv6);
714
715 ipv6_repr.emit(&mut Ipv6Packet::new_unchecked(&mut reply[14..54]));
716 icmp_repr.emit(
717 &gateway_ipv6,
718 &ipv6.src_addr(),
719 &mut Icmpv6Packet::new_unchecked(&mut reply[54..]),
720 &smoltcp::phy::ChecksumCapabilities::default(),
721 );
722
723 Some(reply)
724}
725
726fn icmp_repr_buffer_len_v6(data: &[u8]) -> usize {
727 Icmpv6Repr::EchoReply {
728 ident: 0,
729 seq_no: 0,
730 data,
731 }
732 .buffer_len()
733}
734
735fn classify_ipv4(payload: &[u8]) -> FrameAction {
737 let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
738 return FrameAction::Passthrough;
739 };
740 classify_transport(
741 ipv4.next_header(),
742 ipv4.src_addr().into(),
743 ipv4.dst_addr().into(),
744 ipv4.payload(),
745 )
746}
747
748fn classify_ipv6(payload: &[u8]) -> FrameAction {
750 let Ok(ipv6) = Ipv6Packet::new_checked(payload) else {
751 return FrameAction::Passthrough;
752 };
753 classify_transport(
754 ipv6.next_header(),
755 ipv6.src_addr().into(),
756 ipv6.dst_addr().into(),
757 ipv6.payload(),
758 )
759}
760
761fn classify_transport(
763 protocol: IpProtocol,
764 src_ip: std::net::IpAddr,
765 dst_ip: std::net::IpAddr,
766 transport_payload: &[u8],
767) -> FrameAction {
768 match protocol {
769 IpProtocol::Tcp => {
770 let Ok(tcp) = TcpPacket::new_checked(transport_payload) else {
771 return FrameAction::Passthrough;
772 };
773 if tcp.syn() && !tcp.ack() {
774 FrameAction::TcpSyn {
775 src: SocketAddr::new(src_ip, tcp.src_port()),
776 dst: SocketAddr::new(dst_ip, tcp.dst_port()),
777 }
778 } else {
779 FrameAction::Passthrough
780 }
781 }
782 IpProtocol::Udp => {
783 let Ok(udp) = UdpPacket::new_checked(transport_payload) else {
784 return FrameAction::Passthrough;
785 };
786 if DnsPortType::from_udp(udp.dst_port()) == DnsPortType::Dns {
790 FrameAction::Dns
791 } else {
792 FrameAction::UdpRelay {
793 src: SocketAddr::new(src_ip, udp.src_port()),
794 dst: SocketAddr::new(dst_ip, udp.dst_port()),
795 }
796 }
797 }
798 _ => FrameAction::Passthrough, }
800}
801
802#[cfg(test)]
807mod tests {
808 use super::*;
809 use std::sync::Arc;
810
811 use smoltcp::phy::ChecksumCapabilities;
812 use smoltcp::wire::{
813 ArpOperation, ArpPacket, ArpRepr, EthernetRepr, Icmpv4Packet, Icmpv4Repr, Ipv4Repr,
814 };
815
816 use crate::device::SmoltcpDevice;
817 use crate::shared::SharedState;
818
819 fn build_tcp_syn_frame(
821 src_ip: [u8; 4],
822 dst_ip: [u8; 4],
823 src_port: u16,
824 dst_port: u16,
825 ) -> Vec<u8> {
826 let mut frame = vec![0u8; 14 + 20 + 20]; frame[12] = 0x08; frame[13] = 0x00;
831
832 let ip = &mut frame[14..34];
834 ip[0] = 0x45; let total_len = 40u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
837 ip[6] = 0x40; ip[8] = 64; ip[9] = 6; ip[12..16].copy_from_slice(&src_ip);
841 ip[16..20].copy_from_slice(&dst_ip);
842
843 let tcp = &mut frame[34..54];
845 tcp[0..2].copy_from_slice(&src_port.to_be_bytes());
846 tcp[2..4].copy_from_slice(&dst_port.to_be_bytes());
847 tcp[12] = 0x50; tcp[13] = 0x02; frame
851 }
852
853 fn build_udp_frame(src_ip: [u8; 4], dst_ip: [u8; 4], src_port: u16, dst_port: u16) -> Vec<u8> {
855 let mut frame = vec![0u8; 14 + 20 + 8]; frame[12] = 0x08;
859 frame[13] = 0x00;
860
861 let ip = &mut frame[14..34];
863 ip[0] = 0x45;
864 let total_len = 28u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
866 ip[8] = 64;
867 ip[9] = 17; ip[12..16].copy_from_slice(&src_ip);
869 ip[16..20].copy_from_slice(&dst_ip);
870
871 let udp = &mut frame[34..42];
873 udp[0..2].copy_from_slice(&src_port.to_be_bytes());
874 udp[2..4].copy_from_slice(&dst_port.to_be_bytes());
875 let udp_len = 8u16;
876 udp[4..6].copy_from_slice(&udp_len.to_be_bytes());
877
878 frame
879 }
880
881 fn build_icmpv4_echo_frame(
883 src_mac: [u8; 6],
884 dst_mac: [u8; 6],
885 src_ip: [u8; 4],
886 dst_ip: [u8; 4],
887 ident: u16,
888 seq_no: u16,
889 data: &[u8],
890 ) -> Vec<u8> {
891 let ipv4_repr = Ipv4Repr {
892 src_addr: Ipv4Addr::from(src_ip),
893 dst_addr: Ipv4Addr::from(dst_ip),
894 next_header: IpProtocol::Icmp,
895 payload_len: 8 + data.len(),
896 hop_limit: 64,
897 };
898 let icmp_repr = Icmpv4Repr::EchoRequest {
899 ident,
900 seq_no,
901 data,
902 };
903 let frame_len = 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
904 let mut frame = vec![0u8; frame_len];
905
906 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
907 EthernetRepr {
908 src_addr: EthernetAddress(src_mac),
909 dst_addr: EthernetAddress(dst_mac),
910 ethertype: EthernetProtocol::Ipv4,
911 }
912 .emit(&mut eth_frame);
913
914 ipv4_repr.emit(
915 &mut Ipv4Packet::new_unchecked(&mut frame[14..34]),
916 &ChecksumCapabilities::default(),
917 );
918 icmp_repr.emit(
919 &mut Icmpv4Packet::new_unchecked(&mut frame[34..]),
920 &ChecksumCapabilities::default(),
921 );
922
923 frame
924 }
925
926 fn build_arp_request_frame(src_mac: [u8; 6], src_ip: [u8; 4], target_ip: [u8; 4]) -> Vec<u8> {
928 let mut frame = vec![0u8; 14 + 28];
929
930 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
931 EthernetRepr {
932 src_addr: EthernetAddress(src_mac),
933 dst_addr: EthernetAddress([0xff; 6]),
934 ethertype: EthernetProtocol::Arp,
935 }
936 .emit(&mut eth_frame);
937
938 ArpRepr::EthernetIpv4 {
939 operation: ArpOperation::Request,
940 source_hardware_addr: EthernetAddress(src_mac),
941 source_protocol_addr: Ipv4Addr::from(src_ip),
942 target_hardware_addr: EthernetAddress([0x00; 6]),
943 target_protocol_addr: Ipv4Addr::from(target_ip),
944 }
945 .emit(&mut ArpPacket::new_unchecked(&mut frame[14..]));
946
947 frame
948 }
949
950 #[test]
951 fn classify_tcp_syn() {
952 let frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
953 match classify_frame(&frame) {
954 FrameAction::TcpSyn { src, dst } => {
955 assert_eq!(
956 src,
957 SocketAddr::new(Ipv4Addr::new(10, 0, 0, 2).into(), 54321)
958 );
959 assert_eq!(
960 dst,
961 SocketAddr::new(Ipv4Addr::new(93, 184, 216, 34).into(), 443)
962 );
963 }
964 _ => panic!("expected TcpSyn"),
965 }
966 }
967
968 #[test]
969 fn classify_tcp_ack_is_passthrough() {
970 let mut frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
971 frame[34 + 13] = 0x10; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
974 }
975
976 #[test]
977 fn classify_udp_dns() {
978 let frame = build_udp_frame([10, 0, 0, 2], [10, 0, 0, 1], 12345, 53);
979 assert!(matches!(classify_frame(&frame), FrameAction::Dns));
980 }
981
982 #[test]
983 fn classify_udp_non_dns() {
984 let frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
985 match classify_frame(&frame) {
986 FrameAction::UdpRelay { src, dst } => {
987 assert_eq!(src.port(), 12345);
988 assert_eq!(dst.port(), 443);
989 }
990 _ => panic!("expected UdpRelay"),
991 }
992 }
993
994 #[test]
995 fn classify_arp_is_passthrough() {
996 let mut frame = vec![0u8; 42]; frame[12] = 0x08;
998 frame[13] = 0x06; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1000 }
1001
1002 #[test]
1003 fn classify_garbage_is_passthrough() {
1004 assert!(matches!(classify_frame(&[]), FrameAction::Passthrough));
1005 assert!(matches!(classify_frame(&[0; 5]), FrameAction::Passthrough));
1006 }
1007
1008 #[test]
1009 fn gateway_replies_to_icmp_echo_requests() {
1010 fn drive_one_frame(
1011 device: &mut SmoltcpDevice,
1012 iface: &mut Interface,
1013 sockets: &mut SocketSet<'_>,
1014 shared: &Arc<SharedState>,
1015 poll_config: &PollLoopConfig,
1016 now: Instant,
1017 ) {
1018 let frame = device.stage_next_frame().expect("expected staged frame");
1019 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1020 device.drop_staged_frame();
1021 return;
1022 }
1023 let _ = iface.poll_ingress_single(now, device, sockets);
1024 let _ = iface.poll_egress(now, device, sockets);
1025 }
1026
1027 let shared = Arc::new(SharedState::new(4));
1028 let poll_config = PollLoopConfig {
1029 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1030 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1031 gateway: GatewayIps {
1032 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1033 ipv6: Some(Ipv6Addr::LOCALHOST),
1034 },
1035 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1036 guest_ipv6: None,
1037 mtu: 1500,
1038 };
1039 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1040 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1041 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1042 let mut iface = create_interface(&mut device, &poll_config);
1043 let mut sockets = SocketSet::new(vec![]);
1044 let now = smoltcp_now();
1045
1046 shared
1049 .tx_ring
1050 .push(build_arp_request_frame(
1051 poll_config.guest_mac,
1052 guest_ipv4.octets(),
1053 gateway_ipv4.octets(),
1054 ))
1055 .unwrap();
1056 shared
1057 .tx_ring
1058 .push(build_icmpv4_echo_frame(
1059 poll_config.guest_mac,
1060 poll_config.gateway_mac,
1061 guest_ipv4.octets(),
1062 gateway_ipv4.octets(),
1063 0x1234,
1064 0xABCD,
1065 b"ping",
1066 ))
1067 .unwrap();
1068
1069 drive_one_frame(
1070 &mut device,
1071 &mut iface,
1072 &mut sockets,
1073 &shared,
1074 &poll_config,
1075 now,
1076 );
1077 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1078
1079 drive_one_frame(
1080 &mut device,
1081 &mut iface,
1082 &mut sockets,
1083 &shared,
1084 &poll_config,
1085 now,
1086 );
1087
1088 let reply = shared.rx_ring.pop().expect("expected ICMP echo reply");
1089 let eth = EthernetFrame::new_checked(&reply).expect("valid ethernet frame");
1090 assert_eq!(eth.src_addr(), EthernetAddress(poll_config.gateway_mac));
1091 assert_eq!(eth.dst_addr(), EthernetAddress(poll_config.guest_mac));
1092 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1093
1094 let ipv4 = Ipv4Packet::new_checked(eth.payload()).expect("valid IPv4 packet");
1095 assert_eq!(ipv4.src_addr(), gateway_ipv4);
1096 assert_eq!(ipv4.dst_addr(), guest_ipv4);
1097 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
1098
1099 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).expect("valid ICMP packet");
1100 let icmp_repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default())
1101 .expect("valid ICMP echo reply");
1102 assert_eq!(
1103 icmp_repr,
1104 Icmpv4Repr::EchoReply {
1105 ident: 0x1234,
1106 seq_no: 0xABCD,
1107 data: b"ping",
1108 }
1109 );
1110 }
1111
1112 fn test_gateway() -> GatewayIps {
1113 GatewayIps {
1114 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1115 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1116 }
1117 }
1118
1119 #[test]
1120 fn resolve_host_dst_matches_ipv4() {
1121 let gw = test_gateway();
1122 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1123 assert_eq!(
1124 resolve_host_dst(dst, gw),
1125 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
1126 );
1127 }
1128
1129 #[test]
1130 fn resolve_host_dst_matches_ipv6() {
1131 let gw = test_gateway();
1132 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1133 assert_eq!(
1134 resolve_host_dst(dst, gw),
1135 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
1136 );
1137 }
1138
1139 #[test]
1140 fn resolve_host_dst_passes_through_when_family_absent() {
1141 let gw = GatewayIps {
1142 ipv4: None,
1143 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1144 };
1145 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 1)), 8080);
1147 assert_eq!(resolve_host_dst(dst, gw), dst);
1148 }
1149
1150 #[test]
1151 fn resolve_host_dst_passes_through_non_gateway() {
1152 let gw = test_gateway();
1153 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1154 assert_eq!(resolve_host_dst(dst, gw), dst);
1155 }
1156
1157 #[test]
1158 fn external_icmp_echo_requests_are_not_answered_locally() {
1159 fn drive_one_frame(
1160 device: &mut SmoltcpDevice,
1161 iface: &mut Interface,
1162 sockets: &mut SocketSet<'_>,
1163 shared: &Arc<SharedState>,
1164 poll_config: &PollLoopConfig,
1165 now: Instant,
1166 ) {
1167 let frame = device.stage_next_frame().expect("expected staged frame");
1168 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1169 device.drop_staged_frame();
1170 return;
1171 }
1172 let _ = iface.poll_ingress_single(now, device, sockets);
1173 let _ = iface.poll_egress(now, device, sockets);
1174 }
1175
1176 let shared = Arc::new(SharedState::new(4));
1177 let poll_config = PollLoopConfig {
1178 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1179 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1180 gateway: GatewayIps {
1181 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1182 ipv6: Some(Ipv6Addr::LOCALHOST),
1183 },
1184 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1185 guest_ipv6: None,
1186 mtu: 1500,
1187 };
1188 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1189 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1190 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1191 let mut iface = create_interface(&mut device, &poll_config);
1192 let mut sockets = SocketSet::new(vec![]);
1193 let now = smoltcp_now();
1194
1195 shared
1196 .tx_ring
1197 .push(build_arp_request_frame(
1198 poll_config.guest_mac,
1199 guest_ipv4.octets(),
1200 gateway_ipv4.octets(),
1201 ))
1202 .unwrap();
1203 shared
1204 .tx_ring
1205 .push(build_icmpv4_echo_frame(
1206 poll_config.guest_mac,
1207 poll_config.gateway_mac,
1208 guest_ipv4.octets(),
1209 [142, 251, 216, 46],
1210 0x1234,
1211 0xABCD,
1212 b"ping",
1213 ))
1214 .unwrap();
1215
1216 drive_one_frame(
1217 &mut device,
1218 &mut iface,
1219 &mut sockets,
1220 &shared,
1221 &poll_config,
1222 now,
1223 );
1224 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1225
1226 drive_one_frame(
1227 &mut device,
1228 &mut iface,
1229 &mut sockets,
1230 &shared,
1231 &poll_config,
1232 now,
1233 );
1234 assert!(
1235 shared.rx_ring.pop().is_none(),
1236 "external ICMP should not be answered locally"
1237 );
1238 }
1239}