Qt, http2: Add Follow HTTP/2 Stream functionality

The HTTP/2 protocol multiplexes a single TCP connection into multiple
independent streams. The Follow TCP output can interleave multiple
HTTP/2 streams, making it harder to analyze a single HTTP/2 stream.

Add the ability to select HTTP/2 Streams within a TCP stream.
Internally, the HTTP/2 dissector now stores the known Stream IDs in a
set for every TCP session which allows an amortized O(n) lookup time for
the previous/next/max Stream ID.

[Peter: make the dissector responsible for clamping the HTTP/2 Stream ID
instead of the Qt code, that should permit future optimizations.]

Change-Id: I5d78f29904ae8f227ae36e1a883155c0ed719200
Reviewed-on: https://code.wireshark.org/review/32221
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Alexander Gryanko <xpahos@gmail.com>
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
This commit is contained in:
Alexander Gryanko 2019-02-27 07:55:52 +03:00 committed by Alexis La Goutte
parent 893a2d9c62
commit 9fff62e2a8
23 changed files with 418 additions and 72 deletions

View File

@ -898,6 +898,8 @@ libwireshark.so.0 libwireshark0 #MINVER#
hfinfo_bitshift@Base 1.12.0~rc1
host_name_lookup_process@Base 1.9.1
hostlist_table_set_gui_info@Base 1.99.0
http2_get_stream_id_ge@Base 3.1.1
http2_get_stream_id_le@Base 3.1.1
http_tcp_dissector_add@Base 2.1.0
http_tcp_dissector_delete@Base 2.3.0
http_tcp_port_add@Base 2.1.0

View File

