This is a static archive of our old Q&A Site. Please post any new questions and answers at ask.wireshark.org.

lua help: private header

0

Hi,

I am new to Lua. I need to parse packets like below

ETHERNET HEADER + PRIVATE HEADER (14 bytes) + ETHERNET HEADER + PAYLOAD

I have a lua script to parse the PRIVATE HEADER, but don't know how to parse the remaining... Can someone please help?

Here is my script.

foo_proto = Proto("foo","foo Protocol")
function foo_proto.dissector(buffer,pinfo,tree)
    pinfo.cols.protocol = "foo"
    local subtree = tree:add(foo_proto,buffer(),"foo Header")
    subtree:add(buffer(0,2),"index: " .. buffer(0,2):uint())
    subtree:add(buffer(2,2),"vr:" .. buffer(2,2):uint())
    subtree:add(buffer(4,2),"cmd: " .. buffer(4,2):uint())
    subtree:add(buffer(6,4),"cmd_1: " .. buffer(6,4):uint())
    subtree:add(buffer(10,4),"cmd_2: " .. buffer(10,4):uint())
end
ether_table = DissectorTable.get("ethertype")
ether_table:add(2048,foo_proto)

asked 29 Jan '16, 22:18

yacare's gravatar image

yacare
216611
accept rate: 0%


One Answer:

1

What exactly is unclear in this part of Lua API description?

The first pass of eth dissector processes the first ethernet header and calls your dissector for the payload because you've hooked your dissector into the ethertype table, handing over to you the rest of the packet in the tvb parameter (referred to as buffer in your Lua foo_proto.dissector function).

Your dissector processes your private header and invokes the eth dissector, handing the rest of the packet to it in the tvb parameter, the packet info table in the pinfo parameter, and the node in the dissection tree where it should hook itself as tree - exactly the same parameters you expect to get when you're invoked yourself.

So the result would be:

...
    subtree:add(buffer(10,4),"cmd_2: " .. buffer(10,4):uint())
    Dissector.get("eth"):call(buffer:range(14):tvb(),pinfo,tree)
end
...

You may also want to define the individual proto.fields rather than just place the text names of the fields and their values into the dissection tree; if you do so, you can use the field names as display filters, e.g. foo.index == 7

To do so, you would just add the definition of the protocol fields next to your Proto definition:

foo_proto = Proto("foo","foo Protocol")
index = ProtoField.new("Index","foo.index",ftypes.UINT16)
foo_proto.fields = {index}
...

And then, you would replace

subtree:add(buffer(0,2),"index: " .. buffer(0,2):uint())

by

subtree:add(index,buffer:range(0,2))

answered 30 Jan '16, 01:38

sindy's gravatar image

sindy
6.0k4851
accept rate: 24%

edited 30 Jan '16, 06:29

Thanks Sindy. This really helps.

Running into another issue. The script works fine when the inner eth-type is not 0x0800. When the inner ethernet frame has 0x0800, it carries the normal IP payload. For instance,

dst_mac + src_mac + eth-type(0x0800) + private-header(14 bytes) + dst_mac + src_mac + eth-type(0x0800) + IP-header + IP payload

The scripts interpret the IP-header as private header. I know this is expected, but wonder any way to handle such scenario. Note that private-header is never present in the inner ethernet frames.

Thanks!

