Ethereal-dev: [Ethereal-dev] New feature: analysing Multicast streams

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Miha Jemec <m.jemec@xxxxxxxxxxx>
Date: Mon, 24 Apr 2006 16:18:10 +0200
Hi!

In my daily work I have a lot to do with the video solutions. One of the
major problems we encounter are the traffic bursts which may
significantly degrade the video quality. Analysing this kind of problems
with the ethereal I/O graph can be difficult and thus we developed
another mechanism.

Background:
If you imagine a real solution there are points in the network where
speed reduction occurs (ethernet links, DSL lines, etc). One example can
be: 

>---100 Mbit/s link-----|ethernet switch|----10 Mbit/s link -------->

If this is video network for carrying one MPEG2 stream with average
bandwidth of 5 Mbit/s, the crucial information is how big are the bursts
inside this stream. Because of limited memory of the output queue inside
the ethernet switch we can get packet drops if the bursts get to high. 

With the new feature we can: 
1. Measure how big the bursts are for a video streams (it uses sliding
window algorithm)
2. Measure how big the output buffer should be that no packet drop will
occur (it uses Leaky bucket algorithm) 
3. Detect if we have loses inside the MPEG2 video stream (if there are
already MPEG2 packets missing) - this part of code is not added yet, see
Limitations

The addition is called Multicast streams and works as follows:

- it uses the TAP system
- the main "stream" logic is taken from rtp_strems.* files
- the TAP system checks for UDP packets where the destination MAC
address starts with "01:00:5E" (ethernet multicast address)
- it creates an entry for every new multicast stream
- based on sliding window and leaky bucket algorithm it calculates for
every stream average BW, max BW, burst size, max buffer needed, some
alarms if the limits are exceeded,...
- the same calculation is done for all streams together
- inside the window dialog you can specify the burst interval, the alarm
limits and output speeds

To do & limitations:

- Currently the analysis can be done only for multicast streams, it
means that VoD (Video on demand) or PayTV streams, which are normally
unicast can not be analysed. 

- since the MPEG2 is patended I don't know if decoding of MPEG2 packets
is allowed? Can we look inside this packets and calculate packets drops
based on some counter information inside the payload? Can someone please
answer this question? If we can do this, I will post this part of code
too.

-  some more flexibility will be added

Attached is picture of the two windows, the source code and make files
(all go inside gtk directory) for Linux. Tested on FC3, FC4, FC5. 
I don't know what has to be added for Windows compatibility, maybe
changes inside the Makefile.nmake are enough?

Here is also a link with one Multicast stream capture file, you can play
with

http://www2.arnes.si/~ljmik6/pub/video_streams.cap (13MB)

Below is also a detailed explanation of the columns inside the window.

The major credit goes to my co-worker Jakob Bratkovic, who wrote the
whole calculation and analysing part of the code.

Regards, 

Miha Jemec
Iskratel
Slovenia

Column explanation:
Max burst - the highest number of packets inside a sliding window time
interval. The time interval can be specified inside the Set parameters
window
Max Bw - same as the above one, only in Mbps instead of pps
Burst Alarms -  how many times the bursts exceeded the limit set inside
the Parameters dialog
Max buffer - how big the output queue should be that no packet will be
dropped at specified output speed
Buff alarms - how many times this was not the case (the required buffer
was higher than available one)

Inside the Set parameters dialog, beside the Burst Interval and Alarm
Limits, you can also specify the output speed for single stream and
output speed for all streams. 


Attachment: video.jpg
Description: JPEG image

/* mcast_stream.c
 *
 * Copyright 2006, Iskratel , Slovenia
 * By Jakob Bratkovic <j.bratkovic@xxxxxxxxxxx> and 
 * Miha Jemec <m.jemec@xxxxxxxxxxx>
 *
 * based on rtp_stream.c
 * Copyright 2003, Alcatel Business Systems
 * By Lars Ruoff <lars.ruoff@xxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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 "mcast_stream.h"
#include "mcast_stream_dlg.h"

#include "globals.h"

#include <epan/tap.h>
#include "register.h"

#include "alert_box.h"
#include "simple_dialog.h"
#include "file_util.h"
#include <time.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#include <string.h>
#include <epan/addr_resolv.h>

gint32 trigger=50; /* limit for triggering the burst alarm (in packets per second) */
gint32 bufferalarm = 10000; /* limit for triggernig the buffer alarm (in bytes) */
guint16 burstint = 100; /* burts interval in ms */
gint32 emptyspeed = 5000; /* outgoing speed for single stream (kbps)*/
gint32 cumulemptyspeed = 100000; /* outgoiong speed for all streams (kbps)*/

t_buffer **bufflist;

/* sliding window and buffer usage */
gint32 buffsize = (int)((double)MAX_SPEED * 100 / 1000) * 2;;
guint16 comparetimes(struct timeval *t1, struct timeval *t2, guint16 burstint);
static void buffusagecalc(mcast_stream_info_t *strinfo, packet_info *pinfo, double emptyspeed);
static void slidingwindow(mcast_stream_info_t *strinfo, packet_info *pinfo);


/****************************************************************************/
/* the one and only global mcaststream_tapinfo_t structure */
static mcaststream_tapinfo_t the_tapinfo_struct =
	{0, NULL, 0, NULL, 0, FALSE};


/****************************************************************************/
/* GCompareFunc style comparison function for _mcast_stream_info */
static gint mcast_stream_info_cmp(gconstpointer aa, gconstpointer bb)
{
	const struct _mcast_stream_info* a = aa;
	const struct _mcast_stream_info* b = bb;

        if (a==b)
                return 0;
        if (a==NULL || b==NULL)
                return 1;
        if (ADDRESSES_EQUAL(&(a->src_addr), &(b->src_addr))
                && (a->src_port == b->src_port)
                && ADDRESSES_EQUAL(&(a->dest_addr), &(b->dest_addr))
                && (a->dest_port == b->dest_port))
                return 0;
        else
                return 1;

}


/****************************************************************************/
/* when there is a [re]reading of packet's */
void mcaststream_reset(mcaststream_tapinfo_t *tapinfo)
{
	GList* list;

	/* free the data items first */
	list = g_list_first(tapinfo->strinfo_list);
	while (list)
	{
		/* XYZ I don't know how to clean this */
		/*g_free(list->element.buff); */
		g_free(list->data);
		list = g_list_next(list);
	}
	g_list_free(tapinfo->strinfo_list);
	tapinfo->strinfo_list = NULL;

	/* XYZ and why does the line below causes a crach? */
	/*g_free(tapinfo->allstreams->element.buff);*/
	g_free(tapinfo->allstreams);
	tapinfo->allstreams = NULL;

	tapinfo->nstreams = 0;
	tapinfo->npackets = 0;

	++(tapinfo->launch_count);

	return;
}

static void mcaststream_reset_cb(void *arg)
{
	mcaststream_reset(arg);
}

/****************************************************************************/
/* redraw the output */
static void mcaststream_draw(void *arg _U_)
{
/* XXX: see mcaststream_on_update in mcast_streams_dlg.c for comments
	gtk_signal_emit_by_name(top_level, "signal_mcaststream_update");
*/
	mcaststream_dlg_update(the_tapinfo_struct.strinfo_list);
	return;
}



