forked from osmocom/wireshark
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
pespin/rlcmac
parent
a6932f56dc
commit
c2d77d910d
|
@ -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
|
||||
|
|
|
@ -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>]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue