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 ¶ms.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}