http2: fix the stream mode reassembly issue

- Point all MSP related DATA frames to their MSP instead of
  using wmem_tree_lookup32_array_le().
- Add test_grpc_streaming_mode_reassembly testcase for verifying
  this feature.

close #17633
This commit is contained in:
Huang Qiangxiong 2021-10-21 00:26:19 +08:00 committed by huangqiangxiong
parent 1dfb3edac1
commit ec36885eda
3 changed files with 58 additions and 25 deletions

View File

@ -187,12 +187,14 @@ typedef struct _http2_multisegment_pdu_t {
/* struct for per-stream, per-direction streaming DATA frame reassembly */
typedef struct {
/* This tree is indexed by http2 frame num and keeps track of all pdus
* spanning multiple segments in DATAs for this direction of http2 stream. */
wmem_tree_t *multisegment_pdus;
/* This tree is indexed by http2 frame num and keeps track of the frag_offset
/* This map is keyed by http2 frame num and keeps track of all MSPs for this
* direction of http2 stream. Different http2 frames will point to the same
* MSP if they contain the data of this MSP. If a frame contains the data of
* two MSPs, it will point to the second MSP. */
wmem_map_t *multisegment_pdus;
/* This map is keyed by http2 frame num and keeps track of the frag_offset
* of the first byte of a DATA frame for fragment_add() after first scan. */
wmem_tree_t *http2fn_frag_offset;
wmem_map_t *http2fn_frag_offset;
/* how many bytes the current uncompleted MSP still need. (only valid for first scan) */
gint prev_deseg_len;
/* the current uncompleted MSP (only valid for first scan) */
@ -2559,7 +2561,7 @@ dissect_http2_data_partial_body(tvbuff_t *tvb, packet_info *pinfo, http2_session
* Part1.
*
* And another case is the entire DATA is one of middle parts of a multisegment PDU. We call it MoMSP. Following
* case shows a multisegment PDU composed of Part3 + MoMSP + MoMSP + MoMSP + Part3:
* case shows a multisegment PDU composed of Part3 + MoMSP + MoMSP + MoMSP + Part1:
*
* +-------------------------------- One Multisegment PDU ---------------------------------+
* | |
@ -2580,15 +2582,15 @@ dissect_http2_data_partial_body(tvbuff_t *tvb, packet_info *pinfo, http2_session
* of entire message).
* - If a DATA contains Part2, 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 DATA contains Part3, we will need call subdissector once on Part3 or Parts2+3 (because unknown
* - If a DATA contains Part3, we will need call subdissector once on Part3 or Part2+3 (because unknown
* during first scan). The subdissector will output desegment_len (!= 0). Then we will call fragment_add()
* with a new reassembly id on Part3 for starting a new MSP.
* - If a DATA only contains MoMSP (entire DATA 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 tree with first frame
* number (guint64) as key. Each MSP in the tree has a pointer referred to previous MSP, because we may need
* two MSPs to dissect a DATA contains Part1 + Part3 at the same time. The multisegment_pdus tree is built during
* 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 DATA that contains Part1 + Part3 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 http2_streaming_reassembly_info_t
* for each direction of a HTTP2 STREAM. The prev_deseg_len record how many bytes of next DATAs belong to previous
* PDU during first scan. The last_msp member of http2_streaming_reassembly_info_t is always point to last MSP which
@ -2611,10 +2613,9 @@ reassemble_http2_data_according_to_subdissector(tvbuff_t *tvb, packet_info *pinf
{
guint orig_offset = offset;
gint orig_length = length;
wmem_tree_key_t key[2];
gint datalen = 0;
gint bytes_belong_to_prev_msp = 0; /* bytes belongs to previous MSP */
guint32 num32[2], reassembly_id = 0, frag_offset = 0;
guint32 reassembly_id = 0, frag_offset = 0;
fragment_head *head = NULL;
gboolean need_more = FALSE;
http2_multisegment_pdu_t *cur_msp = NULL, *prev_msp = NULL;
@ -2623,17 +2624,11 @@ reassemble_http2_data_according_to_subdissector(tvbuff_t *tvb, packet_info *pinf
guint16 save_can_desegment;
int save_desegment_offset;
guint32 save_desegment_len;
http2_frame_num_t *frame_ptr;
proto_tree_add_bytes_format(http2_tree, hf_http2_data_data, tvb, offset,
length, NULL, "DATA payload (%u byte%s)", length, plurality(length, "", "s"));
num32[0] = (guint32) (cur_frame_num >> 32);
num32[1] = (guint32) cur_frame_num;
key[0].length = 2;
key[0].key = num32;
key[1].length = 0;
key[1].key = NULL;
save_can_desegment = pinfo->can_desegment;
save_desegment_offset = pinfo->desegment_offset;
save_desegment_len = pinfo->desegment_len;
@ -2653,13 +2648,18 @@ reassemble_http2_data_according_to_subdissector(tvbuff_t *tvb, packet_info *pinf
reassembly_id = reassembly_info->last_msp->streaming_reassembly_id;
frag_offset = reassembly_info->last_msp->length;
if (reassembly_info->http2fn_frag_offset == NULL) {
reassembly_info->http2fn_frag_offset = wmem_tree_new(wmem_file_scope());
reassembly_info->http2fn_frag_offset = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
}
wmem_tree_insert32_array(reassembly_info->http2fn_frag_offset, key, GUINT_TO_POINTER(frag_offset));
frame_ptr = (http2_frame_num_t*)wmem_memdup(wmem_file_scope(), &cur_frame_num, sizeof(http2_frame_num_t));
wmem_map_insert(reassembly_info->http2fn_frag_offset, frame_ptr, GUINT_TO_POINTER(frag_offset));
/* This DATA contains the data of previous msp, so we point to it. That may be override 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 */
cur_msp = (http2_multisegment_pdu_t *) wmem_tree_lookup32_array_le(reassembly_info->multisegment_pdus, key);
if (reassembly_info->multisegment_pdus) {
cur_msp = (http2_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 DATA contains a beginning of a MSP. (Part3)
@ -2685,7 +2685,9 @@ reassemble_http2_data_according_to_subdissector(tvbuff_t *tvb, packet_info *pinf
}
reassembly_id = prev_msp->streaming_reassembly_id;
}
frag_offset = GPOINTER_TO_UINT(wmem_tree_lookup32_array(reassembly_info->http2fn_frag_offset, key));
if (reassembly_info->http2fn_frag_offset) {
frag_offset = GPOINTER_TO_UINT(wmem_map_lookup(reassembly_info->http2fn_frag_offset, &cur_frame_num));
}
}
/* handling Part1 or MoMSP (entire DATA being middle part of a MSP) */
@ -2814,9 +2816,10 @@ reassemble_http2_data_according_to_subdissector(tvbuff_t *tvb, packet_info *pinf
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_tree_new(wmem_file_scope());
reassembly_info->multisegment_pdus = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
}
wmem_tree_insert32_array(reassembly_info->multisegment_pdus, key, cur_msp);
frame_ptr = (http2_frame_num_t*)wmem_memdup(wmem_file_scope(), &cur_frame_num, sizeof(http2_frame_num_t));
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;

Binary file not shown.

View File

@ -165,6 +165,36 @@ class case_dissect_grpc(subprocesstest.SubprocessTestCase):
self.assertTrue(self.grepOutput('tutorial.PersonSearchService/Search')) # grpc request
self.assertTrue(self.grepOutput('tutorial.Person')) # grpc response
def test_grpc_streaming_mode_reassembly(self, cmd_tshark, features, dirs, capture_file):
'''gRPC/HTTP2 streaming mode reassembly'''
if not features.have_nghttp2:
self.skipTest('Requires nghttp2.')
self.assertRun((cmd_tshark,
'-r', capture_file('grpc_stream_reassembly_sample.pcapng.gz'),
'-d', 'tcp.port==50051,http2',
'-d', 'tcp.port==44363,http2',
'-2', # make http2.body.reassembled.in available
'-Y', # Case1: In frame28, one http DATA contains 4 completed grpc messages (json data seq=1,2,3,4).
'(frame.number == 28 && grpc && json.value.number == "1" && json.value.number == "2"'
' && json.value.number == "3" && json.value.number == "4" && http2.body.reassembled.in == 45) ||'
# Case2: In frame28, last grpc message (the 5th) only has 4 bytes, which need one more byte
# to be a message head. a completed message is reassembled in frame45. (json data seq=5)
'(frame.number == 45 && grpc && http2.body.fragment == 28 && json.value.number == "5"'
' && http2.body.reassembled.in == 61) ||'
# Case3: In frame45, one http DATA frame contains two partial fragment, one is part of grpc
# message of previous http DATA (frame28), another is first part of grpc message of next http
# DATA (which will be reassembled in next http DATA frame61). (json data seq=6)
'(frame.number == 61 && grpc && http2.body.fragment == 45 && json.value.number == "6") ||'
# Case4: A big grpc message across frame100, frame113, frame126 and finally reassembled in frame139.
'(frame.number == 100 && grpc && http2.body.reassembled.in == 139) ||'
'(frame.number == 113 && !grpc && http2.body.reassembled.in == 139) ||'
'(frame.number == 126 && !grpc && http2.body.reassembled.in == 139) ||'
'(frame.number == 139 && grpc && json.value.number == "9") ||'
# Case5: An large grpc message of 200004 bytes.
'(frame.number == 164 && grpc && grpc.message_length == 200004)',
))
self.assertEqual(self.countOutput('DATA'), 8)
@fixtures.mark_usefixtures('test_env')
@fixtures.uses_fixtures
class case_dissect_http(subprocesstest.SubprocessTestCase):