archy/core/openwrt/src/tollgate/nodogsplash.rs
ssmithx c1f191128f fix(tollgate): restore DHCP/DNS in nodogsplash's pre-auth walled garden
Found live: new clients on the archipelago SSID couldn't get an IP
address at all. configure()'s users_to_router rebuild replaced the
nodogsplash package's stock default list (DNS, DHCP, SSH/Telnet to the
router) with only our two TollGate-specific ports (2121, 2050) —
dropping `allow udp port 67`, so DHCP DISCOVER from an unauthenticated
client hit ndsRTR's default REJECT before ever reaching dnsmasq.

Carries over DNS (53) and DHCP (67/udp) from the stock default —
without them a client can't get an IP or resolve anything before
authenticating. Deliberately does not carry over SSH/Telnet (22/23):
the stock default exposes router shell access to every unauthenticated
device on the network, which isn't an appropriate default for a public
pay-as-you-go hotspot.
2026-07-02 20:51:27 +00:00

93 lines
5.0 KiB
Rust

use anyhow::{Context, Result};
use crate::opkg::PkgManager;
use crate::tollgate::TollGateConfig;
use crate::Router;
/// Install NoDogSplash and immediately stop it, before configuring anything.
///
/// 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")?;
// The nodogsplash package's own uci-defaults populate an anonymous
// `@nodogsplash[0]` section on first install, pointed at `br-lan` (its
// stock default — see `install_and_stop`). NoDogSplash supports multiple
// simultaneous gateway instances, one per config section, so leaving this
// in place alongside our own `nodogsplash.main` doesn't get overridden by
// it — it starts a *second* instance gating br-lan for real. Delete it;
// `main` is the only instance this project manages.
let _ = router.uci_delete("nodogsplash.@nodogsplash[0]");
router.uci_set("nodogsplash.main", "nodogsplash")?;
router.uci_set("nodogsplash.main.enabled", "1")?;
router.uci_set("nodogsplash.main.gatewayinterface", "br-tollgate")?;
router.uci_set("nodogsplash.main.gatewayname", &format!("{} Portal", cfg.ssid))?;
router.uci_set("nodogsplash.main.gatewaydomainname", "TollGate.lan")?;
router.uci_set("nodogsplash.main.gatewayport", "2050")?;
// Pre-auth "walled garden": traffic an unauthenticated client must still
// reach before ndsctl authorizes their MAC. `uci_delete` + rebuild (rather
// than only adding our own entries) is deliberate — the stock package
// config ships a `users_to_router` default of its own (DNS, DHCP, plus
// SSH/Telnet to the router), and `uci_set`/`add_list` on an existing
// *named* section does not clear an inherited default list, so without
// an explicit delete first, re-provisioning would silently keep
// whatever was there before.
//
// DNS (53) and DHCP (67, udp) are carried over from that stock default —
// without them a client can't even get an IP or resolve the portal
// domain before authenticating (confirmed live: omitting udp/67 here
// broke DHCP entirely for new clients on the archipelago SSID). 2121
// (TollGate payment) and 2050 (NDS's own splash portal) are ours.
// SSH/Telnet (22/23) are deliberately *not* carried over — the stock
// default exposes router shell access to every unauthenticated device
// on a public pay-as-you-go network, which is a bad default here.
let _ = router.uci_delete("nodogsplash.main.users_to_router");
router.uci_add_list("nodogsplash.main.users_to_router", "allow udp port 53")?;
router.uci_add_list("nodogsplash.main.users_to_router", "allow tcp port 53")?;
router.uci_add_list("nodogsplash.main.users_to_router", "allow udp port 67")?;
router.uci_add_list("nodogsplash.main.users_to_router", "allow tcp port 2121")?;
router.uci_add_list("nodogsplash.main.users_to_router", "allow tcp port 2050")?;
router.uci_commit(Some("nodogsplash"))?;
Ok(())
}
/// (Re)start the nodogsplash service so config changes and gate state take effect.
pub fn restart(router: &Router) -> Result<()> {
router.run_ok("/etc/init.d/nodogsplash enable")?;
router.run_ok("/etc/init.d/nodogsplash restart || /etc/init.d/nodogsplash start")?;
Ok(())
}