aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--debian/libwireshark0.symbols2
-rw-r--r--doc/tshark.pod23
-rw-r--r--docbook/CMakeLists.txt1
-rw-r--r--docbook/wsug_graphics/ws-follow-http2-stream.pngbin0 -> 57209 bytes
-rw-r--r--docbook/wsug_src/WSUG_chapter_advanced.adoc9
-rw-r--r--epan/dissectors/packet-http2.c151
-rw-r--r--epan/dissectors/packet-http2.h26
-rw-r--r--epan/dissectors/packet-tcp.c4
-rw-r--r--epan/dissectors/packet-tcp.h4
-rw-r--r--epan/dissectors/packet-udp.c4
-rw-r--r--epan/follow.h7
-rw-r--r--sharkd_session.c3
-rw-r--r--test/suite_dissection.py27
-rw-r--r--ui/cli/tap-follow.c23
-rw-r--r--ui/qt/follow_stream_dialog.cpp91
-rw-r--r--ui/qt/follow_stream_dialog.h5
-rw-r--r--ui/qt/follow_stream_dialog.ui16
-rw-r--r--ui/qt/main_window.h4
-rw-r--r--ui/qt/main_window.ui9
-rw-r--r--ui/qt/main_window_slots.cpp15
-rw-r--r--ui/qt/packet_list.cpp1
-rw-r--r--ui/qt/proto_tree.cpp1
-rw-r--r--ui/qt/tcp_stream_dialog.ui64
23 files changed, 418 insertions, 72 deletions
diff --git a/debian/libwireshark0.symbols b/debian/libwireshark0.symbols
index 7e6e9df4c7..bcb243932e 100644
--- a/debian/libwireshark0.symbols
+++ b/debian/libwireshark0.symbols
@@ -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
diff --git a/doc/tshark.pod b/doc/tshark.pod
index 803272705b..afcacf332e 100644
--- a/doc/tshark.pod
+++ b/doc/tshark.pod
@@ -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
diff --git a/docbook/CMakeLists.txt b/docbook/CMakeLists.txt
index 12b2820090..2742ce5b2b 100644
--- a/docbook/CMakeLists.txt
+++ b/docbook/CMakeLists.txt
@@ -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
diff --git a/docbook/wsug_graphics/ws-follow-http2-stream.png b/docbook/wsug_graphics/ws-follow-http2-stream.png
new file mode 100644
index 0000000000..616dfd7fbe
--- /dev/null
+++ b/docbook/wsug_graphics/ws-follow-http2-stream.png
Binary files differ
diff --git a/docbook/wsug_src/WSUG_chapter_advanced.adoc b/docbook/wsug_src/WSUG_chapter_advanced.adoc
index fe5bdffa50..6eb53fc329 100644
--- a/docbook/wsug_src/WSUG_chapter_advanced.adoc
+++ b/docbook/wsug_src/WSUG_chapter_advanced.adoc
@@ -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
diff --git a/epan/dissectors/packet-http2.c b/epan/dissectors/packet-http2.c
index f59cec3fab..0bd6698d2b 100644
--- a/epan/dissectors/packet-http2.c
+++ b/epan/dissectors/packet-http2.c
@@ -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)
diff --git a/epan/dissectors/packet-http2.h b/epan/dissectors/packet-http2.h
index a121dcd4b3..b76f09c31f 100644
--- a/epan/dissectors/packet-http2.h
+++ b/epan/dissectors/packet-http2.h
@@ -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
/*
diff --git a/epan/dissectors/packet-tcp.c b/epan/dissectors/packet-tcp.c
index 010a090c9c..8b74680b7d 100644
--- a/epan/dissectors/packet-tcp.c
+++ b/epan/dissectors/packet-tcp.c
@@ -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);
}
diff --git a/epan/dissectors/packet-tcp.h b/epan/dissectors/packet-tcp.h
index b009522b1b..dfee9cdeb4 100644
--- a/epan/dissectors/packet-tcp.h
+++ b/epan/dissectors/packet-tcp.h
@@ -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
diff --git a/epan/dissectors/packet-udp.c b/epan/dissectors/packet-udp.c
index 1e74cd7ac5..99def2fa6e 100644
--- a/epan/dissectors/packet-udp.c
+++ b/epan/dissectors/packet-udp.c
@@ -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);
}
diff --git a/epan/follow.h b/epan/follow.h
index f8053d139c..aaa366e0e8 100644
--- a/epan/follow.h
+++ b/epan/follow.h
@@ -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);
diff --git a/sharkd_session.c b/sharkd_session.c
index c2f7b53c90..4ca9d0af1f 100644
--- a/sharkd_session.c
+++ b/sharkd_session.c
@@ -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);
diff --git a/test/suite_dissection.py b/test/suite_dissection.py
index eac8529d03..ba7500f72e 100644
--- a/test/suite_dissection.py
+++ b/test/suite_dissection.py
@@ -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):
diff --git a/ui/cli/tap-follow.c b/ui/cli/tap-follow.c
index bb6380fc5a..c4d7399fd1 100644
--- a/ui/cli/tap-follow.c
+++ b/ui/cli/tap-follow.c
@@ -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.");
}
diff --git a/ui/qt/follow_stream_dialog.cpp b/ui/qt/follow_stream_dialog.cpp
index d6f96f7679..c845830420 100644
--- a/ui/qt/follow_stream_dialog.cpp
+++ b/ui/qt/follow_stream_dialog.cpp
@@ -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 */
diff --git a/ui/qt/follow_stream_dialog.h b/ui/qt/follow_stream_dialog.h
index 4988092a6c..a2c3928fae 100644
--- a/ui/qt/follow_stream_dialog.h
+++ b/ui/qt/follow_stream_dialog.h
@@ -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
diff --git a/ui/qt/follow_stream_dialog.ui b/ui/qt/follow_stream_dialog.ui
index 066b90b857..2d70316471 100644
--- a/ui/qt/follow_stream_dialog.ui
+++ b/ui/qt/follow_stream_dialog.ui
@@ -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>
diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h
index 2be67fbe16..a5d8077012 100644
--- a/ui/qt/main_window.h
+++ b/ui/qt/main_window.h
@@ -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();
diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui
index 1f03a535dc..06ffdd1df4 100644
--- a/ui/qt/main_window.ui
+++ b/ui/qt/main_window.ui
@@ -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>
diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp
index 3516730475..a164c20865 100644
--- a/ui/qt/main_window_slots.cpp
+++ b/ui/qt/main_window_slots.cpp
@@ -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());
diff --git a/ui/qt/packet_list.cpp b/ui/qt/packet_list.cpp
index 3304fe362c..bc8ffc287a 100644
--- a/ui/qt/packet_list.cpp
+++ b/ui/qt/packet_list.cpp
@@ -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();
diff --git a/ui/qt/proto_tree.cpp b/ui/qt/proto_tree.cpp
index d23c0879aa..bc72ca7b3d 100644
--- a/ui/qt/proto_tree.cpp
+++ b/ui/qt/proto_tree.cpp
@@ -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();
}
diff --git a/ui/qt/tcp_stream_dialog.ui b/ui/qt/tcp_stream_dialog.ui
index d21074818e..d732f2665e 100644
--- a/ui/qt/tcp_stream_dialog.ui
+++ b/ui/qt/tcp_stream_dialog.ui
@@ -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>