Skip to main content

strate_net_silo/
icmp.rs

1use smoltcp::{
2    socket::icmp,
3    wire::{IpAddress, Ipv4Address, Ipv6Address},
4};
5use strate_net::syscalls::clock_gettime_ns;
6
7use strate_net::syscalls::call;
8
9use crate::{
10    ip::{icmp_checksum, icmpv6_checksum},
11    state::{NetworkStrate, PendingPing, PING_TIMEOUT_NS},
12};
13
14impl NetworkStrate {
15    pub(crate) fn process_icmp(&mut self) {
16        let now_ns = clock_gettime_ns().unwrap_or(0);
17
18        // Expire timed-out pending pings.
19        self.pending_pings
20            .retain(|ping| now_ns.saturating_sub(ping.send_ts_ns) < PING_TIMEOUT_NS);
21
22        let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
23        if !socket.can_recv() {
24            return;
25        }
26        while socket.can_recv() {
27            let Ok((data, _addr)) = socket.recv() else {
28                break;
29            };
30            if data.len() < 16 {
31                let _ = call::debug_log(b"[ping] recv too short\n");
32                continue;
33            }
34            let is_v6_reply = data[0] == 129;
35            if data[0] != 0 && !is_v6_reply {
36                let _ = call::debug_log(b"[ping] recv unexpected type ");
37                let _ = call::debug_log(&[data[0]]);
38                let _ = call::debug_log(b"\n");
39                continue;
40            }
41            let ident = u16::from_be_bytes([data[4], data[5]]);
42            if ident != self.ping_ident {
43                let _ = call::debug_log(b"[ping] recv wrong ident ");
44                let _ = call::debug_log(&[(ident >> 8) as u8, ident as u8]);
45                let _ = call::debug_log(b" != ");
46                let _ = call::debug_log(&[(self.ping_ident >> 8) as u8, self.ping_ident as u8]);
47                let _ = call::debug_log(b"\n");
48                continue;
49            }
50            let token = u64::from_le_bytes(data[8..16].try_into().unwrap_or([0u8; 8]));
51            // Match against all pending pings (supports pipelining).
52            if let Some(idx) = self
53                .pending_pings
54                .iter()
55                .position(|p| p.token == token && p.is_v6 == is_v6_reply)
56            {
57                let pending = self.pending_pings.remove(idx);
58                let rtt_us = now_ns.saturating_sub(pending.send_ts_ns) / 1000;
59                self.ping_replies.push((pending.seq, rtt_us));
60                let _ = call::debug_log(b"[ping] reply seq=");
61                let _ = call::debug_log(&[(pending.seq >> 8) as u8, pending.seq as u8]);
62                let _ = call::debug_log(b" rtt=");
63                let _ = call::debug_log(&[(rtt_us / 1000) as u8]);
64                let _ = call::debug_log(b"ms\n");
65            } else {
66                let _ = call::debug_log(b"[ping] recv: no match for token ");
67                let _ = call::debug_log(&token.to_le_bytes());
68                let _ = call::debug_log(b"\n");
69            }
70        }
71    }
72
73    pub(crate) fn is_local_ipv4(&self, target: Ipv4Address) -> bool {
74        self.ip_config
75            .as_ref()
76            .is_some_and(|cfg| cfg.host == target)
77    }
78
79    pub(crate) fn is_local_ipv6(&self, target: Ipv6Address) -> bool {
80        if target == self.link_local_addr {
81            return true;
82        }
83        self.ipv6_config
84            .as_ref()
85            .is_some_and(|cfg| cfg.address.address() == target)
86    }
87
88    pub(crate) fn send_ping(&mut self, target: Ipv4Address, seq: u16, _file_id: u64) -> bool {
89        if self.is_local_ipv4(target) {
90            self.ping_replies.push((seq, 1));
91            return true;
92        }
93
94        let token = self.alloc_ping_token();
95
96        let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
97        if !socket.is_open() {
98            socket.bind(icmp::Endpoint::Ident(self.ping_ident)).ok();
99        }
100        if !socket.can_send() {
101            let _ = call::debug_log(b"[ping] send_ping v4: can_send false\n");
102            return false;
103        }
104
105        let payload_len = 40;
106        let icmp_len = 8 + payload_len;
107        let Ok(buf) = socket.send(icmp_len, IpAddress::Ipv4(target)) else {
108            return false;
109        };
110        buf[0] = 8;
111        buf[1] = 0;
112        buf[2] = 0;
113        buf[3] = 0;
114        buf[4..6].copy_from_slice(&self.ping_ident.to_be_bytes());
115        buf[6..8].copy_from_slice(&seq.to_be_bytes());
116        buf[8..16].copy_from_slice(&token.to_le_bytes());
117        for byte in buf[16..icmp_len].iter_mut() {
118            *byte = 0xAA;
119        }
120        let checksum = icmp_checksum(buf);
121        buf[2..4].copy_from_slice(&checksum.to_be_bytes());
122
123        let _ = call::debug_log(b"[ping] sent seq=");
124        let _ = call::debug_log(&[(seq >> 8) as u8, seq as u8]);
125        let _ = call::debug_log(b" token=");
126        let _ = call::debug_log(&token.to_le_bytes());
127        let _ = call::debug_log(b"\n");
128
129        self.pending_pings.push(PendingPing {
130            seq,
131            token,
132            send_ts_ns: 0, // stamped by main loop before interface.poll()
133            is_v6: false,
134        });
135        true
136    }
137
138    pub(crate) fn send_ping6(&mut self, target: Ipv6Address, seq: u16, _file_id: u64) -> bool {
139        if self.is_local_ipv6(target) {
140            self.ping_replies.push((seq, 1));
141            return true;
142        }
143
144        let token = self.alloc_ping_token();
145
146        let socket = self.sockets.get_mut::<icmp::Socket>(self.icmp_handle);
147        if !socket.is_open() {
148            socket.bind(icmp::Endpoint::Ident(self.ping_ident)).ok();
149        }
150        if !socket.can_send() {
151            let _ = call::debug_log(b"[ping] send_ping6: can_send false\n");
152            return false;
153        }
154
155        let payload_len = 40;
156        let icmp_len = 8 + payload_len;
157        let Ok(buf) = socket.send(icmp_len, IpAddress::Ipv6(target)) else {
158            return false;
159        };
160        buf[0] = 128;
161        buf[1] = 0;
162        buf[2] = 0;
163        buf[3] = 0;
164        buf[4..6].copy_from_slice(&self.ping_ident.to_be_bytes());
165        buf[6..8].copy_from_slice(&seq.to_be_bytes());
166        buf[8..16].copy_from_slice(&token.to_le_bytes());
167        for byte in buf[16..icmp_len].iter_mut() {
168            *byte = 0xAA;
169        }
170
171        let _ = call::debug_log(b"[ping] sent v6 seq=");
172        let _ = call::debug_log(&[(seq >> 8) as u8, seq as u8]);
173        let _ = call::debug_log(b" token=");
174        let _ = call::debug_log(&token.to_le_bytes());
175        let _ = call::debug_log(b"\n");
176
177        let src = if target.is_unicast_link_local() {
178            self.link_local_addr
179        } else {
180            self.ipv6_config
181                .as_ref()
182                .map(|cfg| cfg.address.address())
183                .unwrap_or(self.link_local_addr)
184        };
185        let checksum = icmpv6_checksum(&src.octets(), &target.octets(), buf);
186        buf[2..4].copy_from_slice(&checksum.to_be_bytes());
187
188        self.pending_pings.push(PendingPing {
189            seq,
190            token,
191            send_ts_ns: 0, // stamped by main loop before interface.poll()
192            is_v6: true,
193        });
194        true
195    }
196}