Ask Your Question
0

How to create multi-layer Lua dissector?

asked 2020-11-24 23:17:41 +0000

tarhan gravatar image

updated 2020-11-25 01:46:47 +0000

cmaynard gravatar image

I have several UDP protocols with same 12-bytes header.
I have read blog: https://mika-s.github.io/wireshark/lu...
I want to create modular dissectors with one base dissector not binded to any ports. Base dissector looking like this:

    base_protocol = Proto("my_base", "My base protocol")  
    descriptor = ProtoField.uint16("my.descriptor", 'Descriptor', base.DEC)  
    data = ProtoField.none('tb.data', 'Main payload', base.HEX)  
    base_protocol.fields = { descriptor, data }  
    function base_protocol.dissector(buffer, pinfo, tree)    
      local length = buffer:len()  
      if length == 0 then return end  
      pinfo.cols.protocol = base_protocol.name  

      local subtree = tree:add(base_protocol, buffer(), "Base protocol Header")  

      subtree:add_le(descriptor, buffer(0, 2))
      subtree:add_le(data, buffer(12, length - 12))
      return 12
    end

    local udp_port = DissectorTable.get('udp.port')
    udp_port:add_for_decode_as(base_protocol)

That base dissector is not attached to any particular port.
I want to create several other "child" dissectors (each one at own UDP port) which will receive Tvb where 0 offset is 12th byte from base_protocol. Child will add own subtree IN addition to header tree from base protocol.
I have tried to attach "parent" using local my_port = DissectorTable.get('my_base') but failed. Also I need to access "parent's" field descriptor. How can I achieve this?

edit retag flag offensive close merge delete

2 Answers

Sort by ยป oldest newest most voted
0

answered 2020-12-03 22:26:18 +0000

cmaynard gravatar image

Here I provide an alternate solution that:

  • Avoids the parent needing to know the child ports
  • Avoids the backward "eth:ethertype:ip:udp:child:parent" problem

This solution relies on the ability of the parent protocol to heuristically determine if the UDP payload handed to it is for the parent dissector or not.

Parent:

local p_parent = Proto("parent", "Parent Protocol")

local child_table = DissectorTable.new("parent.port", "parent.port", ftypes.UINT32, base.DEC, p_parent)
local data_dissector = Dissector.get("data")

-- Fields
local pf = {
    field1 = ProtoField.foo("parent.field1", "Field1", base.FOO),
    field2 = ProtoField.foo("parent.field2", "Field2", base.FOO),
    ...
    fieldN = ProtoField.foo("parent.fieldN", "FieldN", base.FOO)
}
p_parent.fields = pf

-- Dissection
local function dissect_parent(tvbuf, pinfo, tree)

    if tvbuf:len() < PARENT_LENGTH then
        return false
    end

    if -- Any other parent criteria fails
        return false
    end

    -- Assume this packet is for the parent
    local parent_tree = tree:add(p_parent, tvbuf(0, PARENT_LENGTH))
    local offset = 0

    pinfo.cols.protocol:set("Parent")

    parent_tree:add(pf.field1, tvbuf(offset, FIELD1_LENGTH))
    offset = offset + FIELD1_LENGTH
    parent_tree:add(pf.length, tvbuf(offset, FIELD2_LENGTH))
    ...
    offset = offset + FIELD(N-1)_LENGTH
    parent_tree:add(pf.length, tvbuf(offset, FIELDN_LENGTH))

    -- Hand off remainder to child:
    local tvb_sub = tvbuf:range(PARENT_LENGTH, -1):tvb()
    local child

    child = child_table:get_dissector(pinfo.src_port)
    if child ~= nil then
        child(tvb_sub, pinfo, tree)
    else
        child = child_table:get_dissector(pinfo.dst_port)
        if child ~= nil then
            child(tvb_sub, pinfo, tree)
        else
            -- No child registered for this port so hand off to generic data dissector
            data_dissector:call(tvb_sub, pinfo, tree)
        end
    end
    return true
end

function p_parent.dissector(tvbuf, pinfo, tree)
    dissect_parent(tvbuf, pinfo, tree)
end

p_parent:register_heuristic("udp", dissect_parent)

