1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
11#[cfg(unix)]
12use std::os::fd::FromRawFd;
13use std::sync::Arc;
14
15use smoltcp::wire::{
16 EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, Icmpv4Packet, Icmpv4Repr,
17 Icmpv6Packet, Icmpv6Repr, IpProtocol, Ipv4Packet, Ipv4Repr, Ipv6Packet, Ipv6Repr,
18};
19
20use crate::netstack::{poll::PollLoopConfig, shared::SharedState};
21use crate::policy::{NetworkPolicy, Protocol};
22
23const ECHO_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
29
30const RECV_BUF_SIZE: usize = 1500;
32
33const ETH_HDR_LEN: usize = 14;
35
36const IPV4_HDR_LEN: usize = 20;
38
39const IPV6_HDR_LEN: usize = 40;
41
42#[cfg_attr(windows, allow(dead_code))]
51#[derive(Debug, Clone, Copy)]
52enum EchoBackend {
53 Available,
55 Unavailable,
57}
58
59pub struct IcmpRelay {
66 shared: Arc<SharedState>,
67 gateway_mac: EthernetAddress,
68 guest_mac: EthernetAddress,
69 tokio_handle: tokio::runtime::Handle,
70 backend_v4: EchoBackend,
71 backend_v6: EchoBackend,
72}
73
74impl IcmpRelay {
79 pub fn new(
81 shared: Arc<SharedState>,
82 gateway_mac: [u8; 6],
83 guest_mac: [u8; 6],
84 tokio_handle: tokio::runtime::Handle,
85 ) -> Self {
86 let backend_v4 = probe_icmp_socket_v4();
87 let backend_v6 = probe_icmp_socket_v6();
88
89 if matches!(backend_v4, EchoBackend::Unavailable) {
90 tracing::debug!(
91 "unprivileged ICMPv4 echo sockets unavailable — external ICMPv4 relay disabled"
92 );
93 }
94 if matches!(backend_v6, EchoBackend::Unavailable) {
95 tracing::debug!(
96 "unprivileged ICMPv6 echo sockets unavailable — external ICMPv6 relay disabled"
97 );
98 }
99
100 Self {
101 shared,
102 gateway_mac: EthernetAddress(gateway_mac),
103 guest_mac: EthernetAddress(guest_mac),
104 tokio_handle,
105 backend_v4,
106 backend_v6,
107 }
108 }
109
110 pub fn relay_outbound_if_echo(
117 &self,
118 frame: &[u8],
119 config: &PollLoopConfig,
120 policy: &NetworkPolicy,
121 platform_policy: Option<&NetworkPolicy>,
122 ) -> bool {
123 let Ok(eth) = EthernetFrame::new_checked(frame) else {
124 return false;
125 };
126
127 match eth.ethertype() {
128 EthernetProtocol::Ipv4 if matches!(self.backend_v4, EchoBackend::Available) => {
129 self.try_relay_icmpv4(ð, config, policy, platform_policy)
130 }
131 EthernetProtocol::Ipv6 if matches!(self.backend_v6, EchoBackend::Available) => {
132 self.try_relay_icmpv6(ð, config, policy, platform_policy)
133 }
134 _ => false,
135 }
136 }
137}
138
139impl IcmpRelay {
140 fn try_relay_icmpv4(
142 &self,
143 eth: &EthernetFrame<&[u8]>,
144 config: &PollLoopConfig,
145 policy: &NetworkPolicy,
146 platform_policy: Option<&NetworkPolicy>,
147 ) -> bool {
148 let Ok(ipv4) = Ipv4Packet::new_checked(eth.payload()) else {
149 return false;
150 };
151 if ipv4.next_header() != IpProtocol::Icmp {
152 return false;
153 }
154
155 let dst_ip: Ipv4Addr = ipv4.dst_addr();
157 if config.gateway.ipv4 == Some(dst_ip) {
158 return false;
159 }
160
161 let Ok(icmp) = Icmpv4Packet::new_checked(ipv4.payload()) else {
162 return false;
163 };
164 let Ok(Icmpv4Repr::EchoRequest {
165 ident,
166 seq_no,
167 data,
168 }) = Icmpv4Repr::parse(&icmp, &smoltcp::phy::ChecksumCapabilities::default())
169 else {
170 return false; };
172
173 if platform_policy.is_some_and(|platform| {
175 platform
176 .evaluate_egress_ip(IpAddr::V4(dst_ip), Protocol::Icmpv4, &self.shared)
177 .is_deny()
178 }) || policy
179 .evaluate_egress_ip(IpAddr::V4(dst_ip), Protocol::Icmpv4, &self.shared)
180 .is_deny()
181 {
182 tracing::debug!(dst = %dst_ip, "ICMP echo denied by policy");
183 return true; }
185
186 let src_ip: Ipv4Addr = ipv4.src_addr();
187 let guest_ident = ident;
188 let echo_data = data.to_vec();
189
190 let shared = self.shared.clone();
191 let gateway_mac = self.gateway_mac;
192 let guest_mac = self.guest_mac;
193
194 tracing::debug!(dst = %dst_ip, seq_no, bytes = echo_data.len(), "relaying ICMPv4 echo request");
195
196 self.tokio_handle.spawn(async move {
197 if let Err(e) = icmpv4_echo_task(
198 dst_ip,
199 src_ip,
200 guest_ident,
201 seq_no,
202 echo_data,
203 shared,
204 gateway_mac,
205 guest_mac,
206 )
207 .await
208 {
209 tracing::debug!(dst = %dst_ip, error = %e, "ICMPv4 echo relay failed");
210 }
211 });
212
213 true
214 }
215
216 fn try_relay_icmpv6(
218 &self,
219 eth: &EthernetFrame<&[u8]>,
220 config: &PollLoopConfig,
221 policy: &NetworkPolicy,
222 platform_policy: Option<&NetworkPolicy>,
223 ) -> bool {
224 let Ok(ipv6) = Ipv6Packet::new_checked(eth.payload()) else {
225 return false;
226 };
227 if ipv6.next_header() != IpProtocol::Icmpv6 {
228 return false;
229 }
230
231 let dst_ip: Ipv6Addr = ipv6.dst_addr();
233 if config.gateway.ipv6 == Some(dst_ip) {
234 return false;
235 }
236
237 let Ok(icmp) = Icmpv6Packet::new_checked(ipv6.payload()) else {
238 return false;
239 };
240 let Ok(Icmpv6Repr::EchoRequest {
241 ident,
242 seq_no,
243 data,
244 }) = Icmpv6Repr::parse(
245 &ipv6.src_addr(),
246 &ipv6.dst_addr(),
247 &icmp,
248 &smoltcp::phy::ChecksumCapabilities::default(),
249 )
250 else {
251 return false; };
253
254 if platform_policy.is_some_and(|platform| {
256 platform
257 .evaluate_egress_ip(IpAddr::V6(dst_ip), Protocol::Icmpv6, &self.shared)
258 .is_deny()
259 }) || policy
260 .evaluate_egress_ip(IpAddr::V6(dst_ip), Protocol::Icmpv6, &self.shared)
261 .is_deny()
262 {
263 tracing::debug!(dst = %dst_ip, "ICMPv6 echo denied by policy");
264 return true;
265 }
266
267 let src_ip: Ipv6Addr = ipv6.src_addr();
268 let guest_ident = ident;
269 let echo_data = data.to_vec();
270
271 let shared = self.shared.clone();
272 let gateway_mac = self.gateway_mac;
273 let guest_mac = self.guest_mac;
274
275 tracing::debug!(dst = %dst_ip, seq_no, bytes = echo_data.len(), "relaying ICMPv6 echo request");
276
277 self.tokio_handle.spawn(async move {
278 if let Err(e) = icmpv6_echo_task(
279 dst_ip,
280 src_ip,
281 guest_ident,
282 seq_no,
283 echo_data,
284 shared,
285 gateway_mac,
286 guest_mac,
287 )
288 .await
289 {
290 tracing::debug!(dst = %dst_ip, error = %e, "ICMPv6 echo relay failed");
291 }
292 });
293
294 true
295 }
296}
297
298#[cfg(unix)]
304fn probe_icmp_socket_v4() -> EchoBackend {
305 let fd = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_ICMP) };
307 if fd >= 0 {
308 unsafe { libc::close(fd) };
309 EchoBackend::Available
310 } else {
311 EchoBackend::Unavailable
312 }
313}
314
315#[cfg(windows)]
317fn probe_icmp_socket_v4() -> EchoBackend {
318 EchoBackend::Unavailable
319}
320
321#[cfg(unix)]
323fn probe_icmp_socket_v6() -> EchoBackend {
324 let fd = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_ICMPV6) };
326 if fd >= 0 {
327 unsafe { libc::close(fd) };
328 EchoBackend::Available
329 } else {
330 EchoBackend::Unavailable
331 }
332}
333
334#[cfg(windows)]
336fn probe_icmp_socket_v6() -> EchoBackend {
337 EchoBackend::Unavailable
338}
339
340fn open_icmp_socket_v4(dst: Ipv4Addr) -> std::io::Result<tokio::net::UdpSocket> {
350 #[cfg(windows)]
351 {
352 let _ = dst;
353 Err(std::io::Error::new(
354 std::io::ErrorKind::Unsupported,
355 "external ICMPv4 relay is not implemented on Windows",
356 ))
357 }
358
359 #[cfg(unix)]
360 {
361 let fd = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_ICMP) };
363 if fd < 0 {
364 return Err(std::io::Error::last_os_error());
365 }
366
367 if let Err(e) = set_nonblock_cloexec(fd) {
369 unsafe { libc::close(fd) };
370 return Err(e);
371 }
372
373 let addr = libc::sockaddr_in {
374 sin_family: libc::AF_INET as libc::sa_family_t,
375 sin_port: 0,
376 sin_addr: libc::in_addr {
377 s_addr: u32::from(dst).to_be(),
378 },
379 sin_zero: [0; 8],
380 #[cfg(target_os = "macos")]
381 sin_len: std::mem::size_of::<libc::sockaddr_in>() as u8,
382 };
383
384 let ret = unsafe {
386 libc::connect(
387 fd,
388 &addr as *const libc::sockaddr_in as *const libc::sockaddr,
389 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
390 )
391 };
392 if ret < 0 {
393 let err = std::io::Error::last_os_error();
394 unsafe { libc::close(fd) };
395 return Err(err);
396 }
397
398 let std_sock = unsafe { std::net::UdpSocket::from_raw_fd(fd) };
400 tokio::net::UdpSocket::from_std(std_sock)
401 }
402}
403
404fn open_icmp_socket_v6(dst: Ipv6Addr) -> std::io::Result<tokio::net::UdpSocket> {
406 #[cfg(windows)]
407 {
408 let _ = dst;
409 Err(std::io::Error::new(
410 std::io::ErrorKind::Unsupported,
411 "external ICMPv6 relay is not implemented on Windows",
412 ))
413 }
414
415 #[cfg(unix)]
416 {
417 let fd = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_ICMPV6) };
418 if fd < 0 {
419 return Err(std::io::Error::last_os_error());
420 }
421
422 if let Err(e) = set_nonblock_cloexec(fd) {
423 unsafe { libc::close(fd) };
424 return Err(e);
425 }
426
427 let addr = libc::sockaddr_in6 {
428 sin6_family: libc::AF_INET6 as libc::sa_family_t,
429 sin6_port: 0,
430 sin6_flowinfo: 0,
431 sin6_addr: libc::in6_addr {
432 s6_addr: dst.octets(),
433 },
434 sin6_scope_id: 0,
435 #[cfg(target_os = "macos")]
436 sin6_len: std::mem::size_of::<libc::sockaddr_in6>() as u8,
437 };
438
439 let ret = unsafe {
440 libc::connect(
441 fd,
442 &addr as *const libc::sockaddr_in6 as *const libc::sockaddr,
443 std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t,
444 )
445 };
446 if ret < 0 {
447 let err = std::io::Error::last_os_error();
448 unsafe { libc::close(fd) };
449 return Err(err);
450 }
451
452 let std_sock = unsafe { std::net::UdpSocket::from_raw_fd(fd) };
453 tokio::net::UdpSocket::from_std(std_sock)
454 }
455}
456
457#[cfg(unix)]
459fn set_nonblock_cloexec(fd: libc::c_int) -> std::io::Result<()> {
460 unsafe {
461 let flags = libc::fcntl(fd, libc::F_GETFL);
462 if flags < 0 {
463 return Err(std::io::Error::last_os_error());
464 }
465 if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 {
466 return Err(std::io::Error::last_os_error());
467 }
468 let flags = libc::fcntl(fd, libc::F_GETFD);
469 if flags < 0 {
470 return Err(std::io::Error::last_os_error());
471 }
472 if libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) < 0 {
473 return Err(std::io::Error::last_os_error());
474 }
475 }
476 Ok(())
477}
478
479#[allow(clippy::too_many_arguments)]
481async fn icmpv4_echo_task(
482 dst_ip: Ipv4Addr,
483 guest_src_ip: Ipv4Addr,
484 guest_ident: u16,
485 seq_no: u16,
486 echo_data: Vec<u8>,
487 shared: Arc<SharedState>,
488 gateway_mac: EthernetAddress,
489 guest_mac: EthernetAddress,
490) -> std::io::Result<()> {
491 let socket = open_icmp_socket_v4(dst_ip)?;
492
493 let icmp_repr = Icmpv4Repr::EchoRequest {
498 ident: guest_ident,
499 seq_no,
500 data: &echo_data,
501 };
502 let mut icmp_buf = vec![0u8; icmp_repr.buffer_len()];
503 icmp_repr.emit(
504 &mut Icmpv4Packet::new_unchecked(&mut icmp_buf),
505 &smoltcp::phy::ChecksumCapabilities::default(),
506 );
507
508 socket.send(&icmp_buf).await?;
509
510 let mut recv_buf = vec![0u8; RECV_BUF_SIZE];
514 let n = tokio::time::timeout(ECHO_TIMEOUT, socket.recv(&mut recv_buf))
515 .await
516 .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "ICMP echo timeout"))??;
517
518 let (reply_seq, reply_data) = parse_icmpv4_echo_reply(&recv_buf[..n])?;
519
520 let frame = construct_icmpv4_echo_reply(
522 dst_ip,
523 guest_src_ip,
524 guest_ident,
525 reply_seq,
526 reply_data,
527 gateway_mac,
528 guest_mac,
529 );
530
531 let frame_len = frame.len();
532 if shared.push_rx_frame_and_wake(frame) {
533 tracing::debug!(dst = %dst_ip, seq_no = reply_seq, frame_len, "ICMPv4 echo reply injected");
534 } else {
535 tracing::debug!("ICMP echo reply dropped — rx_ring full");
536 }
537
538 Ok(())
539}
540
541#[allow(clippy::too_many_arguments)]
543async fn icmpv6_echo_task(
544 dst_ip: Ipv6Addr,
545 guest_src_ip: Ipv6Addr,
546 guest_ident: u16,
547 seq_no: u16,
548 echo_data: Vec<u8>,
549 shared: Arc<SharedState>,
550 gateway_mac: EthernetAddress,
551 guest_mac: EthernetAddress,
552) -> std::io::Result<()> {
553 let socket = open_icmp_socket_v6(dst_ip)?;
554
555 let icmp_repr = Icmpv6Repr::EchoRequest {
556 ident: guest_ident,
557 seq_no,
558 data: &echo_data,
559 };
560 let mut icmp_buf = vec![0u8; icmp_repr.buffer_len()];
561 icmp_repr.emit(
564 &guest_src_ip,
565 &dst_ip,
566 &mut Icmpv6Packet::new_unchecked(&mut icmp_buf),
567 &smoltcp::phy::ChecksumCapabilities::default(),
568 );
569
570 socket.send(&icmp_buf).await?;
571
572 let mut recv_buf = vec![0u8; RECV_BUF_SIZE];
573 let n = tokio::time::timeout(ECHO_TIMEOUT, socket.recv(&mut recv_buf))
574 .await
575 .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "ICMPv6 echo timeout"))??;
576
577 let (reply_seq, reply_data) = parse_icmpv6_echo_reply(&recv_buf[..n], dst_ip, guest_src_ip)?;
578
579 let frame = construct_icmpv6_echo_reply(
580 dst_ip,
581 guest_src_ip,
582 guest_ident,
583 reply_seq,
584 reply_data,
585 gateway_mac,
586 guest_mac,
587 );
588
589 let frame_len = frame.len();
590 if shared.push_rx_frame_and_wake(frame) {
591 tracing::debug!(dst = %dst_ip, seq_no = reply_seq, frame_len, "ICMPv6 echo reply injected");
592 } else {
593 tracing::debug!("ICMPv6 echo reply dropped — rx_ring full");
594 }
595
596 Ok(())
597}
598
599fn parse_icmpv4_echo_reply(buf: &[u8]) -> std::io::Result<(u16, &[u8])> {
603 if let Ok(reply_icmp) = Icmpv4Packet::new_checked(buf)
604 && let Ok(Icmpv4Repr::EchoReply {
605 ident: _,
606 seq_no,
607 data,
608 }) = Icmpv4Repr::parse(&reply_icmp, &smoltcp::phy::ChecksumCapabilities::default())
609 {
610 return Ok((seq_no, data));
611 }
612
613 let reply_icmp = Icmpv4Packet::new_checked(extract_ipv4_icmp_payload(buf)?)
614 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
615 let Icmpv4Repr::EchoReply {
616 ident: _,
617 seq_no,
618 data,
619 } = Icmpv4Repr::parse(&reply_icmp, &smoltcp::phy::ChecksumCapabilities::default())
620 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?
621 else {
622 return Err(std::io::Error::new(
623 std::io::ErrorKind::InvalidData,
624 "host ICMPv4 reply was not an echo reply",
625 ));
626 };
627
628 Ok((seq_no, data))
629}
630
631fn parse_icmpv6_echo_reply(
636 buf: &[u8],
637 remote_ip: Ipv6Addr,
638 guest_ip: Ipv6Addr,
639) -> std::io::Result<(u16, &[u8])> {
640 if let Ok(reply_icmp) = Icmpv6Packet::new_checked(buf)
641 && let Ok(Icmpv6Repr::EchoReply {
642 ident: _,
643 seq_no,
644 data,
645 }) = Icmpv6Repr::parse(
646 &remote_ip,
647 &guest_ip,
648 &reply_icmp,
649 &smoltcp::phy::ChecksumCapabilities::default(),
650 )
651 {
652 return Ok((seq_no, data));
653 }
654
655 let reply_icmp = Icmpv6Packet::new_checked(extract_ipv6_icmp_payload(buf)?)
656 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
657 let Icmpv6Repr::EchoReply {
658 ident: _,
659 seq_no,
660 data,
661 } = Icmpv6Repr::parse(
662 &remote_ip,
663 &guest_ip,
664 &reply_icmp,
665 &smoltcp::phy::ChecksumCapabilities::default(),
666 )
667 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?
668 else {
669 return Err(std::io::Error::new(
670 std::io::ErrorKind::InvalidData,
671 "host ICMPv6 reply was not an echo reply",
672 ));
673 };
674
675 Ok((seq_no, data))
676}
677
678fn extract_ipv4_icmp_payload(buf: &[u8]) -> std::io::Result<&[u8]> {
684 if buf.len() < IPV4_HDR_LEN {
685 return Err(std::io::Error::new(
686 std::io::ErrorKind::InvalidData,
687 "host ICMPv4 reply was shorter than an IPv4 header",
688 ));
689 }
690
691 let version = buf[0] >> 4;
692 let header_len = usize::from(buf[0] & 0x0f) * 4;
693 if version != 4 || header_len < IPV4_HDR_LEN || header_len > buf.len() {
694 return Err(std::io::Error::new(
695 std::io::ErrorKind::InvalidData,
696 "host ICMPv4 reply did not contain a usable IPv4 header",
697 ));
698 }
699 if buf[9] != u8::from(IpProtocol::Icmp) {
700 return Err(std::io::Error::new(
701 std::io::ErrorKind::InvalidData,
702 "host ICMPv4 reply did not contain an ICMP payload",
703 ));
704 }
705
706 Ok(&buf[header_len..])
707}
708
709fn extract_ipv6_icmp_payload(buf: &[u8]) -> std::io::Result<&[u8]> {
711 if buf.len() < IPV6_HDR_LEN {
712 return Err(std::io::Error::new(
713 std::io::ErrorKind::InvalidData,
714 "host ICMPv6 reply was shorter than an IPv6 header",
715 ));
716 }
717
718 let version = buf[0] >> 4;
719 if version != 6 {
720 return Err(std::io::Error::new(
721 std::io::ErrorKind::InvalidData,
722 "host ICMPv6 reply did not contain a usable IPv6 header",
723 ));
724 }
725 if buf[6] != u8::from(IpProtocol::Icmpv6) {
726 return Err(std::io::Error::new(
727 std::io::ErrorKind::InvalidData,
728 "host ICMPv6 reply did not contain an ICMPv6 payload",
729 ));
730 }
731
732 Ok(&buf[IPV6_HDR_LEN..])
733}
734
735fn construct_icmpv4_echo_reply(
737 src_ip: Ipv4Addr,
738 dst_ip: Ipv4Addr,
739 ident: u16,
740 seq_no: u16,
741 data: &[u8],
742 gateway_mac: EthernetAddress,
743 guest_mac: EthernetAddress,
744) -> Vec<u8> {
745 let icmp_repr = Icmpv4Repr::EchoReply {
746 ident,
747 seq_no,
748 data,
749 };
750 let ipv4_repr = Ipv4Repr {
751 src_addr: src_ip,
752 dst_addr: dst_ip,
753 next_header: IpProtocol::Icmp,
754 payload_len: icmp_repr.buffer_len(),
755 hop_limit: 64,
756 };
757 let frame_len = ETH_HDR_LEN + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
758 let mut buf = vec![0u8; frame_len];
759
760 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
762 EthernetRepr {
763 src_addr: gateway_mac,
764 dst_addr: guest_mac,
765 ethertype: EthernetProtocol::Ipv4,
766 }
767 .emit(&mut eth_frame);
768
769 ipv4_repr.emit(
771 &mut Ipv4Packet::new_unchecked(&mut buf[ETH_HDR_LEN..ETH_HDR_LEN + IPV4_HDR_LEN]),
772 &smoltcp::phy::ChecksumCapabilities::default(),
773 );
774
775 icmp_repr.emit(
777 &mut Icmpv4Packet::new_unchecked(&mut buf[ETH_HDR_LEN + IPV4_HDR_LEN..]),
778 &smoltcp::phy::ChecksumCapabilities::default(),
779 );
780
781 buf
782}
783
784fn construct_icmpv6_echo_reply(
786 src_ip: Ipv6Addr,
787 dst_ip: Ipv6Addr,
788 ident: u16,
789 seq_no: u16,
790 data: &[u8],
791 gateway_mac: EthernetAddress,
792 guest_mac: EthernetAddress,
793) -> Vec<u8> {
794 let icmp_repr = Icmpv6Repr::EchoReply {
795 ident,
796 seq_no,
797 data,
798 };
799 let frame_len = ETH_HDR_LEN + IPV6_HDR_LEN + icmp_repr.buffer_len();
800 let mut buf = vec![0u8; frame_len];
801
802 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
804 EthernetRepr {
805 src_addr: gateway_mac,
806 dst_addr: guest_mac,
807 ethertype: EthernetProtocol::Ipv6,
808 }
809 .emit(&mut eth_frame);
810
811 Ipv6Repr {
813 src_addr: src_ip,
814 dst_addr: dst_ip,
815 next_header: IpProtocol::Icmpv6,
816 payload_len: icmp_repr.buffer_len(),
817 hop_limit: 64,
818 }
819 .emit(&mut Ipv6Packet::new_unchecked(
820 &mut buf[ETH_HDR_LEN..ETH_HDR_LEN + IPV6_HDR_LEN],
821 ));
822
823 icmp_repr.emit(
825 &src_ip,
826 &dst_ip,
827 &mut Icmpv6Packet::new_unchecked(&mut buf[ETH_HDR_LEN + IPV6_HDR_LEN..]),
828 &smoltcp::phy::ChecksumCapabilities::default(),
829 );
830
831 buf
832}
833
834#[cfg(test)]
839mod tests {
840 use super::*;
841
842 use smoltcp::phy::ChecksumCapabilities;
843
844 #[test]
845 fn construct_icmpv4_reply_roundtrips() {
846 let frame = construct_icmpv4_echo_reply(
847 Ipv4Addr::new(8, 8, 8, 8),
848 Ipv4Addr::new(100, 96, 0, 2),
849 0x1234,
850 0x0001,
851 b"hello",
852 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
853 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
854 );
855
856 let eth = EthernetFrame::new_checked(&frame).unwrap();
857 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
858 assert_eq!(
859 eth.src_addr(),
860 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01])
861 );
862 assert_eq!(
863 eth.dst_addr(),
864 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02])
865 );
866
867 let ipv4 = Ipv4Packet::new_checked(eth.payload()).unwrap();
868 assert_eq!(ipv4.src_addr(), Ipv4Addr::new(8, 8, 8, 8));
869 assert_eq!(ipv4.dst_addr(), Ipv4Addr::new(100, 96, 0, 2));
870 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
871
872 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).unwrap();
873 let repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default()).unwrap();
874 assert_eq!(
875 repr,
876 Icmpv4Repr::EchoReply {
877 ident: 0x1234,
878 seq_no: 0x0001,
879 data: b"hello",
880 }
881 );
882 }
883
884 #[test]
885 fn construct_icmpv6_reply_roundtrips() {
886 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
887 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
888 let frame = construct_icmpv6_echo_reply(
889 src,
890 dst,
891 0x5678,
892 0x0002,
893 b"v6ping",
894 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
895 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
896 );
897
898 let eth = EthernetFrame::new_checked(&frame).unwrap();
899 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv6);
900
901 let ipv6 = Ipv6Packet::new_checked(eth.payload()).unwrap();
902 assert_eq!(ipv6.next_header(), IpProtocol::Icmpv6);
903
904 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).unwrap();
905 let repr = Icmpv6Repr::parse(&src, &dst, &icmp, &ChecksumCapabilities::default()).unwrap();
906 assert_eq!(
907 repr,
908 Icmpv6Repr::EchoReply {
909 ident: 0x5678,
910 seq_no: 0x0002,
911 data: b"v6ping",
912 }
913 );
914
915 assert_ne!(icmp.checksum(), 0, "ICMPv6 checksum must not be zero");
917 assert!(
918 icmp.verify_checksum(&src, &dst,),
919 "ICMPv6 checksum must be valid"
920 );
921 }
922
923 #[test]
924 fn construct_icmpv4_reply_preserves_ident_and_seqno() {
925 let frame = construct_icmpv4_echo_reply(
926 Ipv4Addr::new(1, 2, 3, 4),
927 Ipv4Addr::new(10, 0, 0, 2),
928 0xABCD,
929 0xEF01,
930 b"test-payload",
931 EthernetAddress([0; 6]),
932 EthernetAddress([0; 6]),
933 );
934
935 let eth = EthernetFrame::new_checked(&frame).unwrap();
936 let ipv4 = Ipv4Packet::new_checked(eth.payload()).unwrap();
937 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).unwrap();
938 let repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default()).unwrap();
939 assert_eq!(
940 repr,
941 Icmpv4Repr::EchoReply {
942 ident: 0xABCD,
943 seq_no: 0xEF01,
944 data: b"test-payload",
945 }
946 );
947 }
948
949 #[test]
950 fn construct_icmpv6_reply_preserves_ident_and_seqno() {
951 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
952 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
953 let frame = construct_icmpv6_echo_reply(
954 src,
955 dst,
956 0xBEEF,
957 0xCAFE,
958 b"test6",
959 EthernetAddress([0; 6]),
960 EthernetAddress([0; 6]),
961 );
962
963 let eth = EthernetFrame::new_checked(&frame).unwrap();
964 let ipv6 = Ipv6Packet::new_checked(eth.payload()).unwrap();
965 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).unwrap();
966 let repr = Icmpv6Repr::parse(&src, &dst, &icmp, &ChecksumCapabilities::default()).unwrap();
967 assert_eq!(
968 repr,
969 Icmpv6Repr::EchoReply {
970 ident: 0xBEEF,
971 seq_no: 0xCAFE,
972 data: b"test6",
973 }
974 );
975 }
976
977 #[test]
978 fn probe_does_not_panic() {
979 let _ = probe_icmp_socket_v4();
981 let _ = probe_icmp_socket_v6();
982 }
983
984 #[test]
985 fn parse_icmpv4_reply_accepts_bare_icmp() {
986 let icmp_repr = Icmpv4Repr::EchoReply {
987 ident: 0x1234,
988 seq_no: 0x0001,
989 data: b"hello",
990 };
991 let mut buf = vec![0u8; icmp_repr.buffer_len()];
992 icmp_repr.emit(
993 &mut Icmpv4Packet::new_unchecked(&mut buf),
994 &ChecksumCapabilities::default(),
995 );
996
997 let (seq_no, data) = parse_icmpv4_echo_reply(&buf).unwrap();
998 assert_eq!(seq_no, 0x0001);
999 assert_eq!(data, b"hello");
1000 }
1001
1002 #[test]
1003 fn parse_icmpv4_reply_accepts_ipv4_plus_icmp() {
1004 let frame = construct_icmpv4_echo_reply(
1005 Ipv4Addr::new(8, 8, 8, 8),
1006 Ipv4Addr::new(100, 96, 0, 2),
1007 0x1234,
1008 0x0001,
1009 b"hello",
1010 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
1011 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
1012 );
1013 let eth = EthernetFrame::new_checked(&frame).unwrap();
1014
1015 let (seq_no, data) = parse_icmpv4_echo_reply(eth.payload()).unwrap();
1016 assert_eq!(seq_no, 0x0001);
1017 assert_eq!(data, b"hello");
1018 }
1019
1020 #[test]
1021 fn parse_icmpv4_reply_accepts_macos_ping_socket_shape() {
1022 let buf = [
1023 0x45, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x01, 0x73, 0xef, 0x08, 0x08,
1024 0x08, 0x08, 0xc0, 0xa8, 0x01, 0x35, 0x00, 0x00, 0xa9, 0xf8, 0x12, 0x34, 0x00, 0x01,
1025 0x68, 0x65, 0x6c, 0x6c, 0x6f,
1026 ];
1027
1028 let (seq_no, data) = parse_icmpv4_echo_reply(&buf).unwrap();
1029 assert_eq!(seq_no, 0x0001);
1030 assert_eq!(data, b"hello");
1031 }
1032
1033 #[test]
1034 fn parse_icmpv6_reply_accepts_bare_icmpv6() {
1035 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
1036 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
1037 let icmp_repr = Icmpv6Repr::EchoReply {
1038 ident: 0x1234,
1039 seq_no: 0x0002,
1040 data: b"hello6",
1041 };
1042 let mut buf = vec![0u8; icmp_repr.buffer_len()];
1043 icmp_repr.emit(
1044 &src,
1045 &dst,
1046 &mut Icmpv6Packet::new_unchecked(&mut buf),
1047 &ChecksumCapabilities::default(),
1048 );
1049
1050 let (seq_no, data) = parse_icmpv6_echo_reply(&buf, src, dst).unwrap();
1051 assert_eq!(seq_no, 0x0002);
1052 assert_eq!(data, b"hello6");
1053 }
1054
1055 #[test]
1056 fn parse_icmpv6_reply_accepts_ipv6_plus_icmpv6() {
1057 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
1058 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
1059 let frame = construct_icmpv6_echo_reply(
1060 src,
1061 dst,
1062 0x5678,
1063 0x0002,
1064 b"v6ping",
1065 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
1066 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
1067 );
1068 let eth = EthernetFrame::new_checked(&frame).unwrap();
1069
1070 let (seq_no, data) = parse_icmpv6_echo_reply(eth.payload(), src, dst).unwrap();
1071 assert_eq!(seq_no, 0x0002);
1072 assert_eq!(data, b"v6ping");
1073 }
1074}