nx_https_url_connect.nx source
↩ module page · 220 lines · 9210 B
1// nx_https_url_connect.nx -- step 2 of the nx_https_client
2// orchestrator wiring arc.
3//
4// Takes an already-validated NxHttpsTarget (from
5// nx_https_url_for_fetch step 1) PLUS the URL buffer it was
6// parsed from, resolves the host via DNS, builds a sockaddr, and
7// TCP-connects to host:port. Returns a connected file descriptor
8// ready for the TLS handshake (step 3).
9//
10// Composes 3 shipped primitives:
11// nx_https_url_for_fetch (c0117caa) -- already-parsed target
12// nx_dns_resolve_default -- hostname -> packed IPv4
13// sys_socket / sys_connect / sys_close -- BSD socket primitives
14//
15// Public API:
16// nx_https_url_connect(target, url_buf, now_unix, out_fd) -> verdict
17// nx_https_connect_verdict_is_valid(v) -> 0|1
18//
19// Why the url_buf parameter is separate:
20// NxHttpsTarget.url.host_off is an OFFSET into the URL string
21// the caller originally passed to nx_https_url_for_fetch. This
22// primitive needs the resolved host bytes, so the caller must
23// pass the same url_buf. An alternative API would have
24// NxHttpsTarget carry the url_buf pointer itself -- that's a
25// refactor for a future commit; for now we keep the explicit
26// parameter to avoid breaking step 1's existing API.
27//
28// Sealed verdict enum:
29// NX_HTTPS_CONNECT_OK socket connected; *out_fd valid
30// NX_HTTPS_CONNECT_DNS_FAIL DNS resolution returned no IPv4
31// NX_HTTPS_CONNECT_SOCKET_FAIL sys_socket returned negative
32// NX_HTTPS_CONNECT_CONNECT_FAIL sys_connect returned negative
33//
34// On any non-OK return, *out_fd is set to -1 and any socket
35// already allocated is closed. Callers don't need to clean up on
36// failure verdicts.
37//
38// Per Cardinals 9 (single-responsibility -- step 2 only; TLS +
39// HTTP are separate primitives), 12 (defensive at boundaries --
40// close fd on every failure path to avoid descriptor leaks), 23
41// (preamble names the url_buf API caveat + the queued refactor).
42//
43// license_tier: INDEPENDENT_REDERIVE
44// genealogy_id: international-research-sources/ietf/rfc_1035 + bsd_sockets
45// lineage_id: nishi_https_url_connect_q10
46
47// nx_safety_envelope:
48// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
49// sil_target: SIL1
50// evidence: [bulk_applied_2026-05-19, https-url-connect-step-2]
51// verdict: NOT_YET_EVALUATED
52
53import "nx_syscalls.nx"
54import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
55import "nx_dns_resolve_a_record.nx"
56import "nx_https_url_for_fetch.nx"
57const NX_MAGIC_65535: i64 = 65535
58
59const NX_HTTPS_CONNECT_OK: i64 = 1
60const NX_HTTPS_CONNECT_DNS_FAIL: i64 = 2
61const NX_HTTPS_CONNECT_SOCKET_FAIL: i64 = 3
62const NX_HTTPS_CONNECT_CONNECT_FAIL: i64 = 4
63const NX_HTTPS_CONNECT_VERDICT_N: i64 = 5
64
65const NX_HTTPS_AF_INET: i64 = 2
66const NX_HTTPS_SOCK_STREAM: i64 = 1
67
68func nx_https_connect_verdict_is_valid(v: i64) -> i64 {
69 if v < NX_HTTPS_CONNECT_OK { return 0 }
70 if v >= NX_HTTPS_CONNECT_VERDICT_N { return 0 }
71 return 1
72}
73
74// Build an IPv4 sockaddr_in into a 16-byte buffer.
75// [0..1] sin_family = AF_INET = 2 (little-endian short)
76// [2..3] sin_port = port in BIG-endian
77// [4..7] sin_addr = ipv4 bytes a.b.c.d in network order
78// [8..15] padding zero
79//
80// `ipv4_packed` is the DNS result's packed format: low 32 bits
81// hold the 4 octets in big-endian order (1.2.3.4 -> 0x01020304).
82func nx_https_build_sockaddr(out: *u8, ipv4_packed: i64, port: i64) -> i64 {
83 out[0] = (NX_HTTPS_AF_INET & 0xff) as u8
84 out[1] = ((NX_HTTPS_AF_INET >> 8) & 0xff) as u8
85 out[2] = ((port >> 8) & 0xff) as u8
86 out[3] = (port & 0xff) as u8
87 out[4] = ((ipv4_packed >> 24) & 0xff) as u8
88 out[5] = ((ipv4_packed >> 16) & 0xff) as u8
89 out[6] = ((ipv4_packed >> 8) & 0xff) as u8
90 out[7] = (ipv4_packed & 0xff) as u8
91 var i: i64 = 8
92 while i < 16 { out[i] = 0 as u8; i = i + 1 }
93 return 16
94}
95
96// ---- OPTIONAL connect-override (curl --connect-to class; the vantage false-RED cure) ----
97// When armed, EVERY nx_https_url_connect in this process connects to the pinned a.b.c.d:port
98// endpoint (skipping DNS) while the caller keeps SNI + Host + cert-name = the URL host. Lets
99// verifiers fetch our OWN vhosts deterministically from the sovereign edge (127.0.0.1:8443),
100// bypassing the DSM nginx that co-squats :443 for unclaimed SNIs -- the nx_page_verify /
101// nav-canary false-RED family. Default OFF (static box null) = byte-identical legacy DNS path.
102// Same semantics as the proven nx_https_get_cli override; parser mirrors hgc_parse_ipport
103// (the CLI migrates onto this exported pair on next touch, migrate-on-touch law).
104static hco_box: *i64
105
106// Parse "a.b.c.d:port" -> packed ipv4 + port. Returns 1 ok / 0 bad-spec (outs untouched on 0).
107func nx_https_parse_ipport(s: *u8, ip_out: *i64, port_out: *i64) -> i64 {
108 var packed: i64 = 0
109 var val: i64 = 0
110 var nocts: i64 = 0
111 var port: i64 = 0
112 var indots: i64 = 1
113 var i: i64 = 0
114 while s[i] != (0 as u8) {
115 let c: i64 = s[i] as i64
116 if indots == 1 {
117 if c == 46 { packed = (packed << 8) | (val & 0xff); nocts = nocts + 1; val = 0 }
118 else { if c == 58 { packed = (packed << 8) | (val & 0xff); nocts = nocts + 1; val = 0; indots = 0 }
119 else { if c < 48 { return 0 } if c > 57 { return 0 } val = val * 10 + (c - 48) } }
120 } else {
121 if c < 48 { return 0 }
122 if c > 57 { return 0 }
123 port = port * 10 + (c - 48)
124 }
125 i = i + 1
126 }
127 // no ':' -> no port given
128 if indots == 1 { return 0 }
129 if nocts != 4 { return 0 }
130 if port <= 0 { return 0 }
131 if port > NX_MAGIC_65535 { return 0 }
132 ip_out[0] = packed
133 port_out[0] = port
134 return 1
135}
136
137// Arm the process-wide override from "a.b.c.d:port". Returns 1 armed / 0 bad-spec (state untouched).
138func nx_https_connect_override_set(spec: *u8) -> i64 {
139 let ipbox: *i64 = sys_mmap(8) as *i64
140 let portbox: *i64 = sys_mmap(8) as *i64
141 if nx_https_parse_ipport(spec, ipbox, portbox) != 1 { return 0 }
142 if (hco_box as i64) == 0 { hco_box = sys_mmap(16) as *i64 }
143 hco_box[0] = ipbox[0]
144 hco_box[1] = portbox[0]
145 return 1
146}
147
148// Resolve target.url.host via DNS + TCP-connect to (resolved IP,
149// target.port). Writes the connected fd to *out_fd on success.
150func nx_https_url_connect(
151 target: *NxHttpsTarget,
152 url_buf: *u8,
153 now_unix: i64,
154 out_fd: *i64
155) -> i64 {
156 *out_fd = 0 - 1
157
158 // ---- connect-override armed? pinned-endpoint connect, DNS skipped (TLS/SNI stay the URL host) ----
159 if (hco_box as i64) != 0 { if hco_box[1] != 0 {
160 let ofd: i64 = sys_socket(NX_HTTPS_AF_INET, NX_HTTPS_SOCK_STREAM, 0)
161 if ofd < 0 { return NX_HTTPS_CONNECT_SOCKET_FAIL }
162 sys_set_socket_timeout(ofd, 10)
163 let osa: *u8 = sys_mmap(16)
164 nx_https_build_sockaddr(osa, hco_box[0], hco_box[1])
165 if nx_connect_bounded(ofd, osa, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(ofd); return NX_HTTPS_CONNECT_CONNECT_FAIL }
166 let ond: *u8 = sys_mmap(4)
167 ond[0] = 1 as u8
168 sys_setsockopt(ofd, 6, 1, ond, 4)
169 *out_fd = ofd
170 return NX_HTTPS_CONNECT_OK
171 } }
172
173 // ---- DNS resolve ----
174 let host_ptr: *u8 = url_buf + target.url.host_off
175 let host_len: i64 = target.url.host_len
176
177 let dns_result: *DnsResolveResult = nx_dns_resolve_default(
178 host_ptr, host_len, now_unix
179 )
180 if dns_result.verdict != NX_DNS_R_OK { return NX_HTTPS_CONNECT_DNS_FAIL }
181 if dns_result.ipv4_packed == 0 { return NX_HTTPS_CONNECT_DNS_FAIL }
182
183 // ---- TCP socket ----
184 let fd: i64 = sys_socket(NX_HTTPS_AF_INET, NX_HTTPS_SOCK_STREAM, 0)
185 if fd < 0 { return NX_HTTPS_CONNECT_SOCKET_FAIL }
186 sys_set_socket_timeout(fd, 10) // 10s read/write timeout: a hung server fails gracefully, never blocks forever
187
188 // ---- Build sockaddr + connect ----
189 let sa: *u8 = sys_mmap(16)
190 nx_https_build_sockaddr(sa, dns_result.ipv4_packed, target.port)
191
192 let cr: i64 = nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS)
193 if cr < 0 {
194 sys_close(fd)
195 return NX_HTTPS_CONNECT_CONNECT_FAIL
196 }
197
198 // TCP_NODELAY (IPPROTO_TCP=6, TCP_NODELAY=1): disable Nagle on the CLIENT socket.
199 // Without it, client-side Nagle x server delayed-ACK adds up to ~40ms per burst --
200 // MEASURED live by nx_video_qoe_live 2026-07-03: 20fps ran clean (3ms path) but every
201 // faster cadence hit an exact ~40ms cycle floor. Latency-first for every sovereign TLS
202 // client (funcheck, researchers, the native video client, QoE probes). Mirrors the
203 // accept-side nodelay the sites daemons already set. Best-effort: a failure here only
204 // costs latency, never correctness -- so the verdict does not change.
205 let nodelay: *u8 = sys_mmap(4)
206 nodelay[0] = 1 as u8
207 nodelay[1] = 0 as u8
208 nodelay[2] = 0 as u8
209 nodelay[3] = 0 as u8
210 sys_setsockopt(fd, 6, 1, nodelay, 4)
211
212 *out_fd = fd
213 return NX_HTTPS_CONNECT_OK
214}
215
216// Compile-only smoke. Real KAT in nx_https_url_connect_test.nx --
217// verifies sockaddr-builder byte layout + verdict dispatch.
218func main() -> i64 {
219 return 0
220}