QUIC: improve "Follow QUIC Stream" support

This functionality has been added in d2a660d8, where its limitations
are described.
Improvements:
* the Substream index menu now properly filters for available stream numbers;
* Follow Stream selects the first stream in the current packet

Known issue (which is still there):  if a packet contains multiple QUIC
streams, then we will show data also from streams other than the selected
one (see #16093)

Note that there is no way to follow a QUIC connection.

Close #17453
This commit is contained in:
Nardi Ivan 2021-06-21 18:01:15 +02:00 committed by Wireshark GitLab Utility
parent a6932f56dc
commit c2d77d910d
7 changed files with 189 additions and 18 deletions

View File

@ -1332,6 +1332,8 @@ libwireshark.so.0 libwireshark0 #MINVER#
q932_PresentedNumberScreened_vals@Base 2.1.0
q932_PresentedNumberUnscreened_vals@Base 2.1.0
q932_ScreeningIndicator_vals@Base 2.5.0
quic_get_stream_id_le@Base 3.5.0
quic_get_stream_id_ge@Base 3.5.0
qs_func_vals@Base 1.9.1
qs_rate_vals_ext@Base 1.9.1
raknet_add_udp_dissector@Base 2.3.0

View File

@ -1261,7 +1261,7 @@ stream on the first TCP session (index 0) with HTTP/2 Stream ID 1.
0000002B 00 40 00 00 00 00 00 00 01 89 50 4e 47 0d 0a 1a .@...... ..PNG...
QUIC streams can be selected through B<-z "follow,quic,hex,3,0">, the first
number indicates the UDP stream index whereas the second number selects the QUIC
number indicates the QUIC connection number whereas the second number selects the QUIC
Stream ID.
=item B<-z> h225,counter[I<,filter>]

View File

@ -187,7 +187,7 @@ a HTTP/2 Stream Index (field name `http2.streamid`) which are unique within a
TCP connection. The “Stream” selector determines the TCP connection whereas the
“Substream” selector is used to pick the HTTP/2 Stream ID.
The QUIC protocol is similar, the first number selects the UDP stream index
The QUIC protocol is similar, the first number selects the QUIC connection number
while the "Substream" field selects the QUIC Stream ID.
.The “Follow SIP Call” dialog box

View File

@ -40,6 +40,7 @@
* - 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.
* - "Follow QUIC Stream" doesn't work with STREAM IDs larger than 32 bit
*/
#include <config.h>
@ -333,6 +334,14 @@ typedef struct _quic_stream_state {
void *subdissector_private;
} quic_stream_state;
/**
* Data used to allow "Follow QUIC Stream" functionality
*/
typedef struct _quic_follow_stream {
guint32 num;
guint64 stream_id;
} quic_follow_stream;
/**
* State for a single QUIC connection, identified by one or more Destination
* Connection IDs (DCID).
@ -366,6 +375,8 @@ typedef struct quic_info_data {
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. */
wmem_list_t *streams_list; /**< Ordered list of QUIC Stream ID in this connection (both directions). Used by "Follow QUIC Stream" functionality */
wmem_map_t *streams_map; /**< Map pinfo->num --> First stream in that frame (guint -> quic_follow_stream). Used by "Follow QUIC Stream" functionality */
gquic_info_data_t *gquic_info; /**< GQUIC info for >Q050 flows. */
} quic_info_data_t;
@ -666,6 +677,8 @@ static const val64_string quic_frame_id_direction[] = {
static void
quic_extract_header(tvbuff_t *tvb, guint8 *long_packet_type, guint32 *version,
quic_cid_t *dcid, quic_cid_t *scid);
static void
quic_streams_add(packet_info *pinfo, quic_info_data_t *quic_info, guint64 stream_id);
static void
quic_hp_cipher_reset(quic_hp_cipher *hp_cipher)
@ -1805,6 +1818,10 @@ dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree
proto_item_append_text(ti_ft, " fin=%d", !!(frame_type & FTFLAGS_STREAM_FIN));
if (!PINFO_FD_VISITED(pinfo)) {
quic_streams_add(pinfo, quic_info, stream_id);
}
if (frame_type & FTFLAGS_STREAM_OFF) {
proto_tree_add_item_ret_varint(ft_tree, hf_quic_stream_offset, tvb, offset, -1, ENC_VARINT_QUIC, &stream_offset, &lenvar);
offset += lenvar;
@ -3774,6 +3791,104 @@ quic_cleanup(void)
/* Follow QUIC Stream functionality {{{ */
static void
quic_streams_add(packet_info *pinfo, quic_info_data_t *quic_info, guint64 stream_id)
{
/* List: ordered list of Stream IDs in this connection */
if (!quic_info->streams_list) {
quic_info->streams_list = wmem_list_new(wmem_file_scope());
}
if (!wmem_list_find(quic_info->streams_list, (void *)(stream_id))) {
wmem_list_insert_sorted(quic_info->streams_list, (void *)(stream_id),
uint64_compare);
}
/* Map: first Stream ID for each UDP payload */
quic_follow_stream *stream;
if (!quic_info->streams_map) {
quic_info->streams_map = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
}
stream = wmem_map_lookup(quic_info->streams_map, GUINT_TO_POINTER(pinfo->num));
if (!stream) {
stream = wmem_new0(wmem_file_scope(), quic_follow_stream);
stream->num = pinfo->num;
stream->stream_id = stream_id;
wmem_map_insert(quic_info->streams_map, GUINT_TO_POINTER(stream->num), stream);
}
}
static quic_info_data_t *
get_conn_by_number(guint conn_number)
{
quic_info_data_t *conn;
wmem_list_frame_t *elem;
elem = wmem_list_head(quic_connections);
while (elem) {
conn = (quic_info_data_t *)wmem_list_frame_data(elem);
if (conn->number == conn_number)
return conn;
elem = wmem_list_frame_next(elem);
}
return NULL;
}
gboolean
quic_get_stream_id_le(guint streamid, guint sub_stream_id, guint *sub_stream_id_out)
{
quic_info_data_t *quic_info;
wmem_list_frame_t *curr_entry;
guint64 prev_stream_id;
quic_info = get_conn_by_number(streamid);
if (!quic_info) {
return FALSE;
}
prev_stream_id = G_MAXUINT64;
curr_entry = wmem_list_head(quic_info->streams_list);
while (curr_entry) {
if ((guint64)wmem_list_frame_data(curr_entry) > sub_stream_id &&
prev_stream_id != G_MAXUINT64) {
*sub_stream_id_out = (guint)prev_stream_id;
return TRUE;
}
prev_stream_id = (guint64)wmem_list_frame_data(curr_entry);
curr_entry = wmem_list_frame_next(curr_entry);
}
if (prev_stream_id != G_MAXUINT64) {
*sub_stream_id_out = (guint)prev_stream_id;
return TRUE;
}
return FALSE;
}
gboolean
quic_get_stream_id_ge(guint streamid, guint sub_stream_id, guint *sub_stream_id_out)
{
quic_info_data_t *quic_info;
wmem_list_frame_t *curr_entry;
quic_info = get_conn_by_number(streamid);
if (!quic_info) {
return FALSE;
}
curr_entry = wmem_list_head(quic_info->streams_list);
while (curr_entry) {
if ((guint64)wmem_list_frame_data(curr_entry) >= sub_stream_id) {
/* StreamIDs are 64 bits long in QUIC, but "Follow Stream" generic code uses guint variables */
*sub_stream_id_out = (guint)(guint64)wmem_list_frame_data(curr_entry);
return TRUE;
}
curr_entry = wmem_list_frame_next(curr_entry);
}
return FALSE;
}
static gchar *
quic_follow_conv_filter(epan_dissect_t *edt _U_, packet_info *pinfo, guint *stream, guint *sub_stream)
{
@ -3784,11 +3899,15 @@ quic_follow_conv_filter(epan_dissect_t *edt _U_, packet_info *pinfo, guint *stre
if (!conn) {
return NULL;
}
// XXX Look up stream ID for the current packet.
guint stream_id = 0;
*stream = conn->number;
*sub_stream = stream_id;
return g_strdup_printf("quic.connection.number eq %u and quic.stream.stream_id eq %u", conn->number, stream_id);
/* First Stream ID in the selected packet */
quic_follow_stream *s;
s = wmem_map_lookup(conn->streams_map, GUINT_TO_POINTER(pinfo->num));
if (s) {
*stream = conn->number;
*sub_stream = (guint)s->stream_id;
return g_strdup_printf("quic.connection.number eq %u and quic.stream.stream_id eq %u", conn->number, *sub_stream);
}
}
return NULL;

View File

@ -79,6 +79,22 @@ void
quic_add_loss_bits(packet_info *pinfo, guint64 value);
void
quic_proto_tree_add_version(tvbuff_t *tvb, proto_tree *tree, int hfindex, guint offset);
/**
* Retrieves the QUIC Stream ID which is smaller than or equal to the provided
* ID. If available, sub_stream_id_out will be set and TRUE is returned.
*/
WS_DLL_PUBLIC gboolean
quic_get_stream_id_le(guint streamid, guint sub_stream_id, guint *sub_stream_id_out);
/**
* Retrieves the QUIC Stream ID which is greater than or equal to the provided
* ID. If available, sub_stream_id_out will be set and TRUE is returned.
*/
WS_DLL_PUBLIC gboolean
quic_get_stream_id_ge(guint streamid, guint sub_stream_id, guint *sub_stream_id_out);
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -252,6 +252,9 @@ void FollowStreamDialog::updateWidgets(bool follow_in_progress)
ui->cbDirections->setEnabled(enable);
ui->cbCharset->setEnabled(enable);
ui->streamNumberSpinBox->setEnabled(enable);
if (follow_type_ == FOLLOW_HTTP2 || follow_type_ == FOLLOW_QUIC) {
ui->subStreamNumberSpinBox->setEnabled(enable);
}
ui->leFind->setEnabled(enable);
ui->bFind->setEnabled(enable);
b_filter_out_->setEnabled(enable);
@ -399,12 +402,32 @@ void FollowStreamDialog::on_streamNumberSpinBox_valueChanged(int stream_num)
sub_stream_num = ui->subStreamNumberSpinBox->value();
ui->subStreamNumberSpinBox->blockSignals(false);
/* We need to find a suitable sub stream for the new stream */
guint sub_stream_num_new = static_cast<guint>(sub_stream_num);
gboolean ok;
if (sub_stream_num < 0) {
sub_stream_num = 0;
// Stream ID 0 should always exist as it is used for control messages.
sub_stream_num_new = 0;
ok = TRUE;
} else if (follow_type_ == FOLLOW_HTTP2) {
ok = http2_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
if (!ok) {
ok = http2_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
}
} else if (follow_type_ == FOLLOW_QUIC) {
ok = quic_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
if (!ok) {
ok = quic_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
}
} else {
// Should not happen, this field is only visible for suitable protocols.
return;
}
sub_stream_num = static_cast<gint>(sub_stream_num_new);
if (stream_num >= 0) {
if (stream_num >= 0 && ok) {
follow(previous_filter_, true, stream_num, sub_stream_num);
previous_sub_stream_num_ = sub_stream_num;
}
}
@ -432,8 +455,11 @@ void FollowStreamDialog::on_subStreamNumberSpinBox_valueChanged(int sub_stream_n
ok = http2_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
}
} else if (follow_type_ == FOLLOW_QUIC) {
// TODO clamp the stream IDs correctly for QUIC
ok = TRUE;
if (previous_sub_stream_num_ < sub_stream_num) {
ok = quic_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
} else {
ok = quic_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
}
} else {
// Should not happen, this field is only visible for suitable protocols.
return;
@ -935,9 +961,15 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index,
follow_filter = gchar_free_to_qstring(get_follow_conv_func(follower_)(cap_file_.capFile()->edt, &cap_file_.capFile()->edt->pi, &stream_num, &sub_stream_num));
}
if (follow_filter.isEmpty()) {
QMessageBox::warning(this,
tr("Error creating filter for this stream."),
tr("A transport or network layer header is needed."));
if (follow_type_ == FOLLOW_QUIC) {
QMessageBox::warning(this,
tr("Error creating filter for this stream."),
tr("QUIC streams not found on the selected packet."));
} else {
QMessageBox::warning(this,
tr("Error creating filter for this stream."),
tr("A transport or network layer header is needed."));
}
return false;
}
@ -1038,17 +1070,18 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index,
ui->streamNumberSpinBox->setMaximum(stream_count-1);
ui->streamNumberSpinBox->setValue(stream_num);
ui->streamNumberSpinBox->blockSignals(false);
ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
ui->streamNumberSpinBox->setToolTip(tr("Total number of QUIC connections: %Ln", "", stream_count));
ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
// TODO extract number of QUIC streams?
stream_count = G_MAXINT32;
guint substream_max_id = 0;
quic_get_stream_id_le(static_cast<guint>(stream_num), G_MAXINT32, &substream_max_id);
stream_count = static_cast<gint>(substream_max_id);
ui->subStreamNumberSpinBox->blockSignals(true);
ui->subStreamNumberSpinBox->setEnabled(true);
ui->subStreamNumberSpinBox->setMaximum(stream_count);
ui->subStreamNumberSpinBox->setValue(sub_stream_num);
ui->subStreamNumberSpinBox->blockSignals(false);
ui->subStreamNumberSpinBox->setToolTip(tr("%Ln total sub stream(s).", "", stream_count));
ui->subStreamNumberSpinBox->setToolTip(tr("Max QUIC Stream ID for the selected connection: %Ln", "", stream_count));
ui->subStreamNumberSpinBox->setToolTip(ui->subStreamNumberSpinBox->toolTip());
ui->subStreamNumberSpinBox->setVisible(true);
ui->subStreamNumberLabel->setVisible(true);

View File

@ -1189,6 +1189,7 @@ void MainWindow::setMenusForSelectedPacket()
is_dccp = proto_is_frame_protocol(capture_file_.capFile()->edt->pi.layers, "dccp");
is_http = proto_is_frame_protocol(capture_file_.capFile()->edt->pi.layers, "http");
is_http2 = proto_is_frame_protocol(capture_file_.capFile()->edt->pi.layers, "http2");
/* TODO: to follow a QUIC stream we need a *decrypted* QUIC connection, i.e. checking for "quic" in the protocol stack is not enough */
is_quic = proto_is_frame_protocol(capture_file_.capFile()->edt->pi.layers, "quic");
is_sip = proto_is_frame_protocol(capture_file_.capFile()->edt->pi.layers, "sip");
}