From 6060ad23b24020a55ec85ab89d27cbbe357e4166 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Thu, 2 Jul 2026 21:54:43 +0000 Subject: [PATCH] fix(tollgate): verify br-tollgate's kernel-level IP after restart, retry if missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found live again, in a different shape: after a later round of service restarts (dnsmasq restart while debugging), br-tollgate desynced a second time — but this time netifd's own status reported the interface up with 192.168.99.1 assigned, while `ip -4 addr show br-tollgate` was genuinely empty at the kernel level. dnsmasq logged "DHCP packet received on br-tollgate which has no address" and silently dropped every DISCOVER — clients associated to the archipelago SSID fine but never got an IP. A single blind ifdown/ifup (the previous fix) isn't trustworthy against this netifd race — replace it with a loop that checks the actual kernel address after each cycle and retries up to 5 times, failing loudly (rather than silently leaving DHCP broken) if it never converges. --- core/openwrt/src/tollgate/mod.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/core/openwrt/src/tollgate/mod.rs b/core/openwrt/src/tollgate/mod.rs index c05d96a4..8017f8f0 100644 --- a/core/openwrt/src/tollgate/mod.rs +++ b/core/openwrt/src/tollgate/mod.rs @@ -86,12 +86,27 @@ fn restart_services(router: &Router, enabled: bool) -> Result<()> { // Reload wireless so wireless.tollgate.disabled takes effect on the radio — // `network restart` alone doesn't reliably reconfigure wifi interfaces. router.run_ok("wifi down 2>&1; wifi up 2>&1")?; - // Observed live: netifd can lose the race to claim br-tollgate as the - // wifi vif attaches to it during the restart above, leaving the - // interface reported "up: false, DEVICE_CLAIM_FAILED" even though the - // bridge device and its member both exist. An explicit down/up of just - // this logical interface resolves it — needed before NoDogSplash starts, - // since it refuses to start against an interface netifd hasn't brought up. - router.run_ok("sleep 2; ifdown tollgate 2>&1; sleep 1; ifup tollgate 2>&1; sleep 2")?; + // Observed live, twice, in two different ways: netifd can lose the race + // to claim br-tollgate as the wifi vif attaches to it during the restart + // above. The first time it showed up as netifd reporting + // "up: false, DEVICE_CLAIM_FAILED"; the second time netifd reported the + // interface up with its address assigned while the kernel-level device + // genuinely had none (`ip -4 addr show br-tollgate` empty) — dnsmasq + // logged "DHCP packet received on br-tollgate which has no address" and + // silently dropped every DISCOVER. A single blind ifdown/ifup isn't + // trustworthy here — verify the address actually landed at the kernel + // level (not just what netifd claims) and retry the cycle if not, since + // NoDogSplash refuses to start against an interface that isn't really up + // and dnsmasq will silently refuse to answer DHCP without erroring loudly. + router.run_ok( + "sleep 2; \ + for i in 1 2 3 4 5; do \ + ifdown tollgate 2>&1; sleep 1; ifup tollgate 2>&1; sleep 2; \ + ip -4 addr show br-tollgate 2>/dev/null | grep -q 'inet ' && break; \ + echo \"br-tollgate has no kernel-level IPv4 address after cycle $i, retrying\"; \ + done; \ + ip -4 addr show br-tollgate 2>/dev/null | grep -q 'inet ' || \ + { echo 'br-tollgate never got a kernel-level IPv4 address after 5 cycles'; exit 1; }" + )?; Ok(()) }