HTTP/GRPC-Web: support dissecting chunked data in streaming reassembly mode

This commit is contained in:
huangqiangxiong 2023-04-17 16:10:08 +00:00 committed by Martin Mathieson
parent 0bff5811f1
commit 9a4c503eec
13 changed files with 1198 additions and 79 deletions

View File

@ -3733,6 +3733,8 @@ Huang Qiangxiong <qiangxiong.huang[AT]qq.com> {
Protobuf dissector
gRPC dissector
HTTP2 dissector: add streaming mode reassembly and dissecting DATA according to content-type features.
Reassembly: add types and functions to simplify streaming data reassembly.
HTTP dissector: support dissecting chunked data in streaming reassembly mode.
}
Jeffrey Nichols <jsnichols[AT]suprocktech.com> {

View File

@ -86,6 +86,9 @@ synchronous.
* Implement built-in dissector for FiRa UWB Controller Interface (UCI) protocol.
Recognizes PCAP traces with the link type LINKTYPE_FIRA_UCI=299.
* The reassemble_streaming_data_and_call_subdissector() API has been added to provide a simpler way to
reassemble the streaming data of a high level protocol that is not on top of TCP.
// === Removed Features and Support
// === Removed Dissectors
@ -142,6 +145,12 @@ FiRa UWB Controller Interface (UCI)
* The SIP dissector now has a new preference to set default charset for
displaying the body of SIP messages in raw text view.
* The HTTP dissector now supports dissecting chunked data in streaming reassembly
mode. Subdissectors of HTTP can register itself in "streaming_content_type"
subdissector table for enabling streaming reassembly mode while transferring in
chunked encoding. This feature ensures the server stream messages of GRPC-Web
over HTTP/1.1 can be dissected even if the last chunk is absent.
Too many other protocols have been updated to list them all here.
=== New and Updated Capture File Support

View File

@ -136,6 +136,18 @@ static int hf_http_unknown_header = -1;
static int hf_http_http2_settings_uri = -1;
static int hf_http_path_segment = -1;
static int hf_http_path_sub_segment = -1;
static int hf_http_body_fragments = -1;
static int hf_http_body_fragment = -1;
static int hf_http_body_fragment_overlap = -1;
static int hf_http_body_fragment_overlap_conflicts = -1;
static int hf_http_body_fragment_multiple_tails = -1;
static int hf_http_body_fragment_too_long_fragment = -1;
static int hf_http_body_fragment_error = -1;
static int hf_http_body_fragment_count = -1;
static int hf_http_body_reassembled_in = -1;
static int hf_http_body_reassembled_length = -1;
static int hf_http_body_reassembled_data = -1;
static int hf_http_body_segment = -1;
static gint ett_http = -1;
static gint ett_http_ntlmssp = -1;
@ -149,6 +161,8 @@ static gint ett_http_encoded_entity = -1;
static gint ett_http_header_item = -1;
static gint ett_http_http2_settings_item = -1;
static gint ett_http_path = -1;
static gint ett_http_body_fragment = -1;
static gint ett_http_body_fragments = -1;
static expert_field ei_http_chat = EI_INIT;
static expert_field ei_http_te_and_length = EI_INIT;
@ -176,6 +190,31 @@ static dissector_handle_t gssapi_handle;
static ws_mempbrk_pattern pbrk_gen_delims;
static ws_mempbrk_pattern pbrk_sub_delims;
/* reassembly table for streaming chunk mode */
static reassembly_table http_streaming_reassembly_table;
static const fragment_items http_body_fragment_items = {
/* Fragment subtrees */
&ett_http_body_fragment,
&ett_http_body_fragments,
/* Fragment fields */
&hf_http_body_fragments,
&hf_http_body_fragment,
&hf_http_body_fragment_overlap,
&hf_http_body_fragment_overlap_conflicts,
&hf_http_body_fragment_multiple_tails,
&hf_http_body_fragment_too_long_fragment,
&hf_http_body_fragment_error,
&hf_http_body_fragment_count,
&hf_http_body_reassembled_in,
&hf_http_body_reassembled_length,
&hf_http_body_reassembled_data,
"Reassembled HTTP Chunked Body fragments"
};
/* HTTP chunk virtual frame number (similar to HTTP2 frame num) */
#define get_http_chunk_frame_num get_virtual_frame_num64
/* Stuff for generation/handling of fields for custom HTTP headers */
typedef struct _header_field_t {
gchar* header_name;
@ -325,6 +364,26 @@ typedef struct {
char *upgrade;
} headers_t;
/* request or response streaming reassembly data */
typedef struct {
/* reassembly information only for request or response with chunked and streaming data */
streaming_reassembly_info_t* streaming_reassembly_info;
/* subdissector handler for request or response with chunked and streaming data */
dissector_handle_t streaming_handle;
/* message being passed to subdissector if the request or response has chunked and streaming data */
http_message_info_t* message_info;
headers_t* main_headers;
} http_streaming_reassembly_data_t;
/* http request or response private data */
typedef struct {
/* tcp forward flow of request message */
tcp_flow_t* req_fwd_flow;
/* request or response streaming reassembly data */
http_streaming_reassembly_data_t* req_streaming_reassembly_data;
http_streaming_reassembly_data_t* res_streaming_reassembly_data;
} http_req_res_private_data_t;
static gint parse_http_status_code(const guchar *line, const guchar *lineend);
static int is_http_request_or_reply(packet_info *pinfo, const gchar *data, int linelen,
http_type_t *type, ReqRespDissector
@ -336,7 +395,7 @@ static void process_header(tvbuff_t *tvb, int offset, int next_offset,
const guchar *line, int linelen, int colon_offset,
packet_info *pinfo, proto_tree *tree,
headers_t *eh_ptr, http_conv_t *conv_data,
http_type_t http_type, wmem_map_t *header_value_map);
http_type_t http_type, wmem_map_t *header_value_map, gboolean streaming_chunk_mode);
static gint find_header_hf_value(tvbuff_t *tvb, int offset, guint header_len);
static gboolean check_auth_ntlmssp(proto_item *hdr_item, tvbuff_t *tvb,
packet_info *pinfo, gchar *value);
@ -350,6 +409,7 @@ static gboolean check_auth_kerberos(proto_item *hdr_item, tvbuff_t *tvb,
static dissector_table_t port_subdissector_table;
static dissector_table_t media_type_subdissector_table;
static dissector_table_t streaming_content_type_dissector_table;
static dissector_table_t upgrade_subdissector_table;
static heur_dissector_list_t heur_subdissector_list;
@ -1055,6 +1115,8 @@ push_req_res(http_conv_t *conv_data)
conv_data->req_res_tail = req_res;
}
req_res->private_data = wmem_new0(wmem_file_scope(), http_req_res_private_data_t);
return req_res;
}
@ -1132,10 +1194,10 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
ReqRespDissector reqresp_dissector;
proto_tree *req_tree;
int colon_offset;
headers_t headers;
headers_t *headers = NULL;
int datalen;
int reported_datalen = -1;
dissector_handle_t handle;
dissector_handle_t handle = NULL;
gboolean dissected = FALSE;
gboolean first_loop = TRUE;
gboolean have_seen_http = FALSE;
@ -1146,14 +1208,56 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
int reported_length;
guint16 word;
gboolean leading_crlf = FALSE;
http_message_info_t message_info;
wmem_map_t *header_value_map = wmem_map_new(pinfo->pool, g_str_hash, g_str_equal);
http_message_info_t* message_info = NULL;
wmem_map_t* header_value_map = NULL;
int chunk_offset = 0;
wmem_map_t *chunk_map = NULL;
/*
* For supporting dissecting chunked data in streaming reassembly mode.
*
* If a HTTP request or response is chunked encoding (the transfer-encoding
* header is 'chunked') and its content-type matching a subdissector in
* "streaming_content_type" dissector table, then we switch to dissect in
* streaming chunk mode. In streaming chunk mode, we dissect the data as soon
* as possible, unlike normal mode, we don't start reassembling until the end
* of the request or response message or at the end of the TCP stream. In
* streaming chunk mode, the first reassembled PDU contains HTTP headers
* and at least one completed chunk of this request or response message. And
* subsequent PDUs consist of one or more chunks:
*
* ----- +-- Reassembled Streaming Content PDU(s) --+-- Reassembled Streaming Content PDU(s) --+--- Reassembled ...
* HLProtos | 1*high-proto-pdu | 1*high-proto-pdu | 1*high-proto-pdu
* ----- +-------------------------------------+----+--------------------------------+---------+-------------------
* | de-chunked-data | de-chunked-data | de-chunked-data
* HTTP +-------- First Reassembled HTTP PDU -----------+--- Second Reassembled HTTP PDU -----+- Third PDU -+ +- Fourth ---
* | headers and 1*chunk | 1*chunk | 1*chunk | | 1*chunk ...
* ----- +--------- TCP segment ---------+ +-----------TCP segment -----------+ +---- TCP segment ---------+ +------------
* TCP | headers | *chunk | part-chunk | | part-chunk | *chunk | part-chunk | | part-chunk | 1*chunk | | 1*chunk ...
* ----- +---------+--------+------------+ +------------+--------+------------+ +------------+-------------+ +------------
*
* Notation:
* - headers HTTP headers of a request or response message.
* - part-chunk The front or rear part of a HTTP chunk.
* - *chunk Zero or more completed HTTP chunks of a HTTP message.
* - 1*chunk One or more completed HTTP chunks of a HTTP message.
* - de-chunked-data De-chunked HTTP body data based on one or more completed chunks.
* - 1*high-proto-pdu One or more high level protocol (on top of HTTP) PDUs.
* - HLProtos High Level Protocols like GRPC-Web.
*
* The headers and message_info of the req_res are allocated in file scope that
* helps to provide information for dissecting subsequent PDUs which only
* contains chunks without headers.
*/
gboolean streaming_chunk_mode = FALSE;
gboolean begin_with_chunk = FALSE;
http_streaming_reassembly_data_t* streaming_reassembly_data = NULL;
struct tcp_analysis* tcpd = get_tcp_conversation_data(NULL, pinfo);
conversation_t *conversation;
http_req_res_t *curr = (http_req_res_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_http, HTTP_PROTO_DATA_REQRES);
http_info_value_t *stat_info = NULL;
http_req_res_private_data_t* prv_data = curr ? (http_req_res_private_data_t*)curr->private_data : NULL;
http_req_res_private_data_t* tail_prv_data = NULL;
conversation = find_or_create_conversation(pinfo);
if (cmp_address(&pinfo->src, conversation_key_addr1(conversation->key_ptr)) == 0 && pinfo->srcport == conversation_key_port1(conversation->key_ptr)) {
@ -1244,7 +1348,8 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* desegmentation if we're told to.
*/
if (!req_resp_hdrs_do_reassembly(tvb, offset, pinfo,
http_desegment_headers, http_desegment_body, FALSE, &chunk_offset)) {
http_desegment_headers, http_desegment_body, FALSE, &chunk_offset,
streaming_content_type_dissector_table, &handle)) {
/*
* More data needed for desegmentation.
*/
@ -1252,6 +1357,34 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
}
if (!PINFO_FD_VISITED(pinfo) && conv_data->req_res_tail && conv_data->req_res_tail->private_data) {
tail_prv_data = (http_req_res_private_data_t*) conv_data->req_res_tail->private_data;
}
/* Check whether the first line is the beginning of a chunk. If it is the beginning
* of a chunk, the headers and at least one chunk of HTTP request or response should
* be dissected in the previous packets, and now we are processing subsequent chunks.
*/
if (http_desegment_body && http_dechunk_body) {
begin_with_chunk = starts_with_chunk_size(tvb, offset, pinfo);
if (begin_with_chunk &&
((prv_data && ( /* This packet has been parsed */
/* and now we are in a HTTP request chunk stream */
(prv_data->req_fwd_flow == tcpd->fwd && prv_data->req_streaming_reassembly_data) ||
/* and now we are in a HTTP response chunk stream */
(prv_data->req_fwd_flow == tcpd->rev && prv_data->res_streaming_reassembly_data)))
||
(tail_prv_data && ( /* This packet has not been parsed and headers info in conv_data->req_res_tail */
/* and now we are in a HTTP request chunk stream */
(tail_prv_data->req_fwd_flow == tcpd->fwd && tail_prv_data->req_streaming_reassembly_data) ||
/* and now we are in a HTTP response chunk stream */
(tail_prv_data->req_fwd_flow == tcpd->rev && tail_prv_data->res_streaming_reassembly_data)))))
{
streaming_chunk_mode = TRUE;
}
}
/*
* Is the first line a request or response?
*
@ -1263,15 +1396,19 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
http_type = HTTP_OTHERS; /* type not known yet */
is_request_or_reply = is_http_request_or_reply(pinfo, (const gchar *)firstline,
first_linelen, &http_type, NULL, conv_data);
if (is_request_or_reply) {
if (is_request_or_reply || streaming_chunk_mode) {
gboolean try_desegment_body;
/*
* Yes, it's a request or response.
* Put the first line from the buffer into the summary
* (but leave out the line terminator).
*/
col_add_fstr(pinfo->cinfo, COL_INFO, "%s ", format_text(pinfo->pool, firstline, first_linelen));
if (streaming_chunk_mode && begin_with_chunk) {
col_add_str(pinfo->cinfo, COL_INFO, "Chunk Stream ");
} else {
/*
* Yes, it's a request or response.
* Put the first line from the buffer into the summary
* (but leave out the line terminator).
*/
col_add_fstr(pinfo->cinfo, COL_INFO, "%s ", format_text(pinfo->pool, firstline, first_linelen));
}
/*
* Do header desegmentation if we've been told to,
@ -1288,7 +1425,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* will be added so disable body segmentation too in that case.
*/
try_desegment_body = (http_desegment_body && !end_of_stream);
if (try_desegment_body && http_type == HTTP_RESPONSE) {
if (try_desegment_body && http_type == HTTP_RESPONSE && !streaming_chunk_mode) {
/*
* The response_code is not yet set, so extract
* the response code from the current line.
@ -1319,7 +1456,8 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
}
if (!req_resp_hdrs_do_reassembly(tvb, offset, pinfo,
http_desegment_headers, try_desegment_body, http_type == HTTP_RESPONSE, &chunk_offset)) {
http_desegment_headers, try_desegment_body, http_type == HTTP_RESPONSE, &chunk_offset,
streaming_content_type_dissector_table, &handle)) {
/*
* More data needed for desegmentation.
*/
@ -1328,6 +1466,14 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
return -1;
}
if (handle && http_desegment_body && http_dechunk_body) {
/* This handle is set because there is a header 'Transfer-Encoding: chunked', and
* a streaming mode reassembly supported subdissector is found according to the
* header of Content-Type.
*/
streaming_chunk_mode = TRUE;
}
} else if (have_seen_http) {
/*
* If we know this is HTTP then call it continuation.
@ -1335,7 +1481,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
col_set_str(pinfo->cinfo, COL_INFO, "Continuation");
}
if (is_request_or_reply || have_seen_http) {
if (is_request_or_reply || have_seen_http || streaming_chunk_mode) {
/*
* Now set COL_PROTOCOL and create the http tree for the
* cases where we set COL_INFO above.
@ -1351,6 +1497,37 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
is_tls = proto_is_frame_protocol(pinfo->layers, "tls");
if (!PINFO_FD_VISITED(pinfo) && begin_with_chunk
&& streaming_chunk_mode && conv_data->req_res_tail) {
/* point this packet beginning with a chunk to req_res info created in previous packet. */
curr = conv_data->req_res_tail;
prv_data = (http_req_res_private_data_t*)curr->private_data;
p_set_proto_data(wmem_file_scope(), pinfo, proto_http, HTTP_PROTO_DATA_REQRES, curr);
}
if (prv_data) {
if (prv_data->req_fwd_flow == tcpd->fwd && prv_data->req_streaming_reassembly_data) {
/* in request flow */
streaming_reassembly_data = prv_data->req_streaming_reassembly_data;
} else if (prv_data->req_fwd_flow == tcpd->rev && prv_data->res_streaming_reassembly_data) {
/* in response flow */
streaming_reassembly_data = prv_data->res_streaming_reassembly_data;
}
if (streaming_reassembly_data) {
streaming_chunk_mode = TRUE;
headers = streaming_reassembly_data->main_headers;
handle = streaming_reassembly_data->streaming_handle;
message_info = streaming_reassembly_data->message_info;
header_value_map = (wmem_map_t*) message_info->data;
}
}
if (streaming_chunk_mode && begin_with_chunk) {
datalen = reported_length;
goto dissecting_body;
}
stat_info = wmem_new(pinfo->pool, http_info_value_t);
stat_info->framenum = pinfo->num;
stat_info->response_code = 0;
@ -1367,14 +1544,15 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* Process the packet data, a line at a time.
*/
http_type = HTTP_OTHERS; /* type not known yet */
headers.content_type = NULL; /* content type not known yet */
headers.content_type_parameters = NULL; /* content type parameters too */
headers.have_content_length = FALSE; /* content length not known yet */
headers.content_length = 0; /* content length set to 0 (avoid a gcc warning) */
headers.content_encoding = NULL; /* content encoding not known yet */
headers.transfer_encoding_chunked = FALSE;
headers.transfer_encoding = HTTP_TE_NONE;
headers.upgrade = NULL; /* assume no upgrade header */
if (headers == NULL) {
DISSECTOR_ASSERT_HINT(!PINFO_FD_VISITED(pinfo) || (PINFO_FD_VISITED(pinfo) && !streaming_chunk_mode),
"The headers variable should not be NULL if it is in streaming mode during a non first scan.");
DISSECTOR_ASSERT_HINT(header_value_map == NULL, "The header_value_map variable should be NULL while headers is NULL.");
headers = wmem_new0((streaming_chunk_mode ? wmem_file_scope() : pinfo->pool), headers_t);
header_value_map = wmem_map_new((streaming_chunk_mode ? wmem_file_scope() : pinfo->pool), g_str_hash, g_str_equal);
}
saw_req_resp_or_header = FALSE; /* haven't seen anything yet */
while (tvb_offset_exists(tvb, offset)) {
/*
@ -1515,8 +1693,12 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
if (http_type == HTTP_REQUEST) {
curr = push_req(conv_data, pinfo);
curr->request_method = wmem_strdup(wmem_file_scope(), stat_info->request_method);
prv_data = curr->private_data;
prv_data->req_fwd_flow = tcpd->fwd;
} else if (http_type == HTTP_RESPONSE) {
curr = push_res(conv_data, pinfo);
prv_data = curr->private_data;
prv_data->req_fwd_flow = tcpd->rev;
}
}
if (reqresp_dissector) {
@ -1528,8 +1710,8 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* Header.
*/
process_header(tvb, offset, next_offset, line, linelen,
colon_offset, pinfo, http_tree, &headers, conv_data,
http_type, header_value_map);
colon_offset, pinfo, http_tree, headers, conv_data,
http_type, header_value_map, streaming_chunk_mode);
}
offset = next_offset;
}
@ -1691,10 +1873,10 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* keep information about the request and associate that with
* the response in order to handle that.
*/
if (headers.have_content_length &&
headers.transfer_encoding == HTTP_TE_NONE) {
if (datalen > headers.content_length)
datalen = (int)headers.content_length;
if (headers->have_content_length &&
headers->transfer_encoding == HTTP_TE_NONE) {
if (datalen > headers->content_length)
datalen = (int)headers->content_length;
/*
* XXX - limit the reported length in the tvbuff we'll
@ -1710,8 +1892,8 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* "packet is malformed" (running past the reassembled
* length).
*/
if (reported_datalen > headers.content_length)
reported_datalen = (int)headers.content_length;
if (reported_datalen > headers->content_length)
reported_datalen = (int)headers->content_length;
} else {
switch (http_type) {
@ -1721,7 +1903,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* Content-Length header and no Transfer-Encoding
* header.
*/
if (headers.transfer_encoding == HTTP_TE_NONE)
if (headers->transfer_encoding == HTTP_TE_NONE)
datalen = 0;
else
reported_datalen = -1;
@ -1752,13 +1934,43 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
}
if (!PINFO_FD_VISITED(pinfo) && streaming_chunk_mode && streaming_reassembly_data == NULL) {
DISSECTOR_ASSERT(!begin_with_chunk && handle && http_dechunk_body && http_desegment_body
&& headers && headers->content_type && header_value_map);
message_info = wmem_new0(wmem_file_scope(), http_message_info_t);
message_info->media_str = headers->content_type_parameters;
message_info->type = http_type;
message_info->data = header_value_map;
streaming_reassembly_data = wmem_new0(wmem_file_scope(), http_streaming_reassembly_data_t);
streaming_reassembly_data->streaming_handle = handle;
streaming_reassembly_data->streaming_reassembly_info = streaming_reassembly_info_new();
streaming_reassembly_data->message_info = message_info;
streaming_reassembly_data->main_headers = headers;
if (prv_data->req_fwd_flow == tcpd->fwd) {
prv_data->req_streaming_reassembly_data = streaming_reassembly_data;
} else {
prv_data->res_streaming_reassembly_data = streaming_reassembly_data;
}
}
if (message_info == NULL) {
message_info = wmem_new0(pinfo->pool, http_message_info_t);
message_info->media_str = headers->content_type_parameters;
message_info->type = http_type;
message_info->data = header_value_map;
}
dissecting_body:
if (datalen > 0) {
/*
* There's stuff left over; process it.
*/
tvbuff_t *next_tvb;
guint chunked_datalen = 0;
char *media_str = NULL;
const gchar *file_data;
/*
@ -1781,7 +1993,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
/*
* Handle *transfer* encodings.
*/
if (headers.transfer_encoding_chunked) {
if (headers->transfer_encoding_chunked) {
if (!http_dechunk_body) {
/* Chunking disabled, cannot dissect further. */
/* XXX: Should this be sent to the follow tap? */
@ -1816,7 +2028,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
}
/* Handle other transfer codings after de-chunking. */
switch (headers.transfer_encoding) {
switch (headers->transfer_encoding) {
case HTTP_TE_COMPRESS:
case HTTP_TE_DEFLATE:
case HTTP_TE_GZIP:
@ -1843,8 +2055,8 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* shouldn't appear in a Content-Encoding header, but
* we handle it in any case).
*/
if (headers.content_encoding != NULL &&
g_ascii_strcasecmp(headers.content_encoding, "identity") != 0) {
if (headers->content_encoding != NULL &&
g_ascii_strcasecmp(headers->content_encoding, "identity") != 0) {
/*
* We currently don't handle, for example, "compress";
* just handle them as data for now.
@ -1860,10 +2072,10 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
#ifdef HAVE_ZLIB
if (http_decompress_body &&
(g_ascii_strcasecmp(headers.content_encoding, "gzip") == 0 ||
g_ascii_strcasecmp(headers.content_encoding, "deflate") == 0 ||
g_ascii_strcasecmp(headers.content_encoding, "x-gzip") == 0 ||
g_ascii_strcasecmp(headers.content_encoding, "x-deflate") == 0))
(g_ascii_strcasecmp(headers->content_encoding, "gzip") == 0 ||
g_ascii_strcasecmp(headers->content_encoding, "deflate") == 0 ||
g_ascii_strcasecmp(headers->content_encoding, "x-gzip") == 0 ||
g_ascii_strcasecmp(headers->content_encoding, "x-deflate") == 0))
{
uncomp_tvb = tvb_child_uncompress(tvb, next_tvb, 0,
tvb_captured_length(next_tvb));
@ -1872,7 +2084,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
#ifdef HAVE_BROTLI
if (http_decompress_body &&
g_ascii_strcasecmp(headers.content_encoding, "br") == 0)
g_ascii_strcasecmp(headers->content_encoding, "br") == 0)
{
uncomp_tvb = tvb_child_uncompress_brotli(tvb, next_tvb, 0,
tvb_captured_length(next_tvb));
@ -1885,7 +2097,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
e_tree = proto_tree_add_subtree_format(http_tree, next_tvb,
0, tvb_captured_length(next_tvb), ett_http_encoded_entity, &e_ti,
"Content-encoded entity body (%s): %u bytes",
headers.content_encoding,
headers->content_encoding,
tvb_captured_length(next_tvb));
if (uncomp_tvb != NULL) {
@ -1938,7 +2150,7 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
eo_info->hostname = curr->http_host;
eo_info->filename = curr->request_uri;
}
eo_info->content_type = headers.content_type;
eo_info->content_type = headers->content_type;
eo_info->payload_len = tvb_captured_length(next_tvb);
eo_info->payload_data = tvb_get_ptr(next_tvb, 0, eo_info->payload_len);
@ -1962,28 +2174,25 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* First, if we have a Content-Type value, check whether
* there's a subdissector for that media type.
*/
handle = NULL;
if (headers.content_type != NULL) {
if (headers->content_type != NULL && handle == NULL) {
/*
* We didn't find any subdissector that
* registered for the port, and we have a
* Content-Type value. Is there any subdissector
* for that content type?
*/
if (headers.content_type_parameters)
media_str = wmem_strdup(pinfo->pool, headers.content_type_parameters);
/*
* Calling the string handle for the media type
* dissector table will set pinfo->match_string
* to headers.content_type for us.
* to headers->content_type for us.
*/
pinfo->match_string = headers.content_type;
pinfo->match_string = headers->content_type;
handle = dissector_get_string_handle(
media_type_subdissector_table,
headers.content_type);
headers->content_type);
if (handle == NULL &&
strncmp(headers.content_type, "multipart/", sizeof("multipart/")-1) == 0) {
strncmp(headers->content_type, "multipart/", sizeof("multipart/")-1) == 0) {
/* Try to decode the unknown multipart subtype anyway */
handle = dissector_get_string_handle(
media_type_subdissector_table,
@ -2003,14 +2212,22 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
pinfo->match_uint);
}
message_info.type = http_type;
message_info.media_str = media_str;
message_info.data = header_value_map;
if (handle != NULL) {
/*
* We have a subdissector - call it.
*/
dissected = call_dissector_only(handle, next_tvb, pinfo, tree, &message_info);
if (streaming_chunk_mode) {
pinfo->match_string = headers->content_type;
/* reassemble and call subdissector */
dissected = reassemble_streaming_data_and_call_subdissector(next_tvb, pinfo, 0,
tvb_reported_length_remaining(next_tvb, 0), http_tree, proto_tree_get_parent_tree(tree),
http_streaming_reassembly_table, streaming_reassembly_data->streaming_reassembly_info,
get_http_chunk_frame_num(tvb, pinfo, offset), handle,
proto_tree_get_parent_tree(tree), message_info,
"HTTP", &http_body_fragment_items, hf_http_body_segment);
} else {
dissected = call_dissector_only(handle, next_tvb, pinfo, tree, message_info);
}
if (!dissected)
expert_add_info(pinfo, http_tree, &ei_http_subdissector_failed);
}
@ -2033,12 +2250,12 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
if (ti != NULL)
proto_item_set_len(ti, offset);
} else {
if (headers.content_type != NULL) {
if (headers->content_type != NULL) {
/*
* Calling the default media handle if there is a content-type that
* wasn't handled above.
*/
call_dissector_with_data(media_handle, next_tvb, pinfo, tree, &message_info);
call_dissector_with_data(media_handle, next_tvb, pinfo, tree, message_info);
} else {
/* Call the default data dissector */
call_data_dissector(next_tvb, pinfo, http_tree);
@ -2072,14 +2289,14 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
* An HTTP/1.1 upgrade only proceeds if the server responds
* with 101 Switching Protocols. See RFC 7230 Section 6.7.
*/
if (headers.upgrade && curr->response_code == 101) {
next_handle = dissector_get_string_handle(upgrade_subdissector_table, headers.upgrade);
if (headers->upgrade && curr->response_code == 101) {
next_handle = dissector_get_string_handle(upgrade_subdissector_table, headers->upgrade);
if (!next_handle) {
char *slash_pos = strchr(headers.upgrade, '/');
char *slash_pos = strchr(headers->upgrade, '/');
if (slash_pos) {
/* Try again without version suffix. */
next_handle = dissector_get_string_handle(upgrade_subdissector_table,
wmem_strndup(pinfo->pool, headers.upgrade, slash_pos - headers.upgrade));
wmem_strndup(pinfo->pool, headers->upgrade, slash_pos - headers->upgrade));
}
}
server_acked = TRUE;
@ -2094,7 +2311,8 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
}
tap_queue_packet(http_tap, pinfo, stat_info);
if (stat_info)
tap_queue_packet(http_tap, pinfo, stat_info);
return offset - orig_offset;
}
@ -2453,6 +2671,8 @@ chunked_encoding_dissector(tvbuff_t **tvb_ptr, packet_info *pinfo,
proto_item *pi_chunked = NULL;
guint8 *raw_data;
gint raw_len;
gint chunk_counter = 0;
gint last_chunk_id = -1;
if ((tvb_ptr == NULL) || (*tvb_ptr == NULL)) {
return 0;
@ -2522,6 +2742,8 @@ chunked_encoding_dissector(tvbuff_t **tvb_ptr, packet_info *pinfo,
tvb_memcpy(tvb, (guint8 *)(raw_data + raw_len), chunk_offset, chunk_size);
raw_len += chunk_size;
++chunk_counter;
if (subtree) {
proto_tree *chunk_subtree;
proto_item *chunk_size_item;
@ -2532,6 +2754,7 @@ chunked_encoding_dissector(tvbuff_t **tvb_ptr, packet_info *pinfo,
chunk_offset - offset + chunk_size + 2,
ett_http_chunk_data, NULL,
"End of chunked encoding");
last_chunk_id = chunk_counter - 1;
} else {
chunk_subtree = proto_tree_add_subtree_format(subtree, tvb,
offset,
@ -2603,6 +2826,18 @@ chunked_encoding_dissector(tvbuff_t **tvb_ptr, packet_info *pinfo,
*tvb_ptr = new_tvb;
}
if (chunk_counter > 0) {
proto_item* ti_http = proto_tree_get_parent(tree);
proto_item_append_text(ti_http, ", has %d chunk%s%s",
chunk_counter, plurality(chunk_counter, "", "s"),
(last_chunk_id < 0 ? "" : " (including last chunk)"));
if (last_chunk_id == 0) {
/* only append text to column while starting with last chunk */
col_append_sep_str(pinfo->cinfo, COL_INFO, " ", "[Last Chunk]");
}
}
/* Size of chunked-body or 0 if none was found. */
return orig_datalen - datalen;
}
@ -3139,7 +3374,8 @@ static void
process_header(tvbuff_t *tvb, int offset, int next_offset,
const guchar *line, int linelen, int colon_offset,
packet_info *pinfo, proto_tree *tree, headers_t *eh_ptr,
http_conv_t *conv_data, http_type_t http_type, wmem_map_t *header_value_map)
http_conv_t *conv_data, http_type_t http_type, wmem_map_t *header_value_map,
gboolean streaming_chunk_mode)
{
int len;
int line_end_offset;
@ -3159,6 +3395,8 @@ process_header(tvbuff_t *tvb, int offset, int next_offset,
tap_credential_t* auth;
http_req_res_t *curr_req_res = (http_req_res_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_http, HTTP_PROTO_DATA_REQRES);
http_info_value_t *stat_info = p_get_proto_data(pinfo->pool, pinfo, proto_http, HTTP_PROTO_DATA_INFO);
wmem_allocator_t *scope = (!PINFO_FD_VISITED(pinfo) && streaming_chunk_mode) ? wmem_file_scope() :
((PINFO_FD_VISITED(pinfo) && streaming_chunk_mode) ? NULL : pinfo->pool);
len = next_offset - offset;
line_end_offset = offset + linelen;
@ -3228,16 +3466,18 @@ process_header(tvbuff_t *tvb, int offset, int next_offset,
* has value_bytes_len bytes in it.
*/
value_bytes_len = line_end_offset - value_offset;
value_bytes = (char *)wmem_alloc(pinfo->pool, value_bytes_len+1);
value_bytes = (char *)wmem_alloc((scope ? scope : pinfo->pool), value_bytes_len+1);
memcpy(value_bytes, &line[value_offset - offset], value_bytes_len);
value_bytes[value_bytes_len] = '\0';
value = tvb_get_string_enc(pinfo->pool, tvb, value_offset, value_bytes_len, ENC_ASCII);
/* The length of the value might change after UTF-8 sanitization */
value_len = (int)strlen(value);
if (header_value_map) {
if (scope == pinfo->pool) {
wmem_map_insert(header_value_map, header_name, value_bytes);
}
} else if (scope) { /* (!PINFO_FD_VISITED(pinfo) && streaming_chunk_mode) */
wmem_map_insert(header_value_map, wmem_strdup(scope, header_name), value_bytes);
} /* else skip while (PINFO_FD_VISITED(pinfo) && streaming_chunk_mode) */
if (hf_index == -1) {
/*
@ -3368,7 +3608,10 @@ process_header(tvbuff_t *tvb, int offset, int next_offset,
break;
case HDR_CONTENT_TYPE:
eh_ptr->content_type = wmem_strdup(pinfo->pool, value);
if (scope == NULL) { /* identical to (PINFO_FD_VISITED(pinfo) && streaming_chunk_mode) */
break; /* eh_ptr->content_type[_parameters] must have been set during first scan */
}
eh_ptr->content_type = wmem_strdup(scope, value);
for (i = 0; i < value_len; i++) {
c = value[i];
@ -3410,6 +3653,7 @@ process_header(tvbuff_t *tvb, int offset, int next_offset,
break;
case HDR_CONTENT_LENGTH:
DISSECTOR_ASSERT_HINT(!streaming_chunk_mode, "In streaming chunk mode, there will never be content-length header.");
errno = 0;
eh_ptr->content_length = g_ascii_strtoll(value, &p, 10);
up = (guchar *)p;
@ -3440,7 +3684,10 @@ process_header(tvbuff_t *tvb, int offset, int next_offset,
break;
case HDR_CONTENT_ENCODING:
eh_ptr->content_encoding = wmem_strndup(pinfo->pool, value, value_len);
if (scope == NULL) { /* identical to (PINFO_FD_VISITED(pinfo) && streaming_chunk_mode) */
break; /* eh_ptr->content_encoding must have been set during first scan */
}
eh_ptr->content_encoding = wmem_strndup(scope, value, value_len);
break;
case HDR_TRANSFER_ENCODING:
@ -3460,7 +3707,10 @@ process_header(tvbuff_t *tvb, int offset, int next_offset,
break;
case HDR_UPGRADE:
eh_ptr->upgrade = wmem_ascii_strdown(pinfo->pool, value, value_len);
if (scope == NULL) { /* identical to (PINFO_FD_VISITED(pinfo) && streaming_chunk_mode) */
break;
}
eh_ptr->upgrade = wmem_ascii_strdown(scope, value, value_len);
break;
case HDR_COOKIE:
@ -4362,6 +4612,67 @@ proto_register_http(void)
FT_STRING, BASE_NONE, NULL, 0,
NULL, HFILL } },
/* Body fragments */
{ &hf_http_body_fragments,
{ "Reassembled HTTP Chunked Body fragments", "http.body.fragments",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_fragment,
{ "Body fragment", "http.body.fragment",
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_fragment_overlap,
{ "Body fragment overlap", "http.body.fragment.overlap",
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_fragment_overlap_conflicts,
{ "Body fragment overlapping with conflicting data", "http.body.fragment.overlap.conflicts",
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_fragment_multiple_tails,
{ "Body has multiple tail fragments", "http.body.fragment.multiple_tails",
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_fragment_too_long_fragment,
{ "Body fragment too long", "http.body.fragment.too_long_fragment",
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_fragment_error,
{ "Body defragment error", "http.body.fragment.error",
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_fragment_count,
{ "Body fragment count", "http.body.fragment.count",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http_body_reassembled_in,
{ "Reassembled body in frame", "http.body.reassembled.in",
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
"Reassembled body in frame number", HFILL }
},
{ &hf_http_body_reassembled_length,
{ "Reassembled body length", "http.body.reassembled.length",
FT_UINT32, BASE_DEC, NULL, 0x0,
"Reassembled body in frame number", HFILL }
},
{ &hf_http_body_reassembled_data,
{ "Reassembled body data", "http.body.reassembled.data",
FT_BYTES, BASE_NONE, NULL, 0x0,
"Reassembled body data for multisegment PDU spanning across DATAs", HFILL }
},
{ &hf_http_body_segment,
{ "Body segment", "http.body.segment",
FT_BYTES, BASE_NONE, NULL, 0x0,
"A body segment used in reassembly", HFILL}
},
};
static gint *ett[] = {
&ett_http,
@ -4375,6 +4686,8 @@ proto_register_http(void)
&ett_http_encoded_entity,
&ett_http_header_item,
&ett_http_http2_settings_item,
&ett_http_body_fragment,
&ett_http_body_fragments,
&ett_http_path
};
@ -4414,6 +4727,8 @@ proto_register_http(void)
http_tls_handle = register_dissector("http-over-tls", dissect_http_tls, proto_http); /* RFC 2818 */
http_sctp_handle = register_dissector("http-over-sctp", dissect_http_sctp, proto_http);
reassembly_table_register(&http_streaming_reassembly_table, &addresses_ports_reassembly_table_functions);
http_module = prefs_register_protocol(proto_http, reinit_http);
prefs_register_bool_preference(http_module, "desegment_headers",
"Reassemble HTTP headers spanning multiple TCP segments",
@ -4671,6 +4986,8 @@ proto_reg_handoff_message_http(void)
dissector_add_uint_range_with_preference("tcp.port", TCP_DEFAULT_RANGE, http_tcp_handle);
dissector_add_uint_range_with_preference("sctp.port", SCTP_DEFAULT_RANGE, http_sctp_handle);
streaming_content_type_dissector_table = find_dissector_table("streaming_content_type");
reinit_http();
}

View File

@ -58,6 +58,8 @@ typedef struct _http_req_res_t {
struct _http_req_res_t *next;
/** pointer to the previous element in the linked list, NULL for the head node */
struct _http_req_res_t *prev;
/** private data used by http dissector */
void* private_data;
} http_req_res_t;
/** Conversation data of a HTTP connection. */
@ -86,6 +88,11 @@ typedef struct _http_conv_t {
* startoffset on connections that have proxied/tunneled/Upgraded.
*/
/* TRUE means current message is chunked streaming, and not ended yet.
* This is only meaningful during the first scan.
*/
gboolean message_ended;
} http_conv_t;
typedef enum _http_type {

View File

@ -848,7 +848,8 @@ dissect_rtspmessage(tvbuff_t *tvb, int offset, packet_info *pinfo,
* assumes zero if missing.
*/
if (!req_resp_hdrs_do_reassembly(tvb, offset, pinfo,
rtsp_desegment_headers, rtsp_desegment_body, FALSE, NULL)) {
rtsp_desegment_headers, rtsp_desegment_body, FALSE, NULL,
NULL, NULL)) {
/*
* More data needed for desegmentation.
*/

View File

@ -3509,7 +3509,8 @@ dissect_sip_common(tvbuff_t *tvb, int offset, int remaining_length, packet_info
* RFC 6594, Section 20.14. requires Content-Length for TCP.
*/
if (!req_resp_hdrs_do_reassembly(tvb, offset, pinfo,
sip_desegment_headers, sip_desegment_body, FALSE, NULL)) {
sip_desegment_headers, sip_desegment_body, FALSE, NULL,
NULL, NULL)) {
/*
* More data needed for desegmentation.
*/

View File

@ -3175,6 +3175,341 @@ reassembly_table_cleanup(void)
g_list_free(reassembly_table_list);
}
/* One instance of this structure is created for each pdu that spans across
* multiple segments. (MSP) */
typedef struct _multisegment_pdu_t {
guint64 first_frame;
guint64 last_frame;
guint start_offset_at_first_frame;
guint end_offset_at_last_frame;
gint length; /* length of this MSP */
guint32 streaming_reassembly_id;
/* pointer to previous multisegment_pdu */
struct _multisegment_pdu_t* prev_msp;
} multisegment_pdu_t;
/* struct for keeping the reassembly information of each stream */
struct streaming_reassembly_info_t {
/* This map is keyed by frame num and keeps track of all MSPs for this
* stream. Different frames will point to the same MSP if they contain
* part data of this MSP. If a frame contains data that
* belongs to two MSPs, it will point to the second MSP. */
wmem_map_t* multisegment_pdus;
/* This map is keyed by frame num and keeps track of the frag_offset
* of the first byte of frames for fragment_add() after first scan. */
wmem_map_t* frame_num_frag_offset_map;
/* how many bytes the current uncompleted MSP still needs. (only valid for first scan) */
gint prev_deseg_len;
/* the current uncompleted MSP (only valid for first scan) */
multisegment_pdu_t* last_msp;
};
static guint32
create_streaming_reassembly_id(void)
{
static guint32 global_streaming_reassembly_id = 0;
return ++global_streaming_reassembly_id;
}
streaming_reassembly_info_t*
streaming_reassembly_info_new(void)
{
return wmem_new0(wmem_file_scope(), streaming_reassembly_info_t);
}
/* Following is an example of ProtoA and ProtoB protocols from the declaration of this function in 'reassemble.h':
*
* +------------------ A Multisegment PDU of ProtoB ----------------------+
* | |
* +--- ProtoA payload1 ---+ +- payload2 -+ +- Payload3 -+ +- Payload4 -+ +- ProtoA payload5 -+
* | EoMSP | OmNFP | BoMSP | | MoMSP | | MoMSP | | MoMSP | | EoMSP | BoMSP |
* +-------+-------+-------+ +------------+ +------------+ +------------+ +---------+---------+
* | |
* +----------------------------------------------------------------------+
*
* For a ProtoA payload composed of EoMSP + OmNFP + BoMSP will call fragment_add() twice on EoMSP and BoMSP; and call
* process_reassembled_data() once for generating tvb of a MSP to which EoMSP belongs; and call subdissector twice on
* reassembled MSP of EoMSP and OmNFP + BoMSP. After that finds BoMSP is a beginning of a MSP at first scan.
*
* The rules are:
*
* - If a ProtoA payload contains EoMSP, we will need call fragment_add(), process_reassembled_data() and subdissector
* once on it to end a MSP. (May run twice or more times at first scan, because subdissector may only return the
* head length of message by pinfo->desegment_len. We need run second time for subdissector to determine the length
* of entire message).
*
* - If a ProtoA payload contains OmNFP, we will need only call subdissector once on it. The subdissector need dissect
* all non-fragment PDUs in it. (no desegment_len should output)
*
* - If a ProtoA payload contains BoMSP, we will need call subdissector once on BoMSP or OmNFP+BoMSP (because unknown
* during first scan). The subdissector will output desegment_len (!= 0). Then we will call fragment_add()
* with a new reassembly id on BoMSP for starting a new MSP.
*
* - If a ProtoA payload only contains MoMSP (entire payload is part of a MSP), we will only call fragment_add() once
* or twice (at first scan) on it. The subdissector will not be called.
*
* In this implementation, only multisegment PDUs are recorded in multisegment_pdus map keyed by the numbers (guint64)
* of frames belongs to MSPs. Each MSP in the map has a pointer referred to previous MSP, because we may need
* two MSPs to dissect a ProtoA payload that contains EoMSP + BoMSP at the same time. The multisegment_pdus map is built
* during first scan (pinfo->visited == FALSE) with help of prev_deseg_len and last_msp fields of streaming_reassembly_info_t
* for each direction of a ProtoA STREAM. The prev_deseg_len record how many bytes of subsequent ProtoA payloads belong to
* previous PDU during first scan. The last_msp member of streaming_reassembly_info_t is always point to last MSP which
* is created during scan previous or early ProtoA payloads. Since subdissector might return only the head length of entire
* message (by pinfo->desegment_len) when there is not enough data to determine the message length, we need to reopen
* reassembly fragments for adding more bytes during scanning the next ProtoA payload. We have to use fragment_add()
* instead of fragment_add_check() or fragment_add_seq_next().
*
* Read more: please refer to comments of the declaration of this function in 'reassemble.h'.
*/
gint
reassemble_streaming_data_and_call_subdissector(
tvbuff_t* tvb, packet_info* pinfo, guint offset, gint length,
proto_tree* segment_tree, proto_tree* reassembled_tree, reassembly_table streaming_reassembly_table,
streaming_reassembly_info_t* reassembly_info, guint64 cur_frame_num,
dissector_handle_t subdissector_handle, proto_tree* subdissector_tree, void* subdissector_data,
const char* label, const fragment_items* frag_hf_items, int hf_segment_data
)
{
gint orig_length = length;
gint datalen = 0;
gint bytes_belong_to_prev_msp = 0; /* bytes belong to previous MSP */
guint32 reassembly_id = 0, frag_offset = 0;
fragment_head* head = NULL;
gboolean need_more = FALSE;
multisegment_pdu_t* cur_msp = NULL, * prev_msp = NULL;
guint16 save_can_desegment;
int save_desegment_offset;
guint32 save_desegment_len;
guint64* frame_ptr;
save_can_desegment = pinfo->can_desegment;
save_desegment_offset = pinfo->desegment_offset;
save_desegment_len = pinfo->desegment_len;
/* calculate how many bytes of this payload belongs to previous MSP (EoMSP) */
if (!PINFO_FD_VISITED(pinfo)) {
/* this is first scan */
if (reassembly_info->prev_deseg_len > 0) {
/* part or all of current payload belong to previous MSP */
bytes_belong_to_prev_msp = MIN(reassembly_info->prev_deseg_len, length);
reassembly_info->prev_deseg_len -= bytes_belong_to_prev_msp;
need_more = (reassembly_info->prev_deseg_len > 0);
} /* else { beginning of a new PDU (might be a NFP or MSP) } */
if (bytes_belong_to_prev_msp > 0) {
DISSECTOR_ASSERT(reassembly_info->last_msp != NULL);
reassembly_id = reassembly_info->last_msp->streaming_reassembly_id;
frag_offset = reassembly_info->last_msp->length;
if (reassembly_info->frame_num_frag_offset_map == NULL) {
reassembly_info->frame_num_frag_offset_map = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
}
frame_ptr = (guint64*)wmem_memdup(wmem_file_scope(), &cur_frame_num, sizeof(guint64));
wmem_map_insert(reassembly_info->frame_num_frag_offset_map, frame_ptr, GUINT_TO_POINTER(frag_offset));
/* This payload contains the data of previous msp, so we point to it. That may be overriden late. */
wmem_map_insert(reassembly_info->multisegment_pdus, frame_ptr, reassembly_info->last_msp);
}
} else {
/* not first scan, use information of multisegment_pdus built during first scan */
if (reassembly_info->multisegment_pdus) {
cur_msp = (multisegment_pdu_t*)wmem_map_lookup(reassembly_info->multisegment_pdus, &cur_frame_num);
}
if (cur_msp) {
if (cur_msp->first_frame == cur_frame_num) {
/* Current payload contains a beginning of a MSP. (BoMSP)
* The cur_msp contains information about the beginning MSP.
* If prev_msp is not null, that means this payload also contains
* the last part of previous MSP. (EoMSP) */
prev_msp = cur_msp->prev_msp;
} else {
/* Current payload is not a first frame of a MSP (not include BoMSP). */
prev_msp = cur_msp;
cur_msp = NULL;
}
}
if (prev_msp && prev_msp->last_frame >= cur_frame_num) {
if (prev_msp->last_frame == cur_frame_num) {
/* this payload contains part of previous MSP (contains EoMSP) */
bytes_belong_to_prev_msp = prev_msp->end_offset_at_last_frame - offset;
} else { /* if (prev_msp->last_frame > cur_frame_num) */
/* this payload all belongs to previous MSP */
bytes_belong_to_prev_msp = length;
need_more = TRUE;
}
reassembly_id = prev_msp->streaming_reassembly_id;
}
if (reassembly_info->frame_num_frag_offset_map) {
frag_offset = GPOINTER_TO_UINT(wmem_map_lookup(reassembly_info->frame_num_frag_offset_map, &cur_frame_num));
}
}
/* handling EoMSP or MoMSP (entire payload being middle part of a MSP) */
while (bytes_belong_to_prev_msp > 0) {
tvbuff_t* reassembled_tvb = NULL;
DISSECTOR_ASSERT(reassembly_id > 0);
pinfo->can_desegment = 2; /* this will be decreased one while passing to subdissector */
pinfo->desegment_offset = 0;
pinfo->desegment_len = 0;
head = fragment_add(&streaming_reassembly_table, tvb, offset, pinfo, reassembly_id, NULL,
frag_offset, bytes_belong_to_prev_msp, need_more);
if (head) {
if (frag_hf_items->hf_reassembled_in) {
proto_item_set_generated(
proto_tree_add_uint(segment_tree, *(frag_hf_items->hf_reassembled_in), tvb, offset,
bytes_belong_to_prev_msp, head->reassembled_in)
);
}
if (!need_more) {
reassembled_tvb = process_reassembled_data(tvb, offset, pinfo,
wmem_strdup_printf(pinfo->pool, "Reassembled %s", label),
head, frag_hf_items, NULL, reassembled_tree);
}
}
proto_tree_add_bytes_format(segment_tree, hf_segment_data, tvb, offset,
bytes_belong_to_prev_msp, NULL, "%s Segment data (%u byte%s)", label,
bytes_belong_to_prev_msp, plurality(bytes_belong_to_prev_msp, "", "s"));
if (reassembled_tvb) {
datalen = tvb_reported_length(reassembled_tvb);
/* normally, this stage will dissect one or more completed pdus */
/* Note, don't call_dissector_with_data because sometime the pinfo->curr_layer_num will changed
* after calling that will make reassembly failed! */
call_dissector_only(subdissector_handle, reassembled_tvb, pinfo, subdissector_tree, subdissector_data);
}
if (pinfo->desegment_len) {
/* that must only happen during first scan the reassembly_info->prev_deseg_len might be only the
* head length of entire message. */
DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo));
DISSECTOR_ASSERT_HINT(pinfo->desegment_len != DESEGMENT_ONE_MORE_SEGMENT
&& pinfo->desegment_len != DESEGMENT_UNTIL_FIN, "Subdissector MUST NOT "
"set pinfo->desegment_len to DESEGMENT_ONE_MORE_SEGMENT or DESEGMENT_UNTIL_FIN. "
"Instead, it can set pinfo->desegment_len to the length of head if the length of "
"entire message is not able to be determined.");
DISSECTOR_ASSERT_HINT(pinfo->desegment_offset == 0, "Subdissector MUST NOT set pinfo->desegment_len greater than "
"the length of one message if the length message is undetermined.");
/* Remove the data added by previous fragment_add(), and reopen fragments for adding more bytes. */
fragment_truncate(&streaming_reassembly_table, pinfo, reassembly_id, NULL, reassembly_info->last_msp->length);
fragment_set_partial_reassembly(&streaming_reassembly_table, pinfo, reassembly_id, NULL);
reassembly_info->prev_deseg_len = bytes_belong_to_prev_msp + pinfo->desegment_len;
bytes_belong_to_prev_msp = MIN(reassembly_info->prev_deseg_len, length);
reassembly_info->prev_deseg_len -= bytes_belong_to_prev_msp;
need_more = (reassembly_info->prev_deseg_len > 0);
} else {
/* We will arrive here, only when the MSP is defragmented and dissected or this
* payload all belongs to previous MSP (only fragment_add() with need_more=TRUE called)
*/
offset += bytes_belong_to_prev_msp;
length -= bytes_belong_to_prev_msp;
DISSECTOR_ASSERT(length >= 0);
if (!PINFO_FD_VISITED(pinfo)) {
reassembly_info->last_msp->length += bytes_belong_to_prev_msp;
}
if (!PINFO_FD_VISITED(pinfo) && reassembled_tvb) {
/* completed current msp */
reassembly_info->last_msp->last_frame = cur_frame_num;
reassembly_info->last_msp->end_offset_at_last_frame = offset;
reassembly_info->prev_deseg_len = 0;
}
bytes_belong_to_prev_msp = 0; /* break */
}
}
/* to find and handle OmNFP, and find BoMSP at first scan. */
if (length > 0) {
if (!PINFO_FD_VISITED(pinfo)) {
/* It is first scan, to dissect remaining bytes to find whether it is OmNFP only, or BoMSP only or OmNFP + BoMSP. */
datalen = length;
DISSECTOR_ASSERT(cur_msp == NULL);
} else {
/* Not first scan */
if (cur_msp) {
/* There's a BoMSP. Let's calculate the length of OmNFP between EoMSP and BoMSP */
datalen = cur_msp->start_offset_at_first_frame - offset; /* if result is zero that means no OmNFP */
} else {
/* This payload is not a beginning of MSP. The remaining bytes all belong to OmNFP without BoMSP */
datalen = length;
}
}
DISSECTOR_ASSERT(datalen >= 0);
/* Dissect the remaining of this payload. If (datalen == 0) means remaining only have one BoMSP without OmNFP. */
if (datalen > 0) {
/* we dissect if it is not dissected before or it is a non-fragment pdu (between two multisegment pdus) */
pinfo->can_desegment = 2;
pinfo->desegment_offset = 0;
pinfo->desegment_len = 0;
call_dissector_only(subdissector_handle, tvb_new_subset_length(tvb, offset, datalen),
pinfo, subdissector_tree, subdissector_data);
if (pinfo->desegment_len) {
/* only happen during first scan */
DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo) && datalen == length);
offset += pinfo->desegment_offset;
length -= pinfo->desegment_offset;
} else {
/* all remaining bytes are consumed by subdissector */
offset += datalen;
length -= datalen;
}
if (!PINFO_FD_VISITED(pinfo)) {
reassembly_info->prev_deseg_len = pinfo->desegment_len;
}
} /* else all remaining bytes (BoMSP) belong to a new MSP */
DISSECTOR_ASSERT(length >= 0);
}
/* handling BoMSP */
if (length > 0) {
col_append_sep_fstr(pinfo->cinfo, COL_INFO, " ", "[%s segment of a reassembled PDU] ", label);
if (!PINFO_FD_VISITED(pinfo)) {
/* create a msp for current frame during first scan */
cur_msp = wmem_new0(wmem_file_scope(), multisegment_pdu_t);
cur_msp->first_frame = cur_frame_num;
cur_msp->start_offset_at_first_frame = offset;
cur_msp->length = length;
cur_msp->streaming_reassembly_id = reassembly_id = create_streaming_reassembly_id();
cur_msp->prev_msp = reassembly_info->last_msp;
reassembly_info->last_msp = cur_msp;
if (reassembly_info->multisegment_pdus == NULL) {
reassembly_info->multisegment_pdus = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
}
frame_ptr = (guint64*)wmem_memdup(wmem_file_scope(), &cur_frame_num, sizeof(guint64));
wmem_map_insert(reassembly_info->multisegment_pdus, frame_ptr, cur_msp);
} else {
DISSECTOR_ASSERT(cur_msp && cur_msp->start_offset_at_first_frame == offset);
reassembly_id = cur_msp->streaming_reassembly_id;
}
/* add first fragment of the new MSP to reassembly table */
head = fragment_add(&streaming_reassembly_table, tvb, offset, pinfo, reassembly_id,
NULL, 0, length, TRUE);
if (head && frag_hf_items->hf_reassembled_in) {
proto_item_set_generated(
proto_tree_add_uint(segment_tree, *(frag_hf_items->hf_reassembled_in),
tvb, offset, length, head->reassembled_in)
);
}
proto_tree_add_bytes_format(segment_tree, hf_segment_data, tvb, offset, length,
NULL, "%s Segment data (%u byte%s)", label, length, plurality(length, "", "s"));
}
pinfo->can_desegment = save_can_desegment;
pinfo->desegment_offset = save_desegment_offset;
pinfo->desegment_len = save_desegment_len;
return orig_length;
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*

View File

@ -529,4 +529,324 @@ extern void reassembly_tables_init(void);
extern void
reassembly_table_cleanup(void);
/* ===================== Streaming data reassembly helper ===================== */
/** a private structure for keeping streaming reassembly information */
typedef struct streaming_reassembly_info_t streaming_reassembly_info_t;
/**
* Allocate a streaming reassembly information in wmem_file_scope.
*/
WS_DLL_PUBLIC streaming_reassembly_info_t*
streaming_reassembly_info_new(void);
/**
* This function provides a simple way to reassemble the streaming data of a higher level
* protocol that is not on top of TCP but on another protocol which might be on top of TCP.
*
* For example, suppose there are two streaming protocols ProtoA and ProtoB. ProtoA is a protocol on top
* of TCP. ProtoB is a protocol on top of ProtoA.
*
* ProtoA dissector should use tcp_dissect_pdus() or pinfo->can_desegment/desegment_offset/desegment_len
* to reassemble its own messages on top of TCP. After the PDUs of ProtoA are reassembled, ProtoA dissector
* can call reassemble_streaming_data_and_call_subdissector() to help ProtoB dissector to reassemble the
* PDUs of ProtoB. ProtoB needs to use fields pinfo->can_desegment/desegment_offset/desegment_len to tell
* its requirements about reassembly (to reassemble_streaming_data_and_call_subdissector()).
*
* ----- +-- Reassembled ProtoB PDU --+-- Reassembled ProtoB PDU --+-- Reassembled ProtoB PDU --+----------------
* ProtoB: | ProtoB header and payload | ProtoB header and payload | ProtoB header and payload | ...
* +----------------------------+---------+------------------+--------+-------------------+--+-------------
* ----- ^ >>> Reassemble with reassemble_streaming_data_and_call_subdissector() and pinfo->desegment_len.. <<< ^
* +----------------------------+---------+------------------+--------+-------------------+--+-------------
* | ProtoA payload1 | ProtoA payload2 | ProtoA payload3 | ...
* +--------------------------------------+---------------------------+----------------------+-------------
* ^ ^ ^ ^
* | >>> Do de-chunk <<< |\ >>> Do de-chunk <<< \ \ >>> Do de-chunk <<< \
* | | \ \ \ \
* | | \ \ \ ...
* | | \ \ \ \
* +-------- First Reassembled ProtoA PDU ---------+-- Second Reassembled ProtoA PDU ---+- Third Reassembled Prot...
* ProtoA: | Header | ProtoA payload1 | Header | ProtoA payload2 | Header | ProtoA payload3 .
* +--------+----------------------+---------------+--------+---------------------------+--------+-+----------------
* ----- ^ >>> Reassemble with tcp_dissect_pdus() or pinfo->can_desegment/desegment_offset/desegment_len <<< ^
* +--------+----------------------+---------------+--------+---------------------------+--------+-+----------------
* TCP: | TCP segment | TCP segment | TCP segment | ...
* ----- +-------------------------------+-------------------------------+-------------------------------+----------------
*
* The function reassemble_streaming_data_and_call_subdissector() uses fragment_add() and process_reassembled_data()
* to complete its reassembly task.
*
* The reassemble_streaming_data_and_call_subdissector() will handle many cases. The most complicated one is:
*
* +-------------------------------------- Payload of a ProtoA PDU -----------------------------------------------+
* | EoMSP: end of a multisegment PDU | OmNFP: one or more non-fragment PDUs | BoMSP: begin of a multisegment PDU |
* +----------------------------------+--------------------------------------+------------------------------------+
* Note, we use short name 'MSP' for 'Multisegment PDU', and 'NFP' for 'Non-fragment PDU'.
*
* In this case, the payload of a ProtoA PDU contains:
* - EoMSP (Part1): At the begin of the ProtoA payload, there is the last part of a multisegment PDU of ProtoB.
* - OmNFP (Part2): The middle part of ProtoA payload payload contains one or more non-fragment PDUs of ProtoB.
* - BoMSP (Part3): At the tail of the ProtoA payload, there is the begin of a new multisegment PDU of ProtoB.
*
* All of three parts are optional. For example, one ProtoA payload could contain only EoMSP, OmNFP or BoMSP; or contain
* EoMSP and OmNFP without BoMSP; or contain EoMSP and BoMSP without OmNFP; or contain OmNFP and BoMSP without
* EoMSP.
*
* +---- A ProtoB MSP ---+ +-- A ProtoB MSP --+-- A ProtoB MSP --+ +-- A ProtoB MSP --+
* | | | | | | |
* +- A ProtoA payload -+ +-------+-------+-------+ +-------+-------+ +-------+-------+ +-------+ +-------+ +-------+
* | OmNFP | BoMSP | | EoMSP | OmNFP | BoMSP | | EoMSP | BoMSP | | EoMSP | OmNFP | | BoMSP | | EoMSP | | OmNFP |
* +---------+----------+ +-------+-------+-------+ +-------+-------+ +-------+-------+ +-------+ +-------+ +-------+
* | | | | | | |
* +---------------------+ +------------------+------------------+ +------------------+
*
* And another case is the entire ProtoA payload is one of middle parts of a multisegment PDU. We call it:
* - MoMSP: The middle part of a multisegment PDU of ProtoB.
*
* Following case shows a multisegment PDU composed of [BoMSP + MoMSP + MoMSP + MoMSP + EoMSP]:
*
* +------------------ A Multisegment PDU of ProtoB ----------------------+
* | |
* +--- ProtoA payload1 ---+ +- payload2 -+ +- Payload3 -+ +- Payload4 -+ +- ProtoA payload5 -+
* | EoMSP | OmNFP | BoMSP | | MoMSP | | MoMSP | | MoMSP | | EoMSP | BoMSP |
* +-------+-------+-------+ +------------+ +------------+ +------------+ +---------+---------+
* | |
* +----------------------------------------------------------------------+
*
* The function reassemble_streaming_data_and_call_subdissector() will handle all of the above cases and manage
* the information used during the reassembly. The caller (ProtoA dissector) only needs to initialize the relevant
* variables and pass these variables and its own completed payload to this function.
*
* The subdissector (ProtoB dissector) needs to set the pinfo->desegment_len to cooperate with the function
* reassemble_streaming_data_and_call_subdissector() to complete the reassembly task.
* The pinfo->desegment_len should contain the estimated number of additional bytes required for completing
* the current PDU (MSP), and set pinfo->desegment_offset to the offset in the tvbuff at which the dissector will
* continue processing when next called. Next time the subdissector is called, it will be passed a tvbuff composed
* of the end of the data from the previous tvbuff together with desegment_len more bytes. If the dissector cannot
* tell how many more bytes it will need, it should set pinfo->desegment_len to additional bytes required for parsing
* message head. It will then be called again as soon as more data becomes available. Subdissector MUST NOT set the
* pinfo->desegment_len to DESEGMENT_ONE_MORE_SEGMENT or DESEGMENT_UNTIL_FIN, we don't support these two values yet.
*
* Following is sample code of ProtoB which on top of ProtoA mentioned above:
* <code>
* // file packet-proto-b.c
* ...
*
* static int
* dissect_proto_b(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data)
* {
* while (offset < tvb_len)
* {
* if (tvb_len - offset < PROTO_B_MESSAGE_HEAD_LEN) {
* // need at least X bytes for getting a ProtoB message
* if (pinfo->can_desegment) {
* pinfo->desegment_offset = offset;
* // calculate how many additional bytes needed to parse head of a ProtoB message
* pinfo->desegment_len = PROTO_B_MESSAGE_HEAD_LEN - (tvb_len - offset);
* return tvb_len;
* }
* ...
* }
* ...
* // e.g. length is at offset 4
* body_len = (guint)tvb_get_ntohl(tvb, offset + 4);
*
* if (tvb_len - offset - PROTO_B_MESSAGE_HEAD_LEN < body_len) {
* // need X bytes for dissecting a ProtoB message
* if (pinfo->can_desegment) {
* pinfo->desegment_offset = offset;
* // caculate how many additional bytes need to parsing body of a ProtoB message
* pinfo->desegment_len = body_len - (tvb_len - offset - PROTO_B_MESSAGE_HEAD_LEN);
* return tvb_len;
* }
* ...
* }
* ...
* }
* }
* </code>
*
* Following is sample code of ProtoA mentioned above:
* <code>
* // file packet-proto-a.c
* ...
* // reassembly table for streaming chunk mode
* static reassembly_table proto_a_streaming_reassembly_table;
* ...
* // heads for displaying reassembley information
* static int hf_msg_fragments = -1;
* static int hf_msg_fragment = -1;
* static int hf_msg_fragment_overlap = -1;
* static int hf_msg_fragment_overlap_conflicts = -1;
* static int hf_msg_fragment_multiple_tails = -1;
* static int hf_msg_fragment_too_long_fragment = -1;
* static int hf_msg_fragment_error = -1;
* static int hf_msg_fragment_count = -1;
* static int hf_msg_reassembled_in = -1;
* static int hf_msg_reassembled_length = -1;
* static int hf_msg_body_segment = -1;
* ...
* static gint ett_msg_fragment = -1;
* static gint ett_msg_fragments = -1;
* ...
* static const fragment_items msg_frag_items = {
* &ett_msg_fragment,
* &ett_msg_fragments,
* &hf_msg_fragments,
* &hf_msg_fragment,
* &hf_msg_fragment_overlap,
* &hf_msg_fragment_overlap_conflicts,
* &hf_msg_fragment_multiple_tails,
* &hf_msg_fragment_too_long_fragment,
* &hf_msg_fragment_error,
* &hf_msg_fragment_count,
* &hf_msg_reassembled_in,
* &hf_msg_reassembled_length,
* "ProtoA Message fragments"
* };
* ...
* static int
* dissect_proto_a(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data)
* {
* ...
* streaming_reassembly_info_t* streaming_reassembly_info = NULL;
* ...
* proto_a_tree = proto_item_add_subtree(ti, ett_proto_a);
* ...
* if (!PINFO_FD_VISITED(pinfo)) {
* streaming_reassembly_info = streaming_reassembly_info_new();
* // save streaming reassembly info in the stream conversation or something like that
* save_reassembly_info(pinfo, stream_id, flow_dir, streaming_reassembly_info);
* } else {
* streaming_reassembly_info = get_reassembly_info(pinfo, stream_id, flow_dir);
* }
* ...
* while (offset < tvb_len)
* {
* ...
* payload_len = xxx;
* ...
* if (dissecting_in_streaming_mode) {
* // reassemble and call subdissector
* reassemble_streaming_data_and_call_subdissector(tvb, pinfo, offset,
* payload_len, proto_a_tree, proto_tree_get_parent_tree(proto_a_tree),
* proto_a_streaming_reassembly_table, streaming_reassembly_info,
* get_virtual_frame_num64(tvb, pinfo, offset), subdissector_handle,
* proto_tree_get_parent_tree(tree), NULL,
* "ProtoA", &msg_frag_items, hf_msg_body_segment);
* ...
* }
* }
*
* ...
* void proto_register_proto_a(void) {
* ...
* static hf_register_info hf[] = {
* ...
* {&hf_msg_fragments,
* {"Reassembled ProtoA Message fragments", "protoa.msg.fragments",
* FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_fragment,
* {"Message fragment", "protoa.msg.fragment",
* FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_fragment_overlap,
* {"Message fragment overlap", "protoa.msg.fragment.overlap",
* FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_fragment_overlap_conflicts,
* {"Message fragment overlapping with conflicting data",
* "protoa.msg.fragment.overlap.conflicts",
* FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_fragment_multiple_tails,
* {"Message has multiple tail fragments",
* "protoa.msg.fragment.multiple_tails",
* FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_fragment_too_long_fragment,
* {"Message fragment too long", "protoa.msg.fragment.too_long_fragment",
* FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_fragment_error,
* {"Message defragmentation error", "protoa.msg.fragment.error",
* FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_fragment_count,
* {"Message fragment count", "protoa.msg.fragment.count",
* FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_reassembled_in,
* {"Reassembled in", "protoa.msg.reassembled.in",
* FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_reassembled_length,
* {"Reassembled length", "protoa.msg.reassembled.length",
* FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
* {&hf_msg_body_segment,
* {"ProtoA body segment", "protoa.msg.body.segment",
* FT_BYTES, BASE_NONE, NULL, 0x00, NULL, HFILL } },
* }
* ...
* static gint *ett[] = {
* ...
* &ett_msg_fragment,
* &ett_msg_fragments
* }
* ...
* reassembly_table_register(&proto_a_streaming_reassembly_table,
* &addresses_ports_reassembly_table_functions);
* ...
* }
* </code>
*
* @param tvb TVB contains (ProtoA) payload which will be passed to subdissector.
* @param pinfo Packet information.
* @param offset The beginning offset of payload in TVB.
* @param length The length of payload in TVB.
* @param segment_tree The tree for adding segment items.
* @param reassembled_tree The tree for adding reassembled information items.
* @param streaming_reassembly_table The reassembly table used for this kind of streaming reassembly.
* @param reassembly_info The structure for keeping streaming reassembly information. This should be initialized
* by streaming_reassembly_info_new(). Subdissector should keep it for each flow of per stream,
* like per direction flow of a STREAM of HTTP/2 or each request or response message flow of
* HTTP/1.1 chunked stream.
* @param cur_frame_num The uniq index of current payload and number must always be increasing from the previous frame
* number, so we can use "<" and ">" comparisons to determine before and after in time. You can use
* get_virtual_frame_num64() if the ProtoA does not has a suitable field representing payload frame num.
* @param subdissector_handle The subdissector the reassembly for. We will call subdissector for reassembly and dissecting.
* The subdissector should set pinfo->desegment_len to the length it needed if the payload is
* not enough for it to dissect.
* @param subdissector_tree The tree to be passed to subdissector.
* @param subdissector_data The data argument to be passed to subdissector.
* @param label The name of the data being reassembling. It can just be the name of protocol (ProtoA), for
* example, "[ProtoA segment of a reassembled PDU]".
* @param frag_hf_items The fragment field items for displaying fragment and reassembly information in tree. Please
* refer to process_reassembled_data().
* @param hf_segment_data The field item to show something like "ProtoA segment data (123 bytes)".
*
* @return Handled data length. Just equal to the length argument now.
*/
WS_DLL_PUBLIC gint
reassemble_streaming_data_and_call_subdissector(
tvbuff_t* tvb, packet_info* pinfo, guint offset, gint length,
proto_tree* segment_tree, proto_tree* reassembled_tree, reassembly_table streaming_reassembly_table,
streaming_reassembly_info_t* reassembly_info, guint64 cur_frame_num,
dissector_handle_t subdissector_handle, proto_tree* subdissector_tree, void* subdissector_data,
const char* label, const fragment_items* frag_hf_items, int hf_segment_data
);
/**
* Return a 64 bits virtual frame number that is identified as follows:
*
* +--- 32 bits ---+--------- 8 bits -------+----- 24 bits --------------+
* | pinfo->num | pinfo->curr_layer_num | tvb->raw_offset + offset |
* +---------------------------------------------------------------------+
*
* This allows for a single virtual frame to be uniquely identified across a capture with the
* added benefit that the number will always be increasing from the previous virtual frame so
* we can use "<" and ">" comparisons to determine before and after in time.
*
* This frame number similar to HTTP2 frame number.
*/
static inline guint64
get_virtual_frame_num64(tvbuff_t* tvb, packet_info* pinfo, gint offset)
{
return (((guint64)pinfo->num) << 32) + (((guint64)pinfo->curr_layer_num) << 24)
+ ((guint64)tvb_raw_offset(tvb) + offset);
}
/* ========================================================================= */
#endif

View File

@ -27,7 +27,8 @@
gboolean
req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
const gboolean desegment_headers, const gboolean desegment_body,
gboolean desegment_until_fin, int *last_chunk_offset)
gboolean desegment_until_fin, int *last_chunk_offset,
dissector_table_t streaming_subdissector_table, dissector_handle_t *streaming_chunk_handle)
{
gint next_offset = offset;
gint next_offset_sav;
@ -40,6 +41,24 @@ req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
gboolean chunked_encoding = FALSE;
gchar *line;
gchar *content_type = NULL;
dissector_handle_t streaming_handle = NULL;
gboolean streaming_chunk_mode = FALSE;
DISSECTOR_ASSERT_HINT((streaming_subdissector_table && streaming_chunk_handle)
|| (streaming_subdissector_table == NULL && streaming_chunk_handle == NULL),
"The streaming_subdissector_table and streaming_chunk_handle arguments must "
"be both given or both NULL.");
/* Check whether the first line is the beginning of a chunk.
* If it is the beginning of a chunk, we assume we are working
* in streaming chunk mode. The headers of HTTP request or response
* and at least one chunk should have been dissected in the previous
* packets and now we are processing subsequent chunks.
*/
if (desegment_body && streaming_subdissector_table
&& starts_with_chunk_size(tvb, offset, pinfo)) {
streaming_chunk_mode = TRUE;
}
/*
* Do header desegmentation if we've been told to.
@ -77,8 +96,11 @@ req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
* marking end of headers) or request one more byte (we
* don't know how many bytes we'll need, so we just ask
* for one).
*
* If tvb starts with chunk size, then we just ignore headers parsing.
*/
if (desegment_headers && pinfo->can_desegment) {
if (!streaming_chunk_mode
&& desegment_headers && pinfo->can_desegment) {
for (;;) {
next_offset_sav = next_offset;
@ -161,6 +183,10 @@ req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
while (*content_type == ' ') {
content_type++;
}
g_strchomp(content_type);
if (streaming_subdissector_table) {
streaming_handle = dissector_get_string_handle(streaming_subdissector_table, content_type);
}
} else if (g_ascii_strncasecmp( line, "Transfer-Encoding:", 18) == 0) {
/*
* Find out if this Transfer-Encoding is
@ -201,6 +227,14 @@ req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
}
}
if (streaming_chunk_mode) {
/* the tvb starts with chunk size without HTTP headers */
chunked_encoding = TRUE;
} else if (desegment_body && chunked_encoding && streaming_handle && streaming_chunk_handle) {
streaming_chunk_mode = TRUE;
*streaming_chunk_handle = streaming_handle;
}
/*
* The above loop ends when we reached the end of the headers, so
* there should be content_length bytes after the 4 terminating bytes
@ -217,6 +251,12 @@ req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
* data until we reach the end of the stream, or a
* zero sized chunk.
*
* But if streaming_chunk_mode is TRUE,
* we will just pull one more chunk if the end of
* this tvb is in middle of a chunk. Because we want
* to dissect chunks with subdissector as soon as
* possible.
*
* XXX
* This doesn't bother with trailing headers; I don't
* think they are really used, and we'd have to use
@ -238,6 +278,10 @@ req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
tvb_reported_length_remaining(tvb,
next_offset);
if (reported_length_remaining == 0 && streaming_chunk_mode) {
return TRUE;
}
if (reported_length_remaining < 1) {
pinfo->desegment_offset = offset;
pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
@ -325,9 +369,20 @@ req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
* Fetch this chunk, plus the
* trailing CRLF.
*/
pinfo->desegment_offset = offset;
pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
return FALSE;
if (streaming_chunk_mode) {
gint size_remaining = chunk_size + linelen + 4 - reported_length_remaining;
if (size_remaining == 0) {
return TRUE;
} else {
pinfo->desegment_offset = offset;
pinfo->desegment_len = size_remaining;
return FALSE;
}
} else {
pinfo->desegment_offset = offset;
pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
return FALSE;
}
}
}

View File

@ -30,11 +30,44 @@
* found. The result can be fed back into a future call in order to skip
* to a later chunk and reduce processing from O(N^2) to O(N). Use 0 for
* the initial call. Only set when chunked TE is found. May be NULL.
* @param streaming_subdissector_table For searching a streaming reassembly
* mode supported subdissector on it by the content-type header value.
* @param[out] streaming_chunk_handle Only set when this is the beginning of
* a chunk stream. (There is 'Transfer-Encoding: chunked' header and a
* streaming reassembly mode supported subdissector is found according to
* Content-Type header)
* @return TRUE if desegmentation is complete otherwise FALSE
*/
WS_DLL_PUBLIC gboolean
req_resp_hdrs_do_reassembly(tvbuff_t *tvb, const int offset, packet_info *pinfo,
const gboolean desegment_headers, const gboolean desegment_body,
gboolean desegment_until_fin, int *last_chunk_offset);
gboolean desegment_until_fin, int *last_chunk_offset,
dissector_table_t streaming_subdissector_table, dissector_handle_t *streaming_chunk_handle);
/** Check whether the first line is the beginning of a chunk. */
static inline gboolean
starts_with_chunk_size(tvbuff_t* tvb, const int offset, packet_info* pinfo)
{
guint chunk_size = 0;
gint linelen = tvb_find_line_end(tvb, offset, tvb_reported_length_remaining(tvb, offset), NULL, TRUE);
if (linelen < 0)
return FALSE;
gchar* chunk_string = tvb_get_string_enc(pinfo->pool, tvb, offset, linelen, ENC_ASCII);
gchar* c = chunk_string;
/* ignore extensions */
if ((c = strchr(c, ';'))) {
*c = '\0';
}
if (sscanf(chunk_string, "%x", &chunk_size) < 1) {
return FALSE; /* can not get chunk size*/
} else if (chunk_size > (1U << 31)) {
return FALSE; /* chunk size is unreasonable */
}
return TRUE;
}
#endif

View File

@ -1418,6 +1418,7 @@ libwireshark.so.0 libwireshark0 #MINVER#
read_keytab_file@Base 1.9.1
read_keytab_file_from_preferences@Base 1.9.1
read_prefs_file@Base 1.9.1
reassemble_streaming_data_and_call_subdissector@Base 4.1.0
reassembly_table_destroy@Base 1.9.1
reassembly_table_init@Base 1.9.1
reassembly_table_register@Base 2.3.0
@ -1644,6 +1645,7 @@ libwireshark.so.0 libwireshark0 #MINVER#
stream_find_frag@Base 1.9.1
stream_new@Base 3.5.0
stream_process_reassembled@Base 1.9.1
streaming_reassembly_info_new@Base 4.1.0
string_to_name_resolve@Base 1.9.1
sua_co_class_type_acro_values@Base 1.9.1
t30_facsimile_control_field_vals_ext@Base 1.12.0~rc1

Binary file not shown.

View File

@ -355,6 +355,43 @@ class case_dissect_grpc_web(subprocesstest.SubprocessTestCase):
self.assertEqual(self.countOutput('greet.HelloRequest'), 1)
self.assertEqual(self.countOutput('greet.HelloReply'), 1)
def test_grpc_web_server_stream_over_http1(self, cmd_tshark, features, dirs, capture_file):
'''gRPC-Web data server stream over http1'''
well_know_types_dir = os.path.join(dirs.protobuf_lang_files_dir, 'well_know_types').replace('\\', '/')
user_defined_types_dir = os.path.join(dirs.protobuf_lang_files_dir, 'user_defined_types').replace('\\', '/')
self.assertRun((cmd_tshark,
'-r', capture_file('grpc_web.pcapng.gz'),
'-o', 'uat:protobuf_search_paths: "{}","{}"'.format(well_know_types_dir, 'FALSE'),
'-o', 'uat:protobuf_search_paths: "{}","{}"'.format(user_defined_types_dir, 'TRUE'),
'-o', 'protobuf.preload_protos: TRUE',
'-o', 'protobuf.pbf_as_hf: TRUE',
'-d', 'tcp.port==57226,http',
'-Y', '(tcp.stream eq 9) && ((pbf.greet.HelloRequest.name && grpc.message_length == 10)'
'|| (pbf.greet.HelloReply.message && grpc.message_length == 18))',
))
self.assertTrue(self.grepOutput('GRPC-Web'))
self.assertEqual(self.countOutput('greet.HelloRequest'), 1)
self.assertEqual(self.countOutput('greet.HelloReply'), 9)
def test_grpc_web_reassembly_and_stream_over_http1(self, cmd_tshark, features, dirs, capture_file):
'''gRPC-Web data reassembly and server stream over http1'''
well_know_types_dir = os.path.join(dirs.protobuf_lang_files_dir, 'well_know_types').replace('\\', '/')
user_defined_types_dir = os.path.join(dirs.protobuf_lang_files_dir, 'user_defined_types').replace('\\', '/')
self.assertRun((cmd_tshark,
'-r', capture_file('grpc_web.pcapng.gz'),
'-o', 'uat:protobuf_search_paths: "{}","{}"'.format(well_know_types_dir, 'FALSE'),
'-o', 'uat:protobuf_search_paths: "{}","{}"'.format(user_defined_types_dir, 'TRUE'),
'-o', 'protobuf.preload_protos: TRUE',
'-o', 'protobuf.pbf_as_hf: TRUE',
'-d', 'tcp.port==57226,http',
'-Y', '(tcp.stream eq 10) && ((pbf.greet.HelloRequest.name && grpc.message_length == 80004)'
'|| (pbf.greet.HelloReply.message && (grpc.message_length == 23 || grpc.message_length == 80012)))',
))
self.assertTrue(self.grepOutput('GRPC-Web'))
self.assertEqual(self.countOutput('greet.HelloRequest'), 2)
self.assertEqual(self.countOutput('greet.HelloReply'), 6)
@fixtures.mark_usefixtures('test_env')
@fixtures.uses_fixtures