sharkd: Add phs tap to sharkd

Add `phs` tap to `sharkd`, providing the same information as
`tshark`'s `-z io,phs` option.

Additionally, modify how `tshark -z io,phs` (and therefore `sharkd`'s
new `phs` tap) handles packet comments (aka `pkt_comment` protocol
frames).  Previously, `pkt_comment` protocol frames were handled no
differently from any other protocol in `io,phs`'s `tap_packet`
callback `protohierstat_packet` but were skipped in its `tap_draw`
callback `protohierstat_draw`.  This behavior seems to have been first
introduced in 80ae3708.  For captures containing packet comments, this
lead to surprising `tshark -z io,phs` output with multiple root-level
`eth` trees.  Below is example output of the old behavior for the
`test/captures/protohier-with-comments.pcapng` capture in this
repository with two packet comments, one on an ICMPv6 packet and
another on an SSDP packet:

    # tshark -qz io,phs -r ./test/captures/protohier-with-comments.pcapng
    ===================================================================
    Protocol Hierarchy Statistics
    Filter:

    eth                                      frames:113 bytes:21809
      ipv6                                   frames:38 bytes:7456
        icmpv6                               frames:35 bytes:3574
        udp                                  frames:3 bytes:3882
          data                               frames:3 bytes:3882
      ip                                     frames:69 bytes:13993
        udp                                  frames:59 bytes:13391
          mdns                               frames:1 bytes:138
          ssdp                               frames:29 bytes:8561
          nbns                               frames:20 bytes:2200
          nbdgm                              frames:1 bytes:248
            smb                              frames:1 bytes:248
              mailslot                       frames:1 bytes:248
                browser                      frames:1 bytes:248
          dhcp                               frames:4 bytes:1864
          dns                                frames:4 bytes:380
        igmp                                 frames:10 bytes:602
      arp                                    frames:6 bytes:360
    eth                                      frames:2 bytes:377
      ipv6                                   frames:1 bytes:110
        icmpv6                               frames:1 bytes:110
      ip                                     frames:1 bytes:267
        udp                                  frames:1 bytes:267
          ssdp                               frames:1 bytes:267
    ===================================================================

Despite the comment in `phs_draw` in `ui/cli/tap-protohierstat.c`,
this does not seem to match the behavior for PHS as shown in the GUI.
The GUI seems to ignore the `pkt_comment` protocol frames and merges
their children up a level.  This commit tries to reproduce this
behavior in the `tshark -z io,phs` output by ignoring `pkt_comment`
protocol frames in `protohierstat_packet` instead of
`protohierstat_draw`.  The result is output like the following:

    # tshark -qz io,phs -r ./test/captures/protohier-with-comments.pcapng
    ===================================================================
    Protocol Hierarchy Statistics
    Filter:

    eth                                      frames:115 bytes:22186
      ipv6                                   frames:39 bytes:7566
        icmpv6                               frames:36 bytes:3684
        udp                                  frames:3 bytes:3882
          data                               frames:3 bytes:3882
      ip                                     frames:70 bytes:14260
        udp                                  frames:60 bytes:13658
          mdns                               frames:1 bytes:138
          ssdp                               frames:30 bytes:8828
          nbns                               frames:20 bytes:2200
          nbdgm                              frames:1 bytes:248
            smb                              frames:1 bytes:248
              mailslot                       frames:1 bytes:248
                browser                      frames:1 bytes:248
          dhcp                               frames:4 bytes:1864
          dns                                frames:4 bytes:380
        igmp                                 frames:10 bytes:602
      arp                                    frames:6 bytes:360
    ===================================================================

Note that there are no `pkt_comment` protocols and only a single
root-level `eth` protocol.  Additionally, the commented ICMPv6 and
SSDP packets have been merged into the first `eth` tree, and the frame
and byte counts have been incremented appropriately.
This commit is contained in:
Niels Widger 2023-08-09 11:51:20 -04:00 committed by AndersBroman
parent de5dc5dd23
commit c96b79f105
7 changed files with 387 additions and 33 deletions

View File

@ -3056,6 +3056,7 @@ if(BUILD_sharkd)
${APPLE_SYSTEM_CONFIGURATION_LIBRARY}
${WIN_WS2_32_LIBRARY}
${SPEEXDSP_LIBRARIES}
${M_LIBRARIES}
)
set(sharkd_FILES
#
@ -3066,6 +3067,7 @@ if(BUILD_sharkd)
sharkd.c
sharkd_daemon.c
sharkd_session.c
${TSHARK_TAP_SRC}
)
set_executable_resources(sharkd "SharkD")
add_executable(sharkd ${sharkd_FILES})

View File

