code wiki / _hdl_build / nx_aw_sni_router.nx

nx_aw_sni_router.nx source

↩ module page · 268 lines · 12556 B

1// nx_aw_sni_router.nx -- SOVEREIGN SNI router for .240:443 (TLS PASSTHROUGH, no termination). Peeks the 2// ClientHello's SNI, picks a backend by domain (apex nishifamily.com -> :8443 sovereign sites daemon; 3// EVERYTHING else -> local Synology nginx :443 fallback), connects, REPLAYS the ClientHello, splices the raw 4// TLS stream. Each backend completes its own TLS with the shared LE cert (SAN covers both domains), so the 5// router never holds a key. Fork-per-connection, bounded + reaped; splice via a second fork (one process per 6// direction) with socket timeouts so no connection can hang a slot. DNAT .240:443 -> NX_RT_PORT. 7// argv[1] (optional) = listen port (default 7443). license_tier: ORIGINAL (spine from nx_aw_tlsproxy) 8import "nx_syscalls.nx" 9import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 10import "nx_sni_route.nx" 11const NX_MAGIC_1000000000: i64 = 1000000000 12 13const NX_RT_PORT: i64 = 7443 14const NX_RT_MAX: i64 = 64 15const NX_RT_BUF: i64 = 16384 16 17func rt_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v } 18func rt_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19 20// connect to 127.0.0.1:<port>; return fd or -1 21func rt_connect(port: i64) -> i64 { 22 let bfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0) 23 if bfd < 0 { return 0 - 1 } 24 let a: *u8 = sys_mmap(16) 25 a[0]=2 as u8; a[1]=0 as u8 26 a[2]=((port >> 8) & 0xff) as u8; a[3]=(port & 0xff) as u8 27 a[4]=127 as u8; a[5]=0 as u8; a[6]=0 as u8; a[7]=1 as u8 28 var z: i64 = 8; while z < 16 { a[z]=0 as u8; z=z+1 } 29 if nx_connect_bounded(bfd, a, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(bfd); return 0 - 1 } 30 return bfd 31} 32 33// ---- DATA-DRIVEN backend routing from property_registry.conf (R1b 2026-07-15) -------------------- 34// The registry's own header calls itself "data-driven routing for nx_sni_router". Now we honour it: 35// any domain listed there (col1=domain col2=port) routes to its sovereign backend; unlisted -> sni_backend 36// (nishifamily hardcode + else->nginx). Add a domain = a registry line + reload; NEVER a router recompile. 37const NX_RT_REGISTRY: *u8 = "/volume1/homes/elderwesto/nishihost/property_registry.conf" as *u8 38const NX_RT_REG_MAX: i64 = 64 39 40func rt_field(buf: *u8, pos: i64, end: i64, out: *u8, cap: i64) -> i64 { 41 var p: i64 = pos 42 var skip: i64 = 1 43 while skip == 1 { 44 if p >= end { skip = 0 } 45 else { let c: i64 = buf[p] as i64; if c == 32 { p = p + 1 } else { if c == 9 { p = p + 1 } else { skip = 0 } } } 46 } 47 if p >= end { return 0 - 1 } 48 let c0: i64 = buf[p] as i64 49 if c0 == 10 { return 0 - 1 } 50 if c0 == 13 { return 0 - 1 } 51 if c0 == 35 { return 0 - 1 } 52 var o: i64 = 0 53 var cp: i64 = 1 54 while cp == 1 { 55 if p >= end { cp = 0 } 56 else { let c: i64 = buf[p] as i64 57 if c == 32 { cp = 0 } else { if c == 9 { cp = 0 } else { if c == 10 { cp = 0 } else { if c == 13 { cp = 0 } 58 else { if o < cap - 1 { out[o] = buf[p] as u8; o = o + 1 } p = p + 1 } } } } } 59 } 60 out[o] = 0 as u8 61 return p 62} 63 64func rt_next_line(buf: *u8, pos: i64, end: i64) -> i64 { 65 var p: i64 = pos 66 var go: i64 = 1 67 while go == 1 { if p >= end { go = 0 } else { if (buf[p] as i64) == 10 { p = p + 1; go = 0 } else { p = p + 1 } } } 68 return p 69} 70 71// case-insensitive label-aware match: host == dom OR host ends with ".dom" (whole registrable domain). 72func rt_label_match(host: *u8, hn: i64, dom: *u8, dom_n: i64) -> i64 { 73 if dom_n <= 0 { return 0 } 74 if hn < dom_n { return 0 } 75 let start: i64 = hn - dom_n 76 var i: i64 = 0 77 while i < dom_n { 78 var a: i64 = host[start + i] as i64 79 var b: i64 = dom[i] as i64 80 if a >= 65 { if a <= 90 { a = a + 32 } } 81 if b >= 65 { if b <= 90 { b = b + 32 } } 82 if a != b { return 0 } 83 i = i + 1 84 } 85 if start == 0 { return 1 } 86 if (host[start - 1] as i64) == 46 { return 1 } 87 return 0 88} 89 90// parse property_registry.conf -> parallel domain/port arrays; returns count. 91func rt_load_registry(buf: *u8, n: i64, doms: *i64, dlens: *i64, ports: *i64, max: i64) -> i64 { 92 var count: i64 = 0 93 var pos: i64 = 0 94 while pos < n { 95 if count >= max { pos = n } 96 else { 97 let db: *u8 = sys_mmap(256) 98 let p1: i64 = rt_field(buf, pos, n, db, 256) 99 if p1 < 0 { pos = rt_next_line(buf, pos, n) } 100 else { 101 let pb: *u8 = sys_mmap(32) 102 let p2: i64 = rt_field(buf, p1, n, pb, 32) 103 if p2 < 0 { pos = rt_next_line(buf, pos, n) } 104 else { 105 var dl: i64 = 0 106 while db[dl] != (0 as u8) { dl = dl + 1 } 107 doms[count] = db as i64; dlens[count] = dl; ports[count] = rt_atoi(pb) 108 count = count + 1; pos = rt_next_line(buf, pos, n) 109 } 110 } 111 } 112 } 113 return count 114} 115 116// look up the SNI host in the registry table; return its backend port, or -1 if unlisted. 117func rt_lookup(host: *u8, hn: i64, doms: *i64, dlens: *i64, ports: *i64, count: i64) -> i64 { 118 if hn <= 0 { return 0 - 1 } 119 var i: i64 = 0 120 while i < count { 121 if rt_label_match(host, hn, doms[i] as *u8, dlens[i]) == 1 { return ports[i] } 122 i = i + 1 123 } 124 return 0 - 1 125} 126 127// (mtime,size) signature for HOT-RELOAD change detection (stat mtime byte 88 = i64[11], size byte 48 128// = i64[6]; same convention as nx_torrent_up). 0 ok / -1 stat-fail. 129func rt_statsig(path: *u8, statb: *u8, out2: *i64) -> i64 { 130 if sys_fstatat(path, statb) != 0 { out2[0] = 0 - 1; out2[1] = 0 - 1; return 0 - 1 } 131 let sb: *i64 = statb as *i64 132 out2[0] = sb[11] 133 out2[1] = sb[6] 134 return 0 135} 136 137func main(argc: i64, argv: *i64) -> i64 { 138 var port: i64 = NX_RT_PORT 139 if argc > 1 { port = rt_atoi(argv[1] as *u8) } 140 141 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0) 142 if lfd < 0 { rt_puts("RT-SOCKET-FAIL\n" as *u8); return 2 } 143 let one: *u8 = sys_mmap(4); one[0]=1 as u8; one[1]=0 as u8; one[2]=0 as u8; one[3]=0 as u8 144 sys_setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, one, 4) 145 let a: *u8 = sys_mmap(16) 146 a[0]=2 as u8; a[1]=0 as u8 147 a[2]=((port >> 8) & 0xff) as u8; a[3]=(port & 0xff) as u8 148 a[4]=0 as u8; a[5]=0 as u8; a[6]=0 as u8; a[7]=0 as u8 149 var z: i64 = 8; while z < 16 { a[z]=0 as u8; z=z+1 } 150 if sys_bind(lfd, a, 16) < 0 { rt_puts("RT-BIND-FAIL\n" as *u8); return 3 } 151 if sys_listen(lfd, 64) < 0 { rt_puts("RT-LISTEN-FAIL\n" as *u8); return 4 } 152 // ZOMBIE-REAP: accept-timeout so the wait4(WNOHANG) at the loop top reaps idle splice/conn children 153 // promptly (every 5s) instead of only when the next connection lands. cfd<0 already -> continue. 154 sys_set_socket_timeout(lfd, 5) 155 // DATA-DRIVEN: load property_registry (domain->backend). Unlisted hosts fall to sni_backend/nginx. 156 let reg_box: *i64 = sys_mmap(8) as *i64; reg_box[0] = 0 157 let reg_buf: *u8 = sys_read_file(NX_RT_REGISTRY, reg_box) 158 let reg_doms: *i64 = (sys_mmap(NX_RT_REG_MAX * 8)) as *i64 159 let reg_dlens: *i64 = (sys_mmap(NX_RT_REG_MAX * 8)) as *i64 160 let reg_ports: *i64 = (sys_mmap(NX_RT_REG_MAX * 8)) as *i64 161 var reg_n: i64 = 0 162 if (reg_buf as i64) != 0 { reg_n = rt_load_registry(reg_buf, reg_box[0], reg_doms, reg_dlens, reg_ports, NX_RT_REG_MAX) } 163 // HOT-RELOAD state: re-read the registry on (mtime,size) change, checked per accepted connection. 164 // Loads into TEMP arrays; copies over only on a non-empty parse (a broken file never wipes routing). 165 let hr_statb: *u8 = sys_mmap(160) 166 let hr_sig: *i64 = (sys_mmap(16)) as *i64 167 var reg_mt: i64 = 0 - 1 168 var reg_sz: i64 = 0 - 1 169 if rt_statsig(NX_RT_REGISTRY, hr_statb, hr_sig) == 0 { reg_mt = hr_sig[0]; reg_sz = hr_sig[1] } 170 let tmp_doms: *i64 = (sys_mmap(NX_RT_REG_MAX * 8)) as *i64 171 let tmp_dlens: *i64 = (sys_mmap(NX_RT_REG_MAX * 8)) as *i64 172 let tmp_ports: *i64 = (sys_mmap(NX_RT_REG_MAX * 8)) as *i64 173 rt_puts("nx_aw_sni_router: :443 passthrough up -> DATA-DRIVEN (property_registry) sovereign backends / else->nginx:443\n" as *u8) 174 175 let reap: *i64 = sys_mmap(8) as *i64 176 let sa: *u8 = sys_mmap(64) 177 let sl: *i64 = sys_mmap(8) as *i64 178 var live: i64 = 0 179 var served: i64 = 0 180 while served < NX_MAGIC_1000000000 { 181 while sys_wait4(0 - 1, reap, 1) > 0 { live = live - 1 } 182 sl[0] = 16 183 let cfd: i64 = sys_accept_with_addr(lfd, sa, sl) 184 if cfd < 0 { continue } 185 // hot-reload check (parent, pre-fork): this very connection routes with fresh registry rows. 186 if rt_statsig(NX_RT_REGISTRY, hr_statb, hr_sig) == 0 { 187 if hr_sig[0] != reg_mt || hr_sig[1] != reg_sz { 188 reg_mt = hr_sig[0] 189 reg_sz = hr_sig[1] 190 let rb2: *i64 = (sys_mmap(8)) as *i64 191 rb2[0] = 0 192 let rbuf2: *u8 = sys_read_file(NX_RT_REGISTRY, rb2) 193 if (rbuf2 as i64) != 0 { 194 let rn2: i64 = rt_load_registry(rbuf2, rb2[0], tmp_doms, tmp_dlens, tmp_ports, NX_RT_REG_MAX) 195 if rn2 > 0 { 196 var ri2: i64 = 0 197 while ri2 < rn2 { 198 reg_doms[ri2] = tmp_doms[ri2] 199 reg_dlens[ri2] = tmp_dlens[ri2] 200 reg_ports[ri2] = tmp_ports[ri2] 201 ri2 = ri2 + 1 202 } 203 reg_n = rn2 204 rt_puts("hot-reload: registry\n" as *u8) 205 } 206 } 207 } 208 } 209 if live >= NX_RT_MAX { if sys_wait4(0 - 1, reap, 0) > 0 { live = live - 1 } } 210 let pid: i64 = sys_fork() 211 if pid == 0 { 212 sys_close(lfd) 213 sys_set_socket_timeout(cfd, 20) 214 let hbuf: *u8 = sys_mmap(NX_RT_BUF) 215 // read until the FULL ClientHello TLS record is buffered (large/fragmented ClientHellos span 216 // TCP segments; a single read can miss the SNI -> wrong backend). Bounded by NX_RT_BUF, 64 217 // iterations, and the 20s socket timeout. We then replay all hn bytes + splice the rest. 218 var hn: i64 = 0 219 var rloop: i64 = 0 220 while rloop < 64 { 221 if hn >= 5 { 222 let rl: i64 = ((hbuf[3] as i64) << 8) | (hbuf[4] as i64) // TLS record length (bytes 3-4) 223 if hn >= 5 + rl { break } // full ClientHello buffered 224 } 225 if hn >= NX_RT_BUF { break } 226 let rr: i64 = sys_read(cfd, (hbuf as i64 + hn) as *u8, NX_RT_BUF - hn) 227 if rr <= 0 { break } 228 hn = hn + rr 229 rloop = rloop + 1 230 } 231 if hn > 0 { 232 let host: *u8 = sys_mmap(256) 233 let hl: i64 = sni_extract(hbuf, hn, host, 255) 234 var bport: i64 = rt_lookup(host, hl, reg_doms, reg_dlens, reg_ports, reg_n) 235 if bport < 0 { bport = sni_backend(host, hl) } 236 let bfd: i64 = rt_connect(bport) 237 if bfd >= 0 { 238 sys_set_socket_timeout(bfd, 20) 239 sys_write(bfd, hbuf, hn) // replay the ClientHello (incl any 0-RTT) 240 let g: i64 = sys_fork() 241 if g == 0 { 242 // client -> backend 243 let b2: *u8 = sys_mmap(NX_RT_BUF) 244 var run: i64 = 1 245 while run == 1 { let r: i64 = sys_read(cfd, b2, NX_RT_BUF); if r <= 0 { run = 0 } else { sys_write(bfd, b2, r) } } 246 sys_shutdown(bfd, 1) 247 sys_exit(0) 248 } 249 // backend -> client 250 let b1: *u8 = sys_mmap(NX_RT_BUF) 251 var run2: i64 = 1 252 while run2 == 1 { let r: i64 = sys_read(bfd, b1, NX_RT_BUF); if r <= 0 { run2 = 0 } else { sys_write(cfd, b1, r) } } 253 sys_shutdown(cfd, 1) 254 let gst: *i64 = sys_mmap(8) as *i64 255 sys_wait4(g, gst, 0) 256 sys_close(bfd) 257 } 258 } 259 sys_close(cfd) 260 sys_exit(0) 261 } 262 sys_close(cfd) 263 if pid > 0 { live = live + 1 } 264 served = served + 1 265 } 266 sys_close(lfd) 267 return 0 268}