(30 Jan '16, 18:25) yacare

Well, I was hoping that the ether_table:add(2048,foo_proto) was just a bad example, not that you really misuse the ethertype value assigned to IP for your private header. Have you given a thought to what happens if, by mistake, someone connects your equipment sending such packets to a network which interprets this ethertype the normal way?

From the point of view of Lua dissector it is not that complex, though. As you have told the eth dissector to invoke your one when(ever) it finds ethertype 2048, it is then your dissector's responsibility to choose between "foo" and "ip" dissection, through keeping track about how many times it has already been invoked on that particular frame. You also have to bear in mind that the same frame may get dissected several times in a row.

So your code might look as follows:

foo_proto = Proto("foo","foo Protocol")
index = ProtoField.new("Index","foo.index",ftypes.UINT16)
...
foo_proto.fields = {index, ... }

local dis_eth = Dissector.get("eth") – fetch the pointer just once, – during initialization local depth = 0 local frame_num = 0 – frames are counted from 1 so – 0 will be different from any – real frame number

function foo_proto.dissector(buffer,pinfo,tree) if pinfo.number ~= frame_num then – accessing this frame – for the first time depth = 0 frame_num = pinfo.number end depth = depth + 1 – track the number of recursions if depth > 1 then dis_orig:call(buffer,pinfo,tree) else pinfo.cols.protocol = "foo" local subtree = tree:add(foo_proto,buffer(),"foo Header") subtree:add(index,buffer:range(0,2)) … dis_eth:call(buffer:range(14):tvb(),pinfo,tree) end depth = depth - 1 – track the number of recursions end

ether_table = DissectorTable.get("ethertype") dis_orig = ether_table:get_dissector(2048) – save the pointer to – the original dissector ether_table:add(2048,foo_proto)

(31 Jan ‘16, 00:54) sindy

Another method to stop the recursion might be to check some values in the pinfo argument, although I’m not sure what’s exposed to Lua, e.g. the curr_layer_num member of pinfo shows how “deep” in the packet tree you are.

(31 Jan ‘16, 01:08) grahamb ♦

Also, the line pinfo.cols.protocol = “foo”, if used alone, only has a practical effect on the Protocol column in the packet list if foo is the highest layer available in the frame. The reason is that normally each dissector sets this column to its own value - the goal is that always only the highest-layer protocol is shown. This is usually not an issue because by using foo in your display filter, you can visualize only packets which do contain the “foo” layer.

If you would really insist to see “foo” in the Protocol column although there is more data in the frame after “foo”, you would have to use

pinfo.cols.protocol:fence()

after setting the value. However, it does not prevent the subsequent dissectors from adding their own texts; it only keeps your one in front of them.

(31 Jan ‘16, 01:18) sindy

@grahamb, as pinfo.curr_layer_num is currently missing in the list of pinfo fields made available to Lua, one might wonder whether it has actually been?

A quick test on 2.0.1 proves that your doubt was justified as it hasn’t:

tree:add("Layer: " .. pinfo.curr_layer_num)

yields

Lua Error: [string “C:\Users\JohnDoe\AppData\Roaming\Wireshark\p…"]:15: No such ‘curr_layer_num’ getter attribute/field for object type ‘Pinfo’

Also, just to make it 100% clear if someone adds the wrapper for Lua in future, does this pinfo attribute indicate a “layer” in terms of how many dissectors have already done their job in the current run over the frame, or “layer” in terms of the OSI model?

(31 Jan ‘16, 01:39) sindy

It’s the number of times a dissector has indicated it will try to handle the data in the packet. However, dissectors can explicitly ask for the count not to be incremented via the add_pro_name parameter to dissector_try_uint_new() and dissector_try_guid_new().

The value should track the number of entries in the pinfo->layers list, however if the dissector didn’t handle any data in the packet its name is removed from the layers list, but the count isn’t decremented. This looks like a bug to me.

The comment for the commit (319bf245) that added curr_layer_num states:

Add curr_layer_num which can be used to keep track of multiple occurances of the same protocol in a frame.
(31 Jan ‘16, 06:42) grahamb ♦

Thanks Sindy and Grahamb.

I modified my script as below. I want the foo_proto.dissector interpret the foo header only on the outer ethernet frame. However, I got an error saying “attempt to index global ‘dis_orig’ (a nil value)” at line 30 (that is “dis_orig:call(buffer,pinfo,tree)"). Somehow, we can’t get the dissector for 2048 from ether_table….

foo_proto = Proto("foo","foo Protocol")
local Command = {
[0] = "CMD_A",
[1] = "CMD_B",
}

local f_ifindex = ProtoField.uint16("foo.ifindex", "IFINDEX", base.DEC) local f_vrf = ProtoField.uint16("foo.vrf", "Vrf", base.DEC) local f_cmd = ProtoField.uint16("foo.cmd", "Cmd", base.HEX,Command) local f_cmd_p1 = ProtoField.uint32("foo.cmd_param1", "Cmd_Param1", base.DEC_HEX) local f_cmd_p2 = ProtoField.uint32("foo.cmd_param1", "Cmd_Param2", base.DEC_HEX)

foo_proto.fields = { f_ifindex, f_vrf, f_cmd, f_cmd_p1, f_cmd_p2 }

local dis_eth = Dissector.get("eth") local frame_num = 0

function foo_proto.dissector(buffer,pinfo,tree) pinfo.cols.protocol = "foo" if pinfo.number ~= frame_num then frame_num = pinfo.number local subtree = tree:add(foo_proto,buffer(0,14),"foo Header") subtree:add(f_ifindex,buffer:range(0,2)) subtree:add(f_vrf,buffer:range(2,2)) subtree:add(f_cmd,buffer:range(4,2)) subtree:add(f_cmd_p1,buffer:range(6,4)) subtree:add(f_cmd_p2,buffer:range(10,4)) dis_eth:call(buffer:range(14):tvb(),pinfo,tree) else dis_orig:call(buffer,pinfo,tree) end end

– load the dissector only when ethernet type is 0x0800 ether_table = DissectorTable.get("ethertype") dis_orig = ether_table:get_dissector(2048) ether_table:add(2048, foo_proto)

(31 Jan ‘16, 18:06) yacare

Changed to dis_orig = ether_table:get_dissector(0x0800). Still no luck. As a test, 0x0806 (ARP) made the error go away… dis_orig = ether_table:get_dissector(0x0806)

I have to use following and it works!

dis_orig = Dissector.get(“ip”)

(31 Jan ‘16, 18:46) yacare

0x0806 (ARP) made the error go away

Of course it did, because the error is reported when the script attempts to use the value, not when it fetches it. So unless there was an ARP payload in the inner Ethernet, the script never needed to use the pointer. So this provides a useful debug information only if you do have packets with ARP payload.

I have to use dis_orig = Dissector.get(“ip”) and it works

The reason why I’ve suggested to mine the dissector from the ethertype table rather than directly choose the “ip” one from the dissector table was that this is a Q&A site and someone else reading the answer may not want to replace/augment the dissection of IP but of some other protocol. So my audacious guess (because it does work for me the way I’ve suggested it) is that you have either disabled dissection of IP at some moment in the past, i.e. removed the row from the ethertype table (try to open a capture file of regular traffic to check that) or that you have two versions of Lua script in place, one of which destroys the pointer before the second one can access it.

The last point: I haven’t introduced the recursion tracking counter just for fun. Try to double-click the packet in the packet list pane, so that it opens in a separate window. In such case, the same packet is dissected twice in a row, so without the recursion counter, the second pass of the dissector uses ip dissection also on the private header. You cannot anticipate in what other use scenario the same packet may be dissected twice in a row, so even if you never open packets in separate windows, you should still stay on the safe side.

(31 Jan ‘16, 22:48) sindy

Thanks Sindy. Understand your last point now. It makes perfect sense. Just modified my script accordingly.

(01 Feb ‘16, 08:29) yacare
showing 5 of 10 show 5 more comments