nx_router_ctl.nx source
↩ module page · 99 lines · 9869 B
1// nx_router_ctl.nx -- SOVEREIGN router control (operator: "you have api access, get it going, towards apis";
2// roadmap NR-7). Drives the home GL.iNet GL-BE9300 (OpenWrt) via its uci config API over OUR OWN sovereign SSH
3// (nx_ssh_lib -- curve25519 KEX, chacha/aes, exec channel), NOT WSL bash. Verbs:
4// fwadd [port] [destip] -> idempotent DNAT redirect wan:port -> destip:port (TCP+UDP); opens the seeder to WAN
5// fwlist -> show our Nishi firewall redirects
6// wan -> WAN interface IP (are we actually reachable?)
7// Password from data/router.pw (0600; vault-migratable). Router LAN IP 192.168.8.1. license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_ssh_lib.nx"
10const RC_MAGIC_6881: i64 = 6881
11const RC_MAGIC_8192: i64 = 8192
12
13const RC_PW: *u8 = "data/router.pw" as *u8
14const RC_ROUTER: i64 = (192 << 24) | (168 << 16) | (8 << 8) | 1 // 192.168.8.1 (GL.iNet LAN)
15
16func rc_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
17func rc_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 }
18func rc_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 }
19func rc_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 }
20
21// build one idempotent uci DNAT redirect section into buf at o; returns new o.
22func rc_redir(buf: *u8, o: i64, sect: *u8, proto: *u8, port: i64, destip: *u8) -> i64 {
23 o=rc_app(buf,o,"uci -q delete firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,";" as *u8)
24 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,"=redirect;" as *u8)
25 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".name=Nishi-" as *u8); o=rc_appn(buf,o,port); o=rc_app(buf,o,"-" as *u8); o=rc_app(buf,o,proto); o=rc_app(buf,o,";" as *u8)
26 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".src=wan;" as *u8)
27 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".proto=" as *u8); o=rc_app(buf,o,proto); o=rc_app(buf,o,";" as *u8)
28 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".src_dport=" as *u8); o=rc_appn(buf,o,port); o=rc_app(buf,o,";" as *u8)
29 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".dest=lan;" as *u8)
30 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".dest_ip=" as *u8); o=rc_app(buf,o,destip); o=rc_app(buf,o,";" as *u8)
31 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".dest_port=" as *u8); o=rc_appn(buf,o,port); o=rc_app(buf,o,";" as *u8)
32 o=rc_app(buf,o,"uci set firewall." as *u8); o=rc_app(buf,o,sect); o=rc_app(buf,o,".target=DNAT;" as *u8)
33 return o
34}
35
36func main(argc: i64, argv: *i64) -> i64 {
37 var verb: *u8 = "fwadd" as *u8
38 if argc>1 { verb = argv[1] as *u8 }
39 var port: i64 = RC_MAGIC_6881
40 if argc>2 { var v: i64=0; var i: i64=0; let a: *u8=argv[2] as *u8; while a[i]!=(0 as u8) { if a[i]>=(48 as u8) { if a[i]<=(57 as u8) { v=v*10+((a[i] as i64)-48) } } i=i+1 } if v>0 { port=v } }
41 var destip: *u8 = "192.168.8.227" as *u8
42 if argc>3 { destip = argv[3] as *u8 }
43 // raw string args (dhcpreserve <mac> <ip> [name])
44 var a2: *u8 = "" as *u8; var a3: *u8 = "" as *u8; var a4: *u8 = "Nishi" as *u8
45 if argc>2 { a2 = argv[2] as *u8 }
46 if argc>3 { a3 = argv[3] as *u8 }
47 if argc>4 { a4 = argv[4] as *u8 }
48
49 ssh_puts("NX-ROUTER-CTL verb=" as *u8); ssh_puts(verb); ssh_puts(" (GL.iNet 192.168.8.1 via sovereign SSH+uci)\n" as *u8)
50 // read the router password (sovereign secret file)
51 let pwbox: *i64 = sys_mmap(16) as *i64
52 let pw: *u8 = sys_read_file(RC_PW, pwbox)
53 if (pw as i64) == 0 { ssh_puts(" cannot read data/router.pw\n" as *u8); sys_exit(3); return 3 }
54 var pwlen: i64 = pwbox[0]
55 while pwlen > 0 { if pw[pwlen-1] == 10 as u8 { pwlen = pwlen - 1 } else { if pw[pwlen-1] == 13 as u8 { pwlen = pwlen - 1 } else { break } } }
56 // sovereign SSH
57 let st: *SshState = sys_mmap(SSH_STATE_BYTES) as *SshState
58 if ssh_open_session(st, RC_ROUTER) != 0 { ssh_puts(" SSH session FAILED (router :22 unreachable / KEX mismatch vs dropbear)\n" as *u8); sys_exit(5); return 5 }
59 if ssh_userauth_password(st, "root" as *u8, 4, pw, pwlen) != 1 { ssh_puts(" SSH auth FAILED (wrong router password?)\n" as *u8); sys_close(st.fd); sys_exit(6); return 6 }
60 ssh_puts(" SSH+auth OK -> running uci\n" as *u8)
61 // compose the command per verb
62 // FLAT dispatch (verbs are mutually exclusive; only one sets cmd). Full network-control API surface.
63 let cmd: *u8 = sys_mmap(RC_MAGIC_8192); var o: i64=0; var handled: i64=0
64 // PORT-SCOPED section names (nishitcp<port>/nishiudp<port>) so multiple forwards coexist -- adding :25
65 // must NOT clobber the existing :6881 seeder forward. (Legacy 6881 lives in unsuffixed nishitcp/nishiudp;
66 // left intact for back-compat.)
67 let stcp: *u8 = sys_mmap(32); var sc1: i64 = rc_app(stcp, 0, "nishitcp" as *u8); sc1 = rc_appn(stcp, sc1, port); stcp[sc1] = 0 as u8
68 let sudp: *u8 = sys_mmap(32); var sc2: i64 = rc_app(sudp, 0, "nishiudp" as *u8); sc2 = rc_appn(sudp, sc2, port); sudp[sc2] = 0 as u8
69 if rc_streq(verb, "fwadd" as *u8) == 1 {
70 o = rc_redir(cmd, o, stcp, "tcp" as *u8, port, destip)
71 o = rc_redir(cmd, o, sudp, "udp" as *u8, port, destip)
72 o = rc_app(cmd, o, "uci commit firewall;/etc/init.d/firewall reload;echo NISHI_FWDONE;uci show firewall|grep -i nishi" as *u8); handled=1 }
73 if rc_streq(verb, "fwdel" as *u8) == 1 {
74 o = rc_app(cmd, o, "uci -q delete firewall." as *u8); o = rc_app(cmd, o, stcp); o = rc_app(cmd, o, ";uci -q delete firewall." as *u8); o = rc_app(cmd, o, sudp); o = rc_app(cmd, o, ";uci commit firewall;/etc/init.d/firewall reload;echo NISHI_FWDEL_DONE" as *u8); handled=1 }
75 if rc_streq(verb, "fwlist" as *u8) == 1 {
76 o = rc_app(cmd, o, "uci show firewall|grep -iE 'redirect|nishi';echo ---redirect-count---;uci show firewall|grep -c redirect" as *u8); handled=1 }
77 if rc_streq(verb, "wan" as *u8) == 1 {
78 o = rc_app(cmd, o, "ifstatus wan 2>/dev/null|grep -m3 address;echo ---;ubus call network.interface.wan status 2>/dev/null|grep -m2 address" as *u8); handled=1 }
79 if rc_streq(verb, "status" as *u8) == 1 {
80 o = rc_app(cmd, o, "ubus call system board 2>/dev/null;echo ---glversion---;cat /etc/glversion 2>/dev/null;echo ---uptime/load---;uptime;cat /proc/loadavg" as *u8); handled=1 }
81 if rc_streq(verb, "clients" as *u8) == 1 {
82 o = rc_app(cmd, o, "echo ===DHCP-LEASES:expiry_mac_ip_name===;cat /tmp/dhcp.leases 2>/dev/null;echo ===ARP-REACHABLE===;ip neigh 2>/dev/null|grep -i reachable" as *u8); handled=1 }
83 if rc_streq(verb, "dns" as *u8) == 1 {
84 o = rc_app(cmd, o, "echo ===WAN-DNS===;uci -q get network.wan.dns;echo ===dnsmasq===;uci show dhcp 2>/dev/null|grep -iE 'server|domain';echo ===resolv===;grep -i nameserver /tmp/resolv.conf.d/resolv.conf.auto 2>/dev/null" as *u8); handled=1 }
85 if rc_streq(verb, "dhcpreserve" as *u8) == 1 {
86 o = rc_app(cmd, o, "uci -q delete dhcp.nishihost;uci set dhcp.nishihost=host;uci set dhcp.nishihost.mac=" as *u8); o=rc_app(cmd,o,a2); o=rc_app(cmd,o,";uci set dhcp.nishihost.ip=" as *u8); o=rc_app(cmd,o,a3); o=rc_app(cmd,o,";uci set dhcp.nishihost.name=" as *u8); o=rc_app(cmd,o,a4); o=rc_app(cmd,o,";uci commit dhcp;/etc/init.d/dnsmasq restart;echo NISHI_DHCP_DONE;uci show dhcp|grep nishihost" as *u8); handled=1 }
87 if rc_streq(verb, "v6" as *u8) == 1 {
88 o = rc_app(cmd, o, "echo ===WAN6-STATUS===;ifstatus wan6 2>/dev/null|grep -iE 'up|address|mask|prefix';echo ===WAN6-PD-PREFIX===;ubus call network.interface.wan6 status 2>/dev/null|grep -iE 'address|mask|prefix';echo ===LAN6-CFG===;uci show network 2>/dev/null|grep -iE 'ip6assign|ip6class|dhcpv6|ipv6|ra_';echo ===DHCP-RA===;uci show dhcp 2>/dev/null|grep -iE 'ra=|ra_|dhcpv6|ndp|master';echo ===LAN6-GLOBAL===;ip -6 addr show br-lan 2>/dev/null|grep -i 'scope global'|head" as *u8); handled=1 }
89 if rc_streq(verb, "v6on" as *u8) == 1 {
90 o = rc_app(cmd, o, "uci set network.wan.ipv6='1';uci set network.wan6.proto='dhcpv6';uci set dhcp.lan.ra='server';uci set dhcp.lan.dhcpv6='server';uci set dhcp.lan.ra_slaac='1';uci commit network;uci commit dhcp;ifup wan6 2>/dev/null;/etc/init.d/odhcpd restart 2>/dev/null;sleep 12;echo ===WAN6-GLOBAL+PD===;ifstatus wan6 2>/dev/null|grep -iE 'address|prefix|mask';ip -6 addr 2>/dev/null|grep -i 'scope global'|head;echo ===V6-DEFAULT-ROUTE===;ip -6 route show default 2>/dev/null|head;echo V6ON_DONE" as *u8); handled=1 }
91 if rc_streq(verb, "v6off" as *u8) == 1 {
92 o = rc_app(cmd, o, "uci set network.wan.ipv6='0';uci set dhcp.lan.ra='disabled';uci set dhcp.lan.dhcpv6='disabled';uci commit network;uci commit dhcp;ifdown wan6 2>/dev/null;/etc/init.d/odhcpd restart 2>/dev/null;echo V6OFF_REVERTED" as *u8); handled=1 }
93 if rc_streq(verb, "dash" as *u8) == 1 {
94 o = rc_app(cmd, o, "echo ===STATUS===;ubus call system board 2>/dev/null|grep -E 'model|version|hostname';echo ===WAN-IP===;ifstatus wan 2>/dev/null|grep -m1 address;echo ===NISHI-FORWARDS===;uci show firewall|grep -i nishi;echo ===CLIENT-COUNT===;cat /tmp/dhcp.leases 2>/dev/null|wc -l" as *u8); handled=1 }
95 if handled==0 { o = rc_app(cmd, o, "echo verbs: fwadd[port destip] | fwdel | fwlist | wan | status | clients | dns | dhcpreserve mac ip name | dash" as *u8) }
96 ssh_exec(st, cmd, o)
97 sys_close(st.fd)
98 sys_exit(0); return 0
99}