code wiki / (root) / nx_netscope_dns.nx

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}