Add edge clock sync diagnostics

This commit is contained in:
mikecarper committed 2026-07-23 10:40:45 -07:00
1 parent 836af4b623
commit 6c99ae39a9
6 files changed
+274 -76

No files matched your search

+49 -14
View File
@@ -588,8 +588,12 @@ send text.flood checking ridge link
**Usage:**
- `get clock.sync`
- `get clock.sync.status`
- `get clock.sync.status.table`
- `get clock.sync.status.<1-16>`
- `get clock.sync.mesh`
- `set clock.sync.mesh <on|off>`
- `get clock.sync.mesh.edge`
- `set clock.sync.mesh.edge <on|off>`
- `clock.sync.mesh now`
- `get clock.sync.internet`
- `set clock.sync.internet <on|off>`
@@ -600,6 +604,7 @@ send text.flood checking ridge link
**Defaults:**
- `clock.sync.mesh`: `on` for nRF52 repeaters; `off` for other builds
- `clock.sync.mesh.edge`: `on`
- `clock.sync.internet`: `off`
- `clock.sync.drift`: `3600` seconds
- `clock.sync.samples`: `9`
@@ -633,17 +638,44 @@ a different full received path; all direct, zero-hop receptions count as the
same empty path. This prevents repeated packets or multiple names arriving over
one route from increasing the vote count.
At least the configured number of distinct fresh paths (nine by default) and a
strict majority of all fresh samples must fall within ten minutes of the
median. The effective quorum is therefore the larger of `clock.sync.samples`
and half the fresh sample count plus one. For example, a 9-vs-7 split can
succeed but an 8-vs-8 split cannot. The median is used. `clock.sync.samples`
accepts `3` through `16`; samples older than two hours are ignored. Status
reports `collecting` when fewer than the configured number of paths exist, and
`no consensus` when enough paths exist but the effective quorum does not agree.
Mesh collection begins immediately after boot. Following a successful estimate,
it resumes two hours before the next seven-day deadline so only evidence that
can still be fresh at evaluation time is processed.
For a node at the edge of the network where every packet arrives through one
relay path, `set clock.sync.mesh.edge on` changes the evidence requirement from
distinct receive paths to distinct sources. Signature-verified adverts are
deduplicated by public key, and Public-channel timestamps are deduplicated by
case-insensitive display name. Repeated packets from one source still count
once. Changing edge mode clears the in-memory sample table so evidence collected
under the other policy is not reused. The setting is persistent and defaults on.
At least the configured number of distinct fresh evidence sources (nine by
default) and a strict majority of all fresh samples must fall within ten minutes
of the median. In normal mode each source must use a distinct receive path. In
edge mode signed adverts are distinct by public key and Public-channel messages
are distinct by display name, but all may use the same receive path. The
effective quorum is therefore the larger of
`clock.sync.samples` and half the fresh sample count plus one. For example, a
9-vs-7 split can succeed but an 8-vs-8 split cannot. The median is used.
`clock.sync.samples` accepts `3` through `16`; samples older than two hours are
ignored. Status reports `mode=paths` or `mode=edge` and labels the collected
evidence as `paths` or `sources`. It reports `reason=need-more-paths` or
`reason=need-more-sources` when fewer than the configured number exist, and
`reason=no-consensus` when enough evidence exists but the effective quorum does
not agree. Mesh collection begins immediately after boot. Following a
successful estimate, it resumes two hours before the next seven-day deadline so
only evidence that can still be fresh at evaluation time is processed.
`get clock.sync.status` reports whether the clock was set and a `reason` when it
was not. Common reasons include `waiting-deadline`, `need-more-sources`,
`need-more-paths`, `no-consensus`, `within-drift`, `mesh-off`, and suppression
by CLI, GPS, or internet time. It also reports whether collection is active, the
fresh evidence count, the number of occupied table slots, and the next
evaluation deadline.
`get clock.sync.status.table` shows the active sample table in compact form.
Each item is `slot:type:id-prefix:age`, where type `A` is a signed advert and
type `P` is a Public-channel message. A trailing `!` marks a stale sample. If
the compact reply is truncated, query any slot with
`get clock.sync.status.<1-16>`. The detail view reports the full source and path
hashes, age-adjusted epoch, difference from the local clock, and freshness.
A timestamp is eligible for a clock-sync sample only when it falls between the
UTC build epoch embedded by `build.sh` and that time plus ten calendar years.
@@ -679,7 +711,9 @@ path hashes are also truncated, unauthenticated routing hints; requiring unique
paths prevents ordinary duplicate-route inflation but is not a cryptographic
identity check. Signed adverts authenticate the advert contents but do not
prove that the advertising node's own clock is correct. Mesh time is therefore
a consensus estimate, not an authoritative time service.
a consensus estimate, not an authoritative time service. Edge mode intentionally
gives up receive-path diversity. Public-channel display names can be spoofed, so
one sender can claim multiple names and inflate the edge-mode vote count.
`clock.sync.internet` is available on WiFi MQTT repeater-observer builds. Its
initial and seven-day queries run on the MQTT/WiFi task and are read-only until
@@ -700,9 +734,10 @@ replays until corrected time passes the previously observed value.
**Example:**
```text
set clock.sync.drift 1800
set clock.sync.samples 9
set clock.sync.samples 3
set clock.sync.mesh on
set clock.sync.internet on
set clock.sync.mesh.edge on
clock.sync.mesh now
get clock.sync.status
```
+3 -2
View File
@@ -86,10 +86,11 @@ set flood.retry.ignore none
| `flood.channel.scope` | Assigns a transport-region scope to received unscoped floods. Exact channel keys beat `txt:*`; `login:*` covers the remote-login family, and `other:*` covers every remaining flood type except TRACE, including OTA. TRACE remains unchanged across scope boundaries. ACL permission `4` can manage the table. | `get flood.channel.scope[.n]`, `set flood.channel.scope[.n] <channel|txt:*|login:*|other:*> <region>`, `del flood.channel.scope.<n>|all` | `set flood.channel.scope login:* west` |
| `flood.filter` | Persistent repeater-only forwarding rules for flood routes `0x00`/`0x01`, selected by payload type and optional received hop count/range (omitted means `all`). New tables seed slot 1 with `ota all suspend=tempradio`; only rows marked `suspend=tempradio` are skipped during temporary-radio operation. Login-capable `anon_req`/`path`/`response` types become filterable at hop `7`; flood `txt_msg` becomes filterable at hop `5`. Standard direct traceroute, other direct routing, and local receive/logging are unchanged. | `get flood.filter[.n]`, `set flood.filter[.n] <type> [N|N+|N-M|all] [suspend=tempradio]`, `del flood.filter.<n>|all` | `set flood.filter.1 0x0C all suspend=tempradio` |
| `flood.moderation` | Decrypts keyed `GRP_TXT` channels and applies drop, per-username messages/minute, and maximum-hop controls, optionally matched against the first 1-3 path hashes. Supports `public`, `#channel`, and 128/256-bit channel keys. Sender names and truncated path hashes are moderation hints, not authenticated identities. | `get flood.moderation[.n]`, `set flood.moderation[.n] <channel> <sender> <drop|rate=X/min|hops=N> [path=...]`, `del flood.moderation.<n>|all` | `set flood.moderation public "Noisy User" rate=5/min hops=4` |
| `clock.sync.mesh` | Defaults on for nRF52 repeaters and off for other builds; a saved setting overrides that default. After 30 minutes of uptime, it estimates UTC from a configurable consensus of fresh signed-advert or valid Public-channel sources, then repeats lazily seven days after each successful estimate. Failed attempts retry every 30 minutes; `clock.sync.mesh now` queues an immediate LoRa-only attempt without bypassing quorum or source suppression. Only timestamps from firmware build time through build time plus ten years are recorded. Successful CLI, GPS, or WiFi/NTP clock updates suppress LoRa time collection until reboot; after reboot LoRa is the fallback if NTP cannot sync. | `get clock.sync.mesh`, `set clock.sync.mesh <on|off>`, `clock.sync.mesh now`, `get clock.sync.status` | `set clock.sync.mesh on` |
| `clock.sync.mesh` | Defaults on for nRF52 repeaters and off for other builds; a saved setting overrides that default. After 30 minutes of uptime, it estimates UTC from a configurable consensus of fresh signed-advert or valid Public-channel sources, then repeats lazily seven days after each successful estimate. Failed attempts retry every 30 minutes; `clock.sync.mesh now` queues an immediate LoRa-only attempt without bypassing quorum or source suppression. Only timestamps from firmware build time through build time plus ten years are recorded. Successful CLI, GPS, or WiFi/NTP clock updates suppress LoRa time collection until reboot; after reboot LoRa is the fallback if NTP cannot sync. Status reports the reason a clock was not set; its `.table` and `.1` through `.16` forms inspect collected samples. | `get clock.sync.mesh`, `set clock.sync.mesh <on|off>`, `clock.sync.mesh now`, `get clock.sync.status[.table|.1-.16]` | `set clock.sync.mesh on` |
| `clock.sync.mesh.edge` | Defaults on so edge repeaters can collect clock evidence when all packets arrive through one relay path. Signed adverts are deduplicated by public key and Public-channel timestamps by case-insensitive display name; all may share one receive path. Public display names are unauthenticated and can be spoofed. Changing this setting clears current clock samples. | `get clock.sync.mesh.edge`, `set clock.sync.mesh.edge <on|off>` | `set clock.sync.mesh.edge on` |
| `clock.sync.internet` | Adds a read-only internet/NTP estimate at the same initial and seven-day checks on WiFi MQTT repeater-observer builds. Other builds retain the setting but report internet unavailable. | `get clock.sync.internet`, `set clock.sync.internet <on|off>` | `set clock.sync.internet on` |
| `clock.sync.drift` | Absolute correction threshold in seconds. The clock is moved forward or backward only when the estimate differs by more than this value. | `get clock.sync.drift`, `set clock.sync.drift <30-86400>` | `set clock.sync.drift 3600` |
| `clock.sync.samples` | Minimum number of distinct, in-window receive paths that must agree before mesh time can be used. A strict majority of all fresh samples is also required. Range `3-16`; default `9`. | `get clock.sync.samples`, `set clock.sync.samples <3-16>` | `set clock.sync.samples 9` |
| `clock.sync.samples` | Minimum fresh evidence count before mesh time can be used: distinct receive paths in normal mode or distinct advert keys/Public-channel display names in edge mode. A strict majority of all fresh samples is also required. Range `3-16`; default `9`. | `get clock.sync.samples`, `set clock.sync.samples <3-16>` | `set clock.sync.samples 9` |
| `outpath` | Overrides the primary direct route used for replies to the current remote client. | `get outpath`, `set outpath <hops>`, `set outpath direct`, `set outpath clear`, `set outpath flood` | `set outpath A1B2C3,D4E5F6` |
| `altpath` | Adds a secondary direct route for repeater replies to the current remote client. | `get altpath`, `set altpath <hops>`, `set altpath direct`, `set altpath clear`, `set altpath flood` | `set altpath 71CE82,BA09F0` |