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}