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