/****************************************************************************/
/* whenever a udp packet is seen by the tap listener */
static int mcaststream_packet(void *arg, packet_info *pinfo, epan_dissect_t *edt _U_, const void *arg2 _U_)
{
	mcaststream_tapinfo_t *tapinfo = arg;
        mcast_stream_info_t tmp_strinfo;
        mcast_stream_info_t *strinfo = NULL;
        GList* list;
	float deltatime;

        /* gather infos on the stream this packet is part of */
        COPY_ADDRESS(&(tmp_strinfo.src_addr), &(pinfo->src));
        tmp_strinfo.src_port = pinfo->srcport;
        COPY_ADDRESS(&(tmp_strinfo.dest_addr), &(pinfo->dst));
        tmp_strinfo.dest_port = pinfo->destport;

	/* first we ignore non multicast packets; we filter out only those ethernet packets
	 * which start with the 01:00:5E multicast address */
	if (strncmp("01:00:5e", g_strdup(get_addr_name(&(pinfo->dl_dst))), 8) != 0)
		return 0;

	/* check wether we already have a stream with these parameters in the list */
	list = g_list_first(tapinfo->strinfo_list);
	while (list)
	{
		if (mcast_stream_info_cmp(&tmp_strinfo, (mcast_stream_info_t*)(list->data))==0)
		{
			strinfo = (mcast_stream_info_t*)(list->data);  /*found!*/
			break;
		}
		list = g_list_next(list);
	}

	/* not in the list? then create a new entry */
	if (!strinfo) {
		/*printf("nov sip %s sp %d dip %s dp %d\n", g_strdup(get_addr_name(&(pinfo->src))), 
			pinfo->srcport, g_strdup(get_addr_name(&(pinfo->dst))), pinfo->destport);*/
		tmp_strinfo.npackets = 0;
		tmp_strinfo.apackets = 0;
		tmp_strinfo.first_frame_num = pinfo->fd->num;
		tmp_strinfo.start_sec = pinfo->fd->abs_ts.secs;
		tmp_strinfo.start_usec = pinfo->fd->abs_ts.nsecs/1000;
		tmp_strinfo.start_rel_sec = pinfo->fd->rel_ts.secs;
		tmp_strinfo.start_rel_usec = pinfo->fd->rel_ts.nsecs/1000;
		tmp_strinfo.vlan_id = 0;

		/* reset Mcast stats */
		tmp_strinfo.average_bw = 0;
		tmp_strinfo.total_bytes = 0;

		/* reset slidingwindow and buffer parameters */
		tmp_strinfo.element.buff = (struct timeval *)g_malloc(buffsize * sizeof(struct timeval));
		tmp_strinfo.element.first=0;
		tmp_strinfo.element.last=0;
		tmp_strinfo.element.burstsize=1;
		tmp_strinfo.element.topburstsize=1;
		tmp_strinfo.element.numbursts=0;
		tmp_strinfo.element.burststatus=0;
		tmp_strinfo.element.count=1;
		tmp_strinfo.element.buffusage=pinfo->fd->pkt_len;
		tmp_strinfo.element.topbuffusage=pinfo->fd->pkt_len;
		tmp_strinfo.element.numbuffalarms=0;
		tmp_strinfo.element.buffstatus=0;
		tmp_strinfo.element.maxbw=0;

		strinfo = g_malloc(sizeof(mcast_stream_info_t));
		*strinfo = tmp_strinfo;  /* memberwise copy of struct */
		tapinfo->strinfo_list = g_list_append(tapinfo->strinfo_list, strinfo);
		strinfo->element.buff = (struct timeval *)g_malloc(buffsize * sizeof(struct timeval));

		/* set time with the first packet */
		if (tapinfo->npackets == 0) {
			tapinfo->allstreams = g_malloc(sizeof(mcast_stream_info_t));
			tapinfo->allstreams->element.buff = 
					(struct timeval *)g_malloc(buffsize * sizeof(struct timeval));
			tapinfo->allstreams->start_rel_sec = pinfo->fd->rel_ts.secs;
			tapinfo->allstreams->start_rel_usec = pinfo->fd->rel_ts.nsecs/1000;
			tapinfo->allstreams->total_bytes = 0;
			tapinfo->allstreams->element.first=0;
			tapinfo->allstreams->element.last=0;
			tapinfo->allstreams->element.burstsize=1;
			tapinfo->allstreams->element.topburstsize=1;
			tapinfo->allstreams->element.numbursts=0;
			tapinfo->allstreams->element.burststatus=0;
			tapinfo->allstreams->element.count=1;
			tapinfo->allstreams->element.buffusage=pinfo->fd->pkt_len;
			tapinfo->allstreams->element.topbuffusage=pinfo->fd->pkt_len;
			tapinfo->allstreams->element.numbuffalarms=0;
			tapinfo->allstreams->element.buffstatus=0;
			tapinfo->allstreams->element.maxbw=0;
		}
	}

	/* time between first and last packet in the group */
	strinfo->stop_rel_sec = pinfo->fd->rel_ts.secs;
	strinfo->stop_rel_usec = pinfo->fd->rel_ts.nsecs/1000;
	deltatime = ((float)((strinfo->stop_rel_sec * 1000000 + strinfo->stop_rel_usec)
					- (strinfo->start_rel_sec*1000000 + strinfo->start_rel_usec)))/1000000;

	/* calculate average bandwidth for this stream */
	strinfo->total_bytes = strinfo->total_bytes + pinfo->fd->pkt_len;
	if (deltatime > 0)
		strinfo->average_bw = (((float)(strinfo->total_bytes*8) / deltatime) / 1000000);

	/* increment the packets counter for this stream and calculate average pps */
	++(strinfo->npackets);
	strinfo->apackets = strinfo->npackets / deltatime;

	/* time between first and last packet in any group */
	tapinfo->allstreams->stop_rel_sec = pinfo->fd->rel_ts.secs;
	tapinfo->allstreams->stop_rel_usec = pinfo->fd->rel_ts.nsecs/1000;
	deltatime = ((float)((tapinfo->allstreams->stop_rel_sec * 1000000 + tapinfo->allstreams->stop_rel_usec)
		- (tapinfo->allstreams->start_rel_sec*1000000 + tapinfo->allstreams->start_rel_usec)))/1000000;

	/* increment the packets counter of all streams */
	++(tapinfo->npackets);

	/* calculate average bandwidth for all streams */
	tapinfo->allstreams->total_bytes = tapinfo->allstreams->total_bytes + pinfo->fd->pkt_len;
	if (deltatime > 0)
		tapinfo->allstreams->average_bw = (((float)(tapinfo->allstreams->total_bytes *8) / deltatime) / 1000000);

	/* sliding window and buffercalc for this group*/
	slidingwindow(strinfo, pinfo);
	buffusagecalc(strinfo, pinfo, emptyspeed*1000);
	/* sliding window and buffercalc for all groups */
	slidingwindow(tapinfo->allstreams, pinfo);
	buffusagecalc(tapinfo->allstreams, pinfo, cumulemptyspeed*1000);
	/* end of sliding window */

	return 1;  /* refresh output */

}

/****************************************************************************/
/* scan for Mcast streams */
void mcaststream_scan(void)
{
	gboolean was_registered = the_tapinfo_struct.is_registered;
	if (!the_tapinfo_struct.is_registered)
		register_tap_listener_mcast_stream();

	cf_retap_packets(&cfile, FALSE);

	if (!was_registered)
		remove_tap_listener_mcast_stream();
}


/****************************************************************************/
const mcaststream_tapinfo_t* mcaststream_get_info(void)
{
	return &the_tapinfo_struct;
}


/****************************************************************************/
/* TAP INTERFACE */
/****************************************************************************/

/* XXX just copied from gtk/rpc_stat.c */
void protect_thread_critical_region(void);
void unprotect_thread_critical_region(void);

/****************************************************************************/
void
remove_tap_listener_mcast_stream(void)
{
	if (the_tapinfo_struct.is_registered) {
		protect_thread_critical_region();
		remove_tap_listener(&the_tapinfo_struct);
		unprotect_thread_critical_region();

		the_tapinfo_struct.is_registered = FALSE;
	}
}


