code wiki / (root) / nx_torrent_leech_probe.nx

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}