diff --git a/debian/libwireshark0.symbols b/debian/libwireshark0.symbols index 48334cdee8..7b52712fb9 100644 --- a/debian/libwireshark0.symbols +++ b/debian/libwireshark0.symbols @@ -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 diff --git a/doc/tshark.pod b/doc/tshark.pod index cefb3f63b9..2bb10e2e21 100644 --- a/doc/tshark.pod +++ b/doc/tshark.pod @@ -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>] diff --git a/docbook/wsug_src/WSUG_chapter_advanced.adoc b/docbook/wsug_src/WSUG_chapter_advanced.adoc index cd793ea1a5..5f72b769ec 100644 --- a/docbook/wsug_src/WSUG_chapter_advanced.adoc +++ b/docbook/wsug_src/WSUG_chapter_advanced.adoc @@ -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 diff --git a/epan/dissectors/packet-quic.c b/epan/dissectors/packet-quic.c index 50947a527a..12069f024e 100644 --- a/epan/dissectors/packet-quic.c +++ b/epan/dissectors/packet-quic.c @@ -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 @@ -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; diff --git a/epan/dissectors/packet-quic.h b/epan/dissectors/packet-quic.h index 3b2fb80435..8790f86ce8 100644 --- a/epan/dissectors/packet-quic.h +++ b/epan/dissectors/packet-quic.h @@ -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 */ diff --git a/ui/qt/follow_stream_dialog.cpp b/ui/qt/follow_stream_dialog.cpp index 4e36e09ea5..cdd598d256 100644 --- a/ui/qt/follow_stream_dialog.cpp +++ b/ui/qt/follow_stream_dialog.cpp @@ -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(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(stream_num), sub_stream_num_new, &sub_stream_num_new); + if (!ok) { + ok = http2_get_stream_id_le(static_cast(stream_num), sub_stream_num_new, &sub_stream_num_new); + } + } else if (follow_type_ == FOLLOW_QUIC) { + ok = quic_get_stream_id_ge(static_cast(stream_num), sub_stream_num_new, &sub_stream_num_new); + if (!ok) { + ok = quic_get_stream_id_le(static_cast(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(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(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(stream_num), sub_stream_num_new, &sub_stream_num_new); + } else { + ok = quic_get_stream_id_le(static_cast(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(stream_num), G_MAXINT32, &substream_max_id); + stream_count = static_cast(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); diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp index 7ed97620b0..4e6eb12743 100644 --- a/ui/qt/main_window_slots.cpp +++ b/ui/qt/main_window_slots.cpp @@ -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"); }