/****************************************************************************/
void
register_tap_listener_mcast_stream(void)
{
	GString *error_string;
	if (!the_tapinfo_struct.is_registered) {
		error_string = register_tap_listener("udp", &the_tapinfo_struct,
			NULL, mcaststream_reset_cb, mcaststream_packet,
			mcaststream_draw);

		if (error_string != NULL) {
			simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
				      error_string->str);
			g_string_free(error_string, TRUE);
			exit(1);
		}

		the_tapinfo_struct.is_registered = TRUE;
	}
}

/*******************************************************************************/
/* sliding window and buffer calculations */

/* compare two times */
guint16 comparetimes(struct timeval *t1, struct timeval *t2, guint16 burstint){
    if(((t2->tv_sec - t1->tv_sec)*1000 + (t2->tv_usec - t1->tv_usec)/1000) > burstint){
        return 1;
    } else{
        return 0;
    }
}

/* calculate buffer usage */
void buffusagecalc(mcast_stream_info_t *strinfo, packet_info *pinfo, double emptyspeed)
{
    gint32 sec=0, usec=0, cur, prev;
    struct timeval *buffer;
    double timeelapsed;

    buffer = strinfo->element.buff;
    cur = strinfo->element.last;
    if(cur == 0){
        cur = buffsize - 1;
        prev = cur - 1;
    } else if(cur == 1){
        prev = buffsize - 1;
        cur = 0;
    } else{
        cur=cur-1;
        prev=cur-1;
    }

    sec = buffer[cur].tv_sec - buffer[prev].tv_sec;
    usec = buffer[cur].tv_usec - buffer[prev].tv_usec;
    timeelapsed = (double)usec/1000000 + (double)sec;

    /* bytes added to buffer */
    strinfo->element.buffusage+=pinfo->fd->pkt_len;

    /* bytes cleared from buffer */
    strinfo->element.buffusage-=(timeelapsed * emptyspeed / 8);

    if(strinfo->element.buffusage < 0) strinfo->element.buffusage=0;
    if(strinfo->element.buffusage > strinfo->element.topbuffusage) 
				strinfo->element.topbuffusage = strinfo->element.buffusage;
    /* check for buffer losses */
    if((strinfo->element.buffusage >= bufferalarm) && (strinfo->element.buffstatus == 0)){
        strinfo->element.buffstatus = 1;
        strinfo->element.numbuffalarms++;
    } else if(strinfo->element.buffusage < bufferalarm){
        strinfo->element.buffstatus = 0;
    }

    return;
}

/* sliding window calculation */
void slidingwindow(mcast_stream_info_t *strinfo, packet_info *pinfo)
{
    struct timeval *buffer;
    gint32 diff;

    buffer = strinfo->element.buff;

    diff = strinfo->element.last - strinfo->element.first;
    if(diff < 0) diff+=buffsize;

    /* check if buffer is full */
    if(diff >= (buffsize - 2)){
        fprintf(stderr, "Warning: capture buffer full\n");
        strinfo->element.first++;
        if(strinfo->element.first >= buffsize) strinfo->element.first = strinfo->element.first % buffsize;
    }

    /* burst count */
    buffer[strinfo->element.last].tv_sec = pinfo->fd->rel_ts.secs;
    buffer[strinfo->element.last].tv_usec = pinfo->fd->rel_ts.nsecs/1000;
    while(comparetimes((struct timeval *)&(buffer[strinfo->element.first]), 
						(struct timeval *)&(buffer[strinfo->element.last]), burstint)){
        strinfo->element.first++;
        if(strinfo->element.first >= buffsize) strinfo->element.first = strinfo->element.first % buffsize;
        diff--;
    }
    strinfo->element.burstsize = diff;
    if(strinfo->element.burstsize > strinfo->element.topburstsize) {
	strinfo->element.topburstsize = strinfo->element.burstsize;
	strinfo->element.maxbw = (float)(strinfo->element.topburstsize) * 1000 / burstint * pinfo->fd->pkt_len * 8 / 1000000;
    }

    strinfo->element.last++;
    if(strinfo->element.last >= buffsize) strinfo->element.last = strinfo->element.last % buffsize;
    /* trigger check */
    if((strinfo->element.burstsize >= trigger) && (strinfo->element.burststatus == 0)){
        strinfo->element.burststatus = 1;
        strinfo->element.numbursts++;
    } else if(strinfo->element.burstsize < trigger){
        strinfo->element.burststatus = 0;
    }

    strinfo->element.count++;
}

/* mcast_stream.h
 *
 * Copyright 2006, Iskratel , Slovenia
 * By Jakob Bratkovic <j.bratkovic@xxxxxxxxxxx> and
 * Miha Jemec <m.jemec@xxxxxxxxxxx>
 *
 * based on rtp_stream.h
 * Copyright 2003, Alcatel Business Systems
 * By Lars Ruoff <lars.ruoff@xxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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.
 */

#ifndef Mcast_STREAM_H_INCLUDED
#define Mcast_STREAM_H_INCLUDED

#include <glib.h>
#include <stdio.h>
#include <epan/address.h>

/** @file
 *  ??? 
 *  @ingroup dialog_group
 *  @todo what's this?
 */

#define INTERFACE        2
#define FILTER           3
#define TRIGGER          4
#define TIMER            5
#define REFRESHTIMER     6
#define EMPTYSPEED       7
#define BUFFERALARM      8
#define CUMULEMPTYSPEED  9

#define MAX_SPEED 200000

/* typedefs for sliding window and buffer size */
typedef struct buffer{
    struct timeval *buff;   /* packet times */
    gint32 first;              /* pointer to the first element */
    gint32 last;               /* pointer to the last element */
    gint32 burstsize;          /* current burst */
    gint32 topburstsize;       /* maximum burst in the refresh interval*/
    gint32 count;              /* packet counter */
    gint32 burststatus;        /* burst status */
    gint32 numbursts;          /* number of bursts */
    gint32 buffusage;         /* buffer usage */
    gint32 buffstatus;        /* buffer status */
    gint32 numbuffalarms;      /* number of alarms triggered by buffer underruns */
    gint32 topbuffusage;      /* top buffer usage in refresh interval */
    float  maxbw;            /* maximum bandwidth usage */
} t_buffer;


/* defines an mcast stream */
typedef struct _mcast_stream_info {
	address src_addr;
	guint16 src_port;
	address dest_addr;
	guint16 dest_port;
	guint32 npackets;
	guint32 apackets;
	guint32 total_bytes;
	float   average_bw;

	guint32 first_frame_num; /* frame number of first frame */
	/* start of recording (GMT) of this stream */
	guint32 start_sec;         /* seconds */
	guint32 start_usec;        /* microseconds */
	guint32 start_rel_sec;         /* start stream rel seconds */
	guint32 start_rel_usec;        /* start stream rel microseconds */
	guint32 stop_rel_sec;         /* stop stream rel seconds */
	guint32 stop_rel_usec;        /* stop stream rel microseconds */
	guint16 vlan_id;
	
	/*for the sliding window */
	t_buffer element;

} mcast_stream_info_t;


/* structure that holds the information about all detected streams */
/* struct holding all information of the tap */
typedef struct _mcaststream_tapinfo {
	int     nstreams;       /* number of streams in the list */
	GList*  strinfo_list;   /* list with all streams */
	guint32 npackets;       /* total number of mcast packets of all streams */
	mcast_stream_info_t* allstreams; /* structure holding information common for all streams */

	guint32 launch_count;   /* number of times the tap has been run */
	gboolean is_registered; /* if the tap listener is currently registered or not */
} mcaststream_tapinfo_t;

/****************************************************************************/
/* INTERFACE */

/*
* Registers the mcast_streams tap listener (if not already done).
* From that point on, the Mcast streams list will be updated with every redissection.
* This function is also the entry point for the initialization routine of the tap system.
* So whenever mcast_stream.c is added to the list of ETHEREAL_TAP_SRCs, the tap will be registered on startup.
* If not, it will be registered on demand by the mcast_streams and mcast_analysis functions that need it.
*/
void register_tap_listener_mcast_stream(void);

