nx_torrent_seed.nx source
↩ module page · 563 lines · 33552 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 (syscall 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(52, 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 }
81func ts_wn(fd: i64, v: i64) -> i64 {
82 let t: *u8=sys_mmap(28); var m: i64=v; if m<0 {m=0-m; sys_write(fd,"-" as *u8,1)}
83 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}
84 let b: *u8=sys_mmap(28); var i: i64=0; while i<k {b[i]=t[k-1-i]; i=i+1}; sys_write(fd,b,k); return 0
85}
86func ts_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
87func 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 }
88
89// address helper (AF_INET, INADDR_ANY for a listening seeder, or a specific ip for the gate loopback)
90func ts_sockaddr(sa: *u8, port: i64, i0: i64, i1: i64, i2: i64, i3: i64) -> i64 {
91 sa[0]=2 as u8; sa[1]=0 as u8
92 sa[2]=((port>>8)&0xff) as u8; sa[3]=(port&0xff) as u8
93 sa[4]=i0 as u8; sa[5]=i1 as u8; sa[6]=i2 as u8; sa[7]=i3 as u8
94 var k: i64=8; while k<16 { sa[k]=0 as u8; k=k+1 }
95 return 0
96}
97func ts_read_n(fd: i64, buf: *u8, n: i64) -> i64 {
98 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
99}
100func ts_write_n(fd: i64, buf: *u8, n: i64) -> i64 {
101 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
102}
103// read one length-prefixed message into mb. return: >0 body-len, 0 keep-alive, -1 disconnect, -2 oversized(drained)
104func ts_read_msg(fd: i64, mb: *u8, cap: i64) -> i64 {
105 if ts_read_n(fd, mb, 4) != 4 { return 0-1 }
106 let blen: i64 = _pw_get_u32(mb, 0)
107 if blen == 0 { return 0 }
108 if blen > cap-4 {
109 // oversized (e.g. a huge bitfield we don't need) -> drain + ignore, stay in sync
110 let skip: *u8 = sys_mmap(TS_MAGIC_65536)
111 var left: i64 = blen
112 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 }
113 return 0-2
114 }
115 if ts_read_n(fd, mb+4, blen) != blen { return 0-1 }
116 return blen
117}
118
119// per-connection DH private key (MSE_N limbs). Not a CSPRNG, but varies by wall-clock so Ya isn't constant
120// across peers; nonzero + ~128-bit. (Hardening TODO: a real random source for forward secrecy.)
121func ts_dh_priv(x: *i64) -> i64 { return mse_gen_priv(x) } // CSPRNG per-connection key (nx_mse_wire)
122// ts_read_msg over an MseCtx (transparent decrypt; plaintext ctx == raw == identical to ts_read_msg).
123func ts_read_msg_ctx(ctx: *MseCtx, afd: i64, mb: *u8, cap: i64) -> i64 {
124 if mse_read(ctx, afd, 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 let skip: *u8 = sys_mmap(TS_MAGIC_65536); var left: i64 = blen
129 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 }
130 return 0-2
131 }
132 if mse_read(ctx, afd, mb+4, blen) != blen { return 0-1 }
133 return blen
134}
135func 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 }
136func ts_hexval(c: i64) -> i64 {
137 if c>=48 { if c<=57 { return c-48 } }
138 if c>=97 { if c<=102 { return c-87 } } // a-f
139 if c>=65 { if c<=70 { return c-55 } } // A-F
140 return 0
141}
142// 40-char hex info_hash -> 20 raw bytes. returns 1 on ok, 0 if too short.
143func ts_hex2bin(hex: *u8, out: *u8) -> i64 {
144 var i: i64=0
145 while i<20 {
146 let hc: i64 = hex[i*2] as i64; let lc: i64 = hex[i*2+1] as i64
147 if hc==0 { return 0 } if lc==0 { return 0 }
148 out[i] = ((ts_hexval(hc)*16)+ts_hexval(lc)) as u8
149 i=i+1
150 }
151 return 1
152}
153
154// Build an all-present BITFIELD for npc pieces (a seed HAS everything). MSB-first; trailing bits 0 per BEP-3.
155func ts_build_full_bitfield(npc: i64, bf: *u8) -> i64 {
156 let full: i64 = npc / 8
157 let rem: i64 = npc - full*8
158 var bi: i64=0
159 while bi<full { bf[bi]=0xff as u8; bi=bi+1 }
160 var bflen: i64 = full
161 if rem>0 {
162 var mask: i64=0; var pw: i64=128; var k: i64=0
163 while k<rem { mask=mask+pw; pw=pw/2; k=k+1 } // top `rem` bits set, no shifts
164 bf[full]=mask as u8
165 bflen = full+1
166 }
167 return bflen
168}
169// PARTIAL SEED: derive <dir>/download.done from srcpath (<dir>/download.part) and read the per-piece have map
170// (npc bytes, 1=have). Returns 1 = PARTIAL mode (advertise+serve ONLY have pieces -> safe to seed a selective/
171// complete-on-wanted torrent), 0 = FULL mode (no download.done, e.g. a plain seeded file -> advertise all).
172func ts_load_have(srcpath: *u8, npc: i64, have: *u8) -> i64 {
173 let dp: *u8 = sys_mmap(768); var o: i64=0; var lastsl: i64=0; var i: i64=0
174 while srcpath[i]!=(0 as u8) { if srcpath[i]==(47 as u8) { lastsl=i } i=i+1 }
175 i=0; while i<=lastsl { dp[o]=srcpath[i]; o=o+1; i=i+1 }
176 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
177 let fd: i64 = sys_openat_rd(dp); if fd<0 { return 0 }
178 let n: i64 = sys_read(fd, have, npc); sys_close(fd)
179 if n < npc { return 0 } // no/short done file -> FULL mode
180 return 1
181}
182// bitfield (MSB-first per BEP-3) from the have map -- only pieces we actually hold get their bit set.
183func ts_build_bitfield_have(npc: i64, have: *u8, bf: *u8) -> i64 {
184 let nbytes: i64 = (npc+7)/8; var b: i64=0; while b<nbytes { bf[b]=0 as u8; b=b+1 }
185 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 }
186 return nbytes
187}
188
189// Read + parse the peer's 68-byte handshake off a fresh socket. Writes the info_hash the peer WANTS
190// into out_ih (20 bytes) and its peer id into out_pid. Returns 1 ok, 0 on failure. Sets the no-hang timeout.
191func ts_read_hs(afd: i64, out_ih: *u8, out_pid: *u8) -> i64 {
192 sys_set_socket_timeout(afd, TS_PEER_TIMEOUT)
193 let hb: *u8 = sys_mmap(128)
194 if ts_read_n(afd, hb, 68) != 68 { return 0 }
195 if nx_pw_parse_handshake(hb, 68, out_ih, out_pid) != 1 { return 0 }
196 return 1
197}
198
199// ==== THE SEED CORE (post-handshake) ==== reply handshake -> BITFIELD(all) -> UNCHOKE -> serve REQUEST->PIECE
200// from the file. Assumes the peer's handshake is already read+validated. ih = info_hash to echo/serve; srcpath =
201// the file; plen/total/npc = geometry. Returns #blocks served, or -1 on a send failure.
202func ts_serve_after_hs(ctx: *MseCtx, afd: i64, ih: *u8, my_pid: *u8, srcpath: *u8, plen: i64, total: i64, npc: i64) -> i64 {
203 // 2. reply with our handshake (same info_hash) -- via ctx (encrypted if MSE, raw if plaintext)
204 let oh: *u8 = sys_mmap(128); nx_pw_build_handshake(ih, my_pid, oh)
205 if mse_write(ctx, afd, oh, 68) < 0 { return 0-1 }
206 // 3. BITFIELD: advertise ONLY the pieces we hold (PARTIAL mode from download.done) so a selective/
207 // complete-on-wanted torrent seeds honestly; FULL if no download.done (a plain seeded file).
208 let have: *u8 = sys_mmap(npc + 64); let have_loaded: i64 = ts_load_have(srcpath, npc, have)
209 let bf: *u8 = sys_mmap(npc/8 + 64)
210 var bflen: i64 = 0
211 if have_loaded==1 { bflen = ts_build_bitfield_have(npc, have, bf) } else { bflen = ts_build_full_bitfield(npc, bf) }
212 let bfmsg: *u8 = sys_mmap(npc/8 + 128)
213 let bfl: i64 = nx_pw_build_msg(NX_PW_BITFIELD, bf, bflen, bfmsg)
214 if mse_write(ctx, afd, bfmsg, bfl) < 0 { return 0-1 }
215 // 4. UNCHOKE (a seed unchokes leechers so they can request)
216 let un: *u8 = sys_mmap(16); let ul: i64 = nx_pw_build_msg(NX_PW_UNCHOKE, 0 as *u8, 0, un)
217 if mse_write(ctx, afd, un, ul) < 0 { return 0-1 }
218 // 5. request loop -- serve blocks straight from the file
219 let fd: i64 = sys_openat_rd(srcpath); if fd < 0 { return 0-1 }
220 let mb: *u8 = sys_mmap(TS_MSG_CAP)
221 let frame: *u8 = sys_mmap(TS_BLOCK_MAX + 64) // rebuilt per request -> mse_write's in-place encrypt is safe
222 var served: i64 = 0
223 var run: i64 = 1
224 while run == 1 {
225 let mlen: i64 = ts_read_msg_ctx(ctx, afd, mb, TS_MSG_CAP)
226 if mlen == 0-1 { run = 0 } // peer gone / timeout
227 else { if mlen > 0 {
228 let id: i64 = nx_pw_msg_id(mb)
229 if id == NX_PW_REQUEST {
230 let index: i64 = _pw_get_u32(mb, 5)
231 let begin: i64 = _pw_get_u32(mb, 9)
232 let length: i64 = _pw_get_u32(mb, 13)
233 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)
234 if may_serve==1 { if length>0 { if length<=TS_BLOCK_MAX { if index>=0 { if index<npc {
235 let off: i64 = index*plen + begin
236 var rl: i64 = length
237 if off+rl > total { rl = total-off } // clamp to EOF (last piece)
238 if rl > 0 { if off>=0 {
239 // PIECE frame: [len BE][id=7][index 4][begin 4][block rl]
240 _pw_put_u32(frame, 0, 1 + 8 + rl)
241 frame[4] = NX_PW_PIECE as u8
242 _pw_put_u32(frame, 5, index)
243 _pw_put_u32(frame, 9, begin)
244 sys_lseek(fd, off, 0)
245 let dst: *u8 = frame + 13
246 if ts_read_n(fd, dst, rl) == rl { // FILE read stays raw (not the socket)
247 if mse_write(ctx, afd, frame, 13+rl) < 0 { run = 0 } else { served = served+1 }
248 }
249 } }
250 } } } } }
251 }
252 // INTERESTED / NOT_INTERESTED / HAVE / CANCEL / BITFIELD -> ignore (already unchoked, we serve on request)
253 } }
254 }
255 sys_close(fd)
256 return served
257}
258
259// serve ONE connected peer for a SINGLE known torrent (ih must match). DEMUX: 0x13 => plaintext BT handshake;
260// else => encrypted MSE. Both paths converge on ts_serve_after_hs via an MseCtx (plaintext ctx = passthrough).
261func ts_serve_peer(afd: i64, ih: *u8, my_pid: *u8, srcpath: *u8, plen: i64, total: i64, npc: i64) -> i64 {
262 sys_set_socket_timeout(afd, TS_PEER_TIMEOUT)
263 let fb: *u8 = sys_mmap(8); if ts_read_n(afd, fb, 1) != 1 { return 0-1 } // peek the first byte
264 let ctx: *MseCtx = sys_mmap(MSE_CTX_BYTES) as *MseCtx
265 let gih: *u8 = sys_mmap(20); let gpid: *u8 = sys_mmap(20)
266 if mse_classify_first_byte(fb[0] as i64) == 0 {
267 // PLAINTEXT: fb[0]=0x13; read the remaining 67 bytes of the BT handshake
268 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 }
269 if nx_pw_parse_handshake(hb, 68, gih, gpid) != 1 { return 0-1 }
270 if ts_memeq(gih, ih, 20) != 1 { return 0-1 }
271 mse_ctx_plain(ctx)
272 return ts_serve_after_hs(ctx, afd, ih, my_pid, srcpath, plen, total, npc)
273 }
274 // MSE: fb[0] is Ya[0]. Do the encrypted handshake (SKEY = our ih), then read the peer's ENCRYPTED BT handshake.
275 let xpriv: *i64 = sys_mmap((MSE_N+2)*8) as *i64; ts_dh_priv(xpriv)
276 if mse_accept_pfx(afd, ih, xpriv, fb, 1, ctx) != 1 { return 0-1 }
277 let hb: *u8 = sys_mmap(128); if mse_read(ctx, afd, hb, 68) != 68 { return 0-1 }
278 if nx_pw_parse_handshake(hb, 68, gih, gpid) != 1 { return 0-1 }
279 if ts_memeq(gih, ih, 20) != 1 { return 0-1 }
280 return ts_serve_after_hs(ctx, afd, ih, my_pid, srcpath, plen, total, npc)
281}
282
283// row helpers for the registry (tab-separated fields, newline rows)
284func 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 }
285func 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 }
286func ts_field(row: *u8, rlen: i64, idx: i64, out: *u8) -> i64 {
287 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 }
288 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
289}
290// resolve an info_hash (20 raw bytes) in the registry -> srcpath + piece_length + total. returns npc, or -1.
291// registry row = <name>\t<ih_hex40>\t<piece_length>\t<total>\t<srcpath>
292func ts_lookup(reg: *u8, want_ih: *u8, out_src: *u8, out_plen: *i64, out_total: *i64) -> i64 {
293 let buf: *u8 = sys_mmap(TS_MAGIC_1048576)
294 let fd: i64 = sys_openat_rd(reg); if fd<0 { return 0-1 }
295 let blen: i64 = sys_read(fd, buf, TS_MAGIC_1048576); sys_close(fd); if blen<=0 { return 0-1 }
296 let ihhex: *u8 = sys_mmap(64); let rawih: *u8 = sys_mmap(20)
297 let numbuf: *u8 = sys_mmap(32)
298 var ls: i64=0
299 while ls<blen {
300 let le: i64 = ts_eol(buf, ls, blen)
301 let row: *u8 = ((buf as i64)+ls) as *u8; let rlen: i64 = le-ls
302 ts_field(row, rlen, 1, ihhex)
303 if ts_hex2bin(ihhex, rawih)==1 { if ts_memeq(rawih, want_ih, 20)==1 {
304 ts_field(row, rlen, 2, numbuf); let plen: i64 = ts_atoi(numbuf); out_plen[0]=plen
305 ts_field(row, rlen, 3, numbuf); let total: i64 = ts_atoi(numbuf); out_total[0]=total
306 ts_field(row, rlen, 4, out_src)
307 if plen<=0 { return 0-1 }
308 return (total + plen - 1) / plen
309 } }
310 ls = le+1
311 }
312 return 0-1
313}
314
315// MSE registry SKEY-scan: find the registered info_hash whose HASH(req2,ih)==target (from mse_skey_target).
316// Writes that ih (20 raw bytes) to out_ih + its srcpath/plen/total. Returns npc, or -1 if none match.
317func ts_registry_scan(reg: *u8, target: *u8, out_ih: *u8, out_src: *u8, out_plen: *i64, out_total: *i64) -> i64 {
318 let buf: *u8 = sys_mmap(TS_MAGIC_1048576)
319 let fd: i64 = sys_openat_rd(reg); if fd<0 { return 0-1 }
320 let blen: i64 = sys_read(fd, buf, TS_MAGIC_1048576); sys_close(fd); if blen<=0 { return 0-1 }
321 let ihhex: *u8 = sys_mmap(64); let rawih: *u8 = sys_mmap(20); let numbuf: *u8 = sys_mmap(32)
322 var ls: i64=0
323 while ls<blen {
324 let le: i64 = ts_eol(buf, ls, blen)
325 let row: *u8 = ((buf as i64)+ls) as *u8; let rlen: i64 = le-ls
326 ts_field(row, rlen, 1, ihhex)
327 if ts_hex2bin(ihhex, rawih)==1 { if mse_skey_matches(rawih, target)==1 {
328 var i: i64=0; while i<20 { out_ih[i]=rawih[i]; i=i+1 }
329 ts_field(row, rlen, 2, numbuf); let plen: i64 = ts_atoi(numbuf); out_plen[0]=plen
330 ts_field(row, rlen, 3, numbuf); let total: i64 = ts_atoi(numbuf); out_total[0]=total
331 ts_field(row, rlen, 4, out_src)
332 if plen<=0 { return 0-1 }
333 return (total + plen - 1) / plen
334 } }
335 ls = le+1
336 }
337 return 0-1
338}
339
340// serve ONE connected peer, resolving the torrent by info_hash against the registry. DEMUX: 0x13 => plaintext
341// (peer's handshake carries the info_hash -> ts_lookup); else => MSE (DH, then SKEY-scan identifies the torrent).
342func ts_serve_peer_registry(afd: i64, my_pid: *u8, reg: *u8) -> i64 {
343 sys_set_socket_timeout(afd, TS_PEER_TIMEOUT)
344 let fb: *u8 = sys_mmap(8); if ts_read_n(afd, fb, 1) != 1 { return 0-1 }
345 let ctx: *MseCtx = sys_mmap(MSE_CTX_BYTES) as *MseCtx
346 let gih: *u8 = sys_mmap(20); let gpid: *u8 = sys_mmap(20)
347 let src: *u8 = sys_mmap(TS_MAGIC_1024); let plenb: *i64 = sys_mmap(16) as *i64; let totalb: *i64 = sys_mmap(16) as *i64
348 if mse_classify_first_byte(fb[0] as i64) == 0 {
349 // PLAINTEXT
350 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 }
351 if nx_pw_parse_handshake(hb, 68, gih, gpid) != 1 { return 0-1 }
352 let npc: i64 = ts_lookup(reg, gih, src, plenb, totalb)
353 if npc < 0 { return 0-2 }
354 mse_ctx_plain(ctx)
355 return ts_serve_after_hs(ctx, afd, gih, my_pid, src, plenb[0], totalb[0], npc)
356 }
357 // MSE: DH phase (stream-syncs past the peer's PadA), then identify the torrent by SKEY-scan
358 let xpriv: *i64 = sys_mmap((MSE_N+2)*8) as *i64; ts_dh_priv(xpriv)
359 let sbytes: *u8 = sys_mmap(128); let xr: *u8 = sys_mmap(24)
360 if mse_accept_dh(afd, xpriv, fb, 1, sbytes, xr) != 1 { return 0-1 }
361 let target: *u8 = sys_mmap(24); mse_skey_target(sbytes, xr, target)
362 let npc: i64 = ts_registry_scan(reg, target, gih, src, plenb, totalb)
363 if npc < 0 { return 0-2 } // no registered torrent matched -> not for us
364 if mse_accept_finish(afd, gih, sbytes, ctx) != 1 { return 0-1 }
365 let hb: *u8 = sys_mmap(128); if mse_read(ctx, afd, hb, 68) != 68 { return 0-1 } // peer's encrypted BT handshake
366 let g2: *u8 = sys_mmap(20); let gp2: *u8 = sys_mmap(20)
367 if nx_pw_parse_handshake(hb, 68, g2, gp2) != 1 { return 0-1 }
368 if ts_memeq(g2, gih, 20) != 1 { return 0-1 }
369 return ts_serve_after_hs(ctx, afd, gih, my_pid, src, plenb[0], totalb[0], npc)
370}
371
372// registry seeding daemon: LISTEN forever, per peer resolve the requested info_hash in the registry + serve it.
373// One port serves EVERY registered torrent -- the real multi-torrent seed. Fork-per-peer + WNOHANG reap.
374func ts_accept_loop_registry(port: i64, my_pid: *u8, reg: *u8, max_conns: i64) -> i64 {
375 let sa: *u8 = sys_mmap(16); ts_sockaddr(sa, port, 0, 0, 0, 0) // INADDR_ANY (inbound peers)
376 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
377 if lfd < 0 { ts_w(1, "nx_torrent_seed SOCKET-FAIL\n" as *u8); sys_exit(1); return 1 }
378 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
379 sys_setsockopt(lfd, SOL_SOCKET, 2, one, 4) // SO_REUSEADDR
380 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 }
381 if sys_listen(lfd, 16) < 0 { ts_w(1, "nx_torrent_seed LISTEN-FAIL\n" as *u8); sys_exit(1); return 1 }
382 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)
383 let rg_ipf: *i64 = sys_mmap(TS_IPF_MAXP*16 + 64) as *i64
384 let rg_ipf_n: i64 = ipf_load(TS_IPF_PATH, rg_ipf, TS_IPF_MAXP)
385 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) }
386 let st: *i64 = sys_mmap(16) as *i64
387 let maxlive: i64 = ts_max_live()
388 ts_w(1, "nx_torrent_seed[reg] max_live=" as *u8); ts_wn(1, maxlive); ts_w(1, "\n" as *u8)
389 var live: i64 = 0
390 var satlogged: i64 = 0
391 var served_conns: i64 = 0
392 var consec: i64 = 0
393 var run: i64 = 1
394 while run == 1 {
395 // ★REAP FIRST, AND READ THE OUTCOME. Reaping used to discard the exit status entirely, and the child
396 // exited 0 unconditionally, so the parent could never learn that every handshake was failing.
397 var reaped: i64 = sys_wait4(0-1, st, 1)
398 while reaped > 0 {
399 if live > 0 { live = live - 1 }
400 if ((st[0] >> 8) & 0xff) == 0 { consec = 0 } else { consec = consec + 1 }
401 reaped = sys_wait4(0-1, st, 1)
402 }
403 let d: i64 = ts_backoff_ms(consec)
404 if d > 0 { sys_sleep_ms(d) }
405 let afd: i64 = sys_accept(lfd)
406 // ★THE MISSING BRANCH. `if afd >= 0 {...}` with no else meant a failing accept re-looped instantly.
407 if afd < 0 { consec = consec + 1 } else {
408 if ts_ipf_reject(afd, rg_ipf, rg_ipf_n) == 1 { sys_close(afd) } else {
409 if live >= maxlive {
410 // saturated: refuse the connection outright (no DH burn); peers retry. Log once per episode.
411 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 }
412 sys_close(afd)
413 } else {
414 satlogged = 0
415 let pid: i64 = sys_fork()
416 if pid == 0 {
417 sys_close(lfd)
418 let n: i64 = ts_serve_peer_registry(afd, my_pid, reg)
419 ts_w(1, "nx_torrent_seed[reg] served blocks=" as *u8); ts_wn(1, n); ts_w(1, "\n" as *u8)
420 sys_close(afd)
421 // ★THE EXIT CODE IS THE SIGNAL. Without it the parent is blind to a fast-failing serve.
422 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.
423 sys_exit(0)
424 }
425 sys_close(afd)
426 live = live + 1
427 served_conns = served_conns + 1
428 if max_conns > 0 { if served_conns >= max_conns { sys_wait4(0-1, st, 0); run = 0 } }
429 }
430 } }
431 }
432 sys_close(lfd)
433 return 0
434}
435
436// ---- ACCEPT BACKOFF POLICY ----
437// ★WHY THIS EXISTS (measured live 2026-07-30). Both accept loops had NO negative branch on sys_accept:
438// `if afd >= 0 { ... }` with no else, so a persistently failing accept (EMFILE, ENFILE, ECONNABORTED)
439// re-looped with ZERO delay -- an unconditional 100%-of-a-core spin. Separately the forked child called
440// sys_exit(0) UNCONDITIONALLY, so the parent could never learn that ts_serve_peer_registry had returned -2;
441// /tmp/seed.log showed `served blocks=-2` repeating without end while the parent forked again immediately,
442// forever. A sibling measured nx_torrent_seed at 71.6% of a core and named the signature exactly: a hot
443// accept loop with no errno branch and no backoff.
444// ★THE maxlive SATURATION GUARD ABOVE DOES NOT COVER THIS. It bounds CONCURRENT handlers; when handlers
445// fail INSTANTLY, `live` never rises, so the loop forks flat out and stays under the cap the whole time.
446// A limit on concurrency is not a limit on RATE.
447// ★ONE MECHANISM FOR BOTH FAULTS, because they are the same failure: work is attempted as fast as the CPU
448// allows and none of it succeeds. A failed accept and a fast-failing serve feed one counter.
449// ★PURE FUNCTION ON PURPOSE -- same discipline as `hc_keep_delay` and `ba_verdict`. A policy welded into an
450// accept loop beside its own syscalls cannot be tested without putting a host into the required state, and
451// "every peer handshake fails" is not a condition you can summon on a shared box. That is how it survived.
452// ★NEVER STOPS ACCEPTING, ALWAYS SELF-HEALS: the delay is capped and ONE successful serve resets it to 0.
453const TS_BK_TOL: i64 = 8 // real peers abort mid-handshake; tolerate a burst before slowing at all
454const TS_BK_N2: i64 = 64
455const TS_BK_N3: i64 = 512
456const TS_BK_D1: i64 = 50
457const TS_BK_D2: i64 = 250
458const TS_BK_D3: i64 = 1000 // ceiling: one attempt per second is still a live seeder, not a dead one
459func ts_backoff_ms(consec_fail: i64) -> i64 {
460 if consec_fail < TS_BK_TOL { return 0 }
461 if consec_fail < TS_BK_N2 { return TS_BK_D1 }
462 if consec_fail < TS_BK_N3 { return TS_BK_D2 }
463 return TS_BK_D3
464}
465// The pre-fix behaviour, kept ONLY so the gate can prove the defect was real (see hc_keep_delay's twin).
466func ts_backoff_ms_old(consec_fail: i64) -> i64 { return 0 }
467
468// ---- daemon: accept loop, fork per peer, reap zombies ----
469// max_conns > 0 -> serve that many accepted peers then exit (orphan-safe one-shot for demos/tests);
470// max_conns == 0 -> run forever (the real seeding daemon).
471func ts_accept_loop(port: i64, ih: *u8, my_pid: *u8, src: *u8, plen: i64, total: i64, npc: i64, max_conns: i64) -> i64 {
472 let sa: *u8 = sys_mmap(16); ts_sockaddr(sa, port, 0, 0, 0, 0) // INADDR_ANY
473 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
474 if lfd < 0 { ts_w(1, "nx_torrent_seed SOCKET-FAIL\n" as *u8); sys_exit(1); return 1 }
475 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
476 sys_setsockopt(lfd, SOL_SOCKET, 2, one, 4) // SO_REUSEADDR
477 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 }
478 if sys_listen(lfd, 16) < 0 { ts_w(1, "nx_torrent_seed LISTEN-FAIL\n" as *u8); sys_exit(1); return 1 }
479 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)
480 let al_ipf: *i64 = sys_mmap(TS_IPF_MAXP*16 + 64) as *i64
481 let al_ipf_n: i64 = ipf_load(TS_IPF_PATH, al_ipf, TS_IPF_MAXP)
482 let st: *i64 = sys_mmap(16) as *i64
483 let maxlive: i64 = ts_max_live()
484 var live: i64 = 0
485 var satlogged: i64 = 0
486 var served_conns: i64 = 0
487 var run: i64 = 1
488 var consec: i64 = 0
489 while run == 1 {
490 // reap finished handlers (WNOHANG) so we never accumulate zombies -- and LEARN from the exit code
491 var reaped: i64 = sys_wait4(0-1, st, 1)
492 while reaped > 0 {
493 if live > 0 { live = live - 1 }
494 if ((st[0] >> 8) & 0xff) == 0 { consec = 0 } else { consec = consec + 1 }
495 reaped = sys_wait4(0-1, st, 1)
496 }
497 let d: i64 = ts_backoff_ms(consec)
498 if d > 0 { sys_sleep_ms(d) }
499 let afd: i64 = sys_accept(lfd)
500 if afd < 0 { consec = consec + 1 } else {
501 if ts_ipf_reject(afd, al_ipf, al_ipf_n) == 1 { sys_close(afd) } else {
502 if live >= maxlive {
503 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 }
504 sys_close(afd)
505 } else {
506 satlogged = 0
507 let pid: i64 = sys_fork()
508 if pid == 0 {
509 sys_close(lfd)
510 let n: i64 = ts_serve_peer(afd, ih, my_pid, src, plen, total, npc)
511 ts_w(1, "nx_torrent_seed served blocks=" as *u8); ts_wn(1, n); ts_w(1, "\n" as *u8)
512 sys_close(afd)
513 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.
514 sys_exit(0)
515 }
516 sys_close(afd)
517 live = live + 1
518 served_conns = served_conns + 1
519 if max_conns > 0 { if served_conns >= max_conns {
520 // wait for the in-flight handler to finish, then exit cleanly (no orphan)
521 sys_wait4(0-1, st, 0)
522 run = 0
523 } }
524 }
525 } }
526 }
527 sys_close(lfd)
528 return 0
529}
530
531func main(argc: i64, argv: *i64) -> i64 {
532 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 }
533 let mode: *u8 = argv[1] as *u8
534 // our peer id: "-NX0001-" + 12 filler bytes (Azureus-style client tag, deterministic)
535 let my_pid: *u8 = sys_mmap(20)
536 let tag: *u8 = "-NX0001-nishiseed01" as *u8
537 var z: i64=0; while z<19 { my_pid[z]=tag[z]; z=z+1 } my_pid[19]=48 as u8
538 if mode[0] == (115 as u8) { if mode[5] == (49 as u8) {
539 // "serve1": single torrent from args (optional trailing max_conns; 0/absent = forever)
540 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 }
541 let port: i64 = ts_atoi(argv[2] as *u8)
542 let ih: *u8 = sys_mmap(20)
543 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 }
544 let plen: i64 = ts_atoi(argv[4] as *u8)
545 let total: i64 = ts_atoi(argv[5] as *u8)
546 let src: *u8 = argv[6] as *u8
547 var maxc: i64 = 0
548 if argc >= 8 { maxc = ts_atoi(argv[7] as *u8) }
549 let npc: i64 = (total + plen - 1) / plen
550 return ts_accept_loop(port, ih, my_pid, src, plen, total, npc, maxc)
551 } else {
552 // "serve [port] [registry] [max_conns]": the real multi-torrent seeding daemon (serves every registered info_hash)
553 var port: i64 = TS_MAGIC_6881
554 if argc >= 3 { port = ts_atoi(argv[2] as *u8) }
555 var reg: *u8 = TS_REG_DEFAULT
556 if argc >= 4 { reg = argv[3] as *u8 }
557 var maxc: i64 = 0
558 if argc >= 5 { maxc = ts_atoi(argv[4] as *u8) }
559 return ts_accept_loop_registry(port, my_pid, reg, maxc)
560 } }
561 ts_w(1, "usage: nx_torrent_seed serve [port] [registry] | serve1 <port> <ih_hex40> <plen> <total> <src> [max_conns]\n" as *u8)
562 sys_exit(2); return 2
563}