luawiresharkwireshark-dissector

Two Wireshark Dissectors with shared fields


I am writing a wireshark dissector for a custom protocol. The protocol has two variants, which are indistinguishable in general when looking at the dump. So usually the user will simply select the correct variant to decode.

Both variants share quite a lot of ProtoFields and also a lot of structure, which is why I had hoped to write most of the dissection code only once and then have two top-level dissectors calling those components.

The rough idea is:

local custom_var1 = Proto("custom_var1", "My custom protocol Variant 1")
local custom_var2 = Proto("custom_var2", "My custom protocol Variant 2")

-- my actual header and data blocks are a lot more complex than single integers of course
local header = ProtoField.uint8("custom.head", "Header")
local data1 = ProtoField.uint64("custom.data1", "Data 1")
local data2 = ProtoField.uint32("custom.data2", "Data 2")
local data3 = ProtoField.uint8("custom.data3", "Data 3")

custom_var1.fields = {header, data1, data2}
custom_var2.fields = {header, data1, data2, data3}

local function dissect_header(tvb, tree)
    tree:add(header, tvb(0, 1))
end

local function dissect_data1(tvb, tree)
    tree:add(data1, tvb(0, 8))
end

local function dissect_data2(tvb, tree)
    tree:add(data2, tvb(0, 4))
end

local function dissect_data3(tvb, tree)
    tree:add(data3, tvb(0, 1))
end

function custom_var1.dissector(tvb, pinfo, root)
    pinfo.cols.protocol:set(custom_var1.name)
    local tree = root:add(custom_var1, tvb(0, 13))

    dissect_header(tvb(0), tree)
    dissect_data1(tvb(1), tree)
    dissect_data2(tvb(9), tree)
end

function custom_var2.dissector(tvb, pinfo, root)
    pinfo.cols.protocol:set(custom_var2.name)
    local tree = root:add(custom_var2, tvb(0, 14))

    dissect_header(tvb(0), tree)
    dissect_data2(tvb(1), tree)
    dissect_data1(tvb(5), tree)
    dissect_data3(tvb(13), tree)
end

tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(31337, custom_var1)
tcp_port:add(31337, custom_var2)

My problem: When I place the lua file into my plugin directory and start wireshark, I see a Wireshark Debug Console (with black background and not white, a with the lua console) with the message 18:08:56.505 Err LUA PANIC: fields can be registered only once followed by Press any key to exit before I can do anything else. After pressing a key, wireshark immediately exists.

How can I write two dissectors with shared fields, without too much code duplication?


Solution

  • Why not just write one dissector and add a preference for the user to choose which variant to apply? For example:

    -- Protocol
    local p_custom = Proto("custom", "My custom protocol")
    local data_dis = Dissector.get("data")
    
    -- Preferences
    local default_settings = {
        variant = 1
    }
    
    local variant_pref_enum = {
        { 1, "1", 1 },
        { 2, "2", 2 }
    }
    
    p_custom.prefs.variant = Pref.enum("Variant", default_settings.variant,
        "The variant", variant_pref_enum)
    
    -- Fields
    local pf = {
        header = ProtoField.bytes("custom.head", "Header"),
        data1 = ProtoField.uint64("custom.data1", "Data 1"),
        data2 = ProtoField.uint32("custom.data2", "Data 2"),
        data3 = ProtoField.uint8("custom.data3", "Data 3")
    }
    p_custom.fields = pf
    
    -- Dissection
    function p_custom.dissector(tvbuf, pinfo, tree)
        local custom_tree = tree:add(p_custom, tvbuf(0, -1))
    
        custom_tree:add(pf.header, tvbuf(0, 1))
        if p_custom.prefs.variant == 1 then
            -- Dissect Variant 1
            pinfo.cols.protocol:set("CUSTOM 1")
            custom_tree:append_text(": Variant 1")
            custom_tree:add(pf.data1, tvbuf(1, 8))
            custom_tree:add(pf.data2, tvbuf(9, 4))
        elseif p_custom.prefs.variant == 2 then
            -- Dissect Variant 2
            pinfo.cols.protocol:set("CUSTOM 2")
            custom_tree:append_text(": Variant 2")
            custom_tree:add(pf.data2, tvbuf(1, 4))
            custom_tree:add(pf.data1, tvbuf(5, 8))
            custom_tree:add(pf.data3, tvbuf(13, 1))
        else
            -- Unknown Variant
            pinfo.cols.protocol:set("CUSTOM ?")
            custom_tree:append_text(": Unknown Variant")
            data_dis:call(tvbuf:range(1, tvbuf:len() - 1):tvb(), pinfo, tree)
        end
    end
    
    -- Registration
    local tcp_port = DissectorTable.get("tcp.port")
    tcp_port:add(31337, p_custom)
    

    Of course, if there's information available in the common header that can help determine which variant to apply automatically, then a preference isn't needed at all.