feat: implement ZeroTier exit gateway switcher

Adds the luci-app-zt-gateway package: a LuCI app + rpcd/ucode backend +
shell switch script that reconfigures which remote ZeroTier node acts as
the internet exit gateway for WIBLAN clients (10.11.13.0/24).

  - Makefile (luci.mk, arch-independent)
  - UCI config skeleton with three sample gateways
  - rpcd ACL + menu entry
  - zt-gateway.uc rpcd backend exposing status / switch / health /
    drain_status / cancel_drain ubus methods
  - zt-gateway-switch shell script implementing force + graceful modes:
      * force: pre-flight ping, atomic route replace, conntrack flush,
        UCI/hotplug/rc.local persistence
      * graceful: dual-table drain using CONNMARK fwmark 0x100 at
        priority 99, background drain monitor with two-consecutive-zero
        completion and timeout-forced fallback to force
  - LuCI overview.js: gateway radio list, mode select, drain progress
    panel, cancel-drain button, health polling
  - Docker test harness (docker-compose + Dockerfile.router +
    router/gw entrypoints) exercising the switch script against real
    iproute2/iptables/conntrack on two simulated exit nodes

Verified against the harness: force switch, graceful drain to natural
completion, pre-flight blocking of unreachable gateways (force + graceful),
and drain-timeout forced fallback.
This commit is contained in:
2026-06-19 02:51:21 +05:30
parent f6bf6e7cd4
commit eb04a2597d
12 changed files with 1376 additions and 10 deletions

21
docker/gw-entrypoint.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# gw-entrypoint.sh — shared by all simulated ZT exit gateways.
#
# Argument 1: this gateway's IP (within 10.11.12.0/24)
# Argument 2: router's ZT-side IP (10.11.12.1 by default; here we use the
# docker-compose gateway of the zt-exit-net network).
set -eu
my_ip="${1:?missing my_ip}"
router_ip="${2:-10.11.12.1}"
echo 1 > /proc/sys/net/ipv4/ip_forward
# Reply to ARP for our own IP (already done by the kernel). Make sure we
# can reach the WIBLAN subnet by routing back through the router.
ip route replace 10.11.13.0/24 via "$router_ip" 2>/dev/null || true
# Simple ping responder is all we need for pre-flight tests. No NAT/NAT
# rules required because test traffic only verifies the policy routing on
# the router side, not end-to-end internet egress.
echo "[gw ${my_ip}] up; routes via ${router_ip}"
exec sleep infinity