1 概述

报文重组是为了处理两种情况. 第一种情况是因为 MTU 等限制, 导致协议数据需要通过多个报文来承载:
Wireshark原理: 数据重组 (TODO) - 图1
另一种情况, 则是有的协议本身定义了分块规则, 导致可能一个报文中也可能有多个数据段:
Wireshark原理: 数据重组 (TODO) - 图2
实际情况中这两种情况是可能混合出现的. 不论是哪种情况, 还是混合, 协议解析程序都必须先正确取得原始的协议数据, 然后才能对它进行正确分析.

2 用户手册中的报文重组

Network protocols often need to transport large chunks of data which are complete in themselves, e.g. when transferring a file. The underlying protocol might not be able to handle that chunk size (e.g. limitation of the network packet size), or is stream-based like TCP, which doesn’t know data chunks at all.

In that case the network protocol has to handle the chunk boundaries itself and (if required) spread the data over multiple packets. It obviously also needs a mechanism to determine the chunk boundaries on the receiving side.

Wireshark calls this mechanism reassembly, although a specific protocol specification might use a different term for this (e.g. desegmentation, defragmentation, etc).

Wireshark是如何处理的

For some of the network protocols Wireshark knows of, a mechanism is implemented to find, decode and display these chunks of data. Wireshark will try to find the corresponding packets of this chunk, and will show the combined data as additional pages in the “Packet Bytes” pane (for information about this pane. See Section 3.20, “The “Packet Bytes” Pane”).

Wireshark原理: 数据重组 (TODO) - 图3
Reassembly might take place at several protocol layers, so it’s possible that multiple tabs in the “Packet Bytes” pane appear.

For example, in a HTTP GET response, the requested data (e.g. an HTML page) is returned. Wireshark will show the hex dump of the data in a new tab “Uncompressed entity body” in the “Packet Bytes” pane.
http_gnu.zip
企业微信截图_16218484238073.png

  1. HTTP/1.1 200 OK
  2. Date: Sun, 17 Nov 2019 02:09:12 GMT
  3. Server: Apache/2.4.7
  4. Content-Location: home.zh-cn.html
  5. Vary: negotiate,accept-language,Accept-Encoding
  6. TCN: choice
  7. Access-Control-Allow-Origin: (null)
  8. Accept-Ranges: bytes
  9. Content-Encoding: gzip
  10. Cache-Control: max-age=0
  11. Expires: Sun, 17 Nov 2019 02:09:12 GMT
  12. Content-Length: 10454
  13. Keep-Alive: timeout=3, max=100
  14. Connection: Keep-Alive
  15. Content-Type: text/html
  16. Content-Language: zh-cn

Reassembly is enabled in the preferences by default but can be disabled in the preferences for the protocol in question. Enabling or disabling reassembly settings for a protocol typically requires two things:

  1. The lower level protocol (e.g., TCP) must support reassembly. Often this reassembly can be enabled or disabled via the protocol preferences.
  2. The higher level protocol (e.g., HTTP) must use the reassembly mechanism to reassemble fragmented protocol data. This too can often be enabled or disabled via the protocol preferences.

The tooltip of the higher level protocol setting will notify you if and which lower level protocol setting also has to be considered.

TCP重组

像 HTTP 或 TLS 这样的协议, (协议数据)可能会跨多个 TCP segments. 通过打开 TCP 首选项 “Allow subdissector to reassemble TCP streams”(默认打开) 可以让 Wireshark 收集连续的 TCP segment 然后再把它们交给上层协议(比如, 重组完整的 HTTP 消息). 最终的 segment 在报文列表中会标记为 “[TCP segment of a reassembled PDU]”.
企业微信截图_16219924898073.png

不打开这个选项会减少内存占用和处理开销, 如果你只关心 TCP 序列号分析的话(Section 7.5, “TCP Analysis”). 但记住, 这样的话会导致上层协议解析错误. 例如, HTTP 消息可能显示为”Continuation”, TLS 记录可能会显示为”Ingnored Unknown Record”. 如果在 TCP 连接建立后才开始抓包, 或者某些 TCP segments 丢失或乱序, 这些后果也会产生.

为了重组乱序的 TCP segments, 需要打开 TCP 首选项”Reassemble out-of-order segments”(默认关闭). 若所有的报文都有序接收, 此选项没有影响; 否则, 它假定新的或丢失的 segments 属于同一 PDU. 注意:

  • 假定丢失的报文会在之后收到(乱序或重传). 应用程序通常会重传 segment 直到它们被确认, 但如果抓包时丢失了报文, Wireshark 就不能重建 TCP 流了. 在此情况下, 你可以关闭这个选项, 希望使用部分解析而不是对每个 TCP segment 都标上 “[TCP segment of a reassembled PDU]” .
  • 当在监听模式下抓包时(IEEE 802.11), 更可能因为信号接收问题而丢包. 这时推荐关闭这个选项.
  • 如果新的和丢失的 segments 实际上是不同 PDU 的一部分, 则当前处理会被推迟, 直到拿到所有 segments. 例如, 假定 6 个 segments 组成 2 个 PDUs: ABC, DEF. 当接收顺序为 ABECDF, 程序会在接收到 ABEC 后开始处理第一个 PDU. 然而 Wireshark 要求丢失的 segment D 也要被接收才行. 此 issue 将在未来解决.
  • In the GUI and during a two-pass dissection (tshark -2), the previous scenario will display both PDUs in the packet with last segment (F) rather than displaying it in the first packet that has the final missing segment of a PDU. This issue will be addressed in the future.
  • When enabled, fields such as the SMB “Time from request” (smb.time) might be smaller if the request follows other out-of-order segments (this reflects application behavior). If the previous scenario however occurs, then the time of the request is based on the frame where all missing segments are received.

不论这两个与重组相关的选项设置如何, 你都可以使用”Follow Protocol Stream“功能, 它会用正确的顺序显示 segments.

3 开发手册中的报文重组

Wireshark 开发手册中的 “9.5 How to reassemble split packets“ 一节讲述了报文重组相关问题, 本文此章领会这部分内容. 另外 Wireshark 源码目录中的 doc/README.dissector 文件的 “2.7 Reassembly/desegmentation for protocols running atop TCP”, “2.8 Using udp_dissect_pdus()” 也讲述了报文重组问题, 请参考原文, 或者我的 Wireshark 文章.

Some protocols have times when they have to split a large packet across multiple other packets. In this case the dissection can’t be carried out correctly until you have all the data. The first packet doesn’t have enough data, and the subsequent packets don’t have the expect format. To dissect these packets you need to wait until all the parts have arrived and then start the dissection.

The following sections will guide you through two common cases. For a description of all possible functions, structures and parameters, see epan/reassemble.h.

如何重组UDP报文

As an example, let’s examine a protocol that is layered on top of UDP that splits up its own data stream. If a packet is bigger than some given size, it will be split into chunks, and somehow signaled within its protocol.

To deal with such streams, we need several things to trigger from. We need to know that this packet is part of a multi-packet sequence. We need to know how many packets are in the sequence. We also need to know when we have all the packets.

For this example we’ll assume there is a simple in-protocol signaling mechanism to give details. A flag byte that signals the presence of a multi-packet sequence and also the last packet, followed by an ID of the sequence and a packet sequence number.

  1. msg_pkt ::= SEQUENCE {
  2. .....
  3. flags ::= SEQUENCE {
  4. fragment BOOLEAN, // 是否分段
  5. last_fragment BOOLEAN, // 是否最后一个分段
  6. .....
  7. }
  8. msg_id INTEGER(0..65535), // 完整消息id
  9. frag_id INTEGER(0..65535), // 如果是分段, 表示分段id
  10. .....
  11. }

