aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskkeye <[email protected]>2025-02-09 01:06:04 -0500
committerUlf Lilleengen <[email protected]>2025-02-13 10:27:30 +0100
commit74c1fd64d218c3ac5d1ab7a2c1155a87e4169150 (patch)
tree0fec915e2bc962669da9e190b6b14ecd978abaac
parentf54ba5a48e8783c673488c9107a3549ec35bd45e (diff)
embassy-net: add ICMP sockets and a ping utility
-rw-r--r--embassy-net/Cargo.toml2
-rw-r--r--embassy-net/src/icmp.rs704
-rw-r--r--embassy-net/src/lib.rs2
3 files changed, 708 insertions, 0 deletions
diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml
index a0827d96b..eda372cb6 100644
--- a/embassy-net/Cargo.toml
+++ b/embassy-net/Cargo.toml
@@ -33,6 +33,8 @@ packet-trace = []
33#! the [smoltcp feature flag documentation](https://github.com/smoltcp-rs/smoltcp#feature-flags) 33#! the [smoltcp feature flag documentation](https://github.com/smoltcp-rs/smoltcp#feature-flags)
34#! for more details 34#! for more details
35 35
36## Enable ICMP support
37icmp = ["smoltcp/socket-icmp"]
36## Enable UDP support 38## Enable UDP support
37udp = ["smoltcp/socket-udp"] 39udp = ["smoltcp/socket-udp"]
38## Enable Raw support 40## Enable Raw support
diff --git a/embassy-net/src/icmp.rs b/embassy-net/src/icmp.rs
new file mode 100644
index 000000000..4cce5db50
--- /dev/null
+++ b/embassy-net/src/icmp.rs
@@ -0,0 +1,704 @@
1//! ICMP sockets.
2
3use core::future::poll_fn;
4use core::mem;
5use core::task::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}
40
41/// Error returned by [`IcmpSocket::recv_from`].
42#[derive(PartialEq, Eq, Clone, Copy, Debug)]
43#[cfg_attr(feature = "defmt", derive(defmt::Format))]
44pub enum RecvError {
45 /// Provided buffer was smaller than the received packet.
46 Truncated,
47}
48
49/// An ICMP socket.
50pub struct IcmpSocket<'a> {
51 stack: Stack<'a>,
52 handle: SocketHandle,
53}
54
55impl<'a> IcmpSocket<'a> {
56 /// Create a new ICMP socket using the provided stack and buffers.
57 pub fn new(
58 stack: Stack<'a>,
59 rx_meta: &'a mut [PacketMetadata],
60 rx_buffer: &'a mut [u8],
61 tx_meta: &'a mut [PacketMetadata],
62 tx_buffer: &'a mut [u8],
63 ) -> Self {
64 let handle = stack.with_mut(|i| {
65 let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
66 let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
67 let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
68 let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
69 i.sockets.add(icmp::Socket::new(
70 icmp::PacketBuffer::new(rx_meta, rx_buffer),
71 icmp::PacketBuffer::new(tx_meta, tx_buffer),
72 ))
73 });
74
75 Self { stack, handle }
76 }
77
78 /// Bind the socket to the given endpoint.
79 pub fn bind<T>(&mut self, endpoint: T) -> Result<(), BindError>
80 where
81 T: Into<IcmpEndpoint>,
82 {
83 let endpoint = endpoint.into();
84
85 if !endpoint.is_specified() {
86 return Err(BindError::InvalidEndpoint);
87 }
88
89 match self.with_mut(|s, _| s.bind(endpoint)) {
90 Ok(()) => Ok(()),
91 Err(icmp::BindError::InvalidState) => Err(BindError::InvalidState),
92 Err(icmp::BindError::Unaddressable) => Err(BindError::NoRoute),
93 }
94 }
95
96 fn with<R>(&self, f: impl FnOnce(&icmp::Socket, &Interface) -> R) -> R {
97 self.stack.with(|i| {
98 let socket = i.sockets.get::<icmp::Socket>(self.handle);
99 f(socket, &i.iface)
100 })
101 }
102
103 fn with_mut<R>(&self, f: impl FnOnce(&mut icmp::Socket, &mut Interface) -> R) -> R {
104 self.stack.with_mut(|i| {
105 let socket = i.sockets.get_mut::<icmp::Socket>(self.handle);
106 let res = f(socket, &mut i.iface);
107 i.waker.wake();
108 res
109 })
110 }
111
112 /// Dequeue a packet received from a remote endpoint, copy the payload into the given slice,
113 /// and return the amount of octets copied as well as the `IpAddress`
114 ///
115 /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
116 /// the packet is dropped and a `RecvError::Truncated` error is returned.
117 pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpAddress), RecvError> {
118 poll_fn(move |cx| {
119 self.with_mut(|s, _| match s.recv_slice(buf) {
120 Ok(x) => Poll::Ready(Ok(x)),
121 // No data ready
122 Err(icmp::RecvError::Exhausted) => {
123 //s.register_recv_waker(cx.waker());
124 cx.waker().wake_by_ref();
125 Poll::Pending
126 }
127 Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)),
128 })
129 })
130 .await
131 }
132
133 /// Dequeue a packet received from a remote endpoint and calls the provided function with the
134 /// slice of the packet and the remote endpoint address and returns `Poll::Ready` with the
135 /// function's returned value.
136 ///
137 /// **Note**: when the size of the provided buffer is smaller than the size of the payload,
138 /// the packet is dropped and a `RecvError::Truncated` error is returned.
139 pub async fn recv_with<F, R>(&self, f: F) -> Result<R, RecvError>
140 where
141 F: FnOnce((&[u8], IpAddress)) -> R,
142 {
143 let mut f = Some(f);
144 poll_fn(move |cx| {
145 self.with_mut(|s, _| match s.recv() {
146 Ok(x) => Poll::Ready(Ok(unwrap!(f.take())(x))),
147 Err(icmp::RecvError::Exhausted) => {
148 cx.waker().wake_by_ref();
149 Poll::Pending
150 }
151 Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)),
152 })
153 })
154 .await
155 }
156
157 /// Enqueue a packet to be sent to a given remote address, and fill it from a slice.
158 pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError>
159 where
160 T: Into<IpAddress>,
161 {
162 let remote_endpoint = remote_endpoint.into();
163 poll_fn(move |cx| {
164 self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) {
165 // Entire datagram has been sent
166 Ok(()) => Poll::Ready(Ok(())),
167 Err(icmp::SendError::BufferFull) => {
168 s.register_send_waker(cx.waker());
169 Poll::Pending
170 }
171 Err(icmp::SendError::Unaddressable) => Poll::Ready(Err(SendError::NoRoute)),
172 })
173 })
174 .await
175 }
176
177 /// Enqueue a packet to be sent to a given remote address with a zero-copy function.
178 ///
179 /// This method will wait until the buffer can fit the requested size before
180 /// calling the function to fill its contents.
181 pub async fn send_to_with<T, F, R>(&self, size: usize, remote_endpoint: T, f: F) -> Result<R, SendError>
182 where
183 T: Into<IpAddress>,
184 F: FnOnce(&mut [u8]) -> R,
185 {
186 let mut f = Some(f);
187 let remote_endpoint = remote_endpoint.into();
188 poll_fn(move |cx| {
189 self.with_mut(|s, _| match s.send(size, remote_endpoint) {
190 Ok(buf) => Poll::Ready(Ok(unwrap!(f.take())(buf))),
191 Err(icmp::SendError::BufferFull) => {
192 s.register_send_waker(cx.waker());
193 Poll::Pending
194 }
195 Err(icmp::SendError::Unaddressable) => Poll::Ready(Err(SendError::NoRoute)),
196 })
197 })
198 .await
199 }
200
201 /// Check whether the socket is open.
202 pub fn is_open(&self) -> bool {
203 self.with(|s, _| s.is_open())
204 }
205
206 /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet.
207 pub fn may_send(&self) -> bool {
208 self.with(|s, _| s.can_send())
209 }
210
211 /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer.
212 pub fn may_recv(&self) -> bool {
213 self.with(|s, _| s.can_recv())
214 }
215
216 /// Return the maximum number packets the socket can receive.
217 pub fn packet_recv_capacity(&self) -> usize {
218 self.with(|s, _| s.packet_recv_capacity())
219 }
220
221 /// Return the maximum number packets the socket can receive.
222 pub fn packet_send_capacity(&self) -> usize {
223 self.with(|s, _| s.packet_send_capacity())
224 }
225
226 /// Return the maximum number of bytes inside the recv buffer.
227 pub fn payload_recv_capacity(&self) -> usize {
228 self.with(|s, _| s.payload_recv_capacity())
229 }
230
231 /// Return the maximum number of bytes inside the transmit buffer.
232 pub fn payload_send_capacity(&self) -> usize {
233 self.with(|s, _| s.payload_send_capacity())
234 }
235
236 /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
237 pub fn hop_limit(&self) -> Option<u8> {
238 self.with(|s, _| s.hop_limit())
239 }
240
241 /// Set the hop limit field in the IP header of sent packets.
242 pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
243 self.with_mut(|s, _| s.set_hop_limit(hop_limit))
244 }
245}
246
247impl Drop for IcmpSocket<'_> {
248 fn drop(&mut self) {
249 self.stack.with_mut(|i| i.sockets.remove(self.handle));
250 }
251}
252
253pub mod ping {
254 //! Ping utilities.
255 //!
256 //! This module allows for an easy ICMP Echo message interface used to
257 //! ping devices with an [ICMP Socket](IcmpSocket).
258 //!
259 //! ## Usage
260 //!
261 //! ```
262 //! use core::net::Ipv4Addr;
263 //! use core::str::FromStr;
264 //!
265 //! use embassy_net::icmp::ping::{PingManager, PingParams};
266 //! use embassy_net::icmp::PacketMetadata;
267 //!
268 //! let mut rx_buffer = [0; 256];
269 //! let mut tx_buffer = [0; 256];
270 //! let mut rx_meta = [PacketMetadata::EMPTY];
271 //! let mut tx_meta = [PacketMetadata::EMPTY];
272 //!
273 //! let mut ping_manager = PingManager::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer);
274 //! let addr = "192.168.8.1";
275 //! let mut ping_params = PingParams::new(Ipv4Addr::from_str(addr).unwrap());
276 //! ping_params.set_payload(b"Hello, router!");
277 //! match ping_manager.ping(&ping_params).await {
278 //! Ok(time) => info!("Ping time of {}: {}ms", addr, time.as_millis()),
279 //! Err(ping_error) => warn!("{:?}", ping_error),
280 //! };
281 //! ```
282
283 use super::*;
284 use core::net::{IpAddr, Ipv6Addr};
285 use embassy_time::{Duration, Instant, Timer, WithTimeout};
286
287 /// Error returned by [`ping()`](PingManager::ping).
288 #[derive(PartialEq, Eq, Clone, Copy, Debug)]
289 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
290 pub enum PingError {
291 /// The target did not respond.
292 ///
293 /// The packet was sent but the Reply packet has not been recieved
294 /// in the timeout set by [`set_timeout()`](PingParams::set_timeout).
295 DestinationHostUnreachable,
296 /// The target has not been specified.
297 InvalidTargetAddress,
298 /// The source has not been specified (Ipv6 only).
299 #[cfg(feature = "proto-ipv6")]
300 InvalidSourceAddress,
301 /// The socket could not queue the packet in the buffer.
302 SocketSendTimeout,
303 /// Container error for [`icmp::BindError`].
304 SocketBindError(BindError),
305 /// Container error for [`icmp::SendError`].
306 SocketSendError(SendError),
307 /// Container error for [`icmp::RecvError`].
308 SocketRecvError(RecvError),
309 }
310
311 /// Manages ICMP ping operations.
312 ///
313 /// This struct provides functionality to send ICMP echo requests (pings) to a specified target
314 /// and measure the round-trip time for the requests. It supports both IPv4 and IPv6, depending
315 /// on the enabled features.
316 ///
317 /// # Fields
318 ///
319 /// * `stack` - The network stack instance used for managing network operations.
320 /// * `rx_meta` - Metadata buffer for receiving packets.
321 /// * `rx_buffer` - Buffer for receiving packets.
322 /// * `tx_meta` - Metadata buffer for transmitting packets.
323 /// * `tx_buffer` - Buffer for transmitting packets.
324 /// * `ident` - Identifier for the ICMP echo requests.
325 ///
326 /// # Methods
327 ///
328 /// * [`new`](PingManager::new) - Creates a new instance of `PingManager` with the specified stack and buffers.
329 /// * [`ping`](PingManager::ping) - Sends ICMP echo requests to the specified target and returns the average round-trip time.
330 pub struct PingManager<'d> {
331 stack: Stack<'d>,
332 rx_meta: &'d mut [PacketMetadata],
333 rx_buffer: &'d mut [u8],
334 tx_meta: &'d mut [PacketMetadata],
335 tx_buffer: &'d mut [u8],
336 ident: u16,
337 }
338
339 impl<'d> PingManager<'d> {
340 /// Creates a new instance of [`PingManager`] with a [`Stack`] instance
341 /// and the buffers used for RX and TX.
342 ///
343 /// **note**: This does not yet creates the ICMP socket.
344 pub fn new(
345 stack: Stack<'d>,
346 rx_meta: &'d mut [PacketMetadata],
347 rx_buffer: &'d mut [u8],
348 tx_meta: &'d mut [PacketMetadata],
349 tx_buffer: &'d mut [u8],
350 ) -> Self {
351 Self {
352 stack,
353 rx_meta,
354 rx_buffer,
355 tx_meta,
356 tx_buffer,
357 ident: 0,
358 }
359 }
360
361 /// Sends ICMP echo requests to the specified target and returns the average round-trip time.
362 ///
363 /// # Arguments
364 ///
365 /// * `params` - Parameters for configuring the ping operation.
366 ///
367 /// # Returns
368 ///
369 /// * `Ok(Duration)` - The average round-trip time for the ping requests.
370 /// * `Err(PingError)` - An error occurred during the ping operation.
371 pub async fn ping<'a>(&mut self, params: &PingParams<'a>) -> Result<Duration, PingError> {
372 // Input validation
373 if params.target().is_none() {
374 return Err(PingError::InvalidTargetAddress);
375 }
376 #[cfg(feature = "proto-ipv6")]
377 if params.target().unwrap().is_ipv6() && params.source().is_none() {
378 return Err(PingError::InvalidSourceAddress);
379 }
380 // Increment the ident (wrapping u16) to respect standards
381 self.ident = self.ident.wrapping_add(1u16);
382 // Used to calculate the average duration
383 let mut total_duration = Duration::default();
384 let mut num_of_durations = 0u16;
385 // Increment the sequence number as per standards
386 for seq_no in 0..params.count() {
387 // Make sure each ping takes at least 1 second to respect standards
388 let rate_limit_start = Instant::now();
389
390 // make a single ping
391 // - shorts out errors
392 // - select the ip version
393 let ping_duration = match params.target().unwrap() {
394 #[cfg(feature = "proto-ipv4")]
395 IpAddr::V4(_) => self.single_ping_v4(params, seq_no).await?,
396 #[cfg(feature = "proto-ipv6")]
397 IpAddr::V6(_) => self.single_ping_v6(params, seq_no).await?,
398 };
399
400 // safely add up the durations of each ping
401 if let Some(dur) = total_duration.checked_add(ping_duration) {
402 total_duration = dur;
403 num_of_durations += 1;
404 }
405
406 // 1 sec min per ping
407 let rate_limit_end = rate_limit_start.elapsed();
408 if rate_limit_end <= Duration::from_secs(1) {
409 Timer::after(Duration::from_secs(1).checked_sub(rate_limit_end).unwrap()).await;
410 }
411 }
412 // calculate and return the average duration
413 Ok(total_duration.checked_div(num_of_durations as u32).unwrap())
414 }
415
416 #[cfg(feature = "proto-ipv4")]
417 fn create_repr_ipv4<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv4Repr<'b> {
418 Icmpv4Repr::EchoRequest {
419 ident: self.ident,
420 seq_no,
421 data: params.payload,
422 }
423 }
424
425 #[cfg(feature = "proto-ipv6")]
426 fn create_repr_ipv6<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv6Repr<'b> {
427 Icmpv6Repr::EchoRequest {
428 ident: self.ident,
429 seq_no,
430 data: params.payload,
431 }
432 }
433
434 #[cfg(feature = "proto-ipv4")]
435 async fn single_ping_v4(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result<Duration, PingError> {
436 let ping_repr = self.create_repr_ipv4(params, seq_no);
437
438 // Create the socket and set hop limit and bind it to the endpoint with the ident
439 let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer);
440 socket.set_hop_limit(params.hop_limit);
441 if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) {
442 return Err(PingError::SocketBindError(e));
443 }
444
445 // Helper func to fill the buffer when sending the ICMP packet
446 fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv4Repr<'_>) -> Instant {
447 let mut icmp_packet = Icmpv4Packet::new_unchecked(buf);
448 ping_repr.emit(&mut icmp_packet, &ChecksumCapabilities::default());
449 Instant::now()
450 }
451
452 // Send with timeout the ICMP packet filling it with the helper function
453 let send_result = socket
454 .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| {
455 fill_packet_buffer(buf, ping_repr)
456 })
457 .with_timeout(Duration::from_millis(100))
458 .await;
459 // Filter and translate potential errors from sending the packet
460 let now = match send_result {
461 Ok(send_result) => match send_result {
462 Ok(i) => i,
463 Err(e) => return Err(PingError::SocketSendError(e)),
464 },
465 Err(_) => return Err(PingError::SocketSendTimeout),
466 };
467
468 // Helper function for the recieve helper function to validate the echo reply
469 fn filter_pong(buf: &[u8], seq_no: u16) -> bool {
470 let pong_packet = match Icmpv4Packet::new_checked(buf) {
471 Ok(pak) => pak,
472 Err(_) => return false,
473 };
474 pong_packet.echo_seq_no() == seq_no
475 }
476
477 // Helper function to recieve and return the correct echo reply when it finds it
478 async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> {
479 while match socket.recv_with(|(buf, _)| filter_pong(buf, seq_no)).await {
480 Ok(b) => !b,
481 Err(e) => return Err(PingError::SocketRecvError(e)),
482 } {}
483 Ok(())
484 }
485
486 // Calls the recieve helper function with a timeout
487 match recv_pong(&socket, seq_no).with_timeout(params.timeout).await {
488 Ok(res) => res?,
489 Err(_) => return Err(PingError::DestinationHostUnreachable),
490 }
491
492 // Return the round trip duration
493 Ok(now.elapsed())
494 }
495
496 #[cfg(feature = "proto-ipv6")]
497 async fn single_ping_v6(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result<Duration, PingError> {
498 let ping_repr = self.create_repr_ipv6(params, seq_no);
499
500 // Create the socket and set hop limit and bind it to the endpoint with the ident
501 let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer);
502 socket.set_hop_limit(params.hop_limit);
503 if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) {
504 return Err(PingError::SocketBindError(e));
505 }
506
507 // Helper func to fill the buffer when sending the ICMP packet
508 fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv6Repr<'_>, params: &PingParams<'_>) -> Instant {
509 let mut icmp_packet = Icmpv6Packet::new_unchecked(buf);
510 let target = match params.target().unwrap() {
511 IpAddr::V4(_) => unreachable!(),
512 IpAddr::V6(addr) => addr,
513 };
514 ping_repr.emit(
515 &params.source().unwrap(),
516 &target,
517 &mut icmp_packet,
518 &ChecksumCapabilities::default(),
519 );
520 Instant::now()
521 }
522
523 // Send with timeout the ICMP packet filling it with the helper function
524 let send_result = socket
525 .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| {
526 fill_packet_buffer(buf, ping_repr, params)
527 })
528 .with_timeout(Duration::from_millis(100))
529 .await;
530 let now = match send_result {
531 Ok(send_result) => match send_result {
532 Ok(i) => i,
533 Err(e) => return Err(PingError::SocketSendError(e)),
534 },
535 Err(_) => return Err(PingError::SocketSendTimeout),
536 };
537
538 // Helper function for the recieve helper function to validate the echo reply
539 fn filter_pong(buf: &[u8], seq_no: u16) -> bool {
540 let pong_packet = match Icmpv6Packet::new_checked(buf) {
541 Ok(pak) => pak,
542 Err(_) => return false,
543 };
544 pong_packet.echo_seq_no() == seq_no
545 }
546
547 // Helper function to recieve and return the correct echo reply when it finds it
548 async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> {
549 while match socket.recv_with(|(buf, _)| filter_pong(buf, seq_no)).await {
550 Ok(b) => !b,
551 Err(e) => return Err(PingError::SocketRecvError(e)),
552 } {}
553 Ok(())
554 }
555
556 // Calls the recieve helper function with a timeout
557 match recv_pong(&socket, seq_no).with_timeout(params.timeout).await {
558 Ok(res) => res?,
559 Err(_) => return Err(PingError::DestinationHostUnreachable),
560 }
561
562 // Return the round trip duration
563 Ok(now.elapsed())
564 }
565 }
566
567
568 /// Parameters for configuring the ping operation.
569 ///
570 /// This struct provides various configuration options for performing ICMP ping operations,
571 /// including the target IP address, payload data, hop limit, number of pings, and timeout duration.
572 ///
573 /// # Fields
574 ///
575 /// * `target` - The target IP address for the ping operation.
576 /// * `source` - The source IP address for the ping operation (IPv6 only).
577 /// * `payload` - The data to be sent in the payload field of the ping.
578 /// * `hop_limit` - The hop limit to be used by the socket.
579 /// * `count` - The number of pings to be sent in one ping operation.
580 /// * `timeout` - The timeout duration before returning a [`PingError::DestinationHostUnreachable`] error.
581 pub struct PingParams<'a> {
582 target: Option<IpAddr>,
583 #[cfg(feature = "proto-ipv6")]
584 source: Option<Ipv6Addr>,
585 payload: &'a [u8],
586 hop_limit: Option<u8>,
587 count: u16,
588 timeout: Duration,
589 }
590
591 impl Default for PingParams<'_> {
592 fn default() -> Self {
593 Self {
594 target: None,
595 #[cfg(feature = "proto-ipv6")]
596 source: None,
597 payload: b"embassy-net",
598 hop_limit: None,
599 count: 4,
600 timeout: Duration::from_secs(4),
601 }
602 }
603 }
604
605 impl<'a> PingParams<'a> {
606 /// Creates a new instance of [`PingParams`] with the specified target IP address.
607 pub fn new<T: Into<IpAddr>>(target: T) -> Self {
608 Self {
609 target: Some(target.into()),
610 #[cfg(feature = "proto-ipv6")]
611 source: None,
612 payload: b"embassy-net",
613 hop_limit: None,
614 count: 4,
615 timeout: Duration::from_secs(4),
616 }
617 }
618
619 /// Sets the target IP address for the ping.
620 pub fn set_target<T: Into<IpAddr>>(&mut self, target: T) -> &mut Self {
621 self.target = Some(target.into());
622 self
623 }
624
625 /// Retrieves the target IP address for the ping.
626 pub fn target(&self) -> Option<IpAddr> {
627 self.target
628 }
629
630 /// Sets the source IP address for the ping (IPv6 only).
631 #[cfg(feature = "proto-ipv6")]
632 pub fn set_source(&mut self, source: Ipv6Addr) -> &mut Self {
633 self.source = Some(source);
634 self
635 }
636
637 /// Retrieves the source IP address for the ping (IPv6 only).
638 #[cfg(feature = "proto-ipv6")]
639 pub fn source(&self) -> Option<Ipv6Addr> {
640 self.source
641 }
642
643 /// Sets the data used in the payload field of the ping with the provided slice.
644 pub fn set_payload(&mut self, payload: &'a [u8]) -> &mut Self {
645 self.payload = payload;
646 self
647 }
648
649 /// Gives a reference to the slice of data that's going to be sent in the payload field
650 /// of the ping.
651 pub fn payload(&self) -> &'a [u8] {
652 self.payload
653 }
654
655 /// Sets the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit).
656 ///
657 /// **Note**: A hop limit of [`Some(0)`](Some()) is equivalent to a hop limit of [`None`].
658 pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) -> &mut Self {
659 let mut hop_limit = hop_limit;
660 if hop_limit.is_some_and(|x| x == 0) {
661 hop_limit = None
662 }
663 self.hop_limit = hop_limit;
664 self
665 }
666
667 /// Retrieves the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit).
668 pub fn hop_limit(&self) -> Option<u8> {
669 self.hop_limit
670 }
671
672 /// Sets the count used for specifying the number of pings done on one
673 /// [`ping()`](PingManager::ping) call.
674 ///
675 /// **Note**: A count of 0 will be set as 1.
676 pub fn set_count(&mut self, count: u16) -> &mut Self {
677 let mut count = count;
678 if count == 0 {
679 count = 1;
680 }
681 self.count = count;
682 self
683 }
684
685 /// Retrieve the count used for specifying the number of pings done on one
686 /// [`ping()`](PingManager::ping) call.
687 pub fn count(&self) -> u16 {
688 self.count
689 }
690
691 /// Sets the timeout used before returning [`PingError::DestinationHostUnreachable`]
692 /// when waiting for the Echo Reply icmp packet.
693 pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self {
694 self.timeout = timeout;
695 self
696 }
697
698 /// Retrieve the timeout used before returning [`PingError::DestinationHostUnreachable`]
699 /// when waiting for the Echo Reply icmp packet.
700 pub fn timeout(&self) -> Duration {
701 self.timeout
702 }
703 }
704}
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs
index 47bd5191f..1bb112252 100644
--- a/embassy-net/src/lib.rs
+++ b/embassy-net/src/lib.rs
@@ -22,6 +22,8 @@ pub mod tcp;
22mod time; 22mod time;
23#[cfg(feature = "udp")] 23#[cfg(feature = "udp")]
24pub mod udp; 24pub mod udp;
25#[cfg(feature = "icmp")]
26pub mod icmp;
25 27
26use core::cell::RefCell; 28use core::cell::RefCell;
27use core::future::{poll_fn, Future}; 29use core::future::{poll_fn, Future};