aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2018-06-01 15:11:47 +0200
committerAnders Broman <a.broman58@gmail.com>2018-06-28 06:10:35 +0000
commitca423314373b0a4ce7d6bc1cf94c4995e1263ea2 (patch)
tree9a39a7a9f4f9a06bc51e77f115fb597349f29bf9
parente6935f96354574699379009d7f55857ba69c55b1 (diff)
tcp: add support for reassembling out-of-order segments
Currently out-of-order segments will result in cutting a stream into two pieces while the out-of-order segment itself is ignored. For example, a stream of segments "ABDCE" is interpreted as "AB", "DE" with "C" ignored. This behavior breaks TLS decryption or prevent application layer PDUs (such as HTTP requests/responses) from being reconstructed. To fix this, buffer segments when a gap is detected. The proposed approach extends the "multi-segment PDU" (MSP) mechanism which is normally used for linking multiple, sequential TCP segments into a single PDU. When a gap is detected between segments, it is assumed that the segments within this gap are out-of-order and will be received (or retransmitted) later. The current implementation has a limitation though, if multiple gaps exist, then the subdissector will only be called when all gaps are filled (the subdissector will receive segments later than necessary). For example with "ACEBD", "ABC" can already be processed after "B" is received (with "E" still buffered), but due to how MSP are extended, it must receive "D" too before it reassembles "ABCDE". In practice this could mean that the request/response times between HTTP requests and responses are slightly off, but at least the stream is correct now. (These limitations are documented in the User's Guide.) As the feature fails at least the 802.11 decryption test where packets are missing (instead of OoO), hide this feature behind a preference. Tested with captures containing out-of-order TCP segments from the linked bug reports, comparing the effect of toggling the preference on the summary output of tshark, the verbose output (-V) and the two-pass output (-2 or -2V). Captures marked with "ok" just needed "simple" out-of-order handling. Captures marked with "ok2" additionally required the reassembly API change to set the correct reassembled length. This change does "regress" on bug 10289 though when the preference is enabled as retransmitted single-segment PDUs are now passed to subdissectors. I added a TODO comment for this unrelated cosmetic issue. Bug: 3389 # capture 2907 (HTTP) ok Bug: 4727 # capture 4590 (HTTP) ok Bug: 9461 # capture 12130 (TLS/HTTP/RPC-over-HTTP +key 12131) ok Bug: 12006 # capture 14236 (HTTP) ok2; capture 15261 (HTTP) ok Bug: 13517 # capture 15370 (HTTP) ok; capture 16059 (MQ) ok Bug: 13754 # capture 15593 (MySQL) ok2 Bug: 14649 # capture 16305 (WebSocket) ok Change-Id: If3938c5c1c96db8f7f50e39ea779f623ce657d56 Reviewed-on: https://code.wireshark.org/review/27943 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman <a.broman58@gmail.com>
-rw-r--r--docbook/release-notes.asciidoc6
-rw-r--r--docbook/wsug_src/WSUG_chapter_advanced.asciidoc63
-rw-r--r--epan/dissectors/packet-tcp.c165
-rw-r--r--epan/dissectors/packet-tcp.h9
-rw-r--r--epan/reassemble.c74
-rw-r--r--epan/reassemble.h12
-rw-r--r--test/captures/http-ooo.pcapbin0 -> 1209 bytes
-rw-r--r--test/suite_dissection.py50
8 files changed, 359 insertions, 20 deletions
diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc
index c9129ca317..379af9702b 100644
--- a/docbook/release-notes.asciidoc
+++ b/docbook/release-notes.asciidoc
@@ -39,12 +39,12 @@ Dumpcap might not quit if Wireshark or TShark crashes.
The following features are new (or have been significantly updated)
since version 2.6.0:
-* The membership operator now supports ranges, allowing display filters such as
- `tcp.port in {4430..4434}` to be expressed. See the User’s Guide, chapter
- _Building display filter expressions_ for details.
* TShark now supports the `-G elastic-mapping` option which generates an ElasticSearch mapping file.
* The “Capture Information” dialog has been added back (wsbuglink:12004[]).
* The Ethernet dissector no longer validates the frame check sequence (checksum) by default.
+* The TCP dissector gained a new “Reassemble out-of-order segments” preference
+ to fix dissection and decryption issues in case TCP segments are received
+ out-of-order. See the User’s Guide, chapter _TCP Reassembly_ for details.
=== Removed Features and Support
diff --git a/docbook/wsug_src/WSUG_chapter_advanced.asciidoc b/docbook/wsug_src/WSUG_chapter_advanced.asciidoc
index 25bb6ffb80..27b0d17145 100644
--- a/docbook/wsug_src/WSUG_chapter_advanced.asciidoc
+++ b/docbook/wsug_src/WSUG_chapter_advanced.asciidoc
@@ -357,6 +357,9 @@ capture file is first opened. Packets are processed in the order in
which they appear in the packet list. You can enable or disable this
feature via the “Analyze TCP sequence numbers” TCP dissector preference.
+For analysis of data or protocols layered on top of TCP (such as HTTP), see
+<<ChAdvReassemblyTcp>>.
+
.“TCP Analysis” packet detail items
image::wsug_graphics/ws-tcp-analysis.png[{screenshot-attrs}]
@@ -838,6 +841,66 @@ settings for a protocol typically requires two things:
The tooltip of the higher level protocol setting will notify you if and which
lower level protocol setting also has to be considered.
+[[ChAdvReassemblyTcp]]
+
+==== TCP Reassembly
+
+Protocols such as HTTP or TLS are likely to span multiple TCP segments. The
+TCP protocol preference “Allow subdissector to reassemble TCP streams” (enabled
+by default) makes it possible for Wireshark to collect a contiguous sequence of
+TCP segments and hand them over to the higher level protocol (for example, to
+reconstruct a full HTTP message). All but the final segment will be marked with
+“[TCP segment of a reassembled PDU]” in the packet list.
+
+Disable this preference to reduce memory and processing overhead if you are only
+interested in TCP sequence number analysis (<<ChAdvTCPAnalysis>>). Keep in mind,
+though, that higher level protocols might be wrongly dissected. For example,
+HTTP messages could be shown as “Continuation” and TLS records could be shown as
+“Ignored Unknown Record”. Such results can also be observed if you start
+capturing while a TCP connection was already started or when TCP segments
+are lost or delivered out-of-order.
+
+To reassemble of out-of-order TCP segments, the TCP protocol preference
+“Reassemble out-of-order segments” (currently disabled by default) must be
+enabled in addition to the previous preference.
+If all packets are received in-order, this preference will not have any effect.
+Otherwise (if missing segments are encountered while sequentially processing a
+packet capture), it is assumes that the new and missing segments belong to the
+same PDU. Caveats:
+
+* Lost packets are assumed to be received out-of-order or retransmitted later.
+ Applications usually retransmit segments until these are acknowledged, but if
+ the packet capture drops packets, then Wireshark will not be able to
+ reconstruct the TCP stream. In such cases, you can try to disable this
+ preference and hopefully have a partial dissection instead of seeing just
+ “[TCP segment of a reassembled PDU]” for every TCP segment.
+// See test/suite_decryption.py (suite_decryption.case_decrypt_80211)
+// which would break when enabling the preference.
+* When doing a capture in monitor mode (IEEE 802.11), packets are more likely to
+ get lost due to signal reception issues. In that case it is recommended to
+ disable the option.
+// See test/suite_dissection.py (case_dissect_tcp.check_tcp_out_of_order)
+* If the new and missing segments are in fact part of different PDUs,
+ then processing is currently delayed until no more segments are missing, even
+ if the begin of the missing segments completed a PDU. For example, assume six
+ segments forming two PDUs `ABC` and `DEF`. When received as `ABECDF`, an
+ application can start processing the first PDU after receiving `ABEC`.
+ Wireshark however requires the missing segment `D` to be received as well.
+ This issue will be addressed in the future.
+// See test/suite_dissection.py (case_dissect_tcp.test_tcp_out_of_order_twopass)
+* In the GUI and during a two-pass dissection (`tshark -2`), the previous
+ scenario will display both PDUs in the packet with last segment (`F`) rather
+ than displaying it in the first packet that has the final missing segment of a
+ PDU. This issue will be addressed in the future.
+* When enabled, fields such as the SMB “Time from request” (`smb.time`) might be
+ smaller if the request follows other out-of-order segments (this reflects
+ application behavior). If the previous scenario however occurs, then the time
+ of the request is based on the frame where all missing segments are received.
+
+Regardless of the setting of these two reassembly-related preferences, you can
+always use the “Follow TCP Stream” option (<<ChAdvFollowStreamSection>>) which
+displays segments in the expected order.
+
[[ChAdvNameResolutionSection]]
=== Name Resolution
diff --git a/epan/dissectors/packet-tcp.c b/epan/dissectors/packet-tcp.c
index f2b6563d32..fa3abaee6b 100644
--- a/epan/dissectors/packet-tcp.c
+++ b/epan/dissectors/packet-tcp.c
@@ -2981,6 +2981,37 @@ static reassembly_table tcp_reassembly_table;
/* Enable desegmenting of TCP streams */
static gboolean tcp_desegment = TRUE;
+/* Enable buffering of out-of-order TCP segments before passing it to a
+ * subdissector (depends on "tcp_desegment"). */
+static gboolean tcp_reassemble_out_of_order = FALSE;
+
+/* Returns true iff any gap exists in the segments associated with msp up to the
+ * given sequence number (it ignores any gaps after the sequence number). */
+static gboolean
+missing_segments(packet_info *pinfo, struct tcp_multisegment_pdu *msp, guint32 seq)
+{
+ fragment_head *fd_head;
+ guint32 frag_offset = seq - msp->seq;
+
+ if ((gint32)frag_offset <= 0) {
+ return FALSE;
+ }
+
+ fd_head = fragment_get(&tcp_reassembly_table, pinfo, msp->first_frame, NULL);
+ /* msp implies existence of fragments, this should never be NULL. */
+ DISSECTOR_ASSERT(fd_head);
+
+ /* Find length of contiguous fragments. */
+ guint32 max = 0;
+ for (fragment_item *frag = fd_head; frag; frag = frag->next) {
+ guint32 frag_end = frag->offset + frag->len;
+ if (frag->offset <= max && max < frag_end) {
+ max = frag_end;
+ }
+ }
+ return max < frag_offset;
+}
+
static void
desegment_tcp(tvbuff_t *tvb, packet_info *pinfo, int offset,
guint32 seq, guint32 nxtseq,
@@ -2999,6 +3030,7 @@ desegment_tcp(tvbuff_t *tvb, packet_info *pinfo, int offset,
proto_item *item;
struct tcp_multisegment_pdu *msp;
gboolean cleared_writable = col_get_writable(pinfo->cinfo, COL_PROTOCOL);
+ const gboolean reassemble_ooo = tcp_desegment && tcp_reassemble_out_of_order;
again:
ipfd_head = NULL;
@@ -3027,8 +3059,11 @@ again:
if (tcpd) {
/* Have we seen this PDU before (and is it the start of a multi-
* segment PDU)?
+ * Only shortcircuit here when the first segment of the MSP is known,
+ * and when this this first segment is not one to complete the MSP.
*/
- if ((msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32(tcpd->fwd->multisegment_pdus, seq))) {
+ if ((msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32(tcpd->fwd->multisegment_pdus, seq)) &&
+ !(msp->flags & MSP_FLAGS_MISSING_FIRST_SEGMENT) && msp->last_frame != pinfo->num) {
const char* str;
/* Yes. This could be because we've dissected this frame before
@@ -3071,6 +3106,18 @@ again:
/* The above code only finds retransmission if the PDU boundaries and the seq coincide I think
* If we have sequence analysis active use the TCP_A_RETRANSMISSION flag.
* XXXX Could the above code be improved?
+ * XXX the following check works great for filtering duplicate
+ * retransmissions, but could there be a case where it prevents
+ * "tcp_reassemble_out_of_order" from functioning due to skipping
+ * retransmission of a lost segment?
+ * If the latter is enabled, it could use use "maxnextseq" for ignoring
+ * retransmitted single-segment PDUs (that would require storing
+ * per-packet state (tcp_per_packet_data_t) to make it work for two-pass
+ * and random access dissection). Retransmitted segments that are part
+ * of a MSP should already be passed only once to subdissectors due to
+ * the "reassembled_in" check below.
+ * The following should also check for TCP_A_SPURIOUS_RETRANSMISSION to
+ * address bug 10289.
*/
if((tcpd->ta) && ((tcpd->ta->flags&TCP_A_RETRANSMISSION) == TCP_A_RETRANSMISSION)){
const char* str = "Retransmitted ";
@@ -3081,7 +3128,62 @@ again:
return;
}
/* Else, find the most previous PDU starting before this sequence number */
- msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32_le(tcpd->fwd->multisegment_pdus, seq-1);
+ if (!msp) {
+ msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32_le(tcpd->fwd->multisegment_pdus, seq-1);
+ }
+ }
+
+ if (reassemble_ooo && tcpd && !PINFO_FD_VISITED(pinfo)) {
+ /* If there is a gap between this segment and any previous ones (that
+ * is, seqno is larger than the maximum expected seqno), then it is
+ * possibly an out-of-order segment. The very first segment is expected
+ * to be in-order though (otherwise captures starting in midst of a
+ * connection would never be reassembled).
+ */
+ if (tcpd->fwd->maxnextseq) {
+ /* Segments may be missing due to packet loss (assume later
+ * retransmission) or out-of-order (assume it will appear later).
+ *
+ * Extend an unfinished MSP when (1) missing segments exist between
+ * the start of the previous, (2) unfinished MSP and new segment.
+ *
+ * Create a new MSP when no (1) previous MSP exists and (2) a gap is
+ * detected between the previous largest nxtseq and the new segment.
+ */
+ /* Whether a previous MSP exists with missing segments. */
+ gboolean has_unfinished_msp = msp && !(msp->flags & MSP_FLAGS_GOT_ALL_SEGMENTS);
+ /* Whether the new segment creates a new gap. */
+ gboolean has_gap = LT_SEQ(tcpd->fwd->maxnextseq, seq);
+
+ if (has_unfinished_msp && missing_segments(pinfo, msp, seq)) {
+ /* The last PDU is part of a MSP which still needed more data,
+ * extend it (if necessary) to cover the entire new segment.
+ */
+ if (LT_SEQ(msp->nxtpdu, nxtseq)) {
+ msp->nxtpdu = nxtseq;
+ }
+ } else if (!has_unfinished_msp && has_gap) {
+ /* Either the previous segment was a single PDU that did not
+ * belong to a MSP, or the previous MSP was completed and cannot
+ * be extended.
+ * Create a new one starting at the expected next position and
+ * extend it to the end of the new segment.
+ */
+ msp = pdu_store_sequencenumber_of_next_pdu(pinfo,
+ tcpd->fwd->maxnextseq, nxtseq,
+ tcpd->fwd->multisegment_pdus);
+
+ msp->flags |= MSP_FLAGS_MISSING_FIRST_SEGMENT;
+ }
+ /* Now that the MSP is updated or created, continue adding the
+ * segments to the MSP below. The subdissector will not be called as
+ * the MSP is not complete yet. */
+ }
+ if (tcpd->fwd->maxnextseq == 0 || LT_SEQ(tcpd->fwd->maxnextseq, nxtseq)) {
+ /* Update the maximum expected seqno if unknown or if the new
+ * segment succeeds previous segments. */
+ tcpd->fwd->maxnextseq = nxtseq;
+ }
}
if (msp && msp->seq <= seq && msp->nxtpdu > seq) {
@@ -3103,6 +3205,21 @@ again:
}
last_fragment_len = len;
+
+ if (reassemble_ooo) {
+ /*
+ * If the previous segment requested more data (setting
+ * FD_PARTIAL_REASSEMBLY as the next segment length is unknown), but
+ * subsequently an OoO segment was received (for an earlier hole),
+ * then "fragment_add" would truncate the reassembled PDU to the end
+ * of this OoO segment. To prevent that, explicitly specify the MSP
+ * length before calling "fragment_add".
+ */
+ fragment_reset_tot_len(&tcp_reassembly_table, pinfo,
+ msp->first_frame, NULL,
+ MAX(seq + len, msp->nxtpdu) - msp->seq);
+ }
+
ipfd_head = fragment_add(&tcp_reassembly_table, tvb, offset,
pinfo, msp->first_frame, NULL,
seq - msp->seq, len,
@@ -3123,6 +3240,19 @@ again:
msp->nxtpdu = nxtseq;
}
+ if (reassemble_ooo && !PINFO_FD_VISITED(pinfo)) {
+ /* If the first segment of the MSP was seen, remember it. */
+ if (msp->seq == seq) {
+ msp->flags &= ~MSP_FLAGS_MISSING_FIRST_SEGMENT;
+ }
+ /* Remember when all segments are ready to avoid subsequent
+ * out-of-order packets from extending this MSP. If a subsdissector
+ * needs more segments, the flag will be cleared below. */
+ if (ipfd_head) {
+ msp->flags |= MSP_FLAGS_GOT_ALL_SEGMENTS;
+ }
+ }
+
if( (msp->nxtpdu < nxtseq)
&& (msp->nxtpdu >= seq)
&& (len > 0)) {
@@ -3214,6 +3344,10 @@ again:
* desegmented, or does it think we need even more
* data?
*/
+ if (reassemble_ooo && !PINFO_FD_VISITED(pinfo) && pinfo->desegment_len) {
+ /* "desegment_len" isn't 0, so it needs more data to extend the MSP. */
+ msp->flags &= ~MSP_FLAGS_GOT_ALL_SEGMENTS;
+ }
old_len = (int)(tvb_reported_length(next_tvb) - last_fragment_len);
if (pinfo->desegment_len &&
pinfo->desegment_offset<=old_len) {
@@ -3247,13 +3381,24 @@ again:
* This means that the next segment
* will complete reassembly even if it
* is only one single byte in length.
+ * If this is an OoO segment, then increment the MSP end.
*/
- msp->nxtpdu = seq + tvb_reported_length_remaining(tvb, offset) + 1;
+ msp->nxtpdu = MAX(seq + tvb_reported_length_remaining(tvb, offset), msp->nxtpdu) + 1;
msp->flags |= MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT;
} else if (pinfo->desegment_len == DESEGMENT_UNTIL_FIN) {
tcpd->fwd->flags |= TCP_FLOW_REASSEMBLE_UNTIL_FIN;
} else {
- msp->nxtpdu=seq + last_fragment_len + pinfo->desegment_len;
+ if (seq + last_fragment_len >= msp->nxtpdu) {
+ /* This is the segment (overlapping) the end of the MSP. */
+ msp->nxtpdu = seq + last_fragment_len + pinfo->desegment_len;
+ } else {
+ /* This is a segment before the end of the MSP, so it
+ * must be an out-of-order segmented that completed the
+ * MSP. The requested additional data is relative to
+ * that end.
+ */
+ msp->nxtpdu += pinfo->desegment_len;
+ }
}
/* Since we need at least some more data
@@ -5507,10 +5652,15 @@ decode_tcp_ports(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
}
- if (tcp_no_subdissector_on_error && tcpd && tcpd->ta && tcpd->ta->flags & (TCP_A_RETRANSMISSION | TCP_A_OUT_OF_ORDER)) {
+ if (tcp_no_subdissector_on_error && !(tcp_desegment && tcp_reassemble_out_of_order) &&
+ tcpd && tcpd->ta && tcpd->ta->flags & (TCP_A_RETRANSMISSION | TCP_A_OUT_OF_ORDER)) {
/* Don't try to dissect a retransmission high chance that it will mess
* subdissectors for protocols that require in-order delivery of the
* PDUs. (i.e. DCE/RPCoverHTTP and encryption)
+ * If OoO reassembly is enabled and if this segment was previously lost,
+ * then this retransmission could have finished reassembly, so continue.
+ * XXX should this option be removed? "tcp_reassemble_out_of_order"
+ * should have addressed the above in-order requirement.
*/
return FALSE;
}
@@ -7520,6 +7670,11 @@ proto_register_tcp(void)
"Allow subdissector to reassemble TCP streams",
"Whether subdissector can request TCP streams to be reassembled",
&tcp_desegment);
+ prefs_register_bool_preference(tcp_module, "reassemble_out_of_order",
+ "Reassemble out-of-order segments",
+ "Whether out-of-order segments should be buffered and reordered before passing it to a subdissector. "
+ "To use this option you must also enable \"Allow subdissector to reassemble TCP streams\".",
+ &tcp_reassemble_out_of_order);
prefs_register_bool_preference(tcp_module, "analyze_sequence_numbers",
"Analyze TCP sequence numbers",
"Make the TCP dissector analyze TCP sequence numbers to find and flag segment retransmissions, missing segments and RTT",
diff --git a/epan/dissectors/packet-tcp.h b/epan/dissectors/packet-tcp.h
index 3d2530fa6c..7f84351ade 100644
--- a/epan/dissectors/packet-tcp.h
+++ b/epan/dissectors/packet-tcp.h
@@ -175,6 +175,10 @@ struct tcp_multisegment_pdu {
nstime_t last_frame_time;
guint32 flags;
#define MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT 0x00000001
+/* Whether this MSP is finished and no more segments can be added. */
+#define MSP_FLAGS_GOT_ALL_SEGMENTS 0x00000002
+/* Whether the first segment of this MSP was not yet seen. */
+#define MSP_FLAGS_MISSING_FIRST_SEGMENT 0x00000004
};
@@ -335,6 +339,11 @@ typedef struct _tcp_flow_t {
/* see TCP_A_* in packet-tcp.c */
guint32 lastsegmentflags;
+ /* The next (largest) sequence number after all segments seen so far.
+ * Valid only on the first pass and used to handle out-of-order segments
+ * during reassembly. */
+ guint32 maxnextseq;
+
/* This tree is indexed by sequence number and keeps track of all
* all pdus spanning multiple segments for this flow.
*/
diff --git a/epan/reassemble.c b/epan/reassemble.c
index 02ba1a2db4..b3ceef915d 100644
--- a/epan/reassemble.c
+++ b/epan/reassemble.c
@@ -680,6 +680,33 @@ fragment_add_seq_offset(reassembly_table *table, const packet_info *pinfo, const
fd_head->fragment_nr_offset = fragment_offset;
}
+/*
+ * 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
@@ -736,6 +763,39 @@ fragment_set_tot_len(reassembly_table *table, const packet_info *pinfo,
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;
+}
+
guint32
fragment_get_tot_len(reassembly_table *table, const packet_info *pinfo,
const guint32 id, const void *data)
@@ -907,6 +967,7 @@ MERGE_FRAG(fragment_head *fd_head, fragment_item *fd)
}
fd_i->next = fd;
}
+
/*
* This function adds a new fragment to the fragment hash table.
* If this is the first fragment seen for this datagram, a new entry
@@ -970,18 +1031,7 @@ fragment_add_work(fragment_head *fd_head, tvbuff_t *tvb, const int offset,
* Yes. Set flag in already empty fds &
* point old fds to malloc'ed data.
*/
- for(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;
+ fragment_reset_defragmentation(fd_head);
} else {
/*
* No. Bail out since we have no idea what to
diff --git a/epan/reassemble.h b/epan/reassemble.h
index 449d336b19..3c234e8ab1 100644
--- a/epan/reassemble.h
+++ b/epan/reassemble.h
@@ -365,6 +365,18 @@ fragment_set_tot_len(reassembly_table *table, const packet_info *pinfo,
const guint32 id, const void *data, const guint32 tot_len);
/*
+ * Similar to fragment_set_tot_len, it sets the expected number of bytes (for
+ * fragment_add functions) for a previously started reassembly. If the specified
+ * length already matches the reassembled length, then nothing will be done.
+ *
+ * If the fragments were previously reassembled, then this state will be
+ * cleared, allowing new fragments to extend the reassembled result again.
+ */
+void
+fragment_reset_tot_len(reassembly_table *table, const packet_info *pinfo,
+ const guint32 id, const void *data, const guint32 tot_len);
+
+/*
* Return the expected index for the last block (for fragment_add_seq functions)
* or the expected number of bytes (for fragment_add functions).
*/
diff --git a/test/captures/http-ooo.pcap b/test/captures/http-ooo.pcap
new file mode 100644
index 0000000000..be0b5d2f88
--- /dev/null
+++ b/test/captures/http-ooo.pcap
Binary files differ
diff --git a/test/suite_dissection.py b/test/suite_dissection.py
index b81d048b53..cfec7c4c66 100644
--- a/test/suite_dissection.py
+++ b/test/suite_dissection.py
@@ -29,3 +29,53 @@ class case_dissect_http2(subprocesstest.SubprocessTestCase):
),
env=config.test_env)
self.assertTrue(self.grepOutput('DATA'))
+
+class case_dissect_tcp(subprocesstest.SubprocessTestCase):
+ def check_tcp_out_of_order(self, extraArgs=[]):
+ capture_file = os.path.join(config.capture_dir, 'http-ooo.pcap')
+ self.runProcess([config.cmd_tshark,
+ '-r', capture_file,
+ '-otcp.reassemble_out_of_order:TRUE',
+ '-Y', 'http',
+ ] + extraArgs,
+ env=config.test_env)
+ self.assertEqual(self.countOutput('HTTP'), 5)
+ # TODO PDU /1 (segments in frames 1, 2, 4) should be reassembled in
+ # frame 4, but it is currently done in frame 6 because the current
+ # implementation reassembles only contiguous segments and PDU /2 has
+ # segments in frames 6, 3, 7.
+ self.assertTrue(self.grepOutput(r'^\s*6\s.*PUT /1 HTTP/1.1'))
+ self.assertTrue(self.grepOutput(r'^\s*7\s.*GET /2 HTTP/1.1'))
+ self.assertTrue(self.grepOutput(r'^\s*10\s.*PUT /3 HTTP/1.1'))
+ self.assertTrue(self.grepOutput(r'^\s*11\s.*PUT /4 HTTP/1.1'))
+ self.assertTrue(self.grepOutput(r'^\s*15\s.*PUT /5 HTTP/1.1'))
+
+ def test_tcp_out_of_order_onepass(self):
+ self.check_tcp_out_of_order()
+
+ @unittest.skip("MSP splitting is not implemented yet")
+ def test_tcp_out_of_order_twopass(self):
+ self.check_tcp_out_of_order(extraArgs=['-2'])
+
+ def test_tcp_out_of_order_twopass_with_bug(self):
+ # TODO fix the issue below, remove this and enable
+ # "test_tcp_out_of_order_twopass"
+ capture_file = os.path.join(config.capture_dir, 'http-ooo.pcap')
+ self.runProcess((config.cmd_tshark,
+ '-r', capture_file,
+ '-otcp.reassemble_out_of_order:TRUE',
+ '-Y', 'http',
+ '-2',
+ ),
+ env=config.test_env)
+ self.assertEqual(self.countOutput('HTTP'), 3)
+ self.assertTrue(self.grepOutput(r'^\s*7\s.*PUT /1 HTTP/1.1'))
+ self.assertTrue(self.grepOutput(r'^\s*7\s.*GET /2 HTTP/1.1'))
+ # TODO ideally this should not be concatenated.
+ # Normally a multi-segment PDU (MSP) covers only a single PDU, but OoO
+ # segments can extend MSP such that it covers two (or even more) PDUs.
+ # Until MSP splitting is implemented, two PDUs are shown in a single
+ # packet (and in case of -2, they are only shown in the last packet).
+ self.assertTrue(self.grepOutput(r'^\s*11\s.*PUT /3 HTTP/1.1'))
+ self.assertTrue(self.grepOutput(r'^\s*11\s.*PUT /4 HTTP/1.1'))
+ self.assertTrue(self.grepOutput(r'^\s*15\s.*PUT /5 HTTP/1.1'))