1. Wireshark對C插件的支持
每個解析器解碼自己的協議部分, 然后把封裝協議的解碼傳遞給后續協議。
因此它可能總是從一個Frame解析器開始, Frame解析器解析捕獲文件自己的數據包細節(如:時間戳), 將數據交給一個解碼Ethernet頭部的Ethernet frame解析器, 然后將載荷交給下一個解析器(如:IP), 如此等等. 在每一步, 數據包的細節會被解碼並顯示.
可以用兩種可能的方式實現協議解析. 一是寫一個解析器模塊, 編譯到主程序中, 這意味着它將永遠是可用的. 另一種方式是實現一個插件(共享庫/DLL), 它注冊自身用於處理解析。
插件形式和內置形式的解析器之間的差別很小. 在Windows平台, 通過列於libwireshark.def中的函數, 我們可以訪問有限的函數, 但它們幾乎已經夠用了.
比較大的好處是插件解析器的構建周期要遠小於內置. 因此以插件開始會使最初的開發工作變得簡單, 而最終代碼的布署會和內置解析器一樣。
另見 README.developer 文件doc/README.developer包含更多有關實現解析器(而且在某些情況下, 比本文檔要新一些)的信息.
2. 編譯構建C解析器
首先需要決定解析器是要以built-in方式,還是以plugin方式實現。plugin方式實現比較容易上手。
解析器初始化:
#include "config.h" #include <epan/packet.h> #define FOO_PORT 9877 static int proto_foo = -1; void proto_register_foo(void) { proto_foo = proto_register_protocol ( "FOO Protocol", /* name */ "FOO", /* short name */ "foo" /* abbrev */ ); }
首先include一些必需的頭文件。proto_foo用來記錄我們的協議,當將此解析器注冊到主程序時,它的值將會更新。把所有非外部使用的變量和函數聲明為static是一個好的編程實踐,可以避免名字空間污染。一般情況下這不是問題,除非我們的解析器非常大,分成了多個文件。
我們#define了協議的UDP端口FOO_PORT。
現在我們已經有了與主程序交互所需的基本東西了。接下來實現2個解析器構建函數(dissector setup functions)。
首先調用proto_register_protocol()函數來注冊協議。可以給它3個名字用來將來在不同的地方顯示。比如full和short name用於“Preferences”和“Enabled protocols”對話框。abbrev name用於顯示過濾器。
接下來我們需要handoff例程。
void proto_reg_handoff_foo(void) { static dissector_handle_t foo_handle; foo_handle = create_dissector_handle(dissect_foo, proto_foo); dissector_add_uint("udp.port", FOO_PORT, foo_handle); }
首先創建一個dissector handle,它和foo協議及執行實際解析工作的函數關聯。接下來將此handle與UDP端口號關聯,以便主程序在看到此端口上的UDP數據時調用我們的解析器。
標准wireshark解析器習慣是把proto_register_foo()和proto_reg_handoff_foo()做為解析器代碼的最后2個函數。
最后我們來編寫一些解析器代碼。目前將它做為基本的占位符。
static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); /* Clear out stuff in the info column */ col_clear(pinfo->cinfo,COL_INFO); }
此函數用於解析交給它的packets。packet數據放在名為tvb的特殊緩存中。對此隨着我們對協議細節了解的深入將會變得非常熟悉。packet_info結構包含有關協議的一般數據,我們應該在此更新信息。tree參數是細節解析發生的地方。
現在我們進行最小化的實現。第1行我們設置我們協議的文本,以示用戶可以看到協議被識別了。另外唯一做的事情是清除INFO列中的所有數據,如果它正在被顯示的話。
此時,我們已經准備好基本的解析器,可以進行編譯和安裝了。它什么也不做,除了識別協議並標識它。
為了編譯此解析器並創建插件,除了packet-foo.c中的源代碼,還有一堆必需的支持文件,它們是:
- Makefile.am - This is the UNIX/Linux makefile template
- CMakeLists.txt - 使用cmake編譯時所需的腳本
- Makefile.common - This contains the file names of this plugin
- Makefile.nmake - This contains the Wireshark plugin makefile for Windows
- moduleinfo.h - This contains plugin version info
- moduleinfo.nmake - This contains DLL version info for Windows
- packet-foo.h, packet-foo.c - This is your dissector source
- plugin.rc.in - This contains the DLL resource template for Windows
這些文件的例子可以在plugins/gryphon中找到,把所有與gryphon相關的東西都改成foo即可。plugin.rc.in不需要改動,windows編譯不需要的文件也不需要改動。
把以上文件准備好、修改好之后,cmd進入plugins/foo目錄,運行
nmake -f Makefile.nmake xxx
來進行編譯,就像編譯wireshark源碼一樣。編譯好之后生成foo.dll,將它拷貝到編譯好的wireshark的plugins目錄(可能會有中間目錄,視情況)。
還可以修改plugins目錄下面的Makefile.nmake文件,在PLUGIN_LIST中加入新插件的目錄名,這樣下次編譯wireshark時會一起編譯你的插件。