end


Child1:

-- Protocol
local p_child1 = Proto("child1", "Child1 Protocol")

-- Fields
local pf = {
    field1 = ProtoField.foo("child1.field1", "Field1", base.FOO),
    field2 = ProtoField.foo("child1.field2", "Field2", base.FOO),
    ...
    fieldN = ProtoField.foo("child1.fieldN", "FieldN", base.FOO)
}
p_child1.fields = pf

function p_child1.init()
    DissectorTable.get("parent.port"):add(CHILD1_PORT, p_child1)
end

-- Dissection
function p_child1.dissector(tvbuf, pinfo, tree)

    pinfo.cols.protocol:set("Child1")

    local child1_tree = tree:add(p_child1, tvbuf(0, -1))
    local offset = 0
    child1_tree:add(pf.field1, tvbuf(offset, FIELD1_LENGTH))
    offset = offset + FIELD1_LENGTH
    child1_tree:add(pf.field2, tvbuf(offset, FIELD2_LENGTH))
    ...
    offset = offset + FIELD(N-1)_LENGTH
    child1_tree:add(pf.fieldN, tvbuf(offset, FIELDN_LENGTH))
end

And so on for Child2 ... ChildN.

edit flag offensive delete link more

Comments

Awesome. I've write parent and couple dissectors using method and it works. But only in Windows. In Linux for some reason loading order for lua scripts is not ASCII. So Wireshark tried to load child dissector before loading parent dissector. In result there is error about missing field parent.descriptor in following line: descriptor_field = Field.new('parent.descriptor') And I could not place that in init method. So I "solved" this by placing folder with my dissectors outside plugins folder and required them in correct order from init.lua file.

tarhan gravatar imagetarhan ( 2020-12-07 09:05:49 +0000 )edit
0

answered 2020-12-02 23:19:16 +0000

cmaynard gravatar image

The MongoDB Lua dissector you provided a link to doesn't behave the way your code sample does. Rather, the header.lua file simply contains header-specific dissection of the MongoDB protocol, whereas it appears you're looking to stack 2 separate protocols, those being (1) the "base" or "parent" dissector, and (2), one of a number of different "child" dissectors. Assuming that's the case, then you'll need the parent dissector to register for all known ports and then call the appropriate child dissector based on the specific port number, either through a static table lookup or through a registered table. For example:

1. Static Table:

Parent:

local data_dissector = Dissector.get("data")
local child_dissector_table = {}
local udp_ports = {PORT1, PORT2, ... PORTN}

local parent = Proto("Parent", "Parent Protocol")

function parent.init()
    child_dissector_table[PORT1] = Dissector.get("child1")
    child_dissector_table[PORT2] = Dissector.get("child2")
    ...
    child_dissector_table[PORTN] = Dissector.get("childN")
end

function parent.dissector(tvbuf, pinfo, tree)

    -- Dissect parent stuff here.

    -- Hand off remainder to child, like so:
    local tvb_sub = tvbuf:range(offset_to_child_data, length_of_child_data):tvb()
    if child_dissector_table[pinfo.src_port] ~= nil then
        child_dissector_table[pinfo.src_port]:call(tvb_sub, pinfo, tree)
    elseif child_dissector_table[pinfo.dst_port] ~= nil then
        child_dissector_table[pinfo.dst_port]:call(tvb_sub, pinfo, tree)
    else
        data_dissector:call(tvb_sub, pinfo, tree)
    end
end

local function register_udp_ports(ports)
    local udp_port_table = DissectorTable.get("udp.port")

    for k, v in next, ports do
        udp_port_table:add(v, parent)
    end
end

register_udp_ports(udp_ports)

2. Child Registration:

Parent:

local data_dissector = Dissector.get("data")
local port_table = DissectorTable.new("parent.port", "parent.port", ftypes.UINT16, base.DEC)
local udp_ports = {PORT1, PORT2, ... PORTN}

local parent = Proto("Parent", "Parent Protocol")