重组第一步

  1. #include <epan/reassemble.h>
  2. ...
  3. save_fragmented = pinfo->fragmented;
  4. flags = tvb_get_guint8(tvb, offset); offset++;
  5. if (flags & FL_FRAGMENT) { /* fragmented */
  6. tvbuff_t* new_tvb = NULL;
  7. fragment_data *frag_msg = NULL;
  8. guint16 msg_seqid = tvb_get_ntohs(tvb, offset); offset += 2;
  9. guint16 msg_num = tvb_get_ntohs(tvb, offset); offset += 2;
  10. pinfo->fragmented = TRUE;
  11. frag_msg = fragment_add_seq_check(msg_reassembly_table,
  12. tvb, offset, pinfo,
  13. msg_seqid, NULL, /* ID for fragments belonging together */
  14. msg_num, /* fragment sequence number */
  15. tvb_captured_length_remaining(tvb, offset), /* fragment length - to the end */
  16. flags & FL_FRAG_LAST); /* More fragments? */

We start by saving the fragmented state of this packet, so we can restore it later. Next comes some protocol specific stuff, to dig the fragment data out of the stream if it’s present. Having decided it is present, we let the function fragment_add_seq_check() do its work. We need to provide this with a certain amount of parameters:

  • The msg_reassembly_table table is for bookkeeping and is described later.The tvb buffer we are dissecting.
  • The offset where the partial packet starts.
  • The provided packet info.
  • The sequence number of the fragment stream. There may be several streams of fragments in flight, and this is used to key the relevant one to be used for reassembly.
  • Optional additional data for identifying the fragment. Can be set to NULL (as is done in the example) for most dissectors.
  • msg_num is the packet number within the sequence.
  • The length here is specified as the rest of the tvb as we want the rest of the packet data.
  • Finally a parameter that signals if this is the last fragment or not. This might be a flag as in this case, or there may be a counter in the protocol.

重组第二步

  1. new_tvb = process_reassembled_data(tvb, offset, pinfo,
  2. "Reassembled Message", frag_msg, &msg_frag_items,
  3. NULL, msg_tree);
  4. if (frag_msg) { /* Reassembled */
  5. col_append_str(pinfo->cinfo, COL_INFO,
  6. " (Message Reassembled)");
  7. } else { /* Not last packet of reassembled Short Message */
  8. col_append_fstr(pinfo->cinfo, COL_INFO,
  9. " (Message fragment %u)", msg_num);
  10. }
  11. if (new_tvb) { /* take it all */
  12. next_tvb = new_tvb;
  13. } else { /* make a new subset */
  14. next_tvb = tvb_new_subset_remaining(tvb, offset);
  15. }
  16. }
  17. else { /* Not fragmented */
  18. next_tvb = tvb_new_subset_remaining(tvb, offset);
  19. }
  20. .....
  21. pinfo->fragmented = save_fragmented;

Having passed the fragment data to the reassembly handler, we can now check if we have the whole message. If there is enough information, this routine will return the newly reassembled data buffer.

After that, we add a couple of informative messages to the display to show that this is part of a sequence. Then a bit of manipulation of the buffers and the dissection can proceed. Normally you will probably not bother dissecting further unless the fragments have been reassembled as there won’t be much to find. Sometimes the first packet in the sequence can be partially decoded though if you wish.

Now the mysterious data we passed into the fragment_add_seq_check().

重组初始化

  1. static reassembly_table reassembly_table;
  2. static void
  3. proto_register_msg(void)
  4. {
  5. reassembly_table_register(&msg_reassemble_table,
  6. &addresses_ports_reassembly_table_functions);
  7. }

First a reassembly_tablestructure is declared and initialised in the protocol initialisation routine. The second parameter specifies the functions that should be used for identifying fragments. We will use addresses_ports_reassembly_table_functions in order to identify fragments by the given sequence number (msg_seqid), the source and destination addresses and ports from the packet.

Following that, a fragment_items structure is allocated and filled in with a series of ett items, hf data items, and a string tag. The ett and hf values should be included in the relevant tables like all the other variables your protocol may use. The hf variables need to be placed in the structure something like the following. Of course the names may need to be adjusted.

