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:
21
docker/gw-entrypoint.sh
Executable file
21
docker/gw-entrypoint.sh
Executable 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
|
||||
56
docker/router-entrypoint.sh
Executable file
56
docker/router-entrypoint.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
# router-entrypoint.sh
|
||||
#
|
||||
# Brings up the simulated br-zt bridge that mirrors production: the
|
||||
# ZeroTier member interface (here, the container's interface on
|
||||
# zt-exit-net) is enslaved to br-zt and its IP is moved onto br-zt. This
|
||||
# makes 'ip route ... dev br-zt' actually reach 10.99.12.0/24.
|
||||
set -eu
|
||||
|
||||
WIBLAN_CIDR="${ZTG_WIBLAN_CIDR:-10.99.13.0/24}"
|
||||
|
||||
echo "[entrypoint] bringing up br-zt bridge"
|
||||
|
||||
# Find the interface holding 10.99.12.x.
|
||||
ZT_IFACE=$(ip -o -4 addr show \
|
||||
| awk '$4 ~ /^10\.99\.12\./ {print $2; exit}')
|
||||
if [ -z "$ZT_IFACE" ]; then
|
||||
echo "[entrypoint] WARNING: no interface in 10.99.12.0/24; tests will fail" >&2
|
||||
ZT_IFACE=eth1
|
||||
fi
|
||||
echo "[entrypoint] ZT-side interface: $ZT_IFACE"
|
||||
|
||||
ip link add name br-zt type bridge 2>/dev/null || true
|
||||
ip link set br-zt up
|
||||
|
||||
if [ "$ZT_IFACE" != "br-zt" ]; then
|
||||
ADDR=$(ip -o -4 addr show dev "$ZT_IFACE" \
|
||||
| awk '$4 ~ /^10\.99\.12\./ {print $4; exit}')
|
||||
if [ -n "$ADDR" ]; then
|
||||
ip addr del "$ADDR" dev "$ZT_IFACE" 2>/dev/null || true
|
||||
ip addr add "$ADDR" dev br-zt
|
||||
fi
|
||||
ip link set "$ZT_IFACE" master br-zt
|
||||
fi
|
||||
|
||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||
echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects 2>/dev/null || true
|
||||
|
||||
# Seed the baseline policy routing the production router boots with.
|
||||
ip rule del from "$WIBLAN_CIDR" table 100 2>/dev/null || true
|
||||
ip rule add from "$WIBLAN_CIDR" table 100 priority 100
|
||||
|
||||
# Seed the current gateway's host route + table 100 default. These mirror
|
||||
# what /etc/rc.local installs on boot in production.
|
||||
ip route replace 10.99.12.3 dev br-zt
|
||||
ip route replace default via 10.99.12.3 dev br-zt table 100
|
||||
ip route replace "$WIBLAN_CIDR" dev br-zt table 1
|
||||
|
||||
echo "[entrypoint] baseline state:"
|
||||
ip -o -4 addr show dev br-zt
|
||||
echo "--- table 100:"
|
||||
ip route show table 100
|
||||
echo "--- rule:"
|
||||
ip rule show
|
||||
|
||||
exec "$@"
|
||||
Reference in New Issue
Block a user