fix: routing ip rule missing after reboot, busybox compat, skill restructure

Routing fixes (2026-07-14):
- Add missing ip rule 'from 10.11.13.0/24 lookup 100' to hotplug ifup case
- Add UCI network rule persistence so netifd restores it on boot
- Verify ip rule exists in zt-gateway-switch do_force/do_graceful
- Fix BRIDGE_PORTS auto-detect: use /proc/net/dev instead of broken
  awk-over-ip pipeline (busybox awk mishandles exit in compound if)
- Validate bridge port candidate exists as network interface
- Fix setup-routing: use dev br-zt not dev ztX (ZT iface has no IP
  when enslaved to bridge, causing 'Nexthop has invalid gateway')
- Replace ip rule replace (GNU-only) with del+add for busybox

Infrastructure:
- Fix deploy:install stdin starvation: ssh/scp consume pipe data in
  find|while loop; add </dev/null to prevent truncation
- Move luci-dev skill from root/ to skills/ with .agents/skills/ symlink
- Add policy routing and busybox gotcha sections to SKILL.md
- Add diagnostics doc for the routing fix session
This commit is contained in:
2026-07-14 18:01:30 +05:30
parent 10081b33e2
commit 581d625044
9 changed files with 294 additions and 15 deletions

View File

@@ -52,8 +52,20 @@ BRIDGE="${ZTG_BRIDGE:-br-zt}"
if [ -n "${ZTG_BRIDGE_PORTS:-}" ]; then
BRIDGE_PORTS="$ZTG_BRIDGE_PORTS"
else
BRIDGE_PORTS=$(ip -o link show 2>/dev/null \
| awk -F': ' '/^[0-9]+:/{gsub(/@.*/, "", $2); if ($2 ~ /^zt/ && $2 != "br-zt") print $2; exit}')
# Auto-detect: try UCI first, then /proc/net/dev (busybox-safe),
# then ip link as last resort. Busybox awk mishandles `exit` inside
# compound `if` blocks in `-F` pipelines, so avoid that pattern.
# Validate that any candidate actually exists as a network interface.
BRIDGE_PORTS=$(uci -q get zt-gateway.global.bridge_ports 2>/dev/null || true)
if [ -n "$BRIDGE_PORTS" ] && ! ip link show "$BRIDGE_PORTS" >/dev/null 2>&1; then
BRIDGE_PORTS=""
fi
if [ -z "$BRIDGE_PORTS" ]; then
BRIDGE_PORTS=$(awk -F': ' '/^zt/{print $1; exit}' /proc/net/dev 2>/dev/null || true)
fi
if [ -z "$BRIDGE_PORTS" ]; then
BRIDGE_PORTS=$(ip -br link 2>/dev/null | awk '/^zt/ && !/br-zt/{print $1; exit}')
fi
if [ -z "$BRIDGE_PORTS" ]; then
BRIDGE_PORTS="ztabc0"
fi
@@ -309,11 +321,11 @@ cmd_setup_routing() {
fi
if [ -n "$EXIT_GW" ]; then
log "exit gateway: ${EXIT_GW}"
ip route replace default via "$EXIT_GW" dev "$BRIDGE_PORTS" table "$TABLE_MAIN"
ip route replace default via "$EXIT_GW" dev "$BRIDGE" table "$TABLE_MAIN"
ip route replace "$WIBLAN_CIDR" dev "$BRIDGE" table "$TABLE_MAIN"
# Table 101 (drain): default via exit gateway
ip route replace default via "$EXIT_GW" dev "$BRIDGE_PORTS" table "$TABLE_DRAIN"
ip route replace default via "$EXIT_GW" dev "$BRIDGE" table "$TABLE_DRAIN"
else
log "warning: could not determine exit gateway; using WIBLAN_GW"
ip route replace default via "$WIBLAN_GW" dev "$BRIDGE" table "$TABLE_MAIN"
@@ -325,12 +337,12 @@ cmd_setup_routing() {
ip route replace "$WIBLAN_CIDR" dev "$BRIDGE" table "$TABLE_MWAN"
# ip rule: WIBLAN subnet -> main policy table
ip rule add from "$WIBLAN_CIDR" table "$TABLE_MAIN" priority 100 2>/dev/null || \
ip rule replace from "$WIBLAN_CIDR" table "$TABLE_MAIN" priority 100
ip rule del from "$WIBLAN_CIDR" table "$TABLE_MAIN" priority 100 2>/dev/null || true
ip rule add from "$WIBLAN_CIDR" table "$TABLE_MAIN" priority 100
# ip rule: fwmark 0x100 -> drain table
ip rule add fwmark "$FWMARK" table "$TABLE_DRAIN" priority "$DRAIN_PRIORITY" 2>/dev/null || \
ip rule replace fwmark "$FWMARK" table "$TABLE_DRAIN" priority "$DRAIN_PRIORITY"
ip rule del fwmark "$FWMARK" table "$TABLE_DRAIN" priority "$DRAIN_PRIORITY" 2>/dev/null || true
ip rule add fwmark "$FWMARK" table "$TABLE_DRAIN" priority "$DRAIN_PRIORITY"
log "routing setup complete"
@@ -362,6 +374,14 @@ cmd_setup_routing() {
uci -q set "network.zt_wiblan_subnet.interface=${BRIDGE}"
uci -q set "network.zt_wiblan_subnet.table=${TABLE_MAIN}"
fi
# Policy rule: WIBLAN subnet → main policy table
if ! uci -q get "network.zt_wiblan_rule" >/dev/null 2>&1; then
uci -q set "network.zt_wiblan_rule=rule"
uci -q set "network.zt_wiblan_rule.src=${WIBLAN_CIDR}"
uci -q set "network.zt_wiblan_rule.lookup=${TABLE_MAIN}"
uci -q set "network.zt_wiblan_rule.priority=100"
fi
uci commit network
log "routing UCI config committed"
fi
@@ -542,12 +562,16 @@ case "$ACTION" in
# mwan3 return
ip route replace "$ZTG_WIBLAN_CIDR" dev "$ZTG_BRIDGE" table "$ZTG_TABLE_MWAN"
# ip rule: WIBLAN subnet → main policy table
ip rule del from "$ZTG_WIBLAN_CIDR" table "$ZTG_TABLE_MAIN" priority 100 2>/dev/null || true
ip rule add from "$ZTG_WIBLAN_CIDR" table "$ZTG_TABLE_MAIN" priority 100
# ip rule for drain fwmark
ip rule del fwmark "$ZTG_FWMARK" table "$ZTG_TABLE_DRAIN" \
priority "$ZTG_DRAIN_PRIORITY" 2>/dev/null || true
ip rule add fwmark "$ZTG_FWMARK" table "$ZTG_TABLE_DRAIN" \
priority "$ZTG_DRAIN_PRIORITY" 2>/dev/null || \
ip rule replace fwmark "$ZTG_FWMARK" table "$ZTG_TABLE_DRAIN" \
priority "$ZTG_DRAIN_PRIORITY"
priority "$ZTG_DRAIN_PRIORITY"
;;
ifdown)
logger -t zt-gw-hotplug "ifdown ${ZTG_BRIDGE}: cleaning up"