/* proto_hier_stats.c * Routines for calculating statistics based on protocol. * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include #include "file.h" #include "frame_tvbuff.h" #include "ui/proto_hier_stats.h" #include "ui/progress_dlg.h" #include "epan/epan_dissect.h" #include "epan/proto.h" #include /* Update the progress bar this many times when scanning the packet list. */ #define N_PROGBAR_UPDATES 100 #define STAT_NODE_STATS(n) ((ph_stats_node_t*)(n)->data) #define STAT_NODE_HFINFO(n) (STAT_NODE_STATS(n)->hfinfo) static int pc_proto_id = -1; static GNode* find_stat_node(GNode *parent_stat_node, header_field_info *needle_hfinfo) { GNode *needle_stat_node, *up_parent_stat_node; header_field_info *hfinfo; ph_stats_node_t *stats; /* Look down the tree */ needle_stat_node = g_node_first_child(parent_stat_node); while (needle_stat_node) { hfinfo = STAT_NODE_HFINFO(needle_stat_node); if (hfinfo && hfinfo->id == needle_hfinfo->id) { return needle_stat_node; } needle_stat_node = g_node_next_sibling(needle_stat_node); } /* Look up the tree */ up_parent_stat_node = parent_stat_node; while (up_parent_stat_node && up_parent_stat_node->parent) { needle_stat_node = g_node_first_child(up_parent_stat_node->parent); while (needle_stat_node) { hfinfo = STAT_NODE_HFINFO(needle_stat_node); if (hfinfo && hfinfo->id == needle_hfinfo->id) { return needle_stat_node; } needle_stat_node = g_node_next_sibling(needle_stat_node); } up_parent_stat_node = up_parent_stat_node->parent; } /* None found. Create one. */ stats = g_new(ph_stats_node_t, 1); /* Intialize counters */ stats->hfinfo = needle_hfinfo; stats->num_pkts_total = 0; stats->num_pdus_total = 0; stats->num_pkts_last = 0; stats->num_bytes_total = 0; stats->num_bytes_last = 0; stats->last_pkt = 0; needle_stat_node = g_node_new(stats); g_node_append(parent_stat_node, needle_stat_node); return needle_stat_node; } static void process_node(proto_node *ptree_node, GNode *parent_stat_node, ph_stats_t *ps) { field_info *finfo; ph_stats_node_t *stats; proto_node *proto_sibling_node; GNode *stat_node; finfo = PNODE_FINFO(ptree_node); /* We don't fake protocol nodes we expect them to have a field_info. * Even with a faked proto tree, we don't fake nodes when PTREE_FINFO(tree) * is NULL in order to avoid crashes here and elsewhere. (See epan/proto.c) */ ws_assert(finfo); stat_node = find_stat_node(parent_stat_node, finfo->hfinfo); stats = STAT_NODE_STATS(stat_node); /* Only increment the total packet count once per packet for a given * node, since there could be multiple PDUs in a frame. * (All the other statistics should be incremented every time, * including the count for how often a protocol was the last * protocol in a packet.) */ if (stats->last_pkt != ps->tot_packets) { stats->num_pkts_total++; stats->last_pkt = ps->tot_packets; } stats->num_pdus_total++; stats->num_bytes_total += finfo->length + finfo->appendix_length; proto_sibling_node = ptree_node->next; /* Skip entries that are not protocols, e.g. * toplevel tree item of desegmentation "[Reassembled TCP Segments]") * XXX: We should probably skip PINOs with field_type FT_BYTES too. * * XXX: We look at siblings not children, and thus don't descend into * the tree to pick up embedded protocols not added to the toplevel of * the tree. */ while (proto_sibling_node && !proto_registrar_is_protocol(PNODE_FINFO(proto_sibling_node)->hfinfo->id)) { proto_sibling_node = proto_sibling_node->next; } if (proto_sibling_node) { process_node(proto_sibling_node, stat_node, ps); } else { stats->num_pkts_last++; stats->num_bytes_last += finfo->length + finfo->appendix_length; } } static void process_tree(proto_tree *protocol_tree, ph_stats_t* ps) { proto_node *ptree_node; /* * Skip over non-protocols and comments. (Packet comments are a PINO * with FT_PROTOCOL field type). This keeps us from having a top-level * "Packet comments" item that steals items from "Frame". */ ptree_node = ((proto_node *)protocol_tree)->first_child; while (ptree_node && (ptree_node->finfo->hfinfo->id == pc_proto_id || !proto_registrar_is_protocol(ptree_node->finfo->hfinfo->id))) { ptree_node = ptree_node->next; } if (!ptree_node) { return; } process_node(ptree_node, ps->stats_tree, ps); } static gboolean process_record(capture_file *cf, frame_data *frame, column_info *cinfo, wtap_rec *rec, Buffer *buf, ph_stats_t* ps) { epan_dissect_t edt; double cur_time; /* Load the record from the capture file */ if (!cf_read_record(cf, frame, rec, buf)) return FALSE; /* failure */ /* Dissect the record tree not visible */ epan_dissect_init(&edt, cf->epan, TRUE, FALSE); /* Don't fake protocols. We need them for the protocol hierarchy */ epan_dissect_fake_protocols(&edt, FALSE); epan_dissect_run(&edt, cf->cd_t, rec, frame_tvbuff_new_buffer(&cf->provider, frame, buf), frame, cinfo); /* Get stats from this protocol tree */ process_tree(edt.tree, ps); if (frame->has_ts) { /* Update times */ cur_time = nstime_to_sec(&frame->abs_ts); if (cur_time < ps->first_time) ps->first_time = cur_time; if (cur_time > ps->last_time) ps->last_time = cur_time; } /* Free our memory. */ epan_dissect_cleanup(&edt); return TRUE; /* success */ } ph_stats_t* ph_stats_new(capture_file *cf) { ph_stats_t *ps; guint32 framenum; frame_data *frame; progdlg_t *progbar = NULL; int count; wtap_rec rec; Buffer buf; float progbar_val; gchar status_str[100]; int progbar_nextstep; int progbar_quantum; if (!cf) return NULL; if (cf->read_lock) { ws_warning("Failing to compute protocol hierarchy stats on \"%s\" since a read is in progress", cf->filename); return NULL; } cf->read_lock = TRUE; cf->stop_flag = FALSE; pc_proto_id = proto_registrar_get_id_byname("pkt_comment"); /* Initialize the data */ ps = g_new(ph_stats_t, 1); ps->tot_packets = 0; ps->tot_bytes = 0; ps->stats_tree = g_node_new(NULL); ps->first_time = 0.0; ps->last_time = 0.0; /* Update the progress bar when it gets to this value. */ progbar_nextstep = 0; /* When we reach the value that triggers a progress bar update, bump that value by this amount. */ progbar_quantum = cf->count/N_PROGBAR_UPDATES; /* Count of packets at which we've looked. */ count = 0; /* Progress so far. */ progbar_val = 0.0f; wtap_rec_init(&rec); ws_buffer_init(&buf, 1514); for (framenum = 1; framenum <= cf->count; framenum++) { frame = frame_data_sequence_find(cf->provider.frames, framenum); /* Create the progress bar if necessary. We check on every iteration of the loop, so that it takes no longer than the standard time to create it (otherwise, for a large file, we might take considerably longer than that standard time in order to get to the next progress bar step). */ if (progbar == NULL) progbar = delayed_create_progress_dlg( cf->window, "Computing", "protocol hierarchy statistics", TRUE, &cf->stop_flag, progbar_val); /* Update the progress bar, but do it only N_PROGBAR_UPDATES times; when we update it, we have to run the GTK+ main loop to get it to repaint what's pending, and doing so may involve an "ioctl()" to see if there's any pending input from an X server, and doing that for every packet can be costly, especially on a big file. */ if (count >= progbar_nextstep) { /* let's not divide by zero. I should never be started * with count == 0, so let's assert that */ ws_assert(cf->count > 0); progbar_val = (gfloat) count / cf->count; if (progbar != NULL) { snprintf(status_str, sizeof(status_str), "%4u of %u frames", count, cf->count); update_progress_dlg(progbar, progbar_val, status_str); } progbar_nextstep += progbar_quantum; } if (cf->stop_flag) { /* Well, the user decided to abort the statistics. computation process Just stop. */ break; } /* Skip frames that are hidden due to the display filter. XXX - should the progress bar count only packets that passed the display filter? If so, it should probably do so for other loops (see "file.c") that look only at those packets. */ if (frame->passed_dfilter) { if (frame->has_ts) { if (ps->tot_packets == 0) { double cur_time = nstime_to_sec(&frame->abs_ts); ps->first_time = cur_time; ps->last_time = cur_time; } } /* We throw away the statistics if we quit in the middle, * so increment this first so that the count starts at 1 * when processing records, since we initialize the stat * nodes' last_pkt to 0. */ ps->tot_packets++; /* we don't care about colinfo */ if (!process_record(cf, frame, NULL, &rec, &buf, ps)) { /* * Give up, and set "stop_flag" so we * just abort rather than popping up * the statistics window. */ cf->stop_flag = TRUE; break; } ps->tot_bytes += frame->pkt_len; } count++; } wtap_rec_cleanup(&rec); ws_buffer_free(&buf); /* We're done calculating the statistics; destroy the progress bar if it was created. */ if (progbar != NULL) destroy_progress_dlg(progbar); if (cf->stop_flag) { /* * We quit in the middle; throw away the statistics * and return NULL, so our caller doesn't pop up a * window with the incomplete statistics. */ ph_stats_free(ps); ps = NULL; } ws_assert(cf->read_lock); cf->read_lock = FALSE; return ps; } static gboolean stat_node_free(GNode *node, gpointer data _U_) { ph_stats_node_t *stats = (ph_stats_node_t *)node->data; g_free(stats); return FALSE; } void ph_stats_free(ph_stats_t *ps) { if (ps->stats_tree) { g_node_traverse(ps->stats_tree, G_IN_ORDER, G_TRAVERSE_ALL, -1, stat_node_free, NULL); g_node_destroy(ps->stats_tree); } g_free(ps); }