Skip to main content

embassy_net/
icmp.rs

1//! ICMP sockets.
2
3use core::future::{Future, poll_fn};
4use core::mem;
5use core::task::{Context, Poll};
6
7use smoltcp::iface::{Interface, SocketHandle};
8pub use smoltcp::phy::ChecksumCapabilities;
9use smoltcp::socket::icmp;
10pub use smoltcp::socket::icmp::{Endpoint as IcmpEndpoint, PacketMetadata};
11use smoltcp::wire::IpAddress;
12#[cfg(feature = "proto-ipv4")]
13pub use smoltcp::wire::{Icmpv4Message, Icmpv4Packet, Icmpv4Repr};
14#[cfg(feature = "proto-ipv6")]
15pub use smoltcp::wire::{Icmpv6Message, Icmpv6Packet, Icmpv6Repr};
16
17use crate::Stack;
18
19/// Error returned by [`IcmpSocket::bind`].
20#[derive(PartialEq, Eq, Clone, Copy, Debug)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22pub enum BindError {
23    /// The socket was already open.
24    InvalidState,
25    /// The endpoint isn't specified
26    InvalidEndpoint,
27    /// No route to host.
28    NoRoute,
29}
30
31/// Error returned by [`IcmpSocket::send_to`].
32#[derive(PartialEq, Eq, Clone, Copy, Debug)]
33#[cfg_attr(feature = "defmt", derive(defmt::Format))]
34pub enum SendError {
35    /// No route to host.
36    NoRoute,
37    /// Socket not bound to an outgoing port.
38    SocketNotBound,
39    /// There is not enough transmit buffer capacity to ever send this packet.
40    PacketTooLarge,
41}
42
43/// Error returned by [`IcmpSocket::recv_from`].
44#[derive(PartialEq, Eq, Clone, Copy, Debug)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46pub enum RecvError {
47    /// Provided buffer was smaller than the received packet.
48    Truncated,
49}
50
51/// An ICMP socket.
52pub struct IcmpSocket<'a> {
53    stack: Stack<'a>,
54    handle: SocketHandle,
55}
56
57impl<'a> IcmpSocket<'a> {
58    /// Create a new ICMP socket using the provided stack and buffers.
59    pub fn new(
60        stack: Stack<'a>,
61        rx_meta: &'a mut [PacketMetadata],
62        rx_buffer: &'a mut [u8],
63        tx_meta: &'a mut [PacketMetadata],
64        tx_buffer: &'a mut [u8],
65    ) -> Self {
66        let handle = stack.with_mut(|i| {
67            let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
68            let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
69            let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
70            let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
71            i.sockets.add(icmp::Socket::new(
72                icmp::PacketBuffer::new(rx_meta, rx_buffer),
73                icmp::PacketBuffer::new(tx_meta, tx_buffer),
74            ))
75        });
76
77        Self { stack, handle }
78    }
79
80    /// Bind the socket to the given endpoint.
81    pub fn bind<T>(&mut self, endpoint: T) -> Result<(), BindError>
82    where
83        T: Into<IcmpEndpoint>,
84    {
85        let endpoint = endpoint.into();
86
87        if !endpoint.is_specified() {
88            return Err(BindError::InvalidEndpoint);
89        }
90
91        match self.with_mut(|s, _| s.bind(endpoint)) {
92            Ok(()) => Ok(()),
93            Err(icmp::BindError::InvalidState) => Err(BindError::InvalidState),
94            Err(icmp::BindError::Unaddressable) => Err(BindError::NoRoute),
95        }
96    }
97
98    fn with<R>(&self, f: impl FnOnce(&icmp::Socket, &Interface) -> R) -> R {
99        self.stack.with(|i| {
100            let socket = i.sockets.get::<icmp::Socket>(self.handle);
101            f(socket, &i.iface)
102        })
103    }
104
105    fn with_mut<R>(&self, f: impl FnOnce(&mut icmp::Socket, &mut Interface) -> R) -> R {
106        self.stack.with_mut(|i| {
107            let socket = i.sockets.get_mut::<icmp::Socket>(self.handle);
108            let res = f(socket, &mut i.iface);
109            i.waker.wake();
110            res
111        })
112    }
113
114    /// Wait until the socket becomes readable.
115    ///
116    /// A socket is readable when a packet has been received, or when there are queued packets in
117    /// the buffer.
118    pub fn wait_recv_ready(&self) -> impl Future<Output = ()> + '_ {
119        poll_fn(move |cx| self.poll_recv_ready(cx))
120    }
121
122    /// Wait until a datagram can be read.
123    ///
124    /// When no datagram is readable, this method will return `Poll::Pending` and
125    /// register the current task to be notified when a datagram is received.
126    ///
127    /// When a datagram is received, this method will return `Poll::Ready`.
128    pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
129        self.with_mut(|s, _| {
130            if s.can_recv() {
131                Poll::Ready(())
132            } else {
133                // socket buffer is empty wait until at least one byte has arrived
134                s.register_recv_waker(cx.waker());
135                Poll::Pending
136            }
137        })
138    }
139
140    /// Receive a datagram.
141    ///
142    /// This method will wait until a datagram is received.
143    ///
144    /// Returns the number of bytes received and the remote endpoint.
145    pub fn recv_from<'s>(
146        &'s self,
147        buf: &'s mut [u8],
148    ) -> impl Future<Output = Result<(usize, IpAddress), RecvError>> + 's {
149        poll_fn(|cx| self.poll_recv_from(buf, cx))
150    }
151
152    /// Receive a datagram.
153    ///
154    /// When no datagram is available, this method will return `Poll::Pending` and
155    /// register the current task to be notified when a datagram is received.
156    ///
157    /// When a datagram is received, this method will return `Poll::Ready` with the
158    /// number of bytes received and the remote endpoint.
159    pub fn poll_recv_from(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll<Result<(usize, IpAddress), RecvError>> {
160        self.with_mut(|s, _| match s.recv_slice(buf) {
161            Ok((n, meta)) => Poll::Ready(Ok((n, meta))),
162            // No data ready
163            Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)),
164            Err(icmp::RecvError::Exhausted) => {
165                s.register_recv_waker(cx.waker());
166                Poll::Pending
167            }
168        })
169    }
170
171    /// Dequeue a packet received from a remote endpoint and calls the provided function with the
172    /// slice of the packet and the remote endpoint address and returns `Poll::Ready` with the
173    /// function's returned value.
174    ///
175    /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
176    /// the packet is dropped and a `RecvError::Truncated` error is returned.
177    pub async fn recv_from_with<F, R>(&self, f: F) -> Result<R, RecvError>
178    where
179        F: FnOnce((&[u8], IpAddress)) -> R,
180    {
181        let mut f = Some(f);
182        poll_fn(move |cx| {
183            self.with_mut(|s, _| match s.recv() {
184                Ok(x) => Poll::Ready(Ok(unwrap!(f.take())(x))),
185                Err(icmp::RecvError::Exhausted) => {
186                    cx.waker().wake_by_ref();
187                    Poll::Pending
188                }
189                Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)),
190            })
191        })
192        .await
193    }
194
195    /// Wait until the socket becomes writable.
196    ///
197    /// A socket becomes writable when there is space in the buffer, from initial memory or after
198    /// dispatching datagrams on a full buffer.
199    pub fn wait_send_ready(&self) -> impl Future<Output = ()> + '_ {
200        poll_fn(|cx| self.poll_send_ready(cx))
201    }
202
203    /// Wait until a datagram can be sent.
204    ///
205    /// When no datagram can be sent (i.e. the buffer is full), this method will return
206    /// `Poll::Pending` and register the current task to be notified when
207    /// space is freed in the buffer after a datagram has been dispatched.
208    ///
209    /// When a datagram can be sent, this method will return `Poll::Ready`.
210    pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
211        self.with_mut(|s, _| {
212            if s.can_send() {
213                Poll::Ready(())
214            } else {
215                // socket buffer is full wait until a datagram has been dispatched
216                s.register_send_waker(cx.waker());
217                Poll::Pending
218            }
219        })
220    }
221
222    /// Send a datagram to the specified remote endpoint.
223    ///
224    /// This method will wait until the datagram has been sent.
225    ///
226    /// If the socket's send buffer is too small to fit `buf`, this method will return `Err(SendError::PacketTooLarge)`
227    ///
228    /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)`
229    pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError>
230    where
231        T: Into<IpAddress>,
232    {
233        let remote_endpoint: IpAddress = remote_endpoint.into();
234        poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await
235    }
236
237    /// Send a datagram to the specified remote endpoint.
238    ///
239    /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`.
240    ///
241    /// When the socket's send buffer is full, this method will return `Poll::Pending`
242    /// and register the current task to be notified when the buffer has space available.
243    ///
244    /// If the socket's send buffer is too small to fit `buf`, this method will return `Poll::Ready(Err(SendError::PacketTooLarge))`
245    ///
246    /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`.
247    pub fn poll_send_to<T>(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll<Result<(), SendError>>
248    where
249        T: Into<IpAddress>,
250    {
251        // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer.
252        let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < buf.len());
253        if send_capacity_too_small {
254            return Poll::Ready(Err(SendError::PacketTooLarge));
255        }
256
257        self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint.into()) {
258            // Entire datagram has been sent
259            Ok(()) => Poll::Ready(Ok(())),
260            Err(icmp::SendError::BufferFull) => {
261                s.register_send_waker(cx.waker());
262                Poll::Pending
263            }
264            Err(icmp::SendError::Unaddressable) => {
265                // If no sender/outgoing port is specified, there is not really "no route"
266                if s.is_open() {
267                    Poll::Ready(Err(SendError::NoRoute))
268                } else {
269                    Poll::Ready(Err(SendError::SocketNotBound))
270                }
271            }
272        })
273    }
274
275    /// Enqueue a packet to be sent to a given remote address with a zero-copy function.
276    ///
277    /// This method will wait until the buffer can fit the requested size before
278    /// passing it to the closure. The closure returns the number of bytes
279    /// written into the buffer.
280    pub async fn send_to_with<T, F, R>(&mut self, max_size: usize, remote_endpoint: T, f: F) -> Result<R, SendError>
281    where
282        T: Into<IpAddress>,
283        F: FnOnce(&mut [u8]) -> (usize, R),
284    {
285        // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer.
286        let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < max_size);
287        if send_capacity_too_small {
288            return Err(SendError::PacketTooLarge);
289        }
290
291        let mut f = Some(f);
292        let remote_endpoint = remote_endpoint.into();
293        poll_fn(move |cx| {
294            self.with_mut(|s, _| {
295                let mut ret = None;
296
297                match s.send_with(max_size, remote_endpoint, |buf| {
298                    let (size, r) = unwrap!(f.take())(buf);
299                    ret = Some(r);
300                    size
301                }) {
302                    Ok(_n) => Poll::Ready(Ok(unwrap!(ret))),
303                    Err(icmp::SendError::BufferFull) => {
304                        s.register_send_waker(cx.waker());
305                        Poll::Pending
306                    }
307                    Err(icmp::SendError::Unaddressable) => Poll::Ready(Err(SendError::NoRoute)),
308                }
309            })
310        })
311        .await
312    }
313
314    /// Flush the socket.
315    ///
316    /// This method will wait until the socket is flushed.
317    pub fn flush(&mut self) -> impl Future<Output = ()> + '_ {
318        poll_fn(|cx| {
319            self.with_mut(|s, _| {
320                if s.send_queue() == 0 {
321                    Poll::Ready(())
322                } else {
323                    s.register_send_waker(cx.waker());
324                    Poll::Pending
325                }
326            })
327        })
328    }
329
330    /// Check whether the socket is open.
331    pub fn is_open(&self) -> bool {
332        self.with(|s, _| s.is_open())
333    }
334
335    /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet.
336    pub fn may_send(&self) -> bool {
337        self.with(|s, _| s.can_send())
338    }
339
340    /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer.
341    pub fn may_recv(&self) -> bool {
342        self.with(|s, _| s.can_recv())
343    }
344
345    /// Return the maximum number packets the socket can receive.
346    pub fn packet_recv_capacity(&self) -> usize {
347        self.with(|s, _| s.packet_recv_capacity())
348    }
349
350    /// Return the maximum number packets the socket can receive.
351    pub fn packet_send_capacity(&self) -> usize {
352        self.with(|s, _| s.packet_send_capacity())
353    }
354
355    /// Return the maximum number of bytes inside the recv buffer.
356    pub fn payload_recv_capacity(&self) -> usize {
357        self.with(|s, _| s.payload_recv_capacity())
358    }
359
360    /// Return the maximum number of bytes inside the transmit buffer.
361    pub fn payload_send_capacity(&self) -> usize {
362        self.with(|s, _| s.payload_send_capacity())
363    }
364
365    /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
366    pub fn hop_limit(&self) -> Option<u8> {
367        self.with(|s, _| s.hop_limit())
368    }
369
370    /// Set the hop limit field in the IP header of sent packets.
371    pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
372        self.with_mut(|s, _| s.set_hop_limit(hop_limit))
373    }
374}
375
376impl Drop for IcmpSocket<'_> {
377    fn drop(&mut self) {
378        self.stack.with_mut(|i| i.sockets.remove(self.handle));
379    }
380}
381
382pub mod ping {
383    //! Ping utilities.
384    //!
385    //! This module allows for an easy ICMP Echo message interface used to
386    //! ping devices with an [ICMP Socket](IcmpSocket).
387    //!
388    //! ## Usage
389    //!
390    //! ```
391    //! use core::net::Ipv4Addr;
392    //! use core::str::FromStr;
393    //!
394    //! use embassy_net::icmp::ping::{PingManager, PingParams};
395    //! use embassy_net::icmp::PacketMetadata;
396    //!
397    //! let mut rx_buffer = [0; 256];
398    //! let mut tx_buffer = [0; 256];
399    //! let mut rx_meta = [PacketMetadata::EMPTY];
400    //! let mut tx_meta = [PacketMetadata::EMPTY];
401    //!
402    //! let mut ping_manager = PingManager::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer);
403    //! let addr = "192.168.8.1";
404    //! let mut ping_params = PingParams::new(Ipv4Addr::from_str(addr).unwrap());
405    //! ping_params.set_payload(b"Hello, router!");
406    //! match ping_manager.ping(&ping_params).await {
407    //!     Ok(time) => info!("Ping time of {}: {}ms", addr, time.as_millis()),
408    //!     Err(ping_error) => warn!("{:?}", ping_error),
409    //! };
410    //! ```
411
412    use core::net::IpAddr;
413    #[cfg(feature = "proto-ipv6")]
414    use core::net::Ipv6Addr;
415
416    use embassy_time::{Duration, Instant, Timer, WithTimeout};
417    #[cfg(feature = "proto-ipv6")]
418    use smoltcp::wire::IpAddress;
419    #[cfg(feature = "proto-ipv6")]
420    use smoltcp::wire::Ipv6Address;
421
422    use super::*;
423
424    /// Error returned by [`ping()`](PingManager::ping).
425    #[derive(PartialEq, Eq, Clone, Copy, Debug)]
426    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
427    pub enum PingError {
428        /// The target did not respond.
429        ///
430        /// The packet was sent but the Reply packet has not been recieved
431        /// in the timeout set by [`set_timeout()`](PingParams::set_timeout).
432        DestinationHostUnreachable,
433        /// The target has not been specified.
434        InvalidTargetAddress,
435        /// The source has not been specified (Ipv6 only).
436        #[cfg(feature = "proto-ipv6")]
437        InvalidSourceAddress,
438        /// The socket could not queue the packet in the buffer.
439        SocketSendTimeout,
440        /// Container error for [`icmp::BindError`].
441        SocketBindError(BindError),
442        /// Container error for [`icmp::SendError`].
443        SocketSendError(SendError),
444        /// Container error for [`icmp::RecvError`].
445        SocketRecvError(RecvError),
446    }
447
448    /// Manages ICMP ping operations.
449    ///
450    /// This struct provides functionality to send ICMP echo requests (pings) to a specified target
451    /// and measure the round-trip time for the requests. It supports both IPv4 and IPv6, depending
452    /// on the enabled features.
453    ///
454    /// # Fields
455    ///
456    /// * `stack` - The network stack instance used for managing network operations.
457    /// * `rx_meta` - Metadata buffer for receiving packets.
458    /// * `rx_buffer` - Buffer for receiving packets.
459    /// * `tx_meta` - Metadata buffer for transmitting packets.
460    /// * `tx_buffer` - Buffer for transmitting packets.
461    /// * `ident` - Identifier for the ICMP echo requests.
462    ///
463    /// # Methods
464    ///
465    /// * [`new`](PingManager::new) - Creates a new instance of `PingManager` with the specified stack and buffers.
466    /// * [`ping`](PingManager::ping) - Sends ICMP echo requests to the specified target and returns the average round-trip time.
467    pub struct PingManager<'d> {
468        stack: Stack<'d>,
469        rx_meta: &'d mut [PacketMetadata],
470        rx_buffer: &'d mut [u8],
471        tx_meta: &'d mut [PacketMetadata],
472        tx_buffer: &'d mut [u8],
473        ident: u16,
474    }
475
476    impl<'d> PingManager<'d> {
477        /// Creates a new instance of [`PingManager`] with a [`Stack`] instance
478        /// and the buffers used for RX and TX.
479        ///
480        /// **note**: This does not yet creates the ICMP socket.
481        pub fn new(
482            stack: Stack<'d>,
483            rx_meta: &'d mut [PacketMetadata],
484            rx_buffer: &'d mut [u8],
485            tx_meta: &'d mut [PacketMetadata],
486            tx_buffer: &'d mut [u8],
487        ) -> Self {
488            Self {
489                stack,
490                rx_meta,
491                rx_buffer,
492                tx_meta,
493                tx_buffer,
494                ident: 0,
495            }
496        }
497
498        /// Sends ICMP echo requests to the specified target and returns the average round-trip time.
499        ///
500        /// # Arguments
501        ///
502        /// * `params` - Parameters for configuring the ping operation.
503        ///
504        /// # Returns
505        ///
506        /// * `Ok(Duration)` - The average round-trip time for the ping requests.
507        /// * `Err(PingError)` - An error occurred during the ping operation.
508        pub async fn ping<'a>(&mut self, params: &PingParams<'a>) -> Result<Duration, PingError> {
509            // Input validation
510            if params.target().is_none() {
511                return Err(PingError::InvalidTargetAddress);
512            }
513            #[cfg(feature = "proto-ipv6")]
514            if params.target().unwrap().is_ipv6() && params.source().is_none() {
515                return Err(PingError::InvalidSourceAddress);
516            }
517            // Increment the ident (wrapping u16) to respect standards
518            self.ident = self.ident.wrapping_add(1u16);
519            // Used to calculate the average duration
520            let mut total_duration = Duration::default();
521            let mut num_of_durations = 0u16;
522            // Increment the sequence number as per standards
523            for seq_no in 0..params.count() {
524                // Make sure each ping takes at least 1 second to respect standards
525                let rate_limit_start = Instant::now();
526
527                // make a single ping
528                // - shorts out errors
529                // - select the ip version
530                let ping_duration = match params.target.unwrap() {
531                    #[cfg(feature = "proto-ipv4")]
532                    IpAddress::Ipv4(_) => self.single_ping_v4(params, seq_no).await?,
533                    #[cfg(feature = "proto-ipv6")]
534                    IpAddress::Ipv6(_) => self.single_ping_v6(params, seq_no).await?,
535                };
536
537                // safely add up the durations of each ping
538                if let Some(dur) = total_duration.checked_add(ping_duration) {
539                    total_duration = dur;
540                    num_of_durations += 1;
541                }
542
543                // 1 sec min per ping
544                let rate_limit_end = rate_limit_start.elapsed();
545                if rate_limit_end <= params.rate_limit {
546                    Timer::after(params.rate_limit.checked_sub(rate_limit_end).unwrap()).await;
547                }
548            }
549            // calculate and return the average duration
550            Ok(total_duration.checked_div(num_of_durations as u32).unwrap())
551        }
552
553        #[cfg(feature = "proto-ipv4")]
554        fn create_repr_ipv4<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv4Repr<'b> {
555            Icmpv4Repr::EchoRequest {
556                ident: self.ident,
557                seq_no,
558                data: params.payload,
559            }
560        }
561
562        #[cfg(feature = "proto-ipv6")]
563        fn create_repr_ipv6<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv6Repr<'b> {
564            Icmpv6Repr::EchoRequest {
565                ident: self.ident,
566                seq_no,
567                data: params.payload,
568            }
569        }
570
571        #[cfg(feature = "proto-ipv4")]
572        async fn single_ping_v4(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result<Duration, PingError> {
573            let ping_repr = self.create_repr_ipv4(params, seq_no);
574
575            // Create the socket and set hop limit and bind it to the endpoint with the ident
576            let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer);
577            socket.set_hop_limit(params.hop_limit);
578            if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) {
579                return Err(PingError::SocketBindError(e));
580            }
581
582            // Helper func to fill the buffer when sending the ICMP packet
583            fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv4Repr<'_>) -> Instant {
584                let mut icmp_packet = Icmpv4Packet::new_unchecked(buf);
585                ping_repr.emit(&mut icmp_packet, &ChecksumCapabilities::default());
586                Instant::now()
587            }
588
589            // Send with timeout the ICMP packet filling it with the helper function
590            let send_result = socket
591                .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| {
592                    (buf.len(), fill_packet_buffer(buf, ping_repr))
593                })
594                .with_timeout(Duration::from_millis(100))
595                .await;
596            // Filter and translate potential errors from sending the packet
597            let now = match send_result {
598                Ok(send_result) => match send_result {
599                    Ok(i) => i,
600                    Err(e) => return Err(PingError::SocketSendError(e)),
601                },
602                Err(_) => return Err(PingError::SocketSendTimeout),
603            };
604
605            // Helper function for the recieve helper function to validate the echo reply
606            fn filter_pong(buf: &[u8], seq_no: u16) -> bool {
607                let pong_packet = match Icmpv4Packet::new_checked(buf) {
608                    Ok(pak) => pak,
609                    Err(_) => return false,
610                };
611                pong_packet.echo_seq_no() == seq_no
612            }
613
614            // Helper function to recieve and return the correct echo reply when it finds it
615            async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> {
616                while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await {
617                    Ok(b) => !b,
618                    Err(e) => return Err(PingError::SocketRecvError(e)),
619                } {}
620                Ok(())
621            }
622
623            // Calls the recieve helper function with a timeout
624            match recv_pong(&socket, seq_no).with_timeout(params.timeout).await {
625                Ok(res) => res?,
626                Err(_) => return Err(PingError::DestinationHostUnreachable),
627            }
628
629            // Return the round trip duration
630            Ok(now.elapsed())
631        }
632
633        #[cfg(feature = "proto-ipv6")]
634        async fn single_ping_v6(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result<Duration, PingError> {
635            let ping_repr = self.create_repr_ipv6(params, seq_no);
636
637            // Create the socket and set hop limit and bind it to the endpoint with the ident
638            let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer);
639            socket.set_hop_limit(params.hop_limit);
640            if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) {
641                return Err(PingError::SocketBindError(e));
642            }
643
644            // Helper func to fill the buffer when sending the ICMP packet
645            fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv6Repr<'_>, params: &PingParams<'_>) -> Instant {
646                let mut icmp_packet = Icmpv6Packet::new_unchecked(buf);
647                let target = match params.target().unwrap() {
648                    IpAddr::V4(_) => unreachable!(),
649                    IpAddr::V6(addr) => addr,
650                };
651                ping_repr.emit(
652                    &params.source().unwrap(),
653                    &target,
654                    &mut icmp_packet,
655                    &ChecksumCapabilities::default(),
656                );
657                Instant::now()
658            }
659
660            // Send with timeout the ICMP packet filling it with the helper function
661            let send_result = socket
662                .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| {
663                    (buf.len(), fill_packet_buffer(buf, ping_repr, params))
664                })
665                .with_timeout(Duration::from_millis(100))
666                .await;
667            let now = match send_result {
668                Ok(send_result) => match send_result {
669                    Ok(i) => i,
670                    Err(e) => return Err(PingError::SocketSendError(e)),
671                },
672                Err(_) => return Err(PingError::SocketSendTimeout),
673            };
674
675            // Helper function for the recieve helper function to validate the echo reply
676            fn filter_pong(buf: &[u8], seq_no: u16) -> bool {
677                let pong_packet = match Icmpv6Packet::new_checked(buf) {
678                    Ok(pak) => pak,
679                    Err(_) => return false,
680                };
681                pong_packet.echo_seq_no() == seq_no
682            }
683
684            // Helper function to recieve and return the correct echo reply when it finds it
685            async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> {
686                while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await {
687                    Ok(b) => !b,
688                    Err(e) => return Err(PingError::SocketRecvError(e)),
689                } {}
690                Ok(())
691            }
692
693            // Calls the recieve helper function with a timeout
694            match recv_pong(&socket, seq_no).with_timeout(params.timeout).await {
695                Ok(res) => res?,
696                Err(_) => return Err(PingError::DestinationHostUnreachable),
697            }
698
699            // Return the round trip duration
700            Ok(now.elapsed())
701        }
702    }
703
704    /// Parameters for configuring the ping operation.
705    ///
706    /// This struct provides various configuration options for performing ICMP ping operations,
707    /// including the target IP address, payload data, hop limit, number of pings, and timeout duration.
708    ///
709    /// # Fields
710    ///
711    /// * `target` - The target IP address for the ping operation.
712    /// * `source` - The source IP address for the ping operation (IPv6 only).
713    /// * `payload` - The data to be sent in the payload field of the ping.
714    /// * `hop_limit` - The hop limit to be used by the socket.
715    /// * `count` - The number of pings to be sent in one ping operation.
716    /// * `timeout` - The timeout duration before returning a [`PingError::DestinationHostUnreachable`] error.
717    /// * `rate_limit` - The minimum time per echo request.
718    pub struct PingParams<'a> {
719        target: Option<IpAddress>,
720        #[cfg(feature = "proto-ipv6")]
721        source: Option<Ipv6Address>,
722        payload: &'a [u8],
723        hop_limit: Option<u8>,
724        count: u16,
725        timeout: Duration,
726        rate_limit: Duration,
727    }
728
729    impl Default for PingParams<'_> {
730        fn default() -> Self {
731            Self {
732                target: None,
733                #[cfg(feature = "proto-ipv6")]
734                source: None,
735                payload: b"embassy-net",
736                hop_limit: None,
737                count: 4,
738                timeout: Duration::from_secs(4),
739                rate_limit: Duration::from_secs(1),
740            }
741        }
742    }
743
744    impl<'a> PingParams<'a> {
745        /// Creates a new instance of [`PingParams`] with the specified target IP address.
746        pub fn new<T: Into<IpAddr>>(target: T) -> Self {
747            Self {
748                target: Some(PingParams::ip_addr_to_smoltcp(target)),
749                #[cfg(feature = "proto-ipv6")]
750                source: None,
751                payload: b"embassy-net",
752                hop_limit: None,
753                count: 4,
754                timeout: Duration::from_secs(4),
755                rate_limit: Duration::from_secs(1),
756            }
757        }
758
759        fn ip_addr_to_smoltcp<T: Into<IpAddr>>(ip_addr: T) -> IpAddress {
760            match ip_addr.into() {
761                #[cfg(feature = "proto-ipv4")]
762                IpAddr::V4(v4) => IpAddress::Ipv4(v4),
763                #[cfg(not(feature = "proto-ipv4"))]
764                IpAddr::V4(_) => unreachable!(),
765                #[cfg(feature = "proto-ipv6")]
766                IpAddr::V6(v6) => IpAddress::Ipv6(v6),
767                #[cfg(not(feature = "proto-ipv6"))]
768                IpAddr::V6(_) => unreachable!(),
769            }
770        }
771
772        /// Sets the target IP address for the ping.
773        pub fn set_target<T: Into<IpAddr>>(&mut self, target: T) -> &mut Self {
774            self.target = Some(PingParams::ip_addr_to_smoltcp(target));
775            self
776        }
777
778        /// Retrieves the target IP address for the ping.
779        pub fn target(&self) -> Option<IpAddr> {
780            self.target.map(|t| t.into())
781        }
782
783        /// Sets the source IP address for the ping (IPv6 only).
784        #[cfg(feature = "proto-ipv6")]
785        pub fn set_source<T: Into<Ipv6Address>>(&mut self, source: T) -> &mut Self {
786            self.source = Some(source.into());
787            self
788        }
789
790        /// Retrieves the source IP address for the ping (IPv6 only).
791        #[cfg(feature = "proto-ipv6")]
792        pub fn source(&self) -> Option<Ipv6Addr> {
793            self.source
794        }
795
796        /// Sets the data used in the payload field of the ping with the provided slice.
797        pub fn set_payload(&mut self, payload: &'a [u8]) -> &mut Self {
798            self.payload = payload;
799            self
800        }
801
802        /// Gives a reference to the slice of data that's going to be sent in the payload field
803        /// of the ping.
804        pub fn payload(&self) -> &'a [u8] {
805            self.payload
806        }
807
808        /// Sets the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit).
809        ///
810        /// **Note**: A hop limit of [`Some(0)`](Some()) is equivalent to a hop limit of [`None`].
811        pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) -> &mut Self {
812            let mut hop_limit = hop_limit;
813            if hop_limit.is_some_and(|x| x == 0) {
814                hop_limit = None
815            }
816            self.hop_limit = hop_limit;
817            self
818        }
819
820        /// Retrieves the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit).
821        pub fn hop_limit(&self) -> Option<u8> {
822            self.hop_limit
823        }
824
825        /// Sets the count used for specifying the number of pings done on one
826        /// [`ping()`](PingManager::ping) call.
827        ///
828        /// **Note**: A count of 0 will be set as 1.
829        pub fn set_count(&mut self, count: u16) -> &mut Self {
830            let mut count = count;
831            if count == 0 {
832                count = 1;
833            }
834            self.count = count;
835            self
836        }
837
838        /// Retrieve the count used for specifying the number of pings done on one
839        /// [`ping()`](PingManager::ping) call.
840        pub fn count(&self) -> u16 {
841            self.count
842        }
843
844        /// Sets the timeout used before returning [`PingError::DestinationHostUnreachable`]
845        /// when waiting for the Echo Reply icmp packet.
846        pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self {
847            self.timeout = timeout;
848            self
849        }
850
851        /// Retrieve the timeout used before returning [`PingError::DestinationHostUnreachable`]
852        /// when waiting for the Echo Reply icmp packet.
853        pub fn timeout(&self) -> Duration {
854            self.timeout
855        }
856
857        /// Sets the `rate_limit`: minimum time per echo request.
858        pub fn set_rate_limit(&mut self, rate_limit: Duration) -> &mut Self {
859            self.rate_limit = rate_limit;
860            self
861        }
862
863        /// Retrieve the rate_limit.
864        pub fn rate_limit(&self) -> Duration {
865            self.rate_limit
866        }
867    }
868}