This is a static archive of our old Q&A Site. Please post any new questions and answers at ask.wireshark.org.

Wireshark STOMP protocol dissector

0

I have an app that talks to a rabbitmq server. The message exchange occurs trough the STOMP protocol. My question is why are the STOMP messages from client to server not decoded in the user interface i.e. when i right click and use the "Follow TCP Stream" menuu in the wireshark i do not see the communication from the client to the server decoded. Websocket protocol defines something called masking key which basically say that the traffic from client to server should be encoded with that random masking key value. When i click on any websocket packet in Wireshark i see two fields i.e. "Payload" and "Unmask Payload", the last one basically represent the payload after the masking key has been applied. Now the question is why I am seeing the value of the payload instead of the "Unmasked Payload" when i right click and choose "Follow TCP Stream". For decoding stomp i am using the (stomp.lua) plugin https://github.com/ficoos/wireshark-stomp-plugin

the link to an example trace file is here

link text

Basically this is the follow up question from the one i posted in the stackoverflow i.e. link text

asked 04 Jul '15, 01:07

tito's gravatar image

tito
11115
accept rate: 0%

edited 04 Jul '15, 06:57


2 Answers:

1

As @Hadriel noted in his answer on SO, the STOMP lua dissector is written to only dissect the protocol when carried directly over TCP. To modify the dissector to dissect traffic carried over WebSocket you'll have to modify the dissector's registration function at the bottom of the script to register with the WebSocket dissector instead of the TCP dissector:

--    local tcp_dissector_table = DissectorTable.get("tcp.port")
--    tcp_dissector_table:add(p_stomp.prefs["tcp_port"], p_stomp)
    local ws_dissector_table = DissectorTable.get("ws.port")
    ws_dissector_table:add(p_stomp.prefs["tcp_port"], p_stomp)

If you now set the STOMP port preference (still called TCP unless you also modify that part of the script as well) to "8080", then the traffic is correctly dissected as STOMP.

answered 04 Jul '15, 08:44

grahamb's gravatar image

grahamb ♦
19.8k330206
accept rate: 22%

Also, the "Follow TCP Stream" dialog window will continue to just show what it showed originally, because all it does is show the TCP stream contents, which the masked payload is. (well... for websocket it will also show the unmasked payload in the "Follow TCP Stream" output, because the websocket dissector is clever and sets the unmasked payload to be treated as real buffer content of the original TCP stream)

