-- -- 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 -- convert uint32 integer to byte array function uint32_to_byte_array(value) local bytes = {} while value > 0 do table.insert(bytes, bit32.band(value, 0xFF)) value = bit32.rshift(value, 8) end return bytes end -- varint decoding function function decode_varint(value) local bytes = uint32_to_byte_array(value) local result = 0 local shift = 0 for _, byte in ipairs(bytes) do result = bit32.bor(result, bit32.lshift(bit32.band(byte, 0x7F), shift)) if bit32.band(byte, 0x80) == 0 then return result end shift = shift + 7 end LM_DBG("Malformed Varint encoding") 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 magic_code = tvbuf:range(offset, 4):string() local meta_size = tvbuf:range(offset + 8, 4):uint() local body_size = tvbuf:range(offset + 4, 4):uint() local tvb_meta = tvbuf:range(offset + PROTO_HEADER_LENGTH, meta_size):tvb() if magic_code == MAGIC_CODE_PRPC then -- update 'Protocol' field if offset == 0 then pktinfo.cols.protocol:set("ProtoBuf") end -- dissect rpc meta fields pktinfo.private["pb_msg_type"] = "message,brpc.policy.RpcMeta" -- 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 protobuf_dissector:call(tvb_meta, pktinfo, tree) -- https://ask.wireshark.org/question/31800/protobuf-dissector-with-nested-structures/?answer=31924#post-id-31924 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 direction, method local service_name, method_name, correlation_id, attachment_size -- only process new fields added after the previous call to protobuf_dissector for k = pre_field_nums + 1, cur_field_nums do local v = protobuf_field_names[k] 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() elseif v.value == "attachment_size" then attachment_size = protobuf_field_values[k].range:le_uint() 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 attachment_size then attachment_size = decode_varint(attachment_size) else attachment_size = 0 end if method ~= nil then -- dissect rpc body local tvb_body = tvbuf:range(offset + PROTO_HEADER_LENGTH + meta_size, body_size - meta_size - attachment_size):tvb() pktinfo.private["pb_msg_type"] = "application/grpc,/" .. method .. "," .. direction protobuf_dissector:call(tvb_body, pktinfo, tree) end elseif magic_code == 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