Skip to main content

atmos/kernel/net/
icmp.rs

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; // ICMP
25    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; // echo request
36    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; // ICMP
83    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; // echo reply
94    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    // 送信できていないのに「返信した」と報告しない
111    // (送信統計も、実際に送出できたときだけ数える)。
112    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        // Echo Request. Reply with Echo Reply.
145        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        // Build Echo Reply (Type 0, Code 0)
149        let payload = if icmp.len() > 8 { &icmp[8..] } else { &[] };
150        // 返信できなかったなら「処理した」ではなく失敗として返す
151        // (相手からは ping が通らないように見えるのに、こちらのログには
152        //  何も残らない、という状態を作らない)。
153        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    // 1. ARP resolve
195    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        // ARP 要求が送れなければ解決は絶対に成立しない。500ms 待ってから
201        // 「ARP timeout」と報告するより、送出できなかった事実を返す方が正確。
202        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            // フリーズ対策: スリープで完全に待つのではなく yield_now() で
212            // パケット回収を高頻度で継続しつつ、他タスクへ CPU を譲る。
213            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    // 2. Clear LAST_PING_REPLY
222    unsafe {
223        super::LAST_PING_REPLY = None;
224    }
225
226    // 3. Build and send ICMP Echo Request
227    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    // 4. Wait for ICMP Echo Reply (max 1000ms)
233    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        // フリーズ対策: スリープで完全に待つのではなく yield_now() で
250        // パケット回収を高頻度で継続しつつ、他タスクへ CPU を譲る。
251        crate::kernel::scheduler::yield_now();
252    }
253
254    Err("ping timeout")
255}