/*
* Removes the mcast_streams tap listener (if not already done)
* From that point on, the Mcast streams list won't be updated any more.
*/
void remove_tap_listener_mcast_stream(void);

/*
* Retrieves a constant reference to the unique info structure of the mcast_streams tap listener.
* The user should not modify the data pointed to.
*/
const mcaststream_tapinfo_t* mcaststream_get_info(void);

/*
* Cleans up memory of mcast streams tap.
*/
void mcaststream_reset(mcaststream_tapinfo_t *tapinfo);

/*
* Scans all packets for Mcast streams and updates the Mcast streams list.
* (redissects all packets)
*/
void mcaststream_scan(void);

#endif /*Mcast_STREAM_H_INCLUDED*/
/* mcast_stream_dlg.c
 *
 * Copyright 2006, Iskratel , Slovenia
 * By Jakob Bratkovic <j.bratkovic@xxxxxxxxxxx> and
 * Miha Jemec <m.jemec@xxxxxxxxxxx>
 *
 * based on rtp_stream_dlg.c
 * Copyright 2003, Alcatel Business Systems
 * By Lars Ruoff <lars.ruoff@xxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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 "mcast_stream_dlg.h"
#include "mcast_stream.h"

#include "globals.h"
#include "epan/filesystem.h"

#include "../stat_menu.h"
#include "gui_stat_menu.h"
#include "dlg_utils.h"
#include "gui_utils.h"
#include "compat_macros.h"
#include "gtkglobals.h"
#include "simple_dialog.h"

#include "image/clist_ascend.xpm"
#include "image/clist_descend.xpm"

#include <epan/address.h>

#include <string.h>
#include <locale.h>
#include <epan/addr_resolv.h>

/* Capture callback data keys */
#define E_MCAST_ENTRY_1     "burst_interval"
#define E_MCAST_ENTRY_2     "burst_alarm"
#define E_MCAST_ENTRY_3     "buffer_alarm"
#define E_MCAST_ENTRY_4     "stream_speed"
#define E_MCAST_ENTRY_5     "total_speed"

extern guint16 burstint;
extern guint32 trigger;
extern guint32 bufferalarm;
extern gint32 emptyspeed;
extern gint32 cumulemptyspeed; 

static const gchar FWD_LABEL_TEXT[] = "Select a stream with left mouse button";
static const gchar PAR_LABEL_TEXT[] = "\nBurst int: ms   Burst alarm: pps    Buffer alarm: KB    Stream empty speed: Mbps    Total empty speed: Mbps\n";

/****************************************************************************/
static GtkWidget *mcast_stream_dlg = NULL;
static GtkWidget *mcast_params_dlg = NULL;

static GtkWidget *clist = NULL;
static GtkWidget *top_label = NULL;
static GtkWidget *label_fwd = NULL;
static GtkWidget *label_par = NULL;

static mcast_stream_info_t* selected_stream_fwd = NULL;  /* current selection */
static GList *last_list = NULL;

static guint32 streams_nb = 0;     /* number of displayed streams */

#define NUM_COLS 12
static const gchar *titles[NUM_COLS] =  {"Src IP addr", "Src port",  "Dst IP addr", "Dst port", "Packets", "Packets/s", "Awg Bw", "Max Bw", "Max burst", "Burst Alarms", "Max buffer", "Buff Alarms"};

/****************************************************************************/
/* append a line to clist */
static void add_to_clist(mcast_stream_info_t* strinfo)
{
	gchar label_text[256];
	gint added_row;
	gchar *data[NUM_COLS];
	int i;
	char *savelocale;

	/* save the current locale */
	savelocale = setlocale(LC_NUMERIC, NULL);
	/* switch to "C" locale to avoid problems with localized decimal separators
		in g_snprintf("%f") functions */
	setlocale(LC_NUMERIC, "C");
	data[0] = g_strdup(get_addr_name(&(strinfo->src_addr)));
	data[1] = g_strdup_printf("%u", strinfo->src_port);
	data[2] = g_strdup(get_addr_name(&(strinfo->dest_addr)));
	data[3] = g_strdup_printf("%u", strinfo->dest_port);
	data[4] = g_strdup_printf("%u", strinfo->npackets);
	data[5] = g_strdup_printf("%u /s", strinfo->apackets);
	data[6] = g_strdup_printf("%2.1f Mbps", strinfo->average_bw);
	data[7] = g_strdup_printf("%2.1f Mbps", strinfo->element.maxbw);
	data[8] = g_strdup_printf("%u / %dms", strinfo->element.topburstsize, burstint);
	data[9] = g_strdup_printf("%u", strinfo->element.numbursts);
	data[10] = g_strdup_printf("%.1f KB", (float)strinfo->element.topbuffusage/1000);
	data[11] = g_strdup_printf("%u", strinfo->element.numbuffalarms);

	/* restore previous locale setting */
	setlocale(LC_NUMERIC, savelocale);

	added_row = gtk_clist_append(GTK_CLIST(clist), data);
	for (i = 0; i < NUM_COLS; i++)
		g_free(data[i]);

	/* set data pointer of last row to point to user data for that row */
	gtk_clist_set_row_data(GTK_CLIST(clist), added_row, strinfo);

	/* Update the top label with the number of detected streams */
	sprintf(label_text,
	        "Detected %d Multicast streams,   Average Bw: %.1f Mbps   Max Bw: %.1f Mbps   Max burst: %d / %dms   Max buffer: %.1f KB",
	        ++streams_nb, 
		mcaststream_get_info()->allstreams->average_bw, mcaststream_get_info()->allstreams->element.maxbw, 
		mcaststream_get_info()->allstreams->element.topburstsize, burstint, 
		(float)(mcaststream_get_info()->allstreams->element.topbuffusage)/1000);
	gtk_label_set(GTK_LABEL(top_label), label_text);

	g_snprintf(label_text, 200, "\nBurst int: %u ms   Burst alarm: %u pps   Buffer alarm: %u Bytes   Stream empty speed: %u Kbps   Total empty speed: %u Kbps\n", 
		burstint, trigger, bufferalarm, emptyspeed, cumulemptyspeed);
	gtk_label_set_text(GTK_LABEL(label_par), label_text);
}

/****************************************************************************/
/* CALLBACKS                                                                */
/****************************************************************************/
static void
mcaststream_on_destroy                      (GtkObject       *object _U_,
                                        gpointer         user_data _U_)
{
	/* Remove the stream tap listener */
	remove_tap_listener_mcast_stream();

	/* Is there a params window open? */
        if (mcast_params_dlg != NULL)
                window_destroy(mcast_params_dlg);

	/* Clean up memory used by stream tap */
	mcaststream_reset((mcaststream_tapinfo_t*) mcaststream_get_info());

	/* Note that we no longer have a "Mcast Streams" dialog box. */
	mcast_stream_dlg = NULL;
}


/****************************************************************************/
static void
mcaststream_on_unselect                  (GtkButton       *button _U_,
                                        gpointer         user_data _U_)
{
	selected_stream_fwd = NULL;
	gtk_clist_unselect_all(GTK_CLIST(clist));
	gtk_label_set_text(GTK_LABEL(label_fwd), FWD_LABEL_TEXT);
}


