/*
 * Copyright (c) 2004-2005 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagtuntap.c 13924 2011-02-10 01:51:27Z sfd $
 */

/*****************************************************************************/
/* Includes                                                                  */
/*****************************************************************************/

/* Endace headers. */
#include "dag_config_api.h"

#include "dagapi.h"
#include "dagutil.h"
#include "dagclarg.h"

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagtuntap.c 13924 2011-02-10 01:51:27Z sfd $";
static const char* const kRevisionString = "$Revision: 13924 $";


/*****************************************************************************/
/* Macros and constants                                                      */
/*****************************************************************************/
#define DEFAULT_DEVICE "/dev/dag0"

#define MAC_BUFSIZE 30
#define MAC_PACKSIZE 7
#define NUM_PORTS 4
#define PORTS_BUFSIZE NUM_PORTS+1

/*****************************************************************************/
/* Data structures                                                           */
/*****************************************************************************/

typedef struct
{
	int tun_fd;
	int configured;
	char mac[MAC_PACKSIZE];
} port_t;

typedef struct
{
	int stream;
	int txstream;
	unsigned int tnum;
	unsigned int mtu;
	int mac_override:1;
	int fast:1;
	int daemon:1;
	int allports:1;
	int append_crc:1;
	int card_ports;
	port_t ports[NUM_PORTS];
	char device[DAGNAME_BUFSIZE]; /* Which DAG device to use              */
} conf_t;

typedef struct
{
	int dag_fd;
	int exit_now;
} globals_t;

/* Commandline argument codes. */
enum
{
	CLA_DEVICE,
	CLA_HELP,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_PORT,
	CLA_TNUM,
	CLA_MAC,
	CLA_TXSTREAM,
	CLA_MTU,
	CLA_FAST,
	CLA_DAEMON,
	CLA_ALLPORTS
};

/*****************************************************************************/
/* File-scope variables                                                      */
/*****************************************************************************/
static conf_t uConfiguration; /* Configuration options                 */
static pthread_t uRxThread;
static pthread_t uTxThread;
static char dagname[DAGNAME_BUFSIZE] = DEFAULT_DEVICE;
static globals_t uGlobals;

/*****************************************************************************/
/*** Functions                                                             ***/
/*****************************************************************************/
static char *packtomac(const char *buf);

