aboutsummaryrefslogtreecommitdiffstats
path: root/test/lua/dissectFPM.lua
diff options
context:
space:
mode:
Diffstat (limited to 'test/lua/dissectFPM.lua')
-rw-r--r--test/lua/dissectFPM.lua452
1 files changed, 452 insertions, 0 deletions
diff --git a/test/lua/dissectFPM.lua b/test/lua/dissectFPM.lua
new file mode 100644
index 0000000000..da52d74a5c
--- /dev/null
+++ b/test/lua/dissectFPM.lua
@@ -0,0 +1,452 @@
+----------------------------------------
+--
+-- author: Hadriel Kaplan <hadriel@128technology.com>
+-- Copyright (c) 2015, Hadriel Kaplan
+-- This code is in the Public Domain, or the BSD (3 clause) license
+-- if Public Domain does not apply in your country.
+--
+-- Version: 1.0
+--
+------------------------------------------
+--[[
+ This code is a plugin for Wireshark, to dissect Quagga FPM Netlink
+ protocol messages over TCP.
+
+ This script is used for testing, so it does some odd things:
+ * it dissects the FPM in two ways, controlled by a pref setting:
+ 1) using the desegment_offset/desegment_len method
+ 2) using the dissect_tcp_pdus() method
+ * it removes any existing FPM dissector; there isn't one right now
+ but there likely will be in the future.
+
+ Wireshark has a "Netlink" protocol dissector, but it currently expects
+ to be running on a Linux cooked-mode SLL header and link type. That's
+ because Netlink has traditionally been used between the Linux kernel
+ and user-space apps. But the open-source Quagga, zebra, and the
+ commercial ZebOS routing products also send Netlink messages over TCP
+ to other processes or even outside the box, to a "Forwarding Plane Manager"
+ (FPM) that controls forwarding-plane devices (typically hardware).
+
+ The Netlink message is encapsulated within an FPM header, which identifies
+ an FPM message version (currently 1), the type of message it contains
+ (namely a Netlink message), and its length.
+
+ So we have:
+ struct fpm_msg_hdr_t
+ {
+ uint8_t version;
+ uint8_t msg_type;
+ uint16_t msg_len;
+ }
+ followed by a Netlink message.
+]]----------------------------------------
+
+
+----------------------------------------
+-- do not modify this table
+local debug_level = {
+ DISABLED = 0,
+ LEVEL_1 = 1,
+ LEVEL_2 = 2
+}
+
+-- set this DEBUG to debug_level.LEVEL_1 to enable printing debug_level info
+-- set it to debug_level.LEVEL_2 to enable really verbose printing
+-- note: this will be overridden by user's preference settings
+local DEBUG = debug_level.LEVEL_1
+
+local default_settings =
+{
+ debug_level = DEBUG,
+ enabled = true, -- whether this dissector is enabled or not
+ port = 2620,
+ max_msg_len = 4096,
+ desegment = true, -- whether to TCP desegement or not
+ dissect_tcp = false, -- whether to use the dissect_tcp_pdus method or not
+ subdissect = true, -- whether to call sub-dissector or not
+ subdiss_type = wtap.NETLINK, -- the encap we get the subdissector for
+}
+
+local dprint = function() end
+local dprint2 = function() end
+local function reset_debug_level()
+ if default_settings.debug_level > debug_level.DISABLED then
+ dprint = function(...)
+ print(table.concat({"Lua:", ...}," "))
+ end
+
+ if default_settings.debug_level > debug_level.LEVEL_1 then
+ dprint2 = dprint
+ end
+ end
+end
+-- call it now
+reset_debug_level()
+
+
+----------------------------------------
+-- creates a Proto object, but doesn't register it yet
+local fpmProto = Proto("fpm", "FPM Header")
+
+
+----------------------------------------
+-- a function to convert tables of enumerated types to valstring tables
+-- i.e., from { "name" = number } to { number = "name" }
+local function makeValString(enumTable)
+ local t = {}
+ for name,num in pairs(enumTable) do
+ t[num] = name
+ end
+ return t
+end
+
+local MsgType = {
+ NONE = 0,
+ NETLINK = 1,
+}
+local msgtype_valstr = makeValString(MsgType)
+
+
+----------------------------------------
+-- a table of all of our Protocol's fields
+local hdr_fields =
+{
+ version = ProtoField.uint8 ("fpm.version", "Version", base.DEC),
+ msg_type = ProtoField.uint8 ("fpm.type", "Type", base.DEC, msgtype_valstr),
+ msg_len = ProtoField.uint16("fpm.length", "Length", base.DEC),
+}
+
+-- create a flat array table of the above that can be registered
+local pfields = {}
+
+-- recursive function to flatten the table into pfields
+local function flattenTable(tbl)
+ for k,v in pairs(tbl) do
+ if type(v) == 'table' then
+ flattenTable(v)
+ else
+ pfields[#pfields+1] = v
+ end
+ end
+end
+-- call it
+flattenTable(hdr_fields)
+
+-- register them
+fpmProto.fields = pfields
+
+dprint2("fpmProto ProtoFields registered")
+
+
+----------------------------------------
+-- some forward "declarations" of helper functions we use in the dissector
+local createSLL
+
+-- due to a bug in wireshark, we need to keep newly created tvb's for longer
+-- than the duration of the dissect function
+local tvbs = {}
+
+function fpmProto.init()
+ tvbs = {}
+end
+
+
+local FPM_MSG_HDR_LEN = 4
+
+----------------------------------------
+-- the following function is used for the new dissect_tcp_pdus method
+-- this one returns the length of the full message
+local function get_fpm_length(tvbuf, pktinfo, offset)
+ dprint2("FPM get_fpm_length function called")
+ local lengthVal = tvbuf:range(offset + 2, 2):uint()
+
+ if lengthVal > default_settings.max_msg_len then
+ -- too many bytes, invalid message
+ dprint("FPM message length is too long: ", lengthVal)
+ lengthVal = tvbuf:len()
+ end
+
+ return lengthVal
+end
+
+-- the following is the dissection function called for
+-- the new dissect_tcp_pdus method
+local function dissect_fpm_pdu(tvbuf, pktinfo, root)
+ dprint2("FPM dissect_fpm_pdu function called")
+
+ local lengthTvbr = tvbuf:range(2, 2)
+ local lengthVal = lengthTvbr:uint()
+
+ -- set the protocol column to show our protocol name
+ pktinfo.cols.protocol:set("FPM")
+
+ -- We start by adding our protocol to the dissection display tree.
+ local tree = root:add(fpmProto, tvbuf:range(offset, lengthVal))
+
+ local versionTvbr = tvbuf:range(0, 1)
+ local versionVal = versionTvbr:uint()
+ tree:add(hdr_fields.version, versionTvbr)
+
+ local msgTypeTvbr = tvbuf:range(1, 1)
+ local msgTypeVal = msgTypeTvbr:uint()
+ tree:add(hdr_fields.msg_type, msgTypeTvbr)
+
+ tree:add(hdr_fields.msg_len, lengthTvbr)
+
+ local result
+ if (versionVal == 1) and (msgTypeVal == MsgType.NETLINK) then
+ -- it carries a Netlink message, so we're going to create
+ -- a fake Linux SLL header for the built-in Netlink dissector
+ local payload = tvbuf:raw(FPM_MSG_HDR_LEN, lengthVal - FPM_MSG_HDR_LEN)
+ result = createSLL(payload)
+ end
+
+ -- looks good, go dissect it
+ if result then
+ -- ok now the hard part - try calling a sub-dissector?
+ -- only if settings/prefs told us to of course...
+ if default_settings.subdissect then
+ dprint2("FPM trying sub-dissector for wtap encap type:", default_settings.subdiss_type)
+
+ -- due to a bug in wireshark, we need to keep newly created tvb's for longer
+ -- than the duration of the dissect function
+ tvbs[#tvbs+1] = ByteArray.new(result, true):tvb("Netlink Message")
+ DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvbs[#tvbs], pktinfo, root)
+
+ -- local tvb = ByteArray.new(result, true):tvb("Netlink Message")
+ -- DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvb, pktinfo, root)
+ dprint2("FPM returning from sub-dissector")
+ end
+ else
+ dprint("FPM header not correctly dissected")
+ end
+
+ return lengthVal, 0
+end
+
+
+----------------------------------------
+-- the following function is used for dissecting using the
+-- old desegment_offset/desegment_len method
+-- it's a separate function because we run over TCP and thus might
+-- need to parse multiple messages in a single segment
+local function dissect(tvbuf, pktinfo, root, offset, origlen)
+ dprint2("FPM dissect function called")
+
+ local pktlen = origlen - offset
+
+ if pktlen < FPM_MSG_HDR_LEN then
+ -- we need more bytes
+ pktinfo.desegment_offset = offset
+ pktinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
+ return 0, DESEGMENT_ONE_MORE_SEGMENT
+ end
+
+ local lengthTvbr = tvbuf:range(offset + 2, 2)
+ local lengthVal = lengthTvbr:uint()
+
+ if lengthVal > default_settings.max_msg_len then
+ -- too many bytes, invalid message
+ dprint("FPM message length is too long: ", lengthVal)
+ return pktlen, 0
+ end
+
+ if pktlen < lengthVal then
+ dprint2("Need more bytes to desegment FPM")
+ pktinfo.desegment_offset = offset
+ pktinfo.desegment_len = (lengthVal - pktlen)
+ return 0, -(lengthVal - pktlen)
+ end
+
+ -- set the protocol column to show our protocol name
+ pktinfo.cols.protocol:set("FPM")
+
+ -- We start by adding our protocol to the dissection display tree.
+ local tree = root:add(fpmProto, tvbuf:range(offset, lengthVal))
+
+ local versionTvbr = tvbuf:range(offset, 1)
+ local versionVal = versionTvbr:uint()
+ tree:add(hdr_fields.version, versionTvbr)
+
+ local msgTypeTvbr = tvbuf:range(offset + 1, 1)
+ local msgTypeVal = msgTypeTvbr:uint()
+ tree:add(hdr_fields.msg_type, msgTypeTvbr)
+
+ tree:add(hdr_fields.msg_len, lengthTvbr)
+
+ local result
+ if (versionVal == 1) and (msgTypeVal == MsgType.NETLINK) then
+ -- it carries a Netlink message, so we're going to create
+ -- a fake Linux SLL header for the built-in Netlink dissector
+ local payload = tvbuf:raw(offset + FPM_MSG_HDR_LEN, lengthVal - FPM_MSG_HDR_LEN)
+ result = createSLL(payload)
+ end
+
+ -- looks good, go dissect it
+ if result then
+ -- ok now the hard part - try calling a sub-dissector?
+ -- only if settings/prefs told us to of course...
+ if default_settings.subdissect then
+ dprint2("FPM trying sub-dissector for wtap encap type:", default_settings.subdiss_type)
+
+ -- due to a bug in wireshark, we need to keep newly created tvb's for longer
+ -- than the duration of the dissect function
+ tvbs[#tvbs+1] = ByteArray.new(result, true):tvb("Netlink Message")
+ DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvbs[#tvbs], pktinfo, root)
+
+ -- local tvb = ByteArray.new(result, true):tvb("Netlink Message")
+ -- DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvb, pktinfo, root)
+ dprint2("FPM returning from sub-dissector")
+ end
+ else
+ dprint("FPM header not correctly dissected")
+ end
+
+ return lengthVal, 0
+end
+
+
+----------------------------------------
+-- The following creates the callback function for the dissector.
+-- It's the same as doing "appProto.dissector = function (tvbuf,pkt,root)"
+-- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object.
+-- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call
+-- this function and pass it these arguments for the packet it's dissecting.
+function fpmProto.dissector(tvbuf, pktinfo, root)
+ dprint2("fpmProto.dissector called")
+
+ local bytes_consumed = 0
+
+ if default_settings.dissect_tcp then
+ dprint2("using new dissect_tcp_pdus method")
+ dissect_tcp_pdus(tvbuf, root, FPM_MSG_HDR_LEN, get_fpm_length, dissect_fpm_pdu, default_settings.desegment)
+ bytes_consumed = tvbuf:len()
+ else
+ dprint2("using old desegment_offset/desegment_len method")
+ -- get the length of the packet buffer (Tvb).
+ local pktlen = tvbuf:len()
+ local offset, bytes_needed = 0, 0
+
+ tvbs = {}
+ while bytes_consumed < pktlen do
+ offset, bytes_needed = dissect(tvbuf, pktinfo, root, bytes_consumed, pktlen)
+ if offset == 0 then
+ if bytes_consumed > 0 then
+ return bytes_consumed
+ else
+ return bytes_needed
+ end
+ end
+ bytes_consumed = bytes_consumed + offset
+ end
+ end
+
+ return bytes_consumed
+end
+
+
+----------------------------------------
+-- we want to have our protocol dissection invoked for a specific TCP port,
+-- so get the TCP dissector table and add our protocol to it
+-- first remove any existing dissector for that port, if there is one
+local old_dissector = DissectorTable.get("tcp.port"):get_dissector(default_settings.port)
+if old_dissector then
+ dprint("Retrieved existing dissector")
+end
+
+local function enableDissector()
+ DissectorTable.get("tcp.port"):set(default_settings.port, fpmProto)
+end
+-- call it now
+enableDissector()
+
+local function disableDissector()
+ if old_dissector then
+ DissectorTable.get("tcp.port"):set(default_settings.port, old_dissector)
+ end
+end
+
+
+--------------------------------------------------------------------------------
+-- preferences handling stuff
+--------------------------------------------------------------------------------
+
+local debug_pref_enum = {
+ { 1, "Disabled", debug_level.DISABLED },
+ { 2, "Level 1", debug_level.LEVEL_1 },
+ { 3, "Level 2", debug_level.LEVEL_2 },
+}
+
+----------------------------------------
+-- register our preferences
+fpmProto.prefs.enabled = Pref.bool("Dissector enabled", default_settings.enabled,
+ "Whether the FPM dissector is enabled or not")
+
+
+fpmProto.prefs.desegment = Pref.bool("Reassemble FPM messages spanning multiple TCP segments",
+ default_settings.desegment,
+ "Whether the FPM dissector should reassemble"..
+ " messages spanning multiple TCP segments."..
+ " To use this option, you must also enable"..
+ " \"Allow subdissectors to reassemble TCP"..
+ " streams\" in the TCP protocol settings.")
+
+fpmProto.prefs.dissect_tcp = Pref.bool("Use dissect_tcp_pdus", default_settings.dissect_tcp,
+ "Whether the FPM dissector should use the new" ..
+ " dissect_tcp_pdus model or not")
+
+fpmProto.prefs.subdissect = Pref.bool("Enable sub-dissectors", default_settings.subdissect,
+ "Whether the FPM packet's content" ..
+ " should be dissected or not")
+
+fpmProto.prefs.debug = Pref.enum("Debug", default_settings.debug_level,
+ "The debug printing level", debug_pref_enum)
+
+----------------------------------------
+-- a function for handling prefs being changed
+function fpmProto.prefs_changed()
+ dprint2("prefs_changed called")
+
+ default_settings.dissect_tcp = fpmProto.prefs.dissect_tcp
+
+ default_settings.subdissect = fpmProto.prefs.subdissect
+
+ default_settings.debug_level = fpmProto.prefs.debug
+ reset_debug_level()
+
+ if default_settings.enabled ~= fpmProto.prefs.enabled then
+ default_settings.enabled = fpmProto.prefs.enabled
+ if default_settings.enabled then
+ enableDissector()
+ else
+ disableDissector()
+ end
+ -- have to reload the capture file for this type of change
+ reload()
+ end
+
+end
+
+dprint2("pcapfile Prefs registered")
+
+
+----------------------------------------
+-- the hatype field of the SLL must be 824 decimal, in big-endian encoding (0x0338)
+local ARPHRD_NETLINK = "\003\056"
+local WS_NETLINK_ROUTE = "\000\000"
+local function emptyBytes(num)
+ return string.rep("\000", num)
+end
+
+createSLL = function (payload)
+ dprint2("FPM createSLL function called")
+ local sllmsg =
+ {
+ emptyBytes(2), -- Unused 2B
+ ARPHRD_NETLINK, -- netlink type
+ emptyBytes(10), -- Unused 10B
+ WS_NETLINK_ROUTE, -- Route type
+ payload -- the Netlink message
+ }
+ return table.concat(sllmsg)
+end