@ -1219,7 +1219,7 @@ Example: B<-z flow,tcp,network> will show data flow for all TCP frames
=item B<-z> follow,I<prot>,I<mode>,I<filter>[I<,range>]
Displays the contents of a TCP or UDP stream between two nodes. The data
Displays the contents of a TCP or UDP stream between two nodes. The data
sent by the second node is prefixed with a tab to differentiate it from the
data sent by the first node.
@ -1241,10 +1241,12 @@ of each section of output plus a newline precedes each section of output.
I<filter> specifies the stream to be displayed. UDP/TCP streams are selected
with either the stream index or IP address plus port pairs. TLS streams are
selected with the stream index. For example:
selected with the stream index. HTTP/2 streams are selected by combination of
UDP/TCP and HTTP/2 streams indices. For example:
ip-addr0:port0,ip-addr1:port1
stream-index
stream-index,substream-index
I<range> optionally specifies which "chunks" of the stream should be displayed.
@ -1277,6 +1279,23 @@ display the contents of a TCP stream between 200.57.7.197 port 32891 and
4
....
Example: B<-z "follow,http2,hex,0,1"> will display the contents of a HTTP/2
stream on the first TCP session (index 0) with HTTP/2 Stream ID 1.
===================================================================
Follow: http2,hex
Filter: tcp.stream eq 0 and http2.streamid eq 1
Node 0: 172.16.5.1:49178
Node 1: 172.16.5.10:8443
00000000 00 00 2c 01 05 00 00 00 01 82 04 8b 63 c1 ac 2a ..,..... ....c..*
00000010 27 1d 9d 57 ae a9 bf 87 41 8c 0b a2 5c 2e 2e da '..W.... A...\...
00000020 e1 05 c7 9a 69 9f 7a 88 25 b6 50 c3 ab b6 25 c3 ....i.z. %.P...%.
00000030 53 03 2a 2f 2a S.*/*
00000000 00 00 22 01 04 00 00 00 01 88 5f 87 35 23 98 ac .."..... .._.5#..
00000010 57 54 df 61 96 c3 61 be 94 03 8a 61 2c 6a 08 2f WT.a..a. ...a,j./
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...
=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

@ -163,6 +163,7 @@ set(WSUG_GRAPHICS
wsug_graphics/ws-filters.png
wsug_graphics/ws-find-packet.png
wsug_graphics/ws-follow-stream.png
wsug_graphics/ws-follow-http2-stream.png
wsug_graphics/ws-go-menu.png
wsug_graphics/ws-goto-packet.png
wsug_graphics/ws-gui-colors-preferences.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -101,6 +101,15 @@ You can switch between streams using the “Stream” selector.
You can search for text by entering it in the “Find” entry box and
pressing btn:[Find Next].
.The “Follow HTTP/2 Stream” dialog box
image::wsug_graphics/ws-follow-http2-stream.png[{screenshot-attrs}]
The HTTP/2 Stream dialog is similar to the "Follow TCP Stream" dialog, except
for an additional "Substream" dialog field. HTTP/2 Streams are identified by
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.
[[ChAdvShowPacketBytes]]
=== Show Packet Bytes

View File

@ -36,16 +36,16 @@
#ifdef HAVE_NGHTTP2
#include <epan/uat.h>
#include <nghttp2/nghttp2.h>
#endif
#include "packet-tcp.h"
#include <epan/tap.h>
#include <epan/stats_tree.h>
#include <epan/reassemble.h>
#include <epan/follow.h>
#include <epan/addr_resolv.h>
#include "packet-tcp.h"
#include "wsutil/pint.h"
#include "wsutil/strtoi.h"
@ -199,11 +199,13 @@ typedef struct {
nghttp2_hd_inflater *hd_inflater[2];
http2_header_repr_info_t header_repr_info[2];
wmem_map_t *per_stream_info;
guint32 current_stream_id;
#endif
guint32 current_stream_id;
tcp_flow_t *fwd_flow;
} http2_session_t;
static GHashTable* streamid_hash = NULL;
void proto_register_http2(void);
void proto_reg_handoff_http2(void);
@ -212,6 +214,7 @@ struct HTTP2Tap {
};
static int http2_tap = -1;
static int http2_follow_tap = -1;
static const guint8* st_str_http2 = "HTTP2";
static const guint8* st_str_http2_type = "Type";
@ -884,6 +887,8 @@ http2_init_protocol(void)
proto_register_field_array(proto_http2, hf_uat, num_header_fields);
}
#endif
/* Init hash table with mapping of stream id -> frames count for Follow HTTP2 */
streamid_hash = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)g_hash_table_destroy);
}
static void
@ -895,6 +900,7 @@ http2_cleanup_protocol(void) {
proto_add_deregistered_data(hf_uat);
proto_free_deregistered_fields();
#endif
g_hash_table_destroy(streamid_hash);
}
static dissector_handle_t http2_handle;
@ -1881,6 +1887,109 @@ inflate_http2_header_block(tvbuff_t *tvb, packet_info *pinfo, guint offset,
}
#endif
static gchar*
http2_follow_conv_filter(packet_info *pinfo, guint *stream, guint *sub_stream)
{
http2_session_t *h2session;
struct tcp_analysis *tcpd;
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)))
{
h2session = get_http2_session(pinfo);
tcpd = get_tcp_conversation_data(NULL, pinfo);
if (tcpd == NULL)
return NULL;
if (h2session == NULL)
return NULL;
*stream = tcpd->stream;
*sub_stream = h2session->current_stream_id;
return g_strdup_printf("tcp.stream eq %u and http2.streamid eq %u", tcpd->stream, h2session->current_stream_id);
}
return NULL;
}
static guint32
get_http2_stream_count(guint streamid)
{
guint32 result = 0;
guint32 key;
GHashTable *entry;
GList *entry_set, *it;
entry = (GHashTable*)g_hash_table_lookup(streamid_hash, GUINT_TO_POINTER(streamid));
if (entry != NULL) {
entry_set = g_hash_table_get_keys(entry);
/* this is a doubly-linked list, g_list_sort has the same time complexity */
for (it = entry_set; it != NULL; it = it->next) {
key = GPOINTER_TO_UINT(it->data);
result = key > result ? key : result;
}
g_list_free(entry_set);
}
return result;
}
static gboolean
is_http2_stream_contains(guint streamid, gint sub_stream_id)
{
GHashTable *entry;
entry = (GHashTable*)g_hash_table_lookup(streamid_hash, GUINT_TO_POINTER(streamid));
if (entry == NULL) {
return FALSE;
}
if (!g_hash_table_contains(entry, GINT_TO_POINTER(sub_stream_id))) {
return FALSE;
}
return TRUE;
}
gboolean
http2_get_stream_id_le(guint streamid, guint sub_stream_id, guint *sub_stream_id_out)
{
// HTTP/2 Stream IDs are always 31 bit.
gint max_id = (gint)get_http2_stream_count(streamid);
gint id = (gint)(sub_stream_id & MASK_HTTP2_STREAMID);
if (id > max_id) {
id = max_id;
}
for (; id >= 0; id--) {
if (is_http2_stream_contains(streamid, id)) {
*sub_stream_id_out = (guint)id;
return TRUE;
}
}
return FALSE;
}
gboolean
http2_get_stream_id_ge(guint streamid, guint sub_stream_id, guint *sub_stream_id_out)
{
// HTTP/2 Stream IDs are always 31 bit.
gint max_id = (gint)get_http2_stream_count(streamid);
for (gint id = (gint)(sub_stream_id & MASK_HTTP2_STREAMID); id <= max_id; id++) {
if (is_http2_stream_contains(streamid, id)) {
*sub_stream_id_out = (guint)id;
return TRUE;
}
}
return FALSE;
}
static gchar*
http2_follow_index_filter(guint stream, guint sub_stream)
{
return g_strdup_printf("tcp.stream eq %u and http2.streamid eq %u", stream, sub_stream);
}
static guint8
dissect_http2_header_flags(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http2_tree, guint offset, guint8 type)
{
@ -2645,6 +2754,8 @@ dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat
guint16 length;
guint32 streamid;
struct HTTP2Tap *http2_stats;
GHashTable* entry;
struct tcp_analysis* tcpd;
if(!p_get_proto_data(wmem_file_scope(), pinfo, proto_http2, 0)) {
http2_header_data_t *header_data;
@ -2702,6 +2813,12 @@ dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat
proto_tree_add_item(http2_tree, hf_http2_type, tvb, offset, 1, ENC_BIG_ENDIAN);
type = tvb_get_guint8(tvb, offset);
gint type_idx;
const gchar *type_str = try_val_to_str_idx(type, http2_type_vals, &type_idx);
if (type_str == NULL) {
type_str = wmem_strdup_printf(wmem_packet_scope(), "Unknown type (%d)", type);
}
offset += 1;
flags = dissect_http2_header_flags(tvb, pinfo, http2_tree, offset, type);
@ -2710,17 +2827,27 @@ dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat
proto_tree_add_item(http2_tree, hf_http2_r, tvb, offset, 4, ENC_BIG_ENDIAN);
proto_tree_add_item(http2_tree, hf_http2_streamid, tvb, offset, 4, ENC_BIG_ENDIAN);
streamid = tvb_get_ntohl(tvb, offset) & MASK_HTTP2_STREAMID;
proto_item_append_text(ti, ": %s, Stream ID: %u, Length %u", val_to_str(type, http2_type_vals, "Unknown type (%d)"), streamid, length);
proto_item_append_text(ti, ": %s, Stream ID: %u, Length %u", type_str, streamid, length);
offset += 4;
/* append stream id after frame type on info column, like: HEADERS[1], DATA[1], HEADERS[3], DATA[3] */
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s[%u]", val_to_str(type, http2_type_vals, "Unknown type (%d)"), streamid);
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s[%u]", type_str, streamid);
/* fill hash table with stream ids and skip all unknown frames */
tcpd = get_tcp_conversation_data(NULL, pinfo);
if (tcpd != NULL && type_idx != -1) {
entry = (GHashTable*)g_hash_table_lookup(streamid_hash, GUINT_TO_POINTER(tcpd->stream));
if (entry == NULL) {
entry = g_hash_table_new(NULL, NULL);
g_hash_table_insert(streamid_hash, GUINT_TO_POINTER(tcpd->stream), entry);
}
g_hash_table_add(entry, GUINT_TO_POINTER(streamid));
}
#ifdef HAVE_NGHTTP2
/* Mark the current stream, used for per-stream processing later in the dissection */
http2_session_t *http2_session = get_http2_session(pinfo);
http2_session->current_stream_id = streamid;
#endif
/* Collect stats */
http2_stats = wmem_new0(wmem_packet_scope(), struct HTTP2Tap);
@ -2779,9 +2906,11 @@ dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* dat
proto_tree_add_item(http2_tree, hf_http2_unknown, tvb, offset, -1, ENC_NA);
break;
}
tap_queue_packet(http2_tap, pinfo, http2_stats);
if (have_tap_listener(http2_follow_tap)) {
tap_queue_packet(http2_follow_tap, pinfo, tvb);
}
return tvb_captured_length(tvb);
}
@ -3386,6 +3515,10 @@ proto_register_http2(void)
&addresses_ports_reassembly_table_functions);
http2_tap = register_tap("http2");
http2_follow_tap = register_tap("http2_follow");
register_follow_stream(proto_http2, "http2_follow", http2_follow_conv_filter, http2_follow_index_filter, tcp_follow_address_filter,
tcp_port_to_display, follow_tvb_tap_listener);
}
static void http2_stats_tree_init(stats_tree* st)

