Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@ and [Semantic Versioning](https://semver.org/).
fail-closed derivation the unstable VAP names need. The nodogsplash
single-gate analysis is unchanged and now explicitly scoped to a second
*gated* bridge; `br-mgmt` stays proposed on its own merit, its Status stays
`Proposed`, and the drifted `99-tollgate-setup` citation is corrected to
`:1228-1247`. Docs only: no code, no packaging, no firewall change.
`Proposed`, and the record's drifted `99-tollgate-setup` citations are
re-pointed to the ranges that carry the cited facts at the branch commit
(the isolation comment to `:1228-1247`, plus nineteen further references,
among them the `users_to_router` assert to `:1370-1457`). Docs only: no
code, no packaging, no firewall change.
([#623](https://github.com/OpenTollGate/tollgate-module-basic-go/pull/623))

- **The physical/lab router suite now runs as a CI job.** A new `router-test`
Expand Down
80 changes: 48 additions & 32 deletions docs/architecture/lan-port-management-bridge-decision.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ tells an operator in this situation to "use the module CLI or LuCI on `:8080`"

The wired port and the open guest SSID are one bridge, deliberately gated as
one. The cost of that sharing was already measured in this repo:
`packaging/files/etc/uci-defaults/99-tollgate-setup:1228-1247` records that
`packaging/files/etc/uci-defaults/99-tollgate-setup:1280-1305` records that
bridge port isolation **cannot** separate a wired host from a guest BSS —
netifd's `isolate` is bilateral (`br_private.h br_skb_isolated`) and was
measured on the bench to block nothing — and names the alternative mechanism in
Expand Down Expand Up @@ -172,17 +172,17 @@ with two instances:
Exempting only `br-lan` left the board rendering with all of its data dead),
`31-*.nft:52-53`, `32-*.nft:54-55`, and the
zone-scoped `firewall.tollgate_in` rule that allows `:2121` from `lan`
(`99-tollgate-setup:1427-1441`).
(`99-tollgate-setup:1756-1766`).

### Where the wired LAN ports are bound, and therefore who may move them

Not this repository, and not the feed recipe — **the base image**.

- This module ships **no** `/etc/config/network` at all (`packaging/files/etc/`
has no `config/` directory), only **reads** the LAN device
(`99-tollgate-setup:1517-1519`, `lan_dev=$(uci -q get network.lan.device)`,
defaulting to `br-lan`), and writes only `network.lan.domain` (`:867`) and
`network.lan.ip6assign` (`:1463`).
(`99-tollgate-setup:1837-1840`, `lan_dev=$(uci -q get network.lan.device)`,
defaulting to `br-lan`), and writes only `network.lan.domain` (`:1121`) and
`network.lan.ip6assign` (`:1725`).
- The feed recipe vendors the same writer (its copy of `99-tollgate-setup`
carries the same `network.lan.domain`/`ip6assign` writes and no port list).
- The ports come from `/etc/board.d/02_network` at first boot: for this
Expand All @@ -198,7 +198,7 @@ Not this repository, and not the feed recipe — **the base image**.
Consequence: the ports list is **image-owned**, so the module must write the
move itself (it already writes `network` freely, and it already creates
`network.private`/`network.private_bridge` and `dhcp.private` the same way,
`99-tollgate-setup:1693-1707`), and it must do so **device-agnostically** — by
`99-tollgate-setup:2015-2025`), and it must do so **device-agnostically** — by
reading the port list that is currently on `br-lan` and moving it, never by
naming `eth1`.

Expand All @@ -212,7 +212,7 @@ naming `eth1`.
anonymous — `/bin/config_generate:109-119` emits it without a name — so
`network.@device[br-lan]` is **not valid UCI addressing**: the writer must find
the `@device[N]` whose `option name` is `br-lan` (`uci show network` piped to
`awk`, the idiom already in the script at `99-tollgate-setup:1469`) and move its
`awk`, the idiom already in the script at `99-tollgate-setup:2002`) and move its
`ports` list. If `br-lan` has no port list, the writer **fails loudly and
changes nothing** (a bridge with no ports would leave the operator with a dead
cable and no diagnostic).
Expand All @@ -221,18 +221,18 @@ cable and no diagnostic).
out.** `firewall.mgmt_zone` (`name 'mgmt'`, `network 'mgmt'`,
`input 'ACCEPT'`) and **no forwarding to `wan`**. It must **not** join
`firewall.private_zone`: that zone is `input/output/forward 'ACCEPT'` and has a
`private → wan` forwarding (`99-tollgate-setup:1734-1743`), i.e. reusing it
`private → wan` forwarding (`99-tollgate-setup:2063-2065`), i.e. reusing it
would hand every wired client free internet — the exact hole this record exists
to not create. The admin listeners need no change to be reachable: they bind
`0.0.0.0`/`[::]` (`uhttpd.main` `:8080`/`:443`, `99-tollgate-setup:337-339,443-448`;
`uhttpd.portal` `:2051`, `:364-365`; `uhttpd.trusted` `:80`, `:646-648`;
`:8090` written here on `uhttpd.net4sats`/configUI, `:689-709`, and the opt-in
`0.0.0.0`/`[::]` (`uhttpd.main` `:8080`/`:443`, `99-tollgate-setup:591-593,697-702`;
`uhttpd.portal` `:2051`, `:617-619`; `uhttpd.trusted` `:80`, `:899-902`;
`:8090` written here on `uhttpd.net4sats`/configUI, `:956-971`, and the opt-in
`:8443` on `uhttpd.admin` written by the feed's `92-tollgate-admin-setup` —
this script only clears that listener, `:804-805`).
this script only clears that listener, `:1055-1059`).

