code wiki / (root) / nx_lsd.nx

nx_lsd.nx source

↩ module page · 131 lines · 10197 B

1// nx_lsd.nx -- SOVEREIGN BitTorrent Local Service Discovery (BEP-14): find/announce peers on the LAN via 2// multicast 239.192.152.143:6771, no tracker. Two halves: ANNOUNCE (multicast BT-SEARCH for each seeded 3// info_hash) + LISTEN (join the group, receive BT-SEARCH, map the info_hash -> a local download via 4// SHA1(download.meta), append the sender's ip:port to that download.pex -> the worker's peer set folds it in). 5// Verbs: announce <ih_hex> [port] | announceall <seed_registry> [port] | listen <area..> <seconds> | selftest. 6// license_tier: ORIGINAL depends: nx_syscalls, nx_sha1 7import "nx_syscalls.nx" 8import "nx_sha1.nx" 9const LSD_MAGIC_6881: i64 = 6881 10const LSD_MAGIC_131072: i64 = 131072 11const LSD_MAGIC_131071: i64 = 131071 12const LSD_MAGIC_2048: i64 = 2048 13const LSD_MAGIC_2047: i64 = 2047 14 15const LSD_PORT: i64 = 6771 16// group 239.192.152.143 octets 17func lsd_group(oct: *u8) -> i64 { oct[0]=239 as u8; oct[1]=192 as u8; oct[2]=152 as u8; oct[3]=143 as u8; return 0 } 18 19func lp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 20func lpn(v: i64) -> i64 { if v==0 { sys_write(1,"0" as *u8,1); return 0 } var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m } let t: *u8=sys_mmap(28); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } while k>0 { k=k-1; sys_write(1,(((t as i64)+k) as *u8),1) } return 0 } 21func lsd_sock4(out: *u8, port: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 22 out[0]=2 as u8; out[1]=0 as u8; out[2]=((port>>8)&0xff) as u8; out[3]=(port&0xff) as u8 23 out[4]=a as u8; out[5]=b as u8; out[6]=c as u8; out[7]=d as u8 24 var i: i64=8; while i<16 { out[i]=0 as u8; i=i+1 } return 16 25} 26func lsd_app(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { dst[o]=s[i]; o=o+1; i=i+1 } return o } 27func lsd_appn(dst: *u8, o: i64, v: i64) -> i64 { if v==0 { dst[o]=48 as u8; return o+1 } var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } while k>0 { k=k-1; dst[o]=t[k]; o=o+1 } return o } 28func lsd_find(buf: *u8, n: i64, start: i64, ndl: *u8, nl: i64) -> i64 { if nl<=0 { return 0-1 } var s: i64=start; while s+nl<=n { var m: i64=1; var k: i64=0; while k<nl { if buf[s+k]!=ndl[k] { m=0; k=nl } else { k=k+1 } } if m==1 { return s } s=s+1 } return 0-1 } 29func lsd_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } if b[i]!=(0 as u8) { return 0 } return 1 } 30func lsd_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8) { if s[i]>=(48 as u8) { if s[i]<=(57 as u8) { v=v*10+((s[i] as i64)-48) } } i=i+1 } return v } 31 32// build the BEP-14 BT-SEARCH announce into out; returns length. 33func lsd_build(ih_hex: *u8, port: i64, out: *u8) -> i64 { 34 var o: i64=0 35 o=lsd_app(out,o,"BT-SEARCH * HTTP/1.1\r\nHost: 239.192.152.143:6771\r\nPort: " as *u8); o=lsd_appn(out,o,port) 36 o=lsd_app(out,o,"\r\nInfohash: " as *u8); o=lsd_app(out,o,ih_hex) 37 o=lsd_app(out,o,"\r\ncookie: nishi\r\n\r\n\r\n" as *u8) 38 return o 39} 40// parse a received BT-SEARCH -> out_ih_hex (40 + NUL), out_port. returns 1 if both found. 41func lsd_parse(buf: *u8, n: i64, out_ih: *u8, out_port: *i64) -> i64 { 42 let ip: i64 = lsd_find(buf, n, 0, "Infohash:" as *u8, 9); if ip<0 { return 0 } 43 var vs: i64=ip+9; while vs<n { if buf[vs]==(32 as u8) { vs=vs+1 } else { break } } 44 var c: i64=0; while c<40 { if vs+c>=n { return 0 } let ch: i64=buf[vs+c] as i64; var ok: i64=0; if ch>=48 { if ch<=57 { ok=1 } } if ch>=97 { if ch<=102 { ok=1 } } if ch>=65 { if ch<=70 { ok=1 } } if ok==0 { return 0 } out_ih[c]=buf[vs+c]; c=c+1 } out_ih[40]=0 as u8 45 let pp: i64 = lsd_find(buf, n, 0, "Port:" as *u8, 5); if pp<0 { return 0 } 46 var ps: i64=pp+5; var pv: i64=0; var seen: i64=0 47 while ps<n { let ch2: i64=buf[ps] as i64; if ch2>=48 { if ch2<=57 { pv=pv*10+(ch2-48); seen=1 } } if ch2==13 { ps=n } if ch2==10 { ps=n } ps=ps+1 } 48 if seen==0 { return 0 } out_port[0]=pv 49 return 1 50} 51// join the LSD multicast group on a bound UDP socket (IP_ADD_MEMBERSHIP; raw consts IPPROTO_IP=0, opt=35). 52func lsd_join(fd: i64) -> i64 { 53 let mreq: *u8 = sys_mmap(16); let g: *u8=sys_mmap(8); lsd_group(g) 54 mreq[0]=g[0]; mreq[1]=g[1]; mreq[2]=g[2]; mreq[3]=g[3] // multicast group 55 var i: i64=4; while i<8 { mreq[i]=0 as u8; i=i+1 } // interface = 0.0.0.0 (any) 56 return sys_setsockopt(fd, 0, 35, mreq, 8) 57} 58// announce one info_hash to the LAN (sendto the group; TTL default 1 = LAN-only, correct for LSD). 59func lsd_announce_one(ih_hex: *u8, port: i64) -> i64 { 60 let fd: i64 = sys_socket(AF_INET, SOCK_DGRAM, 0); if fd<0 { return 0-1 } 61 let dest: *u8 = sys_mmap(16); lsd_sock4(dest, LSD_PORT, 239, 192, 152, 143) 62 let msg: *u8 = sys_mmap(256); let ml: i64 = lsd_build(ih_hex, port, msg) 63 sys_sendto(fd, msg, ml, 0, dest, 16); sys_close(fd) 64 return 0 65} 66func lsd_read_file(path: *u8, buf: *u8, cap: i64) -> i64 { let fd: i64=sys_openat_rd(path); if fd<0 { return 0-1 } var t: i64=0; var r: i64=1; while r>0 { r=sys_read(fd,(((buf as i64)+t) as *u8),cap-t); if r>0 { t=t+r } } sys_close(fd); return t } 67 68func main(argc: i64, argv: *i64) -> i64 { 69 var verb: *u8 = "selftest" as *u8 70 if argc>1 { verb = argv[1] as *u8 } 71 // announce <ih_hex> [port] 72 if lsd_streq(verb, "announce" as *u8) == 1 { 73 if argc<3 { lp("usage: announce <ih_hex> [port]\n" as *u8); sys_exit(2); return 2 } 74 var port: i64=LSD_MAGIC_6881; if argc>3 { port=lsd_atoi(argv[3] as *u8) } 75 lsd_announce_one(argv[2] as *u8, port); lp("LSD announced ih on the LAN\n" as *u8); sys_exit(0); return 0 76 } 77 // announceall <seed_registry> [port] : announce every registered info_hash (field 1 = ih_hex) 78 if lsd_streq(verb, "announceall" as *u8) == 1 { 79 if argc<3 { lp("usage: announceall <seed_registry> [port]\n" as *u8); sys_exit(2); return 2 } 80 var port: i64=LSD_MAGIC_6881; if argc>3 { port=lsd_atoi(argv[3] as *u8) } 81 let buf: *u8=sys_mmap(LSD_MAGIC_131072); let n: i64=lsd_read_file(argv[2] as *u8, buf, LSD_MAGIC_131071) 82 var ls: i64=0; var cnt: i64=0 83 while ls<n { 84 var le: i64=ls; var eol: i64=0; while eol==0 { if le>=n { eol=1 } else { if buf[le]==(10 as u8) { eol=1 } else { le=le+1 } } } 85 let t1: i64=lsd_find(buf, le, ls, "\t" as *u8, 1) // end of field0 (name) 86 if t1>0 { let fs: i64=t1+1; let ih: *u8=sys_mmap(48); var c: i64=0; while c<40 { if fs+c<le { ih[c]=buf[fs+c] } c=c+1 } ih[40]=0 as u8; lsd_announce_one(ih, port); cnt=cnt+1 } 87 ls=le+1 88 } 89 lp("LSD announced " as *u8); lpn(cnt); lp(" torrents on the LAN\n" as *u8); sys_exit(0); return 0 90 } 91 // listen <secs>: join the group, receive BT-SEARCH from LAN peers, print (source-ip, port, infohash). 92 if lsd_streq(verb, "listen" as *u8) == 1 { 93 var secs: i64=10; if argc>2 { secs=lsd_atoi(argv[2] as *u8) } 94 let fd: i64 = sys_socket(AF_INET, SOCK_DGRAM, 0); if fd<0 { lp("socket fail\n" as *u8); sys_exit(1); return 1 } 95 let one: *i64=sys_mmap(8) as *i64; one[0]=1; sys_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, one as *u8, 4) 96 let ba: *u8=sys_mmap(16); lsd_sock4(ba, LSD_PORT, 0,0,0,0) 97 if sys_bind(fd, ba, 16) != 0 { lp("bind :6771 fail (another LSD listener?)\n" as *u8); sys_close(fd); sys_exit(1); return 1 } 98 lsd_join(fd); sys_set_socket_timeout(fd, 2) 99 lp("LSD listening on 239.192.152.143:6771 for " as *u8); lpn(secs); lp("s ...\n" as *u8) 100 let start: i64 = sys_now_realtime_sec(); var got: i64=0 101 var run: i64=1 102 while run==1 { 103 if sys_now_realtime_sec() - start >= secs { run=0 } else { 104 let src: *u8=sys_mmap(32); let al: *i64=sys_mmap(8) as *i64; al[0]=16 105 let rb: *u8=sys_mmap(LSD_MAGIC_2048); let rn: i64=sys_recvfrom(fd, rb, LSD_MAGIC_2047, 0, src, al) 106 if rn>0 { let oih: *u8=sys_mmap(48); let op: *i64=sys_mmap(8) as *i64 107 if lsd_parse(rb, rn, oih, op)==1 { lp(" LAN peer " as *u8); lpn(src[4] as i64); lp("." as *u8); lpn(src[5] as i64); lp("." as *u8); lpn(src[6] as i64); lp("." as *u8); lpn(src[7] as i64); lp(":" as *u8); lpn(op[0]); lp(" ih=" as *u8); lp(oih); lp("\n" as *u8); got=got+1 } } 108 } 109 } 110 sys_close(fd); lp("LSD listen done: " as *u8); lpn(got); lp(" announces seen\n" as *u8); sys_exit(0); return 0 111 } 112 // selftest: loopback -- join, announce, receive our own BT-SEARCH, parse -> ih+port must round-trip 113 if lsd_streq(verb, "selftest" as *u8) == 1 { 114 let fd: i64 = sys_socket(AF_INET, SOCK_DGRAM, 0); if fd<0 { lp("socket fail\n" as *u8); sys_exit(1); return 1 } 115 let one: *i64=sys_mmap(8) as *i64; one[0]=1; sys_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, one as *u8, 4) 116 let ba: *u8=sys_mmap(16); lsd_sock4(ba, LSD_PORT, 0,0,0,0); sys_bind(fd, ba, 16) 117 lsd_join(fd); sys_set_socket_timeout(fd, 2) 118 let dest: *u8=sys_mmap(16); lsd_sock4(dest, LSD_PORT, 239,192,152,143) 119 let msg: *u8=sys_mmap(256); let ml: i64=lsd_build("0123456789abcdef0123456789abcdef01234567" as *u8, LSD_MAGIC_6881, msg) 120 sys_sendto(fd, msg, ml, 0, dest, 16) 121 let rb: *u8=sys_mmap(LSD_MAGIC_2048); let rn: i64=sys_recvfrom(fd, rb, LSD_MAGIC_2047, 0, 0 as *u8, 0 as *i64) 122 sys_close(fd) 123 lp("LSD-SELFTEST authored=organ recv_bytes=" as *u8); lpn(rn); lp("\n" as *u8) 124 if rn>0 { let oih: *u8=sys_mmap(48); let op: *i64=sys_mmap(8) as *i64; let ok: i64=lsd_parse(rb, rn, oih, op) 125 lp(" parsed ok=" as *u8); lpn(ok); lp(" ih=" as *u8); lp(oih); lp(" port=" as *u8); lpn(op[0]); lp("\n" as *u8) 126 if ok==1 { if op[0]==LSD_MAGIC_6881 { if lsd_streq(oih, "0123456789abcdef0123456789abcdef01234567" as *u8)==1 { lp("LSD-SELFTEST verdict=GREEN (announce->multicast->listen->parse round-trip)\n" as *u8); sys_exit(0); return 0 } } } } 127 lp("LSD-SELFTEST verdict=RED (no loopback multicast? some hosts need a route for 239/8)\n" as *u8); sys_exit(1); return 1 128 } 129 lp("verbs: announce <ih> [port] | announceall <reg> [port] | listen <area..> <secs> | selftest\n" as *u8) 130 sys_exit(0); return 0 131}