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 desegment 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
|