@ -56,6 +56,7 @@
#include <ui/rtp_stream.h>
#include <ui/tap-rtp-common.h>
#include <ui/tap-rtp-analysis.h>
#include <ui/cli/tap-protohierstat.h>
#include <wsutil/version_info.h>
#include <epan/to_str.h>
@ -1031,6 +1032,11 @@ sharkd_session_process_info(void)
sharkd_json_value_string("tap", "rtp-streams");
json_dumper_end_object(&dumper);
json_dumper_begin_object(&dumper);
sharkd_json_value_string("name", "Protocol Hierarchy Statistics");
sharkd_json_value_string("tap", "phs");
json_dumper_end_object(&dumper);
json_dumper_begin_object(&dumper);
sharkd_json_value_string("name", "Expert Information");
sharkd_json_value_string("tap", "expert");
@ -2629,6 +2635,80 @@ sharkd_session_free_tap_srt_cb(void *arg)
g_free(srt_data);
}
struct sharkd_phs_req
{
const char *tap_name;
phs_t *rs;
};
static void
sharkd_session_process_tap_phs_cb_aux(phs_t *rs)
{
for (; rs; rs = rs->sibling) {
if (rs->protocol == -1) {
return;
}
sharkd_json_object_open(NULL);
sharkd_json_value_string("proto", rs->proto_name);
sharkd_json_value_anyf("frames", "%u", rs->frames);
sharkd_json_value_anyf("bytes", "%lu", rs->bytes);
if (rs->child != NULL && rs->child->protocol != -1) {
sharkd_json_array_open("protos");
sharkd_session_process_tap_phs_cb_aux(rs->child);
sharkd_json_array_close();
}
sharkd_json_object_close();
}
}
static tap_packet_status
sharkd_session_packet_tap_phs_cb(void *pphs_req, packet_info *pinfo, epan_dissect_t *edt, const void *dummy, tap_flags_t flags)
{
struct sharkd_phs_req *phs_req = (struct sharkd_phs_req *)pphs_req;
phs_t *rs = phs_req->rs;
return protohierstat_packet(rs, pinfo, edt, dummy, flags);
}
/**
* sharkd_session_process_tap_phs_cb()
*
* Output phs tap:
* (m) tap - tap name
* (m) type - tap output type
* (m) filter - tap filter argument
* (m) protos - array of proto objects
*
* proto object:
* (m) proto - protocol name
* (m) frames - frame count
* (m) bytes - bytes count
* (o) protos - array of proto objects
*/
static void
sharkd_session_process_tap_phs_cb(void *arg)
{
struct sharkd_phs_req *phs_req = (struct sharkd_phs_req *)arg;
phs_t *rs = phs_req->rs;
sharkd_json_object_open(NULL);
sharkd_json_value_string("tap", phs_req->tap_name);
sharkd_json_value_string("type", "phs");
sharkd_json_value_string("filter", rs->filter ? rs->filter : "");
sharkd_json_array_open("protos");
sharkd_session_process_tap_phs_cb_aux(rs);
sharkd_json_array_close();
sharkd_json_object_close();
}
static void
sharkd_session_free_tap_phs_cb(void *arg)
{
struct sharkd_phs_req *phs_req = (struct sharkd_phs_req *)arg;
phs_t *rs = phs_req->rs;
free_phs(rs);
g_free(phs_req);
}
struct sharkd_export_object_list
{
struct sharkd_export_object_list *next;
@ -3247,6 +3327,26 @@ sharkd_session_process_tap(char *buf, const jsmntok_t *tokens, int count)
tap_data = mcaststream_tapinfo;
tap_free = sharkd_session_process_free_tap_multicast_cb;
}
else if (!strncmp(tok_tap, "phs", 3))
{
phs_t *rs;
pc_proto_id = proto_registrar_get_id_byname("pkt_comment");
rs = new_phs_t(NULL, tap_filter);
struct sharkd_phs_req *phs_req;
phs_req = (struct sharkd_phs_req *)g_malloc0(sizeof(*phs_req));
phs_req->tap_name = tok_tap;
phs_req->rs = rs;
tap_error = register_tap_listener("frame", phs_req, tap_filter, TL_REQUIRES_PROTO_TREE, NULL,
sharkd_session_packet_tap_phs_cb,
sharkd_session_process_tap_phs_cb, NULL);
tap_data = phs_req;
tap_free = sharkd_session_free_tap_phs_cb;
}
else
{
sharkd_json_error(

Binary file not shown.

Binary file not shown.

View File

@ -309,6 +309,195 @@ class TestSharkd:
}},
))
def test_sharkd_req_tap_phs(self, check_sharkd_session, capture_file):
check_sharkd_session((
{"jsonrpc":"2.0", "id":1, "method":"load",
"params":{"file": capture_file('protohier-with-comments.pcapng')}
},
{"jsonrpc":"2.0", "id":2, "method":"tap", "params":{"tap0": "phs"}},
{"jsonrpc":"2.0", "id":3, "method":"load",
"params":{"file": capture_file('protohier-without-comments.pcapng')}
},
{"jsonrpc":"2.0", "id":4, "method":"tap", "params":{"tap0": "phs"}},
), (
{"jsonrpc":"2.0","id":1,"result":{"status":"OK"}},
{"jsonrpc":"2.0","id":2,"result":{
"taps":[{
"tap":"phs",
"type":"phs",
"filter":"",
"protos":[{
"proto":"eth",
"frames":115,
"bytes":22186,
"protos":[{
"proto":"ipv6",
"frames":39,
"bytes":7566,
"protos":[{
"proto":"icmpv6",
"frames":36,
"bytes":3684
},{
"proto":"udp",
"frames":3,
"bytes":3882,
"protos":[{
"proto":"data",
"frames":3,
"bytes":3882
}]
}]
},{
"proto":"ip",
"frames":70,
"bytes":14260,
"protos":[{
"proto":"udp",
"frames":60,
"bytes":13658,
"protos":[{
"proto":"mdns",
"frames":1,
"bytes":138
},{
"proto":"ssdp",
"frames":30,
"bytes":8828
},{
"proto":"nbns",
"frames":20,
"bytes":2200
},{
"proto":"nbdgm",
"frames":1,
"bytes":248,
"protos":[{
"proto":"smb",
"frames":1,
"bytes":248,
"protos":[{
"proto":"mailslot",
"frames":1,
"bytes":248,
"protos":[{
"proto":"browser",
"frames":1,
"bytes":248
}]
}]
}]
},{"proto":"dhcp",
"frames":4,
"bytes":1864
},{
"proto":"dns",
"frames":4,
"bytes":380
}]
},{
"proto":"igmp",
"frames":10,
"bytes":602
}]
},{
"proto":"arp",
"frames":6,
"bytes":360
}]
}]
}]
}},
{"jsonrpc":"2.0","id":3,"result":{"status":"OK"}},
{"jsonrpc":"2.0","id":4,"result":{
"taps":[{
"tap":"phs",
"type":"phs",
"filter":"",
"protos":[{
"proto":"eth",
"frames":115,
"bytes":22186,
"protos":[{
"proto":"ipv6",
"frames":39,
"bytes":7566,
"protos":[{
"proto":"icmpv6",
"frames":36,
"bytes":3684
},{
"proto":"udp",
"frames":3,
"bytes":3882,
"protos":[{
"proto":"data",
"frames":3,
"bytes":3882
}]
}]
},{
"proto":"ip",
"frames":70,
"bytes":14260,
"protos":[{
"proto":"udp",
"frames":60,
"bytes":13658,
"protos":[{
"proto":"mdns",
"frames":1,
"bytes":138
},{
"proto":"ssdp",
"frames":30,
"bytes":8828
},{
"proto":"nbns",
"frames":20,
"bytes":2200
},{
"proto":"nbdgm",
"frames":1,
"bytes":248,
"protos":[{
"proto":"smb",
"frames":1,
"bytes":248,
"protos":[{
"proto":"mailslot",
"frames":1,
"bytes":248,
"protos":[{
"proto":"browser",
"frames":1,
"bytes":248
}]
}]
}]
},{"proto":"dhcp",
"frames":4,
"bytes":1864
},{
"proto":"dns",
"frames":4,
"bytes":380
}]
},{
"proto":"igmp",
"frames":10,
"bytes":602
}]
},{
"proto":"arp",
"frames":6,
"bytes":360
}]
}]
}]
}},
))
def test_sharkd_req_follow_bad(self, check_sharkd_session, capture_file):
# Unrecognized taps currently produce no output (not even err).
check_sharkd_session((

View File

@ -21,25 +21,14 @@
#include <epan/stat_tap_ui.h>
#include <wsutil/cmdarg_err.h>
#include "tap-protohierstat.h"
static int pc_proto_id = -1;
int pc_proto_id = -1;
void register_tap_listener_protohierstat(void);
typedef struct _phs_t {
struct _phs_t *sibling;
struct _phs_t *child;
struct _phs_t *parent;
char *filter;
int protocol;
const char *proto_name;
guint32 frames;
guint64 bytes;
} phs_t;
static phs_t *
new_phs_t(phs_t *parent)
phs_t *
new_phs_t(phs_t *parent, const char *filter)
{
phs_t *rs;
rs = g_new(phs_t, 1);
@ -47,6 +36,9 @@ new_phs_t(phs_t *parent)
rs->child = NULL;
rs->parent = parent;
rs->filter = NULL;
if (filter != NULL) {
rs->filter = g_strdup(filter);
}
rs->protocol = -1;
rs->proto_name = NULL;
rs->frames = 0;
@ -54,8 +46,30 @@ new_phs_t(phs_t *parent)
return rs;
}
void
free_phs(phs_t *rs)
{
if (!rs) {
return;
}
if (rs->filter) {
g_free(rs->filter);
rs->filter = NULL;
}
if (rs->sibling)
{
free_phs(rs->sibling);
rs->sibling = NULL;
}
if (rs->child)
{
free_phs(rs->child);
rs->child = NULL;
}
g_free(rs);
}
static tap_packet_status
tap_packet_status
protohierstat_packet(void *prs, packet_info *pinfo, epan_dissect_t *edt, const void *dummy _U_, tap_flags_t flags _U_)
{
phs_t *rs = (phs_t *)prs;
@ -76,13 +90,23 @@ protohierstat_packet(void *prs, packet_info *pinfo, epan_dissect_t *edt, const v
for (node=edt->tree->first_child; node; node=node->next) {
fi = PNODE_FINFO(node);
/*
* If the first child is a tree of comments, skip over it.
* This keeps us from having a top-level "pkt_comment"
* entry that represents a nonexistent protocol,
* and matches how the GUI treats comments.
*/
if (G_UNLIKELY(fi->hfinfo->id == pc_proto_id)) {
continue;
}
/* first time we saw a protocol at this leaf */
if (rs->protocol == -1) {
rs->protocol = fi->hfinfo->id;
rs->proto_name = fi->hfinfo->abbrev;
rs->frames = 1;
rs->bytes = pinfo->fd->pkt_len;
rs->child = new_phs_t(rs);
rs->child = new_phs_t(rs, NULL);
rs = rs->child;
continue;
}
@ -98,7 +122,7 @@ protohierstat_packet(void *prs, packet_info *pinfo, epan_dissect_t *edt, const v
if (!tmprs) {
for (tmprs=rs; tmprs->sibling; tmprs=tmprs->sibling)
;
tmprs->sibling = new_phs_t(rs->parent);
tmprs->sibling = new_phs_t(rs->parent, NULL);
rs = tmprs->sibling;
rs->protocol = fi->hfinfo->id;
rs->proto_name = fi->hfinfo->abbrev;
@ -110,7 +134,7 @@ protohierstat_packet(void *prs, packet_info *pinfo, epan_dissect_t *edt, const v
rs->bytes += pinfo->fd->pkt_len;
if (!rs->child) {
rs->child = new_phs_t(rs);
rs->child = new_phs_t(rs, NULL);
}
rs = rs->child;
}
@ -127,16 +151,6 @@ phs_draw(phs_t *rs, int indentation)
if (rs->protocol == -1) {
return;
}
/*
* If the first child is a tree of comments, skip over it.
* This keeps us from having a top-level "pkt_comment"
* entry that represents a nonexistent protocol,
* and matches how the GUI treats comments.
*/
if (G_UNLIKELY(rs->protocol == pc_proto_id)) {
phs_draw(rs->child, indentation);
continue;
}
str[0] = 0;
stroff = 0;
for (i=0; i<indentation; i++) {
@ -187,14 +201,12 @@ protohierstat_init(const char *opt_arg, void *userdata _U_)
pc_proto_id = proto_registrar_get_id_byname("pkt_comment");
rs = new_phs_t(NULL);
rs->filter = g_strdup(filter);
rs = new_phs_t(NULL, filter);
error_string = register_tap_listener("frame", rs, filter, TL_REQUIRES_PROTO_TREE, NULL, protohierstat_packet, protohierstat_draw, NULL);
if (error_string) {
/* error, we failed to attach to the tap. clean up */
g_free(rs->filter);
g_free(rs);
free_phs(rs);
cmdarg_err("Couldn't register io,phs tap: %s",
error_string->str);

View File

@ -0,0 +1,51 @@
/** @file
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __TAP_PROTO_HIER_STAT_H__
#define __TAP_PROTO_HIER_STAT_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
extern int pc_proto_id;
typedef struct _phs_t {
struct _phs_t *sibling;
struct _phs_t *child;
struct _phs_t *parent;
char *filter;
int protocol;
const char *proto_name;
guint32 frames;
guint64 bytes;
} phs_t;
extern phs_t * new_phs_t(phs_t *parent, const char *filter);
extern void free_phs(phs_t *rs);
extern tap_packet_status protohierstat_packet(void *prs, packet_info *pinfo, epan_dissect_t *edt, const void *dummy _U_, tap_flags_t flags _U_);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TAP_PROTO_HIER_STAT_H__ */
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 8
* tab-width: 8
* indent-tabs-mode: t
* End:
*
* vi: set shiftwidth=8 tabstop=8 noexpandtab:
* :indentSize=8:tabSize=8:noTabs=false:
*/