code wiki / (root) / nx_torrent_seed.nx

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}