#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
/*
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <string.h>
#include <ctype.h>

#include <pcap.h>
*/
#include "defaults.h"

#ifndef NV
#define NV double
#endif

#ifndef newSVuv
SV*
newSVuv(U32 in)
{ 
  SV* out = newSViv(in);
  sv_setuv(out,in);
  return out;
}
#endif

#define minlen	ETH_HLEN + IP_HLEN + 4		/* need src/dst ports in packet for filtering	*/

const int nxtrateupd = 60, nxtupd = 300, oneday = 86400, onehour = 3600;

SV	** vpp;
AV	* dnsrequest;
HV	* stats;
double	rate, bw, ba;
u_int32_t	ra, now, next, start, nextp;
int * udp_tcp_hdr;

union buffer
{
	unsigned char e[PCAP_ERRBUF_SIZE+1];
	unsigned char s[512];
} out;

union naddr
{
	unsigned char	s[4];
	u_int32_t	host;
	struct in_addr	naddr;
} me, trgt;

/*	'dumptofile' is the flag indicating that this is/is not a dump to file/STDERR
	'signal_dump' is the flag indicating a dump was initiated by a signal
 */
int	dumptofile, signal_dump = 0, nleft, dnsRflag = 0, socklen = sizeof(struct sockaddr);
int	maxfd, max, run, hup, nfound, dump_head, nextrate;
int	dnsFD, lFD, wFD, pFD, dnsFDm = 0, lFDm = 0, wFDm = 0, pFDm = 0;
int	payoff = 0, pktlen = minlen;
size_t	paysize;

FILE	* WFD;

char	filepath[512], tmp[512], pbuf[512], * bptr;
u_char	haystack[65536];	/*	payload buffer	*/

union sock
{
struct sockaddr_in si;
struct sockaddr sa;
};

union sock listner, sender, dnsaddr;

pcap_t  * pcap;
bpf_u_int32 netp, maskp;

fd_set	rset, wset;

struct timeval tloop;

struct sigaction sa;
sigset_t set;

unsigned char * match = NULL, * nomatch = NULL;

void
debug_packet(unsigned char * str, int len)
{
  int i, c, d = 0, hex = 1;	/*	set hex = 1, alpha = 0	*/
  for (i=0;i<len;i++) {
    c = (int)*(str +i);
    if (hex != 0)
      fprintf(stderr,"%02X",c);
    else {
      if (isgraph(c) == 0)
        c = (int)('.');
      fprintf(stderr,"%c",(u_char)c);
    }
    d++;
    if (d >= 8) {	/*	set character segmentation here	*/
      d = 0;
      fprintf(stderr," ");
    }
  }
  fprintf(stderr,"\n");
}

int
dumpmore()
{
  int nwritten;

  if (nleft < 1)
    return 0;
  nwritten = write(wFD,bptr,nleft);
  if (nwritten <= 0) {
    if (errno == EINTR || errno == EWOULDBLOCK)
      nwritten = 0;
    else
      return 0;		/*	force flush if fatal error	*/
  }
  bptr += nwritten;
  nleft -= nwritten;
  if (nleft < 0)
    nleft = 0;
  return nleft;
}
   
int
my_dump(char * buf, int len)
{
  if (len < 1)
    return 0;
  strncpy(pbuf,buf,len);
  bptr 	= pbuf;
  nleft	= len;
  return dumpmore();
}

void
closeMOST()
{
  if (dnsFD != 0)
    close(dnsFD);
  if (lFD != 0)
    close(lFD);
  pcap_breakloop(pcap);
  dnsFD	= 0;
  lFD	= 0;
  pFD	= 0;
  maxfd	= 0;
}

