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 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 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 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 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); continue;
142 }
143
144 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 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 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 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 req[0] = 0x23;
241
242 let mut last_err = "ntp response timeout";
243
244 for attempt in 0..3 {
245 let src_port: u16 = 51230u16.wrapping_add((attempt as u16) * 7);
248
249 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 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); 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 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 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 } else if rx.payload[24..32] != req[40..48] {
287 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 crate::kernel::scheduler::yield_now();
308 }
309
310 crate::info!(
311 "NTP: Attempt {} for '{}' timed out. Retrying...",
312 attempt + 1,
313 host
314 );
315 crate::kernel::scheduler::sleep(10); }
318
319 Err(last_err)
320}
321
322pub fn ntp_sync() -> Result<u64, &'static str> {
323 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 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}