:::tips 此文档较长, 为提升加载速度, 分为 2 部分, 第 1 部分见:
https://www.yuque.com/zzqcn/wireshark/vagcvf :::


2 高级解析器主题

原标题: Advanced dissector topics

2.1 介绍

一些高级特性正在持续进行开发, 当用到这些特性时最好是查阅相应的头文件和源码以获取详情.

2.2 跟踪会话

原标题: Following “conversations”

在 Wireshark 中, 会话(conversation)定义为两个地址:端口之间的一系列报文. 会话对报文的方向不敏感. 对于从 ServerA:1000 -> ClientA:2000 的报文和从 ClientA:2000 -> ServerA:1000 的报文, 将返回相同的会话.

2.2.1 Conversation Routines

有 7 个会话相关的函数接口:
conversation_new, find_conversation, find_or_create_conversation, conversation_add_proto_data, conversation_get_proto_data, conversation_delete_proto_data, and conversation_set_dissector.

2.2.1.1 conversation_init

这是一个内部函数, 因此你不需要调用此函数, 但应注意它会在每次抓包开始时, 以及过滤报文前被调用. 此函数会销毁所有已保存的会话, 但不会清理传入 conversation_add_proto_data 函数的 data 指针. 如果你传入了一个 malloc 分配的指针, 那么你需要负责清理它.

有关 data 指针的用法见 2.2.1.5.

2.2.1.2 conversation_new

此函数创建一个基于 2 组地址/端口对的新会话. 如果需要将会话和私有数据结构关联, 必须调用 conversation_add_proto_data 函数. ptype 参数用于区分不同协议的会话, 即 TCP 和 UDP. options 参数用于定义会话接受任意目的地址和/或端口. options = 0 表示此时已知道目的地址和端口. options 参数的更多用法见 2.4.

conversation_new 原型:

  1. conversation_t *conversation_new(guint32 setup_frame, address *addr1,
  2. address *addr2, port_type ptype, guint32 port1, guint32 port2,
  3. guint options);

Where:

  • guint32 setup_frame = The lowest numbered frame for this conversation
  • address* addr1 = first data packet address
  • address* addr2 = second data packet address
  • port_type ptype = port type, this is defined in packet.h
  • guint32 port1 = first data packet port
  • guint32 port2 = second data packet port
  • guint options = conversation options, NO_ADDR2 and/or NO_PORT2

setup_frame 表示此会话首帧的编号, 它用于在同一个抓包 session 中区分 addr1/port1 和 addr2/port2 相同的多个会话.

“addr1” and “port1” are the first address/port pair; “addr2” and “port2” are the second address/port pair. A conversation doesn’t have source and destination address/port pairs - packets in a conversation go in both directions - so “addr1”/“port1” may be the source or destination address/port pair; “addr2”/“port2” would be the other pair.

If NO_ADDR2 is specified, the conversation is set up so that a conversation lookup will match only the “addr1” address; if NO_PORT2 is specified, the conversation is set up so that a conversation lookup will match only the “port1” port; if both are specified, i.e. NO_ADDR2|NO_PORT2, the conversation is set up so that the lookup will match only the “addr1”/“port1” address/port pair. This can be used if a packet indicates that, later in the capture, a conversation will be created using certain addresses and ports, in the case where the packet doesn’t specify the addresses and ports of both sides.

2.2.1.3 find_conversation

Call this routine to look up a conversation. If no conversation is found, the routine will return a NULL value.

The find_conversation prototype:

  1. conversation_t *find_conversation(guint32 frame_num, address *addr_a,
  2. address *addr_b, port_type ptype, guint32 port_a, guint32 port_b,
  3. guint options);

Where:
guint32 frame_num = a frame number to match
address addr_a = first address
address
addr_b = second address
port_type ptype = port type
guint32 port_a = first data packet port
guint32 port_b = second data packet port
guint options = conversation options, NO_ADDR_B and/or NO_PORT_B

frame_num is a frame number to match. The conversation returned is where
(frame_num >= conversation->setup_frame
&& frame_num < conversation->next->setup_frame)
Suppose there are a total of 3 conversations (A, B, and C) that match addr_a/port_a and addr_b/port_b, where the setup_frame used in conversation_new() for A, B and C are 10, 50, and 100 respectively. The frame_num passed in find_conversation is compared to the setup_frame of each conversation. So if (frame_num >= 10 && frame_num < 50), conversation A is returned. If (frame_num >= 50 && frame_num < 100), conversation B is returned. If (frame_num >= 100) conversation C is returned.

“addr_a” and “port_a” are the first address/port pair; “addr_b” and “port_b” are the second address/port pair. Again, as a conversation doesn’t have source and destination address/port pairs, so “addr_a”/“port_a” may be the source or destination address/port pair; “addr_b”/“port_b” would be the other pair. The search will match the “a” address/port pair against both the “1” and “2” address/port pairs, and match the “b” address/port pair against both the “2” and “1” address/port pairs; you don’t have to worry about which side the “a” or “b” pairs correspond to.

If the NO_ADDR_B flag was specified to “find_conversation()”, the “addr_b” address will be treated as matching any “wildcarded” address; if the NO_PORT_B flag was specified, the “port_b” port will be treated as matching any “wildcarded” port. If both flags are specified, i.e. NO_ADDR_B|NO_PORT_B, the “addr_b” address will be treated as matching any “wildcarded” address and the “port_b” port will be treated as matching any “wildcarded” port.

2.2.1.4 find_conversation_pinfo

This convenience function will find an existing conversation (by calling find_conversation())

The find_conversation_pinfo prototype:

  1. extern conversation_t *find_conversation_pinfo(packet_info *pinfo, const guint options);

Where:
packet_info *pinfo = the packet_info structure
const guint options = conversation options, NO_ADDR_B and/or NO_PORT_B

The frame number and the addresses necessary for find_conversation() are taken from the pinfo structure (as is commonly done).

2.2.1.5 find_or_create_conversation

This convenience function will find an existing conversation (by calling find_conversation()) and, if a conversation does not already exist, create a new conversation by calling conversation_new().

