code wiki / bin / nx_netdiag.nx

nx_netdiag.nx source

↩ module page · 152 lines · 5953 B

1// nx_netdiag.nx -- sovereign NishiLang network path diagnostic. 2// 3// Substrate-honesty principle (same as nx_wire_logger): any time we'd 4// reach for ping/curl/traceroute to ask "can I reach X, and how fast?", 5// we owe ourselves a native primitive. This does BOUNDED non-blocking 6// TCP-connect probes (via sys_poll) to a small table of candidate paths, 7// classifies each CONNECTED(ms) / TIMEOUT / REFUSED, and prints a 8// one-screen verdict + fix in a SINGLE run -- the 10-second / 3-step 9// answer to "why is it shit on LAN and fine on the internet?". 10// 11// No external tools. Composes only nx_syscalls (socket/connect/poll). 12// 13// license_tier: ORIGINAL 14// lineage_id: nishi_netdiag_q10 15import "nx_syscalls.nx" 16 17const ND_AF_INET: i64 = 2 18const ND_SOCK_NB: i64 = 2049 // SOCK_STREAM(1) | SOCK_NONBLOCK(0o4000=2048) 19const ND_POLLOUT: i64 = 4 20const ND_POLLERR: i64 = 8 21const ND_POLLHUP: i64 = 16 22 23const ND_TIMEOUT: i64 = 0 - 1 24const ND_REFUSED: i64 = 0 - 2 25const ND_SOCKFAIL: i64 = 0 - 3 26const ND_DEBUG: i64 = 0 27const ND_SYS_POLL: i64 = 7 // x86_64 poll(2); netdiag is x86_64-only, so pin it 28 29func nd_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != 0 { n = n + 1 } return n } 30func nd_puts(s: *u8) -> i64 { sys_write(1, s, nd_slen(s)); return 0 } 31func nd_putdec(v: i64) -> i64 { 32 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 33 var x: i64 = v 34 if x < 0 { sys_write(1, "-" as *u8, 1); x = 0 - x } 35 let buf: *u8 = sys_mmap(32) 36 var d: i64 = 0 37 var t: i64 = x 38 while t > 0 { d = d + 1; t = t / 10 } 39 var i: i64 = d - 1 40 while i >= 0 { buf[i] = (48 + (x % 10)) as u8; x = x / 10; i = i - 1 } 41 sys_write(1, buf, d) 42 return 0 43} 44 45// 16-byte sockaddr_in for a.b.c.d:port (port host-order; addr/port net-order). 46func nd_make_sockaddr(out: *u8, a: i64, b: i64, c: i64, d: i64, port: i64) -> i64 { 47 out[0] = 2 as u8 48 out[1] = 0 as u8 49 out[2] = ((port >> 8) & 0xff) as u8 50 out[3] = (port & 0xff) as u8 51 out[4] = a as u8 52 out[5] = b as u8 53 out[6] = c as u8 54 out[7] = d as u8 55 var i: i64 = 8 56 while i < 16 { out[i] = 0 as u8; i = i + 1 } 57 return 16 58} 59 60// Bounded TCP-connect probe: non-blocking connect + poll(timeout_ms). 61// Returns connect time in ms (>=0), or ND_TIMEOUT / ND_REFUSED / ND_SOCKFAIL. 62func nd_tcp_probe(a: i64, b: i64, c: i64, d: i64, port: i64, timeout_ms: i64) -> i64 { 63 let addr: *u8 = sys_mmap(16) 64 nd_make_sockaddr(addr, a, b, c, d, port) 65 let fd: i64 = sys_socket(ND_AF_INET, ND_SOCK_NB, 0) 66 if fd < 0 { return ND_SOCKFAIL } 67 let t0: i64 = sys_now_us() 68 let cr: i64 = sys_connect(fd, addr, 16) 69 var result: i64 = ND_REFUSED 70 var pr: i64 = 0 71 var rev: i64 = 0 72 if cr == 0 { 73 result = 0 74 } else { 75 let pfd: *u8 = sys_mmap(8) 76 pfd[0] = (fd & 0xff) as u8 77 pfd[1] = ((fd >> 8) & 0xff) as u8 78 pfd[2] = ((fd >> 16) & 0xff) as u8 79 pfd[3] = ((fd >> 24) & 0xff) as u8 80 pfd[4] = ND_POLLOUT as u8 81 pfd[5] = 0 as u8 82 pfd[6] = 0 as u8 83 pfd[7] = 0 as u8 84 pr = __syscall(ND_SYS_POLL, pfd, 1, timeout_ms, 0, 0, 0) 85 if pr == 0 { 86 result = ND_TIMEOUT 87 } else { 88 if pr > 0 { 89 rev = ((pfd[6] as i64) & 0xff) | (((pfd[7] as i64) & 0xff) << 8) 90 if (rev & (ND_POLLERR + ND_POLLHUP)) != 0 { 91 result = ND_REFUSED 92 } else { 93 if (rev & ND_POLLOUT) != 0 { 94 result = (sys_now_us() - t0) / 1000 95 } else { 96 result = ND_REFUSED 97 } 98 } 99 } else { 100 result = ND_REFUSED 101 } 102 } 103 } 104 if ND_DEBUG == 1 { 105 nd_puts(" [dbg fd=" as *u8); nd_putdec(fd) 106 nd_puts(" cr=" as *u8); nd_putdec(cr) 107 nd_puts(" pr=" as *u8); nd_putdec(pr) 108 nd_puts(" rev=" as *u8); nd_putdec(rev) 109 nd_puts("]" as *u8) 110 } 111 sys_close(fd) 112 return result 113} 114 115func nd_report(label: *u8, r: i64) -> i64 { 116 nd_puts(" " as *u8) 117 nd_puts(label) 118 nd_puts(": " as *u8) 119 if r == ND_TIMEOUT { nd_puts("TIMEOUT (unreachable / dead hairpin)\n" as *u8); return 0 } 120 if r == ND_REFUSED { nd_puts("REFUSED / ERROR\n" as *u8); return 0 } 121 if r == ND_SOCKFAIL { nd_puts("SOCKET-FAIL\n" as *u8); return 0 } 122 nd_puts("CONNECTED in " as *u8) 123 nd_putdec(r) 124 nd_puts(" ms\n" as *u8) 125 return 0 126} 127 128func main() -> i64 { 129 nd_puts("=== nx_netdiag: sovereign LAN path diagnostic (bits-up, no external tools) ===\n" as *u8) 130 let t_daemon: i64 = nd_tcp_probe(192, 168, 8, 227, 8443, 3000) 131 let t_lan: i64 = nd_tcp_probe(192, 168, 8, 240, 443, 3000) 132 let t_pub: i64 = nd_tcp_probe(75, 28, 18, 94, 443, 3000) 133 nd_report("daemon 192.168.8.227:8443 (direct) " as *u8, t_daemon) 134 nd_report("LAN 192.168.8.240:443 (split-horizon)" as *u8, t_lan) 135 nd_report("public 75.28.18.94:443 (NAT hairpin) " as *u8, t_pub) 136 nd_puts("\nVERDICT:\n" as *u8) 137 if t_lan >= 0 { 138 if t_pub == ND_TIMEOUT { 139 nd_puts(" LAN-direct (.240) path WORKS; public-IP NAT hairpin is DEAD.\n" as *u8) 140 nd_puts(" => A browser that resolves the domain to the PUBLIC IP (DNS-over-HTTPS\n" as *u8) 141 nd_puts(" bypasses LAN DNS) stalls on the dead hairpin, then retries == slow.\n" as *u8) 142 nd_puts(" FIX (3 steps): (1) make the client resolve the domain to 192.168.8.240\n" as *u8) 143 nd_puts(" (disable the browser's secure-DNS / use the router/Nishi resolver);\n" as *u8) 144 nd_puts(" (2) re-run nx_netdiag to confirm; (3) load the site.\n" as *u8) 145 } else { 146 nd_puts(" Both LAN-direct and public-IP paths reachable -- network OK.\n" as *u8) 147 } 148 } else { 149 nd_puts(" LAN-direct (.240) NOT reachable -- daemon down or wrong IP; check the daemon.\n" as *u8) 150 } 151 return 0 152}