nx_netscope_dns.nx source
↩ module page · 436 lines · 19608 B
1// nx_netscope_dns.nx -- NX-NETSCOPE L0 (timeout-recv keystone) + L1
2// (DNS multi-path parallel prober). The headline EXCEED + the <5s
3// one-shot diagnosis of the dual-homed split-horizon DNS race that makes
4// nishifamily.com / andelinwest.com resolve slowly (or NXDOMAIN) from the
5// LAN. See docs/NX_NETSCOPE_CHARTER.md sections 2/3/8.
6//
7// REUSES (never reinvents -- standing rule): nx_dns_build_query,
8// nx_dns_parse_response_a, nx_dns_get_u16_be and the DNS flag/RCODE
9// constants from nx_dns.nx; the socket+sockaddr flow from
10// nx_dns_resolve_a_record.nx; the SO_RCVTIMEO timeval pattern from
11// nx_iot_set_rcvtimeo_ms (ported here to avoid dragging the announce
12// module's deps).
13//
14// EXCEED vs dig/nslookup: those query ONE resolver (or the OS resolver,
15// which is the very thing that's broken). This fires a (server x host)
16// MATRIX, each cell SO_RCVTIMEO-bounded (never hangs), dissects the RCODE
17// (NXDOMAIN/SERVFAIL/REFUSED vs A), and flags the two failure modes the
18// OS hides: the WOULD-POISON race (one resolver answers NXDOMAIN while
19// another has the A -> Windows smart-multi-homed accepts the wrong one)
20// and the PUBLIC-vs-RFC1918 hairpin (an owned domain resolving to its
21// public IP instead of the internal 192.168.8.240).
22//
23// license_tier: ORIGINAL
24// genealogy_id: nishi_nx_netscope_l0_l1
25
26import "nx_syscalls.nx"
27import "nx_dns.nx"
28const NXNS_MAGIC_2048: i64 = 2048
29
30// ---- tunables (Charter: timeouts in svc-config; literals here are the
31// bootstrap defaults until svc-config wiring lands) ----
32const NXNS_SO_RCVTIMEO: i64 = 20 // Linux SO_RCVTIMEO optname
33const NXNS_DNS_PORT: i64 = 53
34const NXNS_EAGAIN: i64 = 0 - 11 // -EAGAIN/-EWOULDBLOCK = recv timed out
35
36// ---- per-cell verdicts (sealed enum) ----
37const NXNS_OK_A: i64 = 0 // got an A record (rcode 0, ancount>0)
38const NXNS_NOERROR_NO_A: i64 = 1 // rcode 0 but no A (ancount 0 / only other types)
39const NXNS_NXDOMAIN: i64 = 2 // rcode 3
40const NXNS_SERVFAIL: i64 = 3 // rcode 2
41const NXNS_REFUSED: i64 = 4 // rcode 5
42const NXNS_RCODE_OTHER: i64 = 5 // rcode 1/4/6+...
43const NXNS_TIMEOUT: i64 = 6 // SO_RCVTIMEO fired -- THE no-hang guarantee
44const NXNS_NOT_RESPONSE: i64 = 7 // QR=0
45const NXNS_TRUNCATED: i64 = 8 // TC=1
46const NXNS_TXID_MISMATCH: i64 = 9 // possible spoof / stale packet
47const NXNS_SOCKET_FAIL: i64 = 10
48const NXNS_SEND_FAIL: i64 = 11
49const NXNS_BAD_HEADER: i64 = 12
50const NXNS_PENDING: i64 = 99 // L1.1 parallel: cell awaiting poll() response
51const NXNS_POLLIN: i64 = 1 // poll() events/revents bit for "readable"
52
53// DNS RCODE values (RFC 1035 sec 4.1.1).
54const NXNS_RCODE_SERVFAIL: i64 = 2
55const NXNS_RCODE_NXDOMAIN: i64 = 3
56const NXNS_RCODE_REFUSED: i64 = 5
57
58// ProbeCell: result of one (server x host) probe. 64 bytes (8 i64).
59struct ProbeCell {
60 server_ip: i64, // resolver queried, packed BE in low 32 bits
61 host_idx: i64, // index into the caller's host list
62 verdict: i64, // NXNS_* above
63 rcode: i64, // raw DNS RCODE (0..15), -1 if no response
64 ancount: i64, // answer count from the header
65 ipv4_packed: i64, // first A record IPv4 (BE low 32), 0 if none
66 latency_ms: i64, // monotonic wall delta for this cell
67 is_rfc1918: i64, // 1 if ipv4_packed is private (10/172.16-31/192.168)
68}
69
70const NXNS_PROBECELL_BYTES: i64 = 64
71
72// ===== L0: timeout-recv keystone =================================
73//
74// Set SO_RCVTIMEO to `timeout_ms` (timeval = {sec, usec}, the proven
75// nx_iot_set_rcvtimeo_ms layout) so the blocking recv can NEVER hang.
76// Ported here (not imported) to keep the netscope module dependency-thin.
77func nxns_set_rcvtimeo_ms(fd: i64, ms: i64) -> i64 {
78 let tv: *u8 = sys_mmap(16)
79 let sec: i64 = ms / 1000
80 let usec: i64 = (ms - sec * 1000) * 1000
81 tv[0] = (sec & 0xff) as u8
82 tv[1] = ((sec >> 8) & 0xff) as u8
83 tv[2] = ((sec >> 16) & 0xff) as u8
84 tv[3] = ((sec >> 24) & 0xff) as u8
85 tv[4] = 0 as u8; tv[5] = 0 as u8; tv[6] = 0 as u8; tv[7] = 0 as u8
86 tv[8] = (usec & 0xff) as u8
87 tv[9] = ((usec >> 8) & 0xff) as u8
88 tv[10] = ((usec >> 16) & 0xff) as u8
89 tv[11] = ((usec >> 24) & 0xff) as u8
90 tv[12] = 0 as u8; tv[13] = 0 as u8; tv[14] = 0 as u8; tv[15] = 0 as u8
91 return sys_setsockopt(fd, SOL_SOCKET, NXNS_SO_RCVTIMEO, tv, 16)
92}
93
94// nx_dns_recv_timed: bounded recvfrom. Returns bytes (>0), NXNS_EAGAIN
95// (-11) when SO_RCVTIMEO fired, or the negative errno on other failure.
96// THE keystone that makes every higher layer non-hanging + measurable.
97func nx_dns_recv_timed(fd: i64, buf: *u8, cap: i64, timeout_ms: i64,
98 from: *u8, from_len: *i64) -> i64 {
99 nxns_set_rcvtimeo_ms(fd, timeout_ms)
100 let got: i64 = sys_recvfrom(fd, buf, cap, 0, from, from_len)
101 return got
102}
103
104// ===== RFC1918 classifier ========================================
105// ipv4 is packed BE in the low 32 bits (a<<24|b<<16|c<<8|d).
106func nxns_is_rfc1918(ipv4: i64) -> i64 {
107 let a: i64 = (ipv4 >> 24) & 0xff
108 let b: i64 = (ipv4 >> 16) & 0xff
109 if a == 10 { return 1 }
110 if a == 192 { if b == 168 { return 1 } }
111 if a == 172 { if b >= 16 { if b <= 31 { return 1 } } }
112 return 0
113}
114
115// ===== shared response dissection (used by sequential + parallel) =
116//
117// Given a recv result `got` (bytes, or <=0 / NXNS_EAGAIN on no-answer),
118// classify into the cell: TIMEOUT / BAD_HEADER / TXID_MISMATCH /
119// NOT_RESPONSE / TRUNCATED / NXDOMAIN / SERVFAIL / REFUSED / RCODE_OTHER
120// / NOERROR_NO_A / OK_A (+ ipv4 + rfc1918). Caller sets cell.latency_ms.
121// Dissects the RCODE itself because nx_dns_parse_response_a collapses
122// every non-zero RCODE into one verdict (we need the granularity).
123func nxns_dissect_response(resp: *u8, got: i64, tx_id: i64, cell: *ProbeCell) -> i64 {
124 if got == NXNS_EAGAIN { cell.verdict = NXNS_TIMEOUT; return NXNS_TIMEOUT }
125 if got <= 0 { cell.verdict = NXNS_TIMEOUT; return NXNS_TIMEOUT }
126 if got < NX_DNS_HEADER_LEN { cell.verdict = NXNS_BAD_HEADER; return NXNS_BAD_HEADER }
127 let rtx: i64 = nx_dns_get_u16_be(resp, 0)
128 if rtx != (tx_id & 0xffff) { cell.verdict = NXNS_TXID_MISMATCH; return NXNS_TXID_MISMATCH }
129 let flags: i64 = nx_dns_get_u16_be(resp, 2)
130 if (flags & NX_DNS_FLAG_QR) == 0 { cell.verdict = NXNS_NOT_RESPONSE; return NXNS_NOT_RESPONSE }
131 if (flags & NX_DNS_FLAG_TC) != 0 { cell.verdict = NXNS_TRUNCATED; return NXNS_TRUNCATED }
132 let rcode: i64 = flags & NX_DNS_RCODE_MASK
133 cell.rcode = rcode
134 cell.ancount = nx_dns_get_u16_be(resp, 6)
135 if rcode == NXNS_RCODE_NXDOMAIN { cell.verdict = NXNS_NXDOMAIN; return NXNS_NXDOMAIN }
136 if rcode == NXNS_RCODE_SERVFAIL { cell.verdict = NXNS_SERVFAIL; return NXNS_SERVFAIL }
137 if rcode == NXNS_RCODE_REFUSED { cell.verdict = NXNS_REFUSED; return NXNS_REFUSED }
138 if rcode != 0 { cell.verdict = NXNS_RCODE_OTHER; return NXNS_RCODE_OTHER }
139 let octets: *u8 = sys_mmap(8)
140 let pv: i64 = nx_dns_parse_response_a(resp, got, tx_id, octets)
141 if pv != NX_DNS_VERDICT_OK { cell.verdict = NXNS_NOERROR_NO_A; return NXNS_NOERROR_NO_A }
142 cell.ipv4_packed = ((octets[0] & 0xff) << 24)
143 | ((octets[1] & 0xff) << 16)
144 | ((octets[2] & 0xff) << 8)
145 | (octets[3] & 0xff)
146 cell.is_rfc1918 = nxns_is_rfc1918(cell.ipv4_packed)
147 cell.verdict = NXNS_OK_A
148 return NXNS_OK_A
149}
150
151// ===== L1: single timed (server x host) probe ====================
152//
153// Builds an A query, opens a UDP socket, SO_RCVTIMEO-bounds it, sends to
154// server_ip:53, recvs (bounded), and DISSECTS the header itself (the
155// existing nx_dns_parse_response_a collapses every non-zero RCODE into
156// one verdict -- we need NXDOMAIN vs SERVFAIL vs REFUSED to detect the
157// race). Returns a filled *ProbeCell. tx_id MUST be distinct per cell
158// (caller passes a CSPRNG/derived id) so cross-cell replies can't alias.
159func nx_dns_probe_one(server_ip: i64, host: *u8, host_len: i64,
160 host_idx: i64, timeout_ms: i64, tx_id: i64) -> *ProbeCell {
161 let raw: *u8 = sys_mmap(NXNS_PROBECELL_BYTES)
162 let cell: *ProbeCell = raw as *ProbeCell
163 cell.server_ip = server_ip
164 cell.host_idx = host_idx
165 cell.verdict = NXNS_BAD_HEADER
166 cell.rcode = 0 - 1
167 cell.ancount = 0
168 cell.ipv4_packed = 0
169 cell.latency_ms = 0
170 cell.is_rfc1918 = 0
171
172 let query: *u8 = sys_mmap(512)
173 let qlen: i64 = nx_dns_build_query(host, host_len, NX_DNS_TYPE_A, tx_id, query, 512)
174 if qlen <= 0 { cell.verdict = NXNS_BAD_HEADER; return cell }
175
176 let sfd: i64 = sys_socket(AF_INET, SOCK_DGRAM, 0)
177 if sfd < 0 { cell.verdict = NXNS_SOCKET_FAIL; return cell }
178
179 // sockaddr_in: AF_INET (LE u16), port (BE u16), 4 IPv4 BE, 8 pad.
180 let sa: *u8 = sys_mmap(16)
181 sa[0] = (AF_INET & 0xff) as u8
182 sa[1] = ((AF_INET >> 8) & 0xff) as u8
183 sa[2] = ((NXNS_DNS_PORT >> 8) & 0xff) as u8
184 sa[3] = (NXNS_DNS_PORT & 0xff) as u8
185 sa[4] = ((server_ip >> 24) & 0xff) as u8
186 sa[5] = ((server_ip >> 16) & 0xff) as u8
187 sa[6] = ((server_ip >> 8) & 0xff) as u8
188 sa[7] = (server_ip & 0xff) as u8
189 var spi: i64 = 8
190 while spi < 16 { sa[spi] = 0 as u8; spi = spi + 1 }
191
192 let t0: i64 = sys_now_ms()
193 let sent: i64 = sys_sendto(sfd, query, qlen, 0, sa, 16)
194 if sent != qlen {
195 sys_close(sfd)
196 cell.verdict = NXNS_SEND_FAIL
197 cell.latency_ms = sys_now_ms() - t0
198 return cell
199 }
200
201 let resp: *u8 = sys_mmap(NXNS_MAGIC_2048)
202 let from: *u8 = sys_mmap(16)
203 let from_len: *i64 = sys_mmap(16) as *i64
204 *from_len = 16
205 let got: i64 = nx_dns_recv_timed(sfd, resp, NXNS_MAGIC_2048, timeout_ms, from, from_len)
206 sys_close(sfd)
207 cell.latency_ms = sys_now_ms() - t0
208 nxns_dissect_response(resp, got, tx_id, cell)
209 return cell
210}
211
212// ===== L1 driver: one-shot (server x host) matrix ================
213//
214// Probes every (server[i] x host[j]) cell into the caller-provided
215// `cells` buffer (n_servers*n_hosts ProbeCells, row-major host-major:
216// cell index = s*n_hosts + h). SO_RCVTIMEO-bounded so worst-case wall
217// = n_servers*n_hosts*timeout_ms; pick timeout_ms so that stays < 5s
218// (e.g. 3 servers x 2 hosts x 700ms = 4.2s). tx_id_base XORs the cell
219// index so each cell has a distinct transaction id (anti-aliasing).
220// `hosts`/`host_lens` are parallel arrays of length n_hosts.
221func nx_dns_probe_matrix(servers: *i64, n_servers: i64,
222 hosts: *i64, host_lens: *i64, n_hosts: i64,
223 timeout_ms: i64, tx_id_base: i64,
224 cells: *ProbeCell) -> i64 {
225 var s: i64 = 0
226 while s < n_servers {
227 var h: i64 = 0
228 while h < n_hosts {
229 let idx: i64 = s * n_hosts + h
230 let host_ptr: *u8 = (hosts[h]) as *u8
231 let tx: i64 = (tx_id_base + idx * 0x101 + 0x7a3) & 0xffff
232 let c: *ProbeCell = nx_dns_probe_one(servers[s], host_ptr, host_lens[h], h, timeout_ms, tx)
233 // copy the 8 fields into the caller's row-major slot
234 let dst_base: i64 = cells as i64
235 let dst: *ProbeCell = (dst_base + idx * NXNS_PROBECELL_BYTES) as *ProbeCell
236 dst.server_ip = c.server_ip
237 dst.host_idx = c.host_idx
238 dst.verdict = c.verdict
239 dst.rcode = c.rcode
240 dst.ancount = c.ancount
241 dst.ipv4_packed = c.ipv4_packed
242 dst.latency_ms = c.latency_ms
243 dst.is_rfc1918 = c.is_rfc1918
244 h = h + 1
245 }
246 s = s + 1
247 }
248 return n_servers * n_hosts
249}
250
251// ===== L1.1: true parallel poll() fan-in =========================
252//
253// struct pollfd { int fd; short events; short revents; } = 8 bytes on
254// Linux x86_64: fd @+0 (i32 LE), events @+4 (i16), revents @+6 (i16).
255// A negative fd makes poll() ignore that slot (revents forced 0) -- we
256// use -1 to mark a cell as resolved/skipped.
257func nxns_pollfd_set_fd(buf: *u8, i: i64, fd: i64) -> i64 {
258 let o: i64 = i * 8
259 buf[o] = (fd & 0xff) as u8
260 buf[o+1] = ((fd >> 8) & 0xff) as u8
261 buf[o+2] = ((fd >> 16) & 0xff) as u8
262 buf[o+3] = ((fd >> 24) & 0xff) as u8
263 return 0
264}
265func nxns_pollfd_get_fd(buf: *u8, i: i64) -> i64 {
266 let o: i64 = i * 8
267 let v: i64 = (buf[o] & 0xff) | ((buf[o+1] & 0xff) << 8)
268 | ((buf[o+2] & 0xff) << 16) | ((buf[o+3] & 0xff) << 24)
269 if (v & 0x80000000) != 0 { return v - 0x100000000 } // sign-extend (e.g. -1)
270 return v
271}
272func nxns_pollfd_set_events(buf: *u8, i: i64, ev: i64) -> i64 {
273 let o: i64 = i * 8
274 buf[o+4] = (ev & 0xff) as u8
275 buf[o+5] = ((ev >> 8) & 0xff) as u8
276 return 0
277}
278func nxns_pollfd_get_revents(buf: *u8, i: i64) -> i64 {
279 let o: i64 = i * 8
280 return (buf[o+6] & 0xff) | ((buf[o+7] & 0xff) << 8)
281}
282
283// nx_dns_probe_matrix_parallel: the EXCEED -- fire ALL (server x host)
284// queries first, then poll() fan-in over every socket with ONE shared
285// deadline. Worst-case wall = a SINGLE timeout_ms (the slowest/silent
286// cell), not the sum -- so a poisoning resolver that TIMES OUT no longer
287// serializes the whole diagnosis. Same result shape as
288// nx_dns_probe_matrix (row-major cell index = s*n_hosts + h).
289func nx_dns_probe_matrix_parallel(servers: *i64, n_servers: i64,
290 hosts: *i64, host_lens: *i64, n_hosts: i64,
291 timeout_ms: i64, tx_id_base: i64,
292 cells: *ProbeCell) -> i64 {
293 let n: i64 = n_servers * n_hosts
294 let pollfds: *u8 = sys_mmap(n * 8)
295 let txids: *i64 = sys_mmap(n * 8) as *i64
296 let cells_base: i64 = cells as i64
297 let t0: i64 = sys_now_ms()
298
299 // ---- phase 1: build + send every query, register each fd ----
300 var idx: i64 = 0
301 while idx < n {
302 let s: i64 = idx / n_hosts
303 let h: i64 = idx - s * n_hosts
304 let cp: *ProbeCell = (cells_base + idx * NXNS_PROBECELL_BYTES) as *ProbeCell
305 cp.server_ip = servers[s]
306 cp.host_idx = h
307 cp.verdict = NXNS_PENDING
308 cp.rcode = 0 - 1
309 cp.ancount = 0
310 cp.ipv4_packed = 0
311 cp.latency_ms = 0
312 cp.is_rfc1918 = 0
313 let tx: i64 = (tx_id_base + idx * 0x101 + 0x7a3) & 0xffff
314 txids[idx] = tx
315 nxns_pollfd_set_fd(pollfds, idx, 0 - 1) // default: ignore slot
316 nxns_pollfd_set_events(pollfds, idx, NXNS_POLLIN)
317 let query: *u8 = sys_mmap(512)
318 let host_ptr: *u8 = (hosts[h]) as *u8
319 let qlen: i64 = nx_dns_build_query(host_ptr, host_lens[h], NX_DNS_TYPE_A, tx, query, 512)
320 if qlen <= 0 { cp.verdict = NXNS_BAD_HEADER; idx = idx + 1; continue }
321 let fd: i64 = sys_socket(AF_INET, SOCK_DGRAM, 0)
322 if fd < 0 { cp.verdict = NXNS_SOCKET_FAIL; idx = idx + 1; continue }
323 let sa: *u8 = sys_mmap(16)
324 sa[0] = (AF_INET & 0xff) as u8
325 sa[1] = ((AF_INET >> 8) & 0xff) as u8
326 sa[2] = ((NXNS_DNS_PORT >> 8) & 0xff) as u8
327 sa[3] = (NXNS_DNS_PORT & 0xff) as u8
328 sa[4] = ((servers[s] >> 24) & 0xff) as u8
329 sa[5] = ((servers[s] >> 16) & 0xff) as u8
330 sa[6] = ((servers[s] >> 8) & 0xff) as u8
331 sa[7] = (servers[s] & 0xff) as u8
332 var spi: i64 = 8
333 while spi < 16 { sa[spi] = 0 as u8; spi = spi + 1 }
334 let sent: i64 = sys_sendto(fd, query, qlen, 0, sa, 16)
335 if sent != qlen { sys_close(fd); cp.verdict = NXNS_SEND_FAIL; idx = idx + 1; continue }
336 nxns_pollfd_set_fd(pollfds, idx, fd) // register for the fan-in
337 idx = idx + 1
338 }
339
340 // ---- phase 2: poll() fan-in until all resolved or deadline ----
341 let deadline: i64 = t0 + timeout_ms
342 var looping: i64 = 1
343 while looping == 1 {
344 var pending: i64 = 0
345 var j: i64 = 0
346 while j < n {
347 if nxns_pollfd_get_fd(pollfds, j) >= 0 { pending = pending + 1 }
348 j = j + 1
349 }
350 if pending == 0 { looping = 0; continue }
351 let remaining: i64 = deadline - sys_now_ms()
352 if remaining <= 0 { looping = 0; continue }
353 let r: i64 = sys_poll(pollfds, n, remaining)
354 if r <= 0 { looping = 0; continue } // 0=timeout, <0=error
355 var k: i64 = 0
356 while k < n {
357 let fd2: i64 = nxns_pollfd_get_fd(pollfds, k)
358 if fd2 >= 0 {
359 if nxns_pollfd_get_revents(pollfds, k) != 0 {
360 let resp: *u8 = sys_mmap(NXNS_MAGIC_2048)
361 let from: *u8 = sys_mmap(16)
362 let flen: *i64 = sys_mmap(16) as *i64
363 *flen = 16
364 let got: i64 = sys_recvfrom(fd2, resp, NXNS_MAGIC_2048, 0, from, flen)
365 let cp2: *ProbeCell = (cells_base + k * NXNS_PROBECELL_BYTES) as *ProbeCell
366 cp2.latency_ms = sys_now_ms() - t0
367 nxns_dissect_response(resp, got, txids[k], cp2)
368 sys_close(fd2)
369 nxns_pollfd_set_fd(pollfds, k, 0 - 1)
370 }
371 }
372 k = k + 1
373 }
374 }
375
376 // ---- phase 3: any still-pending cell -> TIMEOUT; close leftovers ----
377 var m: i64 = 0
378 while m < n {
379 let fd3: i64 = nxns_pollfd_get_fd(pollfds, m)
380 let cp3: *ProbeCell = (cells_base + m * NXNS_PROBECELL_BYTES) as *ProbeCell
381 if cp3.verdict == NXNS_PENDING {
382 cp3.verdict = NXNS_TIMEOUT
383 cp3.latency_ms = sys_now_ms() - t0
384 }
385 if fd3 >= 0 { sys_close(fd3) }
386 m = m + 1
387 }
388 return n
389}
390
391// ===== L1 verdict: WOULD-POISON race + hairpin detection =========
392//
393// For a given host column, the WOULD-POISON race exists when at least
394// one resolver returns a usable answer (OK_A) AND at least one OTHER
395// resolver returns a negative (NXDOMAIN/SERVFAIL/REFUSED/TIMEOUT) for
396// the SAME host. On a dual-homed Windows box, "smart multi-homed name
397// resolution" can accept the negative first and cache it machine-wide --
398// the exact intermittent failure the operator sees. Returns 1 if the
399// race exists for host column `host_col`, else 0.
400func nx_dns_host_would_poison(cells: *ProbeCell, n_servers: i64, n_hosts: i64,
401 host_col: i64) -> i64 {
402 var saw_ok: i64 = 0
403 var saw_neg: i64 = 0
404 var s: i64 = 0
405 while s < n_servers {
406 let idx: i64 = s * n_hosts + host_col
407 let base: i64 = cells as i64
408 let c: *ProbeCell = (base + idx * NXNS_PROBECELL_BYTES) as *ProbeCell
409 let v: i64 = c.verdict
410 if v == NXNS_OK_A { saw_ok = 1 }
411 if v == NXNS_NXDOMAIN { saw_neg = 1 }
412 if v == NXNS_SERVFAIL { saw_neg = 1 }
413 if v == NXNS_REFUSED { saw_neg = 1 }
414 if v == NXNS_TIMEOUT { saw_neg = 1 }
415 s = s + 1
416 }
417 if saw_ok == 1 { if saw_neg == 1 { return 1 } }
418 return 0
419}
420
421// PUBLIC-vs-RFC1918 hairpin: for an owned domain that SHOULD resolve to
422// the internal NAS (RFC1918), a resolver returning a PUBLIC IP means the
423// LAN path dies on a NAT hairpin. Returns 1 if any OK_A cell in the host
424// column resolved to a non-RFC1918 (public) address.
425func nx_dns_host_hairpin_risk(cells: *ProbeCell, n_servers: i64, n_hosts: i64,
426 host_col: i64) -> i64 {
427 var s: i64 = 0
428 while s < n_servers {
429 let idx: i64 = s * n_hosts + host_col
430 let base: i64 = cells as i64
431 let c: *ProbeCell = (base + idx * NXNS_PROBECELL_BYTES) as *ProbeCell
432 if c.verdict == NXNS_OK_A { if c.is_rfc1918 == 0 { return 1 } }
433 s = s + 1
434 }
435 return 0
436}