Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【腾讯犀牛鸟开源课题实战】wireshark协议解析 #190

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/images/trpc_wireshark_dissector_request.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions trpc/tools/trpc_dissector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[English](./README.md)

# 前言

tRPC-Dissector是tRPC协议的Wireshark解析器,提供了一对一应答的帧头与ProtoBuf包头解析。

# 使用说明

1. 解析器脚本载入Wireshark-Lua脚本目录(trpc-cpp/trpc/tools/trpc-dissector/tRPC_dissector.lua)。

- 根据项目服务端口配置,修改Lua脚本中server_port参数。
- 打开Wireshark,“帮助”选项卡->关于Wireshark->“文件夹”选项卡->个人Lua插件,将Lua脚本载入该目录。
- “分析”选项卡->重载lua脚本。

2. 导入proto文件[trpc.proto](https://github.com/trpc-group/trpc/blob/main/trpc/trpc.proto)。

- “编辑”选项卡->首选项->Protocols->ProtoBuf,将trpc.proto文件加入ProtoBuf搜索路径。

- 勾选“Load .proto files on startup.”、“Dissect Protobuf fields as Wireshark fields.”、“Show details of message, fields and enums.”、“Show all fields of bytes type as string.”选项。

# 解析效果

Request包解析效果

![trpc_wireshark_dissector_request.png](../../../docs/images/trpc_wireshark_dissector_request.png)

Response包解析效果

![trpc_wireshark_dissector_response.png](../../../docs/images/trpc_wireshark_dissector_response.png)
102 changes: 102 additions & 0 deletions trpc/tools/trpc_dissector/tRPC_dissector.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
do
local protocol_name = "trpc"
local trpc_proto = Proto(protocol_name, "tRPC Protocol Dissector")

local field_magic = ProtoField.uint16(protocol_name .. ".magic", "Magic", base.HEX)
local field_type = ProtoField.uint8(protocol_name .. ".type", "Packet Type", base.DEC)
local field_stream = ProtoField.uint8(protocol_name .. ".stream", "Stream Type", base.DEC)
local field_total_size = ProtoField.uint32(protocol_name .. ".total_size", "Total Size", base.DEC)
local field_header_size= ProtoField.uint16(protocol_name .. ".header_size", "Header Size", base.DEC)
local field_unique_id = ProtoField.uint32(protocol_name .. ".unique_id", "Unique ID", base.DEC)
local field_version = ProtoField.uint8(protocol_name .. ".version", "Version", base.DEC)
local field_reserved = ProtoField.uint8(protocol_name .. ".reserved", "Reserved", base.DEC)
trpc_proto.fields = {field_magic, field_type, field_stream, field_total_size, field_header_size, field_unique_id, field_version, field_reserved}

local MAGIC_CODE_TRPC = "0930"
local PROTO_HEADER_LENGTH = 16

local tcp_src_port = Field.new("tcp.srcport")
local tcp_dst_port = Field.new("tcp.dstport")
local tcp_stream = Field.new("tcp.stream")

local data_dissector = Dissector.get("data")
local protobuf_dissector = Dissector.get("protobuf")

function trpc_proto.init()
-- tcp_stream_id <-> {client_port, server_port}
stream_map = {}
end

function trpc_proto.dissector(buffer, packet, tree)
packet.cols.protocol:set("tRPC")

local f_src_port = tcp_src_port()()
local f_dst_port = tcp_dst_port()()

-- record which one is client and which one is server
-- by first tcp syn frame
stream_n = tcp_stream().value
if stream_map[stream_n] == nil then
stream_map[stream_n] = {f_src_port, f_dst_port}
end

if f_src_port == stream_map[stream_n][1] then
packet.private["pb_msg_type"] = "message,trpc.RequestProtocol"
end
if f_src_port == stream_map[stream_n][2] then
packet.private["pb_msg_type"] = "message,trpc.ResponseProtocol"
end

local magic_value = buffer(0, 2)
local type_value = buffer(2, 1)
local stream_value = buffer(3, 1)
local total_size_value = buffer(4, 4)
local header_size_value = buffer(8, 2)
local unique_id_value = buffer(10, 4)
local version_value = buffer(14, 1)
local reserved_value = buffer(15, 1)

local header_length = header_size_value:uint()
local subtree = tree:add(trpc_proto, buffer(), "tRPC Protocol Data")

data_dissector:call(buffer, packet, tree)

if buffer:len() < 16 then
return
elseif buffer:len() < 16 + header_length then
return
end

local t = subtree:add(trpc_proto, buffer)
t:add(field_magic, magic_value)
t:add(field_type, type_value)
t:add(field_stream, stream_value)
t:add(field_total_size, total_size_value)
t:add(field_header_size, header_size_value)
t:add(field_unique_id, unique_id_value)
t:add(field_version, version_value)
t:add(field_reserved, reserved_value)

pcall(Dissector.call, protobuf_dissector, buffer(16, header_length):tvb(), packet, subtree)
end

-- heuristic
local function heur_dissect_proto(tvbuf, pktinfo, root)
if (tvbuf:len() < PROTO_HEADER_LENGTH) then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

又调整了一下,主要解决多连接下识别的问题,最终版本用这个吧

--
--
-- Tencent is pleased to support the open source community by making tRPC available.
--
-- Copyright (C) 2024 THL A29 Limited, a Tencent company.
-- All rights reserved.
--
-- If you have downloaded a copy of the tRPC source code from Tencent,
-- please note that tRPC source code is licensed under the  Apache 2.0 License,
-- A copy of the Apache 2.0 License is included in this file.
--
--
-- tRPC is licensed under the Apache 2.0 License, and includes source codes from
-- the following components:
-- 1. incubator-brpc
-- Copyright (C) 2019 The Apache Software Foundation
-- incubator-brpc is licensed under the Apache 2.0 License.
--
--

local protocol_name    = "trpc" 
local trpc_proto       = Proto(protocol_name, "tRPC Protocol Dissector")

local field_magic      = ProtoField.uint16(protocol_name .. ".magic",       "Magic",       base.HEX)
local field_type       = ProtoField.uint8(protocol_name .. ".type",         "Packet Type", base.DEC)
local field_stream     = ProtoField.uint8(protocol_name .. ".stream",       "Stream Type", base.DEC)
local field_total_size = ProtoField.uint32(protocol_name .. ".total_size",  "Total Size",  base.DEC)
local field_header_size= ProtoField.uint16(protocol_name .. ".header_size", "Header Size", base.DEC)
local field_unique_id  = ProtoField.uint32(protocol_name .. ".unique_id",   "Unique ID",   base.DEC)
local field_version    = ProtoField.uint8(protocol_name .. ".version",      "Version",     base.DEC)
local field_reserved   = ProtoField.uint8(protocol_name .. ".reserved",     "Reserved",    base.DEC)
trpc_proto.fields      = {field_magic, field_type, field_stream, field_total_size, field_header_size, field_unique_id, field_version, field_reserved}

local MAGIC_CODE_TRPC = "0930"
local PROTO_HEADER_LENGTH = 16

local tcp_src_port     = Field.new("tcp.srcport")
local tcp_dst_port     = Field.new("tcp.dstport")
local tcp_stream       = Field.new("tcp.stream")

local proto_f_protobuf_field_name  = Field.new("protobuf.field.name")
local proto_f_protobuf_field_value = Field.new("protobuf.field.value")

local data_dissector     = Dissector.get("data")
local protobuf_dissector = Dissector.get("protobuf")

----------------------------------------
-- declare functions
local check_length  = function() end
local dissect_proto = function() end

----------------------------------------
-- main dissector
function trpc_proto.dissector(tvbuf, pktinfo, root)
    local pktlen = tvbuf:len()

    local bytes_consumed = 0

    while bytes_consumed < pktlen do
        local result = dissect_proto(tvbuf, pktinfo, root, bytes_consumed)

        if result > 0 then
            bytes_consumed = bytes_consumed + result
        elseif result == 0 then
            -- hit an error
            return 0
        else
            pktinfo.desegment_offset = bytes_consumed
            -- require more bytes
            pktinfo.desegment_len = -result

            return pktlen
        end
    end

    return bytes_consumed
end

--------------------------------------------------------------------------------
-- heuristic
-- tcp_stream_id <-> {client_port, server_port, {request_id<->method_name}}
local stream_map = {}
local function heur_dissect_proto(tvbuf, pktinfo, root)
    -- dynmaic decide client or server data
    -- by first tcp syn frame
    local f_src_port = tcp_src_port()()
    local f_dst_port = tcp_dst_port()()
    local stream_n = tcp_stream().value
    if stream_map[stream_n] == nil then
        stream_map[stream_n] = {f_src_port, f_dst_port, {}}
    end

    if (tvbuf:len() < PROTO_HEADER_LENGTH) then
        return false
    end

    local magic = tvbuf:range(0, 2):bytes():tohex()
    -- for range dissectors
    if magic ~= MAGIC_CODE_TRPC then
        return false
    end

    trpc_proto.dissector(tvbuf, pktinfo, root)

    pktinfo.conversation = trpc_proto

    return true
end

trpc_proto:register_heuristic("tcp", heur_dissect_proto)

--------------------------------------------------------------------------------

-- check packet length, return length of packet if valid
check_length = function(tvbuf, offset)
    local msglen = tvbuf:len() - offset

    if msglen ~= tvbuf:reported_length_remaining(offset) then
        -- captured packets are being sliced/cut-off, so don't try to desegment/reassemble
        LM_WARN("Captured packet was shorter than original, can't reassemble")
        return 0
    end

    if msglen < PROTO_HEADER_LENGTH then
        -- we need more bytes, so tell the main dissector function that we
        -- didn't dissect anything, and we need an unknown number of more
        -- bytes (which is what "DESEGMENT_ONE_MORE_SEGMENT" is used for)
        return -DESEGMENT_ONE_MORE_SEGMENT
    end

    -- if we got here, then we know we have enough bytes in the Tvb buffer
    -- to at least figure out whether this is valid trpc packet

    local magic = tvbuf:range(offset, 2):bytes():tohex()
    if magic ~= MAGIC_CODE_TRPC then
        return 0
    end

    local packet_size = tvbuf:range(offset+4, 4):uint()
    if msglen < packet_size then
        -- Need more bytes to desegment full trpc packet
        return -(packet_size - msglen)
    end

    return packet_size
end

--------------------------------------------------------------------------------

dissect_proto = function(tvbuf, pktinfo, root, offset)
    local len = check_length(tvbuf, offset)
    if len <= 0 then
        return len
    end

    -- update 'Protocol' field
    if offset == 0 then
        pktinfo.cols.protocol:set("tRPC")
    end

    local f_src_port = tcp_src_port()()
    local f_dst_port = tcp_dst_port()()

    local direction
    local stream_n = tcp_stream().value
    if f_src_port == stream_map[stream_n][1] then
        pktinfo.private["pb_msg_type"] = "message,trpc.RequestProtocol"
        direction = "request"
    end
    if f_src_port == stream_map[stream_n][2] then
        pktinfo.private["pb_msg_type"] = "message,trpc.ResponseProtocol"
        direction = "response"
    end

    -- check packet length, 
    local magic_value       = tvbuf(offset, 2)
    local type_value        = tvbuf(offset+2, 1)
    local stream_value      = tvbuf(offset+3, 1)
    local total_size_value  = tvbuf(offset+4, 4)
    local header_size_value = tvbuf(offset+8, 2)
    local unique_id_value   = tvbuf(offset+10, 4)
    local version_value     = tvbuf(offset+14, 1)
    local reserved_value    = tvbuf(offset+15, 1)

    local header_length   = header_size_value:uint()
    local total_length    = total_size_value:uint()
    local tree            = root:add(trpc_proto, tvbuf:range(offset, len), "tRPC Protocol Data")

    data_dissector:call(tvbuf, pktinfo, tree)

    local t = tree:add(trpc_proto, tvbuf)
    t:add(field_magic, magic_value)
    t:add(field_type, type_value)
    t:add(field_stream, stream_value)
    t:add(field_total_size, total_size_value)
    t:add(field_header_size, header_size_value)
    t:add(field_unique_id, unique_id_value)
    t:add(field_version, version_value)
    t:add(field_reserved, reserved_value)

    -- solve the problem of parsing errors when multiple RPCs are included in a packet
    local protobuf_field_names = { proto_f_protobuf_field_name() }
    local pre_field_nums = #protobuf_field_names
    pcall(Dissector.call, protobuf_dissector, tvbuf(offset+16, header_length):tvb(), pktinfo, tree)

    -- Add bussiness rpc pb
    -- Get invoke rpc method name from trpc.RequestProtocol
    protobuf_field_names = { proto_f_protobuf_field_name() }
    local cur_field_nums = #protobuf_field_names
    local protobuf_field_values = { proto_f_protobuf_field_value() }
    local method
    -- default request id
    local request_id = 0
    for k = pre_field_nums + 1, cur_field_nums do
        local v = protobuf_field_names[k]
        if v.value == "func" then
            method = protobuf_field_values[k].range:string(ENC_UTF8)
        elseif v.value == "request_id" then
            request_id = protobuf_field_values[k].range:uint()
        end
    end

    local tvb_body = tvbuf:range(offset + 16 + header_length, total_length - header_length - 16):tvb()
    if method ~= nil then
        -- only req contains method, correlate it with request id so that response protocol can use.
        stream_map[stream_n][3][request_id] = method
        pktinfo.private["pb_msg_type"] = "application/trpc," .. method .. "," .. direction
    else
        -- get method for the same request id
        method = stream_map[stream_n][3][request_id]
        pktinfo.private["pb_msg_type"] = "application/trpc," .. method .. "," .. direction
    end
    pcall(Dissector.call, protobuf_dissector, tvb_body, pktinfo, tree)

    return total_length
end

return false
end

local magic = tvbuf:range(0, 2):bytes():tohex()
-- for range dissectors
if magic ~= MAGIC_CODE_TRPC then
return false
end
trpc_proto.dissector(tvbuf, pktinfo, root)
pktinfo.conversation = trpc_proto

return true
end

trpc_proto:register_heuristic("tcp", heur_dissect_proto)

end
Loading