Appearance
Appearance
Protobuf 内容通常由 Wireshark 从某些更高层 dissectors 中解析,包括 gRPC 或其他基于 UDP/TCP 的 dissectors。你可以通过以下方式,在用 C 编写的 dissector 中添加 Protobuf 处理支持:
dissector_handle_tprotobuf_handle=find_dissector("protobuf");...call_dissector_with_data(protobuf_handle,tvb,pinfo,tree,"message,tutorial.AddressBook");或通过以下方式,在用 Lua 编写的 dissector 中添加:
localprotobuf_dissector=Dissector.get("protobuf")...pinfo.private["pb_msg_type"]="message,tutorial.AddressBook"pcall(Dissector.call,protobuf_dissector,tvb,pinfo,tree)更高层 dissectors 可以通过 data 参数或 private_table["pb_msg_type"] 提供 protobuf 消息类型信息。消息类型信息的格式为: "message," message_type_name message_type_name 是消息类型的完整名称,前缀为 package 名称。在解析 Protobuf 内容时,Protobuf dissector 会根据给定的 message_type_name,从 'Protobuf Search Paths' 首选项中搜索消息定义文件 (*.proto)。
我们用一个示例来说明如何使用 protobuf dissector。
Protocol Buffers (Protobuf) 消息的二进制线格式不是自描述协议。Wireshark 应配置 Protocol Buffers 语言文件 (*.proto),以便基于 message、enum 和 field 定义正确解析 Protobuf 数据。Wireshark 支持使用 Protocol Buffers 语言第 2 版或第 3 版语法编写的 *.proto 文件。下面是一个名为 addressbook.proto 的 *.proto 文件示例:
// This file comes from the official Protobuf example with a little modification.syntax="proto3";packagetutorial;import"google/protobuf/timestamp.proto";messagePerson{stringname=1;int32id=2;// Unique ID number for this person.stringemail=3;enumPhoneType{MOBILE=0;HOME=1;WORK=2;}messagePhoneNumber{stringnumber=1;PhoneTypetype=2;}repeatedPhoneNumberphone=4;google.protobuf.Timestamplast_updated=5;bytesportrait_image=6;}messageAddressBook{repeatedPersonpeople=1;}根据 'import "google/protobuf/timestamp.proto"' 这一行,文件 addressbook.proto 依赖 Protobuf 的官方 *.proto 库。(库可在 protocol buffers 的下载页面下载。)
你可以通过在 Protobuf 协议首选项中设置 Protobuf Search Paths,告诉 Wireshark 在哪里查找 *.proto 文件。如果 addressbook.proto 文件的完整路径是 d:/protos/my_proto_files/addressbook.proto,依赖文件 "google/protobuf/timestamp.proto" 的真实路径是 d:/protos/protobuf-3.4.1/include/google/protobuf/timestamp.proto(位于 Protobuf 官方库目录 d:/protos/protobuf-3.4.1/include 中)。你应将 d:/protos/protobuf-3.4.1/include/ 和 d:/protos/my_proto_files 路径添加到 'Protobuf Search Paths' 表中,并启用 'd:/protos/my_proto_files' 记录的 'Load all files' 选项,以便从 addressbook.proto 文件预加载消息定义:此首选项对话框可通过菜单 'Edit->Preferences->Protocols->ProtoBuf->Protobuf search paths' 找到。
如果你的协议运行在 UDP 之上,并且每个 UDP 的 payload 都是一条 Protobuf 消息,你可以直接使用 Wireshark 的内置 Protobuf UDP dissector。例如,如果端口 8127 上的 UDP payload 是 addressbook.proto 文件中定义的 'tutorial.AddressBook' 消息类型,并且该文件可在 'Protobuf Search Paths' 中找到,你可以在 Protobuf 首选项中的 'Protobuf UDP Message Types' 表里添加一条关于 UDP 端口与消息类型映射的记录:
> 注意,'UDP ports' 字段可以是一个范围,例如 "8127,8200-8300,9127"。
然后,你可以用 Wireshark 打开 SampleCaptures 页面上的示例抓包文件 protobuf_udp_addressbook.pcapng,查看已解析的 Protobuf 详细信息:
解析 Protobuf UDP 数据包的另一种方式是编写一个简单的 Lua 脚本,为每个根消息类型创建一个 dissector,这样你就可以在 UDP 数据包上使用 "Decode as..." 功能。此方法将在下一节介绍。
如果某个 Protobuf UDP 和 TCP 协议的根消息类型是 addressbook.proto 中的 'tutorial.AddressBook',并且一个 UDP 包的整个 payload 是一条 'tutorial.AddressBook' 消息,而该协议在 TCP 上的每条消息都是一个以 4 字节大端长度为前缀的 'tutorial.AddressBook' 消息([4bytes length][a message][4bytes length][a message]...),那么你可以将以下名为 'create_protobuf_dissector.lua' 的文件放到你的 'Personal configuration' 目录的 'plugins' 子目录中:
dolocalprotobuf_dissector=Dissector.get("protobuf")-- Create protobuf dissector based on UDP or TCP.-- The UDP dissector will take the whole tvb as a message.-- The TCP dissector will parse tvb as format:-- [4bytes length][a message][4bytes length][a message]...-- @param name The name of the new dissector.-- @param desc The description of the new dissector.-- @param for_udp Register the new dissector to UDP table.(Enable 'Decode as')-- @param for_tcp Register the new dissector to TCP table.(Enable 'Decode as')-- @param msgtype Message type. This must be the root message defined in your .proto file.localfunctioncreate_protobuf_dissector(name,desc,for_udp,for_tcp,msgtype)localproto=Proto(name,desc)localf_length=ProtoField.uint32(name..".length","Length",base.DEC)proto.fields={f_length}proto.dissector=function(tvb,pinfo,tree)localsubtree=tree:add(proto,tvb())iffor_udpandpinfo.port_type==3then-- UDPifmsgtype~=nilthenpinfo.private["pb_msg_type"]="message,"..msgtypeendpcall(Dissector.call,protobuf_dissector,tvb,pinfo,subtree)elseiffor_tcpandpinfo.port_type==2then-- TCPlocaloffset=0localremaining_len=tvb:len()whileremaining_len>0doifremaining_len<4then-- head not enoughpinfo.desegment_offset=offsetpinfo.desegment_len=DESEGMENT_ONE_MORE_SEGMENTreturn-1endlocaldata_len=tvb(offset,4):uint()ifremaining_len-4<data_lenthen-- data not enoughpinfo.desegment_offset=offsetpinfo.desegment_len=data_len-(remaining_len-4)return-1endsubtree:add(f_length,tvb(offset,4))ifmsgtype~=nilthenpinfo.private["pb_msg_type"]="message,"..msgtypeendpcall(Dissector.call,protobuf_dissector,tvb(offset+4,data_len):tvb(),pinfo,subtree)offset=offset+4+data_lenremaining_len=remaining_len-4-data_lenendendpinfo.columns.protocol:set(name)endiffor_udpthenDissectorTable.get("udp.port"):add_for_decode_as(proto)endiffor_tcpthenDissectorTable.get("tcp.port"):add_for_decode_as(proto)endreturnprotoend-- default pure protobuf udp and tcp dissector without message typecreate_protobuf_dissector("protobuf_udp","Protobuf UDP")create_protobuf_dissector("protobuf_tcp","Protobuf TCP")-- add more protobuf dissectors with message typescreate_protobuf_dissector("AddrBook","Tutorial AddressBook",true,true,"tutorial.AddressBook")end> 注意,你可以通过菜单 'About->Folders->Personal configuration',在 Wireshark 的 'About' 对话框中找到 'Personal configuration' 目录。
现在你可以清空 Protobuf 首选项中的 'Protobuf UDP Message Types' 表,然后重新打开 protobuf_udp_addressbook.pcapng 文件,并点击 'Decode As' 菜单选择 ADDRBOOK 协议:
该 UDP 数据包会被解析为 AddrBook 协议,该协议以 Protobuf 'tutorial.AddressBook' 消息作为根消息:
要测试 Protobuf TCP dissector,你可以打开 SampleCaptures 页面上的 protobuf_ tcp_ addressbook.pcapng,并点击 'Decode As' 菜单选择 ADDRBOOK 协议(位于 18127 TCP 端口)。4 字节长度字段和 AddressBook 消息将被解析为:
如果你有一个使用类似 'package.NewMessage' 根消息类型的新 Protobuf TCP dissector,并且每条消息也以 4 字节长度字段为前缀,那么你只需向 'create_protobuf_dissector.lua' 文件添加以下一行:
create_protobuf_dissector("NewProtocolName","New Protocol Name",true,true,"package.NewMessage")subdissector 可以在 "protobuf_field" dissector table 中注册自身,以解析字段的值。"protobuf_field" 表中记录的 key 是字段的完整名称。
例如,addressbook.proto 的 Person 消息类型中有一个 bytes 类型字段,用于携带肖像图像。我们可以将以下 Lua 脚本 'protobuf_portrait_field.lua' 放入 'Personal configuration' 目录的 'plugins' 子目录中:
dolocalprotobuf_field_table=DissectorTable.get("protobuf_field")localpng_dissector=Dissector.get("png")protobuf_field_table:add("tutorial.Person.portrait_image",png_dissector)end然后,打开 SampleCaptures 页面上的示例抓包文件 protobuf_udp_addressbook_with_image.pcapng,你会发现 'portait_image' Protobuf 字段被解析为 PNG 图像数据:
> 你可以在 Lua 中通过 pinfo.match_string,或在 C 代码中通过 pinfo->match_string 获取 subdissector 中的字段名称。
前文已经介绍了 'Protobuf Search Paths' 和 'Protobuf UDP Message Types' 表,还有一些其他首选项:
在 Wireshark 启动时加载 .proto 文件。默认情况下,.proto 文件仅在第一次调用 Protobuf dissector 时加载。这使得 tshark 可以通过在显示过滤器(-Y 选项)中提供 protobuf 字段名称,使用 'protobuf fields as wireshark fields' 功能。
如果启用此选项,Protobuf 消息和字段将被解析为 Wireshark 字段。所有这些 Wireshark 字段的名称都将以前缀 "pbf."(用于字段)或 "pbm."(用于消息)开头,后面跟随它们的完整名称。例如,你可以输入 'pbf.tutorial.Person.name == "Lily"' 作为显示过滤器,在前文提到的抓包文件中搜索包含名为 "Lily" 的人员的 protobuf 消息。
如果启用此选项,将显示消息和字段的详细信息,包括字段的 wire type 和 field number 格式、字段和 enum_value 的 value nodes 等。建议关闭此选项,因为显示过多细节可能会干扰你。
将所有 bytes 类型字段显示为 string。例如,ETCD 字符串定义为 bytes 类型,我们可以启用此选项来检查这类字段的字符串内容。
使未在线上序列化的 Protobuf 字段以默认值显示。默认值会根据以下情况显示:
1) Explicitly-declared default values in 'proto2', for example: `optional int32 result_per_page = 3 [default = 10]; // default value is 10` 2) For bools, the default value is false. 3) For enums, the default value is the first defined enum value, which must be 0 in 'proto3' (but allowed to be other in 'proto2'). 4) For numeric types, the default value is zero. There are no default values for fields 'repeated' or 'bytes' and 'string' without default value declared. If the missing field is 'required' in a 'proto2' file, a warning item will be added to the tree.这两个选项仅在无法在 'Protobuf Search Paths' 中找到消息类型或字段定义时使用。建议默认关闭它们。








