diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
commit | e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch) | |
tree | 68cb5ef9081156392f1dd62a00c6ccc1451b93df /epan/reassemble.c | |
parent | Initial commit. (diff) | |
download | wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip |
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'epan/reassemble.c')
-rw-r--r-- | epan/reassemble.c | 3554 |
1 files changed, 3554 insertions, 0 deletions
diff --git a/epan/reassemble.c b/epan/reassemble.c new file mode 100644 index 00000000..6f06a8f1 --- /dev/null +++ b/epan/reassemble.c @@ -0,0 +1,3554 @@ +/* reassemble.c + * Routines for {fragment,segment} reassembly + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <string.h> + +#include <epan/packet.h> +#include <epan/exceptions.h> +#include <epan/reassemble.h> +#include <epan/tvbuff-int.h> + +#include <wsutil/str_util.h> +#include <wsutil/ws_assert.h> + +/* + * Functions for reassembly tables where the endpoint addresses, and a + * fragment ID, are used as the key. + */ +typedef struct _fragment_addresses_key { + address src; + address dst; + guint32 id; +} fragment_addresses_key; + +GList* reassembly_table_list = NULL; + +static guint +fragment_addresses_hash(gconstpointer k) +{ + const fragment_addresses_key* key = (const fragment_addresses_key*) k; + guint hash_val; +/* + int i; +*/ + + hash_val = 0; + +/* More than likely: in most captures src and dst addresses are the + same, and would hash the same. + We only use id as the hash as an optimization. + + for (i = 0; i < key->src.len; i++) + hash_val += key->src.data[i]; + for (i = 0; i < key->dst.len; i++) + hash_val += key->dst.data[i]; +*/ + + hash_val += key->id; + + return hash_val; +} + +static gint +fragment_addresses_equal(gconstpointer k1, gconstpointer k2) +{ + const fragment_addresses_key* key1 = (const fragment_addresses_key*) k1; + const fragment_addresses_key* key2 = (const fragment_addresses_key*) k2; + + /* + * key.id is the first item to compare since it's the item most + * likely to differ between sessions, thus short-circuiting + * the comparison of addresses. + */ + return (key1->id == key2->id) && + (addresses_equal(&key1->src, &key2->src)) && + (addresses_equal(&key1->dst, &key2->dst)); +} + +/* + * Create a fragment key for temporary use; it can point to non- + * persistent data, and so must only be used to look up and + * delete entries, not to add them. + */ +static gpointer +fragment_addresses_temporary_key(const packet_info *pinfo, const guint32 id, + const void *data _U_) +{ + fragment_addresses_key *key = g_slice_new(fragment_addresses_key); + + /* + * Do a shallow copy of the addresses. + */ + copy_address_shallow(&key->src, &pinfo->src); + copy_address_shallow(&key->dst, &pinfo->dst); + key->id = id; + + return (gpointer)key; +} + +/* + * Create a fragment key for permanent use; it must point to persistent + * data, so that it can be used to add entries. + */ +static gpointer +fragment_addresses_persistent_key(const packet_info *pinfo, const guint32 id, + const void *data _U_) +{ + fragment_addresses_key *key = g_slice_new(fragment_addresses_key); + + /* + * Do a deep copy of the addresses. + */ + copy_address(&key->src, &pinfo->src); + copy_address(&key->dst, &pinfo->dst); + key->id = id; + + return (gpointer)key; +} + +static void +fragment_addresses_free_temporary_key(gpointer ptr) +{ + fragment_addresses_key *key = (fragment_addresses_key *)ptr; + g_slice_free(fragment_addresses_key, key); +} + +static void +fragment_addresses_free_persistent_key(gpointer ptr) +{ + fragment_addresses_key *key = (fragment_addresses_key *)ptr; + + if(key){ + /* + * Free up the copies of the addresses from the old key. + */ + free_address(&key->src); + free_address(&key->dst); + + g_slice_free(fragment_addresses_key, key); + } +} + +const reassembly_table_functions +addresses_reassembly_table_functions = { + fragment_addresses_hash, + fragment_addresses_equal, + fragment_addresses_temporary_key, + fragment_addresses_persistent_key, + fragment_addresses_free_temporary_key, + fragment_addresses_free_persistent_key +}; + +/* + * Functions for reassembly tables where the endpoint addresses and ports, + * and a fragment ID, are used as the key. + */ +typedef struct _fragment_addresses_ports_key { + address src_addr; + address dst_addr; + guint32 src_port; + guint32 dst_port; + guint32 id; +} fragment_addresses_ports_key; + +static guint +fragment_addresses_ports_hash(gconstpointer k) +{ + const fragment_addresses_ports_key* key = (const fragment_addresses_ports_key*) k; + guint hash_val; +/* + int i; +*/ + + hash_val = 0; + +/* More than likely: in most captures src and dst addresses and ports + are the same, and would hash the same. + We only use id as the hash as an optimization. + + for (i = 0; i < key->src.len; i++) + hash_val += key->src_addr.data[i]; + for (i = 0; i < key->dst.len; i++) + hash_val += key->dst_addr.data[i]; + hash_val += key->src_port; + hash_val += key->dst_port; +*/ + + hash_val += key->id; + + return hash_val; +} + +static gint +fragment_addresses_ports_equal(gconstpointer k1, gconstpointer k2) +{ + const fragment_addresses_ports_key* key1 = (const fragment_addresses_ports_key*) k1; + const fragment_addresses_ports_key* key2 = (const fragment_addresses_ports_key*) k2; + + /* + * key.id is the first item to compare since it's the item most + * likely to differ between sessions, thus short-circuiting + * the comparison of addresses and ports. + */ + return (key1->id == key2->id) && + (addresses_equal(&key1->src_addr, &key2->src_addr)) && + (addresses_equal(&key1->dst_addr, &key2->dst_addr)) && + (key1->src_port == key2->src_port) && + (key1->dst_port == key2->dst_port); +} + +/* + * Create a fragment key for temporary use; it can point to non- + * persistent data, and so must only be used to look up and + * delete entries, not to add them. + */ +static gpointer +fragment_addresses_ports_temporary_key(const packet_info *pinfo, const guint32 id, + const void *data _U_) +{ + fragment_addresses_ports_key *key = g_slice_new(fragment_addresses_ports_key); + + /* + * Do a shallow copy of the addresses. + */ + copy_address_shallow(&key->src_addr, &pinfo->src); + copy_address_shallow(&key->dst_addr, &pinfo->dst); + key->src_port = pinfo->srcport; + key->dst_port = pinfo->destport; + key->id = id; + + return (gpointer)key; +} + +/* + * Create a fragment key for permanent use; it must point to persistent + * data, so that it can be used to add entries. + */ +static gpointer +fragment_addresses_ports_persistent_key(const packet_info *pinfo, + const guint32 id, const void *data _U_) +{ + fragment_addresses_ports_key *key = g_slice_new(fragment_addresses_ports_key); + + /* + * Do a deep copy of the addresses. + */ + copy_address(&key->src_addr, &pinfo->src); + copy_address(&key->dst_addr, &pinfo->dst); + key->src_port = pinfo->srcport; + key->dst_port = pinfo->destport; + key->id = id; + + return (gpointer)key; +} + +static void +fragment_addresses_ports_free_temporary_key(gpointer ptr) +{ + fragment_addresses_ports_key *key = (fragment_addresses_ports_key *)ptr; + g_slice_free(fragment_addresses_ports_key, key); +} + +static void +fragment_addresses_ports_free_persistent_key(gpointer ptr) +{ + fragment_addresses_ports_key *key = (fragment_addresses_ports_key *)ptr; + + if(key){ + /* + * Free up the copies of the addresses from the old key. + */ + free_address(&key->src_addr); + free_address(&key->dst_addr); + + g_slice_free(fragment_addresses_ports_key, key); + } +} + +const reassembly_table_functions +addresses_ports_reassembly_table_functions = { + fragment_addresses_ports_hash, + fragment_addresses_ports_equal, + fragment_addresses_ports_temporary_key, + fragment_addresses_ports_persistent_key, + fragment_addresses_ports_free_temporary_key, + fragment_addresses_ports_free_persistent_key +}; + +typedef struct _reassembled_key { + guint32 id; + guint32 frame; +} reassembled_key; + +static gint +reassembled_equal(gconstpointer k1, gconstpointer k2) +{ + const reassembled_key* key1 = (const reassembled_key*) k1; + const reassembled_key* key2 = (const reassembled_key*) k2; + + /* + * We assume that the frame numbers are unlikely to be equal, + * so we check them first. + */ + return key1->frame == key2->frame && key1->id == key2->id; +} + +static guint +reassembled_hash(gconstpointer k) +{ + const reassembled_key* key = (const reassembled_key*) k; + + return key->frame; +} + +static void +reassembled_key_free(gpointer ptr) +{ + g_slice_free(reassembled_key, (reassembled_key *)ptr); +} + +/* + * For a fragment hash table entry, free the associated fragments. + * The entry value (fd_chain) is freed herein and the entry is freed + * when the key freeing routine is called (as a consequence of returning + * TRUE from this function). + */ +static gboolean +free_all_fragments(gpointer key_arg _U_, gpointer value, gpointer user_data _U_) +{ + fragment_head *fd_head; + fragment_item *fd_i = NULL, *tmp_fd; + + /* g_hash_table_new_full() was used to supply a function + * to free the key and anything to which it points + */ + fd_head = (fragment_head *)value; + if (fd_head != NULL) { + fd_i = fd_head->next; + if(fd_head->tvb_data && !(fd_head->flags&FD_SUBSET_TVB)) + tvb_free(fd_head->tvb_data); + g_slice_free(fragment_head, fd_head); + } + + for (; fd_i != NULL; fd_i = tmp_fd) { + tmp_fd=fd_i->next; + + if(fd_i->tvb_data && !(fd_i->flags&FD_SUBSET_TVB)) + tvb_free(fd_i->tvb_data); + g_slice_free(fragment_item, fd_i); + } + + return TRUE; +} + +/* ------------------------- */ +static fragment_head *new_head(const guint32 flags) +{ + fragment_head *fd_head; + /* If head/first structure in list only holds no other data than + * 'datalen' then we don't have to change the head of the list + * even if we want to keep it sorted + */ + fd_head=g_slice_new0(fragment_head); + + fd_head->flags=flags; + return fd_head; +} + +/* + * For a reassembled-packet hash table entry, free the fragment data + * to which the value refers. (The key is freed by reassembled_key_free.) + */ +static void +free_fd_head(fragment_head *fd_head) +{ + fragment_item *fd_i, *tmp; + + if (fd_head->flags & FD_SUBSET_TVB) + fd_head->tvb_data = NULL; + if (fd_head->tvb_data) + tvb_free(fd_head->tvb_data); + for (fd_i = fd_head->next; fd_i; fd_i = tmp) { + tmp = fd_i->next; + if (fd_i->flags & FD_SUBSET_TVB) + fd_i->tvb_data = NULL; + if (fd_i->tvb_data) { + tvb_free(fd_i->tvb_data); + } + g_slice_free(fragment_item, fd_i); + } + g_slice_free(fragment_head, fd_head); +} + +static void +unref_fd_head(gpointer data) +{ + fragment_head *fd_head = (fragment_head *) data; + fd_head->ref_count--; + + if (fd_head->ref_count == 0) { + free_fd_head(fd_head); + } +} + +static void +reassembled_table_insert(GHashTable *reassembled_table, reassembled_key *key, fragment_head *fd_head) +{ + fragment_head *old_fd_head; + fd_head->ref_count++; + if ((old_fd_head = g_hash_table_lookup(reassembled_table, key)) != NULL) { + if (old_fd_head->ref_count == 1) { + /* We're replacing the last entry in the reassembled + * table for an old reassembly. Does it have a tvb? + * We might still be using that tvb's memory for an + * address via set_address_tvb(). (See #19094.) + */ + if (old_fd_head->tvb_data && fd_head->tvb_data) { + /* Free it when the new tvb is freed */ + tvb_set_child_real_data_tvbuff(fd_head->tvb_data, old_fd_head->tvb_data); + } + /* XXX: Set the old data to NULL regardless. If we + * have old data but not new data, that is odd (we're + * replacing a reassembly with tvb data with something + * with no tvb data, possibly because a zero length or + * null tvb was passed into a defragment function, + * which is a dissector bug.) + * This leaks the tvb data if we couldn't add it to + * a new tvb's chain, but we might not be able to free + * it yet if set_address_tvb() was used. + */ + old_fd_head->tvb_data = NULL; + } + } + g_hash_table_insert(reassembled_table, key, fd_head); +} + +typedef struct register_reassembly_table { + reassembly_table *table; + const reassembly_table_functions *funcs; +} register_reassembly_table_t; + +/* + * Register a reassembly table. + */ +void +reassembly_table_register(reassembly_table *table, + const reassembly_table_functions *funcs) +{ + register_reassembly_table_t* reg_table; + + DISSECTOR_ASSERT(table); + DISSECTOR_ASSERT(funcs); + + reg_table = g_new(register_reassembly_table_t,1); + + reg_table->table = table; + reg_table->funcs = funcs; + + reassembly_table_list = g_list_prepend(reassembly_table_list, reg_table); +} + +/* + * Initialize a reassembly table, with specified functions. + */ +void +reassembly_table_init(reassembly_table *table, + const reassembly_table_functions *funcs) +{ + if (table->temporary_key_func == NULL) + table->temporary_key_func = funcs->temporary_key_func; + if (table->persistent_key_func == NULL) + table->persistent_key_func = funcs->persistent_key_func; + if (table->free_temporary_key_func == NULL) + table->free_temporary_key_func = funcs->free_temporary_key_func; + if (table->fragment_table != NULL) { + /* + * The fragment hash table exists. + * + * Remove all entries and free fragment data for each entry. + * + * The keys, and anything to which they point, are freed by + * calling the table's key freeing function. The values + * are freed in free_all_fragments(). + */ + g_hash_table_foreach_remove(table->fragment_table, + free_all_fragments, NULL); + } else { + /* The fragment table does not exist. Create it */ + table->fragment_table = g_hash_table_new_full(funcs->hash_func, + funcs->equal_func, funcs->free_persistent_key_func, NULL); + } + + if (table->reassembled_table != NULL) { + /* + * The reassembled-packet hash table exists. + * + * Remove all entries and free reassembled packet + * data and key for each entry. + */ + g_hash_table_remove_all(table->reassembled_table); + } else { + /* The fragment table does not exist. Create it */ + table->reassembled_table = g_hash_table_new_full(reassembled_hash, + reassembled_equal, reassembled_key_free, unref_fd_head); + } +} + +/* + * Destroy a reassembly table. + */ +void +reassembly_table_destroy(reassembly_table *table) +{ + /* + * Clear the function pointers. + */ + table->temporary_key_func = NULL; + table->persistent_key_func = NULL; + table->free_temporary_key_func = NULL; + if (table->fragment_table != NULL) { + /* + * The fragment hash table exists. + * + * Remove all entries and free fragment data for each entry. + * + * The keys, and anything to which they point, are freed by + * calling the table's key freeing function. The values + * are freed in free_all_fragments(). + */ + g_hash_table_foreach_remove(table->fragment_table, + free_all_fragments, NULL); + + /* + * Now destroy the hash table. + */ + g_hash_table_destroy(table->fragment_table); + table->fragment_table = NULL; + } + if (table->reassembled_table != NULL) { + /* + * The reassembled-packet hash table exists. + * + * Remove all entries and free reassembled packet + * data and key for each entry. + */ + + g_hash_table_remove_all(table->reassembled_table); + + /* + * Now destroy the hash table. + */ + g_hash_table_destroy(table->reassembled_table); + table->reassembled_table = NULL; + } +} + +/* + * Look up an fd_head in the fragment table, optionally returning the key + * for it. + */ +static fragment_head * +lookup_fd_head(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data, gpointer *orig_keyp) +{ + gpointer key; + gpointer value; + + /* Create key to search hash with */ + key = table->temporary_key_func(pinfo, id, data); + + /* + * Look up the reassembly in the fragment table. + */ + if (!g_hash_table_lookup_extended(table->fragment_table, key, orig_keyp, + &value)) + value = NULL; + /* Free the key */ + table->free_temporary_key_func(key); + + return (fragment_head *)value; +} + +/* + * Insert an fd_head into the fragment table, and return the key used. + */ +static gpointer +insert_fd_head(reassembly_table *table, fragment_head *fd_head, + const packet_info *pinfo, const guint32 id, const void *data) +{ + gpointer key; + + /* + * We're going to use the key to insert the fragment, + * so make a persistent version of it. + */ + key = table->persistent_key_func(pinfo, id, data); + g_hash_table_insert(table->fragment_table, key, fd_head); + return key; +} + +/* This function cleans up the stored state and removes the reassembly data and + * (with one exception) all allocated memory for matching reassembly. + * + * The exception is : + * If the PDU was already completely reassembled, then the tvbuff containing the + * reassembled data WILL NOT be free()d, and the pointer to that tvbuff will be + * returned. + * Othervise the function will return NULL. + * + * So, if you call fragment_delete and it returns non-NULL, YOU are responsible + * to tvb_free() that tvbuff. + */ +tvbuff_t * +fragment_delete(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data) +{ + fragment_head *fd_head; + fragment_item *fd; + tvbuff_t *fd_tvb_data=NULL; + gpointer key; + + fd_head = lookup_fd_head(table, pinfo, id, data, &key); + if(fd_head==NULL){ + /* We do not recognize this as a PDU we have seen before. return */ + return NULL; + } + + fd_tvb_data=fd_head->tvb_data; + /* loop over all partial fragments and free any tvbuffs */ + for(fd=fd_head->next;fd;){ + fragment_item *tmp_fd; + tmp_fd=fd->next; + + if (fd->tvb_data && !(fd->flags & FD_SUBSET_TVB)) + tvb_free(fd->tvb_data); + g_slice_free(fragment_item, fd); + fd=tmp_fd; + } + g_slice_free(fragment_head, fd_head); + g_hash_table_remove(table->fragment_table, key); + + return fd_tvb_data; +} + +/* This function is used to check if there is partial or completed reassembly state + * matching this packet. I.e. Is there reassembly going on or not for this packet? + */ +fragment_head * +fragment_get(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data) +{ + return lookup_fd_head(table, pinfo, id, data, NULL); +} + +fragment_head * +fragment_get_reassembled_id(reassembly_table *table, const packet_info *pinfo, + const guint32 id) +{ + fragment_head *fd_head; + reassembled_key key; + + /* create key to search hash with */ + key.frame = pinfo->num; + key.id = id; + fd_head = (fragment_head *)g_hash_table_lookup(table->reassembled_table, &key); + + return fd_head; +} + +/* To specify the offset for the fragment numbering, the first fragment is added with 0, and + * afterwards this offset is set. All additional calls to off_seq_check will calculate + * the number in sequence in regards to the offset */ +void +fragment_add_seq_offset(reassembly_table *table, const packet_info *pinfo, const guint32 id, + const void *data, const guint32 fragment_offset) +{ + fragment_head *fd_head; + + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + if (!fd_head) + return; + + /* Reseting the offset is not allowed */ + if ( fd_head->fragment_nr_offset != 0 ) + return; + + fd_head->fragment_nr_offset = fragment_offset; +} + +static void +update_first_gap(fragment_head *fd_head, fragment_item *inserted, gboolean multi_insert) +{ + guint32 frag_end = inserted->offset + inserted->len; + fragment_item *iter; + guint32 contiguous; + + if (inserted->offset > fd_head->contiguous_len) { + /* first inserted node is after first gap */ + return; + } else if (fd_head->first_gap == NULL) { + /* we haven't seen first fragment yet */ + if (inserted->offset != 0) { + /* inserted node is not first fragment */ + return; + } + contiguous = inserted->len; + iter = inserted; + } else { + contiguous = MAX(fd_head->contiguous_len, frag_end); + iter = multi_insert ? inserted : fd_head->first_gap; + } + + while (iter->next) { + if (iter->next->offset > contiguous) { + break; + } + iter = iter->next; + contiguous = MAX(contiguous, iter->offset + iter->len); + } + + /* iter is either pointing to last fragment before gap or tail */ + fd_head->first_gap = iter; + fd_head->contiguous_len = contiguous; +} + +/* + * Keeping first gap and contiguous length in sync significantly speeds up + * LINK_FRAG() when fragments in capture file are mostly ordered. However, when + * fragments are removed from the list, the first gap can point to fragments + * that were either moved to another list or freed. Therefore when any fragment + * before first gap is removed, the first gap (and contiguous length) must be + * invalidated. + */ +static void fragment_reset_first_gap(fragment_head *fd_head) +{ + fd_head->first_gap = NULL; + fd_head->contiguous_len = 0; + if (fd_head->next) { + gboolean multi_insert = (fd_head->next->next != NULL); + update_first_gap(fd_head, fd_head->next, multi_insert); + } +} + +/* + * Determines whether list modification requires first gap reset. On entry + * modified is NULL if all elements were removed, otherwise it points to + * element (reachable from fd_head) whose next pointer was changed. + */ +static void fragment_items_removed(fragment_head *fd_head, fragment_item *modified) +{ + if ((fd_head->first_gap == modified) || + ((modified != NULL) && (modified->offset > fd_head->contiguous_len))) { + /* Removed elements were after first gap */ + return; + } + fragment_reset_first_gap(fd_head); +} + +/* + * For use with fragment_add (and not the fragment_add_seq functions). + * When the reassembled result is wrong (perhaps it needs to be extended), this + * function clears any previous reassembly result, allowing the new reassembled + * length to be set again. + */ +static void +fragment_reset_defragmentation(fragment_head *fd_head) +{ + /* Caller must ensure that this function is only called when + * defragmentation is safe to undo. */ + DISSECTOR_ASSERT(fd_head->flags & FD_DEFRAGMENTED); + + for (fragment_item *fd_i = fd_head->next; fd_i; fd_i = fd_i->next) { + if (!fd_i->tvb_data) { + fd_i->tvb_data = tvb_new_subset_remaining(fd_head->tvb_data, fd_i->offset); + fd_i->flags |= FD_SUBSET_TVB; + } + fd_i->flags &= (~FD_TOOLONGFRAGMENT) & (~FD_MULTIPLETAILS); + } + fd_head->flags &= ~(FD_DEFRAGMENTED|FD_PARTIAL_REASSEMBLY|FD_DATALEN_SET); + fd_head->flags &= ~(FD_TOOLONGFRAGMENT|FD_MULTIPLETAILS); + fd_head->datalen = 0; + fd_head->reassembled_in = 0; + fd_head->reas_in_layer_num = 0; +} + +/* This function can be used to explicitly set the total length (if known) + * for reassembly of a PDU. + * This is useful for reassembly of PDUs where one may have the total length specified + * in the first fragment instead of as for, say, IPv4 where a flag indicates which + * is the last fragment. + * + * Such protocols might fragment_add with a more_frags==TRUE for every fragment + * and just tell the reassembly engine the expected total length of the reassembled data + * using fragment_set_tot_len immediately after doing fragment_add for the first packet. + * + * Note that for FD_BLOCKSEQUENCE tot_len is the index for the tail fragment. + * i.e. since the block numbers start at 0, if we specify tot_len==2, that + * actually means we want to defragment 3 blocks, block 0, 1 and 2. + */ +void +fragment_set_tot_len(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data, const guint32 tot_len) +{ + fragment_head *fd_head; + fragment_item *fd; + guint32 max_offset = 0; + + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + if (!fd_head) + return; + + /* If we're setting a block sequence number, verify that it + * doesn't conflict with values set by existing fragments. + * XXX - eliminate this check? + */ + if (fd_head->flags & FD_BLOCKSEQUENCE) { + for (fd = fd_head->next; fd; fd = fd->next) { + if (fd->offset > max_offset) { + max_offset = fd->offset; + if (max_offset > tot_len) { + fd_head->error = "Bad total reassembly block count"; + THROW_MESSAGE(ReassemblyError, fd_head->error); + } + } + } + } + + if (fd_head->flags & FD_DEFRAGMENTED) { + if (max_offset != tot_len) { + fd_head->error = "Defragmented complete but total length not satisfied"; + THROW_MESSAGE(ReassemblyError, fd_head->error); + } + } + + /* We got this far so the value is sane. */ + fd_head->datalen = tot_len; + fd_head->flags |= FD_DATALEN_SET; +} + +void +fragment_reset_tot_len(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data, const guint32 tot_len) +{ + fragment_head *fd_head; + + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + if (!fd_head) + return; + + /* + * If FD_PARTIAL_REASSEMBLY is set, it would make the next fragment_add + * call set the reassembled length based on the fragment offset and + * length. As the length is known now, be sure to disable that magic. + */ + fd_head->flags &= ~FD_PARTIAL_REASSEMBLY; + + /* If the length is already as expected, there is nothing else to do. */ + if (tot_len == fd_head->datalen) + return; + + if (fd_head->flags & FD_DEFRAGMENTED) { + /* + * Fragments were reassembled before, clear it to allow + * increasing the reassembled length. + */ + fragment_reset_defragmentation(fd_head); + } + + fd_head->datalen = tot_len; + fd_head->flags |= FD_DATALEN_SET; +} + +void +fragment_truncate(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data, const guint32 tot_len) + +{ + tvbuff_t *old_tvb_data; + fragment_head *fd_head; + + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + if (!fd_head) + return; + + /* Caller must ensure that this function is only called when + * we are defragmented. */ + DISSECTOR_ASSERT(fd_head->flags & FD_DEFRAGMENTED); + + /* + * If FD_PARTIAL_REASSEMBLY is set, it would make the next fragment_add + * call set the reassembled length based on the fragment offset and + * length. As the length is known now, be sure to disable that magic. + */ + fd_head->flags &= ~FD_PARTIAL_REASSEMBLY; + + /* If the length is already as expected, there is nothing else to do. */ + if (tot_len == fd_head->datalen) + return; + + DISSECTOR_ASSERT(fd_head->datalen > tot_len); + + old_tvb_data=fd_head->tvb_data; + fd_head->tvb_data = tvb_clone_offset_len(old_tvb_data, 0, tot_len); + tvb_set_free_cb(fd_head->tvb_data, g_free); + + if (old_tvb_data) + tvb_add_to_chain(fd_head->tvb_data, old_tvb_data); + fd_head->datalen = tot_len; + + /* Keep the fragments before the split point, dividing any if + * necessary. + * XXX: In rare cases, there might be fragments marked as overlap that + * have data both before and after the split point, and which only + * overlap after the split point. In that case, after dividing the + * fragments the first part no longer overlap. + * However, at this point we can't test for overlap conflicts, + * so we'll just leave the overlap flags as-is. + */ + fd_head->flags &= ~(FD_OVERLAP|FD_OVERLAPCONFLICT|FD_TOOLONGFRAGMENT|FD_MULTIPLETAILS); + fragment_item *fd_i, *prev_fd = NULL; + for (fd_i = fd_head->next; fd_i && (fd_i->offset < tot_len); fd_i = fd_i->next) { + fd_i->flags &= ~(FD_TOOLONGFRAGMENT|FD_MULTIPLETAILS); + /* Check for the split point occuring in the middle of the + * fragment. */ + if (fd_i->offset + fd_i->len > tot_len) { + fd_i->len = tot_len - fd_i->offset; + } + fd_head->flags |= fd_i->flags & (FD_OVERLAP|FD_OVERLAPCONFLICT); + prev_fd = fd_i; + + /* Below should do nothing since this is already defragmented */ + if (fd_i->flags & FD_SUBSET_TVB) + fd_i->flags &= ~FD_SUBSET_TVB; + else if (fd_i->tvb_data) + tvb_free(fd_i->tvb_data); + + fd_i->tvb_data=NULL; + } + + /* Remove all the other fragments, as they are past the split point. */ + if (prev_fd) { + prev_fd->next = NULL; + } else { + fd_head->next = NULL; + } + fd_head->contiguous_len = MIN(fd_head->contiguous_len, tot_len); + fragment_items_removed(fd_head, prev_fd); + fragment_item *tmp_fd; + for (; fd_i; fd_i = tmp_fd) { + tmp_fd=fd_i->next; + + if (fd_i->tvb_data && !(fd_i->flags & FD_SUBSET_TVB)) + tvb_free(fd_i->tvb_data); + g_slice_free(fragment_item, fd_i); + } +} + +guint32 +fragment_get_tot_len(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data) +{ + fragment_head *fd_head; + + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + + if(fd_head){ + return fd_head->datalen; + } + + return 0; +} + +/* This function will set the partial reassembly flag for a fh. + When this function is called, the fh MUST already exist, i.e. + the fh MUST be created by the initial call to fragment_add() before + this function is called. + Also note that this function MUST be called to indicate a fh will be + extended (increase the already stored data) +*/ + +void +fragment_set_partial_reassembly(reassembly_table *table, + const packet_info *pinfo, const guint32 id, + const void *data) +{ + fragment_head *fd_head; + + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + + /* + * XXX - why not do all the stuff done early in "fragment_add_work()", + * turning off FD_DEFRAGMENTED and pointing the fragments' data + * pointers to the appropriate part of the already-reassembled + * data, and clearing the data length and "reassembled in" frame + * number, here? We currently have a hack in the TCP dissector + * not to set the "reassembled in" value if the "partial reassembly" + * flag is set, so that in the first pass through the packets + * we don't falsely set a packet as reassembled in that packet + * if the dissector decided that even more reassembly was needed. + */ + if(fd_head){ + fd_head->flags |= FD_PARTIAL_REASSEMBLY; + } +} + +/* + * This function gets rid of an entry from a fragment table, given + * a pointer to the key for that entry. + * + * The key freeing routine will be called by g_hash_table_remove(). + */ +static void +fragment_unhash(reassembly_table *table, gpointer key) +{ + /* + * Remove the entry from the fragment table. + */ + g_hash_table_remove(table->fragment_table, key); +} + +/* + * This function adds fragment_head structure to a reassembled-packet + * hash table, using the frame numbers of each of the frames from + * which it was reassembled as keys, and sets the "reassembled_in" + * frame number. + */ +static void +fragment_reassembled(reassembly_table *table, fragment_head *fd_head, + const packet_info *pinfo, const guint32 id) +{ + reassembled_key *new_key; + fragment_item *fd; + + fd_head->ref_count = 0; + if (fd_head->next == NULL) { + /* + * This was not fragmented, so there's no fragment + * table; just hash it using the current frame number. + */ + new_key = g_slice_new(reassembled_key); + new_key->frame = pinfo->num; + new_key->id = id; + reassembled_table_insert(table->reassembled_table, new_key, fd_head); + } else { + /* + * Hash it with the frame numbers for all the frames. + */ + for (fd = fd_head->next; fd != NULL; fd = fd->next){ + new_key = g_slice_new(reassembled_key); + new_key->frame = fd->frame; + new_key->id = id; + reassembled_table_insert(table->reassembled_table, new_key, fd_head); + } + } + fd_head->flags |= FD_DEFRAGMENTED; + fd_head->reassembled_in = pinfo->num; + fd_head->reas_in_layer_num = pinfo->curr_layer_num; +} + +/* + * This function is a variant of the above for the single sequence + * case, using id+offset (i.e., the original sequence number) for the id + * in the key. + */ +static void +fragment_reassembled_single(reassembly_table *table, fragment_head *fd_head, + const packet_info *pinfo, const guint32 id) +{ + reassembled_key *new_key; + fragment_item *fd; + + fd_head->ref_count = 0; + if (fd_head->next == NULL) { + /* + * This was not fragmented, so there's no fragment + * table; just hash it using the current frame number. + */ + new_key = g_slice_new(reassembled_key); + new_key->frame = pinfo->num; + new_key->id = id; + reassembled_table_insert(table->reassembled_table, new_key, fd_head); + } else { + /* + * Hash it with the frame numbers for all the frames. + */ + for (fd = fd_head->next; fd != NULL; fd = fd->next){ + new_key = g_slice_new(reassembled_key); + new_key->frame = fd->frame; + new_key->id = id + fd->offset; + reassembled_table_insert(table->reassembled_table, new_key, fd_head); + } + } + fd_head->flags |= FD_DEFRAGMENTED; + fd_head->reassembled_in = pinfo->num; + fd_head->reas_in_layer_num = pinfo->curr_layer_num; +} + +static void +LINK_FRAG(fragment_head *fd_head,fragment_item *fd) +{ + fragment_item *fd_i; + + /* add fragment to list, keep list sorted */ + if (fd_head->next == NULL || fd->offset < fd_head->next->offset) { + /* New first fragment */ + fd->next = fd_head->next; + fd_head->next = fd; + } else { + fd_i = fd_head->next; + if (fd_head->first_gap != NULL) { + if (fd->offset >= fd_head->first_gap->offset) { + /* fragment is after first gap */ + fd_i = fd_head->first_gap; + } + } + for(; fd_i->next; fd_i=fd_i->next) { + if (fd->offset < fd_i->next->offset ) + break; + } + fd->next = fd_i->next; + fd_i->next = fd; + } + + update_first_gap(fd_head, fd, FALSE); +} + +static void +MERGE_FRAG(fragment_head *fd_head, fragment_item *fd) +{ + fragment_item *fd_i, *tmp, *inserted = fd; + gboolean multi_insert; + + if (fd == NULL) return; + + multi_insert = (fd->next != NULL); + + if (fd_head->next == NULL) { + fd_head->next = fd; + update_first_gap(fd_head, fd, multi_insert); + return; + } + + if ((fd_head->first_gap != NULL) && + (fd->offset >= fd_head->first_gap->offset)) { + /* all new fragments go after first gap */ + fd_i = fd_head->first_gap; + } else { + /* at least one new fragment goes before first gap */ + if (fd->offset < fd_head->next->offset) { + /* inserted fragment is new head, "swap" the lists */ + tmp = fd_head->next; + fd_head->next = fd; + fd = tmp; + } + fd_i = fd_head->next; + } + + /* Traverse the list linked to fragment head ("main" list), checking if + * fd pointer ("merge" list) should go before or after fd_i->next. Swap + * fd_i->next ("main") and fd pointers ("merge") if "merge" list should + * go before iterated element (fd_i). After the swap what formerly was + * "merge" list essentially becomes part of "main" list (just detached + * element, i.e. fd, is now head of new "merge list"). + */ + for(; fd_i->next; fd_i=fd_i->next) { + if (fd->offset < fd_i->next->offset) { + tmp = fd_i->next; + fd_i->next = fd; + fd = tmp; + } + } + /* Reached "main" list end, attach remaining elements */ + fd_i->next = fd; + + update_first_gap(fd_head, inserted, multi_insert); +} + +/* + * This function adds a new fragment to the fragment hash table. + * If this is the first fragment seen for this datagram, a new entry + * is created in the hash table, otherwise this fragment is just added + * to the linked list of fragments for this packet. + * The list of fragments for a specific datagram is kept sorted for + * easier handling. + * + * Returns a pointer to the head of the fragment data list if we have all the + * fragments, NULL otherwise. + * + * This function assumes frag_offset being a byte offset into the defragment + * packet. + * + * 01-2002 + * Once the fh is defragmented (= FD_DEFRAGMENTED set), it can be + * extended using the FD_PARTIAL_REASSEMBLY flag. This flag should be set + * using fragment_set_partial_reassembly() before calling fragment_add + * with the new fragment. FD_TOOLONGFRAGMENT and FD_MULTIPLETAILS flags + * are lowered when a new extension process is started. + */ +static gboolean +fragment_add_work(fragment_head *fd_head, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 frag_offset, + const guint32 frag_data_len, const gboolean more_frags, + const guint32 frag_frame, const gboolean allow_overlaps) +{ + fragment_item *fd; + fragment_item *fd_i; + guint32 dfpos, fraglen, overlap; + tvbuff_t *old_tvb_data; + guint8 *data; + + /* create new fd describing this fragment */ + fd = g_slice_new(fragment_item); + fd->next = NULL; + fd->flags = 0; + fd->frame = frag_frame; + fd->offset = frag_offset; + fd->len = frag_data_len; + fd->tvb_data = NULL; + + /* + * Are we adding to an already-completed reassembly? + */ + if (fd_head->flags & FD_DEFRAGMENTED) { + /* + * Yes. Does this fragment go past the end of the results + * of that reassembly? + */ + if (frag_offset + frag_data_len > fd_head->datalen) { + /* + * Yes. Have we been requested to continue reassembly? + */ + if (fd_head->flags & FD_PARTIAL_REASSEMBLY) { + /* + * Yes. Set flag in already empty fds & + * point old fds to malloc'ed data. + */ + fragment_reset_defragmentation(fd_head); + } else if (!allow_overlaps) { + /* + * No. Bail out since we have no idea what to + * do with this fragment (and if we keep going + * we'll run past the end of a buffer sooner + * or later). + */ + g_slice_free(fragment_item, fd); + + /* + * This is an attempt to add a fragment to a + * reassembly that had already completed. + * If it had no error, we don't want to + * mark it with an error, and if it had an + * error, we don't want to overwrite it, so + * we don't set fd_head->error. + */ + if (frag_offset >= fd_head->datalen) { + /* + * The fragment starts past the end + * of the reassembled data. + */ + THROW_MESSAGE(ReassemblyError, "New fragment past old data limits"); + } else { + /* + * The fragment starts before the end + * of the reassembled data, but + * runs past the end. That could + * just be a retransmission with extra + * data, but the calling dissector + * didn't set FD_PARTIAL_REASSEMBLY + * so it won't be handled correctly. + * + * XXX: We could set FD_TOOLONGFRAGMENT + * below instead. + */ + THROW_MESSAGE(ReassemblyError, "New fragment overlaps old data (retransmission?)"); + } + } + } else { + /* + * No. That means it overlaps the completed reassembly. + * This is probably a retransmission and normal + * behavior. (If not, it's because the dissector + * doesn't handle reused sequence numbers correctly, + * e.g. #10503). Handle below. + */ + } + } + + /* Do this after we may have bailed out (above) so that we don't leave + * fd_head->frame in a bad state if we do */ + if (fd->frame > fd_head->frame) + fd_head->frame = fd->frame; + + if (!more_frags) { + /* + * This is the tail fragment in the sequence. + */ + if (fd_head->flags & FD_DATALEN_SET) { + /* ok we have already seen other tails for this packet + * it might be a duplicate. + */ + if (fd_head->datalen != (fd->offset + fd->len) ){ + /* Oops, this tail indicates a different packet + * len than the previous ones. Something's wrong. + */ + fd->flags |= FD_MULTIPLETAILS; + fd_head->flags |= FD_MULTIPLETAILS; + } + } else { + /* This was the first tail fragment; now we know + * what the length of the packet should be. + */ + fd_head->datalen = fd->offset + fd->len; + fd_head->flags |= FD_DATALEN_SET; + } + } + + + + /* If the packet is already defragmented, this MUST be an overlap. + * The entire defragmented packet is in fd_head->data. + * Even if we have previously defragmented this packet, we still + * check it. Someone might play overlap and TTL games. + */ + if (fd_head->flags & FD_DEFRAGMENTED) { + guint32 end_offset = fd->offset + fd->len; + fd->flags |= FD_OVERLAP; + fd_head->flags |= FD_OVERLAP; + /* make sure it's not too long */ + /* XXX: We probably don't call this, unlike the _seq() + * functions, because we throw an exception above. + */ + if (end_offset > fd_head->datalen || end_offset < fd->offset || end_offset < fd->len) { + fd->flags |= FD_TOOLONGFRAGMENT; + fd_head->flags |= FD_TOOLONGFRAGMENT; + } + /* make sure it doesn't conflict with previous data */ + else if ( tvb_memeql(fd_head->tvb_data, fd->offset, + tvb_get_ptr(tvb,offset,fd->len),fd->len) ){ + fd->flags |= FD_OVERLAPCONFLICT; + fd_head->flags |= FD_OVERLAPCONFLICT; + } + /* it was just an overlap, link it and return */ + LINK_FRAG(fd_head,fd); + return TRUE; + } + + + + /* If we have reached this point, the packet is not defragmented yet. + * Save all payload in a buffer until we can defragment. + */ + if (!tvb_bytes_exist(tvb, offset, fd->len)) { + g_slice_free(fragment_item, fd); + THROW(BoundsError); + } + fd->tvb_data = tvb_clone_offset_len(tvb, offset, fd->len); + LINK_FRAG(fd_head,fd); + + + if( !(fd_head->flags & FD_DATALEN_SET) ){ + /* if we don't know the datalen, there are still missing + * packets. Cheaper than the check below. + */ + return FALSE; + } + + /* Check if we have received the entire fragment. */ + if (fd_head->contiguous_len < fd_head->datalen) { + /* + * The amount of contiguous data we have is less than the + * amount of data we're trying to reassemble, so we haven't + * received all packets yet. + */ + return FALSE; + } + + /* we have received an entire packet, defragment it and + * free all fragments + */ + /* store old data just in case */ + old_tvb_data=fd_head->tvb_data; + data = (guint8 *) g_malloc(fd_head->datalen); + fd_head->tvb_data = tvb_new_real_data(data, fd_head->datalen, fd_head->datalen); + tvb_set_free_cb(fd_head->tvb_data, g_free); + + /* add all data fragments */ + for (dfpos=0,fd_i=fd_head->next;fd_i;fd_i=fd_i->next) { + if (fd_i->len) { + /* + * The contiguous length check above also + * ensures that the only gaps that exist here + * are ones where a fragment starts past the + * end of the reassembled datagram, and there's + * a gap between the previous fragment and + * that fragment. + * + * A "DESEGMENT_UNTIL_FIN" was involved wherein the + * FIN packet had an offset less than the highest + * fragment offset seen. [Seen from a fuzz-test: + * bug #2470]). + * + * Note that the "overlap" compare must only be + * done for fragments with (offset+len) <= fd_head->datalen + * and thus within the newly g_malloc'd buffer. + */ + + if (fd_i->offset >= fd_head->datalen) { + /* + * Fragment starts after the end + * of the reassembled packet. + * + * This can happen if the length was + * set after the offending fragment + * was added to the reassembly. + * + * Flag this fragment, but don't + * try to extract any data from + * it, as there's no place to put + * it. + * + * XXX - add different flag value + * for this. + */ + fd_i->flags |= FD_TOOLONGFRAGMENT; + fd_head->flags |= FD_TOOLONGFRAGMENT; + } else if (fd_i->offset + fd_i->len < fd_i->offset) { + /* Integer overflow, unhandled by rest of + * code so error out. This check handles + * all possible remaining overflows. + */ + fd_head->error = "offset + len < offset"; + } else if (!fd_i->tvb_data) { + fd_head->error = "no data"; + } else { + fraglen = fd_i->len; + if (fd_i->offset + fraglen > fd_head->datalen) { + /* + * Fragment goes past the end + * of the packet, as indicated + * by the last fragment. + * + * This can happen if the + * length was set after the + * offending fragment was + * added to the reassembly. + * + * Mark it as such, and only + * copy from it what fits in + * the packet. + */ + fd_i->flags |= FD_TOOLONGFRAGMENT; + fd_head->flags |= FD_TOOLONGFRAGMENT; + fraglen = fd_head->datalen - fd_i->offset; + } + overlap = dfpos - fd_i->offset; + /* Guaranteed to be >= 0, previous code + * has checked for gaps. */ + if (overlap) { + /* duplicate/retransmission/overlap */ + guint32 cmp_len = MIN(fd_i->len,overlap); + + fd_i->flags |= FD_OVERLAP; + fd_head->flags |= FD_OVERLAP; + if ( memcmp(data + fd_i->offset, + tvb_get_ptr(fd_i->tvb_data, 0, cmp_len), + cmp_len) + ) { + fd_i->flags |= FD_OVERLAPCONFLICT; + fd_head->flags |= FD_OVERLAPCONFLICT; + } + } + /* XXX: As in the fragment_add_seq funcs + * like fragment_defragment_and_free() the + * existing behavior does not overwrite + * overlapping bytes even if there is a + * conflict. It only adds new bytes. + * + * Since we only add fragments to a reassembly + * if the reassembly isn't complete, the most + * common case for overlap conflicts is when + * an earlier reassembly isn't fully contained + * in the capture, and we've reused an + * indentification number / wrapped around + * offset sequence numbers much later in the + * capture. In that case, we probably *do* + * want to overwrite conflicting bytes, since + * the earlier fragments didn't form a complete + * reassembly and should be effectively thrown + * out rather than mixed with the new ones? + */ + if (fd_i->offset + fraglen > dfpos) { + memcpy(data+dfpos, + tvb_get_ptr(fd_i->tvb_data, overlap, fraglen-overlap), + fraglen-overlap); + dfpos = fd_i->offset + fraglen; + } + } + + if (fd_i->flags & FD_SUBSET_TVB) + fd_i->flags &= ~FD_SUBSET_TVB; + else if (fd_i->tvb_data) + tvb_free(fd_i->tvb_data); + + fd_i->tvb_data=NULL; + } + } + + if (old_tvb_data) + tvb_add_to_chain(tvb, old_tvb_data); + /* mark this packet as defragmented. + allows us to skip any trailing fragments */ + fd_head->flags |= FD_DEFRAGMENTED; + fd_head->reassembled_in=pinfo->num; + fd_head->reas_in_layer_num = pinfo->curr_layer_num; + + /* we don't throw until here to avoid leaking old_data and others */ + if (fd_head->error) { + THROW_MESSAGE(ReassemblyError, fd_head->error); + } + + return TRUE; +} + +static fragment_head * +fragment_add_common(reassembly_table *table, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 id, + const void *data, const guint32 frag_offset, + const guint32 frag_data_len, const gboolean more_frags, + const gboolean check_already_added, + const guint32 frag_frame) +{ + fragment_head *fd_head; + fragment_item *fd_item; + gboolean already_added; + + + /* + * Dissector shouldn't give us garbage tvb info. + * + * XXX - should this code take responsibility for preventing + * reassembly if data is missing due to the packets being + * sliced, rather than leaving it up to dissectors? + */ + DISSECTOR_ASSERT(tvb_bytes_exist(tvb, offset, frag_data_len)); + + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + +#if 0 + /* debug output of associated fragments. */ + /* leave it here for future debugging sessions */ + if(strcmp(pinfo->current_proto, "DCERPC") == 0) { + printf("proto:%s num:%u id:%u offset:%u len:%u more:%u visited:%u\n", + pinfo->current_proto, pinfo->num, id, frag_offset, frag_data_len, more_frags, pinfo->fd->visited); + if(fd_head != NULL) { + for(fd_item=fd_head->next;fd_item;fd_item=fd_item->next){ + printf("fd_frame:%u fd_offset:%u len:%u datalen:%u\n", + fd_item->frame, fd_item->offset, fd_item->len, fd_item->datalen); + } + } + } +#endif + + /* + * Is this the first pass through the capture? + */ + if (!pinfo->fd->visited) { + /* + * Yes, so we could be doing reassembly. If + * "check_already_added" is true, and fd_head is non-null, + * meaning that this fragment would be added to an + * in-progress reassembly, check if we have seen this + * fragment before, i.e., if we have already added it to + * that reassembly. That can be true even on the first pass + * since we sometimes might call a subdissector multiple + * times. + * + * We check both the frame number and the fragment offset, + * so that we support multiple fragments from the same + * frame being added to the same reassembled PDU. + */ + if (check_already_added && fd_head != NULL) { + /* + * fd_head->frame is the maximum of the frame + * numbers of all the fragments added to this + * reassembly; if this frame is later than that + * frame, we know it hasn't been added yet. + */ + if (frag_frame <= fd_head->frame) { + already_added = FALSE; + /* + * The first item in the reassembly list + * is not a fragment, it's a data structure + * for the reassembled packet, so we + * start checking with the next item. + */ + for (fd_item = fd_head->next; fd_item; + fd_item = fd_item->next) { + if (frag_frame == fd_item->frame && + frag_offset == fd_item->offset) { + already_added = TRUE; + break; + } + } + if (already_added) { + /* + * Have we already finished + * reassembling? + */ + if (fd_head->flags & FD_DEFRAGMENTED) { + /* + * Yes. + * XXX - can this ever happen? + */ + THROW_MESSAGE(ReassemblyError, + "Frame already added in first pass"); + } else { + /* + * No. + */ + return NULL; + } + } + } + } + } else { + /* + * No, so we've already done all the reassembly and added + * all the fragments. Do we have a reassembly and, if so, + * have we finished reassembling? + */ + if (fd_head != NULL && fd_head->flags & FD_DEFRAGMENTED) { + /* + * Yes. This is probably being done after the + * first pass, and we've already done the work + * on the first pass. + * + * If the reassembly got a fatal error, throw that + * error again. + */ + if (fd_head->error) + THROW_MESSAGE(ReassemblyError, fd_head->error); + + /* + * Is it later in the capture than all of the + * fragments in the reassembly? + */ + if (frag_frame > fd_head->frame) { + /* + * Yes, so report this as a problem, + * possibly a retransmission. + */ + THROW_MESSAGE(ReassemblyError, "New fragment overlaps old data (retransmission?)"); + } + + /* + * Does this fragment go past the end of the + * results of that reassembly? + */ + if (frag_offset + frag_data_len > fd_head->datalen) { + /* + * Yes. + */ + if (frag_offset >= fd_head->datalen) { + /* + * The fragment starts past the + * end of the reassembled data. + */ + THROW_MESSAGE(ReassemblyError, "New fragment past old data limits"); + } else { + /* + * The fragment starts before the end + * of the reassembled data, but + * runs past the end. That could + * just be a retransmission. + */ + THROW_MESSAGE(ReassemblyError, "New fragment overlaps old data (retransmission?)"); + } + } + + return fd_head; + } else { + /* + * No. + */ + return NULL; + } + } + + if (fd_head==NULL){ + /* not found, this must be the first snooped fragment for this + * packet. Create list-head. + */ + fd_head = new_head(0); + + /* + * Insert it into the hash table. + */ + insert_fd_head(table, fd_head, pinfo, id, data); + } + + if (fragment_add_work(fd_head, tvb, offset, pinfo, frag_offset, + frag_data_len, more_frags, frag_frame, FALSE)) { + /* + * Reassembly is complete. + */ + return fd_head; + } else { + /* + * Reassembly isn't complete. + */ + return NULL; + } +} + +fragment_head * +fragment_add(reassembly_table *table, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 id, const void *data, + const guint32 frag_offset, const guint32 frag_data_len, + const gboolean more_frags) +{ + return fragment_add_common(table, tvb, offset, pinfo, id, data, + frag_offset, frag_data_len, more_frags, TRUE, pinfo->num); +} + +/* + * For use when you can have multiple fragments in the same frame added + * to the same reassembled PDU, e.g. with ONC RPC-over-TCP. + */ +fragment_head * +fragment_add_multiple_ok(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void *data, + const guint32 frag_offset, + const guint32 frag_data_len, const gboolean more_frags) +{ + return fragment_add_common(table, tvb, offset, pinfo, id, data, + frag_offset, frag_data_len, more_frags, FALSE, pinfo->num); +} + +/* + * For use in protocols like TCP when you are adding an out of order segment + * that arrived in an earlier frame because the correct fragment id could not + * be determined until later. By allowing fd->frame to be different than + * pinfo->num, show_fragment_tree will display the correct fragment numbers. + * + * Note that pinfo is still used to set reassembled_in if we have all the + * fragments, so that results on subsequent passes can be the same as the + * first pass. + */ +fragment_head * +fragment_add_out_of_order(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void *data, + const guint32 frag_offset, + const guint32 frag_data_len, + const gboolean more_frags, const guint32 frag_frame) +{ + return fragment_add_common(table, tvb, offset, pinfo, id, data, + frag_offset, frag_data_len, more_frags, TRUE, frag_frame); +} + +fragment_head * +fragment_add_check_with_fallback(reassembly_table *table, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 id, + const void *data, const guint32 frag_offset, + const guint32 frag_data_len, const gboolean more_frags, + const guint32 fallback_frame) +{ + reassembled_key reass_key; + fragment_head *fd_head; + gpointer orig_key; + gboolean late_retransmission = FALSE; + + /* + * If this isn't the first pass, look for this frame in the table + * of reassembled packets. + */ + if (pinfo->fd->visited) { + reass_key.frame = pinfo->num; + reass_key.id = id; + return (fragment_head *)g_hash_table_lookup(table->reassembled_table, &reass_key); + } + + /* Looks up a key in the GHashTable, returning the original key and the associated value + * and a gboolean which is TRUE if the key was found. This is useful if you need to free + * the memory allocated for the original key, for example before calling g_hash_table_remove() + */ + fd_head = lookup_fd_head(table, pinfo, id, data, &orig_key); + if ((fd_head == NULL) && (fallback_frame != pinfo->num)) { + /* Check if there is completed reassembly reachable from fallback frame */ + reass_key.frame = fallback_frame; + reass_key.id = id; + fd_head = (fragment_head *)g_hash_table_lookup(table->reassembled_table, &reass_key); + if (fd_head != NULL) { + /* Found completely reassembled packet, hash it with current frame number */ + reassembled_key *new_key = g_slice_new(reassembled_key); + new_key->frame = pinfo->num; + new_key->id = id; + reassembled_table_insert(table->reassembled_table, new_key, fd_head); + late_retransmission = TRUE; + } + } + if (fd_head == NULL) { + /* not found, this must be the first snooped fragment for this + * packet. Create list-head. + */ + fd_head = new_head(0); + + /* + * Save the key, for unhashing it later. + */ + orig_key = insert_fd_head(table, fd_head, pinfo, id, data); + } + + /* + * If this is a short frame, then we can't, and don't, do + * reassembly on it. We just give up. + */ + if (!tvb_bytes_exist(tvb, offset, frag_data_len)) { + return NULL; + } + + if (fragment_add_work(fd_head, tvb, offset, pinfo, frag_offset, + frag_data_len, more_frags, pinfo->num, late_retransmission)) { + /* Nothing left to do if it was a late retransmission */ + if (late_retransmission) { + return fd_head; + } + /* + * Reassembly is complete. + * Remove this from the table of in-progress + * reassemblies, add it to the table of + * reassembled packets, and return it. + */ + + /* + * Remove this from the table of in-progress reassemblies, + * and free up any memory used for it in that table. + */ + fragment_unhash(table, orig_key); + + /* + * Add this item to the table of reassembled packets. + */ + fragment_reassembled(table, fd_head, pinfo, id); + return fd_head; + } else { + /* + * Reassembly isn't complete. + */ + return NULL; + } +} + +fragment_head * +fragment_add_check(reassembly_table *table, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 id, + const void *data, const guint32 frag_offset, + const guint32 frag_data_len, const gboolean more_frags) +{ + return fragment_add_check_with_fallback(table, tvb, offset, pinfo, id, data, + frag_offset, frag_data_len, more_frags, pinfo->num); +} + +static void +fragment_defragment_and_free (fragment_head *fd_head, const packet_info *pinfo) +{ + fragment_item *fd_i = NULL; + fragment_item *last_fd = NULL; + guint32 dfpos = 0, size = 0; + tvbuff_t *old_tvb_data = NULL; + guint8 *data; + + for(fd_i=fd_head->next;fd_i;fd_i=fd_i->next) { + if(!last_fd || last_fd->offset!=fd_i->offset){ + size+=fd_i->len; + } + last_fd=fd_i; + } + + /* store old data in case the fd_i->data pointers refer to it */ + old_tvb_data=fd_head->tvb_data; + data = (guint8 *) g_malloc(size); + fd_head->tvb_data = tvb_new_real_data(data, size, size); + tvb_set_free_cb(fd_head->tvb_data, g_free); + fd_head->len = size; /* record size for caller */ + + /* add all data fragments */ + last_fd=NULL; + for (fd_i=fd_head->next; fd_i; fd_i=fd_i->next) { + if (fd_i->len) { + if(!last_fd || last_fd->offset != fd_i->offset) { + /* First fragment or in-sequence fragment */ + memcpy(data+dfpos, tvb_get_ptr(fd_i->tvb_data, 0, fd_i->len), fd_i->len); + dfpos += fd_i->len; + } else { + /* duplicate/retransmission/overlap */ + fd_i->flags |= FD_OVERLAP; + fd_head->flags |= FD_OVERLAP; + if(last_fd->len != fd_i->len + || tvb_memeql(last_fd->tvb_data, 0, tvb_get_ptr(fd_i->tvb_data, 0, last_fd->len), last_fd->len) ) { + fd_i->flags |= FD_OVERLAPCONFLICT; + fd_head->flags |= FD_OVERLAPCONFLICT; + } + } + } + last_fd=fd_i; + } + + /* we have defragmented the pdu, now free all fragments*/ + for (fd_i=fd_head->next;fd_i;fd_i=fd_i->next) { + if (fd_i->flags & FD_SUBSET_TVB) + fd_i->flags &= ~FD_SUBSET_TVB; + else if (fd_i->tvb_data) + tvb_free(fd_i->tvb_data); + fd_i->tvb_data=NULL; + } + if (old_tvb_data) + tvb_free(old_tvb_data); + + /* mark this packet as defragmented. + * allows us to skip any trailing fragments. + */ + fd_head->flags |= FD_DEFRAGMENTED; + fd_head->reassembled_in=pinfo->num; + fd_head->reas_in_layer_num = pinfo->curr_layer_num; +} + +/* + * This function adds a new fragment to the entry for a reassembly + * operation. + * + * The list of fragments for a specific datagram is kept sorted for + * easier handling. + * + * Returns TRUE if we have all the fragments, FALSE otherwise. + * + * This function assumes frag_number being a block sequence number. + * The bsn for the first block is 0. + */ +static gboolean +fragment_add_seq_work(fragment_head *fd_head, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 frag_number, + const guint32 frag_data_len, const gboolean more_frags) +{ + fragment_item *fd; + fragment_item *fd_i; + fragment_item *last_fd; + guint32 max, dfpos; + guint32 frag_number_work; + + /* Enables the use of fragment sequence numbers, which do not start with 0 */ + frag_number_work = frag_number; + if ( fd_head->fragment_nr_offset != 0 ) + if ( frag_number_work >= fd_head->fragment_nr_offset ) + frag_number_work = frag_number - fd_head->fragment_nr_offset; + + /* if the partial reassembly flag has been set, and we are extending + * the pdu, un-reassemble the pdu. This means pointing old fds to malloc'ed data. + */ + if(fd_head->flags & FD_DEFRAGMENTED && frag_number_work >= fd_head->datalen && + fd_head->flags & FD_PARTIAL_REASSEMBLY){ + guint32 lastdfpos = 0; + dfpos = 0; + for(fd_i=fd_head->next; fd_i; fd_i=fd_i->next){ + if( !fd_i->tvb_data ) { + if( fd_i->flags & FD_OVERLAP ) { + /* this is a duplicate of the previous + * fragment. */ + fd_i->tvb_data = tvb_new_subset_remaining(fd_head->tvb_data, lastdfpos); + } else { + fd_i->tvb_data = tvb_new_subset_remaining(fd_head->tvb_data, dfpos); + lastdfpos = dfpos; + dfpos += fd_i->len; + } + fd_i->flags |= FD_SUBSET_TVB; + } + fd_i->flags &= (~FD_TOOLONGFRAGMENT) & (~FD_MULTIPLETAILS); + } + fd_head->flags &= ~(FD_DEFRAGMENTED|FD_PARTIAL_REASSEMBLY|FD_DATALEN_SET); + fd_head->flags &= (~FD_TOOLONGFRAGMENT) & (~FD_MULTIPLETAILS); + fd_head->datalen=0; + fd_head->reassembled_in=0; + fd_head->reas_in_layer_num = 0; + } + + + /* create new fd describing this fragment */ + fd = g_slice_new(fragment_item); + fd->next = NULL; + fd->flags = 0; + fd->frame = pinfo->num; + fd->offset = frag_number_work; + fd->len = frag_data_len; + fd->tvb_data = NULL; + + /* fd_head->frame is the maximum of the frame numbers of all the + * fragments added to the reassembly. */ + if (fd->frame > fd_head->frame) + fd_head->frame = fd->frame; + + if (!more_frags) { + /* + * This is the tail fragment in the sequence. + */ + if (fd_head->flags&FD_DATALEN_SET) { + /* ok we have already seen other tails for this packet + * it might be a duplicate. + */ + if (fd_head->datalen != fd->offset ){ + /* Oops, this tail indicates a different packet + * len than the previous ones. Something's wrong. + */ + fd->flags |= FD_MULTIPLETAILS; + fd_head->flags |= FD_MULTIPLETAILS; + } + } else { + /* this was the first tail fragment, now we know the + * sequence number of that fragment (which is NOT + * the length of the packet!) + */ + fd_head->datalen = fd->offset; + fd_head->flags |= FD_DATALEN_SET; + } + } + + /* If the packet is already defragmented, this MUST be an overlap. + * The entire defragmented packet is in fd_head->data + * Even if we have previously defragmented this packet, we still check + * check it. Someone might play overlap and TTL games. + */ + if (fd_head->flags & FD_DEFRAGMENTED) { + fd->flags |= FD_OVERLAP; + fd_head->flags |= FD_OVERLAP; + + /* make sure it's not past the end */ + if (fd->offset > fd_head->datalen) { + /* new fragment comes after the end */ + fd->flags |= FD_TOOLONGFRAGMENT; + fd_head->flags |= FD_TOOLONGFRAGMENT; + LINK_FRAG(fd_head,fd); + return TRUE; + } + /* make sure it doesn't conflict with previous data */ + dfpos=0; + last_fd=NULL; + for (fd_i=fd_head->next;fd_i && (fd_i->offset!=fd->offset);fd_i=fd_i->next) { + if (!last_fd || last_fd->offset!=fd_i->offset){ + dfpos += fd_i->len; + } + last_fd=fd_i; + } + if(fd_i){ + /* new fragment overlaps existing fragment */ + if(fd_i->len!=fd->len){ + /* + * They have different lengths; this + * is definitely a conflict. + */ + fd->flags |= FD_OVERLAPCONFLICT; + fd_head->flags |= FD_OVERLAPCONFLICT; + LINK_FRAG(fd_head,fd); + return TRUE; + } + DISSECTOR_ASSERT(fd_head->len >= dfpos + fd->len); + if (tvb_memeql(fd_head->tvb_data, dfpos, + tvb_get_ptr(tvb,offset,fd->len),fd->len) ){ + /* + * They have the same length, but the + * data isn't the same. + */ + fd->flags |= FD_OVERLAPCONFLICT; + fd_head->flags |= FD_OVERLAPCONFLICT; + LINK_FRAG(fd_head,fd); + return TRUE; + } + /* it was just an overlap, link it and return */ + LINK_FRAG(fd_head,fd); + return TRUE; + } else { + /* + * New fragment doesn't overlap an existing + * fragment - there was presumably a gap in + * the sequence number space. + * + * XXX - what should we do here? Is it always + * the case that there are no gaps, or are there + * protcols using sequence numbers where there + * can be gaps? + * + * If the former, the check below for having + * received all the fragments should check for + * holes in the sequence number space and for the + * first sequence number being 0. If we do that, + * the only way we can get here is if this fragment + * is past the end of the sequence number space - + * but the check for "fd->offset > fd_head->datalen" + * would have caught that above, so it can't happen. + * + * If the latter, we don't have a good way of + * knowing whether reassembly is complete if we + * get packet out of order such that the "last" + * fragment doesn't show up last - but, unless + * in-order reliable delivery of fragments is + * guaranteed, an implementation of the protocol + * has no way of knowing whether reassembly is + * complete, either. + * + * For now, we just link the fragment in and + * return. + */ + LINK_FRAG(fd_head,fd); + return TRUE; + } + } + + /* If we have reached this point, the packet is not defragmented yet. + * Save all payload in a buffer until we can defragment. + */ + /* check len, there may be a fragment with 0 len, that is actually the tail */ + if (fd->len) { + if (!tvb_bytes_exist(tvb, offset, fd->len)) { + /* abort if we didn't capture the entire fragment due + * to a too-short snapshot length */ + g_slice_free(fragment_item, fd); + return FALSE; + } + + fd->tvb_data = tvb_clone_offset_len(tvb, offset, fd->len); + } + LINK_FRAG(fd_head,fd); + + + if( !(fd_head->flags & FD_DATALEN_SET) ){ + /* if we don't know the sequence number of the last fragment, + * there are definitely still missing packets. Cheaper than + * the check below. + */ + return FALSE; + } + + + /* check if we have received the entire fragment + * this is easy since the list is sorted and the head is faked. + * common case the whole list is scanned. + */ + max = 0; + for(fd_i=fd_head->next;fd_i;fd_i=fd_i->next) { + if ( fd_i->offset==max ){ + max++; + } + } + /* max will now be datalen+1 if all fragments have been seen */ + + if (max <= fd_head->datalen) { + /* we have not received all packets yet */ + return FALSE; + } + + + if (max > (fd_head->datalen+1)) { + /* oops, too long fragment detected */ + fd->flags |= FD_TOOLONGFRAGMENT; + fd_head->flags |= FD_TOOLONGFRAGMENT; + } + + + /* we have received an entire packet, defragment it and + * free all fragments + */ + fragment_defragment_and_free(fd_head, pinfo); + + return TRUE; +} + +/* + * This function adds a new fragment to the fragment hash table. + * If this is the first fragment seen for this datagram, a new entry + * is created in the hash table, otherwise this fragment is just added + * to the linked list of fragments for this packet. + * + * Returns a pointer to the head of the fragment data list if we have all the + * fragments, NULL otherwise. + * + * This function assumes frag_number being a block sequence number. + * The bsn for the first block is 0. + */ +static fragment_head * +fragment_add_seq_common(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void *data, + guint32 frag_number, const guint32 frag_data_len, + const gboolean more_frags, const guint32 flags, + gpointer *orig_keyp) +{ + fragment_head *fd_head; + gpointer orig_key; + + fd_head = lookup_fd_head(table, pinfo, id, data, &orig_key); + + /* have we already seen this frame ?*/ + if (pinfo->fd->visited) { + if (fd_head != NULL && fd_head->flags & FD_DEFRAGMENTED) { + if (orig_keyp != NULL) + *orig_keyp = orig_key; + return fd_head; + } else { + return NULL; + } + } + + if (fd_head==NULL){ + /* not found, this must be the first snooped fragment for this + * packet. Create list-head. + */ + fd_head= new_head(FD_BLOCKSEQUENCE); + + if((flags & (REASSEMBLE_FLAGS_NO_FRAG_NUMBER|REASSEMBLE_FLAGS_802_11_HACK)) + && !more_frags) { + /* + * This is the last fragment for this packet, and + * is the only one we've seen. + * + * Either we don't have sequence numbers, in which + * case we assume this is the first fragment for + * this packet, or we're doing special 802.11 + * processing, in which case we assume it's one + * of those reassembled packets with a non-zero + * fragment number (see packet-80211.c); just + * return a pointer to the head of the list; + * fragment_add_seq_check will then add it to the table + * of reassembled packets. + */ + if (orig_keyp != NULL) + *orig_keyp = NULL; + fd_head->reassembled_in=pinfo->num; + fd_head->reas_in_layer_num = pinfo->curr_layer_num; + return fd_head; + } + + orig_key = insert_fd_head(table, fd_head, pinfo, id, data); + if (orig_keyp != NULL) + *orig_keyp = orig_key; + + /* + * If we weren't given an initial fragment number, + * make it 0. + */ + if (flags & REASSEMBLE_FLAGS_NO_FRAG_NUMBER) + frag_number = 0; + } else { + if (orig_keyp != NULL) + *orig_keyp = orig_key; + + if (flags & REASSEMBLE_FLAGS_NO_FRAG_NUMBER) { + fragment_item *fd; + /* + * If we weren't given an initial fragment number, + * use the next expected fragment number as the fragment + * number for this fragment. + */ + for (fd = fd_head->next; fd != NULL; fd = fd->next) { + if (fd->next == NULL) + frag_number = fd->offset + 1; + } + } + } + + if (fragment_add_seq_work(fd_head, tvb, offset, pinfo, + frag_number, frag_data_len, more_frags)) { + /* + * Reassembly is complete. + */ + return fd_head; + } else { + /* + * Reassembly isn't complete. + */ + return NULL; + } +} + +fragment_head * +fragment_add_seq(reassembly_table *table, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 id, const void *data, + const guint32 frag_number, const guint32 frag_data_len, + const gboolean more_frags, const guint32 flags) +{ + return fragment_add_seq_common(table, tvb, offset, pinfo, id, data, + frag_number, frag_data_len, + more_frags, flags, NULL); +} + +/* + * This does the work for "fragment_add_seq_check()" and + * "fragment_add_seq_next()". + * + * This function assumes frag_number being a block sequence number. + * The bsn for the first block is 0. + * + * If REASSEMBLE_FLAGS_NO_FRAG_NUMBER, it uses the next expected fragment number + * as the fragment number if there is a reassembly in progress, otherwise + * it uses 0. + * + * If not REASSEMBLE_FLAGS_NO_FRAG_NUMBER, it uses the "frag_number" argument as + * the fragment number. + * + * If this is the first fragment seen for this datagram, a new + * "fragment_head" structure is allocated to refer to the reassembled + * packet. + * + * This fragment is added to the linked list of fragments for this packet. + * + * If "more_frags" is false and REASSEMBLE_FLAGS_802_11_HACK (as the name + * implies, a special hack for 802.11) or REASSEMBLE_FLAGS_NO_FRAG_NUMBER + * (implying messages must be in order since there's no sequence number) are + * set in "flags", then this (one element) list is returned. + * + * If, after processing this fragment, we have all the fragments, + * "fragment_add_seq_check_work()" removes that from the fragment hash + * table if necessary and adds it to the table of reassembled fragments, + * and returns a pointer to the head of the fragment list. + * + * Otherwise, it returns NULL. + * + * XXX - Should we simply return NULL for zero-length fragments? + */ +static fragment_head * +fragment_add_seq_check_work(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void *data, + const guint32 frag_number, + const guint32 frag_data_len, + const gboolean more_frags, const guint32 flags) +{ + reassembled_key reass_key; + fragment_head *fd_head; + gpointer orig_key; + + /* + * Have we already seen this frame? + * If so, look for it in the table of reassembled packets. + */ + if (pinfo->fd->visited) { + reass_key.frame = pinfo->num; + reass_key.id = id; + return (fragment_head *)g_hash_table_lookup(table->reassembled_table, &reass_key); + } + + fd_head = fragment_add_seq_common(table, tvb, offset, pinfo, id, data, + frag_number, frag_data_len, + more_frags, + flags, + &orig_key); + if (fd_head) { + /* + * Reassembly is complete. + * + * If this is in the table of in-progress reassemblies, + * remove it from that table. (It could be that this + * was the first and last fragment, so that no + * reassembly was done.) + */ + if (orig_key != NULL) + fragment_unhash(table, orig_key); + + /* + * Add this item to the table of reassembled packets. + */ + fragment_reassembled(table, fd_head, pinfo, id); + return fd_head; + } else { + /* + * Reassembly isn't complete. + */ + return NULL; + } +} + +fragment_head * +fragment_add_seq_check(reassembly_table *table, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 id, + const void *data, + const guint32 frag_number, const guint32 frag_data_len, + const gboolean more_frags) +{ + return fragment_add_seq_check_work(table, tvb, offset, pinfo, id, data, + frag_number, frag_data_len, + more_frags, 0); +} + +fragment_head * +fragment_add_seq_802_11(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void *data, + const guint32 frag_number, const guint32 frag_data_len, + const gboolean more_frags) +{ + return fragment_add_seq_check_work(table, tvb, offset, pinfo, id, data, + frag_number, frag_data_len, + more_frags, + REASSEMBLE_FLAGS_802_11_HACK); +} + +fragment_head * +fragment_add_seq_next(reassembly_table *table, tvbuff_t *tvb, const int offset, + const packet_info *pinfo, const guint32 id, + const void *data, const guint32 frag_data_len, + const gboolean more_frags) +{ + /* Use a dummy frag_number (0), it is ignored since + * REASSEMBLE_FLAGS_NO_FRAG_NUMBER is set. */ + return fragment_add_seq_check_work(table, tvb, offset, pinfo, id, data, + 0, frag_data_len, more_frags, + REASSEMBLE_FLAGS_NO_FRAG_NUMBER); +} + +static void +fragment_add_seq_single_move(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data, + const guint32 offset) +{ + fragment_head *fh, *new_fh; + fragment_item *fd, *prev_fd; + tvbuff_t *old_tvb_data; + if (offset == 0) { + return; + } + fh = lookup_fd_head(table, pinfo, id, data, NULL); + if (fh == NULL) { + /* Shouldn't be called this way. + * Probably wouldn't hurt to just create fh in this case. */ + ws_assert_not_reached(); + return; + } + if (fh->flags & FD_DATALEN_SET && fh->datalen <= offset) { + /* Don't take from past the end. <= because we don't + * want to take a First fragment from the next one + * either */ + return; + } + new_fh = lookup_fd_head(table, pinfo, id+offset, data, NULL); + if (new_fh != NULL) { + /* Attach to the end of the sorted list. */ + prev_fd = NULL; + for(fd = fh->next; fd != NULL; fd=fd->next) { + prev_fd = fd; + } + /* Don't take a reassembly starting with a First fragment. */ + fd = new_fh->next; + if (fd && fd->offset != 0) { + fragment_item *inserted = fd; + gboolean multi_insert = (inserted->next != NULL); + if (prev_fd) { + prev_fd->next = fd; + } else { + fh->next = fd; + } + for (; fd; fd=fd->next) { + fd->offset += offset; + if (fh->frame < fd->frame) { + fh->frame = fd->frame; + } + } + update_first_gap(fh, inserted, multi_insert); + /* If previously found a Last fragment, + * transfer that info to the new one. */ + if (new_fh->flags & FD_DATALEN_SET) { + fh->flags |= FD_DATALEN_SET; + fh->datalen = new_fh->datalen + offset; + } + /* Now remove and delete */ + new_fh->next = NULL; + old_tvb_data = fragment_delete(table, pinfo, id+offset, data); + if (old_tvb_data) + tvb_free(old_tvb_data); + } + } +} + +static fragment_head * +fragment_add_seq_single_work(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void* data, + const guint32 frag_data_len, + const gboolean first, const gboolean last, + const guint32 max_frags, const guint32 max_age, + const guint32 flags) +{ + reassembled_key reass_key; + tvbuff_t *old_tvb_data; + gpointer orig_key; + fragment_head *fh, *new_fh; + fragment_item *fd, *prev_fd; + guint32 frag_number, tmp_offset; + /* Have we already seen this frame? + * If so, look for it in the table of reassembled packets. + * Note here we store in the reassembly table by the single sequence + * number rather than the sequence number of the First fragment. */ + if (pinfo->fd->visited) { + reass_key.frame = pinfo->num; + reass_key.id = id; + fh = (fragment_head *)g_hash_table_lookup(table->reassembled_table, &reass_key); + return fh; + } + /* First let's figure out where we want to add our new fragment */ + fh = NULL; + if (first) { + frag_number = 0; + fh = lookup_fd_head(table, pinfo, id-frag_number, data, NULL); + if ((flags & REASSEMBLE_FLAGS_AGING) && + fh && ((fh->frame + max_age) < pinfo->num)) { + old_tvb_data = fragment_delete(table, pinfo, id-frag_number, data); + if (old_tvb_data) + tvb_free(old_tvb_data); + fh = NULL; + } + if (fh == NULL) { + /* Not found. Create list-head. */ + fh = new_head(FD_BLOCKSEQUENCE); + insert_fd_head(table, fh, pinfo, id-frag_number, data); + } + /* As this is the first fragment, we might have added segments + * for this reassembly to the previous one in-progress. */ + fd = NULL; + for (frag_number=1; frag_number < max_frags; frag_number++) { + new_fh = lookup_fd_head(table, pinfo, id-frag_number, data, NULL); + if (new_fh != NULL) { + prev_fd = NULL; + new_fh->frame = 0; + for (fd=new_fh->next; fd && fd->offset < frag_number; fd=fd->next) { + prev_fd = fd; + if (new_fh->frame < fd->frame) { + new_fh->frame = fd->frame; + } + } + if (prev_fd) { + prev_fd->next = NULL; + } else { + new_fh->next = NULL; + } + fragment_items_removed(new_fh, prev_fd); + break; + } + } + if (fd != NULL) { + tmp_offset = 0; + for (prev_fd = fd; prev_fd; prev_fd = prev_fd->next) { + prev_fd->offset -= frag_number; + tmp_offset = prev_fd->offset; + if (fh->frame < prev_fd->frame) { + fh->frame = prev_fd->frame; + } + } + MERGE_FRAG(fh, fd); + if (new_fh != NULL) { + /* If we've moved a Last packet, change datalen. + * Second part of this test prob. redundant? */ + if (new_fh->flags & FD_DATALEN_SET && + new_fh->datalen >= frag_number) { + fh->flags |= FD_DATALEN_SET; + fh->datalen = new_fh->datalen - frag_number; + new_fh->flags &= ~FD_DATALEN_SET; + new_fh->datalen = 0; + } + /* If we've moved all the fragments, + * delete the old head */ + if (new_fh->next == NULL) { + old_tvb_data = fragment_delete(table, pinfo, id-frag_number, data); + if (old_tvb_data) + tvb_free(old_tvb_data); + } + } else { + /* Look forward and take off the next (this is + * necessary in some edge cases where max_frags + * prevented some fragments from going on the + * previous First, but they can go on this one. */ + fragment_add_seq_single_move(table, pinfo, id, + data, tmp_offset); + } + } + frag_number = 0; /* For the rest of the function */ + } else { + for (frag_number=1; frag_number < max_frags; frag_number++) { + fh = lookup_fd_head(table, pinfo, id-frag_number, data, NULL); + if ((flags & REASSEMBLE_FLAGS_AGING) && + fh && ((fh->frame + max_age) < pinfo->num)) { + old_tvb_data = fragment_delete(table, pinfo, id-frag_number, data); + if (old_tvb_data) + tvb_free(old_tvb_data); + fh = NULL; + } + if (fh != NULL) { + if (fh->flags & FD_DATALEN_SET && + fh->datalen < frag_number) { + /* This fragment is after the Last + * fragment, so must go after here. */ + fh = NULL; + } + break; + } + } + if (fh == NULL) { /* Didn't find location, use default */ + frag_number = 1; + /* Already looked for frag_number 1, so just create */ + fh = new_head(FD_BLOCKSEQUENCE); + insert_fd_head(table, fh, pinfo, id-frag_number, data); + } + } + if (last) { + /* Look for fragments past the end set by this Last fragment. */ + prev_fd = NULL; + for (fd=fh->next; fd && fd->offset <= frag_number; fd=fd->next) { + prev_fd = fd; + } + /* fd is now all fragments offset > frag_number (the Last). + * It shouldn't have a fragment with offset frag_number+1, + * as that would be a First fragment not marked as such. + * However, this can happen if we had unreassembled fragments + * (missing, or at the start of the capture) and we've also + * looped around on the sequence numbers. It can also happen + * if bit errors mess up Last or First. */ + if (fd != NULL) { + if (prev_fd) { + prev_fd->next = NULL; + } else { + fh->next = NULL; + } + fragment_items_removed(fh, prev_fd); + fh->frame = 0; + for (prev_fd=fh->next; prev_fd; prev_fd=prev_fd->next) { + if (fh->frame < prev_fd->frame) { + fh->frame = prev_fd->frame; + } + } + while (fd && fd->offset == frag_number+1) { + /* Definitely have bad data here. Best to + * delete these and leave unreassembled. */ + fragment_item *tmp_fd; + tmp_fd=fd->next; + + if (fd->tvb_data && !(fd->flags & FD_SUBSET_TVB)) + tvb_free(fd->tvb_data); + g_slice_free(fragment_item, fd); + fd=tmp_fd; + } + } + if (fd != NULL) { + /* Move these onto the next frame. */ + new_fh = lookup_fd_head(table, pinfo, id+1, data, NULL); + if (new_fh==NULL) { + /* Not found. Create list-head. */ + new_fh = new_head(FD_BLOCKSEQUENCE); + insert_fd_head(table, new_fh, pinfo, id+1, data); + } + tmp_offset = 0; + for (prev_fd = fd; prev_fd; prev_fd = prev_fd->next) { + prev_fd->offset -= (frag_number+1); + tmp_offset = prev_fd->offset; + if (new_fh->frame < fd->frame) { + new_fh->frame = fd->frame; + } + } + MERGE_FRAG(new_fh, fd); + /* If we previously found a different Last fragment, + * transfer that information to the new reassembly. */ + if (fh->flags & FD_DATALEN_SET && + fh->datalen > frag_number) { + new_fh->flags |= FD_DATALEN_SET; + new_fh->datalen = fh->datalen - (frag_number+1); + fh->flags &= ~FD_DATALEN_SET; + fh->datalen = 0; + } else { + /* Look forward and take off the next (this is + * necessary in some edge cases where max_frags + * prevented some fragments from going on the + * previous First, but they can go on this one. */ + fragment_add_seq_single_move(table, pinfo, id+1, + data, tmp_offset); + } + } + } else { + fragment_add_seq_single_move(table, pinfo, id-frag_number, data, + frag_number+1); + } + /* Having cleaned up everything, finally ready to add our new + * fragment. Note that only this will ever complete a reassembly. */ + fh = fragment_add_seq_common(table, tvb, offset, pinfo, + id-frag_number, data, + frag_number, frag_data_len, + !last, 0, &orig_key); + if (fh) { + /* + * Reassembly is complete. + * + * If this is in the table of in-progress reassemblies, + * remove it from that table. (It could be that this + * was the first and last fragment, so that no + * reassembly was done.) + */ + if (orig_key != NULL) + fragment_unhash(table, orig_key); + + /* + * Add this item to the table of reassembled packets. + */ + fragment_reassembled_single(table, fh, pinfo, id-frag_number); + return fh; + } else { + /* + * Reassembly isn't complete. + */ + return NULL; + } +} + +fragment_head * +fragment_add_seq_single(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void* data, + const guint32 frag_data_len, + const gboolean first, const gboolean last, + const guint32 max_frags) +{ + return fragment_add_seq_single_work(table, tvb, offset, pinfo, + id, data, frag_data_len, + first, last, max_frags, 0, 0); +} + +fragment_head * +fragment_add_seq_single_aging(reassembly_table *table, tvbuff_t *tvb, + const int offset, const packet_info *pinfo, + const guint32 id, const void* data, + const guint32 frag_data_len, + const gboolean first, const gboolean last, + const guint32 max_frags, const guint32 max_age) +{ + return fragment_add_seq_single_work(table, tvb, offset, pinfo, + id, data, frag_data_len, + first, last, max_frags, max_age, + REASSEMBLE_FLAGS_AGING); +} + +void +fragment_start_seq_check(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data, + const guint32 tot_len) +{ + fragment_head *fd_head; + + /* Have we already seen this frame ?*/ + if (pinfo->fd->visited) { + return; + } + + /* Check if fragment data exists */ + fd_head = lookup_fd_head(table, pinfo, id, data, NULL); + + if (fd_head == NULL) { + /* Create list-head. */ + fd_head = g_slice_new(fragment_head); + fd_head->next = NULL; + fd_head->first_gap = NULL; + fd_head->contiguous_len = 0; + fd_head->frame = 0; + fd_head->len = 0; + fd_head->fragment_nr_offset = 0; + fd_head->datalen = tot_len; + fd_head->reassembled_in = 0; + fd_head->reas_in_layer_num = 0; + fd_head->flags = FD_BLOCKSEQUENCE|FD_DATALEN_SET; + fd_head->tvb_data = NULL; + fd_head->error = NULL; + + insert_fd_head(table, fd_head, pinfo, id, data); + } +} + +fragment_head * +fragment_end_seq_next(reassembly_table *table, const packet_info *pinfo, + const guint32 id, const void *data) +{ + reassembled_key reass_key; + reassembled_key *new_key; + fragment_head *fd_head; + fragment_item *fd; + gpointer orig_key; + guint32 max_offset = 0; + + /* + * Have we already seen this frame? + * If so, look for it in the table of reassembled packets. + */ + if (pinfo->fd->visited) { + reass_key.frame = pinfo->num; + reass_key.id = id; + return (fragment_head *)g_hash_table_lookup(table->reassembled_table, &reass_key); + } + + fd_head = lookup_fd_head(table, pinfo, id, data, &orig_key); + + if (fd_head) { + for (fd = fd_head->next; fd; fd = fd->next) { + if (fd->offset > max_offset) { + max_offset = fd->offset; + } + } + fd_head->datalen = max_offset; + fd_head->flags |= FD_DATALEN_SET; + + fragment_defragment_and_free (fd_head, pinfo); + + /* + * Remove this from the table of in-progress reassemblies, + * and free up any memory used for it in that table. + */ + fragment_unhash(table, orig_key); + + /* + * Add this item to the table of reassembled packets. + */ + fragment_reassembled(table, fd_head, pinfo, id); + if (fd_head->next != NULL) { + new_key = g_slice_new(reassembled_key); + new_key->frame = pinfo->num; + new_key->id = id; + reassembled_table_insert(table->reassembled_table, new_key, fd_head); + } + + return fd_head; + } else { + /* + * Fragment data not found. + */ + return NULL; + } +} + +/* + * Process reassembled data; if we're on the frame in which the data + * was reassembled, put the fragment information into the protocol + * tree, and construct a tvbuff with the reassembled data, otherwise + * just put a "reassembled in" item into the protocol tree. + * offset from start of tvb, result up to end of tvb + */ +tvbuff_t * +process_reassembled_data(tvbuff_t *tvb, const int offset, packet_info *pinfo, + const char *name, fragment_head *fd_head, const fragment_items *fit, + gboolean *update_col_infop, proto_tree *tree) +{ + tvbuff_t *next_tvb; + gboolean update_col_info; + proto_item *frag_tree_item; + + if (fd_head != NULL && pinfo->num == fd_head->reassembled_in && pinfo->curr_layer_num == fd_head->reas_in_layer_num) { + /* + * OK, we've reassembled this. + * Is this something that's been reassembled from more + * than one fragment? + */ + if (fd_head->next != NULL) { + /* + * Yes. + * Allocate a new tvbuff, referring to the + * reassembled payload, and set + * the tvbuff to the list of tvbuffs to which + * the tvbuff we were handed refers, so it'll get + * cleaned up when that tvbuff is cleaned up. + */ + next_tvb = tvb_new_chain(tvb, fd_head->tvb_data); + + /* Add the defragmented data to the data source list. */ + add_new_data_source(pinfo, next_tvb, name); + + /* show all fragments */ + if (fd_head->flags & FD_BLOCKSEQUENCE) { + update_col_info = !show_fragment_seq_tree( + fd_head, fit, tree, pinfo, next_tvb, &frag_tree_item); + } else { + update_col_info = !show_fragment_tree(fd_head, + fit, tree, pinfo, next_tvb, &frag_tree_item); + } + } else { + /* + * No. + * Return a tvbuff with the payload. next_tvb ist from offset until end + */ + next_tvb = tvb_new_subset_remaining(tvb, offset); + pinfo->fragmented = FALSE; /* one-fragment packet */ + update_col_info = TRUE; + } + if (update_col_infop != NULL) + *update_col_infop = update_col_info; + } else { + /* + * We don't have the complete reassembled payload, or this + * isn't the final frame of that payload. + */ + next_tvb = NULL; + + /* + * If we know what frame this was reassembled in, + * and if there's a field to use for the number of + * the frame in which the packet was reassembled, + * add it to the protocol tree. + */ + if (fd_head != NULL && fit->hf_reassembled_in != NULL) { + proto_item *fei = proto_tree_add_uint(tree, + *(fit->hf_reassembled_in), tvb, + 0, 0, fd_head->reassembled_in); + proto_item_set_generated(fei); + } + } + return next_tvb; +} + +/* + * Show a single fragment in a fragment subtree, and put information about + * it in the top-level item for that subtree. + */ +static void +show_fragment(fragment_item *fd, const int offset, const fragment_items *fit, + proto_tree *ft, proto_item *fi, const gboolean first_frag, + const guint32 count, tvbuff_t *tvb, packet_info *pinfo) +{ + proto_item *fei=NULL; + int hf; + + if (first_frag) { + gchar *name; + if (count == 1) { + name = g_strdup(proto_registrar_get_name(*(fit->hf_fragment))); + } else { + name = g_strdup(proto_registrar_get_name(*(fit->hf_fragments))); + } + proto_item_set_text(fi, "%u %s (%u byte%s): ", count, name, tvb_captured_length(tvb), + plurality(tvb_captured_length(tvb), "", "s")); + g_free(name); + } else { + proto_item_append_text(fi, ", "); + } + proto_item_append_text(fi, "#%u(%u)", fd->frame, fd->len); + + if (fd->flags & (FD_OVERLAPCONFLICT + |FD_MULTIPLETAILS|FD_TOOLONGFRAGMENT) ) { + hf = *(fit->hf_fragment_error); + } else { + hf = *(fit->hf_fragment); + } + if (fd->len == 0) { + fei = proto_tree_add_uint_format(ft, hf, + tvb, offset, fd->len, + fd->frame, + "Frame: %u (no data)", + fd->frame); + } else { + fei = proto_tree_add_uint_format(ft, hf, + tvb, offset, fd->len, + fd->frame, + "Frame: %u, payload: %u-%u (%u byte%s)", + fd->frame, + offset, + offset+fd->len-1, + fd->len, + plurality(fd->len, "", "s")); + } + proto_item_set_generated(fei); + mark_frame_as_depended_upon(pinfo->fd, fd->frame); + if (fd->flags & (FD_OVERLAP|FD_OVERLAPCONFLICT + |FD_MULTIPLETAILS|FD_TOOLONGFRAGMENT) ) { + /* this fragment has some flags set, create a subtree + * for it and display the flags. + */ + proto_tree *fet=NULL; + + fet = proto_item_add_subtree(fei, *(fit->ett_fragment)); + if (fd->flags&FD_OVERLAP) { + fei=proto_tree_add_boolean(fet, + *(fit->hf_fragment_overlap), + tvb, 0, 0, + TRUE); + proto_item_set_generated(fei); + } + if (fd->flags&FD_OVERLAPCONFLICT) { + fei=proto_tree_add_boolean(fet, + *(fit->hf_fragment_overlap_conflict), + tvb, 0, 0, + TRUE); + proto_item_set_generated(fei); + } + if (fd->flags&FD_MULTIPLETAILS) { + fei=proto_tree_add_boolean(fet, + *(fit->hf_fragment_multiple_tails), + tvb, 0, 0, + TRUE); + proto_item_set_generated(fei); + } + if (fd->flags&FD_TOOLONGFRAGMENT) { + fei=proto_tree_add_boolean(fet, + *(fit->hf_fragment_too_long_fragment), + tvb, 0, 0, + TRUE); + proto_item_set_generated(fei); + } + } +} + +static gboolean +show_fragment_errs_in_col(fragment_head *fd_head, const fragment_items *fit, + packet_info *pinfo) +{ + if (fd_head->flags & (FD_OVERLAPCONFLICT + |FD_MULTIPLETAILS|FD_TOOLONGFRAGMENT) ) { + col_add_fstr(pinfo->cinfo, COL_INFO, "[Illegal %s]", fit->tag); + return TRUE; + } + + return FALSE; +} + +/* This function will build the fragment subtree; it's for fragments + reassembled with "fragment_add()". + + It will return TRUE if there were fragmentation errors + or FALSE if fragmentation was ok. +*/ +gboolean +show_fragment_tree(fragment_head *fd_head, const fragment_items *fit, + proto_tree *tree, packet_info *pinfo, tvbuff_t *tvb, proto_item **fi) +{ + fragment_item *fd; + proto_tree *ft; + gboolean first_frag; + guint32 count = 0; + /* It's not fragmented. */ + pinfo->fragmented = FALSE; + + *fi = proto_tree_add_item(tree, *(fit->hf_fragments), tvb, 0, -1, ENC_NA); + proto_item_set_generated(*fi); + + ft = proto_item_add_subtree(*fi, *(fit->ett_fragments)); + first_frag = TRUE; + for (fd = fd_head->next; fd != NULL; fd = fd->next) { + count++; + } + for (fd = fd_head->next; fd != NULL; fd = fd->next) { + show_fragment(fd, fd->offset, fit, ft, *fi, first_frag, count, tvb, pinfo); + first_frag = FALSE; + } + + if (fit->hf_fragment_count) { + proto_item *fli = proto_tree_add_uint(ft, *(fit->hf_fragment_count), + tvb, 0, 0, count); + proto_item_set_generated(fli); + } + + if (fit->hf_reassembled_length) { + proto_item *fli = proto_tree_add_uint(ft, *(fit->hf_reassembled_length), + tvb, 0, 0, tvb_captured_length (tvb)); + proto_item_set_generated(fli); + } + + if (fit->hf_reassembled_data) { + proto_item *fli = proto_tree_add_item(ft, *(fit->hf_reassembled_data), + tvb, 0, tvb_captured_length(tvb), ENC_NA); + proto_item_set_generated(fli); + } + + return show_fragment_errs_in_col(fd_head, fit, pinfo); +} + +/* This function will build the fragment subtree; it's for fragments + reassembled with "fragment_add_seq()" or "fragment_add_seq_check()". + + It will return TRUE if there were fragmentation errors + or FALSE if fragmentation was ok. +*/ +gboolean +show_fragment_seq_tree(fragment_head *fd_head, const fragment_items *fit, + proto_tree *tree, packet_info *pinfo, tvbuff_t *tvb, proto_item **fi) +{ + guint32 offset, next_offset, count = 0; + fragment_item *fd, *last_fd; + proto_tree *ft; + gboolean first_frag; + + /* It's not fragmented. */ + pinfo->fragmented = FALSE; + + *fi = proto_tree_add_item(tree, *(fit->hf_fragments), tvb, 0, -1, ENC_NA); + proto_item_set_generated(*fi); + + ft = proto_item_add_subtree(*fi, *(fit->ett_fragments)); + offset = 0; + next_offset = 0; + last_fd = NULL; + first_frag = TRUE; + for (fd = fd_head->next; fd != NULL; fd = fd->next){ + count++; + } + for (fd = fd_head->next; fd != NULL; fd = fd->next){ + if (last_fd == NULL || last_fd->offset != fd->offset) { + offset = next_offset; + next_offset += fd->len; + } + last_fd = fd; + show_fragment(fd, offset, fit, ft, *fi, first_frag, count, tvb, pinfo); + first_frag = FALSE; + } + + if (fit->hf_fragment_count) { + proto_item *fli = proto_tree_add_uint(ft, *(fit->hf_fragment_count), + tvb, 0, 0, count); + proto_item_set_generated(fli); + } + + if (fit->hf_reassembled_length) { + proto_item *fli = proto_tree_add_uint(ft, *(fit->hf_reassembled_length), + tvb, 0, 0, tvb_captured_length (tvb)); + proto_item_set_generated(fli); + } + + if (fit->hf_reassembled_data) { + proto_item *fli = proto_tree_add_item(ft, *(fit->hf_reassembled_data), + tvb, 0, tvb_captured_length(tvb), ENC_NA); + proto_item_set_generated(fli); + } + + return show_fragment_errs_in_col(fd_head, fit, pinfo); +} + +static void +reassembly_table_init_reg_table(gpointer p, gpointer user_data _U_) +{ + register_reassembly_table_t* reg_table = (register_reassembly_table_t*)p; + reassembly_table_init(reg_table->table, reg_table->funcs); +} + +static void +reassembly_table_init_reg_tables(void) +{ + g_list_foreach(reassembly_table_list, reassembly_table_init_reg_table, NULL); +} + +static void +reassembly_table_cleanup_reg_table(gpointer p, gpointer user_data _U_) +{ + register_reassembly_table_t* reg_table = (register_reassembly_table_t*)p; + reassembly_table_destroy(reg_table->table); +} + +static void +reassembly_table_cleanup_reg_tables(void) +{ + g_list_foreach(reassembly_table_list, reassembly_table_cleanup_reg_table, NULL); +} + +void reassembly_tables_init(void) +{ + register_init_routine(&reassembly_table_init_reg_tables); + register_cleanup_routine(&reassembly_table_cleanup_reg_tables); +} + +static void +reassembly_table_free(gpointer p, gpointer user_data _U_) +{ + register_reassembly_table_t* reg_table = (register_reassembly_table_t*)p; + reassembly_table_destroy(reg_table->table); + g_free(reg_table); +} + +void +reassembly_table_cleanup(void) +{ + g_list_foreach(reassembly_table_list, reassembly_table_free, NULL); + g_list_free(reassembly_table_list); +} + +/* One instance of this structure is created for each pdu that spans across + * multiple segments. (MSP) */ +typedef struct _multisegment_pdu_t { + guint64 first_frame; + guint64 last_frame; + guint start_offset_at_first_frame; + guint end_offset_at_last_frame; + gint length; /* length of this MSP */ + guint32 streaming_reassembly_id; + /* pointer to previous multisegment_pdu */ + struct _multisegment_pdu_t* prev_msp; +} multisegment_pdu_t; + +/* struct for keeping the reassembly information of each stream */ +struct streaming_reassembly_info_t { + /* This map is keyed by frame num and keeps track of all MSPs for this + * stream. Different frames will point to the same MSP if they contain + * part data of this MSP. If a frame contains data that + * belongs to two MSPs, it will point to the second MSP. */ + wmem_map_t* multisegment_pdus; + /* This map is keyed by frame num and keeps track of the frag_offset + * of the first byte of frames for fragment_add() after first scan. */ + wmem_map_t* frame_num_frag_offset_map; + /* how many bytes the current uncompleted MSP still needs. (only valid for first scan) */ + gint prev_deseg_len; + /* the current uncompleted MSP (only valid for first scan) */ + multisegment_pdu_t* last_msp; +}; + +static guint32 +create_streaming_reassembly_id(void) +{ + static guint32 global_streaming_reassembly_id = 0; + return ++global_streaming_reassembly_id; +} + +streaming_reassembly_info_t* +streaming_reassembly_info_new(void) +{ + return wmem_new0(wmem_file_scope(), streaming_reassembly_info_t); +} + +/* Following is an example of ProtoA and ProtoB protocols from the declaration of this function in 'reassemble.h': + * + * +------------------ A Multisegment PDU of ProtoB ----------------------+ + * | | + * +--- ProtoA payload1 ---+ +- payload2 -+ +- Payload3 -+ +- Payload4 -+ +- ProtoA payload5 -+ + * | EoMSP | OmNFP | BoMSP | | MoMSP | | MoMSP | | MoMSP | | EoMSP | BoMSP | + * +-------+-------+-------+ +------------+ +------------+ +------------+ +---------+---------+ + * | | + * +----------------------------------------------------------------------+ + * + * For a ProtoA payload composed of EoMSP + OmNFP + BoMSP will call fragment_add() twice on EoMSP and BoMSP; and call + * process_reassembled_data() once for generating tvb of a MSP to which EoMSP belongs; and call subdissector twice on + * reassembled MSP of EoMSP and OmNFP + BoMSP. After that finds BoMSP is a beginning of a MSP at first scan. + * + * The rules are: + * + * - If a ProtoA payload contains EoMSP, we will need call fragment_add(), process_reassembled_data() and subdissector + * once on it to end a MSP. (May run twice or more times at first scan, because subdissector may only return the + * head length of message by pinfo->desegment_len. We need run second time for subdissector to determine the length + * of entire message). + * + * - If a ProtoA payload contains OmNFP, we will need only call subdissector once on it. The subdissector need dissect + * all non-fragment PDUs in it. (no desegment_len should output) + * + * - If a ProtoA payload contains BoMSP, we will need call subdissector once on BoMSP or OmNFP+BoMSP (because unknown + * during first scan). The subdissector will output desegment_len (!= 0). Then we will call fragment_add() + * with a new reassembly id on BoMSP for starting a new MSP. + * + * - If a ProtoA payload only contains MoMSP (entire payload is part of a MSP), we will only call fragment_add() once + * or twice (at first scan) on it. The subdissector will not be called. + * + * In this implementation, only multisegment PDUs are recorded in multisegment_pdus map keyed by the numbers (guint64) + * of frames belongs to MSPs. Each MSP in the map has a pointer referred to previous MSP, because we may need + * two MSPs to dissect a ProtoA payload that contains EoMSP + BoMSP at the same time. The multisegment_pdus map is built + * during first scan (pinfo->visited == FALSE) with help of prev_deseg_len and last_msp fields of streaming_reassembly_info_t + * for each direction of a ProtoA STREAM. The prev_deseg_len record how many bytes of subsequent ProtoA payloads belong to + * previous PDU during first scan. The last_msp member of streaming_reassembly_info_t is always point to last MSP which + * is created during scan previous or early ProtoA payloads. Since subdissector might return only the head length of entire + * message (by pinfo->desegment_len) when there is not enough data to determine the message length, we need to reopen + * reassembly fragments for adding more bytes during scanning the next ProtoA payload. We have to use fragment_add() + * instead of fragment_add_check() or fragment_add_seq_next(). + * + * Read more: please refer to comments of the declaration of this function in 'reassemble.h'. + */ +gint +reassemble_streaming_data_and_call_subdissector( + tvbuff_t* tvb, packet_info* pinfo, guint offset, gint length, + proto_tree* segment_tree, proto_tree* reassembled_tree, reassembly_table streaming_reassembly_table, + streaming_reassembly_info_t* reassembly_info, guint64 cur_frame_num, + dissector_handle_t subdissector_handle, proto_tree* subdissector_tree, void* subdissector_data, + const char* label, const fragment_items* frag_hf_items, int hf_segment_data +) +{ + gint orig_length = length; + gint datalen = 0; + gint bytes_belong_to_prev_msp = 0; /* bytes belong to previous MSP */ + guint32 reassembly_id = 0, frag_offset = 0; + fragment_head* head = NULL; + gboolean need_more = FALSE; + gboolean found_BoMSP = FALSE; + multisegment_pdu_t* cur_msp = NULL, * prev_msp = NULL; + guint16 save_can_desegment; + int save_desegment_offset; + guint32 save_desegment_len; + guint64* frame_ptr; + + save_can_desegment = pinfo->can_desegment; + save_desegment_offset = pinfo->desegment_offset; + save_desegment_len = pinfo->desegment_len; + + /* calculate how many bytes of this payload belongs to previous MSP (EoMSP) */ + if (!PINFO_FD_VISITED(pinfo)) { + /* this is first scan */ + if (reassembly_info->prev_deseg_len == DESEGMENT_ONE_MORE_SEGMENT) { + /* assuming the entire tvb belongs to the previous MSP */ + bytes_belong_to_prev_msp = length; + reassembly_info->prev_deseg_len = length; + } else if (reassembly_info->prev_deseg_len > 0) { + /* part or all of current payload belong to previous MSP */ + bytes_belong_to_prev_msp = MIN(reassembly_info->prev_deseg_len, length); + reassembly_info->prev_deseg_len -= bytes_belong_to_prev_msp; + need_more = (reassembly_info->prev_deseg_len > 0); + } /* else { beginning of a new PDU (might be a NFP or MSP) } */ + + if (bytes_belong_to_prev_msp > 0) { + DISSECTOR_ASSERT(reassembly_info->last_msp != NULL); + reassembly_id = reassembly_info->last_msp->streaming_reassembly_id; + frag_offset = reassembly_info->last_msp->length; + if (reassembly_info->frame_num_frag_offset_map == NULL) { + reassembly_info->frame_num_frag_offset_map = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal); + } + frame_ptr = (guint64*)wmem_memdup(wmem_file_scope(), &cur_frame_num, sizeof(guint64)); + wmem_map_insert(reassembly_info->frame_num_frag_offset_map, frame_ptr, GUINT_TO_POINTER(frag_offset)); + /* This payload contains the data of previous msp, so we point to it. That may be overriden late. */ + wmem_map_insert(reassembly_info->multisegment_pdus, frame_ptr, reassembly_info->last_msp); + } + } else { + /* not first scan, use information of multisegment_pdus built during first scan */ + if (reassembly_info->multisegment_pdus) { + cur_msp = (multisegment_pdu_t*)wmem_map_lookup(reassembly_info->multisegment_pdus, &cur_frame_num); + } + if (cur_msp) { + if (cur_msp->first_frame == cur_frame_num) { + /* Current payload contains a beginning of a MSP. (BoMSP) + * The cur_msp contains information about the beginning MSP. + * If prev_msp is not null, that means this payload also contains + * the last part of previous MSP. (EoMSP) */ + prev_msp = cur_msp->prev_msp; + } else { + /* Current payload is not a first frame of a MSP (not include BoMSP). */ + prev_msp = cur_msp; + cur_msp = NULL; + } + } + + if (prev_msp && prev_msp->last_frame >= cur_frame_num) { + if (prev_msp->last_frame == cur_frame_num) { + /* this payload contains part of previous MSP (contains EoMSP) */ + bytes_belong_to_prev_msp = prev_msp->end_offset_at_last_frame - offset; + } else { /* if (prev_msp->last_frame > cur_frame_num) */ + /* this payload all belongs to previous MSP */ + bytes_belong_to_prev_msp = length; + need_more = TRUE; + } + reassembly_id = prev_msp->streaming_reassembly_id; + } + if (reassembly_info->frame_num_frag_offset_map) { + frag_offset = GPOINTER_TO_UINT(wmem_map_lookup(reassembly_info->frame_num_frag_offset_map, &cur_frame_num)); + } + } + + /* handling EoMSP or MoMSP (entire payload being middle part of a MSP) */ + while (bytes_belong_to_prev_msp > 0) { + tvbuff_t* reassembled_tvb = NULL; + DISSECTOR_ASSERT(reassembly_id > 0); + pinfo->can_desegment = 2; /* this will be decreased one while passing to subdissector */ + pinfo->desegment_offset = 0; + pinfo->desegment_len = 0; + + head = fragment_add(&streaming_reassembly_table, tvb, offset, pinfo, reassembly_id, NULL, + frag_offset, bytes_belong_to_prev_msp, need_more); + + if (head) { + if (frag_hf_items->hf_reassembled_in) { + proto_item_set_generated( + proto_tree_add_uint(segment_tree, *(frag_hf_items->hf_reassembled_in), tvb, offset, + bytes_belong_to_prev_msp, head->reassembled_in) + ); + } + + if (!need_more) { + reassembled_tvb = process_reassembled_data(tvb, offset, pinfo, + wmem_strdup_printf(pinfo->pool, "Reassembled %s", label), + head, frag_hf_items, NULL, reassembled_tree); + } + } + + proto_tree_add_bytes_format(segment_tree, hf_segment_data, tvb, offset, + bytes_belong_to_prev_msp, NULL, "%s Segment data (%u byte%s)", label, + bytes_belong_to_prev_msp, plurality(bytes_belong_to_prev_msp, "", "s")); + + if (reassembled_tvb) { + /* normally, this stage will dissect one or more completed pdus */ + /* Note, don't call_dissector_with_data because sometime the pinfo->curr_layer_num will changed + * after calling that will make reassembly failed! */ + call_dissector_only(subdissector_handle, reassembled_tvb, pinfo, subdissector_tree, subdissector_data); + } + + if (pinfo->desegment_len) { + /* that must only happen during first scan the reassembly_info->prev_deseg_len might be only the + * head length of entire message. */ + DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo)); + DISSECTOR_ASSERT_HINT(pinfo->desegment_len != DESEGMENT_UNTIL_FIN, "Subdissector MUST NOT " + "set pinfo->desegment_len to DESEGMENT_UNTIL_FIN. Instead, it can set pinfo->desegment_len to " + " DESEGMENT_ONE_MORE_SEGMENT or the length of head if the length of entire message is not able to be determined."); + + if (pinfo->desegment_offset > 0) { + DISSECTOR_ASSERT_HINT(pinfo->desegment_offset > reassembly_info->last_msp->length + && pinfo->desegment_offset < reassembly_info->last_msp->length + bytes_belong_to_prev_msp, + wmem_strdup_printf(pinfo->pool, + "Subdissector MUST NOT set pinfo->desegment_offset(%d) in previous or next part of MSP, must between (%d, %d).", + pinfo->desegment_offset, reassembly_info->last_msp->length, reassembly_info->last_msp->length + bytes_belong_to_prev_msp)); + + /* shorten the bytes_belong_to_prev_msp and just truncate the ressembled tvb */ + bytes_belong_to_prev_msp = pinfo->desegment_offset - reassembly_info->last_msp->length; + fragment_truncate(&streaming_reassembly_table, pinfo, reassembly_id, NULL, pinfo->desegment_offset); + found_BoMSP = TRUE; + } else { + if (pinfo->desegment_len == DESEGMENT_ONE_MORE_SEGMENT) { + /* just need more bytes, all remaining bytes belongs to previous MSP (to run fragment_add again) */ + bytes_belong_to_prev_msp = length; + } + + /* Remove the data added by previous fragment_add(), and reopen fragments for adding more bytes. */ + fragment_truncate(&streaming_reassembly_table, pinfo, reassembly_id, NULL, reassembly_info->last_msp->length); + fragment_set_partial_reassembly(&streaming_reassembly_table, pinfo, reassembly_id, NULL); + + reassembly_info->prev_deseg_len = bytes_belong_to_prev_msp + pinfo->desegment_len; + bytes_belong_to_prev_msp = MIN(reassembly_info->prev_deseg_len, length); + reassembly_info->prev_deseg_len -= bytes_belong_to_prev_msp; + need_more = (reassembly_info->prev_deseg_len > 0); + continue; + } + } + + if (pinfo->desegment_len == 0 || found_BoMSP) { + /* We will arrive here, only when the MSP is defragmented and dissected or this + * payload all belongs to previous MSP (only fragment_add() with need_more=TRUE called) + * or BoMSP is parsed while pinfo->desegment_offset > 0 and pinfo->desegment_len != 0 + */ + offset += bytes_belong_to_prev_msp; + length -= bytes_belong_to_prev_msp; + DISSECTOR_ASSERT(length >= 0); + if (!PINFO_FD_VISITED(pinfo)) { + reassembly_info->last_msp->length += bytes_belong_to_prev_msp; + } + + if (!PINFO_FD_VISITED(pinfo) && reassembled_tvb) { + /* completed current msp */ + reassembly_info->last_msp->last_frame = cur_frame_num; + reassembly_info->last_msp->end_offset_at_last_frame = offset; + reassembly_info->prev_deseg_len = pinfo->desegment_len; + } + bytes_belong_to_prev_msp = 0; /* break */ + } + } + + /* to find and handle OmNFP, and find BoMSP at first scan. */ + if (length > 0 && !found_BoMSP) { + if (!PINFO_FD_VISITED(pinfo)) { + /* It is first scan, to dissect remaining bytes to find whether it is OmNFP only, or BoMSP only or OmNFP + BoMSP. */ + datalen = length; + DISSECTOR_ASSERT(cur_msp == NULL); + } else { + /* Not first scan */ + if (cur_msp) { + /* There's a BoMSP. Let's calculate the length of OmNFP between EoMSP and BoMSP */ + datalen = cur_msp->start_offset_at_first_frame - offset; /* if result is zero that means no OmNFP */ + } else { + /* This payload is not a beginning of MSP. The remaining bytes all belong to OmNFP without BoMSP */ + datalen = length; + } + } + DISSECTOR_ASSERT(datalen >= 0); + + /* Dissect the remaining of this payload. If (datalen == 0) means remaining only have one BoMSP without OmNFP. */ + if (datalen > 0) { + /* we dissect if it is not dissected before or it is a non-fragment pdu (between two multisegment pdus) */ + pinfo->can_desegment = 2; + pinfo->desegment_offset = 0; + pinfo->desegment_len = 0; + + call_dissector_only(subdissector_handle, tvb_new_subset_length(tvb, offset, datalen), + pinfo, subdissector_tree, subdissector_data); + + if (pinfo->desegment_len) { + DISSECTOR_ASSERT_HINT(pinfo->desegment_len != DESEGMENT_UNTIL_FIN, "Subdissector MUST NOT " + "set pinfo->desegment_len to DESEGMENT_UNTIL_FIN. Instead, it can set pinfo->desegment_len to " + " DESEGMENT_ONE_MORE_SEGMENT or the length of head if the length of entire message is not able to be determined."); + /* only happen during first scan */ + DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo) && datalen == length); + offset += pinfo->desegment_offset; + length -= pinfo->desegment_offset; + } else { + /* all remaining bytes are consumed by subdissector */ + offset += datalen; + length -= datalen; + } + if (!PINFO_FD_VISITED(pinfo)) { + reassembly_info->prev_deseg_len = pinfo->desegment_len; + } + } /* else all remaining bytes (BoMSP) belong to a new MSP */ + DISSECTOR_ASSERT(length >= 0); + } + + /* handling BoMSP */ + if (length > 0) { + col_append_sep_fstr(pinfo->cinfo, COL_INFO, " ", "[%s segment of a reassembled PDU] ", label); + if (!PINFO_FD_VISITED(pinfo)) { + /* create a msp for current frame during first scan */ + cur_msp = wmem_new0(wmem_file_scope(), multisegment_pdu_t); + cur_msp->first_frame = cur_frame_num; + cur_msp->last_frame = G_MAXUINT64; + cur_msp->start_offset_at_first_frame = offset; + cur_msp->length = length; + cur_msp->streaming_reassembly_id = reassembly_id = create_streaming_reassembly_id(); + cur_msp->prev_msp = reassembly_info->last_msp; + reassembly_info->last_msp = cur_msp; + if (reassembly_info->multisegment_pdus == NULL) { + reassembly_info->multisegment_pdus = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal); + } + frame_ptr = (guint64*)wmem_memdup(wmem_file_scope(), &cur_frame_num, sizeof(guint64)); + wmem_map_insert(reassembly_info->multisegment_pdus, frame_ptr, cur_msp); + } else { + DISSECTOR_ASSERT(cur_msp && cur_msp->start_offset_at_first_frame == offset); + reassembly_id = cur_msp->streaming_reassembly_id; + } + /* add first fragment of the new MSP to reassembly table */ + head = fragment_add(&streaming_reassembly_table, tvb, offset, pinfo, reassembly_id, + NULL, 0, length, TRUE); + + if (head && frag_hf_items->hf_reassembled_in) { + proto_item_set_generated( + proto_tree_add_uint(segment_tree, *(frag_hf_items->hf_reassembled_in), + tvb, offset, length, head->reassembled_in) + ); + } + proto_tree_add_bytes_format(segment_tree, hf_segment_data, tvb, offset, length, + NULL, "%s Segment data (%u byte%s)", label, length, plurality(length, "", "s")); + } + + pinfo->can_desegment = save_can_desegment; + pinfo->desegment_offset = save_desegment_offset; + pinfo->desegment_len = save_desegment_len; + + return orig_length; +} + +gint +additional_bytes_expected_to_complete_reassembly(streaming_reassembly_info_t* reassembly_info) +{ + return reassembly_info->prev_deseg_len; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ |