所谓插件技术,就是在程序的设计开发过程中,把整个应用程序分成宿主程序和插件两个部分,宿主程序与插件能够相互通信,并且,在宿主程序不变的情况下,可以通过增减插件或修改插件来调整应用程序的功能。运用插件技术可以开发出伸缩性良好、便于维护的应用程序。它著名的应用实例有:媒体播放器winamp、微软的网络浏览器ie等。
由于现在网络协议种类繁多,为了可以随时增加新的协议分析器,一般的协议分析器都采用插件技术,这样如果需要对一个新的协议分析只需要开发编写这个协议分析器并调用注册函数在系统注册就可以使用了。通过增加插件使程序有很强的可扩展性,各个功能模块内聚。
Wireshark由于采用插件技术,一个程序员开发一种新的协议分析模块的时候不需要了解所有的代码,仅知道要生成的插件dll需要提供两个对外的接口就可以了。它们分别是proto_register接口,作用是注册解析器的协议信息,另外一个是proto_reg_handoff接口,作用是用来注册解析器的解析句柄。在Wireshark中有一个脚本专门来发现开发者定义的类似proto_reg_handoff_xxx和proto_register_xxx这样的注册函数名,然后自动生成调用这些注册函数的代码。这样开发者不需要知道自己的注册函数如何被调用的。
下面来看两段代码,一个关于proto_register的实现,另一个是关于proto_reg_handoff的实现。
proto_register接口的实现:
static int proto_ge = -1;
void proto_register_ge(void)
{
module_t *ge_module;
proto_ge = proto_register_protocol(
"Global Eyes Protocol", /*name/
"GE", /*abbrv name*/
"ge" /*short name*/
);
/* register preference*/
ge_module = prefs_register_protocol(proto_ge, proto_reg_handoff_ge);
prefs_register_uint_preference(ge_module, "tcp.port",
"UAS listen port","Set the port for UAS",10, 5555);
prefs_register_bool_preference(ge_module, "reassemble_large_packet", "reassemble large packet",
"Set if reassemble large packet or not", &reassemble_large_packet);
prefs_register_uint_preference(ge_module, "max_show_ie",
"max count of ie array to display","Set max count of ie array to display",
10, &MAX_ARRAY_COUNT);
}
函数“proto_register_protocol”注册协议。这里要给协议起3个名字以适用不同的地方。全名和短名用在诸如首选项(Preferences)、已激活协议(Enabled protocols)对话框和记录中已生成的域名列表内,而缩略名则用于过滤器。静态整型变量proto_ge用于记录协议注册的信息,其具体表示在系统中该协议信息生成的ID值,它被初始化为-1,当解析器注册到主程序中后,其值便会得到更新。
prefs_register_protocol用于获得首选项的模块地址,以便将自定义的相关信息写入到首选项中。
proto_reg_handoff接口的实现:
写一个格式为proto_reg_handoff_ge()的函数,在函数内调用注册函数dissector_add告诉系统在什么时候需要调用这个协议模块。比如事先写好了一个名为dissect_ge的协议解析模块,它是用来解析tcp协议端口为5555的数据。可以利用这些语句来将这个解析器注册到系统中
void proto_reg_handoff_ge()
{
static int initialized=FALSE;
static int server_port=0;
if (initialized) {
if(server_port!=5555)
dissector_delete("tcp.port", 5555, ge_handle);
else
return;
} else {
initialized=TRUE;
}
ge_handle = create_dissector_handle(dissect_ge, proto_ge);
dissector_add("tcp.port", 5555, ge_handle);
}
这段代码告诉系统当tcp协议数据流端口为5555的时候要调用dissect_ge这个函数模块。
通过以上2个函数就把自定义的解析器传给了wireshark.接下来要实现的是解析函数dissect_ge。
解析函数dissect_ge:
为了完成解析函数,先假设要解析的包结构ge_header原型如下:
typedef struct ge_header
{
int magic;
int version;
};
解析函数原型定义参数列表如下:
static void dissect_ge(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
解析器添加节点可以用proto_tree_add_item函数,这里用以添加协议解析的总的根树节点。
static void dissect_ge( tvbuff_t *tvb,packet_info *pinfo,proto_tree *tree)
{
proto_item *ti=NULL;
if(tree)
{
ti=proto_tree_add_item(tree, proto_ge,tvb,0,-1,FALSE);
}
}
这里为解析器添加了根树。因为proto_ge 代表的是整个协议。proto_tree_add_item的第一个参数proto_tree,表示要将其添加到那个协议树下,回调函数dissect_ge从wireshark获得的proto_tree父节点的指针在正常情况下是不为空的,其指向整个帧的根节点。第二个参数是整型的协议信息ID值,用于标识节点信息,这里表示proto_ge所标示的注册信息,通过标示这个信息将在协议根节点树上显示Global Eyes Protocol。第三个参数是数据存储区,第四个参数是此信息的在数据区中起始地址,第五个参数是此信息的在数据区中长度(长度设为“-1”表示缓冲区内的全部数据),最后一个参数是字节序,“TRUE”表示“little endian”,“FALSE”表示“big endian” (尽管对于单字节数据无所谓字节顺序,但最好还是保持指定字节顺序的良好习惯)。这里的返回值ti是新创建项的指针。
做了这个更改之后,在包明细面板区中应该会出现一个针对该协议的标签;选择该标签后,在包字节面板区中包的内容就会高亮显示。
现在添加协议解析功能。在这一步需要构建一组帮助解析的表结构。首先定义一组静态数组。
static int hf_ge_magic =-1; //用于记录注册信息的ID
static int hf_ge_version =-1;
static gint ett_gehead =-1;//用于记录生成的节点ID
static hf_register_info结构用于标识节点的注册信息,对于每一个协议字段都需要对其进行注册
static hf_register_info hf[]=
{
{
{ &hf_ge_magic,
{ "Magic", "ge.magic", FT_UINT16, BASE_HEX, NULL, 0x0, "", HFILL }
},
{ &hf_ge_version,
{"Version", "ge.version",FT_UINT16, BASE_DEC, NULL, 0x0, "", HFILL }
}
}
};
ett[]用于标识根节点下要生成的子树节点ID的数组
static gint *ett[]=
{
&ett_gehead
};
接下来,在协议注册代码之后,对这些数组进行注册。这需要对proto_register_ge函数做些修改。
void proto_register_ge(void)
{
static gint *ett[]=
{
&ett_gehead
};
module_t *ge_module;
proto_ge = proto_register_protocol("
Global Eyes Protocol", /*name/
"GE", /*short name*/
"ge" /*abbrv name*/
);
proto_register_field_array(proto_ge,hf,array_length(hf));
proto_register_subtree_array(ett,array_length(ett));
/*用于注册生成每一个根节下的子节点域的ID数组,它并不代表真正的一个子节点,它仅仅代表节点域,但它可以用来生成一个子节点*/
/* register preference*/
ge_module = prefs_register_protocol(proto_ge, proto_reg_handoff_ge);
prefs_register_uint_preference(ge_module, "tcp.port",
"UAS listen port","Set the port for UAS",10, 5555);
prefs_register_bool_preference(ge_module, "reassemble_large_packet", "reassemble large packet",
"Set if reassemble large packet or not", &reassemble_large_packet);
prefs_register_uint_preference(ge_module, "max_show_ie",
"max count of ie array to display","Set max count of ie array to display",
10, &MAX_ARRAY_COUNT);
}
现在已经通过proto_register_subtree_array生成了节点ett_gehead的ID值,
接下来将用ett_gehead的ID来生成一个子树节点,继续在dissect_ge中添加代码:
static void dissect_ge( tvbuff_t *tvb,packet_info *pinfo,proto_tree *tree)
{
proto_tree * ge_tree;
proto_item *ti=NULL;
if(tree)
{
ti=proto_tree_add_item(tree, proto_ge,tvb,0,-1,FALSE);
ge_tree = proto_item_add_subtree(ti, ett_gehead);
}
}
proto_item_add_subtree函数返回一个子树,其在ett_gehead这个域下,解析字段时就可以把解析到的值添加到这个树上了。
对于每一个协议字段,都需要定义一个hf_register_info结构,并进行了注册。这里只需要把其位移传进去就可以了,修改的dissect_ge如下:
static void dissect_ge( tvbuff_t *tvb,packet_info *pinfo,proto_tree *tree)
{
proto_tree * ge_tree;
proto_item *ti=NULL;
//包信息结构参数“pinfo”包含了协议的基本数据,以供更新。下面的代码意思是检查包的协议,如果不是GE,则设置为GE
if (check_col(pinfo->cinfo, COL_PROTOCOL))
col_set_str(pinfo->cinfo, COL_PROTOCOL, "GE");
//如果包中的信息不为空,先清空
if(check_col(pinfo->cinfo,COL_INFO))
{
col_clear(pinfo->cinfo,COL_INFO);
}
if(tree)
{
ti=proto_tree_add_item(tree, proto_ge,tvb,0,-1,FALSE);
ge_tree = proto_item_add_subtree(ti, ett_gehead);
}
proto_tree_add_item(ge_tree, hf_ge_magic,tvb,0,4,FALSE);
proto_tree_add_item(ge_tree, hf_ge_version,tvb,4,8,FALSE);
}
这样一个简单的协议解析器就完成了。