QUIC: add STREAM data reassembly support
Prepare for adding HTTP/3 support which depends on QUIC to provide an stream of data. Reassembly code is mostly lifted from the TCP dissector which shares similar characteristics. Bug: 13881 Ping-Bug: 16761 Change-Id: Iba07dade111b740418b8b315d0485e200cdfe9f0 Reviewed-on: https://code.wireshark.org/review/38083 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman <a.broman58@gmail.com>
This commit is contained in:
parent
1dd8bb2811
commit
a9f39a29fe
|
@ -31,6 +31,12 @@
|
|||
* configured either at the TLS Protocol preferences, or embedded in a pcapng
|
||||
* file. Sample captures and secrets can be found at:
|
||||
* https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=13881
|
||||
*
|
||||
* Limitations:
|
||||
* - STREAM offsets larger than 32-bit are unsupported.
|
||||
* - STREAM with sizes larger than 32 bit are unsupported. STREAM sizes can be
|
||||
* up to 62 bit in QUIC, but the TVB and reassembly API is limited to 32 bit.
|
||||
* - Out-of-order and overlapping STREAM frame data is not handled.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
@ -41,7 +47,9 @@
|
|||
#include <epan/to_str.h>
|
||||
#include "packet-tls-utils.h"
|
||||
#include "packet-tls.h"
|
||||
#include "packet-tcp.h" /* used for STREAM reassembly. */
|
||||
#include "packet-quic.h"
|
||||
#include <epan/reassemble.h>
|
||||
#include <epan/prefs.h>
|
||||
#include <wsutil/pint.h>
|
||||
|
||||
|
@ -154,6 +162,17 @@ static int hf_quic_af_sequence_number = -1;
|
|||
static int hf_quic_af_packet_tolerance = -1;
|
||||
static int hf_quic_af_update_max_ack_delay = -1;
|
||||
static int hf_quic_ts = -1;
|
||||
static int hf_quic_reassembled_in = -1;
|
||||
static int hf_quic_reassembled_length = -1;
|
||||
static int hf_quic_reassembled_data = -1;
|
||||
static int hf_quic_fragments = -1;
|
||||
static int hf_quic_fragment = -1;
|
||||
static int hf_quic_fragment_overlap = -1;
|
||||
static int hf_quic_fragment_overlap_conflict = -1;
|
||||
static int hf_quic_fragment_multiple_tails = -1;
|
||||
static int hf_quic_fragment_too_long_fragment = -1;
|
||||
static int hf_quic_fragment_error = -1;
|
||||
static int hf_quic_fragment_count = -1;
|
||||
|
||||
static expert_field ei_quic_connection_unknown = EI_INIT;
|
||||
static expert_field ei_quic_ft_unknown = EI_INIT;
|
||||
|
@ -166,10 +185,32 @@ static gint ett_quic_short_header = -1;
|
|||
static gint ett_quic_connection_info = -1;
|
||||
static gint ett_quic_ft = -1;
|
||||
static gint ett_quic_ftflags = -1;
|
||||
static gint ett_quic_fragments = -1;
|
||||
static gint ett_quic_fragment = -1;
|
||||
|
||||
static dissector_handle_t quic_handle;
|
||||
static dissector_handle_t tls13_handshake_handle;
|
||||
|
||||
static dissector_table_t quic_proto_dissector_table;
|
||||
|
||||
/* Fields for showing reassembly results for fragments of QUIC stream data. */
|
||||
static const fragment_items quic_stream_fragment_items = {
|
||||
&ett_quic_fragment,
|
||||
&ett_quic_fragments,
|
||||
&hf_quic_fragments,
|
||||
&hf_quic_fragment,
|
||||
&hf_quic_fragment_overlap,
|
||||
&hf_quic_fragment_overlap_conflict,
|
||||
&hf_quic_fragment_multiple_tails,
|
||||
&hf_quic_fragment_too_long_fragment,
|
||||
&hf_quic_fragment_error,
|
||||
&hf_quic_fragment_count,
|
||||
&hf_quic_reassembled_in,
|
||||
&hf_quic_reassembled_length,
|
||||
&hf_quic_reassembled_data,
|
||||
"Fragments"
|
||||
};
|
||||
|
||||
/*
|
||||
* PROTECTED PAYLOAD DECRYPTION (done in first pass)
|
||||
*
|
||||
|
@ -259,6 +300,8 @@ struct quic_cid_item {
|
|||
*/
|
||||
typedef struct _quic_stream_state {
|
||||
guint64 stream_id;
|
||||
wmem_tree_t *multisegment_pdus;
|
||||
void *subdissector_private;
|
||||
} quic_stream_state;
|
||||
|
||||
/**
|
||||
|
@ -287,6 +330,7 @@ typedef struct quic_info_data {
|
|||
quic_cid_item_t client_cids; /**< SCID of client from first Initial Packet. */
|
||||
quic_cid_item_t server_cids; /**< SCID of server from first Retry/Handshake. */
|
||||
quic_cid_t client_dcid_initial; /**< DCID from Initial Packet. */
|
||||
dissector_handle_t app_handle; /**< Application protocol handle (NULL if unknown). */
|
||||
wmem_map_t *client_streams; /**< Map from Stream ID -> STREAM info (guint64 -> quic_stream_state), sent by the client. */
|
||||
wmem_map_t *server_streams; /**< Map from Stream ID -> STREAM info (guint64 -> quic_stream_state), sent by the server. */
|
||||
} quic_info_data_t;
|
||||
|
@ -974,6 +1018,399 @@ quic_connection_destroy(gpointer data, gpointer user_data _U_)
|
|||
}
|
||||
/* QUIC Connection tracking. }}} */
|
||||
|
||||
/* QUIC Streams tracking and reassembly. {{{ */
|
||||
static reassembly_table quic_reassembly_table;
|
||||
|
||||
#ifdef HAVE_LIBGCRYPT_AEAD
|
||||
/** Perform sequence analysis for STREAM frames. */
|
||||
static quic_stream_state *
|
||||
quic_get_stream_state(packet_info *pinfo, quic_info_data_t *quic_info, gboolean from_server, guint64 stream_id)
|
||||
{
|
||||
wmem_map_t **streams_p = from_server ? &quic_info->server_streams : &quic_info->client_streams;
|
||||
wmem_map_t *streams = *streams_p;
|
||||
quic_stream_state *stream = NULL;
|
||||
|
||||
if (PINFO_FD_VISITED(pinfo)) {
|
||||
DISSECTOR_ASSERT(streams);
|
||||
stream = (quic_stream_state *)wmem_map_lookup(streams, &stream_id);
|
||||
DISSECTOR_ASSERT(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Initialize per-connection and per-stream state.
|
||||
if (!streams) {
|
||||
streams = wmem_map_new(wmem_file_scope(), wmem_int64_hash, g_int64_equal);
|
||||
*streams_p = streams;
|
||||
} else {
|
||||
stream = (quic_stream_state *)wmem_map_lookup(streams, &stream_id);
|
||||
}
|
||||
if (!stream) {
|
||||
stream = wmem_new0(wmem_file_scope(), quic_stream_state);
|
||||
stream->stream_id = stream_id;
|
||||
stream->multisegment_pdus = wmem_tree_new(wmem_file_scope());
|
||||
wmem_map_insert(streams, &stream->stream_id, stream);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
static void
|
||||
process_quic_stream(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree,
|
||||
quic_info_data_t *quic_info, quic_stream_info *stream_info)
|
||||
{
|
||||
if (quic_info->app_handle) {
|
||||
tvbuff_t *next_tvb = tvb_new_subset_remaining(tvb, offset);
|
||||
// Traverse the STREAM frame tree.
|
||||
proto_tree *top_tree = proto_tree_get_parent_tree(tree);
|
||||
//top_tree = proto_tree_get_parent_tree(top_tree);
|
||||
call_dissector_with_data(quic_info->app_handle, next_tvb, pinfo, top_tree, stream_info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reassemble stream data within a STREAM frame.
|
||||
*/
|
||||
static void
|
||||
desegment_quic_stream(tvbuff_t *tvb, int offset, int length, packet_info *pinfo,
|
||||
proto_tree *tree, quic_info_data_t *quic_info,
|
||||
quic_stream_info *stream_info,
|
||||
quic_stream_state *stream)
|
||||
{
|
||||
fragment_head *fh;
|
||||
int last_fragment_len;
|
||||
gboolean must_desegment;
|
||||
gboolean called_dissector;
|
||||
int another_pdu_follows;
|
||||
int deseg_offset;
|
||||
struct tcp_multisegment_pdu *msp;
|
||||
guint32 seq = (guint32)stream_info->stream_offset;
|
||||
const guint32 nxtseq = seq + (guint32)length;
|
||||
guint32 reassembly_id = 0;
|
||||
|
||||
// XXX fix the tvb accessors below such that no new tvb is needed.
|
||||
tvb = tvb_new_subset_length(tvb, 0, offset + length);
|
||||
|
||||
again:
|
||||
fh = NULL;
|
||||
last_fragment_len = 0;
|
||||
must_desegment = FALSE;
|
||||
called_dissector = FALSE;
|
||||
another_pdu_follows = 0;
|
||||
msp = NULL;
|
||||
|
||||
/*
|
||||
* Initialize these to assume no desegmentation.
|
||||
* If that's not the case, these will be set appropriately
|
||||
* by the subdissector.
|
||||
*/
|
||||
pinfo->desegment_offset = 0;
|
||||
pinfo->desegment_len = 0;
|
||||
|
||||
/*
|
||||
* Initialize this to assume that this segment will just be
|
||||
* added to the middle of a desegmented chunk of data, so
|
||||
* that we should show it all as data.
|
||||
* If that's not the case, it will be set appropriately.
|
||||
*/
|
||||
deseg_offset = offset;
|
||||
|
||||
/* Have we seen this PDU before (and is it the start of a multi-
|
||||
* segment PDU)?
|
||||
*/
|
||||
if ((msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32(stream->multisegment_pdus, seq)) &&
|
||||
nxtseq <= msp->nxtpdu) {
|
||||
// TODO show expert info for retransmission? Additional checks may be
|
||||
// necessary here to tell a retransmission apart from other (normal?)
|
||||
// conditions. See also similar code in packet-tcp.c.
|
||||
#if 0
|
||||
proto_tree_add_debug_text(tree, "TODO retransmission expert info frame %d stream_id=%" G_GINT64_MODIFIER "u offset=%d visited=%d reassembly_id=0x%08x",
|
||||
pinfo->num, stream->stream_id, offset, PINFO_FD_VISITED(pinfo), reassembly_id);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
/* Else, find the most previous PDU starting before this sequence number */
|
||||
if (!msp && seq > 0) {
|
||||
msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32_le(stream->multisegment_pdus, seq-1);
|
||||
}
|
||||
|
||||
{
|
||||
// A single stream can contain multiple fragments (e.g. for HTTP/3
|
||||
// HEADERS and DATA frames). Let's hope that a single stream within a
|
||||
// QUIC packet does not contain multiple partial fragments, that would
|
||||
// result in a reassembly ID collision here. If that collision becomes
|
||||
// an issue, we would have to replace "msp->first_frame" with a new
|
||||
// field in "msp" that is initialized with "stream_info->stream_offset".
|
||||
#if 0
|
||||
guint64 reassembly_id_data[2];
|
||||
reassembly_id_data[0] = stream_info->stream_id;
|
||||
reassembly_id_data[1] = msp ? msp->first_frame : pinfo->num;
|
||||
reassembly_id = wmem_strong_hash((const guint8 *)&reassembly_id_data, sizeof(reassembly_id_data));
|
||||
#else
|
||||
// XXX for debug (visibility) purposes, do not use a hash but concatenate
|
||||
reassembly_id = ((msp ? msp->first_frame : pinfo->num) << 16) | (guint32)stream_info->stream_id;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (msp && msp->seq <= seq && msp->nxtpdu > seq) {
|
||||
int len;
|
||||
|
||||
if (!PINFO_FD_VISITED(pinfo)) {
|
||||
msp->last_frame=pinfo->num;
|
||||
msp->last_frame_time=pinfo->abs_ts;
|
||||
}
|
||||
|
||||
/* OK, this PDU was found, which means the segment continues
|
||||
* a higher-level PDU and that we must desegment it.
|
||||
*/
|
||||
if (msp->flags & MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT) {
|
||||
/* The dissector asked for the entire segment */
|
||||
len = tvb_captured_length_remaining(tvb, offset);
|
||||
} else {
|
||||
len = MIN(nxtseq, msp->nxtpdu) - seq;
|
||||
}
|
||||
last_fragment_len = len;
|
||||
|
||||
fh = fragment_add(&quic_reassembly_table, tvb, offset,
|
||||
pinfo, reassembly_id, NULL,
|
||||
seq - msp->seq, len,
|
||||
nxtseq < msp->nxtpdu);
|
||||
|
||||
if (!PINFO_FD_VISITED(pinfo)
|
||||
&& msp->flags & MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT) {
|
||||
msp->flags &= (~MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT);
|
||||
|
||||
/* If we consumed the entire segment there is no
|
||||
* other pdu starting anywhere inside this segment.
|
||||
* So update nxtpdu to point at least to the start
|
||||
* of the next segment.
|
||||
* (If the subdissector asks for even more data we
|
||||
* will advance nxtpdu even further later down in
|
||||
* the code.)
|
||||
*/
|
||||
msp->nxtpdu = nxtseq;
|
||||
}
|
||||
|
||||
if( (msp->nxtpdu < nxtseq)
|
||||
&& (msp->nxtpdu >= seq)
|
||||
&& (len > 0)) {
|
||||
another_pdu_follows=msp->nxtpdu - seq;
|
||||
}
|
||||
} else {
|
||||
/* This segment was not found in our table, so it doesn't
|
||||
* contain a continuation of a higher-level PDU.
|
||||
* Call the normal subdissector.
|
||||
*/
|
||||
|
||||
stream_info->offset = seq;
|
||||
process_quic_stream(tvb, offset, pinfo, tree, quic_info, stream_info);
|
||||
called_dissector = TRUE;
|
||||
|
||||
/* 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 (pinfo->desegment_len) {
|
||||
if (!PINFO_FD_VISITED(pinfo))
|
||||
must_desegment = TRUE;
|
||||
|
||||
/*
|
||||
* Set "deseg_offset" to the offset in "tvb"
|
||||
* of the first byte of data that the
|
||||
* subdissector didn't process.
|
||||
*/
|
||||
deseg_offset = offset + pinfo->desegment_offset;
|
||||
}
|
||||
|
||||
/* Either no desegmentation is necessary, or this is
|
||||
* segment contains the beginning but not the end of
|
||||
* a higher-level PDU and thus isn't completely
|
||||
* desegmented.
|
||||
*/
|
||||
fh = NULL;
|
||||
}
|
||||
|
||||
/* is it completely desegmented? */
|
||||
if (fh) {
|
||||
/*
|
||||
* Yes, we think it is.
|
||||
* We only call subdissector for the last segment.
|
||||
* Note that the last segment may include more than what
|
||||
* we needed.
|
||||
*/
|
||||
if (fh->reassembled_in == pinfo->num) {
|
||||
/*
|
||||
* OK, this is the last segment.
|
||||
* Let's call the subdissector with the desegmented data.
|
||||
*/
|
||||
|
||||
tvbuff_t *next_tvb = tvb_new_chain(tvb, fh->tvb_data);
|
||||
add_new_data_source(pinfo, next_tvb, "Reassembled QUIC");
|
||||
stream_info->offset = seq;
|
||||
process_quic_stream(next_tvb, 0, pinfo, tree, quic_info, stream_info);
|
||||
called_dissector = TRUE;
|
||||
|
||||
int old_len = (int)(tvb_reported_length(next_tvb) - last_fragment_len);
|
||||
if (pinfo->desegment_len &&
|
||||
pinfo->desegment_offset <= old_len) {
|
||||
/*
|
||||
* "desegment_len" isn't 0, so it needs more
|
||||
* data for something - and "desegment_offset"
|
||||
* is before "old_len", so it needs more data
|
||||
* to dissect the stuff we thought was
|
||||
* completely desegmented (as opposed to the
|
||||
* stuff at the beginning being completely
|
||||
* desegmented, but the stuff at the end
|
||||
* being a new higher-level PDU that also
|
||||
* needs desegmentation).
|
||||
*/
|
||||
fragment_set_partial_reassembly(&quic_reassembly_table,
|
||||
pinfo, reassembly_id, NULL);
|
||||
|
||||
/* Update msp->nxtpdu to point to the new next
|
||||
* pdu boundary.
|
||||
*/
|
||||
if (pinfo->desegment_len == DESEGMENT_ONE_MORE_SEGMENT) {
|
||||
/* We want reassembly of at least one
|
||||
* more segment so set the nxtpdu
|
||||
* boundary to one byte into the next
|
||||
* segment.
|
||||
* This means that the next segment
|
||||
* will complete reassembly even if it
|
||||
* is only one single byte in length.
|
||||
* If this is an OoO segment, then increment the MSP end.
|
||||
*/
|
||||
msp->nxtpdu = MAX(seq + tvb_reported_length_remaining(tvb, offset), msp->nxtpdu) + 1;
|
||||
msp->flags |= MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT;
|
||||
#if 0
|
||||
} else if (pinfo->desegment_len == DESEGMENT_UNTIL_FIN) {
|
||||
tcpd->fwd->flags |= TCP_FLOW_REASSEMBLE_UNTIL_FIN;
|
||||
#endif
|
||||
} else {
|
||||
if (seq + last_fragment_len >= msp->nxtpdu) {
|
||||
/* This is the segment (overlapping) the end of the MSP. */
|
||||
msp->nxtpdu = seq + last_fragment_len + pinfo->desegment_len;
|
||||
} else {
|
||||
/* This is a segment before the end of the MSP, so it
|
||||
* must be an out-of-order segmented that completed the
|
||||
* MSP. The requested additional data is relative to
|
||||
* that end.
|
||||
*/
|
||||
msp->nxtpdu += pinfo->desegment_len;
|
||||
}
|
||||
}
|
||||
|
||||
/* Since we need at least some more data
|
||||
* there can be no pdu following in the
|
||||
* tail of this segment.
|
||||
*/
|
||||
another_pdu_follows = 0;
|
||||
offset += last_fragment_len;
|
||||
seq += last_fragment_len;
|
||||
if (tvb_captured_length_remaining(tvb, offset) > 0)
|
||||
goto again;
|
||||
} else {
|
||||
proto_item *frag_tree_item;
|
||||
proto_tree *parent_tree = proto_tree_get_parent(tree);
|
||||
show_fragment_tree(fh, &quic_stream_fragment_items,
|
||||
parent_tree, pinfo, next_tvb, &frag_tree_item);
|
||||
// TODO move tree item if needed.
|
||||
|
||||
if(pinfo->desegment_len) {
|
||||
if (!PINFO_FD_VISITED(pinfo))
|
||||
must_desegment = TRUE;
|
||||
/* See packet-tcp.h for details about this. */
|
||||
deseg_offset = fh->datalen - pinfo->desegment_offset;
|
||||
deseg_offset = tvb_reported_length(tvb) - deseg_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (must_desegment && !PINFO_FD_VISITED(pinfo)) {
|
||||
// TODO handle DESEGMENT_UNTIL_FIN if needed, maybe use the FIN bit?
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!called_dissector || pinfo->desegment_len != 0) {
|
||||
if (fh != NULL && fh->reassembled_in != 0 &&
|
||||
!(fh->flags & FD_PARTIAL_REASSEMBLY)) {
|
||||
/*
|
||||
* We know what frame this PDU is reassembled in;
|
||||
* let the user know.
|
||||
*/
|
||||
proto_item *item = proto_tree_add_uint(tree, hf_quic_reassembled_in, tvb, 0,
|
||||
0, fh->reassembled_in);
|
||||
proto_item_set_generated(item);
|
||||
}
|
||||
}
|
||||
pinfo->can_desegment = 0;
|
||||
pinfo->desegment_offset = 0;
|
||||
pinfo->desegment_len = 0;
|
||||
|
||||
if (another_pdu_follows) {
|
||||
/* there was another pdu following this one. */
|
||||
pinfo->can_desegment = 2;
|
||||
offset += another_pdu_follows;
|
||||
seq += another_pdu_follows;
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dissect_quic_stream_payload(tvbuff_t *tvb, int offset, int length, packet_info *pinfo,
|
||||
proto_tree *tree, quic_info_data_t *quic_info,
|
||||
quic_stream_info *stream_info,
|
||||
quic_stream_state *stream)
|
||||
{
|
||||
/* QUIC application data is most likely not properly dissected when
|
||||
* reassembly is not enabled. Therefore we do not even offer "desegment"
|
||||
* preference to disable reassembly.
|
||||
*/
|
||||
|
||||
pinfo->can_desegment = 2;
|
||||
desegment_quic_stream(tvb, offset, length, pinfo, tree, quic_info, stream_info, stream);
|
||||
}
|
||||
#endif /* HAVE_LIBGCRYPT_AEAD */
|
||||
/* QUIC Streams tracking and reassembly. }}} */
|
||||
|
||||
void
|
||||
quic_stream_add_proto_data(packet_info *pinfo, quic_stream_info *stream_info, void *proto_data)
|
||||
{
|
||||
quic_stream_state *stream = quic_get_stream_state(pinfo, stream_info->quic_info, stream_info->from_server, stream_info->stream_id);
|
||||
stream->subdissector_private = proto_data;
|
||||
}
|
||||
|
||||
void *quic_stream_get_proto_data(packet_info *pinfo, quic_stream_info *stream_info)
|
||||
{
|
||||
quic_stream_state *stream = quic_get_stream_state(pinfo, stream_info->quic_info, stream_info->from_server, stream_info->stream_id);
|
||||
return stream->subdissector_private;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBGCRYPT_AEAD
|
||||
static int
|
||||
|
@ -1186,6 +1623,14 @@ dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree
|
|||
if (have_tap_listener(quic_follow_tap)) {
|
||||
tap_queue_packet(quic_follow_tap, pinfo, tvb_new_subset_length(tvb, offset, (int)length));
|
||||
}
|
||||
quic_stream_state *stream = quic_get_stream_state(pinfo, quic_info, from_server, stream_id);
|
||||
quic_stream_info stream_info = {
|
||||
.stream_id = stream_id,
|
||||
.stream_offset = stream_offset,
|
||||
.quic_info = quic_info,
|
||||
.from_server = from_server,
|
||||
};
|
||||
dissect_quic_stream_payload(tvb, offset, (int)length, pinfo, ft_tree, quic_info, &stream_info, stream);
|
||||
offset += (int)length;
|
||||
}
|
||||
break;
|
||||
|
@ -1784,7 +2229,7 @@ quic_update_key(guint32 version, int hash_algo, quic_pp_state_t *pp_state)
|
|||
|
||||
/**
|
||||
* Retrieves the header protection cipher for short header packets and prepares
|
||||
* the packet protection cipher.
|
||||
* the packet protection cipher. The application layer protocol is also queried.
|
||||
*/
|
||||
static gcry_cipher_hd_t
|
||||
quic_get_1rtt_hp_cipher(packet_info *pinfo, quic_info_data_t *quic_info, gboolean from_server)
|
||||
|
@ -1833,6 +2278,17 @@ quic_get_1rtt_hp_cipher(packet_info *pinfo, quic_info_data_t *quic_info, gboolea
|
|||
// Rotate the 1-RTT key for the client and server for the next key update.
|
||||
quic_update_key(quic_info->version, quic_info->hash_algo, client_pp);
|
||||
quic_update_key(quic_info->version, quic_info->hash_algo, server_pp);
|
||||
|
||||
// For efficiency, look up the application layer protocol once. The
|
||||
// handshake must have been completed before, so ALPN is known.
|
||||
const char *proto_name = tls_get_alpn(pinfo);
|
||||
if (proto_name) {
|
||||
quic_info->app_handle = dissector_get_string_handle(quic_proto_dissector_table, proto_name);
|
||||
// If no specific handle is found, alias "h3-*" to "h3".
|
||||
if (!quic_info->app_handle && g_str_has_prefix(proto_name, "h3-")) {
|
||||
quic_info->app_handle = dissector_get_string_handle(quic_proto_dissector_table, "h3");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Header Protect cipher does not change after Key Update.
|
||||
|
@ -3068,6 +3524,7 @@ proto_register_quic(void)
|
|||
FT_BYTES, BASE_NONE, NULL, 0x0,
|
||||
NULL, HFILL }
|
||||
},
|
||||
|
||||
/* MAX_DATA */
|
||||
{ &hf_quic_md_maximum_data,
|
||||
{ "Maximum Data", "quic.md.maximum_data",
|
||||
|
@ -3221,6 +3678,63 @@ proto_register_quic(void)
|
|||
FT_UINT64, BASE_DEC, NULL, 0x0,
|
||||
NULL, HFILL }
|
||||
},
|
||||
|
||||
/* Fields for QUIC Stream data reassembly. */
|
||||
{ &hf_quic_fragment_overlap,
|
||||
{ "Fragment overlap", "quic.fragment.overlap",
|
||||
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
|
||||
"Fragment overlaps with other fragments", HFILL }
|
||||
},
|
||||
{ &hf_quic_fragment_overlap_conflict,
|
||||
{ "Conflicting data in fragment overlap", "quic.fragment.overlap.conflict",
|
||||
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
|
||||
"Overlapping fragments contained conflicting data", HFILL }
|
||||
},
|
||||
{ &hf_quic_fragment_multiple_tails,
|
||||
{ "Multiple tail fragments found", "quic.fragment.multipletails",
|
||||
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
|
||||
"Several tails were found when reassembling the pdu", HFILL }
|
||||
},
|
||||
{ &hf_quic_fragment_too_long_fragment,
|
||||
{ "Fragment too long", "quic.fragment.toolongfragment",
|
||||
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
|
||||
"Fragment contained data past end of the pdu", HFILL }
|
||||
},
|
||||
{ &hf_quic_fragment_error,
|
||||
{ "Reassembling error", "quic.fragment.error",
|
||||
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
|
||||
"Reassembling error due to illegal fragments", HFILL }
|
||||
},
|
||||
{ &hf_quic_fragment_count,
|
||||
{ "Fragment count", "quic.fragment.count",
|
||||
FT_UINT32, BASE_DEC, NULL, 0x0,
|
||||
NULL, HFILL }
|
||||
},
|
||||
{ &hf_quic_fragment,
|
||||
{ "QUIC STREAM Data Fragment", "quic.fragment",
|
||||
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
|
||||
NULL, HFILL }
|
||||
},
|
||||
{ &hf_quic_fragments,
|
||||
{ "Reassembled QUIC STREAM Data Fragments", "quic.fragments",
|
||||
FT_NONE, BASE_NONE, NULL, 0x0,
|
||||
"QUIC STREAM Data Fragments", HFILL }
|
||||
},
|
||||
{ &hf_quic_reassembled_in,
|
||||
{ "Reassembled PDU in frame", "quic.reassembled_in",
|
||||
FT_FRAMENUM, BASE_NONE, NULL, 0x0,
|
||||
"The PDU that doesn't end in this fragment is reassembled in this frame", HFILL }
|
||||
},
|
||||
{ &hf_quic_reassembled_length,
|
||||
{ "Reassembled QUIC STREAM Data length", "quic.reassembled.length",
|
||||
FT_UINT32, BASE_DEC, NULL, 0x0,
|
||||
"The total length of the reassembled payload", HFILL }
|
||||
},
|
||||
{ &hf_quic_reassembled_data,
|
||||
{ "Reassembled QUIC STREAM Data", "quic.reassembled.data",
|
||||
FT_BYTES, BASE_NONE, NULL, 0x0,
|
||||
"The reassembled payload", HFILL }
|
||||
},
|
||||
};
|
||||
|
||||
static gint *ett[] = {
|
||||
|
@ -3228,7 +3742,9 @@ proto_register_quic(void)
|
|||
&ett_quic_short_header,
|
||||
&ett_quic_connection_info,
|
||||
&ett_quic_ft,
|
||||
&ett_quic_ftflags
|
||||
&ett_quic_ftflags,
|
||||
&ett_quic_fragments,
|
||||
&ett_quic_fragment,
|
||||
};
|
||||
|
||||
static ei_register_info ei[] = {
|
||||
|
@ -3269,6 +3785,19 @@ proto_register_quic(void)
|
|||
|
||||
register_follow_stream(proto_quic, "quic_follow", quic_follow_conv_filter, quic_follow_index_filter, quic_follow_address_filter,
|
||||
udp_port_to_display, follow_quic_tap_listener);
|
||||
|
||||
// TODO implement custom reassembly functions that uses the QUIC Connection
|
||||
// ID instead of address and port numbers.
|
||||
reassembly_table_register(&quic_reassembly_table,
|
||||
&addresses_ports_reassembly_table_functions);
|
||||
|
||||
/*
|
||||
* Application protocol. QUIC with TLS uses ALPN.
|
||||
* https://tools.ietf.org/html/draft-ietf-quic-transport-23#section-7
|
||||
* This could in theory be an arbitrary octet string with embedded NUL
|
||||
* bytes, but in practice these do not exist yet.
|
||||
*/
|
||||
quic_proto_dissector_table = register_dissector_table("quic.proto", "QUIC Protocol", proto_quic, FT_STRING, FALSE);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -16,6 +16,32 @@ extern "C" {
|
|||
|
||||
#include "ws_symbol_export.h"
|
||||
|
||||
/**
|
||||
* Metadata for a STREAM frame.
|
||||
* https://tools.ietf.org/html/draft-ietf-quic-transport-23#section-19.8
|
||||
*/
|
||||
typedef struct _quic_stream_info {
|
||||
guint64 stream_id; /**< 62-bit Stream ID. */
|
||||
guint64 stream_offset; /**< 62-bit stream offset. */
|
||||
guint32 offset; /**< Offset within the stream (different for reassembled data). */
|
||||
struct quic_info_data *quic_info; /**< Opaque data structure to find the QUIC session. */
|
||||
gboolean from_server;
|
||||
} quic_stream_info;
|
||||
|
||||
/**
|
||||
* Obtain Stream Type from a Stream ID.
|
||||
* https://tools.ietf.org/html/draft-ietf-quic-transport-23#section-2.1
|
||||
*/
|
||||
#define QUIC_STREAM_TYPE(stream_id) ((stream_id) & 3U)
|
||||
#define QUIC_STREAM_CLIENT_BIDI 0
|
||||
#define QUIC_STREAM_SERVER_BIDI 1
|
||||
#define QUIC_STREAM_CLIENT_UNI 2
|
||||
#define QUIC_STREAM_SERVER_UNI 3
|
||||
|
||||
/** Set/Get protocol-specific data for the QUIC STREAM. */
|
||||
void quic_stream_add_proto_data(struct _packet_info *pinfo, quic_stream_info *stream_info, void *proto_data);
|
||||
void *quic_stream_get_proto_data(struct _packet_info *pinfo, quic_stream_info *stream_info);
|
||||
|
||||
/** Returns the number of items for quic.connection.number. */
|
||||
WS_DLL_PUBLIC guint32 get_quic_connections_count(void);
|
||||
|
||||
|
|
Loading…
Reference in New Issue