The find_or_create_conversation prototype:

  1. extern conversation_t *find_or_create_conversation(packet_info *pinfo);

Where:
packet_info *pinfo = the packet_info structure

The frame number and the addresses necessary for find_conversation() and conversation_new() are taken from the pinfo structure (as is commonly done) and no ‘options’ are used.

2.2.1.6 conversation_add_proto_data

通过 conversation_new 创建会话后, 可以用此函数将某数据和它关联.

conversation_add_proto_data 原型:

  1. void conversation_add_proto_data(conversation_t *conv, int proto,
  2. void *proto_data);

Where:
conversation_t conv = the conversation in question
int proto = registered protocol number
void
data = dissector data structure

“conversation” is the value returned by conversation_new. “proto” is a unique protocol number created with proto_register_protocol. Protocols are typically registered in the proto_register_XXXX section of your dissector. “data” is a pointer to the data you wish to associate with the conversation. “data” usually points to “wmem_alloc’d” memory; the memory will be automatically freed each time a new dissection begins and thus need not be managed (freed) by the dissector. Using the protocol number allows several dissectors to associate data with a given conversation.

2.2.1.7 conversation_get_proto_data

After you have located a conversation with find_conversation, you can use this function to retrieve any data associated with it.

The conversation_get_proto_data prototype:

  1. void *conversation_get_proto_data(conversation_t *conv, int proto);

Where:
conversation_t *conv = the conversation in question
int proto = registered protocol number

“conversation” is the conversation created with conversation_new. “proto” is a unique protocol number created with proto_register_protocol, typically in the proto_register_XXXX portion of a dissector. The function returns a pointer to the data requested, or NULL if no data was found.

2.2.1.8 conversation_delete_proto_data

After you are finished with a conversation, you can remove your association with this function. Please note that ONLY the conversation entry is removed. If you have allocated any memory for your data (other than with wmem_alloc), you must free it as well.

The conversation_delete_proto_data prototype:

  1. void conversation_delete_proto_data(conversation_t *conv, int proto);

Where:
conversation_t *conv = the conversation in question
int proto = registered protocol number

“conversation” is the conversation created with conversation_new. “proto” is a unique protocol number created with proto_register_protocol, typically in the proto_register_XXXX portion of a dissector.

2.2.1.9 conversation_set_dissector

可用此函数为会话设置一个协议解析器, 从而当会话参数(address, port_type, port等)在报文解析阶段匹配时, 即调用此解析器.

conversation_set_dissector 原型:

  1. void conversation_set_dissector(conversation_t *conversation,
  2. const dissector_handle_t handle);

Where:
conversation_t *conv = the conversation in question
const dissector_handle_t handle = the dissector handle.

zzqcn 示例:

  1. // epan/dissectors/packet-ftp.c, create_and_link_data_conversation()
  2. /* Create data conversation and set dissector */
  3. ftp_data_conversation_t *p_ftp_data_conv;
  4. conversation_t *data_conversation = conversation_new(pinfo->num,
  5. addr_a, addr_b, ENDPOINT_TCP, port_a, port_b, NO_PORT2);
  6. conversation_set_dissector(data_conversation, ftpdata_handle);
  7. // epan/dissectors/packet-tcp.c, decode_tcp_ports()
  8. if (try_conversation_dissector(&pinfo->src, &pinfo->dst, ENDPOINT_TCP,
  9. src_port, dst_port, next_tvb, pinfo, tree, tcpinfo, 0)) {
  10. pinfo->want_pdu_tracking -= !!(pinfo->want_pdu_tracking);
  11. handle_export_pdu_conversation(pinfo, next_tvb, src_port, dst_port, tcpinfo);
  12. return TRUE;
  13. }

zzqcn ftp 解析器在通过控制连接解析出接下来的数据连接会话时, 先创建数据连接的会话, 并为其关联 ftpdata 解析器; tcp 解析器在遇到上一步 ftp 数据连接会话对应的报文时, 就可调用 try_conversation_dissector, 最终调用到 ftpdata 解析器.

2.2.2 使用会话相关的时间戳

原标题: Using timestamps relative to the conversation

有一个框架用于计算相对于会话开始的时间戳. 首先首包的时间戳必须保存在协议数据中, 以便计算当前报文的相对时间戳. 末包的时间戳也需要保存在协议数据对. 这样会话中当前报文与前一报文的时间差值就可以被计算出来.

So add the following items to the struct that is used for the protocol data:

nstime_t ts_first; nstime_t ts_prev;

The ts_prev value should only be set during the first run through the packets (ie PINFO_FD_VISITED(pinfo) is false).

zzqcn 这两个时间值保存的示例, 可见 tcp 和 udp 对应的协议分析数据结构, 如 struct udp_analysis 的成员.

Next step is to use the per-packet information (described in section 2.5) to keep the calculated delta timestamp, as it can only be calculated on the first run through the packets. This is because a packet can be selected in random order once the whole file has been read.

After calculating the conversation timestamps, it is time to put them in the appropriate columns with the function ‘col_set_time’ (described in section 1.5.9). The column used for relative timestamps is:

COL_REL_TIME, / Delta time to last frame in conversation /

Last but not least, there MUST be a preference in each dissector that uses conversation timestamps that makes it possible to enable and disable the calculation of conversation timestamps. The main argument for this is that a higher level conversation is able to overwrite the values of lower level conversations in these two columns. Being able to actively select which protocols may overwrite the conversation timestamp columns gives the user the power to control these columns. (A second reason is that conversation timestamps use the per-packet data structure which uses additional memory, which should be avoided if these timestamps are not needed)

Have a look at the differences to packet-tcp.[ch] in SVN 22966 and SVN 23058 to see the implementation of conversation timestamps for the tcp-dissector.

2.2.3 使用wmem_file_scope的会话代码示例

原标题: The example conversation code using wmem_file_scope memory

