setup wizard: fix routing, DHCP, WiFi AP, firewall, and deploy task
Setup script fixes: - _cidr_to_mask: pad to 4 octets (/24 -> 255.255.255.0) - UCI quoting: remove embedded shell quotes from uci set calls - Bridge ports: auto-detect zt* interface instead of hardcoding ztabc0 - Bridge netmask: default to /23 (255.255.254.0) for ZT+WIBLAN - DHCP/WiFi AP: reference interface name (zt_wiblan) not device name (br_zt) - Firewall zone: add zt_wiblan to LAN zone for nftables fw4 - ZT IP persistence: ensure ZT-assigned IP stays on interface for ARP - Exit gateway routing: table 100/101 route via exit gateway, not self - New setup-wifi-ap subcommand for WIBLAN WiFi AP UBUS handler: - Add setup-wifi-ap to validation regex and error message Deploy task: - Auto-discover files from root/ and htdocs/ instead of hardcoded list - Clear LuCI cache before restarting services Documentation: - New docs/SETUP-GATEWAY.md with architecture, config, pitfalls, checklist - Updated docs/INSTALL.md with deploy task and setup wizard sections - Updated docs/PROGRESS.md with session log and learnings
This commit is contained in:
350
docs/SETUP-GATEWAY.md
Normal file
350
docs/SETUP-GATEWAY.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# ZeroTier Exit Gateway Setup Guide
|
||||
|
||||
This document covers configuring an OpenWrt router as a ZeroTier exit gateway,
|
||||
including the common pitfalls encountered during development.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
WiFi Client (10.11.13.x)
|
||||
│
|
||||
▼
|
||||
WIBLAN AP (phy0-ap2) ─── br-zt bridge ─── ZeroTier (ztk4jpk77j)
|
||||
│ │
|
||||
│ ZeroTier tunnel
|
||||
│ │
|
||||
│ ▼
|
||||
│ Exit Gateway (amsterdam)
|
||||
│ │
|
||||
│ Internet (NAT)
|
||||
│
|
||||
├── DHCP (dnsmasq on br-zt)
|
||||
├── DNS (dnsmasq → upstream)
|
||||
└── Policy Routing (table 100 → exit gateway)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### ZeroTier Network Configuration
|
||||
|
||||
**Critical**: Enable "Allow Ethernet Bridging" on the ZeroTier network controller
|
||||
at [my.zerotier.com](https://my.zerotier.com) → network → Settings.
|
||||
|
||||
Without this, L2 frames (ARP, DHCP) cannot be bridged across peers, and clients
|
||||
will get IPs but cannot communicate.
|
||||
|
||||
### ZeroTier Managed Routes
|
||||
|
||||
The ZeroTier network must have a managed route for the WIBLAN subnet:
|
||||
|
||||
```
|
||||
Managed Routes → Add: 10.11.13.0/24 → (empty = auto via member)
|
||||
```
|
||||
|
||||
This tells ZeroTier to route traffic for `10.11.13.0/24` through the exit gateway.
|
||||
|
||||
## Setup Script Commands
|
||||
|
||||
The `zt-gateway-setup` script provides these commands:
|
||||
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `setup-bridge` | Create `br-zt` bridge, add ZT interface, configure firewall |
|
||||
| `setup-routing` | Policy routing (tables 100/101, ip rules) |
|
||||
| `setup-dhcp` | DHCP for WIBLAN clients on `br-zt` |
|
||||
| `setup-wifi-ap` | Create WIBLAN WiFi AP bridged to `br-zt` |
|
||||
| `setup-hotplug` | Hotplug script to re-apply routes on ifup |
|
||||
| `setup-persistence` | rc.local + UCI routes for reboot survival |
|
||||
| `setup-all` | Run all commands in order |
|
||||
|
||||
Run from LuCI UI or CLI:
|
||||
```bash
|
||||
/usr/sbin/zt-gateway-setup setup-all
|
||||
```
|
||||
|
||||
## Network Configuration
|
||||
|
||||
### Bridge Device
|
||||
|
||||
```uci
|
||||
config device 'br_zt'
|
||||
option type 'bridge'
|
||||
option name 'br-zt'
|
||||
list ports 'ztk4jpk77j' # ZeroTier interface
|
||||
```
|
||||
|
||||
**Note**: UCI section names cannot contain hyphens. Use underscores (`br_zt`)
|
||||
for section names, but the actual device name uses hyphens (`br-zt`).
|
||||
|
||||
### Bridge Interface
|
||||
|
||||
```uci
|
||||
config interface 'zt_wiblan'
|
||||
option proto 'static'
|
||||
option device 'br-zt'
|
||||
option ipaddr '10.11.13.1'
|
||||
option netmask '255.255.254.0' # /23 to cover ZT (10.11.12.x) + WIBLAN (10.11.13.x)
|
||||
```
|
||||
|
||||
**Key**: The netmask MUST be `/23` (255.255.254.0), not `/24`. The bridge needs
|
||||
to be in the same subnet as the ZeroTier network (10.11.12.0/23) for ARP to work.
|
||||
|
||||
### ZeroTier Interface
|
||||
|
||||
```uci
|
||||
config interface 'wbtier'
|
||||
option proto 'none'
|
||||
option device 'ztk4jpk77j'
|
||||
```
|
||||
|
||||
**Important**: The ZT interface must keep its assigned IP even when added to
|
||||
the bridge. If the IP is lost, ARP responses fail and connectivity breaks.
|
||||
|
||||
## Firewall Configuration (nftables fw4)
|
||||
|
||||
### Add Bridge Interface to LAN Zone
|
||||
|
||||
```uci
|
||||
config zone
|
||||
option name 'lan'
|
||||
list network 'lan'
|
||||
list network 'zt_wiblan' # Add this
|
||||
```
|
||||
|
||||
Without this, nftables fw4's default `drop` policy blocks all traffic from `br-zt`.
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
nft list chain inet fw4 input | grep br-zt
|
||||
# Should show: iifname { "br-zt", ... } jump input_lan
|
||||
```
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
### Policy Routing Rules
|
||||
|
||||
```uci
|
||||
config rule
|
||||
option src '10.11.13.0/24'
|
||||
option lookup '100'
|
||||
option priority '100'
|
||||
```
|
||||
|
||||
This routes traffic FROM WIBLAN clients through table 100.
|
||||
|
||||
### Table 100 (Main Policy)
|
||||
|
||||
```uci
|
||||
config route
|
||||
option interface 'brzt' # Interface name, not device
|
||||
option target '0.0.0.0'
|
||||
option netmask '0.0.0.0'
|
||||
option gateway '10.11.12.3' # Exit gateway IP (NOT WIBLAN_GW)
|
||||
option table '100'
|
||||
|
||||
config route
|
||||
option interface 'brzt'
|
||||
option target '10.11.13.0'
|
||||
option netmask '255.255.255.0'
|
||||
option table '100'
|
||||
```
|
||||
|
||||
**Critical**: The default route in table 100 MUST point to the exit gateway IP
|
||||
(e.g., `10.11.12.3`), NOT to the local bridge IP (`10.11.13.1`). Using the
|
||||
local IP creates a routing loop.
|
||||
|
||||
## DHCP Configuration
|
||||
|
||||
```uci
|
||||
config dhcp 'br_zt'
|
||||
option interface 'zt_wiblan' # Interface name, NOT device name
|
||||
option start '356' # Offset in /23: 10.11.12.0 + 356 = 10.11.13.100
|
||||
option limit '101' # 101 addresses: 10.11.13.100 - 10.11.13.200
|
||||
option leasetime '12h'
|
||||
list dhcp_option '3,10.11.13.1' # Gateway
|
||||
list dhcp_option '6,10.11.13.1' # DNS (use router's dnsmasq)
|
||||
```
|
||||
|
||||
**Key points**:
|
||||
- `interface` must reference the **interface** name (`zt_wiblan`), not the
|
||||
device name (`br-zt` or `br_zt`). dnsmasq binds to interfaces, not devices.
|
||||
- In a `/23` network, `start` is an offset from the network base
|
||||
(`10.11.12.0`). To get `10.11.13.100`, use offset `356` (256 + 100).
|
||||
- DNS should point to the router's dnsmasq (`10.11.13.1`) for reliability.
|
||||
Direct `8.8.8.8` works but adds routing complexity.
|
||||
|
||||
## WiFi AP Configuration
|
||||
|
||||
```uci
|
||||
config wifi-iface 'wifinetN'
|
||||
option device 'radio0'
|
||||
option mode 'ap'
|
||||
option ssid 'WIBLAN'
|
||||
option encryption 'psk2'
|
||||
option key 'your-key'
|
||||
option network 'zt_wiblan' # Interface name, NOT device name
|
||||
```
|
||||
|
||||
**Same rule as DHCP**: `network` must reference the **interface** name,
|
||||
not the device name.
|
||||
|
||||
After configuration:
|
||||
```bash
|
||||
wifi reload
|
||||
# Verify bridge membership:
|
||||
brctl show br-zt
|
||||
# Should show both ztk4jpk77j and phy0-apX
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### 1. Bridge Port Has No IP (ARP Fails)
|
||||
|
||||
**Symptom**: One-way connectivity (A→B works, B→A doesn't).
|
||||
|
||||
**Cause**: When the ZT interface is added to a bridge, its assigned IP can be
|
||||
lost. Without an IP, the interface cannot respond to ARP requests.
|
||||
|
||||
**Fix**: Ensure the ZT interface keeps its assigned IP:
|
||||
```bash
|
||||
# Detect ZT IP:
|
||||
ZT_IP=$(zerotier-cli listnetworks | awk '/OK/{for(i=6;i<=NF;i++) if($i~/\//){split($i,a,"/"); print a[1]; exit}}')
|
||||
ZT_BITS=$(zerotier-cli listnetworks | awk '/OK/{for(i=6;i<=NF;i++) if($i~/\//){split($i,a,"/"); print a[2]; exit}}')
|
||||
|
||||
# Add to ZT interface:
|
||||
ip addr add "${ZT_IP}/${ZT_BITS}" dev ztk4jpk77j
|
||||
```
|
||||
|
||||
### 2. Wrong Interface Reference in UCI
|
||||
|
||||
**Symptom**: dnsmasq doesn't serve DHCP, WiFi AP not bridged.
|
||||
|
||||
**Cause**: Using device name (`br-zt`, `br_zt`) instead of interface name
|
||||
(`zt_wiblan`) in `dhcp.*.interface` or `wireless.*.network`.
|
||||
|
||||
**Fix**: Always reference the **interface** name:
|
||||
```bash
|
||||
uci set dhcp.br_zt.interface=zt_wiblan
|
||||
uci set wireless.wifinet1.network=zt_wiblan
|
||||
```
|
||||
|
||||
### 3. Firewall Zone Missing
|
||||
|
||||
**Symptom**: Traffic from WIBLAN clients is silently dropped.
|
||||
|
||||
**Cause**: nftables fw4 has `policy drop` on INPUT/FORWARD. The bridge
|
||||
interface isn't in any firewall zone.
|
||||
|
||||
**Fix**: Add the interface to the LAN zone:
|
||||
```bash
|
||||
uci add_list firewall.@zone[0].network=zt_wiblan
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall restart
|
||||
```
|
||||
|
||||
### 4. DHCP Range Wrong in /23
|
||||
|
||||
**Symptom**: Clients get IPs in wrong subnet (e.g., `10.11.12.x` instead
|
||||
of `10.11.13.x`).
|
||||
|
||||
**Cause**: In a `/23` network, dnsmasq's `start` is an offset from the
|
||||
network base (`10.11.12.0`), not from `10.11.13.0`.
|
||||
|
||||
**Fix**: Calculate correct offset:
|
||||
```
|
||||
10.11.13.100 = 10.11.12.0 + 356 → start=356
|
||||
10.11.13.200 = 10.11.12.0 + 456 → limit=101 (356+101-1=456)
|
||||
```
|
||||
|
||||
### 5. Table 100 Routes to Self
|
||||
|
||||
**Symptom**: Client traffic loops back to the gateway.
|
||||
|
||||
**Cause**: Table 100 default route points to `WIBLAN_GW` (local bridge IP)
|
||||
instead of the exit gateway IP.
|
||||
|
||||
**Fix**: Route via the exit gateway:
|
||||
```bash
|
||||
ip route replace default via 10.11.12.3 dev ztk4jpk77j table 100
|
||||
```
|
||||
|
||||
### 6. UCI Values Have Embedded Quotes
|
||||
|
||||
**Symptom**: UCI values contain literal single quotes (e.g., `'static'`
|
||||
instead of `static`).
|
||||
|
||||
**Cause**: Shell quotes in `uci set` commands are passed as part of the value:
|
||||
```bash
|
||||
# WRONG:
|
||||
uci set "network.zt_wiblan.proto='static'" # Value becomes 'static'
|
||||
# RIGHT:
|
||||
uci set "network.zt_wiblan.proto=static" # Value becomes static
|
||||
```
|
||||
|
||||
### 7. ZeroTier Ethernet Bridging Disabled
|
||||
|
||||
**Symptom**: WiFi clients get DHCP leases but cannot reach gateway or internet.
|
||||
|
||||
**Cause**: ZeroTier network controller has "Allow Ethernet Bridging" disabled.
|
||||
L2 frames (ARP, DHCP) cannot traverse the tunnel.
|
||||
|
||||
**Fix**: Enable at my.zerotier.com → network → Settings → "Allow Ethernet
|
||||
Bridging".
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After setup, verify each component:
|
||||
|
||||
```bash
|
||||
# 1. Bridge membership
|
||||
brctl show br-zt
|
||||
# Should show: ztk4jpk77j + phy0-apX
|
||||
|
||||
# 2. Bridge IPs
|
||||
ip addr show br-zt
|
||||
# Should show: 10.11.13.1/23
|
||||
|
||||
# 3. ZT interface IP
|
||||
ip addr show ztk4jpk77j
|
||||
# Should show: 10.11.12.x/23
|
||||
|
||||
# 4. Firewall zones
|
||||
nft list chain inet fw4 input | grep br-zt
|
||||
# Should show: iifname { "br-zt", ... } jump input_lan
|
||||
|
||||
# 5. Policy routing
|
||||
ip rule show | grep "from 10.11.13.0/24"
|
||||
# Should show: 100: from 10.11.13.0/24 lookup 100
|
||||
|
||||
# 6. Table 100 route
|
||||
ip route show table 100
|
||||
# Should show: default via 10.11.12.3 dev ztk4jpk77j
|
||||
|
||||
# 7. DHCP
|
||||
cat /tmp/dhcp.leases | grep 10.11.13
|
||||
# Should show client leases
|
||||
|
||||
# 8. Connectivity
|
||||
ping -c 3 10.11.13.135 # From router to client
|
||||
# From client: ping 10.11.13.1 (gateway)
|
||||
# From client: ping 8.8.8.8 (internet via exit gateway)
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The setup script supports these overrides for testing:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `ZTG_BRIDGE` | `br-zt` | Bridge device name |
|
||||
| `ZTG_BRIDGE_PORTS` | auto-detect | Space-separated bridge ports |
|
||||
| `ZTG_WIBLAN_CIDR` | `10.11.13.0/24` | WIBLAN subnet |
|
||||
| `ZTG_WIBLAN_GW` | `10.11.13.1` | WIBLAN gateway IP |
|
||||
| `ZTG_BRIDGE_NETMASK` | `255.255.254.0` | Bridge netmask (/23) |
|
||||
| `ZTG_TABLE_MAIN` | `100` | Main policy table |
|
||||
| `ZTG_TABLE_DRAIN` | `101` | Drain policy table |
|
||||
| `ZTG_WIFI_SSID` | `WIBLAN` | WiFi AP SSID |
|
||||
| `ZTG_WIFI_KEY` | `zt-r0ute-2026` | WiFi AP WPA2 key |
|
||||
| `ZTG_WIFI_RADIO` | auto-detect | WiFi radio device |
|
||||
| `ZTG_SKIP_PERSIST` | `0` | Skip UCI persistence (testing) |
|
||||
Reference in New Issue
Block a user