reassembly: Store pointer to first gap

Store pointer to first gap to reduce number of full list traversals
needed when linking new fragments. When all captured fragments are in
order, the first gap is effectively pointing to list tail. The best case
scenario, where the list traversals are completely eliminated, happens
every time for protocols that always have the fragments ordered (most
notably USBLL Full-Speed capture containing Bulk OUT transfers with
a lot of retransmissions).

The memory usage is increased by a single pointer and 32-bit contiguous
length counter per fragment head. The additional CPU usage is constant
per insertion, i.e. does not increase with the number of fragments in
the list.

Fixes #17311
This commit is contained in:
Tomasz Moń 2022-11-27 10:28:10 +01:00
parent 8623cd9746
commit 5853886d50
No known key found for this signature in database
GPG Key ID: 397DFEBE343AD96F
2 changed files with 124 additions and 29 deletions

View File

@ -687,6 +687,75 @@ fragment_add_seq_offset(reassembly_table *table, const packet_info *pinfo, const
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
@ -874,6 +943,8 @@ fragment_truncate(reassembly_table *table, const packet_info *pinfo,
} 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;
@ -1035,41 +1106,73 @@ LINK_FRAG(fragment_head *fd_head,fragment_item *fd)
fd->next = fd_head->next;
fd_head->next = fd;
} else {
for(fd_i = fd_head->next; fd_i->next; fd_i=fd_i->next) {
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;
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->offset < fd_head->next->offset) {
tmp = fd_head->next;
fd_head->next = fd;
fd = tmp;
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;
}
for(fd_i = fd_head->next; fd_i->next; fd_i=fd_i->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);
}
/*
@ -1101,7 +1204,7 @@ fragment_add_work(fragment_head *fd_head, tvbuff_t *tvb, const int offset,
{
fragment_item *fd;
fragment_item *fd_i;
guint32 max, dfpos, fraglen, overlap;
guint32 dfpos, fraglen, overlap;
tvbuff_t *old_tvb_data;
guint8 *data;
@ -1260,26 +1363,8 @@ fragment_add_work(fragment_head *fd_head, tvbuff_t *tvb, const int offset,
return FALSE;
}
/*
* Check if we have received the entire fragment.
* This is easy since the list is sorted and the head is faked.
*
* First, we compute the amount of contiguous data that's
* available. (The check for fd_i->offset <= max rules out
* fragments that don't start before or at the end of the
* previous fragment, i.e. fragments that have a gap between
* them and the previous fragment.)
*/
max = 0;
for (fd_i=fd_head->next;fd_i;fd_i=fd_i->next) {
if ( ((fd_i->offset)<=max) &&
((fd_i->offset+fd_i->len)>max) ){
max = fd_i->offset+fd_i->len;
}
}
if (max < (fd_head->datalen)) {
/* 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
@ -1301,7 +1386,7 @@ fragment_add_work(fragment_head *fd_head, tvbuff_t *tvb, const int offset,
for (dfpos=0,fd_i=fd_head->next;fd_i;fd_i=fd_i->next) {
if (fd_i->len) {
/*
* The loop above that calculates max also
* 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
@ -2333,6 +2418,8 @@ fragment_add_seq_single_move(reassembly_table *table, const packet_info *pinfo,
/* 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 {
@ -2344,6 +2431,7 @@ fragment_add_seq_single_move(reassembly_table *table, const packet_info *pinfo,
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) {
@ -2420,6 +2508,7 @@ fragment_add_seq_single_work(reassembly_table *table, tvbuff_t *tvb,
} else {
new_fh->next = NULL;
}
fragment_items_removed(new_fh, prev_fd);
break;
}
}
@ -2506,6 +2595,7 @@ fragment_add_seq_single_work(reassembly_table *table, tvbuff_t *tvb,
} 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) {
@ -2639,6 +2729,8 @@ fragment_start_seq_check(reassembly_table *table, const packet_info *pinfo,
/* 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;

View File

@ -63,6 +63,9 @@ typedef struct _fragment_item {
typedef struct _fragment_head {
struct _fragment_item *next;
struct _fragment_item *first_gap; /**< pointer to last fragment before first gap.
* NULL if there is no fragment starting at offset 0 */
guint32 contiguous_len; /**< contigous length from head up to first gap */
guint32 frame; /**< maximum of all frame numbers added to reassembly */
guint32 len; /**< When flags&FD_BLOCKSEQUENCE and FD_DEFRAGMENTED
* are set, the number of bytes of the full datagram.