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}