/****************************************************************************/
static void
mcaststream_on_filter                    (GtkButton       *button _U_,
                                        gpointer         user_data _U_)
{
	gchar *filter_string = NULL;
	gchar *filter_string_fwd = NULL;
	gchar ip_version[3];

	if (selected_stream_fwd==NULL)
		return;

	if (selected_stream_fwd)
	{
		if (selected_stream_fwd->src_addr.type==AT_IPv6){
			strcpy(ip_version,"v6");
		}		
		else{
			strcpy(ip_version,"");
		}
		filter_string_fwd = g_strdup_printf(
			"(ip%s.src==%s && udp.srcport==%u && ip%s.dst==%s && udp.dstport==%u)",
			ip_version,
			address_to_str(&(selected_stream_fwd->src_addr)),
			selected_stream_fwd->src_port,
			ip_version,
			address_to_str(&(selected_stream_fwd->dest_addr)),
			selected_stream_fwd->dest_port);
        filter_string = filter_string_fwd;
	}

        gtk_entry_set_text(GTK_ENTRY(main_display_filter_widget), filter_string);
        g_free(filter_string);

/*
	main_filter_packets(&cfile, filter_string, FALSE);
	mcaststream_dlg_update(mcaststream_get_info()->strinfo_list);
*/
}


/****************************************************************************/
/* when the user selects a row in the stream list */
static void
mcaststream_on_select_row(GtkCList *clist,
                                            gint row _U_,
                                            gint column _U_,
                                            GdkEventButton *event _U_,
                                            gpointer user_data _U_)
{
	gchar label_text[80];

	selected_stream_fwd = gtk_clist_get_row_data(GTK_CLIST(clist), row);
	g_snprintf(label_text, 80, "Selected: %s:%u -> %s:%u",
			get_addr_name(&(selected_stream_fwd->src_addr)),
			selected_stream_fwd->src_port,
			get_addr_name(&(selected_stream_fwd->dest_addr)),
			selected_stream_fwd->dest_port
	);
	gtk_label_set_text(GTK_LABEL(label_fwd), label_text);

/*
	gtk_widget_set_sensitive(filter_bt, TRUE);
*/
	/* TODO: activate other buttons when implemented */
}


/****************************************************************************/
typedef struct column_arrows {
	GtkWidget *table;
	GtkWidget *ascend_pm;
	GtkWidget *descend_pm;
} column_arrows;


/****************************************************************************/
static void
mcaststream_click_column_cb(GtkCList *clist, gint column, gpointer data)
{
	column_arrows *col_arrows = (column_arrows *) data;
	int i;

	gtk_clist_freeze(clist);

	for (i=0; i<NUM_COLS; i++) {
		gtk_widget_hide(col_arrows[i].ascend_pm);
		gtk_widget_hide(col_arrows[i].descend_pm);
	}

	if (column == clist->sort_column) {
		if (clist->sort_type == GTK_SORT_ASCENDING) {
			clist->sort_type = GTK_SORT_DESCENDING;
			gtk_widget_show(col_arrows[column].descend_pm);
		} else {
			clist->sort_type = GTK_SORT_ASCENDING;
			gtk_widget_show(col_arrows[column].ascend_pm);
		}
	} else {
		clist->sort_type = GTK_SORT_ASCENDING;
		gtk_widget_show(col_arrows[column].ascend_pm);
		gtk_clist_set_sort_column(clist, column);
	}
	gtk_clist_thaw(clist);

	gtk_clist_sort(clist);
}


/****************************************************************************/
static gint
mcaststream_sort_column(GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2)
{
	char *text1 = NULL;
	char *text2 = NULL;
	int i1, i2;

	const GtkCListRow *row1 = (const GtkCListRow *) ptr1;
	const GtkCListRow *row2 = (const GtkCListRow *) ptr2;

	text1 = GTK_CELL_TEXT (row1->cell[clist->sort_column])->text;
	text2 = GTK_CELL_TEXT (row2->cell[clist->sort_column])->text;

	switch(clist->sort_column){
	case 0:
	case 2:
		return strcmp (text1, text2);
	case 1:
	case 3:
	case 4:
	case 5:
	case 6:
	case 7:
	case 8:
	case 9:
	case 10:
	case 11:
		i1=atoi(text1);
		i2=atoi(text2);
		return i1-i2;
	}
	g_assert_not_reached();
	return 0;
}


/****************************************************************************/
/* INTERFACE                                                                */
/****************************************************************************/
static void mcast_params_destroy_cb(GtkWidget *win _U_, gpointer user_data _U_)
{
        /* Note that we no longer have a mcast params dialog box. */
        mcast_params_dlg = NULL;
}


static void
mcast_params_ok_cb(GtkWidget *ok_bt _U_, gpointer parent_w)
{
	GtkWidget   *fnumber_te;
	const gchar *fnumber_text;
	gint32        fnumber;
	char        *p;

	fnumber_te = (GtkWidget *)OBJECT_GET_DATA(parent_w, E_MCAST_ENTRY_1);
	fnumber_text = gtk_entry_get_text(GTK_ENTRY(fnumber_te));
	fnumber = strtoul(fnumber_text, &p, 10);
	if ( (p == fnumber_text || *p != '\0') || (fnumber <=0) || (fnumber > 1000) ){
		simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "The burst interval should be between 1 and 1000 ms ");
		return; }
	burstint = fnumber;

	fnumber_te = (GtkWidget *)OBJECT_GET_DATA(parent_w, E_MCAST_ENTRY_2);
	fnumber_text = gtk_entry_get_text(GTK_ENTRY(fnumber_te));
	fnumber = strtoul(fnumber_text, &p, 10);
	if ( (p == fnumber_text || *p != '\0') || (fnumber <=0) ){
		simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "The burst alarm treshold you entered isn't valid.");
		return; }
	trigger = fnumber;

	fnumber_te = (GtkWidget *)OBJECT_GET_DATA(parent_w, E_MCAST_ENTRY_3);
	fnumber_text = gtk_entry_get_text(GTK_ENTRY(fnumber_te));
	fnumber = strtoul(fnumber_text, &p, 10);
	if ( (p == fnumber_text || *p != '\0') || (fnumber <=0) ){
		simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "The buffer alarm treshold you entered isn't valid.");
		return; }
	bufferalarm = fnumber;

	fnumber_te = (GtkWidget *)OBJECT_GET_DATA(parent_w, E_MCAST_ENTRY_4);
	fnumber_text = gtk_entry_get_text(GTK_ENTRY(fnumber_te));
	fnumber = strtoul(fnumber_text, &p, 10);
	if ( (p == fnumber_text || *p != '\0') || (fnumber <=0) || (fnumber > 10000000) ){
		simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "The stream empty speed should be between 1 and 10000000");
		return; }
	emptyspeed = fnumber;

	fnumber_te = (GtkWidget *)OBJECT_GET_DATA(parent_w, E_MCAST_ENTRY_5);
	fnumber_text = gtk_entry_get_text(GTK_ENTRY(fnumber_te));
	fnumber = strtoul(fnumber_text, &p, 10);
	if ( (p == fnumber_text || *p != '\0') || (fnumber <=0) || (fnumber > 10000000) ){
		simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "The total empty speed should be between 1 and 10000000");
		return; }
	cumulemptyspeed = fnumber; 

	window_destroy(GTK_WIDGET(parent_w));

	/* Clean up memory used by stream tap */
        mcaststream_reset((mcaststream_tapinfo_t*) mcaststream_get_info());
	/* retap all packets */
        cf_retap_packets(&cfile, FALSE);

}



