code wiki / _hdl_build / nx_proxy_route.nx
nx_proxy_route.nx
buildroot/runtime/_hdl_build/nx_proxy_route.nx
about
nx_proxy_route.nx -- SOVEREIGN data-driven PROXY route table (pure Nishi, no TLS, no crypto).
The DYNAMIC-routing analog of nx_host_router's static docroot table: a config file maps
(host, path-prefix) -> (backend_port, mode) so an app/site route is a CONFIG ROW, not a
recompiled `sd2_contains(" /gallery")` branch in the TLS daemon. This closes the nx_host_router
gap for DYNAMIC routes -> "migrate a bunch of sites" becomes add-a-row, zero recompile.
SOTA basis: reverse-proxy / API-gateway route tables reloadable without rebuild
(knowledge/fetched/plat_reverse_proxy.raw, plat_api_mgmt.raw); see
knowledge/research/2026-06-29-platform-sclass-exceed-standard.md. license_tier: ORIGINAL
Config line: "<host|*> <path-prefix> <backend_port> <mode>" (hash = comment, blank lines ok)
host exact (case-insensitive) or '*' wildcard; exact host wins over '*'
path-prefix request-line path prefix, boundary-safe (/gen matches /gen,/gen/,/gen?x NOT /generated)
backend_port loopback 127.0.0.1:<port> to reverse-proxy to
mode buffered | stream | gated -- THE WORDS ARE THE VOCABULARY. The integers below are
the internal encoding handed to the daemon and are NOT accepted conf spellings.
Match rule: among all rows whose host+prefix match, LONGEST prefix wins; tie -> exact host beats '*'.
FAIL-CLOSED MODE PARSE -- 2026-08-21, /compare/trafficsafety ITEM 1.
pr_mode used to `return PR_MODE_BUFFERED` for ANY token it did not recognise, so a mistyped
`gated` silently became the PERMISSIVE mode with no diagnostic, in the table that fronts every
request. `gated` is the FAIL-CLOSED variant (302 /login when the backend is down); `buffered`
is the permissive one -- so the old default was wrong in the dangerous direction by construction.
NOW: the three documented words resolve to themselves, an OMITTED token keeps the documented
default (buffered), and ANY other token resolves to PR_MODE_GATED -- fail closed.
WHY NOT A HARD REFUSAL AT REQUEST TIME, decided from the call sites and not from taste.
pr_lookup has exactly one production caller (nx_sites_daemon_v2) and its 0 return does not mean
"refuse", it means "no route" -- the daemon then falls through to the legacy static cascade,
which is precisely the path that answered a machine client with the HOMEPAGE as a 200 and
triggered a retry storm (seq1294, both edge outages). Refusing a row at request time would
therefore make the failure LESS safe, not more, and would brick every route in a conf holding
one typo. The refusal belongs at LOAD time: pr_conf_scan enumerates every offending row with
its line number so the loader ANNOUNCES a worklist once per (re)load, instead of emitting one
wrong answer per request forever.
dependencies 1 imports · 3 importers
imports: nx_syscalls.nx
imported by: nx_docportal_admin_daemon.nxnx_proxy_route_gate.nxnx_sites_daemon_v2.nx
structs
| none |
consts
| 37 | const PR_MODE_BUFFERED: i64 = 0 |
| 38 | const PR_MODE_STREAM: i64 = 1 |
| 39 | const PR_MODE_GATED: i64 = 2 |
| 44 | const PR_ASCII_TAB: i64 = 9 |
| 45 | const PR_ASCII_LF: i64 = 10 |
| 46 | const PR_ASCII_CR: i64 = 13 |
| 47 | const PR_ASCII_SP: i64 = 32 |
| 48 | const PR_ASCII_HASH: i64 = 35 |
| 49 | const PR_ASCII_STAR: i64 = 42 |
| 51 | const PR_ASCII_ZERO: i64 = 48 |
| 52 | const PR_DEC_BASE: i64 = 10 |
| 55 | const PR_ROW_TOKS: i64 = 5 // host prefix port mode [timeout=<s>] -- the 5th is OPTIONAL (2026-09-02, search L2a) |
| 56 | const PR_TOK_HOST: i64 = 0 |
| 57 | const PR_TOK_PREFIX: i64 = 1 |
| 58 | const PR_TOK_PORT: i64 = 2 |
| 59 | const PR_TOK_MODE: i64 = 3 |
| 60 | const PR_TOK_TMO: i64 = 4 |
| 62 | const PR_TOK_OFF_BASE: i64 = 0 |
| 63 | const PR_TOK_LEN_BASE: i64 = 5 // == PR_ROW_TOKS: the len array sits past the off array |
| 64 | const PR_I64_BYTES: i64 = 8 |
| 65 | const PR_LINE_ORIGIN: i64 = 1 |
| 118 | const PR_TMO_KEY: *u8 = "timeout=" as *u8 |
| 119 | const PR_TMO_KEYLEN: i64 = 8 |
| 327 | const PR_ANN_LINES_CAP: i64 = 16 |
| 328 | const PR_ANN_LINES_BYTES: i64 = 128 |
| 329 | const PR_ANN_BOX_BYTES: i64 = 8 |
| 330 | const PR_ANN_NUM_BYTES: i64 = 24 |
functions
| 67 | func pr_lower(c: u8) -> u8 |
| 73 | func pr_ieq(a: *u8, b: *u8, n: i64) -> i64 |
| 79 | func pr_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } called by 1: pr_word |
| 82 | func pr_word(s: *u8, n: i64, lit: *u8) -> i64 |
| 90 | func pr_parse_u(s: *u8, n: i64) -> i64 |
| 106 | func pr_mode_known(s: *u8, n: i64) -> i64 |
| 120 | func pr_tmo_prefixed(s: *u8, n: i64) -> i64 |
| 126 | func pr_tmo_known(s: *u8, n: i64) -> i64 |
| 133 | func pr_parse_tmo(s: *u8, n: i64) -> i64 |
| 138 | func pr_mode(s: *u8, n: i64) -> i64 |
| 148 | func pr_path_pref(path: *u8, pn: i64, pref: *u8, prefn: i64) -> i64 called by 1: pr_lookup_tmo |
| 168 | func pr_row_toks(cfg: *u8, cfgn: i64, start: i64, tok: *i64) -> i64 |
| 217 | func pr_lookup(cfg: *u8, cfgn: i64, host: *u8, hn: i64, path: *u8, pn: i64, out_port: *i64, out_mode: *i64) -> i64 |
| 222 | func pr_lookup_tmo(cfg: *u8, cfgn: i64, host: *u8, hn: i64, path: *u8, pn: i64, out_port: *i64, out_mode: *i64, out_tmo: *i64) -> i64 called by 5: dad_hb_msdad_early_ok_tabpr_lookupmainmain calls 7: sys_mmappr_row_tokspr_ieqpr_path_prefpr_parse_upr_mode+1 |
| 283 | func pr_conf_scan(cfg: *u8, cfgn: i64, out_lines: *i64, cap: i64, out_listed: *i64, out_rows: *i64) -> i64 |
| 332 | func pr_ann_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 333 | func pr_ann_num(v: i64) -> i64 |
| 344 | func pr_conf_announce(cfg: *u8, cfgn: i64) -> i64 |