function parent.dissector(tvbuf, pinfo, tree)

    -- Dissect parent stuff here.

    -- Hand off remainder to child, like so:
    local tvb_sub = tvbuf:range(offset_to_child_data, length_of_child_data):tvb()
    local child

    child = port_table.get_dissector(pinfo.src_port)
    if child ~= nil then
        child:call(tvb_sub, pinfo, tree)
    else
        child = port_table.get_dissector(pinfo.dst_port)
        if child ~= nil then
            child:call(tvb_sub, pinfo, tree)
        else
            data_dissector:call(tvb_sub, pinfo, tree)
        end
    end
end

local function register_udp_ports(ports)
    local udp_port_table = DissectorTable.get("udp.port")

    for k, v in next, ports do
        udp_port_table:add(v, parent)
    end
end

register_udp_ports(udp_ports)

Child1:

DissectorTable.get("parent.port"):add(PORT1, child1)

Child2:

DissectorTable.get("parent.port"):add(PORT2, child2)

...

ChildN:

DissectorTable.get("parent.port"):add(PORTN, childN)

DISCLAIMER: This is all pseudocode of course, but this is the general idea.

edit flag offensive delete link more

Comments

  1. I'm sorry. Maybe I do not explain correctly. Example about MongoDB dissectors is only for idea about modular design. I understand that in that example parent need to register to all used ports. It is not I want.
  2. You understand correctly my desire that I want to create stack of "non" related protocols. Base protocol should work like IP for UDP, or UDP for protocols below. In any of child protocols I want to register to stack below base protocol like you done in second example using DissectorTable.get("parent.port"):add(PORTN, childN). But I do not want to parent know anything about ports of child protocol. I want base protocol to be another UDP. E.g. UDP dissector do not know about ports that will be used by child non related dissectors like SSDP, RTP or similar. At same time I want to access fields of base ...

(more)
tarhan gravatar imagetarhan ( 2020-12-03 19:14:59 +0000 )edit

What you want is completely backwards, with the child being handed the buffer of data and calling the parent. But that's like the UDP dissector being handed a buffer, assuming there's an IPv4 header in front of the UDP header, calling the IPv4 dissector to dissect the IPv4 header, and then finally dissecting the rest of the packet as UDP plus the UDP payload. In that case you'd get a protocol "stack" something like "eth:ethertype:udp:ip", or in the case of the parent+child example, "eth:ethertype:ip:udp:child:parent", which again, in either case, is backwards.

cmaynard gravatar imagecmaynard ( 2020-12-03 21:13:57 +0000 )edit

While I diagree with this approach, if that's really what you want to do, then maybe what you're looking for is something like:

Parent:

-- Protocol
local p_parent = Proto("parent", "Parent Protocol")

-- Fields
local pf = {
    field1 = ProtoField.foo("parent.field1", "Field1", base.FOO),
    field2 = ProtoField.foo("parent.field2", "Field2", base.FOO),
    ...
    fieldN = ProtoField.foo("parent.fieldN", "FieldN", base.FOO)
}
p_parent.fields = pf

-- Dissection
function p_parent.dissector(tvbuf, pinfo, tree)
    local parent_tree = tree:add(p_parent, tvbuf(0, PARENT_LENGTH))
    local offset = 0

    pinfo.cols.protocol:set("Parent")

    parent_tree:add(pf.field1, tvbuf(0, FIELD1_LENGTH))
    offset = offset + FIELD1_LENGTH
    parent_tree:add(pf.length, tvbuf(offset, FIELD2_LENGTH))
    ...
    offset = offset + FIELD(N-1)_LENGTH
    parent_tree:add(pf.length, tvbuf(offset, FIELDN_LENGTH))
end

Child1:

-- Protocol
local p_child1 = Proto("child1", "Child1 Protocol")

-- Fields
local pf = {
    field1 = ProtoField.foo("child1.field1", "Field1", base.FOO),
    field2 = ProtoField.foo("child1.field2", "Field2", base.FOO),
    ...
    fieldN = ProtoField.foo("child1.fieldN", "FieldN", base ...
(more)
cmaynard gravatar imagecmaynard ( 2020-12-03 21:14:15 +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

1 follower

Stats

Asked: 2020-11-24 23:17:41 +0000

Seen: 1,726 times

Last updated: Dec 03 '20