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}