Add conversation tracking and tshark tap support to ICMPv6. Fixes bug 5810.
authorcmaynard <cmaynard@f5534014-38df-0310-8fa8-9805f1628bb7>
Mon, 11 Apr 2011 17:42:01 +0000 (17:42 +0000)
committercmaynard <cmaynard@f5534014-38df-0310-8fa8-9805f1628bb7>
Mon, 11 Apr 2011 17:42:01 +0000 (17:42 +0000)
TODO: Add a Wireshark tap or look into possibly using the stats tree instead.
Also, like ICMP, the ICMPv6 payload appears to carry the sender's timestamp, so
it might be possible to make use of this information to estimate the total SRT.
(See bug 5770 for more details.)

git-svn-id: http://anonsvn.wireshark.org/wireshark/trunk@36561 f5534014-38df-0310-8fa8-9805f1628bb7

CMakeLists.txt
Makefile.common
doc/tshark.pod
epan/dissectors/packet-icmpv6.c
tap-icmpv6stat.c [new file with mode: 0644]

index 097dac5..f721448 100644 (file)
@@ -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
index 1ae047e..9767b94 100644 (file)
@@ -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  \
index 82e5b6f..f444873 100644 (file)
@@ -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.
index 41b5a3f..9313e1d 100644 (file)
 #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 (file)
index 0000000..12659b2
--- /dev/null
@@ -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);
+}
+