nx_torrent_leech_probe.nx source
↩ module page · 204 lines · 12462 B
1// nx_torrent_leech_probe.nx -- pull a WHOLE file off a BitTorrent seeder + verify every piece.
2//
3// Two jobs, one organ:
4// (GATE, no args) = the INTEGRATION proof: originate a torrent (nx_torrent_mkinfo) -> seed it
5// (nx_torrent_seed.ts_serve_peer) -> leech the FULL file back -> verify EACH piece's
6// SHA-1 against the info-dict's ADVERTISED hashes (what a real client checks) -> assert
7// the reassembled file's info_hash == the original's. Proves originate->seed->leech->verify.
8// (LIVE) <ip> <port> <ih_hex40> <plen> <total> <outfile>
9// = connect a REAL remote seeder (e.g. the NAS), download the file, write it, and print
10// the reassembled file's info_hash (compare to the seed's -> byte-perfect P2P transfer).
11//
12// license_tier: ORIGINAL module: nishi-core.torrent.leech_probe
13// depends: nishi-core.torrent.seed, nishi-core.torrent.mkinfo, nishi-core.torrent.peerwire, nishi-core.crypto.sha1
14import "nx_torrent_seed.nx"
15import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
16import "nx_torrent_mkinfo.nx"
17import "nx_peerwire.nx"
18import "nx_sha1.nx"
19const LP_MAGIC_65536: i64 = 65536
20const LP_MAGIC_4096: i64 = 4096
21const LP_MAGIC_53611: i64 = 53611
22
23const LP_BLK: i64 = 16384
24
25func lp_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
26func lp_wn(v: i64) -> i64 {
27 let t: *u8=sys_mmap(28); var m: i64=v; if m<0 {m=0-m; sys_write(1,"-" as *u8,1)}
28 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}
29 let b: *u8=sys_mmap(28); var i: i64=0; while i<k {b[i]=t[k-1-i]; i=i+1}; sys_write(1,b,k); return 0
30}
31
32// Leech the whole file from an already-connected seeder socket cfd.
33// ih=20-byte info_hash, my_pid=our id, plen/total/npc = geometry, outbuf = where to assemble (total bytes),
34// expect_pieces = optional npc*20 advertised hashes (0 as *u8 to skip verify). Returns #pieces verified,
35// or -1 on handshake failure. Sets *got_all=1 if every piece downloaded.
36func lp_leech(ctx: *MseCtx, cfd: i64, ih: *u8, my_pid: *u8, plen: i64, total: i64, npc: i64, outbuf: *u8, expect_pieces: *u8, got_all: *i64) -> i64 {
37 sys_set_socket_timeout(cfd, 15)
38 got_all[0] = 0
39 let ch: *u8 = sys_mmap(128); nx_pw_build_handshake(ih, my_pid, ch)
40 if mse_write(ctx, cfd, ch, 68) < 0 { return 0-1 }
41 let ph: *u8 = sys_mmap(128)
42 if mse_read(ctx, cfd, ph, 68) != 68 { return 0-1 }
43 let gih: *u8 = sys_mmap(20); let gpid: *u8 = sys_mmap(20)
44 if nx_pw_parse_handshake(ph, 68, gih, gpid) != 1 { return 0-1 }
45 // interested + wait for unchoke (capture the bitfield to know what the seed has)
46 let im: *u8 = sys_mmap(16); let il: i64 = nx_pw_build_msg(NX_PW_INTERESTED, 0 as *u8, 0, im); mse_write(ctx, cfd, im, il)
47 let mb: *u8 = sys_mmap(LP_MAGIC_65536)
48 var unchoked: i64=0; var guard: i64=0
49 let have_bf: *u8 = sys_mmap(npc/8 + 16); var bf_len: i64=0
50 while unchoked==0 { if guard>=16 { guard=99 } else {
51 let ml: i64 = ts_read_msg_ctx(ctx, cfd, mb, LP_MAGIC_65536)
52 if ml==0-1 { unchoked=0-1 } else { if ml>0 {
53 let id: i64 = nx_pw_msg_id(mb)
54 if id==NX_PW_BITFIELD { bf_len=ml-1; var b: i64=0; while b<bf_len { have_bf[b]=mb[5+b]; b=b+1 } }
55 if id==NX_PW_UNCHOKE { unchoked=1 }
56 } }
57 guard=guard+1
58 } }
59 if unchoked!=1 { return 0-1 }
60 // download each piece the seed advertises (a full seed advertises all)
61 let piece: *u8 = sys_mmap(plen+64)
62 var verified: i64=0; var downloaded: i64=0
63 var i: i64=0
64 while i<npc {
65 var seed_has: i64=1
66 if bf_len>0 { if nx_pw_bitfield_has(have_bf, bf_len, i)!=1 { seed_has=0 } }
67 if seed_has==1 {
68 var psize: i64=plen; if i==npc-1 { psize=total-i*plen }
69 var begin: i64=0; var piece_ok: i64=1
70 while begin<psize {
71 var blk: i64=LP_BLK; if blk>psize-begin { blk=psize-begin }
72 let rq: *u8 = sys_mmap(32); let rl: i64 = nx_pw_build_request(i, begin, blk, rq); mse_write(ctx, cfd, rq, rl)
73 let pb: *u8 = sys_mmap(LP_BLK+128); let pl: i64 = ts_read_msg_ctx(ctx, cfd, pb, LP_BLK+128)
74 if pl>=9 { if nx_pw_msg_id(pb)==NX_PW_PIECE {
75 let bgn: i64 = _pw_get_u32(pb, 9)
76 var j: i64=0; while j<blk { piece[bgn+j]=pb[13+j]; j=j+1 }
77 } else { piece_ok=0; begin=psize } } else { piece_ok=0; begin=psize }
78 begin=begin+blk
79 }
80 if piece_ok==1 {
81 downloaded=downloaded+1
82 var k: i64=0; while k<psize { outbuf[i*plen+k]=piece[k]; k=k+1 }
83 if (expect_pieces as i64)!=0 {
84 let gh: *u8 = sys_mmap(20); sha1(piece, psize, gh)
85 let ep: *u8 = expect_pieces + i*20
86 var eq: i64=1; var m: i64=0; while m<20 { if gh[m]!=ep[m] { eq=0; m=20 } else { m=m+1 } }
87 if eq==1 { verified=verified+1 }
88 }
89 }
90 }
91 i=i+1
92 }
93 if downloaded==npc { got_all[0]=1 }
94 return verified
95}
96
97func lp_sockaddr_ip(sa: *u8, port: i64, ipstr: *u8) -> i64 {
98 // parse a dotted IPv4 into 4 octets
99 var oct: *u8 = sys_mmap(4); var o: i64=0; var v: i64=0; var i: i64=0
100 while ipstr[i]!=(0 as u8) { let c: i64=ipstr[i] as i64; if c==46 { oct[o]=v as u8; o=o+1; v=0 } else { if c>=48 { if c<=57 { v=v*10+(c-48) } } } i=i+1 }
101 oct[o]=v as u8
102 return ts_sockaddr(sa, port, oct[0] as i64, oct[1] as i64, oct[2] as i64, oct[3] as i64)
103}
104
105// ---- LIVE: pull from a remote seeder, write outfile, print reassembled info_hash ----
106func lp_live(ipstr: *u8, port: i64, ihhex: *u8, plen: i64, total: i64, outfile: *u8, mse: i64) -> i64 {
107 let ih: *u8 = sys_mmap(20)
108 if ts_hex2bin(ihhex, ih) != 1 { lp_w("bad info_hash hex\n" as *u8); sys_exit(2); return 2 }
109 let my_pid: *u8 = sys_mmap(20); let tag: *u8 = "-NX0001-nishileech1" as *u8
110 var z: i64=0; while z<19 { my_pid[z]=tag[z]; z=z+1 } my_pid[19]=48 as u8
111 let npc: i64 = (total+plen-1)/plen
112 let sa: *u8 = sys_mmap(16); lp_sockaddr_ip(sa, port, ipstr)
113 let cfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0); sys_set_socket_timeout(cfd, 15)
114 lp_w("leech-probe: connecting " as *u8); lp_w(ipstr); lp_w(":" as *u8); lp_wn(port); if mse==1 { lp_w(" [MSE-encrypted]" as *u8) } lp_w(" ...\n" as *u8)
115 if nx_connect_bounded(cfd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { lp_w("CONNECT-FAIL\n" as *u8); sys_exit(1); return 1 }
116 let ctx: *MseCtx = sys_mmap(MSE_CTX_BYTES) as *MseCtx
117 if mse==1 {
118 let xpriv: *i64 = sys_mmap((MSE_N+2)*8) as *i64; ts_dh_priv(xpriv)
119 if mse_connect(cfd, ih, xpriv, ctx) != 1 { lp_w("MSE-HANDSHAKE-FAIL\n" as *u8); sys_close(cfd); sys_exit(1); return 1 }
120 lp_w("leech-probe: MSE handshake OK (RC4-encrypted stream)\n" as *u8)
121 } else { mse_ctx_plain(ctx) }
122 let outbuf: *u8 = sys_mmap(total+64)
123 let ga: *i64 = sys_mmap(8) as *i64
124 let v: i64 = lp_leech(ctx, cfd, ih, my_pid, plen, total, npc, outbuf, 0 as *u8, ga)
125 sys_close(cfd)
126 if v==0-1 { lp_w("HANDSHAKE/UNCHOKE-FAIL\n" as *u8); sys_exit(1); return 1 }
127 // write the downloaded file
128 let wf: i64 = sys_openat_wr(outfile, 0x1a4); if wf>=0 { sys_write(wf, outbuf, total); sys_close(wf) }
129 lp_w("downloaded pieces got_all=" as *u8); lp_wn(ga[0]); lp_w(" bytes=" as *u8); lp_wn(total); lp_w(" -> " as *u8); lp_w(outfile); lp_w("\n" as *u8)
130 // reassembled info_hash (compare to the seed's -> byte-perfect transfer)
131 let ih2: *u8 = sys_mmap(20); let info: *u8 = sys_mmap(npc*20+LP_MAGIC_4096); let pieces: *u8 = sys_mmap(npc*20+64)
132 mk_build_info(outfile, "downloaded" as *u8, plen, total, npc, info, pieces, ih2)
133 // NOTE: info_hash depends on 'name' too, so this recomputes with name="downloaded"; to match the seed's
134 // exact info_hash pass the SAME name. Here we print the raw per-file identity for a same-name compare.
135 let hx: *u8 = sys_mmap(48); mk_hex(hx,0,ih2,20); hx[40]=0 as u8
136 lp_w("reassembled_info_hash(name=downloaded)=" as *u8); lp_w(hx); lp_w("\n" as *u8)
137 if ga[0]==1 { lp_w("verdict=GREEN (full file pulled from the remote seeder)\n" as *u8); sys_exit(0); return 0 }
138 lp_w("verdict=PARTIAL\n" as *u8); sys_exit(1); return 1
139}
140
141// ---- GATE: originate -> seed -> leech FULL file -> verify each piece vs advertised hashes ----
142func lp_gate() -> i64 {
143 let path: *u8 = "/tmp/_nx_leech_src.bin" as *u8
144 let name: *u8 = "leech_src.bin" as *u8
145 let plen: i64 = 64
146 let total: i64 = 200 // 4 pieces (64,64,64,8)
147 let PORT: i64 = LP_MAGIC_53611
148 let fb: *u8 = sys_mmap(total+16); var i: i64=0; while i<total { fb[i]=((i*97+5)&0xff) as u8; i=i+1 }
149 let wf: i64 = sys_openat_wr(path, 0x1a4); if wf<0 { lp_w("LEECH-GATE file=FAIL verdict=RED\n" as *u8); sys_exit(1); return 1 }
150 sys_write(wf, fb, total); sys_close(wf)
151 let npc: i64 = (total+plen-1)/plen
152 // originate: real info_hash + per-piece hashes
153 let info: *u8 = sys_mmap(npc*20+LP_MAGIC_4096); let pieces: *u8 = sys_mmap(npc*20+64); let ih: *u8 = sys_mmap(20)
154 let ilen: i64 = mk_build_info(path, name, plen, total, npc, info, pieces, ih)
155 var mk_ok: i64=0; if ilen>0 { mk_ok=1 }
156 let seed_pid: *u8 = sys_mmap(20); i=0; while i<20 { seed_pid[i]=83 as u8; i=i+1 }
157 let leech_pid: *u8 = sys_mmap(20); i=0; while i<20 { leech_pid[i]=76 as u8; i=i+1 }
158 let sa: *u8 = sys_mmap(16); ts_sockaddr(sa, PORT, 127, 0, 0, 1)
159 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
160 var bound: i64=0
161 if lfd>=0 { 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; sys_setsockopt(lfd, SOL_SOCKET, 2, one, 4); sys_set_socket_timeout(lfd, 5); if sys_bind(lfd, sa, 16)>=0 { if sys_listen(lfd, 4)>=0 { bound=1 } } }
162 if bound==0 { lp_w("LEECH-GATE bound=0 verdict=RED\n" as *u8); sys_exit(1); return 1 }
163 let pid: i64 = sys_fork()
164 if pid==0 {
165 let afd: i64 = sys_accept(lfd)
166 if afd>=0 { ts_serve_peer(afd, ih, seed_pid, path, plen, total, npc); sys_close(afd) }
167 sys_close(lfd); sys_exit(0)
168 }
169 let cfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0); sys_set_socket_timeout(cfd, 5)
170 var conn_ok: i64=0; if nx_connect_bounded(cfd, sa, 16, NX_CONN_DEFAULT_MS)>=0 { conn_ok=1 }
171 let outbuf: *u8 = sys_mmap(total+64); let ga: *i64 = sys_mmap(8) as *i64
172 let ctx: *MseCtx = sys_mmap(MSE_CTX_BYTES) as *MseCtx; mse_ctx_plain(ctx) // gate leecher is plaintext (no-regression)
173 var verified: i64=0
174 if conn_ok==1 { verified = lp_leech(ctx, cfd, ih, leech_pid, plen, total, npc, outbuf, pieces, ga) }
175 sys_close(cfd)
176 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0); sys_close(lfd)
177 // reassembled file byte-equal to source?
178 var file_eq: i64=1; i=0; while i<total { if outbuf[i]!=fb[i] { file_eq=0; i=total } else { i=i+1 } }
179 lp_w("LEECH-GATE authored=organ originate=" as *u8); lp_wn(mk_ok)
180 lp_w(" conn=" as *u8); lp_wn(conn_ok)
181 lp_w(" got_all=" as *u8); lp_wn(ga[0])
182 lp_w(" pieces_verified=" as *u8); lp_wn(verified); lp_w("/" as *u8); lp_wn(npc)
183 lp_w(" file_eq_source=" as *u8); lp_wn(file_eq)
184 var allok: i64=0
185 if mk_ok==1 { if conn_ok==1 { if ga[0]==1 { if verified==npc { if file_eq==1 { allok=1 } } } } }
186 if allok==1 { lp_w(" verdict=GREEN (originate->seed->leech, every piece sha1-verified vs advertised)\n" as *u8); sys_exit(0); return 0 }
187 lp_w(" verdict=RED\n" as *u8); sys_exit(1); return 1
188}
189
190func main(argc: i64, argv: *i64) -> i64 {
191 if argc < 2 { return lp_gate() }
192 // optional leading "mse" token selects the encrypted path: nx_torrent_leech_probe mse <ip> <port> ...
193 var base: i64 = 1; var mse: i64 = 0
194 let a1: *u8 = argv[1] as *u8
195 if a1[0]==(109 as u8) { if a1[1]==(115 as u8) { if a1[2]==(101 as u8) { mse=1; base=2 } } } // "mse"
196 if argc < base+6 { lp_w("usage: nx_torrent_leech_probe [mse] <ip> <port> <ih_hex40> <plen> <total> <outfile>\n" as *u8); sys_exit(2); return 2 }
197 let ipstr: *u8 = argv[base] as *u8
198 let port: i64 = mk_atoi(argv[base+1] as *u8)
199 let ihhex: *u8 = argv[base+2] as *u8
200 let plen: i64 = mk_atoi(argv[base+3] as *u8)
201 let total: i64 = mk_atoi(argv[base+4] as *u8)
202 let outfile: *u8 = argv[base+5] as *u8
203 return lp_live(ipstr, port, ihhex, plen, total, outfile, mse)
204}