Ask Your Question

Protobuf dissector with nested structures

asked 2023-06-20 12:01:42 +0000

Tim gravatar image

updated 2023-07-02 20:43:43 +0000

Chuckc gravatar image

We have our own protocol for communication between devices. It is based on protobuf structures over websocket.

PacketMessage.proto :

syntax = "proto3"; package pb_pm;

enum BodyType { BodyType_UNSPECIFIED = 0; RESPONSE = 1; HANDSHAKE = 2; V2X = 3; RTK = 4; SYSTEM = 5; }

enum BodyCompressType { BodyCompressType_UNSPECIFIED = 0; NONE = 1; LZMA = 2; }

message PacketMessage {

optional uint32 messageId = 1; /** [0 - 2^32] */

reserved 2;

BodyCompressType bodyCompressType = 3;

BodyType bodyType = 4;

bytes body = 5;


handshake.proto :

syntax = "proto3";

package pb_hs;

message Handshake { fixed32 protocolVersion = 1; string hostname = 2; string mac = 3; }


syntax = "proto3";

package pb_v2x;

import "V2X/Facility/Facility.proto";

import "V2X/Fluent/Fluent.proto";

message V2X { reserved 1 to 2, 5; oneof v2x { fac.Facility facility = 3; fl.Fluent fluent = 4; } }

I wrote some lua dissectors, and it works fine for one type of body field:

    local protobuf_dissector = Dissector.get("protobuf")
    local proto = Proto("tedix", "TEDIX Protocol")
    proto.dissector = function(tvb, pinfo, tree)
        local subtree = tree:add(proto, tvb())
        if "pb_pm.PacketMessage" ~= nil then
            pinfo.private["pb_msg_type"] = "message," .. "pb_pm.PacketMessage"
        pcall(, protobuf_dissector, tvb, pinfo, subtree)
    local ws_dissector_table = DissectorTable.get("ws.port")
    ws_dissector_table:add(7246, proto)
    local protobuf_field_table = DissectorTable.get("protobuf_field") 
    local message_type_dissector = Dissector.get("protobuf")
    proto_messge_type_V2X = Proto("message_type_dissector", "V2X")
    proto_messge_type_V2X.dissector = function(tvb, pinfo, subtree)
        pinfo.cols.protocol =
        local subsubtree = subtree:add(proto_messge_type_V2X, tvb())
        if "pb_v2x.V2X" ~= nil then
            pinfo.private["pb_msg_type"] = "message," .. "pb_v2x.V2X"
        pcall(, message_type_dissector, tvb, pinfo, subsubtree)
    protobuf_field_table:add("pb_pm.PacketMessage.body", proto_messge_type_V2X) 

The protocol consists of several nested proto structures. Moreover, it is implied that, depending on the BodyType field, there will be different filling of the body field. for example, there may be a message like V2X or Handshake.

How can I write a dissector for such a protocol?

How to get the value of the BodyType field inside the Lua dissector to call different dissectors of the body field?

edit retag flag offensive close merge delete


Do you have a sample pcap (and .proto file) you could share?

Chuckc gravatar imageChuckc ( 2023-06-24 16:00:01 +0000 )edit

Hello, @Chuckc. Yes. Here. In archive there are pcap file with packages with bodyType = HANDSHAKE , and bodyType=V2X . Proto structures, screenshots, and some Lua dissectors.

Tim gravatar imageTim ( 2023-06-27 07:29:59 +0000 )edit

What version of Wireshark are you using? The sample capture included in the archive opens as websocket?

Chuckc gravatar imageChuckc ( 2023-06-28 21:05:20 +0000 )edit

Wireshark Version 4.0.5 (v4.0.5-0-ge556162d8da3).

I tried to open it, and I see that wireshark recognized the packets only as tcp. Although when I saved these packages, they looked like a websocket.

I noticed this problem earlier. In order for wireshark to recognize packets as websocket, it is necessary that it be launched in real time , before the client starts communicating with the server. It transmits http Switching Protocols packet with Upgrade connection. And then wireshark recognise packets as websocket packets.

Wireshark cannot correctly process this packet in the saved pcap file.

Tim gravatar imageTim ( 2023-06-29 12:47:27 +0000 )edit

Sorry for the delay - I missed your update.
It does not open in 4.0.6 but tested today with a dev build (Version 4.1.0rc0-3005-g44a3271adb89) and dissection works.

Chuckc gravatar imageChuckc ( 2023-07-01 19:02:32 +0000 )edit

1 Answer

Sort by ยป oldest newest most voted

answered 2023-07-02 17:09:06 +0000

Chuckc gravatar image

Not pretty but does it work for your captures with more bodyTypes?

message_type_all.lua (replaces message_type_v2x.lua)

    local protobuf_field_table = DissectorTable.get("protobuf_field") 
    local message_type_dissector = Dissector.get("protobuf")

    protobuf_names_f ="")
    protobuf_values_f ="protobuf.field.value")

    proto_message_type = Proto("message_type_dissector", "All types")
    proto_message_type.dissector = function(tvb, pinfo, subtree)

        local bodytypes = {
            ["00"] = "BodyType_UNSPECIFIED",
            ["01"] = "pb_resp.Response",
            ["02"] = "pb_hs.Handshake",
            ["03"] = "pb_v2x.V2X",
            ["04"] = "pb_rtk.RTK",
            ["05"] = "pb_sys.System"

        finfo_names = { protobuf_names_f() }
        finfo_values = { protobuf_values_f() }

        if (#finfo_names > 0) then
            for k, v in pairs(finfo_names) do
                -- process data and add results to the tree
                if string.format("%s", v) == "bodyType" then
                    pinfo.cols.protocol =
                    local subsubtree = subtree:add(proto_message_type, tvb())
                    pinfo.private["pb_msg_type"] = "message," .. string.format("%s", bodytypes[finfo_values[k].display])

                    pcall(, message_type_dissector, tvb, pinfo, subsubtree)

    protobuf_field_table:add("pb_pm.PacketMessage.body", proto_message_type) 

edit flag offensive delete link more


I'm having a similar problem, and this solution, while closer, only works if the bodyType field is before the body field in the protobuf.

j3tracey gravatar imagej3tracey ( 2023-07-14 00:27:09 +0000 )edit

Can you share a capture file showing the fields?

Chuckc gravatar imageChuckc ( 2023-07-14 01:56:58 +0000 )edit

This works for me, and note that the value of finfo_values[k].display or finfo_values[k].value is in hex format, can use finfo_values[k].range:int() etc to covert to the type required.

wasphin gravatar imagewasphin ( 2023-10-13 02:08:12 +0000 )edit

Thanks, It works rather good! By the way ? Could you please advice how it's better to pass the argument with port number to the lua script from the Wireshark interface? Maybe from the display filter string?
ws_dissector_table:add(7246, proto)

Tim gravatar imageTim ( 2023-10-27 13:51:40 +0000 )edit

There is get_filter() to query the display filter and "proto".init which is called when the capture is reloaded.
You could set the display filter then the user would have to reload/refresh the packet list to call the .init which would then look at the current filter. Seems a little duct tape and baling wire but might work.

Or you could add a menu item with a button that process the display filter, update the table then calls reload_packets() or redissect_packets().

Chuckc gravatar imageChuckc ( 2023-10-27 14:37:42 +0000 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools



Asked: 2023-06-20 12:01:42 +0000

Seen: 180 times

Last updated: Jul 02