For a conversation between two IP addresses and ports you can use this as an example. This example uses wmem_alloc() with wmem_file_scope() to allocate memory and stores the data pointer in the conversation ‘data’ variable.

  1. /************************ Global values ************************/
  2. /* define your structure here */
  3. typedef struct {
  4. } my_entry_t;
  5. /* Registered protocol number */
  6. static int my_proto = -1;
  7. /********************* in the dissector routine *********************/
  8. /* the local variables in the dissector */
  9. conversation_t *conversation;
  10. my_entry_t *data_ptr;
  11. /* look up the conversation */
  12. conversation = find_conversation(pinfo->num, &pinfo->src, &pinfo->dst,
  13. pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
  14. /* if conversation found get the data pointer that you stored */
  15. if (conversation)
  16. data_ptr = (my_entry_t*)conversation_get_proto_data(conversation, my_proto);
  17. else {
  18. /* new conversation create local data structure */
  19. data_ptr = wmem_alloc(wmem_file_scope(), sizeof(my_entry_t));
  20. /*** add your code here to setup the new data structure ***/
  21. /* create the conversation with your data pointer */
  22. conversation = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, pinfo->ptype,
  23. pinfo->srcport, pinfo->destport, 0);
  24. conversation_add_proto_data(conversation, my_proto, (void *)data_ptr);
  25. }
  26. /* at this point the conversation data is ready */
  27. /***************** in the protocol register routine *****************/
  28. my_proto = proto_register_protocol("My Protocol", "My Protocol", "my_proto");

2.2.4 开始于特定帧编号的会话代码示例

原标题: An example conversation code that starts at a specific frame number

