Skip to main content

atmos/kernel/net/
dns.rs

1fn parse_host_port(host: &str) -> (&str, u16) {
2    if let Some((h, p_str)) = host.split_once(':') {
3        if let Ok(p) = p_str.parse::<u16>() {
4            return (h, p);
5        }
6    }
7    (host, 80)
8}
9
10fn parse_ipv4_addr(s: &str) -> Option<[u8; 4]> {
11    let mut ip = [0u8; 4];
12    let mut count = 0;
13    let mut current = 0u32;
14    let mut has_digits = false;
15    for c in s.chars() {
16        if c.is_ascii_digit() {
17            let d = (c as u32) - ('0' as u32);
18            current = current * 10 + d;
19            if current > 255 {
20                return None;
21            }
22            has_digits = true;
23        } else if c == '.' {
24            if count >= 3 || !has_digits {
25                return None;
26            }
27            ip[count] = current as u8;
28            count += 1;
29            current = 0;
30            has_digits = false;
31        } else {
32            return None;
33        }
34    }
35    if count == 3 && has_digits {
36        ip[3] = current as u8;
37        Some(ip)
38    } else {
39        None
40    }
41}
42
43pub fn dns_query_a_via_udp(host: &str) -> Result<[u8; 4], &'static str> {
44    let _p = crate::os_lib::web_engine::perf::start();
45    let r = dns_query_a_via_udp_measured(host);
46    crate::os_lib::web_engine::perf::add(
47        crate::os_lib::web_engine::perf::Slot::DnsQuery,
48        _p,
49    );
50    r
51}
52
53fn dns_query_a_via_udp_measured(host: &str) -> Result<[u8; 4], &'static str> {
54    let (host_only, _) = parse_host_port(host);
55    if let Some(ip) = parse_ipv4_addr(host_only) {
56        return Ok(ip);
57    }
58
59    // 対策2: DNS キャッシュ。期限内ならネットワークを一切叩かずに即返す。
60    if let Some(ip) = super::dns_cache_lookup(host_only) {
61        return Ok(ip);
62    }
63
64    let dhcp_dns = super::status().dns;
65    let dhcp_dns2 = super::status().dns2;
66
67    // 問い合わせ先DNSサーバーのリストを作成(重複は排除)
68    let mut dns_servers = alloc::vec::Vec::new();
69    if dhcp_dns != [0, 0, 0, 0] {
70        dns_servers.push(dhcp_dns);
71    }
72    if dhcp_dns2 != [0, 0, 0, 0] && dhcp_dns2 != dhcp_dns {
73        dns_servers.push(dhcp_dns2);
74    }
75    const FALLBACK_DNS: [[u8; 4]; 2] = [[8, 8, 8, 8], [1, 1, 1, 1]];
76    for &fallback_ip in &FALLBACK_DNS {
77        if !dns_servers.contains(&fallback_ip) {
78            dns_servers.push(fallback_ip);
79        }
80    }
81
82    let mut last_err = "dns response timeout";
83
84    // 【2026-07-29】txid と送信元ポートは **3 回の試行で共有**する。
85    //
86    // 以前は試行ごとに新しい txid/ポートを採番していたため、1 回目への応答が
87    // 遅れて 2 回目の待機中に届いても **txid 不一致で捨てられ**、まったく
88    // 活かせなかった(`parse_dns_a_response` は txid 不一致で `Ok(None)`)。
89    // 再送は「新しいクエリ」ではなく「同じクエリの再送」なので、
90    // 同じ txid・同じポートで送るのが標準的な挙動。こうすれば
91    // どの試行への応答でも受け取れる。
92    // 実測で DNS 失敗が全体の 33〜50% を占めており最大の失敗要因
93    // (`spec/tcp_receive.md` の STATS 参照)。
94    let txid = super::next_dns_txid();
95    let src_port = super::next_dns_src_port();
96
97    for attempt in 0..3 {
98
99        let mut query = [0u8; 256];
100        let q_len = build_dns_query(host_only, txid, &mut query)?;
101
102        // リスト内のすべてのDNSサーバーへ並行送信。1件も送れなければ(ARP解決失敗等)
103        // 応答を待っても無駄なので、原因を可視化した上でこの試行の待機をスキップする
104        // (以前は debug! で握り潰され、送信全滅時も毎回フル timeout 分待っていた —
105        // これが「DNSが常にタイムアウトする」の主因の一つ)。
106        // 【2026-07-29】全サーバへの同報をやめ、**試行ごとに 1 サーバ**へ送る。
107        //
108        // QEMU は `-netdev user`(SLIRP ユーザーモードネットワーク)で動いており、
109        // SLIRP は同時 UDP セッション数に上限があり各セッションはアイドルで回収される。
110        // 1 クエリを 3〜4 サーバへ同報するとセッションを 3〜4 倍消費し、
111        // 後続クエリの応答を受け取る経路が確保できなくなる疑いがある。
112        //
113        // 実測の裏付け: 送信は常に `sent_ok=3/3` で成功しているのに応答が返らず、
114        // 早いクエリ(src_port 53000/53002)は成功し後のクエリ(53005/53006)が
115        // 全滅するというパターンだった(`spec/tcp_receive.md`)。
116        //
117        // 冗長性は「試行ごとにサーバを切り替える」ことで担保する。
118        // 3 回の試行で最大 3 サーバを順に試すので、1 台が不調でも救える。
119        let target = dns_servers[(attempt as usize) % dns_servers.len()];
120        let mut any_sent = false;
121        let mut sent_ok = 0u32;
122        for &dns_ip in core::slice::from_ref(&target) {
123            match super::udp::send_udp_ipv4(dns_ip, src_port, 53, &query[..q_len]) {
124                Ok(_) => { any_sent = true; sent_ok += 1; }
125                Err(e) => {
126                    crate::warn!("[NET] DNS: send to {} failed: {}", super::format_ipv4(dns_ip), e);
127                    last_err = e;
128                }
129            }
130        }
131
132        if !any_sent {
133            crate::warn!(
134                "DNS: Attempt {} for host '{}' — send failed to all {} server(s), skipping wait ({}).",
135                attempt + 1,
136                host_only,
137                dns_servers.len(),
138                last_err
139            );
140            crate::kernel::scheduler::sleep(10); // ~100ms backoff before next attempt
141            continue;
142        }
143
144        // 【2026-07-29診断】「送れていないのか、送れているが届かないのか」を
145        // 切り分ける。DNS 失敗が最大の失敗要因(実測 33〜63%)。
146        crate::warn!(
147            "[NET][DNSSEND] host={} attempt={} src_port={} txid={:#06x} sent_ok={}/{}",
148            host_only, attempt + 1, src_port, txid, sent_ok, dns_servers.len()
149        );
150
151        let start_time = crate::kernel::timer::get_system_time_ms();
152        let timeout_ms = 1500u64 + (attempt as u64) * 1000;
153
154        loop {
155            crate::kernel::usb::poll_ethernet_data_plane_only();
156
157            // 応答元 IP の一致は要求しない: sport==53 + txid一致だけで照合する。
158            // 一部のホームルータ/ISPは DNS を透過的に横取りし、問い合わせ先とは
159            // 異なる IP(ルータ自身など)から応答を返す「透過 DNS プロキシ」を行う。
160            // 以前は `dns_servers.contains(&sip)` を必須にしていたため、そうした
161            // 環境では正当な応答が届いていても照合に失敗し、実質「常にタイムアウトする」
162            // 挙動になっていた。txid は送信元ポート(53000+ローテーション)と合わせて
163            // 十分な相関を持つため、送信元 IP の厳密一致は外しても安全側に倒れる。
164            let matched = super::udp::udp_recv_matching(src_port, |_sip, sport, pl| {
165                sport == 53
166                    && pl.len() >= 2
167                    && (((pl[0] as u16) << 8) | pl[1] as u16) == txid
168            });
169
170            if let Some(rx) = matched {
171                // 【2026-07-29】この src_port のクエリは応答を得たので完了。
172                // 同じポートから**全 DNS サーバへ同報**しているため、残りのサーバの
173                // 応答が後からキューに残り続ける。放置すると 6 件のキューが
174                // 消費済みの応答で埋まり、以降の DNS が受信できなくなる
175                // (実測: `udp_queue_len=6` で全て古い応答)。ここで破棄する。
176                let purged = super::udp::purge_port(src_port);
177                if purged > 0 {
178                    crate::debug!(
179                        "[NET] DNS: purged {} duplicate responses for src_port={}",
180                        purged,
181                        src_port
182                    );
183                }
184                if !dns_servers.contains(&rx.src_ip) {
185                    crate::debug!(
186                        "DNS: response for '{}' from unexpected IP {} (transparent proxy?), accepting anyway",
187                        host_only,
188                        super::format_ipv4(rx.src_ip)
189                    );
190                }
191                match parse_dns_a_response(&rx.payload, txid, host_only) {
192                    Ok(Some(parsed)) => {
193                        super::dns_cache_insert(host_only, parsed);
194                        return Ok(parsed);
195                    }
196                    Ok(None) => {}
197                    Err(e) => {
198                        crate::warn!("[NET] DNS parse err from {}: {}", super::format_ipv4(rx.src_ip), e);
199                        last_err = e;
200                    }
201                }
202            }
203
204            let elapsed = crate::kernel::timer::get_system_time_ms().wrapping_sub(start_time);
205            if elapsed >= timeout_ms {
206                break;
207            }
208
209            crate::kernel::scheduler::sleep(1);
210        }
211
212        // 【2026-07-25診断】同一ブート内でHTTPS/DNS接続を重ねるほど失敗し
213        // やすくなる現象(`ordinal=`ログで定量確認済み、詳細はTODO.md)
214        // の原因切り分けのため、DNSタイムアウト時点でのUDP受信キュー全体
215        // の中身をダンプする。もし無関係な古いエントリでキューが埋まって
216        // いれば、本来届いているはずのDNS応答が処理前に退避されている
217        // 可能性が高い、という仮説を検証する。
218        let queue_dump = super::udp::udp_queue_lines(None);
219        crate::warn!(
220            "[NET][DIAG] DNS timeout: src_port={} txid={:#06x} udp_queue_len={} entries={:?}",
221            src_port, txid, queue_dump.len(), queue_dump
222        );
223
224        crate::info!(
225            "DNS: Attempt {} for host '{}' timed out. Retrying...",
226            attempt + 1,
227            host_only
228        );
229    }
230
231    Err(last_err)
232}
233
234
235pub fn ntp_query(host: &str) -> Result<u64, &'static str> {
236    let server_ip = dns_query_a_via_udp(host)?;
237
238    let mut req = [0u8; 48];
239    // LI=0, VN=4, Mode=3(client)。
240    req[0] = 0x23;
241
242    let mut last_err = "ntp response timeout";
243
244    for attempt in 0..3 {
245        // 送信元ポートをクエリ毎に変える(DNS と同じ思想)。固定ポートだと
246        // 他スレッド/古い応答と UDP キューを共有し、正規応答を読み捨ててしまう。
247        let src_port: u16 = 51230u16.wrapping_add((attempt as u16) * 7);
248
249        // RFC 4330: 偽造・リプレイ攻撃防止のため、Transmit Timestamp (パケットの末尾8バイト) に
250        // リクエスト毎に異なる一意な値(Nonce)を設定し、応答の Originate Timestamp と照合する。
251        let nonce = crate::kernel::timer::get_system_time_ms();
252        req[40] = (nonce >> 24) as u8;
253        req[41] = (nonce >> 16) as u8;
254        req[42] = (nonce >> 8) as u8;
255        req[43] = nonce as u8;
256        req[44] = attempt as u8;
257        req[45] = 0;
258        req[46] = 0;
259        req[47] = 0;
260
261        // 送信失敗(初回ARP未解決・TXキュー満杯など)を即 ? で中断せず、リトライへ回す。
262        if super::udp::send_udp_ipv4(server_ip, src_port, 123, &req).is_err() {
263            last_err = "ntp send failed";
264            crate::kernel::scheduler::sleep(5); // ~50ms バックオフ後に再送
265            continue;
266        }
267
268        let start_time = crate::kernel::timer::get_system_time_ms();
269        let timeout_ms = 4000u64;
270
271        loop {
272            crate::kernel::usb::poll_ethernet_data_plane_only();
273
274            // 送信元(server_ip:123)かつ 48B 以上の応答のみを取り出す(取り違え防止)。
275            let matched = super::udp::udp_recv_matching(src_port, |sip, sport, pl| {
276                sip == server_ip && sport == 123 && pl.len() >= 48
277            });
278            if let Some(rx) = matched {
279                // レスポンス検証: Mode=4(server) 必須、stratum=0(Kiss-o'-Death) / stratum>15 を拒否。
280                // (li == 3 unsync 警告は実質問題ない場合が多いため許容する)
281                let mode = rx.payload[0] & 0x7;
282                let stratum = rx.payload[1];
283                if mode != 4 || stratum == 0 || stratum > 15 {
284                    last_err = "ntp invalid response";
285                    // この応答は捨て、同じ試行のタイムアウトまで本物を待ち続ける。
286                } else if rx.payload[24..32] != req[40..48] {
287                    // RFC 4330 準拠: Originate Timestamp (24..32) が、送信した Transmit Timestamp と一致するか検証
288                    last_err = "ntp originate timestamp mismatch (replay attack?)";
289                } else {
290                    let secs_1900 = ((rx.payload[40] as u64) << 24)
291                        | ((rx.payload[41] as u64) << 16)
292                        | ((rx.payload[42] as u64) << 8)
293                        | (rx.payload[43] as u64);
294                    if secs_1900 > crate::kernel::timer::NTP_UNIX_OFFSET {
295                        return Ok(secs_1900 - crate::kernel::timer::NTP_UNIX_OFFSET);
296                    }
297                    last_err = "ntp invalid timestamp";
298                }
299            }
300
301            let elapsed = crate::kernel::timer::get_system_time_ms().wrapping_sub(start_time);
302            if elapsed >= timeout_ms {
303                break;
304            }
305            // フリーズ対策: スリープで完全に待つのではなく yield_now() で
306            // パケット回収を高頻度で継続しつつ、他タスクへ CPU を譲る。
307            crate::kernel::scheduler::yield_now();
308        }
309
310        crate::info!(
311            "NTP: Attempt {} for '{}' timed out. Retrying...",
312            attempt + 1,
313            host
314        );
315        // リトライ間バックオフ(連射によるレート制限/KoD 誘発を回避)。
316        crate::kernel::scheduler::sleep(10); // ~100ms
317    }
318
319    Err(last_err)
320}
321
322pub fn ntp_sync() -> Result<u64, &'static str> {
323    // 1台が詰まっていても他で回復できるようフォールバック列を用意。
324    const HOSTS: [&str; 3] = ["ntp.nict.jp", "time.cloudflare.com", "pool.ntp.org"];
325    let mut last = "ntp response timeout";
326    for h in HOSTS.iter() {
327        match ntp_query(h) {
328            Ok(unix) => {
329                crate::kernel::timer::set_wall_clock(unix);
330                crate::info!("[NET] NTP: synced via {}. unix={}", h, unix);
331                return Ok(unix);
332            }
333            Err(e) => {
334                crate::warn!("[SYS] NTP: Failed to query {}: {}", h, e);
335                last = e;
336            }
337        }
338    }
339    Err(last)
340}
341
342pub fn build_dns_query(domain: &str, txid: u16, out: &mut [u8]) -> Result<usize, &'static str> {
343    if out.len() < 64 {
344        return Err("buffer too small");
345    }
346
347    out[0] = (txid >> 8) as u8;
348    out[1] = txid as u8;
349    out[2] = 0x01;
350    out[3] = 0x00;
351    out[4] = 0x00;
352    out[5] = 0x01;
353    out[6] = 0x00;
354    out[7] = 0x00;
355    out[8] = 0x00;
356    out[9] = 0x00;
357    out[10] = 0x00;
358    out[11] = 0x00;
359
360    let mut idx = 12usize;
361    for label in domain.split('.') {
362        let l = label.len();
363        if l == 0 || l > 63 || idx + 1 + l >= out.len() {
364            return Err("invalid domain");
365        }
366        out[idx] = l as u8;
367        idx += 1;
368        out[idx..idx + l].copy_from_slice(label.as_bytes());
369        idx += l;
370    }
371
372    if idx + 5 >= out.len() {
373        return Err("buffer too small");
374    }
375
376    out[idx] = 0x00;
377    idx += 1;
378
379    out[idx] = 0x00;
380    out[idx + 1] = 0x01;
381    out[idx + 2] = 0x00;
382    out[idx + 3] = 0x01;
383
384    Ok(idx + 4)
385}
386
387pub fn parse_dns_a_response(
388    packet: &[u8],
389    expected_txid: u16,
390    expected_domain: &str,
391) -> Result<Option<[u8; 4]>, &'static str> {
392    if packet.len() < 12 {
393        return Err("dns packet too short");
394    }
395
396    let rx_txid = ((packet[0] as u16) << 8) | packet[1] as u16;
397    if rx_txid != expected_txid {
398        return Ok(None);
399    }
400
401    let is_response = (packet[2] & 0x80) != 0;
402    if !is_response {
403        return Ok(None);
404    }
405
406    let rcode = packet[3] & 0x0F;
407    if rcode != 0 {
408        return Ok(None);
409    }
410
411    let qdcount = ((packet[4] as u16) << 8) | packet[5] as u16;
412    let ancount = ((packet[6] as u16) << 8) | packet[7] as u16;
413    let nscount = ((packet[8] as u16) << 8) | packet[9] as u16;
414    let arcount = ((packet[10] as u16) << 8) | packet[11] as u16;
415
416    let total_rrs = ancount + nscount + arcount;
417
418    let mut idx = 12usize;
419
420    for _ in 0..qdcount {
421        idx = skip_dns_name(packet, idx)?;
422        if idx + 4 > packet.len() {
423            return Err("dns question truncated");
424        }
425        idx += 4;
426    }
427
428    for _ in 0..total_rrs {
429        let (_matches, next_idx) = compare_dns_name(packet, idx, expected_domain)?;
430        idx = next_idx;
431        if idx + 10 > packet.len() {
432            return Err("dns answer truncated");
433        }
434
435        let rr_type = ((packet[idx] as u16) << 8) | packet[idx + 1] as u16;
436        let _rr_class = ((packet[idx + 2] as u16) << 8) | packet[idx + 3] as u16;
437        let _ttl = ((packet[idx + 4] as u32) << 24)
438            | ((packet[idx + 5] as u32) << 16)
439            | ((packet[idx + 6] as u32) << 8)
440            | packet[idx + 7] as u32;
441        let rdlen = ((packet[idx + 8] as u16) << 8) | packet[idx + 9] as u16;
442        idx += 10;
443
444        if idx + rdlen as usize > packet.len() {
445            return Err("dns rdata truncated");
446        }
447
448        if rr_type == 1 && rdlen == 4 {
449            let ip = [
450                packet[idx],
451                packet[idx + 1],
452                packet[idx + 2],
453                packet[idx + 3],
454            ];
455            unsafe {
456                super::NET_STATE.rx_packets = super::NET_STATE.rx_packets.saturating_add(1);
457            }
458            return Ok(Some(ip));
459        }
460
461        idx += rdlen as usize;
462    }
463
464    Ok(None)
465}
466
467
468
469fn skip_dns_name(packet: &[u8], mut idx: usize) -> Result<usize, &'static str> {
470    if idx >= packet.len() {
471        return Err("dns name out of bounds");
472    }
473
474    loop {
475        if idx >= packet.len() {
476            return Err("dns name truncated");
477        }
478
479        let len = packet[idx];
480
481        if len & 0xC0 == 0xC0 {
482            if idx + 1 >= packet.len() {
483                return Err("dns ptr truncated");
484            }
485            return Ok(idx + 2);
486        }
487
488        idx += 1;
489        if len == 0 {
490            return Ok(idx);
491        }
492
493        idx += len as usize;
494    }
495}
496
497fn compare_dns_name(packet: &[u8], start_idx: usize, expected: &str) -> Result<(bool, usize), &'static str> {
498    // まず正確に名前全体をスキップして、次のパース位置を取得する
499    let next_idx = skip_dns_name(packet, start_idx)?;
500    
501    let expected_bytes = expected.as_bytes();
502    let mut expected_idx = 0;
503    
504    let mut cur_idx = start_idx;
505    let mut jumps = 0;
506    
507    loop {
508        if jumps > 10 { return Err("dns pointer loop"); }
509        if cur_idx >= packet.len() { return Err("dns name out of bounds"); }
510        
511        let len = packet[cur_idx];
512        if len & 0xC0 == 0xC0 {
513            if cur_idx + 1 >= packet.len() { return Err("dns ptr truncated"); }
514            let offset = (((len & 0x3F) as usize) << 8) | (packet[cur_idx + 1] as usize);
515            cur_idx = offset;
516            jumps += 1;
517            continue;
518        }
519        
520        cur_idx += 1;
521        if len == 0 {
522            let matches = expected_idx == expected_bytes.len();
523            return Ok((matches, next_idx));
524        }
525        
526        let label_len = len as usize;
527        if cur_idx + label_len > packet.len() { return Err("dns name truncated"); }
528        
529        if expected_idx > 0 {
530            if expected_idx >= expected_bytes.len() || expected_bytes[expected_idx] != b'.' {
531                return Ok((false, next_idx));
532            }
533            expected_idx += 1;
534        }
535        
536        let label = &packet[cur_idx..cur_idx + label_len];
537        if expected_idx + label_len > expected_bytes.len() {
538            return Ok((false, next_idx));
539        }
540        
541        if !label.eq_ignore_ascii_case(&expected_bytes[expected_idx..expected_idx + label_len]) {
542            return Ok((false, next_idx));
543        }
544        
545        expected_idx += label_len;
546        cur_idx += label_len;
547    }
548}