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 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 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 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 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); continue;
135 }
136
137 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 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 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 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 req[0] = 0x23;
234
235 let mut last_err = "ntp response timeout";
236
237 for attempt in 0..3 {
238 let src_port: u16 = 51230u16.wrapping_add((attempt as u16) * 7);
241
242 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 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); 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 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 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 } else if rx.payload[24..32] != req[40..48] {
280 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 crate::kernel::scheduler::yield_now();
301 }
302
303 crate::info!(
304 "NTP: Attempt {} for '{}' timed out. Retrying...",
305 attempt + 1,
306 host
307 );
308 crate::kernel::scheduler::sleep(10); }
311
312 Err(last_err)
313}
314
315pub fn ntp_sync() -> Result<u64, &'static str> {
316 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 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}