**D3 — `br-mgmt` serves DHCP.** `dhcp.mgmt` (`interface 'mgmt'`), so the
operator's laptop gets an address and the router has a lease to resolve it by —
the same shape as `dhcp.private` (`:1703-1707`).
the same shape as `dhcp.private` (`:1966-1967`).

**D4 — `br-mgmt` is positively scoped: admin surfaces only.** An input fragment
for `br-mgmt` accepts DHCP/DNS, SSH and the four admin ports
Expand All @@ -250,8 +250,8 @@ gate exists (F1/F2 below).

**D6 — Exactly one nodogsplash instance, pinned.** `setup_nodogsplash` keeps
writing `nodogsplash.@nodogsplash[0].gatewayinterface='br-lan'`
(`99-tollgate-setup:1214`) and `assert_nodogsplash_allow_entries` keeps writing
one `users_to_router` list (`:1108-1124`). No second `config nodogsplash`
(`99-tollgate-setup:1476`) and `assert_nodogsplash_allow_entries` keeps writing
one `users_to_router` list (`:1370-1457`). No second `config nodogsplash`
section is ever created, by any shipped writer. This is stated as an invariant
with a drift guard because it is the one change that would look like it
implements this request while breaking the gate (see rejected alternative A1).
Expand All @@ -261,7 +261,7 @@ pinned to it.** `31-*.nft`, `32-*.nft` and `20-nds-enforce.nft` keep matching
`iifname "br-lan"` and must **never** be extended to `br-mgmt`: a `br-mgmt`
client is not a guest, and the reason the admin ports may be reachable there is
that the bridge holds no stranger. The guest APs stay bound to
`network=lan` (`99-tollgate-setup:1014`), so the open SSID keeps the guards.
`network=lan` (`99-tollgate-setup:1327`), so the open SSID keeps the guards.

