wireshark进行协议解析的原理
1. 普通解析
Wireshark启动时,所有解析器进行初始化和注册。要注册的信息包括协议名称、各个字段的信息、过滤用的关键字、要关联的下层协议与端口(handoff)等。在解析过程,每个解析器负责解析自己的协议部分, 然后把上层封装数据传递给后续协议解析器,这样就构成一个完整的协议解析链条。
解析链条的最上端是Frame解析器,它负责解析pcap帧头。后续该调用哪个解析器,是通过上层协议注册handoff信息时写在当前协议的hash表来查找的。
例如,考虑ipv4解析器有一个hash表,里面存储的信息形如下表。当它解析完ipv4首部后,就可以根据得到的协议号字段,比如6,那么它就能从此hash表中找到后续解析器tcp。
协议号 | 解析器指针 |
6 | *tcp |
17 | *udp |
…… |
Wireshark中实际的解析表有3种,分别是字符串表,整数表和启发式解析表。如下图所示:
下面以ip协议为例,说明一下它的注册过程。
相关的重要数据结构与全局变量如下。
proto.c
/* Name hashtables for fast detection of duplicate names */
static GHashTable* proto_names = NULL;
static GHashTable* proto_short_names = NULL;
static GHashTable* proto_filter_names = NULL;
/** Register a new protocol.
@param name the full name of the new protocol
@param short_name abbreviated name of the new protocol
@param filter_name protocol name used for a display filter string
@return the new protocol handle */
int
proto_register_protocol(const char *name, const char *short_name, const char *filter_name);
三个全局的哈希表分别用于保存协议名称、协议缩略名和用于过滤器的协议名。
packet.c:
struct dissector_table {
GHashTable *hash_table;
GSList *dissector_handles;
const char *ui_name;
ftenum_t type;
int base;
};
static GHashTable *dissector_tables = NULL;
/*
* List of registered dissectors.
*/
static GHashTable *registered_dissectors = NULL;
static GHashTable *heur_dissector_lists = NULL;
/* Register a dissector by name. */
dissector_handle_t
register_dissector(const char *name, dissector_t dissector, const int proto);
/** A protocol uses this function to register a heuristic sub-dissector list.
* Call this in the parent dissectors proto_register function.
*
* @param name the name of this protocol
* @param list the list of heuristic sub-dissectors to be registered
*/
void register_heur_dissector_list(const char *name,
heur_dissector_list_t *list);
/* a protocol uses the function to register a sub-dissector table */
dissector_table_t register_dissector_table(const char *name, const char *ui_name, const ftenum_t type, const int base);
dissector_tables可以说是“哈希表的哈希表”,它以解析表名为键(如“ip.proto”),以dissector_table结构指针为值。在dissector_table中的哈希表以无符号数的指针为键(如协议号,为指针是glib hash表API的参数要求),以解析器handle为值;heur_dissector_lists是启发式解析相关的东西,这个问题留待以后研究;registered_dissectors是解析器哈希表,它以解析器名为键(如”ip”),以解析器句柄为值。
packet.h:
typedef struct dissector_table *dissector_table_t;
packet-ip.c:
static dissector_table_t ip_dissector_table;
proto_register_ip函数中:
proto_ip = proto_register_protocol("Internet Protocol Version 4", "IPv4", "ip");
...
/* subdissector code */
ip_dissector_table = register_dissector_table("ip.proto", "IP protocol", FT_UINT8, BASE_DEC);
register_heur_dissector_list("ip", &heur_subdissector_list);
...
register_dissector("ip", dissect_ip, proto_ip);
register_init_routine(ip_defragment_init);
ip_tap = register_tap("ip");
register_dissector_table这个函数在packet.c中,在此函数内,创建了名为“ip.proto”的哈希表。解析ip协议后,会查询这个表,找出下一个解析器,并将后续数据的解析移交给它。
packet-ip.c,dissect_ip函数内:
dissector_try_uint_new(ip_dissector_table, nxt, next_tvb, pinfo,
parent_tree, TRUE, iph)
packet.c:
/* Look for a given value in a given uint dissector table and, if found, call the dissector with the arguments supplied, and return TRUE, otherwise return FALSE. */
gboolean
dissector_try_uint_new(dissector_table_t sub_dissectors, const guint32 uint_val, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, const gboolean add_proto_name, void *data)
在dissector_try_uint_new函数中,会找到协议号对应的解析器句柄,并使用它解析其余数据。
2. 启发式(heuristic)解析
//TODO
3. 参考
Wireshark开发指南第6章"How wireshark works"