summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--Makefile.common1
-rw-r--r--doc/tshark.pod11
-rw-r--r--epan/dissectors/packet-icmpv6.c278
-rw-r--r--tap-icmpv6stat.c320
5 files changed, 566 insertions, 45 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 097dac5fc9..f721448aca 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -549,6 +549,7 @@ set(TSHARK_TAP_SRC
tap-h225rassrt.c
tap-httpstat.c
tap-icmpstat.c
+ tap-icmpv6stat.c
tap-iostat.c
tap-iousers.c
tap-mgcpstat.c
diff --git a/Makefile.common b/Makefile.common
index 1ae047e3fd..9767b94c7a 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -116,6 +116,7 @@ TSHARK_TAP_SRC = \
tap-hosts.c \
tap-httpstat.c \
tap-icmpstat.c \
+ tap-icmpv6stat.c \
tap-iostat.c \
tap-iousers.c \
tap-mgcpstat.c \
diff --git a/doc/tshark.pod b/doc/tshark.pod
index 82e5b6f12d..f444873b18 100644
--- a/doc/tshark.pod
+++ b/doc/tshark.pod
@@ -721,6 +721,17 @@ for ICMP echo request packets originating from a specific host.
This option can be used multiple times on the command line.
+=item B<-z> icmpv6,srt[,I<filter>]
+
+Compute total ICMPv6 echo requests, replies, loss, and percent loss, as well as
+minimum, maximum, mean, median and sample standard deviation SRT statistics
+typical of what ping provides.
+
+Example: S<B<-z icmpv6,srt,ip.src==1.2.3.4>> will collect ICMPv6 SRT statistics
+for ICMPv6 echo request packets originating from a specific host.
+
+This option can be used multiple times on the command line.
+
=item B<-z> io,phs[,I<filter>]
Create Protocol Hierarchy Statistics listing both number of packets and bytes.
diff --git a/epan/dissectors/packet-icmpv6.c b/epan/dissectors/packet-icmpv6.c
index 41b5a3f96f..9313e1d7e3 100644
--- a/epan/dissectors/packet-icmpv6.c
+++ b/epan/dissectors/packet-icmpv6.c
@@ -46,11 +46,15 @@
#include <epan/asn1.h>
#include <epan/strutil.h>
#include <epan/expert.h>
+#include <epan/conversation.h>
+#include <epan/emem.h>
+#include <epan/tap.h>
#include "packet-ber.h"
#include "packet-dns.h"
#include "packet-x509af.h"
#include "packet-x509if.h"
+#include "packet-icmp.h" /* same transaction_t used both both v4 and v6 */
/*
* The information used comes from:
@@ -431,6 +435,20 @@ static int hf_icmpv6_rpl_opt_prefix_plifetime = -1;
static int hf_icmpv6_rpl_opt_prefix_length = -1;
static int hf_icmpv6_rpl_opt_targetdesc = -1;
+static int icmpv6_tap = -1;
+
+/* Conversation related data */
+static int hf_icmpv6_resp_in = -1;
+static int hf_icmpv6_resp_to = -1;
+static int hf_icmpv6_resptime = -1;
+
+typedef struct _icmpv6_conv_info_t {
+ emem_tree_t *pdus;
+} icmpv6_conv_info_t;
+
+static icmp_transaction_t *transaction_start(packet_info *pinfo, proto_tree *tree, guint32 *key);
+static icmp_transaction_t *transaction_end(packet_info *pinfo, proto_tree *tree, guint32 *key);
+
static gint ett_icmpv6 = -1;
static gint ett_icmpv6_opt = -1;
static gint ett_icmpv6_mar = -1;
@@ -1056,6 +1074,131 @@ dissect_contained_icmpv6(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tr
pinfo->in_error_pkt = save_in_error_pkt;
}
+
+/* ======================================================================= */
+static conversation_t *_find_or_create_conversation(packet_info *pinfo)
+{
+ conversation_t *conv = NULL;
+
+ /* Have we seen this conversation before? */
+ conv = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst,
+ pinfo->ptype, 0, 0, 0);
+ if ( conv == NULL )
+ {
+ /* No, this is a new conversation. */
+ conv = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst,
+ pinfo->ptype, 0, 0, 0);
+ }
+ return (conv);
+}
+
+/* ======================================================================= */
+static icmp_transaction_t *transaction_start(packet_info *pinfo, proto_tree *tree, guint32 *key)
+{
+ conversation_t *conversation;
+ icmpv6_conv_info_t *icmpv6_info;
+ icmp_transaction_t *icmpv6_trans;
+ emem_tree_key_t icmpv6_key[2];
+ proto_item *it;
+
+ /* Handle the conversation tracking */
+ conversation = _find_or_create_conversation(pinfo);
+ icmpv6_info = conversation_get_proto_data(conversation, proto_icmpv6);
+ if ( icmpv6_info == NULL )
+ {
+ icmpv6_info = se_alloc(sizeof(icmpv6_conv_info_t));
+ icmpv6_info->pdus = se_tree_create_non_persistent(
+ EMEM_TREE_TYPE_RED_BLACK, "icmpv6_pdus");
+ conversation_add_proto_data(conversation, proto_icmpv6, icmpv6_info);
+ }
+
+ icmpv6_key[0].length = 2;
+ icmpv6_key[0].key = key;
+ icmpv6_key[1].length = 0;
+ icmpv6_key[1].key = NULL;
+ if ( !PINFO_FD_VISITED(pinfo) )
+ {
+ icmpv6_trans = se_alloc(sizeof(icmp_transaction_t));
+ icmpv6_trans->rqst_frame = PINFO_FD_NUM(pinfo);
+ icmpv6_trans->resp_frame = 0;
+ icmpv6_trans->rqst_time = pinfo->fd->abs_ts;
+ icmpv6_trans->resp_time = 0.0;
+ se_tree_insert32_array(icmpv6_info->pdus, icmpv6_key, (void *)icmpv6_trans);
+ }
+ else /* Already visited this frame */
+ icmpv6_trans = se_tree_lookup32_array(icmpv6_info->pdus, icmpv6_key);
+
+ if ( icmpv6_trans == NULL )
+ return (NULL);
+
+ /* Print state tracking in the tree */
+ if ( tree && icmpv6_trans->resp_frame &&
+ (icmpv6_trans->rqst_frame == PINFO_FD_NUM(pinfo)) )
+ {
+ it = proto_tree_add_uint(tree, hf_icmpv6_resp_in, NULL, 0, 0,
+ icmpv6_trans->resp_frame);
+ PROTO_ITEM_SET_GENERATED(it);
+ }
+
+ return (icmpv6_trans);
+
+} /* transaction_start() */
+
+/* ======================================================================= */
+static icmp_transaction_t *transaction_end(packet_info *pinfo, proto_tree *tree, guint32 *key)
+{
+ conversation_t *conversation;
+ icmpv6_conv_info_t *icmpv6_info;
+ icmp_transaction_t *icmpv6_trans;
+ emem_tree_key_t icmpv6_key[2];
+ proto_item *it;
+ nstime_t ns;
+
+ conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst,
+ pinfo->ptype, 0, 0, 0);
+ if ( conversation == NULL )
+ return (NULL);
+
+ icmpv6_info = conversation_get_proto_data(conversation, proto_icmpv6);
+ if ( icmpv6_info == NULL )
+ return (NULL);
+
+ icmpv6_key[0].length = 2;
+ icmpv6_key[0].key = key;
+ icmpv6_key[1].length = 0;
+ icmpv6_key[1].key = NULL;
+ icmpv6_trans = se_tree_lookup32_array(icmpv6_info->pdus, icmpv6_key);
+ if ( icmpv6_trans == NULL )
+ return (NULL);
+
+ /* Print state tracking in the tree */
+ if ( icmpv6_trans->rqst_frame &&
+ (icmpv6_trans->rqst_frame < PINFO_FD_NUM(pinfo)) &&
+ ((icmpv6_trans->resp_frame == 0) ||
+ (icmpv6_trans->resp_frame == PINFO_FD_NUM(pinfo))) )
+ {
+ icmpv6_trans->resp_frame = PINFO_FD_NUM(pinfo);
+ if ( tree )
+ {
+ it = proto_tree_add_uint(tree, hf_icmpv6_resp_to, NULL, 0, 0,
+ icmpv6_trans->rqst_frame);
+ PROTO_ITEM_SET_GENERATED(it);
+ }
+
+ nstime_delta(&ns, &pinfo->fd->abs_ts, &icmpv6_trans->rqst_time);
+ icmpv6_trans->resp_time = nstime_to_msec(&ns);
+ if ( tree )
+ {
+ it = proto_tree_add_double_format_value(tree, hf_icmpv6_resptime, NULL,
+ 0, 0, icmpv6_trans->resp_time, "%.3f ms", icmpv6_trans->resp_time);
+ PROTO_ITEM_SET_GENERATED(it);
+ }
+ }
+
+ return (icmpv6_trans);
+
+} /* transaction_end() */
+
static void
dissect_icmpv6_nd_opt(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree)
{
@@ -2827,7 +2970,7 @@ static void
dissect_icmpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
proto_tree *icmp6_tree = NULL, *flag_tree = NULL;
- proto_item *ti = NULL, *hidden_item, *checksum_item, *code_item= NULL, *ti_flag = NULL;
+ proto_item *ti = NULL, *hidden_item, *checksum_item, *code_item = NULL, *ti_flag = NULL;
const char *code_name = NULL;
guint length, reported_length;
vec_t cksum_vec[4];
@@ -2836,6 +2979,7 @@ dissect_icmpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
int offset;
tvbuff_t *next_tvb;
guint8 icmp6_type, icmp6_code;
+ icmp_transaction_t *trans = NULL;
col_set_str(pinfo->cinfo, COL_PROTOCOL, "ICMPv6");
col_clear(pinfo->cinfo, COL_INFO);
@@ -2854,10 +2998,8 @@ dissect_icmpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
col_add_str(pinfo->cinfo, COL_INFO, val_to_str(icmp6_type, icmpv6_type_val, "Unknown (%d)"));
- if (tree) {
- /* Code */
+ if (tree)
code_item = proto_tree_add_item(icmp6_tree, hf_icmpv6_code, tvb, offset, 1, FALSE);
- }
icmp6_code = tvb_get_guint8(tvb, offset);
offset += 1;
@@ -2886,28 +3028,26 @@ dissect_icmpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
break;
}
- if(code_name)
- {
+ if (code_name)
col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", code_name);
- }
- /* RFC 4380 5.2.9. Direct IPv6 Connectivity Test */
- if (pinfo->destport == 0x0dd8 && icmp6_type == ICMP6_ECHO_REQUEST) {
+ /* RFC 4380
+ * 2.7. Teredo UDP Port
+ * 5.2.9. Direct IPv6 Connectivity Test */
+ if (pinfo->destport == 3544 && icmp6_type == ICMP6_ECHO_REQUEST) {
col_set_str(pinfo->cinfo, COL_PROTOCOL, "Teredo");
col_set_str(pinfo->cinfo, COL_INFO, "Direct IPv6 Connectivity Test");
}
if (tree) {
-
- if(code_name)
- {
+ if (code_name)
proto_item_append_text(code_item, " (%s)", code_name);
- }
-
- /* Checksum */
checksum_item = proto_tree_add_item(icmp6_tree, hf_icmpv6_checksum, tvb, offset, 2, FALSE);
+ }
+
+ cksum = tvb_get_ntohs(tvb, offset);
- cksum = tvb_get_ntohs(tvb, offset);
+ if (tree) {
length = tvb_length(tvb);
reported_length = tvb_reported_length(tvb);
if (!pinfo->fragmented && length >= reported_length) {
@@ -2938,9 +3078,66 @@ dissect_icmpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
"ICMPv6 Checksum Incorrect, should be 0x%04x", in_cksum_shouldbe(cksum, computed_cksum));
}
}
+ }
+ offset += 2;
+
+ if (icmp6_type == ICMP6_ECHO_REQUEST || icmp6_type == ICMP6_ECHO_REPLY) {
+ guint16 identifier, sequence;
+
+ /* Identifier */
+ if (tree)
+ proto_tree_add_item(icmp6_tree, hf_icmpv6_echo_identifier, tvb, offset, 2, FALSE);
+ identifier = tvb_get_ntohs(tvb, offset);
+ offset += 2;
+
+ /* Sequence Number */
+ if (tree)
+ proto_tree_add_item(icmp6_tree, hf_icmpv6_echo_sequence_number, tvb, offset, 2, FALSE);
+ sequence = tvb_get_ntohs(tvb, offset);
+ offset += 2;
+
+ col_append_fstr(pinfo->cinfo, COL_INFO, " id=0x%04x, seq=%u", identifier, sequence);
+
+ if (pinfo->destport == 3544 && icmp6_type == ICMP6_ECHO_REQUEST) {
+ /* RFC 4380
+ * 2.7. Teredo UDP Port
+ * 5.2.9. Direct IPv6 Connectivity Test
+ *
+ * TODO: Clarify the nonce: The RFC states, "(It is recommended to
+ * use a random number [the nonce] at least 64 bits long.)"
+ *
+ * Shouldn't the nonce be at least 8 then? Why not just use (-1),
+ * as it could really be any length, couldn't it?
+ */
+ if (tree)
+ proto_tree_add_item(icmp6_tree, hf_icmpv6_nonce, tvb, offset, 4, FALSE);
+ offset += 4;
+ } else {
+ if (!pinfo->in_error_pkt) {
+ guint32 conv_key[2];
+
+ conv_key[1] = (guint32)((identifier << 16) | sequence);
+
+ if (icmp6_type == ICMP6_ECHO_REQUEST) {
+ conv_key[0] = (guint32)cksum;
+ trans = transaction_start(pinfo, icmp6_tree, conv_key);
+ } else { /* ICMP6_ECHO_REPLY */
+ guint16 tmp[2];
+
+ tmp[0] = ~cksum;
+ tmp[1] = ~0x0100; /* The difference between echo request & reply */
+ cksum_vec[0].len = sizeof(tmp);
+ cksum_vec[0].ptr = (guint8 *)tmp;
+ conv_key[0] = in_cksum(cksum_vec, 1);
+ trans = transaction_end(pinfo, icmp6_tree, conv_key);
+ }
+ }
+ next_tvb = tvb_new_subset(tvb, offset, -1, -1);
+ call_dissector(data_handle, next_tvb, pinfo, icmp6_tree);
+ }
+ }
- offset +=2;
-
+ if (tree) {
/* decode... */
switch (icmp6_type) {
case ICMP6_DST_UNREACH: /* Destination Unreachable (1) */
@@ -2965,34 +3162,10 @@ dissect_icmpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
dissect_contained_icmpv6(tvb, offset, pinfo, icmp6_tree);
break;
- case ICMP6_ECHO_REQUEST: /* Echo Request (128) */
- case ICMP6_ECHO_REPLY: /* Echo Reply (129) */
- {
- guint16 identifier, sequence;
- /* Identifier */
- proto_tree_add_item(icmp6_tree, hf_icmpv6_echo_identifier, tvb, offset, 2, FALSE);
- identifier = tvb_get_ntohs(tvb, offset);
- offset += 2;
-
- /* Sequence Number */
- proto_tree_add_item(icmp6_tree, hf_icmpv6_echo_sequence_number, tvb, offset, 2, FALSE);
- sequence = tvb_get_ntohs(tvb, offset);
- offset += 2;
-
- col_append_fstr(pinfo->cinfo, COL_INFO, " id=0x%04x, seq=%u", identifier, sequence);
-
- if (pinfo->destport == 0x0dd8 && icmp6_type == ICMP6_ECHO_REQUEST) {
- /* RFC 4380
- * 5.2.9. Direct IPv6 Connectivity Test
- */
- proto_tree_add_item(icmp6_tree, hf_icmpv6_nonce, tvb, offset, 4, FALSE);
- offset += 4;
- } else {
- next_tvb = tvb_new_subset(tvb, offset, -1, -1);
- call_dissector(data_handle,next_tvb, pinfo, icmp6_tree);
- }
+ case ICMP6_ECHO_REQUEST: /* Echo Request (128) */
+ case ICMP6_ECHO_REPLY: /* Echo Reply (129) */
+ /* Already handled above */
break;
- }
case ICMP6_MEMBERSHIP_QUERY: /* Multicast Listener Query (130) */
case ICMP6_MEMBERSHIP_REPORT: /* Multicast Listener Report (131) */
case ICMP6_MEMBERSHIP_REDUCTION: /* Multicast Listener Done (132) */
@@ -3390,6 +3563,9 @@ dissect_icmpv6(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
break;
} /* switch (icmp6_type) */
} /* if (tree) */
+
+ if (trans)
+ tap_queue_packet(icmpv6_tap, pinfo, trans);
}
void
@@ -4397,6 +4573,17 @@ proto_register_icmpv6(void)
{ &hf_icmpv6_rpl_opt_targetdesc,
{ "Descriptor", "icmpv6.rpl.opt.targetdesc.descriptor", FT_UINT32, BASE_HEX, NULL, 0x0,
"Opaque Data", HFILL }},
+
+ /* Conversation-related [generated] header fields */
+ { &hf_icmpv6_resp_in,
+ { "Response In", "icmpv6.resp_in", FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+ "The response to this request is in this frame", HFILL }},
+ { &hf_icmpv6_resp_to,
+ { "Response To", "icmpv6.resp_to", FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+ "This is the response to the request in this frame", HFILL }},
+ { &hf_icmpv6_resptime,
+ { "Response Time", "icmpv6.resptime", FT_DOUBLE, BASE_NONE, NULL, 0x0,
+ "The time between the request and the response, in ms.", HFILL }}
};
static gint *ett[] = {
@@ -4443,6 +4630,7 @@ proto_register_icmpv6(void)
proto_register_subtree_array(ett, array_length(ett));
register_dissector("icmpv6", dissect_icmpv6, proto_icmpv6);
+ icmpv6_tap = register_tap("icmpv6");
}
void
diff --git a/tap-icmpv6stat.c b/tap-icmpv6stat.c
new file mode 100644
index 0000000000..12659b26a7
--- /dev/null
+++ b/tap-icmpv6stat.c
@@ -0,0 +1,320 @@
+/* tap-icmpv6stat.c
+ * icmpv6stat 2011 Christopher Maynard
+ *
+ * $Id$
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* This module provides icmpv6 echo request/reply SRT statistics to tshark.
+ * It is only used by tshark and not wireshark
+ *
+ * It was based on tap-icmptat.c, which itself was based on tap-rpcstat.c and
+ * doc/README.tapping.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+
+#include <string.h>
+#include "epan/packet_info.h"
+#include <epan/tap.h>
+#include <epan/stat_cmd_args.h>
+#include <epan/dissectors/packet-icmp.h>
+#include <math.h>
+
+/* used to keep track of the ICMPv6 statistics */
+typedef struct _icmpv6stat_t {
+ char *filter;
+ GSList *rt_list;
+ guint num_rqsts;
+ guint num_resps;
+ guint min_frame;
+ guint max_frame;
+ double min_msecs;
+ double max_msecs;
+ double tot_msecs;
+} icmpv6stat_t;
+
+
+/* This callback is never used by tshark but it is here for completeness. When
+ * registering below, we could just have left this function as NULL.
+ *
+ * When used by wireshark, this function will be called whenever we would need
+ * to reset all state, such as when wireshark opens a new file, when it starts
+ * a new capture, when it rescans the packetlist after some prefs have changed,
+ * etc.
+ *
+ * So if your application has some state it needs to clean up in those
+ * situations, here is a good place to put that code.
+ */
+static void
+icmpv6stat_reset(void *tapdata)
+{
+ icmpv6stat_t *icmpv6stat = tapdata;
+
+ g_slist_free(icmpv6stat->rt_list);
+ memset(icmpv6stat, 0, sizeof(icmpv6stat_t));
+ icmpv6stat->min_msecs = 1.0 * G_MAXUINT;
+}
+
+
+static gint compare_doubles(gconstpointer a, gconstpointer b)
+{
+ double ad, bd;
+
+ ad = *(double *)a;
+ bd = *(double *)b;
+
+ if (ad < bd)
+ return -1;
+ if (ad > bd)
+ return 1;
+ return 0;
+}
+
+
+/* This callback is invoked whenever the tap system has seen a packet we might
+ * be interested in. The function is to be used to only update internal state
+ * information in the *tapdata structure, and if there were state changes which
+ * requires the window to be redrawn, return 1 and (*draw) will be called
+ * sometime later.
+ *
+ * This function should be as lightweight as possible since it executes
+ * together with the normal wireshark dissectors. Try to push as much
+ * processing as possible into (*draw) instead since that function executes
+ * asynchronously and does not affect the main thread's performance.
+ *
+ * If it is possible, try to do all "filtering" explicitly since you will get
+ * MUCH better performance than applying a similar display-filter in the
+ * register call.
+ *
+ * The third parameter is tap dependent. Since we register this one to the
+ * "icmpv6" tap, the third parameter type is icmp_transaction_t.
+ *
+ * function returns :
+ * 0: no updates, no need to call (*draw) later
+ * !0: state has changed, call (*draw) sometime later
+ */
+static int
+icmpv6stat_packet(void *tapdata, packet_info *pinfo _U_, epan_dissect_t *edt _U_, const void *data)
+{
+ icmpv6stat_t *icmpv6stat = tapdata;
+ const icmp_transaction_t *trans = data;
+ double *rt;
+
+ if (trans == NULL)
+ return 0;
+
+ if (trans->resp_frame) {
+ rt = g_malloc(sizeof(double));
+ if (rt == NULL)
+ return 0;
+ *rt = trans->resp_time;
+ icmpv6stat->rt_list = g_slist_insert_sorted(icmpv6stat->rt_list, rt, compare_doubles);
+ icmpv6stat->num_resps++;
+ if (icmpv6stat->min_msecs > trans->resp_time) {
+ icmpv6stat->min_frame = trans->resp_frame;
+ icmpv6stat->min_msecs = trans->resp_time;
+ }
+ if (icmpv6stat->max_msecs < trans->resp_time) {
+ icmpv6stat->max_frame = trans->resp_frame;
+ icmpv6stat->max_msecs = trans->resp_time;
+ }
+ icmpv6stat->tot_msecs += trans->resp_time;
+ } else if (trans->rqst_frame)
+ icmpv6stat->num_rqsts++;
+ else
+ return 0;
+
+ return 1;
+}
+
+
+/*
+ * Compute the mean, median and standard deviation.
+ */
+static void compute_stats(icmpv6stat_t *icmpv6stat, double *mean, double *med, double *sdev)
+{
+ GSList *slist = icmpv6stat->rt_list;
+ double diff;
+ double sq_diff_sum = 0.0;
+
+ if (icmpv6stat->num_resps == 0 || slist == NULL) {
+ *mean = 0.0;
+ *med = 0.0;
+ *sdev = 0.0;
+ return;
+ }
+
+ /* (arithmetic) mean */
+ *mean = icmpv6stat->tot_msecs / icmpv6stat->num_resps;
+
+ /* median: If we have an odd number of elements in our list, then the
+ * median is simply the middle element, otherwise the median is computed by
+ * averaging the 2 elements on either side of the mid-point. */
+ if (icmpv6stat->num_resps & 1)
+ *med = *(double *)g_slist_nth_data(slist, icmpv6stat->num_resps / 2);
+ else {
+ *med =
+ (*(double *)g_slist_nth_data(slist, (icmpv6stat->num_resps - 1) / 2) +
+ *(double *)g_slist_nth_data(slist, icmpv6stat->num_resps / 2)) / 2;
+ }
+
+ /* (sample) standard deviation */
+ for ( ; slist; slist = g_slist_next(slist)) {
+ diff = *(double *)slist->data - *mean;
+ sq_diff_sum += diff * diff;
+ }
+ if (icmpv6stat->num_resps > 1)
+ *sdev = sqrt(sq_diff_sum / (icmpv6stat->num_resps - 1));
+ else
+ *sdev = 0.0;
+}
+
+
+/* This callback is used when tshark wants us to draw/update our data to the
+ * output device. Since this is tshark, the only output is stdout.
+ * TShark will only call this callback once, which is when tshark has finished
+ * reading all packets and exits.
+ * If used with wireshark this may be called any time, perhaps once every 3
+ * seconds or so.
+ * This function may even be called in parallel with (*reset) or (*draw), so
+ * make sure there are no races. The data in the icmpv6stat_t can thus change
+ * beneath us. Beware!
+ *
+ * How best to display the data? For now, following other tap statistics
+ * output, but here are a few other alternatives we might choose from:
+ *
+ * -> Windows ping output:
+ * Ping statistics for <IP>:
+ * Packets: Sent = <S>, Received = <R>, Lost = <L> (<LP>% loss),
+ * Approximate round trip times in milli-seconds:
+ * Minimum = <m>ms, Maximum = <M>ms, Average = <A>ms
+ *
+ * -> Cygwin ping output:
+ * ----<HOST> PING Statistics----
+ * <S> packets transmitted, <R> packets received, <LP>% packet loss
+ * round-trip (ms) min/avg/max/med = <m>/<M>/<A>/<D>
+ *
+ * -> Linux ping output:
+ * --- <HOST> ping statistics ---
+ * <S> packets transmitted, <R> received, <LP>% packet loss, time <T>ms
+ * rtt min/avg/max/mdev = <m>/<A>/<M>/<D> ms
+ */
+static void
+icmpv6stat_draw(void *tapdata)
+{
+ icmpv6stat_t *icmpv6stat = tapdata;
+ unsigned int lost;
+ double mean, sdev, med;
+
+ printf("\n");
+ printf("==========================================================================\n");
+ printf("ICMPv6 Service Response Time (SRT) Statistics (all times in ms):\n");
+ printf("Filter: %s\n", icmpv6stat->filter ? icmpv6stat->filter : "<none>");
+ printf("\nRequests Replies Lost %% Loss\n");
+
+ if (icmpv6stat->num_rqsts) {
+ lost = icmpv6stat->num_rqsts - icmpv6stat->num_resps;
+ compute_stats(icmpv6stat, &mean, &med, &sdev);
+
+ printf("%-10u%-10u%-10u%5.1f%%\n\n",
+ icmpv6stat->num_rqsts, icmpv6stat->num_resps, lost,
+ 100.0 * lost / icmpv6stat->num_rqsts);
+ printf("Minimum Maximum Mean Median SDeviation Min Frame Max Frame\n");
+ printf("%-10.3f%-10.3f%-10.3f%-10.3f%-10.3f %-10u%-10u\n",
+ icmpv6stat->min_msecs >= G_MAXUINT ? 0.0 : icmpv6stat->min_msecs,
+ icmpv6stat->max_msecs, mean, med, sdev,
+ icmpv6stat->min_frame, icmpv6stat->max_frame);
+ } else {
+ printf("0 0 0 0.0%%\n\n");
+ printf("Minimum Maximum Mean Median SDeviation Min Frame Max Frame\n");
+ printf("0.000 0.000 0.000 0.000 0.000 0 0\n");
+ }
+ printf("==========================================================================\n");
+}
+
+
+/* When called, this function will create a new instance of icmpv6stat.
+ *
+ * This function is called from tshark when it parses the -z icmpv6, arguments
+ * and it creates a new instance to store statistics in and registers this new
+ * instance for the icmpv6 tap.
+ */
+static void
+icmpv6stat_init(const char *optarg, void* userdata _U_)
+{
+ icmpv6stat_t *icmpv6stat;
+ const char *filter = NULL;
+ GString *error_string;
+
+ if (strstr(optarg, "icmpv6,srt,"))
+ filter = optarg + strlen("icmpv6,srt,");
+
+ icmpv6stat = g_try_malloc(sizeof(icmpv6stat_t));
+ if (icmpv6stat == NULL) {
+ fprintf(stderr, "tshark: g_try_malloc() fatal error.\n");
+ exit(1);
+ }
+ memset(icmpv6stat, 0, sizeof(icmpv6stat_t));
+ icmpv6stat->min_msecs = 1.0 * G_MAXUINT;
+
+ if (filter)
+ icmpv6stat->filter = g_strdup(filter);
+
+/* It is possible to create a filter and attach it to the callbacks. Then the
+ * callbacks would only be invoked if the filter matched.
+ *
+ * Evaluating filters is expensive and if we can avoid it and not use them,
+ * then we gain performance.
+ *
+ * In this case we do the filtering for protocol and version inside the
+ * callback itself but use whatever filter the user provided.
+ */
+
+ error_string = register_tap_listener("icmpv6", icmpv6stat, icmpv6stat->filter,
+ TL_REQUIRES_NOTHING, icmpv6stat_reset, icmpv6stat_packet, icmpv6stat_draw);
+ if (error_string) {
+ /* error, we failed to attach to the tap. clean up */
+ if (icmpv6stat->filter)
+ g_free(icmpv6stat->filter);
+ g_free(icmpv6stat);
+
+ fprintf(stderr, "tshark: Couldn't register icmpv6,srt tap: %s\n",
+ error_string->str);
+ g_string_free(error_string, TRUE);
+ exit(1);
+ }
+}
+
+
+void
+register_tap_listener_icmpv6stat(void)
+{
+ register_stat_cmd_arg("icmpv6,srt", icmpv6stat_init, NULL);
+}
+