1pub fn build_icmp_echo_request(
2 dst_ip: [u8; 4],
3 identifier: u16,
4 sequence: u16,
5 payload: &[u8],
6 out: &mut [u8],
7) -> Result<usize, &'static str> {
8 let total_len = 20usize + 8usize + payload.len();
9 if total_len > out.len() {
10 return Err("buffer too small");
11 }
12
13 let st = super::status();
14
15 out[0] = 0x45;
16 out[1] = 0;
17 out[2] = ((total_len >> 8) & 0xFF) as u8;
18 out[3] = (total_len & 0xFF) as u8;
19 out[4] = 0x43;
20 out[5] = 0x21;
21 out[6] = 0;
22 out[7] = 0;
23 out[8] = 64;
24 out[9] = 1; out[10] = 0;
26 out[11] = 0;
27 out[12..16].copy_from_slice(&st.ip);
28 out[16..20].copy_from_slice(&dst_ip);
29
30 let ip_checksum = super::internet_checksum(&out[0..20]);
31 out[10] = (ip_checksum >> 8) as u8;
32 out[11] = ip_checksum as u8;
33
34 let icmp_start = 20usize;
35 out[icmp_start] = 8; out[icmp_start + 1] = 0;
37 out[icmp_start + 2] = 0;
38 out[icmp_start + 3] = 0;
39 out[icmp_start + 4] = (identifier >> 8) as u8;
40 out[icmp_start + 5] = identifier as u8;
41 out[icmp_start + 6] = (sequence >> 8) as u8;
42 out[icmp_start + 7] = sequence as u8;
43
44 let payload_start = icmp_start + 8;
45 let payload_end = payload_start + payload.len();
46 out[payload_start..payload_end].copy_from_slice(payload);
47
48 let icmp_checksum = super::internet_checksum(&out[icmp_start..payload_end]);
49 out[icmp_start + 2] = (icmp_checksum >> 8) as u8;
50 out[icmp_start + 3] = icmp_checksum as u8;
51
52 unsafe {
53 super::NET_STATE.tx_packets = super::NET_STATE.tx_packets.saturating_add(1);
54 }
55
56 Ok(total_len)
57}
58
59pub fn build_icmp_echo_reply(
60 dst_ip: [u8; 4],
61 identifier: u16,
62 sequence: u16,
63 payload: &[u8],
64 out: &mut [u8],
65) -> Result<usize, &'static str> {
66 let total_len = 20usize + 8usize + payload.len();
67 if total_len > out.len() {
68 return Err("buffer too small");
69 }
70
71 let st = super::status();
72
73 out[0] = 0x45;
74 out[1] = 0;
75 out[2] = ((total_len >> 8) & 0xFF) as u8;
76 out[3] = (total_len & 0xFF) as u8;
77 out[4] = 0x43;
78 out[5] = 0x22;
79 out[6] = 0;
80 out[7] = 0;
81 out[8] = 64;
82 out[9] = 1; out[10] = 0;
84 out[11] = 0;
85 out[12..16].copy_from_slice(&st.ip);
86 out[16..20].copy_from_slice(&dst_ip);
87
88 let ip_checksum = super::internet_checksum(&out[0..20]);
89 out[10] = (ip_checksum >> 8) as u8;
90 out[11] = ip_checksum as u8;
91
92 let icmp_start = 20usize;
93 out[icmp_start] = 0; out[icmp_start + 1] = 0;
95 out[icmp_start + 2] = 0;
96 out[icmp_start + 3] = 0;
97 out[icmp_start + 4] = (identifier >> 8) as u8;
98 out[icmp_start + 5] = identifier as u8;
99 out[icmp_start + 6] = (sequence >> 8) as u8;
100 out[icmp_start + 7] = sequence as u8;
101
102 let payload_start = icmp_start + 8;
103 let payload_end = payload_start + payload.len();
104 out[payload_start..payload_end].copy_from_slice(payload);
105
106 let icmp_checksum = super::internet_checksum(&out[icmp_start..payload_end]);
107 out[icmp_start + 2] = (icmp_checksum >> 8) as u8;
108 out[icmp_start + 3] = icmp_checksum as u8;
109
110 super::arp::transmit_nic_ipv4_packet(&out[..total_len])?;
113
114 unsafe {
115 super::NET_STATE.tx_packets = super::NET_STATE.tx_packets.saturating_add(1);
116 }
117
118 Ok(total_len)
119}
120
121pub fn parse_icmp_echo_reply_ipv4(packet: &[u8]) -> Result<super::PingReply, &'static str> {
122 if packet.len() < 28 {
123 return Err("ipv4 icmp packet too short");
124 }
125
126 let version = packet[0] >> 4;
127 let ihl = (packet[0] & 0x0F) as usize * 4;
128 if version != 4 || ihl < 20 || packet.len() < ihl + 8 {
129 return Err("invalid ipv4 header");
130 }
131 if packet[9] != 1 {
132 return Err("not icmp packet");
133 }
134
135 let total_len = (((packet[2] as u16) << 8) | packet[3] as u16) as usize;
136 if total_len < ihl + 8 || total_len > packet.len() {
137 return Err("invalid ipv4 total length");
138 }
139
140 let src_ip = [packet[12], packet[13], packet[14], packet[15]];
141 let icmp = &packet[ihl..total_len];
142
143 if icmp[0] == 8 && icmp[1] == 0 {
144 let identifier = ((icmp[4] as u16) << 8) | icmp[5] as u16;
146 let sequence = ((icmp[6] as u16) << 8) | icmp[7] as u16;
147 let mut reply_pkt = [0u8; 1500];
148 let payload = if icmp.len() > 8 { &icmp[8..] } else { &[] };
150 if let Err(e) = build_icmp_echo_reply(src_ip, identifier, sequence, payload, &mut reply_pkt)
154 {
155 crate::warn!("[NET] ICMP: echo reply transmit failed: {}", e);
156 return Err("failed to send echo reply");
157 }
158 return Err(alloc::format!("handled echo request (id={:#06X}, seq={})", identifier, sequence).leak());
159 }
160
161 if icmp[0] != 0 || icmp[1] != 0 {
162 return Err(alloc::format!("not echo reply (type={}, code={})", icmp[0], icmp[1]).leak());
163 }
164 if super::internet_checksum(icmp) != 0 {
165 return Err("icmp checksum mismatch");
166 }
167
168 let identifier = ((icmp[4] as u16) << 8) | icmp[5] as u16;
169 let sequence = ((icmp[6] as u16) << 8) | icmp[7] as u16;
170
171 unsafe {
172 super::NET_STATE.rx_packets = super::NET_STATE.rx_packets.saturating_add(1);
173 }
174
175 Ok(super::PingReply {
176 src_ip,
177 identifier,
178 sequence,
179 payload_len: icmp.len().saturating_sub(8),
180 })
181}
182
183
184
185pub fn ping_once_real(dst_ip: [u8; 4]) -> Result<(u16, u32, usize), &'static str> {
186 let identifier: u16 = 0xA11C;
187 let sequence = unsafe {
188 let seq = super::PING_SEQ;
189 super::PING_SEQ = super::PING_SEQ.wrapping_add(1);
190 seq
191 };
192 let payload = b"atmos-ping";
193
194 let next_hop = super::arp::get_next_hop_ip(dst_ip);
196 let mut resolved_mac = super::arp::arp_cache_lookup(next_hop);
197 if resolved_mac.is_none() {
198 let mut arp_req = [0u8; 42];
199 let arp_len = super::arp::build_arp_request(next_hop, &mut arp_req)?;
200 super::arp::transmit_nic_ethernet_frame(&arp_req[..arp_len])?;
203
204 let start_time = crate::kernel::timer::get_system_time_ms();
205 while crate::kernel::timer::get_system_time_ms() - start_time < 500 {
206 crate::kernel::usb::poll_ethernet_data_plane_only();
207 resolved_mac = super::arp::arp_cache_lookup(next_hop);
208 if resolved_mac.is_some() {
209 break;
210 }
211 crate::kernel::scheduler::yield_now();
214 }
215 }
216
217 if resolved_mac.is_none() {
218 return Err("ARP timeout (destination MAC address unresolved)");
219 }
220
221 unsafe {
223 super::LAST_PING_REPLY = None;
224 }
225
226 let mut request = [0u8; 256];
228 let req_len = build_icmp_echo_request(dst_ip, identifier, sequence, payload, &mut request)?;
229 let send_time = crate::kernel::timer::get_system_time_ms();
230 super::arp::transmit_nic_ipv4_packet(&request[..req_len])?;
231
232 while crate::kernel::timer::get_system_time_ms() - send_time < 1000 {
234 crate::kernel::usb::poll_ethernet_data_plane_only();
235
236 unsafe {
237 if let Some(reply) = super::LAST_PING_REPLY {
238 if reply.src_ip == dst_ip
239 && reply.identifier == identifier
240 && reply.sequence == sequence
241 {
242 let rtt = (crate::kernel::timer::get_system_time_ms() - send_time) as u32;
243 super::LAST_PING_REPLY = None;
244 return Ok((sequence, rtt.max(1), reply.payload_len));
245 }
246 }
247 }
248
249 crate::kernel::scheduler::yield_now();
252 }
253
254 Err("ping timeout")
255}