Skip to main content

strate_net_silo/
icmp.rs

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        // Expire timed-out pending pings.
22        self.pending_pings
23            .retain(|ping| now_ns.saturating_sub(ping.send_ts_ns) < PING_TIMEOUT_NS);
24
25        // Drain received packets into a temporary buffer to release the
26        // socket borrow before calling handler methods on &mut self.
27        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        // Build echo reply: swap type 8->0, keep ident+seq, recompute checksum.
76        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        // Build echo reply: swap type 128->129, keep ident+seq, recompute checksum.
105        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, // stamped by main loop before interface.poll()
234            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, // stamped by main loop before interface.poll()
293            is_v6: true,
294        });
295        true
296    }
297}