code wiki / (root) / nx_dns_a.nx

nx_dns_a.nx source

↩ module page · 126 lines · 5763 B

1// nx_dns_a.nx -- minimal DNS A-record resolver on the nx_syscalls layer (X-TORRENT-LIVE-001 L1): 2// resolve a tracker hostname -> IPv4 so we can reach a LIVE tracker. The existing 3// nx_dns_resolve_a_record rides nx_syscalls_x86_64 (layer conflict with the torrent organs), so this 4// is a self-contained UDP A-query to a public resolver (8.8.8.8:53): build the query, send/recv over 5// the proven sovereign UDP plumbing, parse the first A answer (handles name-compression pointers). 6// GATE = LIVE (needs internet, like the H2 arc): resolve a real host -> a valid public IPv4. 7// Reusable fn nx_dns_a_resolve(host, out_ip4). license_tier: ORIGINAL 8// 9// module: nishi-core.torrent.dns_a 10// depends: nishi-core.sys.syscalls 11import "nx_syscalls.nx" 12import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 13const K_MAGIC_4660: i64 = 4660 14const K_MAGIC_2048: i64 = 2048 15 16func da_w(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 17// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 18// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 19// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 20// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 21func da_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 22func da_u16(buf: *u8, o: i64) -> i64 { return ((buf[o] as i64) << 8) | (buf[o+1] as i64) } 23 24// encode hostname into DNS labels at out[off..]; returns new offset 25func da_qname(host: *u8, out: *u8, off: i64) -> i64 { 26 var i: i64 = 0 27 var o: i64 = off 28 while host[i] != (0 as u8) { 29 var j: i64 = i 30 var go: i64 = 1 31 while go == 1 { if host[j] == (0 as u8) { go = 0 } else { if host[j] == (46 as u8) { go = 0 } else { j = j + 1 } } } 32 let llen: i64 = j - i 33 out[o] = llen as u8; o = o + 1 34 var k: i64 = i 35 while k < j { out[o] = host[k]; o = o + 1; k = k + 1 } 36 if host[j] == (46 as u8) { i = j + 1 } else { i = j } 37 } 38 out[o] = 0 as u8; o = o + 1 39 return o 40} 41 42func da_build_query(host: *u8, qid: i64, out: *u8) -> i64 { 43 out[0]=((qid>>8)&0xff) as u8; out[1]=(qid&0xff) as u8 // ID 44 out[2]=1 as u8; out[3]=0 as u8 // flags RD=1 45 out[4]=0 as u8; out[5]=1 as u8 // QDCOUNT=1 46 out[6]=0 as u8; out[7]=0 as u8 // ANCOUNT 47 out[8]=0 as u8; out[9]=0 as u8 // NSCOUNT 48 out[10]=0 as u8; out[11]=0 as u8 // ARCOUNT 49 var o: i64 = da_qname(host, out, 12) 50 out[o]=0 as u8; out[o+1]=1 as u8; o=o+2 // QTYPE=A 51 out[o]=0 as u8; out[o+1]=1 as u8; o=o+2 // QCLASS=IN 52 return o 53} 54 55// skip a DNS name at off; returns offset after it (handles 0xC0 compression pointer) 56func da_skip_name(buf: *u8, off: i64, n: i64) -> i64 { 57 var o: i64 = off 58 while o < n { 59 let b: i64 = buf[o] as i64 60 if b == 0 { return o + 1 } 61 if (b & 0xc0) == 0xc0 { return o + 2 } 62 o = o + 1 + b 63 } 64 return o 65} 66 67// parse first A record IP into out_ip4 (4 bytes); 1 found / 0 not 68func da_parse(buf: *u8, n: i64, out_ip: *u8) -> i64 { 69 if n < 12 { return 0 } 70 let anc: i64 = da_u16(buf, 6) 71 if anc < 1 { return 0 } 72 var o: i64 = da_skip_name(buf, 12, n) 73 o = o + 4 // qtype + qclass 74 var a: i64 = 0 75 while a < anc { 76 o = da_skip_name(buf, o, n) 77 if o + 10 > n { return 0 } 78 let typ: i64 = da_u16(buf, o) 79 let rdlen: i64 = da_u16(buf, o + 8) 80 let rdata: i64 = o + 10 81 if typ == 1 { if rdlen == 4 { 82 out_ip[0]=buf[rdata]; out_ip[1]=buf[rdata+1]; out_ip[2]=buf[rdata+2]; out_ip[3]=buf[rdata+3] 83 return 1 84 } } 85 o = rdata + rdlen 86 a = a + 1 87 } 88 return 0 89} 90 91func da_sockaddr(sa: *u8, port: i64, i0: i64, i1: i64, i2: i64, i3: i64) -> i64 { 92 sa[0]=2 as u8; sa[1]=0 as u8 93 sa[2]=((port>>8)&0xff) as u8; sa[3]=(port&0xff) as u8 94 sa[4]=i0 as u8; sa[5]=i1 as u8; sa[6]=i2 as u8; sa[7]=i3 as u8 95 var k: i64=8; while k<16 { sa[k]=0 as u8; k=k+1 } 96 return 0 97} 98 99// resolve host -> out_ip4. 1 ok / 0 fail. queries 8.8.8.8:53 over UDP. 100func nx_dns_a_resolve(host: *u8, out_ip: *u8) -> i64 { 101 let fd: i64 = sys_socket(AF_INET, SOCK_DGRAM, 0) 102 if fd < 0 { return 0 } 103 sys_set_socket_timeout(fd, 4) 104 let dest: *u8 = sys_mmap(16); da_sockaddr(dest, 53, 8, 8, 8, 8) 105 let q: *u8 = sys_mmap(512); let qlen: i64 = da_build_query(host, K_MAGIC_4660, q) // qid=0x1234 106 if sys_sendto(fd, q, qlen, 0, dest, 16) < 0 { sys_close(fd); return 0 } 107 let r: *u8 = sys_mmap(K_MAGIC_2048); let rn: i64 = sys_recvfrom(fd, r, K_MAGIC_2048, 0, 0 as *u8, 0 as *i64) 108 sys_close(fd) 109 if rn < 12 { return 0 } 110 return da_parse(r, rn, out_ip) 111} 112 113func main() -> i64 { 114 let host: *u8 = "tracker.opentrackr.org" as *u8 115 let ip: *u8 = sys_mmap(4); ip[0]=0 as u8; ip[1]=0 as u8; ip[2]=0 as u8; ip[3]=0 as u8 116 let ok: i64 = nx_dns_a_resolve(host, ip) 117 var valid: i64 = 0 118 if ok == 1 { if (ip[0] as i64) != 0 { if (ip[0] as i64) != 127 { valid = 1 } } } 119 da_w(1, "DNS-GATE authored=organ host=tracker.opentrackr.org ip=" as *u8) 120 da_wn(1, ip[0] as i64); da_w(1, "." as *u8); da_wn(1, ip[1] as i64); da_w(1, "." as *u8) 121 da_wn(1, ip[2] as i64); da_w(1, "." as *u8); da_wn(1, ip[3] as i64) 122 da_w(1, " resolved=" as *u8); da_wn(1, ok); da_w(1, " valid=" as *u8); da_wn(1, valid) 123 if valid == 1 { da_w(1, " verdict=GREEN\n" as *u8); sys_exit(0); return 0 } 124 da_w(1, " verdict=RED(network?)\n" as *u8); sys_exit(1) 125 return 1 126}