static void
mcast_on_params                      (GtkButton       *button _U_,
                                        gpointer         data _U_)
{
	GtkWidget *main_vb;
        GtkWidget *label, *hbuttonbox, *table;
        GtkWidget *ok_bt, *cancel_bt;
	GtkWidget *entry1, *entry2, *entry3, *entry4, *entry5;
	gchar label_text[51];

	if (mcast_params_dlg != NULL) {
                /* There's already a Params dialog box; reactivate it. */
                reactivate_window(mcast_params_dlg);
                return;
        }

	mcast_params_dlg = window_new(GTK_WINDOW_TOPLEVEL, "Ethereal: Set parameters for Multicast Stream Analysis");
        gtk_window_set_default_size(GTK_WINDOW(mcast_params_dlg), 210, 210);

        gtk_widget_show(mcast_params_dlg);
	
        /* Container for each row of widgets */
        main_vb = gtk_vbox_new(FALSE, 3);
        gtk_container_border_width(GTK_CONTAINER(main_vb), 2);
        gtk_container_add(GTK_CONTAINER(mcast_params_dlg), main_vb);
        gtk_widget_show(main_vb);

	table = gtk_table_new (6, 2, FALSE);
	gtk_container_add (GTK_CONTAINER (main_vb), table);

	label = gtk_label_new("  Burst measurement interval (ms)  ");
	gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
	entry1 = gtk_entry_new();
	g_snprintf(label_text, 50, "%u", burstint);
	gtk_entry_set_text(GTK_ENTRY(entry1), label_text);
	gtk_table_attach_defaults(GTK_TABLE(table), entry1, 1, 2, 0, 1);
	label = gtk_label_new("  Burst alarm treshold (packets)   ");
	gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
	entry2 = gtk_entry_new();
	g_snprintf(label_text, 50, "%u", trigger);
	gtk_entry_set_text(GTK_ENTRY(entry2), label_text);
	gtk_table_attach_defaults(GTK_TABLE(table), entry2, 1, 2, 1, 2);
	label = gtk_label_new("  Buffer alarm treshold (bytes)     ");
	gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
	entry3 = gtk_entry_new();
	g_snprintf(label_text, 50, "%u", bufferalarm);
	gtk_entry_set_text(GTK_ENTRY(entry3), label_text);
	gtk_table_attach_defaults(GTK_TABLE(table), entry3, 1, 2, 2, 3);
	label = gtk_label_new("  Stream empty speed (kbit/s)      ");
	gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
	entry4 = gtk_entry_new();
	g_snprintf(label_text, 50, "%u", emptyspeed);
	gtk_entry_set_text(GTK_ENTRY(entry4), label_text);
	gtk_table_attach_defaults(GTK_TABLE(table), entry4, 1, 2, 3, 4);
	label = gtk_label_new("  Total empty speed (kbit/s)       ");
	gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 4, 5);
	entry5 = gtk_entry_new();
	g_snprintf(label_text, 50, "%u", cumulemptyspeed);
	gtk_entry_set_text(GTK_ENTRY(entry5), label_text);
	gtk_table_attach_defaults(GTK_TABLE(table), entry5, 1, 2, 4, 5);

	gtk_widget_show (table);

	/* button row */
	hbuttonbox = gtk_hbutton_box_new ();
	gtk_table_attach_defaults(GTK_TABLE(table), hbuttonbox, 0, 2, 5, 6);
	ok_bt = BUTTON_NEW_FROM_STOCK(GTK_STOCK_OK);
	gtk_container_add (GTK_CONTAINER (hbuttonbox), ok_bt);
	cancel_bt = BUTTON_NEW_FROM_STOCK(GTK_STOCK_CANCEL);
	gtk_container_add (GTK_CONTAINER (hbuttonbox), cancel_bt);
	GTK_WIDGET_SET_FLAGS(cancel_bt, GTK_CAN_DEFAULT);
	gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox), GTK_BUTTONBOX_END);
	gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbuttonbox), 0);
        
	SIGNAL_CONNECT(mcast_params_dlg, "delete_event", window_delete_event_cb, NULL);
        SIGNAL_CONNECT(mcast_params_dlg, "destroy", mcast_params_destroy_cb, NULL);
	SIGNAL_CONNECT(ok_bt, "clicked", mcast_params_ok_cb, mcast_params_dlg);
	window_set_cancel_button(mcast_params_dlg, cancel_bt, window_cancel_button_cb);

	/* Attach pointers to needed widgets */
	OBJECT_SET_DATA(mcast_params_dlg, E_MCAST_ENTRY_1, entry1);
	OBJECT_SET_DATA(mcast_params_dlg, E_MCAST_ENTRY_2, entry2);
	OBJECT_SET_DATA(mcast_params_dlg, E_MCAST_ENTRY_3, entry3);
	OBJECT_SET_DATA(mcast_params_dlg, E_MCAST_ENTRY_4, entry4);
	OBJECT_SET_DATA(mcast_params_dlg, E_MCAST_ENTRY_5, entry5);

	gtk_widget_show_all(mcast_params_dlg);
	window_present(mcast_params_dlg);
}



