Skip to content

feat(ssid)!: the captive SSID carries a leading '!' so it sorts first - #860

Open
Amperstrand wants to merge 4 commits into
mainfrom
pr/ssid-leading-bang
Open

Amperstrand wants to merge 4 commits into
mainfrom
pr/ssid-leading-bang

Conversation

@Amperstrand

Copy link
Copy Markdown
Collaborator

What

Revised 2026-10-10: narrowed to the writer half — the reader tolerance moved to #839. See the commit chore(ssid): narrow this PR to the writer half and the comment below.

The guest-facing captive SSID gains a leading ! so it sorts first in an alphabetically-sorted WiFi list (! is 0x21 — before digits and letters):

captive  = !TollGate-<code>      (was: TollGate-<code>)
private  = <nym>-<code>          (UNCHANGED — guests never see this one)

This PR is the module's writer half only. The reader tolerance is carried by #839 (fix/706-ssid-reader-tolerance, split out of #775). Both PRs modify src/wireless_gateway_manager/brands.go, so the reader was removed here — one writer, one reader, no overlap, no merge conflict. The installer half is OpenTollGate/tollgate-installer → pr/installer-ssid-leading-bang (#82, already merged).

Merge timing: after the current release

Deliberately opened now, merged later, so it rides the next release — no separate tag or build, which is what was asked for. It has no dependency on the entry-port remap, and bundling it into the release about to go out would spend that release's risk budget on naming and entangle "did the port flip work?" with "which network did I join?".

The part that is not cosmetic

TollGate-* is a discovery contract, not just a name: the SSID prefix is the v0.6 discovery signal (docs/architecture/discovery-signaling-decision.md), reseller mode selects upstreams by it, and third-party clients in the wild match it. So the ! is implemented as optional decoration, never as a new prefix:

  • shell — strip_ssid_decoration() is the single strip point, called by code_from_name() and captive_ssid_for_code(); the suffix compare uses the stripped name, so a router deployed under the bare spelling and one under the decorated spelling converge on the same device code.
  • Go — hasTollGateSSID() is the single reader (reseller-mode upstream selection plus the vendor-element score). Its decoration tolerance is not in this PR; it is in fix(wireless_gateway_manager): recognize single-'!'-decorated SSIDs — #706's reader half #839, which trims at most one decoration and compares case-insensitively against both brand prefixes, with a double ! explicitly not a TollGate name.

Sequencing, stated plainly

The tolerant reader is #839, not this PR — so #839 must be on main before this writer ships. Otherwise a router running the new writer advertises a !TollGate-* name that a peer on the same release cannot recognise. Beyond that the caveat is cross-version: a reseller router running an older module matches TollGate-* only, so it will not recognise a !TollGate-* upstream until it takes this upgrade. Today the only such peer is the lab bench, so the practical risk is nil — but do not deploy the writer to a server router ahead of its client routers. That is the one order that must not be reversed.

Verified

  • offline uci-defaults battery: device-code 89 passed / 0 failed, admin-tls-identity 43 passed / 0 failed, band rc=0 — all with the ! in place; the remaining two in the battery are long-running and were still going when this was opened
  • cd src/wireless_gateway_manager && go test -count=1 -tags testenv . → ok tollgate-module-basic-go 9.749s
    (note for reviewers: src/wireless_gateway_manager is a nested module, so go test ./... from src/ silently skips it — run it directory-local, or this package's coverage is invisible)
  • tests assert the bare form still adopts the same code — proven, not assumed

One pre-existing red, not caused here (and not papered over)

tests/packaging/rebrand-literal-gutter_test.sh requires zero occurrences of the whitelabel literal outside its allowlist (docs, CHANGELOG.md, README.md, CONTRIBUTING.md, tests/contract/check-ssid-format.sh). It is already RED on main: 7 hits, in shipped code and tests — 99-tollgate-setup, discovery_log.go (the prefix list and two comments), vendor_element_manager.go, brands_test.go — introduced when the whitelabel brand became a supported upstream (#682).

This branch adds zero of them (count 7 → 6): the decorated whitelabel test case reads its prefix from the shipped tollGateBrandPrefixes list instead of re-spelling it, and two comments/fragments say "decoration" instead of naming the brand. The guard's allowlist is stale in two ways — it knows CHANGELOG.md but not the changelog.d/ fragment directory that #679 introduced, and it bans the brand token as a substring even where the whitelabel prefix is shipped mechanism. Fixing that is a separate, deliberate change; say the word and I will open it rather than widen the allowlist quietly here.

Expected on merge

A router renames its AP on the next setup run — anything connected at that moment drops (testers only, today). And on phones it still will not reliably sort first: iOS and Android order that list by signal strength, not alphabetically. The ! helps where alphabetic sorting exists.

Salvage note

Authored by a worker that hit its iteration cap with the tree uncommitted. Committed here with stray go.mod/go.sum (a GOFLAGS=-mod=mod artifact of this environment) reverted, and the README prose restructured so the local secret hook stops reading SSID examples next to the passphrase sentence as credentials.


Migrated from #706: the Amperstrand/felixfelix-bot forks are being deprecated; identical head (pr/ssid-leading-bang branch, exact commit preserved) now lives on the upstream repo. Review history context lives in #706.

felixfelix-bot and others added 4 commits October 10, 2026 18:11
The guest-facing captive SSID becomes !TollGate-<code> ('!' is 0x21, before
digits and letters, so an alphabetically sorted WiFi list shows it first). The
PRIVATE SSID (<nym>-<code>) is unchanged: guests never see it.

The leading '!' is PRESENTATION ONLY, never part of the discovery contract — the
SSID prefix is the v0.6 discovery signal
(docs/architecture/discovery-signaling-decision.md), reseller mode selects
upstreams by it, and third-party clients match the bare brand prefix. So every
reader treats one leading '!' as optional decoration, and the bare form deployed
routers already carry keeps working:

- shell: strip_ssid_decoration() is the single strip point, called by
  code_from_name() and captive_ssid_for_code(); the suffix compare uses the
  stripped name.
- Go: hasTollGateSSID() is the single reader (reseller-mode upstream selection
  plus the vendor-element score), trimming at most ONE decoration before the
  case-insensitive brand compare — a double '!' is not a TollGate name.

Tests: brands_test.go, reseller_mode_test.go, the uci-defaults
device-code/gatewayname/marker-order suites, tests/contract/check-ssid-format.sh,
plus docs and a changelog fragment.

Salvage note: authored by a worker that hit its iteration cap with the tree
uncommitted; committed here, with stray go.mod/go.sum (GOFLAGS=-mod=mod) reverted
and the README prose restructured so the secret hook does not read SSID examples
in a passphrase block as credentials.
tests/packaging/rebrand-literal-gutter_test.sh requires ZERO occurrences of the
whitelabel literal outside its allowlist (docs, CHANGELOG.md, README.md,
CONTRIBUTING.md, tests/contract/check-ssid-format.sh).

It is already RED on main, independently of this branch: 7 hits, in shipped code
and tests (99-tollgate-setup, discovery_log.go's prefix list and comments,
vendor_element_manager.go, brands_test.go) — introduced when the whitelabel brand
became a supported upstream (#682). That is a separate problem and is NOT papered
over here.

What this commit does is make THIS branch add none of them (the count goes 7 -> 6):

- brands_test.go: the decorated whitelabel case reads its prefix from the
  shipped tollGateBrandPrefixes list instead of re-spelling it, so the file
  states what is new — the decoration — and adds no literal. The spelling itself
  is still pinned by the two bare-brand cases above it.
- reseller_mode_test.go: the comment names the decoration, not the brand.
- the changelog fragment says "the same decoration on its own prefix".

Verified: tests/packaging/rebrand-literal-gutter_test.sh count 7 -> 6, and
`cd src/wireless_gateway_manager && go test -count=1 -tags testenv .` (a nested
module — the parent's ./... skips it) => ok tollgate-module-basic-go 9.749s.
The reader tolerance (hasTollGateSSID trimming at most one leading '!' before
the case-insensitive brand-prefix compare) is carried by #839
(fix/706-ssid-reader-tolerance, split out of #775). Both PRs modify
src/wireless_gateway_manager/brands.go, so keeping the reader here would make
the two conflict and let whichever lands second silently revert the other's
matcher. One writer, one reader, no overlap.

Reverted to main — the reader implementation and its tests:
  - src/wireless_gateway_manager/brands.go
  - src/wireless_gateway_manager/brands_test.go
  - src/wireless_gateway_manager/reseller_mode_test.go

The last one is not a "brands*.go" file but is reader-side all the same: it
adds a `!TollGate-DEF` fixture to TestResellerModeEnabled_ScanAllRadios and
asserts hasTollGateSSID recognises it (count 2 -> 3), so it fails without the
reader. Note for #839: it does not carry that reseller-mode coverage, so the
decorated-SSID-selected-as-upstream assertion still has to land somewhere.

Writer half retained: the shell writer and its single strip point in
packaging/files/etc/uci-defaults/99-tollgate-setup, the architecture docs, and
the contract/uci-defaults suites.

Net diff is now writer-only (no src/ changes at all).

Verified on this rebased tree (no workflow reports on a pr/* head branch — see
the PR thread):
  - go build + go test in src/wireless_gateway_manager: ok
  - tests/contract/check-ssid-format.sh: pass
  - tests/uci-defaults-device-code_test.sh: 89 passed, 0 failed
  - tests/uci-defaults-gatewayname-banner_test.sh: 21 passed, 0 failed
  - tests/uci-defaults-setup-marker-order_test.sh: pass
@felixfelix-bot

Copy link
Copy Markdown
Contributor

Review: APPROVE

Reviewed cold at head mod-pr860. The writer half of #706, correctly narrowed, and I verified its
stated precondition is now satisfied — plus one coordination note about overlap with #957.

The change is as described and the ! rationale is correct. The guest SSID goes
TollGate-<code> → !TollGate-<code>, with the private SSID (which guests never see) unchanged:

captive  = !TollGate-<code>      (was: TollGate-<code>)
private  = <nym>-<code>          (UNCHANGED)

0x21 sorts before every digit and letter; I verified the ordering directly
(!TollGate-abc < 2… < A… < T… < Z…), so the guest network does sort first in an
alphabetically-ordered client scan list.

The stated precondition is satisfied — this was the thing to check. The PR says plainly:
"The tolerant reader is #839, not this PR — so #839 must be on main before this writer
ships
. Otherwise a router running the new writer advertises a !TollGate-* name that a peer on the
same release cannot recognise."
I confirmed #839 is MERGED into main (2026-10-11), and that
main's hasTollGateSSID already carries the decoration tolerance:

name := strings.TrimPrefix(ssid, ssidDecoration)
for _, prefix := range tollGateBrandPrefixes { ... strings.EqualFold(...) }

So the one ordering that must not be reversed has been honoured: reader landed, writer is the
follow-up. Landing this now is safe.

The narrowing is genuine, not just declared. The PR body says "both PRs modify
src/wireless_gateway_manager/brands.go, so the reader was removed here — one writer, one reader, no
overlap, no merge conflict", and I checked: this PR no longer touches brands.go at all
(grep -c brands.go → 0). That is the discipline that avoids the usual split-PR trap where the
narrowed diff silently still carries half of the other one.

The cross-version caveat is stated accurately and worth preserving in the review thread: a
reseller router running an older module matches TollGate-* only, so it will not recognise a
!TollGate-* upstream until it upgrades — and the PR draws the conclusion: "do not deploy the writer
to a server router ahead of its client routers. That is the one order that must not be
reversed."
The PR also notes the only such peer today is the lab bench, so practical risk is nil.
Stating both the hazard and why it is currently harmless is the right form; I am restating it here so
it is visible to whoever writes the deploy order later.

⚠️ Coordination note — #957 appears to supersede the writer change. I compared the two branches
directly: #957 already contains DEVICE_SSID="!${BRAND_HOSTNAME}-${CODE}" (its body says
"DEVICE_SSID now carries the !, so this writer emits the string the installer emits"), and on
99-tollgate-setup #957 is a strict superset — it adds 241 lines this PR lacks, and the reverse
diff shows only this PR's older variants of the same helpers (strip_ssid_decoration with a
different body, plus a generate_admin_password that #957 has since refactored). #957 also carries
things this PR does not, notably the cross-repo gatewayname banner pin.

So these two PRs write the same DEVICE_SSID line. They are not in conflict, but they are not
merge-order-independent
: if #860 lands first, #957's rebase should drop its duplicate writer hunk;
if #957 lands first, this PR becomes largely redundant on the writer side. Recommend deciding which
is the canonical carrier
(I read #957 as the intended one, since it is the later and larger change
and explicitly includes this half) and closing or rebasing the other, rather than merging both and
letting the second one land a no-op. Either way this PR's own content is correct — the note is about
sequencing, not about a defect in it.

The installer half is already merged (tollgate-installer #82), so all three readers/writers
agree on the decorated form.

Not hardware-tested. Router-visible (a WiFi SSID), but deterministic and covered by the shell and
Go suites on this branch. A bench check would read the SSID off a scan on tollgate-A04E and confirm
it sorts first — cheap if wanted, under the console mutex.

Labels: not applied — this account has pull: true / triage: false here and no permission to
create or attach labels. Intended label for whoever can: medium (customer-visible SSID ordering;
correctly narrowed to the writer half with the reader precondition verified as merged — but resolve
its overlap with #957 before merging both).

FULL_REVIEW: ~/reports/reviews/tollgate-module-basic-go-pr860-kimi.md

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Local CI gate — PR #860: feat(ssid)!: the captive SSID carries a leading '!' so it sorts first

Run of scripts/pr-verify.sh (the local CI gate: GitHub Actions is
org-blocked and the ngit lane fires only on maintainer mirror pushes,
so a PR ref gets no CI runs — this comment is the offline verification
evidence, produced on the pinned head SHA).

  • Head: pr/ssid-leading-bang @ 0e0fa18585
  • Base: main @ 410ed742a3
  • Gate: pr-verify 294317251383 on ai-legion, started 2026-10-18 10:52 UTC, wall 378s
  • Logs: /tmp/tollgate-pr-verify/860-20261018T104626Z (on the verifying host)
Leg Result Detail
hygiene PASS no conflict markers in the tracked tree (0s)
contract-fences PASS 9 checks green; 4 skipped (absent from tree: entry-UI marker path fence SSID naming contract gonuts pin fence (offline identity) gonuts pin carriers) (2s)
packaging-sequential PASS 12 suites (9s)
shell-suites FAIL red (2/16): uci-defaults-entry-ui-mapping_test.sh ngit-ci-trigger_test.sh — logs in /tmp/tollgate-pr-verify/860-20261018T104626Z/shell-suites (46s)
packaging-concurrent PASS 12 suites, all at once (2s)
go-battery FAIL 16 modules — see go-battery.log (319s)

Verdict: 2 leg(s) RED — 4 green.

What this gate does not run (no vacuous green)
  • spec-quote drift check (greatspectations tool + pinned nuts fetch)
  • reproducibility / conformance lanes (make release-check territory)
  • router-visible behaviour (PRTA / bench — unit green ≠ router works)

Note: the ngit trigger suite red carries the nak key public
nsec-arg signature — the portability break the NGIT_TRUST
generalization fixed. Trees predating that change reproduce it
on this seat; it is environmental, not PR-caused.

failing-leg log tails
shell-suites — tail
--- ngit-ci-trigger_test.sh.log
ok   - refuses a workflow file that is absent from the worktree
ok   - refuses a workflow that is not present at the declared commit
ok   - refuses a signing key that is not the maintainer key
ok   - never prints the private key on refusal
ok   - refuses a key file with no nsec in it
invalid hex key: '0nsec135xmtewapd2nfqc0v4sdu4yamysct99u59lfedfl7nuv6f7ulv9s9tmwcp' is not valid hex: encoding/hex: invalid byte: U+006E 'n'
ok   - prints the sha256 of the file's content at the declared commit
FAIL - fails loudly when every relay refuses the event
       rc=1 out=workflow : .ngit/act/workflows/fake.yml
commit   : 119eb5a615bc3d92208cfe11c77220cfa982d7f2
sha256   : 0a2be7daf3741ba278eca69d6d25755580a1f4223c3c808b5e7e4d621e953544
ERROR: /tmp/tmp.mPdfF47emH/wrong.key is not the maintainer key (signer ..., expected 9cd14d9acdbab7ca...)
ok   - never prints the private key on the happy path

8 passed, 1 failed
--- uci-defaults-entry-ui-mapping_test.sh.log
== F. the marker is honoured on a same-version reinstall too
ok   reinstall: the driver exits 0
ok   reinstall (board): uhttpd.main (LuCI) is on the secondary :8090 pair
ok   reinstall (board): uhttpd.main (LuCI) is on the secondary :8443 pair
ok   reinstall (board): uhttpd.main holds NO entry-pair listener
ok   reinstall (board): uhttpd.main's home is /www
ok   reinstall (board): uhttpd.admin (the board) is on the entry :8080 pair
ok   reinstall (board): uhttpd.admin (the board) is on the entry :443 pair
ok   reinstall (board): the entry board keeps its webroot and error_page
ok   reinstall (board): exactly one section binds :8080 (admin)
ok   reinstall (board): exactly one section binds :443 (admin)
ok   reinstall (board): exactly one section binds :8090 (main)
ok   reinstall (board): exactly one section binds :8443 (main)

tests: 126 passed, 9 failed
go-battery — tail
time="2026-10-18T12:52:46+02:00" level=error msg="Gate close UNRESOLVED: ndsctl never confirmed the deauthorization of this client and the close budget is spent, so the module stops re-attempting it here — it cannot keep hammering the ndsctl socket, because that storm is what wedges it and stops a PAID purchase from being authorised at all. The gate STAYS TRACKED and the client's access state is UNVERIFIED: ndsctl answered with an error, not with an answer about the client. The reconciliation re-attempts this close whenever it verifies the client's state with ndsctl, and the record is forgotten only when a new session replaces it. OPERATOR ACTION: inspect nodogsplash (ndsctl status) — restart it or reload the module to clear the wedge" abandoned_closes=3 attempts=8 budget=8 last_error="exit status 1" mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T12:52:46+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
FAIL
FAIL	github.com/OpenTollGate/tollgate-module-basic-go/src/merchant	288.035s
FAIL
make: *** [Makefile:7: go-battery] Error 1

@felixfelix-bot

Copy link
Copy Markdown
Contributor

Hardware evidence — config-state read on a live bench

reviewer_model: hermes-manager (this session, glm family)

Vantage: bench router 192.168.1.1, GL.iNet GL-MT6000, OpenWrt 24.10.2,
tollgate-wrt 0.6.0_rc6-r1, nodogsplash 5.0.2-r1.
Read-only: uci show,
brctl show, ip route, iw dev, /etc/init.d/tollgate-wrt reads and one
curl of the module's own API. Nothing on the router was mutated.

Caveat stated up front: this is a config/state read, which is valid
regardless of which client asks. It is not a client-journey measurement —
see the vantage note at the end, which matters for any test in this area.


#860 — SSID carries a leading ! — live and confirmed

$ uci show wireless | grep -i ssid
wireless.default_radio0.ssid='!TollGate-0195'
wireless.default_radio1.ssid='!TollGate-0195'
wireless.private_radio0.ssid='c08r4d0r-0195'
wireless.private_radio1.ssid='c08r4d0r-0195'

$ uci -q get nodogsplash.@nodogsplash[0].gatewayname
!TollGate-0195

$ iw dev | grep -A1 Interface
Interface phy1-ap0
    ssid !TollGate-0195

Two things worth noting, both of which the PR should account for:

  1. The ! is on the live radios on BOTH bands, and the nodogsplash
    gatewayname matches it. So the write path that sets it is working on this
    build — the question is only whether every reader strips it.
  2. The private/management SSID (c08r4d0r-0195) does NOT carry the !.
    That is correct and worth an explicit assertion in the PR: the bang belongs to
    the captive SSID only. If a future change applies it to all APs, the
    management network sorts first too and gets scraped by every nearby client.

Recommendation: add a contract test asserting bang-on-captive,
bang-off-management. That is a one-line assertion today and a real regression
guard later.

#874 — IPv6 "disabled globally until capability" — NOT in effect on this box

$ sysctl -n net.ipv6.conf.all.disable_ipv6
0

$ ip -6 addr show | grep -c inet6
5

$ uci show network | grep -i ipv6
(nothing)

disable_ipv6=0 and five IPv6 addresses are configured. Whatever this PR
does, it is not what this box is running, so this is not evidence for or
against the PR — it only tells us the bench does not currently exercise it.

What a real verification needs: flash/install the build containing this PR,
then re-read sysctl net.ipv6.conf.all.disable_ipv6 (expect 1), ip -6 addr
(expect only link-local, or none), and confirm the capability path can re-enable
it. That is a mutating check and I did not do it.

#917 — tollgate.lan alias — resolves, but not via the mechanism named

$ uci show dhcp | grep -iE 'domain|local'
dhcp.@dnsmasq[0].local='/lan/'
dhcp.@dnsmasq[0].domain='lan'

$ nslookup tollgate.lan 127.0.0.1
Name:    tollgate.lan
Address: 192.168.1.1

It resolves. But note there is no dhcp.@dnsmasq[0].host or address= entry
for tollgate.lan
— it resolves because dnsmasq serves the router's own
hostname in the lan zone (the system hostname is tollgate-0195, and
expandhosts/domain=lan do the rest). So the alias works here without an
explicit alias entry.

That matters for this PR: if it adds an explicit entry, it is making the existing
behaviour explicit (good, more robust) — but the PR body should not claim it
"makes tollgate.lan resolve" on a box where it already resolves by hostname.
Worth verifying the intended path on a stock image where the hostname is not
tollgate-*.

#856 — wired LAN ports into a bridge — already bridged here

$ brctl show
bridge name   bridge id            STP  interfaces
br-private    7fff.9483c4d15758    no   phy0-ap1 phy1-ap1 lan1
br-lan        7fff.9483c4d15758    no   phy1-ap0 lan4 lan2 lan5 lan3 phy0-ap0

$ uci show network
network.@device[0].ports='lan2' 'lan3' 'lan4' 'lan5'
network.private_bridge.ports='lan1' 'lan2' 'lan3' 'lan4' 'lan5'

A real inconsistency worth reporting: br-lan carries lan2..lan5 while
private_bridge is configured for lan1..lan5 — so lan2..lan5 are members of
two bridges' port lists. The live brctl output shows them only on br-lan,
which means the private bridge dropped them (a port cannot be in two bridges).
That is a config-vs-runtime split, and it is exactly the class this PR is
touching.

Recommendation: this PR needs a test asserting that the port sets of
br-lan and br-private are disjoint, because the current box demonstrates
overlapping intents resolving silently to whichever bridge claimed the port
first.

#948 — LOGFILE overridable — currently hardcoded, matches the PR's premise

$ grep -nE 'LOGFILE|procd_set_param' /etc/init.d/tollgate-wrt
19:    procd_set_param command /usr/bin/tollgate-wrt
25:    procd_set_param stdout 1
26:    procd_set_param stderr 1
27:    procd_set_param file /tmp/tollgate-debug.log

procd_set_param file /tmp/tollgate-debug.log is a fixed path — so two
instances on one box share one log. That is the shared-log collision this PR
addresses, confirmed in the shipped init script.

Recommendation: land it. Cheap, and the premise is directly observable.

#988 — preserve manually-configured default route — no default route on this box

$ ip route show
192.168.1.0/24 dev br-lan  proto kernel  src 192.168.1.1
192.168.2.0/24 dev br-private proto kernel src 192.168.2.1

No default route at all, and uci show network | grep -iE 'gateway|defaultroute'
is empty. This box is a wanless lab (its uplink is a STA with no default),
so it cannot demonstrate the bug this PR fixes. Neutral evidence — the bench is
the wrong shape for it.
A meaningful test needs a box with a real WAN default
route plus a manually-added one, then a postinst run, then a re-read.

#883 — unmeterableReportInterval — absent, so the fallback path is what runs

uci show tollgate returns only:

tollgate.device=device
tollgate.device.code='0195'
tollgate.device.nym='c08r4d0r'

No unmeterable/interval/report key anywhere in /etc/config/tollgate. So
this box runs whatever the built-in default is — it neither confirms nor refutes
the PR. Neutral.

#899 — uhttpd listeners — already multi-instance and already split

$ uci show uhttpd | grep -E 'listen|main'
uhttpd.main.listen_http='0.0.0.0:8090' '[::]:8090'
uhttpd.main.commonname='tollgate-0195'

$ netstat -ltnp | grep -E ':(80|443|8080|8090) '
0.0.0.0:80    13284/uhttpd
0.0.0.0:443   13281/uhttpd
0.0.0.0:8090  13280/uhttpd
0.0.0.0:8080  13281/uhttpd

Four listeners on four ports across three uhttpd instances. main.listen_http is
8090, and 8080/443 are served by another instance. This is the fixed
shape the PR describes (fresh installs previously left uhttpd.main without its
own listener).

Recommendation: this PR looks already-applied on this box's lineage. Worth
checking whether the bench image includes it — if so, the PR is a backport to
main and can land on that basis rather than needing new hardware proof.

#2121 module contract — healthy, full mode

{"kind":10021, ... "tags":[["metric","milliseconds"],["step_size","60000"],
 ["tips","1","2"],["price_per_step","cashu","1","","http://192.168.1.159:3368","1"]],
 "content":"","sig":"..."}

kind:10021 with price_per_step and step_size tags = full mode, per the
kit's own discriminator. A degraded-mode box would be missing the tags. This is
the healthy baseline against which the module PRs should be compared.


VANTAGE WARNING — read before any client-journey test here

While reading the box I found my own laptop's MAC authenticated on the portal:

$ ndsctl clients
  mac=50:76:af:2f:33:ed  state=Authenticated       <-- my probing host (wlp59s0)
  mac=6c:6e:07:50:c3:3b  state=Preauthenticated

A state=Authenticated client is bypassed — no HTTP interception, so every
redirect-chain and captive-detection reading taken from it is meaningless as
customer evidence. I made exactly that mistake earlier today on PR #150 and had
to retract the redirect measurements.

Before recording any portal/detection result on this bench:

  1. ndsctl clients — confirm the probing MAC is Preauthenticated, not
    Authenticated and not Trusted.
  2. uci show nodogsplash | grep trustedmac — must be empty for a tester.
  3. If not, use a fresh MAC (macvlan) or a second device, and re-check state
    before recording.

Also relevant: the bench was in use by another context window during my reads
(cdk-mintd started on this host at 17:39 and 17:58; the advertisement moved
bytes/22020096/:3358 -> milliseconds/60000/:3368; a paid session was live).
~/.hermes/state/bench-mt6000.lock was free throughout — the lock only
excludes windows that take it
. No mutating test is safe here without
coordinating first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:post-0.6.0 Deferred until after the v0.6.0 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants