Ethereal-dev: [Ethereal-dev] A first cut at capturing from multiple devices

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

From: Ashok Narayanan <ashokn@xxxxxxxxx>
Date: Thu, 1 Aug 2002 11:59:10 -0400
Folks,

The other day I needed to capture from multiple devices, so I wrote up a quick program to do this. It's a very simple program which basically starts multiple instances of tethereal (one for each interface), and either merges the text output of the programs or merges the capture files (using mergecap). There is nothing special about preventing packet misordering from different interfaces because of scheduling, buffering etc. However, I didn't see any such effects, and I was debugging a signalling protocol going through four routers (and therefore appearing on three interfaces in quick succession).

I've attached the source (multicap.c) for comments and testing. It doesn't require anything special to compile. You need to have executables of tethereal and mergecap available (in the PATH; otherwise you can explicitly specify paths to the executables in question). Before I commit it, I wanted to get some feedback:

1) Is it useful? I have seen multiple requests for this in the past, so perhaps it is.

2) It's is very UNIX-like; using mkstemp() (because &#*$ GCC on Linux doesn't allow me to use anything else), fork(), exec() and other (hopefully POSIX) calls. I have only tested it on Linux; I would appreciate some feedback on testing.

3) It's not autoconf-friendly (yet).

Thanks
-Ashok




--- Asok the Intern ----------------------------------------
Ashok Narayanan
IOS Network Protocols, Cisco Systems
250 Apollo Drive, Chelmsford, MA 01824
Ph: 978-497-8387.  Fax: 978-497-8513 (Attn: Ashok Narayanan)

/*******************************************************************************
 *
 * multicap.c
 *
 * Multi-device capture mechanism
 *
 * $Id$
 *
 * (c) 2002 Ashok Narayanan <ashokn@xxxxxxxxx>
 *
 * 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.
 */

/*
 * NOTES
 *
 * This program allows the user to capture from multiple devices on the 
 * computer, by running multiple instances of tethereal.
 *
 * IMPORTANT: This is a quick hack. There is no guarantee of the
 * ordering of packets. OS scheduling, packet buffering, and other
 * computer events may cause packets sent closely together in time on
 * different interfaces to be misordered.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <stdlib.h>
#include <string.h>

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

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#ifdef NEED_SNPRINTF_H
# include "snprintf.h"
#endif

#include <errno.h>
#include <stdarg.h>
#include <signal.h>
#include <sys/wait.h>

/*
 * Capture options 
 */
typedef struct {
    char *device;
    char *write_to_file;


} capture_opt_t;

/*
 * Info about a single child process
 */
typedef struct {
    
    /* Options for capture */
    char *device;
    char *output_file;

    /* Child info */
    int    pid;
    int    child_pipe;
    FILE  *child_pipe_file;

} child_info_t;

/* 
 * Merged output file name
 */
char *output_file = NULL;

/*
 * Table of child processes 
 */
#define MAX_CHILDREN 8
static child_info_t   child[MAX_CHILDREN];
static int            num_children = 0;
static int            running_children = 0;

/*
 * Devices to use
 */
int num_devices = 0;
char *devices[MAX_CHILDREN];

/*
 * Extra options to pass to child processes 
 */
int   tethereal_opts_num = 0;
char **tethereal_opts;

/*
 * Child process to run
 */
#define DFL_TETHEREAL_CMD "tethereal"
char *tethereal_cmd = DFL_TETHEREAL_CMD;

#define DFL_MERGECAP_CMD "mergecap"
char *mergecap_cmd = DFL_MERGECAP_CMD;

#define CHILD_DID_NOT_RUN    56
#define CHILD_DID_NOT_RUN_STR "#**#**#**#**FAILED"
static void unlink_all_temp_files(void);
static void finalize_and_exit(void);

int delete_tempfiles = 1;

/*------------------------------------------------------------------------------
 * Simple error routine - bail out immediately
 */
static void fail (char *fmt, ...)
{
    va_list argp;

    va_start(argp, fmt);
    vfprintf(stderr, fmt, argp);
    va_end(argp);
    unlink_all_temp_files();
    abort();
}

/*------------------------------------------------------------------------------
 * Remove all temp files created during sniffing 
 */
static void unlink_all_temp_files (void)
{
    int i;

    if (output_file && delete_tempfiles) {
	printf("Removing temporary files\n");
	for (i=0; i<num_children; i++) 
	    if (child[i].output_file) 
		unlink(child[i].output_file);
    }
}

/*------------------------------------------------------------------------------
 * Get a temporary file name. 
 */
static char *
get_temp_filename (void)
{
    char *str;
#define TEMPFILE_TEMPLATE "multicap.tmp.XXXXXX"

    str = malloc(strlen(TEMPFILE_TEMPLATE));
    if (!str) return NULL;
    strcpy(str, TEMPFILE_TEMPLATE);

    if (mkstemp(str) < 0) 
	fail("Could not create temporary file %s: %s\n", str, strerror(errno));

    return str;
}

/*------------------------------------------------------------------------------
 * Output a line of text as if it came from a particular child
 */
