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 config.gateway_mac,
236 config.guest_mac,
237 );
238 let mut port_publisher = PortPublisher::new(
239 &published_ports,
240 config.guest_ipv4,
241 config.guest_ipv6,
242 config.gateway.ipv4,
243 config.gateway.ipv6,
244 config.gateway_mac,
245 config.guest_mac,
246 network_policy.clone(),
247 shared.clone(),
248 &tokio_handle,
249 );
250 let mut udp_relay = UdpRelay::new(
251 shared.clone(),
252 config.gateway_mac,
253 config.guest_mac,
254 tokio_handle.clone(),
255 );
256 let icmp_relay = IcmpRelay::new(
257 shared.clone(),
258 config.gateway_mac,
259 config.guest_mac,
260 tokio_handle.clone(),
261 );
262
263 let mut last_cleanup = std::time::Instant::now();
265
266 let mut poll_fds = [
268 libc::pollfd {
269 fd: shared.tx_wake.as_raw_fd(),
270 events: libc::POLLIN,
271 revents: 0,
272 },
273 libc::pollfd {
274 fd: shared.proxy_wake.as_raw_fd(),
275 events: libc::POLLIN,
276 revents: 0,
277 },
278 ];
279
280 loop {
281 let now = smoltcp_now();
282
283 while let Some(frame) = device.stage_next_frame() {
285 if handle_gateway_icmp_echo(frame, &config, &shared) {
286 device.drop_staged_frame();
287 continue;
288 }
289
290 if icmp_relay.relay_outbound_if_echo(frame, &config, &network_policy) {
291 device.drop_staged_frame();
292 continue;
293 }
294
295 match classify_frame(frame) {
296 FrameAction::TcpSyn { src, dst } => {
297 let allow = match DnsPortType::from_tcp(dst.port()) {
298 DnsPortType::Dns => true,
302 DnsPortType::EncryptedDns => {
311 if tls_state.is_some() {
312 true
313 } else {
314 tracing::debug!(%dst, "DoT port refused (TLS interception not configured); stub should fall back to TCP/53");
315 false
316 }
317 }
318 DnsPortType::AlternativeDns => {
324 tracing::debug!(%dst, "alternative-DNS TCP port refused; stub should fall back to TCP/53");
325 false
326 }
327 DnsPortType::Other => match network_policy.evaluate_egress_with_source(
330 dst,
331 Protocol::Tcp,
332 &shared,
333 HostnameSource::Deferred,
334 ) {
335 EgressEvaluation::Allow | EgressEvaluation::DeferUntilHostname => true,
336 EgressEvaluation::Deny => false,
337 },
338 };
339 if allow && !conn_tracker.has_socket_for(&src, &dst) {
340 conn_tracker.create_tcp_socket(src, dst, &mut sockets);
341 }
342 iface.poll_ingress_single(now, &mut device, &mut sockets);
345 }
346
347 FrameAction::UdpRelay { src, dst } => {
348 if port_publisher.relay_udp_outbound(frame, src, dst) {
349 device.drop_staged_frame();
350 continue;
351 }
352
353 if let Some(ref tls) = tls_state
356 && tls.config.intercepted_ports.contains(&dst.port())
357 && tls.config.block_quic_on_intercept
358 {
359 device.drop_staged_frame();
360 continue;
361 }
362
363 match DnsPortType::from_udp(dst.port()) {
364 DnsPortType::Dns => {
368 device.drop_staged_frame();
369 continue;
370 }
371 DnsPortType::EncryptedDns => {
376 device.drop_staged_frame();
377 continue;
378 }
379 DnsPortType::AlternativeDns => {
382 tracing::debug!(%dst, "alternative-DNS UDP port dropped; stub should fall back to UDP/53");
383 device.drop_staged_frame();
384 continue;
385 }
386 DnsPortType::Other => {}
387 }
388
389 if network_policy
391 .evaluate_egress(dst, Protocol::Udp, &shared)
392 .is_deny()
393 {
394 device.drop_staged_frame();
395 continue;
396 }
397
398 let host_dst = resolve_host_dst(dst, config.gateway);
402 udp_relay.relay_outbound(frame, src, dst, host_dst);
403 device.drop_staged_frame();
404 }
405
406 FrameAction::Dns | FrameAction::Passthrough => {
407 iface.poll_ingress_single(now, &mut device, &mut sockets);
409 }
410 }
411 }
412
413 loop {
417 let result = iface.poll_egress(now, &mut device, &mut sockets);
418 if matches!(result, smoltcp::iface::PollResult::None) {
419 break;
420 }
421 }
422 iface.poll_maintenance(now);
423
424 if device.frames_emitted.swap(false, Ordering::Relaxed) {
427 shared.rx_wake.wake();
428 }
429
430 conn_tracker.relay_data(&mut sockets);
435 dns_interceptor.process(&mut sockets);
436
437 port_publisher.accept_inbound(&mut iface, &mut sockets, &shared, &tokio_handle);
439 port_publisher.relay_data(&mut sockets);
440
441 let new_conns = conn_tracker.take_new_connections(&mut sockets);
443 for conn in new_conns {
444 if let Some(ref tls_state) = tls_state
445 && tls_state
446 .config
447 .intercepted_ports
448 .contains(&conn.dst.port())
449 {
450 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
452 tls_proxy::spawn_tls_proxy(
453 &tokio_handle,
454 conn.dst,
455 connect_dst,
456 conn.from_smoltcp,
457 conn.to_smoltcp,
458 shared.clone(),
459 tls_state.clone(),
460 network_policy.clone(),
461 conn.upstream_connected,
462 );
463 continue;
464 }
465 if conn.dst.port() == 53 {
466 conn.upstream_connected.store(true, Ordering::Release);
473
474 DnsTcpProxy::spawn(
484 &tokio_handle,
485 conn.dst,
486 conn.from_smoltcp,
487 conn.to_smoltcp,
488 dns_forwarder_handle.clone(),
489 shared.clone(),
490 );
491 continue;
492 }
493 if conn.dst.port() == 853
494 && let Some(ref tls_state) = tls_state
495 {
496 conn.upstream_connected.store(true, Ordering::Release);
498
499 DotProxy::spawn(
505 &tokio_handle,
506 conn.dst,
507 conn.from_smoltcp,
508 conn.to_smoltcp,
509 dns_forwarder_handle.clone(),
510 tls_state.clone(),
511 shared.clone(),
512 );
513 continue;
514 }
515 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
517 proxy::spawn_tcp_proxy(
518 &tokio_handle,
519 conn.dst,
520 connect_dst,
521 conn.from_smoltcp,
522 conn.to_smoltcp,
523 shared.clone(),
524 network_policy.clone(),
525 conn.upstream_connected,
526 );
527 }
528
529 if last_cleanup.elapsed() >= std::time::Duration::from_secs(1) {
532 conn_tracker.cleanup_closed(&mut sockets);
533 port_publisher.cleanup_closed(&mut sockets);
534 udp_relay.cleanup_expired();
535 shared.cleanup_resolved_hostnames();
536 last_cleanup = std::time::Instant::now();
537 }
538
539 loop {
542 let result = iface.poll_egress(now, &mut device, &mut sockets);
543 if matches!(result, smoltcp::iface::PollResult::None) {
544 break;
545 }
546 }
547
548 if device.frames_emitted.swap(false, Ordering::Relaxed) {
550 shared.rx_wake.wake();
551 }
552
553 let timeout_ms = iface
554 .poll_delay(now, &sockets)
555 .map(|d| d.total_millis().min(i32::MAX as u64) as i32)
556 .unwrap_or(100); unsafe {
560 libc::poll(
561 poll_fds.as_mut_ptr(),
562 poll_fds.len() as libc::nfds_t,
563 timeout_ms,
564 );
565 }
566
567 if poll_fds[0].revents & libc::POLLIN != 0 {
569 shared.tx_wake.drain();
570 }
571 if poll_fds[1].revents & libc::POLLIN != 0 {
572 shared.proxy_wake.drain();
573 }
574 }
575}
576
577pub(crate) fn resolve_host_dst(dst: SocketAddr, gateway: GatewayIps) -> SocketAddr {
591 match dst.ip() {
592 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => {
593 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst.port())
594 }
595 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => {
596 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), dst.port())
597 }
598 _ => dst,
599 }
600}
601
602fn smoltcp_now() -> Instant {
608 static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
609 let epoch = EPOCH.get_or_init(std::time::Instant::now);
610 let elapsed = epoch.elapsed();
611 Instant::from_millis(elapsed.as_millis() as i64)
612}
613
614fn handle_gateway_icmp_echo(frame: &[u8], config: &PollLoopConfig, shared: &SharedState) -> bool {
621 let Ok(eth) = EthernetFrame::new_checked(frame) else {
622 return false;
623 };
624
625 let reply = match eth.ethertype() {
626 EthernetProtocol::Ipv4 => gateway_icmpv4_echo_reply(ð, config),
627 EthernetProtocol::Ipv6 => gateway_icmpv6_echo_reply(ð, config),
628 _ => None,
629 };
630 let Some(reply) = reply else {
631 return false;
632 };
633
634 shared.push_rx_frame_and_wake(reply);
635
636 true
637}
638
639fn gateway_icmpv4_echo_reply(
641 eth: &EthernetFrame<&[u8]>,
642 config: &PollLoopConfig,
643) -> Option<Vec<u8>> {
644 let gateway_ipv4 = config.gateway.ipv4?;
645 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
646 if ipv4.dst_addr() != gateway_ipv4 || ipv4.next_header() != IpProtocol::Icmp {
647 return None;
648 }
649
650 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).ok()?;
651 let Icmpv4Repr::EchoRequest {
652 ident,
653 seq_no,
654 data,
655 } = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default()).ok()?
656 else {
657 return None;
658 };
659
660 let ipv4_repr = Ipv4Repr {
661 src_addr: gateway_ipv4,
662 dst_addr: ipv4.src_addr(),
663 next_header: IpProtocol::Icmp,
664 payload_len: 8 + data.len(),
665 hop_limit: 64,
666 };
667 let icmp_repr = Icmpv4Repr::EchoReply {
668 ident,
669 seq_no,
670 data,
671 };
672 let mut reply = vec![0u8; 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len()];
673
674 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
675 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
676 reply_eth.set_dst_addr(eth.src_addr());
677 reply_eth.set_ethertype(EthernetProtocol::Ipv4);
678
679 ipv4_repr.emit(
680 &mut Ipv4Packet::new_unchecked(&mut reply[14..34]),
681 &smoltcp::phy::ChecksumCapabilities::default(),
682 );
683 icmp_repr.emit(
684 &mut Icmpv4Packet::new_unchecked(&mut reply[34..]),
685 &smoltcp::phy::ChecksumCapabilities::default(),
686 );
687
688 Some(reply)
689}
690
691fn gateway_icmpv6_echo_reply(
693 eth: &EthernetFrame<&[u8]>,
694 config: &PollLoopConfig,
695) -> Option<Vec<u8>> {
696 let gateway_ipv6 = config.gateway.ipv6?;
697 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
698 if ipv6.dst_addr() != gateway_ipv6 || ipv6.next_header() != IpProtocol::Icmpv6 {
699 return None;
700 }
701
702 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).ok()?;
703 let Icmpv6Repr::EchoRequest {
704 ident,
705 seq_no,
706 data,
707 } = Icmpv6Repr::parse(
708 &ipv6.src_addr(),
709 &ipv6.dst_addr(),
710 &icmp,
711 &smoltcp::phy::ChecksumCapabilities::default(),
712 )
713 .ok()?
714 else {
715 return None;
716 };
717
718 let ipv6_repr = Ipv6Repr {
719 src_addr: gateway_ipv6,
720 dst_addr: ipv6.src_addr(),
721 next_header: IpProtocol::Icmpv6,
722 payload_len: icmp_repr_buffer_len_v6(data),
723 hop_limit: 64,
724 };
725 let icmp_repr = Icmpv6Repr::EchoReply {
726 ident,
727 seq_no,
728 data,
729 };
730 let ipv6_hdr_len = 40;
731 let mut reply = vec![0u8; 14 + ipv6_hdr_len + icmp_repr.buffer_len()];
732
733 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
734 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
735 reply_eth.set_dst_addr(eth.src_addr());
736 reply_eth.set_ethertype(EthernetProtocol::Ipv6);
737
738 ipv6_repr.emit(&mut Ipv6Packet::new_unchecked(&mut reply[14..54]));
739 icmp_repr.emit(
740 &gateway_ipv6,
741 &ipv6.src_addr(),
742 &mut Icmpv6Packet::new_unchecked(&mut reply[54..]),
743 &smoltcp::phy::ChecksumCapabilities::default(),
744 );
745
746 Some(reply)
747}
748
749fn icmp_repr_buffer_len_v6(data: &[u8]) -> usize {
750 Icmpv6Repr::EchoReply {
751 ident: 0,
752 seq_no: 0,
753 data,
754 }
755 .buffer_len()
756}
757
758fn classify_ipv4(payload: &[u8]) -> FrameAction {
760 let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
761 return FrameAction::Passthrough;
762 };
763 classify_transport(
764 ipv4.next_header(),
765 ipv4.src_addr().into(),
766 ipv4.dst_addr().into(),
767 ipv4.payload(),
768 )
769}
770
771fn classify_ipv6(payload: &[u8]) -> FrameAction {
773 let Ok(ipv6) = Ipv6Packet::new_checked(payload) else {
774 return FrameAction::Passthrough;
775 };
776 classify_transport(
777 ipv6.next_header(),
778 ipv6.src_addr().into(),
779 ipv6.dst_addr().into(),
780 ipv6.payload(),
781 )
782}
783
784fn classify_transport(
786 protocol: IpProtocol,
787 src_ip: std::net::IpAddr,
788 dst_ip: std::net::IpAddr,
789 transport_payload: &[u8],
790) -> FrameAction {
791 match protocol {
792 IpProtocol::Tcp => {
793 let Ok(tcp) = TcpPacket::new_checked(transport_payload) else {
794 return FrameAction::Passthrough;
795 };
796 if tcp.syn() && !tcp.ack() {
797 FrameAction::TcpSyn {
798 src: SocketAddr::new(src_ip, tcp.src_port()),
799 dst: SocketAddr::new(dst_ip, tcp.dst_port()),
800 }
801 } else {
802 FrameAction::Passthrough
803 }
804 }
805 IpProtocol::Udp => {
806 let Ok(udp) = UdpPacket::new_checked(transport_payload) else {
807 return FrameAction::Passthrough;
808 };
809 if DnsPortType::from_udp(udp.dst_port()) == DnsPortType::Dns {
813 FrameAction::Dns
814 } else {
815 FrameAction::UdpRelay {
816 src: SocketAddr::new(src_ip, udp.src_port()),
817 dst: SocketAddr::new(dst_ip, udp.dst_port()),
818 }
819 }
820 }
821 _ => FrameAction::Passthrough, }
823}
824
825#[cfg(test)]
830mod tests {
831 use super::*;
832 use std::sync::Arc;
833
834 use smoltcp::phy::ChecksumCapabilities;
835 use smoltcp::wire::{
836 ArpOperation, ArpPacket, ArpRepr, EthernetRepr, Icmpv4Packet, Icmpv4Repr, Ipv4Repr,
837 };
838
839 use crate::device::SmoltcpDevice;
840 use crate::shared::SharedState;
841
842 fn build_tcp_syn_frame(
844 src_ip: [u8; 4],
845 dst_ip: [u8; 4],
846 src_port: u16,
847 dst_port: u16,
848 ) -> Vec<u8> {
849 let mut frame = vec![0u8; 14 + 20 + 20]; frame[12] = 0x08; frame[13] = 0x00;
854
855 let ip = &mut frame[14..34];
857 ip[0] = 0x45; let total_len = 40u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
860 ip[6] = 0x40; ip[8] = 64; ip[9] = 6; ip[12..16].copy_from_slice(&src_ip);
864 ip[16..20].copy_from_slice(&dst_ip);
865
866 let tcp = &mut frame[34..54];
868 tcp[0..2].copy_from_slice(&src_port.to_be_bytes());
869 tcp[2..4].copy_from_slice(&dst_port.to_be_bytes());
870 tcp[12] = 0x50; tcp[13] = 0x02; frame
874 }
875
876 fn build_udp_frame(src_ip: [u8; 4], dst_ip: [u8; 4], src_port: u16, dst_port: u16) -> Vec<u8> {
878 let mut frame = vec![0u8; 14 + 20 + 8]; frame[12] = 0x08;
882 frame[13] = 0x00;
883
884 let ip = &mut frame[14..34];
886 ip[0] = 0x45;
887 let total_len = 28u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
889 ip[8] = 64;
890 ip[9] = 17; ip[12..16].copy_from_slice(&src_ip);
892 ip[16..20].copy_from_slice(&dst_ip);
893
894 let udp = &mut frame[34..42];
896 udp[0..2].copy_from_slice(&src_port.to_be_bytes());
897 udp[2..4].copy_from_slice(&dst_port.to_be_bytes());
898 let udp_len = 8u16;
899 udp[4..6].copy_from_slice(&udp_len.to_be_bytes());
900
901 frame
902 }
903
904 fn build_icmpv4_echo_frame(
906 src_mac: [u8; 6],
907 dst_mac: [u8; 6],
908 src_ip: [u8; 4],
909 dst_ip: [u8; 4],
910 ident: u16,
911 seq_no: u16,
912 data: &[u8],
913 ) -> Vec<u8> {
914 let ipv4_repr = Ipv4Repr {
915 src_addr: Ipv4Addr::from(src_ip),
916 dst_addr: Ipv4Addr::from(dst_ip),
917 next_header: IpProtocol::Icmp,
918 payload_len: 8 + data.len(),
919 hop_limit: 64,
920 };
921 let icmp_repr = Icmpv4Repr::EchoRequest {
922 ident,
923 seq_no,
924 data,
925 };
926 let frame_len = 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
927 let mut frame = vec![0u8; frame_len];
928
929 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
930 EthernetRepr {
931 src_addr: EthernetAddress(src_mac),
932 dst_addr: EthernetAddress(dst_mac),
933 ethertype: EthernetProtocol::Ipv4,
934 }
935 .emit(&mut eth_frame);
936
937 ipv4_repr.emit(
938 &mut Ipv4Packet::new_unchecked(&mut frame[14..34]),
939 &ChecksumCapabilities::default(),
940 );
941 icmp_repr.emit(
942 &mut Icmpv4Packet::new_unchecked(&mut frame[34..]),
943 &ChecksumCapabilities::default(),
944 );
945
946 frame
947 }
948
949 fn build_arp_request_frame(src_mac: [u8; 6], src_ip: [u8; 4], target_ip: [u8; 4]) -> Vec<u8> {
951 let mut frame = vec![0u8; 14 + 28];
952
953 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
954 EthernetRepr {
955 src_addr: EthernetAddress(src_mac),
956 dst_addr: EthernetAddress([0xff; 6]),
957 ethertype: EthernetProtocol::Arp,
958 }
959 .emit(&mut eth_frame);
960
961 ArpRepr::EthernetIpv4 {
962 operation: ArpOperation::Request,
963 source_hardware_addr: EthernetAddress(src_mac),
964 source_protocol_addr: Ipv4Addr::from(src_ip),
965 target_hardware_addr: EthernetAddress([0x00; 6]),
966 target_protocol_addr: Ipv4Addr::from(target_ip),
967 }
968 .emit(&mut ArpPacket::new_unchecked(&mut frame[14..]));
969
970 frame
971 }
972
973 #[test]
974 fn classify_tcp_syn() {
975 let frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
976 match classify_frame(&frame) {
977 FrameAction::TcpSyn { src, dst } => {
978 assert_eq!(
979 src,
980 SocketAddr::new(Ipv4Addr::new(10, 0, 0, 2).into(), 54321)
981 );
982 assert_eq!(
983 dst,
984 SocketAddr::new(Ipv4Addr::new(93, 184, 216, 34).into(), 443)
985 );
986 }
987 _ => panic!("expected TcpSyn"),
988 }
989 }
990
991 #[test]
992 fn classify_tcp_ack_is_passthrough() {
993 let mut frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
994 frame[34 + 13] = 0x10; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
997 }
998
999 #[test]
1000 fn classify_udp_dns() {
1001 let frame = build_udp_frame([10, 0, 0, 2], [10, 0, 0, 1], 12345, 53);
1002 assert!(matches!(classify_frame(&frame), FrameAction::Dns));
1003 }
1004
1005 #[test]
1006 fn classify_udp_non_dns() {
1007 let frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1008 match classify_frame(&frame) {
1009 FrameAction::UdpRelay { src, dst } => {
1010 assert_eq!(src.port(), 12345);
1011 assert_eq!(dst.port(), 443);
1012 }
1013 _ => panic!("expected UdpRelay"),
1014 }
1015 }
1016
1017 #[test]
1018 fn classify_arp_is_passthrough() {
1019 let mut frame = vec![0u8; 42]; frame[12] = 0x08;
1021 frame[13] = 0x06; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1023 }
1024
1025 #[test]
1026 fn classify_garbage_is_passthrough() {
1027 assert!(matches!(classify_frame(&[]), FrameAction::Passthrough));
1028 assert!(matches!(classify_frame(&[0; 5]), FrameAction::Passthrough));
1029 }
1030
1031 #[test]
1032 fn gateway_replies_to_icmp_echo_requests() {
1033 fn drive_one_frame(
1034 device: &mut SmoltcpDevice,
1035 iface: &mut Interface,
1036 sockets: &mut SocketSet<'_>,
1037 shared: &Arc<SharedState>,
1038 poll_config: &PollLoopConfig,
1039 now: Instant,
1040 ) {
1041 let frame = device.stage_next_frame().expect("expected staged frame");
1042 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1043 device.drop_staged_frame();
1044 return;
1045 }
1046 let _ = iface.poll_ingress_single(now, device, sockets);
1047 let _ = iface.poll_egress(now, device, sockets);
1048 }
1049
1050 let shared = Arc::new(SharedState::new(4));
1051 let poll_config = PollLoopConfig {
1052 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1053 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1054 gateway: GatewayIps {
1055 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1056 ipv6: Some(Ipv6Addr::LOCALHOST),
1057 },
1058 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1059 guest_ipv6: None,
1060 mtu: 1500,
1061 };
1062 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1063 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1064 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1065 let mut iface = create_interface(&mut device, &poll_config);
1066 let mut sockets = SocketSet::new(vec![]);
1067 let now = smoltcp_now();
1068
1069 shared
1072 .tx_ring
1073 .push(build_arp_request_frame(
1074 poll_config.guest_mac,
1075 guest_ipv4.octets(),
1076 gateway_ipv4.octets(),
1077 ))
1078 .unwrap();
1079 shared
1080 .tx_ring
1081 .push(build_icmpv4_echo_frame(
1082 poll_config.guest_mac,
1083 poll_config.gateway_mac,
1084 guest_ipv4.octets(),
1085 gateway_ipv4.octets(),
1086 0x1234,
1087 0xABCD,
1088 b"ping",
1089 ))
1090 .unwrap();
1091
1092 drive_one_frame(
1093 &mut device,
1094 &mut iface,
1095 &mut sockets,
1096 &shared,
1097 &poll_config,
1098 now,
1099 );
1100 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1101
1102 drive_one_frame(
1103 &mut device,
1104 &mut iface,
1105 &mut sockets,
1106 &shared,
1107 &poll_config,
1108 now,
1109 );
1110
1111 let reply = shared.rx_ring.pop().expect("expected ICMP echo reply");
1112 let eth = EthernetFrame::new_checked(&reply).expect("valid ethernet frame");
1113 assert_eq!(eth.src_addr(), EthernetAddress(poll_config.gateway_mac));
1114 assert_eq!(eth.dst_addr(), EthernetAddress(poll_config.guest_mac));
1115 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1116
1117 let ipv4 = Ipv4Packet::new_checked(eth.payload()).expect("valid IPv4 packet");
1118 assert_eq!(ipv4.src_addr(), gateway_ipv4);
1119 assert_eq!(ipv4.dst_addr(), guest_ipv4);
1120 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
1121
1122 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).expect("valid ICMP packet");
1123 let icmp_repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default())
1124 .expect("valid ICMP echo reply");
1125 assert_eq!(
1126 icmp_repr,
1127 Icmpv4Repr::EchoReply {
1128 ident: 0x1234,
1129 seq_no: 0xABCD,
1130 data: b"ping",
1131 }
1132 );
1133 }
1134
1135 fn test_gateway() -> GatewayIps {
1136 GatewayIps {
1137 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1138 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1139 }
1140 }
1141
1142 #[test]
1143 fn resolve_host_dst_matches_ipv4() {
1144 let gw = test_gateway();
1145 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1146 assert_eq!(
1147 resolve_host_dst(dst, gw),
1148 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
1149 );
1150 }
1151
1152 #[test]
1153 fn resolve_host_dst_matches_ipv6() {
1154 let gw = test_gateway();
1155 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1156 assert_eq!(
1157 resolve_host_dst(dst, gw),
1158 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
1159 );
1160 }
1161
1162 #[test]
1163 fn resolve_host_dst_passes_through_when_family_absent() {
1164 let gw = GatewayIps {
1165 ipv4: None,
1166 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1167 };
1168 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 1)), 8080);
1170 assert_eq!(resolve_host_dst(dst, gw), dst);
1171 }
1172
1173 #[test]
1174 fn resolve_host_dst_passes_through_non_gateway() {
1175 let gw = test_gateway();
1176 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1177 assert_eq!(resolve_host_dst(dst, gw), dst);
1178 }
1179
1180 #[test]
1181 fn external_icmp_echo_requests_are_not_answered_locally() {
1182 fn drive_one_frame(
1183 device: &mut SmoltcpDevice,
1184 iface: &mut Interface,
1185 sockets: &mut SocketSet<'_>,
1186 shared: &Arc<SharedState>,
1187 poll_config: &PollLoopConfig,
1188 now: Instant,
1189 ) {
1190 let frame = device.stage_next_frame().expect("expected staged frame");
1191 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1192 device.drop_staged_frame();
1193 return;
1194 }
1195 let _ = iface.poll_ingress_single(now, device, sockets);
1196 let _ = iface.poll_egress(now, device, sockets);
1197 }
1198
1199 let shared = Arc::new(SharedState::new(4));
1200 let poll_config = PollLoopConfig {
1201 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1202 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1203 gateway: GatewayIps {
1204 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1205 ipv6: Some(Ipv6Addr::LOCALHOST),
1206 },
1207 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1208 guest_ipv6: None,
1209 mtu: 1500,
1210 };
1211 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1212 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1213 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1214 let mut iface = create_interface(&mut device, &poll_config);
1215 let mut sockets = SocketSet::new(vec![]);
1216 let now = smoltcp_now();
1217
1218 shared
1219 .tx_ring
1220 .push(build_arp_request_frame(
1221 poll_config.guest_mac,
1222 guest_ipv4.octets(),
1223 gateway_ipv4.octets(),
1224 ))
1225 .unwrap();
1226 shared
1227 .tx_ring
1228 .push(build_icmpv4_echo_frame(
1229 poll_config.guest_mac,
1230 poll_config.gateway_mac,
1231 guest_ipv4.octets(),
1232 [142, 251, 216, 46],
1233 0x1234,
1234 0xABCD,
1235 b"ping",
1236 ))
1237 .unwrap();
1238
1239 drive_one_frame(
1240 &mut device,
1241 &mut iface,
1242 &mut sockets,
1243 &shared,
1244 &poll_config,
1245 now,
1246 );
1247 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1248
1249 drive_one_frame(
1250 &mut device,
1251 &mut iface,
1252 &mut sockets,
1253 &shared,
1254 &poll_config,
1255 now,
1256 );
1257 assert!(
1258 shared.rx_ring.pop().is_none(),
1259 "external ICMP should not be answered locally"
1260 );
1261 }
1262}