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.
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.
msg_pkt ::= SEQUENCE { ..... flags ::= SEQUENCE { fragment BOOLEAN, last_fragment BOOLEAN, ..... } msg_id INTEGER(0..65535), frag_id INTEGER(0..65535), ..... }
Reassembling fragments - Part 1.
#include <epan/reassemble.h> ... save_fragmented = pinfo->fragmented; flags = tvb_get_guint8(tvb, offset); offset++; if (flags & FL_FRAGMENT) { /* fragmented */ tvbuff_t* new_tvb = NULL; fragment_data *frag_msg = NULL; guint16 msg_seqid = tvb_get_ntohs(tvb, offset); offset += 2; guint16 msg_num = tvb_get_ntohs(tvb, offset); offset += 2; pinfo->fragmented = TRUE; frag_msg = fragment_add_seq_check(msg_reassembly_table, tvb, offset, pinfo, msg_seqid, NULL, /* ID for fragments belonging together */ msg_num, /* fragment sequence number */ tvb_captured_length_remaining(tvb, offset), /* fragment length - to the end */ 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:
msg_reassembly_table
table is for bookkeeping and is described later.
NULL
(as is done in the example) for most dissectors.
Reassembling fragments part 2.
new_tvb = process_reassembled_data(tvb, offset, pinfo, "Reassembled Message", frag_msg, &msg_frag_items, NULL, msg_tree); if (frag_msg) { /* Reassembled */ col_append_str(pinfo->cinfo, COL_INFO, " (Message Reassembled)"); } else { /* Not last packet of reassembled Short Message */ col_append_fstr(pinfo->cinfo, COL_INFO, " (Message fragment %u)", msg_num); } if (new_tvb) { /* take it all */ next_tvb = new_tvb; } else { /* make a new subset */ next_tvb = tvb_new_subset_remaining(tvb, offset); } } else { /* Not fragmented */ next_tvb = tvb_new_subset_remaining(tvb, offset); } ..... 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()
.
Reassembling fragments - Initialisation.
static reassembly_table reassembly_table; static void proto_register_msg(void) { reassembly_table_register(&msg_reassemble_table, &addresses_ports_reassembly_table_functions); }
First a reassembly_table
structure 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.
Reassembling fragments - Data.
... static int hf_msg_fragments = -1; static int hf_msg_fragment = -1; static int hf_msg_fragment_overlap = -1; static int hf_msg_fragment_overlap_conflicts = -1; static int hf_msg_fragment_multiple_tails = -1; static int hf_msg_fragment_too_long_fragment = -1; static int hf_msg_fragment_error = -1; static int hf_msg_fragment_count = -1; static int hf_msg_reassembled_in = -1; static int hf_msg_reassembled_length = -1; ... static gint ett_msg_fragment = -1; static gint ett_msg_fragments = -1; ... static const fragment_items msg_frag_items = { /* Fragment subtrees */ &ett_msg_fragment, &ett_msg_fragments, /* Fragment fields */ &hf_msg_fragments, &hf_msg_fragment, &hf_msg_fragment_overlap, &hf_msg_fragment_overlap_conflicts, &hf_msg_fragment_multiple_tails, &hf_msg_fragment_too_long_fragment, &hf_msg_fragment_error, &hf_msg_fragment_count, /* Reassembled in field */ &hf_msg_reassembled_in, /* Reassembled length field */ &hf_msg_reassembled_length, /* Tag */ "Message fragments" }; ... static hf_register_info hf[] = { ... {&hf_msg_fragments, {"Message fragments", "msg.fragments", FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } }, {&hf_msg_fragment, {"Message fragment", "msg.fragment", FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, {&hf_msg_fragment_overlap, {"Message fragment overlap", "msg.fragment.overlap", FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } }, {&hf_msg_fragment_overlap_conflicts, {"Message fragment overlapping with conflicting data", "msg.fragment.overlap.conflicts", FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } }, {&hf_msg_fragment_multiple_tails, {"Message has multiple tail fragments", "msg.fragment.multiple_tails", FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } }, {&hf_msg_fragment_too_long_fragment, {"Message fragment too long", "msg.fragment.too_long_fragment", FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } }, {&hf_msg_fragment_error, {"Message defragmentation error", "msg.fragment.error", FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, {&hf_msg_fragment_count, {"Message fragment count", "msg.fragment.count", FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }, {&hf_msg_reassembled_in, {"Reassembled in", "msg.reassembled.in", FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, {&hf_msg_reassembled_length, {"Reassembled length", "msg.reassembled.length", FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }, ... static gint *ett[] = { ... &ett_msg_fragment, &ett_msg_fragments ...
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.
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.
Reassembling TCP fragments.
#include "config.h" #include <epan/packet.h> #include <epan/prefs.h> #include "packet-tcp.h" ... #define FRAME_HEADER_LEN 8 /* This method dissects fully reassembled messages */ static int dissect_foo_message(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree _U_, void *data _U_) { /* TODO: implement your dissecting code */ return tvb_captured_length(tvb); } /* determine PDU length of protocol foo */ static guint get_foo_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) { /* TODO: change this to your needs */ return (guint)tvb_get_ntohl(tvb, offset+4); /* e.g. length is at offset 4 */ } /* The main dissecting routine */ static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN, get_foo_message_len, dissect_foo_message, data); return tvb_captured_length(tvb); } ...
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.