static void output_index(int index, char *fmt, ...)
{
    va_list argp;

    printf("%d-> ", index+1);
    va_start(argp, fmt);
    vprintf(fmt, argp);
    va_end(argp);
}

/*------------------------------------------------------------------------------
 * Run a single instance of tethereal as a child.
 * This function returns immediately after filling in entries
 */
static void
start_single_tethereal (int index)
{
    int  pfd[2];
    int retval;
#define MAX_OPTS 24    
    char *cmd_opts[MAX_OPTS];
    int num_opts;
    int i;

    if (index >= num_children || 
	!child[index].device) 
	fail("Bad call to start_single_tethereal(): index: %d\n", index);

    /* Start the child process */
    if (pipe(pfd) < 0)
	fail("Pipe creation failed: %s\n", strerror(errno));
    retval = fork();
    if (retval < 0)
	fail("fork() failed: %s\n", strerror(errno));
    else if (retval == 0 ) {
	/* Child process */
	
	/* Close standard input */
	close(0);
	
	/* Dup standard out and standard err to parent */
	dup2(pfd[1], 1);
	dup2(pfd[1], 2);

	/* Build the options */
	num_opts = 0;
	cmd_opts[num_opts++] = tethereal_cmd;   /* Command */
	cmd_opts[num_opts++] = "-l";            /* Flush output */
	cmd_opts[num_opts++] = "-ta";           /* Absolute timestamps */

	/* Capture device */
	cmd_opts[num_opts++] = "-i";
	cmd_opts[num_opts++] = child[index].device; 

	/* Output to file */
	if (child[index].output_file) {
	    cmd_opts[num_opts++] = "-w";
	    cmd_opts[num_opts++] = child[index].output_file; 
	}

	/* Add any further tethereal options */
	for (i=0; i<tethereal_opts_num; i++)
	    cmd_opts[num_opts++] = tethereal_opts[i];

	/* Terminator */
	cmd_opts[num_opts++] = NULL;            /* Terminator */

	/* Start the sniffer */
	if (execv(tethereal_cmd, cmd_opts) < 0) {
	    printf("%s execl() failed: %s\n", CHILD_DID_NOT_RUN_STR, strerror(errno));
	    close(1);
	    close(2);
	    close(pfd[1]);
	    exit(CHILD_DID_NOT_RUN);
	}
    } 

    /* Parent process */
    child[index].pid = retval;
    child[index].child_pipe = pfd[0];
    child[index].child_pipe_file = fdopen(child[index].child_pipe, "r");
    running_children ++;
}

/*------------------------------------------------------------------------------
 * Watch the output of all the child processes and display them
 */
static void
merge_display_output (void)
{
    int i,max_fd, status;
    fd_set readfds, writefds, exceptfds;
#define READBUF_SIZE 256
    char readbuf[READBUF_SIZE];
    char *c;

    /* Do this forever */
    for (;;) {

	/* Build the FD sets */
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
	max_fd = 0;
	for (i=0; i<num_children; i++) {
	    if (child[i].child_pipe != -1) {
		FD_SET(child[i].child_pipe, &readfds);
		FD_SET(child[i].child_pipe, &exceptfds);
		if (max_fd < child[i].child_pipe)
		    max_fd = child[i].child_pipe;
	    }
	}

	/* Select */
	if (select(max_fd + 1, &readfds, &writefds, &exceptfds, NULL) < 0) 
	    fail("select failed %d: %s", i, strerror(errno));

	for (i=0; i<num_children; i++) {
	    /* Any exceptions? */
	    if (FD_ISSET(child[i].child_pipe, &exceptfds)) {
		/* Close this child up */
	    child_died:
		waitpid(child[i].pid, &status, 0);
		child[i].child_pipe = -1;
		if (child[i].output_file) {
		    unlink(child[i].output_file);
		    child[i].output_file = NULL;
		}
		running_children --;
		if (running_children == 0) {
		    /* We're done */
		    finalize_and_exit();
		}
		/* Don't take input from this FD */
		continue;
	    }

	    /* Any input? */
	    if (FD_ISSET(child[i].child_pipe, &readfds)) {
		c = fgets(readbuf, READBUF_SIZE, child[i].child_pipe_file);
		readbuf[READBUF_SIZE-1] = 0;
		if (!strncmp(readbuf, CHILD_DID_NOT_RUN_STR, strlen(CHILD_DID_NOT_RUN_STR))) {
		    output_index(i, "ERROR: Child failed to run: %s", c + strlen(CHILD_DID_NOT_RUN_STR));
		    goto child_died;
		}
		if (c)
		    output_index(i, c);
	    }
	}
    }
}

/*------------------------------------------------------------------------------
 * Finalize and exit
 */