如果是在Mac OSX系統中編譯插件(CMake方式),需要修改主目錄下的CMakeLists.txt,搜索plugin字符串,找到set(PLUGIN_SRC_DIRS下面的行,在路徑中加入plugins/foo(plugins目錄的Makefile.am文件可能不需要修改,其中SUBDIRS項中列出了各插件的源碼目錄) ;然后如同之前文章所述,進入build目錄,執行make –j 6 plugins,即可編譯插件們。
然后啟動wireshark,打開Dissector Tables窗口,可以查到以下信息,說明wireshark已經正確加載我們的插件。

打開foo.pcap,效果如下圖所示,此時沒有協議解析樹,只在報文列表中添加了協議名:

3. 完善C解析器
接下來可以做一些復雜一點的解析工作。最簡單的事情是對載荷進行標記。
首先創建一個subtree用來放解析結果。這有助於在detailed display中更佳顯示。對解析器的調用有2種情況。一種情況用於獲取packet的摘要,另一種情況用於解析packet的細節。這兩種情況由tree指針的不同來區別。如果tree指針為NULL,用於獲取簡略信息。如果是非NULL,則需要解析協議的各個細部。記住這些后,讓我們來增強我們的解析器。
static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); /* Clear out stuff in the info column */ col_clear(pinfo->cinfo,COL_INFO); if (tree) { /* we are being asked for details */ proto_item *ti = NULL; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); } }
這里所做的是把一個subtree加入到解析中。此subtree會保存此協議的所有細節,且不會在不需要時弄亂顯示。
我們還可以標記被此協議所消費的數據區域。在目前的情況下,這統治是傳遞過來的所有數據,因為我們假定此協議不再封裝其他協議。因此,我們用proto_tree_add_item()往tree里添加新的節點,標識它的協議名,用tvb緩沖區做為數據,並消費此數據的0到最后1個字節(-1表示結束)。ENC_NA(not applicable)是編碼參數。
在這些改變之后,在detailed display中就會有此協議的標識,且選中它將會高亮此packet的剩余內容。如下圖所示:

