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::secrets::config::SecretsConfig;
35use crate::shared::SharedState;
36use crate::tls::{proxy as tls_proxy, state::TlsState};
37use crate::udp_relay::UdpRelay;
38
39pub enum FrameAction {
50 TcpSyn { src: SocketAddr, dst: SocketAddr },
53
54 UdpRelay { src: SocketAddr, dst: SocketAddr },
57
58 Dns,
60
61 Passthrough,
64}
65
66pub struct PollLoopConfig {
69 pub gateway_mac: [u8; 6],
71 pub guest_mac: [u8; 6],
73 pub gateway: GatewayIps,
77 pub guest_ipv4: Option<Ipv4Addr>,
79 pub guest_ipv6: Option<Ipv6Addr>,
81 pub mtu: usize,
83}
84
85#[derive(Debug, Clone, Copy)]
90pub struct GatewayIps {
91 pub ipv4: Option<Ipv4Addr>,
93 pub ipv6: Option<Ipv6Addr>,
95}
96
97pub fn classify_frame(frame: &[u8]) -> FrameAction {
107 let Ok(eth) = EthernetFrame::new_checked(frame) else {
108 return FrameAction::Passthrough;
109 };
110
111 match eth.ethertype() {
112 EthernetProtocol::Ipv4 => classify_ipv4(eth.payload()),
113 EthernetProtocol::Ipv6 => classify_ipv6(eth.payload()),
114 _ => FrameAction::Passthrough, }
116}
117
118pub fn create_interface(device: &mut SmoltcpDevice, config: &PollLoopConfig) -> Interface {
125 let hw_addr = HardwareAddress::Ethernet(EthernetAddress(config.gateway_mac));
126 let iface_config = Config::new(hw_addr);
127 let mut iface = Interface::new(iface_config, device, smoltcp_now());
128
129 iface.update_ip_addrs(|addrs| {
131 if let Some(ipv4) = config.gateway.ipv4 {
132 addrs
133 .push(IpCidr::new(IpAddress::Ipv4(ipv4), 30)) .expect("failed to add gateway IPv4 address");
135 }
136 if let Some(ipv6) = config.gateway.ipv6 {
137 addrs
138 .push(IpCidr::new(IpAddress::Ipv6(ipv6), 64))
139 .expect("failed to add gateway IPv6 address");
140 }
141 });
142
143 if let Some(ipv4) = config.gateway.ipv4 {
145 iface
146 .routes_mut()
147 .add_default_ipv4_route(ipv4)
148 .expect("failed to add default IPv4 route");
149 }
150 if let Some(ipv6) = config.gateway.ipv6 {
151 iface
152 .routes_mut()
153 .add_default_ipv6_route(ipv6)
154 .expect("failed to add default IPv6 route");
155 }
156
157 iface.set_any_ip(true);
159
160 iface
161}
162
163#[allow(clippy::too_many_arguments)]
194pub fn smoltcp_poll_loop(
195 shared: Arc<SharedState>,
196 config: PollLoopConfig,
197 network_policy: NetworkPolicy,
198 dns_config: DnsConfig,
199 tls_state: Option<Arc<TlsState>>,
200 published_ports: Vec<PublishedPort>,
201 max_connections: Option<usize>,
202 tokio_handle: tokio::runtime::Handle,
203 secrets: Arc<SecretsConfig>,
204) {
205 let mut device = SmoltcpDevice::new(shared.clone(), config.mtu);
206 let mut iface = create_interface(&mut device, &config);
207 let mut sockets = SocketSet::new(vec![]);
208 let mut conn_tracker = ConnectionTracker::new(max_connections);
209
210 let gateway_ips: Arc<HashSet<IpAddr>> = Arc::new(
216 config
217 .gateway
218 .ipv4
219 .map(IpAddr::V4)
220 .into_iter()
221 .chain(config.gateway.ipv6.map(IpAddr::V6))
222 .collect(),
223 );
224 shared.set_gateway_ips(config.gateway.ipv4, config.gateway.ipv6);
227 let network_policy = Arc::new(network_policy);
228
229 let (mut dns_interceptor, dns_forwarder_handle) = DnsInterceptor::new(
230 &mut sockets,
231 dns_config,
232 shared.clone(),
233 &tokio_handle,
234 gateway_ips,
235 network_policy.clone(),
236 config.gateway,
237 config.gateway_mac,
238 config.guest_mac,
239 );
240 let mut port_publisher = PortPublisher::new(
241 &published_ports,
242 config.guest_ipv4,
243 config.guest_ipv6,
244 config.gateway.ipv4,
245 config.gateway.ipv6,
246 config.gateway_mac,
247 config.guest_mac,
248 network_policy.clone(),
249 shared.clone(),
250 &tokio_handle,
251 );
252 let mut udp_relay = UdpRelay::new(
253 shared.clone(),
254 config.gateway_mac,
255 config.guest_mac,
256 tokio_handle.clone(),
257 );
258 let icmp_relay = IcmpRelay::new(
259 shared.clone(),
260 config.gateway_mac,
261 config.guest_mac,
262 tokio_handle.clone(),
263 );
264
265 let mut last_cleanup = std::time::Instant::now();
267
268 let mut poll_fds = [
270 libc::pollfd {
271 fd: shared.tx_wake.as_raw_fd(),
272 events: libc::POLLIN,
273 revents: 0,
274 },
275 libc::pollfd {
276 fd: shared.proxy_wake.as_raw_fd(),
277 events: libc::POLLIN,
278 revents: 0,
279 },
280 ];
281
282 loop {
283 let now = smoltcp_now();
284
285 while let Some(frame) = device.stage_next_frame() {
287 if handle_gateway_icmp_echo(frame, &config, &shared) {
288 device.drop_staged_frame();
289 continue;
290 }
291
292 if icmp_relay.relay_outbound_if_echo(frame, &config, &network_policy) {
293 device.drop_staged_frame();
294 continue;
295 }
296
297 match classify_frame(frame) {
298 FrameAction::TcpSyn { src, dst } => {
299 let allow = match DnsPortType::from_tcp(dst.port()) {
300 DnsPortType::Dns => true,
304 DnsPortType::EncryptedDns => {
313 if tls_state.is_some() {
314 true
315 } else {
316 tracing::debug!(%dst, "DoT port refused (TLS interception not configured); stub should fall back to TCP/53");
317 false
318 }
319 }
320 DnsPortType::AlternativeDns => {
326 tracing::debug!(%dst, "alternative-DNS TCP port refused; stub should fall back to TCP/53");
327 false
328 }
329 DnsPortType::Other => match network_policy.evaluate_egress_with_source(
332 dst,
333 Protocol::Tcp,
334 &shared,
335 HostnameSource::Deferred,
336 ) {
337 EgressEvaluation::Allow | EgressEvaluation::DeferUntilHostname => true,
338 EgressEvaluation::Deny => false,
339 },
340 };
341 if allow && !conn_tracker.has_socket_for(&src, &dst) {
342 conn_tracker.create_tcp_socket(src, dst, &mut sockets);
343 }
344 iface.poll_ingress_single(now, &mut device, &mut sockets);
347 }
348
349 FrameAction::UdpRelay { src, dst } => {
350 if port_publisher.relay_udp_outbound(frame, src, dst) {
351 device.drop_staged_frame();
352 continue;
353 }
354
355 if let Some(ref tls) = tls_state
358 && tls.config.intercepted_ports.contains(&dst.port())
359 && tls.config.block_quic_on_intercept
360 {
361 device.drop_staged_frame();
362 continue;
363 }
364
365 match DnsPortType::from_udp(dst.port()) {
366 DnsPortType::Dns => {
370 device.drop_staged_frame();
371 continue;
372 }
373 DnsPortType::EncryptedDns => {
378 device.drop_staged_frame();
379 continue;
380 }
381 DnsPortType::AlternativeDns => {
384 tracing::debug!(%dst, "alternative-DNS UDP port dropped; stub should fall back to UDP/53");
385 device.drop_staged_frame();
386 continue;
387 }
388 DnsPortType::Other => {}
389 }
390
391 if network_policy
393 .evaluate_egress(dst, Protocol::Udp, &shared)
394 .is_deny()
395 {
396 device.drop_staged_frame();
397 continue;
398 }
399
400 let host_dst = resolve_host_dst(dst, config.gateway);
404 udp_relay.relay_outbound(frame, src, dst, host_dst);
405 device.drop_staged_frame();
406 }
407
408 FrameAction::Dns | FrameAction::Passthrough => {
409 iface.poll_ingress_single(now, &mut device, &mut sockets);
411 }
412 }
413 }
414
415 loop {
419 let result = iface.poll_egress(now, &mut device, &mut sockets);
420 if matches!(result, smoltcp::iface::PollResult::None) {
421 break;
422 }
423 }
424 iface.poll_maintenance(now);
425
426 if device.frames_emitted.swap(false, Ordering::Relaxed) {
429 shared.rx_wake.wake();
430 }
431
432 conn_tracker.relay_data(&mut sockets);
437 dns_interceptor.process(&mut sockets);
438
439 port_publisher.accept_inbound(&mut iface, &mut sockets, &shared, &tokio_handle);
441 port_publisher.relay_data(&mut sockets);
442
443 let new_conns = conn_tracker.take_new_connections(&mut sockets);
445 for conn in new_conns {
446 if let Some(ref tls_state) = tls_state
447 && tls_state
448 .config
449 .intercepted_ports
450 .contains(&conn.dst.port())
451 {
452 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
454 tls_proxy::spawn_tls_proxy(
455 &tokio_handle,
456 conn.dst,
457 connect_dst,
458 conn.from_smoltcp,
459 conn.to_smoltcp,
460 shared.clone(),
461 tls_state.clone(),
462 network_policy.clone(),
463 conn.proxy_connect,
464 );
465 continue;
466 }
467 if conn.dst.port() == 53 {
468 conn.proxy_connect.mark_connected();
475
476 DnsTcpProxy::spawn(
486 &tokio_handle,
487 conn.dst,
488 conn.from_smoltcp,
489 conn.to_smoltcp,
490 dns_forwarder_handle.clone(),
491 shared.clone(),
492 );
493 continue;
494 }
495 if conn.dst.port() == 853
496 && let Some(ref tls_state) = tls_state
497 {
498 conn.proxy_connect.mark_connected();
500
501 DotProxy::spawn(
507 &tokio_handle,
508 conn.dst,
509 conn.from_smoltcp,
510 conn.to_smoltcp,
511 dns_forwarder_handle.clone(),
512 tls_state.clone(),
513 shared.clone(),
514 );
515 continue;
516 }
517 let connect_dst = resolve_host_dst(conn.dst, config.gateway);
519 proxy::spawn_tcp_proxy(
520 &tokio_handle,
521 conn.dst,
522 connect_dst,
523 conn.from_smoltcp,
524 conn.to_smoltcp,
525 shared.clone(),
526 network_policy.clone(),
527 secrets.clone(),
528 tls_state.clone(),
529 conn.proxy_connect,
530 );
531 }
532
533 if last_cleanup.elapsed() >= std::time::Duration::from_secs(1) {
536 conn_tracker.cleanup_closed(&mut sockets);
537 port_publisher.cleanup_closed(&mut sockets);
538 udp_relay.cleanup_expired();
539 shared.cleanup_resolved_hostnames();
540 last_cleanup = std::time::Instant::now();
541 }
542
543 loop {
546 let result = iface.poll_egress(now, &mut device, &mut sockets);
547 if matches!(result, smoltcp::iface::PollResult::None) {
548 break;
549 }
550 }
551
552 if device.frames_emitted.swap(false, Ordering::Relaxed) {
554 shared.rx_wake.wake();
555 }
556
557 let timeout_ms = iface
558 .poll_delay(now, &sockets)
559 .map(|d| d.total_millis().min(i32::MAX as u64) as i32)
560 .unwrap_or(100); unsafe {
564 libc::poll(
565 poll_fds.as_mut_ptr(),
566 poll_fds.len() as libc::nfds_t,
567 timeout_ms,
568 );
569 }
570
571 if poll_fds[0].revents & libc::POLLIN != 0 {
573 shared.tx_wake.drain();
574 }
575 if poll_fds[1].revents & libc::POLLIN != 0 {
576 shared.proxy_wake.drain();
577 }
578 }
579}
580
581pub(crate) fn resolve_host_dst(dst: SocketAddr, gateway: GatewayIps) -> SocketAddr {
595 match dst.ip() {
596 IpAddr::V4(v4) if gateway.ipv4 == Some(v4) => {
597 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), dst.port())
598 }
599 IpAddr::V6(v6) if gateway.ipv6 == Some(v6) => {
600 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), dst.port())
601 }
602 _ => dst,
603 }
604}
605
606fn smoltcp_now() -> Instant {
612 static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
613 let epoch = EPOCH.get_or_init(std::time::Instant::now);
614 let elapsed = epoch.elapsed();
615 Instant::from_millis(elapsed.as_millis() as i64)
616}
617
618fn handle_gateway_icmp_echo(frame: &[u8], config: &PollLoopConfig, shared: &SharedState) -> bool {
625 let Ok(eth) = EthernetFrame::new_checked(frame) else {
626 return false;
627 };
628
629 let reply = match eth.ethertype() {
630 EthernetProtocol::Ipv4 => gateway_icmpv4_echo_reply(ð, config),
631 EthernetProtocol::Ipv6 => gateway_icmpv6_echo_reply(ð, config),
632 _ => None,
633 };
634 let Some(reply) = reply else {
635 return false;
636 };
637
638 shared.push_rx_frame_and_wake(reply);
639
640 true
641}
642
643fn gateway_icmpv4_echo_reply(
645 eth: &EthernetFrame<&[u8]>,
646 config: &PollLoopConfig,
647) -> Option<Vec<u8>> {
648 let gateway_ipv4 = config.gateway.ipv4?;
649 let ipv4 = Ipv4Packet::new_checked(eth.payload()).ok()?;
650 if ipv4.dst_addr() != gateway_ipv4 || ipv4.next_header() != IpProtocol::Icmp {
651 return None;
652 }
653
654 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).ok()?;
655 let Icmpv4Repr::EchoRequest {
656 ident,
657 seq_no,
658 data,
659 } = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default()).ok()?
660 else {
661 return None;
662 };
663
664 let ipv4_repr = Ipv4Repr {
665 src_addr: gateway_ipv4,
666 dst_addr: ipv4.src_addr(),
667 next_header: IpProtocol::Icmp,
668 payload_len: 8 + data.len(),
669 hop_limit: 64,
670 };
671 let icmp_repr = Icmpv4Repr::EchoReply {
672 ident,
673 seq_no,
674 data,
675 };
676 let mut reply = vec![0u8; 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len()];
677
678 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
679 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
680 reply_eth.set_dst_addr(eth.src_addr());
681 reply_eth.set_ethertype(EthernetProtocol::Ipv4);
682
683 ipv4_repr.emit(
684 &mut Ipv4Packet::new_unchecked(&mut reply[14..34]),
685 &smoltcp::phy::ChecksumCapabilities::default(),
686 );
687 icmp_repr.emit(
688 &mut Icmpv4Packet::new_unchecked(&mut reply[34..]),
689 &smoltcp::phy::ChecksumCapabilities::default(),
690 );
691
692 Some(reply)
693}
694
695fn gateway_icmpv6_echo_reply(
697 eth: &EthernetFrame<&[u8]>,
698 config: &PollLoopConfig,
699) -> Option<Vec<u8>> {
700 let gateway_ipv6 = config.gateway.ipv6?;
701 let ipv6 = Ipv6Packet::new_checked(eth.payload()).ok()?;
702 if ipv6.dst_addr() != gateway_ipv6 || ipv6.next_header() != IpProtocol::Icmpv6 {
703 return None;
704 }
705
706 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).ok()?;
707 let Icmpv6Repr::EchoRequest {
708 ident,
709 seq_no,
710 data,
711 } = Icmpv6Repr::parse(
712 &ipv6.src_addr(),
713 &ipv6.dst_addr(),
714 &icmp,
715 &smoltcp::phy::ChecksumCapabilities::default(),
716 )
717 .ok()?
718 else {
719 return None;
720 };
721
722 let ipv6_repr = Ipv6Repr {
723 src_addr: gateway_ipv6,
724 dst_addr: ipv6.src_addr(),
725 next_header: IpProtocol::Icmpv6,
726 payload_len: icmp_repr_buffer_len_v6(data),
727 hop_limit: 64,
728 };
729 let icmp_repr = Icmpv6Repr::EchoReply {
730 ident,
731 seq_no,
732 data,
733 };
734 let ipv6_hdr_len = 40;
735 let mut reply = vec![0u8; 14 + ipv6_hdr_len + icmp_repr.buffer_len()];
736
737 let mut reply_eth = EthernetFrame::new_unchecked(&mut reply);
738 reply_eth.set_src_addr(EthernetAddress(config.gateway_mac));
739 reply_eth.set_dst_addr(eth.src_addr());
740 reply_eth.set_ethertype(EthernetProtocol::Ipv6);
741
742 ipv6_repr.emit(&mut Ipv6Packet::new_unchecked(&mut reply[14..54]));
743 icmp_repr.emit(
744 &gateway_ipv6,
745 &ipv6.src_addr(),
746 &mut Icmpv6Packet::new_unchecked(&mut reply[54..]),
747 &smoltcp::phy::ChecksumCapabilities::default(),
748 );
749
750 Some(reply)
751}
752
753fn icmp_repr_buffer_len_v6(data: &[u8]) -> usize {
754 Icmpv6Repr::EchoReply {
755 ident: 0,
756 seq_no: 0,
757 data,
758 }
759 .buffer_len()
760}
761
762fn classify_ipv4(payload: &[u8]) -> FrameAction {
764 let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
765 return FrameAction::Passthrough;
766 };
767 classify_transport(
768 ipv4.next_header(),
769 ipv4.src_addr().into(),
770 ipv4.dst_addr().into(),
771 ipv4.payload(),
772 )
773}
774
775fn classify_ipv6(payload: &[u8]) -> FrameAction {
777 let Ok(ipv6) = Ipv6Packet::new_checked(payload) else {
778 return FrameAction::Passthrough;
779 };
780 classify_transport(
781 ipv6.next_header(),
782 ipv6.src_addr().into(),
783 ipv6.dst_addr().into(),
784 ipv6.payload(),
785 )
786}
787
788fn classify_transport(
790 protocol: IpProtocol,
791 src_ip: std::net::IpAddr,
792 dst_ip: std::net::IpAddr,
793 transport_payload: &[u8],
794) -> FrameAction {
795 match protocol {
796 IpProtocol::Tcp => {
797 let Ok(tcp) = TcpPacket::new_checked(transport_payload) else {
798 return FrameAction::Passthrough;
799 };
800 if tcp.syn() && !tcp.ack() {
801 FrameAction::TcpSyn {
802 src: SocketAddr::new(src_ip, tcp.src_port()),
803 dst: SocketAddr::new(dst_ip, tcp.dst_port()),
804 }
805 } else {
806 FrameAction::Passthrough
807 }
808 }
809 IpProtocol::Udp => {
810 let Ok(udp) = UdpPacket::new_checked(transport_payload) else {
811 return FrameAction::Passthrough;
812 };
813 if DnsPortType::from_udp(udp.dst_port()) == DnsPortType::Dns {
817 FrameAction::Dns
818 } else {
819 FrameAction::UdpRelay {
820 src: SocketAddr::new(src_ip, udp.src_port()),
821 dst: SocketAddr::new(dst_ip, udp.dst_port()),
822 }
823 }
824 }
825 _ => FrameAction::Passthrough, }
827}
828
829#[cfg(test)]
834mod tests {
835 use super::*;
836 use std::sync::Arc;
837
838 use smoltcp::phy::ChecksumCapabilities;
839 use smoltcp::wire::{
840 ArpOperation, ArpPacket, ArpRepr, EthernetRepr, Icmpv4Packet, Icmpv4Repr, Ipv4Repr,
841 };
842
843 use crate::device::SmoltcpDevice;
844 use crate::shared::SharedState;
845
846 fn build_tcp_syn_frame(
848 src_ip: [u8; 4],
849 dst_ip: [u8; 4],
850 src_port: u16,
851 dst_port: u16,
852 ) -> Vec<u8> {
853 let mut frame = vec![0u8; 14 + 20 + 20]; frame[12] = 0x08; frame[13] = 0x00;
858
859 let ip = &mut frame[14..34];
861 ip[0] = 0x45; let total_len = 40u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
864 ip[6] = 0x40; ip[8] = 64; ip[9] = 6; ip[12..16].copy_from_slice(&src_ip);
868 ip[16..20].copy_from_slice(&dst_ip);
869
870 let tcp = &mut frame[34..54];
872 tcp[0..2].copy_from_slice(&src_port.to_be_bytes());
873 tcp[2..4].copy_from_slice(&dst_port.to_be_bytes());
874 tcp[12] = 0x50; tcp[13] = 0x02; frame
878 }
879
880 fn build_udp_frame(src_ip: [u8; 4], dst_ip: [u8; 4], src_port: u16, dst_port: u16) -> Vec<u8> {
882 let mut frame = vec![0u8; 14 + 20 + 8]; frame[12] = 0x08;
886 frame[13] = 0x00;
887
888 let ip = &mut frame[14..34];
890 ip[0] = 0x45;
891 let total_len = 28u16; ip[2..4].copy_from_slice(&total_len.to_be_bytes());
893 ip[8] = 64;
894 ip[9] = 17; ip[12..16].copy_from_slice(&src_ip);
896 ip[16..20].copy_from_slice(&dst_ip);
897
898 let udp = &mut frame[34..42];
900 udp[0..2].copy_from_slice(&src_port.to_be_bytes());
901 udp[2..4].copy_from_slice(&dst_port.to_be_bytes());
902 let udp_len = 8u16;
903 udp[4..6].copy_from_slice(&udp_len.to_be_bytes());
904
905 frame
906 }
907
908 fn build_icmpv4_echo_frame(
910 src_mac: [u8; 6],
911 dst_mac: [u8; 6],
912 src_ip: [u8; 4],
913 dst_ip: [u8; 4],
914 ident: u16,
915 seq_no: u16,
916 data: &[u8],
917 ) -> Vec<u8> {
918 let ipv4_repr = Ipv4Repr {
919 src_addr: Ipv4Addr::from(src_ip),
920 dst_addr: Ipv4Addr::from(dst_ip),
921 next_header: IpProtocol::Icmp,
922 payload_len: 8 + data.len(),
923 hop_limit: 64,
924 };
925 let icmp_repr = Icmpv4Repr::EchoRequest {
926 ident,
927 seq_no,
928 data,
929 };
930 let frame_len = 14 + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
931 let mut frame = vec![0u8; frame_len];
932
933 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
934 EthernetRepr {
935 src_addr: EthernetAddress(src_mac),
936 dst_addr: EthernetAddress(dst_mac),
937 ethertype: EthernetProtocol::Ipv4,
938 }
939 .emit(&mut eth_frame);
940
941 ipv4_repr.emit(
942 &mut Ipv4Packet::new_unchecked(&mut frame[14..34]),
943 &ChecksumCapabilities::default(),
944 );
945 icmp_repr.emit(
946 &mut Icmpv4Packet::new_unchecked(&mut frame[34..]),
947 &ChecksumCapabilities::default(),
948 );
949
950 frame
951 }
952
953 fn build_arp_request_frame(src_mac: [u8; 6], src_ip: [u8; 4], target_ip: [u8; 4]) -> Vec<u8> {
955 let mut frame = vec![0u8; 14 + 28];
956
957 let mut eth_frame = EthernetFrame::new_unchecked(&mut frame);
958 EthernetRepr {
959 src_addr: EthernetAddress(src_mac),
960 dst_addr: EthernetAddress([0xff; 6]),
961 ethertype: EthernetProtocol::Arp,
962 }
963 .emit(&mut eth_frame);
964
965 ArpRepr::EthernetIpv4 {
966 operation: ArpOperation::Request,
967 source_hardware_addr: EthernetAddress(src_mac),
968 source_protocol_addr: Ipv4Addr::from(src_ip),
969 target_hardware_addr: EthernetAddress([0x00; 6]),
970 target_protocol_addr: Ipv4Addr::from(target_ip),
971 }
972 .emit(&mut ArpPacket::new_unchecked(&mut frame[14..]));
973
974 frame
975 }
976
977 #[test]
978 fn classify_tcp_syn() {
979 let frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
980 match classify_frame(&frame) {
981 FrameAction::TcpSyn { src, dst } => {
982 assert_eq!(
983 src,
984 SocketAddr::new(Ipv4Addr::new(10, 0, 0, 2).into(), 54321)
985 );
986 assert_eq!(
987 dst,
988 SocketAddr::new(Ipv4Addr::new(93, 184, 216, 34).into(), 443)
989 );
990 }
991 _ => panic!("expected TcpSyn"),
992 }
993 }
994
995 #[test]
996 fn classify_tcp_ack_is_passthrough() {
997 let mut frame = build_tcp_syn_frame([10, 0, 0, 2], [93, 184, 216, 34], 54321, 443);
998 frame[34 + 13] = 0x10; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1001 }
1002
1003 #[test]
1004 fn classify_udp_dns() {
1005 let frame = build_udp_frame([10, 0, 0, 2], [10, 0, 0, 1], 12345, 53);
1006 assert!(matches!(classify_frame(&frame), FrameAction::Dns));
1007 }
1008
1009 #[test]
1010 fn classify_udp_non_dns() {
1011 let frame = build_udp_frame([10, 0, 0, 2], [8, 8, 8, 8], 12345, 443);
1012 match classify_frame(&frame) {
1013 FrameAction::UdpRelay { src, dst } => {
1014 assert_eq!(src.port(), 12345);
1015 assert_eq!(dst.port(), 443);
1016 }
1017 _ => panic!("expected UdpRelay"),
1018 }
1019 }
1020
1021 #[test]
1022 fn classify_arp_is_passthrough() {
1023 let mut frame = vec![0u8; 42]; frame[12] = 0x08;
1025 frame[13] = 0x06; assert!(matches!(classify_frame(&frame), FrameAction::Passthrough));
1027 }
1028
1029 #[test]
1030 fn classify_garbage_is_passthrough() {
1031 assert!(matches!(classify_frame(&[]), FrameAction::Passthrough));
1032 assert!(matches!(classify_frame(&[0; 5]), FrameAction::Passthrough));
1033 }
1034
1035 #[test]
1036 fn gateway_replies_to_icmp_echo_requests() {
1037 fn drive_one_frame(
1038 device: &mut SmoltcpDevice,
1039 iface: &mut Interface,
1040 sockets: &mut SocketSet<'_>,
1041 shared: &Arc<SharedState>,
1042 poll_config: &PollLoopConfig,
1043 now: Instant,
1044 ) {
1045 let frame = device.stage_next_frame().expect("expected staged frame");
1046 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1047 device.drop_staged_frame();
1048 return;
1049 }
1050 let _ = iface.poll_ingress_single(now, device, sockets);
1051 let _ = iface.poll_egress(now, device, sockets);
1052 }
1053
1054 let shared = Arc::new(SharedState::new(4));
1055 let poll_config = PollLoopConfig {
1056 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1057 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1058 gateway: GatewayIps {
1059 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1060 ipv6: Some(Ipv6Addr::LOCALHOST),
1061 },
1062 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1063 guest_ipv6: None,
1064 mtu: 1500,
1065 };
1066 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1067 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1068 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1069 let mut iface = create_interface(&mut device, &poll_config);
1070 let mut sockets = SocketSet::new(vec![]);
1071 let now = smoltcp_now();
1072
1073 shared
1076 .tx_ring
1077 .push(build_arp_request_frame(
1078 poll_config.guest_mac,
1079 guest_ipv4.octets(),
1080 gateway_ipv4.octets(),
1081 ))
1082 .unwrap();
1083 shared
1084 .tx_ring
1085 .push(build_icmpv4_echo_frame(
1086 poll_config.guest_mac,
1087 poll_config.gateway_mac,
1088 guest_ipv4.octets(),
1089 gateway_ipv4.octets(),
1090 0x1234,
1091 0xABCD,
1092 b"ping",
1093 ))
1094 .unwrap();
1095
1096 drive_one_frame(
1097 &mut device,
1098 &mut iface,
1099 &mut sockets,
1100 &shared,
1101 &poll_config,
1102 now,
1103 );
1104 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1105
1106 drive_one_frame(
1107 &mut device,
1108 &mut iface,
1109 &mut sockets,
1110 &shared,
1111 &poll_config,
1112 now,
1113 );
1114
1115 let reply = shared.rx_ring.pop().expect("expected ICMP echo reply");
1116 let eth = EthernetFrame::new_checked(&reply).expect("valid ethernet frame");
1117 assert_eq!(eth.src_addr(), EthernetAddress(poll_config.gateway_mac));
1118 assert_eq!(eth.dst_addr(), EthernetAddress(poll_config.guest_mac));
1119 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
1120
1121 let ipv4 = Ipv4Packet::new_checked(eth.payload()).expect("valid IPv4 packet");
1122 assert_eq!(ipv4.src_addr(), gateway_ipv4);
1123 assert_eq!(ipv4.dst_addr(), guest_ipv4);
1124 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
1125
1126 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).expect("valid ICMP packet");
1127 let icmp_repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default())
1128 .expect("valid ICMP echo reply");
1129 assert_eq!(
1130 icmp_repr,
1131 Icmpv4Repr::EchoReply {
1132 ident: 0x1234,
1133 seq_no: 0xABCD,
1134 data: b"ping",
1135 }
1136 );
1137 }
1138
1139 fn test_gateway() -> GatewayIps {
1140 GatewayIps {
1141 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1142 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1143 }
1144 }
1145
1146 #[test]
1147 fn resolve_host_dst_matches_ipv4() {
1148 let gw = test_gateway();
1149 let dst = SocketAddr::new(IpAddr::V4(gw.ipv4.unwrap()), 8080);
1150 assert_eq!(
1151 resolve_host_dst(dst, gw),
1152 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
1153 );
1154 }
1155
1156 #[test]
1157 fn resolve_host_dst_matches_ipv6() {
1158 let gw = test_gateway();
1159 let dst = SocketAddr::new(IpAddr::V6(gw.ipv6.unwrap()), 8080);
1160 assert_eq!(
1161 resolve_host_dst(dst, gw),
1162 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
1163 );
1164 }
1165
1166 #[test]
1167 fn resolve_host_dst_passes_through_when_family_absent() {
1168 let gw = GatewayIps {
1169 ipv4: None,
1170 ipv6: Some("fd42:6d73:62::1".parse().unwrap()),
1171 };
1172 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 96, 0, 1)), 8080);
1174 assert_eq!(resolve_host_dst(dst, gw), dst);
1175 }
1176
1177 #[test]
1178 fn resolve_host_dst_passes_through_non_gateway() {
1179 let gw = test_gateway();
1180 let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 443);
1181 assert_eq!(resolve_host_dst(dst, gw), dst);
1182 }
1183
1184 #[test]
1185 fn external_icmp_echo_requests_are_not_answered_locally() {
1186 fn drive_one_frame(
1187 device: &mut SmoltcpDevice,
1188 iface: &mut Interface,
1189 sockets: &mut SocketSet<'_>,
1190 shared: &Arc<SharedState>,
1191 poll_config: &PollLoopConfig,
1192 now: Instant,
1193 ) {
1194 let frame = device.stage_next_frame().expect("expected staged frame");
1195 if handle_gateway_icmp_echo(frame, poll_config, shared) {
1196 device.drop_staged_frame();
1197 return;
1198 }
1199 let _ = iface.poll_ingress_single(now, device, sockets);
1200 let _ = iface.poll_egress(now, device, sockets);
1201 }
1202
1203 let shared = Arc::new(SharedState::new(4));
1204 let poll_config = PollLoopConfig {
1205 gateway_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
1206 guest_mac: [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
1207 gateway: GatewayIps {
1208 ipv4: Some(Ipv4Addr::new(100, 96, 0, 1)),
1209 ipv6: Some(Ipv6Addr::LOCALHOST),
1210 },
1211 guest_ipv4: Some(Ipv4Addr::new(100, 96, 0, 2)),
1212 guest_ipv6: None,
1213 mtu: 1500,
1214 };
1215 let guest_ipv4 = poll_config.guest_ipv4.unwrap();
1216 let gateway_ipv4 = poll_config.gateway.ipv4.unwrap();
1217 let mut device = SmoltcpDevice::new(shared.clone(), poll_config.mtu);
1218 let mut iface = create_interface(&mut device, &poll_config);
1219 let mut sockets = SocketSet::new(vec![]);
1220 let now = smoltcp_now();
1221
1222 shared
1223 .tx_ring
1224 .push(build_arp_request_frame(
1225 poll_config.guest_mac,
1226 guest_ipv4.octets(),
1227 gateway_ipv4.octets(),
1228 ))
1229 .unwrap();
1230 shared
1231 .tx_ring
1232 .push(build_icmpv4_echo_frame(
1233 poll_config.guest_mac,
1234 poll_config.gateway_mac,
1235 guest_ipv4.octets(),
1236 [142, 251, 216, 46],
1237 0x1234,
1238 0xABCD,
1239 b"ping",
1240 ))
1241 .unwrap();
1242
1243 drive_one_frame(
1244 &mut device,
1245 &mut iface,
1246 &mut sockets,
1247 &shared,
1248 &poll_config,
1249 now,
1250 );
1251 let _ = shared.rx_ring.pop().expect("expected ARP reply");
1252
1253 drive_one_frame(
1254 &mut device,
1255 &mut iface,
1256 &mut sockets,
1257 &shared,
1258 &poll_config,
1259 now,
1260 );
1261 assert!(
1262 shared.rx_ring.pop().is_none(),
1263 "external ICMP should not be answered locally"
1264 );
1265 }
1266}