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 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 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, 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, is_v6: true,
193 });
194 true
195 }
196}