code wiki / (root) / nx_sni_route.nx

nx_sni_route.nx source

↩ module page · 106 lines · 5247 B

1// nx_sni_route.nx -- SOVEREIGN TLS SNI extraction + backend routing decision (PURE: no syscalls, no imports, 2// so it is unit-gateable offline against hand-crafted ClientHellos). Used by nx_aw_sni_router (the :443 3// passthrough front) to pick a backend by domain WITHOUT terminating TLS. Defensive byte parsing of the 4// ClientHello (untrusted network input): every offset is bounds-checked; any malformation -> default backend. 5// license_tier: ORIGINAL 6 7const NX_BK_NISHI: i64 = 8443 // sites daemon (nishifamily games + andelinwest static + /search) 8const NX_BK_NGINX: i64 = 443 // Synology nginx fallback (keeps ALL other vhosts + DSM exactly as-is) 9 10func sni_be16(b: *u8, i: i64) -> i64 { return ((b[i] as i64) << 8) | (b[i + 1] as i64) } 11 12// Extract the SNI host_name from a TLS ClientHello held in b[0..n). Lowercased into out (cap outcap). 13// Returns the host length, or 0 if absent/malformed. 14func sni_extract(b: *u8, n: i64, out: *u8, outcap: i64) -> i64 { 15 if n < 44 { return 0 } 16 if (b[0] as i64) != 22 { return 0 } // TLS record type 0x16 = handshake 17 if (b[5] as i64) != 1 { return 0 } // handshake type 0x01 = ClientHello 18 // fixed prefix: 5 (record hdr) + 4 (hs hdr) + 2 (client_version) + 32 (random) = 43; b[43] = session_id_len 19 var pos: i64 = 43 20 let sid: i64 = b[pos] as i64; pos = pos + 1 + sid 21 if pos + 2 > n { return 0 } 22 let cs: i64 = sni_be16(b, pos); pos = pos + 2 + cs // cipher_suites 23 if pos + 1 > n { return 0 } 24 let cp: i64 = b[pos] as i64; pos = pos + 1 + cp // compression_methods 25 if pos + 2 > n { return 0 } 26 let extlen: i64 = sni_be16(b, pos); pos = pos + 2 27 var ee: i64 = pos + extlen 28 if ee > n { ee = n } 29 while pos + 4 <= ee { 30 let et: i64 = sni_be16(b, pos) 31 let el: i64 = sni_be16(b, pos + 2) 32 let ed: i64 = pos + 4 33 if et == 0 { // server_name extension 34 // ed: server_name_list_len(2), entry_type(1)=0, name_len(2), name 35 if ed + 5 <= n { 36 let nl: i64 = sni_be16(b, ed + 3) 37 if ed + 5 + nl <= n { 38 var i: i64 = 0 39 while i < nl { 40 if i < outcap { 41 var c: i64 = b[ed + 5 + i] as i64 42 if c >= 65 { if c <= 90 { c = c + 32 } } // lowercase A-Z 43 out[i] = c as u8 44 } 45 i = i + 1 46 } 47 return nl 48 } 49 } 50 return 0 51 } 52 pos = ed + el 53 } 54 return 0 55} 56 57func sni_contains(h: *u8, hn: i64, ndl: *u8, nn: i64) -> i64 { 58 if nn == 0 { return 0 } 59 var i: i64 = 0 60 while i + nn <= hn { 61 var j: i64 = 0 62 while j < nn { if h[i + j] != ndl[j] { break } j = j + 1 } 63 if j == nn { return 1 } 64 i = i + 1 65 } 66 return 0 67} 68 69// exact host match (host already lowercased by sni_extract) -- NO substring, so www.nishifamily.com 70// does NOT match nishifamily.com (their certs differ: the LE cert has no www SAN). 71func sni_eq(h: *u8, hn: i64, lit: *u8, ln: i64) -> i64 { 72 if hn != ln { return 0 } 73 var i: i64 = 0 74 while i < ln { if h[i] != lit[i] { return 0 } i = i + 1 } 75 return 1 76} 77 78// LABEL-AWARE registrable-domain match (the SAME rule nx_sni_cert_select uses): host matches `dom` iff 79// host == dom (the apex) OR host ends with ".dom" (a real sub-label). So EVERY subdomain of a domain we 80// own (admin./mail./www./anything.andelinwest.com) rides the sovereign stack -- no per-subdomain rule, 81// no whack-a-mole -- while a look-alike (evilandelinwest.com) does NOT match (the byte before the suffix 82// must be '.'). This is why the apex "worked forever" (explicit rule) but new subdomains didn't (fell to 83// nginx); routing the whole registrable domain fixes the class, not one instance. Bounds-checked. 84func sni_dom(h: *u8, hn: i64, dom: *u8, dn: i64) -> i64 { 85 if dn <= 0 { return 0 } 86 if hn < dn { return 0 } 87 let start: i64 = hn - dn 88 var i: i64 = 0 89 while i < dn { if h[start + i] != dom[i] { return 0 } i = i + 1 } 90 if start == 0 { return 1 } // exact apex 91 if (h[start - 1] as i64) == 46 { return 1 } // 46 == '.' -> a real sub-label 92 return 0 93} 94 95// SOVEREIGN-EDGE routing. 96// andelinwest.com -> WHOLE registrable domain (apex + ALL subdomains) rides the sovereign sites 97// daemon (:8443), which SNI-selects the *.andelinwest.com wildcard LE cert (Route B) and path-routes 98// via proxy_routes.conf. Fixes the CLASS (admin./mail./future subdomains) not one instance. 99// nishifamily.com -> APEX ONLY. Its subdomains (chat./git./matrix./mfg./www.) are OTHER services the 100// DSM reverse proxy carries to 192.168.1.73 -- suffix-matching them would break those, so keep exact. 101// Everything else + any no-SNI/fragmented ClientHello -> local Synology nginx (:443) for DSM's own stuff. 102func sni_backend(h: *u8, hn: i64) -> i64 { 103 if sni_dom(h, hn, "andelinwest.com" as *u8, 15) == 1 { return NX_BK_NISHI } 104 if sni_eq(h, hn, "nishifamily.com" as *u8, 15) == 1 { return NX_BK_NISHI } 105 return NX_BK_NGINX 106}