1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
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
|