Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / test / lua / dissectFPM.lua
blobda52d74a5ce59a0b0a69ed3410d086872b6bc963
1 ----------------------------------------
2 --
3 -- author: Hadriel Kaplan <hadriel@128technology.com>
4 -- Copyright (c) 2015, Hadriel Kaplan
5 -- This code is in the Public Domain, or the BSD (3 clause) license
6 -- if Public Domain does not apply in your country.
7 --
8 -- Version: 1.0
9 --
10 ------------------------------------------
11 --[[
12 This code is a plugin for Wireshark, to dissect Quagga FPM Netlink
13 protocol messages over TCP.
15 This script is used for testing, so it does some odd things:
16 * it dissects the FPM in two ways, controlled by a pref setting:
17 1) using the desegment_offset/desegment_len method
18 2) using the dissect_tcp_pdus() method
19 * it removes any existing FPM dissector; there isn't one right now
20 but there likely will be in the future.
22 Wireshark has a "Netlink" protocol dissector, but it currently expects
23 to be running on a Linux cooked-mode SLL header and link type. That's
24 because Netlink has traditionally been used between the Linux kernel
25 and user-space apps. But the open-source Quagga, zebra, and the
26 commercial ZebOS routing products also send Netlink messages over TCP
27 to other processes or even outside the box, to a "Forwarding Plane Manager"
28 (FPM) that controls forwarding-plane devices (typically hardware).
30 The Netlink message is encapsulated within an FPM header, which identifies
31 an FPM message version (currently 1), the type of message it contains
32 (namely a Netlink message), and its length.
34 So we have:
35 struct fpm_msg_hdr_t
37 uint8_t version;
38 uint8_t msg_type;
39 uint16_t msg_len;
41 followed by a Netlink message.
42 ]]----------------------------------------
45 ----------------------------------------
46 -- do not modify this table
47 local debug_level = {
48 DISABLED = 0,
49 LEVEL_1 = 1,
50 LEVEL_2 = 2
53 -- set this DEBUG to debug_level.LEVEL_1 to enable printing debug_level info
54 -- set it to debug_level.LEVEL_2 to enable really verbose printing
55 -- note: this will be overridden by user's preference settings
56 local DEBUG = debug_level.LEVEL_1
58 local default_settings =
60 debug_level = DEBUG,
61 enabled = true, -- whether this dissector is enabled or not
62 port = 2620,
63 max_msg_len = 4096,
64 desegment = true, -- whether to TCP desegement or not
65 dissect_tcp = false, -- whether to use the dissect_tcp_pdus method or not
66 subdissect = true, -- whether to call sub-dissector or not
67 subdiss_type = wtap.NETLINK, -- the encap we get the subdissector for
70 local dprint = function() end
71 local dprint2 = function() end
72 local function reset_debug_level()
73 if default_settings.debug_level > debug_level.DISABLED then
74 dprint = function(...)
75 print(table.concat({"Lua:", ...}," "))
76 end
78 if default_settings.debug_level > debug_level.LEVEL_1 then
79 dprint2 = dprint
80 end
81 end
82 end
83 -- call it now
84 reset_debug_level()
87 ----------------------------------------
88 -- creates a Proto object, but doesn't register it yet
89 local fpmProto = Proto("fpm", "FPM Header")
92 ----------------------------------------
93 -- a function to convert tables of enumerated types to valstring tables
94 -- i.e., from { "name" = number } to { number = "name" }
95 local function makeValString(enumTable)
96 local t = {}
97 for name,num in pairs(enumTable) do
98 t[num] = name
99 end
100 return t
103 local MsgType = {
104 NONE = 0,
105 NETLINK = 1,
107 local msgtype_valstr = makeValString(MsgType)
110 ----------------------------------------
111 -- a table of all of our Protocol's fields
112 local hdr_fields =
114 version = ProtoField.uint8 ("fpm.version", "Version", base.DEC),
115 msg_type = ProtoField.uint8 ("fpm.type", "Type", base.DEC, msgtype_valstr),
116 msg_len = ProtoField.uint16("fpm.length", "Length", base.DEC),
119 -- create a flat array table of the above that can be registered
120 local pfields = {}
122 -- recursive function to flatten the table into pfields
123 local function flattenTable(tbl)
124 for k,v in pairs(tbl) do
125 if type(v) == 'table' then
126 flattenTable(v)
127 else
128 pfields[#pfields+1] = v
132 -- call it
133 flattenTable(hdr_fields)
135 -- register them
136 fpmProto.fields = pfields
138 dprint2("fpmProto ProtoFields registered")
141 ----------------------------------------
142 -- some forward "declarations" of helper functions we use in the dissector
143 local createSLL
145 -- due to a bug in wireshark, we need to keep newly created tvb's for longer
146 -- than the duration of the dissect function
147 local tvbs = {}
149 function fpmProto.init()
150 tvbs = {}
154 local FPM_MSG_HDR_LEN = 4
156 ----------------------------------------
157 -- the following function is used for the new dissect_tcp_pdus method
158 -- this one returns the length of the full message
159 local function get_fpm_length(tvbuf, pktinfo, offset)
160 dprint2("FPM get_fpm_length function called")
161 local lengthVal = tvbuf:range(offset + 2, 2):uint()
163 if lengthVal > default_settings.max_msg_len then
164 -- too many bytes, invalid message
165 dprint("FPM message length is too long: ", lengthVal)
166 lengthVal = tvbuf:len()
169 return lengthVal
172 -- the following is the dissection function called for
173 -- the new dissect_tcp_pdus method
174 local function dissect_fpm_pdu(tvbuf, pktinfo, root)
175 dprint2("FPM dissect_fpm_pdu function called")
177 local lengthTvbr = tvbuf:range(2, 2)
178 local lengthVal = lengthTvbr:uint()
180 -- set the protocol column to show our protocol name
181 pktinfo.cols.protocol:set("FPM")
183 -- We start by adding our protocol to the dissection display tree.
184 local tree = root:add(fpmProto, tvbuf:range(offset, lengthVal))
186 local versionTvbr = tvbuf:range(0, 1)
187 local versionVal = versionTvbr:uint()
188 tree:add(hdr_fields.version, versionTvbr)
190 local msgTypeTvbr = tvbuf:range(1, 1)
191 local msgTypeVal = msgTypeTvbr:uint()
192 tree:add(hdr_fields.msg_type, msgTypeTvbr)
194 tree:add(hdr_fields.msg_len, lengthTvbr)
196 local result
197 if (versionVal == 1) and (msgTypeVal == MsgType.NETLINK) then
198 -- it carries a Netlink message, so we're going to create
199 -- a fake Linux SLL header for the built-in Netlink dissector
200 local payload = tvbuf:raw(FPM_MSG_HDR_LEN, lengthVal - FPM_MSG_HDR_LEN)
201 result = createSLL(payload)
204 -- looks good, go dissect it
205 if result then
206 -- ok now the hard part - try calling a sub-dissector?
207 -- only if settings/prefs told us to of course...
208 if default_settings.subdissect then
209 dprint2("FPM trying sub-dissector for wtap encap type:", default_settings.subdiss_type)
211 -- due to a bug in wireshark, we need to keep newly created tvb's for longer
212 -- than the duration of the dissect function
213 tvbs[#tvbs+1] = ByteArray.new(result, true):tvb("Netlink Message")
214 DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvbs[#tvbs], pktinfo, root)
216 -- local tvb = ByteArray.new(result, true):tvb("Netlink Message")
217 -- DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvb, pktinfo, root)
218 dprint2("FPM returning from sub-dissector")
220 else
221 dprint("FPM header not correctly dissected")
224 return lengthVal, 0
228 ----------------------------------------
229 -- the following function is used for dissecting using the
230 -- old desegment_offset/desegment_len method
231 -- it's a separate function because we run over TCP and thus might
232 -- need to parse multiple messages in a single segment
233 local function dissect(tvbuf, pktinfo, root, offset, origlen)
234 dprint2("FPM dissect function called")
236 local pktlen = origlen - offset
238 if pktlen < FPM_MSG_HDR_LEN then
239 -- we need more bytes
240 pktinfo.desegment_offset = offset
241 pktinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
242 return 0, DESEGMENT_ONE_MORE_SEGMENT
245 local lengthTvbr = tvbuf:range(offset + 2, 2)
246 local lengthVal = lengthTvbr:uint()
248 if lengthVal > default_settings.max_msg_len then
249 -- too many bytes, invalid message
250 dprint("FPM message length is too long: ", lengthVal)
251 return pktlen, 0
254 if pktlen < lengthVal then
255 dprint2("Need more bytes to desegment FPM")
256 pktinfo.desegment_offset = offset
257 pktinfo.desegment_len = (lengthVal - pktlen)
258 return 0, -(lengthVal - pktlen)
261 -- set the protocol column to show our protocol name
262 pktinfo.cols.protocol:set("FPM")
264 -- We start by adding our protocol to the dissection display tree.
265 local tree = root:add(fpmProto, tvbuf:range(offset, lengthVal))
267 local versionTvbr = tvbuf:range(offset, 1)
268 local versionVal = versionTvbr:uint()
269 tree:add(hdr_fields.version, versionTvbr)
271 local msgTypeTvbr = tvbuf:range(offset + 1, 1)
272 local msgTypeVal = msgTypeTvbr:uint()
273 tree:add(hdr_fields.msg_type, msgTypeTvbr)
275 tree:add(hdr_fields.msg_len, lengthTvbr)
277 local result
278 if (versionVal == 1) and (msgTypeVal == MsgType.NETLINK) then
279 -- it carries a Netlink message, so we're going to create
280 -- a fake Linux SLL header for the built-in Netlink dissector
281 local payload = tvbuf:raw(offset + FPM_MSG_HDR_LEN, lengthVal - FPM_MSG_HDR_LEN)
282 result = createSLL(payload)
285 -- looks good, go dissect it
286 if result then
287 -- ok now the hard part - try calling a sub-dissector?
288 -- only if settings/prefs told us to of course...
289 if default_settings.subdissect then
290 dprint2("FPM trying sub-dissector for wtap encap type:", default_settings.subdiss_type)
292 -- due to a bug in wireshark, we need to keep newly created tvb's for longer
293 -- than the duration of the dissect function
294 tvbs[#tvbs+1] = ByteArray.new(result, true):tvb("Netlink Message")
295 DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvbs[#tvbs], pktinfo, root)
297 -- local tvb = ByteArray.new(result, true):tvb("Netlink Message")
298 -- DissectorTable.get("wtap_encap"):try(default_settings.subdiss_type, tvb, pktinfo, root)
299 dprint2("FPM returning from sub-dissector")
301 else
302 dprint("FPM header not correctly dissected")
305 return lengthVal, 0
309 ----------------------------------------
310 -- The following creates the callback function for the dissector.
311 -- It's the same as doing "appProto.dissector = function (tvbuf,pkt,root)"
312 -- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object.
313 -- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call
314 -- this function and pass it these arguments for the packet it's dissecting.
315 function fpmProto.dissector(tvbuf, pktinfo, root)
316 dprint2("fpmProto.dissector called")
318 local bytes_consumed = 0
320 if default_settings.dissect_tcp then
321 dprint2("using new dissect_tcp_pdus method")
322 dissect_tcp_pdus(tvbuf, root, FPM_MSG_HDR_LEN, get_fpm_length, dissect_fpm_pdu, default_settings.desegment)
323 bytes_consumed = tvbuf:len()
324 else
325 dprint2("using old desegment_offset/desegment_len method")
326 -- get the length of the packet buffer (Tvb).
327 local pktlen = tvbuf:len()
328 local offset, bytes_needed = 0, 0
330 tvbs = {}
331 while bytes_consumed < pktlen do
332 offset, bytes_needed = dissect(tvbuf, pktinfo, root, bytes_consumed, pktlen)
333 if offset == 0 then
334 if bytes_consumed > 0 then
335 return bytes_consumed
336 else
337 return bytes_needed
340 bytes_consumed = bytes_consumed + offset
344 return bytes_consumed
348 ----------------------------------------
349 -- we want to have our protocol dissection invoked for a specific TCP port,
350 -- so get the TCP dissector table and add our protocol to it
351 -- first remove any existing dissector for that port, if there is one
352 local old_dissector = DissectorTable.get("tcp.port"):get_dissector(default_settings.port)
353 if old_dissector then
354 dprint("Retrieved existing dissector")
357 local function enableDissector()
358 DissectorTable.get("tcp.port"):set(default_settings.port, fpmProto)
360 -- call it now
361 enableDissector()
363 local function disableDissector()
364 if old_dissector then
365 DissectorTable.get("tcp.port"):set(default_settings.port, old_dissector)
370 --------------------------------------------------------------------------------
371 -- preferences handling stuff
372 --------------------------------------------------------------------------------
374 local debug_pref_enum = {
375 { 1, "Disabled", debug_level.DISABLED },
376 { 2, "Level 1", debug_level.LEVEL_1 },
377 { 3, "Level 2", debug_level.LEVEL_2 },
380 ----------------------------------------
381 -- register our preferences
382 fpmProto.prefs.enabled = Pref.bool("Dissector enabled", default_settings.enabled,
383 "Whether the FPM dissector is enabled or not")
386 fpmProto.prefs.desegment = Pref.bool("Reassemble FPM messages spanning multiple TCP segments",
387 default_settings.desegment,
388 "Whether the FPM dissector should reassemble"..
389 " messages spanning multiple TCP segments."..
390 " To use this option, you must also enable"..
391 " \"Allow subdissectors to reassemble TCP"..
392 " streams\" in the TCP protocol settings.")
394 fpmProto.prefs.dissect_tcp = Pref.bool("Use dissect_tcp_pdus", default_settings.dissect_tcp,
395 "Whether the FPM dissector should use the new" ..
396 " dissect_tcp_pdus model or not")
398 fpmProto.prefs.subdissect = Pref.bool("Enable sub-dissectors", default_settings.subdissect,
399 "Whether the FPM packet's content" ..
400 " should be dissected or not")
402 fpmProto.prefs.debug = Pref.enum("Debug", default_settings.debug_level,
403 "The debug printing level", debug_pref_enum)
405 ----------------------------------------
406 -- a function for handling prefs being changed
407 function fpmProto.prefs_changed()
408 dprint2("prefs_changed called")
410 default_settings.dissect_tcp = fpmProto.prefs.dissect_tcp
412 default_settings.subdissect = fpmProto.prefs.subdissect
414 default_settings.debug_level = fpmProto.prefs.debug
415 reset_debug_level()
417 if default_settings.enabled ~= fpmProto.prefs.enabled then
418 default_settings.enabled = fpmProto.prefs.enabled
419 if default_settings.enabled then
420 enableDissector()
421 else
422 disableDissector()
424 -- have to reload the capture file for this type of change
425 reload()
430 dprint2("pcapfile Prefs registered")
433 ----------------------------------------
434 -- the hatype field of the SLL must be 824 decimal, in big-endian encoding (0x0338)
435 local ARPHRD_NETLINK = "\003\056"
436 local WS_NETLINK_ROUTE = "\000\000"
437 local function emptyBytes(num)
438 return string.rep("\000", num)
441 createSLL = function (payload)
442 dprint2("FPM createSLL function called")
443 local sllmsg =
445 emptyBytes(2), -- Unused 2B
446 ARPHRD_NETLINK, -- netlink type
447 emptyBytes(10), -- Unused 10B
448 WS_NETLINK_ROUTE, -- Route type
449 payload -- the Netlink message
451 return table.concat(sllmsg)