From f24ffb0bcd12c3dce1e48b7a34c48a6be62c45e4 Mon Sep 17 00:00:00 2001 From: Ryan Doyle Date: Mon, 27 Mar 2017 21:48:26 +1100 Subject: 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 Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman --- epan/dissectors/packet-http2.c | 516 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 513 insertions(+), 3 deletions(-) (limited to 'epan/dissectors') 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 #include +#include #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"); } -- cgit v1.2.1