forked from osmocom/wireshark
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:
parent
cfb23d8743
commit
f24ffb0bcd
|
@ -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.
|
@ -0,0 +1 @@
|
|||
CLIENT_RANDOM 59b4b71f50e71bff50f88388679c0156714d158bf10edd29f1d45fb4fffb3010 750fc27332a9cc17802defc48bd5693c9278d68680ae64d9dffa1e638ebd17a7902ad69501c413571b7c63dc23a0918b
|
|
@ -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:
|
||||
#
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue