From d2a660d805df50a2cbf92dc9e75114d5c05b616d Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 2 Oct 2019 01:20:43 +0100 Subject: [PATCH] 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 Tested-by: Petri Dish Buildbot Reviewed-by: Alexis La Goutte Reviewed-by: Anders Broman --- debian/libwireshark0.symbols | 1 + doc/tshark.pod | 6 ++ docbook/wsug_src/WSUG_chapter_advanced.adoc | 3 + epan/dissectors/CMakeLists.txt | 1 + epan/dissectors/packet-quic.c | 101 +++++++++++++++++++- epan/dissectors/packet-quic.h | 26 +++++ epan/follow.h | 3 +- ui/cli/tap-follow.c | 7 +- ui/qt/follow_stream_dialog.cpp | 45 ++++++++- ui/qt/main_window.h | 1 + ui/qt/main_window.ui | 9 ++ ui/qt/main_window_slots.cpp | 10 +- ui/qt/packet_list.cpp | 1 + ui/qt/proto_tree.cpp | 1 + 14 files changed, 202 insertions(+), 13 deletions(-) create mode 100644 epan/dissectors/packet-quic.h diff --git a/debian/libwireshark0.symbols b/debian/libwireshark0.symbols index bcb243932e..f46f929fc2 100644 --- a/debian/libwireshark0.symbols +++ b/debian/libwireshark0.symbols @@ -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 diff --git a/doc/tshark.pod b/doc/tshark.pod index afcacf332e..625cb19976 100644 --- a/doc/tshark.pod +++ b/doc/tshark.pod @@ -1228,6 +1228,8 @@ I 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 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 diff --git a/docbook/wsug_src/WSUG_chapter_advanced.adoc b/docbook/wsug_src/WSUG_chapter_advanced.adoc index 6eb53fc329..5d943d5f53 100644 --- a/docbook/wsug_src/WSUG_chapter_advanced.adoc +++ b/docbook/wsug_src/WSUG_chapter_advanced.adoc @@ -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 diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index b64f8b1b14..bf14d37fd1 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -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 diff --git a/epan/dissectors/packet-quic.c b/epan/dissectors/packet-quic.c index e373c8887c..ffdc83861c 100644 --- a/epan/dissectors/packet-quic.c +++ b/epan/dissectors/packet-quic.c @@ -34,9 +34,14 @@ #include #include "packet-tls-utils.h" #include "packet-tls.h" +#include "packet-quic.h" #include #include +#include +#include +#include + #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"); } /* diff --git a/epan/dissectors/packet-quic.h b/epan/dissectors/packet-quic.h new file mode 100644 index 0000000000..5e91881fb0 --- /dev/null +++ b/epan/dissectors/packet-quic.h @@ -0,0 +1,26 @@ +/* packet-quic.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * 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__ */ diff --git a/epan/follow.h b/epan/follow.h index aaa366e0e8..c0df89c0ce 100644 --- a/epan/follow.h +++ b/epan/follow.h @@ -43,7 +43,8 @@ typedef enum { FOLLOW_TLS, FOLLOW_UDP, FOLLOW_HTTP, - FOLLOW_HTTP2 + FOLLOW_HTTP2, + FOLLOW_QUIC, } follow_type_t; /* Show Type */ diff --git a/ui/cli/tap-follow.c b/ui/cli/tap-follow.c index c4d7399fd1..f905e59add 100644 --- a/ui/cli/tap-follow.c +++ b/ui/cli/tap-follow.c @@ -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; diff --git a/ui/qt/follow_stream_dialog.cpp b/ui/qt/follow_stream_dialog.cpp index c845830420..cf6e08e559 100644 --- a/ui/qt/follow_stream_dialog.cpp +++ b/ui/qt/follow_stream_dialog.cpp @@ -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(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(stream_num), sub_stream_num_new, &sub_stream_num_new); + } else { + 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; } else { - ok = http2_get_stream_id_le(static_cast(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(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 */ diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h index 21ad5cc762..60f145f20e 100644 --- a/ui/qt/main_window.h +++ b/ui/qt/main_window.h @@ -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(); diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui index 06ffdd1df4..d61882fc21 100644 --- a/ui/qt/main_window.ui +++ b/ui/qt/main_window.ui @@ -416,6 +416,7 @@ + @@ -1720,6 +1721,14 @@ HTTP/2 Stream + + + false + + + QUIC Stream + + Time Sequence (tcptrace) diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp index ccf8f1af2e..ec2288addd 100644 --- a/ui/qt/main_window_slots.cpp +++ b/ui/qt/main_window_slots.cpp @@ -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()); diff --git a/ui/qt/packet_list.cpp b/ui/qt/packet_list.cpp index 79063c1051..20d34b5cf7 100644 --- a/ui/qt/packet_list.cpp +++ b/ui/qt/packet_list.cpp @@ -544,6 +544,7 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event) submenu->addAction(window()->findChild("actionAnalyzeFollowTLSStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowHTTPStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowHTTP2Stream")); + submenu->addAction(window()->findChild("actionAnalyzeFollowQUICStream")); ctx_menu->addSeparator(); diff --git a/ui/qt/proto_tree.cpp b/ui/qt/proto_tree.cpp index bc72ca7b3d..e18314ac19 100644 --- a/ui/qt/proto_tree.cpp +++ b/ui/qt/proto_tree.cpp @@ -286,6 +286,7 @@ void ProtoTree::contextMenuEvent(QContextMenuEvent *event) submenu->addAction(window()->findChild("actionAnalyzeFollowTLSStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowHTTPStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowHTTP2Stream")); + submenu->addAction(window()->findChild("actionAnalyzeFollowQUICStream")); ctx_menu.addSeparator(); }