View File

@ -1,5 +1,5 @@
/* packet-http2.h
* Routines for HTTP2 dissection
* Routines for HTTP/2 dissection
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
@ -10,6 +10,10 @@
#ifndef __PACKET_HTTP2_H__
#define __PACKET_HTTP2_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
int dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_ );
/** Get header value from current or the other direction stream.
@ -26,12 +30,30 @@ int dissect_http2_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void*
const gchar* http2_get_header_value(packet_info *pinfo, const gchar* name, gboolean the_other_direction);
/**
* Get the HTTP2 Stream ID for the current PDU (typically the DATA frame).
* Get the HTTP/2 Stream ID for the current PDU (typically the DATA frame).
* Only valid when called from a HTTP/2 subdissector.
* Returns 0 if no HTTP/2 session was found.
*/
guint32 http2_get_stream_id(packet_info *pinfo);
/**
* Retrieves the HTTP/2 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
http2_get_stream_id_le(guint streamid, guint sub_stream_id, guint *sub_stream_id_out);
/**
* Retrieves the HTTP/2 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
http2_get_stream_id_ge(guint streamid, guint sub_stream_id, guint *sub_stream_id_out);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
/*

View File

@ -904,7 +904,7 @@ tcp_seq_analysis_packet( void *ptr, packet_info *pinfo, epan_dissect_t *edt _U_,
}
gchar *tcp_follow_conv_filter(packet_info *pinfo, guint *stream)
gchar *tcp_follow_conv_filter(packet_info *pinfo, guint *stream, guint *sub_stream _U_)
{
conversation_t *conv;
struct tcp_analysis *tcpd;
@ -925,7 +925,7 @@ gchar *tcp_follow_conv_filter(packet_info *pinfo, guint *stream)
return NULL;
}
gchar *tcp_follow_index_filter(guint stream)
gchar *tcp_follow_index_filter(guint stream, guint sub_stream _U_)
{
return g_strdup_printf("tcp.stream eq %u", stream);
}

View File

@ -509,8 +509,8 @@ WS_DLL_PUBLIC guint32 get_tcp_stream_count(void);
WS_DLL_PUBLIC guint32 get_mptcp_stream_count(void);
/* Follow Stream functionality shared with HTTP (and SSL?) */
extern gchar *tcp_follow_conv_filter(packet_info *pinfo, guint *stream);
extern gchar *tcp_follow_index_filter(guint stream);
extern gchar *tcp_follow_conv_filter(packet_info *pinfo, guint *stream, guint *sub_stream);
extern gchar *tcp_follow_index_filter(guint stream, guint sub_stream);
extern gchar *tcp_follow_address_filter(address *src_addr, address *dst_addr, int src_port, int dst_port);
#ifdef __cplusplus

