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}