(04 Jul '15, 09:16) Hadriel

Oh, and there's a small bug in the original stomp.lua script from github: it adds the proto to the TCP port dissector table every time p_stomp.init() is invoked - but that will happen a lot (like multiple times per file lifetime), so it will keep adding the dissector to the table again and again. And it won't remove it from a previous table if you changed the port number preference.

(04 Jul '15, 09:21) Hadriel

@grahamb @Hadriel many thanks for the response. I have done the changes that you suggested and i can now see that the traffic is dissected correctlly in wireshark main window but i still have one question regarding the comment that @Hadriel made i..e "well... for websocket it will also show the unmasked payload in the "Follow TCP Stream" output," in "Follow TCP Stream" i still only see the traffic comming from the server to the client in a clear text but the traffic from client to server is still masked. Do I understand that correctly that i shell see the unmasked payload in the "Follow TCP Stream" for the traffic that client -> server in case i am running stomp over ws ?

(04 Jul '15, 09:56) tito

Hmmm... I see what you mean - I wasn't paying close attention and assumed it was showing the masked and unmasked payload of the same message, but it's not.

The actual content of the websocket payload coming back from the server to the client is not masked to begin with, and the "Follow TCP Stream" output is just showing it as it originally was.

That's probably just how "Follow TCP Stream" works - showing TCP stream content as raw bytes. I'm not sure there's any other choice with that particular feature.

If you just want to see the STOMP packets, you could use a display filter of "stomp", which will at least only display the frames that contain STOMP messages. But you'll still have to click on each one to see it. Alternatively, you could edit the Lua script to open a TextWindow and put all the STOMP message content in it.

(04 Jul '15, 10:15) Hadriel

1

For posterity's sake, below is a corrected version of the plugin with bugs fixed:

--
-- Original source: https://github.com/ficoos/wireshark-stomp-plugin
--
-- Modified to handle STOMP over HTTP/Websocket
--
-- Copyright 2009-2012 Red Hat, Inc.
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
--
-- Refer to the README and COPYING files for full details of the license
--
local p_stomp = Proto("stomp", "STOMP")

local f_command = ProtoField.string("stomp.command", "Command", FT_STRING) local f_body = ProtoField.string("stomp.body", "Body") local f_header = ProtoField.string("stomp.header", "Header") local f_header_key = ProtoField.string("stomp.header.key", "Key") local f_header_value = ProtoField.string("stomp.header.value", "Value")

p_stomp.fields = { f_command, f_body, f_header, f_header_key, f_header_value, }

local settings = { TCP_PORT = 54321, WEBSOCKET_PORT = 9000 }

p_stomp.prefs["tcp_port"] = Pref.uint( "Standards-based TCP Port", settings.TCP_PORT, "TCP Port for STOMP standards-compliant communication (0 to disable)" )

p_stomp.prefs["websocket_port"] = Pref.uint( "STOMP in Websocket for HTTP server TCP port", settings.WEBSOCKET_PORT, "The TCP server port number for STOMP in Websocket payload (0 to disable)" )

p_stomp.prefs["warning_text"] = Pref.statictext( "Warning: The Standards-based TCP port number must not be the ".. "same as the Websocket TCP port number." )

local Headers = { content_length = "content-length", }

local function _partition(buf, s) local buf_len = buf:len() - 1 local s_len = s:len() for i=0,buf_len do if buf(i, s_len):string() == s then if (i + s_len) >= buf:len() then return buf(0, i), buf(i, s_len), ByteArray.new() else return buf(0, i), buf(i, s_len), buf(i + s_len) end end end return nil, nil, buf

end

local function read_line(buf) return _partition(buf, "\n") end

local function read_command(buf) return read_line(buf) end

local KNOWN_COMMANDS = { – client commands ["SEND"] = true, ["SUBSCRIBE"] = true, ["UNSUBSCRIBE"] = true, ["BEGIN"] = true, ["COMMIT"] = true, ["ABORT"] = true, ["ACK"] = true, ["NACK"] = true, ["DISCONNECT"] = true, ["CONNECT"] = true, ["STOMP"] = true,

-- server commands
["CONNECTED"] = true,
["MESSAGE"] = true,
["RECEIPT"] = true,
["ERROR"] = true,

}

function p_stomp.dissector(buf, pinfo, root) local offset = pinfo.desegment_offset or 0 local command = nil local headers = {} local body = nil local sep = nil local rest = nil local content_length = nil rest = buf(offset) command, sep, rest = read_command(rest) if not sep then if rest:len() > 12 then return else pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT return end end offset = offset + command:len() + sep:len() – This is here to fuzz out bad data that contains \n if not KNOWN_COMMANDS[command:string()] then return end

do
    local header = nil
    while true do
        header, sep, rest = read_line(rest)
        if not sep then
            pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
            return
        end
        offset = offset + header:len() + sep:len()

        if header:len() == 0 then
            break
        end

        local key, sep, value = _partition(header, ":")
        if
            content_length == nil
            and key
            and key:string() == Headers.content_length
        then
            content_length = tonumber(value:string())
        end
        table.insert(headers, {header, key, value})
    end
end
if content_length == nil then
    body, sep, rest = _partition(rest, "\0")
    if not sep then
        pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
        return
    end
    offset = offset + body:len() + sep:len()
else
    if rest:len() < content_length then
        pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
        return
    end
    body = rest(0, content_length)
    offset = offset + body:len() + 1
end

pinfo.cols.protocol = "STOMP"
pinfo.cols.info = command:string()
local subtree = root:add(p_stomp, buf(0))
subtree:add(f_command, command)
for _, header_info in ipairs(headers) do
    local header, key, value = unpack(header_info)
    local header_tree = subtree:add(f_header, header)
    if key then
        header_tree:add(f_header_key, key)
    end
    if value then
        header_tree:add(f_header_value, value)
    end
end
subtree:add(f_body, body)
pinfo.desegment_offset = offset

end

local errmsg = "Error: The STOMP Preferences setting has the same ".. "Standards-based TCP port number as the Websocket TCP ".. "port number!\n\nPlease correct this before continuing."

function p_stomp.prefs_changed() – raw TCP and Websocket cannot use same port number if (p_stomp.prefs.tcp_port == p_stomp.prefs.websocket_port) and (p_stomp.prefs.tcp_port ~= 0) then

    if gui_enabled() then
        local tw = TextWindow.new("STOMP Preference Error")
        tw:set(errmsg)
    else
        print(errmsg)
    end
    return
end

if settings.TCP_PORT ~= p_stomp.prefs.tcp_port then
    -- the tcp port number preference changed
    local tcp_dissector_table = DissectorTable.get("tcp.port")
    if settings.TCP_PORT ~= 0 then
        -- remove our proto from the number it was previously dissecting
        tcp_dissector_table:remove(settings.TCP_PORT, p_stomp)
    end
    settings.TCP_PORT = p_stomp.prefs.tcp_port
    if settings.TCP_PORT ~= 0 then
        -- add our proto for the new port number
        tcp_dissector_table:add(settings.TCP_PORT, p_stomp)
    end
end

if settings.WEBSOCKET_PORT ~= p_stomp.prefs.websocket_port then
    -- the tcp port number preference changed
    local ws_dissector_table = DissectorTable.get("ws.port")
    if settings.WEBSOCKET_PORT ~= 0 then
        -- remove our proto from the number it was previously dissecting
        ws_dissector_table:remove(settings.WEBSOCKET_PORT, p_stomp)
    end
    settings.WEBSOCKET_PORT = p_stomp.prefs.websocket_port
    if settings.WEBSOCKET_PORT ~= 0 then
        -- add our proto for the new port number
        ws_dissector_table:add(settings.WEBSOCKET_PORT, p_stomp)
    end
end

end

if settings.TCP_PORT ~= 0 then DissectorTable.get("tcp.port"):add(settings.TCP_PORT, p_stomp) end

if settings.WEBSOCKET_PORT ~= 0 then DissectorTable.get("ws.port"):add(settings.WEBSOCKET_PORT, p_stomp) end

answered 04 Jul ‘15, 09:28

Hadriel's gravatar image

Hadriel
2.7k2939
accept rate: 18%

edited 04 Jul ‘15, 14:53

@Hadriel i get the following startup erro when i replaced the old pluggin with this one

Lua: Error During execution of prefs apply callback: [string “/home/mike/.wireshark/plugins/stomp.lua”]:209: bad argument #1 to ‘remove’ (string expected, got boolean)

(04 Jul ‘15, 10:22) tito

Ooops. I suppose I should actually test it before posting. :) I just updated it - it should be good now. The line:

p_stomp.prefs["websocket_port"] = Pref.bool(

…should have been:

p_stomp.prefs["websocket_port"] = Pref.uint(
(04 Jul ‘15, 11:01) Hadriel

@Hadriel there is one more error i.e. line 51

p_stomp.prefs[“websocket_port”] = Pref.uintl( should be p_stomp.prefs[“websocket_port”] = Pref.uint(

however now when i put the filter stomp in wireshark i am not able to see any messages. Could you please take another look into it. ?

(04 Jul ‘15, 11:35) tito

It works for me (with the one line’s type fixed).

Since you found the original “Lua: Error During execution of prefs apply callback” issue, I have to assume you changed the websocket TCP port number the Lua script uses, to be something other than 8080. But 8080 is the server port number for the capture file you sent, so why did you need to change that preference?

If your real live traffic uses a different port number, are you sure you’re putting in the correct server port number in that preference field? It needs to be the TCP port number of the HTTP server - namely, the destination TCP port number of the connection-creating TCP SYN. (well, technically it’s the TCP source port number that the HTTP Response came back from; but in practical terms that will be the destination TCP port number of the first TCP SYN of the connection)

(04 Jul ‘15, 11:48) Hadriel

One other thing to note with the above version is that if the STOMP preferences for both tcp and websocket ports are the same, and the STOMP is carried over WebSocket, then the STOMP registration in the tcp table will prevent the WebSocket dissector having a go at it first leading to the STOMP dissector not being called.

I suppose n any particular environment STOMP is likely either run over tcp or WebSocket, but not both at the same time. Maybe the port prefs should be exclusive?

(04 Jul ‘15, 12:55) grahamb ♦

OK, added a check for same port numbers, and warning/error message.

(04 Jul ‘15, 14:54) Hadriel
showing 5 of 6 show 1 more comments