View File

@ -425,7 +425,7 @@ udp_build_filter(packet_info *pinfo)
return NULL;
}
static gchar *udp_follow_conv_filter(packet_info *pinfo, guint *stream)
static gchar *udp_follow_conv_filter(packet_info *pinfo, guint *stream, guint *sub_stream _U_)
{
conversation_t *conv;
struct udp_analysis *udpd;
@ -446,7 +446,7 @@ static gchar *udp_follow_conv_filter(packet_info *pinfo, guint *stream)
return NULL;
}
static gchar *udp_follow_index_filter(guint stream)
static gchar *udp_follow_index_filter(guint stream, guint sub_stream _U_)
{
return g_strdup_printf("udp.stream eq %u", stream);
}

View File

@ -42,7 +42,8 @@ typedef enum {
FOLLOW_TCP,
FOLLOW_TLS,
FOLLOW_UDP,
FOLLOW_HTTP
FOLLOW_HTTP,
FOLLOW_HTTP2
} follow_type_t;
/* Show Type */
@ -99,8 +100,8 @@ typedef struct _follow_info {
struct register_follow;
typedef struct register_follow register_follow_t;
typedef gchar* (*follow_conv_filter_func)(packet_info *pinfo, guint *stream);
typedef gchar* (*follow_index_filter_func)(guint stream);
typedef gchar* (*follow_conv_filter_func)(packet_info *pinfo, guint *stream, guint *sub_stream);
typedef gchar* (*follow_index_filter_func)(guint stream, guint sub_stream);
typedef gchar* (*follow_address_filter_func)(address* src_addr, address* dst_addr, int src_port, int dst_port);
typedef gchar* (*follow_port_to_display_func)(wmem_allocator_t *allocator, guint port);

View File

@ -2740,13 +2740,14 @@ sharkd_follower_visit_layers_cb(const void *key _U_, void *value, void *user_dat
const int proto_id = get_follow_proto_id(follower);
guint32 ignore_stream;
guint32 ignore_sub_stream;
if (proto_is_frame_protocol(pi->layers, proto_get_protocol_filter_name(proto_id)))
{
const char *layer_proto = proto_get_protocol_short_name(find_protocol_by_id(proto_id));
char *follow_filter;
follow_filter = get_follow_conv_func(follower)(pi, &ignore_stream);
follow_filter = get_follow_conv_func(follower)(pi, &ignore_stream, &ignore_sub_stream);
json_dumper_begin_array(&dumper);
json_dumper_value_string(&dumper, layer_proto);

View File

@ -57,6 +57,33 @@ class case_dissect_http2(subprocesstest.SubprocessTestCase):
))
self.assertTrue(self.grepOutput('DATA'))
def test_http2_follow_0(self, cmd_tshark, features, dirs, capture_file):
'''Follow HTTP/2 Stream ID 0 test'''
if not features.have_nghttp2:
self.skipTest('Requires nghttp2.')
key_file = os.path.join(dirs.key_dir, 'http2-data-reassembly.keys')
self.assertRun((cmd_tshark,
'-r', capture_file('http2-data-reassembly.pcap'),
'-o', 'tls.keylog_file: {}'.format(key_file),
'-z', 'follow,http2,hex,0,0'
))
self.assertTrue(self.grepOutput('00000000 00 00 12 04 00 00 00 00'))
self.assertFalse(self.grepOutput('00000000 00 00 2c 01 05 00 00 00'))
def test_http2_follow_1(self, cmd_tshark, features, dirs, capture_file):
'''Follow HTTP/2 Stream ID 1 test'''
if not features.have_nghttp2:
self.skipTest('Requires nghttp2.')
key_file = os.path.join(dirs.key_dir, 'http2-data-reassembly.keys')
self.assertRun((cmd_tshark,
'-r', capture_file('http2-data-reassembly.pcap'),
'-o', 'tls.keylog_file: {}'.format(key_file),
'-z', 'follow,http2,hex,0,1'
))
self.assertFalse(self.grepOutput('00000000 00 00 12 04 00 00 00 00'))
self.assertTrue(self.grepOutput('00000000 00 00 2c 01 05 00 00 00'))
@fixtures.mark_usefixtures('test_env')
@fixtures.uses_fixtures
class case_dissect_tcp(subprocesstest.SubprocessTestCase):

View File

@ -37,6 +37,7 @@ typedef struct _cli_follow_info {
/* filter */
int stream_index;
int sub_stream_index;
int port[2];
address addr[2];
union {
@ -338,6 +339,13 @@ follow_arg_filter(const char **opt_argp, follow_info_t *follow_info)
((*opt_argp)[len] == 0 || (*opt_argp)[len] == ','))
{
*opt_argp += len;
/* if it's HTTP2 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] == ','))
{
*opt_argp += len;
}
}
else
{
@ -438,11 +446,20 @@ static void follow_stream(const char *opt_argp, void *userdata)
register_follow_t* follower = (register_follow_t*)userdata;
follow_index_filter_func index_filter;
follow_address_filter_func address_filter;
int proto_id = get_follow_proto_id(follower);
const char* proto_filter_name = proto_get_protocol_filter_name(proto_id);
opt_argp += strlen(STR_FOLLOW);
opt_argp += strlen(proto_get_protocol_filter_name(get_follow_proto_id(follower)));
opt_argp += strlen(proto_filter_name);
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) {
cli_follow_info->sub_stream_index = -1;
} else {
cli_follow_info->sub_stream_index = 0;
}
follow_info = g_new0(follow_info_t, 1);
follow_info->gui_data = cli_follow_info;
cli_follow_info->follower = follower;
@ -455,8 +472,8 @@ static void follow_stream(const char *opt_argp, void *userdata)
if (cli_follow_info->stream_index >= 0)
{
index_filter = get_follow_index_func(follower);
follow_info->filter_out_filter = index_filter(cli_follow_info->stream_index);
if (follow_info->filter_out_filter == NULL)
follow_info->filter_out_filter = index_filter(cli_follow_info->stream_index, cli_follow_info->sub_stream_index);
if (follow_info->filter_out_filter == NULL || cli_follow_info->sub_stream_index < 0)
{
follow_exit("Error creating filter for this stream.");
}

View File

@ -16,6 +16,7 @@
#include "epan/follow.h"
#include "epan/dissectors/packet-tcp.h"
#include "epan/dissectors/packet-udp.h"
#include "epan/dissectors/packet-http2.h"
#include "epan/prefs.h"
#include "epan/addr_resolv.h"
#include "epan/charsets.h"
@ -75,7 +76,8 @@ FollowStreamDialog::FollowStreamDialog(QWidget &parent, CaptureFile &cf, follow_
last_from_server_(0),
turns_(0),
use_regex_find_(false),
terminating_(false)
terminating_(false),
previous_sub_stream_num_(0)
{
ui->setupUi(this);
loadGeometry(parent.width() * 2 / 3, parent.height());
@ -94,6 +96,9 @@ FollowStreamDialog::FollowStreamDialog(QWidget &parent, CaptureFile &cf, follow_
case FOLLOW_HTTP:
follower_ = get_follow_by_name("HTTP");
break;
case FOLLOW_HTTP2:
follower_ = get_follow_by_name("HTTP2");
break;
default :
g_assert_not_reached();
}
@ -369,8 +374,47 @@ void FollowStreamDialog::on_streamNumberSpinBox_valueChanged(int stream_num)
{
if (file_closed_) return;
int sub_stream_num = 0;
ui->subStreamNumberSpinBox->blockSignals(true);
sub_stream_num = ui->subStreamNumberSpinBox->value();
ui->subStreamNumberSpinBox->blockSignals(false);
if (sub_stream_num < 0) {
sub_stream_num = 0;
}
if (stream_num >= 0) {
follow(previous_filter_, true, stream_num);
follow(previous_filter_, true, stream_num, sub_stream_num);
}
}
void FollowStreamDialog::on_subStreamNumberSpinBox_valueChanged(int sub_stream_num)
{
if (file_closed_) return;
int stream_num = 0;
ui->streamNumberSpinBox->blockSignals(true);
stream_num = ui->streamNumberSpinBox->value();
ui->streamNumberSpinBox->blockSignals(false);
guint sub_stream_num_new = static_cast<guint>(sub_stream_num);
gboolean ok;
/* previous_sub_stream_num_ is a hack to track which buttons was pressed without event handling */
if (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 (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);
}
sub_stream_num = static_cast<gint>(sub_stream_num_new);
if (ok) {
follow(previous_filter_, true, stream_num, sub_stream_num);
previous_sub_stream_num_ = sub_stream_num;
}
}
@ -388,6 +432,8 @@ void FollowStreamDialog::removeStreamControls()
ui->horizontalLayout->removeItem(ui->streamNumberSpacer);
ui->streamNumberLabel->setVisible(false);
ui->streamNumberSpinBox->setVisible(false);
ui->subStreamNumberLabel->setVisible(false);
ui->subStreamNumberSpinBox->setVisible(false);
}
void FollowStreamDialog::resetStream()
@ -455,6 +501,7 @@ FollowStreamDialog::readStream()
case FOLLOW_TCP :
case FOLLOW_UDP :
case FOLLOW_HTTP :
case FOLLOW_HTTP2:
case FOLLOW_TLS :
ret = readFollowStream();
break;
@ -771,7 +818,7 @@ FollowStreamDialog::showBuffer(char *buffer, size_t nchars, gboolean is_from_ser
return FRS_OK;
}
bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index, guint stream_num)
bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index, guint stream_num, guint sub_stream_num)
{
QString follow_filter;
const char *hostname0 = NULL, *hostname1 = NULL;
@ -815,9 +862,9 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index,
/* Create a new filter that matches all packets in the TCP stream,
and set the display filter entry accordingly */
if (use_stream_index) {
follow_filter = gchar_free_to_qstring(get_follow_index_func(follower_)(stream_num));
follow_filter = gchar_free_to_qstring(get_follow_index_func(follower_)(stream_num, sub_stream_num));
} else {
follow_filter = gchar_free_to_qstring(get_follow_conv_func(follower_)(&cap_file_.capFile()->edt->pi, &stream_num));
follow_filter = gchar_free_to_qstring(get_follow_conv_func(follower_)(&cap_file_.capFile()->edt->pi, &stream_num, &sub_stream_num));
}
if (follow_filter.isEmpty()) {
QMessageBox::warning(this,
@ -844,6 +891,15 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index,
return false;
}
/* disable substream spin box for all protocols except HTTP2 */
ui->subStreamNumberSpinBox->blockSignals(true);
ui->subStreamNumberSpinBox->setEnabled(false);
ui->subStreamNumberSpinBox->setValue(0);
ui->subStreamNumberSpinBox->setKeyboardTracking(false);
ui->subStreamNumberSpinBox->blockSignals(false);
ui->subStreamNumberSpinBox->setVisible(false);
ui->subStreamNumberLabel->setVisible(false);
switch (follow_type_)
{
case FOLLOW_TCP:
@ -870,6 +926,31 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index,
break;
}
case FOLLOW_HTTP2:
{
int stream_count = get_tcp_stream_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());
guint substream_max_id = 0;
http2_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(ui->subStreamNumberSpinBox->toolTip());
ui->subStreamNumberSpinBox->setVisible(true);
ui->subStreamNumberLabel->setVisible(true);
break;
}
case FOLLOW_TLS:
case FOLLOW_HTTP:
/* No extra handling */

View File

@ -42,7 +42,7 @@ public:
explicit FollowStreamDialog(QWidget &parent, CaptureFile &cf, follow_type_t type = FOLLOW_TCP);
~FollowStreamDialog();
bool follow(QString previous_filter = QString(), bool use_stream_index = false, guint stream_num = 0);
bool follow(QString previous_filter = QString(), bool use_stream_index = false, guint stream_num = 0, guint sub_stream_num = 0);
public slots:
void captureEvent(CaptureEvent e);
@ -69,6 +69,7 @@ private slots:
void goToPacketForTextPos(int text_pos);
void on_streamNumberSpinBox_valueChanged(int stream_num);
void on_subStreamNumberSpinBox_valueChanged(int sub_stream_num);
void on_buttonBox_rejected();
@ -122,6 +123,8 @@ private:
bool use_regex_find_;
bool terminating_;
int previous_sub_stream_num_;
};
#endif // FOLLOW_STREAM_DIALOG_H

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>594</width>
<height>620</height>
<width>609</width>
<height>600</height>
</rect>
</property>
<property name="sizePolicy">
@ -41,7 +41,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,1,0,0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,1,0,0,0,0">
<item>
<widget class="QComboBox" name="cbDirections">
<property name="sizeAdjustPolicy">
@ -99,6 +99,16 @@
<item>
<widget class="QSpinBox" name="streamNumberSpinBox"/>
</item>
<item>
<widget class="QLabel" name="subStreamNumberLabel">
<property name="text">
<string>Substream</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="subStreamNumberSpinBox"/>
</item>
</layout>
</item>
<item>