void
q_handler (int sig)
{
  switch (sig) {
	case SIGUSR1	:
		if (hup != 0)
		  break;
		if (wFD == 0)
		  signal_dump = 1;	/*	ignore dump in process	*/
		break;
	case SIGHUP	:
		if (hup == 0);
		  hup	= 1;
		if (wFD == 0)
		  signal_dump = 1;	/*	ignore dump in process	*/
		break;
	case SIGINT	:
	case SIGQUIT	:
	case SIGTERM	: 
		hup	= -1;
		closeMOST();
		if (dumptofile) {
		  if (wFD == 0)
		    signal_dump = 1;
		}
		else
		  run = 0;
		break;
  }
}

void
set_signals (void)
{
  sa.sa_handler = q_handler;
  sigemptyset (&sa.sa_mask);
  sigaddset (&sa.sa_mask, SIGTERM);
  sigaddset (&sa.sa_mask, SIGHUP);
  sigaddset (&sa.sa_mask, SIGUSR1);
  sigaddset (&sa.sa_mask, SIGQUIT);
  sigaddset (&sa.sa_mask, SIGINT);
/*  sa.sa_flags = SA_RESTART;
*/
  sa.sa_flags = 0;
  sigaction (SIGINT, &sa, NULL);
  sigaction (SIGQUIT, &sa, NULL);
  sigaction (SIGUSR1, &sa, NULL);
  sigaction (SIGTERM, &sa, NULL);
  sigaction (SIGHUP, &sa, NULL);
}


u_int32_t
fetch_uv(HV * hp, char * key)
{
  u_int32_t val;
/*	perl likes to store ints as NV's so
	to preserve space we convert to UV's
 */
  vpp = hv_fetch(hp,key,1,0);
  if (SvIOK(*vpp))
    return SvUVX(*vpp);
  val = SvUV(*vpp);
  sv_setuv(*vpp,val);
  return val;
}

void
set_uv(HV * hp, char * key, u_int32_t val)
{
  vpp = hv_fetch(hp,key,1,1);
  sv_setuv(*vpp, val);
}

void
inc_sv(HV * hp, char * key)
{
  vpp = hv_fetch(hp,key,1,0);
  sv_inc(*vpp);
}

void
set_nv(HV * hp, char * key, double val)
{
  vpp = hv_fetch(hp,key,1,1);
  sv_setnv(*vpp, val);
}

void
add_nv(HV * hp, char * key, u_int32_t val)
{
  vpp = hv_fetch(hp,key,1,0);
  sv_setnv(*vpp, SvNVX(*vpp) + val);
}

/*	NV = (NV + UV) * NV	*/
void
aEQaPLUSbXm(HV * hp, char * key1, char * key2, double multiply)
{
  double tmpb;

  vpp = hv_fetch(hp,key2,1,0);
  if (key2[0] == 'B')
    tmpb = (double)SvNVX(*vpp);
  else if (SvIOK(*vpp))
    tmpb = SvUVX(*vpp);
  else
    tmpb = SvUV(*vpp);
  vpp = hv_fetch(hp,key1,1,0);
  sv_setnv(*vpp,((SvNVX(*vpp) + tmpb) * multiply));
}

void
init_hv(HV * dusr, int32_t len)
{
  hv_store(dusr,"B",1,newSVnv(len),0);
  hv_store(dusr,"C",1,newSVuv(1),0);
  hv_store(dusr,"E",1,newSVuv(now),0);
  hv_store(dusr,"N",1,newRV_noinc((SV *)newAV()),0);
  hv_store(dusr,"R",1,newSVnv(0),0);
  hv_store(dusr,"S",1,newSVuv(start),0);
  hv_store(dusr,"T",1,newSVuv(0),0);
  hv_store(dusr,"W",1,newSVnv(0),0);
}

