diff options
Diffstat (limited to 'embassy-net/src/icmp.rs')
| -rw-r--r-- | embassy-net/src/icmp.rs | 233 |
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 | ||
| 3 | use core::future::poll_fn; | 3 | use core::future::{poll_fn, Future}; |
| 4 | use core::mem; | 4 | use core::mem; |
| 5 | use core::task::Poll; | 5 | use core::task::{Context, Poll}; |
| 6 | 6 | ||
| 7 | use smoltcp::iface::{Interface, SocketHandle}; | 7 | use smoltcp::iface::{Interface, SocketHandle}; |
| 8 | pub use smoltcp::phy::ChecksumCapabilities; | 8 | pub 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 | ||