Sometimes a dissector has determined that a new conversation is needed that starts at a specific frame number, when a capture session encompasses multiple conversation that reuse the same src/dest ip/port pairs. You can use the conversation->setup_frame returned by find_conversation with pinfo->num to determine whether or not there already exists a conversation that starts at the specific frame number.

  1. /* in the dissector routine */
  2. conversation = find_conversation(pinfo->num, &pinfo->src, &pinfo->dst,
  3. pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
  4. if (conversation == NULL || (conversation->setup_frame != pinfo->num)) {
  5. /* It's not part of any conversation or the returned
  6. * conversation->setup_frame doesn't match the current frame
  7. * create a new one.
  8. */
  9. conversation = conversation_new(pinfo->num, &pinfo->src,
  10. &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport,
  11. NULL, 0);
  12. }

2.2.5 使用会话索引字段的会话代码示例

原标题: The example conversation code using conversation index field

Sometimes the conversation isn’t enough to define a unique data storage value for the network traffic. For example if you are storing information about requests carried in a conversation, the request may have an
identifier that is used to define the request. In this case the conversation and the identifier are required to find the data storage pointer. You can use the conversation data structure index value to uniquely define the conversation.

See packet-afs.c for an example of how to use the conversation index. In this dissector multiple requests are sent in the same conversation. To store information for each request the dissector has an internal hash table based upon the conversation index and values inside the request packets.

  1. /* in the dissector routine */
  2. /* to find a request value, first lookup conversation to get index */
  3. /* then used the conversation index, and request data to find data */
  4. /* in the local hash table */
  5. conversation = find_or_create_conversation(pinfo);
  6. request_key.conversation = conversation->index;
  7. request_key.service = pntoh16(&rxh->serviceId);
  8. request_key.callnumber = pntoh32(&rxh->callNumber);
  9. request_val = (struct afs_request_val *)g_hash_table_lookup(
  10. afs_request_hash, &request_key);
  11. /* only allocate a new hash element when it's a request */
  12. opcode = 0;
  13. if (!request_val && !reply)
  14. {
  15. new_request_key = wmem_alloc(wmem_file_scope(), sizeof(struct afs_request_key));
  16. *new_request_key = request_key;
  17. request_val = wmem_alloc(wmem_file_scope(), sizeof(struct afs_request_val));
  18. request_val -> opcode = pntoh32(&afsh->opcode);
  19. opcode = request_val->opcode;
  20. g_hash_table_insert(afs_request_hash, new_request_key,
  21. request_val);
  22. }

2.3 动态会话解析器注册

原标题: Dynamic conversation dissector registration

NOTE: This sections assumes that all information is available to create a complete conversation, source port/address and destination port/address. If either the destination port or address is known, see section 2.4 Dynamic server port dissector registration.

For protocols that negotiate a secondary port connection, for example packet-msproxy.c, a conversation can install a dissector to handle the secondary protocol dissection. After the conversation is created for the negotiated ports use the conversation_set_dissector to define the dissection routine.

Before we create these conversations or assign a dissector to them we should first check that the conversation does not already exist and if it exists whether it is registered to our protocol or not.

We should do this because it is uncommon but it does happen that multiple different protocols can use the same socketpair during different stages of an application cycle. By keeping track of the frame number a conversation was started in Wireshark can still tell these different protocols apart.

The second argument to conversation_set_dissector is a dissector handle, which is created with a call to create_dissector_handle or register_dissector.

create_dissector_handle takes as arguments a pointer to the dissector function and a protocol ID as returned by proto_register_protocol; register_dissector takes as arguments a string giving a name for the dissector, a pointer to the dissector function, and a protocol ID.

The protocol ID is the ID for the protocol dissected by the function. The function will not be called if the protocol has been disabled by the user; instead, the data for the protocol will be dissected as raw data.

An example -

  1. /* the handle for the dynamic dissector *
  2. static dissector_handle_t sub_dissector_handle;
  3. /* prototype for the dynamic dissector */
  4. static void sub_dissector(tvbuff_t *tvb, packet_info *pinfo,
  5. proto_tree *tree);
  6. /* in the main protocol dissector, where the next dissector is setup */
  7. /* if conversation has a data field, create it and load structure */
  8. /* First check if a conversation already exists for this
  9. socketpair
  10. */
  11. conversation = find_conversation(pinfo->num,
  12. &pinfo->src, &pinfo->dst, protocol,
  13. src_port, dst_port, 0);
  14. /* If there is no such conversation, or if there is one but for
  15. someone else's protocol then we just create a new conversation
  16. and assign our protocol to it.
  17. */
  18. if ( (conversation == NULL) ||
  19. (conversation->dissector_handle != sub_dissector_handle) ) {
  20. new_conv_info = wmem_alloc(wmem_file_scope(), sizeof(struct _new_conv_info));
  21. new_conv_info->data1 = value1;
  22. /* create the conversation for the dynamic port */
  23. conversation = conversation_new(pinfo->num,
  24. &pinfo->src, &pinfo->dst, protocol,
  25. src_port, dst_port, new_conv_info, 0);
  26. /* set the dissector for the new conversation */
  27. conversation_set_dissector(conversation, sub_dissector_handle);
  28. }
  29. ...
  30. void
  31. proto_register_PROTOABBREV(void)
  32. {
  33. ...
  34. sub_dissector_handle = create_dissector_handle(sub_dissector,
  35. proto);
  36. ...
  37. }

2.4 动态server port解析器注册

原标题: Dynamic server port dissector registration

NOTE: While this example used both NO_ADDR2 and NO_PORT2 to create a conversation with only one port and address set, this isn’t a requirement. Either the second port or the second address can be set when the conversation is created.

For protocols that define a server address and port for a secondary protocol, a conversation can be used to link a protocol dissector to the server port and address. The key is to create the new conversation with the second address and port set to the “accept any” values.

Some server applications can use the same port for different protocols during different stages of a transaction. For example it might initially use SNMP to perform some discovery and later switch to use TFTP using the same port. In order to handle this properly we must first check whether such a conversation already exists or not and if it exists we also check whether the registered dissector_handle for that conversation is “our” dissector or not.

If not we create a new conversation on top of the previous one and set this new conversation to use our protocol.
Since Wireshark keeps track of the frame number where a conversation started wireshark will still be able to keep the packets apart even though they do use the same socketpair.
(See packet-tftp.c and packet-snmp.c for examples of this)

There are two support routines that will allow the second port and/or address to be set later.

  1. conversation_set_port2( conversation_t *conv, guint32 port);
  2. conversation_set_addr2( conversation_t *conv, address addr);

These routines will change the second address or port for the conversation. So, the server port conversation will be converted into a more complete conversation definition. Don’t use these routines if you want to create a conversation between the server and client and retain the server port definition, you must create a new conversation.

An example -

  1. /* the handle for the dynamic dissector *
  2. static dissector_handle_t sub_dissector_handle;
  3. ...
  4. /* in the main protocol dissector, where the next dissector is setup */
  5. /* if conversation has a data field, create it and load structure */
  6. new_conv_info = wmem_alloc(wmem_file_scope(), sizeof(struct _new_conv_info));
  7. new_conv_info->data1 = value1;
  8. /* create the conversation for the dynamic server address and port */
  9. /* NOTE: The second address and port values don't matter because the */
  10. /* NO_ADDR2 and NO_PORT2 options are set. */
  11. /* First check if a conversation already exists for this
  12. IP/protocol/port
  13. */
  14. conversation = find_conversation(pinfo->num,
  15. &server_src_addr, 0, protocol,
  16. server_src_port, 0, NO_ADDR2 | NO_PORT_B);
  17. /* If there is no such conversation, or if there is one but for
  18. someone else's protocol then we just create a new conversation
  19. and assign our protocol to it.
  20. */
  21. if ( (conversation == NULL) ||
  22. (conversation->dissector_handle != sub_dissector_handle) ) {
  23. conversation = conversation_new(pinfo->num,
  24. &server_src_addr, 0, protocol,
  25. server_src_port, 0, new_conv_info, NO_ADDR2 | NO_PORT2);
  26. /* set the dissector for the new conversation */
  27. conversation_set_dissector(conversation, sub_dissector_handle);
  28. }

2.5 单包信息

原标题: Per-packet information

Information can be stored for each data packet that is processed by the dissector. The information is added with the p_add_proto_data function and retrieved with the p_get_proto_data function. The data pointers passed into the p_add_proto_data are not managed by the proto_data routines, however the data pointer memory scope must match that of the scope parameter.

The two most common use cases for p_add_proto_data/p_get_proto_data are for persistent data about the packet for the lifetime of the capture (file scope) and to exchange data between dissectors across a single packet (packet scope). It is also used to provide packet data for Decode As dialog (packet scope).

These functions are declared in .

  1. void
  2. p_add_proto_data(wmem_allocator_t *scope, packet_info *pinfo, int proto, guint32 key, void *proto_data);
  3. void *
  4. p_get_proto_data(wmem_allocator_t *scope, packet_info *pinfo, int proto, guint32 key);

Where:
scope - Lifetime of the data to be stored, typically wmem_file_scope()
or pinfo->pool (packet scope). Must match scope of data
allocated.
pinfo - The packet info pointer.
proto - Protocol id returned by the proto_register_protocol call
during initialization
key - key associated with ‘proto_data’
proto_data - pointer to the dissector data.

2.6 用户偏好设置

原标题: User Preferences

If the dissector has user options, there is support for adding these preferences to a configuration dialog.

You must register the module with the preferences routine with

  1. module_t *prefs_register_protocol(proto_id, void (*apply_cb)(void));
  2. // or
  3. module_t *prefs_register_protocol_subtree(const char *subtree, int id, void (*apply_cb)(void));

Where:

  • proto_id - the value returned by “proto_register_protocol()” when the protocol was registered.
  • apply_cb - Callback routine that is called when preferences are applied. It may be NULL, which inhibits the callback.
  • subtree - grouping preferences tree node name (several protocols can be grouped under one preferences subtree)

Then you can register the fields that can be configured by the user with these routines -

  1. /* Register a preference with an unsigned integral value. */
  2. void prefs_register_uint_preference(module_t *module, const char *name,
  3. const char *title, const char *description, guint base, guint *var);
  4. /* Register a preference with an Boolean value. */
  5. void prefs_register_bool_preference(module_t *module, const char *name,
  6. const char *title, const char *description, gboolean *var);
  7. /* Register a preference with an enumerated value. */
  8. void prefs_register_enum_preference(module_t *module, const char *name,
  9. const char *title, const char *description, gint *var,
  10. const enum_val_t *enumvals, gboolean radio_buttons)
  11. /* Register a preference with a character-string value. */
  12. void prefs_register_string_preference(module_t *module, const char *name,
  13. const char *title, const char *description, char **var)
  14. /* Register a preference with a file name (string) value.
  15. * File name preferences are basically like string preferences
  16. * except that the GUI gives the user the ability to browse for the
  17. * file. Set for_writing TRUE to show a Save dialog instead of normal Open.
  18. */
  19. void prefs_register_filename_preference(module_t *module, const char *name,
  20. const char *title, const char *description, char **var,
  21. gboolean for_writing)
  22. /* Register a preference with a range of unsigned integers (e.g.,
  23. * "1-20,30-40").
  24. */
  25. void prefs_register_range_preference(module_t *module, const char *name,
  26. const char *title, const char *description, range_t *var,
  27. guint32 max_value)

Where: module - Returned by the prefs_register_protocol routine
name - This is appended to the name of the protocol, with a
“.” between them, to construct a name that identifies
the field in the preference file; the name itself
should not include the protocol name, as the name in
the preference file will already have it. Make sure that
only lower-case ASCII letters, numbers, underscores and
dots appear in the preference name.
title - Field title in the preferences dialog
description - Comments added to the preference file above the
preference value and shown as tooltip in the GUI, or NULL
var - pointer to the storage location that is updated when the
field is changed in the preference dialog box. Note that
with string preferences the given pointer is overwritten
with a pointer to a new copy of the string during the
preference registration. The passed-in string may be
freed, but you must keep another pointer to the string
in order to free it.
base - Base that the unsigned integer is expected to be in,
see strtoul(3).
enumvals - an array of enum_val_t structures. This must be
NULL-terminated; the members of that structure are:

  1. a short name, to be used with the "-o" flag - it<br /> should not contain spaces or upper-case letters,<br /> so that it's easier to put in a command line;
  2. a description, which is used in the GUI (and<br /> which, for compatibility reasons, is currently<br /> what's written to the preferences file) - it can<br /> contain spaces, capital letters, punctuation,<br /> etc.;
  3. the numerical value corresponding to that name<br /> and description<br /> radio_buttons - TRUE if the field is to be displayed in the<br /> preferences dialog as a set of radio buttons,<br /> FALSE if it is to be displayed as an option<br /> menu<br /> max_value - The maximum allowed value for a range (0 is the minimum).

These functions are declared in .

An example from packet-rtpproxy.c -

  1. proto_rtpproxy = proto_register_protocol ( "Sippy RTPproxy Protocol", "RTPproxy", "rtpproxy");
  2. ...
  3. rtpproxy_module = prefs_register_protocol(proto_rtpproxy, proto_reg_handoff_rtpproxy);
  4. prefs_register_bool_preference(rtpproxy_module, "establish_conversation",
  5. "Establish Media Conversation",
  6. "Specifies that RTP/RTCP/T.38/MSRP/etc streams are decoded based "
  7. "upon port numbers found in RTPproxy answers",
  8. &rtpproxy_establish_conversation);
  9. prefs_register_uint_preference(rtpproxy_module, "reply.timeout",
  10. "RTPproxy reply timeout", /* Title */
  11. "Maximum timeout value in waiting for reply from RTPProxy (in milliseconds).", /* Descr */
  12. 10,
  13. &rtpproxy_timeout);

This will create preferences “rtpproxy.establish_conversation” and “rtpproxy.reply.timeout”, the first of which is an Boolean and the second of which is a unsigned integer.

Note that a warning will pop up if you’ve saved such preference to the preference file and you subsequently take the code out. The way to make a preference obsolete is to register it as such:

  1. /* Register a preference that used to be supported but no longer is. */
  2. void prefs_register_obsolete_preference(module_t *module,
  3. const char *name);

2.7 TCP上层协议的重组

原标题: Reassembly/desegmentation for protocols running atop TCP

There are two main ways of reassembling a Protocol Data Unit (PDU) which spans across multiple TCP segments. The first approach is simpler, but assumes you are running atop of TCP when this occurs (but your dissector might run atop of UDP, too, for example), and that your PDUs consist of a fixed amount of data that includes enough information to determine the PDU length, possibly followed by additional data. The second method is more generic but requires more code and is less efficient.

2.7.1 使用tcp_dissect_pdus()

For the first method, you register two different dissection methods, one for the TCP case, and one for the other cases. It is a good idea to also have a dissect_PROTO_common function which will parse the generic content that you can find in all PDUs which is called from dissect_PROTO_tcp when the reassembly is complete and from dissect_PROTO_udp (or dissect_PROTO_other).

To register the distinct dissector functions, consider the following example, stolen from packet-hartip.c:

  1. #include "packet-tcp.h"
  2. dissector_handle_t hartip_tcp_handle;
  3. dissector_handle_t hartip_udp_handle;
  4. hartip_tcp_handle = create_dissector_handle(dissect_hartip_tcp, proto_hartip);
  5. hartip_udp_handle = create_dissector_handle(dissect_hartip_udp, proto_hartip);
  6. dissector_add_uint("udp.port", HARTIP_PORT, hartip_udp_handle);
  7. dissector_add_uint_with_preference("tcp.port", HARTIP_PORT, hartip_tcp_handle);

The dissect_hartip_udp function does very little work and calls dissect_hartip_common, while dissect_hartip_tcp calls tcp_dissect_pdus with a reference to a callback which will be called with reassembled data:

  1. static int
  2. dissect_hartip_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
  3. void *data)
  4. {
  5. if (!tvb_bytes_exist(tvb, 0, HARTIP_HEADER_LENGTH))
  6. return 0;
  7. tcp_dissect_pdus(tvb, pinfo, tree, hartip_desegment, HARTIP_HEADER_LENGTH,
  8. get_dissect_hartip_len, dissect_hartip_pdu, data);
  9. return tvb_reported_length(tvb);
  10. }

(The dissect_hartip_pdu function acts similarly to dissect_hartip_udp.)
The arguments to tcp_dissect_pdus are:

  1. the tvbuff pointer, packet_info pointer, and proto_tree pointer<br /> passed to the dissector;
  2. a gboolean flag indicating whether desegmentation is enabled for<br /> your protocol;
  3. the number of bytes of PDU data required to determine the length<br /> of the PDU;
  4. a routine that takes as arguments a packet_info pointer, a tvbuff<br /> pointer and an offset value representing the offset into the tvbuff<br /> at which a PDU begins, and a void pointer for user data, and should<br /> return the total length of the PDU in bytes (or 0 if more bytes are<br /> needed to determine the message length).<br /> The routine must not throw exceptions (it is guaranteed that the<br /> number of bytes specified by the previous argument to<br /> tcp_dissect_pdus is available, but more data might not be available,<br /> so don't refer to any data past that);
  5. a new_dissector_t routine to dissect the pdu that's passed a tvbuff<br /> pointer, packet_info pointer, proto_tree pointer and a void pointer for<br /> user data, with the tvbuff containing a possibly-reassembled PDU. (The<br /> "reported_length" of the tvbuff will be the length of the PDU);
  6. a void pointer to user data that is passed to the length-determining<br /> routine, and the dissector routine referenced in the previous parameter.

2.7.2 修改pinfo结构体

The second reassembly mode is preferred when the dissector cannot determine how many bytes it will need to read in order to determine the size of a PDU. It may also be useful if your dissector needs to support reassembly from protocols other than TCP.

Your dissect_PROTO will initially be passed a tvbuff containing the payload of the first packet. It should dissect as much data as it can, noting that it may contain more than one complete PDU. If the end of the provided tvbuff coincides with the end of a PDU then all is well and your dissector can just return as normal. (If it is a new-style dissector, it should return the number of bytes successfully processed.)

If the dissector discovers that the end of the tvbuff does /not/ coincide with the end of a PDU, (ie, there is half of a PDU at the end of the tvbuff), it can indicate this to the parent dissector, by updating the pinfo struct. The desegment_offset field is the offset in the tvbuff at which the dissector will continue processing when next called. The desegment_len field should contain the estimated number of additional bytes required for completing the PDU. Next time your dissect_PROTO is called, it will be passed a tvbuff composed of the end of the data from the previous tvbuff together with desegment_len more bytes.

If the dissector cannot tell how many more bytes it will need, it should set desegment_len=DESEGMENT_ONE_MORE_SEGMENT; it will then be called again as soon as any more data becomes available. Dissectors should set the desegment_len to a reasonable value when possible rather than always setting DESEGMENT_ONE_MORE_SEGMENT as it will generally be more efficient. Also, you must not set desegment_len=1 in this case, in the hope that you can change your mind later: once you return a positive value from desegment_len, your PDU boundary is set in stone.

  1. static hf_register_info hf[] = {
  2. {&hf_cstring,
  3. {"C String", "c.string", FT_STRING, BASE_NONE, NULL, 0x0,
  4. NULL, HFILL}
  5. }
  6. };
  7. /**
  8. * Dissect a buffer containing ASCII C strings.
  9. *
  10. * @param tvb The buffer to dissect.
  11. * @param pinfo Packet Info.
  12. * @param tree The protocol tree.
  13. * @param data Optional data parameter given by parent dissector.
  14. **/
  15. static int dissect_cstr(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void *data _U_)
  16. {
  17. guint offset = 0;
  18. while(offset < tvb_reported_length(tvb)) {
  19. gint available = tvb_reported_length_remaining(tvb, offset);
  20. gint len = tvb_strnlen(tvb, offset, available);
  21. if( -1 == len ) {
  22. /* we ran out of data: ask for more */
  23. pinfo->desegment_offset = offset;
  24. pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
  25. return (offset + available);
  26. }
  27. col_set_str(pinfo->cinfo, COL_INFO, "C String");
  28. len += 1; /* Add one for the '\0' */
  29. if (tree) {
  30. proto_tree_add_item(tree, hf_cstring, tvb, offset, len,
  31. ENC_ASCII|ENC_NA);
  32. }
  33. offset += (guint)len;
  34. }
  35. /* if we get here, then the end of the tvb coincided with the end of a
  36. string. Happy days. */
  37. return tvb_captured_length(tvb);
  38. }

This simple dissector will repeatedly return DESEGMENT_ONE_MORE_SEGMENT requesting more data until the tvbuff contains a complete C string. The C string will then be added to the protocol tree. Note that there may be more than one complete C string in the tvbuff, so the dissection is done in a loop.

2.8 使用udp_dissect_pdus()

As noted in section 2.7.1, TCP has an API to dissect its PDU that can handle a PDU spread across multiple packets or multiple PDUs spread across a single packet. This section describes a similar mechanism for UDP, but is only applicable for one or more PDUs in a single packet. If a protocol runs on top of TCP as well as UDP, a common PDU dissection function can be created for both.

To register the distinct dissector functions, consider the following example using UDP and TCP dissection, stolen from packet-dnp.c:

  1. #include "packet-tcp.h"
  2. #include "packet-udp.h"
  3. dissector_handle_t dnp3_tcp_handle;
  4. dissector_handle_t dnp3_udp_handle;
  5. dnp3_tcp_handle = create_dissector_handle(dissect_dnp3_tcp, proto_dnp3);
  6. dnp3_udp_handle = create_dissector_handle(dissect_dnp3_udp, proto_dnp3);
  7. dissector_add_uint("tcp.port", TCP_PORT_DNP, dnp3_tcp_handle);
  8. dissector_add_uint("udp.port", UDP_PORT_DNP, dnp3_udp_handle);

Both dissect_dnp3_tcp and dissect_dnp3_udp call tcp_dissect_pdus and udp_dissect_pdus respectively, with a reference to the same callbacks which are called to handle PDU data.

  1. static int
  2. dissect_dnp3_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
  3. {
  4. return udp_dissect_pdus(tvb, pinfo, tree, DNP_HDR_LEN, dnp3_udp_check_header,
  5. get_dnp3_message_len, dissect_dnp3_message, data);
  6. }
  7. static int
  8. dissect_dnp3_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
  9. {
  10. if (!check_dnp3_header(tvb, FALSE)) {
  11. return 0;
  12. }
  13. tcp_dissect_pdus(tvb, pinfo, tree, TRUE, DNP_HDR_LEN,
  14. get_dnp3_message_len, dissect_dnp3_message, data);
  15. return tvb_captured_length(tvb);
  16. }

(udp_dissect_pdus has an option of a heuristic check function within it while tcp_dissect_pdus does not, so it’s done outside)

The arguments to udp_dissect_pdus are:

  1. the tvbuff pointer, packet_info pointer, and proto_tree pointer<br /> passed to the dissector;
  2. the number of bytes of PDU data required to determine the length<br /> of the PDU;
  3. an optional routine (passing NULL is okay) that takes as arguments a<br /> packet_info pointer, a tvbuff pointer and an offset value representing the<br /> offset into the tvbuff at which a PDU begins, and a void pointer for user<br /> data, and should return TRUE if the packet belongs to the dissector.<br /> The routine must not throw exceptions (it is guaranteed that the<br /> number of bytes specified by the previous argument to<br /> udp_dissect_pdus is available, but more data might not be available,<br /> so don't refer to any data past that);
  4. a routine that takes as arguments a packet_info pointer, a tvbuff<br /> pointer and an offset value representing the offset into the tvbuff<br /> at which a PDU begins, and a void pointer for user data, and should<br /> return the total length of the PDU in bytes. If return value is 0,<br /> it's treated the same as a failed heuristic.<br /> The routine must not throw exceptions (it is guaranteed that the<br /> number of bytes specified by the previous argument to<br /> tcp_dissect_pdus is available, but more data might not be available,<br /> so don't refer to any data past that);
  5. a new_dissector_t routine to dissect the pdu that's passed a tvbuff<br /> pointer, packet_info pointer, proto_tree pointer and a void pointer for<br /> user data, with the tvbuff containing a possibly-reassembled PDU. (The<br /> "reported_length" of the tvbuff will be the length of the PDU);
  6. a void pointer to user data that is passed to the length-determining<br /> routine, and the dissector routine referenced in the previous parameter.

2.9 PINOs (Protocols in name only)

For the typical dissector there is a 1-1 relationship between it and it’s protocol. However, there are times when a protocol needs multiple “names” because it has multiple dissection functions going into the same dissector table. The multiple names removes confusion when picking dissection through Decode As functionality.

Once the “main” protocol name has been created through proto_register_protocol, additional “pinos” can be created with proto_register_protocol_in_name_only. These pinos have all of the naming conventions of a protocol, but are stored separately as to remove confusion from real protocols. “pinos” the main protocol’s properties for things like enable/disable. i.e. If the “main” protocol has been disabled, all of its pinos will be disabled as well. Pinos should not have any fields registered with them or heuristic tables
associated with them.

Another use case for pinos is when a protocol contains a TLV design and it wants to create a dissector table to handle dissection of the “V”. Dissector tables require a “protocol”, but the dissection functions for that table typically aren’t a protocol. In this case proto_register_protocol_in_name_only creates the necessary placeholder for the dissector table. In addition, because a dissector table exists, “V”s of the TLVs can be dissected outside of the original dissector file.

2.10 实现Decode As功能

While the Decode As functionality is available through the GUI, the underlying functionality is controlled by dissectors themselves. To create Decode As functionality for a dissector, two things are required:
1. A dissector table
2. A series of structures to assist the GUI in how to present the dissector
table data.

Consider the following example using IP dissection, stolen from packet-ip.c:

  1. static build_valid_func ip_da_build_value[1] = {ip_value};
  2. static decode_as_value_t ip_da_values = {ip_prompt, 1, ip_da_build_value};
  3. static decode_as_t ip_da = {"ip", "ip.proto", 1, 0, &ip_da_values, NULL, NULL,
  4. decode_as_default_populate_list, decode_as_default_reset, decode_as_default_change, NULL};
  5. ...
  6. ip_dissector_table = register_dissector_table("ip.proto", "IP protocol", ip_proto, FT_UINT8, BASE_DEC);
  7. ...
  8. register_decode_as(&ip_da);

ip_da_build_value contains all of the function pointers (typically just 1) that can be used to retrieve the value(s) that go into the dissector table. This is usually data saved by the dissector during packet dissector with an API like p_add_proto_data and retrieved in the “value” function with p_get_proto_data.

ip_da_values contains all of the function pointers (typically just 1) that provide the text explaining the name and use of the value field that will be passed to the dissector table to change the dissection output.

ip_da pulls everything together including the dissector (protocol) name, the “layer type” of the dissector, the dissector table name, the function pointer values as well as handlers for populating, applying and resetting the changes to the dissector table through Decode As GUI functionality. For dissector tables that are an integer or string type, the provided “default” handling functions shown in the example should suffice.

All entries into a dissector table that use Decode As must have a unique protocol ID. If a protocol wants multiple entries into a dissector table, a pino should be used (see section 2.9)

2.11 ptvcursors

The ptvcursor API allows a simpler approach to writing dissectors for simple protocols. The ptvcursor API works best for protocols whose fields are static and whose format does not depend on the value of other fields. However, even if only a portion of your protocol is statically defined, then that portion could make use of ptvcursors.

The ptvcursor API lets you extract data from a tvbuff, and add it to a protocol tree in one step. It also keeps track of the position in the tvbuff so that you can extract data again without having to compute any offsets —- hence the “cursor” name of the API.

The three steps for a simple protocol are:
1. Create a new ptvcursor with ptvcursor_new()
2. Add fields with multiple calls of ptvcursor_add()
3. Delete the ptvcursor with ptvcursor_free()

ptvcursor offers the possibility to add subtrees in the tree as well. It can be done in very simple steps :
1. Create a new subtree with ptvcursor_push_subtree(). The old subtree is
pushed in a stack and the new subtree will be used by ptvcursor.
2. Add fields with multiple calls of ptvcursor_add(). The fields will be
added in the new subtree created at the previous step.
3. Pop the previous subtree with ptvcursor_pop_subtree(). The previous
subtree is again used by ptvcursor.
Note that at the end of the parsing of a packet you must have popped each subtree you pushed. If it’s not the case, the dissector will generate an error.

To use the ptvcursor API, include the “ptvcursor.h” file. The PGM dissector is an example of how to use it. You don’t need to look at it as a guide; instead, the API description here should be good enough.

2.11.1 ptvcursor API

  1. ptvcursor_t*
  2. ptvcursor_new(proto_tree* tree, tvbuff_t* tvb, gint offset);

This creates a new ptvcursor_t object for iterating over a tvbuff.
You must call this and use this ptvcursor_t object so you can use the ptvcursor API.

  1. proto_item*
  2. ptvcursor_add(ptvcursor_t* ptvc, int hf, gint length, const guint encoding);

This will extract ‘length’ bytes from the tvbuff and place it in the proto_tree as field ‘hf’, which is a registered header_field. The pointer to the proto_item that is created is passed back to you. Internally, the ptvcursor advances its cursor so the next call to ptvcursor_add starts where this call finished. The ‘encoding’ parameter is relevant for certain type of fields (See above under proto_tree_add_item()).

  1. proto_item*
  2. ptvcursor_add_ret_uint(ptvcursor_t* ptvc, int hf, gint length, const guint encoding, guint32 *retval);
  3. // Like ptvcursor_add, but returns uint value retrieved
  4. proto_item*
  5. ptvcursor_add_ret_int(ptvcursor_t* ptvc, int hf, gint length, const guint encoding, gint32 *retval);
  6. // Like ptvcursor_add, but returns int value retrieved
  7. proto_item*
  8. ptvcursor_add_ret_string(ptvcursor_t* ptvc, int hf, gint length, const guint encoding, wmem_allocator_t *scope, const guint8 **retval);
  9. // Like ptvcursor_add, but returns string retrieved
  10. proto_item*
  11. ptvcursor_add_ret_boolean(ptvcursor_t* ptvc, int hf, gint length, const guint encoding, gboolean *retval);
  12. // Like ptvcursor_add, but returns boolean value retrieved
  13. proto_item*
  14. ptvcursor_add_no_advance(ptvcursor_t* ptvc, int hf, gint length, const guint encoding);
  15. // Like ptvcursor_add, but does not advance the internal cursor.
  16. void
  17. ptvcursor_advance(ptvcursor_t* ptvc, gint length);
  18. // Advances the internal cursor without adding anything to the proto_tree.
  19. void
  20. ptvcursor_free(ptvcursor_t* ptvc);
  21. // Frees the memory associated with the ptvcursor. You must call this
  22. // after your dissection with the ptvcursor API is completed.
  23. proto_tree*
  24. ptvcursor_push_subtree(ptvcursor_t* ptvc, proto_item* it, gint ett_subtree);
  25. // Pushes the current subtree in the tree stack of the cursor, creates a new
  26. // one and sets this one as the working tree.
  27. void
  28. ptvcursor_pop_subtree(ptvcursor_t* ptvc);
  29. // Pops a subtree in the tree stack of the cursor
  30. proto_tree*
  31. ptvcursor_add_with_subtree(ptvcursor_t* ptvc, int hfindex, gint length,
  32. const guint encoding, gint ett_subtree);

Adds an item to the tree and creates a subtree.
If the length is unknown, length may be defined as SUBTREE_UNDEFINED_LENGTH. In this case, at the next pop, the item length will be equal to the advancement of the cursor since the creation of the subtree.

  1. proto_tree*
  2. ptvcursor_add_text_with_subtree(ptvcursor_t* ptvc, gint length,
  3. gint ett_subtree, const char* format, ...);

Add a text node to the tree and create a subtree.
If the length is unknown, length may be defined as SUBTREE_UNDEFINED_LENGTH. In this case, at the next pop, the item length will be equal to the advancement of the cursor since the creation of the subtree.

2.11.2 Miscellaneous functions

  1. // Returns the tvbuff associated with the ptvcursor.
  2. tvbuff_t*
  3. ptvcursor_tvbuff(ptvcursor_t* ptvc);
  4. // Returns the current offset.
  5. gint
  6. ptvcursor_current_offset(ptvcursor_t* ptvc);
  7. // Returns the proto_tree associated with the ptvcursor.
  8. proto_tree*
  9. ptvcursor_tree(ptvcursor_t* ptvc);
  10. // Sets a new proto_tree for the ptvcursor.
  11. void
  12. ptvcursor_set_tree(ptvcursor_t* ptvc, proto_tree *tree);
  13. // Creates a subtree and adds it to the cursor as the working tree but does
  14. // not save the old working tree.
  15. proto_tree*
  16. ptvcursor_set_subtree(ptvcursor_t* ptvc, proto_item* it, gint ett_subtree);

2.12 优化

A protocol dissector may be called in 2 different ways - with, or without a non-null “tree” argument.

If the proto_tree argument is null, Wireshark does not need to use the protocol tree information from your dissector, and therefore is passing the dissector a null “tree” argument so that it doesn’t need to do work necessary to build the protocol tree.

In the interest of speed, if “tree” is NULL, avoid building a protocol tree and adding stuff to it, or even looking at any packet data needed only if you’re building the protocol tree, if possible.

Note, however, that you must fill in column information, create conversations, reassemble packets, do calls to “expert” functions, build any other persistent state needed for dissection, and call subdissectors regardless of whether “tree” is NULL or not.

This might be inconvenient to do without doing most of the dissection work; the routines for adding items to the protocol tree can be passed a null protocol tree pointer, in which case they’ll return a null item pointer, and “proto_item_add_subtree()” returns a null tree pointer if passed a null item pointer, so, if you’re careful not to dereference any null tree or item pointers, you can accomplish this by doing all the dissection work. This might not be as efficient as skipping that work if you’re not building a protocol tree, but if the code would have a lot of tests whether “tree” is null if you skipped that work, you might still be better off just doing all that work regardless of whether “tree” is null or not.

Note also that there is no guarantee, the first time the dissector is called, whether “tree” will be null or not; your dissector must work correctly, building or updating whatever state information is necessary, in either case.

参考

  • Wireshark源码: doc/README.dissector