A Wireshark protocol dissector and pcapng converter for MeshCore, a LoRa mesh networking protocol.
meshcore_dissector.lua-- Wireshark Lua dissector that decodes MeshCore packets with full protocol tree, display filters, and info column summaries (download)meshcore_json2pcap.py-- Python script that converts JSON radio logs to pcapng files (download)
- 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 == 4for ADVERTs)
The dissector is a single Lua file -- no compilation required. Copy meshcore_dissector.lua to your Wireshark personal plugins folder and it auto-loads on startup.
mkdir -p ~/.local/lib/wireshark/plugins
cp meshcore_dissector.lua ~/.local/lib/wireshark/plugins/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/"Copy meshcore_dissector.lua to:
%APPDATA%\Wireshark\plugins\
Typically this is C:\Users\<YourName>\AppData\Roaming\Wireshark\plugins\. Create the plugins folder if it doesn't exist.
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.
After copying the file, restart Wireshark or press Ctrl+Shift+L (Analyze > Reload Lua Plugins) to load the dissector without restarting.
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.
A sample pcapng capture is included at docs/samples/sample_1.pcapng so you can try the dissector without needing a radio.
The converter requires Python 3.6+ with no external dependencies.
python3 meshcore_json2pcap.py capture.jsonThis writes capture.pcapng in the same directory. You can also specify an output path:
python3 meshcore_json2pcap.py capture.json output.pcapngThe 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 |
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.
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 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.
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:
| 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 |
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" -VTests 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/ -vGPLv2 -- see LICENSE for the full text. This license is compatible with the main Wireshark project.

