-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add wireshark support for trpc
- Loading branch information
1 parent
1b97ae0
commit 6c521ff
Showing
1 changed file
with
236 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
-- | ||
-- | ||
-- 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 |