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>
This commit is contained in:
Ryan Doyle 2017-03-27 21:48:26 +11:00 committed by Anders Broman
parent cfb23d8743
commit f24ffb0bcd
5 changed files with 580 additions and 3 deletions

View File

@ -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");
}

Binary file not shown.

View File

@ -0,0 +1 @@
CLIENT_RANDOM 59b4b71f50e71bff50f88388679c0156714d158bf10edd29f1d45fb4fffb3010 750fc27332a9cc17802defc48bd5693c9278d68680ae64d9dffa1e638ebd17a7902ad69501c413571b7c63dc23a0918b

60
test/suite-dissection.sh Executable file
View File

@ -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:
#

View File

@ -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