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