Skip to main content

atmos/kernel/net/
dns.rs

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