Skip to main content

atmos/kernel/net/
udp.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3
4
5
6pub fn udp_queue_lines(port_filter: Option<u16>) -> Vec<String> {
7    let mut lines = Vec::new();
8    let _lock = super::NET_LOCK.lock();
9    unsafe {
10        for i in 0..super::UDP_RX_QUEUE_SIZE {
11            let e = super::UDP_RX_QUEUE[i];
12            if !e.valid {
13                continue;
14            }
15            if let Some(port) = port_filter {
16                if e.dst_port != port {
17                    continue;
18                }
19            }
20
21            let preview_len = e.payload_len.min(24);
22            let preview = payload_preview(&e.payload[..preview_len]);
23            lines.push(alloc::format!(
24                "{}:{} -> :{} len={} data='{}'",
25                super::format_ipv4(e.src_ip),
26                e.src_port,
27                e.dst_port,
28                e.payload_len,
29                preview
30            ));
31        }
32    }
33
34    lines
35}
36
37pub fn udp_recv_next_line(port: u16) -> Option<String> {
38    let dgram = udp_recv_next_datagram(port)?;
39    let text = payload_preview(&dgram.payload);
40    Some(alloc::format!(
41        "UDP RX {}:{} -> :{} len={} data='{}'",
42        super::format_ipv4(dgram.src_ip),
43        dgram.src_port,
44        dgram.dst_port,
45        dgram.payload.len(),
46        text
47    ))
48}
49
50pub fn udp_recv_next_datagram(port: u16) -> Option<super::UdpDatagram> {
51    udp_recv_matching(port, |_sip, _sport, _pl| true)
52}
53
54/// dst_port が一致し、かつ述語 `pred(src_ip, src_port, payload)` を満たす最古の
55/// データグラムだけを取り出してキューから削除する。述語を満たさないエントリは
56/// キューに残すため、並行クエリ(DNS browser_loader / img_load、NTP)が互いの
57/// 応答を「取り出して照合失敗で破棄」してしまう競合を構造的に解消する。
58pub fn udp_recv_matching<F>(port: u16, pred: F) -> Option<super::UdpDatagram>
59where
60    F: Fn([u8; 4], u16, &[u8]) -> bool,
61{
62    let _lock = super::NET_LOCK.lock();
63    unsafe {
64        // 照合(宛先ポート一致 + 述語 + 最古優先)は純粋モジュールへ委譲する。
65        let queue = &*core::ptr::addr_of!(super::UDP_RX_QUEUE);
66        let i = super::udp_queue::find_matching(queue, port, |e| {
67            pred(e.src_ip, e.src_port, e.payload())
68        })?;
69        let e = super::UDP_RX_QUEUE[i];
70        // 【2026-07-29】消費した応答と、同じ宛先ポート宛の重複応答をまとめて破棄する。
71        //
72        // `net/dns.rs:74-88` を読んで確認した仕様:
73        //   let src_port = next_dns_src_port();   // 1 クエリにつき 1 ポート
74        //   for &dns_ip in &dns_servers { send_udp_ipv4(dns_ip, src_port, 53, ...) }
75        // 同一ポートから**複数の DNS サーバへ同じクエリ**を送るため、
76        // 応答も同じポート宛に複数届く。1 件だけ消費すると残りが永久に滞留し、
77        // クエリを重ねるほど 6 件のキューが消費済みの古い応答で埋まって
78        // 新しい DNS 応答が入らなくなる(実測: `udp_queue_len=6` で全て古い応答)。
79        //
80        // ただし**ここ(汎用の recv)で一括破棄してはいけない**。
81        // `udp_recv_matching` は「述語で送信元を絞り、一致しないエントリは残す」
82        // 契約であり(`test_udp_recv_matching_predicate_filters_by_source` が固定)、
83        // ポート単位で消すとその契約を壊す。破棄は「そのクエリが完了した」と
84        // 判断できる呼び出し側(DNS 層)で行う。→ `purge_port()` 参照。
85        super::UDP_RX_QUEUE[i] = super::UdpRxEntry::empty();
86
87        let mut payload = Vec::with_capacity(e.payload_len);
88        for idx in 0..e.payload_len {
89            payload.push(e.payload[idx]);
90        }
91
92        Some(super::UdpDatagram {
93            src_ip: e.src_ip,
94            src_port: e.src_port,
95            dst_port: e.dst_port,
96            payload,
97        })
98    }
99}
100
101pub fn send_udp_ipv4(
102    dst_ip: [u8; 4],
103    src_port: u16,
104    dst_port: u16,
105    payload: &[u8],
106) -> Result<usize, &'static str> {
107    send_udp_ipv4_from(super::status().ip, dst_ip, src_port, dst_port, payload)
108}
109
110pub(crate) fn send_udp_ipv4_from(
111    src_ip: [u8; 4],
112    dst_ip: [u8; 4],
113    src_port: u16,
114    dst_port: u16,
115    payload: &[u8],
116) -> Result<usize, &'static str> {
117    if payload.len() > 1400 {
118        return Err("payload too large");
119    }
120
121    let total_len = 20usize + 8usize + payload.len();
122    let mut pkt = [0u8; 1536];
123
124    // IPv4 header
125    pkt[0] = 0x45;
126    pkt[1] = 0;
127    pkt[2] = ((total_len >> 8) & 0xFF) as u8;
128    pkt[3] = (total_len & 0xFF) as u8;
129    pkt[4] = 0x12;
130    pkt[5] = 0x34;
131    pkt[6] = 0;
132    pkt[7] = 0;
133    pkt[8] = 64;
134    pkt[9] = 17; // UDP
135
136    pkt[12..16].copy_from_slice(&src_ip);
137    pkt[16..20].copy_from_slice(&dst_ip);
138
139    let csum = super::ipv4_header_checksum(&pkt[0..20]);
140    pkt[10] = (csum >> 8) as u8;
141    pkt[11] = csum as u8;
142
143    // UDP header
144    pkt[20] = (src_port >> 8) as u8;
145    pkt[21] = src_port as u8;
146    pkt[22] = (dst_port >> 8) as u8;
147    pkt[23] = dst_port as u8;
148
149    let udp_len = 8usize + payload.len();
150    pkt[24] = (udp_len >> 8) as u8;
151    pkt[25] = udp_len as u8;
152    pkt[26] = 0;
153    pkt[27] = 0;
154
155    let data_start = 28usize;
156    let data_end = data_start + payload.len();
157    pkt[data_start..data_end].copy_from_slice(payload);
158
159    // UDP チェックサム(IPv4 疑似ヘッダ + UDP ヘッダ + データ)。
160    // checksum=0 は IPv4 UDP では仕様上「未計算」を意味する正当な値だが、一部の
161    // ホームルータのファイアウォールや厳格なネットワーク経路はこれを不審として
162    // 静かに破棄することがある。DNS 応答が実ネットワークでは届かず QEMU の
163    // 簡易 NAT (SLIRP) では届いていた症状の一因と考えられるため、実際に計算する。
164    let mut pseudo = [0u8; 12 + 1408];
165    pseudo[0..4].copy_from_slice(&src_ip);
166    pseudo[4..8].copy_from_slice(&dst_ip);
167    pseudo[8] = 0;
168    pseudo[9] = 17; // UDP
169    pseudo[10] = (udp_len >> 8) as u8;
170    pseudo[11] = udp_len as u8;
171    pseudo[12..12 + udp_len].copy_from_slice(&pkt[20..20 + udp_len]);
172    let udp_csum = super::internet_checksum(&pseudo[..12 + udp_len]);
173    // 計算結果が 0 になった場合は「未計算」と区別するため 0xFFFF で送信する(RFC768)。
174    let udp_csum = if udp_csum == 0 { 0xFFFF } else { udp_csum };
175    pkt[26] = (udp_csum >> 8) as u8;
176    pkt[27] = udp_csum as u8;
177
178    let sent = super::arp::transmit_nic_ipv4_packet(&pkt[..total_len])?;
179    Ok(sent)
180}
181
182pub(crate) fn parse_udp_ipv4_packet(packet: &[u8]) -> Result<(), &'static str> {
183    let ihl = ((packet[0] & 0x0F) as usize) * 4;
184    if ihl < 20 || packet.len() < ihl + 8 {
185        return Err("udp packet too short");
186    }
187
188    let total_len = (((packet[2] as u16) << 8) | packet[3] as u16) as usize;
189    if total_len < ihl + 8 || total_len > packet.len() {
190        return Err("invalid ipv4 total length");
191    }
192
193    let udp = &packet[ihl..total_len];
194    let src_port = ((udp[0] as u16) << 8) | udp[1] as u16;
195    let dst_port = ((udp[2] as u16) << 8) | udp[3] as u16;
196
197    let dst_ip = [packet[16], packet[17], packet[18], packet[19]];
198    let my_ip = super::status().ip;
199    let is_dhcp_broadcast = dst_ip == [255, 255, 255, 255] && dst_port == 68;
200    let is_dhcp_client_rx = dst_port == 68;
201    if dst_ip != my_ip && !is_dhcp_broadcast && !(my_ip == [0, 0, 0, 0] && is_dhcp_client_rx) {
202        if is_dhcp_client_rx {
203            crate::info!(
204                "UDP Parse: Dropped DHCP packet to {:?} (my_ip={:?})",
205                dst_ip,
206                my_ip
207            );
208        } else {
209            // 診断用: DNS/NTP 等の応答がここで宛先IP不一致により捨てられていないかを
210            // 可視化する。「送信元IPの厳密一致は要求していない」(dns.rs 側)のに対し、
211            // ここでは「宛先IP=自分のIP」を要求している非対称性が疑わしいため。
212            let src_ip = [packet[12], packet[13], packet[14], packet[15]];
213            crate::debug!(
214                "[SYS] UDP Parse: dropped {}:{} -> {}:{} (dst != my_ip={})",
215                super::format_ipv4(src_ip),
216                src_port,
217                super::format_ipv4(dst_ip),
218                dst_port,
219                super::format_ipv4(my_ip)
220            );
221        }
222        return Err("udp packet not for local ip");
223    }
224
225    let udp_len = (((udp[4] as u16) << 8) | udp[5] as u16) as usize;
226    if udp_len < 8 || udp_len > udp.len() {
227        if is_dhcp_client_rx {
228            crate::info!(
229                "UDP Parse: Invalid UDP length {} (buffer {})",
230                udp_len,
231                udp.len()
232            );
233        }
234        return Err("invalid udp length");
235    }
236
237    let payload = &udp[8..udp_len];
238    let src_ip = [packet[12], packet[13], packet[14], packet[15]];
239
240    if is_dhcp_client_rx {
241        crate::info!(
242            "UDP Parse: Accepted DHCP packet from {:?}:{} len={}",
243            src_ip,
244            src_port,
245            payload.len()
246        );
247    }
248
249    udp_queue_push(src_ip, src_port, dst_port, payload);
250
251    unsafe {
252        super::NET_STATE.rx_packets = super::NET_STATE.rx_packets.saturating_add(1);
253    }
254
255    Ok(())
256}
257
258/// 表の操作判断そのものは純粋モジュール `udp_queue` へ委譲する
259/// (満杯時の LRU 退避・ペイロード切り詰めはホスト側ユニットテストで検証済み。
260/// `tests/src/test_udp_queue.rs`)。ここは age の採番・ロック・グローバル配列
261/// へのアクセスという副作用のみを担当する。
262fn udp_queue_push(src_ip: [u8; 4], src_port: u16, dst_port: u16, payload: &[u8]) {
263    let _lock = super::NET_LOCK.lock();
264    unsafe {
265        let age = super::UDP_RX_TICK;
266        super::UDP_RX_TICK = super::UDP_RX_TICK.saturating_add(1);
267
268        let queue = &mut *core::ptr::addr_of_mut!(super::UDP_RX_QUEUE);
269        let _ = super::udp_queue::push(queue, src_ip, src_port, dst_port, payload, age);
270    }
271}
272
273fn payload_preview(payload: &[u8]) -> String {
274    let mut out = String::new();
275    for &b in payload {
276        let c = b as char;
277        if c.is_ascii_graphic() || c == ' ' {
278            out.push(c);
279        } else {
280            out.push('.');
281        }
282    }
283    out
284}
285
286/// 指定した宛先ポート宛の受信エントリを全て破棄し、件数を返す。
287///
288/// 【2026-07-29】DNS は 1 つの `src_port` から複数サーバへ同じクエリを送るため
289/// (`net/dns.rs:74-88`)、応答も同じポート宛に複数届く。1 件だけ消費すると
290/// 残りが永久に滞留し、クエリを重ねるほどキューが消費済みの応答で埋まって
291/// 新しい DNS 応答が入らなくなる(実測: `udp_queue_len=6` で全て古い応答)。
292///
293/// **クエリが完了したと判断できる呼び出し側だけが呼ぶこと。**
294/// 汎用の `udp_recv_matching` 側で一括破棄すると、述語で送信元を絞る用途
295/// (NTP 等)の契約を壊す。
296pub fn purge_port(port: u16) -> usize {
297    let _lock = super::NET_LOCK.lock();
298    unsafe {
299        let queue = &mut *core::ptr::addr_of_mut!(super::UDP_RX_QUEUE);
300        super::udp_queue::purge_port(queue, port)
301    }
302}