You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: prevent RF crosstalk via ServiceEnvelope payload mutation for virtual channels (#41)
* docs: amend v1.6.2 release notes
* fix: prevent RF crosstalk via ServiceEnvelope payload mutation for virtual channels
Virtual channel topic rewriting was insufficient to prevent RF rebroadcast.
The radio firmware uses packet.channel (PSK hash) to look up the decryption
key, not the MQTT topic string. Packets from extra MQTT roots sharing the
same channel key (e.g. LongFast/AQ==) were being decrypted and rebroadcast
over RF by the local node, bridging two independent MQTT server regions.
Fix: mutate the ServiceEnvelope protobuf before radio injection:
- envelope.channel_id -> virtual channel name (e.g. 'NC-LongFast')
- packet.channel -> synthetic hash unique to the virtual channel name
The radio firmware finds no matching PSK for the synthetic hash, cannot
decrypt, and does not rebroadcast. The encrypted payload bytes are unchanged,
so MeshMonitor can still decrypt using the original key via its Channel
Database entry for the virtual channel name.
Confirmed via live MQTT packet inspection: packet.channel carries the PSK
hash (not a slot index), and radio firmware matches by hash, not by name.
* docs: update Virtual Channels documentation with MeshMonitor Channel Database setup
* fix: elevate virtual channel rewrite log to INFO; fix docs log example
* chore: release v1.6.3
* docs: clarify MeshMonitor Channel DB matches by PSK not channel name
* docs: add MeshMonitor Enforce Channel Name Validation guidance for shared-PSK channels
* docs: remove confusing MeshMonitor custom name setup, channels work natively
* fix: revert channel_id mutation, keep original names for native MeshMonitor support
* docs: clarify Enforce Channel Name Validation usage for shared keys
* docs: document known defect where MeshMonitor merges shared-key channels
Copy file name to clipboardExpand all lines: CONFIG.md
+31-11Lines changed: 31 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -122,26 +122,46 @@ BLE support requires custom implementation using the `bleak` library. See the [m
122
122
123
123
### Extra MQTT Roots (Virtual Channels)
124
124
125
-
Monitor encrypted traffic from additional regions beyond your primary root topic**without causing RF crosstalk**.
125
+
Monitor encrypted traffic from additional MQTT servers or root topics beyond your primary node configuration**without causing RF crosstalk**.
126
126
127
127
**Configuration:**
128
128
```env
129
-
EXTRA_MQTT_ROOTS=msh/US/OH, msh/US/CA:California
129
+
# Format: <root>:<alias>, ...
130
+
# The alias becomes the channel prefix in MeshMonitor (e.g. NC-LongFast)
131
+
EXTRA_MQTT_ROOTS=msh/US/NC:NC, msh/US/OH:Ohio
130
132
```
131
133
132
-
When configured, the proxy automatically subscribes to `{root}/2/e/#` for each specified root in addition to your node's primary root.
134
+
When configured, the proxy subscribes to `{root}/2/e/#` for each extra root in addition to your node's primary root.
133
135
134
-
**Topic Rewriting (Virtual Channels)**:
135
-
To prevent "crosstalk" (where downloading Ohio's `LongFast` traffic inadvertently causes your local Michigan USB radio to broadcast it over RF), the proxy utilizes a **Virtual Channel** mapping.
136
+
**How Virtual Channels Work:**
136
137
137
-
If a packet arrives from an extra root, its channel name is rewritten on-the-fly to include a prefix:
138
-
-`msh/US/OH` defaults to the prefix `OH` ➔ `OH-LongFast`
When a packet arrives from an extra root, the proxy performs a two-part rewrite before injecting it into the radio:
140
139
141
-
By rewriting the channel name to a "Virtual Channel" (e.g., `OH-LongFast`), your local USB radio ignores it. However, if you are using MeshMonitor's Channel Database, the encrypted payload is successfully decrypted, allowing you to seamlessly monitor cross-region traffic!
140
+
1.**Topic rewrite:** The MQTT topic channel name is prefixed with the alias.
2.**Payload mutation (crosstalk prevention):** The proxy mutates the `packet.channel` field inside the `ServiceEnvelope` protobuf.
144
+
-`packet.channel` integer (PSK hash) → a synthetic hash derived from the virtual channel name
145
+
146
+
The radio firmware uses this PSK hash to look up its decryption key. Since no local channel has the synthetic hash configured, the radio **cannot decrypt the packet and will not rebroadcast it over RF** — completely preventing cross-region crosstalk.
147
+
148
+
**MeshMonitor Channel Database Setup:**
149
+
150
+
Because the proxy preserves the original `channel_id` string and leaves the encrypted payload bytes untouched, MeshMonitor natively receives the exact original packet data.
151
+
152
+
You just need to ensure the original channel name and PSK exist in your MeshMonitor Channel Database. You do **not** need to create special `NC-` prefixed names.
153
+
154
+
> [!CAUTION]
155
+
> **Known Limitation (Shared Keys):** If you use the same key for multiple channels (e.g., both your local `LongFast` and the virtual `LongFast` use `AQ==`), **MeshMonitor will decode traffic for both into the exact same channel display**.
156
+
>
157
+
> You cannot turn on "Enforce Channel Name Validation" in MeshMonitor to separate them. Because the proxy must fake the PSK hash to prevent the physical radio from transmitting the packet, MeshMonitor's strict "Enforce Name" validation will fail the hash check and refuse to decrypt entirely.
158
+
>
159
+
> As a result, virtual channel traffic will simply be merged into your existing local channel display if they share a key.
160
+
> **Monitoring only:** Virtual Channels are strictly read-only. Because the hardware radio does not know about virtual channels, there is no way to send a reply on a virtual channel. This is by design — the feature is intended for safe, passive cross-region monitoring without bridging two networks.
161
+
162
+
> [!IMPORTANT]
163
+
> **Loop Prevention:** When MeshMonitor echoes a virtual channel packet back to the proxy, the proxy's uplink filter automatically drops it (since the virtual channel is not defined on the physical radio). This prevents an infinite `proxy → MeshMonitor → MQTT → proxy` feedback loop.
142
164
143
-
> [!TIP]
144
-
> **Loop Prevention & MeshMonitor Architecture:** Virtual Channels are intentionally sent to the proxy's transmission queue so they can be received natively over the socket connection by MeshMonitor (which acts as a Virtual Node Server). When MeshMonitor echoes the packed back to the proxy, the proxy automatically **drops** the Virtual Channel instead of republishing it. This breaks the infinite MQTT loop while keeping MeshMonitor informed!
Copy file name to clipboardExpand all lines: README.md
+8-7Lines changed: 8 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
A production-ready MQTT proxy for Meshtastic devices that enables bidirectional message forwarding between Meshtastic nodes and MQTT brokers. Supports TCP and Serial interface connections with a clean factory pattern architecture.
4
4
5
-
**Version**: 1.6.2
5
+
**Version**: 1.6.3
6
6
7
7
## Features
8
8
@@ -21,6 +21,7 @@ A production-ready MQTT proxy for Meshtastic devices that enables bidirectional
21
21
- ✅ **Traffic Optimization** - Smart subscription strategy (`msh/2/e/#`) to prevent serial link saturation
Copy file name to clipboardExpand all lines: RELEASE_NOTES.md
+21Lines changed: 21 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,3 +1,24 @@
1
+
# Release v1.6.3
2
+
3
+
## Virtual Channel RF Crosstalk Prevention
4
+
5
+
This release fixes a critical bug where Virtual Channel packets injected into the local radio (via `EXTRA_MQTT_ROOTS`) could be decrypted and rebroadcast over RF to nearby nodes, effectively bridging two independent MQTT server regions against the user's intent.
6
+
7
+
## 🐛 Bug Fixes
8
+
9
+
***Fix: Virtual Channel RF Crosstalk** (PR #41)
10
+
***Root Cause:** The radio firmware uses `packet.channel` — a PSK hash integer embedded in the `ServiceEnvelope` protobuf payload — to look up its decryption key. Because the topic-only rewrite (`NC-LongFast`) left the original PSK hash intact, the radio could still match its local channel key and decrypt the packet, causing it to rebroadcast over RF.
11
+
***Fix:** The proxy now mutates the `packet.channel` field in the protobuf payload before injecting it into the radio:
12
+
*`packet.channel` — replaced with a synthetic hash unique to the virtual channel name that no local radio will ever have configured
13
+
* The `packet.encrypted` bytes and original `channel_id` string are left completely untouched. MeshMonitor natively decrypts the message using its standard Channel Database. **Known Defect:** Because the PSK hash is faked, MeshMonitor's "Enforce Channel Name Validation" cannot be used. Consequently, if multiple channels share the exact same key, MeshMonitor will decode and merge their traffic into a single channel display.
14
+
* Added `INFO`-level log entry on every virtual channel rewrite for easy discovery of exact virtual channel names.
15
+
16
+
## 📝 Documentation
17
+
18
+
* Updated `CONFIG.md` Virtual Channels section to accurately describe the payload mutation mechanism and added **MeshMonitor Channel Database setup instructions** explaining which channel name and key to configure for each virtual channel.
19
+
20
+
---
21
+
1
22
# Release v1.6.2
2
23
3
24
This release introduces two major new features to handle advanced broker routing and to improve fidelity with Meshtastic node configurations: **Virtual Channels (Multi-Root)** and **Per-Channel Uplink/Downlink Filtering**.
Virtual channel topic rewriting (renaming `LongFast` → `NC-LongFast` in the MQTT topic) was insufficient to prevent RF rebroadcast crosstalk.
4
+
5
+
The radio firmware uses **`packet.channel`** (a PSK hash integer inside the `ServiceEnvelope` protobuf payload) to look up the decryption key — **not** the MQTT topic string. When packets from extra MQTT roots shared the same PSK as a local channel (e.g. `LongFast` / `AQ==`), the radio successfully decrypted and rebroadcast them over RF, effectively bridging two independent MQTT server regions.
6
+
7
+
## Fix
8
+
9
+
Mutate the `ServiceEnvelope` protobuf **before** injecting to the radio:
10
+
-`packet.channel` → synthetic hash derived from the virtual channel name (range 200-254)
11
+
12
+
The radio firmware finds no matching PSK for the synthetic hash → cannot decrypt → **does not rebroadcast over RF** ✅
13
+
14
+
The `packet.encrypted` bytes and the `channel_id` string name are **completely untouched**. This allows MeshMonitor to decrypt the packet using its standard Channel Database natively.
15
+
16
+
**Known Defect:** Because the PSK hash is faked to prevent radio crosstalk, MeshMonitor's "Enforce Channel Name Validation" cannot be used (it strictly fails the hash check). Consequently, if multiple monitored channels share the exact same key, MeshMonitor will decode and merge their traffic into a single channel display.
17
+
18
+
## Setup for Users
19
+
20
+
Users just ensure the original PSK exists in their MeshMonitor Channel Database. No custom `NC-` prefixed entries are required. "Enforce Channel Name Validation" must remain OFF.
21
+
22
+
## Testing
23
+
- All 67 existing tests pass
24
+
- Live verified: `packet.channel` carries PSK hash (not slot index) via `inspect_payload.py`
25
+
- Confirmed MeshMonitor successfully decrypts natively, with documentation updated on the shared-PSK defect.
0 commit comments