static void finalize_and_exit (void)
{
    int i;
    int status;
#define CMDMAX 256
    char cmdstr[CMDMAX];
    int cmdlen = 0;
    int retval = 0;

    if (output_file) {
	cmdlen += snprintf(cmdstr + cmdlen, CMDMAX - cmdlen, "%s -w %s ", 
			   mergecap_cmd, output_file);
	if (cmdlen > CMDMAX) fail("finalize: mergecap string too big: %d: %s\n", cmdlen, cmdstr);
    }

    for (i=0; i<num_children; i++) {
	if (child[i].child_pipe != -1) {
	    kill(child[i].pid, SIGINT);
	    waitpid(child[i].pid, &status, 0);
	    child[i].child_pipe = -1;
	    running_children --;
	}
	if (output_file) {
	    cmdlen += snprintf(cmdstr + cmdlen, CMDMAX - cmdlen, "%s ", 
			       child[i].output_file);
	    if (cmdlen > CMDMAX) fail("finalize: mergecap string too big: %d: %s\n", cmdlen, cmdstr);
	}
    }

    if (running_children != 0) {
	/* Some bug? */
	printf("BUG? Running Children is %d\n", running_children);
    }

    if (output_file) {
	printf("Running [%s]\n", cmdstr);
	system(cmdstr);
    }

 done:
    unlink_all_temp_files();

    exit(retval);
#undef CMDMAX
}

/*------------------------------------------------------------------------------
 * Signal handler 
 */
void sighandler(int sig)
{
    printf("Caught signal %d\n", sig);
    finalize_and_exit();
}

/*------------------------------------------------------------------------------
 * Build the child table info from parameters
 */
void build_child_table(void)
{
    int i;

    memset(child, 0, sizeof(child));
    for (i=0; i<num_devices; i++) {
	child[i].device = devices[i];
	if (output_file) {
	    child[i].output_file = get_temp_filename();
	}
    }
    num_children = num_devices;
}

/*------------------------------------------------------------------------------
 * Parse options
 */
void parse_cli(int argc, char *argv[])
{
    int i;

    if (argc == 1)
	goto usage;

    for (i=1; i<argc; i++) {
	if (!strcmp(argv[i], "-i")) {
	    if (num_devices >= MAX_CHILDREN) 
		fail("Too many devices specified; limit is %d", MAX_CHILDREN);
	    devices[num_devices++] = argv[++i];
	    continue;
	}

	if (!strcmp(argv[i], "-w")) {
	    if (output_file) 
		fail("Cannot specify -w twice\n");
	    output_file = argv[++i];
	    continue;
	}

	if (!strcmp(argv[i], "-t")) {
	    tethereal_cmd = argv[++i];
	    continue;
	}

	if (!strcmp(argv[i], "-m")) {
	    mergecap_cmd = argv[++i];
	    continue;
	}

	if (!strcmp(argv[i], "-X")) {
	    delete_tempfiles = 0;
	    continue;
	}

	if (!strcmp(argv[i], "--")) {
	    ++i;
	    tethereal_opts_num = argc-i;
	    tethereal_opts = &argv[i];
	    break;
	}

	/* No valid option - is it '-h'? */
	if (strcmp(argv[i], "-h")) {
	    printf("ERROR: Unknown argument %s\n\n", argv[i]);
	}

	/* Usage */
    usage:
	printf("USAGE: %s -i <dev> [-i <dev> ...] [-w <file>] [options] \n\n", basename(argv[0]));
	printf("   -i <dev>             : Capture from this device. At least one is required\n");
	printf("   -w <file>            : Capture to this file\n");
	printf("   -X                   : Don't delete temporary files\n");
	printf("   -t <tethereal-cmd>   : Use this command to run Tethereal\n");
	printf("   -m <mergecap-cmd>    : Use this command to run Mergecap\n");
	printf("   -- <tethereal-opts>  : Any options after -- are passed directly to Tethereal\n"
	       "                          Don't put -i or -w after this\n");
	printf("\n");
	exit(1);
    }

    /* Check that we have what we need */
    if (num_devices == 0) {
	printf("Error: Must specify at least one capture device\n\n");
	goto usage;
    }
}

/*------------------------------------------------------------------------------
 * main
 */
int main(int argc, char *argv[])
{
    int i;
    
    signal(SIGINT, sighandler);
    signal(SIGHUP, sighandler);

    parse_cli(argc, argv);
    build_child_table();

    /* Print out our configuration */
    printf("%s\n", basename(argv[0]));
    if (output_file) printf(" Write captured packets to [%s]\n", output_file);
    if (tethereal_cmd != DFL_TETHEREAL_CMD) printf(" Run tethereal using [%s]\n", tethereal_cmd);
    if (mergecap_cmd != DFL_MERGECAP_CMD) printf(" Run mergecap using [%s]\n", tethereal_cmd);
    if (!delete_tempfiles) printf(" Do not delete temporary files\n");
    if (tethereal_opts_num > 0) {
	printf(" Extra tethereal options: [");
	for (i=0; i<tethereal_opts_num; i++) printf("%s ", tethereal_opts[i]);
	printf("]\n");
    }
    printf(" Input sources:\n");
    printf("   No     Device      %s\n", delete_tempfiles ? "" : "Temp Filename");
    for (i=0; i<num_devices; i++) {
	printf("   %d     %06s       %s\n", i+1, child[i].device, 
	       delete_tempfiles ? "" : child[i].output_file);	
    }
    printf("\n");

    for (i=0; i<num_children; i++) 
	start_single_tethereal(i);

    merge_display_output();

}