code wiki / (root) / nx_dns_io.nx

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}