void
sniffit(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
{
  SV	** vpp;
  HV	* dusr;
  AV	* hn;			/* hostnames	*/
  double	multiplier, tmpd;
  u_int32_t	e, s;
  int32_t	len	= h->len;
  u_char * pcaptr, * hay, * hayend;

  union naddr	ip_src, ip_dst;
  struct ether_header  * eth	= (struct ether_header *)bytes;
  struct iphdr * iph		= (struct iphdr *)(bytes + ETH_HLEN);
  udp_tcp_hdr			= (int *)(bytes + ETH_HLEN + IP_HLEN);

  if (	(iph->ihl*4 != IP_HLEN) ||		/* drop non-standard packets	*/
	(iph->frag_off & htons(IP_OFFMASK)) ||	/* drop fragments		*/
	(len < pktlen)	)			/* minimum packet len = minlen || snaplen	*/
    return;

  if (match != NULL || nomatch != NULL) {
    pcaptr = (u_char *)(bytes + payoff);
    hay = haystack;
    hayend = hay + paysize;
    while (hay < hayend) {
      *hay = (u_char) tolower((int) *pcaptr);
      hay++;
      pcaptr++;
    }
    *hayend = '\0';	/*	make sure there is a terminating null at end of char string	*/

/*	drop if match needed and not found	*/

    if (match != NULL && strstr((char *)haystack,(char *)match) == NULL) {
      return;
    }
/*	drop if no match needed and found	*/
    if (nomatch != NULL && strstr((char *)haystack,(char *)match) != NULL) {
      return;
    }
  }

  ip_src.host	= iph->saddr;
  ip_dst.host	= iph->daddr;

  if (me.host == ip_src.host)
    trgt.host	= ip_dst.host;
  else
    trgt.host	= ip_src.host;

/*	do global averaging first	*/
  if (next > now || (rate < 1 && ra < 5)) {
    ba += len;
    ra++;
  } else {
/* force compilier to boost to doubles for now, onehour	*/
    multiplier = onehour;
    tmpd = now;
    if (rate < 1) {
/*	tmpd = now - start		*/
      tmpd -= start;
      if (tmpd < 1)
	tmpd = 1;
    } else {
/*	tmpd = onehour + now + nextrate - next	---	where next was (now + nextrate) so it really is (now - old)	*/
      tmpd = multiplier + tmpd + nextrate - next;		/* first interval was 5 minutes	*/
    }
/*	multiplier = onehour / tmpd	*/
    multiplier /= tmpd;
    rate	= (rate + ra) * multiplier;
    bw		= (bw + ba) * multiplier;
    ba		= len;
    ra		= 1;
    next	= now + nxtrateupd;
    nextrate	= nxtrateupd;
  }

/*	collect user stats		*/
  if (hv_exists(stats,(char *)trgt.s,4)) {
    vpp		= hv_fetch(stats,(char *)trgt.s,4,0);
    dusr	= (HV *)SvRV(*vpp);

    if ((e = fetch_uv(dusr,"E")) + nxtupd > now ||
	(e == fetch_uv(dusr,"S") && fetch_uv(dusr,"C") < 5)) {
      add_nv(dusr,"B", len);
      inc_sv(dusr,"C");
    } else {
/* force compilier to boost to doubles for now, onehour	*/
      multiplier = onehour;
      tmpd = now;
      if ((s = fetch_uv(dusr,"S")) == e) {
/*	tmpd = now - start		*/
	tmpd -= s;
      } else {
/*	tmpd = onehour + now - old	*/
	tmpd = multiplier + now - e;
      }
      multiplier /= tmpd;
      aEQaPLUSbXm(dusr,"R","C",multiplier);
      aEQaPLUSbXm(dusr,"W","B",multiplier);
      set_uv(dusr,"E",now);
      set_nv(dusr,"B",(double)len);
      set_uv(dusr,"C",1);
    }
  } else {
    dusr	= newHV();
    init_hv(dusr,len);
    hv_store(stats,(char *)trgt.s,4,newRV_noinc((SV *)dusr),0);
  }
  if ((fetch_uv(dusr,"T")) < now) {
    dnsRflag = 1;
    av_push(dnsrequest,newSVpv((char *)trgt.s,4));
  }
}

MODULE = Net::Connection::Sniffer	PACKAGE = Net::Connection::Sniffer

PROTOTYPES: DISABLE

 # run, now, next, start 		I32
 # rate, bw				NV

 #	SVt_NULL,       /* 0 */
 #	SVt_IV,         /* 1 */
 #	SVt_NV,         /* 2 */
 #	SVt_RV,         /* 3 */
 #	SVt_PV,         /* 4 */
 #	SVt_PVIV,       /* 5 */
 #	SVt_PVNV,       /* 6 */
 #	SVt_PVMG,       /* 7 */
 #	SVt_PVBM,       /* 8 */
 #	SVt_PVLV,       /* 9 */
 #	SVt_PVAV,       /* 10 */
 #	SVt_PVHV,       /* 11 */
 #	SVt_PVCV,       /* 12 */
 #	SVt_PVGV,       /* 13 */
 #	SVt_PVFM,       /* 14 */
 #	SVt_PVIO        /* 15 */

void
p2xs_gvars(pnow, pstart, prate, pbw)
	SV	* pnow
	SV	* pstart
	SV	* prate
	SV	* pbw
  INIT:
	if (items != 4)
	  croak("Usage: Net::Connection::Sniffer::p2xs_gvars(pnow, pstart, prate, pbw)");

  CODE:
	now	= SvUV(pnow);
	nextp	= now;
	nextrate= 300;
	next	= now + nextrate;	/*	first increment is 5 minutes, following are different	*/
	start	= SvUV(pstart);
	rate	= SvNV(prate);
	bw	= SvNV(pbw);
	hup	= 0;
	ra	= 0;
	ba	= 0;

PROTOTYPES: ENABLE

void
xs2p_gvars()
  PPCODE:
	EXTEND(SP,4);
	PUSHs(sv_2mortal(newSVuv(now)));
	PUSHs(sv_2mortal(newSVuv(start)));
	PUSHs(sv_2mortal(newSVnv(rate)));
	PUSHs(sv_2mortal(newSVnv(bw)));
	XSRETURN(4);

void
xs_daemon_init(sniffer,hpref,dnsref,nhost,dnshost,port,listenon,bpfstr,dev,snaplen,promisc,to)
	SV	* sniffer
	SV	* hpref
	SV	* dnsref
	SV	* nhost
	SV	* dnshost
	int	port
	SV	* listenon
	char	* bpfstr
	char	* dev
	int	snaplen
	int	promisc
	int	to
    PREINIT:
	unsigned char * ip, * lip, * sniffp;
	STRLEN	len;
	char	errorbuf[PCAP_ERRBUF_SIZE+1];
	struct bpf_program real_fp;
    CODE:
	pktlen = snaplen;
	if (SvPOK(sniffer) == 0)
	  croak("sniffer is not a 'path' or 'STDERR'");

	if (! SvROK(hpref))
	  croak("stats is not a REF");
	if (SvTYPE(SvRV(hpref)) != SVt_PVHV)
	  croak("stats is not a hash REF");

	if (! SvROK(dnsref))
	  croak("dnslookup is not REF");
	if (SvTYPE(SvRV(dnsref)) != SVt_PVAV)
	  croak("dnslookup is not an array REF");

	if (SvPOK(nhost) == 0)
	  croak("nhost is not a netaddr");
	ip = (u_char *)SvPV(nhost,len);
	if (len != 4)
	  croak("nhost length of netaddr length is %d, should be 4", len);
	strncpy((char *)me.s,(char *)ip,4);

	if (SvPOK(dnshost) == 0)
	  croak("dnshost is not a netaddr");
	ip = (u_char *)SvPV(dnshost,len);
	if (len != 4)
	  croak("dnshost length of netaddr length is %d, should be 4", len);
	bzero(&dnsaddr.sa,socklen);
	dnsaddr.si.sin_family = PF_INET;
	dnsaddr.si.sin_addr.s_addr = *((unsigned long *)ip);
	dnsaddr.si.sin_port = htons(53);

	if ((dnsFD = socket(PF_INET,SOCK_DGRAM,0)) < 0)
	  croak("could not open name server socket");

	if(port != 0) {
	  if (SvPOK(listenon) == 0)
	    croak("listen is not a netaddr");
	  lip = (u_char *)SvPV(listenon,len);
	  if (len != 4)
	    croak("listen netaddr length is %d, should be 4", len);

	  if ((lFD = socket(PF_INET,SOCK_DGRAM,0)) < 0)
	    croak("could not open 'listen on' socket");
	  fcntl(lFD, F_SETFL, O_NONBLOCK);

	  bzero(&listner.sa,socklen);
	  listner.si.sin_family = PF_INET;
	  listner.si.sin_addr.s_addr = *((unsigned long *)lip);
	  listner.si.sin_port = htons(port);
	  if (bind(lFD,&listner.sa,socklen) < 0)
	    croak("could not bind 'listen on' to port %d",port);
	}

	sniffp = (u_char *)SvPV(sniffer,len);
	if (strncmp("STDERR",(char *)sniffp,6) == 0) {
	  fcntl(fileno(stderr), F_SETFL, O_NONBLOCK);
	  dumptofile = 0;
	  WFD = stderr;
	} else {
	  dumptofile = 1;
	  strncpy(filepath,(char *)sniffp,len +1);
	  strncpy(tmp,(char *)sniffp,len);
	  strcpy((tmp + len),".tmp");
	}
	wFD = 0;

	if ((pcap = pcap_open_live(dev,snaplen,promisc,to,errorbuf)) == NULL)
	  croak("error: %s",errorbuf);
	if (pcap_lookupnet(dev,&netp,&maskp,errorbuf) < 0)
	  croak("error: %s",errorbuf);
	if (pcap_compile(pcap,&real_fp,bpfstr,1,maskp) < 0)
	  croak("error: %s", pcap_geterr(pcap));
	if (pcap_setfilter(pcap,&real_fp) < 0)
	  croak("error: %s", pcap_geterr(pcap));
	pcap_freecode(&real_fp);
	if (pcap_setnonblock(pcap,1,errorbuf) < 0)
	  croak("error: %s",errorbuf);
	if ((pFD =  pcap_get_selectable_fd(pcap)) < 1)
	  croak("can not get selectable pFD");

	stats = (HV *)SvRV(hpref);
	dnsrequest = (AV *)SvRV(dnsref);
	sigemptyset(&set);
	sigprocmask(SIG_SETMASK, &set, NULL);

 #####################################################################
 #
 #	vector value indicates the		args
 #	"return from" function		
 #	0	initial entry			run	
 #	1	send to dnsFD			send buffer
 #	2	send to lFD			msg || undef, 1 dump else 0 no dump
 #	3	initialize wFD			undef, 1 dump else 0 no dump (for compatibility)
 #	4	receive from dnsFD
 #	5	print wFD request		print buffer
 #	6	close wFD
 #	7	purge return
 #	8	set run = 0
 #
#define INITIALIZE	0
#define SEND_dns	1
#define SEND_listen	2
#define INIT_wFD	3
#define RECV_dns	4
#define PRINT_dumptxt	5
#define CLOSE_wFD	6
#define WAS_PURGE	7
#define TERMINATE	8

int
_enter_constants(...)
    ALIAS:
	Net::Connection::Sniffer::INITIALIZE	= 0
	Net::Connection::Sniffer::SEND_dns	= 1
	Net::Connection::Sniffer::SEND_listen	= 2
	Net::Connection::Sniffer::INIT_wFD	= 3
	Net::Connection::Sniffer::RECV_dns	= 4
	Net::Connection::Sniffer::PRINT_dumptxt	= 5
	Net::Connection::Sniffer::CLOSE_wFD	= 6
	Net::Connection::Sniffer::WAS_PURGE	= 7
	Net::Connection::Sniffer::TERMINATE	= 8
    CODE:
	RETVAL = ix;
    OUTPUT:
	RETVAL

 #
 #	first return value indicates 		args
 #	the requested perl run operation
 #	0	end, run is zero
 #	1	listen interrupt		now, sender.naddr, message received
 #	2	dnslookup
 #	3	dump request			hup, init
 #	4	dns receive			len, buffer
 #	5	purge interrupt -- dns alarm is checked here in Perl
 #
#define END_RUN		0
#define LISTEN_MSG	1
#define DNS_NEEDED	2
#define DUMP_REQUEST	3
#define DNS_RECEIVE	4
#define PURGE		5

int
_exit_constants()
    ALIAS:
	Net::Connection::Sniffer::END_RUN	= 0
	Net::Connection::Sniffer::LISTEN_MSG	= 1
	Net::Connection::Sniffer::DNS_NEEDED	= 2
	Net::Connection::Sniffer::DUMP_REQUEST	= 3
	Net::Connection::Sniffer::DNS_RECEIVE	= 4
	Net::Connection::Sniffer::PURGE		= 5
    CODE:
	RETVAL = ix;
    OUTPUT:
	RETVAL

void
xs_while(vector,...)
	int	vector
    PREINIT:
	unsigned char * buf;
	STRLEN	len;
	int	dnslen, listenlen;
    PPCODE:
	switch(vector)
	{
	case SEND_dns	:	
		if (av_len(dnsrequest) < 0) {
		  dnsRflag = 0;
		  av_undef(dnsrequest);
		}
		buf = (unsigned char *)SvPV(ST(1),len);
		sendto(dnsFD,buf,len,0,&dnsaddr.sa,socklen);
		break;
	case WAS_PURGE	:
		break;
	case PRINT_dumptxt :
		buf = (unsigned char *)SvPV(ST(1),len);
		my_dump((char *)buf,len);
		break;
	case SEND_listen :
		if (SvPOK(ST(1)) == 0)
		  break;
		buf = (unsigned char *)SvPV(ST(1),len);
		sendto(lFD,buf,len,0,&sender.sa,socklen);
	case INIT_wFD	:
		signal_dump = 0;
		if (SvIOK(ST(2)) == 0)
		  croak("arg2 is not a boolean 0/1");
		if (SvIV(ST(2)) != 0) {
		  if (dumptofile) {
		    WFD = fopen(tmp,"w");
		  }
		  wFD = fileno(WFD);
		  dump_head = 1;
		  nleft = 0;
		}
		break;
	case CLOSE_wFD	:
		if (dumptofile != 0) {
 #		  fsync(wFD);
		  while(1) {
		    if (close(wFD) < 0) {
		      if (errno == EINTR)
			continue;		/*	try again	*/
		      break;			/*	else fatal	*/
		    } else
		      rename(tmp,filepath);
		    break;
		  }
		}
		wFD = 0;
		break;
	case INITIALIZE	:
		run = SvIV(ST(1));
		ra = 0;
		ba = 0;
		maxfd = dnsFD + 1;
		if (lFD != 0 && lFD >= maxfd)
		  maxfd = lFD +1;
		if (pFD >= maxfd)
		  maxfd = pFD +1;
		max = maxfd;
		FD_ZERO(&rset);
		FD_ZERO(&wset);
		set_signals();
		goto WHILE_run;
	case TERMINATE	:
		run = 0;
	default		:
		break;
	}
	now = (u_int32_t)time(NULL);		/*	update time when returning from external call	*/

      WHILE_run:
	while (run) {
	  if (vector != 0) {
	    switch(vector) {
	/*	INITIALIZE == 0, don't have to clear vector	*/
	      case SEND_dns	:
		vector = 0;
		goto LISTENCHECK;
	      case SEND_listen	:
		vector = 0;
		goto DNSCHECK;
	      case RECV_dns	:
		vector = 0;
		goto WFDCHECK;
	      case PRINT_dumptxt	:
	      case CLOSE_wFD	:
		vector = 0;
		goto TIMECHECK;
	      default		:
		vector = 0;
	/*	continue by falling out the bottom of this	*/
		break;
	    }
	  }

	/* clear any closed/changed file descriptors	*/
	  if (wFD != wFDm) {
	    if (wFDm != 0) {
	      FD_CLR(wFDm,&wset);
	    }
	    max = maxfd;	/* downgrade max fd count	*/
	    wFDm = wFD;
	  }
	  if (pFD != pFDm) {
	    if (pFDm != 0) {
	      FD_CLR(pFDm,&rset);
	    }
	    pFDm = pFD;
	  }
	  if (dnsFD != dnsFDm) {
	    if (dnsFDm != 0) {
	      FD_CLR(dnsFDm,&rset);
	    }
	    dnsFDm = dnsFD;
	  }
	  if (lFD != lFDm) {
	    if (lFDm != 0) {
	      FD_CLR(lFDm,&rset);
	    }
	    lFDm = lFD;
	  }
	/* enable active file descriptors	*/
	  if (wFD != 0) {
	    if (wFD >= maxfd)
	      max = wFD +1;
	    FD_SET(wFD,&wset);
	  }
	  if (pFD != 0)			/*	unless terminated		*/
	    FD_SET(pFD,&rset);
	  if (dnsFD != 0)		/*	unless terminated		*/
	    FD_SET(dnsFD,&rset);
	  if (lFD != 0)			/*	if active			*/
	    FD_SET(lFD,&rset);

	  if (now > nextp)		/*	if one second timeout exceeded	*/
	    goto TIMEFORCE;

	  tloop.tv_sec = 1;		/*	wake up every second		*/
	  tloop.tv_usec = 0;
	  nfound = select(max,&rset,&wset,NULL,&tloop);
 	  if (nfound < 0) {
 	    if (errno == EINTR ||
 		errno == EWOULDBLOCK ||
 		errno == ECONNABORTED ||
 		errno == EPIPE)
 	      continue;
 # 	    croak("encountered fatal error %d: %s",errno,strerror(errno));
 	  }

	  now = (u_int32_t)time(NULL);
	  if (nfound == 0)
	    goto TIMECHECK;

	  if (FD_ISSET(pFD,&rset)) {
 #	hold signals
 #	    sigprocmask (SIG_BLOCK, &sa.sa_mask, 0);
	    pcap_dispatch(pcap,-1,sniffit,(u_char *)"");
 #	    sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0);
	    if (dnsRflag != 0) {
	      XPUSHs(sv_2mortal(newSViv(DNS_NEEDED)));
	      XSRETURN(1);
	    }
	  }

	LISTENCHECK:
	  if (lFD != 0 && FD_ISSET(lFD,&rset)) {
	    listenlen = recvfrom(lFD,out.s,512,0,&sender.sa,(socklen_t *)&socklen);
	    if (listenlen > 0) {
	      EXTEND(SP,4);
	      PUSHs(sv_2mortal(newSViv(LISTEN_MSG)));
	      PUSHs(sv_2mortal(newSVuv(now)));
	      PUSHs(sv_2mortal(newSVpv((char *)&sender.si.sin_addr.s_addr,4)));
	      PUSHs(sv_2mortal(newSVpv((char *)out.s,listenlen)));
	      XSRETURN(4);
	    }
	  }

	DNSCHECK:
	  if (FD_ISSET(dnsFD,&rset)) {
	    dnslen = recv(dnsFD,out.s,512,0);
	    if (dnslen > 0) {
	      EXTEND(SP,3);
	      PUSHs(sv_2mortal(newSViv(DNS_RECEIVE)));
	      PUSHs(sv_2mortal(newSViv(dnslen)));
	      PUSHs(sv_2mortal(newSVpv((char *)out.s,dnslen)));
	      XSRETURN(3);
	    }
	  }

	WFDCHECK:
	  if (wFD != 0 && FD_ISSET(wFD,&wset) && dumpmore() == 0) {
	    EXTEND(SP,3);
	    PUSHs(sv_2mortal(newSViv(DUMP_REQUEST)));
	    PUSHs(sv_2mortal(newSViv(hup)));
	    PUSHs(sv_2mortal(newSViv(dump_head)));
	    dump_head = 0;
	    XSRETURN(3);
	  }

	TIMECHECK:
	  if (now > nextp) {		/* purge once a second			*/
	TIMEFORCE:
	    nextp = now;
	    if (run > 0)
	      run--;
	    if (wFD != 0)
	      signal_dump = 0;
	    EXTEND(SP,3);
	    PUSHs(sv_2mortal(newSViv(PURGE)));
	    PUSHs(sv_2mortal(newSViv(now)));
	    PUSHs(sv_2mortal(newSViv(signal_dump)));
	    XSRETURN(3);
	  }
	}
	closeMOST();
	if (wFD != 0 && dumptofile != 0)
	  close(wFD);
	pcap_close(pcap);
	XPUSHs(sv_2mortal(newSViv(END_RUN)));
	XSRETURN(1);

 # ############## THIS STUFF IS FOR TESTING ONLY!

void
inc_sv(hpp,key)
	SV	* hpp
	char	* key
  ALIAS:
	Net::Connection::Sniffer::fetch_uv = 1
  PREINIT:
	HV	* hp;
  INIT:
	if (SvTYPE(SvRV(hpp)) != SVt_PVHV)
	  croak("hp is not a hash REF");
	hp = (HV *)SvRV(hpp);
  PPCODE:
	if (ix == 1) {
	  XPUSHs(sv_2mortal(newSVuv(fetch_uv(hp,key))));
	  XSRETURN(1);
	} else
	  inc_sv(hp,key);

void
set_uv(hpp,key,vp)
	SV	* hpp
	char	* key
	SV	* vp
  ALIAS:
	Net::Connection::Sniffer::set_nv = 2
	Net::Connection::Sniffer::add_nv = 1
  PREINIT:
	HV	* hp;
	u_int32_t	val32;
	double	valnv;
  INIT:
	if (SvTYPE(SvRV(hpp)) != SVt_PVHV)
	  croak("hp is not a hash REF");
	hp = (HV *)SvRV(hpp);
  CODE:
	if (ix == 2) {
	  valnv = SvNV(vp);
	  set_nv(hp,key,valnv);
	} else {
	  val32 = SvUV(vp);
	  if (ix == 1)
	    add_nv(hp,key,val32);
	  else
	    set_uv(hp,key,val32);
	}

void
aEQaPLUSbXm(hpp,key1,key2,multi)
	SV	* hpp
	char	* key1
	char	* key2
	NV	multi
  PREINIT:
	HV	* hp;
  INIT:
	if (SvTYPE(SvRV(hpp)) != SVt_PVHV)
	  croak("hp is not a hash REF");
	hp = (HV *)SvRV(hpp);
  CODE:
	aEQaPLUSbXm(hp,key1,key2,multi);

void
init_hv(hpp,len)
	SV	* hpp
	I32	len
  PREINIT:
	HV	* hp;
  INIT:
	if (SvTYPE(SvRV(hpp)) != SVt_PVHV)
	  croak("hp is not a hash REF");
	hp = (HV *)SvRV(hpp);
  CODE:
	init_hv(hp,len);

void
match_init(mtch,nomtch,paystart,paystop)
	SV	* mtch
	SV	* nomtch
	int	paystart
	int	paystop
  PREINIT:
	STRLEN	len;
  CODE:
	if (SvPOK(mtch) == 0) {
	  match = NULL;
	} else {
	  match = (u_char *)SvPV(mtch,len);
	  if (len == 0)
	    match = NULL;
	}
	if (SvPOK(nomtch) == 0) {
	  nomatch = NULL;
	} else {
	  nomatch = (u_char *)SvPV(nomtch,len);
	  if (len == 0)
	    nomatch = NULL;
	}
	if (match != NULL || nomatch != NULL) {
	  payoff = paystart;
	  paysize = paystop - paystart;
	  if (paysize < 1)
	    croak("payload length specifier to short");
	}