Add per-profile TX routing and bridge filtering
Persist Companion TX preferences per contact and channel. Default repeater, room, and sensor replies to both active TX profiles, with a reply-only force option for an RX-only secondary profile. Track both reply copies before a temporary-radio handoff and account for OTA copies under queue backpressure. Add bridge/crossover filter modes and the third built-in wardriving filter, plus compact filter CLI syntax, documentation, and CI coverage. Fix quoted target names being interpreted as slot/key selectors, partial recovery of invalid saved reply settings, filter suspension during tempradio2, and unnecessary packet allocation while OTA traffic is throttled. Validation: 1,477 native tests, 52 Python integration tests, 29 filter UI tests, and five firmware builds covering ESP32, nRF52, and STM32 passed.
This commit is contained in:
1 parent
0e0aa8e014
commit
95aca52b4e
60 files changed
+3188
-362
No files matched your search
@@ -214,6 +214,7 @@
|
||||
pathPrefix: "",
|
||||
sender: "",
|
||||
tempRadio: "any",
|
||||
via: "radio",
|
||||
verdict: "continue",
|
||||
scopeGate: "unchanged",
|
||||
targetKind: "scope",
|
||||
@@ -257,6 +258,8 @@
|
||||
pathKind: enumValue(input.pathKind, PATH_KINDS, "Path matcher", "none"),
|
||||
pathPrefix: "",
|
||||
sender: clean(input.sender),
|
||||
via: enumValue(input.via === "cross,bridge" ? "bridge,cross" : input.via,
|
||||
["radio", "bridge", "cross", "bridge,cross"], "Traffic path", "radio"),
|
||||
tempRadio: enumValue(input.tempRadio, ["any", "active", "inactive"], "Temporary-radio matcher", "any"),
|
||||
verdict: enumValue(input.verdict, ["continue", "drop"], "Forwarding verdict", "continue"),
|
||||
scopeGate: enumValue(input.scopeGate, ["unchanged", "require_allowed", "bypass_global"], "Scope-gate action", "unchanged"),
|
||||
@@ -356,6 +359,7 @@
|
||||
if (rule.pathKind === "prefix") matches.push(`path=prefix:${rule.pathPrefix}`);
|
||||
else if (rule.pathKind !== "none") matches.push(`path=${rule.pathKind}`);
|
||||
if (rule.sender) matches.push(`sender=${quoteDsl(rule.sender)}`);
|
||||
if (rule.via !== "radio") matches.push(`via=${rule.via}`);
|
||||
if (rule.tempRadio !== "any") matches.push(`tempradio=${rule.tempRadio}`);
|
||||
const actions = [];
|
||||
if (rule.verdict === "drop") actions.push("drop");
|
||||
@@ -464,6 +468,7 @@
|
||||
rule.pathPrefix = value.slice(7);
|
||||
} else rule.pathKind = value;
|
||||
} else if (name === "sender") rule.sender = value;
|
||||
else if (name === "via") rule.via = value;
|
||||
else if (name === "tempradio") rule.tempRadio = value;
|
||||
else throw new FilterToolError(`Unsupported receive-time matcher: ${token}`);
|
||||
});
|
||||
@@ -564,6 +569,7 @@
|
||||
else if (rule.pathKind.startsWith("bucket:")) conditions.push(`a path matching ${rule.pathKind}`);
|
||||
else if (rule.pathKind.startsWith("loop:")) conditions.push(`the ${rule.pathKind.slice(5)} own-ID loop threshold`);
|
||||
if (rule.sender) conditions.push(`decrypted sender "${rule.sender}"`);
|
||||
conditions.push(`traffic path ${rule.via}`);
|
||||
if (rule.tempRadio !== "any") conditions.push(`temporary radio ${rule.tempRadio}`);
|
||||
const mode = rule.mode === "shadow"
|
||||
? "In shadow mode, report that it would "
|
||||
@@ -970,6 +976,7 @@
|
||||
regionName: clean(input.regionName),
|
||||
sender,
|
||||
tempRadio: Boolean(input.tempRadio),
|
||||
via: enumValue(input.via, ["radio", "bridge", "cross"], "Packet traffic path", "radio"),
|
||||
blacklist: Boolean(input.blacklist),
|
||||
buckets: Array.from(new Set(buckets)),
|
||||
loopLevel: requiredInteger(input.loopLevel == null ? 0 : input.loopLevel, 0, 3, "Loop result"),
|
||||
@@ -1002,6 +1009,7 @@
|
||||
const rule = normalizeRule(inputRule);
|
||||
const packet = normalizePacket(inputPacket);
|
||||
const misses = [];
|
||||
if (!rule.via.split(",").includes(packet.via)) misses.push(`traffic path is ${packet.via}`);
|
||||
if (rule.route === "flood" && !["unscoped_flood", "scoped_flood"].includes(packet.route)) misses.push("route is not flood");
|
||||
else if (rule.route !== "flood" && rule.route !== packet.route) misses.push(`route is ${packet.route}`);
|
||||
if (!typeMatches(rule.type, packet.type)) misses.push(`payload type ${packet.type} is outside ${rule.type}`);
|
||||
@@ -1228,6 +1236,12 @@
|
||||
owner: "system",
|
||||
channel: "#wardriving",
|
||||
},
|
||||
{
|
||||
...documentedDropRule("wardriving-bridge-cross", "any", "all", 220),
|
||||
owner: "system",
|
||||
channel: "#wardriving",
|
||||
via: "bridge,cross",
|
||||
},
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -1302,6 +1316,7 @@
|
||||
pathPrefix: field("path-prefix").value,
|
||||
sender: field("sender").value,
|
||||
tempRadio: field("temp-radio").value,
|
||||
via: field("via").value,
|
||||
verdict: field("verdict").value,
|
||||
scopeGate: field("scope-gate").value,
|
||||
targetKind: field("target-kind").value,
|
||||
@@ -1335,6 +1350,7 @@
|
||||
"path-prefix": rule.pathPrefix,
|
||||
sender: rule.sender,
|
||||
"temp-radio": rule.tempRadio,
|
||||
via: rule.via,
|
||||
verdict: rule.verdict,
|
||||
"scope-gate": rule.scopeGate,
|
||||
"target-kind": rule.targetKind,
|
||||
@@ -1564,6 +1580,7 @@
|
||||
regionName: packetField("region-name").value,
|
||||
sender: packetField("sender").value,
|
||||
tempRadio: packetField("temp-radio").checked,
|
||||
via: packetField("via").value,
|
||||
blacklist: packetField("blacklist").value === "yes",
|
||||
buckets: packetField("buckets").value,
|
||||
loopLevel: packetField("loop-level").value,
|
||||
|
||||
+166
-24
@@ -743,6 +743,54 @@ set flag bit 0.
|
||||
|
||||
---
|
||||
|
||||
## Set Companion TX routing per contact or channel
|
||||
|
||||
```text
|
||||
set tx.user "Alice Jones" radio2
|
||||
get tx.user "Alice Jones"
|
||||
set tx.channel 0 both
|
||||
get tx.channel 0
|
||||
set tx.channel #wardriving radio
|
||||
```
|
||||
|
||||
Both command families accept `auto`, `radio`, `radio2`, `both`, or `off`.
|
||||
`auto` uses the global crossing rules. Explicit choices override
|
||||
`radio2.cross`, but radio2 must still be enabled in `rxtx` to transmit there.
|
||||
`radio2` never falls back to the primary profile; `both` uses whichever of its
|
||||
selected profiles can transmit. Set a target back to `auto` to remove its
|
||||
override.
|
||||
|
||||
Use an exact contact/channel name, a zero-based channel index, or
|
||||
`key:<12-64 hex digits>` for a unique contact public-key prefix. Duplicate
|
||||
names/prefixes are rejected. `get tx.user` and `get tx.channel` show override
|
||||
counts; queries with a target show its saved choice and current effective
|
||||
profile selection. Choices are saved with the contact/channel and work for
|
||||
phone-app and terminal messages. A contact's `off` setting also blocks its
|
||||
ACKs and addressed responses.
|
||||
|
||||
See [Companion routing details and examples](radio_profiles.md#companion-tx-routing-by-contact-or-channel)
|
||||
for temporary profiles, persistence, and the traffic covered by each setting.
|
||||
|
||||
## Set repeater, room-server, and sensor reply profiles
|
||||
|
||||
```text
|
||||
get tx.reply
|
||||
set tx.reply both
|
||||
set tx.reply both force
|
||||
set tx.reply radio
|
||||
set tx.reply auto
|
||||
```
|
||||
|
||||
Persistent choices: `auto`, `radio`, `radio2`, `both` (default), and `off`.
|
||||
Explicit choices override `radio2.cross` for local replies, including CLI,
|
||||
ACKs, telemetry, room subscription posts, and served LoRa OTA responses.
|
||||
Append `force` to `both` or `radio2` to permit reply TX on an active RX-only
|
||||
second profile. It never enables a disabled profile or ordinary forwarding.
|
||||
Omitting `force` removes that override. `off` suppresses remote replies too.
|
||||
|
||||
See [reply routing and OTA examples](radio_profiles.md#repeater-room-server-and-sensor-replies)
|
||||
for temporary profiles, receive-only exceptions, and queued reply handling.
|
||||
|
||||
## Set Companion display rotation
|
||||
|
||||
SSD1306 Full Companion builds support a persisted runtime orientation:
|
||||
@@ -2428,6 +2476,42 @@ setting.
|
||||
|
||||
---
|
||||
|
||||
#### Bridge and radio crossover filters
|
||||
|
||||
`mode=` selects where a generalized `flood.rule` / `flood.filter` row applies:
|
||||
|
||||
| Mode | Where the rule applies |
|
||||
| --- | --- |
|
||||
| `radio` | Ordinary LoRa flood forwarding. This is the default when `mode=` is omitted. |
|
||||
| `bridge` | Packets entering or leaving an RS232 or ESP-NOW bridge, and MeshCore RX/TX packet publication through MQTT. |
|
||||
| `cross` | Transmissions copied to the other radio profile, including a copy's retries. The same-profile transmission remains eligible. |
|
||||
| `bridge,cross` | Both transport paths using one rule. `cross,bridge` is equivalent. |
|
||||
|
||||
The third built-in repeater rule blocks authenticated `#wardriving` text and
|
||||
data at all hops on bridge and crossover paths, including temporary-radio
|
||||
sessions. It leaves same-profile traffic subject to the separate four-hop
|
||||
rule. `set radio2.cross on` does not override this filter.
|
||||
|
||||
```text
|
||||
# Factory slot: inspect before replacing an existing rule.
|
||||
get flood.rule.3
|
||||
set flood.rule.3 type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
# On an existing device, add it without overwriting an occupied slot.
|
||||
set flood.rule type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
```
|
||||
|
||||
Saved tables are preserved on upgrade; new defaults are seeded only when the
|
||||
table is first initialized. Bridge/cross modes accept drop, rate, priority and
|
||||
stop actions; scope rewrites, retry actions and TX timing are rejected.
|
||||
These modes cover direct as well as flood packets, in both crossing directions.
|
||||
The default authenticates the channel key, avoiding false drops from short
|
||||
channel-hash collisions. FULL room servers support explicit transport rules;
|
||||
companions and compact FPF6 profiles do not expose this table.
|
||||
|
||||
See [filtering documentation](flood_filtering.md#filter-bridge-and-radio-crossover-traffic)
|
||||
for persistence, queue/rate semantics, exceptions, and how to remove one or
|
||||
both blocks.
|
||||
|
||||
#### Block selected flood channels with FPF7
|
||||
|
||||
The separate `flood.channel.block` command and 15-row table have been retired.
|
||||
@@ -2714,9 +2798,13 @@ For setup guidance, interactions with the existing forwarding controls, and
|
||||
worked moderation examples, see [Flood Filtering and Moderation](flood_filtering.md).
|
||||
|
||||
**Usage:**
|
||||
|
||||
- `get flood.rule`
|
||||
- `get flood.rule.<n>`
|
||||
- `set flood.rule[.<n>] type=<type> [hops=<range>] [channel=<channel>] [prefix=<path-prefix>] [in=<input-scope>] <drop|scope=<name>|region=<name>|rate=<N>/min|retry|stop> [priority=<0-255>] [tx=slow] [suspend=tempradio]`
|
||||
- `get fr[.<n>]` (numbered form returns a complete short setter for copying)
|
||||
- `set fr[.<n>] <type> [h=<range>] [m=r|b|c|bc] [c=<channel>] <d|s=<scope>|r=<region>|q=<N>|r|s> [...]`
|
||||
- `del fr.<n>` or `del fr all`
|
||||
- `set flood.rule[.<n>] type=<type> [mode=radio|bridge|cross|bridge,cross] [hops=<range>] [channel=<channel>] [prefix=<path-prefix>] [in=<input-scope>] <drop|scope=<name>|region=<name>|rate=<N>/min|retry|stop> [priority=<0-255>] [tx=slow] [suspend=tempradio]`
|
||||
- `del flood.rule.<n>`
|
||||
- `del flood.rule all`
|
||||
- `get flood.filter`
|
||||
@@ -2733,16 +2821,21 @@ worked moderation examples, see [Flood Filtering and Moderation](flood_filtering
|
||||
- `del flood.filter all`
|
||||
|
||||
The extended table is available on repeaters with the rule engine enabled and
|
||||
on FULL-profile ESP32 room servers. A FULL room server exposes both
|
||||
`flood.rule` and `flood.filter`, has 31 slots, and requires an administrator
|
||||
on FULL-profile ESP32 room servers. A FULL room server exposes
|
||||
`fr`, `flood.rule`, and `flood.filter`, has 31 slots, and requires an administrator
|
||||
for remote changes. It does not have the repeater's passive path blacklist, so
|
||||
`flood.filter.blacklist*` and `path=blacklist` are repeater-only; use the
|
||||
ordered `prefix=` match on a room server. Standard room-server profiles do not
|
||||
compile this table.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `n`: Forward-rule slot in the build's compiled table (`1-63` on generalized
|
||||
repeaters and `1-31` on FULL room servers; compact profiles may use fewer).
|
||||
- `mode`: Optional; omitted means `radio`. `bridge` filters bridge admission,
|
||||
`cross` filters copies to the other radio profile, and `bridge,cross` covers
|
||||
both with one row. `cross,bridge` is an alias. See the
|
||||
[mode matrix](#bridge-and-radio-crossover-filters) for scope and supported actions.
|
||||
- `type`: Payload type name, full `PAYLOAD_TYPE_*` name, decimal value `0-15`,
|
||||
hexadecimal value `0x00-0x0F`, or `any`.
|
||||
- `hops`: Optional; omitted means `all`.
|
||||
@@ -2806,8 +2899,8 @@ compile this table.
|
||||
wildcard, or has no usable transport key, both the rewrite and its `stop`
|
||||
are inert so lower-order safety rows still run. A direct `scope=` target does
|
||||
not depend on region configuration.
|
||||
- `suspend=tempradio`: Optional. Skip this row only while the temporary radio
|
||||
is actually active.
|
||||
- `suspend=tempradio`: Optional. Skip this row while either `tempradio` or
|
||||
`tempradio2` is active, including scheduled temporary sessions.
|
||||
- `require=region`: Legacy alias for `in=allowed`. Apply the row only if the
|
||||
original incoming packet already passes this repeater's
|
||||
region gate. An incoming transport scope must resolve to a locally allowed
|
||||
@@ -2847,14 +2940,17 @@ The payload names follow the [MeshCore packet-format allocation](https://docs.me
|
||||
| `0x0E` | `14` | reserved |
|
||||
| `0x0F` | `raw_custom` | `PAYLOAD_TYPE_RAW_CUSTOM` |
|
||||
|
||||
**Route scope:** Rules are evaluated only for the two flood route values:
|
||||
**Route scope:** `mode=radio` rules are evaluated only for the two flood route values:
|
||||
`ROUTE_TYPE_TRANSPORT_FLOOD` (`0x00`, flood plus transport codes) and
|
||||
`ROUTE_TYPE_FLOOD` (`0x01`, unscoped flood). Direct routes `0x02` and `0x03`
|
||||
are never affected.
|
||||
are unaffected by radio rules. Bridge/cross rules also cover direct routes at
|
||||
their respective transport boundaries.
|
||||
|
||||
**Behavior:** Match fields within one row are ANDed. Every FPF7 row is matched
|
||||
**Behavior:** Match fields within one row are ANDed. Radio rows are matched
|
||||
against the same immutable receive-time packet, before any rule changes its
|
||||
scope. Matching rows are processed in descending `priority`. At equal numeric
|
||||
scope. Bridge/cross rows match the packet at their admission boundary; a TX
|
||||
packet can already have an appended hop or rewritten scope. Each mode evaluates
|
||||
only its applicable rows, processed in descending `priority`. At equal numeric
|
||||
priority, authenticated channel matches precede raw hashes, which precede an
|
||||
unrestricted channel matcher; lower slot wins after that. The first matching
|
||||
`stop` row is included and all lower-order FPF7 matches are discarded. A stop
|
||||
@@ -2903,10 +2999,11 @@ region names rather than transient numeric region
|
||||
IDs. Removing, reordering, or reusing a region ID therefore cannot silently
|
||||
retarget a rule. If a saved input or target region name is absent, that input
|
||||
match or rewrite is inert; restoring the same region name reactivates it.
|
||||
While the temporary radio is active, only rows explicitly marked
|
||||
`suspend=tempradio` are skipped. `tempradio` is a radio state, not an OTA mode;
|
||||
normal payload types can also use the temporary channel. Other rows remain in
|
||||
force. A malformed persisted table fails open (no general rules are applied).
|
||||
While either `tempradio` or `tempradio2` is active, only rows explicitly marked
|
||||
`suspend=tempradio` are skipped. Temporary radio is a radio state, not an OTA
|
||||
mode; normal payload types can also use the temporary channel. Other rows
|
||||
remain in force, and suspended rows resume after both temporary sessions end.
|
||||
A malformed persisted table fails open (no general rules are applied).
|
||||
|
||||
Within one receive evaluation, rows that use the same channel key share one
|
||||
authentication result. The cache is discarded after that packet and stores
|
||||
@@ -2951,10 +3048,10 @@ Deleting or replacing the last active `retry` row restores the legacy global
|
||||
retry eligibility. Firmware that predates the `retry`/`hash:XX` FPF7 extension
|
||||
cannot preserve tables containing those rows; remove them before downgrading.
|
||||
|
||||
**Default row:** Repeater firmware and FULL ESP32 room-server firmware seed a
|
||||
**Default rows:** Repeater firmware and FULL ESP32 room-server firmware seed a
|
||||
new flood-filter table with
|
||||
`ota all suspend=tempradio` in slot 1. This blocks repeated LoRa OTA (`0x0C`)
|
||||
floods at every received hop unless temporary radio is actually active. The OTA
|
||||
floods at every received hop unless either temporary radio profile is active. The OTA
|
||||
core independently refuses OTA receive, relay, and transmit outside temporary
|
||||
radio. The row is editable and deletable; once the table is saved, deletion is
|
||||
persistent. Restore the exact seeded row with:
|
||||
@@ -2966,6 +3063,18 @@ set flood.filter.1 0x0C all suspend=tempradio
|
||||
Omitting `all` is equivalent. Omit `.1` as well to reuse an identical rule or
|
||||
the first empty slot instead of replacing slot 1.
|
||||
|
||||
Generalized repeaters also seed slot 2 with the authenticated `#wardriving`
|
||||
radio hop limit and slot 3 with the bridge/crossover block:
|
||||
|
||||
```text
|
||||
set flood.rule.2 type=any channel=#wardriving hops=5+ drop
|
||||
set flood.rule.3 type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
```
|
||||
|
||||
These numbered commands replace those slots. Saved tables are preserved on
|
||||
upgrade; use the unnumbered third command to add the new rule without replacing
|
||||
an occupied slot. See [restoring the three defaults](flood_filtering.md#restore-the-factory-seeded-rows).
|
||||
|
||||
**Remote-admin lockout warning:** There are no hidden payload-type or short-hop
|
||||
exceptions. FPF7 drop and rate rows may block `req`, `response`, `txt_msg`,
|
||||
`anon_req`, `path`, ACK, and multipart traffic beginning at hop `0` when their
|
||||
@@ -2980,14 +3089,47 @@ or action.
|
||||
form for full details, including channel, prefix, original-scope condition,
|
||||
action, timing, rate, and temporary-radio suspension.
|
||||
|
||||
If all of those fields plus long names would exceed one CLI reply, the
|
||||
numbered form automatically switches to a non-truncating compact spelling.
|
||||
The compact aliases are also accepted by `set`: `c=` means `channel=`, `p=`
|
||||
means `prefix=`, `i=*|n|s|a|u|s:<scope>|r:<region>` means the corresponding
|
||||
`in=` condition, `q=N` means `rate=N/min`, `pri=N` means priority, and `f=str`
|
||||
combines slow timing (`s`), temporary-radio suspension (`t`), and retry
|
||||
allowance (`r`). Packet type is shown numerically
|
||||
in that fallback. Normal-sized rows keep the descriptive spelling above.
|
||||
`fr` is a shorter alias for `flood.rule`, with the same explicit-action
|
||||
requirement. Short and long field names can be mixed in either command:
|
||||
|
||||
```text
|
||||
set flood.rule.3 type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
set fr.3 any c=#wardriving m=bc d
|
||||
get fr.3
|
||||
```
|
||||
|
||||
The two setters above save the same row. `get fr.3` returns the complete
|
||||
short setter; `get fr` lists the table. Unnumbered `set fr ...` reuses an
|
||||
identical row or adds one to the first empty slot. `del fr.N` and `del fr all`
|
||||
have the same behavior as their long forms.
|
||||
|
||||
| Full token | Short token |
|
||||
| --- | --- |
|
||||
| `type=...` | Positional type or `t=...`; `*` means `any` |
|
||||
| `hops=...` | Positional range or `h=...`; `*` means `all` |
|
||||
| `mode=radio/bridge/cross/bridge,cross` | `m=r/b/c/bc` (choose one value) |
|
||||
| `channel=...`, `prefix=...` | `c=...`, `p=...` |
|
||||
| `path=blacklist` | `p=bl` (repeater only) |
|
||||
| `in=any/none/scoped/allowed/unknown` | `i=*/n/s/a/u` (choose one value) |
|
||||
| `in=scope:name`, `in=region:name` | `i=s:name`, `i=r:name` |
|
||||
| `drop`, `stop`, `retry` | `d`, `s`, `r` |
|
||||
| `scope=name`, `region=name` | `s=name`, `r=name` |
|
||||
| `rate=N/min`, `priority=N` | `q=N`, `pri=N` |
|
||||
| `tx=slow`, `suspend=tempradio`, `retry` | `f=s`, `f=t`, `f=r`; combine as `f=str` |
|
||||
|
||||
All existing validation and permissions apply. `m=cb`, `m=b,c`, and `m=c,b`
|
||||
also mean bridge and crossover. Short aliases for `flood.channel.scope`,
|
||||
`flood.moderation`, and blacklist management are not added.
|
||||
|
||||
The numbered long getters use this complete short setter when descriptive
|
||||
fields would exceed a reply. Defaults are omitted and specific payload types
|
||||
are printed numerically. If it still cannot fit, the reply is
|
||||
`Err - compact command exceeds reply size`, never a partial setter.
|
||||
Private channel keys are shown as `key:XXXXXXXX`; that reference can be
|
||||
reused while an active rule on the same device holds the key. Copying it to
|
||||
another device requires the original key, unless that device already has a
|
||||
matching rule. Unknown or ambiguous references are rejected. See the
|
||||
[shorthand guide](flood_filtering.md#shorthand-for-copying-rules) for examples.
|
||||
|
||||
On generalized repeaters, filter rows, scope-rewrite rows, the shared
|
||||
blacklist, and `flood.channel.data` compatibility state are committed in one
|
||||
@@ -3135,7 +3277,7 @@ del flood.moderation.3
|
||||
**Filter manager scope:** Permission `5` can use an explicit allowlist of
|
||||
non-secret operational/filter status commands and can change the forwarding
|
||||
controls `repeat`, `loop.detect`, `flood.max*`, `flood.channel.data*`,
|
||||
`flood.filter*`, `flood.rule*`, and
|
||||
`flood.filter*`, `flood.rule*`, `fr[.N]`, and
|
||||
`flood.moderation*`. It cannot read
|
||||
guest, WiFi, MQTT, bridge, or other credentials, and it cannot change regions,
|
||||
ACL entries, radio settings, or other admin configuration. Permission `4`
|
||||
|
||||
@@ -131,3 +131,29 @@ For every command's full details, see the [bridge CLI reference](cli_commands.md
|
||||
|
||||
For a broader comparison with a Linux-based bridge, see the
|
||||
[MeshCore Nexus bridging guide](https://meshcore.nexus/guides/lora-bridging#method-2-meshcore-esp-now-bridge).
|
||||
|
||||
## Keep wardriving traffic off the bridge
|
||||
|
||||
New generalized repeater filter tables include a third built-in rule that
|
||||
blocks authenticated `#wardriving` text and data in both bridge directions,
|
||||
at every hop count. The same row blocks crossover between radio profiles:
|
||||
|
||||
```text
|
||||
get flood.rule.3
|
||||
set flood.rule type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
```
|
||||
|
||||
The same rule can be added with the shorter command:
|
||||
|
||||
```text
|
||||
set fr any c=#wardriving m=bc d
|
||||
```
|
||||
|
||||
Both forms use the same table. `m=b` means bridge only, `m=c` means crossover
|
||||
only, and `get fr.N` returns a short command for copying a numbered rule.
|
||||
|
||||
The unnumbered command adds the rule safely on existing devices whose saved
|
||||
tables predate this default. It works with either bridge packet source and
|
||||
both ESP-NOW formats. Nearby same-profile LoRa forwarding retains the separate
|
||||
four-hop default. Use `mode=bridge` for a bridge-only block, or `mode=cross` for
|
||||
a crossover-only block. See the [mode matrix and examples](flood_filtering.md#filter-bridge-and-radio-crossover-traffic).
|
||||
+38
-4
@@ -13,7 +13,7 @@ drafts are not uploaded anywhere.
|
||||
The phases and core conditions model current FPF7 behavior, including its
|
||||
forward rows, scope rewrites, and shared blacklist. The readable policy
|
||||
language, JSON, and Base64 bundle are still a prototype: current firmware
|
||||
is configured with <code>set flood.*</code> commands and cannot install a
|
||||
is configured with <code>set flood.*</code> or <code>set fr</code> commands and cannot install a
|
||||
bundle from this page.
|
||||
</p>
|
||||
</div>
|
||||
@@ -111,8 +111,8 @@ in the simulator below. The examples draw from
|
||||
<code>when type=any hops=all path=blacklist do drop</code>
|
||||
</button>
|
||||
<button type="button" data-example="factory">
|
||||
<span>Drop OTA outside temporary-radio mode and distant #wardriving</span>
|
||||
<code>when type=ota hops=all tempradio=inactive do drop<br>when type=any channel=#wardriving hops=5+ do drop</code>
|
||||
<span>Three defaults: OTA, distant #wardriving, and bridge/crossover #wardriving</span>
|
||||
<code>when type=ota hops=all tempradio=inactive do drop<br>when type=any channel=#wardriving hops=5+ do drop<br>when type=any channel=#wardriving hops=all via=bridge,cross do drop</code>
|
||||
</button>
|
||||
<button type="button" data-example="wildcards">
|
||||
<span>Set #BlackHole86 scope on login and bucket-matched other traffic</span>
|
||||
@@ -131,6 +131,12 @@ in the simulator below. The examples draw from
|
||||
<button type="button" data-role="reset-form">Reset form</button>
|
||||
</div>
|
||||
|
||||
<p class="filter-field-help">The factory example contains all three repeater defaults.
|
||||
The playground's <code>via=</code> traffic-path matcher corresponds to firmware
|
||||
<code>mode=radio|bridge|cross|bridge,cross</code>; the advanced Active/Shadow/Disabled
|
||||
mode is a separate playground setting. See the
|
||||
<a href="../flood_filtering/#filter-bridge-and-radio-crossover-traffic">firmware mode guide</a>.
|
||||
This simulator models flood packets; firmware bridge/cross rules also cover direct routes.</p>
|
||||
<h3>Common match settings</h3>
|
||||
<div class="filter-form-grid">
|
||||
<label>
|
||||
@@ -204,6 +210,15 @@ in the simulator below. The examples draw from
|
||||
Decrypted sender (optional)
|
||||
<input data-field="sender" maxlength="31" placeholder="Noisy User">
|
||||
</label>
|
||||
<label>
|
||||
Traffic path (firmware mode)
|
||||
<select data-field="via">
|
||||
<option value="radio">Normal radio forwarding</option>
|
||||
<option value="bridge">Bridge entry/exit</option>
|
||||
<option value="cross">Radio profile crossover</option>
|
||||
<option value="bridge,cross">Bridge and crossover</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Temporary-radio state
|
||||
<select data-field="temp-radio">
|
||||
@@ -439,6 +454,14 @@ in the simulator below. The examples draw from
|
||||
</div>
|
||||
<div class="filter-form-grid filter-simulator-facts">
|
||||
<label>
|
||||
Packet traffic path
|
||||
<select data-packet="via">
|
||||
<option value="radio">Normal radio forwarding</option>
|
||||
<option value="bridge">Bridge entry/exit</option>
|
||||
<option value="cross">Radio profile crossover</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Received route
|
||||
<select data-packet="route">
|
||||
<option value="unscoped_flood">Unscoped flood</option>
|
||||
@@ -609,7 +632,7 @@ The class column shows which broader selector also matches it.
|
||||
|
||||
| Firmware command | FPF7 role |
|
||||
| --- | --- |
|
||||
| `flood.rule` / `flood.filter` | Forward-phase match and action rows |
|
||||
| `fr` / `flood.rule` / `flood.filter` | Forward-phase match and action rows |
|
||||
| `flood.channel.data` | Compatibility view over one visible `type=grp_data` forward drop row |
|
||||
| `flood.channel.scope` | Scope-rewrite phase rows |
|
||||
| `flood.filter.blacklist` | One shared unordered path-ID set referenced by `path=blacklist` rows |
|
||||
@@ -620,6 +643,17 @@ for refusing to retransmit floods associated with internet gateways dumping
|
||||
bulk traffic, but a path ID is truncated and unauthenticated; it identifies a
|
||||
routing pattern, not a person.
|
||||
|
||||
The firmware accepts both full and short CLI forms for the same forward row:
|
||||
|
||||
```text
|
||||
set flood.rule.3 type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
set fr.3 any c=#wardriving m=bc d
|
||||
```
|
||||
|
||||
Use `get fr.3` on the device to obtain a short setter ready to copy. This CLI
|
||||
syntax is separate from the playground's proposed policy/bundle format.
|
||||
See the [shorthand reference](flood_filtering.md#shorthand-for-copying-rules).
|
||||
|
||||
## Proposed evaluation contract
|
||||
|
||||
The simulator uses these rules:
|
||||
|
||||
+188
-24
@@ -158,8 +158,9 @@ sections in the same atomic policy file. FULL room servers have 31 forward
|
||||
slots and empty repeater-only sections. Compact target profiles retain their
|
||||
separate FPF6-era controls. `flood.moderation` has 16 slots. A new
|
||||
repeater FPF7 table starts with `ota all suspend=tempradio` in slot 1 and an
|
||||
authenticated `#wardriving hops=5+` drop in slot 2; FULL room servers seed only
|
||||
the OTA row. `flood.moderation` starts empty. A row can opt into
|
||||
authenticated `#wardriving hops=5+` radio drop in slot 2, and an authenticated
|
||||
`#wardriving hops=all mode=bridge,cross` drop in slot 3. FULL room servers seed
|
||||
only the OTA row; their generalized engine also accepts transport-mode rules. `flood.moderation` starts empty. A row can opt into
|
||||
`suspend=tempradio`;
|
||||
temporary radio is not synonymous with OTA and can carry normal packet types
|
||||
too. A corrupt or truncated table fails open, so corrupt storage does not
|
||||
@@ -400,14 +401,17 @@ scope arbitration.
|
||||
## Runtime flood rules
|
||||
|
||||
On repeaters with the rule engine enabled and on FULL-profile ESP32 room
|
||||
servers, `flood.rule` and `flood.filter` are two names for the same persistent
|
||||
servers, `fr`, `flood.rule`, and `flood.filter` name the same persistent
|
||||
table. The evaluator is fixed firmware, but every row is data, so an
|
||||
authenticated operator can add, replace, inspect, or delete a row without an
|
||||
OTA or reboot. Existing `flood.filter` commands remain compatible. Only FPF6
|
||||
and FPF7 files are accepted; FPF1-FPF5 files are rejected and filtering fails
|
||||
open. A row saved by the extended engine uses FPF7.
|
||||
Older firmware cannot preserve an FPF7 table containing the packed `retry`
|
||||
action or one-byte `hash:XX` matcher; remove those rows before downgrading.
|
||||
action, one-byte `hash:XX` matcher, or `mode=bridge`/`cross` flags; remove those
|
||||
rows before downgrading. Old firmware rejects such a table and fails open,
|
||||
which disables its other filter rows too. New firmware loads older rows as
|
||||
`mode=radio` without changing their behavior.
|
||||
|
||||
The former `flood.channel.block` table is now represented by ordinary FPF7
|
||||
rows. On a generalized repeater, an existing FCB2 file is imported once into
|
||||
@@ -434,7 +438,8 @@ reactivates the rule.
|
||||
The extended form is:
|
||||
|
||||
```text
|
||||
set flood.rule[.<slot>] type=<type> [hops=<range>] [channel=<channel>]
|
||||
set flood.rule[.<slot>] type=<type> [mode=radio|bridge|cross|bridge,cross]
|
||||
[hops=<range>] [channel=<channel>]
|
||||
[prefix=<ID[,ID...]>] [in=<input-scope>] <action> [rate=<N>/min]
|
||||
[priority=<0-255>] [stop] [tx=fast|slow] [suspend=tempradio]
|
||||
get flood.rule
|
||||
@@ -444,13 +449,18 @@ del flood.rule all
|
||||
```
|
||||
|
||||
The command must be entered on one line. Match fields in one row are ANDed.
|
||||
Every row is matched against the same immutable packet state captured on
|
||||
receive, before any rule rewrites its scope. Matching rows are then processed
|
||||
Radio rows are matched against the same immutable packet state captured on
|
||||
receive, before any rule rewrites its scope. Bridge/cross rows match at their
|
||||
transport boundary. Only rows for the current mode participate in ordering
|
||||
and stop processing. Matching rows are then processed
|
||||
by descending numeric `priority`. At an equal numeric priority, authenticated
|
||||
channel matches run before raw `hash:XX` matches, raw hashes run before
|
||||
`channel=*`, and lower slot number breaks the remaining tie. Priority defaults
|
||||
to `0`; an explicitly higher numeric priority overrides specificity.
|
||||
|
||||
Numbered examples replace the selected slots, including factory defaults.
|
||||
Omit the slot number to add a rule without replacing an occupied slot.
|
||||
|
||||
The first matching `stop` row ends the FPF7 forward phase after that row. Higher-order
|
||||
matches and the stop row still apply; lower-order matches do not. A stop-only
|
||||
row is therefore an exception to lower-priority FPF7 rows. It cannot undo a
|
||||
@@ -464,6 +474,8 @@ Match fields:
|
||||
`flood.filter`. The positional form remains accepted.
|
||||
- `hops=` accepts `all`, `N`, `N+`, or `N-M`. The positional form remains
|
||||
accepted. Received hops over 3 are written as `hops=4+`.
|
||||
- `mode=radio|bridge|cross|bridge,cross` selects the admission phase; omitted
|
||||
means `radio`. See [bridge and crossover filtering](#filter-bridge-and-radio-crossover-traffic).
|
||||
- `channel=*|public|#name|hash:XX|128-bit-key|256-bit-key` optionally narrows by
|
||||
channel. `channel=*` is an unconstrained wildcard: it performs no channel
|
||||
authentication and matches every payload selected by `type=`. Thus
|
||||
@@ -636,13 +648,77 @@ Without a slot number, `set` reuses an identical row or selects the first empty
|
||||
slot. Use a slot number to replace a row whose match or action is changing.
|
||||
Omitting the hop expression means `all` (`0-63`).
|
||||
|
||||
Numbered `get` normally uses the long field names. If a rule containing
|
||||
several maximum-length names would exceed one CLI reply, it switches to a
|
||||
non-truncating compact spelling that `set` also accepts: `c=` is `channel=`,
|
||||
`p=` is `prefix=`, `i=*|n|s|a|u|s:<scope>|r:<region>` represents `in=`, `q=N`
|
||||
is `rate=N/min`, and `f=str` combines slow timing (`s`), temporary-radio
|
||||
suspension (`t`), and retry allowance (`r`). The fallback prints packet type
|
||||
numerically.
|
||||
Numbered `get flood.rule.N` and `get flood.filter.N` normally use descriptive
|
||||
field names. On generalized builds, a reply that would be too long uses the
|
||||
complete `set fr.N ...` spelling below. `get fr.N` always requests that spelling.
|
||||
If even the short command exceeds one reply, it returns
|
||||
`Err - compact command exceeds reply size` instead of a partial command.
|
||||
Saving such a rule still succeeds and reports that its details exceed the
|
||||
reply size.
|
||||
|
||||
### Shorthand for copying rules
|
||||
|
||||
On generalized repeaters and FULL ESP32 room servers, `fr` is a shorter name
|
||||
for `flood.rule`. Both forms use the same parser, validation, permissions, and
|
||||
saved rows. Short and full field names can be mixed in either command.
|
||||
`fr` requires an explicit action, just like `flood.rule`.
|
||||
|
||||
These commands set exactly the same rule:
|
||||
|
||||
```text
|
||||
set flood.rule.3 type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
set fr.3 any c=#wardriving m=bc d
|
||||
```
|
||||
|
||||
| Full form | Short form | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `flood.rule[.N]` | `fr[.N]` | Same rule table for `get`, `set`, and `del`. |
|
||||
| `type=any` | `any` or `*` | First token: any payload type. Named/numeric types also work, with optional `t=`. |
|
||||
| `hops=N`, `N+`, `N-M`, or `all` | `h=N`, `h=N+`, `h=N-M`, or `h=*` | Hop condition; a bare range also works. Omitted means all hops. |
|
||||
| `mode=radio`, `bridge`, `cross`, `bridge,cross` | `m=r`, `m=b`, `m=c`, `m=bc` | Select the admission path. Omitted means radio; `cb`, `b,c`, and `c,b` also mean both transport paths. |
|
||||
| `channel=...` | `c=...` | Same authenticated name/key or explicit `hash:XX` matcher. |
|
||||
| `prefix=...` | `p=...` | Ordered path prefix. |
|
||||
| `path=blacklist` | `p=bl` | Shared unordered blacklist; repeater only. |
|
||||
| `in=any`, `none`, `scoped`, `allowed`, `unknown` | `i=*`, `i=n`, `i=s`, `i=a`, `i=u` | Incoming scope condition. |
|
||||
| `in=scope:name`, `in=region:name` | `i=s:name`, `i=r:name` | Match a specific incoming scope or region. |
|
||||
| `drop` | `d` | Drop matching traffic. |
|
||||
| `scope=name`, `region=name` | `s=name`, `r=name` | Rewrite to a public scope or configured region. |
|
||||
| `rate=N/min` | `q=N` | Per-row forwarding allowance. |
|
||||
| `priority=N` | `pri=N` | Higher values run first. |
|
||||
| `stop`, `retry` | `s`, `r` | Stop later rules or allow the configured flood retry. |
|
||||
| `tx=slow`, `suspend=tempradio`, `retry` | `f=s`, `f=t`, `f=r` | Flags can combine, for example `f=str`. |
|
||||
|
||||
The `=` distinguishes `s=name` (scope) from `s` (stop), and `r=name`
|
||||
(region) from `r` (retry). Short forms keep the same action restrictions;
|
||||
for example, `m=bc r` is rejected because transport rules cannot enable retries.
|
||||
|
||||
```text
|
||||
get fr.3
|
||||
```
|
||||
|
||||
Returns a complete command ready to copy:
|
||||
|
||||
```text
|
||||
set fr.3 any m=bc c=#wardriving d
|
||||
```
|
||||
|
||||
The compact reply omits default values and prints specific payload types as
|
||||
numbers. `get fr` lists the table. `set fr ...` without a slot reuses an
|
||||
identical rule or adds it to the first empty slot. `del fr.3` removes slot 3;
|
||||
`del fr all` clears the forward rules.
|
||||
|
||||
Private keys are still hidden in replies. A displayed `c=key:XXXXXXXX` is a
|
||||
local reference to the exact key in an active rule on that device, so it can
|
||||
be pasted to edit or copy a rule there. On another device, or after deleting
|
||||
the last rule holding that key, supply the original key instead. An unknown
|
||||
or ambiguous reference is rejected; it never becomes an unauthenticated
|
||||
channel-hash match.
|
||||
|
||||
The shorthand does not rename `flood.channel.scope`, `flood.moderation`, or
|
||||
the blacklist-management commands. Companions and compact FPF6 profiles do
|
||||
not expose `fr`.
|
||||
|
||||
### Scope actions and legacy rules
|
||||
|
||||
A legacy row without `scope=` is the existing drop action. On extended builds,
|
||||
an explicit `drop` has the same result, while `rate=` by itself creates a
|
||||
@@ -695,8 +771,9 @@ This is a normal editable row. After the table has been saved, deleting it
|
||||
remains persistent across reboot; the firmware does not recreate it. Run the
|
||||
same command to restore the exact seeded row, or omit `.1` to preserve existing
|
||||
slot assignments and use the first empty slot. Operators may add
|
||||
`suspend=tempradio` to any other row that should be skipped while the radio is
|
||||
on a temporary channel.
|
||||
`suspend=tempradio` to any other row that should be skipped while either
|
||||
`tempradio` or `tempradio2` is active. This includes scheduled temporary
|
||||
profiles. The row resumes once both temporary sessions have ended.
|
||||
|
||||
Suspension does not approve a packet or bypass the rest of the filter table. It
|
||||
skips that row, then evaluation continues with the next row and the remaining
|
||||
@@ -705,7 +782,9 @@ the temporary-radio window unless an earlier matching stop row ends FPF7
|
||||
processing. `repeat`, `flood.max*`, region handling, loop detection, and the OTA
|
||||
subsystem's own hop limit also remain in force.
|
||||
|
||||
Standard traceroute uses direct routing and never enters `flood.filter`. For a
|
||||
Standard traceroute uses direct routing and bypasses `mode=radio` filters.
|
||||
Explicit bridge/cross filters can also match direct traffic at those transport
|
||||
boundaries; the built-in channel rule does not match TRACE packets. For a
|
||||
custom flood-form trace, `type=any`, explicit `trace`, scope, region, rate, and
|
||||
drop rows all behave normally. The stock core does not normally flood-forward
|
||||
TRACE packets.
|
||||
@@ -788,13 +867,15 @@ get flood.filter
|
||||
| `path 9+` | 9 or more path entries |
|
||||
| `control 1+` | 1 or more path entries |
|
||||
|
||||
On a new table, the factory OTA rule occupies slot 1, so these unnumbered
|
||||
commands normally fill slots 2 through 7. Existing tables may choose different
|
||||
On a new generalized repeater table, the three factory rules occupy slots
|
||||
1 through 3, so these unnumbered commands normally fill slots 4 through 9.
|
||||
FULL room servers and compact tables seed only OTA and use slots 2 through 7.
|
||||
Existing tables may choose different
|
||||
free slots. All six rules take effect at the thresholds shown; there are no
|
||||
hidden short-hop exceptions. The Control rule allows a flood received with
|
||||
path count `0` to be forwarded
|
||||
once, then stops it at the next repeater. Normal node-discovery Control packets
|
||||
are direct zero-hop packets and never enter `flood.filter`. These rules affect
|
||||
are direct zero-hop packets and bypass these `mode=radio` rules. These rules affect
|
||||
only retransmission by the repeater; local reception and logging remain
|
||||
unchanged.
|
||||
|
||||
@@ -922,7 +1003,8 @@ other words, the controls combine as deny rules:
|
||||
charged immediately before retransmission is approved.
|
||||
|
||||
The first denial is enough to prevent retransmission. A packet that is denied
|
||||
can still appear in local logs or MQTT output. Moderation runs last because its
|
||||
can still appear in local logs or MQTT output, unless a separate
|
||||
`mode=bridge` rule blocks MQTT packet publication. Moderation runs last because its
|
||||
rate counters are charged only for packets that pass every other forwarding
|
||||
control and will actually be retransmitted.
|
||||
|
||||
@@ -936,7 +1018,7 @@ setperm <companion-public-key-hex> 5
|
||||
|
||||
A filter manager can read non-secret operational status and manage `repeat`,
|
||||
`loop.detect`, `flood.max*`, `flood.channel.data*`,
|
||||
`flood.filter*`, `flood.rule*`, and `flood.moderation*`. Delegated `get` access uses an
|
||||
`flood.filter*`, `flood.rule*`, `fr[.N]`, and `flood.moderation*`. Delegated `get` access uses an
|
||||
explicit allowlist: it cannot retrieve guest, WiFi, MQTT, bridge, or other
|
||||
credentials, and it cannot change regions, ACL entries, radio settings, or
|
||||
unrelated administrator settings. Because `flood.filter scope=` derives a
|
||||
@@ -944,7 +1026,7 @@ public hashtag key directly, a filter manager can configure that action without
|
||||
region-manager permission; it still cannot edit the region hierarchy.
|
||||
|
||||
FULL ESP32 room servers use their existing administrator check for remote
|
||||
`flood.rule` and `flood.filter` commands; they do not grant this table through
|
||||
`fr`, `flood.rule`, and `flood.filter` commands; they do not grant this table through
|
||||
permission `5`.
|
||||
|
||||
ACL permission `4`, the region/scope-manager role, can read, add, replace, and
|
||||
@@ -965,6 +1047,79 @@ Use username and path rules as traffic moderation, not as an authorization
|
||||
boundary. For a strict network boundary, combine these tools with region ACLs,
|
||||
private transport/channel keys, and controlled device access.
|
||||
|
||||
## Filter bridge and radio crossover traffic
|
||||
|
||||
The `mode=` matcher selects a separate admission phase within the same
|
||||
`flood.rule` / `flood.filter` table:
|
||||
|
||||
| Mode | Where the rule applies |
|
||||
| --- | --- |
|
||||
| `radio` | Ordinary LoRa flood forwarding. This is the default when `mode=` is omitted. |
|
||||
| `bridge` | Packets entering or leaving an RS232 or ESP-NOW bridge, and MeshCore RX/TX packet publication through MQTT. |
|
||||
| `cross` | Transmissions copied to the other radio profile, including a copy's retries. The same-profile transmission remains eligible. |
|
||||
| `bridge,cross` | Both transport paths using one rule. `cross,bridge` is equivalent. |
|
||||
|
||||
The third built-in repeater filter is:
|
||||
|
||||
```text
|
||||
set flood.rule.3 type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
get flood.rule.3
|
||||
```
|
||||
|
||||
The equivalent short command is `set fr.3 any c=#wardriving m=bc d`.
|
||||
Use `m=b` for bridge only or `m=c` for crossover only.
|
||||
|
||||
It authenticates both `GRP_TXT` and `GRP_DATA` against the public `#wardriving`
|
||||
channel key. A different channel with the same one-byte hash is not blocked.
|
||||
It covers every hop count, including zero-hop/local messages, flood routes and
|
||||
direct routes. It remains active during temporary-radio sessions and with
|
||||
`radio2.cross on`: enabling crossover makes a copy eligible, but does not
|
||||
override the filter. The second built-in rule still permits ordinary
|
||||
`#wardriving` forwarding through four received hops; the third rule keeps
|
||||
those messages on their originating radio profile.
|
||||
|
||||
Bridge filtering runs in both directions before bridge deduplication, inbound
|
||||
queueing, serial writes, ESP-NOW queueing, or MQTT packet publication. It
|
||||
applies with either `bridge.source rx` or `bridge.source tx` and with wrapped or raw
|
||||
ESP-NOW framing. MQTT here is the observer packet output; this does not add
|
||||
MQTT-to-LoRa injection or filter MQTT status/telemetry. Local reception and
|
||||
local packet logs remain available. Switching one radio between profiles is
|
||||
covered by `cross`; the bridge's WiFi channel is a separate setting.
|
||||
|
||||
Transport modes support `drop`, `rate=X/min`, `priority=`, and `stop`, with the
|
||||
existing type, channel, hops, prefix, blacklist, incoming-scope and
|
||||
`suspend=tempradio` matchers. Scope/region rewrites, retry actions, and TX
|
||||
timing actions are rejected in these modes. Ordering and `stop` apply only to
|
||||
rules in the current mode: a radio exception cannot bypass a bridge/cross
|
||||
drop. A combined rule shares one rate counter across bridge and cross
|
||||
admission attempts; retries count too. A rejected queue insertion can consume
|
||||
an admission allowance. Queued packets already admitted are not recalled by
|
||||
editing a rule. Same-profile transmissions use the ordinary radio policy.
|
||||
|
||||
`get flood.rule.N` shows `mode=`; the overview marks each row with its mode.
|
||||
Modes are persistent, case-insensitive, and apply immediately to new admission
|
||||
attempts. `radio` cannot be combined with `bridge` or `cross` in a single row.
|
||||
For transport rules, hop and scope matchers see the packet at that boundary:
|
||||
a TX/crossover packet can already contain an appended hop or rewritten scope.
|
||||
The built-in rule uses all hops and any scope, so these changes cannot bypass it.
|
||||
On FULL room servers, add a transport rule explicitly; their factory table
|
||||
still has only the OTA row. Companions and compact FPF6 builds do not expose
|
||||
this generalized filter table.
|
||||
|
||||
Saved tables, occupied slots, and deleted defaults are preserved on upgrade.
|
||||
To add the new default to an existing table without replacing another rule,
|
||||
omit the slot number (repeating the identical command reuses its row):
|
||||
|
||||
```text
|
||||
set flood.rule type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
get flood.rule
|
||||
```
|
||||
|
||||
To allow only bridge traffic while retaining the crossover block, replace the
|
||||
third rule with `mode=cross`. Use `mode=bridge` to retain only the bridge block.
|
||||
Use `del flood.rule.3` to remove both, after checking that slot 3 is still the
|
||||
built-in rule. Removing it does not change the separate LoRa hop-limit rule.
|
||||
|
||||
## Restore the factory-seeded rows
|
||||
|
||||
The repeater's factory-seeded forwarding rows can be restored through the CLI:
|
||||
@@ -972,9 +1127,18 @@ The repeater's factory-seeded forwarding rows can be restored through the CLI:
|
||||
```text
|
||||
set flood.rule.1 type=ota hops=all drop suspend=tempradio
|
||||
set flood.rule.2 type=any channel=#wardriving hops=5+ drop
|
||||
set flood.rule.3 type=any channel=#wardriving hops=all mode=bridge,cross drop
|
||||
```
|
||||
|
||||
These commands explicitly replace the two seeded generalized-repeater slots.
|
||||
Or use the equivalent short forms:
|
||||
|
||||
```text
|
||||
set fr.1 ota d f=t
|
||||
set fr.2 any 5+ c=#wardriving d
|
||||
set fr.3 any c=#wardriving m=bc d
|
||||
```
|
||||
|
||||
These commands explicitly replace the three seeded generalized-repeater slots.
|
||||
Inspect them first if they may now contain other rules. Compact FPF6 builds use
|
||||
only the first command's `flood.filter.1 0x0C all suspend=tempradio` form.
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ set flood.retry.ignore none
|
||||
| `flood.channel.data.hops` | Sets the compatibility row's hop range: `all` maps to `hops=all`; `1`-`7` repeats through that hop count and maps to `hops=N+1+`. | `get flood.channel.data.hops`, `set flood.channel.data.hops <all|1-7>` | `set flood.channel.data.hops 7` |
|
||||
| `flood.channel.scope` | FPF7 rewrite-phase rows that add a transport scope to received unscoped floods or replace the scope of already-scoped floods. A bare target uses an existing flood-allowed region; `scope=<name>` derives a public hashtag target directly without creating a region, exactly like `flood.filter scope=`. By default, a changed packet bypasses inbound `rxdelay` and is forwarded at the highest outbound queue priority with zero initial `txdelay`, so the selected scope can win at the next hop. `tx=slow` uses an effective inbound `rxdelay` base of `max(2, configured rxdelay * 2)`, retains normal queue priority, and forces the maximum `txdelay` factor of `2.0`; its actual randomized transmit delay ranges from zero through ten packet airtimes. `path=blacklist` and `path=bucket:<1-6>` make a row path-qualified; bridge buckets remain usable while bridge retry is off. An already-matching scope is a no-op. Exact channel keys beat `txt:*`; path-qualified rows beat the ordinary channel fallback. `login:*` covers the remote-login family, and `other:*` covers every remaining flood type, including flood-form TRACE and OTA. Direct traceroute remains outside the flood table. Generalized builds commit these rows with the forward phase and blacklist; compact FPF6 builds retain separate storage. ACL permission `4` can manage the table. | `get flood.channel.scope[.n]`, `set flood.channel.scope[.n] <channel|txt:*|login:*|other:*> <region|scope=name> [path=blacklist|path=bucket:1-6] [tx=slow]`, `del flood.channel.scope.<n>|all` | `set flood.channel.scope #rgdata scope=BlackHole86` |
|
||||
| `flood.channel.scope.require` | Switches group-channel region enforcement to opt-in when the table has entries. Listed authenticated `GRP_TXT`/`GRP_DATA` channels must arrive already scoped to a locally allowed region; unscoped, unknown, or denied incoming scopes are dropped before any rewrite can rescue them. Unlisted group channels bypass only the region gate and retain all other forwarding controls. An empty table preserves global region behavior; non-channel payloads are unchanged. ACL permission `4` can manage the table. | `get flood.channel.scope.require[.n]`, `set flood.channel.scope.require[.n] <public|#channel|key>`, `del flood.channel.scope.require.<n>|all` | `set flood.channel.scope.require #bot` |
|
||||
| `flood.filter` | Persistent flood-route rules selected by payload type and optional hop range. Generalized repeaters have 63 FPF7 forward slots; FULL ESP32 room servers have 31. Repeaters store their scope-rewrite phase, shared unordered blacklist, and channel-data compatibility state in the same atomic FPF7 image. `path=blacklist` is intended for forwarding abuse containment, including bulk internet-to-mesh dumping, but truncated path IDs are not authenticated identities. Fixed 240 KB STM32WL repeaters retain compact FPF6 filtering and separate blacklist storage. New generalized repeater tables seed slot 1 with `ota all suspend=tempradio` and slot 2 with an authenticated `#wardriving hops=5+` drop. Direct routes and local receive/logging are unchanged. | `get flood.filter[.n]`, `set flood.filter[.n] <type> [N|N+|N-M|all] [scope=<name>] [require=region] [tx=slow] [suspend=tempradio]`, `del flood.filter.<n>|all`; repeater only: `get/set/del flood.filter.blacklist[.n]` | `set flood.filter grp_txt all scope=local tx=slow` |
|
||||
| `flood.filter` | Persistent flood-route rules selected by payload type and optional hop range. Generalized repeaters have 63 FPF7 forward slots; FULL ESP32 room servers have 31. Repeaters store their scope-rewrite phase, shared unordered blacklist, and channel-data compatibility state in the same atomic FPF7 image. `path=blacklist` is intended for forwarding abuse containment, including bulk internet-to-mesh dumping, but truncated path IDs are not authenticated identities. Fixed 240 KB STM32WL repeaters retain compact FPF6 filtering and separate blacklist storage. New generalized repeater tables seed slot 1 with `ota all suspend=tempradio` slot 2 with an authenticated `#wardriving hops=5+` radio drop, and slot 3 with `#wardriving hops=all mode=bridge,cross drop`. `mode=radio` is the default; `bridge`, `cross`, and `bridge,cross` select separate transport admission paths, including direct packets. Local receive/logging is unchanged. See the [mode guide](flood_filtering.md#filter-bridge-and-radio-crossover-traffic). | `get flood.filter[.n]`, `set flood.filter[.n] <type> [N|N+|N-M|all] [scope=<name>] [require=region] [tx=slow] [suspend=tempradio]`, `del flood.filter.<n>|all`; repeater only: `get/set/del flood.filter.blacklist[.n]` | `set flood.filter grp_txt all scope=local tx=slow` |
|
||||
| `flood.rule` | Live alias for extended `flood.filter` on rule-engine repeaters and FULL ESP32 room servers. A row can AND packet type, hop range, optional channel match, ordered 1/2/3-byte pbyte source prefix, and original scope/region conditions, then drop, rewrite to a direct scope or configured region, enforce a per-row rate, allow flood retry, and/or stop lower-priority FPF7 rules. `channel=*` means no channel condition; `public`, `#name`, and keys authenticate; `hash:XX` is an unauthenticated one-byte fallback with collision/spoof risk. With no `retry` rows, global retry behavior is unchanged; once any exists, matching `retry` rows allow-list received floods while the global retry gates and `flood.retry.bridge` algorithm still apply. `scope=BlackHole86` directly derives a regionless sink scope; `region=BlackHole86` would require a configured flood-allowed region. All rows match the original receive-time packet. Higher numeric `priority` runs first; equal values automatically order authenticated channels before raw hashes before `channel=*`, then use lower slot as the final tie-break. Repeated rows with one channel key share a per-packet authentication result. Persistent FPF7 stores canonical region names, so region ID reorder or reuse cannot retarget a rule. A missing saved region makes its `region=` rewrite and paired `stop` inert until the name returns, allowing lower safety rows to run. Fixed 240 KB STM32WL profiles keep FPF6 and do not expose this alias; partition sizes are unchanged. | `get flood.rule[.n]`, `set flood.rule[.n] type=<type> [hops=...] [channel=...] [prefix=...] [in=...] <drop|scope=...|region=...|rate=N/min|retry|stop> [priority=0-255]`, `del flood.rule.<n>|all` | `set flood.rule.2 type=any channel=#hamradio retry` |
|
||||
| `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 all repeater, sensor, and room-server builds; a saved setting overrides that default. It estimates UTC as soon as the configured number of fresh signed-advert or valid Public-channel sources is collected, with a 30-minute bootstrap/retry timer when evidence is still insufficient, then repeats lazily seven days after each successful estimate. New evidence retriggers evaluation after a no-consensus result. `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` |
|
||||
@@ -98,6 +98,14 @@ set flood.retry.ignore none
|
||||
| `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` |
|
||||
|
||||
The generalized filter table also accepts `fr` as a short alias for
|
||||
`flood.rule`: `set fr.3 any c=#wardriving m=bc d` is the full third built-in
|
||||
rule. Modes can be `m=r` (radio), `m=b` (bridge), `m=c` (crossover), or `m=bc`
|
||||
(both transport paths). `get fr.N` returns a short setter ready to copy;
|
||||
`del fr.N` deletes that row. Full names and short tokens can be mixed, with
|
||||
the same validation and permissions. See the
|
||||
[shorthand reference](flood_filtering.md#shorthand-for-copying-rules).
|
||||
|
||||
## Other Keymind Commands
|
||||
|
||||
| Command | What it does | How to use | Example |
|
||||
|
||||
+148
-5
@@ -29,7 +29,7 @@ denominator, mode, optional preamble in symbols**. `5` means coding rate 4/5.
|
||||
|
||||
| Mode | Receive on profile 2 | Transmit on profile 2 |
|
||||
| --- | --- | --- |
|
||||
| `rx` | Yes | No |
|
||||
| `rx` | Yes | Only infrastructure replies explicitly configured with `tx.reply ... force` |
|
||||
| `rxtx` (also `rx&tx`) | Yes | Yes |
|
||||
| `off` | No | No |
|
||||
|
||||
@@ -53,7 +53,8 @@ get tempradio2
|
||||
```
|
||||
|
||||
This enables the second profile for 120 minutes (2 hours). Locally generated
|
||||
LoRa OTA packets use the temporary second profile. Ordinary local traffic stays
|
||||
LoRa OTA requests use the temporary second profile. Infrastructure OTA responses
|
||||
follow `tx.reply` below (both TX-capable profiles by default). Ordinary local traffic stays
|
||||
on the primary profile by default. Use an OTA-capable build and the usual OTA
|
||||
setup on every participating node; a temporary profile does not add OTA support
|
||||
to firmware built without it.
|
||||
@@ -62,6 +63,67 @@ New settings take effect after a short reply allowance. Temporary periods and
|
||||
schedules live in RAM and disappear on reboot. Timer expiry also uses monotonic
|
||||
time, so setting the clock backwards cannot extend a temporary session.
|
||||
|
||||
## Repeater, room-server, and sensor replies
|
||||
|
||||
These roles default to **`tx.reply both`**: locally generated replies are queued
|
||||
on `radio` and on an active `radio2` that permits TX. This includes replies to
|
||||
remote CLI/login/telemetry requests, ACKs, returned paths, delayed GPIO command
|
||||
completion, room subscription posts, and locally served LoRa OTA catalog,
|
||||
manifest, data, proof, and leaves responses. Each copy has its own retry state.
|
||||
Forwarded packets, periodic adverts, sensor pushes, and OTA requests continue
|
||||
to use the normal crossing rules.
|
||||
|
||||
```text
|
||||
get tx.reply
|
||||
set tx.reply both
|
||||
set tx.reply both force
|
||||
set tx.reply radio
|
||||
set tx.reply auto
|
||||
```
|
||||
|
||||
| Choice | Replies transmit on |
|
||||
| --- | --- |
|
||||
| `both` | Primary plus an active `rxtx` second profile. Default. |
|
||||
| `radio` | Primary only. |
|
||||
| `radio2` | Second only; no primary fallback. |
|
||||
| `auto` | Normal `radio2.cross` behavior, including permanent/temporary isolation. |
|
||||
| `off` | Neither profile. This also suppresses remote CLI replies and ACKs. |
|
||||
|
||||
Append **`force`** to `both` or `radio2` to allow these replies on an active
|
||||
second profile configured as `rx`. Without `force`, `rx` remains receive-only.
|
||||
Sending the command again without `force` removes the override. It never turns
|
||||
on `radio2 off`, changes the saved `rx` mode, or enables ordinary forwarding/TX
|
||||
there. Existing crossover packet filters still apply to the extra copy.
|
||||
|
||||
The explicit choices override `radio2.cross`, including `off` and mixed
|
||||
permanent/temporary profiles. `radio` means the active primary profile,
|
||||
including `tempradio`; `radio2` also includes an active `tempradio2`.
|
||||
For example, an OTA source can retain its normal channel while allowing OTA
|
||||
and management responses on a temporary receive profile:
|
||||
|
||||
```text
|
||||
set tempradio2 910.5,500,8,5,rx,120
|
||||
set tx.reply both force
|
||||
```
|
||||
|
||||
The source must have OTA support and an active temporary-radio session as
|
||||
usual. A node fetching an update still needs `rxtx` for its OTA requests.
|
||||
Forcing replies does not change OTA's receive gates, scan timing, or preambles.
|
||||
|
||||
This setting survives reboot; older saved profiles adopt `both` without force.
|
||||
The getter reports when the chosen second profile cannot transmit. Already
|
||||
queued replies retain their choice; disabling/changing a profile invalidates
|
||||
stale queued packets. A parameterized repeater `tempradio` command waits for
|
||||
both admitted reply copies to drain before changing the primary settings;
|
||||
at least one must transmit successfully. Those tracked replies omit alternate
|
||||
paths and transport retries so no late success copy can outlive the handoff.
|
||||
Queue capacity and the shared transceiver's airtime budget still limit delivery.
|
||||
OTA data/proof admission counts both copies against its queue credit and
|
||||
reserves receive capacity before allowing the response.
|
||||
|
||||
Companions use the per-contact/channel choices below; `tx.reply` is an
|
||||
infrastructure setting.
|
||||
|
||||
## Companion messages on both profiles
|
||||
|
||||
To send ordinary Companion messages on both `radio` and an active `tempradio2`,
|
||||
@@ -83,6 +145,83 @@ ends or the node reboots. Run `set radio2.cross auto` to restore the default
|
||||
isolation between permanent and temporary profiles. The firmware picker's
|
||||
Companion tempradio2 instructions include both choices and the status commands.
|
||||
|
||||
## Companion TX routing by contact or channel
|
||||
|
||||
Companions can save a transmit choice for each contact and each configured
|
||||
channel. The commands apply to messages sent from the phone app, USB/BLE/TCP
|
||||
Companion clients, and the text terminal:
|
||||
|
||||
```text
|
||||
set tx.user "Alice Jones" radio2
|
||||
get tx.user "Alice Jones"
|
||||
set tx.channel 0 both
|
||||
get tx.channel 0
|
||||
set tx.channel #wardriving radio
|
||||
get tx.channel #wardriving
|
||||
```
|
||||
|
||||
Channel indices match the app and the terminal's `channels` list; Public is
|
||||
normally slot `0`. Names must match exactly, including capitalization and any
|
||||
leading `#`. Quotes always select a literal name, so `"1"` is a channel named
|
||||
`1`, while unquoted `1` selects slot 1. Quote contact names beginning with
|
||||
`key:` to distinguish them from public-key selectors. Duplicate names are
|
||||
rejected: use the channel index or a contact's public key instead.
|
||||
|
||||
| Choice | Locally addressed traffic transmits on |
|
||||
| --- | --- |
|
||||
| `auto` | Existing behavior, including `radio2.cross` and temporary-profile isolation. This is the default. |
|
||||
| `radio` | The primary profile only. |
|
||||
| `radio2` | The second profile only; sending fails if it is off or RX-only. |
|
||||
| `both` | The primary profile and the second profile whenever the second is enabled for TX. |
|
||||
| `off` | Neither profile for this contact/channel. Reception stays enabled. |
|
||||
|
||||
Explicit choices override `radio2.cross`, including `off` and the isolation
|
||||
between a permanent and temporary profile. They do not turn on radio2 or
|
||||
change its `rx`/`rxtx` mode. For example, leave `radio2.cross auto` and use
|
||||
`set tx.channel 0 both` to send Public messages on both `radio` and an active
|
||||
`tempradio2`, while other channels keep their default routing.
|
||||
|
||||
`radio` means the currently active primary profile, whether permanent or
|
||||
`tempradio`; `radio2` similarly includes `tempradio2`. There are still at most
|
||||
two active profiles. Getters show the saved choice and `active=` for newly
|
||||
initiated messages with the current profile configuration. A temporarily
|
||||
unavailable second profile is reported explicitly.
|
||||
|
||||
Contact selection uses the full stored public key after resolving a unique
|
||||
name or key prefix. It never routes by the short on-air destination hash.
|
||||
For key selection, prefix 12–64 hexadecimal digits (whole bytes) with `key:`:
|
||||
|
||||
```text
|
||||
set tx.user key:0123456789AB radio2
|
||||
get tx.user key:0123456789AB
|
||||
```
|
||||
|
||||
Replace the example prefix with your contact's key; use more digits if it is
|
||||
ambiguous. `get tx.user` and `get tx.channel` report override counts and query
|
||||
syntax. To return a target to the global behavior:
|
||||
|
||||
```text
|
||||
set tx.user "Alice Jones" auto
|
||||
set tx.channel 0 auto
|
||||
```
|
||||
|
||||
A user's setting covers addressed texts, login/CLI/telemetry requests, replies,
|
||||
ACKs, and returned paths to that contact. Thus `off` also prevents ACKs to
|
||||
that contact. Channel settings cover group text and group data, including
|
||||
an explicitly routed group datagram. Contact choices do not filter other
|
||||
people's messages inside a group channel. Adverts, raw traces, raw packets,
|
||||
and OTA packets without a contact/channel destination retain global routing.
|
||||
Packets and their retries already queued retain the choice used when created;
|
||||
changing or disabling the radio profile still invalidates stale queued work.
|
||||
Message timeout estimates use the selected profiles' airtime, independently
|
||||
of the scanner's current receive profile.
|
||||
|
||||
Choices survive reboot and normal phone-app updates to the same contact or
|
||||
channel key. Replacing a channel's key or deleting/replacing a contact clears
|
||||
its choice. They use reserved bytes in existing saved records, so record
|
||||
sizes and the phone protocol are unchanged. Older firmware ignores these
|
||||
bytes and may reset the choices when it saves the records.
|
||||
|
||||
## Choose whether traffic crosses between profiles
|
||||
|
||||
```text
|
||||
@@ -99,12 +238,16 @@ set radio2.cross off
|
||||
| `radio` | `tempradio2` | Isolated | Cross | Isolated |
|
||||
| `tempradio` | `radio2` | Isolated | Cross | Isolated |
|
||||
|
||||
Crossing always respects `rx`: nothing transmits on an RX-only second profile.
|
||||
These are the global rules used by Companion targets set to `auto`; an explicit
|
||||
contact/channel choice above overrides crossing for its own outbound packets.
|
||||
Ordinary crossing respects `rx`. The infrastructure-only `tx.reply ... force`
|
||||
override above permits locally generated replies on that profile.
|
||||
With crossing allowed, a packet received there may still be forwarded on the
|
||||
primary profile. Existing routing, forwarding and packet-filter settings apply.
|
||||
An RX-only `tempradio2` also blocks locally generated OTA transmissions under
|
||||
An RX-only `tempradio2` also blocks locally generated OTA requests under
|
||||
`auto` or `off`; they do not fall back to the normal primary channel. Use `rxtx`
|
||||
for an update session that needs to exchange requests and data.
|
||||
for a fetching node that needs to exchange requests and data. Infrastructure
|
||||
OTA responses follow `tx.reply` independently of this crossing table.
|
||||
|
||||
## Preamble
|
||||
|
||||
|
||||
@@ -85,6 +85,26 @@ The browser CLI requires station/LAN mode and `wifi.cli on`. Infrastructure
|
||||
uses its admin login; Companion uses the trusted LAN. Configuration forms keep
|
||||
password fields masked; explicit local CLI getters return their values.
|
||||
|
||||
## Companion contact and channel TX profiles
|
||||
|
||||
Choose where the Companion transmits to a contact or group channel:
|
||||
|
||||
```text
|
||||
set tx.user "Alice Jones" radio2
|
||||
get tx.user "Alice Jones"
|
||||
set tx.channel 0 both
|
||||
set tx.channel #wardriving radio
|
||||
get tx.channel 0
|
||||
```
|
||||
|
||||
Values are `auto`, `radio`, `radio2`, `both`, and `off`. `auto` follows
|
||||
`radio2.cross`; explicit choices override it. Radio2 must still be in `rxtx`
|
||||
mode, and a `radio2`-only send fails when it cannot transmit. These settings
|
||||
persist and also apply to messages sent by the phone app. Use exact names,
|
||||
a channel index, or `key:<unique public-key prefix>` for a contact.
|
||||
See the [routing guide](radio_profiles.md#companion-tx-routing-by-contact-or-channel)
|
||||
for which requests/replies are covered and how to reset a choice.
|
||||
|
||||
## Companion USB mode
|
||||
|
||||
Updated 1.17.1.6 USB Companion builds, including ordinary and Full builds, start
|
||||
|
||||
Reference in new issue
Block a user