Skip to content

Commit

Permalink
Add wireshark dissector for baidu_std protocol (#2408)
Browse files Browse the repository at this point in the history
  • Loading branch information
wasphin authored Oct 17, 2023
1 parent 1a3dcdc commit 12d0728
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 0 deletions.
27 changes: 27 additions & 0 deletions docs/cn/wireshark_baidu_std.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[English version](../en/wireshark_baidu_std.md)

# 介绍

`wireshark_baidu_std.lua` 是针对 [`baidu_std`](baidu_std.md) 协议的 `Wireshark` 解析插件,同时支持 [`streaming_rpc`](streaming_rpc.md) 协议的解析。

请求包的解析示例:

![request](../images/wireshark_baidu_std_request.png)

响应包的解析示例:

![response](../images/wireshark_baidu_std_response.png)


## 使用方式

1.[`wireshark_baidu_std.lua`](../../tools/wireshark_baidu_std.lua) 放到 "Personal Lua Plugins" 目录下;
1.[`options.proto`](../../src/brpc/options.proto)[`streaming_rpc_meta.proto`](../../src/brpc/streaming_rpc_meta.proto) 以及 [`baidu_rpc_meta.proto`](../../src/brpc/policy/baidu_rpc_meta.proto) 放到 "Personal configuration" 目录下的 `protobuf` 目录中,目录如不存在可以手动创建;
1. 参考 [Wireshark Protobuf](https://wiki.wireshark.org/Protobuf#protobuf-search-paths-settings) 配置 `Protobuf` 系统 proto 文件路经,如:`/opt/homebrew/opt/protobuf/include`
![wireshark-protobuf-search-paths](../images/wireshark_protobuf_search_paths.png)
1. 可选,如需想使用相关字段进行过滤,可打开如下选项:
![wireshark-protobuf-settings](../images/wireshark_protobuf_settings.png)

上面提到的 "Personal Lua Plugins" 以及 "Personal configuration" 目录可查看 `About Wireshark``Folders` 页面,相关目录是平台相关的,macOS 下如:

![About Wireshark](../images/wireshark_folders.png)
27 changes: 27 additions & 0 deletions docs/en/wireshark_baidu_std.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[中文版](../cn/wireshark_baidu_std.md)

# Overview

`wireshark_baidu_std.lua` is a Wireshark Lua dissector written for [`baidu_std`](../cn/baidu_std.md) protocol, including the [`streaming_rpc`](streaming_rpc.md) protocol.

Example for the Echo request:

![request](../images/wireshark_baidu_std_request.png)

Example for the Echo response:

![response](../images/wireshark_baidu_std_response.png)


## How to use

1. Put [`wireshark_baidu_std.lua`](../../tools/wireshark_baidu_std.lua) under "Personal Lua Plugins";
1. And put [`options.proto`](../../src/brpc/options.proto), [`streaming_rpc_meta.proto`](../../src/brpc/streaming_rpc_meta.proto) and [`baidu_rpc_meta.proto`](../../src/brpc/policy/baidu_rpc_meta.proto) in `protobuf` directory under "Personal configuration", create if not exist;
1. Set `Protobuf Search Paths` for Protobuf official library include directory(e.g. `/opt/homebrew/opt/protobuf/include`), see [Wireshark Protobuf](https://wiki.wireshark.org/Protobuf#protobuf-search-paths-settings) for more details.
![wireshark-protobuf-search-paths](../images/wireshark_protobuf_search_paths.png)
1. Optional, turn on `Dissect Protobuf fields as Wireshark fields` if you want to use related `baidu_std` fields for filtering:
![wireshark-protobuf-settings](../images/wireshark_protobuf_settings.png)

The "Personal Lua Plugins" and "Personal configuration" folders above can be found under the `Folders` page of `About Wireshark`, for macOS:

![About Wireshark](../images/wireshark_folders.png)
Binary file added docs/images/wireshark_baidu_std_request.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/wireshark_baidu_std_response.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/wireshark_folders.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/wireshark_protobuf_search_paths.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/wireshark_protobuf_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
279 changes: 279 additions & 0 deletions tools/wireshark_baidu_std.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
--

--------------------------------------------------------------------------------
-- function
-- Proto.new(name, desc)
-- proto.dissector
-- proto.fields
-- proto.prefs
-- proto.prefs_changed
-- proto.init
-- proto.name
-- proto.description
--------------------------------------------------------------------------------

local plugin_info = {
name = "baidu",
version = "0.1.0",
description = "baidu_std"
}

local major, minor, patch = get_version():match("(%d+)%.(%d+)%.(%d+)")
local version_code = tonumber(major) * 1000 * 1000 + tonumber(minor) * 1000 + tonumber(patch)

local proto = Proto.new(plugin_info.name, plugin_info.description)

local LM_DBG = function(fmt, ...)
if (tonumber(major) < 3) then
critical(table.concat({ plugin_info.name:upper() .. ":", fmt }, ' '):format(...))
else
print (table.concat({ plugin_info.name:upper() .. ":", fmt }, ' '):format(...))
end
end

LM_DBG("Wireshark version =", get_version())
LM_DBG("Lua version =", _VERSION)

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

local MAGIC_CODE_PRPC = "PRPC"
local MAGIC_CODE_STRM = "STRM"

local PROTO_HEADER_LENGTH = 12


local pf_magic = ProtoField.string(plugin_info.name .. ".magic",
"Magic Code", BASE_NONE)
local pf_body_size = ProtoField.uint32(plugin_info.name .. ".body_size",
"Body Size", BASE_DEC)
local pf_meta_size = ProtoField.uint32(plugin_info.name .. ".meta_size",
"Meta Size", BASE_DEC)

-- used to customize tree item
local pf_any = ProtoField.bytes (plugin_info.name .. ".any",
"Any", BASE_NONE)

proto.fields = {
pf_magic,
pf_body_size,
pf_meta_size,
pf_any
}

----------------------------------------
-- ref fields
local proto_f_magic_code = Field.new(plugin_info.name .. ".magic")
local proto_f_body_size = Field.new(plugin_info.name .. ".body_size")
local proto_f_meta_size = Field.new(plugin_info.name .. ".meta_size")

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

----------------------------------------
-- protobuf dissector
-- Note:
-- options.proto, baidu_rpc_meta.proto and streaming_rpc_meta.proto
-- should be put in Wireshark Protobuf Search Paths.
local protobuf_dissector = Dissector.get("protobuf")

----------------------------------------
-- declare functions

local check_length = function() end
local dissect_proto = function() end

----------------------------------------
-- main dissector
function 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
local function heur_dissect_proto(tvbuf, pktinfo, root)
LM_DBG("heur brpc: pkg number: " .. pktinfo.number)
if (tvbuf:len() < PROTO_HEADER_LENGTH) then
LM_DBG("too short: pkg number: " .. pktinfo.number)
return false
end

local magic = tvbuf:range(0, 4):string()
-- for range dissectors
if magic ~= MAGIC_CODE_PRPC and maigc ~= MAGIC_CODE_STRM then
LM_DBG("invalid magic code: pkg number: " .. pktinfo.number)
return false
end

proto.dissector(tvbuf, pktinfo, root)

pktinfo.conversation = proto

return true
end
proto:register_heuristic("tcp", heur_dissect_proto)

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

local correlation_method_map = {}

local store_method = function(correlation_id, method)
-- TODO: pop items
correlation_method_map[correlation_id] = method
end

local load_method = function(correlation_id)
return correlation_method_map[correlation_id]
end

-- 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 baidu_std packet

local magic = tvbuf:range(offset, 4):string()
if magic ~= MAGIC_CODE_PRPC and magic ~= MAGIC_CODE_STRM then
return 0
end

-- big endian
local packet_size = tvbuf:range(offset+4, 4):uint() + PROTO_HEADER_LENGTH
if msglen < packet_size then
LM_DBG("Need more bytes to desegment full baidu_std 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

local tree = root:add(proto, tvbuf:range(offset, len))

tree:add(pf_magic, tvbuf:range(offset, 4))
tree:add(pf_body_size, tvbuf:range(offset+4, 4))
tree:add(pf_meta_size, tvbuf:range(offset+8, 4))

local meta_size = proto_f_meta_size().value
local body_size = proto_f_body_size().value
local tvb_meta = tvbuf:range(offset + PROTO_HEADER_LENGTH, meta_size):tvb()
if proto_f_magic_code().value == MAGIC_CODE_PRPC then
-- dissect rpc meta fields
pktinfo.private["pb_msg_type"] = "message,brpc.policy.RpcMeta"
protobuf_dissector:call(tvb_meta, pktinfo, tree)

local direction, method

local service_name, method_name, correlation_id

-- https://ask.wireshark.org/question/31800/protobuf-dissector-with-nested-structures/?answer=31924#post-id-31924
local protobuf_field_names = { proto_f_protobuf_field_name() }
local protobuf_field_values = { proto_f_protobuf_field_value() }
for k, v in pairs(protobuf_field_names) do
if v.value == "request" then
direction = "request"
elseif v.value == "response" then
direction = "response"
elseif v.value == "service_name" then
service_name = protobuf_field_values[k].range:string(ENC_UTF8)
elseif v.value == "method_name" then
method_name = protobuf_field_values[k].range:string(ENC_UTF8)
elseif v.value == "correlation_id" then
correlation_id = protobuf_field_values[k].range:uint64()
end
end

if direction == "request" then
method = service_name .. "/" .. method_name
-- NOTE: convert uint64 to string to be used as key of table
store_method(tostring(correlation_id), method)
elseif direction == "response" then
method = load_method(tostring(correlation_id))
end

if method ~= nil then
-- dissect rpc body
local tvb_body = tvbuf:range(offset + PROTO_HEADER_LENGTH + meta_size, body_size - meta_size):tvb()
pktinfo.private["pb_msg_type"] = "application/grpc,/" .. method .. "," .. direction
protobuf_dissector:call(tvb_body, pktinfo, tree)
end
elseif proto_f_magic_code().value == MAGIC_CODE_STRM then
-- dissect streaming meta fields
pktinfo.private["pb_msg_type"] = "message,brpc.StreamFrameMeta"
protobuf_dissector:call(tvb_meta, pktinfo, tree)
end

-- body fields are business related, customized here

return len
end

--------------------------------------------------------------------------------
-- Editor modelines
--
-- Local variables:
-- c-basic-offset: 4
-- tab-width: 4
-- indent-tab-mode: nil
-- End:
--
-- kate: indent-width 4; tab-width 4;
-- vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab
-- :indentSize=4:tabSize=4:noTabs=true

0 comments on commit 12d0728

Please sign in to comment.