You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lua-zmq/ws/dissector.lua

307 lines
8.4 KiB
Lua

-- Copyright (c) 2010, Robert G. Jakabosky <bobby@sharedrealm.com> All rights reserved.
-- cache globals to local for speed.
local format=string.format
local tostring=tostring
local tonumber=tonumber
local sqrt=math.sqrt
local pairs=pairs
-- wireshark API globals
local Pref = Pref
local Proto = Proto
local ProtoField = ProtoField
local DissectorTable = DissectorTable
local ByteArray = ByteArray
local PI_MALFORMED = PI_MALFORMED
local PI_ERROR = PI_ERROR
-- zmq protocol example
-- declare our protocol
local zmq_proto = Proto("zmq","ZMQ","ZeroMQ Protocol")
-- setup preferences
zmq_proto.prefs["tcp_port_start"] =
Pref.string("TCP port range start", "5555", "First TCP port to decode as this protocol")
zmq_proto.prefs["tcp_port_end"] =
Pref.string("TCP port range end", "5555", "Last TCP port to decode as this protocol")
-- current preferences settings.
local current_settings = {
tcp_port_start = -1,
tcp_port_end = -1,
}
-- setup protocol fields.
zmq_proto.fields = {}
local fds = zmq_proto.fields
fds.frame = ProtoField.new("Frame", "zmq.frame", "FT_BYTES", nil, "BASE_NONE")
fds.length = ProtoField.new("Frame Length", "zmq.frame.len", "FT_UINT64", nil, "BASE_DEC")
fds.length8 = ProtoField.new("Frame 8bit Length", "zmq.frame.len8", "FT_UINT8", nil, "BASE_DEC")
fds.length64 = ProtoField.new("Frame 64bit Length", "zmq.frame.len64", "FT_UINT64", nil, "BASE_DEC")
fds.flags = ProtoField.new("Frame Flags", "zmq.frame.flags", "FT_UINT8", nil, "BASE_HEX", "0xFF")
fds.flags_more = ProtoField.new("More", "zmq.frame.flags.more", "FT_UINT8", nil, "BASE_HEX", "0x01")
fds.body = ProtoField.new("Frame body", "zmq.frame.body", "FT_BYTES", nil, "BASE_NONE")
-- un-register zmq to handle tcp port range
local function unregister_tcp_port_range(start_port, end_port)
if not start_port or start_port <= 0 or not end_port or end_port <= 0 then
return
end
local tcp_port_table = DissectorTable.get("tcp.port")
for port = start_port,end_port do
tcp_port_table:remove(port,zmq_proto)
end
end
-- register zmq to handle tcp port range
local function register_tcp_port_range(start_port, end_port)
if not start_port or start_port <= 0 or not end_port or end_port <= 0 then
return
end
local tcp_port_table = DissectorTable.get("tcp.port")
for port = start_port,end_port do
tcp_port_table:add(port,zmq_proto)
end
end
-- handle preferences changes.
function zmq_proto.init(arg1, arg2)
local old_start, old_end
local new_start, new_end
-- check if preferences have changed.
for pref_name,old_v in pairs(current_settings) do
local new_v = zmq_proto.prefs[pref_name]
if new_v ~= old_v then
if pref_name == "tcp_port_start" then
old_start = old_v
new_start = new_v
elseif pref_name == "tcp_port_end" then
old_end = old_v
new_end = new_v
end
-- save new value.
current_settings[pref_name] = new_v
end
end
-- un-register old port range
if old_start and old_end then
unregister_tcp_port_range(tonumber(old_start), tonumber(old_end))
end
-- register new port range.
if new_start and new_end then
register_tcp_port_range(tonumber(new_start), tonumber(new_end))
end
end
-- parse flag bits.
local BITS = {
MORE = 0x01,
RESERVED = 0x7E,
}
local flag_names = {"MORE"}
local bits_lookup = {}
local bits_list = {
{},
{MORE = true},
{MORE = true, RESERVED = true},
{RESERVED = true},
}
local function parse_flags(flags)
return bits_lookup[flags] or bits_lookup[1]
end
-- make bits object
local function make_bits(bits)
local proxy = newproxy(true)
local meta = getmetatable(proxy)
meta.__index = bits
meta.__tostring = function()
return bits.flags
end
-- combind bits into string description.
local flags = nil
for i=1,#flag_names do
local name = flag_names[i]
if bits[name] then
if flags then
flags = flags .. ',' .. name
else
flags = name
end
end
end
-- combind bits into one byte value.
local byte = 0x00
for k,v in pairs(bits) do
local bit = assert(BITS[k], "Invalid bit name.")
byte = byte + BITS[k]
end
bits.flags = flags or ''
bits.byte = byte
return proxy
end
-- make bits objects in bis_lookup
for i=1,#bits_list do
local bits = bits_list[i]
bits = make_bits(bits)
bits_lookup[bits.byte] = bits
end
local function zmq_dissect_frame(buffer, pinfo, frame_tree, tap)
local rang,offset
-- Frame length
offset = 0
local len_off = offset
local len8_rang = buffer(offset,1)
local len_rang = len8_rang
local frame_len = len8_rang:uint()
-- 8bit length field
local ti = frame_tree:add(fds.length8, len8_rang)
ti:set_hidden()
offset = offset + 1
if frame_len == 255 then
local len64_rang = buffer(offset, 8)
len_rang = buffer(len_off, 9)
frame_len = tonumber(tostring(len64_rang:uint64()))
-- 64bit length field.
local ti = frame_tree:add(fds.length64, len64_rang)
ti:set_hidden()
offset = offset + 8
local ti = frame_tree:add(fds.length, len_rang)
ti:set_text(format("Frame Length: %d", frame_length))
else
frame_tree:add(fds.length, len_rang)
end
-- Frame flags
rang = buffer(offset,1)
local flags = rang:uint()
local flags_bits = parse_flags(flags)
local flags_list = flags_bits.flags
local flags_tree = frame_tree:add(fds.flags, rang)
flags_tree:set_text(format('Flags: 0x%02X (%s)', flags, flags_list))
flags_tree:add(fds.flags_more, rang)
offset = offset + 1
if flags_bits.MORE then
tap.more = tap.more + 1
else
-- if the 'more' flag is not set then this is the last frame in a message.
tap.msgs = tap.msgs + 1
end
-- Frame body
local body_len = frame_len - 1
local body = ''
if body_len > 0 then
tap.body_bytes = tap.body_bytes + body_len
rang = buffer(offset, body_len)
local ti = frame_tree:add_le(fds.body, rang)
if body_len <= 4 then
body = format("%08x", rang:uint())
else
body = tostring(rang)
end
ti:set_text(format("%s", body))
end
offset = offset + body_len
-- frame summary
if body_len > 0 then
if flags_bits.MORE then
frame_tree:set_text(format("Frame: [MORE] Body[%u]=%s", body_len, body))
else
frame_tree:set_text(format("Frame: Body[%u]=%s", body_len, body))
end
else
if flags_bits.MORE then
frame_tree:set_text(format("Frame: [MORE] No data"))
else
frame_tree:set_text(format("Frame: No data"))
end
end
end
local DESEGMENT_ONE_MORE_SEGMENT = 0x0fffffff
local DESEGMENT_UNTIL_FIN = 0x0ffffffe
-- packet dissector
function zmq_proto.dissector(tvb,pinfo,tree)
local offset = 0
local tvb_length = tvb:len()
local reported_length = tvb:reported_len()
local length_remaining
local zmq_tree
local rang
local frames = 0
local tap = {}
tap.frames = 0
tap.msgs = 0
tap.more = 0
tap.body_bytes = 0
while(offset < reported_length and offset < tvb_length) do
length_remaining = tvb_length - offset
-- check for fixed part of PDU
if length_remaining < 2 then
pinfo.desegment_offset = offset
pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
break
end
-- decode frame length
-- decode single byte frame length
rang = tvb(offset, 1)
local frame_len = rang:le_uint()
local pdu_len = frame_len + 1
if frame_len == 255 then
-- make sure there is enough bytes
if length_remaining < 10 then
pinfo.desegment_offset = offset
pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
break
end
-- decode extra long frame length.
rang = tvb(offset + 1, 8)
frame_len = tonumber(tostring(rang:uint64()))
pdu_len = frame_len + 9
end
-- provide hints to tcp
if not pinfo.visited then
local remaining_bytes = reported_length - offset
if pdu_len > remaining_bytes then
pinfo.want_pdu_tracking = 2
pinfo.bytes_until_next_pdu = pdu_len - remaining_bytes
end
end
-- check if we need more bytes to dissect this frame.
if length_remaining < pdu_len then
pinfo.desegment_offset = offset
pinfo.desegment_len = (pdu_len - length_remaining)
break
end
-- dissect zmq frame
if not zmq_tree then
zmq_tree = tree:add(zmq_proto,tvb(),"ZMQ frames")
end
rang = tvb(offset, pdu_len)
local frame_tree = zmq_tree:add(fds.frame, rang)
zmq_dissect_frame(rang:tvb(), pinfo, frame_tree, tap)
frames = frames + 1
-- step to next frame.
local offset_before = offset
offset = offset + pdu_len
if offset < offset_before then break end
end
if zmq_tree then
zmq_tree:set_text(format("ZMQ frames=%u", frames))
end
if frames > 0 then
tap.frames = frames
pinfo.tap_data = tap
end
-- Info column
pinfo.cols.protocol = "ZMQ"
pinfo.cols.info = format('ZMQ frames=%u',frames)
end
-- register zmq to handle tcp ports 5550-5560
register_tcp_port_range(5550,5560)