View File

@ -543,12 +543,14 @@ private slots:
void on_actionAnalyzeDecodeAs_triggered();
void on_actionAnalyzeReloadLuaPlugins_triggered();
void openFollowStreamDialog(follow_type_t type, guint stream_num, bool use_stream_index = true);
void openFollowStreamDialog(follow_type_t type, guint stream_num, guint sub_stream_num, bool use_stream_index = true);
void openFollowStreamDialogForType(follow_type_t type);
void on_actionAnalyzeFollowTCPStream_triggered();
void on_actionAnalyzeFollowUDPStream_triggered();
void on_actionAnalyzeFollowTLSStream_triggered();
void on_actionAnalyzeFollowHTTPStream_triggered();
void on_actionAnalyzeFollowHTTP2Stream_triggered();
void statCommandExpertInfo(const char *, void *);
void on_actionAnalyzeExpertInfo_triggered();

View File

@ -415,6 +415,7 @@
<addaction name="actionAnalyzeFollowUDPStream"/>
<addaction name="actionAnalyzeFollowTLSStream"/>
<addaction name="actionAnalyzeFollowHTTPStream"/>
<addaction name="actionAnalyzeFollowHTTP2Stream"/>
</widget>
<widget class="QMenu" name="menuConversationFilter">
<property name="title">
@ -1711,6 +1712,14 @@
<string notr="true">Ctrl+Alt+Shift+H</string>
</property>
</action>
<action name="actionAnalyzeFollowHTTP2Stream">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>HTTP/2 Stream</string>
</property>
</action>
<action name="actionStatisticsTcpStreamTcptrace">
<property name="text">
<string>Time Sequence (tcptrace)</string>