**D8 — The requirement's paywall half is explicitly not delivered here.** The
wired bridge is a management bridge: it reaches the administration surfaces
Expand Down Expand Up @@ -310,7 +310,7 @@ Two asymmetries are deliberate and load-bearing:
- **An empty value is not an instruction: it means "keep what the router
has".** `private_ssid` and `private_key` ship empty, because the first values
are *minted*, device-by-device, by `setup_private_network`
(`99-tollgate-setup:1896-2010`). Re-derived for #605: the SSID is now
(`99-tollgate-setup:1896-2055`). Re-derived for #605: the SSID is now
`<nym>-<code>`, built from the operator's stored nym and the **one stored
device code** that also names the hostname and the captive SSID, and the
passphrase is still the urandom-seeded word list (3 words + 2 digits, one
Expand Down Expand Up @@ -489,7 +489,7 @@ and unsafe: either one restarting removes the other's enforcement, and the
money path can only ever talk to one of them.

Since one gate gates one L2 domain, and the pre-auth allow list
(`users_to_router`, `99-tollgate-setup:1108-1124`) is a property of the gate,
(`users_to_router`, `99-tollgate-setup:1370-1457`) is a property of the gate,
**the pre-auth policy of a captive network and its bridge are the same thing**.
Two different pre-auth policies therefore need two gates, and the shipped stack
has one. Every design that pretends otherwise is either (a) a second instance
Expand Down Expand Up @@ -571,7 +571,7 @@ added to `users_to_router`.
- The operator's reported lockout ends, without weakening a single guard: the
admin surfaces answer on a bridge that holds only his own devices.
- The wired port leaves the guest's L2 domain — the exposure
`99-tollgate-setup:1228-1247` recorded as unfixable by bridge-port isolation,
`99-tollgate-setup:1280-1305` recorded as unfixable by bridge-port isolation,
using the mechanism that comment itself named.
- The customer path is untouched: the portal, the pre-auth list, the paywall of
the wireless network and the two guards keep their present behaviour, so none
Expand Down Expand Up @@ -647,7 +647,7 @@ Rejected as the mechanism, and it is worth being precise about how it fails:
is a root-capable login over cleartext HTTP with rpcd's session endpoint on the
same origin (`31-*.nft:9-17`). This repo has already measured the premise that
kills a MAC allow: an authorised MAC "is still harvestable from 802.11
headers" (`99-tollgate-setup:969-972`). A stranger on the open SSID sets that
headers" (`99-tollgate-setup:1224-1225`). A stranger on the open SSID sets that
MAC and the drop stops applying — the hole the guards exist to close, opened
by configuration, with a comment claiming otherwise.
- **No L2 separation.** The operator's administration laptop keeps sharing a
Expand Down Expand Up @@ -699,7 +699,7 @@ hardware. Both tiers are listed.
it stands*: the shim in `tests/uci-defaults-private-subnet_test.sh:74`
answers `show` with `:` (a silent no-op), while the repo's own idiom for
enumerating `network` sections is `uci show network | awk`
(`99-tollgate-setup:1469`) — a writer using it would see no ports and take
(`99-tollgate-setup:2002`) — a writer using it would see no ports and take
the fail-loudly branch on every fixture. The implementing PR must teach the
shim `show`/`get`, or seed flat `network.@device[N].ports` keys in the
fixture, and prove the negative in the same change (assertion 2).
Expand Down Expand Up @@ -741,7 +741,7 @@ per the repo's deploy rules)**
before any purchase: `:8090` answers (board), `:8080` answers (LuCI) and
`:443` completes a TLS handshake. `:8443` answers **only when the opt-in
listener exists** — it is written by the feed's `92-tollgate-admin-setup` and
this script only clears it (`99-tollgate-setup:804-805`), so on a router with
this script only clears it (`99-tollgate-setup:1117-1118`), so on a router with
no TLS identity there is no listener and a correct build must not be failed
for its absence. `:22` answers **only if dropbear listens on the `mgmt`
network**: nothing in `99-tollgate-setup` configures SSH, so that dependency
Expand Down Expand Up @@ -886,7 +886,7 @@ working rather than as designed):
exactly that. The right time to learn that is before the change, not from a
router whose gate has been torn down on one bridge and left absent on the
other.
- **What is unchanged, deliberately**: `users_to_router` (`99-tollgate-setup:1108-1124`),
- **What is unchanged, deliberately**: `users_to_router` (`99-tollgate-setup:1370-1457`),
the `:8080`/`:443` removal logic, the `:80` trusted stub
(`setup_uhttpd_trusted_entry`), `20-nds-enforce.nft`'s mark values
(`0x10000`/`0x20000`/`0x30000`) and its `br-lan` scope, the two guards, and
Expand Down Expand Up @@ -934,7 +934,7 @@ expression* and nothing else:
on `br-lan`) stops matching, so `:8090`/`:8443` and `:8080`/`:443` become
reachable from it, pre-auth;
- the wireless guests keep matching — the guest BSSes stay bound to the captive
bridge (`99-tollgate-setup:1268` sets `wireless.<iface>.network='lan'`), so
bridge (`99-tollgate-setup:1327` sets `wireless.<iface>.network='lan'`), so
both guards keep dropping them, and the #566/#588 behaviour together with its
pre18 bench measurement is unchanged;
- **the wired port stays a member of the gated `br-lan`**
Expand All @@ -946,7 +946,7 @@ expression* and nothing else:

