summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Doyle <ryan@doylenet.net>2017-03-27 21:48:26 +1100
committerAnders Broman <a.broman58@gmail.com>2017-06-22 16:22:59 +0000
commitf24ffb0bcd12c3dce1e48b7a34c48a6be62c45e4 (patch)
treedfdced4d009cadc87546597fdccf954cfc66d289
parentcfb23d874345eede2860afa155b181a2e2e208c0 (diff)
downloadwireshark-f24ffb0bcd12c3dce1e48b7a34c48a6be62c45e4.tar.gz
http2: reassemble entity bodies in data frames
This commit reassembles data frames to build up the full entity body. It does this for both client/server request and responses. Additionally, it also decompresses bodies if they have the correct content-encoding header provided and are not partial bodies. Bug: 13543 Change-Id: I1661c9ddd09c1f6cf5a08b2b1921f95103aebb52 Reviewed-on: https://code.wireshark.org/review/20737 Petri-Dish: Anders Broman <a.broman58@gmail.com> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Anders Broman <a.broman58@gmail.com>
-rw-r--r--epan/dissectors/packet-http2.c516
-rw-r--r--test/captures/http2-data-reassembly.pcapbin0 -> 82151 bytes
-rw-r--r--test/keys/http2-data-reassembly.keys1
-rwxr-xr-xtest/suite-dissection.sh60
-rwxr-xr-xtest/test.sh6
5 files changed, 580 insertions, 3 deletions
diff --git a/epan/dissectors/packet-http2.c b/epan/dissectors/packet-http2.c
index 3cb85548ff..e70607a8bc 100644
--- a/epan/dissectors/packet-http2.c
+++ b/epan/dissectors/packet-http2.c
@@ -51,6 +51,7 @@
#include "packet-tcp.h"
#include <epan/tap.h>
#include <epan/stats_tree.h>
+#include <epan/reassemble.h>
#include "wsutil/pint.h"
@@ -68,6 +69,15 @@
VALUE_STRING_ENUM(http2_header_repr_type);
VALUE_STRING_ARRAY(http2_header_repr_type);
+
+/*
+ * Decompression of zlib encoded entities.
+ */
+#ifdef HAVE_ZLIB
+static gboolean http2_decompress_body = TRUE;
+#else
+static gboolean http2_decompress_body = FALSE;
+#endif
#endif
/* Decompressed header field */
@@ -129,6 +139,47 @@ typedef struct {
int has_header_table_size;
} http2_settings_t;
+#ifdef HAVE_NGHTTP2
+typedef guint64 http2_frame_num_t;
+/* struct for per-stream, per-direction DATA frame reassembly */
+typedef struct {
+ http2_frame_num_t data_initiated_in;
+ gboolean has_transfer_encoded_body;
+} http2_data_stream_reassembly_info_t;
+
+/* struct for per-stream, per-direction entity body info */
+typedef struct {
+ gchar *content_encoding;
+ gboolean is_partial_content;
+} http2_data_stream_body_info_t;
+
+/* struct to track header state, so we know if continuation frames are part
+ * of a HEADERS frame or a PUSH_PROMISE. Note: does not take into account
+ * trailing headers */
+typedef struct {
+ http2_frame_num_t header_start_in;
+ http2_frame_num_t header_end_in;
+} http2_header_stream_info_t;
+
+/* struct to reference uni-directional per-stream info */
+typedef struct {
+ http2_data_stream_body_info_t data_stream_body_info;
+ http2_data_stream_reassembly_info_t data_stream_reassembly_info;
+ http2_header_stream_info_t header_stream_info;
+} http2_oneway_stream_info_t;
+
+/* struct to hold per-stream information for both directions */
+typedef struct {
+ /* index into http2_oneway_stream_info_t struct is based off
+ * http2_session_t.fwd_flow, available by calling select_http2_flow_index().
+ * The index could be for either client or server, depending on when
+ * the capture is started but the index will be consistent for the lifetime
+ * of the http2_session_t */
+ http2_oneway_stream_info_t oneway_stream_info[2];
+ gboolean is_stream_http_connect;
+ guint32 stream_id;
+} http2_stream_info_t;
+#endif
/* struct to hold data per HTTP/2 session */
typedef struct {
/* We need to distinguish the direction of the flow to keep track
@@ -145,6 +196,8 @@ typedef struct {
#ifdef HAVE_NGHTTP2
nghttp2_hd_inflater *hd_inflater[2];
http2_header_repr_info_t header_repr_info[2];
+ wmem_map_t *per_stream_info;
+ guint32 current_stream_id;
#endif
tcp_flow_t *fwd_flow;
} http2_session_t;
@@ -200,6 +253,16 @@ static int hf_http2_excl_dependency = -1;
/* Data */
static int hf_http2_data_data = -1;
static int hf_http2_data_padding = -1;
+static int hf_http2_body_fragments = -1;
+static int hf_http2_body_fragment = -1;
+static int hf_http2_body_fragment_overlap = -1;
+static int hf_http2_body_fragment_overlap_conflicts = -1;
+static int hf_http2_body_fragment_multiple_tails = -1;
+static int hf_http2_body_fragment_too_long_fragment = -1;
+static int hf_http2_body_fragment_error = -1;
+static int hf_http2_body_fragment_count = -1;
+static int hf_http2_body_reassembled_in = -1;
+static int hf_http2_body_reassembled_length = -1;
/* Headers */
static int hf_http2_headers = -1;
static int hf_http2_headers_padding = -1;
@@ -269,12 +332,35 @@ static int hf_http2_altsvc_field_value = -1;
#define MAX_HTTP2_HEADER_LINES 200
static expert_field ei_http2_header_size = EI_INIT;
static expert_field ei_http2_header_lines = EI_INIT;
+static expert_field ei_http2_body_decompression_failed = EI_INIT;
static gint ett_http2 = -1;
static gint ett_http2_header = -1;
static gint ett_http2_headers = -1;
static gint ett_http2_flags = -1;
static gint ett_http2_settings = -1;
+static gint ett_http2_encoded_entity = -1;
+static gint ett_http2_body_fragment = -1;
+static gint ett_http2_body_fragments = -1;
+
+static const fragment_items http2_body_fragment_items = {
+ /* Fragment subtrees */
+ &ett_http2_body_fragment,
+ &ett_http2_body_fragments,
+ /* Fragment fields */
+ &hf_http2_body_fragments,
+ &hf_http2_body_fragment,
+ &hf_http2_body_fragment_overlap,
+ &hf_http2_body_fragment_overlap_conflicts,
+ &hf_http2_body_fragment_multiple_tails,
+ &hf_http2_body_fragment_too_long_fragment,
+ &hf_http2_body_fragment_error,
+ &hf_http2_body_fragment_count,
+ &hf_http2_body_reassembled_in,
+ &hf_http2_body_reassembled_length,
+ NULL,
+ "Body fragments"
+};
#ifdef HAVE_NGHTTP2
/* Due to HPACK compression, we may get lots of relatively large
@@ -291,6 +377,8 @@ static char *http2_header_pstr = NULL;
static dissector_handle_t http2_handle;
+static reassembly_table http2_body_reassembly_table;
+
#define FRAME_HEADER_LENGTH 9
#define MAGIC_FRAME_LENGTH 24
#define MASK_HTTP2_RESERVED 0x80000000
@@ -352,6 +440,17 @@ static const value_string http2_type_vals[] = {
#define HTTP2_FLAGS_R2 0xFA
#define HTTP2_FLAGS_R4 0xFB
+/* http header keys and values */
+#define HTTP2_HEADER_CONTENT_ENCODING "content-encoding"
+#define HTTP2_HEADER_STATUS ":status"
+#define HTTP2_HEADER_STATUS_PARTIAL_CONTENT "206"
+#define HTTP2_HEADER_METHOD ":method"
+#define HTTP2_HEADER_METHOD_CONNECT "CONNECT"
+#define HTTP2_HEADER_TRANSFER_ENCODING "transfer-encoding"
+
+/* header matching helpers */
+#define IS_HTTP2_END_STREAM(flags) (flags & HTTP2_FLAGS_END_STREAM)
+
/* Magic Header : PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n */
static guint8 kMagicHello[] = {
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
@@ -422,6 +521,22 @@ hd_inflate_del_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, vo
return FALSE;
}
+
+static http2_stream_info_t*
+get_stream_info(http2_session_t *http2_session)
+{
+ guint32 stream_id = http2_session->current_stream_id;
+ wmem_map_t *stream_map = http2_session->per_stream_info;
+
+ http2_stream_info_t *stream_info = (http2_stream_info_t *)wmem_map_lookup(stream_map, GINT_TO_POINTER(stream_id));
+ if (stream_info == NULL) {
+ stream_info = wmem_new0(wmem_file_scope(), http2_stream_info_t);
+ stream_info->stream_id = stream_id;
+ wmem_map_insert(stream_map, GINT_TO_POINTER(stream_id), stream_info);
+ }
+
+ return stream_info;
+}
#endif
static http2_session_t*
@@ -450,6 +565,9 @@ get_http2_session(packet_info *pinfo)
h2session->hd_inflater[0]);
wmem_register_callback(wmem_file_scope(), hd_inflate_del_cb,
h2session->hd_inflater[1]);
+ h2session->per_stream_info = wmem_map_new(wmem_file_scope(),
+ g_direct_hash,
+ g_direct_equal);
#endif
h2session->fwd_flow = tcpd->fwd;
@@ -477,6 +595,58 @@ select_http2_flow_index(packet_info *pinfo, http2_session_t *h2session)
}
}
+static http2_frame_num_t
+get_http2_frame_num(tvbuff_t *tvb, packet_info *pinfo)
+{
+ /* HTTP2 frames are identified as follows:
+ *
+ * +--- 32 bits ---+--------- 8 bits -------+----- 24 bits -----+
+ * | pinfo->num | pinfo->curr_layer_num | tvb->raw_offset |
+ * +------------------------------------------------------------+
+ *
+ * This allows for a single HTTP2 frame to be uniquely identified across a capture with the
+ * added benefit that the number will always be increasing from the previous HTTP2 frame so
+ * we can use "<" and ">" comparisons to determine before and after in time.
+ *
+ * pinfo->curr_layer_num is used to deliberate when we have multiple TLS records in a
+ * single (non-http2) frame. This ends up being dissected using two separate TVBs
+ * (so tvb->raw_offset isn't useful) and then end up being the same pinfo->num.
+ *
+ * I have seen instances where the pinfo->curr_layer_num can change between the first and second
+ * pass of a packet so this needs to be taken into account when this is used as an identifier.
+ */
+ return (((guint64)pinfo->num) << 32) + (((guint64)pinfo->curr_layer_num) << 24) + ((guint64)tvb_raw_offset(tvb));
+}
+
+static http2_oneway_stream_info_t*
+get_oneway_stream_info(packet_info *pinfo)
+{
+ http2_session_t *http2_session = get_http2_session(pinfo);
+ http2_stream_info_t *http2_stream_info = get_stream_info(http2_session);
+ int flow_index = select_http2_flow_index(pinfo, http2_session);
+
+ return &http2_stream_info->oneway_stream_info[flow_index];
+}
+
+static http2_data_stream_body_info_t*
+get_data_stream_body_info(packet_info *pinfo)
+{
+ return &(get_oneway_stream_info(pinfo)->data_stream_body_info);
+}
+
+
+static http2_data_stream_reassembly_info_t*
+get_data_reassembly_info(packet_info *pinfo)
+{
+ return &(get_oneway_stream_info(pinfo)->data_stream_reassembly_info);
+}
+
+static http2_header_stream_info_t*
+get_header_stream_info(packet_info *pinfo)
+{
+ return &(get_oneway_stream_info(pinfo)->header_stream_info);
+}
+
static void
push_settings(packet_info *pinfo, http2_session_t *h2session,
http2_settings_t *settings)
@@ -686,6 +856,62 @@ static gboolean http2_hdrcache_equal(gconstpointer lhs, gconstpointer rhs)
return alen == blen && memcmp(a, b, alen) == 0;
}
+static int
+is_in_header_context(tvbuff_t *tvb, packet_info *pinfo)
+{
+ http2_header_stream_info_t *stream_info = get_header_stream_info(pinfo);
+ if (get_http2_frame_num(tvb, pinfo) >= stream_info->header_start_in) {
+ /* We either haven't established the frame that the headers end in so we are currently in the HEADERS context,
+ * or if we have, it should be equal or less that the current frame number */
+ if (stream_info->header_end_in == 0 || get_http2_frame_num(tvb, pinfo) <= stream_info->header_end_in) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+populate_http_header_tracking(tvbuff_t *tvb, packet_info *pinfo, http2_session_t *h2session, int header_value_length,
+ const gchar *header_name, const gchar *header_value)
+{
+ /* Populate the content encoding used so we can uncompress the body later if required */
+ if (strcmp(header_name, HTTP2_HEADER_CONTENT_ENCODING) == 0) {
+ http2_data_stream_body_info_t *body_info = get_data_stream_body_info(pinfo);
+ if (body_info->content_encoding == NULL) {
+ body_info->content_encoding = wmem_strndup(wmem_file_scope(), header_value, header_value_length);
+ }
+ }
+
+ /* Is this a partial content? */
+ if (strcmp(header_name, HTTP2_HEADER_STATUS) == 0 &&
+ strcmp(header_value, HTTP2_HEADER_STATUS_PARTIAL_CONTENT) == 0) {
+ http2_data_stream_body_info_t *body_info = get_data_stream_body_info(pinfo);
+ body_info->is_partial_content = TRUE;
+ }
+
+ /* Was this header used to initiate transfer of data frames? We'll use this later for reassembly */
+ if (strcmp(header_name, HTTP2_HEADER_STATUS) == 0 ||
+ strcmp(header_name, HTTP2_HEADER_METHOD) == 0) {
+ http2_data_stream_reassembly_info_t *reassembly_info = get_data_reassembly_info(pinfo);
+ if (reassembly_info->data_initiated_in == 0) {
+ reassembly_info->data_initiated_in = get_http2_frame_num(tvb, pinfo);
+ }
+ }
+
+ /* Do we have transfer encoding of bodies? We don't support reassembling these so mark it as such. */
+ if (strcmp(header_name, HTTP2_HEADER_TRANSFER_ENCODING) == 0) {
+ http2_data_stream_reassembly_info_t *reassembly_info = get_data_reassembly_info(pinfo);
+ reassembly_info->has_transfer_encoded_body = TRUE;
+ }
+
+ /* Store away if the stream is associated with a CONNECT request */
+ if (strcmp(header_name, HTTP2_HEADER_METHOD) == 0 &&
+ strcmp(header_value, HTTP2_HEADER_METHOD_CONNECT) == 0) {
+ http2_stream_info_t *stream_info = get_stream_info(h2session);
+ stream_info->is_stream_http_connect = TRUE;
+ }
+}
+
static void
inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
proto_tree *tree, size_t headlen,
@@ -920,6 +1146,13 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
proto_tree_add_string(header_tree, hf_http2_header_value, tvb, offset, in->length, header_value);
hoffset += header_value_length;
+ /* Only track HEADER and CONTINUATION frames part there of. Don't look at PUSH_PROMISE and trailing CONTINUATION.
+ * Only do it for the first pass in case the current layer changes, altering where the headers frame number,
+ * http2_frame_num_t points to. */
+ if (is_in_header_context(tvb, pinfo) && !PINFO_FD_VISITED(pinfo)) {
+ populate_http_header_tracking(tvb, pinfo, h2session, header_value_length, header_name, header_value);
+ }
+
/* Add encoding representation */
proto_tree_add_string(header_tree, hf_http2_header_repr, tvb, offset, in->length, http2_header_repr_type[in->type].strptr);
@@ -1041,10 +1274,186 @@ dissect_frame_prio(tvbuff_t *tvb, proto_tree *http2_tree, guint offset, guint8 f
return offset;
}
+#ifdef HAVE_NGHTTP2
+static int
+can_uncompress_body(packet_info *pinfo)
+{
+ http2_data_stream_body_info_t *body_info = get_data_stream_body_info(pinfo);
+ gchar *content_encoding = body_info->content_encoding;
+
+ /* Check we have a content-encoding header appropriate as well as checking if this is partial content.
+ * We can't decompress part of a gzip encoded entity */
+ return http2_decompress_body
+ && body_info->is_partial_content == FALSE
+ && content_encoding != NULL
+ && (strncmp(content_encoding, "gzip", 4) == 0 || strncmp(content_encoding, "deflate", 7) == 0);
+}
+
+
+static void
+dissect_http2_data_full_body(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http2_tree)
+{
+ if (!tvb) {
+ return;
+ }
+
+ gint datalen = tvb_reported_length(tvb);
+
+ if (can_uncompress_body(pinfo)) {
+ proto_item *compressed_proto_item = NULL;
+ tvbuff_t *uncompressed_tvb = tvb_child_uncompress(tvb, tvb, 0, datalen);
+ http2_data_stream_body_info_t *body_info = get_data_stream_body_info(pinfo);
+ gchar *compression_method = body_info->content_encoding;
+
+ proto_tree *compressed_entity_tree = proto_tree_add_subtree_format(http2_tree, tvb, 0, datalen, ett_http2_encoded_entity,
+ &compressed_proto_item, "Content-encoded entity body (%s): %u bytes",
+ compression_method == NULL ? "unknown" : compression_method, datalen
+ );
+
+ if (uncompressed_tvb != NULL) {
+ guint uncompressed_length = tvb_captured_length(uncompressed_tvb);
+ add_new_data_source(pinfo, uncompressed_tvb, "Uncompressed entity body");
+ proto_item_append_text(compressed_proto_item, " -> %u bytes", uncompressed_length);
+ proto_tree_add_item(compressed_entity_tree, hf_http2_data_data, uncompressed_tvb, 0, uncompressed_length, ENC_NA);
+
+ } else {
+ proto_tree_add_expert(compressed_entity_tree, pinfo, &ei_http2_body_decompression_failed, tvb, 0, datalen);
+ proto_tree_add_item(compressed_entity_tree, hf_http2_data_data, tvb, 0, datalen, ENC_NA);
+ }
+ } else {
+ proto_tree_add_item(http2_tree, hf_http2_data_data, tvb, 0, datalen, ENC_NA);
+ }
+
+}
+
+static int
+should_attempt_to_reassemble_data_frame(http2_data_stream_reassembly_info_t *reassembly, packet_info *pinfo)
+{
+ /* If we haven't captured the header frame with the request/response we don't know how many data
+ * frames we might have lost before processing */
+ if (reassembly->data_initiated_in == 0) {
+ return FALSE;
+ }
+
+ /* For now, do not reassemble transfer encoded bodies. Chunked encoding is explicitly disallowed by RFC7540,
+ * section 8.1. Additionally, section 8.1.2.2 specifies that the only valid value for the TE header (indicating
+ * which transfer-encoding is allowed) is trailers, suggesting transfer coding other than chunked (gzip,
+ * deflate, etc) are not allowed */
+ if (reassembly->has_transfer_encoded_body) {
+ return FALSE;
+ }
+
+ /* Is this data frame part of an established tunnel? Don't try to reassemble the data if that is the case */
+ http2_stream_info_t *stream_info = get_stream_info(get_http2_session(pinfo));
+ if (stream_info->is_stream_http_connect) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static guint32
+get_reassembly_id_from_stream(packet_info *pinfo)
+{
+ http2_session_t *session = get_http2_session(pinfo);
+ http2_stream_info_t *stream_info = get_stream_info(session);
+ int flow_index = select_http2_flow_index(pinfo, session);
+
+ /* With a stream ID being 31 bits, use the most significant bit to determine the flow direction of the
+ * stream. We use this for the ID in the body reassembly using the reassemble API */
+ return stream_info->stream_id | (flow_index << 31);
+}
+
+static tvbuff_t*
+reassemble_http2_data_into_full_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http2_tree, guint offset,
+ guint8 flags, guint datalen)
+{
+ http2_data_stream_reassembly_info_t *reassembly = get_data_reassembly_info(pinfo);
+
+ /* There are a number of conditions as to why we may not want to reassemble DATA frames */
+ if (!should_attempt_to_reassemble_data_frame(reassembly, pinfo)) {
+ return NULL;
+ }
+
+ /* Continue to add fragments, checking if we have any more fragments */
+ guint32 reassembly_id = get_reassembly_id_from_stream(pinfo);
+ fragment_head *head = NULL;
+ if (IS_HTTP2_END_STREAM(flags) && datalen == 0) {
+ /* Workaround displaying "[Frame: N (no data)]" for a HTTP2 frame that contains no data but ends the stream */
+ head = fragment_end_seq_next(&http2_body_reassembly_table, pinfo, reassembly_id, NULL);
+ } else {
+ head = fragment_add_seq_next(&http2_body_reassembly_table, tvb, offset, pinfo, reassembly_id, NULL,
+ datalen, !IS_HTTP2_END_STREAM(flags));
+ }
+
+ /* Only call this if its the last DATA frame (END_STREAM) as the check in process_reassembled_data() will
+ * incorrectly match for frames that exist in the same packet as the final DATA frame and incorrectly add
+ * reassembly information to those dissection trees */
+ if (head && IS_HTTP2_END_STREAM(flags)) {
+ return process_reassembled_data(tvb, offset, pinfo, "Reassembled body", head,
+ &http2_body_fragment_items, NULL, http2_tree);
+ }
+
+ /* Add frame where reassembly happened. process_reassembled_data() does this automatically if the reassembled
+ * packet matches the packet that is calling the function, but makes some incorrect assumptions for multiple
+ * fragments contained in the same packet */
+ if (head) {
+ proto_tree_add_uint(http2_tree, hf_http2_body_reassembled_in, tvb, 0, 0,
+ head->reassembled_in);
+ }
+
+ /* Reassembly not complete yet*/
+ return NULL;
+}
+
+static void
+dissect_http2_data_partial_body(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http2_tree, guint offset, gint length,
+ guint8 flags)
+{
+ http2_data_stream_reassembly_info_t *reassembly = get_data_reassembly_info(pinfo);
+
+ /* Is the frame part of a body that is going to be reassembled? */
+ if(!IS_HTTP2_END_STREAM(flags)) {
+ proto_item_append_text(http2_tree, " (partial entity body)");
+ }
+
+ /* If we somehow got a transfer-encoded body, display it here */
+ if (reassembly->has_transfer_encoded_body) {
+ proto_item_append_text(http2_tree, " (transfer-encoded body)");
+ }
+
+ /* Is this part of a tunneled connection? */
+ http2_stream_info_t *stream_info = get_stream_info(get_http2_session(pinfo));
+ if (stream_info->is_stream_http_connect) {
+ proto_item_append_text(http2_tree, " (tunneled data)");
+ }
+
+ proto_tree_add_item(http2_tree, hf_http2_data_data, tvb, offset, length, ENC_NA);
+}
+
+static void
+dissect_http2_data_body(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http2_tree, guint offset, guint8 flags, gint length)
+{
+ tvbuff_t *data_tvb = reassemble_http2_data_into_full_frame(tvb, pinfo, http2_tree, offset, flags, length);
+
+ if (data_tvb != NULL) {
+ dissect_http2_data_full_body(data_tvb, pinfo, http2_tree);
+ } else {
+ dissect_http2_data_partial_body(tvb, pinfo, http2_tree, offset, length, flags);
+ }
+}
+
+#else
+static void
+dissect_http2_data_body(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 flags _U_, gint datalen)
+{
+ proto_tree_add_item(http2_tree, hf_http2_data_data, tvb, offset, datalen, ENC_NA);
+}
+#endif
/* Data (0) */
static int
-dissect_http2_data(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree,
+dissect_http2_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http2_tree,
guint offset, guint8 flags)
{
guint16 padding;
@@ -1052,7 +1461,9 @@ dissect_http2_data(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree
offset = dissect_frame_padding(tvb, &padding, http2_tree, offset, flags);
datalen = tvb_reported_length_remaining(tvb, offset) - padding;
- proto_tree_add_item(http2_tree, hf_http2_data_data, tvb, offset, datalen, ENC_NA);
+
+ dissect_http2_data_body(tvb, pinfo, http2_tree, offset, flags, datalen);
+
offset += datalen;
if (padding) {
@@ -1079,6 +1490,28 @@ dissect_http2_headers(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_t
http2_session_t *h2session;
h2session = get_http2_session(pinfo);
+
+ /* Trailing headers coming after a DATA stream should have END_STREAM set. DATA should be complete
+ * so try to reassemble DATA fragments if that is the case */
+ if(IS_HTTP2_END_STREAM(flags) ) {
+ fragment_head *head = fragment_end_seq_next(&http2_body_reassembly_table, pinfo, get_reassembly_id_from_stream(pinfo), NULL);
+ if(head) {
+ tvbuff_t *reassembled_data = process_reassembled_data(tvb, 0, pinfo, "Reassembled body", head,
+ &http2_body_fragment_items, NULL, http2_tree);
+ dissect_http2_data_full_body(reassembled_data, pinfo, http2_tree);
+ }
+ }
+
+ /* Mark this frame as the first header frame seen and last if the END_HEADERS flag
+ * is set. We use this to ensure when we read header values, we are not reading ones
+ * that have come from a PUSH_PROMISE header (and associated CONTINUATION frames) */
+ http2_header_stream_info_t *stream_info = get_header_stream_info(pinfo);
+ if (stream_info->header_start_in == 0) {
+ stream_info->header_start_in = get_http2_frame_num(tvb, pinfo);
+ }
+ if (stream_info->header_end_in == 0 && flags & HTTP2_FLAGS_END_HEADERS) {
+ stream_info->header_end_in = get_http2_frame_num(tvb, pinfo);
+ }
#endif
offset = dissect_frame_padding(tvb, &padding, http2_tree, offset, flags);
@@ -1314,6 +1747,16 @@ dissect_http2_continuation(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *ht
http2_session_t *h2session;
h2session = get_http2_session(pinfo);
+
+ /* Mark this as the last CONTINUATION frame for a HEADERS frame. This is used to know the context when we read
+ * header (is the source a HEADER frame or a PUSH_PROMISE frame?) */
+ if (flags & HTTP2_FLAGS_END_HEADERS) {
+ http2_header_stream_info_t *stream_info = get_header_stream_info(pinfo);
+ if (stream_info->header_start_in != 0 && stream_info->header_end_in == 0) {
+ stream_info->header_end_in = get_http2_frame_num(tvb, pinfo);
+ }
+ }
+
#endif
offset = dissect_frame_padding(tvb, &padding, http2_tree, offset, flags);
@@ -1440,6 +1883,12 @@ dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat
proto_item_append_text(ti, ": %s, Stream ID: %u, Length %u", val_to_str(type, http2_type_vals, "Unknown type (%d)"), streamid, length);
offset += 4;
+#ifdef HAVE_NGHTTP2
+ /* Mark the current stream, used for per-stream processing later in the dissection */
+ http2_session_t *http2_session = get_http2_session(pinfo);
+ http2_session->current_stream_id = streamid;
+#endif
+
/* Collect stats */
http2_stats = wmem_new0(wmem_packet_scope(), struct HTTP2Tap);
http2_stats->type = type;
@@ -1729,6 +2178,57 @@ proto_register_http2(void)
FT_BYTES, BASE_NONE, NULL, 0x0,
"Padding octets", HFILL }
},
+ /* Body fragments */
+ { &hf_http2_body_fragments,
+ { "Body fragments", "http2.body.fragments",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_fragment,
+ { "Body fragment", "http2.body.fragment",
+ FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_fragment_overlap,
+ { "Body fragment overlap", "http2.body.fragment.overlap",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_fragment_overlap_conflicts,
+ { "Body fragment overlapping with conflicting data", "http2.body.fragment.overlap.conflicts",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_fragment_multiple_tails,
+ { "Body has multiple tail fragments", "http2.body.fragment.multiple_tails",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_fragment_too_long_fragment,
+ { "Body fragment too long", "http2.body.fragment.too_long_fragment",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_fragment_error,
+ { "Body defragment error", "http2.body.fragment.error",
+ FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_fragment_count,
+ { "Body fragment count", "http2.body.fragment.count",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_http2_body_reassembled_in,
+ { "Reassembled body in frame", "http2.body.reassembled.in",
+ FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+ "Reassembled body in frame number", HFILL }
+ },
+ { &hf_http2_body_reassembled_length,
+ { "Reassembled body length", "http2.body.reassembled.length",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "Reassembled body in frame number", HFILL }
+ },
/* Headers */
{ &hf_http2_headers,
@@ -1956,7 +2456,10 @@ proto_register_http2(void)
&ett_http2_header,
&ett_http2_headers,
&ett_http2_flags,
- &ett_http2_settings
+ &ett_http2_settings,
+ &ett_http2_encoded_entity,
+ &ett_http2_body_fragment,
+ &ett_http2_body_fragments
};
/* Setup protocol expert items */
@@ -1972,6 +2475,10 @@ proto_register_http2(void)
{ &ei_http2_header_lines,
{ "http2.header_lines_exceeded", PI_UNDECODED, PI_ERROR,
"Decompression stopped after " G_STRINGIFY(MAX_HTTP2_HEADER_LINES) " header lines.", EXPFILL }
+ },
+ { &ei_http2_body_decompression_failed,
+ { "http2.body_decompression_failed", PI_UNDECODED, PI_WARN,
+ "Body decompression failed", EXPFILL }
}
};
@@ -1992,6 +2499,9 @@ proto_register_http2(void)
http2_handle = register_dissector("http2", dissect_http2, proto_http2);
+ reassembly_table_register(&http2_body_reassembly_table,
+ &addresses_ports_reassembly_table_functions);
+
http2_tap = register_tap("http2");
}
diff --git a/test/captures/http2-data-reassembly.pcap b/test/captures/http2-data-reassembly.pcap
new file mode 100644
index 0000000000..b0f15ee9e9
--- /dev/null
+++ b/test/captures/http2-data-reassembly.pcap
Binary files differ
diff --git a/test/keys/http2-data-reassembly.keys b/test/keys/http2-data-reassembly.keys
new file mode 100644
index 0000000000..8a3e2fbd8e
--- /dev/null
+++ b/test/keys/http2-data-reassembly.keys
@@ -0,0 +1 @@
+CLIENT_RANDOM 59b4b71f50e71bff50f88388679c0156714d158bf10edd29f1d45fb4fffb3010 750fc27332a9cc17802defc48bd5693c9278d68680ae64d9dffa1e638ebd17a7902ad69501c413571b7c63dc23a0918b \ No newline at end of file
diff --git a/test/suite-dissection.sh b/test/suite-dissection.sh
new file mode 100755
index 0000000000..dc003fde08
--- /dev/null
+++ b/test/suite-dissection.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+#
+# Test suite for various ad-hoc dissection tests
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+dissection_http2_data_reassembly_test() {
+ if [ $HAVE_NGHTTP2 -ne 0 ]; then
+ test_step_skipped
+ return
+ fi
+
+ local filename="${CAPTURE_DIR}/http2-data-reassembly.pcap"
+ local keys="${TESTS_DIR}/keys/http2-data-reassembly.keys"
+
+ # Check for a reassembled PNG image.
+ $TSHARK -o ssl.keylog_file:$keys -d 'tcp.port==8443,ssl' \
+ -Y 'http2.data.data matches "PNG" && http2.data.data matches "END"' \
+ -r $filename |grep -q DATA
+
+ if [ $? -ne 0 ]; then
+ test_step_failed "could not find DATA frame with reassembled PNG content"
+ else
+ test_step_ok
+ fi
+ return
+}
+
+dissection_suite() {
+ test_step_add "testing http2 data reassembly" dissection_http2_data_reassembly_test
+}
+
+#
+# Editor modelines - https://www.wireshark.org/tools/modelines.html
+#
+# Local variables:
+# sh-basic-offset: 8
+# tab-width: 8
+# indent-tabs-mode: t
+# End:
+#
+# vi: set shiftwidth=8 tabstop=8 noexpandtab:
+# :indentSize=8:tabSize=8:noTabs=false:
+#
diff --git a/test/test.sh b/test/test.sh
index d469eccc22..375a647458 100755
--- a/test/test.sh
+++ b/test/test.sh
@@ -62,6 +62,7 @@ Usage: $THIS [-c] [-h] [-s <suite>]
prerequisites
unittests
wslua
+ dissection
FIN
exit 0
fi
@@ -110,6 +111,7 @@ source $TESTS_DIR/suite-nameres.sh
source $TESTS_DIR/suite-wslua.sh
source $TESTS_DIR/suite-mergecap.sh
source $TESTS_DIR/suite-text2pcap.sh
+source $TESTS_DIR/suite-dissection.sh
test_cleanup() {
if [ $TEST_OUTDIR_CLEAN = 1 ]; then
@@ -172,6 +174,7 @@ test_suite() {
test_suite_add "Mergecap" mergecap_suite
test_suite_add "File formats" fileformats_suite
test_suite_add "Text2pcap" text2pcap_suite
+ test_suite_add "Dissection" dissection_suite
}
@@ -223,6 +226,9 @@ if [ -n "$RUN_SUITE" ] ; then
"text2pcap")
test_suite_run "Text2pcap" text2pcap_suite
exit $? ;;
+ "dissection")
+ test_suite_run "Dissection" dissection_suite
+ exit $? ;;
esac
fi