From ec36885eda09c17b92d9bb6faf69984475f0b2ed Mon Sep 17 00:00:00 2001 From: Huang Qiangxiong Date: Thu, 21 Oct 2021 00:26:19 +0800 Subject: [PATCH] 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 --- epan/dissectors/packet-http2.c | 53 +++++++++--------- .../grpc_stream_reassembly_sample.pcapng.gz | Bin 0 -> 6642 bytes test/suite_dissection.py | 30 ++++++++++ 3 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 test/captures/grpc_stream_reassembly_sample.pcapng.gz diff --git a/epan/dissectors/packet-http2.c b/epan/dissectors/packet-http2.c index 1664b59458..ed61babcb6 100644 --- a/epan/dissectors/packet-http2.c +++ b/epan/dissectors/packet-http2.c @@ -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; diff --git a/test/captures/grpc_stream_reassembly_sample.pcapng.gz b/test/captures/grpc_stream_reassembly_sample.pcapng.gz new file mode 100644 index 0000000000000000000000000000000000000000..f5e22e0b08fe46fea508bb326352c33c1551c4c6 GIT binary patch literal 6642 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 u)**|0bdjd~)8Fs;L_Y5$9}hZ5ZY literal 0 HcmV?d00001 diff --git a/test/suite_dissection.py b/test/suite_dissection.py index 45616d6727..8810b4a0eb 100644 --- a/test/suite_dissection.py +++ b/test/suite_dissection.py @@ -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):