HTTP/GRPC-Web: support dissecting chunked data in streaming reassembly mode
This commit is contained in:
parent
0bff5811f1
commit
9a4c503eec
2
AUTHORS
2
AUTHORS
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue