fix(tollgate): verify br-tollgate's kernel-level IP after restart, retry if missing

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.
This commit is contained in:
ssmithx 2026-07-02 21:54:43 +00:00
parent c1f191128f
commit 6060ad23b2

View File

@ -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(())
}