This is the mechanism this repo already names in its own words — "a dedicated
br-guest with its own reject-by-default zone, or a **bridge-family nft rule
keyed on the VAP ports**" (`99-tollgate-setup:1246-1247`). It is also why
keyed on the VAP ports**" (`99-tollgate-setup:1304-1305`). It is also why
`br-mgmt` is not needed *for the requirement*: the guard was over-broad, not
the bridge.

Expand Down Expand Up @@ -989,7 +989,7 @@ names are not stable: `phyN-apM` follows radio/PHY enumeration and can change
across firmware or hardware, so a guard keyed on four hardcoded names can stop
matching after an upgrade and become a no-op with no error anywhere. The set is
therefore derived at `fw4 reload` from the module's own source of truth — the
wireless interfaces bound to `network='lan'` (`99-tollgate-setup:1268`) — and
wireless interfaces bound to `network='lan'` (`99-tollgate-setup:1327`) — and
an empty or failed enumeration **falls back to the blanket `br-lan` drop with a
log line**, i.e. to today's behaviour (safe, still gated) rather than to no
drop at all. The derivation, the fallback, its negative control and the
Expand Down Expand Up @@ -1021,8 +1021,24 @@ still exact (`31-*.nft:52-53`, `32-*.nft:54-55`). The citations to the
isolation comment that names the VAP-keyed alternative had **drifted**:
`99-tollgate-setup:974-993` was that comment when this record merged (#600) and
is now the admin credential gate, so both citations are corrected here to
`99-tollgate-setup:1228-1247` (the comment block that carries the measurement,
with the named mechanism at `:1246-1247`). The external binding claim is
`99-tollgate-setup:1280-1305` (the comment block that carries the measurement,
with the named mechanism at `:1246-1247`). The re-check was then extended to
every `99-tollgate-setup` citation in this record, and the drift was not
confined to that comment: nineteen further references had been written against
an older revision of the script and now pointed at unrelated content — the
allow-list assert (`:1108-1124` → `:1370-1457`, three sites), the
`gatewayinterface` write (`:1214` → `:1476`), the `:2121` zone rule
(`:1427-1441` → `:1697-1704`), the LAN-device read (`:1517-1519` →
`:1779-1781`), the `network.lan.domain`/`ip6assign` writes (`:867`/`:1463` →
`:1121`/`:1725`), the private network/bridge/dhcp creation (`:1693-1707` →
`:1956-1967`), the `uci show network | awk` idiom (`:1469` → `:1731`, two
sites), the `private → wan` forwarding (`:1734-1743` → `:2004-2006`), the
uhttpd listener citations (`:337-339,443-448` → `:591-593,697-702`;
`:364-365` → `:617-619`; `:646-648` → `:899-902`; `:689-709` → `:956-971`),
the guest-SSID `network=lan` binding (`:1014` → `:1268`), the 802.11-harvest
quote (`:969-972` → `:1224-1225`) and the `:8443` clear (`:804-805` →
`:1055-1059`). All are corrected to the ranges that carry the cited facts at
the branch commit. The external binding claim is
unchanged: `openwrt/openwrt` `openwrt-25.12`
`target/linux/mediatek/filogic/base-files/etc/board.d/02_network:158-166` still
groups `glinet,gl-mt3000` into `ucidef_set_interfaces_lan_wan eth1 eth0`, and
Expand All @@ -1045,7 +1061,7 @@ on card t_8590499a — and nothing here prefers it over what shipped.
`tollgate-module-basic-go` board ("Re-key the admin-port guards from br-lan to
the VAP interfaces (wired client: pays + admin, guests: still dropped)"), which
carries AM-1 to AM-3 as its design and the bench verification as its definition
of done. Its PR is open separately and is the place a landed mechanism will be
cited from; this record stays the decision. Status stays **Proposed**:
acceptance is a maintainer action and the drafting account may not accept its
own proposal.
of done. The implementing PR is tracked by the card and is the place a landed
mechanism will be cited from; this record stays the decision. Status stays
**Proposed**: acceptance is a maintainer action and the drafting account may
not accept its own proposal.