1 ----------------------------------------
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.
10 ------------------------------------------
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.
41 followed by a Netlink message.
42 ]]----------------------------------------
45 ----------------------------------------
46 -- do not modify this table
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
=
61 enabled
= true, -- whether this dissector is enabled or not
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:", ...}," "))
78 if default_settings
.debug_level
> debug_level
.LEVEL_1
then
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
)
97 for name
,num
in pairs(enumTable
) do
107 local msgtype_valstr
= makeValString(MsgType
)
110 ----------------------------------------
111 -- a table of all of our Protocol's 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
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
128 pfields
[#pfields
+1] = v
133 flattenTable(hdr_fields
)
136 fpmProto
.fields
= pfields
138 dprint2("fpmProto ProtoFields registered")
141 ----------------------------------------
142 -- some forward "declarations" of helper functions we use in the dissector
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
149 function fpmProto
.init()
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()
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
)
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
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")
221 dprint("FPM header not correctly dissected")
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
)
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
)
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
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")
302 dprint("FPM header not correctly dissected")
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()
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
331 while bytes_consumed
< pktlen
do
332 offset
, bytes_needed
= dissect(tvbuf
, pktinfo
, root
, bytes_consumed
, pktlen
)
334 if bytes_consumed
> 0 then
335 return bytes_consumed
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
)
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
417 if default_settings
.enabled
~= fpmProto
.prefs
.enabled
then
418 default_settings
.enabled
= fpmProto
.prefs
.enabled
419 if default_settings
.enabled
then
424 -- have to reload the capture file for this type of change
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")
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
)