現在,讓我們進行下一步,添加一些協議解析。這一步我們需要創建2個表來幫助解析。這需要在proto_register_foo()函數中添加一些代碼。
在proto_register_foo()的前面添加了2個static數組。這些數組在proto_register_protocol()調用之后被注冊。
void proto_register_foo(void) { static hf_register_info hf[] = { { &hf_foo_pdu_type, { "FOO PDU Type", "foo.type", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } } }; /* Setup protocol subtree array */ static gint *ett[] = { &ett_foo }; proto_foo = proto_register_protocol ( "FOO Protocol", /* name */ "FOO", /* short name */ "foo" /* abbrev */ ); proto_register_field_array(proto_foo, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); }
變量hf_foo_pdu_type和ett_foo也需要在此文件的前面聲明。
static int hf_foo_pdu_type = -1; static gint ett_foo = -1;
現在我們可以用一些細節來增加協議的顯示。
if (tree) { /* we are being asked for details */ proto_item *ti = NULL; proto_tree *foo_tree = NULL; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, 0, 1, ENC_BIG_ENDIAN); }
現在解析開始看起來更加有趣了。我們開始破解此協議的第1個比特。packet起始處的一個字節數據定義了foo協議的packet type。
proto_item_add_subtree()調用往協議樹中增加了一個子節點。此節點的展開是由ett_foo變量控制的。它會記住節點是否應該展開,在你在packet中移動的時候。所有后續的解析會添加到此樹中,就像在接下來的調用中看到的那樣。proto_tree_add_item向foo_tree添加了新項,並用hf_foo_pdu_type來控制此項的格式。pdu type是1個字節的數據,從0開始。我們假定它是網絡字節序(也叫big endian),因此用ENC_BIG_ENDIAN。對於1個字節的數來說,沒用字節序之說,但這是好的編程實踐。
我們來看static數組中的定義細節:
- hf_foo_pdu_type - 此節點的索引
- FOO PDU Type - 此項的標識
- foo.type - 過濾用的字符串。它使我們可以在過濾器框中輸入foo.type=1的語句
- FT_UINT8 - 指出此項是一個8bit的無符號整數。
- BASE_DEC - 對於整型來說,它令其打印為一個10進制數。還可以是16進制(BASE_HEX)或8進制(BASE_OCT)。
我們目前忽略結構中的其余成員。
如果此時編譯並安裝此插件,我們會看到它開始顯示一些看起來有用的東西。
現在我們來完成這個簡單協議的解析。我們需要添加更多的變量在hf數組中,以及更多的函數調用。
static int hf_foo_flags = -1; static int hf_foo_sequenceno = -1; static int hf_foo_initialip = -1; ... static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { gint offset = 0; ... if (tree) { /* we are being asked for details */ proto_item *ti = NULL; proto_tree *foo_tree = NULL; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(foo_tree, hf_foo_sequenceno, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; proto_tree_add_item(foo_tree, hf_foo_initialip, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; } ... } void proto_register_foo(void) { ... ... { &hf_foo_flags, { "FOO PDU Flags", "foo.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_foo_sequenceno, { "FOO PDU Sequence Number", "foo.seqn", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_foo_initialip, { "FOO PDU Initial IP", "foo.initialip", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL } }, ... ... } ...

再修改一些細節,比如flag的位顯示方式、foo協議樹子節點字符串,packet列表中Info列的顯示等等,最后效果如下:

4. 完整代碼
/* packet-foo.c * Routines for Foo protocol packet disassembly * By zzq */ #include "config.h" #include <epan/packet.h> #include <epan/prefs.h> //#include <epan/dissectors/packet-tcp.h> #include "packet-foo.h" #define FOO_PORT 9877 #define FOO_NAME "Foo Protocol" #define FOO_SHORT_NAME "Foo" #define FOO_ABBREV "foo" static int proto_foo = -1; static int hf_foo_pdu_type = -1; static int hf_foo_flags = -1; static int hf_foo_seqno = -1; static int hf_foo_ip = -1; static gint ett_foo = -1; static const value_string pkt_type_names[] = { {1, "Initilize"}, {2, "Terminate"}, {3, "Data"}, {0, NULL} }; #define FOO_START_FLAG 0x01 #define FOO_END_FLAG 0x02 #define FOO_PRIOR_FLAG 0x04 static int hf_foo_start_flag = -1; static int hf_foo_end_flag = -1; static int hf_foo_prior_flag = -1; void proto_register_foo(void); void proto_reg_handoff_foo(void); static int dissect_foo(tvbuff_t*, packet_info*, proto_tree*, void*); void proto_register_foo(void) { static hf_register_info hf[] = { { &hf_foo_pdu_type, { "Type", "foo.type", FT_UINT8, BASE_DEC, VALS(pkt_type_names), 0x0, NULL, HFILL } }, { &hf_foo_flags, { "Flags", "foo.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_foo_start_flag, { "Start Flag", "foo.flags.start", FT_BOOLEAN, 8, NULL, FOO_START_FLAG, NULL, HFILL } }, { &hf_foo_end_flag, { "End Flag", "foo.flags.end", FT_BOOLEAN, 8, NULL, FOO_END_FLAG, NULL, HFILL } }, { &hf_foo_prior_flag, { "Priority Flag", "foo.flags.prior", FT_BOOLEAN, 8, NULL, FOO_PRIOR_FLAG, NULL, HFILL } }, { &hf_foo_seqno, { "Sequence Number", "foo.seq", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_foo_ip, { "IP Address", "foo.ip", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL } } }; static gint *ett[] = { &ett_foo }; proto_foo = proto_register_protocol ( FOO_NAME, FOO_SHORT_NAME, FOO_ABBREV); proto_register_field_array(proto_foo, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); } static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) { guint8 packet_type = tvb_get_guint8(tvb, 0); col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); /* Clear out stuff in the info column */ col_clear(pinfo->cinfo,COL_INFO); col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s", val_to_str(packet_type, pkt_type_names, "Unknown (0x%02x)")); /* proto details display */ if(tree) { proto_item* ti = NULL; proto_tree* foo_tree = NULL; gint offset = 0; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); proto_item_append_text(ti, ", Type %s", val_to_str(packet_type, pkt_type_names, "Unknown (0x%02x)")); foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, ENC_BIG_ENDIAN); proto_tree_add_item(foo_tree, hf_foo_start_flag, tvb, offset, 1, ENC_BIG_ENDIAN); proto_tree_add_item(foo_tree, hf_foo_end_flag, tvb, offset, 1, ENC_BIG_ENDIAN); proto_tree_add_item(foo_tree, hf_foo_prior_flag, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(foo_tree, hf_foo_seqno, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; proto_tree_add_item(foo_tree, hf_foo_ip, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; } return tvb_reported_length(tvb); } void proto_reg_handoff_foo(void) { static dissector_handle_t foo_handle; foo_handle = new_create_dissector_handle(dissect_foo, proto_foo); dissector_add_uint("udp.port", FOO_PORT, foo_handle); }