static void
print_version(void)
{
	printf("dagflood (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}

/*****************************************************************************/
static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dagtuntap - Endace DAG tun/tap network interface daemon.\n");
	printf("Usage: dagtuntap [options]\n");
	dagclarg_display_usage(clarg, stdout);
}


/*****************************************************************************/
static void
init_options(void)
{
	memset (&uConfiguration, 0, sizeof(uConfiguration));

	strncpy(uConfiguration.device,dagname,DAGNAME_BUFSIZE);
	uConfiguration.txstream = 1;
	uConfiguration.mtu = 1500;
	uConfiguration.ports[0].configured = 1; /* default to Port A */
}

static void
print_configuration(void)
{
	int count;
	char tempstr[80];
	int len = 0;
	int tnum;

	dagutil_verbose("Configuration:\n");
	dagutil_verbose("Device: %s\n", uConfiguration.device);
	dagutil_verbose("Receive stream: %d\n", uConfiguration.stream);
	dagutil_verbose("Transmit stream: %d\n", uConfiguration.txstream);
	dagutil_verbose("MTU: %d\n", uConfiguration.mtu);
	dagutil_verbose("Fast Polling: %s\n", uConfiguration.fast?"On":"Off");
	dagutil_verbose("Daemon mode: %s\n", uConfiguration.daemon?"On":"Off");
	dagutil_verbose("Append CRC: %s\n", uConfiguration.append_crc?"On":"Off");

	tnum = uConfiguration.tnum;

	for (count = 0; count < uConfiguration.card_ports; count++) {
		len = 0;
		len += snprintf(tempstr + len, 80 - len, "Port %c: ", count + 'A');
		if (uConfiguration.ports[count].configured) {
			len += snprintf(tempstr + len, 80 - len, "tap%d MAC %s",
					tnum, packtomac(uConfiguration.ports[count].mac));
			tnum++;
		} else {
			len += snprintf(tempstr + len, 80 - len, "Not enabled");
		}
		len += snprintf(tempstr + len, 80 - len, "\n");
		dagutil_verbose("%s", tempstr);
	}
}

/*****************************************************************************/
static char *
mactopack(const char *buf) {
	static char macstr[MAC_PACKSIZE];
	unsigned int a,b,c,d,e,f;

	if(!buf)
		return NULL;

	sscanf(buf, "%x:%x:%x:%x:%x:%x", &a, &b, &c, &d, &e, &f);
	macstr[0] = (unsigned char)(a&0xff);
	macstr[1] = (unsigned char)(b&0xff);
	macstr[2] = (unsigned char)(c&0xff);
	macstr[3] = (unsigned char)(d&0xff);
	macstr[4] = (unsigned char)(e&0xff);
	macstr[5] = (unsigned char)(f&0xff);
	macstr[6] = '\0';
	return macstr;
}

/*****************************************************************************/
static char *
packtomac(const char *buf) {
	static char mac_buf[MAC_BUFSIZE];

	if(!buf)
		return NULL;

	snprintf(mac_buf, MAC_BUFSIZE, "%02x:%02x:%02x:%02x:%02x:%02x",
		 buf[0]&0xff, buf[1]&0xff, buf[2]&0xff,
		 buf[3]&0xff, buf[4]&0xff, buf[5]&0xff);

	return mac_buf;
}

/*****************************************************************************/
int
macpackincrement(char *buf) {
	int count, carry=1;

	if(!buf)
		return -1;

	for(count=5; count>-1; count--) {
		if (carry) {
			buf[count]++;
			if (buf[count] == 0)
				carry = 1;
			else
				carry = 0;
		}
	}

	return 0;
}

/*****************************************************************************/
int
parse_commandline(int argc, char *argv[])
{
	FILE* errorfile = NULL;
	ClArgPtr clarg = NULL;
	int result;
	int argindex;
	int code;
	char port;
	char ports[PORTS_BUFSIZE];
	unsigned int tnum;
	unsigned int txstream;
	unsigned int mtu;
	char mac_buf[MAC_BUFSIZE];
	int count;

	init_options();

	/* Set up the command line options. */
	clarg = dagclarg_init(argc, (const char* const *) argv);

	dagclarg_add(clarg, "display help (this page)", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	dagclarg_add(clarg, "display version information", "--version", 'V', CLA_VERSION);
	dagclarg_add(clarg, "increase verbosity", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add_string(clarg, "DAG device to use.  Default: dag0.", "--device", 'd', "device", dagname, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add_string(clarg, "Physical ports on Card to use. Default: A. Specify multiple ports as ABCD", "--ports", 'p', "ports", ports, PORTS_BUFSIZE, CLA_PORT);
	dagclarg_add(clarg, "Use all available physical ports (equivalent to -p ABCD on 4 port card)\n", "--allports", 'c', CLA_ALLPORTS);
	dagclarg_add_uint(clarg, "Tx stream buffer. Default: 1.", "--txstream", 's', "stream", &txstream, CLA_TXSTREAM);

	dagclarg_add_uint(clarg, "TUN/TAP device number. Default: 0.", "--tnum", 't', "num", &tnum, CLA_TNUM);
	dagclarg_add_string(clarg, "Override MAC Addresses, incrementing from Port A", "--mac", 'a', "address", mac_buf, MAC_BUFSIZE, CLA_MAC);
	dagclarg_add_uint(clarg, "Interface MTU. Default: 1500.", "--mtu", 'm', "MTU", &mtu, CLA_MTU);
	dagclarg_add(clarg, "Use fast polling. Low latency but high CPU utilisation", "--fastpoll", 'f', CLA_FAST);
	dagclarg_add(clarg, "Fork into background, run as daemon.", "--daemon", 'b', CLA_DAEMON);
	

	/* Parse the command line options. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch (code)
		{
		case CLA_DEVICE:
			if (-1 == dag_parse_name(dagname, uConfiguration.device, DAGNAME_BUFSIZE, &uConfiguration.stream))
			{
				dagutil_error("dag_parse_name(%s): %s\n", dagname, strerror(errno));
				return EXIT_FAILURE;
			}
			dagutil_verbose_level(2, "device=%s:%d\n", uConfiguration.device, uConfiguration.stream);
			
			break;

		case CLA_PORT:
			uConfiguration.ports[0].configured = 0; /* clear default config */
			/* parse option string one char at a time */
			for (count = 0; count < strlen(ports); count++) {
				if (count > NUM_PORTS) break;
				port = tolower(ports[count]);
				if( (port < 0) || (port - 'a' > NUM_PORTS) ) {
					dagutil_panic("invalid port: %c\n", port);
				}
				uConfiguration.ports[(port - 'a')].configured = 1;
				dagutil_verbose_level(2, "enabling port=%c\n", port -'a' + 'A');
			}
			break;

		case CLA_ALLPORTS:
			uConfiguration.allports = 1;
			break;
		case CLA_TNUM:
			uConfiguration.tnum = tnum;
			dagutil_verbose_level(2, "tun/tap number=%d\n", uConfiguration.tnum);
			break;

		case CLA_MAC:
			memcpy(uConfiguration.ports[0].mac, mactopack(mac_buf), MAC_PACKSIZE);
			uConfiguration.mac_override = 1;
			dagutil_verbose_level(2, "MAC address=%s\n", packtomac(uConfiguration.ports[0].mac));
			break;

		case CLA_TXSTREAM:
			if (!(txstream%2)) {
				dagutil_error("Invalid txstream %d. tx streams are always odd numbers\n", txstream);
				return EXIT_FAILURE;
			}
			uConfiguration.txstream = txstream;
			dagutil_verbose_level(2, "tx stream=%d\n", uConfiguration.txstream);
			break;

		case CLA_MTU:
			uConfiguration.mtu = mtu;
			break;

		case CLA_FAST:
			uConfiguration.fast = 1;
			break;

		case CLA_DAEMON:
			uConfiguration.daemon = 1;
			break;

		case CLA_HELP:
			print_usage(clarg);
			exit(EXIT_SUCCESS);
			break;

		case CLA_VERBOSE:
			dagutil_inc_verbosity();
			errorfile = stderr;
			break;

		case CLA_VERSION:
			print_version();
			exit(EXIT_SUCCESS);
			break;

		default:
			/* Unknown option. */
				dagutil_error("unknown option %s\n", argv[argindex]); 
				print_usage(clarg);
				return EXIT_FAILURE;
		}

		result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	if (-1 == result)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}
	
	return EXIT_SUCCESS;
}


/*****************************************************************************/
int get_port_count(dag_card_ref_t card_ref, int *count)
{
	dag_component_t root;
	
	root = dag_config_get_root_component(card_ref);

	*count = dag_component_get_subcomponent_count_of_type(root, kComponentPort);

	return 0;
}


/*****************************************************************************/
const char *get_mac_address(dag_card_ref_t card_ref, int portnum)
{
	attr_uuid_t attr;
	int temp;
	const char *result = NULL;
	
	temp = dagutil_get_verbosity();
	dagutil_set_verbosity(0);

	attr = dag_config_get_indexed_named_attribute_uuid(card_ref, "mac_address", 0);
	if(attr == kNullAttributeUuid)
		goto exit;

	result = dag_config_get_string_attribute(card_ref, attr);

	if (strncmp("00:00:00:00:00:00", result, 17) == 0)
		result = NULL;

exit:
	dagutil_set_verbosity(temp);
	return result;
}


/*****************************************************************************/
int
set_crc_strip(dag_card_ref_t card_ref, int strip)
{
	dag_component_t root;
	dag_component_t terf;
	attr_uuid_t attr;
	uint32_t value;

	if (strip) {
		value = 3;
		dagutil_verbose_level(2, "Setting terf_strip32 on %s\n", uConfiguration.device);
	} else {
		value = 1;
		dagutil_verbose_level(2, "Setting noterf_strip on %s\n", uConfiguration.device);
	}

	root = dag_config_get_root_component(card_ref);
	if (dag_component_get_subcomponent_count_of_type(root, kComponentTerf))
		terf = dag_component_get_subcomponent(root, kComponentTerf, 0);
/* Use the same component kComponentTerf as kComponentTrTerf is not used anymore*/
/*
	else if (dag_component_get_subcomponent_count_of_type(root, kComponentTrTerf))
		terf = dag_component_get_subcomponent(root, kComponentTrTerf, 0);
*/
	else {
		dagutil_warning("terf/trterf not supported by this firmware\n");
		return EXIT_FAILURE;
	}

	attr = dag_component_get_attribute_uuid(terf, kUint32AttributeTerfStripCrc);
	if ( attr )
		dag_config_set_uint32_attribute(card_ref, attr, value);

	return EXIT_SUCCESS;
}


/*****************************************************************************/
void
set_align64(dag_card_ref_t card_ref)
{
	dag_component_t root;
	dag_component_t gpp;
	attr_uuid_t attr;
	
	root = dag_config_get_root_component(card_ref);
	if (dag_component_get_subcomponent_count_of_type(root, kComponentGpp))
		gpp = dag_component_get_subcomponent(root, kComponentGpp, 0);
	else
		return;

	attr = dag_component_get_attribute_uuid(gpp, kBooleanAttributeAlign64);

	if(attr != kNullAttributeUuid)
		dag_config_set_uint32_attribute(card_ref, attr, 1);
	
}


/*****************************************************************************/
int
set_snaplen(dag_card_ref_t card_ref)
{
	dag_component_t root;
	dag_component_t gpp;
	attr_uuid_t attr;
	uint32_t value;

	value = uConfiguration.mtu + 18 + 2 + 2; /* MAC + optional vlan tag + offset/pad bytes */
	if(value%8)
		value += 8-(value%8);

	root = dag_config_get_root_component(card_ref);
	if (dag_component_get_subcomponent_count_of_type(root, kComponentGpp))
		gpp = dag_component_get_subcomponent(root, kComponentGpp, 0);
	else if (dag_component_get_subcomponent_count_of_type(root, kComponentSRGPP))
		gpp = dag_component_get_subcomponent(root, kComponentSRGPP, 0);
	else
		return EXIT_FAILURE;

	attr = dag_component_get_attribute_uuid(gpp, kUint32AttributeSnaplength);
	dag_config_set_uint32_attribute(card_ref, attr, value);

	return EXIT_SUCCESS;
}


/*****************************************************************************/
static void*
transmit_thread(void *context)
{
	dag_record_t hdr;
	unsigned char *erf_p = NULL;
	unsigned char *payload_p = NULL;
	int l = 1;
	unsigned short rlen, wlen;
	int count;
	fd_set fds;
	int max_fd;
	struct timeval tv;
	int dag_fd;

	dag_fd = uGlobals.dag_fd;

	memset(&hdr, 0, sizeof(hdr));

	hdr.type = ERF_TYPE_ETH;
	hdr.flags.vlen = 1;
	
	/* Reserve tx buffer memory for first packet and initialize pointers */
	if ( (erf_p = dag_tx_get_stream_space(dag_fd, uConfiguration.txstream, 64*1024)) == NULL ) {
		uGlobals.exit_now = 1;
		dagutil_error("dag_tx_get_stream_space %d: %s\n", 
			      dag_record_size,
			      strerror(errno));
		pthread_exit(NULL);
	}
	
	payload_p = erf_p + dag_record_size + 2 - 4;

	while(!uGlobals.exit_now){
		FD_ZERO(&fds);
		max_fd = 0;
		for (count = 0; count < uConfiguration.card_ports; count++) {
			if (uConfiguration.ports[count].configured) {
				FD_SET(uConfiguration.ports[count].tun_fd, &fds);
				if (uConfiguration.ports[count].tun_fd > max_fd)
					max_fd = uConfiguration.ports[count].tun_fd;
			}
		}
		tv.tv_sec=0;
		tv.tv_usec=10000;

		select(max_fd+1, &fds, NULL, NULL, &tv);
		
		for (count = 0; count < uConfiguration.card_ports; count++) {
			if ( (uConfiguration.ports[count].configured) && (FD_ISSET(uConfiguration.ports[count].tun_fd, &fds)) ) {
				/* see if packet is there, read tun flags+proto to bitbucket */
				if ((l = read(uConfiguration.ports[count].tun_fd, payload_p, 64*1024-dag_record_size+2)) > 0) {
					//printf("got pkt %d\n", l);
				
					/* copy template ERF header into temp buffer */
					memcpy(erf_p, &hdr, dag_record_size);
				
					/* Set tx interface */
					((dag_record_t*)erf_p)->flags.iface = count;

					/* Some DAG cards (4.3GE) will drop short packets on tx.
					 * The ERF record on these cards needs to have a minimum
					 * wlen of 60 with noterf_strip.
					 * l is the TUN packet size, and includes a 4 byte header,
					 * so l must be increased to a minimum of 64 to produce a
					 * wlen of at least 60.
					 */
					if(l < 64)
						l = 64;
				
					/* subtract 4 byte TUN header */
					wlen = l - 4;
				
					/* set wlen and rlen in packet ERF header */
					rlen = l + dag_record_size + 2 - 4;
					
					if(uConfiguration.append_crc) {
						/* setting nocrc_strip failed, so pad record with incorrect crc! */
						rlen += 4;
						wlen += 4;
					}
					
					if(rlen%8)
						rlen += 8-(rlen%8);
				
					((dag_record_t*)erf_p)->rlen = htons(rlen);
					((dag_record_t*)erf_p)->wlen = htons(wlen);
				
					/* debug */
					dagutil_verbose_level(2, "tx packet rlen %d wlen %d port %d\n", rlen, wlen, count);
				
					/* tell card to send packet */
					dag_tx_stream_commit_bytes(dag_fd, uConfiguration.txstream, rlen);
				} else if( l < 0 && (errno != EAGAIN && errno != EINTR) ) {
					uGlobals.exit_now = 1;
					dagutil_error("read: %s\n", strerror(errno));
					pthread_exit(NULL);
				}

				/* Reserve tx buffer memory for next packet */
				if ( (erf_p = dag_tx_get_stream_space(dag_fd, uConfiguration.txstream, 64*1024)) == NULL ) {
					uGlobals.exit_now = 1;
					dagutil_error("dag_tx_get_stream_space %d: %s\n", 
						      dag_record_size,
						      strerror(errno));
					pthread_exit(NULL);
				}
			
				payload_p = erf_p + dag_record_size + 2 - 4;
			}
		}
		
	}
	return NULL;
}


/*****************************************************************************/
static void*
receive_thread(void* context)
{
	dag_record_t *erf;
	uint8_t *record;
	int written;
	int dag_fd;
	unsigned short wlen;

	dag_fd = uGlobals.dag_fd;

	while(!uGlobals.exit_now) {
		if ( (record = dag_rx_stream_next_record(dag_fd, uConfiguration.stream)) == NULL ) {
			if ( errno == EAGAIN ) {
				if (uConfiguration.fast)
					dagutil_microsleep(10);
				continue;
			} else {
				uGlobals.exit_now = 1;
				dagutil_error("dag_rx_stream_next_record: %s", strerror(errno));
				pthread_exit(NULL);
			}
		}
		erf = (dag_record_t*)record;
		dagutil_verbose_level(2, "rx record rlen %d wlen %d port %d\n",
				      ntohs(erf->rlen), ntohs(erf->wlen), erf->flags.iface);
		if (erf->flags.rxerror)
			continue;
		if (!uConfiguration.ports[erf->flags.iface].configured)
			continue;
		if ( (erf->type != ERF_TYPE_ETH) && (erf->type != ERF_TYPE_COLOR_ETH) && (erf->type != ERF_TYPE_DSM_COLOR_ETH) )
			continue;

		wlen = ntohs(erf->wlen);
		memset(record+14, 0, 2);
		memcpy(record+16, record+31, 2);

		if ( (written = write(uConfiguration.ports[erf->flags.iface].tun_fd, record+14, wlen+4)) != wlen+4 ) {
			uGlobals.exit_now = 1;
			dagutil_error("write %d got %d: %s",
				      wlen+4, written, strerror(errno));
			pthread_exit(NULL);
		}
	}
	return NULL;
}


static void
anysig(int sig)
{
	/* Restore the default signal handlers, so the next interrupt will have the default effect (e.g. exit) */
	dagutil_set_signal_handler(SIG_DFL);
	
	/* Tell the main loop to exit */
	uGlobals.exit_now = 1;
	return;
}

/*****************************************************************************/
int main(int argc, char *argv[])
{
	dag_card_ref_t card_ref = NULL;
	int ports;
	int s = -1;
	struct ifreq ifr;
	char buf[6];
	const char *mac;
	int result;
	uint32_t mindata;
	struct timeval maxwait, poll;
	int retval = EXIT_SUCCESS;
	int temp; 
	int count;

	dagutil_set_progname("dagtuntap");

	if ( (retval = parse_commandline(argc, argv)) != EXIT_SUCCESS)
		goto fail;

	if(uConfiguration.daemon) {
		dagutil_verbose("Forking into background\n");
		if (dagutil_get_verbosity() > 1)
			dagutil_set_verbosity(1);
		temp = daemon(0,0);
		dagutil_daemon(LOG_PID | LOG_CONS, LOG_DAEMON);
		dagutil_verbose_level(0, "Started\n");
	}

	temp = dagutil_get_verbosity();
	dagutil_set_verbosity(0);
	if ((card_ref = dag_config_init(uConfiguration.device)) == NULL) {
		dagutil_error("%s not available: %s\n",
			      uConfiguration.device,
			      strerror(errno));
		goto fail_dag;
	}
	dagutil_set_verbosity(temp);

	uGlobals.dag_fd = dag_config_get_card_fd(card_ref);

	get_port_count(card_ref, &ports);
	uConfiguration.card_ports = ports;

	/* For allports option enable all available ports. */
	if (uConfiguration.allports) {
		for (count = 0; count < ports; count++)
			uConfiguration.ports[count].configured = 1;
	}

	/* Check if user requested an interface that is not present */
	for (count = ports; count < NUM_PORTS; count++) {
		if (uConfiguration.ports[count].configured) {
			dagutil_error("Port %c not in range A-%c\n",
				      count + 'A',
				      ports + 'A' - 1);
			goto fail_dag;
		}
	}

	if (!uConfiguration.mac_override) {
		dagutil_verbose_level(2, "MAC not supplied, reading card MAC\n");
		for (count = 0; count < ports; count++) {
		mac = get_mac_address(card_ref, count);
		if (mac) {
			memcpy(uConfiguration.ports[count].mac, mactopack(mac), MAC_PACKSIZE);
			uConfiguration.mac_override = 1;
		} else
			dagutil_verbose("DAG card has no MAC Address assigned to port %c and user did not supply MAC (with -m). Using randomly generated MAC\n", count + 'A');
		}
	} else {
		/* user specified MAC for Port A, increment across to all ports */
		for (count = 1; count < ports; count++) {
			memcpy(uConfiguration.ports[count].mac, uConfiguration.ports[count-1].mac, MAC_PACKSIZE);
			macpackincrement(uConfiguration.ports[count].mac);
		}		
	}

	/* Tell the Terf/TrTerf NOT to strip off 32-bit of CRC on transmit,
	 * because we are not supplying a CRC
	 */
	if (set_crc_strip(card_ref, 0))
		uConfiguration.append_crc = 1;
		
	/* Print configuration details */
	print_configuration();

	/* Set align64. Not strictly necessary since ERF frames not forwarded?
	 */
	//set_align64(card_ref);

	/* Set snap length */
	if ( (retval = set_snaplen(card_ref)) != EXIT_SUCCESS ) {
		dagutil_warning("set_snaplen: gpp not supported by this firmware, snaplen unchanged\n");
	}

	/* Open tun device(s)
	 */
	for (count = 0; count < ports; count++) {
		if (uConfiguration.ports[count].configured) {
			snprintf(buf, 6, "tap%d", uConfiguration.tnum + count);
			if ((uConfiguration.ports[count].tun_fd = open("/dev/net/tun", O_RDWR)) <0) {
				dagutil_error("open /dev/net/tun: %s\n", strerror(errno));
				retval = EXIT_FAILURE;
				goto fail_dag;
			}

			memset(&ifr, 0, sizeof(ifr));

			/* Flags: IFF_TUN   - TUN device (no Ethernet headers)
			 *        IFF_TAP   - TAP device
			 *
			 *        IFF_NO_PI - Do not provide packet information
			 */
			ifr.ifr_flags = IFF_TAP;
	
			strncpy(ifr.ifr_name, buf, dagutil_min(6,IFNAMSIZ));

			if( ioctl(uConfiguration.ports[count].tun_fd, TUNSETIFF, (void *) &ifr) < 0 ){
				retval = EXIT_FAILURE;
				dagutil_error("TUNSETIFF: %s\n", strerror(errno));
				goto fail_tun;
			}
	
			/* OS does not need to compute Ethernet checksum on received frames */
			if( ioctl(uConfiguration.ports[count].tun_fd, TUNSETNOCSUM, 1) < 0 ){
				retval = EXIT_FAILURE;
				dagutil_error("TUNSETNOCSUM: %s\n", strerror(errno));
				goto fail_tun;
			}

			if (uConfiguration.mac_override) {
				dagutil_verbose_level(2, "Setting MAC Address for Port %c : %s\n", ('A' + count),
						packtomac(uConfiguration.ports[count].mac));
	
				memcpy(ifr.ifr_hwaddr.sa_data, uConfiguration.ports[count].mac, MAC_PACKSIZE);
				ifr.ifr_hwaddr.sa_family = AF_LOCAL;

				/* open socket */
				if( (s = socket(AF_INET, SOCK_DGRAM, 0)) == -1 ) {
					retval = EXIT_FAILURE;
					dagutil_error("Open socket: %s\n", strerror(errno));
					goto fail_tun;
				}

				/* set mac */
				memcpy(ifr.ifr_hwaddr.sa_data, uConfiguration.ports[count].mac, MAC_PACKSIZE);
				if( ioctl(s, SIOCSIFHWADDR, &ifr) < 0 ){
					retval = EXIT_FAILURE;
					dagutil_error("SIOCSIFHWADDR: %s\n", strerror(errno));
					close(s);
					goto fail_tun;
				}
				close(s);
				s = -1;
			}
		}
	}

	/* attach receive stream */
	if (dag_attach_stream(uGlobals.dag_fd, uConfiguration.stream, 0, 0)) {
		retval = EXIT_FAILURE;
		dagutil_error("dag_attach_stream %d: %s\n",
			      uConfiguration.stream,
			      strerror(errno));
		goto fail_tun;
	}

	/* attach transmit stream */
	if (dag_attach_stream(uGlobals.dag_fd, uConfiguration.txstream, 0, 0)) {
		retval = EXIT_FAILURE;
		dagutil_error("dag_attach_stream %d: %s\n",
			      uConfiguration.txstream,
			      strerror(errno));
		goto fail_rxstream;
	}
		
	/* Configure stream polling */
	maxwait.tv_sec = 0;
	maxwait.tv_usec = 10000;
	poll.tv_sec = 0;
	poll.tv_usec = 1000;
	mindata = dag_record_size;

	if (uConfiguration.fast) {
		maxwait.tv_sec = 0;
		maxwait.tv_usec = 0;
		mindata = 0;
	}

	dag_set_stream_poll(uGlobals.dag_fd, uConfiguration.stream, mindata, &maxwait, &poll);
	dag_set_stream_poll(uGlobals.dag_fd, uConfiguration.txstream, mindata, &maxwait, &poll);

	dagutil_set_signal_handler(anysig);

	/* Start rx stream */
	if (dag_start_stream(uGlobals.dag_fd, uConfiguration.stream)) {
		retval = EXIT_FAILURE;
		dagutil_error("dag_start_stream %d: %s\n",
			      uConfiguration.stream,
			      strerror(errno));
		goto fail_txstream;
	}

	/* Start tx stream */
	if (dag_start_stream(uGlobals.dag_fd, uConfiguration.txstream)) {
		retval = EXIT_FAILURE;
		dagutil_error("dag_start_stream %d: %s\n",
			      uConfiguration.txstream,
			      strerror(errno));
		goto fail_rxstop;
	}

	/* start RxThread */
	result = pthread_create(&uRxThread, NULL, receive_thread, NULL);
	if (0 != result)
	{
		retval = EXIT_FAILURE;
		dagutil_error("could not create receive thread: %s\n", strerror(result));
		goto fail_txstart;
	}

	/* start TxThread */
	result = pthread_create(&uTxThread, NULL, transmit_thread, NULL);
	if (0 != result)
	{
		retval = EXIT_FAILURE;
		dagutil_error("could not create transmit thread: %s\n", strerror(result));
		uGlobals.exit_now = 1;
		goto fail_rxthread;
	}

	/* Wait for exit conditions */
	pthread_join(uTxThread, NULL);
 fail_rxthread:
	pthread_join(uRxThread, NULL);

	/* shut down */
 fail_txstart:
	dag_stop_stream(uGlobals.dag_fd, uConfiguration.txstream);
 fail_rxstop:
	dag_stop_stream(uGlobals.dag_fd, uConfiguration.stream);
 fail_txstream:
	dag_detach_stream(uGlobals.dag_fd, uConfiguration.txstream);
 fail_rxstream:
	dag_detach_stream(uGlobals.dag_fd, uConfiguration.stream);
 fail_tun:
	for(count=0; count < ports; count++) {
		if(uConfiguration.ports[count].configured) {
			close(uConfiguration.ports[count].tun_fd);
			uConfiguration.ports[count].configured = 0;
		}
	}
 fail_dag:
	dag_config_dispose(card_ref);
 fail:
	dagutil_verbose_level(0, "Exiting\n");
	return retval;
}