View File

@ -1114,7 +1114,7 @@ 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;
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;
/* Making the menu context-sensitive allows for easier selection of the
desired item and has the added benefit, with large captures, of
@ -1173,6 +1173,7 @@ void MainWindow::setMenusForSelectedPacket()
&is_ip, &is_tcp, &is_udp, &is_sctp,
&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");
}
}
@ -1205,6 +1206,7 @@ void MainWindow::setMenusForSelectedPacket()
main_ui_->actionAnalyzeFollowUDPStream->setEnabled(is_udp);
main_ui_->actionAnalyzeFollowTLSStream->setEnabled(is_tls);
main_ui_->actionAnalyzeFollowHTTPStream->setEnabled(is_http);
main_ui_->actionAnalyzeFollowHTTP2Stream->setEnabled(is_http2);
foreach(QAction *cc_action, cc_actions) {
cc_action->setEnabled(frame_selected);
@ -2697,7 +2699,7 @@ void MainWindow::on_actionAnalyzeReloadLuaPlugins_triggered()
reloadLuaPlugins();
}
void MainWindow::openFollowStreamDialog(follow_type_t type, guint stream_num, bool use_stream_index) {
void MainWindow::openFollowStreamDialog(follow_type_t type, guint stream_num, guint sub_stream_num, bool use_stream_index) {
FollowStreamDialog *fsd = new FollowStreamDialog(*this, capture_file_, type);
connect(fsd, SIGNAL(updateFilter(QString, bool)), this, SLOT(filterPackets(QString, bool)));
connect(fsd, SIGNAL(goToPacket(int)), packet_list_, SLOT(goToPacket(int)));
@ -2706,14 +2708,14 @@ void MainWindow::openFollowStreamDialog(follow_type_t type, guint stream_num, bo
if (use_stream_index) {
// If a specific conversation was requested, then ignore any previous
// display filters and display all related packets.
fsd->follow("", true, stream_num);
fsd->follow("", true, stream_num, sub_stream_num);
} else {
fsd->follow(getFilter());
}
}
void MainWindow::openFollowStreamDialogForType(follow_type_t type) {
openFollowStreamDialog(type, 0, false);
openFollowStreamDialog(type, 0, 0, false);
}
void MainWindow::on_actionAnalyzeFollowTCPStream_triggered()
@ -2736,6 +2738,11 @@ void MainWindow::on_actionAnalyzeFollowHTTPStream_triggered()
openFollowStreamDialogForType(FOLLOW_HTTP);
}
void MainWindow::on_actionAnalyzeFollowHTTP2Stream_triggered()
{
openFollowStreamDialogForType(FOLLOW_HTTP2);
}
void MainWindow::openSCTPAllAssocsDialog()
{
SCTPAllAssocsDialog *sctp_dialog = new SCTPAllAssocsDialog(this, capture_file_.capFile());

View File

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

View File

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

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>850</width>
<width>969</width>
<height>640</height>
</rect>
</property>
@ -90,12 +90,12 @@
</item>
<item>
<widget class="QComboBox" name="graphTypeComboBox">
<property name="frame">
<bool>false</bool>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="frame">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@ -119,19 +119,19 @@
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="maWindowSizeSpinBox" />
<widget class="QDoubleSpinBox" name="maWindowSizeSpinBox"/>
</item>
<item>
<widget class="QCheckBox" name="selectSACKsCheckBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Allow SACK segments as well as data packets to be selected by clicking on the graph</string>
</property>
<property name="text">
<string>Select SACKs</string>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
@ -180,6 +180,9 @@
</item>
<item>
<widget class="QRadioButton" name="dragRadioButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Drag using the mouse button.</string>
</property>
@ -189,9 +192,6 @@
<property name="checkable">
<bool>true</bool>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<attribute name="buttonGroup">
<string notr="true">mouseButtonGroup</string>
</attribute>
@ -199,6 +199,9 @@
</item>
<item>
<widget class="QRadioButton" name="zoomRadioButton">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Select using the mouse button.</string>
</property>
@ -208,9 +211,6 @@
<property name="checkable">
<bool>true</bool>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<attribute name="buttonGroup">
<string notr="true">mouseButtonGroup</string>
</attribute>
@ -231,80 +231,80 @@
</item>
<item>
<widget class="QCheckBox" name="bySeqNumberCheckBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Display Round Trip Time vs Sequence Number</string>
</property>
<property name="text">
<string>RTT By Sequence Number</string>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showSegLengthCheckBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Display graph of Segment Length vs Time</string>
</property>
<property name="text">
<string>Segment Length</string>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showThroughputCheckBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Display graph of Mean Transmitted Bytes vs Time</string>
</property>
<property name="text">
<string>Throughput</string>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showGoodputCheckBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Display graph of Mean ACKed Bytes vs Time</string>
</property>
<property name="text">
<string>Goodput</string>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showRcvWinCheckBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Display graph of Receive Window Size vs Time</string>
</property>
<property name="text">
<string>Rcv Win</string>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showBytesOutCheckBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Display graph of Outstanding Bytes vs Time</string>
</property>
<property name="text">
<string>Bytes Out</string>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>