ts_netstack_smoltcp_socket/
ping.rs1use alloc::vec;
11use core::{
12 net::{IpAddr, Ipv4Addr},
13 time::Duration,
14};
15
16use netcore::smoltcp::{
17 phy::ChecksumCapabilities,
18 wire::{IPV4_HEADER_LEN, Icmpv4Packet, Icmpv4Repr, IpProtocol, Ipv4Packet, Ipv4Repr},
19};
20
21use crate::CreateSocket;
22
23#[derive(Debug)]
25pub enum PingError {
26 Timeout,
28 Ipv6Unsupported,
30 Net(netcore::Error),
32}
33
34impl core::fmt::Display for PingError {
35 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36 match self {
37 Self::Timeout => f.write_str("ping timed out"),
38 Self::Ipv6Unsupported => f.write_str("ICMPv6 ping is unsupported (IPv6 is off)"),
39 Self::Net(e) => write!(f, "netstack error: {e}"),
40 }
41 }
42}
43
44impl core::error::Error for PingError {
45 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
46 match self {
47 Self::Net(e) => Some(e),
48 _ => None,
49 }
50 }
51}
52
53impl From<netcore::Error> for PingError {
54 fn from(e: netcore::Error) -> Self {
55 Self::Net(e)
56 }
57}
58
59const PING_PAYLOAD: &[u8] = b"ts_netstack_smoltcp ping";
60
61#[cfg(feature = "tokio")]
73static PING_IDENT_COUNTER: core::sync::atomic::AtomicU16 = core::sync::atomic::AtomicU16::new(0);
74
75#[cfg(feature = "tokio")]
78fn next_ident() -> u16 {
79 let counter = PING_IDENT_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
80 let seed = (std::process::id() as u16) & 0xFF00;
83 seed ^ counter
84}
85
86#[cfg(feature = "tokio")]
96pub async fn ping<C: CreateSocket + Sync>(
97 chan: &C,
98 src: Ipv4Addr,
99 dst: IpAddr,
100 timeout: Duration,
101) -> Result<Duration, PingError> {
102 let dst = match dst {
103 IpAddr::V4(v4) => v4,
104 IpAddr::V6(_) => return Err(PingError::Ipv6Unsupported),
105 };
106
107 let ident: u16 = next_ident();
111 let seq_no: u16 = 1;
112
113 let sock = chan.raw_open(true, IpProtocol::Icmp).await?;
114
115 let request = build_echo_request(src, dst, ident, seq_no, PING_PAYLOAD);
116
117 let start = tokio::time::Instant::now();
118 sock.send(&request).await?;
119
120 let deadline = start + timeout;
121
122 loop {
123 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
124 if remaining.is_zero() {
125 return Err(PingError::Timeout);
126 }
127
128 let recv = tokio::time::timeout(remaining, sock.recv_bytes()).await;
129 let bytes = match recv {
130 Err(_elapsed) => return Err(PingError::Timeout),
131 Ok(Ok(b)) => b,
132 Ok(Err(e)) => return Err(PingError::Net(e)),
133 };
134
135 if matches_reply(&bytes, src, dst, ident, seq_no) {
136 return Ok(start.elapsed());
137 }
138 }
140}
141
142fn build_echo_request(
148 src: Ipv4Addr,
149 dst: Ipv4Addr,
150 ident: u16,
151 seq_no: u16,
152 payload: &[u8],
153) -> vec::Vec<u8> {
154 let checksum_caps = ChecksumCapabilities::default();
155
156 let icmp_repr = Icmpv4Repr::EchoRequest {
157 ident,
158 seq_no,
159 data: payload,
160 };
161
162 let ipv4_repr = Ipv4Repr {
163 src_addr: src,
164 dst_addr: dst,
165 next_header: IpProtocol::Icmp,
166 payload_len: icmp_repr.buffer_len(),
167 hop_limit: 64,
168 };
169
170 let total = IPV4_HEADER_LEN + icmp_repr.buffer_len();
171 let mut buf = vec![0u8; total];
172
173 {
174 let mut ip_packet = Ipv4Packet::new_unchecked(&mut buf[..]);
175 ipv4_repr.emit(&mut ip_packet, &checksum_caps);
176 }
177
178 {
179 let mut icmp_packet = Icmpv4Packet::new_unchecked(&mut buf[IPV4_HEADER_LEN..]);
180 icmp_repr.emit(&mut icmp_packet, &checksum_caps);
181 }
182
183 buf
184}
185
186fn matches_reply(
188 bytes: &[u8],
189 expect_src: Ipv4Addr,
190 expect_dst: Ipv4Addr,
191 ident: u16,
192 seq_no: u16,
193) -> bool {
194 let checksum_caps = ChecksumCapabilities::default();
195
196 let Ok(ip_packet) = Ipv4Packet::new_checked(bytes) else {
197 return false;
198 };
199 let Ok(ipv4_repr) = Ipv4Repr::parse(&ip_packet, &checksum_caps) else {
200 return false;
201 };
202 if ipv4_repr.next_header != IpProtocol::Icmp {
203 return false;
204 }
205 if ipv4_repr.src_addr != expect_dst || ipv4_repr.dst_addr != expect_src {
207 return false;
208 }
209
210 let Ok(icmp_packet) = Icmpv4Packet::new_checked(ip_packet.payload()) else {
211 return false;
212 };
213 let Ok(icmp_repr) = Icmpv4Repr::parse(&icmp_packet, &checksum_caps) else {
214 return false;
215 };
216
217 matches!(
218 icmp_repr,
219 Icmpv4Repr::EchoReply { ident: i, seq_no: s, .. } if i == ident && s == seq_no
220 )
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 const SRC: Ipv4Addr = Ipv4Addr::new(100, 64, 0, 1);
228 const DST: Ipv4Addr = Ipv4Addr::new(100, 64, 0, 2);
229
230 fn build_echo_reply(from: Ipv4Addr, to: Ipv4Addr, ident: u16, seq_no: u16) -> vec::Vec<u8> {
233 let checksum_caps = ChecksumCapabilities::default();
234 let icmp_repr = Icmpv4Repr::EchoReply {
235 ident,
236 seq_no,
237 data: PING_PAYLOAD,
238 };
239 let ipv4_repr = Ipv4Repr {
240 src_addr: from,
241 dst_addr: to,
242 next_header: IpProtocol::Icmp,
243 payload_len: icmp_repr.buffer_len(),
244 hop_limit: 64,
245 };
246 let mut buf = vec![0u8; IPV4_HEADER_LEN + icmp_repr.buffer_len()];
247 {
248 let mut p = Ipv4Packet::new_unchecked(&mut buf[..]);
249 ipv4_repr.emit(&mut p, &checksum_caps);
250 }
251 {
252 let mut p = Icmpv4Packet::new_unchecked(&mut buf[IPV4_HEADER_LEN..]);
253 icmp_repr.emit(&mut p, &checksum_caps);
254 }
255 buf
256 }
257
258 #[test]
259 fn matches_reply_accepts_matching_ident_and_seq() {
260 let reply = build_echo_reply(DST, SRC, 0xABCD, 7);
261 assert!(matches_reply(&reply, SRC, DST, 0xABCD, 7));
262 }
263
264 #[test]
265 fn matches_reply_rejects_foreign_ident() {
266 let foreign = build_echo_reply(DST, SRC, 0x1111, 7);
269 assert!(!matches_reply(&foreign, SRC, DST, 0xABCD, 7));
270 }
271
272 #[test]
273 fn matches_reply_rejects_foreign_seq() {
274 let foreign = build_echo_reply(DST, SRC, 0xABCD, 99);
275 assert!(!matches_reply(&foreign, SRC, DST, 0xABCD, 7));
276 }
277
278 #[test]
279 fn matches_reply_rejects_non_echo_reply() {
280 let request = build_echo_request(DST, SRC, 0xABCD, 7, PING_PAYLOAD);
282 assert!(!matches_reply(&request, SRC, DST, 0xABCD, 7));
283 }
284
285 #[cfg(feature = "tokio")]
286 #[test]
287 fn next_ident_is_unique_for_concurrent_calls() {
288 let a = next_ident();
291 let b = next_ident();
292 let c = next_ident();
293 assert_ne!(a, b);
294 assert_ne!(b, c);
295 assert_ne!(a, c);
296 }
297}