QUIC: Add Follow QUIC Stream support to Qt and tshark

The QUIC transport protocol provides a stream, similar to HTTP/2. Make
it possible to look at the stream contents. This can be helpful while
HTTP/3 support is not yet complete.

Known issues that will be addressed in the future:

 - If a single packet contains multiple streams, then Follow QUIC Stream
   will wrongly include data from streams other than the selected one.
   This is tracked by bug 16093 and affects HTTP/2 as well.

 - The Substream index menu does not properly filter for available
   stream numbers. If a non-existing stream is selected, then changing
   to another (potentially valid) index results in the "Capture file
   invalid." error. As workaround, clear the display filter first.

 - Follow Stream always selects Stream ID 0 instead of the first or
   currently selected stream field in a packet. Users should manually
   update the stream index as needed.

Change-Id: I5866be380d58c96f0a71a29abdbd1be20ae3534a
Ping-Bug: 13881
Reviewed-on: https://code.wireshark.org/review/34694
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
Reviewed-by: Anders Broman <a.broman58@gmail.com>
This commit is contained in:
Peter Wu 2019-10-02 01:20:43 +01:00 committed by Anders Broman
parent 750ffac7b6
commit d2a660d805
14 changed files with 202 additions and 13 deletions

View File

@ -852,6 +852,7 @@ libwireshark.so.0 libwireshark0 #MINVER#
get_utf_16_string@Base 1.12.0~rc1
get_vlan_hash_table@Base 2.1.0
get_wka_hashtable@Base 1.12.0~rc1
get_quic_connections_count@Base 3.1.1
giop_add_CDR_string@Base 3.0.1
golay_decode@Base 1.9.1
golay_encode@Base 1.9.1

View File

@ -1228,6 +1228,8 @@ I<prot> specifies the transport protocol. It can be one of:
tcp TCP
udp UDP
tls TLS or SSL
http2 HTTP/2 streams
quic QUIC streams
I<mode> specifies the output mode. It can be one of:
@ -1296,6 +1298,10 @@ stream on the first TCP session (index 0) with HTTP/2 Stream ID 1.
00000020 34 a0 5b b8 21 5c 0b ea 62 d1 bf 4.[.!\.. b..
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
Stream ID.
=item B<-z> h225,counter[I<,filter>]
Count ITU-T H.225 messages and their reasons. In the first column you get a

View File

@ -110,6 +110,9 @@ 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
while the "Substream" field selects the QUIC Stream ID.
[[ChAdvShowPacketBytes]]
=== Show Packet Bytes

View File

@ -501,6 +501,7 @@ set(DISSECTOR_PUBLIC_HEADERS
packet-q931.h
packet-q932.h
packet-qsig.h
packet-quic.h
packet-radius.h
packet-raknet.h
packet-ranap.h

View File

@ -34,9 +34,14 @@
#include <epan/to_str.h>
#include "packet-tls-utils.h"
#include "packet-tls.h"
#include "packet-quic.h"
#include <epan/prefs.h>
#include <wsutil/pint.h>
#include <epan/tap.h>
#include <epan/follow.h>
#include <epan/addr_resolv.h>
#if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */
/* Whether to provide support for authentication in addition to decryption. */
#define HAVE_LIBGCRYPT_AEAD
@ -50,6 +55,8 @@
void proto_reg_handoff_quic(void);
void proto_register_quic(void);
static int quic_follow_tap = -1;
/* Initialize the protocol and registered fields */
static int proto_quic = -1;
static int hf_quic_connection_number = -1;
@ -228,6 +235,17 @@ struct quic_cid_item {
quic_cid_t data;
};
/**
* Per-STREAM state, identified by QUIC Stream ID.
*
* Assume that every QUIC Short Header packet has no STREAM frames that overlap
* each other in the same QUIC packet (identified by "frame_num"). Thus, the
* Stream ID and offset uniquely identifies the STREAM Frame info in per packet.
*/
typedef struct _quic_stream_state {
guint64 stream_id;
} quic_stream_state;
/**
* State for a single QUIC connection, identified by one or more Destination
* Connection IDs (DCID).
@ -253,6 +271,8 @@ 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. */
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;
/** Per-packet information about QUIC, populated on the first pass. */
@ -641,6 +661,21 @@ quic_cids_is_known_length(const quic_cid_t *cid)
return (quic_cid_lengths & (1ULL << cid->len)) != 0;
}
/**
* Returns the QUIC connection for the current UDP stream. This may return NULL
* after connection migration if the new UDP association was not properly linked
* via a match based on the Connection ID.
*/
static quic_info_data_t *
quic_connection_from_conv(packet_info *pinfo)
{
conversation_t *conv = find_conversation_pinfo(pinfo, 0);
if (conv) {
return (quic_info_data_t *)conversation_get_proto_data(conv, proto_quic);
}
return NULL;
}
/**
* Tries to lookup a matching connection (Connection ID is optional).
* If connection is found, "from_server" is set accordingly.
@ -676,10 +711,9 @@ quic_connection_find_dcid(packet_info *pinfo, const quic_cid_t *dcid, gboolean *
}
}
} else {
conversation_t *conv = find_conversation_pinfo(pinfo, 0);
if (conv) {
conn = (quic_info_data_t *)conversation_get_proto_data(conv, proto_quic);
check_ports = !!conn;
conn = quic_connection_from_conv(pinfo);
if (conn) {
check_ports = TRUE;
}
}
@ -1087,6 +1121,9 @@ dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree
proto_item_append_text(ti_ft, " len=%" G_GINT64_MODIFIER "u uni=%d", length, !!(stream_id & 2U));
proto_tree_add_item(ft_tree, hf_quic_stream_data, tvb, offset, (int)length, ENC_NA);
if (have_tap_listener(quic_follow_tap)) {
tap_queue_packet(quic_follow_tap, pinfo, tvb_new_subset_length(tvb, offset, (int)length));
}
offset += (int)length;
}
break;
@ -2417,6 +2454,58 @@ quic_cleanup(void)
quic_server_connections = NULL;
}
/* Follow QUIC Stream functionality {{{ */
static gchar *
quic_follow_conv_filter(packet_info *pinfo, guint *stream, guint *sub_stream)
{
if (((pinfo->net_src.type == AT_IPv4 && pinfo->net_dst.type == AT_IPv4) ||
(pinfo->net_src.type == AT_IPv6 && pinfo->net_dst.type == AT_IPv6))) {
gboolean from_server;
quic_info_data_t *conn = quic_connection_find_dcid(pinfo, NULL, &from_server);
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);
}
return NULL;
}
static gchar *
quic_follow_index_filter(guint stream, guint sub_stream)
{
return g_strdup_printf("quic.connection.number eq %u and quic.stream.stream_id eq %u", stream, sub_stream);
}
static gchar *
quic_follow_address_filter(address *src_addr _U_, address *dst_addr _U_, int src_port _U_, int dst_port _U_)
{
// This appears to be solely used for tshark. Let's not support matching by
// IP addresses and UDP ports for now since that fails after connection
// migration. If necessary, use udp_follow_address_filter.
return NULL;
}
static tap_packet_status
follow_quic_tap_listener(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U_, const void *data)
{
// TODO fix filtering for multiple streams, see
// https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=16093
follow_tvb_tap_listener(tapdata, pinfo, NULL, data);
return TAP_PACKET_DONT_REDRAW;
}
guint32 get_quic_connections_count(void)
{
return quic_connections_count;
}
/* Follow QUIC Stream functionality }}} */
void
proto_register_quic(void)
{
@ -2885,6 +2974,9 @@ proto_register_quic(void)
register_init_routine(quic_init);
register_cleanup_routine(quic_cleanup);
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);
}
void
@ -2893,6 +2985,7 @@ proto_reg_handoff_quic(void)
tls13_handshake_handle = find_dissector("tls13-handshake");
dissector_add_uint_with_preference("udp.port", 0, quic_handle);
heur_dissector_add("udp", dissect_quic_heur, "QUIC", "quic", proto_quic, HEURISTIC_ENABLE);
quic_follow_tap = register_tap("quic_follow");
}
/*

View File

@ -0,0 +1,26 @@
/* packet-quic.h
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __PACKET_QUIC_H__
#define __PACKET_QUIC_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include "ws_symbol_export.h"
/** Returns the number of items for quic.connection.number. */
WS_DLL_PUBLIC guint32 get_quic_connections_count(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __PACKET_QUIC_H__ */

View File

@ -43,7 +43,8 @@ typedef enum {
FOLLOW_TLS,
FOLLOW_UDP,
FOLLOW_HTTP,
FOLLOW_HTTP2
FOLLOW_HTTP2,
FOLLOW_QUIC,
} follow_type_t;
/* Show Type */

View File

@ -340,7 +340,7 @@ follow_arg_filter(const char **opt_argp, follow_info_t *follow_info)
{
*opt_argp += len;
/* if it's HTTP2 protocol we should read substream id otherwise it's a range parameter from follow_arg_range */
/* if it's HTTP2 or QUIC protocol we should read substream id otherwise it's a range parameter from follow_arg_range */
if (cli_follow_info->sub_stream_index == -1 && sscanf(*opt_argp, ",%d%n", &cli_follow_info->sub_stream_index, &len) == 1 &&
((*opt_argp)[len] == 0 || (*opt_argp)[len] == ','))
{
@ -454,8 +454,9 @@ static void follow_stream(const char *opt_argp, void *userdata)
cli_follow_info = g_new0(cli_follow_info_t, 1);
cli_follow_info->stream_index = -1;
/* use second parameter only for HTTP2 substream */
if (strncmp(proto_filter_name, "http2", 5) == 0) {
/* use second parameter only for HTTP2 or QUIC substream */
if (g_str_equal(proto_filter_name, "http2") ||
g_str_equal(proto_filter_name, "quic")) {
cli_follow_info->sub_stream_index = -1;
} else {
cli_follow_info->sub_stream_index = 0;

View File

@ -17,6 +17,7 @@
#include "epan/dissectors/packet-tcp.h"
#include "epan/dissectors/packet-udp.h"
#include "epan/dissectors/packet-http2.h"
#include "epan/dissectors/packet-quic.h"
#include "epan/prefs.h"
#include "epan/addr_resolv.h"
#include "epan/charsets.h"
@ -99,6 +100,9 @@ FollowStreamDialog::FollowStreamDialog(QWidget &parent, CaptureFile &cf, follow_
case FOLLOW_HTTP2:
follower_ = get_follow_by_name("HTTP2");
break;
case FOLLOW_QUIC:
follower_ = get_follow_by_name("QUIC");
break;
default :
g_assert_not_reached();
}
@ -405,10 +409,18 @@ void FollowStreamDialog::on_subStreamNumberSpinBox_valueChanged(int sub_stream_n
// Stream ID 0 should always exist as it is used for control messages.
sub_stream_num_new = 0;
ok = TRUE;
} else if (previous_sub_stream_num_ < sub_stream_num){
ok = http2_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
} else if (follow_type_ == FOLLOW_HTTP2) {
if (previous_sub_stream_num_ < sub_stream_num){
ok = http2_get_stream_id_ge(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
} else {
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;
} else {
ok = http2_get_stream_id_le(static_cast<guint>(stream_num), sub_stream_num_new, &sub_stream_num_new);
// Should not happen, this field is only visible for suitable protocols.
return;
}
sub_stream_num = static_cast<gint>(sub_stream_num_new);
@ -502,6 +514,7 @@ FollowStreamDialog::readStream()
case FOLLOW_UDP :
case FOLLOW_HTTP :
case FOLLOW_HTTP2:
case FOLLOW_QUIC:
case FOLLOW_TLS :
ret = readFollowStream();
break;
@ -891,7 +904,7 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index,
return false;
}
/* disable substream spin box for all protocols except HTTP2 */
/* disable substream spin box for all protocols except HTTP2 and QUIC */
ui->subStreamNumberSpinBox->blockSignals(true);
ui->subStreamNumberSpinBox->setEnabled(false);
ui->subStreamNumberSpinBox->setValue(0);
@ -951,6 +964,30 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index,
break;
}
case FOLLOW_QUIC:
{
int stream_count = get_quic_connections_count();
ui->streamNumberSpinBox->blockSignals(true);
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->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
// TODO extract number of QUIC streams?
stream_count = G_MAXINT32;
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(ui->subStreamNumberSpinBox->toolTip());
ui->subStreamNumberSpinBox->setVisible(true);
ui->subStreamNumberLabel->setVisible(true);
break;
}
case FOLLOW_TLS:
case FOLLOW_HTTP:
/* No extra handling */

View File

@ -554,6 +554,7 @@ private slots:
void on_actionAnalyzeFollowTLSStream_triggered();
void on_actionAnalyzeFollowHTTPStream_triggered();
void on_actionAnalyzeFollowHTTP2Stream_triggered();
void on_actionAnalyzeFollowQUICStream_triggered();
void statCommandExpertInfo(const char *, void *);
void on_actionAnalyzeExpertInfo_triggered();

View File

@ -416,6 +416,7 @@
<addaction name="actionAnalyzeFollowTLSStream"/>
<addaction name="actionAnalyzeFollowHTTPStream"/>
<addaction name="actionAnalyzeFollowHTTP2Stream"/>
<addaction name="actionAnalyzeFollowQUICStream"/>
</widget>
<widget class="QMenu" name="menuConversationFilter">
<property name="title">
@ -1720,6 +1721,14 @@
<string>HTTP/2 Stream</string>
</property>
</action>
<action name="actionAnalyzeFollowQUICStream">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>QUIC Stream</string>
</property>
</action>
<action name="actionStatisticsTcpStreamTcptrace">
<property name="text">
<string>Time Sequence (tcptrace)</string>

View File

@ -1115,7 +1115,8 @@ void MainWindow::recentActionTriggered() {
void MainWindow::setMenusForSelectedPacket()
{
gboolean is_ip = FALSE, is_tcp = FALSE, is_udp = FALSE, is_sctp = FALSE, is_tls = FALSE, is_rtp = FALSE, is_lte_rlc = FALSE, is_http = FALSE, is_http2 = FALSE;
gboolean is_ip = FALSE, is_tcp = FALSE, is_udp = FALSE, is_sctp = FALSE, is_tls = FALSE, is_rtp = FALSE, is_lte_rlc = FALSE,
is_http = FALSE, is_http2 = FALSE, is_quic = FALSE;
/* Making the menu context-sensitive allows for easier selection of the
desired item and has the added benefit, with large captures, of
@ -1175,6 +1176,7 @@ void MainWindow::setMenusForSelectedPacket()
&is_tls, &is_rtp, &is_lte_rlc);
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");
is_quic = proto_is_frame_protocol(capture_file_.capFile()->edt->pi.layers, "quic");
}
}
@ -1208,6 +1210,7 @@ void MainWindow::setMenusForSelectedPacket()
main_ui_->actionAnalyzeFollowTLSStream->setEnabled(is_tls);
main_ui_->actionAnalyzeFollowHTTPStream->setEnabled(is_http);
main_ui_->actionAnalyzeFollowHTTP2Stream->setEnabled(is_http2);
main_ui_->actionAnalyzeFollowQUICStream->setEnabled(is_quic);
foreach(QAction *cc_action, cc_actions) {
cc_action->setEnabled(frame_selected);
@ -2774,6 +2777,11 @@ void MainWindow::on_actionAnalyzeFollowHTTP2Stream_triggered()
openFollowStreamDialogForType(FOLLOW_HTTP2);
}
void MainWindow::on_actionAnalyzeFollowQUICStream_triggered()
{
openFollowStreamDialogForType(FOLLOW_QUIC);
}
void MainWindow::openSCTPAllAssocsDialog()
{
SCTPAllAssocsDialog *sctp_dialog = new SCTPAllAssocsDialog(this, capture_file_.capFile());

View File

@ -544,6 +544,7 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event)
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTLSStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTP2Stream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowQUICStream"));
ctx_menu->addSeparator();

View File

@ -286,6 +286,7 @@ void ProtoTree::contextMenuEvent(QContextMenuEvent *event)
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTLSStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTP2Stream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowQUICStream"));
ctx_menu.addSeparator();
}