static void mcaststream_dlg_create (void)
{
    GtkWidget *mcaststream_dlg_w;
    GtkWidget *main_vb;
    GtkWidget *scrolledwindow;
    GtkWidget *hbuttonbox;
    /*GtkWidget *bt_unselect;*/
    GtkWidget *bt_filter;
    GtkWidget *bt_params;
    GtkWidget *bt_close;
    GtkTooltips *tooltips = gtk_tooltips_new();

    column_arrows *col_arrows;
    GtkWidget *column_lb;
    int i;

    mcaststream_dlg_w = dlg_window_new("Ethereal: Multicast Streams");
    gtk_window_set_default_size(GTK_WINDOW(mcaststream_dlg_w), 620, 400);

    main_vb = gtk_vbox_new (FALSE, 0);
    gtk_container_add(GTK_CONTAINER(mcaststream_dlg_w), main_vb);
    gtk_container_set_border_width (GTK_CONTAINER (main_vb), 12);

    top_label = gtk_label_new ("Detected 0 Multicast streams");
    gtk_box_pack_start (GTK_BOX (main_vb), top_label, FALSE, FALSE, 8);

    scrolledwindow = scrolled_window_new (NULL, NULL);
    gtk_box_pack_start (GTK_BOX (main_vb), scrolledwindow, TRUE, TRUE, 0);

    clist = gtk_clist_new (NUM_COLS);
    gtk_container_add (GTK_CONTAINER (scrolledwindow), clist);

    gtk_clist_set_column_width (GTK_CLIST (clist), 0, 95);
    gtk_clist_set_column_width (GTK_CLIST (clist), 1, 55);
    gtk_clist_set_column_width (GTK_CLIST (clist), 2, 95);
    gtk_clist_set_column_width (GTK_CLIST (clist), 3, 55);
    gtk_clist_set_column_width (GTK_CLIST (clist), 4, 70);
    gtk_clist_set_column_width (GTK_CLIST (clist), 5, 70);
    gtk_clist_set_column_width (GTK_CLIST (clist), 6, 60);
    gtk_clist_set_column_width (GTK_CLIST (clist), 7, 60);
    gtk_clist_set_column_width (GTK_CLIST (clist), 8, 80);
    gtk_clist_set_column_width (GTK_CLIST (clist), 9, 85);
    gtk_clist_set_column_width (GTK_CLIST (clist), 10, 80);
    gtk_clist_set_column_width (GTK_CLIST (clist), 11, 80);

    gtk_clist_set_column_justification(GTK_CLIST(clist), 0, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 1, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 2, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 3, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 4, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 5, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 6, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 7, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 8, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 9, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 10, GTK_JUSTIFY_CENTER);
    gtk_clist_set_column_justification(GTK_CLIST(clist), 11, GTK_JUSTIFY_CENTER);

    gtk_clist_column_titles_show (GTK_CLIST (clist));

    gtk_clist_set_compare_func(GTK_CLIST(clist), mcaststream_sort_column);
    gtk_clist_set_sort_column(GTK_CLIST(clist), 0);
    gtk_clist_set_sort_type(GTK_CLIST(clist), GTK_SORT_ASCENDING);

    gtk_widget_show(mcaststream_dlg_w);

    /* sort by column feature */
    col_arrows = (column_arrows *) g_malloc(sizeof(column_arrows) * NUM_COLS);

    for (i=0; i<NUM_COLS; i++) {
        col_arrows[i].table = gtk_table_new(2, 2, FALSE);
        gtk_table_set_col_spacings(GTK_TABLE(col_arrows[i].table), 5);
        column_lb = gtk_label_new(titles[i]);
        gtk_table_attach(GTK_TABLE(col_arrows[i].table), column_lb, 0, 1, 0, 2, GTK_SHRINK, GTK_SHRINK, 0, 0);
        gtk_widget_show(column_lb);

        col_arrows[i].ascend_pm = xpm_to_widget(clist_ascend_xpm);
        gtk_table_attach(GTK_TABLE(col_arrows[i].table), col_arrows[i].ascend_pm, 1, 2, 1, 2, GTK_SHRINK, GTK_SHRINK, 0, 0);
        col_arrows[i].descend_pm = xpm_to_widget(clist_descend_xpm);
        gtk_table_attach(GTK_TABLE(col_arrows[i].table), col_arrows[i].descend_pm, 1, 2, 0, 1, GTK_SHRINK, GTK_SHRINK, 0, 0);
        /* make src-ip be the default sort order */
        if (i == 0) {
            gtk_widget_show(col_arrows[i].ascend_pm);
        }
        gtk_clist_set_column_widget(GTK_CLIST(clist), i, col_arrows[i].table);
        gtk_widget_show(col_arrows[i].table);
    }

    SIGNAL_CONNECT(clist, "click-column", mcaststream_click_column_cb, col_arrows);

    label_fwd = gtk_label_new (FWD_LABEL_TEXT);
    //gtk_box_pack_start (GTK_BOX (main_vb), label_fwd, FALSE, FALSE, 0);

    label_par = gtk_label_new (PAR_LABEL_TEXT);
    gtk_box_pack_start (GTK_BOX (main_vb), label_par, FALSE, FALSE, 0);

    /* button row */
    hbuttonbox = gtk_hbutton_box_new ();
    gtk_box_pack_start (GTK_BOX (main_vb), hbuttonbox, FALSE, FALSE, 0);
    gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox), GTK_BUTTONBOX_END);
    gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbuttonbox), 0);

    /*bt_unselect = gtk_button_new_with_label ("Unselect");
    gtk_container_add (GTK_CONTAINER (hbuttonbox), bt_unselect);
    gtk_tooltips_set_tip (tooltips, bt_unselect, "Undo stream selection", NULL);*/

    bt_params = gtk_button_new_with_label ("Set parameters");
    gtk_container_add (GTK_CONTAINER (hbuttonbox), bt_params);
    gtk_tooltips_set_tip (tooltips, bt_params, "Set buffer, limit and speed parameters", NULL);

    bt_filter = gtk_button_new_with_label ("Prepare Filter");
    gtk_container_add (GTK_CONTAINER (hbuttonbox), bt_filter);
    gtk_tooltips_set_tip (tooltips, bt_filter, "Prepare a display filter of the selected stream", NULL);

    bt_close = BUTTON_NEW_FROM_STOCK(GTK_STOCK_CLOSE);
    gtk_container_add (GTK_CONTAINER (hbuttonbox), bt_close);
    gtk_tooltips_set_tip (tooltips, bt_close, "Close this dialog", NULL);
    GTK_WIDGET_SET_FLAGS(bt_close, GTK_CAN_DEFAULT);

    SIGNAL_CONNECT(clist, "select_row", mcaststream_on_select_row, NULL);
    //SIGNAL_CONNECT(bt_unselect, "clicked", mcaststream_on_unselect, NULL);
    SIGNAL_CONNECT(bt_params, "clicked", mcast_on_params, NULL);
    SIGNAL_CONNECT(bt_filter, "clicked", mcaststream_on_filter, NULL);
    window_set_cancel_button(mcaststream_dlg_w, bt_close, window_cancel_button_cb);

    SIGNAL_CONNECT(mcaststream_dlg_w, "delete_event", window_delete_event_cb, NULL);
    SIGNAL_CONNECT(mcaststream_dlg_w, "destroy", mcaststream_on_destroy, NULL);

    gtk_widget_show_all(mcaststream_dlg_w);
    window_present(mcaststream_dlg_w);

    mcaststream_on_unselect(NULL, NULL);

    mcast_stream_dlg = mcaststream_dlg_w;
}


/****************************************************************************/
/* PUBLIC								    */
/****************************************************************************/

/****************************************************************************/
/* update the contents of the dialog box clist */
/* list: pointer to list of mcast_stream_info_t* */
void mcaststream_dlg_update(GList *list)
{
	if (mcast_stream_dlg != NULL) {
		gtk_clist_clear(GTK_CLIST(clist));
		streams_nb = 0;

		list = g_list_first(list);
		while (list)
		{
			add_to_clist((mcast_stream_info_t*)(list->data));
			list = g_list_next(list);
		}

		mcaststream_on_unselect(NULL, NULL);
	}

	last_list = list;
}


/****************************************************************************/
/* update the contents of the dialog box clist */
/* list: pointer to list of mcast_stream_info_t* */
void mcaststream_dlg_show(GList *list)
{
	if (mcast_stream_dlg != NULL) {
		/* There's already a dialog box; reactivate it. */
		reactivate_window(mcast_stream_dlg);
		/* Another list since last call? */
		if (list != last_list) {
			mcaststream_dlg_update(list);
		}
	}
	else {
		/* Create and show the dialog box */
		mcaststream_dlg_create();
		mcaststream_dlg_update(list);
	}
}


/****************************************************************************/
/* entry point when called via the GTK menu */
static void mcaststream_launch(GtkWidget *w _U_, gpointer data _U_)
{
	/* Register the tap listener */
	register_tap_listener_mcast_stream();

	/* Scan for Mcast streams (redissect all packets) */
	mcaststream_scan();

	/* Show the dialog box with the list of streams */
	mcaststream_dlg_show(mcaststream_get_info()->strinfo_list);

	/* Tap listener will be removed and cleaned up in mcaststream_on_destroy */
}

/****************************************************************************/
void
register_tap_listener_mcast_stream_dlg(void)
{
	register_stat_menu_item("Multicat Streams", REGISTER_STAT_GROUP_NONE,
	    mcaststream_launch, NULL, NULL, NULL);
}
/* mcast_stream_dlg.h
 *
 * Copyright 2006, Iskratel , Slovenia
 * By Jakob Bratkovic <j.bratkovic@xxxxxxxxxxx> and
 * Miha Jemec <m.jemec@xxxxxxxxxxx>
 *
 * based on rtp_stream_dlg.h
 * Copyright 2003, Alcatel Business Systems
 * By Lars Ruoff <lars.ruoff@xxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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.
 */

#ifndef Mcast_STREAM_DLG_H_INCLUDED
#define Mcast_STREAM_DLG_H_INCLUDED

#include <gtk/gtk.h>

/** @file
 *  "Mcast Stream Analysis" dialog box.
 */

/**
 * Create or reactivate the mcast streams dialog box.
 *
 * @param list pointer to list of mcast_stream_info_t*
 */
void mcaststream_dlg_show(GList *list);

/**
 * Update the contents of the dialog box clist with that of list.
 *
 * @param list pointer to list of mcast_stream_info_t*
 */
void mcaststream_dlg_update(GList *list);

#endif /*Mcast_STREAM_DLG_H_INCLUDED*/
# Makefile.am
# Automake file for the GTK interface routines for Ethereal
#
# $Id: Makefile.am 16593 2005-11-25 23:42:52Z ulfl $
#
# Ethereal - Network traffic analyzer
# By Gerald Combs <gerald@xxxxxxxxxxxx>
# 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.

include Makefile.common

noinst_LIBRARIES = libui.a

CLEANFILES = \
	libui.a		\
	*~

MAINTAINERCLEANFILES = \
	$(GENERATED_FILES)	\
	Makefile.in

ethereal-tap-register.c: $(ETHEREAL_TAP_SRC) $(top_srcdir)/make-tapreg-dotc
	@echo Making ethereal-tap-register.c
	@$(top_srcdir)/make-tapreg-dotc ethereal-tap-register.c $(srcdir) $(ETHEREAL_TAP_SRC)

