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