diff --git a/ws/dissector.lua b/ws/dissector.lua new file mode 100644 index 0000000..0b70a79 --- /dev/null +++ b/ws/dissector.lua @@ -0,0 +1,306 @@ +-- Copyright (c) 2010, Robert G. Jakabosky 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) + diff --git a/ws/reg_all_taps.lua b/ws/reg_all_taps.lua new file mode 100644 index 0000000..1e958c7 --- /dev/null +++ b/ws/reg_all_taps.lua @@ -0,0 +1,4 @@ + +-- register all zmq taps. +require"zmq.ws.stats_tap" + diff --git a/ws/stats_tap.lua b/ws/stats_tap.lua new file mode 100644 index 0000000..6a0abf1 --- /dev/null +++ b/ws/stats_tap.lua @@ -0,0 +1,57 @@ + +local tap = require"zmq.ws.tap" + +local format = string.format + +local stats_tap_mt = {} +stats_tap_mt.__index = stats_tap_mt + +function stats_tap_mt:packet(pinfo, tvb, tree, data) + -- count all ZeroMQ packets + self.count = self.count + 1 + data = data or pinfo.tap_data + if not data then + return + end + -- frames + self.frames = self.frames + (data.frames or 0) + -- frames with more flag set + self.more = self.more + (data.more or 0) + -- whole messages. + self.msgs = self.msgs + (data.msgs or 0) + -- total bytes in frame bodies. + self.body_bytes = self.body_bytes + (data.body_bytes or 0) +end + +function stats_tap_mt:draw() + return format([[ +ZeroMQ Packets: %d +Frames: %d +Messages: %d +Flags: More: %d +Payload bytes: %d +]], + self.count, + self.frames, + self.msgs, + self.more, + self.body_bytes) +end + +function stats_tap_mt:reset() + self.count = 0 + self.frames = 0 + self.msgs = 0 + self.more = 0 + self.body_bytes = 0 +end + +local function create_stats_tap() + local tap = setmetatable({}, stats_tap_mt) + + tap:reset() -- initialize tap. + return tap, 'zmq', 'lua' +end + +tap("ZeroMQ stats tap", create_stats_tap) + diff --git a/ws/tap.lua b/ws/tap.lua new file mode 100644 index 0000000..57bc5b7 --- /dev/null +++ b/ws/tap.lua @@ -0,0 +1,83 @@ + +local gui_enabled = gui_enabled +local register_menu = register_menu +local MENU_TOOLS_UNSORTED = MENU_TOOLS_UNSORTED + +local win_instances = 0 + +local function create_window_tap(name, create) + win_instances = win_instances + 1 + + local td, tap_filter, tap_type = create() + + -- tap's output window. + local win = TextWindow.new(name .. " " .. win_instances) + + -- this tap will be local to the menu_function that called it + local tap = Listener.new(tap_type, tap_filter) + + -- callback to remove the tap when the text window closes + function remove_tap() + if tap and tap.remove then + tap:remove() + end + end + + -- make sure the tap doesn't hang around after the window was closed + win:set_atclose(remove_tap) + + -- this function will be called for every packet + function tap.packet(pinfo,tvb, tree, tapdata) + return td:packet(pinfo, tvb, tree, tapdata) + end + + -- this function will be called once every few seconds to redraw the window + function tap.draw() + local text = td:draw() + win:set(text) + end + + -- this function will be called at the end of the capture run. + function tap.reset() + return td:reset() + end +end + +local function create_tshark_tap(name, create) + + local td, tap_filter, tap_type = create() + + -- this tap will be local to the menu_function that called it + local tap = Listener.new(tap_type, tap_filter) + + -- this function will be called for every packet + function tap.packet(pinfo,tvb,tapdata) + return td:packet(pinfo, tvb, tapdata) + end + + -- this function will be called once every few seconds to redraw the window + function tap.draw() + local text = td:draw() + debug(name .. " results:\n" .. text) + end + + -- this function will be called at the end of the capture run. + function tap.reset() + return td:reset() + end +end + +return function (name, create) + if gui_enabled() then + -- menu callback. + local create_tap = function() + create_window_tap(name, create) + end + -- register menu item if running from wireshark + register_menu(name, create_tap, MENU_TOOLS_UNSORTED) + else + -- we are running from tshark, create a non-gui tap now. + create_tshark_tap(name, create) + end +end +