From 7439a1251c67b93c661861ed2b8dbab4e906d7a0 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Thu, 2 Jul 2026 19:28:48 +0000 Subject: [PATCH] fix(tollgate): fix nodogsplash provisioning order (found live: gated br-lan) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deploying the previous commit's fix live exposed a real bug: nodogsplash's OpenWrt package postinst auto-enables and starts the service immediately on install, using its stock default config — gatewayinterface=br-lan. Since NoDogSplash only touches IPv4 iptables, this silently cut IPv4 (not IPv6) connectivity for anything on br-lan, including the admin management box plugged into this router's LAN port, for the window between install and our own configure step. Split nodogsplash provisioning into install_and_stop() (runs first, closes that window immediately) and configure() (runs after wifi::provision_ssid has created br-tollgate, so gatewayinterface is pointed at the isolated tollgate bridge instead of br-lan). --- core/openwrt/src/tollgate/mod.rs | 21 ++++++++---- core/openwrt/src/tollgate/nodogsplash.rs | 41 +++++++++++++++++------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/core/openwrt/src/tollgate/mod.rs b/core/openwrt/src/tollgate/mod.rs index 88d8db0a..9f9e3ab0 100644 --- a/core/openwrt/src/tollgate/mod.rs +++ b/core/openwrt/src/tollgate/mod.rs @@ -14,12 +14,14 @@ use crate::{opkg::PkgManager, Router}; /// Full TollGate provisioning sequence: /// 1. Install tollgate-module-basic-go -/// 2. Install and configure NoDogSplash (client gating — tollgate-wrt has no -/// firewall/enforcement code of its own; see `nodogsplash::provision`) +/// 2. Install NoDogSplash and immediately stop it (its postinst auto-starts +/// it against `br-lan` by default — see `nodogsplash::install_and_stop`) /// 3. Write TollGate config: UCI (status/detection only) + the JSON file the /// daemon actually reads /// 4. Create the pay-as-you-go WiFi SSID and its dedicated bridge/network -/// 5. Restart affected services +/// 5. Configure NoDogSplash to gate that bridge (now that it exists) — +/// client gating; tollgate-wrt has no enforcement code of its own +/// 6. Restart affected services pub async fn provision(router: &Router, config: &TollGateConfig) -> Result<()> { info!("[{}] Starting TollGate provisioning", router.host); @@ -40,9 +42,11 @@ pub async fn provision(router: &Router, config: &TollGateConfig) -> Result<()> { // the raw .ipk-extraction fallback (used whenever the package isn't in a // feed, and always on ApkNative) skips dependency resolution entirely. // Without it, tollgate-wrt runs and accepts payments but never actually - // blocks unpaid clients. - nodogsplash::provision(router, pkg_mgr, config) - .context("provision nodogsplash — tollgate-wrt cannot gate clients without it")?; + // blocks unpaid clients. Install + stop happens before anything else so + // its auto-started default config (gating br-lan) is live for as little + // time as possible. + nodogsplash::install_and_stop(router, pkg_mgr) + .context("install nodogsplash — tollgate-wrt cannot gate clients without it")?; config::apply(router, config)?; wifi::provision_ssid(router, config)?; @@ -50,6 +54,11 @@ pub async fn provision(router: &Router, config: &TollGateConfig) -> Result<()> { // the daemon restart below — config.json is only read at startup. config::apply_daemon_config(router, config) .context("write /etc/tollgate/config.json — tollgate-wrt reads this, not UCI")?; + // Also must come after provision_ssid: points gatewayinterface at + // br-tollgate, which provision_ssid is what creates. + nodogsplash::configure(router, config) + .context("configure nodogsplash — tollgate-wrt cannot gate clients without it")?; + restart_services(router, config.enabled)?; nodogsplash::restart(router)?; diff --git a/core/openwrt/src/tollgate/nodogsplash.rs b/core/openwrt/src/tollgate/nodogsplash.rs index 5742cee2..06025bf4 100644 --- a/core/openwrt/src/tollgate/nodogsplash.rs +++ b/core/openwrt/src/tollgate/nodogsplash.rs @@ -4,23 +4,40 @@ use crate::opkg::PkgManager; use crate::tollgate::TollGateConfig; use crate::Router; -/// Install and configure NoDogSplash — the captive-portal engine that -/// `tollgate-wrt` delegates all MAC authorization and gate open/close to via -/// `ndsctl`. `tollgate-wrt` contains no firewall/netfilter code of its own -/// (confirmed: its binary has no `nft`/`ipset`/`iptables` calls at all); the -/// upstream package therefore hard-depends on `+nodogsplash` and its postinst -/// restarts it. Without NoDogSplash actually running, nothing blocks -/// unauthenticated clients from reaching the internet, regardless of what -/// TollGate itself is configured to charge. +/// Install NoDogSplash and immediately stop it, before configuring anything. /// -/// Gates the dedicated `br-tollgate` bridge (see `wifi::provision_network`), -/// not `br-lan` — the paid SSID here lives on its own isolated network/subnet -/// rather than the canonical upstream layout where it's bridged into `lan`. -pub fn provision(router: &Router, pkg_mgr: PkgManager, cfg: &TollGateConfig) -> Result<()> { +/// The OpenWrt package's postinst auto-enables and starts nodogsplash on +/// install using its stock default config — critically, `gatewayinterface` +/// defaults to `br-lan`. On a fresh install that window is real: NoDogSplash +/// only manages IPv4 iptables, so anything plugged into `br-lan` (e.g. an +/// admin's own management box) silently loses IPv4 connectivity (DHCP still +/// listens, but the gate blocks the client until ndsctl authorizes its MAC) +/// until we get a chance to repoint it — a full network re-scan can take +/// long enough for that to matter. Stopping it right after install, before +/// `configure()` ever runs, closes that window as early as possible. +/// +/// `tollgate-wrt` delegates all MAC authorization and gate open/close to +/// NoDogSplash via `ndsctl` — it has no firewall/netfilter code of its own +/// (confirmed: its binary has no `nft`/`ipset`/`iptables` calls at all). +/// Upstream's package therefore hard-depends on `+nodogsplash`, but neither +/// of our install paths (see `tollgate::install`) pull it in automatically. +pub fn install_and_stop(router: &Router, pkg_mgr: PkgManager) -> Result<()> { router .install_package(pkg_mgr, "nodogsplash") .context("install nodogsplash — required by tollgate-wrt for client gating")?; + router.run_ok("/etc/init.d/nodogsplash stop || true")?; + Ok(()) +} +/// Configure NoDogSplash to gate the dedicated `br-tollgate` bridge (see +/// `wifi::provision_network`), not `br-lan` — the paid SSID here lives on its +/// own isolated network/subnet rather than the canonical upstream layout +/// where it's bridged into `lan`. +/// +/// Must run after `wifi::provision_ssid` has created `br-tollgate` — pointing +/// `gatewayinterface` at a bridge that doesn't exist yet is at best a no-op +/// and at worst leaves NoDogSplash in a confused state. +pub fn configure(router: &Router, cfg: &TollGateConfig) -> Result<()> { router.run_ok("touch /etc/config/nodogsplash")?; router.uci_set("nodogsplash.main", "nodogsplash")?; router.uci_set("nodogsplash.main.enabled", "1")?;