重组数据

  1. ...
  2. static int hf_msg_fragments = -1;
  3. static int hf_msg_fragment = -1;
  4. static int hf_msg_fragment_overlap = -1;
  5. static int hf_msg_fragment_overlap_conflicts = -1;
  6. static int hf_msg_fragment_multiple_tails = -1;
  7. static int hf_msg_fragment_too_long_fragment = -1;
  8. static int hf_msg_fragment_error = -1;
  9. static int hf_msg_fragment_count = -1;
  10. static int hf_msg_reassembled_in = -1;
  11. static int hf_msg_reassembled_length = -1;
  12. ...
  13. static gint ett_msg_fragment = -1;
  14. static gint ett_msg_fragments = -1;
  15. ...
  16. static const fragment_items msg_frag_items = {
  17. /* Fragment subtrees */
  18. &ett_msg_fragment,
  19. &ett_msg_fragments,
  20. /* Fragment fields */
  21. &hf_msg_fragments,
  22. &hf_msg_fragment,
  23. &hf_msg_fragment_overlap,
  24. &hf_msg_fragment_overlap_conflicts,
  25. &hf_msg_fragment_multiple_tails,
  26. &hf_msg_fragment_too_long_fragment,
  27. &hf_msg_fragment_error,
  28. &hf_msg_fragment_count,
  29. /* Reassembled in field */
  30. &hf_msg_reassembled_in,
  31. /* Reassembled length field */
  32. &hf_msg_reassembled_length,
  33. /* Tag */
  34. "Message fragments"
  35. };
  36. ...
  37. static hf_register_info hf[] =
  38. {
  39. ...
  40. {&hf_msg_fragments,
  41. {"Message fragments", "msg.fragments",
  42. FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } },
  43. {&hf_msg_fragment,
  44. {"Message fragment", "msg.fragment",
  45. FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
  46. {&hf_msg_fragment_overlap,
  47. {"Message fragment overlap", "msg.fragment.overlap",
  48. FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
  49. {&hf_msg_fragment_overlap_conflicts,
  50. {"Message fragment overlapping with conflicting data",
  51. "msg.fragment.overlap.conflicts",
  52. FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
  53. {&hf_msg_fragment_multiple_tails,
  54. {"Message has multiple tail fragments",
  55. "msg.fragment.multiple_tails",
  56. FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
  57. {&hf_msg_fragment_too_long_fragment,
  58. {"Message fragment too long", "msg.fragment.too_long_fragment",
  59. FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
  60. {&hf_msg_fragment_error,
  61. {"Message defragmentation error", "msg.fragment.error",
  62. FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
  63. {&hf_msg_fragment_count,
  64. {"Message fragment count", "msg.fragment.count",
  65. FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
  66. {&hf_msg_reassembled_in,
  67. {"Reassembled in", "msg.reassembled.in",
  68. FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
  69. {&hf_msg_reassembled_length,
  70. {"Reassembled length", "msg.reassembled.length",
  71. FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
  72. ...
  73. static gint *ett[] =
  74. {
  75. ...
  76. &ett_msg_fragment,
  77. &ett_msg_fragments
  78. ...

These hf variables are used internally within the reassembly routines to make useful links, and to add data to the dissection. It produces links from one packet to another, such as a partial packet having a link to the fully reassembled packet. Likewise there are back pointers to the individual packets from the reassembled one. The other variables are used for flagging up errors.

如何重组TCP报文

A dissector gets a tvbuff_t pointer which holds the payload of a TCP packet. This payload contains the header and data of your application layer protocol.
When dissecting an application layer protocol you cannot assume that each TCP packet contains exactly one application layer message. One application layer message can be split into several TCP packets.

You also cannot assume that a TCP packet contains only one application layer message and that the message header is at the start of your TCP payload. More than one messages can be transmitted in one TCP packet, so that a message can start at an arbitrary position.

This sounds complicated, but there is a simple solution. tcp_dissect_pdus() does all this tcp packet reassembling for you. This function is implemented in epan/dissectors/packet-tcp.h.

  1. // epan/dissectors/packet-tcp.h
  2. /*
  3. * Loop for dissecting PDUs within a TCP stream; assumes that a PDU
  4. * consists of a fixed-length chunk of data that contains enough information
  5. * to determine the length of the PDU, followed by rest of the PDU.
  6. *
  7. * The first three arguments are the arguments passed to the dissector
  8. * that calls this routine.
  9. *
  10. * "proto_desegment" is the dissector's flag controlling whether it should
  11. * desegment PDUs that cross TCP segment boundaries.
  12. *
  13. * "fixed_len" is the length of the fixed-length part of the PDU.
  14. *
  15. * "get_pdu_len()" is a routine called to get the length of the PDU from
  16. * the fixed-length part of the PDU; it's passed "pinfo", "tvb", "offset" and
  17. * "dissector_data".
  18. *
  19. * "dissect_pdu()" is the routine to dissect a PDU.
  20. */
  21. WS_DLL_PUBLIC void
  22. tcp_dissect_pdus(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
  23. gboolean proto_desegment, guint fixed_len,
  24. guint (*get_pdu_len)(packet_info *, tvbuff_t *, int, void*),
  25. dissector_t dissect_pdu, void* dissector_data);

重组 TCP fragments

  1. #include "config.h"
  2. #include <epan/packet.h>
  3. #include <epan/prefs.h>
  4. #include "packet-tcp.h"
  5. ...
  6. #define FRAME_HEADER_LEN 8
  7. /* This method dissects fully reassembled messages */
  8. static int
  9. dissect_foo_message(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree _U_, void *data _U_)
  10. {
  11. /* TODO: implement your dissecting code */
  12. return tvb_captured_length(tvb);
  13. }
  14. /* determine PDU length of protocol foo */
  15. static guint
  16. get_foo_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
  17. {
  18. /* TODO: change this to your needs */
  19. return (guint)tvb_get_ntohl(tvb, offset+4); /* e.g. length is at offset 4 */
  20. }
  21. /* The main dissecting routine */
  22. static int
  23. dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
  24. {
  25. tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN,
  26. get_foo_message_len, dissect_foo_message, data);
  27. return tvb_captured_length(tvb);
  28. }

As you can see this is really simple. Just call tcp_dissect_pdus() in your main dissection routine and move you message parsing code into another function. This function gets called whenever a message has been reassembled.

The parameters tvb, pinfo, tree and data are just handed over to tcp_dissect_pdus(). The 4th parameter is a flag to indicate if the data should be reassembled or not. This could be set according to a dissector preference as well. Parameter 5 indicates how much data has at least to be available to be able to determine the length of the foo message. Parameter 6 is a function pointer to a method that returns this length. It gets called when at least the number of bytes given in the previous parameter is available. Parameter 7 is a function pointer to your real message dissector. Parameter 8 is the data passed in from parent dissector.

Protocols which need more data before the message length can be determined can return zero. Other values smaller than the fixed length will result in an exception.

4 通用重组框架

5 IP重组

6 TCP重组

7 源码分析

通用重组框架

分片

  1. typedef struct _fragment_item {
  2. struct _fragment_item *next;
  3. guint32 frame; /* XXX - does this apply to reassembly heads? */
  4. guint32 offset; /* XXX - does this apply to reassembly heads? */
  5. guint32 len; /* XXX - does this apply to reassembly heads? */
  6. guint32 fragment_nr_offset; /**< offset for frame numbering, for sequences, where the
  7. * provided fragment number of the first fragment does
  8. * not start with 0
  9. * XXX - does this apply only to reassembly heads? */
  10. guint32 datalen; /**< When flags&FD_BLOCKSEQUENCE is set, the
  11. * index of the last block (segments in
  12. * datagram + 1); otherwise the number of
  13. * bytes of the full datagram. Only valid in
  14. * the first item of the fragments list when
  15. * flags&FD_DATALEN is set.*/
  16. guint32 reassembled_in; /**< frame where this PDU was reassembled,
  17. * only valid in the first item of the list
  18. * and when FD_DEFRAGMENTED is set*/
  19. guint8 reas_in_layer_num; /**< The current "depth" or layer number in the current frame where reassembly was completed.
  20. * Example: in SCTP there can be several data chunks and we want the reassemblied tvb for the final
  21. * segment only. */
  22. guint32 flags; /**< XXX - do some of these apply only to reassembly
  23. * heads and others only to fragments within
  24. * a reassembly? */
  25. tvbuff_t *tvb_data;
  26. /**
  27. * Null if the reassembly had no error; non-null if it had
  28. * an error, in which case it's the string for the error.
  29. *
  30. * XXX - this is wasted in all but the reassembly head; we
  31. * should probably have separate data structures for a
  32. * reassembly and for the fragments in a reassembly.
  33. */
  34. const char *error;
  35. } fragment_item, fragment_head;

重组表

  1. /*
  2. * Data structure to keep track of fragments and reassemblies.
  3. */
  4. typedef struct {
  5. GHashTable *fragment_table;
  6. GHashTable *reassembled_table;
  7. fragment_temporary_key temporary_key_func;
  8. fragment_persistent_key persistent_key_func;
  9. GDestroyNotify free_temporary_key_func; /* temporary key destruction function */
  10. } reassembly_table;

重组表中有两个哈希表, 分别是分片表和已重组表.

重组表函数

  1. /*
  2. * Table of functions for a reassembly table.
  3. */
  4. typedef struct {
  5. /* Functions for fragment table */
  6. GHashFunc hash_func; /* hash function */
  7. GEqualFunc equal_func; /* comparison function */
  8. fragment_temporary_key temporary_key_func; /* temporary key creation function */
  9. fragment_persistent_key persistent_key_func; /* persistent key creation function */
  10. GDestroyNotify free_temporary_key_func; /* temporary key destruction function */
  11. GDestroyNotify free_persistent_key_func; /* persistent key destruction function */
  12. } reassembly_table_functions;

用于重组表中的分片哈希表的相关函数. glib 的哈希表支持提供 2 个函数, 用于在删除结点时释放 key 和 value 的内存:

  1. GHashTable *
  2. g_hash_table_new_full (GHashFunc hash_func,
  3. GEqualFunc key_equal_func,
  4. GDestroyNotify key_destroy_func,
  5. GDestroyNotify value_destroy_func);

所以 reassembly_table_functions中的 free_persistent_key_func 用于删除分片哈希表中的 key.
Wireshark 已提供 2 个函数, 分别用于 3 元组和 5 元组哈希 key:

  1. /*
  2. * Tables of functions exported for the benefit of dissectors that
  3. * don't need special items in their keys.
  4. */
  5. WS_DLL_PUBLIC const reassembly_table_functions
  6. addresses_reassembly_table_functions; /* keys have endpoint addresses and an ID */
  7. WS_DLL_PUBLIC const reassembly_table_functions
  8. addresses_ports_reassembly_table_functions; /* keys have endpoint addresses and ports and an ID */

对应 3 元组和 5 元组:

  1. /*
  2. * Functions for reassembly tables where the endpoint addresses, and a
  3. * fragment ID, are used as the key.
  4. */
  5. typedef struct _fragment_addresses_key {
  6. address src;
  7. address dst;
  8. guint32 id;
  9. } fragment_addresses_key;
  10. /*
  11. * Functions for reassembly tables where the endpoint addresses and ports,
  12. * and a fragment ID, are used as the key.
  13. */
  14. typedef struct _fragment_addresses_ports_key {
  15. address src_addr;
  16. address dst_addr;
  17. guint32 src_port;
  18. guint32 dst_port;
  19. guint32 id;
  20. } fragment_addresses_ports_key;

而用于已重组哈希表的 key 不同, 比较简单:

  1. typedef struct _reassembled_key {
  2. guint32 id;
  3. guint32 frame;
  4. } reassembled_key;

重组表注册

  1. /*
  2. * Register a reassembly table. By registering the table with epan, the creation and
  3. * destruction of the table can be managed by epan and not the dissector.
  4. */
  5. WS_DLL_PUBLIC void
  6. reassembly_table_register(reassembly_table *table,
  7. const reassembly_table_functions *funcs)
  8. {
  9. register_reassembly_table_t* reg_table;
  10. ...
  11. reg_table = g_new(register_reassembly_table_t,1);
  12. reg_table->table = table;
  13. reg_table->funcs = funcs;
  14. reassembly_table_list = g_list_prepend(reassembly_table_list, reg_table);
  15. }

需要重组功能的协议调用此接口注册重组表. 注册时生成下面的重组表注册数据结构, 并将其添加到全局链表 reassembly_table_list 中:

  1. typedef struct register_reassembly_table {
  2. reassembly_table *table;
  3. const reassembly_table_functions *funcs;
  4. } register_reassembly_table_t;
  5. GList* reassembly_table_list = NULL;

重组表初始化

Wireshark 初始化时 (通过 epan_init(), 整个协议解析模块的初始化函数), 会调用以下函数初始化已注册的重组表:

  1. /* Initialize internal structures
  2. */
  3. void reassembly_tables_init(void)
  4. {
  5. register_init_routine(&reassembly_table_init_reg_tables);
  6. register_cleanup_routine(&reassembly_table_cleanup_reg_tables);
  7. }

会将重组哈希表的初始化与清理函数挂接到全局初始化与清理链表中.
其中初始化就是对全局重组链表中的每一项调用 reassembly_table_init函数:

  1. /*
  2. * Initialize/destroy a reassembly table.
  3. *
  4. * init: If table doesn't exist: create table;
  5. * else: just remove any entries;
  6. * destroy: remove entries and destroy table;
  7. */
  8. WS_DLL_PUBLIC void
  9. reassembly_table_init(reassembly_table *table,
  10. const reassembly_table_functions *funcs);

主要动作是创建 2 个哈希表, 即分片表和重组表:

  1. table->fragment_table = g_hash_table_new_full(funcs->hash_func,
  2. funcs->equal_func, funcs->free_persistent_key_func, NULL);
  3. ...
  4. table->reassembled_table = g_hash_table_new_full(reassembled_hash,
  5. reassembled_equal, reassembled_key_free, NULL);

这里也可以看到已重组表的删除 key 函数与分片表不同(reassembled_key_free).
清理函数则最终会调用 reassembly_table_destroy 释放资源.

  1. WS_DLL_PUBLIC void
  2. reassembly_table_destroy(reassembly_table *table);

添加分片

  1. /*
  2. * This function adds a new fragment to the reassembly table
  3. * If this is the first fragment seen for this datagram, a new entry
  4. * is created in the table, otherwise this fragment is just added
  5. * to the linked list of fragments for this packet.
  6. * The list of fragments for a specific datagram is kept sorted for
  7. * easier handling.
  8. *
  9. * Datagrams (messages) are identified by a key generated by
  10. * fragment_temporary_key or fragment_persistent_key, based on the "pinfo", "id"
  11. * and "data" pairs. (This is the sole purpose of "data".)
  12. *
  13. * Fragments are identified by "frag_offset".
  14. *
  15. * Returns a pointer to the head of the fragment data list if we have all the
  16. * fragments, NULL otherwise. Note that the reassembled fragments list may have
  17. * a non-zero fragment offset, the only guarantee is that no gaps exist within
  18. * the list.
  19. */
  20. WS_DLL_PUBLIC fragment_head *
  21. fragment_add(reassembly_table *table, tvbuff_t *tvb, const int offset,
  22. const packet_info *pinfo, const guint32 id, const void *data,
  23. const guint32 frag_offset, const guint32 frag_data_len,
  24. const gboolean more_frags);

添加分片有多个函数变种, 其中一些会调用到 fragment_add_common 函数.

  1. // epan/reassemble.c
  2. static fragment_head *
  3. fragment_add_common(reassembly_table *table, tvbuff_t *tvb, const int offset,
  4. const packet_info *pinfo, const guint32 id,
  5. const void *data, const guint32 frag_offset,
  6. const guint32 frag_data_len, const gboolean more_frags,
  7. const gboolean check_already_added)
  8. {
  9. fragment_head *fd_head;
  10. fragment_item *fd_item;
  11. gboolean already_added;
  12. /*
  13. * Dissector shouldn't give us garbage tvb info.
  14. *
  15. * XXX - should this code take responsibility for preventing
  16. * reassembly if data is missing due to the packets being
  17. * sliced, rather than leaving it up to dissectors?
  18. */
  19. DISSECTOR_ASSERT(tvb_bytes_exist(tvb, offset, frag_data_len));
  20. /* key的产生与查表
  21. fragment_temporary_key() lookup()
  22. (pinfo, id, data) -------------------------> key -----------> fragment_head
  23. fragment_table -->
  24. */
  25. fd_head = lookup_fd_head(table, pinfo, id, data, NULL);
  26. ...
  27. // 检查当前报文是否已经处理过了. 如果检查发现此分片已经在分片表中, 则无动作
  28. /*
  29. * Is this the first pass through the capture?
  30. */
  31. if (!pinfo->fd->visited) {
  32. /*
  33. * Yes, so we could be doing reassembly. If
  34. * "check_already_added" is true, and fd_head is non-null,
  35. * meaning that this fragment would be added to an
  36. * in-progress reassembly, check if we have seen this
  37. * fragment before, i.e., if we have already added it to
  38. * that reassembly. That can be true even on the first pass
  39. * since we sometimes might call a subdissector multiple
  40. * times.
  41. *
  42. * We check both the frame number and the fragment offset,
  43. * so that we support multiple fragments from the same
  44. * frame being added to the same reassembled PDU.
  45. */
  46. if (check_already_added && fd_head != NULL) {
  47. /*
  48. * fd_head->frame is the maximum of the frame
  49. * numbers of all the fragments added to this
  50. * reassembly; if this frame is later than that
  51. * frame, we know it hasn't been added yet.
  52. */
  53. if (pinfo->num <= fd_head->frame) {
  54. already_added = FALSE;
  55. /*
  56. * The first item in the reassembly list
  57. * is not a fragment, it's a data structure
  58. * for the reassembled packet, so we
  59. * start checking with the next item.
  60. */
  61. for (fd_item = fd_head->next; fd_item;
  62. fd_item = fd_item->next) {
  63. if (pinfo->num == fd_item->frame &&
  64. frag_offset == fd_item->offset) {
  65. already_added = TRUE;
  66. break;
  67. }
  68. }
  69. if (already_added) {
  70. /*
  71. * Have we already finished
  72. * reassembling?
  73. */
  74. if (fd_head->flags & FD_DEFRAGMENTED) {
  75. /*
  76. * Yes.
  77. * XXX - can this ever happen?
  78. */
  79. THROW_MESSAGE(ReassemblyError,
  80. "Frame already added in first pass");
  81. } else {
  82. /*
  83. * No.
  84. */
  85. return NULL;
  86. }
  87. }
  88. }
  89. }
  90. } else {
  91. */
  92. /*
  93. * No, so we've already done all the reassembly and added
  94. * all the fragments. Do we have a reassembly and, if so,
  95. * have we finished reassembling?
  96. */
  97. if (fd_head != NULL && fd_head->flags & FD_DEFRAGMENTED) {
  98. // FD_DEFRAGMENTED 表示之前已经重组过, 这时需要检查一些特殊情况
  99. /*
  100. * Yes. This is probably being done after the
  101. * first pass, and we've already done the work
  102. * on the first pass.
  103. *
  104. * If the reassembly got a fatal error, throw that
  105. * error again.
  106. */
  107. if (fd_head->error)
  108. THROW_MESSAGE(ReassemblyError, fd_head->error);
  109. /*
  110. * Is it later in the capture than all of the
  111. * fragments in the reassembly?
  112. */
  113. if (pinfo->num > fd_head->frame) {
  114. /*
  115. * Yes, so report this as a problem,
  116. * possibly a retransmission.
  117. */
  118. THROW_MESSAGE(ReassemblyError, "New fragment overlaps old data (retransmission?)");
  119. }
  120. /*
  121. * Does this fragment go past the end of the
  122. * results of that reassembly?
  123. */
  124. if (frag_offset + frag_data_len > fd_head->datalen) {
  125. /*
  126. * Yes.
  127. */
  128. if (frag_offset >= fd_head->datalen) {
  129. /*
  130. * The fragment starts past the
  131. * end of the reassembled data.
  132. */
  133. THROW_MESSAGE(ReassemblyError, "New fragment past old data limits");
  134. } else {
  135. /*
  136. * The fragment starts before the end
  137. * of the reassembled data, but
  138. * runs past the end. That could
  139. * just be a retransmission.
  140. */
  141. THROW_MESSAGE(ReassemblyError, "New fragment overlaps old data (retransmission?)");
  142. }
  143. }
  144. return fd_head;
  145. } else {
  146. /*
  147. * No.
  148. */
  149. return NULL;
  150. }
  151. }
  152. // 正常流程
  153. // 如果是第一个分片, 则创建新的分片链表头, 把它加到分片哈希表
  154. if (fd_head==NULL){
  155. /* not found, this must be the first snooped fragment for this
  156. * packet. Create list-head.
  157. */
  158. fd_head = new_head(0);
  159. /*
  160. * Insert it into the hash table.
  161. */
  162. insert_fd_head(table, fd_head, pinfo, id, data);
  163. }
  164. // fragment_add_work()真正处理分片添加
  165. if (fragment_add_work(fd_head, tvb, offset, pinfo, frag_offset,
  166. frag_data_len, more_frags)) {
  167. /*
  168. * Reassembly is complete.
  169. */
  170. return fd_head;
  171. } else {
  172. /*
  173. * Reassembly isn't complete.
  174. */
  175. return NULL;
  176. }
  177. }

fragment_add_work():

  1. // epan/reassemble.c
  2. /*
  3. * This function adds a new fragment to the fragment hash table.
  4. * If this is the first fragment seen for this datagram, a new entry
  5. * is created in the hash table, otherwise this fragment is just added
  6. * to the linked list of fragments for this packet.
  7. * The list of fragments for a specific datagram is kept sorted for
  8. * easier handling.
  9. *
  10. * Returns a pointer to the head of the fragment data list if we have all the
  11. * fragments, NULL otherwise.
  12. *
  13. * This function assumes frag_offset being a byte offset into the defragment
  14. * packet.
  15. *
  16. * 01-2002
  17. * Once the fh is defragmented (= FD_DEFRAGMENTED set), it can be
  18. * extended using the FD_PARTIAL_REASSEMBLY flag. This flag should be set
  19. * using fragment_set_partial_reassembly() before calling fragment_add
  20. * with the new fragment. FD_TOOLONGFRAGMENT and FD_MULTIPLETAILS flags
  21. * are lowered when a new extension process is started.
  22. */
  23. static gboolean
  24. fragment_add_work(fragment_head *fd_head, tvbuff_t *tvb, const int offset,
  25. const packet_info *pinfo, const guint32 frag_offset,
  26. const guint32 frag_data_len, const gboolean more_frags)
  27. {
  28. fragment_item *fd;
  29. fragment_item *fd_i;
  30. guint32 max, dfpos, fraglen;
  31. tvbuff_t *old_tvb_data;
  32. guint8 *data;
  33. /* create new fd describing this fragment */
  34. fd = g_slice_new(fragment_item);
  35. fd->next = NULL;
  36. fd->flags = 0;
  37. fd->frame = pinfo->num;
  38. fd->offset = frag_offset;
  39. fd->fragment_nr_offset = 0; /* will only be used with sequence */
  40. fd->len = frag_data_len;
  41. fd->tvb_data = NULL;
  42. fd->error = NULL;
  43. // 检查是否已经重组过, 非正常流程
  44. /*
  45. * Are we adding to an already-completed reassembly?
  46. */
  47. if (fd_head->flags & FD_DEFRAGMENTED) {
  48. ...
  49. }
  50. // 设置帧序号, 重组链表头节点中的序号是最大序号
  51. /* Do this after we may have bailed out (above) so that we don't leave
  52. * fd_head->frame in a bad state if we do */
  53. if (fd->frame > fd_head->frame)
  54. fd_head->frame = fd->frame;
  55. // 没有更多分片, 即当前分片是最后一个分片
  56. if (!more_frags) {
  57. /*
  58. * This is the tail fragment in the sequence.
  59. */
  60. if (fd_head->flags & FD_DATALEN_SET) {
  61. /* ok we have already seen other tails for this packet
  62. * it might be a duplicate.
  63. */
  64. if (fd_head->datalen != (fd->offset + fd->len) ){
  65. /* Oops, this tail indicates a different packet
  66. * len than the previous ones. Something's wrong.
  67. */
  68. fd->flags |= FD_MULTIPLETAILS;
  69. fd_head->flags |= FD_MULTIPLETAILS;
  70. }
  71. } else {
  72. // 如果是正常的最后一个分片, 需要设置分片数据总长度, 并设置FD_DATALEN_SET
  73. /* This was the first tail fragment; now we know
  74. * what the length of the packet should be.
  75. */
  76. fd_head->datalen = fd->offset + fd->len;
  77. fd_head->flags |= FD_DATALEN_SET;
  78. }
  79. }
  80. /* If the packet is already defragmented, this MUST be an overlap.
  81. * The entire defragmented packet is in fd_head->data.
  82. * Even if we have previously defragmented this packet, we still
  83. * check it. Someone might play overlap and TTL games.
  84. */
  85. if (fd_head->flags & FD_DEFRAGMENTED) {
  86. ...
  87. /* it was just an overlap, link it and return */
  88. LINK_FRAG(fd_head,fd);
  89. return TRUE;
  90. }
  91. /* If we have reached this point, the packet is not defragmented yet.
  92. * Save all payload in a buffer until we can defragment.
  93. */
  94. if (!tvb_bytes_exist(tvb, offset, fd->len)) {
  95. g_slice_free(fragment_item, fd);
  96. THROW(BoundsError);
  97. }
  98. // 正常流程, 创建一个clone tvb. 分片添加到链表
  99. fd->tvb_data = tvb_clone_offset_len(tvb, offset, fd->len);
  100. LINK_FRAG(fd_head,fd);
  101. if( !(fd_head->flags & FD_DATALEN_SET) ){
  102. /* if we don't know the datalen, there are still missing
  103. * packets. Cheaper than the check below.
  104. */
  105. return FALSE;
  106. }
  107. /*
  108. * Check if we have received the entire fragment.
  109. * This is easy since the list is sorted and the head is faked.
  110. *
  111. * First, we compute the amount of contiguous data that's
  112. * available. (The check for fd_i->offset <= max rules out
  113. * fragments that don't start before or at the end of the
  114. * previous fragment, i.e. fragments that have a gap between
  115. * them and the previous fragment.)
  116. */
  117. max = 0;
  118. for (fd_i=fd_head->next;fd_i;fd_i=fd_i->next) {
  119. if ( ((fd_i->offset)<=max) &&
  120. ((fd_i->offset+fd_i->len)>max) ){
  121. max = fd_i->offset+fd_i->len;
  122. }
  123. }
  124. if (max < (fd_head->datalen)) {
  125. /*
  126. * The amount of contiguous data we have is less than the
  127. * amount of data we're trying to reassemble, so we haven't
  128. * received all packets yet.
  129. */
  130. return FALSE;
  131. }
  132. // 收到完整数据, 分配一块大的内存, 把每个分片的数据拷贝到其中
  133. /* we have received an entire packet, defragment it and
  134. * free all fragments
  135. */
  136. /* store old data just in case */
  137. old_tvb_data=fd_head->tvb_data;
  138. data = (guint8 *) g_malloc(fd_head->datalen);
  139. fd_head->tvb_data = tvb_new_real_data(data, fd_head->datalen, fd_head->datalen);
  140. tvb_set_free_cb(fd_head->tvb_data, g_free);
  141. /* add all data fragments */
  142. for (dfpos=0,fd_i=fd_head;fd_i;fd_i=fd_i->next) {
  143. if (fd_i->len) {
  144. /*
  145. * The loop above that calculates max also
  146. * ensures that the only gaps that exist here
  147. * are ones where a fragment starts past the
  148. * end of the reassembled datagram, and there's
  149. * a gap between the previous fragment and
  150. * that fragment.
  151. *
  152. * A "DESEGMENT_UNTIL_FIN" was involved wherein the
  153. * FIN packet had an offset less than the highest
  154. * fragment offset seen. [Seen from a fuzz-test:
  155. * bug #2470]).
  156. *
  157. * Note that the "overlap" compare must only be
  158. * done for fragments with (offset+len) <= fd_head->datalen
  159. * and thus within the newly g_malloc'd buffer.
  160. */
  161. if (fd_i->offset + fd_i->len > dfpos) {
  162. if (fd_i->offset >= fd_head->datalen) {
  163. /*
  164. * Fragment starts after the end
  165. * of the reassembled packet.
  166. *
  167. * This can happen if the length was
  168. * set after the offending fragment
  169. * was added to the reassembly.
  170. *
  171. * Flag this fragment, but don't
  172. * try to extract any data from
  173. * it, as there's no place to put
  174. * it.
  175. *
  176. * XXX - add different flag value
  177. * for this.
  178. */
  179. fd_i->flags |= FD_TOOLONGFRAGMENT;
  180. fd_head->flags |= FD_TOOLONGFRAGMENT;
  181. } else if (dfpos < fd_i->offset) {
  182. /*
  183. * XXX - can this happen? We've
  184. * already rejected fragments that
  185. * start past the end of the
  186. * reassembled datagram, and
  187. * the loop that calculated max
  188. * should have ruled out gaps,
  189. * but could fd_i->offset +
  190. * fd_i->len overflow?
  191. */
  192. fd_head->error = "dfpos < offset";
  193. } else if (dfpos - fd_i->offset > fd_i->len)
  194. fd_head->error = "dfpos - offset > len";
  195. else if (!fd_i->tvb_data)
  196. fd_head->error = "no data";
  197. else {
  198. fraglen = fd_i->len;
  199. if (fd_i->offset + fraglen > fd_head->datalen) {
  200. /*
  201. * Fragment goes past the end
  202. * of the packet, as indicated
  203. * by the last fragment.
  204. *
  205. * This can happen if the
  206. * length was set after the
  207. * offending fragment was
  208. * added to the reassembly.
  209. *
  210. * Mark it as such, and only
  211. * copy from it what fits in
  212. * the packet.
  213. */
  214. fd_i->flags |= FD_TOOLONGFRAGMENT;
  215. fd_head->flags |= FD_TOOLONGFRAGMENT;
  216. fraglen = fd_head->datalen - fd_i->offset;
  217. }
  218. if (fd_i->offset < dfpos) {
  219. guint32 cmp_len = MIN(fd_i->len,(dfpos-fd_i->offset));
  220. fd_i->flags |= FD_OVERLAP;
  221. fd_head->flags |= FD_OVERLAP;
  222. if ( memcmp(data + fd_i->offset,
  223. tvb_get_ptr(fd_i->tvb_data, 0, cmp_len),
  224. cmp_len)
  225. ) {
  226. fd_i->flags |= FD_OVERLAPCONFLICT;
  227. fd_head->flags |= FD_OVERLAPCONFLICT;
  228. }
  229. }
  230. if (fraglen < dfpos - fd_i->offset) {
  231. /*
  232. * XXX - can this happen?
  233. */
  234. fd_head->error = "fraglen < dfpos - offset";
  235. } else {
  236. memcpy(data+dfpos,
  237. tvb_get_ptr(fd_i->tvb_data, (dfpos-fd_i->offset), fraglen-(dfpos-fd_i->offset)),
  238. fraglen-(dfpos-fd_i->offset));
  239. dfpos=MAX(dfpos, (fd_i->offset + fraglen));
  240. }
  241. }
  242. } else {
  243. if (fd_i->offset + fd_i->len < fd_i->offset) {
  244. /* Integer overflow? */
  245. fd_head->error = "offset + len < offset";
  246. }
  247. }
  248. if (fd_i->flags & FD_SUBSET_TVB)
  249. fd_i->flags &= ~FD_SUBSET_TVB;
  250. else if (fd_i->tvb_data)
  251. tvb_free(fd_i->tvb_data);
  252. fd_i->tvb_data=NULL;
  253. }
  254. }
  255. if (old_tvb_data)
  256. tvb_add_to_chain(tvb, old_tvb_data);
  257. /* mark this packet as defragmented.
  258. allows us to skip any trailing fragments */
  259. fd_head->flags |= FD_DEFRAGMENTED;
  260. fd_head->reassembled_in=pinfo->num;
  261. fd_head->reas_in_layer_num = pinfo->curr_layer_num;
  262. /* we don't throw until here to avoid leaking old_data and others */
  263. if (fd_head->error) {
  264. THROW_MESSAGE(ReassemblyError, fd_head->error);
  265. }
  266. return TRUE;
  267. }

处理分片

  1. /*
  2. * Process reassembled data; if we're on the frame in which the data
  3. * was reassembled, put the fragment information into the protocol
  4. * tree, and construct a tvbuff with the reassembled data, otherwise
  5. * just put a "reassembled in" item into the protocol tree.
  6. */
  7. tvbuff_t *
  8. process_reassembled_data(tvbuff_t *tvb, const int offset, packet_info *pinfo,
  9. const char *name, fragment_head *fd_head, const fragment_items *fit,
  10. gboolean *update_col_infop, proto_tree *tree)
  11. {
  12. tvbuff_t *next_tvb;
  13. gboolean update_col_info;
  14. proto_item *frag_tree_item;
  15. if (fd_head != NULL && pinfo->num == fd_head->reassembled_in && pinfo->curr_layer_num == fd_head->reas_in_layer_num) {
  16. /*
  17. * OK, we've reassembled this.
  18. * Is this something that's been reassembled from more
  19. * than one fragment?
  20. */
  21. if (fd_head->next != NULL) {
  22. /*
  23. * Yes.
  24. * Allocate a new tvbuff, referring to the
  25. * reassembled payload, and set
  26. * the tvbuff to the list of tvbuffs to which
  27. * the tvbuff we were handed refers, so it'll get
  28. * cleaned up when that tvbuff is cleaned up.
  29. */
  30. next_tvb = tvb_new_chain(tvb, fd_head->tvb_data);
  31. /* Add the defragmented data to the data source list. */
  32. add_new_data_source(pinfo, next_tvb, name);
  33. /* show all fragments */
  34. if (fd_head->flags & FD_BLOCKSEQUENCE) {
  35. update_col_info = !show_fragment_seq_tree(
  36. fd_head, fit, tree, pinfo, next_tvb, &frag_tree_item);
  37. } else {
  38. update_col_info = !show_fragment_tree(fd_head,
  39. fit, tree, pinfo, next_tvb, &frag_tree_item);
  40. }
  41. } else {
  42. /*
  43. * No.
  44. * Return a tvbuff with the payload.
  45. */
  46. next_tvb = tvb_new_subset_remaining(tvb, offset);
  47. pinfo->fragmented = FALSE; /* one-fragment packet */
  48. update_col_info = TRUE;
  49. }
  50. if (update_col_infop != NULL)
  51. *update_col_infop = update_col_info;
  52. } else {
  53. /*
  54. * We don't have the complete reassembled payload, or this
  55. * isn't the final frame of that payload.
  56. */
  57. next_tvb = NULL;
  58. /*
  59. * If we know what frame this was reassembled in,
  60. * and if there's a field to use for the number of
  61. * the frame in which the packet was reassembled,
  62. * add it to the protocol tree.
  63. */
  64. if (fd_head != NULL && fit->hf_reassembled_in != NULL) {
  65. proto_item *fei = proto_tree_add_uint(tree,
  66. *(fit->hf_reassembled_in), tvb,
  67. 0, 0, fd_head->reassembled_in);
  68. proto_item_set_generated(fei);
  69. }
  70. }
  71. return next_tvb;
  72. }

IP重组分析

TCP重组分析

通过 gdb 跟踪分析 tshark 打开一个 HTTPS 报文时的执行过程.
bing.zip
企业微信截图_16220808012275.png
从上图可以看出, 第 12 个报文处进行了完整重组, 分片来自第 6, 7, 9, 10, 12 个报文.

命令行:

  1. $ cd ~/dev/wireshark_build/run/
  2. $ gdb ./tshark
  3. (gdb) b desegment_tcp
  4. (gdb) b fragment_add_work
  5. (gdb) r -r ~/pcap/bing.pcap

调试时, tcp协议使用以下默认选项:

  1. # Whether subdissector can request TCP streams to be reassembled
  2. # TRUE or FALSE (case-insensitive)
  3. #tcp.desegment_tcp_streams: TRUE
  4. # Whether out-of-order segments should be buffered and reordered before passing it to a subdissector. To use this option you must also enable "Allow subdissector to reassemble TCP streams".
  5. # TRUE or FALSE (case-insensitive)
  6. #tcp.reassemble_out_of_order: FALSE

调用栈

  1. #0 fragment_add_work (fd_head=..., tvb=..., offset=..., pinfo=..., frag_offset=..., frag_data_len=..., more_frags=...)
  2. #1 0x00007ffff3d1121d in fragment_add_common (table=..., tvb=..., offset=..., pinfo=..., id=..., data=..., frag_offset=...,
  3. frag_data_len=..., more_frags=..., check_already_added=...)
  4. #2 0x00007ffff3d11282 in fragment_add (table=..., tvb=..., offset=..., pinfo=..., id=..., data=..., frag_offset=...,
  5. frag_data_len=..., more_frags=...)
  6. #3 0x00007ffff31f4198 in desegment_tcp (tvb=..., pinfo=..., offset=..., seq=..., nxtseq=..., sport=..., dport=..., tree=...,
  7. tcp_tree=..., tcpd=..., tcpinfo=...)
  8. #4 0x00007ffff31fa945 in dissect_tcp_payload (tvb=..., pinfo=..., offset=..., seq=..., nxtseq=..., sport=..., dport=..., tree=...,
  9. tcp_tree=..., tcpd=..., tcpinfo=...)
  10. #5 0x00007ffff31fdfc4 in dissect_tcp (tvb=..., pinfo=..., tree=..., data=...)
  11. ...
  12. #29 0x00007ffff3ccf552 in call_dissector_with_data (handle=..., tvb=..., pinfo=..., tree=..., data=...)
  13. #30 0x00007ffff3cca9d8 in dissect_record (edt=..., file_type_subtype=..., rec=..., tvb=..., fd=..., cinfo=...)
  14. #31 0x00007ffff3cbf1d3 in epan_dissect_run_with_taps (edt=..., file_type_subtype=..., rec=..., tvb=..., fd=..., cinfo=...)
  15. #32 0x0000555555576b50 in process_packet_single_pass (cf=..., edt=..., offset=..., rec=..., buf=..., tap_flags=...)
  16. #33 0x0000555555576016 in process_cap_file_single_pass (cf=..., pdh=..., max_packet_count=..., max_byte_count=..., err=...,
  17. err_info=..., err_framenum=...)
  18. #34 0x0000555555576575 in process_cap_file (cf=..., save_file=..., out_file_type=..., out_file_name_res=..., max_packet_count=...,
  19. max_byte_count=...)
  20. #35 0x0000555555573d12 in main (argc=..., argv=...)

流程

  1. 1. 第一个分片
  2. - 创建分片链表头fd_head, 插入重组哈希表(insert_fd_head)
  3. - fragment_add_work, 重组完成返回分片表头, 否则返回NULL
  4. - 拷贝数据 (tvb_clone_offset_len)
  5. - 将分片插入有序链表 (LINK_FRAG)
  6. 2. 中间的分片
  7. - tcpd(struct tcp_analysis) 已存在
  8. - fragment_add_work
  9. - 1, 调用LINK_FRAG插入有序链表
  10. 3. 最后一个分片
  11. - tcpd(struct tcp_analysis) 已存在
  12. - fragment_add_work
  13. - more_fragsFALSE, 没有更多分片, 设置fd_headdatalen
  14. - 调用LINK_FRAG插入有序链表
  15. - 遍历有序链表, 检查数据是否完整
  16. - 分配一个大缓冲区, 以容纳重组后的PDU
  17. - 遍历有序链表, 将分片数据拷贝到大缓冲区, 然后释放原分片数据内存
  18. - fd_head置为已重组
  19. - 返回fd_head. 注意之前的分片时都返回NULL
  20. - 对重组后的数据创建新tvb (next_tvb = tvb_new_chain)
  21. - 调用上层解析器 (process_tcp_payload)
  22. - 又创建tvb (next_tvb = tvb_new_subset_remaining)
  23. - dissect_ssl (TLS)

// TODO:

  1. struct tcp_analysis, pdu, struct tcp_multisegment_pdu, desegment_tcp调用时机, TCP是怎样判断是分片的
  2. TCP序列号分析: tcp_analyze_sequence_number

tcp_analysis 是 tcp conversation 的数据: tcpd=get_tcp_conversation_data(conv,pinfo);

layer的概念:

  1. if (add_proto_name) {
  2. pinfo->curr_layer_num++;
  3. wmem_list_append(pinfo->layers, GINT_TO_POINTER(proto_get_id(handle->protocol)));
  4. }

如何确定是分片, 或者说需要重组?

pkt #6

  1. - 获取conversation
  2. - conversation获取tcp_analysis
  3. - 检查报文是IP分片情况(pinfo->fragmented)
  4. - 如果不是, 分析TCP序列号, 计算win
  5. - 如果打开tcp重组, pinfo->can_desegment=2
  6. - 调用dissect_tcp_payload()
  7. - 调用desegment_tcp()
  8. - 检查当前报文是不是要重组报文的一部分(msp) epan/dissectors/packet-tcp.c #3142
  9. - 如果不是, 创建msp
  10. - 调用process_tcp_payload()
  11. - 调用decode_tcp_ports()
  12. - 创建新tvb
  13. - 通过dissector_try_uint_new()调用到dissect_ssl()
  14. - dissect_ssl3_record()检测到需要重组, 返回到decode_tcp_ports()
  15. 此时tls还正确设置了pinfo->desegment_len, 表示还需要多少数据
  16. - 返回desegment_tcp(), must_desegment = TRUE
  17. - 添加msp (pdu_store_sequencenumber_of_next_pdu())
  18. - 添加分片 (fragement_add())

pkt #7, #9, #10

  1. - 获取conversation
  2. - conversation获取tcp_analysis
  3. - 检查报文是IP分片情况(pinfo->fragmented)
  4. - 如果不是, 分析TCP序列号, 计算win
  5. - 如果打开tcp重组, pinfo->can_desegment=2
  6. - 调用dissect_tcp_payload()
  7. - 调用desegment_tcp()
  8. - 检查当前报文是不是要重组报文的一部分(msp) epan/dissectors/packet-tcp.c #3142
  9. - 添加分片 (fragement_add()) epan/dissectors/packet-tcp.c #3323

pkt #12

  1. - 获取conversation
  2. - conversation获取tcp_analysis
  3. - 检查报文是IP分片情况(pinfo->fragmented)
  4. - 如果不是, 分析TCP序列号, 计算win
  5. - 如果打开tcp重组, pinfo->can_desegment=2
  6. - 调用dissect_tcp_payload()
  7. - 调用desegment_tcp()
  8. - 检查当前报文是不是要重组报文的一部分(msp) epan/dissectors/packet-tcp.c #3142
  9. - 添加分片 (fragement_add()) epan/dissectors/packet-tcp.c #3323
  10. - 添加分片返回非空值, 表示所有分片到齐
  11. - 创建新的tvb, 主要是把之前的tvb和重组后的tvb链接起来
  12. - 调用process_tcp_payload()
  13. - 调用decode_tcp_ports()
  14. - 创建新tvb
  15. - 通过dissector_try_uint_new()调用到dissect_ssl()
  16. - dissect_ssl3_record() TLS PDU完整, 解析完成

HTTP重组分析

// TODO

DNS重组分析

// TODO

RTMPT重组分析

通过 gdb 跟踪分析 tshark 打开一个 RTMP 报文时的执行过程.
![3JY_ES{CRGRT9@NFQ}0072.png

  1. $ cd ~/dev/wireshark_build/run/
  2. $ gdb ./tshark
  3. (gdb) set print frame-info short-location
  4. (gdb) set print frame-arguments none
  5. (gdb) b dissect_rtmpt_tcp
  6. (gdb) b fragment_add_work
  7. (gdb) r -r ~/pcap/renren_rtmp.pcap
  1. #0 dissect_rtmpt_tcp (tvb=..., pinfo=..., tree=..., data=...)
  2. #1 0x00007ffff3ccb0c2 in call_dissector_through_handle (handle=..., tvb=..., pinfo=..., tree=..., data=...)
  3. #2 0x00007ffff3ccb313 in call_dissector_work (handle=..., tvb=..., pinfo_arg=..., tree=..., add_proto_name=..., data=...)
  4. #3 0x00007ffff3ccc4ed in dissector_try_uint_new (sub_dissectors=..., uint_val=..., tvb=..., pinfo=..., tree=..., add_proto_name=...,
  5. data=...)
  6. #4 0x00007ffff31fa149 in decode_tcp_ports (tvb=..., offset=..., pinfo=..., tree=..., src_port=..., dst_port=..., tcpd=..., tcpinfo=...)
  7. #5 0x00007ffff31fa68a in process_tcp_payload (tvb=..., offset=..., pinfo=..., tree=..., tcp_tree=..., src_port=..., dst_port=..., seq=...,
  8. nxtseq=..., is_tcp_segment=..., tcpd=..., tcpinfo=...)
  9. #6 0x00007ffff31f3bee in desegment_tcp (tvb=..., pinfo=..., offset=..., seq=..., nxtseq=..., sport=..., dport=..., tree=..., tcp_tree=...,
  10. tcpd=..., tcpinfo=...)
  11. #7 0x00007ffff31fa945 in dissect_tcp_payload (tvb=..., pinfo=..., offset=..., seq=..., nxtseq=..., sport=..., dport=..., tree=...,
  12. tcp_tree=..., tcpd=..., tcpinfo=...)
  13. #8 0x00007ffff31fdfc4 in dissect_tcp (tvb=..., pinfo=..., tree=..., data=...)
  14. ...
  15. #32 0x00007ffff3ccf552 in call_dissector_with_data (handle=..., tvb=..., pinfo=..., tree=..., data=...)
  16. #33 0x00007ffff3cca9d8 in dissect_record (edt=..., file_type_subtype=..., rec=..., tvb=..., fd=..., cinfo=...)
  17. #34 0x00007ffff3cbf1d3 in epan_dissect_run_with_taps (edt=..., file_type_subtype=..., rec=..., tvb=..., fd=..., cinfo=...)
  18. #35 0x0000555555576b50 in process_packet_single_pass (cf=..., edt=..., offset=..., rec=..., buf=..., tap_flags=...)
  19. #36 0x0000555555576016 in process_cap_file_single_pass (cf=..., pdh=..., max_packet_count=..., max_byte_count=..., err=..., err_info=...,
  20. err_framenum=...)
  21. #37 0x0000555555576575 in process_cap_file (cf=..., save_file=..., out_file_type=..., out_file_name_res=..., max_packet_count=...,
  22. max_byte_count=...)
  23. #38 0x0000555555573d12 in main (argc=..., argv=...)
  1. /* Represents a header or a chunk that is split over two TCP
  2. * segments
  3. */
  4. typedef struct rtmpt_frag {
  5. int ishdr;
  6. guint32 seq;
  7. guint32 lastseq;
  8. int have;
  9. int len;
  10. union {
  11. guint8 d[18]; /* enough for a complete header (3 + 11 + 4) */
  12. guint32 id;
  13. } saved;
  14. } rtmpt_frag_t;
  15. /* The full message header information for the last packet on a particular
  16. * ID - used for defaulting short headers
  17. */
  18. typedef struct rtmpt_id {
  19. guint32 ts; /* bytes 1-3 */
  20. guint32 tsd;
  21. guint32 len; /* bytes 4-6 */
  22. guint32 src; /* bytes 8-11 */
  23. guint8 cmd; /* byte 7 */
  24. wmem_tree_t *packets;
  25. } rtmpt_id_t;
  26. /* Historical view of a whole TCP connection
  27. */
  28. typedef struct rtmpt_conv {
  29. wmem_tree_t *seqs[2];
  30. wmem_tree_t *frags[2];
  31. wmem_tree_t *ids[2];
  32. wmem_tree_t *packets[2];
  33. wmem_tree_t *chunksize[2];
  34. wmem_tree_t *txids[2];
  35. } rtmpt_conv_t;

dissect_rtmpt_tcp:

  1. static int
  2. dissect_rtmpt_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
  3. {
  4. conversation_t *conv;
  5. rtmpt_conv_t *rconv;
  6. int cdir;
  7. struct tcpinfo *tcpinfo;
  8. /* Reject the packet if data is NULL */
  9. if (data == NULL) {
  10. return 0;
  11. }
  12. tcpinfo = (struct tcpinfo*)data;
  13. conv = find_or_create_conversation(pinfo);
  14. rconv = (rtmpt_conv_t*)conversation_get_proto_data(conv, proto_rtmpt);
  15. if (!rconv) {
  16. rconv = rtmpt_init_rconv(conv);
  17. }
  18. cdir = (addresses_equal(conversation_key_addr1(conv->key_ptr), &pinfo->src) &&
  19. addresses_equal(conversation_key_addr2(conv->key_ptr), &pinfo->dst) &&
  20. conversation_key_port1(conv->key_ptr) == pinfo->srcport &&
  21. conversation_key_port2(conv->key_ptr) == pinfo->destport) ? 0 : 1;
  22. dissect_rtmpt_common(tvb, pinfo, tree, rconv, cdir, tcpinfo->seq, tcpinfo->lastackseq);
  23. return tvb_reported_length(tvb);
  24. }

第 5 个报文(Handshake C0+C1)的完整数据分散在 2 个 TCP 报文 #4 #5 中.

  1. 1. 第一个分片, #4
  2. - rtmp会话不存在, 初始化 (rtmpt_init_rconv)
  3. - rtmp会话创建后要插入到通用会话表项中的协议私有数据树中 (conversation_add_proto_data(conv, proto_rtmpt, rconv))
  4. void
  5. conversation_add_proto_data(conversation_t *conv, const int proto, void *proto_data)
  6. {
  7. /* Add it to the list of items for this conversation. */
  8. if (conv->data_list == NULL)
  9. conv->data_list = wmem_tree_new(wmem_file_scope());
  10. wmem_tree_insert32(conv->data_list, proto, proto_data);
  11. }
  12. - rtmp会话是分2个方向的, 因此之后要判断方向(cdir)
  13. - dissect_rtmpt_common
  14. - rtmp会话中查找当前方向之前的分片 (tf = (rtmpt_frag_t *)wmem_tree_lookup32_le(rconv->frags[cdir], seq+offset-1))
  15. - 没找到, 说明这是首部, 解析rtmp首部信息
  16. - 将当前分片id信息(rtmpt_id)插入查找树 (wmem_tree_insert32(rconv->ids[cdir], id, ti))
  17. - 创建rtmp packet(pdu), 填入分片相关信息, 如整个报文的总长度(tp->want = basic_hlen + message_hlen + body_len)
  18. 这时候还缺一部分, 在后面的报文
  19. - rtmp packet插入ti->packets
  20. - 分配内存, 以容纳所有分片 (tp->data.p = (guint8 *)wmem_alloc(wmem_file_scope(), tp->bhlen+tp->mhlen+tp->len))
  21. 将当前分片数据拷贝到此内存中
  22. - 新建rtmp frag, 填写分片信息, 插入查找树 (wmem_tree_insert32(rconv->frags[cdir], seq+offset-want-1, tf2))
  23. 2. 最后一个分片
  24. - rtmp会话已存在
  25. - dissect_rtmpt_common
  26. - 找到已保存的上一个分片信息tf
  27. - 进而找到分片id tipdu tp, 跳到unchunk
  28. - 已有所有分片(tp->have == tp->want), 开始解析
  29. - dissect_rtmpt

参考