aboutsummaryrefslogtreecommitdiff
path: root/embassy-net/src
diff options
context:
space:
mode:
authorskkeye <[email protected]>2025-02-10 00:51:59 -0500
committerUlf Lilleengen <[email protected]>2025-02-13 10:27:30 +0100
commit7b352654653466063fd149a766e2deb14bcfc1ad (patch)
treeeab382a2f48fff9288b95c442f2e7af64aa212a1 /embassy-net/src
parent7d2ffa76e5a34868a25a84e09e1e9f169d2d951f (diff)
feat: Feature match udp sockets
fix: fixed compile proto-ipv4/v6 edge cases in the ping module
Diffstat (limited to 'embassy-net/src')
-rw-r--r--embassy-net/src/icmp.rs233
1 files changed, 186 insertions, 47 deletions
diff --git a/embassy-net/src/icmp.rs b/embassy-net/src/icmp.rs
index 68f6fd536..22c31a589 100644
--- a/embassy-net/src/icmp.rs
+++ b/embassy-net/src/icmp.rs
@@ -1,8 +1,8 @@
1//! ICMP sockets. 1//! ICMP sockets.
2 2
3use core::future::poll_fn; 3use core::future::{poll_fn, Future};
4use core::mem; 4use core::mem;
5use core::task::Poll; 5use core::task::{Context, Poll};
6 6
7use smoltcp::iface::{Interface, SocketHandle}; 7use smoltcp::iface::{Interface, SocketHandle};
8pub use smoltcp::phy::ChecksumCapabilities; 8pub use smoltcp::phy::ChecksumCapabilities;
@@ -36,6 +36,8 @@ pub enum SendError {
36 NoRoute, 36 NoRoute,
37 /// Socket not bound to an outgoing port. 37 /// Socket not bound to an outgoing port.
38 SocketNotBound, 38 SocketNotBound,
39 /// There is not enough transmit buffer capacity to ever send this packet.
40 PacketTooLarge,
39} 41}
40 42
41/// Error returned by [`IcmpSocket::recv_from`]. 43/// Error returned by [`IcmpSocket::recv_from`].
@@ -109,25 +111,61 @@ impl<'a> IcmpSocket<'a> {
109 }) 111 })
110 } 112 }
111 113
112 /// Dequeue a packet received from a remote endpoint, copy the payload into the given slice, 114 /// Wait until the socket becomes readable.
113 /// and return the amount of octets copied as well as the `IpAddress`
114 /// 115 ///
115 /// **Note**: when the size of the provided buffer is smaller than the size of the payload, 116 /// A socket is readable when a packet has been received, or when there are queued packets in
116 /// the packet is dropped and a `RecvError::Truncated` error is returned. 117 /// the buffer.
117 pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpAddress), RecvError> { 118 pub fn wait_recv_ready(&self) -> impl Future<Output = ()> + '_ {
118 poll_fn(move |cx| { 119 poll_fn(move |cx| self.poll_recv_ready(cx))
119 self.with_mut(|s, _| match s.recv_slice(buf) { 120 }
120 Ok(x) => Poll::Ready(Ok(x)), 121
121 // No data ready 122 /// Wait until a datagram can be read.
122 Err(icmp::RecvError::Exhausted) => { 123 ///
123 //s.register_recv_waker(cx.waker()); 124 /// When no datagram is readable, this method will return `Poll::Pending` and
124 cx.waker().wake_by_ref(); 125 /// register the current task to be notified when a datagram is received.
125 Poll::Pending 126 ///
126 } 127 /// When a datagram is received, this method will return `Poll::Ready`.
127 Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), 128 pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
128 }) 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 }
129 }) 168 })
130 .await
131 } 169 }
132 170
133 /// Dequeue a packet received from a remote endpoint and calls the provided function with the 171 /// Dequeue a packet received from a remote endpoint and calls the provided function with the
@@ -136,7 +174,7 @@ impl<'a> IcmpSocket<'a> {
136 /// 174 ///
137 /// **Note**: when the size of the provided buffer is smaller than the size of the payload, 175 /// **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. 176 /// 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> 177 pub async fn recv_from_with<F, R>(&self, f: F) -> Result<R, RecvError>
140 where 178 where
141 F: FnOnce((&[u8], IpAddress)) -> R, 179 F: FnOnce((&[u8], IpAddress)) -> R,
142 { 180 {
@@ -154,40 +192,106 @@ impl<'a> IcmpSocket<'a> {
154 .await 192 .await
155 } 193 }
156 194
157 /// Enqueue a packet to be sent to a given remote address, and fill it from a slice. 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)`
158 pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError> 229 pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError>
159 where 230 where
160 T: Into<IpAddress>, 231 T: Into<IpAddress>,
161 { 232 {
162 let remote_endpoint = remote_endpoint.into(); 233 let remote_endpoint: IpAddress = remote_endpoint.into();
163 poll_fn(move |cx| { 234 poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await
164 self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { 235 }
165 // Entire datagram has been sent 236
166 Ok(()) => Poll::Ready(Ok(())), 237 /// Send a datagram to the specified remote endpoint.
167 Err(icmp::SendError::BufferFull) => { 238 ///
168 s.register_send_waker(cx.waker()); 239 /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`.
169 Poll::Pending 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))
170 } 270 }
171 Err(icmp::SendError::Unaddressable) => Poll::Ready(Err(SendError::NoRoute)), 271 }
172 })
173 }) 272 })
174 .await
175 } 273 }
176 274
177 /// Enqueue a packet to be sent to a given remote address with a zero-copy function. 275 /// Enqueue a packet to be sent to a given remote address with a zero-copy function.
178 /// 276 ///
179 /// This method will wait until the buffer can fit the requested size before 277 /// This method will wait until the buffer can fit the requested size before
180 /// calling the function to fill its contents. 278 /// 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> 279 pub async fn send_to_with<T, F, R>(&mut self, size: usize, remote_endpoint: T, f: F) -> Result<R, SendError>
182 where 280 where
183 T: Into<IpAddress>, 281 T: Into<IpAddress>,
184 F: FnOnce(&mut [u8]) -> R, 282 F: FnOnce(&mut [u8]) -> R,
185 { 283 {
284 // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer.
285 let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < size);
286 if send_capacity_too_small {
287 return Err(SendError::PacketTooLarge);
288 }
289
186 let mut f = Some(f); 290 let mut f = Some(f);
187 let remote_endpoint = remote_endpoint.into(); 291 let remote_endpoint = remote_endpoint.into();
188 poll_fn(move |cx| { 292 poll_fn(move |cx| {
189 self.with_mut(|s, _| match s.send(size, remote_endpoint) { 293 self.with_mut(|s, _| match s.send(size, remote_endpoint) {
190 Ok(buf) => Poll::Ready(Ok(unwrap!(f.take())(buf))), 294 Ok(buf) => Poll::Ready(Ok({ unwrap!(f.take())(buf) })),
191 Err(icmp::SendError::BufferFull) => { 295 Err(icmp::SendError::BufferFull) => {
192 s.register_send_waker(cx.waker()); 296 s.register_send_waker(cx.waker());
193 Poll::Pending 297 Poll::Pending
@@ -198,6 +302,22 @@ impl<'a> IcmpSocket<'a> {
198 .await 302 .await
199 } 303 }
200 304
305 /// Flush the socket.
306 ///
307 /// This method will wait until the socket is flushed.
308 pub fn flush(&mut self) -> impl Future<Output = ()> + '_ {
309 poll_fn(|cx| {
310 self.with_mut(|s, _| {
311 if s.send_queue() == 0 {
312 Poll::Ready(())
313 } else {
314 s.register_send_waker(cx.waker());
315 Poll::Pending
316 }
317 })
318 })
319 }
320
201 /// Check whether the socket is open. 321 /// Check whether the socket is open.
202 pub fn is_open(&self) -> bool { 322 pub fn is_open(&self) -> bool {
203 self.with(|s, _| s.is_open()) 323 self.with(|s, _| s.is_open())
@@ -280,9 +400,15 @@ pub mod ping {
280 //! }; 400 //! };
281 //! ``` 401 //! ```
282 402
283 use core::net::{IpAddr, Ipv6Addr}; 403 use core::net::IpAddr;
404 #[cfg(feature = "proto-ipv6")]
405 use core::net::Ipv6Addr;
284 406
285 use embassy_time::{Duration, Instant, Timer, WithTimeout}; 407 use embassy_time::{Duration, Instant, Timer, WithTimeout};
408 #[cfg(feature = "proto-ipv6")]
409 use smoltcp::wire::IpAddress;
410 #[cfg(feature = "proto-ipv6")]
411 use smoltcp::wire::Ipv6Address;
286 412
287 use super::*; 413 use super::*;
288 414
@@ -392,11 +518,11 @@ pub mod ping {
392 // make a single ping 518 // make a single ping
393 // - shorts out errors 519 // - shorts out errors
394 // - select the ip version 520 // - select the ip version
395 let ping_duration = match params.target().unwrap() { 521 let ping_duration = match params.target.unwrap() {
396 #[cfg(feature = "proto-ipv4")] 522 #[cfg(feature = "proto-ipv4")]
397 IpAddr::V4(_) => self.single_ping_v4(params, seq_no).await?, 523 IpAddress::Ipv4(_) => self.single_ping_v4(params, seq_no).await?,
398 #[cfg(feature = "proto-ipv6")] 524 #[cfg(feature = "proto-ipv6")]
399 IpAddr::V6(_) => self.single_ping_v6(params, seq_no).await?, 525 IpAddress::Ipv6(_) => self.single_ping_v6(params, seq_no).await?,
400 }; 526 };
401 527
402 // safely add up the durations of each ping 528 // safely add up the durations of each ping
@@ -478,7 +604,7 @@ pub mod ping {
478 604
479 // Helper function to recieve and return the correct echo reply when it finds it 605 // Helper function to recieve and return the correct echo reply when it finds it
480 async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { 606 async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> {
481 while match socket.recv_with(|(buf, _)| filter_pong(buf, seq_no)).await { 607 while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await {
482 Ok(b) => !b, 608 Ok(b) => !b,
483 Err(e) => return Err(PingError::SocketRecvError(e)), 609 Err(e) => return Err(PingError::SocketRecvError(e)),
484 } {} 610 } {}
@@ -548,7 +674,7 @@ pub mod ping {
548 674
549 // Helper function to recieve and return the correct echo reply when it finds it 675 // Helper function to recieve and return the correct echo reply when it finds it
550 async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { 676 async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> {
551 while match socket.recv_with(|(buf, _)| filter_pong(buf, seq_no)).await { 677 while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await {
552 Ok(b) => !b, 678 Ok(b) => !b,
553 Err(e) => return Err(PingError::SocketRecvError(e)), 679 Err(e) => return Err(PingError::SocketRecvError(e)),
554 } {} 680 } {}
@@ -581,9 +707,9 @@ pub mod ping {
581 /// * `timeout` - The timeout duration before returning a [`PingError::DestinationHostUnreachable`] error. 707 /// * `timeout` - The timeout duration before returning a [`PingError::DestinationHostUnreachable`] error.
582 /// * `rate_limit` - The minimum time per echo request. 708 /// * `rate_limit` - The minimum time per echo request.
583 pub struct PingParams<'a> { 709 pub struct PingParams<'a> {
584 target: Option<IpAddr>, 710 target: Option<IpAddress>,
585 #[cfg(feature = "proto-ipv6")] 711 #[cfg(feature = "proto-ipv6")]
586 source: Option<Ipv6Addr>, 712 source: Option<Ipv6Address>,
587 payload: &'a [u8], 713 payload: &'a [u8],
588 hop_limit: Option<u8>, 714 hop_limit: Option<u8>,
589 count: u16, 715 count: u16,
@@ -610,7 +736,7 @@ pub mod ping {
610 /// Creates a new instance of [`PingParams`] with the specified target IP address. 736 /// Creates a new instance of [`PingParams`] with the specified target IP address.
611 pub fn new<T: Into<IpAddr>>(target: T) -> Self { 737 pub fn new<T: Into<IpAddr>>(target: T) -> Self {
612 Self { 738 Self {
613 target: Some(target.into()), 739 target: Some(PingParams::ip_addr_to_smoltcp(target)),
614 #[cfg(feature = "proto-ipv6")] 740 #[cfg(feature = "proto-ipv6")]
615 source: None, 741 source: None,
616 payload: b"embassy-net", 742 payload: b"embassy-net",
@@ -621,21 +747,34 @@ pub mod ping {
621 } 747 }
622 } 748 }
623 749
750 fn ip_addr_to_smoltcp<T: Into<IpAddr>>(ip_addr: T) -> IpAddress {
751 match ip_addr.into() {
752 #[cfg(feature = "proto-ipv4")]
753 IpAddr::V4(v4) => IpAddress::Ipv4(v4),
754 #[cfg(not(feature = "proto-ipv4"))]
755 IpAddr::V4(_) => unreachable!(),
756 #[cfg(feature = "proto-ipv6")]
757 IpAddr::V6(v6) => IpAddress::Ipv6(v6),
758 #[cfg(not(feature = "proto-ipv6"))]
759 IpAddr::V6(_) => unreachable!(),
760 }
761 }
762
624 /// Sets the target IP address for the ping. 763 /// Sets the target IP address for the ping.
625 pub fn set_target<T: Into<IpAddr>>(&mut self, target: T) -> &mut Self { 764 pub fn set_target<T: Into<IpAddr>>(&mut self, target: T) -> &mut Self {
626 self.target = Some(target.into()); 765 self.target = Some(PingParams::ip_addr_to_smoltcp(target));
627 self 766 self
628 } 767 }
629 768
630 /// Retrieves the target IP address for the ping. 769 /// Retrieves the target IP address for the ping.
631 pub fn target(&self) -> Option<IpAddr> { 770 pub fn target(&self) -> Option<IpAddr> {
632 self.target 771 self.target.map(|t| t.into())
633 } 772 }
634 773
635 /// Sets the source IP address for the ping (IPv6 only). 774 /// Sets the source IP address for the ping (IPv6 only).
636 #[cfg(feature = "proto-ipv6")] 775 #[cfg(feature = "proto-ipv6")]
637 pub fn set_source(&mut self, source: Ipv6Addr) -> &mut Self { 776 pub fn set_source<T: Into<Ipv6Address>>(&mut self, source: T) -> &mut Self {
638 self.source = Some(source); 777 self.source = Some(source.into());
639 self 778 self
640 } 779 }
641 780