1use alloc::vec::Vec;
2use smoltcp::{
3 socket::icmp,
4 wire::{IpAddress, Ipv4Address, Ipv6Address},
5};
6use strate_net::syscalls::clock_gettime_ns;
7
8use strate_net::syscalls::call;
9
10use crate::{
11 ip::{icmp_checksum, icmpv6_checksum},
12 state::{NetworkStrate, PendingPing, PING_TIMEOUT_NS},
13};
14
15const MAX_ICMP_QUEUE: usize = 32;
16
17impl NetworkStrate {
18 pub(crate) fn process_icmp(&mut self) {
19 let now_ns = clock_gettime_ns().unwrap_or(0);
20
21 self.pending_pings
23 .retain(|ping| now_ns.saturating_sub(ping.send_ts_ns) < PING_TIMEOUT_NS);
24
25 let mut packets: Vec<([u8; 128], IpAddress)> = Vec::new();
28 {
29 let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
30 if !socket.can_recv() {
31 return;
32 }
33 while socket.can_recv() && packets.len() < MAX_ICMP_QUEUE {
34 let Ok((data, addr)) = socket.recv() else {
35 break;
36 };
37 let mut buf = [0u8; 128];
38 let n = data.len().min(128);
39 buf[..n].copy_from_slice(&data[..n]);
40 packets.push((buf, addr));
41 }
42 }
43
44 for (data, addr) in packets.iter() {
45 if data.len() < 8 {
46 let _ = call::debug_log(b"[ping] recv too short\n");
47 continue;
48 }
49
50 match data[0] {
51 8 => self.handle_echo_request_v4(data, *addr),
52 128 => self.handle_echo_request_v6(data, *addr),
53 0 => self.handle_echo_reply_v4(data),
54 129 => self.handle_echo_reply_v6(data),
55 other => {
56 let _ = call::debug_log(b"[ping] recv unexpected type ");
57 let _ = call::debug_log(&[other]);
58 let _ = call::debug_log(b"\n");
59 }
60 }
61 }
62 }
63
64 fn handle_echo_request_v4(&mut self, data: &[u8], addr: IpAddress) {
65 let IpAddress::Ipv4(src) = addr else {
66 return;
67 };
68
69 let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
70 if !socket.can_send() {
71 let _ = call::debug_log(b"[ping] echo request v4: can_send false\n");
72 return;
73 }
74
75 let payload_len = data.len();
77 let Ok(buf) = socket.send(payload_len, IpAddress::Ipv4(src)) else {
78 let _ = call::debug_log(b"[ping] echo request v4: send failed\n");
79 return;
80 };
81 buf.copy_from_slice(data);
82 buf[0] = 0;
83 buf[1] = 0;
84 let checksum = icmp_checksum(buf);
85 buf[2..4].copy_from_slice(&checksum.to_be_bytes());
86
87 let _ = call::debug_log(b"[ping] echo reply v4 sent to ");
88 let octets = src.octets();
89 let _ = call::debug_log(&[octets[0], b'.', octets[1], b'.', octets[2], b'.', octets[3]]);
90 let _ = call::debug_log(b"\n");
91 }
92
93 fn handle_echo_request_v6(&mut self, data: &[u8], addr: IpAddress) {
94 let IpAddress::Ipv6(src) = addr else {
95 return;
96 };
97
98 let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
99 if !socket.can_send() {
100 let _ = call::debug_log(b"[ping] echo request v6: can_send false\n");
101 return;
102 }
103
104 let payload_len = data.len();
106 let Ok(buf) = socket.send(payload_len, IpAddress::Ipv6(src)) else {
107 let _ = call::debug_log(b"[ping] echo request v6: send failed\n");
108 return;
109 };
110 buf.copy_from_slice(data);
111 buf[0] = 129;
112 buf[1] = 0;
113
114 let dst = src.octets();
115 let src_addr = self.link_local_addr;
116 let checksum = icmpv6_checksum(&src_addr.octets(), &dst, buf);
117 buf[2..4].copy_from_slice(&checksum.to_be_bytes());
118
119 let _ = call::debug_log(b"[ping] echo reply v6 sent\n");
120 }
121
122 fn handle_echo_reply_v4(&mut self, data: &[u8]) {
123 if data.len() < 16 {
124 return;
125 }
126 let ident = u16::from_be_bytes([data[4], data[5]]);
127 if ident != self.ping_ident {
128 return;
129 }
130 let token = u64::from_le_bytes(data[8..16].try_into().unwrap_or([0u8; 8]));
131 if let Some(idx) = self
132 .pending_pings
133 .iter()
134 .position(|p| p.token == token && !p.is_v6)
135 {
136 let pending = self.pending_pings.remove(idx);
137 let now_ns = clock_gettime_ns().unwrap_or(0);
138 let rtt_us = now_ns.saturating_sub(pending.send_ts_ns) / 1000;
139 self.ping_replies.push((pending.seq, rtt_us));
140 let _ = call::debug_log(b"[ping] reply seq=");
141 let _ = call::debug_log(&[(pending.seq >> 8) as u8, pending.seq as u8]);
142 let _ = call::debug_log(b" rtt=");
143 let _ = call::debug_log(&[(rtt_us / 1000) as u8]);
144 let _ = call::debug_log(b"ms\n");
145 }
146 }
147
148 fn handle_echo_reply_v6(&mut self, data: &[u8]) {
149 if data.len() < 16 {
150 return;
151 }
152 let ident = u16::from_be_bytes([data[4], data[5]]);
153 if ident != self.ping_ident {
154 return;
155 }
156 let token = u64::from_le_bytes(data[8..16].try_into().unwrap_or([0u8; 8]));
157 if let Some(idx) = self
158 .pending_pings
159 .iter()
160 .position(|p| p.token == token && p.is_v6)
161 {
162 let pending = self.pending_pings.remove(idx);
163 let now_ns = clock_gettime_ns().unwrap_or(0);
164 let rtt_us = now_ns.saturating_sub(pending.send_ts_ns) / 1000;
165 self.ping_replies.push((pending.seq, rtt_us));
166 let _ = call::debug_log(b"[ping] reply v6 seq=");
167 let _ = call::debug_log(&[(pending.seq >> 8) as u8, pending.seq as u8]);
168 let _ = call::debug_log(b" rtt=");
169 let _ = call::debug_log(&[(rtt_us / 1000) as u8]);
170 let _ = call::debug_log(b"ms\n");
171 }
172 }
173
174 pub(crate) fn is_local_ipv4(&self, target: Ipv4Address) -> bool {
175 self.ip_config
176 .as_ref()
177 .is_some_and(|cfg| cfg.host == target)
178 }
179
180 pub(crate) fn is_local_ipv6(&self, target: Ipv6Address) -> bool {
181 if target == self.link_local_addr {
182 return true;
183 }
184 self.ipv6_config
185 .as_ref()
186 .is_some_and(|cfg| cfg.address.address() == target)
187 }
188
189 pub(crate) fn send_ping(&mut self, target: Ipv4Address, seq: u16, _file_id: u64) -> bool {
190 if self.is_local_ipv4(target) {
191 self.ping_replies.push((seq, 1));
192 return true;
193 }
194
195 let token = self.alloc_ping_token();
196
197 let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
198 if !socket.is_open() {
199 socket.bind(icmp::Endpoint::Ident(self.ping_ident)).ok();
200 }
201 if !socket.can_send() {
202 let _ = call::debug_log(b"[ping] send_ping v4: can_send false\n");
203 return false;
204 }
205
206 let payload_len = 40;
207 let icmp_len = 8 + payload_len;
208 let Ok(buf) = socket.send(icmp_len, IpAddress::Ipv4(target)) else {
209 return false;
210 };
211 buf[0] = 8;
212 buf[1] = 0;
213 buf[2] = 0;
214 buf[3] = 0;
215 buf[4..6].copy_from_slice(&self.ping_ident.to_be_bytes());
216 buf[6..8].copy_from_slice(&seq.to_be_bytes());
217 buf[8..16].copy_from_slice(&token.to_le_bytes());
218 for byte in buf[16..icmp_len].iter_mut() {
219 *byte = 0xAA;
220 }
221 let checksum = icmp_checksum(buf);
222 buf[2..4].copy_from_slice(&checksum.to_be_bytes());
223
224 let _ = call::debug_log(b"[ping] sent seq=");
225 let _ = call::debug_log(&[(seq >> 8) as u8, seq as u8]);
226 let _ = call::debug_log(b" token=");
227 let _ = call::debug_log(&token.to_le_bytes());
228 let _ = call::debug_log(b"\n");
229
230 self.pending_pings.push(PendingPing {
231 seq,
232 token,
233 send_ts_ns: 0, is_v6: false,
235 });
236 true
237 }
238
239 pub(crate) fn send_ping6(&mut self, target: Ipv6Address, seq: u16, _file_id: u64) -> bool {
240 if self.is_local_ipv6(target) {
241 self.ping_replies.push((seq, 1));
242 return true;
243 }
244
245 let token = self.alloc_ping_token();
246
247 let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
248 if !socket.is_open() {
249 socket.bind(icmp::Endpoint::Ident(self.ping_ident)).ok();
250 }
251 if !socket.can_send() {
252 let _ = call::debug_log(b"[ping] send_ping6: can_send false\n");
253 return false;
254 }
255
256 let payload_len = 40;
257 let icmp_len = 8 + payload_len;
258 let Ok(buf) = socket.send(icmp_len, IpAddress::Ipv6(target)) else {
259 return false;
260 };
261 buf[0] = 128;
262 buf[1] = 0;
263 buf[2] = 0;
264 buf[3] = 0;
265 buf[4..6].copy_from_slice(&self.ping_ident.to_be_bytes());
266 buf[6..8].copy_from_slice(&seq.to_be_bytes());
267 buf[8..16].copy_from_slice(&token.to_le_bytes());
268 for byte in buf[16..icmp_len].iter_mut() {
269 *byte = 0xAA;
270 }
271
272 let _ = call::debug_log(b"[ping] sent v6 seq=");
273 let _ = call::debug_log(&[(seq >> 8) as u8, seq as u8]);
274 let _ = call::debug_log(b" token=");
275 let _ = call::debug_log(&token.to_le_bytes());
276 let _ = call::debug_log(b"\n");
277
278 let src = if target.is_unicast_link_local() {
279 self.link_local_addr
280 } else {
281 self.ipv6_config
282 .as_ref()
283 .map(|cfg| cfg.address.address())
284 .unwrap_or(self.link_local_addr)
285 };
286 let checksum = icmpv6_checksum(&src.octets(), &target.octets(), buf);
287 buf[2..4].copy_from_slice(&checksum.to_be_bytes());
288
289 self.pending_pings.push(PendingPing {
290 seq,
291 token,
292 send_ts_ns: 0, is_v6: true,
294 });
295 true
296 }
297}