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

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


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.

permanent link

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

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
permanent link

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
Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Markdown Basics

  • *italic* or _italic_
  • **bold** or __bold__
  • link:[text](http://url.com/ "title")
  • image?![alt text](/path/img.jpg "title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Question tags:

×1,620
×1

question asked: 04 Jul '15, 01:07

question was seen: 2,860 times

last updated: 04 Jul '15, 14:54

p​o​w​e​r​e​d by O​S​Q​A