noinst_HEADERS = \
	about_dlg.h	\
	capture_dlg.h	\
	capture_prefs.h	\
	capture_if_details_dlg.h	\
	cfilter_combo_utils.h \
	color_dlg.h	\
	colors.h	\
	column_prefs.h	\
	compat_macros.h	\
	conversations_table.h \
	decode_as_dlg.h	\
	decode_as_dcerpc.h \
	dfilter_expr_dlg.h \
	dlg_utils.h	\
	expert_comp_table.h	\
	file_dlg.h	\
	fileset_dlg.h	\
	filter_dlg.h	\
	find_dlg.h	\
	follow_dlg.h	\
	font_utils.h	\
	goto_dlg.h	\
	graph_analysis.h \
	gsm_map_stat.h	\
	gtkglobals.h	\
	gui_stat_util.h \
	gui_prefs.h	\
	gui_utils.h	\
	help_dlg.h	\
	hostlist_table.h \
	isprint.h	\
	keys.h		\
	layout_prefs.h	\
	main.h		\
	menu.h		\
	mtp3_stat.h	\
	nameres_prefs.h	\
	packet_history.h	\
	packet_list.h	\
	packet_win.h	\
	plugins_dlg.h	\
	prefs_dlg.h	\
	print_prefs.h	\
	proto_dlg.h	\
	proto_draw.h	\
	proto_hier_stats_dlg.h	\
	range_utils.h \
	recent.h	\
	rtp_analysis.h	\
	rtp_stream.h	\
	rtp_stream_dlg.h \
	mcast_stream.h	\
	mcast_stream_dlg.h \
	sat.h		\
	sctp_stat.h	\
	service_response_time_table.h	\
	gui_stat_menu.h	\
	stream_prefs.h	\
	summary_dlg.h   \
	supported_protos_dlg.h   \
	text_page.h	\
	toolbar.h	\
	voip_calls.h	\
	voip_calls_dlg.h \
	webbrowser.h

if USE_GTK2
libui_a_SOURCES = \
	$(ETHEREAL_GTK_SRC) \
	$(noinst_HEADERS) \
	$(ETHEREAL_TAP_SRC)
else
libui_a_SOURCES = \
	ethclist.c	\
	ethclist.h	\
	$(ETHEREAL_GTK_SRC) \
	$(noinst_HEADERS) \
	$(ETHEREAL_TAP_SRC)
endif

libui_a_DEPENDENCIES = 

EXTRA_DIST = \
	capture_if_details_dlg.c	\
	doxygen.cfg.in \
	ethclist.c	\
	ethclist.h	\
	Makefile.common \
	Makefile.nmake \
	print_mswin.c  \
	print_mswin.h

# Common headers
AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/wiretap

doxygen:
if HAVE_DOXYGEN
	$(DOXYGEN) doxygen.cfg
endif		# HAVE_DOXYGEN

# Makefile.common
#     Contains the stuff from Makefile.am and Makefile.nmake that is
#     a) common to both files and
#     b) portable between both files
#
# $Id: Makefile.common 16593 2005-11-25 23:42:52Z ulfl $
#
# Ethereal - Network traffic analyzer
# By Gerald Combs <gerald@xxxxxxxxxxxx>
# 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.

# Generated header files that we want in the distribution.
# (None, so far.)
GENERATED_HEADER_FILES =

# Generated C source files that we want in the distribution.
GENERATED_C_FILES = \
	ethereal-tap-register.c

# All the generated files we want in the distribution.
GENERATED_FILES = $(GENERATED_HEADER_FILES) $(GENERATED_C_FILES)

#
# ethclist.obj is not in here because it is currently gtk+-1.2-only
# code, while the DLL for GTK+ on Windows is gtk+-1.3 or gtk+-2.x.
#
ETHEREAL_GTK_SRC = \
	about_dlg.c	\
	capture_dlg.c	\
	capture_if_dlg.c	\
	capture_if_details_dlg.c	\
	capture_info_dlg.c	\
	capture_prefs.c	\
	cfilter_combo_utils.c	\
	color_dlg.c	\
	color_utils.c	\
	colors.c	\
	column_prefs.c	\
	conversations_table.c	\
	decode_as_dlg.c	\
	decode_as_dcerpc.c	\
	dfilter_expr_dlg.c	\
	dlg_utils.c	\
	drag_and_drop.c 	\
	ethereal-tap-register.c	\
	expert_comp_table.c    \
	file_dlg.c	\
	fileset_dlg.c	\
	filter_dlg.c	\
	find_dlg.c	\
	follow_dlg.c	\
	font_utils.c	\
	goto_dlg.c	\
	graph_analysis.c \
	gui_stat_util.c	\
	gui_prefs.c	\
	gui_utils.c \
	help_dlg.c	\
	hostlist_table.c \
	layout_prefs.c	\
	main.c 	\
	menu.c 	\
	nameres_prefs.c	\
	packet_history.c	\
	packet_list.c	\
	packet_win.c	\
	plugins_dlg.c	\
	prefs_dlg.c	\
	print_dlg.c	\
	print_prefs.c	\
	progress_dlg.c	\
	proto_dlg.c	\
	proto_draw.c	\
	proto_hier_stats_dlg.c	\
	range_utils.c \
	recent.c	\
	rtp_stream.c	\
	mcast_stream.c	\
	sctp_stat.c	\
	sctp_graph_dlg.c	\
	sctp_byte_graph_dlg.c	\
	sctp_error_dlg.c	\
	service_response_time_table.c	\
	simple_dialog.c	\
	stream_prefs.c	\
	summary_dlg.c	\
	supported_protos_dlg.c	\
	tap_dfilter_dlg.c	\
	text_page.c	\
	toolbar.c	\
	voip_calls.c \
	webbrowser.c


ETHEREAL_TAP_SRC = \
	afp_stat.c	\
	ansi_a_stat.c	\
	ansi_map_stat.c	\
	bootp_stat.c	\
	conversations_eth.c	\
	conversations_fc.c	\
	conversations_fddi.c	\
	conversations_ip.c	\
	conversations_ipx.c	\
	conversations_jxta.c		\
	conversations_sctp.c	\
	conversations_tcpip.c	\
	conversations_tr.c	\
	conversations_udpip.c	\
	conversations_wlan.c	\
	conversations_ncp.c     \
	conversations_rsvp.c	\
	dcerpc_stat.c	\
	expert_comp_dlg.c     \
	expert_dlg.c	\
	fc_stat.c	\
	flow_graph.c	\
	gsm_a_stat.c	\
	gsm_map_stat.c	\
	gsm_map_summary.c	\
	h225_counter.c	\
	h225_ras_srt.c	\
	hostlist_eth.c \
	hostlist_fc.c \
	hostlist_fddi.c \
	hostlist_ip.c \
	hostlist_ipx.c \
	hostlist_jxta.c	\
	hostlist_tcpip.c \
	hostlist_tr.c \
	hostlist_udpip.c \
	hostlist_wlan.c \
	hostlist_rsvp.c \
	io_stat.c	\
	ldap_stat.c	\
	mgcp_stat.c	\
	mtp3_stat.c	\
	mtp3_summary.c	\
	ncp_stat.c  \
	rpc_progs.c	\
	rpc_stat.c	\
	rtp_analysis.c	\
	rtp_stream_dlg.c	\
	mcast_stream_dlg.c	\
	stats_tree_stat.c	\
	sctp_assoc_analyse.c	\
	sctp_chunk_stat_dlg.c	\
	sctp_chunk_stat.c	\
	sctp_stat_dlg.c	\
	sip_stat.c	\
	smb_stat.c	\
	t38_analysis.c  \
	tcp_graph.c	\
	voip_calls_dlg.c \
	wsp_stat.c