aaronb/wireshark-meshcore

GitHub: aaronb/wireshark-meshcore

Stars: 15 | Forks: 0

# MeshCore Wireshark Dissector A Wireshark protocol dissector and pcapng converter for [MeshCore](https://github.com/meshcore-dev/MeshCore), a LoRa mesh networking protocol. ![MeshCore dissector showing packet list with protocol tree and decrypted group message](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/0b8b513990022353.png) - **`meshcore_dissector.lua`** -- Wireshark Lua dissector that decodes MeshCore packets with full protocol tree, display filters, and info column summaries ([download](https://raw.githubusercontent.com/aaronb/wireshark-meshcore/main/meshcore_dissector.lua)) - **`meshcore_json2pcap.py`** -- Python script that converts JSON radio logs to pcapng files ([download](https://raw.githubusercontent.com/aaronb/wireshark-meshcore/main/meshcore_json2pcap.py)) ## Features - Decodes all MeshCore packet types: ADVERT, ACK, GRP_TXT, GRP_DATA, TXT_MSG, REQ, RESPONSE, ANON_REQ, PATH, TRACE, MULTIPART, CONTROL, RAW_CUSTOM - Header bitmask dissection (route type, payload type, payload version) - ADVERT appdata parsing: node type, GPS coordinates, node name - CONTROL sub-type parsing: DISCOVER_REQ, DISCOVER_RESP - Two-layer dissection (like 802.11 Radiotap): radio metadata and wire protocol are separate protocol entries - Radio metadata (SNR) from the JSON-to-pcapng converter, filterable via `meshcore_radio.snr_raw` - Display filter support (e.g. `meshcore.payload_type == 4` for ADVERTs) ## Installation ### Linux mkdir -p ~/.local/lib/wireshark/plugins cp meshcore_dissector.lua ~/.local/lib/wireshark/plugins/ ### macOS mkdir -p ~/.local/lib/wireshark/plugins cp meshcore_dissector.lua ~/.local/lib/wireshark/plugins/ Alternatively, if you installed Wireshark as an app bundle: mkdir -p "$HOME/.config/wireshark/plugins" cp meshcore_dissector.lua "$HOME/.config/wireshark/plugins/" ### Windows Copy `meshcore_dissector.lua` to: %APPDATA%\Wireshark\plugins\ Typically this is `C:\Users\\AppData\Roaming\Wireshark\plugins\`. Create the `plugins` folder if it doesn't exist. ### Verifying the plugin directory In Wireshark, go to **Help > About Wireshark > Folders** to see the "Personal Plugins" path for your system. You can also find it via **Edit > Preferences > Appearance > Folders**. ### Reloading After copying the file, restart Wireshark or press **Ctrl+Shift+L** (Analyze > Reload Lua Plugins) to load the dissector without restarting. ## Getting a capture ### From the companion app In the official MeshCore Companion app, go to **Tools > Rx Log > Save Log** to export a JSON radio log from your node. This JSON file can be converted to pcapng for analysis in Wireshark. ### Sample capture A sample pcapng capture is included at [`docs/samples/sample_1.pcapng`](docs/samples/sample_1.pcapng) so you can try the dissector without needing a radio. ## Converting JSON logs to pcapng The converter requires Python 3.6+ with no external dependencies. python3 meshcore_json2pcap.py capture.json This writes `capture.pcapng` in the same directory. You can also specify an output path: python3 meshcore_json2pcap.py capture.json output.pcapng ### Input format The JSON input is an array of packet objects: [ { "timestamp": 1773364730762, "snr": 8.25, "packet": "150220ee81fbaf4d..." } ] | Field | Type | Description | |-------|------|-------------| | `timestamp` | integer | Milliseconds since Unix epoch | | `snr` | float | Signal-to-noise ratio in dB | | `packet` | string | Hex-encoded raw packet bytes | ### Radio metadata header By default, a radio metadata header is prepended to each packet in the pcapng. The format follows the same extensibility pattern as 802.11 Radiotap: a fixed version/length prefix followed by fields. The `length` field tells the dissector how many bytes to skip, so older dissectors can still find the start of the wire protocol even if new fields are added in a future version. | Bytes | Type | Description | |-------|------|-------------| | 0 | uint8 | Version (currently 0) | | 1 | uint8 | Pad (0) | | 2-3 | uint16 LE | Header length in bytes (currently 6) | | 4-5 | int16 LE | SNR * 100 (e.g. 8.25 dB = 825) | The dissector uses a two-layer architecture (like 802.11 Radiotap + 802.11) where the radio metadata and the MeshCore wire protocol appear as separate protocol entries in Wireshark's packet tree. This makes it clear which bytes are capture metadata vs. actual over-the-air data. ### Link-layer type (LINKTYPE) Pcapng files produced by `meshcore_json2pcap.py` use **DLT_USER0 (147)**, a link-layer type from the "user-defined" range reserved for private use. **This value is temporary and will change.** DLT_USER0-USER15 is a 16-slot global namespace shared by every custom protocol tool. If two unrelated dissector plugins claim the same slot, the last one loaded wins silently -- there is no conflict detection. We plan to register an official LINKTYPE with [tcpdump.org](https://www.tcpdump.org/linktypes.html) to eliminate this risk. When that happens, the assigned value will replace DLT_USER0 in both the converter and the dissector. **Existing pcapng files will need to be regenerated from the original JSON logs.** ## Usage in Wireshark Open a converted pcapng file. Packets are automatically decoded as MeshCore. The dissector provides protocol tree dissection and display filters. Wireshark's built-in I/O graphs can visualize mesh traffic over time: ![Wireshark I/O graph showing MeshCore throughput over time](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/59de4eae80022354.png) ### Display filters | Filter | Description | |--------|-------------| | `meshcore.payload_type == 4` | ADVERT packets | | `meshcore.payload_type == 5` | GRP_TXT packets | | `meshcore.route_type == 1` | FLOOD routed packets | | `meshcore.advert.name contains "Repeater"` | ADVERTs with "Repeater" in node name | | `meshcore.channel_hash == 0x81` | Group messages on a specific channel | | `meshcore_radio.snr_raw > 0` | Packets with positive SNR | ### Command-line with tshark tshark -X lua_script:meshcore_dissector.lua -r capture.pcapng tshark -X lua_script:meshcore_dissector.lua -r capture.pcapng -Y "meshcore.payload_type == 4" -V ## Running tests Tests require Python 3.6+ and pytest. Dissector integration tests additionally require tshark (they are skipped if tshark is not available). pip install pytest python3 -m pytest tests/ -v ## License GPLv2 -- see [LICENSE](LICENSE) for the full text. This license is compatible with the main [Wireshark project](https://www.wireshark.org/).