code wiki / (root) / nx_seed_announce_all.nx

nx_seed_announce_all.nx source

↩ module page · 171 lines · 10409 B

1// nx_seed_announce_all.nx -- DURABLE DHT discoverability for the seeder: periodically announce_peer EVERY 2// torrent registered in seed_index.conf so strangers can FIND our seeder via the DHT (BEP-5). Must run ON 3// the NAS -- BEP-5 infers the peer's IP from the announce packet's SOURCE, so announcing from the NAS makes 4// the DHT record the NAS's address for each info_hash. Composes nx_dht_announce (dan_announce). Announces 5// expire (~15-30 min in mainline), so this loops: announce-all -> sleep 900s -> repeat. Supervised daemon. 6// 7// nx_seed_announce_all [registry] -- loop: announce every registered info_hash, sleep 15m, repeat 8// license_tier: ORIGINAL layer: peer-discovery (announce) module: nishi-core.torrent.seed_announce_all 9// depends: nishi-core.torrent.dht_announce 10import "nx_dht_announce.nx" 11const SA_MAGIC_1048576: i64 = 1048576 12const SA_MAGIC_60000: i64 = 60000 13 14const SA_REG: *u8 = "/volume1/ai/torrent/seed_index.conf" as *u8 15const SA_PORT: i64 = 6881 16const SA_INTERVAL_SEC: i64 = 900 // 15 min between announce sweeps (announces expire ~15-30m) 17 18// wait4 status scratch in a `static` -- allocated once. Each sa_* helper below runs in the PARENT 19// (the supervised daemon that never exits), so a per-call sys_mmap here leaked a page per sweep 20// forever; nx_resmon measured this daemon at 8.8 GB committed (worst on the host). The forked 21// grandchildren's argv/envp mmaps are NOT this class -- execve replaces the image immediately. 22static SA_ST: i64 23func sa_wait_scratch() -> *i64 { if SA_ST == 0 { SA_ST = sys_mmap(64) as i64 } return SA_ST as *i64 } 24 25func sa_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 } 26func sa_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 } 27 28// DAILY tracker-refresh: grow trackers.txt from live public lists (nx_tracker_refresh over sovereign TLS). 29// Detached double-fork -> never blocks the announce sweep + no zombie; fail-safe (refresh no-ops on any error). 30// This is the SOVEREIGN "grow, not static" scheduler -- runs on the NAS, no external cron / no Claude. 31func sa_trackerrefresh() -> i64 { 32 let pid: i64 = sys_fork() 33 if pid == 0 { 34 let pid2: i64 = sys_fork() 35 if pid2 == 0 { 36 let argv: *i64 = sys_mmap(32) as *i64 37 argv[0] = "/bin/sh" as *u8 as i64; argv[1] = "-c" as *u8 as i64 38 argv[2] = "cd /volume1/ai/torrent && ./nx_tracker_refresh.elf /volume1/ai/torrent/trackers.txt >/tmp/trackerrefresh.log 2>&1" as *u8 as i64; argv[3] = 0 39 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 40 sys_execve("/bin/sh" as *u8, argv, envp); sys_exit(127) 41 } 42 sys_exit(0) // first child exits -> grandchild reparents to init 43 } 44 let st: *i64 = sa_wait_scratch(); sys_wait4(pid, st, 0) // reap the fast-exiting first child (no zombie) 45 return 0 46} 47// IP-FILTER refresh: re-compile the blocklist from the live firehol list over sovereign TLS (daily). Detached; 48// fail-safe (keeps the existing ipfilter.bin if the fetch fails). Worker+seeder reload it on next launch. 49func sa_ipfilter() -> i64 { 50 let pid: i64 = sys_fork() 51 if pid == 0 { 52 let pid2: i64 = sys_fork() 53 if pid2 == 0 { 54 let argv: *i64 = sys_mmap(32) as *i64 55 argv[0] = "/bin/sh" as *u8 as i64; argv[1] = "-c" as *u8 as i64 56 argv[2] = "cd /volume1/ai/torrent && ./nx_ipfilter_fetch.elf /volume1/ai/torrent/data/ipfilter.bin >/tmp/ipfilter.log 2>&1" as *u8 as i64; argv[3] = 0 57 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 58 sys_execve("/bin/sh" as *u8, argv, envp); sys_exit(127) 59 } 60 sys_exit(0) 61 } 62 let st: *i64 = sa_wait_scratch(); sys_wait4(pid, st, 0) 63 return 0 64} 65// AUTO PORT-MAP: try NAT-PMP + UPnP-IGD to open :6881 on the router (WAN reach). Detached; no-op if the router 66// has both off (like ours now) -- but auto-maps the moment UPnP/NAT-PMP is enabled. Map lifetime 7200s -> renew hourly. 67func sa_portmap() -> i64 { 68 let pid: i64 = sys_fork() 69 if pid == 0 { 70 let pid2: i64 = sys_fork() 71 if pid2 == 0 { 72 let argv: *i64 = sys_mmap(32) as *i64 73 argv[0] = "/bin/sh" as *u8 as i64; argv[1] = "-c" as *u8 as i64 74 argv[2] = "cd /volume1/ai/torrent && ./nx_natpmp.elf 6881 >/tmp/portmap.log 2>&1; ./nx_upnp_igd.elf 6881 >>/tmp/portmap.log 2>&1" as *u8 as i64; argv[3] = 0 75 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 76 sys_execve("/bin/sh" as *u8, argv, envp); sys_exit(127) 77 } 78 sys_exit(0) 79 } 80 let st: *i64 = sa_wait_scratch(); sys_wait4(pid, st, 0) 81 return 0 82} 83// ROUTER FORWARD (the METHOD that actually works on our GL.iNet): re-assert the :6881 DNAT via nx_router_ctl 84// (sovereign SSH -> uci). Idempotent (named sections) + persistent, so this is self-healing if the router 85// config is ever reset/wiped. Detached; needs data/router.pw on the NAS. 86func sa_routerfw() -> i64 { 87 let pid: i64 = sys_fork() 88 if pid == 0 { 89 let pid2: i64 = sys_fork() 90 if pid2 == 0 { 91 let argv: *i64 = sys_mmap(32) as *i64 92 argv[0] = "/bin/sh" as *u8 as i64; argv[1] = "-c" as *u8 as i64 93 argv[2] = "cd /volume1/ai/torrent && ./nx_router_ctl.elf fwadd 6881 192.168.8.227 >/tmp/routerfw.log 2>&1" as *u8 as i64; argv[3] = 0 94 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 95 sys_execve("/bin/sh" as *u8, argv, envp); sys_exit(127) 96 } 97 sys_exit(0) 98 } 99 let st: *i64 = sa_wait_scratch(); sys_wait4(pid, st, 0) 100 return 0 101} 102// LSD (BEP-14): multicast-announce every seeded info_hash on the LAN so local BitTorrent clients discover us 103// (no tracker). Detached; cheap (one UDP multicast per torrent). Re-announced each sweep (~15 min). 104func sa_lsd() -> i64 { 105 let pid: i64 = sys_fork() 106 if pid == 0 { 107 let pid2: i64 = sys_fork() 108 if pid2 == 0 { 109 let argv: *i64 = sys_mmap(32) as *i64 110 argv[0] = "/bin/sh" as *u8 as i64; argv[1] = "-c" as *u8 as i64 111 argv[2] = "cd /volume1/ai/torrent && ./nx_lsd.elf announceall /volume1/ai/torrent/seed_index.conf 6881 >/tmp/lsd.log 2>&1" as *u8 as i64; argv[3] = 0 112 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 113 sys_execve("/bin/sh" as *u8, argv, envp); sys_exit(127) 114 } 115 sys_exit(0) 116 } 117 let st: *i64 = sa_wait_scratch(); sys_wait4(pid, st, 0) 118 return 0 119} 120 121func main(argc: i64, argv: *i64) -> i64 { 122 var reg: *u8 = SA_REG 123 if argc >= 2 { reg = argv[1] as *u8 } 124 let nid: *u8 = sys_mmap(20); var z: i64 = 0; while z < 20 { nid[z] = (0x41 + z) as u8; z = z + 1 } 125 let ihhex: *u8 = sys_mmap(64); let ih: *u8 = sys_mmap(20); let buf: *u8 = sys_mmap(SA_MAGIC_1048576) 126 var loops: i64 = 0 127 var run: i64 = 1 128 while run == 1 { 129 let fd: i64 = sys_openat_rd(reg) 130 var announced: i64 = 0; var total_acks: i64 = 0 131 if fd < 0 { da_p("nx_seed_announce_all: no registry " as *u8); da_p(reg); da_p(" (nothing to announce yet)\n" as *u8) } else { 132 let blen: i64 = sys_read(fd, buf, SA_MAGIC_1048576); sys_close(fd) 133 var ls: i64 = 0 134 while ls < blen { 135 let le: i64 = sa_eol(buf, ls, blen) 136 if le > ls { 137 // field 1 (0-indexed) = ih_hex, between tab#1 and tab#2 138 let t1: i64 = sa_tab(buf, ls, le) 139 if t1 < le { 140 let fs: i64 = t1 + 1; let fe: i64 = sa_tab(buf, fs, le) 141 var o: i64 = 0; var k: i64 = fs; while k < fe { ihhex[o] = buf[k]; o = o + 1; k = k + 1 } ihhex[o] = 0 as u8 142 if o == 40 { if da_hex2bin(ihhex, ih) == 1 { 143 da_p(" announcing ih=" as *u8); da_p(ihhex); da_p(" @ :" as *u8); da_n(SA_PORT); da_p("\n" as *u8) 144 let a: i64 = dan_announce(ih, nid, SA_PORT) 145 announced = announced + 1; total_acks = total_acks + a 146 } } 147 } 148 } 149 ls = le + 1 150 } 151 } 152 loops = loops + 1 153 da_p("nx_seed_announce_all sweep=" as *u8); da_n(loops); da_p(" torrents_announced=" as *u8); da_n(announced); da_p(" total_node_acks=" as *u8); da_n(total_acks); da_p("\n" as *u8) 154 if loops % 96 == 1 { da_p(" [tracker-refresh] growing trackers.txt from live public lists\n" as *u8); sa_trackerrefresh() } // ~daily (96*15min) + on start; sovereign "grow, not static" 155 if loops % 4 == 1 { da_p(" [port-map] NAT-PMP + UPnP try :6881 (WAN reach; no-op if router has both off)\n" as *u8); sa_portmap() } // ~hourly renew (map lifetime 7200s); auto-maps when router UPnP/NAT-PMP is on 156 if loops % 96 == 1 { da_p(" [router-fw] re-assert :6881 DNAT via nx_router_ctl (sovereign SSH+uci; self-heals a wiped router config)\n" as *u8); sa_routerfw() } // ~daily; the method that WORKS on our GL.iNet 157 if loops % 96 == 1 { da_p(" [ip-filter] refresh blocklist from firehol over sovereign TLS (fail-safe)\n" as *u8); sa_ipfilter() } // ~daily; reject known-bad/anti-P2P peer IPs 158 da_p(" [lsd] multicast-announce torrents on the LAN (BEP-14)\n" as *u8); sa_lsd() // every sweep (~15min); LAN peer discovery, no tracker 159 // WALL-CLOCK GATE: enforce >= SA_INTERVAL_SEC between sweeps regardless of the host kernel's nanosleep 160 // behavior. (On the NAS a bare sys_sleep_ms(900000) returned early -> the seeder hammered the DHT every 161 // ~15s. Gating on sys_now_realtime_sec bounds re-announce to the real interval; the 60s inner sleep keeps 162 // CPU near-idle when the kernel sleep DOES hold.) 163 let done_at: i64 = sys_now_realtime_sec() 164 var waiting: i64 = 1 165 while waiting == 1 { 166 let now: i64 = sys_now_realtime_sec() 167 if now - done_at >= SA_INTERVAL_SEC { waiting = 0 } else { sys_sleep_ms(SA_MAGIC_60000) } 168 } 169 } 170 return 0 171}