nx_dns_io.nx source
↩ module page · 126 lines · 5523 B
1// nx_dns_io.nx -- live UDP-composed DNS stub resolver (x86_64).
2//
3// Phase 0a completion brick: composes the pure-byte primitives in
4// nx_dns.nx with the sovereign UDP transport in nx_udp.nx into a
5// single callable `nx_dns_io_resolve_a(...)`. This is the byte that
6// turns DNS from "we have a parser" into "we can actually resolve
7// example.com over the wire."
8//
9// Architecture pinning: this module uses Linux x86_64 syscalls
10// (sys_socket / sys_sendto / sys_recvfrom). Running it under
11// qemu-riscv64 would need the RV64 socket-syscall wrappers, which
12// aren't shipped in nx_syscalls.nx yet (queued separately as the
13// "RV64 socket parity" brick). Live smokes run via the x86_64
14// native path with nxc2's --target x86_64.
15//
16// What it does today:
17// - opens a UDP socket
18// - builds an A-record query from a hostname using nx_dns_build_query
19// - sends it to a caller-supplied IPv4 resolver on port 53 (or any port)
20// - reads ONE response packet
21// - parses + extracts the first A record into out4
22// - returns a sealed verdict
23//
24// What it doesn't do yet:
25// - SO_RCVTIMEO bind for bounded blocking (next iteration, ~20 LOC)
26// - retry-on-timeout (RFC 1035 recommends 5s initial, doubling)
27// - parallel queries to multiple resolvers (Happy Eyeballs for DNS)
28// - CNAME chase (caller-level retry until verdict != CNAME)
29// - TC=1 -> TCP fallback (Phase 0b dependency)
30// - DoH / DoT (queued behind TLS audit)
31//
32// Composes with:
33// - nx_udp -- sovereign UDP transport
34// - nx_dns -- pure parse/build primitives
35// - nx_csprng -- caller supplies tx_id from nx_csprng_u64 per RFC 5452
36//
37// license_tier: INDEPENDENT_REDERIVE
38// genealogy_id: international-research-sources/ietf/rfc_1035 + ietf/rfc_5452
39// lineage_id: nishi_browser_dns_resolver_io_q10
40//
41// nx_safety_envelope:
42// intended_use: "DNS UDP I/O -- live resolver primitive
43// composing nx_dns + nx_udp. Substrate-
44// sovereign replacement for libresolv /
45// getaddrinfo."
46// sil_target: SIL2 (DNS spoof = arbitrary endpoint)
47// asil_target: QM
48// dal_target: DAL C
49// evidence: [RFC_1035_canonical_basis,
50// composes_nx_dns_anti_cycle_bound,
51// composes_nx_udp_socket_balance,
52// first_sovereign_chain_byte_through_kernel_VERIFIED]
53// hazard_register: [bug-tape-DNS-cache-poisoning,
54// bug-tape-DoH-bypass-via-plain-UDP,
55// bug-tape-source-port-randomization-required]
56// residual_risk: "DNS-over-TLS (RFC 7858) / DNS-over-HTTPS
57// (RFC 8484) for confidentiality + integrity
58// is queued; this primitive is plain UDP."
59// verdict: NOT_YET_EVALUATED
60
61import "nx_syscalls_x86_64.nx"
62import "nx_udp.nx"
63import "nx_dns.nx"
64
65// IO-layer verdicts in the 100+ range so they never collide with
66// the parse-layer verdicts (NX_DNS_VERDICT_* in nx_dns.nx, 0..14).
67const NX_DNS_IO_VERDICT_OK: i64 = 1 // == NX_DNS_VERDICT_OK
68const NX_DNS_IO_VERDICT_SOCKET_FAIL: i64 = 100
69const NX_DNS_IO_VERDICT_SEND_FAIL: i64 = 102
70const NX_DNS_IO_VERDICT_RECV_FAIL: i64 = 103
71const NX_DNS_IO_VERDICT_BUILD_FAIL: i64 = 104
72const NX_DNS_IO_VERDICT_SHORT_RESP: i64 = 105
73
74// Per-call buffer ceiling. RFC 1035 ยง2.3.4 caps UDP DNS messages
75// at 512 bytes pre-EDNS0; EDNS0 raises to 4096. We provision 1024
76// to cover typical EDNS0 responses without paying for max.
77const NX_DNS_IO_BUF: i64 = 1024
78
79// Resolve a hostname's A record over UDP against a caller-supplied
80// resolver. Returns NX_DNS_IO_VERDICT_OK + writes 4 IPv4 bytes to
81// out4, OR a non-OK verdict (IO-layer code 100+ for transport failures,
82// nx_dns parse-layer code 2-11 for protocol failures).
83//
84// tx_id MUST come from a CSPRNG (nx_csprng_u64() & 0xffff) per
85// RFC 5452 -- the 16 bits combined with kernel-randomised source
86// port are the only on-path spoof defenses.
87func nx_dns_io_resolve_a(
88 server_a: i64, server_b: i64, server_c: i64, server_d: i64,
89 server_port: i64,
90 name: *u8, name_len: i64,
91 tx_id: i64,
92 out4: *u8
93) -> i64 {
94 let fd: i64 = nx_udp_open()
95 if fd < 0 { return NX_DNS_IO_VERDICT_SOCKET_FAIL }
96 let qbuf: *u8 = sys_mmap(NX_DNS_IO_BUF)
97 let q_len: i64 = nx_dns_build_query(
98 name, name_len, NX_DNS_TYPE_A, tx_id, qbuf, NX_DNS_IO_BUF
99 )
100 if q_len < 0 {
101 sys_close(fd)
102 return NX_DNS_IO_VERDICT_BUILD_FAIL
103 }
104 let dest: *u8 = sys_mmap(16)
105 nx_udp_sockaddr_dest(dest, server_a, server_b, server_c, server_d, server_port)
106 let sr: i64 = nx_udp_send(fd, qbuf, q_len, dest)
107 if sr != q_len {
108 sys_close(fd)
109 return NX_DNS_IO_VERDICT_SEND_FAIL
110 }
111 let rbuf: *u8 = sys_mmap(NX_DNS_IO_BUF)
112 let rr: i64 = nx_udp_recv(fd, rbuf, NX_DNS_IO_BUF, 0 as *u8, 0 as *i64)
113 sys_close(fd)
114 if rr < 0 { return NX_DNS_IO_VERDICT_RECV_FAIL }
115 if rr < NX_DNS_HEADER_LEN { return NX_DNS_IO_VERDICT_SHORT_RESP }
116 return nx_dns_parse_response_a(rbuf, rr, tx_id, out4)
117}
118
119// Sealed-enum validity gate for the IO-layer verdict range. A valid
120// IO verdict is either OK (==1) or in the 100..105 inclusive band.
121func nx_dns_io_verdict_is_valid(v: i64) -> i64 {
122 if v == NX_DNS_IO_VERDICT_OK { return 1 }
123 if v < NX_DNS_IO_VERDICT_SOCKET_FAIL { return 0 }
124 if v > NX_DNS_IO_VERDICT_SHORT_RESP { return 0 }
125 return 1
126}