wireshark/ui/gtk/flow_graph.c
Guy Harris d7b2aad043 Move some headers for UI stuff, and the alert_box.c UI-specific file, to
the ui directory.  (Perhaps some other files that would be used by all
flavors of Wireshark, for any GUI toolkit or for someting such as
ncurses, and not for any command-line tool such as TShark, should be
moved there as well.)

Shuffle some #includes to put the "ui/XXX.h" includes together.

svn path=/trunk/; revision=40529
2012-01-16 01:07:52 +00:00

674 lines
21 KiB
C

/* flow_graph.c
* $Id$
* Allows to display a flow graph of the currently displayed packets
*
* Copyright 2004, Ericsson , Spain
* By Francisco Alcoba <francisco.alcoba@ericsson.com>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <gtk/gtk.h>
#include <epan/epan.h>
#include <epan/packet.h>
#include <epan/filesystem.h>
#include <epan/stat_cmd_args.h>
#include <epan/to_str.h>
#include <epan/tap.h>
#include <epan/dissectors/packet-tcp.h>
#include <epan/strutil.h>
#include "../stat_menu.h"
#include "ui/simple_dialog.h"
#include "ui/gtk/graph_analysis.h"
#include "ui/gtk/gui_stat_menu.h"
#include "ui/gtk/dlg_utils.h"
#include "ui/gtk/gui_utils.h"
#include "ui/gtk/stock_icons.h"
#include "ui/gtk/gtkglobals.h"
#include "ui/gtk/main.h"
#define TYPE_OF_PACKETS_DISPLAYED 0
#define TYPE_OF_PACKETS_ALL 1
#define TYPE_OF_FLOW_GENERAL 0
#define TYPE_OF_FLOW_TCP 1
#define NODE_ADDR_TYPE_SRCDST 0
#define NODE_ADDR_TYPE_NET_SRCDST 1
static int type_of_packets = TYPE_OF_PACKETS_DISPLAYED;
static int type_of_flow = TYPE_OF_FLOW_GENERAL;
static int node_addr_type = NODE_ADDR_TYPE_SRCDST;
static int tap_identifier;
static gboolean have_frame_tap_listener=FALSE;
static gboolean have_tcp_tap_listener=FALSE;
static graph_analysis_info_t *graph_analysis = NULL;
static graph_analysis_data_t *graph_analysis_data = NULL;
static GtkWidget *flow_graph_dlg = NULL;
static GtkWidget *select_all_rb;
static GtkWidget *select_displayed_rb;
static GtkWidget *select_general_rb;
static GtkWidget *select_tcp_rb;
static GtkWidget *src_dst_rb;
static GtkWidget *net_src_dst_rb;
/****************************************************************************/
/* free up memory and initialize the pointers */
static void
flow_graph_reset(void *ptr _U_)
{
graph_analysis_item_t *graph_item;
GList* list;
if (graph_analysis !=NULL){
/* free the graph data items */
list = g_list_first(graph_analysis->list);
while (list)
{
graph_item = list->data;
g_free(graph_item->frame_label);
g_free(graph_item->comment);
g_free(list->data);
list = g_list_next(list);
}
g_list_free(graph_analysis->list);
graph_analysis->nconv = 0;
graph_analysis->list = NULL;
}
}
/****************************************************************************/
static void
flow_graph_data_init(void) {
graph_analysis = g_malloc(sizeof(graph_analysis_info_t));
graph_analysis->nconv = 0;
graph_analysis->list = NULL;
}
/****************************************************************************/
static void
remove_tap_listener_flow_graph(void)
{
protect_thread_critical_region();
remove_tap_listener(&(tap_identifier));
unprotect_thread_critical_region();
have_frame_tap_listener=FALSE;
have_tcp_tap_listener=FALSE;
}
/****************************************************************************/
/* CALLBACKS */
/****************************************************************************/
static void
flow_graph_on_destroy(GObject *object _U_, gpointer user_data _U_)
{
/* remove_tap_listeners */
remove_tap_listener_flow_graph();
/* Clean up memory used by tap */
flow_graph_reset(NULL);
g_assert(graph_analysis != NULL);
g_assert(graph_analysis_data != NULL);
g_free(graph_analysis);
graph_analysis = NULL;
g_free(graph_analysis_data);
graph_analysis_data = NULL;
/* Note that we no longer have a "Flow Graph" dialog box. */
flow_graph_dlg = NULL;
}
/****************************************************************************/
static void
toggle_select_all(GtkWidget *widget _U_, gpointer user_data _U_)
{
/* is the button now active? */
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(select_all_rb))) {
type_of_packets = TYPE_OF_PACKETS_ALL;
}
}
/****************************************************************************/
static void
toggle_select_displayed(GtkWidget *widget _U_, gpointer user_data _U_)
{
/* is the button now active? */
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(select_displayed_rb))) {
type_of_packets = TYPE_OF_PACKETS_DISPLAYED;
}
}
/****************************************************************************/
static void
toggle_select_general(GtkWidget *widget _U_, gpointer user_data _U_)
{
/* is the button now active? */
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(select_general_rb))) {
type_of_flow = TYPE_OF_FLOW_GENERAL;
}
}
/****************************************************************************/
static void
toggle_select_tcp(GtkWidget *widget _U_, gpointer user_data _U_)
{
/* is the button now active? */
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(select_tcp_rb))) {
type_of_flow = TYPE_OF_FLOW_TCP;
}
}
/****************************************************************************/
static void
toggle_select_srcdst(GtkWidget *widget _U_, gpointer user_data _U_)
{
/* is the button now active? */
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(src_dst_rb))) {
node_addr_type = NODE_ADDR_TYPE_SRCDST;
}
}
/****************************************************************************/
static void
toggle_select_netsrcdst(GtkWidget *widget _U_, gpointer user_data _U_)
{
/* is the button now active? */
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(net_src_dst_rb))) {
node_addr_type = NODE_ADDR_TYPE_NET_SRCDST;
}
}
/****************************************************************************/
/* Add a new frame into the graph */
static int
flow_graph_frame_add_to_graph(packet_info *pinfo)
{
graph_analysis_item_t *gai;
int i;
gchar *protocol;
gchar *colinfo;
protocol=NULL;
colinfo=NULL;
if (node_addr_type == NODE_ADDR_TYPE_NET_SRCDST) {
if (pinfo->net_src.type!=AT_NONE && pinfo->net_dst.type!=AT_NONE) {
gai = g_malloc(sizeof(graph_analysis_item_t));
COPY_ADDRESS(&(gai->src_addr),&(pinfo->net_src));
COPY_ADDRESS(&(gai->dst_addr),&(pinfo->net_dst));
}
else return 0;
} else {
if (pinfo->src.type!=AT_NONE && pinfo->dst.type!=AT_NONE) {
gai = g_malloc(sizeof(graph_analysis_item_t));
COPY_ADDRESS(&(gai->src_addr),&(pinfo->src));
COPY_ADDRESS(&(gai->dst_addr),&(pinfo->dst));
}
else return 0;
}
gai->fd = pinfo->fd;
gai->port_src=pinfo->srcport;
gai->port_dst=pinfo->destport;
gai->comment=NULL;
gai->frame_label=NULL;
/* this code doesn't make sense.
g_free(gai->comment);
g_free(gai->frame_label);
*/
if(pinfo->cinfo) {
if (pinfo->cinfo->col_first[COL_INFO]>=0){
for (i = pinfo->cinfo->col_first[COL_INFO]; i <= pinfo->cinfo->col_last[COL_INFO]; i++) {
if (pinfo->cinfo->fmt_matx[i][COL_INFO]) {
colinfo = g_strdup(pinfo->cinfo->col_data[i]);
/* break; ? or g_free(colinfo); before g_strdup() */
}
}
}
if (pinfo->cinfo->col_first[COL_PROTOCOL]>=0){
for (i = pinfo->cinfo->col_first[COL_PROTOCOL]; i <= pinfo->cinfo->col_last[COL_PROTOCOL]; i++) {
if (pinfo->cinfo->fmt_matx[i][COL_PROTOCOL]) {
protocol = g_strdup(pinfo->cinfo->col_data[i]);
/* break; ? or g_free(protocol); before g_strdup() */
}
}
}
}
if (colinfo != NULL) {
if (protocol != NULL) {
gai->frame_label = g_strdup_printf("%.19s", colinfo);
gai->comment = g_strdup_printf("%s: %s", protocol, colinfo);
} else {
gai->frame_label = g_strdup_printf("%.19s", colinfo);
gai->comment = g_strdup_printf("%s", colinfo);
}
} else {
/* This will probably never happen...*/
if (protocol != NULL) {
gai->frame_label = g_strdup_printf("%.19s", protocol);
gai->comment = g_strdup_printf("%s", protocol);
}
}
g_free(protocol);
g_free(colinfo);
gai->line_style=1;
gai->conv_num=0;
gai->display=TRUE;
graph_analysis->list = g_list_append(graph_analysis->list, gai);
return 1;
}
/****************************************************************************/
/* Add a new tcp frame into the graph */
static int
flow_graph_tcp_add_to_graph(packet_info *pinfo, const struct tcpheader *tcph)
{
graph_analysis_item_t *gai;
/* copied from packet-tcp */
const gchar *fstr[] = {"FIN", "SYN", "RST", "PSH", "ACK", "URG", "ECN", "CWR" };
guint i, bpos;
gboolean flags_found = FALSE;
gchar flags[64];
gai = g_malloc(sizeof(graph_analysis_item_t));
gai->fd = pinfo->fd;
if (node_addr_type == NODE_ADDR_TYPE_NET_SRCDST) {
COPY_ADDRESS(&(gai->src_addr),&(pinfo->net_src));
COPY_ADDRESS(&(gai->dst_addr),&(pinfo->net_dst));
} else {
COPY_ADDRESS(&(gai->src_addr),&(pinfo->src));
COPY_ADDRESS(&(gai->dst_addr),&(pinfo->dst));
}
gai->port_src=pinfo->srcport;
gai->port_dst=pinfo->destport;
flags[0] = '\0';
for (i = 0; i < 8; i++) {
bpos = 1 << i;
if (tcph->th_flags & bpos) {
if (flags_found) {
g_strlcat(flags, ", ", sizeof(flags));
}
g_strlcat(flags, fstr[i], sizeof(flags));
flags_found = TRUE;
}
}
if (flags[0] == '\0') {
g_snprintf (flags, sizeof(flags), "<None>");
}
if ((tcph->th_have_seglen)&&(tcph->th_seglen!=0)){
gai->frame_label = g_strdup_printf("%s - Len: %u",flags, tcph->th_seglen);
}
else{
gai->frame_label = g_strdup(flags);
}
if (tcph->th_flags & TH_ACK)
gai->comment = g_strdup_printf("Seq = %u Ack = %u",tcph->th_seq, tcph->th_ack);
else
gai->comment = g_strdup_printf("Seq = %u",tcph->th_seq);
gai->line_style=1;
gai->conv_num=0;
gai->display=TRUE;
graph_analysis->list = g_list_append(graph_analysis->list, gai);
return 1;
}
/****************************************************************************/
/* whenever a frame packet is seen by the tap listener */
static gboolean
flow_graph_frame_packet( void *ptr _U_, packet_info *pinfo, epan_dissect_t *edt _U_, const void *dummy _U_)
{
if ((type_of_packets == TYPE_OF_PACKETS_ALL)||(pinfo->fd->flags.passed_dfilter==1)){
flow_graph_frame_add_to_graph(pinfo);
}
return TRUE;
}
/****************************************************************************/
/* whenever a TCP packet is seen by the tap listener */
static gboolean
flow_graph_tcp_packet( void *ptr _U_, packet_info *pinfo, epan_dissect_t *edt _U_, const void *tcp_info)
{
const struct tcpheader *tcph = tcp_info;
if ((type_of_packets == TYPE_OF_PACKETS_ALL)||(pinfo->fd->flags.passed_dfilter==1)){
flow_graph_tcp_add_to_graph(pinfo,tcph);
}
return TRUE;
}
static void
flow_graph_packet_draw(void *prs _U_)
{
return;
}
/****************************************************************************/
static void
flow_graph_on_ok (GtkButton *button _U_,
gpointer user_data)
{
if ((have_frame_tap_listener==TRUE)
||(have_tcp_tap_listener==TRUE))
{
/* remove_tap_listeners */
remove_tap_listener_flow_graph();
}
/* Scan for displayed packets (retap all packets) */
if (type_of_flow == TYPE_OF_FLOW_GENERAL){
/* Register the tap listener */
if(have_frame_tap_listener==FALSE)
{
/* don't register tap listener, if we have it already */
register_tap_listener("frame", &tap_identifier, NULL,
TL_REQUIRES_COLUMNS,
flow_graph_reset,
flow_graph_frame_packet,
flow_graph_packet_draw
);
have_frame_tap_listener=TRUE;
}
cf_retap_packets(&cfile);
}
else if (type_of_flow == TYPE_OF_FLOW_TCP){
/* Register the tap listener */
if(have_tcp_tap_listener==FALSE)
{
/* don't register tap listener, if we have it already */
register_tap_listener("tcp", &tap_identifier, NULL,
0,
flow_graph_reset,
flow_graph_tcp_packet,
flow_graph_packet_draw
);
have_tcp_tap_listener=TRUE;
}
cf_retap_packets(&cfile);
}
if (graph_analysis_data->dlg.window != NULL){ /* if we still have a window */
graph_analysis_update(graph_analysis_data); /* refresh it xxx */
}
else{
graph_analysis_data->dlg.parent_w = user_data;
graph_analysis_create(graph_analysis_data);
}
}
/****************************************************************************/
/* INTERFACE */
/****************************************************************************/
static void
flow_graph_dlg_create (void)
{
GtkWidget *flow_graph_dlg_w;
GtkWidget *main_vb;
GtkWidget *hbuttonbox;
GtkWidget *bt_cancel, *bt_ok;
#if 0
GtkWidget *top_label = NULL;
#endif
GtkWidget *flow_type_fr, *range_fr, *range_tb, *flow_type_tb, *node_addr_fr, *node_addr_tb;
flow_graph_dlg_w = dlg_window_new("Wireshark: Flow Graph"); /* transient_for top_level */
gtk_window_set_destroy_with_parent (GTK_WINDOW(flow_graph_dlg_w), TRUE);
gtk_window_set_default_size(GTK_WINDOW(flow_graph_dlg_w), 250, 150);
main_vb = gtk_vbox_new (FALSE, 0);
gtk_container_add(GTK_CONTAINER(flow_graph_dlg_w), main_vb);
gtk_container_set_border_width (GTK_CONTAINER (main_vb), 7);
#if 0
top_label = gtk_label_new ("Choose packets to include in the graph");
gtk_box_pack_start (GTK_BOX (main_vb), top_label, FALSE, FALSE, 8);
#endif
gtk_widget_show(flow_graph_dlg_w);
/*** Packet range frame ***/
range_fr = gtk_frame_new("Choose packets");
gtk_box_pack_start(GTK_BOX(main_vb), range_fr, FALSE, FALSE, 5);
range_tb = gtk_table_new(4, 4, FALSE);
gtk_container_set_border_width(GTK_CONTAINER(range_tb), 5);
gtk_container_add(GTK_CONTAINER(range_fr), range_tb);
/* Process all packets */
select_all_rb = gtk_radio_button_new_with_mnemonic_from_widget(NULL, "_All packets");
gtk_widget_set_tooltip_text (select_all_rb, ("Process all packets"));
g_signal_connect(select_all_rb, "toggled", G_CALLBACK(toggle_select_all), NULL);
gtk_table_attach_defaults(GTK_TABLE(range_tb), select_all_rb, 0, 1, 0, 1);
if (type_of_packets == TYPE_OF_PACKETS_ALL) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(select_all_rb),TRUE);
}
gtk_widget_show(select_all_rb);
/* Process displayed packets */
select_displayed_rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(select_all_rb),
"_Displayed packets");
gtk_widget_set_tooltip_text (select_displayed_rb, ("Process displayed packets"));
g_signal_connect(select_displayed_rb, "toggled", G_CALLBACK(toggle_select_displayed), NULL);
gtk_table_attach_defaults(GTK_TABLE(range_tb), select_displayed_rb, 0, 1, 1, 2);
if (type_of_packets == TYPE_OF_PACKETS_DISPLAYED) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(select_displayed_rb),TRUE);
}
gtk_widget_show(select_displayed_rb);
gtk_widget_show(range_tb);
gtk_widget_show(range_fr);
/*** Flow type frame ***/
flow_type_fr = gtk_frame_new("Choose flow type");
gtk_box_pack_start(GTK_BOX(main_vb), flow_type_fr, FALSE, FALSE, 5);
flow_type_tb = gtk_table_new(4, 4, FALSE);
gtk_container_set_border_width(GTK_CONTAINER(flow_type_tb), 5);
gtk_container_add(GTK_CONTAINER(flow_type_fr), flow_type_tb);
/* General information */
select_general_rb = gtk_radio_button_new_with_mnemonic_from_widget(NULL, "_General flow");
gtk_widget_set_tooltip_text (select_general_rb, ("Show all packets, with general information"));
g_signal_connect(select_general_rb, "toggled", G_CALLBACK(toggle_select_general), NULL);
gtk_table_attach_defaults(GTK_TABLE(flow_type_tb), select_general_rb, 0, 1, 0, 1);
if (type_of_flow == TYPE_OF_FLOW_GENERAL) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(select_general_rb),TRUE);
}
gtk_widget_show(select_general_rb);
/* TCP specific information */
select_tcp_rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(select_general_rb),
"_TCP flow");
gtk_widget_set_tooltip_text (select_tcp_rb, ("Show only TCP packets, with TCP specific information"));
g_signal_connect(select_tcp_rb, "toggled", G_CALLBACK(toggle_select_tcp), NULL);
gtk_table_attach_defaults(GTK_TABLE(flow_type_tb), select_tcp_rb, 0, 1, 1, 2);
if (type_of_flow == TYPE_OF_FLOW_TCP) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(select_tcp_rb),TRUE);
}
gtk_widget_show(select_tcp_rb);
gtk_widget_show(flow_type_tb);
gtk_widget_show(flow_type_fr);
/*** Node address type frame ***/
node_addr_fr = gtk_frame_new("Choose node address type");
gtk_box_pack_start(GTK_BOX(main_vb), node_addr_fr, FALSE, FALSE, 5);
node_addr_tb = gtk_table_new(4, 4, FALSE);
gtk_container_set_border_width(GTK_CONTAINER(node_addr_tb), 5);
gtk_container_add(GTK_CONTAINER(node_addr_fr), node_addr_tb);
/* Source / Dest address */
src_dst_rb = gtk_radio_button_new_with_mnemonic_from_widget(NULL, "_Standard source/destination addresses");
gtk_widget_set_tooltip_text (src_dst_rb,
("Nodes in the diagram are identified with source and destination addresses"));
g_signal_connect(src_dst_rb, "toggled", G_CALLBACK(toggle_select_srcdst), NULL);
gtk_table_attach_defaults(GTK_TABLE(node_addr_tb), src_dst_rb, 0, 1, 0, 1);
if (node_addr_type == NODE_ADDR_TYPE_SRCDST) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(src_dst_rb),TRUE);
}
gtk_widget_show(src_dst_rb);
/* Network source / dest address */
net_src_dst_rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(src_dst_rb),
"_Network source/destination addresses");
gtk_widget_set_tooltip_text (net_src_dst_rb,
("Nodes in the diagram are identified with network source and destination addresses"));
g_signal_connect(net_src_dst_rb, "toggled", G_CALLBACK(toggle_select_netsrcdst), NULL);
gtk_table_attach_defaults(GTK_TABLE(node_addr_tb), net_src_dst_rb, 0, 1, 1, 2);
if (node_addr_type == NODE_ADDR_TYPE_NET_SRCDST) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(net_src_dst_rb),TRUE);
}
gtk_widget_show(net_src_dst_rb);
gtk_widget_show(node_addr_tb);
gtk_widget_show(node_addr_fr);
/* button row */
hbuttonbox = gtk_hbutton_box_new ();
gtk_box_pack_start (GTK_BOX (main_vb), hbuttonbox, FALSE, FALSE, 5);
gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox), GTK_BUTTONBOX_SPREAD);
gtk_box_set_spacing (GTK_BOX (hbuttonbox), 30);
bt_ok = gtk_button_new_from_stock(GTK_STOCK_OK);
gtk_container_add(GTK_CONTAINER(hbuttonbox), bt_ok);
gtk_widget_set_tooltip_text (bt_ok, "Show the flow graph");
g_signal_connect(bt_ok, "clicked", G_CALLBACK(flow_graph_on_ok), flow_graph_dlg_w);
gtk_widget_show(bt_ok);
bt_cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
gtk_container_add (GTK_CONTAINER (hbuttonbox), bt_cancel);
#if GTK_CHECK_VERSION(2,18,0)
gtk_widget_set_can_default(bt_cancel, TRUE);
#else
GTK_WIDGET_SET_FLAGS(bt_cancel, GTK_CAN_DEFAULT);
#endif
gtk_widget_set_tooltip_text (bt_cancel, "Cancel this dialog");
window_set_cancel_button(flow_graph_dlg_w, bt_cancel, window_cancel_button_cb);
g_signal_connect(flow_graph_dlg_w, "delete_event", G_CALLBACK(window_delete_event_cb), NULL);
g_signal_connect(flow_graph_dlg_w, "destroy", G_CALLBACK(flow_graph_on_destroy), NULL);
gtk_widget_show_all(flow_graph_dlg_w);
window_present(flow_graph_dlg_w);
flow_graph_dlg = flow_graph_dlg_w;
}
/****************************************************************************/
/* PUBLIC */
/****************************************************************************/
/* init function for tap */
static void
flow_graph_init_tap(const char *dummy _U_, void* userdata _U_)
{
/* The storage allocated by flow_graph_data_init() and graph_analysis_init() */
/* will be considered to be "associated with" the flow_graph_dlg dialog box. */
/* It will be freed when the flow_graph_dlg dialog box is destroyed. */
if (flow_graph_dlg != NULL) {
g_assert(graph_analysis != NULL);
g_assert(graph_analysis_data != NULL);
/* There's already a dialog box; reactivate it. */
reactivate_window(flow_graph_dlg);
} else {
g_assert(graph_analysis == NULL);
g_assert(graph_analysis_data == NULL);
/* initialize graph items store */
flow_graph_data_init();
/* init the Graph Analysis */
graph_analysis_data = graph_analysis_init();
graph_analysis_data->graph_info = graph_analysis;
flow_graph_dlg_create();
}
}
/****************************************************************************/
/* entry point when called via the GTK menu */
void
flow_graph_launch(GtkAction *action _U_, gpointer user_data _U_)
{
flow_graph_init_tap("",NULL);
}
/****************************************************************************/
void
register_tap_listener_flow_graph(void)
{
register_stat_cmd_arg("flow_graph",flow_graph_init_tap,NULL);
}