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}