HTTP3, QUIC: Desegment HTTP3 QPACK Encoder Streams

Return the number of bytes decoded and placed in the tree and
set pinfo->desegment_offset and desegment_len so that the QUIC
disssector can desegment the HTTP3 Encoder stream.

Pass that number of bytes to the nghttp3 decoder so that we don't
end up passing the same bytes twice with reassembly.

Make it so the QUIC data stream desegmenting code puts a link
to the frame data was reassembled in for segments that begin
an MSP as well in more cases, as the TCP dissector does.
(There are a few more cases TODO to produce results similar to
TCP.)

Fix #19475
This commit is contained in:
John Thacker 2023-12-09 22:05:28 -05:00
parent 17d479f876
commit 2e5f5ab645
5 changed files with 75 additions and 43 deletions

View File

@ -1796,14 +1796,14 @@ dissect_http3_qpack_encoder_stream(tvbuff_t *tvb, packet_info *pinfo _U_, proto_
ENDTRY;
}
return offset + decoded;
return decoded;
}
static gint
dissect_http3_qpack_enc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
quic_stream_info *stream_info, http3_stream_info_t *http3_stream)
{
gint remaining, remaining_captured, retval; //, decoded = 0;
gint remaining, remaining_captured, retval, decoded = 0;
proto_item * qpack_update;
proto_tree * qpack_update_tree;
http3_session_info_t *http3_session;
@ -1821,38 +1821,39 @@ dissect_http3_qpack_enc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int
*/
qpack_update = proto_tree_add_item(tree, hf_http3_qpack_encoder, tvb, offset, remaining, ENC_NA);
qpack_update_tree = proto_item_add_subtree(qpack_update, ett_http3_qpack_update);
/* decoded = */ dissect_http3_qpack_encoder_stream(tvb, pinfo, qpack_update_tree, offset,
decoded = dissect_http3_qpack_encoder_stream(tvb, pinfo, qpack_update_tree, offset,
http3_stream);
if (decoded < remaining) {
pinfo->desegment_offset = offset + decoded;
pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
}
#ifdef HAVE_NGHTTP3
if (remaining > 0) {
proto_item * ti;
http3_stream_dir packet_direction = http3_packet_get_direction(stream_info);
nghttp3_qpack_decoder *decoder = http3_session->qpack_decoder[packet_direction];
/* XXX: We could use what's in this decoder state in order to help
* defragment encoded QPACK data that is split across multiple
* packets. (The nghttp3_qpack_decoder handles this already; that
* would be for improving what we put in the proto tree.)
*/
http3_qpack_encoder_state_t *encoder_state = http3_get_qpack_encoder_state(pinfo, tvb, offset);
if (!PINFO_FD_VISITED(pinfo)) {
uint8_t *qpack_buf = (uint8_t *)tvb_memdup(pinfo->pool, tvb, offset, remaining);
/*
* Pass the entire stream buffer to the decoder; we stop adding
* instructions that are split across packet boundaries,
* but the nghttp3_qpack_decoder saves states.
* Since we are now defragmenting, pass only the number of bytes
* decoded to the nghttp3_qpack_decoder. Otherwise, we'll end up
* sending the same bytes to the decoder again when the packet
* is defragmented.
*/
gint qpack_buf_len = remaining;
uint8_t *qpack_buf = (uint8_t *)tvb_memdup(pinfo->pool, tvb, offset, decoded);
gint qpack_buf_len = decoded;
/*
* Get the instr count prior to processing the data.
*/
uint64_t icnt_before = nghttp3_qpack_decoder_get_icnt(decoder);
HTTP3_DISSECTOR_DPRINTF("decode encoder stream: decoder=%p remaining=%u", decoder, remaining);
HTTP3_DISSECTOR_DPRINTF("decode encoder stream: decoder=%p decoded=%u remaining=%u", decoder, decoded, remaining);
encoder_state->nread = nghttp3_qpack_decoder_read_encoder(decoder, qpack_buf, qpack_buf_len);
encoder_state->icnt = nghttp3_qpack_decoder_get_icnt(decoder);
@ -1867,7 +1868,7 @@ dissect_http3_qpack_enc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int
quic_cid_t quic_cid = {.len = 0};
gboolean initial_cid_found = quic_conn_data_get_conn_client_dcid_initial(pinfo, &quic_cid);
proto_tree_add_expert_format(
tree, pinfo, &ei_http3_qpack_failed, tvb, offset, 0, "QPAC decoder %p DCID %s [found=%d] error %d (%s)",
tree, pinfo, &ei_http3_qpack_failed, tvb, offset, 0, "QPACK decoder %p DCID %s [found=%d] error %d (%s)",
decoder, cid_to_string(&quic_cid, pinfo->pool), initial_cid_found, (int)encoder_state->nread, nghttp3_strerror((int)encoder_state->nread));
}
@ -1884,7 +1885,7 @@ dissect_http3_qpack_enc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int
#else
(void)stream_info;
(void)qpack_update;
/*(void)decoded;*/
(void)decoded;
#endif /* HAVE_NGHTTP3 */
return retval;

View File

@ -1626,13 +1626,11 @@ again:
/* Did the subdissector ask us to desegment some more data
* before it could handle the packet?
* If so we have to create some structures in our table but
* this is something we only do the first time we see this
* packet.
* If so we'll have to handle that later.
*/
if (pinfo->desegment_len) {
must_desegment = TRUE;
if (!PINFO_FD_VISITED(pinfo)) {
must_desegment = TRUE;
if (msp)
msp->flags &= ~MSP_FLAGS_GOT_ALL_SEGMENTS;
}
@ -1753,33 +1751,42 @@ again:
}
}
if (must_desegment && !PINFO_FD_VISITED(pinfo)) {
// TODO handle DESEGMENT_UNTIL_FIN if needed, maybe use the FIN bit?
if (must_desegment) {
guint32 deseg_seq = seq + (deseg_offset - offset);
if (((nxtseq - deseg_seq) <= 1024*1024)
&& (!PINFO_FD_VISITED(pinfo))) {
if(pinfo->desegment_len == DESEGMENT_ONE_MORE_SEGMENT) {
/* The subdissector asked to reassemble using the
* entire next segment.
* Just ask reassembly for one more byte
* but set this msp flag so we can pick it up
* above.
*/
msp = pdu_store_sequencenumber_of_next_pdu(pinfo, deseg_seq,
nxtseq+1, stream->multisegment_pdus);
msp->flags |= MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT;
} else {
msp = pdu_store_sequencenumber_of_next_pdu(pinfo,
deseg_seq, nxtseq+pinfo->desegment_len, stream->multisegment_pdus);
}
if (!PINFO_FD_VISITED(pinfo)) {
// TODO handle DESEGMENT_UNTIL_FIN if needed, maybe use the FIN bit?
if ((nxtseq - deseg_seq) <= 1024*1024) {
if(pinfo->desegment_len == DESEGMENT_ONE_MORE_SEGMENT) {
/* The subdissector asked to reassemble using the
* entire next segment.
* Just ask reassembly for one more byte
* but set this msp flag so we can pick it up
* above.
*/
msp = pdu_store_sequencenumber_of_next_pdu(pinfo, deseg_seq,
nxtseq+1, stream->multisegment_pdus);
msp->flags |= MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT;
} else {
msp = pdu_store_sequencenumber_of_next_pdu(pinfo,
deseg_seq, nxtseq+pinfo->desegment_len, stream->multisegment_pdus);
}
/* add this segment as the first one for this new pdu */
fragment_add(&quic_reassembly_table, tvb, deseg_offset,
pinfo, reassembly_id, NULL,
0, nxtseq - deseg_seq,
nxtseq < msp->nxtpdu);
/* add this segment as the first one for this new pdu */
fragment_add(&quic_reassembly_table, tvb, deseg_offset,
pinfo, reassembly_id, NULL,
0, nxtseq - deseg_seq,
nxtseq < msp->nxtpdu);
}
} else {
/* If this is not the first time we have seen the packet, then
* the MSP should already be created. Retrieve it to see if we
* know what later frame the PDU is reassembled in.
*/
if ((msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32(stream->multisegment_pdus, deseg_seq))) {
fh = fragment_get(&quic_reassembly_table, pinfo, reassembly_id, NULL);
}
}
}
@ -1794,6 +1801,10 @@ again:
0, fh->reassembled_in);
proto_item_set_generated(item);
}
/* TODO: Show what's left in the packet as a raw QUIC "segment", like
* packet-tcp.c does here.
*/
}
pinfo->can_desegment = 0;
pinfo->desegment_offset = 0;

Binary file not shown.

View File

@ -177,6 +177,7 @@ def features(cmd_tshark, make_env):
have_lua='with Lua' in tshark_v,
have_lua_unicode='(with UfW patches)' in tshark_v,
have_nghttp2='with nghttp2' in tshark_v,
have_nghttp3='with nghttp3' in tshark_v,
have_kerberos='with Kerberos' in tshark_v,
have_gnutls='with GnuTLS' in tshark_v,
have_pkcs11='and PKCS #11 support' in tshark_v,

View File

@ -464,6 +464,25 @@ class TestDissectHttp2:
# Stream ID 1 bytes, decrypted and uncompressed, human readable
assert grep_output(stdout, '00000000 3a 6d 65 74 68 6f 64 3a')
class TestDissectHttp2:
def test_http3_qpack_reassembly(self, cmd_tshark, features, dirs, capture_file, test_env):
'''HTTP/3 QPACK encoder stream reassembly'''
if not features.have_nghttp3:
pytest.skip('Requires nghttp3.')
stdout = subprocess.check_output((cmd_tshark,
'-r', capture_file('http3-qpack-reassembly-anon.pcapng'),
'-Y', 'http3.frame_type == "HEADERS"',
'-T', 'fields', '-e', 'http3.headers.method',
'-e', 'http3.headers.authority',
'-e', 'http3.headers.referer', '-e', 'http3.headers.user_agent',
'-e', 'http3.qpack.encoder.icnt'
), encoding='utf-8', env=test_env)
assert grep_output(stdout, 'POST')
assert grep_output(stdout, 'googlevideo.com')
assert grep_output(stdout, 'https://www.youtube.com')
assert grep_output(stdout, 'Mozilla/5.0')
assert grep_output(stdout, '21') # Total number of QPACK insertions
class TestDissectProtobuf:
def test_protobuf_udp_message_mapping(self, cmd_tshark, features, dirs, capture_file, test_env):
'''Test Protobuf UDP Message Mapping and parsing google.protobuf.Timestamp features'''