code wiki / _hdl_build / nx_sni_router.nx
nx_sni_router.nx source
↩ module page · 133 lines · 6026 B
1// nx_sni_router.nx -- sovereign TCP SNI-passthrough router = the :443 FRONT of the multi-tenant hosting platform.
2// Accept -> peek the cleartext ClientHello -> sni_extract_hostname -> route the RAW TCP to the matching per-
3// property backend -> splice bytes BOTH ways (replaying the peeked ClientHello first). Does NOT terminate TLS --
4// each backend daemon terminates its OWN (so mTLS lives only in the personal daemon; clients never see a prompt).
5// Fork-per-connection (bounded); the duplex splice forks one direction + runs the other. No /bin/sh, no poll dep.
6// argv[1] = listen port (default 9450); argv[2] = default backend port (default 8443 = today's sites.elf)
7// license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
10import "nx_http_server.nx"
11import "nx_sni_extract.nx"
12import "_hdl_build/nx_property_registry.nx" // data-driven routing (pr_lookup / pr_load)
13const NX_MAGIC_65536: i64 = 65536
14const NX_MAGIC_100000000: i64 = 100000000
15
16const NX_SR_PORT_DEFAULT: i64 = 9450
17const NX_SR_BACKEND_DEFAULT: i64 = 8443
18const NX_SR_PEEK: i64 = 8192
19const NX_SR_RELAY: i64 = 16384
20const NX_SR_MAX_CHILDREN: i64 = 128
21
22func sr_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 }
23
24// the data-driven PROPERTY REGISTRY (rule 11/17): one line per property "domain port tenancy group". Missing/empty
25// -> every host defaults to the fallback backend (today's sites.elf) so NOTHING breaks. Add a property = add a line.
26const NX_SR_REGISTRY: *u8 = "/volume1/homes/elderwesto/nishihost/property_registry.conf" as *u8
27
28// route an SNI host -> a backend port via the registry (NO hardcoded hosts; andelinwest/personal/businesses are
29// all just config lines now -- proven by nx_property_registry_gate 6/6).
30func sr_route(host: *u8, host_n: i64, cfg: *u8, cfg_n: i64, default_port: i64) -> i64 {
31 return pr_lookup(cfg, cfg_n, host, host_n, default_port)
32}
33
34// connect to 127.0.0.1:<port>; fd or -1.
35func sr_connect(port: i64) -> i64 {
36 let bfd: i64 = sys_socket(2, 1, 0)
37 if bfd < 0 { return 0 - 1 }
38 let addr: *u8 = sys_mmap(16)
39 addr[0]=2 as u8; addr[1]=0 as u8
40 addr[2]=((port >> 8) & 0xff) as u8; addr[3]=(port & 0xff) as u8
41 addr[4]=127 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=1 as u8
42 var z: i64 = 8; while z < 16 { addr[z]=0 as u8; z=z+1 }
43 if nx_connect_bounded(bfd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(bfd); return 0 - 1 }
44 return bfd
45}
46
47// one-directional byte pump from->to until EOF/error.
48func sr_pump(from: i64, to: i64) -> i64 {
49 let buf: *u8 = sys_mmap(NX_SR_RELAY)
50 var go: i64 = 1
51 while go == 1 {
52 let n: i64 = sys_read(from, buf, NX_SR_RELAY)
53 if n <= 0 { go = 0 } else {
54 var off: i64 = 0
55 while off < n { let w: i64 = sys_write(to, (buf+off) as *u8, n-off); if w <= 0 { off = n; go = 0 } else { off = off + w } }
56 }
57 }
58 return 0
59}
60
61// full-duplex splice: fork the client->backend direction, run backend->client; tear down when either ends.
62func sr_relay(client: i64, backend: i64) -> i64 {
63 let pid: i64 = sys_fork()
64 if pid == 0 { sr_pump(client, backend); sys_close(backend); sys_close(client); sys_exit(0) }
65 sr_pump(backend, client)
66 sys_close(client); sys_close(backend)
67 nx_kill(pid, 9)
68 let st: *i64 = (sys_mmap(16)) as *i64
69 sys_wait4(pid, st, 0)
70 return 0
71}
72
73// handle ONE accepted connection: peek the ClientHello, route, connect, replay, splice.
74func sr_handle(cfd: i64, cfg: *u8, cfg_n: i64, bport: i64) -> i64 {
75 sys_set_socket_timeout(cfd, 30)
76 let peek: *u8 = sys_mmap(NX_SR_PEEK)
77 let pn: i64 = sys_read(cfd, peek, NX_SR_PEEK)
78 if pn > 0 {
79 let host: *u8 = sys_mmap(256)
80 let hn: i64 = sni_extract_hostname(peek, pn, host, 256)
81 let dst: i64 = sr_route(host, hn, cfg, cfg_n, bport)
82 let bfd: i64 = sr_connect(dst)
83 if bfd >= 0 {
84 var off: i64 = 0
85 while off < pn { let w: i64 = sys_write(bfd, (peek+off) as *u8, pn-off); if w <= 0 { off = pn } else { off = off + w } }
86 sr_relay(cfd, bfd)
87 }
88 }
89 sys_close(cfd)
90 return 0
91}
92
93// the accept loop (factored so a gate can drive it). Returns negative on listen failure.
94func sr_serve(port: i64, bport: i64) -> i64 {
95 // load the data-driven property registry ONCE (children inherit it); missing/empty -> all hosts -> bport.
96 let cfg: *u8 = sys_mmap(NX_MAGIC_65536)
97 let cfg_n: i64 = pr_load(NX_SR_REGISTRY, cfg, NX_MAGIC_65536)
98 let addr_buf: *u8 = sys_mmap(16)
99 nx_http_server_addr_any(addr_buf, port)
100 let lv: *i64 = (sys_mmap(8)) as *i64
101 let lfd: i64 = nx_http_server_listen(addr_buf, 16, lv)
102 if lfd < 0 { sys_write(1, "SR-LISTEN-FAIL\n" as *u8, 15); return 0 - 4 }
103 sys_write(1, "nx_sni_router: SNI-passthrough up\n" as *u8, 34)
104 let sock_addr: *u8 = sys_mmap(64)
105 let sock_len: *i64 = (sys_mmap(8)) as *i64
106 let reap: *i64 = (sys_mmap(8)) as *i64
107 sys_set_socket_timeout(lfd, 5)
108 var served: i64 = 0
109 var live: i64 = 0
110 while served < NX_MAGIC_100000000 {
111 while sys_wait4(0 - 1, reap, 1) > 0 { live = live - 1 }
112 sock_len[0] = 16
113 let cfd: i64 = sys_accept_with_addr(lfd, sock_addr, sock_len)
114 if cfd < 0 { continue }
115 if live >= NX_SR_MAX_CHILDREN { if sys_wait4(0 - 1, reap, 0) > 0 { live = live - 1 } }
116 let pid: i64 = sys_fork()
117 if pid == 0 { sys_close(lfd); sr_handle(cfd, cfg, cfg_n, bport); sys_exit(0) }
118 sys_close(cfd)
119 if pid > 0 { live = live + 1 }
120 served = served + 1
121 }
122 sys_close(lfd)
123 return 0
124}
125
126func main(argc: i64, argv: *i64) -> i64 {
127 var port: i64 = NX_SR_PORT_DEFAULT
128 var bport: i64 = NX_SR_BACKEND_DEFAULT
129 if argc > 1 { port = sr_atoi(argv[1] as *u8) }
130 if argc > 2 { bport = sr_atoi(argv[2] as *u8) }
131 sr_serve(port, bport)
132 return 0
133}