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 config.gateway.ipv4 == Some(dst_ip) {
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, &self.shared)
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 config.gateway.ipv6 == Some(dst_ip) {
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, &self.shared)
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.push_rx_frame_and_wake(frame) {
482 tracing::debug!(dst = %dst_ip, seq_no = reply_seq, frame_len, "ICMPv4 echo reply injected");
483 } else {
484 tracing::debug!("ICMP echo reply dropped — rx_ring full");
485 }
486
487 Ok(())
488}
489
490#[allow(clippy::too_many_arguments)]
492async fn icmpv6_echo_task(
493 dst_ip: Ipv6Addr,
494 guest_src_ip: Ipv6Addr,
495 guest_ident: u16,
496 seq_no: u16,
497 echo_data: Vec<u8>,
498 shared: Arc<SharedState>,
499 gateway_mac: EthernetAddress,
500 guest_mac: EthernetAddress,
501) -> std::io::Result<()> {
502 let socket = open_icmp_socket_v6(dst_ip)?;
503
504 let icmp_repr = Icmpv6Repr::EchoRequest {
505 ident: guest_ident,
506 seq_no,
507 data: &echo_data,
508 };
509 let mut icmp_buf = vec![0u8; icmp_repr.buffer_len()];
510 icmp_repr.emit(
513 &guest_src_ip,
514 &dst_ip,
515 &mut Icmpv6Packet::new_unchecked(&mut icmp_buf),
516 &smoltcp::phy::ChecksumCapabilities::default(),
517 );
518
519 socket.send(&icmp_buf).await?;
520
521 let mut recv_buf = vec![0u8; RECV_BUF_SIZE];
522 let n = tokio::time::timeout(ECHO_TIMEOUT, socket.recv(&mut recv_buf))
523 .await
524 .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "ICMPv6 echo timeout"))??;
525
526 let (reply_seq, reply_data) = parse_icmpv6_echo_reply(&recv_buf[..n], dst_ip, guest_src_ip)?;
527
528 let frame = construct_icmpv6_echo_reply(
529 dst_ip,
530 guest_src_ip,
531 guest_ident,
532 reply_seq,
533 reply_data,
534 gateway_mac,
535 guest_mac,
536 );
537
538 let frame_len = frame.len();
539 if shared.push_rx_frame_and_wake(frame) {
540 tracing::debug!(dst = %dst_ip, seq_no = reply_seq, frame_len, "ICMPv6 echo reply injected");
541 } else {
542 tracing::debug!("ICMPv6 echo reply dropped — rx_ring full");
543 }
544
545 Ok(())
546}
547
548fn parse_icmpv4_echo_reply(buf: &[u8]) -> std::io::Result<(u16, &[u8])> {
552 if let Ok(reply_icmp) = Icmpv4Packet::new_checked(buf)
553 && let Ok(Icmpv4Repr::EchoReply {
554 ident: _,
555 seq_no,
556 data,
557 }) = Icmpv4Repr::parse(&reply_icmp, &smoltcp::phy::ChecksumCapabilities::default())
558 {
559 return Ok((seq_no, data));
560 }
561
562 let reply_icmp = Icmpv4Packet::new_checked(extract_ipv4_icmp_payload(buf)?)
563 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
564 let Icmpv4Repr::EchoReply {
565 ident: _,
566 seq_no,
567 data,
568 } = Icmpv4Repr::parse(&reply_icmp, &smoltcp::phy::ChecksumCapabilities::default())
569 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?
570 else {
571 return Err(std::io::Error::new(
572 std::io::ErrorKind::InvalidData,
573 "host ICMPv4 reply was not an echo reply",
574 ));
575 };
576
577 Ok((seq_no, data))
578}
579
580fn parse_icmpv6_echo_reply(
585 buf: &[u8],
586 remote_ip: Ipv6Addr,
587 guest_ip: Ipv6Addr,
588) -> std::io::Result<(u16, &[u8])> {
589 if let Ok(reply_icmp) = Icmpv6Packet::new_checked(buf)
590 && let Ok(Icmpv6Repr::EchoReply {
591 ident: _,
592 seq_no,
593 data,
594 }) = Icmpv6Repr::parse(
595 &remote_ip,
596 &guest_ip,
597 &reply_icmp,
598 &smoltcp::phy::ChecksumCapabilities::default(),
599 )
600 {
601 return Ok((seq_no, data));
602 }
603
604 let reply_icmp = Icmpv6Packet::new_checked(extract_ipv6_icmp_payload(buf)?)
605 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
606 let Icmpv6Repr::EchoReply {
607 ident: _,
608 seq_no,
609 data,
610 } = Icmpv6Repr::parse(
611 &remote_ip,
612 &guest_ip,
613 &reply_icmp,
614 &smoltcp::phy::ChecksumCapabilities::default(),
615 )
616 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?
617 else {
618 return Err(std::io::Error::new(
619 std::io::ErrorKind::InvalidData,
620 "host ICMPv6 reply was not an echo reply",
621 ));
622 };
623
624 Ok((seq_no, data))
625}
626
627fn extract_ipv4_icmp_payload(buf: &[u8]) -> std::io::Result<&[u8]> {
633 if buf.len() < IPV4_HDR_LEN {
634 return Err(std::io::Error::new(
635 std::io::ErrorKind::InvalidData,
636 "host ICMPv4 reply was shorter than an IPv4 header",
637 ));
638 }
639
640 let version = buf[0] >> 4;
641 let header_len = usize::from(buf[0] & 0x0f) * 4;
642 if version != 4 || header_len < IPV4_HDR_LEN || header_len > buf.len() {
643 return Err(std::io::Error::new(
644 std::io::ErrorKind::InvalidData,
645 "host ICMPv4 reply did not contain a usable IPv4 header",
646 ));
647 }
648 if buf[9] != u8::from(IpProtocol::Icmp) {
649 return Err(std::io::Error::new(
650 std::io::ErrorKind::InvalidData,
651 "host ICMPv4 reply did not contain an ICMP payload",
652 ));
653 }
654
655 Ok(&buf[header_len..])
656}
657
658fn extract_ipv6_icmp_payload(buf: &[u8]) -> std::io::Result<&[u8]> {
660 if buf.len() < IPV6_HDR_LEN {
661 return Err(std::io::Error::new(
662 std::io::ErrorKind::InvalidData,
663 "host ICMPv6 reply was shorter than an IPv6 header",
664 ));
665 }
666
667 let version = buf[0] >> 4;
668 if version != 6 {
669 return Err(std::io::Error::new(
670 std::io::ErrorKind::InvalidData,
671 "host ICMPv6 reply did not contain a usable IPv6 header",
672 ));
673 }
674 if buf[6] != u8::from(IpProtocol::Icmpv6) {
675 return Err(std::io::Error::new(
676 std::io::ErrorKind::InvalidData,
677 "host ICMPv6 reply did not contain an ICMPv6 payload",
678 ));
679 }
680
681 Ok(&buf[IPV6_HDR_LEN..])
682}
683
684fn construct_icmpv4_echo_reply(
686 src_ip: Ipv4Addr,
687 dst_ip: Ipv4Addr,
688 ident: u16,
689 seq_no: u16,
690 data: &[u8],
691 gateway_mac: EthernetAddress,
692 guest_mac: EthernetAddress,
693) -> Vec<u8> {
694 let icmp_repr = Icmpv4Repr::EchoReply {
695 ident,
696 seq_no,
697 data,
698 };
699 let ipv4_repr = Ipv4Repr {
700 src_addr: src_ip,
701 dst_addr: dst_ip,
702 next_header: IpProtocol::Icmp,
703 payload_len: icmp_repr.buffer_len(),
704 hop_limit: 64,
705 };
706 let frame_len = ETH_HDR_LEN + ipv4_repr.buffer_len() + icmp_repr.buffer_len();
707 let mut buf = vec![0u8; frame_len];
708
709 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
711 EthernetRepr {
712 src_addr: gateway_mac,
713 dst_addr: guest_mac,
714 ethertype: EthernetProtocol::Ipv4,
715 }
716 .emit(&mut eth_frame);
717
718 ipv4_repr.emit(
720 &mut Ipv4Packet::new_unchecked(&mut buf[ETH_HDR_LEN..ETH_HDR_LEN + IPV4_HDR_LEN]),
721 &smoltcp::phy::ChecksumCapabilities::default(),
722 );
723
724 icmp_repr.emit(
726 &mut Icmpv4Packet::new_unchecked(&mut buf[ETH_HDR_LEN + IPV4_HDR_LEN..]),
727 &smoltcp::phy::ChecksumCapabilities::default(),
728 );
729
730 buf
731}
732
733fn construct_icmpv6_echo_reply(
735 src_ip: Ipv6Addr,
736 dst_ip: Ipv6Addr,
737 ident: u16,
738 seq_no: u16,
739 data: &[u8],
740 gateway_mac: EthernetAddress,
741 guest_mac: EthernetAddress,
742) -> Vec<u8> {
743 let icmp_repr = Icmpv6Repr::EchoReply {
744 ident,
745 seq_no,
746 data,
747 };
748 let frame_len = ETH_HDR_LEN + IPV6_HDR_LEN + icmp_repr.buffer_len();
749 let mut buf = vec![0u8; frame_len];
750
751 let mut eth_frame = EthernetFrame::new_unchecked(&mut buf);
753 EthernetRepr {
754 src_addr: gateway_mac,
755 dst_addr: guest_mac,
756 ethertype: EthernetProtocol::Ipv6,
757 }
758 .emit(&mut eth_frame);
759
760 Ipv6Repr {
762 src_addr: src_ip,
763 dst_addr: dst_ip,
764 next_header: IpProtocol::Icmpv6,
765 payload_len: icmp_repr.buffer_len(),
766 hop_limit: 64,
767 }
768 .emit(&mut Ipv6Packet::new_unchecked(
769 &mut buf[ETH_HDR_LEN..ETH_HDR_LEN + IPV6_HDR_LEN],
770 ));
771
772 icmp_repr.emit(
774 &src_ip,
775 &dst_ip,
776 &mut Icmpv6Packet::new_unchecked(&mut buf[ETH_HDR_LEN + IPV6_HDR_LEN..]),
777 &smoltcp::phy::ChecksumCapabilities::default(),
778 );
779
780 buf
781}
782
783#[cfg(test)]
788mod tests {
789 use super::*;
790
791 use smoltcp::phy::ChecksumCapabilities;
792
793 #[test]
794 fn construct_icmpv4_reply_roundtrips() {
795 let frame = construct_icmpv4_echo_reply(
796 Ipv4Addr::new(8, 8, 8, 8),
797 Ipv4Addr::new(100, 96, 0, 2),
798 0x1234,
799 0x0001,
800 b"hello",
801 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
802 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
803 );
804
805 let eth = EthernetFrame::new_checked(&frame).unwrap();
806 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv4);
807 assert_eq!(
808 eth.src_addr(),
809 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01])
810 );
811 assert_eq!(
812 eth.dst_addr(),
813 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02])
814 );
815
816 let ipv4 = Ipv4Packet::new_checked(eth.payload()).unwrap();
817 assert_eq!(ipv4.src_addr(), Ipv4Addr::new(8, 8, 8, 8));
818 assert_eq!(ipv4.dst_addr(), Ipv4Addr::new(100, 96, 0, 2));
819 assert_eq!(ipv4.next_header(), IpProtocol::Icmp);
820
821 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).unwrap();
822 let repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default()).unwrap();
823 assert_eq!(
824 repr,
825 Icmpv4Repr::EchoReply {
826 ident: 0x1234,
827 seq_no: 0x0001,
828 data: b"hello",
829 }
830 );
831 }
832
833 #[test]
834 fn construct_icmpv6_reply_roundtrips() {
835 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
836 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
837 let frame = construct_icmpv6_echo_reply(
838 src,
839 dst,
840 0x5678,
841 0x0002,
842 b"v6ping",
843 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
844 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
845 );
846
847 let eth = EthernetFrame::new_checked(&frame).unwrap();
848 assert_eq!(eth.ethertype(), EthernetProtocol::Ipv6);
849
850 let ipv6 = Ipv6Packet::new_checked(eth.payload()).unwrap();
851 assert_eq!(ipv6.next_header(), IpProtocol::Icmpv6);
852
853 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).unwrap();
854 let repr = Icmpv6Repr::parse(&src, &dst, &icmp, &ChecksumCapabilities::default()).unwrap();
855 assert_eq!(
856 repr,
857 Icmpv6Repr::EchoReply {
858 ident: 0x5678,
859 seq_no: 0x0002,
860 data: b"v6ping",
861 }
862 );
863
864 assert_ne!(icmp.checksum(), 0, "ICMPv6 checksum must not be zero");
866 assert!(
867 icmp.verify_checksum(&src, &dst,),
868 "ICMPv6 checksum must be valid"
869 );
870 }
871
872 #[test]
873 fn construct_icmpv4_reply_preserves_ident_and_seqno() {
874 let frame = construct_icmpv4_echo_reply(
875 Ipv4Addr::new(1, 2, 3, 4),
876 Ipv4Addr::new(10, 0, 0, 2),
877 0xABCD,
878 0xEF01,
879 b"test-payload",
880 EthernetAddress([0; 6]),
881 EthernetAddress([0; 6]),
882 );
883
884 let eth = EthernetFrame::new_checked(&frame).unwrap();
885 let ipv4 = Ipv4Packet::new_checked(eth.payload()).unwrap();
886 let icmp = Icmpv4Packet::new_checked(ipv4.payload()).unwrap();
887 let repr = Icmpv4Repr::parse(&icmp, &ChecksumCapabilities::default()).unwrap();
888 assert_eq!(
889 repr,
890 Icmpv4Repr::EchoReply {
891 ident: 0xABCD,
892 seq_no: 0xEF01,
893 data: b"test-payload",
894 }
895 );
896 }
897
898 #[test]
899 fn construct_icmpv6_reply_preserves_ident_and_seqno() {
900 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
901 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
902 let frame = construct_icmpv6_echo_reply(
903 src,
904 dst,
905 0xBEEF,
906 0xCAFE,
907 b"test6",
908 EthernetAddress([0; 6]),
909 EthernetAddress([0; 6]),
910 );
911
912 let eth = EthernetFrame::new_checked(&frame).unwrap();
913 let ipv6 = Ipv6Packet::new_checked(eth.payload()).unwrap();
914 let icmp = Icmpv6Packet::new_checked(ipv6.payload()).unwrap();
915 let repr = Icmpv6Repr::parse(&src, &dst, &icmp, &ChecksumCapabilities::default()).unwrap();
916 assert_eq!(
917 repr,
918 Icmpv6Repr::EchoReply {
919 ident: 0xBEEF,
920 seq_no: 0xCAFE,
921 data: b"test6",
922 }
923 );
924 }
925
926 #[test]
927 fn probe_does_not_panic() {
928 let _ = probe_icmp_socket_v4();
930 let _ = probe_icmp_socket_v6();
931 }
932
933 #[test]
934 fn parse_icmpv4_reply_accepts_bare_icmp() {
935 let icmp_repr = Icmpv4Repr::EchoReply {
936 ident: 0x1234,
937 seq_no: 0x0001,
938 data: b"hello",
939 };
940 let mut buf = vec![0u8; icmp_repr.buffer_len()];
941 icmp_repr.emit(
942 &mut Icmpv4Packet::new_unchecked(&mut buf),
943 &ChecksumCapabilities::default(),
944 );
945
946 let (seq_no, data) = parse_icmpv4_echo_reply(&buf).unwrap();
947 assert_eq!(seq_no, 0x0001);
948 assert_eq!(data, b"hello");
949 }
950
951 #[test]
952 fn parse_icmpv4_reply_accepts_ipv4_plus_icmp() {
953 let frame = construct_icmpv4_echo_reply(
954 Ipv4Addr::new(8, 8, 8, 8),
955 Ipv4Addr::new(100, 96, 0, 2),
956 0x1234,
957 0x0001,
958 b"hello",
959 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
960 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
961 );
962 let eth = EthernetFrame::new_checked(&frame).unwrap();
963
964 let (seq_no, data) = parse_icmpv4_echo_reply(eth.payload()).unwrap();
965 assert_eq!(seq_no, 0x0001);
966 assert_eq!(data, b"hello");
967 }
968
969 #[test]
970 fn parse_icmpv4_reply_accepts_macos_ping_socket_shape() {
971 let buf = [
972 0x45, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x01, 0x73, 0xef, 0x08, 0x08,
973 0x08, 0x08, 0xc0, 0xa8, 0x01, 0x35, 0x00, 0x00, 0xa9, 0xf8, 0x12, 0x34, 0x00, 0x01,
974 0x68, 0x65, 0x6c, 0x6c, 0x6f,
975 ];
976
977 let (seq_no, data) = parse_icmpv4_echo_reply(&buf).unwrap();
978 assert_eq!(seq_no, 0x0001);
979 assert_eq!(data, b"hello");
980 }
981
982 #[test]
983 fn parse_icmpv6_reply_accepts_bare_icmpv6() {
984 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
985 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
986 let icmp_repr = Icmpv6Repr::EchoReply {
987 ident: 0x1234,
988 seq_no: 0x0002,
989 data: b"hello6",
990 };
991 let mut buf = vec![0u8; icmp_repr.buffer_len()];
992 icmp_repr.emit(
993 &src,
994 &dst,
995 &mut Icmpv6Packet::new_unchecked(&mut buf),
996 &ChecksumCapabilities::default(),
997 );
998
999 let (seq_no, data) = parse_icmpv6_echo_reply(&buf, src, dst).unwrap();
1000 assert_eq!(seq_no, 0x0002);
1001 assert_eq!(data, b"hello6");
1002 }
1003
1004 #[test]
1005 fn parse_icmpv6_reply_accepts_ipv6_plus_icmpv6() {
1006 let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
1007 let dst: Ipv6Addr = "fd42:6d73:62::2".parse().unwrap();
1008 let frame = construct_icmpv6_echo_reply(
1009 src,
1010 dst,
1011 0x5678,
1012 0x0002,
1013 b"v6ping",
1014 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]),
1015 EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]),
1016 );
1017 let eth = EthernetFrame::new_checked(&frame).unwrap();
1018
1019 let (seq_no, data) = parse_icmpv6_echo_reply(eth.payload(), src, dst).unwrap();
1020 assert_eq!(seq_no, 0x0002);
1021 assert_eq!(data, b"v6ping");
1022 }
1023}