nx_torrent_seed.nx source
↩ module page · 582 lines · 35150 B
1// nx_torrent_seed.nx -- SOVEREIGN BitTorrent SEEDER (the missing UPLOAD half of the stack).
2//
3// Until now the Nishi torrent stack was a pure LEECHER: DNS->tracker/DHT->peers->handshake->metadata->
4// pieces->disk (all proven live), but it NEVER served pieces back. A pure leecher is punished by
5// BitTorrent tit-for-tat (seeders never optimistically-unchoke it -> it stalls -- exactly what
6// nx_torrent_seedeval's "IGNORING" verdict describes) and it cannot SHARE at all (only the HTTP /dist
7// mirror could). This organ is the symmetric other half: it ACCEPTS incoming peers and SERVES verified
8// blocks from a real file, so we become a real swarm member (seed-while-download + seed-after-complete).
9//
10// Composes nx_peerwire (BEP-3 build/parse) + nx_syscalls (sockets/files). Content is mapped by info_hash
11// via a seed registry (like dist_index.conf): rows = <name>\t<ih_hex40>\t<piece_length>\t<total>\t<srcpath>.
12// The seeder core `ts_serve_peer` is a LIB fn (import strips main) so the gate can drive it directly.
13//
14// nx_torrent_seed serve <port> [registry] -- accept-loop, serve every registered torrent
15// nx_torrent_seed serve1 <port> <ih_hex40> <plen> <total> <src> -- serve ONE torrent (deploy/test convenience)
16//
17// NEVER-BRICK by construction: writes ZERO persistent hardware state -- only network sends + file READS.
18// license_tier: ORIGINAL layer: L4 (peer-wire serving) module: nishi-core.torrent.seed
19// depends: nishi-core.torrent.peerwire, nishi-core.syscalls, nishi-core.torrent.mse_wire
20// MSE/PE: inbound peers are demuxed (0x13=plaintext BT / else=encrypted MSE) so DPI can't fingerprint us,
21// while legacy plaintext peers still work (backward compatible). All post-handshake I/O routes through MseCtx.
22import "nx_peerwire.nx"
23import "nx_mse_wire.nx"
24import "nx_ipfilter.nx" // ip-filter: reject inbound peers whose IP is in a blocked range (privacy while WAN-seeding)
25const TS_MAGIC_2026: i64 = 2026
26const TS_MAGIC_65536: i64 = 65536
27const TS_MAGIC_1048576: i64 = 1048576
28const TS_MAGIC_1024: i64 = 1024
29const TS_MAGIC_6881: i64 = 6881
30
31const TS_IPF_PATH: *u8 = "/volume1/ai/torrent/data/ipfilter.bin" as *u8
32const TS_IPF_MAXP: i64 = 80000
33const TS_SA_CELL: i64 = 32 // getpeername sockaddr scratch (mmap'd + munmap'd per call -- leak-free pair)
34const TS_AL_CELL: i64 = 8 // getpeername addrlen out-cell (same leak-free pair)
35
36// getpeername(afd) -> peer IPv4 as host-order u32 (portable syscall 205; x86-64 52). 0 on failure. LEAK-FREE (capture-then-munmap):
37// this runs in the ACCEPT-LOOP PARENT once per inbound connection -- the old leak-both-pages-per-call version
38// was the fleet certifier's first standing catch (264kB/min at idle = 2 pages x ~33 swarm connections/min;
39// the ep-out-param class, invisible until the 4-meter tooth watched the parent's VmSize trend).
40func ts_peer_ip(afd: i64) -> i64 {
41 let sa: *u8 = sys_mmap(TS_SA_CELL); let al: *i64 = sys_mmap(TS_AL_CELL) as *i64; al[0]=16
42 let rc: i64 = __syscall(205, afd, sa as i64, al as i64, 0, 0, 0)
43 var ip: i64 = 0
44 if rc == 0 { ip = ((sa[4] as i64)<<24)|((sa[5] as i64)<<16)|((sa[6] as i64)<<8)|(sa[7] as i64) }
45 sys_munmap(sa, TS_SA_CELL); sys_munmap(al as *u8, TS_AL_CELL)
46 return ip
47}
48// 1 = this accepted fd's peer is blocked (reject), 0 = ok to serve. cnt<=0 -> never blocks.
49func ts_ipf_reject(afd: i64, arr: *i64, cnt: i64) -> i64 {
50 if cnt <= 0 { return 0 }
51 let ip: i64 = ts_peer_ip(afd); if ip == 0 { return 0 }
52 return ipf_blocked(arr, cnt, ip)
53}
54
55const TS_BLOCK_MAX: i64 = 32768 // cap a single served block (std request = 16384)
56const TS_MSG_CAP: i64 = 131072 // cap an inbound control message (drain if larger)
57const TS_REG_DEFAULT: *u8 = "/volume1/ai/torrent/seed_index.conf" as *u8
58const TS_PEER_TIMEOUT: i64 = 30 // per-peer socket timeout (no-hang law)
59// CONCURRENCY BOUND (2026-07-30 seq1310): a swarm burst used to fork a handler per inbound peer with
60// NO cap on how many run at once -- each pays the MSE DH handshake burn (~20% CPU for seconds), so a
61// busy swarm was a standing NAS load producer. When live handlers >= the cap, the new connection is
62// CLOSED immediately (cheapest signal; BitTorrent peers retry). Limit is DATA-DRIVEN via the conf
63// (one integer, first line); absent/unreadable -> TS_LIVE_DEFAULT.
64const TS_LIMITS_PATH: *u8 = "/volume1/ai/torrent/seed_limits.conf" as *u8
65const TS_LIVE_DEFAULT: i64 = 4
66
67func ts_max_live() -> i64 {
68 let fd: i64 = sys_openat_rd(TS_LIMITS_PATH)
69 if fd < 0 { return TS_LIVE_DEFAULT }
70 let b: *u8 = sys_mmap(32)
71 let n: i64 = sys_read(fd, b, 31)
72 sys_close(fd)
73 if n <= 0 { return TS_LIVE_DEFAULT }
74 b[n] = 0 as u8
75 let v: i64 = ts_atoi(b)
76 if v <= 0 { return TS_LIVE_DEFAULT }
77 return v
78}
79
80func ts_w(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
81// PER-CALL SCRATCH -> LAZY STATICS (2026-08-14). ts_wn did TWO sys_mmap(28) calls PER INVOCATION and
82// freed neither. sys_mmap is a REAL mmap syscall, so the kernel rounds each to a full page: ~8 KB
83// LEAKED PER NUMBER PRINTED. ts_wn is the number printer for EVERY log line this organ writes, and the
84// parent re-logs its SATURATED line every time `satlogged` resets -- which is on every accepted
85// connection -- so a seeder oscillating around max_live prints, and leaks, continuously.
86// MEASURED 2026-08-14 by nx_leak_check on the live seeder parent (pid 5093, PPid=1, RSS 195 MB):
87// mem LEAK(rate=131) and rss LEAK(rate=131) kB/min == ~16 ts_wn calls/min at 8 KB each. It was the only
88// allocating call in the parent's accept loop -- ts_accept_loop_registry's own 4 mmaps are all setup,
89// hoisted above `while run == 1`.
90// THIS IS THE SAME DEFECT nx_seg_store ALREADY FIXED IN ss_hget, in a different file: that header even
91// states the law -- "a per-call allocation in a primitive is a per-CALLER-LOOP leak -- audit the
92// primitive's call frequency, not just its own body." It was fixed there and never swept for siblings.
93// SAFE AS STATICS: t and b are pure scratch, written then consumed by sys_write immediately, with no
94// intervening call that could re-enter ts_wn. Static POINTER + lazy mmap is the established idiom here
95// (a BSS static ARRAY silently crashes handler modules on startup, so never use [N]u8).
96static ts_scr_t: *u8
97static ts_scr_b: *u8
98func ts_wn(fd: i64, v: i64) -> i64 {
99 if (ts_scr_t as i64)==0 { ts_scr_t=sys_mmap(28) }
100 if (ts_scr_b as i64)==0 { ts_scr_b=sys_mmap(28) }
101 let t: *u8=ts_scr_t; var m: i64=v; if m<0 {m=0-m; sys_write(fd,"-" as *u8,1)}
102 var k: i64=0; if m==0 {t[0]=48 as u8;k=1}; while m>0 {t[k]=(48+(m%10)) as u8; m=m/10; k=k+1}
103 let b: *u8=ts_scr_b; var i: i64=0; while i<k {b[i]=t[k-1-i]; i=i+1}; sys_write(fd,b,k); return 0
104}
105func ts_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
106func ts_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 {return v} if c>57 {return v} v=v*10+(c-48); i=i+1 } return v }
107
108// address helper (AF_INET, INADDR_ANY for a listening seeder, or a specific ip for the gate loopback)
109func ts_sockaddr(sa: *u8, port: i64, i0: i64, i1: i64, i2: i64, i3: i64) -> i64 {
110 sa[0]=2 as u8; sa[1]=0 as u8
111 sa[2]=((port>>8)&0xff) as u8; sa[3]=(port&0xff) as u8
112 sa[4]=i0 as u8; sa[5]=i1 as u8; sa[6]=i2 as u8; sa[7]=i3 as u8
113 var k: i64=8; while k<16 { sa[k]=0 as u8; k=k+1 }
114 return 0
115}
116func ts_read_n(fd: i64, buf: *u8, n: i64) -> i64 {
117 var got: i64=0; while got<n { let r: i64=sys_read(fd, buf+got, n-got); if r<=0 { return 0-1 } got=got+r } return got
118}
119func ts_write_n(fd: i64, buf: *u8, n: i64) -> i64 {
120 var off: i64=0; while off<n { let wr: i64=sys_write(fd, buf+off, n-off); if wr<=0 { return 0-1 } off=off+wr } return n
121}
122// read one length-prefixed message into mb. return: >0 body-len, 0 keep-alive, -1 disconnect, -2 oversized(drained)
123func ts_read_msg(fd: i64, mb: *u8, cap: i64) -> i64 {
124 if ts_read_n(fd, mb, 4) != 4 { return 0-1 }
125 let blen: i64 = _pw_get_u32(mb, 0)
126 if blen == 0 { return 0 }
127 if blen > cap-4 {
128 // oversized (e.g. a huge bitfield we don't need) -> drain + ignore, stay in sync
129 let skip: *u8 = sys_mmap(TS_MAGIC_65536)
130 var left: i64 = blen
131 while left > 0 { var take: i64=left; if take>TS_MAGIC_65536 { take=TS_MAGIC_65536 } if ts_read_n(fd, skip, take)!=take { return 0-1 } left=left-take }
132 return 0-2
133 }
134 if ts_read_n(fd, mb+4, blen) != blen { return 0-1 }
135 return blen
136}
137
138// per-connection DH private key (MSE_N limbs). Not a CSPRNG, but varies by wall-clock so Ya isn't constant
139// across peers; nonzero + ~128-bit. (Hardening TODO: a real random source for forward secrecy.)
140func ts_dh_priv(x: *i64) -> i64 { return mse_gen_priv(x) } // CSPRNG per-connection key (nx_mse_wire)
141// ts_read_msg over an MseCtx (transparent decrypt; plaintext ctx == raw == identical to ts_read_msg).
142func ts_read_msg_ctx(ctx: *MseCtx, afd: i64, mb: *u8, cap: i64) -> i64 {
143 if mse_read(ctx, afd, mb, 4) != 4 { return 0-1 }
144 let blen: i64 = _pw_get_u32(mb, 0)
145 if blen == 0 { return 0 }
146 if blen > cap-4 {
147 let skip: *u8 = sys_mmap(TS_MAGIC_65536); var left: i64 = blen
148 while left > 0 { var take: i64=left; if take>TS_MAGIC_65536 { take=TS_MAGIC_65536 } if mse_read(ctx, afd, skip, take)!=take { return 0-1 } left=left-take }
149 return 0-2
150 }
151 if mse_read(ctx, afd, mb+4, blen) != blen { return 0-1 }
152 return blen
153}
154func ts_memeq(a: *u8, b: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if a[i]!=b[i] { return 0 } i=i+1 } return 1 }
155func ts_hexval(c: i64) -> i64 {
156 if c>=48 { if c<=57 { return c-48 } }
157 if c>=97 { if c<=102 { return c-87 } } // a-f
158 if c>=65 { if c<=70 { return c-55 } } // A-F
159 return 0
160}
161// 40-char hex info_hash -> 20 raw bytes. returns 1 on ok, 0 if too short.
162func ts_hex2bin(hex: *u8, out: *u8) -> i64 {
163 var i: i64=0
164 while i<20 {
165 let hc: i64 = hex[i*2] as i64; let lc: i64 = hex[i*2+1] as i64
166 if hc==0 { return 0 } if lc==0 { return 0 }
167 out[i] = ((ts_hexval(hc)*16)+ts_hexval(lc)) as u8
168 i=i+1
169 }
170 return 1
171}
172
173// Build an all-present BITFIELD for npc pieces (a seed HAS everything). MSB-first; trailing bits 0 per BEP-3.
174func ts_build_full_bitfield(npc: i64, bf: *u8) -> i64 {
175 let full: i64 = npc / 8
176 let rem: i64 = npc - full*8
177 var bi: i64=0
178 while bi<full { bf[bi]=0xff as u8; bi=bi+1 }
179 var bflen: i64 = full
180 if rem>0 {
181 var mask: i64=0; var pw: i64=128; var k: i64=0
182 while k<rem { mask=mask+pw; pw=pw/2; k=k+1 } // top `rem` bits set, no shifts
183 bf[full]=mask as u8
184 bflen = full+1
185 }
186 return bflen
187}
188// PARTIAL SEED: derive <dir>/download.done from srcpath (<dir>/download.part) and read the per-piece have map
189// (npc bytes, 1=have). Returns 1 = PARTIAL mode (advertise+serve ONLY have pieces -> safe to seed a selective/
190// complete-on-wanted torrent), 0 = FULL mode (no download.done, e.g. a plain seeded file -> advertise all).
191func ts_load_have(srcpath: *u8, npc: i64, have: *u8) -> i64 {
192 let dp: *u8 = sys_mmap(768); var o: i64=0; var lastsl: i64=0; var i: i64=0
193 while srcpath[i]!=(0 as u8) { if srcpath[i]==(47 as u8) { lastsl=i } i=i+1 }
194 i=0; while i<=lastsl { dp[o]=srcpath[i]; o=o+1; i=i+1 }
195 let sfx: *u8="download.done" as *u8; var j: i64=0; while sfx[j]!=(0 as u8) { dp[o]=sfx[j]; o=o+1; j=j+1 } dp[o]=0 as u8
196 let fd: i64 = sys_openat_rd(dp); if fd<0 { return 0 }
197 let n: i64 = sys_read(fd, have, npc); sys_close(fd)
198 if n < npc { return 0 } // no/short done file -> FULL mode
199 return 1
200}
201// bitfield (MSB-first per BEP-3) from the have map -- only pieces we actually hold get their bit set.
202func ts_build_bitfield_have(npc: i64, have: *u8, bf: *u8) -> i64 {
203 let nbytes: i64 = (npc+7)/8; var b: i64=0; while b<nbytes { bf[b]=0 as u8; b=b+1 }
204 var p: i64=0; while p<npc { if (have[p] as i64)==1 { let by: i64=p/8; let bit: i64=7-(p-by*8); bf[by]=((bf[by] as i64)|(1<<bit)) as u8 } p=p+1 }
205 return nbytes
206}
207
208// Read + parse the peer's 68-byte handshake off a fresh socket. Writes the info_hash the peer WANTS
209// into out_ih (20 bytes) and its peer id into out_pid. Returns 1 ok, 0 on failure. Sets the no-hang timeout.
210func ts_read_hs(afd: i64, out_ih: *u8, out_pid: *u8) -> i64 {
211 sys_set_socket_timeout(afd, TS_PEER_TIMEOUT)
212 let hb: *u8 = sys_mmap(128)
213 if ts_read_n(afd, hb, 68) != 68 { return 0 }
214 if nx_pw_parse_handshake(hb, 68, out_ih, out_pid) != 1 { return 0 }
215 return 1
216}
217
218// ==== THE SEED CORE (post-handshake) ==== reply handshake -> BITFIELD(all) -> UNCHOKE -> serve REQUEST->PIECE
219// from the file. Assumes the peer's handshake is already read+validated. ih = info_hash to echo/serve; srcpath =
220// the file; plen/total/npc = geometry. Returns #blocks served, or -1 on a send failure.
221func ts_serve_after_hs(ctx: *MseCtx, afd: i64, ih: *u8, my_pid: *u8, srcpath: *u8, plen: i64, total: i64, npc: i64) -> i64 {
222 // 2. reply with our handshake (same info_hash) -- via ctx (encrypted if MSE, raw if plaintext)
223 let oh: *u8 = sys_mmap(128); nx_pw_build_handshake(ih, my_pid, oh)
224 if mse_write(ctx, afd, oh, 68) < 0 { return 0-1 }
225 // 3. BITFIELD: advertise ONLY the pieces we hold (PARTIAL mode from download.done) so a selective/
226 // complete-on-wanted torrent seeds honestly; FULL if no download.done (a plain seeded file).
227 let have: *u8 = sys_mmap(npc + 64); let have_loaded: i64 = ts_load_have(srcpath, npc, have)
228 let bf: *u8 = sys_mmap(npc/8 + 64)
229 var bflen: i64 = 0
230 if have_loaded==1 { bflen = ts_build_bitfield_have(npc, have, bf) } else { bflen = ts_build_full_bitfield(npc, bf) }
231 let bfmsg: *u8 = sys_mmap(npc/8 + 128)
232 let bfl: i64 = nx_pw_build_msg(NX_PW_BITFIELD, bf, bflen, bfmsg)
233 if mse_write(ctx, afd, bfmsg, bfl) < 0 { return 0-1 }
234 // 4. UNCHOKE (a seed unchokes leechers so they can request)
235 let un: *u8 = sys_mmap(16); let ul: i64 = nx_pw_build_msg(NX_PW_UNCHOKE, 0 as *u8, 0, un)
236 if mse_write(ctx, afd, un, ul) < 0 { return 0-1 }
237 // 5. request loop -- serve blocks straight from the file
238 let fd: i64 = sys_openat_rd(srcpath); if fd < 0 { return 0-1 }
239 let mb: *u8 = sys_mmap(TS_MSG_CAP)
240 let frame: *u8 = sys_mmap(TS_BLOCK_MAX + 64) // rebuilt per request -> mse_write's in-place encrypt is safe
241 var served: i64 = 0
242 var run: i64 = 1
243 while run == 1 {
244 let mlen: i64 = ts_read_msg_ctx(ctx, afd, mb, TS_MSG_CAP)
245 if mlen == 0-1 { run = 0 } // peer gone / timeout
246 else { if mlen > 0 {
247 let id: i64 = nx_pw_msg_id(mb)
248 if id == NX_PW_REQUEST {
249 let index: i64 = _pw_get_u32(mb, 5)
250 let begin: i64 = _pw_get_u32(mb, 9)
251 let length: i64 = _pw_get_u32(mb, 13)
252 var may_serve: i64=1; if have_loaded==1 { if index>=0 { if index<npc { if (have[index] as i64)==0 { may_serve=0 } } } } // PARTIAL: never serve a piece we don't hold (no garbage/zeros)
253 if may_serve==1 { if length>0 { if length<=TS_BLOCK_MAX { if index>=0 { if index<npc {
254 let off: i64 = index*plen + begin
255 var rl: i64 = length
256 if off+rl > total { rl = total-off } // clamp to EOF (last piece)
257 if rl > 0 { if off>=0 {
258 // PIECE frame: [len BE][id=7][index 4][begin 4][block rl]
259 _pw_put_u32(frame, 0, 1 + 8 + rl)
260 frame[4] = NX_PW_PIECE as u8
261 _pw_put_u32(frame, 5, index)
262 _pw_put_u32(frame, 9, begin)
263 sys_lseek(fd, off, 0)
264 let dst: *u8 = frame + 13
265 if ts_read_n(fd, dst, rl) == rl { // FILE read stays raw (not the socket)
266 if mse_write(ctx, afd, frame, 13+rl) < 0 { run = 0 } else { served = served+1 }
267 }
268 } }
269 } } } } }
270 }
271 // INTERESTED / NOT_INTERESTED / HAVE / CANCEL / BITFIELD -> ignore (already unchoked, we serve on request)
272 } }
273 }
274 sys_close(fd)
275 return served
276}
277
278// serve ONE connected peer for a SINGLE known torrent (ih must match). DEMUX: 0x13 => plaintext BT handshake;
279// else => encrypted MSE. Both paths converge on ts_serve_after_hs via an MseCtx (plaintext ctx = passthrough).
280func ts_serve_peer(afd: i64, ih: *u8, my_pid: *u8, srcpath: *u8, plen: i64, total: i64, npc: i64) -> i64 {
281 sys_set_socket_timeout(afd, TS_PEER_TIMEOUT)
282 let fb: *u8 = sys_mmap(8); if ts_read_n(afd, fb, 1) != 1 { return 0-1 } // peek the first byte
283 let ctx: *MseCtx = sys_mmap(MSE_CTX_BYTES) as *MseCtx
284 let gih: *u8 = sys_mmap(20); let gpid: *u8 = sys_mmap(20)
285 if mse_classify_first_byte(fb[0] as i64) == 0 {
286 // PLAINTEXT: fb[0]=0x13; read the remaining 67 bytes of the BT handshake
287 let hb: *u8 = sys_mmap(128); hb[0]=fb[0]; if ts_read_n(afd, (hb as i64 + 1) as *u8, 67) != 67 { return 0-1 }
288 if nx_pw_parse_handshake(hb, 68, gih, gpid) != 1 { return 0-1 }
289 if ts_memeq(gih, ih, 20) != 1 { return 0-1 }
290 mse_ctx_plain(ctx)
291 return ts_serve_after_hs(ctx, afd, ih, my_pid, srcpath, plen, total, npc)
292 }
293 // MSE: fb[0] is Ya[0]. Do the encrypted handshake (SKEY = our ih), then read the peer's ENCRYPTED BT handshake.
294 let xpriv: *i64 = sys_mmap((MSE_N+2)*8) as *i64; ts_dh_priv(xpriv)
295 if mse_accept_pfx(afd, ih, xpriv, fb, 1, ctx) != 1 { return 0-1 }
296 let hb: *u8 = sys_mmap(128); if mse_read(ctx, afd, hb, 68) != 68 { return 0-1 }
297 if nx_pw_parse_handshake(hb, 68, gih, gpid) != 1 { return 0-1 }
298 if ts_memeq(gih, ih, 20) != 1 { return 0-1 }
299 return ts_serve_after_hs(ctx, afd, ih, my_pid, srcpath, plen, total, npc)
300}
301
302// row helpers for the registry (tab-separated fields, newline rows)
303func ts_eol(b: *u8, from: i64, blen: i64) -> i64 { var e: i64=from; while e<blen { if b[e]==(10 as u8) { return e } e=e+1 } return blen }
304func ts_tab(b: *u8, from: i64, limit: i64) -> i64 { var e: i64=from; while e<limit { if b[e]==(9 as u8) { return e } e=e+1 } return limit }
305func ts_field(row: *u8, rlen: i64, idx: i64, out: *u8) -> i64 {
306 var fs: i64=0; var f: i64=0; while f<idx { let t: i64=ts_tab(row,fs,rlen); fs=t+1; f=f+1 }
307 let fe: i64=ts_tab(row,fs,rlen); var o: i64=0; var k: i64=fs; while k<fe { out[o]=row[k]; o=o+1; k=k+1 } out[o]=0 as u8; return o
308}
309// resolve an info_hash (20 raw bytes) in the registry -> srcpath + piece_length + total. returns npc, or -1.
310// registry row = <name>\t<ih_hex40>\t<piece_length>\t<total>\t<srcpath>
311func ts_lookup(reg: *u8, want_ih: *u8, out_src: *u8, out_plen: *i64, out_total: *i64) -> i64 {
312 let buf: *u8 = sys_mmap(TS_MAGIC_1048576)
313 let fd: i64 = sys_openat_rd(reg); if fd<0 { return 0-1 }
314 let blen: i64 = sys_read(fd, buf, TS_MAGIC_1048576); sys_close(fd); if blen<=0 { return 0-1 }
315 let ihhex: *u8 = sys_mmap(64); let rawih: *u8 = sys_mmap(20)
316 let numbuf: *u8 = sys_mmap(32)
317 var ls: i64=0
318 while ls<blen {
319 let le: i64 = ts_eol(buf, ls, blen)
320 let row: *u8 = ((buf as i64)+ls) as *u8; let rlen: i64 = le-ls
321 ts_field(row, rlen, 1, ihhex)
322 if ts_hex2bin(ihhex, rawih)==1 { if ts_memeq(rawih, want_ih, 20)==1 {
323 ts_field(row, rlen, 2, numbuf); let plen: i64 = ts_atoi(numbuf); out_plen[0]=plen
324 ts_field(row, rlen, 3, numbuf); let total: i64 = ts_atoi(numbuf); out_total[0]=total
325 ts_field(row, rlen, 4, out_src)
326 if plen<=0 { return 0-1 }
327 return (total + plen - 1) / plen
328 } }
329 ls = le+1
330 }
331 return 0-1
332}
333
334// MSE registry SKEY-scan: find the registered info_hash whose HASH(req2,ih)==target (from mse_skey_target).
335// Writes that ih (20 raw bytes) to out_ih + its srcpath/plen/total. Returns npc, or -1 if none match.
336func ts_registry_scan(reg: *u8, target: *u8, out_ih: *u8, out_src: *u8, out_plen: *i64, out_total: *i64) -> i64 {
337 let buf: *u8 = sys_mmap(TS_MAGIC_1048576)
338 let fd: i64 = sys_openat_rd(reg); if fd<0 { return 0-1 }
339 let blen: i64 = sys_read(fd, buf, TS_MAGIC_1048576); sys_close(fd); if blen<=0 { return 0-1 }
340 let ihhex: *u8 = sys_mmap(64); let rawih: *u8 = sys_mmap(20); let numbuf: *u8 = sys_mmap(32)
341 var ls: i64=0
342 while ls<blen {
343 let le: i64 = ts_eol(buf, ls, blen)
344 let row: *u8 = ((buf as i64)+ls) as *u8; let rlen: i64 = le-ls
345 ts_field(row, rlen, 1, ihhex)
346 if ts_hex2bin(ihhex, rawih)==1 { if mse_skey_matches(rawih, target)==1 {
347 var i: i64=0; while i<20 { out_ih[i]=rawih[i]; i=i+1 }
348 ts_field(row, rlen, 2, numbuf); let plen: i64 = ts_atoi(numbuf); out_plen[0]=plen
349 ts_field(row, rlen, 3, numbuf); let total: i64 = ts_atoi(numbuf); out_total[0]=total
350 ts_field(row, rlen, 4, out_src)
351 if plen<=0 { return 0-1 }
352 return (total + plen - 1) / plen
353 } }
354 ls = le+1
355 }
356 return 0-1
357}
358
359// serve ONE connected peer, resolving the torrent by info_hash against the registry. DEMUX: 0x13 => plaintext
360// (peer's handshake carries the info_hash -> ts_lookup); else => MSE (DH, then SKEY-scan identifies the torrent).
361func ts_serve_peer_registry(afd: i64, my_pid: *u8, reg: *u8) -> i64 {
362 sys_set_socket_timeout(afd, TS_PEER_TIMEOUT)
363 let fb: *u8 = sys_mmap(8); if ts_read_n(afd, fb, 1) != 1 { return 0-1 }
364 let ctx: *MseCtx = sys_mmap(MSE_CTX_BYTES) as *MseCtx
365 let gih: *u8 = sys_mmap(20); let gpid: *u8 = sys_mmap(20)
366 let src: *u8 = sys_mmap(TS_MAGIC_1024); let plenb: *i64 = sys_mmap(16) as *i64; let totalb: *i64 = sys_mmap(16) as *i64
367 if mse_classify_first_byte(fb[0] as i64) == 0 {
368 // PLAINTEXT
369 let hb: *u8 = sys_mmap(128); hb[0]=fb[0]; if ts_read_n(afd, (hb as i64 + 1) as *u8, 67) != 67 { return 0-1 }
370 if nx_pw_parse_handshake(hb, 68, gih, gpid) != 1 { return 0-1 }
371 let npc: i64 = ts_lookup(reg, gih, src, plenb, totalb)
372 if npc < 0 { return 0-2 }
373 mse_ctx_plain(ctx)
374 return ts_serve_after_hs(ctx, afd, gih, my_pid, src, plenb[0], totalb[0], npc)
375 }
376 // MSE: DH phase (stream-syncs past the peer's PadA), then identify the torrent by SKEY-scan
377 let xpriv: *i64 = sys_mmap((MSE_N+2)*8) as *i64; ts_dh_priv(xpriv)
378 let sbytes: *u8 = sys_mmap(128); let xr: *u8 = sys_mmap(24)
379 if mse_accept_dh(afd, xpriv, fb, 1, sbytes, xr) != 1 { return 0-1 }
380 let target: *u8 = sys_mmap(24); mse_skey_target(sbytes, xr, target)
381 let npc: i64 = ts_registry_scan(reg, target, gih, src, plenb, totalb)
382 if npc < 0 { return 0-2 } // no registered torrent matched -> not for us
383 if mse_accept_finish(afd, gih, sbytes, ctx) != 1 { return 0-1 }
384 let hb: *u8 = sys_mmap(128); if mse_read(ctx, afd, hb, 68) != 68 { return 0-1 } // peer's encrypted BT handshake
385 let g2: *u8 = sys_mmap(20); let gp2: *u8 = sys_mmap(20)
386 if nx_pw_parse_handshake(hb, 68, g2, gp2) != 1 { return 0-1 }
387 if ts_memeq(g2, gih, 20) != 1 { return 0-1 }
388 return ts_serve_after_hs(ctx, afd, gih, my_pid, src, plenb[0], totalb[0], npc)
389}
390
391// registry seeding daemon: LISTEN forever, per peer resolve the requested info_hash in the registry + serve it.
392// One port serves EVERY registered torrent -- the real multi-torrent seed. Fork-per-peer + WNOHANG reap.
393func ts_accept_loop_registry(port: i64, my_pid: *u8, reg: *u8, max_conns: i64) -> i64 {
394 let sa: *u8 = sys_mmap(16); ts_sockaddr(sa, port, 0, 0, 0, 0) // INADDR_ANY (inbound peers)
395 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
396 if lfd < 0 { ts_w(1, "nx_torrent_seed SOCKET-FAIL\n" as *u8); sys_exit(1); return 1 }
397 let one: *u8 = sys_mmap(4); one[0]=1 as u8; one[1]=0 as u8; one[2]=0 as u8; one[3]=0 as u8
398 sys_setsockopt(lfd, SOL_SOCKET, 2, one, 4) // SO_REUSEADDR
399 if sys_bind(lfd, sa, 16) < 0 { ts_w(1, "nx_torrent_seed BIND-FAIL port=" as *u8); ts_wn(1, port); ts_w(1, "\n" as *u8); sys_exit(1); return 1 }
400 if sys_listen(lfd, 16) < 0 { ts_w(1, "nx_torrent_seed LISTEN-FAIL\n" as *u8); sys_exit(1); return 1 }
401 ts_w(1, "nx_torrent_seed REGISTRY LIVE :" as *u8); ts_wn(1, port); ts_w(1, " reg=" as *u8); ts_w(1, reg); ts_w(1, " max_conns=" as *u8); ts_wn(1, max_conns); ts_w(1, "\n" as *u8)
402 let rg_ipf: *i64 = sys_mmap(TS_IPF_MAXP*16 + 64) as *i64
403 let rg_ipf_n: i64 = ipf_load(TS_IPF_PATH, rg_ipf, TS_IPF_MAXP)
404 if rg_ipf_n > 0 { ts_w(1, "nx_torrent_seed ip-filter ranges=" as *u8); ts_wn(1, rg_ipf_n); ts_w(1, " (reject blocked inbound)\n" as *u8) }
405 let st: *i64 = sys_mmap(16) as *i64
406 let maxlive: i64 = ts_max_live()
407 ts_w(1, "nx_torrent_seed[reg] max_live=" as *u8); ts_wn(1, maxlive); ts_w(1, "\n" as *u8)
408 var live: i64 = 0
409 var satlogged: i64 = 0
410 var served_conns: i64 = 0
411 var consec: i64 = 0
412 var run: i64 = 1
413 while run == 1 {
414 // ★REAP FIRST, AND READ THE OUTCOME. Reaping used to discard the exit status entirely, and the child
415 // exited 0 unconditionally, so the parent could never learn that every handshake was failing.
416 var reaped: i64 = sys_wait4(0-1, st, 1)
417 while reaped > 0 {
418 if live > 0 { live = live - 1 }
419 if ((st[0] >> 8) & 0xff) == 0 { consec = 0 } else { consec = consec + 1 }
420 reaped = sys_wait4(0-1, st, 1)
421 }
422 let d: i64 = ts_backoff_ms(consec)
423 if d > 0 { sys_sleep_ms(d) }
424 let afd: i64 = sys_accept(lfd)
425 // ★THE MISSING BRANCH. `if afd >= 0 {...}` with no else meant a failing accept re-looped instantly.
426 if afd < 0 { consec = consec + 1 } else {
427 if ts_ipf_reject(afd, rg_ipf, rg_ipf_n) == 1 { sys_close(afd) } else {
428 if live >= maxlive {
429 // saturated: refuse the connection outright (no DH burn); peers retry. Log once per episode.
430 if satlogged == 0 { ts_w(1, "nx_torrent_seed[reg] SATURATED live=" as *u8); ts_wn(1, live); ts_w(1, " refusing new peers until a handler exits\n" as *u8); satlogged = 1 }
431 sys_close(afd)
432 } else {
433 satlogged = 0
434 let pid: i64 = sys_fork()
435 if pid == 0 {
436 sys_close(lfd)
437 let n: i64 = ts_serve_peer_registry(afd, my_pid, reg)
438 ts_w(1, "nx_torrent_seed[reg] served blocks=" as *u8); ts_wn(1, n); ts_w(1, "\n" as *u8)
439 sys_close(afd)
440 // ★THE EXIT CODE IS THE SIGNAL. Without it the parent is blind to a fast-failing serve.
441 if n <= 0 { sys_exit(1) } // MEASURED TS_MAGIC_2026-07-30 (nx_seedchurn_gate): useful=11 permille. n==0 is a completed handshake that moved NOTHING; counting it as success is what stopped this brake from ever engaging.
442 sys_exit(0)
443 }
444 sys_close(afd)
445 live = live + 1
446 served_conns = served_conns + 1
447 if max_conns > 0 { if served_conns >= max_conns { sys_wait4(0-1, st, 0); run = 0 } }
448 }
449 } }
450 }
451 sys_close(lfd)
452 return 0
453}
454
455// ---- ACCEPT BACKOFF POLICY ----
456// ★WHY THIS EXISTS (measured live 2026-07-30). Both accept loops had NO negative branch on sys_accept:
457// `if afd >= 0 { ... }` with no else, so a persistently failing accept (EMFILE, ENFILE, ECONNABORTED)
458// re-looped with ZERO delay -- an unconditional 100%-of-a-core spin. Separately the forked child called
459// sys_exit(0) UNCONDITIONALLY, so the parent could never learn that ts_serve_peer_registry had returned -2;
460// /tmp/seed.log showed `served blocks=-2` repeating without end while the parent forked again immediately,
461// forever. A sibling measured nx_torrent_seed at 71.6% of a core and named the signature exactly: a hot
462// accept loop with no errno branch and no backoff.
463// ★THE maxlive SATURATION GUARD ABOVE DOES NOT COVER THIS. It bounds CONCURRENT handlers; when handlers
464// fail INSTANTLY, `live` never rises, so the loop forks flat out and stays under the cap the whole time.
465// A limit on concurrency is not a limit on RATE.
466// ★ONE MECHANISM FOR BOTH FAULTS, because they are the same failure: work is attempted as fast as the CPU
467// allows and none of it succeeds. A failed accept and a fast-failing serve feed one counter.
468// ★PURE FUNCTION ON PURPOSE -- same discipline as `hc_keep_delay` and `ba_verdict`. A policy welded into an
469// accept loop beside its own syscalls cannot be tested without putting a host into the required state, and
470// "every peer handshake fails" is not a condition you can summon on a shared box. That is how it survived.
471// ★NEVER STOPS ACCEPTING, ALWAYS SELF-HEALS: the delay is capped and ONE successful serve resets it to 0.
472const TS_BK_TOL: i64 = 8 // real peers abort mid-handshake; tolerate a burst before slowing at all
473const TS_BK_N2: i64 = 64
474const TS_BK_N3: i64 = 512
475const TS_BK_D1: i64 = 50
476const TS_BK_D2: i64 = 250
477const TS_BK_D3: i64 = 1000 // ceiling: one attempt per second is still a live seeder, not a dead one
478func ts_backoff_ms(consec_fail: i64) -> i64 {
479 if consec_fail < TS_BK_TOL { return 0 }
480 if consec_fail < TS_BK_N2 { return TS_BK_D1 }
481 if consec_fail < TS_BK_N3 { return TS_BK_D2 }
482 return TS_BK_D3
483}
484// The pre-fix behaviour, kept ONLY so the gate can prove the defect was real (see hc_keep_delay's twin).
485func ts_backoff_ms_old(consec_fail: i64) -> i64 { return 0 }
486
487// ---- daemon: accept loop, fork per peer, reap zombies ----
488// max_conns > 0 -> serve that many accepted peers then exit (orphan-safe one-shot for demos/tests);
489// max_conns == 0 -> run forever (the real seeding daemon).
490func ts_accept_loop(port: i64, ih: *u8, my_pid: *u8, src: *u8, plen: i64, total: i64, npc: i64, max_conns: i64) -> i64 {
491 let sa: *u8 = sys_mmap(16); ts_sockaddr(sa, port, 0, 0, 0, 0) // INADDR_ANY
492 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
493 if lfd < 0 { ts_w(1, "nx_torrent_seed SOCKET-FAIL\n" as *u8); sys_exit(1); return 1 }
494 let one: *u8 = sys_mmap(4); one[0]=1 as u8; one[1]=0 as u8; one[2]=0 as u8; one[3]=0 as u8
495 sys_setsockopt(lfd, SOL_SOCKET, 2, one, 4) // SO_REUSEADDR
496 if sys_bind(lfd, sa, 16) < 0 { ts_w(1, "nx_torrent_seed BIND-FAIL port=" as *u8); ts_wn(1, port); ts_w(1, "\n" as *u8); sys_exit(1); return 1 }
497 if sys_listen(lfd, 16) < 0 { ts_w(1, "nx_torrent_seed LISTEN-FAIL\n" as *u8); sys_exit(1); return 1 }
498 ts_w(1, "nx_torrent_seed LIVE :" as *u8); ts_wn(1, port); ts_w(1, " npc=" as *u8); ts_wn(1, npc); ts_w(1, " plen=" as *u8); ts_wn(1, plen); ts_w(1, " max_conns=" as *u8); ts_wn(1, max_conns); ts_w(1, "\n" as *u8)
499 let al_ipf: *i64 = sys_mmap(TS_IPF_MAXP*16 + 64) as *i64
500 let al_ipf_n: i64 = ipf_load(TS_IPF_PATH, al_ipf, TS_IPF_MAXP)
501 let st: *i64 = sys_mmap(16) as *i64
502 let maxlive: i64 = ts_max_live()
503 var live: i64 = 0
504 var satlogged: i64 = 0
505 var served_conns: i64 = 0
506 var run: i64 = 1
507 var consec: i64 = 0
508 while run == 1 {
509 // reap finished handlers (WNOHANG) so we never accumulate zombies -- and LEARN from the exit code
510 var reaped: i64 = sys_wait4(0-1, st, 1)
511 while reaped > 0 {
512 if live > 0 { live = live - 1 }
513 if ((st[0] >> 8) & 0xff) == 0 { consec = 0 } else { consec = consec + 1 }
514 reaped = sys_wait4(0-1, st, 1)
515 }
516 let d: i64 = ts_backoff_ms(consec)
517 if d > 0 { sys_sleep_ms(d) }
518 let afd: i64 = sys_accept(lfd)
519 if afd < 0 { consec = consec + 1 } else {
520 if ts_ipf_reject(afd, al_ipf, al_ipf_n) == 1 { sys_close(afd) } else {
521 if live >= maxlive {
522 if satlogged == 0 { ts_w(1, "nx_torrent_seed SATURATED live=" as *u8); ts_wn(1, live); ts_w(1, " refusing new peers until a handler exits\n" as *u8); satlogged = 1 }
523 sys_close(afd)
524 } else {
525 satlogged = 0
526 let pid: i64 = sys_fork()
527 if pid == 0 {
528 sys_close(lfd)
529 let n: i64 = ts_serve_peer(afd, ih, my_pid, src, plen, total, npc)
530 ts_w(1, "nx_torrent_seed served blocks=" as *u8); ts_wn(1, n); ts_w(1, "\n" as *u8)
531 sys_close(afd)
532 if n <= 0 { sys_exit(1) } // MEASURED TS_MAGIC_2026-07-30 (nx_seedchurn_gate): useful=11 permille. n==0 is a completed handshake that moved NOTHING; counting it as success is what stopped this brake from ever engaging.
533 sys_exit(0)
534 }
535 sys_close(afd)
536 live = live + 1
537 served_conns = served_conns + 1
538 if max_conns > 0 { if served_conns >= max_conns {
539 // wait for the in-flight handler to finish, then exit cleanly (no orphan)
540 sys_wait4(0-1, st, 0)
541 run = 0
542 } }
543 }
544 } }
545 }
546 sys_close(lfd)
547 return 0
548}
549
550func main(argc: i64, argv: *i64) -> i64 {
551 if argc < 2 { ts_w(1, "usage: nx_torrent_seed serve <port> [reg] | serve1 <port> <ih_hex40> <plen> <total> <src>\n" as *u8); sys_exit(2); return 2 }
552 let mode: *u8 = argv[1] as *u8
553 // our peer id: "-NX0001-" + 12 filler bytes (Azureus-style client tag, deterministic)
554 let my_pid: *u8 = sys_mmap(20)
555 let tag: *u8 = "-NX0001-nishiseed01" as *u8
556 var z: i64=0; while z<19 { my_pid[z]=tag[z]; z=z+1 } my_pid[19]=48 as u8
557 if mode[0] == (115 as u8) { if mode[5] == (49 as u8) {
558 // "serve1": single torrent from args (optional trailing max_conns; 0/absent = forever)
559 if argc < 7 { ts_w(1, "usage: nx_torrent_seed serve1 <port> <ih_hex40> <plen> <total> <src> [max_conns]\n" as *u8); sys_exit(2); return 2 }
560 let port: i64 = ts_atoi(argv[2] as *u8)
561 let ih: *u8 = sys_mmap(20)
562 if ts_hex2bin(argv[3] as *u8, ih) != 1 { ts_w(1, "bad info_hash hex (need 40 chars)\n" as *u8); sys_exit(2); return 2 }
563 let plen: i64 = ts_atoi(argv[4] as *u8)
564 let total: i64 = ts_atoi(argv[5] as *u8)
565 let src: *u8 = argv[6] as *u8
566 var maxc: i64 = 0
567 if argc >= 8 { maxc = ts_atoi(argv[7] as *u8) }
568 let npc: i64 = (total + plen - 1) / plen
569 return ts_accept_loop(port, ih, my_pid, src, plen, total, npc, maxc)
570 } else {
571 // "serve [port] [registry] [max_conns]": the real multi-torrent seeding daemon (serves every registered info_hash)
572 var port: i64 = TS_MAGIC_6881
573 if argc >= 3 { port = ts_atoi(argv[2] as *u8) }
574 var reg: *u8 = TS_REG_DEFAULT
575 if argc >= 4 { reg = argv[3] as *u8 }
576 var maxc: i64 = 0
577 if argc >= 5 { maxc = ts_atoi(argv[4] as *u8) }
578 return ts_accept_loop_registry(port, my_pid, reg, maxc)
579 } }
580 ts_w(1, "usage: nx_torrent_seed serve [port] [registry] | serve1 <port> <ih_hex40> <plen> <total> <src> [max_conns]\n" as *u8)
581 sys_exit(2); return 2
582}