code wiki / (root) / nx_netscope_verdict.nx

nx_netscope_verdict.nx source

↩ module page · 229 lines · 10321 B

1// nx_netscope_verdict.nx -- NX-NETSCOPE "easy-for-anyone" verdict layer. 2// 3// The operator's #1 directive: move tooling from esoteric/engineer-only to 4// understandable by ANYONE. Research finding (dog/doggo/q/trippy/mtr + 5// Wireshark Expert-Info + clig.dev + APNIC + NO_COLOR): every modern tool 6// stops at PRETTIER RAW DATA; none ships a pre-interpreted plain-language 7// VERDICT with the FIX attached for the home/dual-homed failure classes. 8// NX-NETSCOPE's L1 detectors already COMPUTE those verdicts -- this layer 9// is the human-readable, fix-attached shell around them (the real exceed). 10// 11// Design (grounded): Wireshark severity ladder OK/NOTE/WARN/ERROR with the 12// worst child propagated to a one-line headline; named problem CATEGORY; 13// every protocol fact translated to plain language (raw token in parens); 14// every non-OK finding ships a copy-pasteable FIX; symbol+WORD+color so 15// color is never load-bearing (ASCII canonical: [OK]/[!!]/[XX]); exit code 16// 0 all-clear / 2 problem-found (clig.dev, machine-gateable). 17// 18// Reuses nx_dns_host_would_poison / nx_dns_host_hairpin_risk and the 19// ProbeCell fields from nx_netscope_dns.nx -- presentation only, no probing. 20// 21// license_tier: ORIGINAL 22 23import "nx_netscope_dns.nx" 24 25// ---- severity ladder (Wireshark model) ---- 26const NXV_OK: i64 = 0 27const NXV_NOTE: i64 = 1 28const NXV_WARN: i64 = 2 29const NXV_ERROR: i64 = 3 30 31// ---- named problem categories ---- 32const NXV_CAT_OK: i64 = 0 33const NXV_CAT_SPLIT_DNS: i64 = 1 // resolvers disagree (the race) -> ERROR 34const NXV_CAT_RESOLVE_FAIL: i64 = 2 // no resolver has a usable answer -> ERROR 35const NXV_CAT_HAIRPIN: i64 = 3 // owned name resolves to a PUBLIC ip -> WARN 36const NXV_CAT_TIMEOUT: i64 = 4 // works, but a resolver is slow/silent -> WARN 37 38func nxv_w(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 } 39 40func nxv_dec(n: i64) -> i64 { 41 if n == 0 { sys_write(1, "0" as *u8, 1); return 0 } 42 var m: i64 = n 43 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 44 let d: *u8 = sys_mmap(24) 45 var k: i64 = 0 46 while m > 0 { d[k] = (0x30 + (m % 10)) as u8; m = m / 10; k = k + 1 } 47 var i: i64 = k - 1 48 while i >= 0 { let one: *u8 = sys_mmap(1); one[0] = d[i]; sys_write(1, one, 1); i = i - 1 } 49 return 0 50} 51 52func nxv_ip(p: i64) -> i64 { 53 nxv_dec((p >> 24) & 0xff); nxv_w("." as *u8, 1) 54 nxv_dec((p >> 16) & 0xff); nxv_w("." as *u8, 1) 55 nxv_dec((p >> 8) & 0xff); nxv_w("." as *u8, 1) 56 nxv_dec(p & 0xff) 57 return 0 58} 59 60// cell accessor by (server, host) -> *ProbeCell 61func nxv_cell(cells: *ProbeCell, n_hosts: i64, s: i64, h: i64) -> *ProbeCell { 62 let base: i64 = cells as i64 63 return (base + (s * n_hosts + h) * NXNS_PROBECELL_BYTES) as *ProbeCell 64} 65 66// ---- count helpers over a host column ---- 67// NOTE: field access on a CALL RESULT (`nxv_cell(...).verdict`) does NOT 68// read the field under the native compiler -- it yields the pointer. Bind 69// to a temp first (the idiom used everywhere else in the tree). 70func nxv_count_ok(cells: *ProbeCell, ns: i64, nh: i64, col: i64) -> i64 { 71 var c: i64 = 0 72 var s: i64 = 0 73 while s < ns { 74 let cell: *ProbeCell = nxv_cell(cells, nh, s, col) 75 if cell.verdict == NXNS_OK_A { c = c + 1 } 76 s = s + 1 77 } 78 return c 79} 80func nxv_count_timeout(cells: *ProbeCell, ns: i64, nh: i64, col: i64) -> i64 { 81 var c: i64 = 0 82 var s: i64 = 0 83 while s < ns { 84 let cell: *ProbeCell = nxv_cell(cells, nh, s, col) 85 if cell.verdict == NXNS_TIMEOUT { c = c + 1 } 86 s = s + 1 87 } 88 return c 89} 90 91// ---- the verdict logic: category + severity for a host ---- 92func nxv_host_category(cells: *ProbeCell, ns: i64, nh: i64, col: i64) -> i64 { 93 if nx_dns_host_would_poison(cells, ns, nh, col) == 1 { return NXV_CAT_SPLIT_DNS } 94 if nxv_count_ok(cells, ns, nh, col) == 0 { return NXV_CAT_RESOLVE_FAIL } 95 if nx_dns_host_hairpin_risk(cells, ns, nh, col) == 1 { return NXV_CAT_HAIRPIN } 96 // NOTE: a lone resolver TIMEOUT alongside a good answer is already a 97 // SPLIT_DNS race (would_poison counts timeout as a race contributor in 98 // the dual-homed setting), so it is reported there -- a standalone 99 // TIMEOUT category is unreachable when any resolver answers. Kept as a 100 // const + render branch for the future "all-agree-but-slow" WARN tier. 101 return NXV_CAT_OK 102} 103func nxv_cat_severity(cat: i64) -> i64 { 104 if cat == NXV_CAT_SPLIT_DNS { return NXV_ERROR } 105 if cat == NXV_CAT_RESOLVE_FAIL { return NXV_ERROR } 106 if cat == NXV_CAT_HAIRPIN { return NXV_WARN } 107 if cat == NXV_CAT_TIMEOUT { return NXV_WARN } 108 return NXV_OK 109} 110 111// ---- plain-language renderers (ASCII canonical: color is never load-bearing) ---- 112func nxv_sym(sev: i64) -> i64 { 113 if sev == NXV_ERROR { nxv_w("[XX]" as *u8, 4); return 0 } 114 if sev == NXV_WARN { nxv_w("[!!]" as *u8, 4); return 0 } 115 nxv_w("[OK]" as *u8, 4) 116 return 0 117} 118func nxv_cat_name(cat: i64) -> i64 { 119 if cat == NXV_CAT_SPLIT_DNS { nxv_w("SPLIT-DNS RACE" as *u8, 14); return 0 } 120 if cat == NXV_CAT_RESOLVE_FAIL { nxv_w("CANNOT RESOLVE" as *u8, 14); return 0 } 121 if cat == NXV_CAT_HAIRPIN { nxv_w("PUBLIC-IP HAIRPIN" as *u8, 17); return 0 } 122 if cat == NXV_CAT_TIMEOUT { nxv_w("RESOLVER TIMEOUT" as *u8, 16); return 0 } 123 nxv_w("OK" as *u8, 2) 124 return 0 125} 126// translate a cell verdict to plain language (raw token in parens for experts). 127func nxv_answer(c: *ProbeCell) -> i64 { 128 let v: i64 = c.verdict 129 if v == NXNS_OK_A { 130 nxv_ip(c.ipv4_packed) 131 if c.is_rfc1918 == 1 { nxv_w(" (on your LAN)" as *u8, 15) } else { nxv_w(" (public)" as *u8, 10) } 132 return 0 133 } 134 if v == NXNS_NXDOMAIN { nxv_w("does not exist (NXDOMAIN)" as *u8, 26); return 0 } 135 if v == NXNS_SERVFAIL { nxv_w("server failed (SERVFAIL)" as *u8, 26); return 0 } 136 if v == NXNS_REFUSED { nxv_w("refused query (REFUSED)" as *u8, 25); return 0 } 137 if v == NXNS_TIMEOUT { nxv_w("no reply (timeout)" as *u8, 25); return 0 } 138 nxv_w("unexpected reply" as *u8, 16) 139 return 0 140} 141func nxv_cell_mark(c: *ProbeCell) -> i64 { 142 if c.verdict == NXNS_OK_A { if c.is_rfc1918 == 1 { nxv_w("[OK] correct" as *u8, 12); return 0 } nxv_w("[!!] public path" as *u8, 16); return 0 } 143 nxv_w("[XX] no good answer" as *u8, 19) 144 return 0 145} 146 147// ---- the FIX block per category (every problem ships a copy-pasteable fix) ---- 148func nxv_fix(cat: i64) -> i64 { 149 if cat == NXV_CAT_SPLIT_DNS { 150 nxv_w("HOW TO FIX (do these in order)\n", 32) 151 nxv_w(" 1. Make your home router answer first: lower the Ethernet NIC metric\n", 70) 152 nxv_w(" below Wi-Fi, and turn off Windows parallel DNS:\n", 52) 153 nxv_w(" (DisableSmartNameResolution = 1)\n", 40) 154 nxv_w(" 2. Clear the wrong cached answer now: ipconfig /flushdns\n", 60) 155 nxv_w(" 3. On the router, point BOTH owned names at the LAN address,\n", 63) 156 nxv_w(" and forward the other box upstream to it.\n", 47) 157 return 0 158 } 159 if cat == NXV_CAT_RESOLVE_FAIL { 160 nxv_w("HOW TO FIX\n", 11) 161 nxv_w(" No reachable resolver has this name. Check the name is correct and\n", 68) 162 nxv_w(" that at least one DNS server is reachable, then re-test.\n", 58) 163 return 0 164 } 165 if cat == NXV_CAT_HAIRPIN { 166 nxv_w("HOW TO FIX\n", 11) 167 nxv_w(" This name points at its PUBLIC address, so LAN traffic must hairpin\n", 69) 168 nxv_w(" through your gateway (often slow/broken). Add a local (LAN) record\n", 68) 169 nxv_w(" on your router so it resolves to the internal address on-LAN.\n", 63) 170 return 0 171 } 172 if cat == NXV_CAT_TIMEOUT { 173 nxv_w("HOW TO FIX\n", 11) 174 nxv_w(" One resolver is slow/silent; lookups can stall. Remove the slow\n", 65) 175 nxv_w(" secondary resolver, or lower its priority, then re-test.\n", 58) 176 return 0 177 } 178 return 0 179} 180 181// nx_netscope_verdict_host: render ONE host's verdict-first report. 182// servers[] = the probed resolver IPs (for the table). Returns severity. 183func nx_netscope_verdict_host(cells: *ProbeCell, ns: i64, nh: i64, col: i64, 184 host_name: *u8, host_len: i64, servers: *i64) -> i64 { 185 let cat: i64 = nxv_host_category(cells, ns, nh, col) 186 let sev: i64 = nxv_cat_severity(cat) 187 188 nxv_w("NX-NETSCOPE . DNS DIAGNOSIS ", 30); sys_write(1, host_name, host_len); nxv_w("\n", 1) 189 nxv_w("----------------------------------------------------------------\n", 65) 190 nxv_w(" ", 2); nxv_sym(sev); nxv_w(" ", 2) 191 if sev == NXV_OK { nxv_w("ALL CLEAR -- this name resolves correctly.\n", 43) } 192 if sev == NXV_WARN { nxv_w("WORKS, BUT DEGRADED -- see below.\n", 34) } 193 if sev == NXV_ERROR { nxv_w("PROBLEM FOUND -- this name fails on your network.\n", 50) } 194 nxv_w(" Category: ", 15); nxv_cat_name(cat); nxv_w("\n\n", 2) 195 196 // resolver table (plain answers) 197 nxv_w(" Resolver Answer for this name Verdict\n", 61) 198 nxv_w(" --------------------------------------------------------------\n", 66) 199 var s: i64 = 0 200 while s < ns { 201 let c: *ProbeCell = nxv_cell(cells, nh, s, col) 202 nxv_w(" ", 2); nxv_ip(servers[s]) 203 if (servers[s] >> 24 & 0xff) == 192 { nxv_w(" (local) ", 9) } else { nxv_w(" (public)", 9) } 204 nxv_w(" ", 2); nxv_answer(c); nxv_w(" ", 2); nxv_cell_mark(c); nxv_w("\n", 1) 205 s = s + 1 206 } 207 nxv_w("\n", 1) 208 if sev != NXV_OK { nxv_fix(cat) } 209 if sev == NXV_OK { 210 nxv_w(" Checks: all resolvers agree . answer on your LAN . fast reply\n", 63) 211 } 212 nxv_w("----------------------------------------------------------------\n", 65) 213 return sev 214} 215 216// nx_netscope_verdict_all: render every host, return the process exit code 217// (0 = all clear/degraded-but-works, 2 = at least one ERROR). clig.dev. 218func nx_netscope_verdict_all(cells: *ProbeCell, ns: i64, nh: i64, 219 hosts: *i64, host_lens: *i64, servers: *i64) -> i64 { 220 var worst: i64 = NXV_OK 221 var h: i64 = 0 222 while h < nh { 223 let sev: i64 = nx_netscope_verdict_host(cells, ns, nh, h, (hosts[h]) as *u8, host_lens[h], servers) 224 if sev > worst { worst = sev } 225 h = h + 1 226 } 227 if worst == NXV_ERROR { return 2 } 228 return 0 229}