「推荐」好用到爆的 RocketMQ 协议解析插件

1,094 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

WireShark 使用 Lua 编写脚本插件解析 RocketMQ 协议

WireShark 可谓是抓包神器,除了本身支持解析 TCP 、 UDP 、 HTTP 等各种协议,更是可以通过编写插件来解析自定义的协议。

先来看看我们最终解析出来的 RocketMQ 协议,可以看到通过 WireShark 我们可以看到协议包的具体发送内容

image-20220427232347021

RocketMQ协议包说明

协议包可以划分为四段,前两段是固定 4 个字节

  • 第一段( 4 字节) : 记录后面三部分的长度
  • 第二段( 4 字节) : 记录第三段(Header )的长度
  • 第三段 : Header 协议头( json 序列化的数据)
  • 第四段 : Body 协议体(自定义二进制序列化的数据,不一定是 json 格式)

image-20220427234244868

怎样去实现自定义协议的解析呢?

WireShark 官方文档有介绍,这里贴个链接,感兴趣的老哥可以去研究一波

新协议和解析器的功能

大体思路

  1. 编写解析 RocketMQ 协议的 protocol_rk.lua 脚本

  2. 打开 Golobal configuration 目录

  3. 找到目录下的 init.lua 并在最后一行写入

    dofile("protocol_rk.lua")

    这行代码用于引入协议 lua 脚本

image-20220427235010449

脚本编写

  • 创建一个协议对象 Proto

    Proto.new(名称,描述)

  • 实现协议的解析器

    实现方法 dissector(tvb, pinfo, tree)

    • tvb : 代表数据包的缓冲区
    • pinfo : 数据包信息,如源端口号、目标端口号、桢长度等
    • tree : UI 树
  • 端口号和协议解析器绑定

    调用 DissectorTable.get("tcp.port"):add(dst_port, rmProtocol)

    ---
    --- Generated by EmmyLua(https://github.com/EmmyLua)
    --- Created by MinXie.
    --- DateTime: 2022/4/27 6:02 PM
    --- tosee https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html#lua_class_Proto
    
    ---  创建一个Proto对象,
    ---  Wireshark 中的新协议。协议有多种用途。主要是剖析协议,但它们也可以是用于注册偏好以用于其他目的的假人。
    
    --- Proto.new(名称,描述)
    local rmProtocol = Proto("rocketmq", "Rocketmq Protocol")
    --- 目标端口
    local dst_ports = { 9876, 10911 }
    --- http://dkolf.de/src/dkjson-lua.fsl/home 使用文档
    local json = dofile("json.lua")
    
    --- 检索或分配。
    --- tvb: 代表数据包的缓冲区
    --- pinfo: 数据包信息,如源端口号、目标端口号、桢长度等
    --- tree: ui树
    function rmProtocol.dissector(tvb, pinfo, tree)
    end
    
    --- DissectorTable 特定协议的子解析器表(例如,http、smtp、sip 等 TCP 子解析器被添加到表“tcp.port”中)。
    --- 有助于将更多解剖器添加到表中,以便它们出现在“解码为...”对话框中。
    for _, dst_port in pairs(dst_ports) do
        DissectorTable.get("tcp.port"):add(dst_port, rmProtocol)
    end
    

dissector(tvb, pinfo, tree) 方法的实现

--- 检索或分配。
--- tvb: 代表数据包的缓冲区
--- pinfo: 数据包信息,如源端口号、目标端口号、桢长度等
function rmProtocol.dissector(tvb, pinfo, tree)
    local subtree = tree:add(rmProtocol, tvb:range(tvb:len()))
    pinfo.cols['protocol'] = rmProtocol.name
    if isRequest(pinfo) then
        pinfo.cols['info'] = "[Request]"
    else
        pinfo.cols['info'] = "[Response]"

    end

    --- rocketmq 包,分四段
    --- 第一段(4字节): 记录后面三部分的长度
    --- 第二段(4字节): 记录第三段的长度
    --- 第三段: header
    --- 第四段: body
    local allLen = tvb(0, 4):uint()
    local headerLen = tvb(4, 4):uint()
    subtree:add("Len:", allLen)
    subtree:add("Header Len:", headerLen)
    subtree:add("Header:", tvb(8, headerLen):string())
    subtree:add("Body:", tvb(8 + headerLen, allLen - headerLen - 4):string())
    pcall(str2struct, subtree:add("HeaderData:", ""), tvb(8, headerLen):string())
    pcall(str2struct, subtree:add("BodyData:", ""), tvb(8 + headerLen, allLen - headerLen - 4):string())
end

--- 数据包的目标端口是9876时为请求
function isRequest(pinfo)
    --- pinfo.dst_port
    --- 此数据包的目标端口。
    local dstPort = pinfo.dst_port;
    for _, dst_port in pairs(dst_ports) do
        if dstPort == dst_port then
            return true
        end
        return false
    end
end

function str2struct(tree, headerStr)
    local table = json.parse(headerStr, 1, "}")
    handleTable(tree, table)
end

function handleTable(tree, table)
    for k, v in pairs(table) do
        if type(v) == "table" then
            handleTable(tree:add(k .. ":", ""), v)
        else
            tree:add(k .. ":", v)
        end
    end
end
  1. 显示协议的名字

    pinfo.cols['protocol'] = rmProtocol.name

image-20220428001528920

  1. 区别是请求还是响应类型

    这里可以通过 pinfo.dst_port 获取目标端口号进行判断

image-20220428001742341

  1. 解析RocketMQ协议包

    • 获取第一段的内容,存储的是后面三段的长度 local allLen = tvb(0, 4):uint()
    • 获取第二段的内容,存储的是第三段的长度 local headerLen = tvb(4, 4):uint()
    • 获取第三段内容,是协议头,json 序列化格式 tvb(8, headerLen):string()
    • 获取第四段内容,是协议体,tvb(8 + headerLen, allLen - headerLen - 4):string()
    • 分别加入到UI树里面进行显示,subtree:add(key , value)
  2. 将 Header 、 Body 解析为树的结构

    调用str2struct方法

str2struct 方法实现

str2struct 方法做的是将json格式的字符串添加到 UI 树里面

里面调用了 json.parse 方法,可以将json转化为一个table

然后不断递归添加到子树上

实现Tree化结构

image-20220428205812471

这样我们就可以非常直观地知道 RocketMQ 底层在通讯的时候,发送的具体内容是什么

像上图的协议头里面,我们就可以清楚地知道发送的 code 是 103 ,version 是 399 ,extFields 里面的 Broker 的参数信息,序列化的协议是 JSON 等

最后,贴一个很好用的 json 脚本库 json.lua

快动手玩起来吧,有不懂的可以在留言区留言