/* reassemble.c * Routines for {fragment,segment} reassembly * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include #include #include #include #include #include #include /* * 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; /* Resetting 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 occurring 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 overridden 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 reassembled 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: */