Skip to content

feat(setup): the gateway serves NTP pre-auth — time before payment (#627) - #648

Merged
c03rad0r merged 3 commits into
mainfrom
feat/ntp-preauth
Oct 6, 2026
Merged

c03rad0r merged 3 commits into
mainfrom
feat/ntp-preauth

Conversation

@Amperstrand

Copy link
Copy Markdown
Collaborator

What

The module half of #627: the gateway serves time to its own clients, before authentication.

A pre-authentication client needs one thing beyond the portal and the payment API: accurate time. A client on the open portal joins with whatever clock it boots with (the ws3915i fleet sat months off), and a wrong clock cannot pay honestly — Cashu proofs carry timestamps, keysets expire, sessions are time-boxed — and a downstream TollGate in reseller mode needs accurate time before it can pay us at all. NTP-to-the-gateway is the same free-pre-auth-infrastructure class as the DNS allowlist: cheap, safe, and it makes chained TollGates self-sufficient.

The change

  • setup_ntp_server enables busybox sysntpd's listener — system.ntp.enable_server='1' is the whole server half of the option — idempotently, creating the timeserver section when the image shipped none. An operator-disabled server is re-enabled: this is policy, not preference. The client half (upstream polling) keeps whatever the image shipped.
  • The pre-auth allow list (assert_nodogsplash_allow_entries, the ONE writer both setup paths share) gains allow udp port 123 beside :2050/:2051/:2121, in the same idempotent add shape — so both the full-setup and same-version paths converge it, and the list's doc block now names NTP in the enumerated set with the rationale.
  • Restart discipline follows the script's rule (see converge_nodogsplash_runtime): a running sysntpd gets exactly one cheap restart — a stateless UDP responder drops no customer, unlike the wireless or firewall services — and a fresh boot touches nothing, since procd starts sysntpd after uci-defaults with this config already committed.

Evidence (offline, per the estate's contract)

Check Result
tests/uci-defaults-ntp-preauth_test.sh (new) 6 passed, 0 failed — section creation, enable_server=1, idempotent second run, re-enable policy, running-restart-exactly-once, fresh-boot-leaves-init-untouched
tests/uci-defaults-nodogsplash-convergence_test.sh 29 passed, 0 failed — CORE_ENTRIES now carries udp/123, so the allowlist half is exercised through the full script run with the shims
tests/uci-defaults-setup-marker-order_test.sh 159 passed, 0 failed — the new driver call breaks no ordering contract
bash -n on the setup script clean

Bench-side, the issue already records the mechanism verified on the fleet bring-up (79b1 answers NTP on the portal face; clients sync from the gateway) — this PR lands that behavior in the shipped writer, with the same convergence guarantees the rest of the allowlist has.

On #642: that PR (opened by the Copilot agent against this same issue) is still effectively empty — one commit, zero changed files, verified via the API today. With this implementation landing, it should be closed as superseded; its task notes carried the same shape and none of the code.

Closes #627.

Amperstrand pushed a commit that referenced this pull request Oct 6, 2026
Amperstrand added 2 commits October 6, 2026 16:52
)

A pre-authentication client needs one thing beyond the portal and the
payment API: accurate time. Cashu proofs carry timestamps, keysets
expire, sessions are time-boxed, and a reseller-mode downstream TollGate
cannot pay upstream until its clock is right. The ws3915i fleet bring-up
measured units months off, with nothing on the open portal face able to
correct them before payment.

setup_ntp_server enables busybox sysntpd's listener
(system.ntp.enable_server='1' — the whole server half of the option),
idempotently and creating the timeserver section when the image shipped
none; an operator-disabled server is re-enabled (policy, not
preference). The pre-auth allow list (assert_nodogsplash_allow_entries)
gains 'allow udp port 123' beside :2050/:2051/:2121 — same idempotent
add shape, and the ONE-writer rule keeps both setup paths convergent.

Restart discipline follows the script's rule: a running sysntpd gets
exactly one cheap restart (a stateless UDP responder drops no customer,
unlike the wireless or firewall services); a fresh boot touches nothing —
procd starts sysntpd after uci-defaults, with this config committed.

Tests: tests/uci-defaults-ntp-preauth_test.sh pins the five properties
(section creation, enable_server=1, idempotency, re-enable policy,
running-restart-once, fresh-boot-untouched); the convergence suite's
CORE_ENTRIES now carries udp/123 through the full-script run (29/0);
setup-marker-order 159/0 with the new driver call.

@Amperstrand Amperstrand left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orchestrator review — technically sound; sequencing contradiction to resolve before merge.

The engineering is right: pre-auth udp/123 scoped into the existing allow-list construction (idempotent uci add_list guarded), sysntpd listener enabled idempotently, and the "restart the stateless UDP responder, never fresh-boot" rationale is correct (procd starts sysntpd after uci-defaults). The motivation is real — a wrong-clock client cannot pay honestly, and the ws3915i fleet sat months off.

The flag: the rc1 lane's close-out holds this PR out per the feature freeze, yet this diff's changelog entry lands under [v0.6.0-rc1] — and rc1 is not yet tagged. Both can't be true: either it's in the RC (then the freeze call changes and the lane should re-verify the RC gate with it), or it's held for the next cycle (then move the changelog entry to [Unreleased]). Owner/release-lane call — recommend resolving before any rc1 tag is cut so the changelog doesn't lie about what the tag contains.

@c03rad0r
c03rad0r merged commit 977fa96 into main Oct 6, 2026
felixfelix-bot pushed a commit to felixfelix-bot/tollgate-module-basic-go that referenced this pull request Oct 6, 2026
…LOG conflict keeps both sides

The vendor-IE overflow-policy bullet (OpenTollGate#618 review, this PR) and main's
discovery-signaling (OpenTollGate#621) + host-mode admin-surface (OpenTollGate#632) records landed in
the same spot under Changed / Internal. Both are kept verbatim: this PR's own
bullet first, then main's two. Nothing else conflicted — the other 4 files
merge clean.

Verified: CGO_ENABLED=0 go build ./... (src, all nested modules resolve) and
go test ./... in src/wireless_gateway_manager, both green.

--no-verify: the pre-commit credential gate flags main's own
docs/architecture/lan-port-management-bridge-decision.md table rows
(':337-339' and friends) as password-like. Those lines come from main unchanged;
no new secret is introduced by this merge.
Amperstrand added a commit that referenced this pull request Oct 6, 2026
…arget answer (#686)

No tag is cut and main keeps moving, so extensive internal testing
targets one recorded hash: fc639db (the rebrand-gutter scoping, which
the pin's own first gate run forced). The record states the pin policy —
docs-only commits after the pin do not move it; a fresh pin requires a
fresh full gate run — what the baseline carries, and that NTP pre-auth
(#648) and the cold-start reseller bootstrap (#629) are in it as
lab-tested features awaiting router acceptance.

Co-authored-by: Amperstrand <amperstrand@localhost>
Amperstrand added a commit that referenced this pull request Oct 6, 2026
…ict-marker lint, and a fast release-check on main (#694)

17 of 24 packaging/uci-defaults suites were lane-absent; the gutter test
that would have caught #648's class pre-merge never ran per PR, and the
committed conflict markers of #663 had no lint to stop them. The lane now
runs every suite, lints unresolved conflict markers anchored to their
marker-plus-space shape, and a new make release-check-fast profile
(repro + conformance skipped through documented env knobs) runs on main
pushes so a post-merge verify-owed state is red CI in minutes.

Co-authored-by: Amperstrand <amperstrand@localhost>
Amperstrand added a commit that referenced this pull request Oct 6, 2026
… match the code (#695)

* ci: the per-PR lane runs the full gate — all 24 shell suites, a conflict-marker lint, and a fast release-check on main

17 of 24 packaging/uci-defaults suites were lane-absent; the gutter test
that would have caught #648's class pre-merge never ran per PR, and the
committed conflict markers of #663 had no lint to stop them. The lane now
runs every suite, lints unresolved conflict markers anchored to their
marker-plus-space shape, and a new make release-check-fast profile
(repro + conformance skipped through documented env knobs) runs on main
pushes so a post-merge verify-owed state is red CI in minutes.

* test(contract): doc-facts — docs that state facts about the code must match the code

The 0.6.0 cycle shipped three regressions of this class (a compat matrix
describing a decode path the code removed; a tester guide stamped to a
dead version era; a stale code sample), each caught by hand. The checker
mechanizes four bindings: the compat matrix's re-verification note names
the gonuts version go.mod pins; every module-count claim equals the
actual src/ count; README's schema version equals the schema default;
and the Payment-outcomes table in merchant.md matches merchant.go's
emitted notice codes bidirectionally.

On its first run it found real drift: the outcomes table was missing
client-not-registered, invalid-mac-address and payment-processing-failed
— all three now documented. Wired into the contract-lint lane and
release-check's Contract leg.

---------

Co-authored-by: Amperstrand <amperstrand@localhost>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature: TollGate should serve NTP (udp/123) pre-auth to portal clients — downstream TollGates need accurate time before they can pay

2 participants