/* Run Idealized Reno over traceroute.

 * Interim copyright notice:
 *
 * (c) Pittsburgh Supercomputing Center, Matthew B Mathis, 1995.
 * This software is under active development and may contain bugs.
 * PLEASE DO NOT REDISTRIBUTE.  Current copies can be obtained
 * from ftp://ftp.psc.edu/pub/net_tools/treno.shar.
 *
 * This software is being developed in connection with "IP Provider Metrics"
 * For more information see: http://pscinfo.psc.edu/~mathis/ippm
 *
 * The final version will be published under an unrestricted copyright.
 * 
 * Comments, suggestions and patches welcome.
 *   - Matt Mathis <mathis@psc.edu>
 *
 * Contributors:
 *	Van Jacobson, and all authors of Reno TCP
 *	Van Jacobson, and all authors of traceroute
 *	Jamshid Mahdavi - MTU discovery for traceroute
 *	Sean Doran - updated signals, bug fixes
 */

#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/ip_var.h>

#include "mplib.h"

#define MINBUF 1500	/* For unexpected messages */

char usage[] = "\
Usage:  treno [<switch> [<val>]]* [<GW1>...<GWn>] <host>\n\
      -p <s>      Set the test precision (default to 10 sec elapsed time)\n\
      -t <ttl>    Send UDP packets (instead of ICMP) with a TTL of <ttl>\n\
      -a <ttl>    Auto increment TTL through <ttl> (forces -t 1 if needed)\n\
      -b <mtu>    Use this initial MTU\n\
      -D          Turn off the receiver's delayed ACK emulation\n\
      -T          Use more thrust (Cray only: set realtime)\n\
      -n          Show addresses only\n\
      -v[<digit>] Verbosity (defaults to 0 or 4)\n\
      -E          Enable event logging\n\
      -V, -d      Version, Debug\n\
      -F <addr>   Select a source interface\n\
      [<GW1>...<GW>] Gateways to be traversed\n\
      <host>     Target host\n";

char lbanner[] = "\
 hop       packets   min rtt BW\n\
(ttl)     in    out   (mS)  k bps   Address\n";
char lfmt[] = "\n\
= %2d %6d %6d %6.2f = %6g %d.%d.%d.%d %s\n";

char sbanner[] = "\
 hop     BW\n\
(ttl)   k bps   Address\n";
char sfmt[] = "\n\
= %2d = %6g %d.%d.%d.%d %s\n";

/* global state */
extern int errno;
extern int numeric;			/* don't format names */
void ring(), halt();			/* signal handlers */
u_short checksum();
int haltf = 0;				/* signal happened */
long starttick, tick=0;			/* current second */
struct timeval now;
u_short ident;
u_short port = 32768+666;                /* UDP (raw) port # */
int on = 1;
int npdepth = 0;

/* command line options */
int duration = 10;
int ttl= -1;
int autottl=0;
int bbytes= 0;		/* default after parsing */
#define BBYTES 8166	/* FDDI MTU */
int dack=1;		/* delayed ack? */
int thrust=0;
int verbose = 0;
#define DEF_VERB 4
/* Verbosity:
 0 = show BW
 2 = show XT
 4 = loss, other instruments
 6 = detail on unexpected events
 8 = show packets
*/
int debug = 0;
int setfrom = 0;
int tos=0;	/* not supported */
struct sockaddr_in ToHost;	/* the remote end */
struct sockaddr_in FromHost;	/* my selected interface */

/* event logger */
#define EVSHIFT 24
#define EVMASK	0xFFFFFF
#define EVRING	1024
int eventbuff[EVRING];
int *evp=eventbuff;
int evfd=0;
#define EVPUT(val) {*evp++ =htonl(val);\
	if (evp >= &eventbuff[EVRING])evflush();}
#define Event(c, v) {if (evfd) {EVPUT(((c)<<EVSHIFT) | ((v) & EVMASK))}}
#define Event2(c, v, v2) {if (evfd) {\
	if (evp >= &eventbuff[EVRING-1]) EVPUT(0); \
	Event(c, v); EVPUT(v2);}}
#define EventTime(c, t) Event2(c, (t)->tv_sec, (t)->tv_usec)

void evflush()
{
  struct timeval stamp;
  NOERROR(write(evfd, eventbuff, (char *)evp - (char *)eventbuff),
	    "evflush");
  evp = eventbuff;

  gettimeofday(&stamp, 0);
  EventTime('L', &stamp);	/* Flush the log (how long did it take?) */
}

/* output buffer and goop */
struct opacket {		/* a (udp) probe packet. */
	struct ip ip;
	struct udphdr udp;
};
#define UDP_MINLEN (sizeof(struct opacket)-SIZEOFstructip)
int sndsock;				/* send socket */
u_int mtu;				/* Path MTU */
u_int maxseg;				/* TCP mss (slightly less than mtu) */
u_int optlen;				/* option length on the probes */
u_int tcpiplen = (20+20+12);		/* Presumed TCP/IP header w/ options */
u_int probelen;				/* probe packet length */
u_int buflen;				/* largest possible packet */
int udp;
char *outbuf = 0;
struct opacket *op;
struct ip *ip;
struct udphdr *up;
struct icmp *ic;
u_short cksm;	/* precomputed */
#ifdef sgi
/*
 * The SGI uses virtual memory page flips to save copies.  Unfortunately
 * it is broken in the sense that VM copy on write is far slower than 
 * it would have been to do conventional copy to an mbuf in the first place.
 * Work around by not re-using buffers until we think the device driver
 * is done with them.
 */
char **bufflist, **bfl;
#undef IP_HDRINCL
#endif /* sgi */

/* input buffer and goop */
int is;				/* icmp (listen) socket */
char *inbuf;
char *buf;
struct sockaddr_in from, goodfrom;

/* send a probe packet at the specified sequence */
/* NB: the ICMP checksum calculation assumes that these are consecutive */
int sendseq(sq)
u_short *sq;
{
  register u_short seq;
  int erf;

again: ;
#ifdef sgi
  if (!*bfl) bfl = bufflist;
  outbuf = *bfl++;
  op = (struct opacket *)outbuf;
  ip = &op->ip;
  up = &op->udp;
  ic = (struct icmp *)op;
#endif /* sgi */
  seq = *sq = SHORT((*sq) + 1);
  Event('O', seq);	/* packet out */
  if (verbose >= 8){
    int bcnt = mtu;
    char *outb = outbuf;
    printf("Sending:\n");
    netprintip(&outb, &bcnt, 2);
  }
  if (udp) {
#ifndef CRAY
    up->uh_dport = htons(SHORT(port+seq));
#else
    ((struct udpiphdr *)ip)->ui_dport = htons(SHORT(port+seq));
#endif
    erf = sendto(sndsock, outbuf, probelen, 0,
		 (struct sockaddr *) &ToHost, sizeof(ToHost));
  } else {
    ic->icmp_seq = seq;
    if (seq && !cksm--) cksm--; /* ones comp magic */
    ic->icmp_cksum = cksm;
    erf = sendto(sndsock, outbuf, probelen, 0,
		 (struct sockaddr *) &ToHost, sizeof(ToHost));
  }
  if (erf != probelen) {
    if (errno == ENOBUFS) {
      Event('x', 0);			/* sendto ENOBUFS */
      return 0;
    } else if (errno == EMSGSIZE) {
      setbuffers(drop_mtu(mtu), 0);
      goto again;
    } else NOERROR(erf, "sendto");
  }
  return 1;
}

/*
 * (P)reload send buffers with precomputed packets
 */

setbuffers(sz, tl)
int sz, tl;
{

  /* refine the ttl */
  if (tl) ttl = tl;

  /* refine the size */
  if (sz && (sz != mtu)) {
    if (mtu && (sz > mtu)) {
      printf("Invalid resize: %d\n", sz);
      exit(1);
    }
    mtu = sz;
    maxseg = mtu-tcpiplen;
/*  if (maxseg > 2048) maxseg &= ~1023;     XXX no rounding */
    printf(" MTU=%d ", mtu);
    Event('M', maxseg);		/* set/adjust MTU and mss */
  }

  /* allocate input and output buffers, if needed */
  if (!outbuf) {
#define slop 100
    buflen = maxseg+tcpiplen+optlen+slop;	 /* largest possible message */
    if (buflen < MINBUF) buflen = MINBUF+slop;
    inbuf = (char *)malloc(buflen);
    outbuf = (char *)malloc(buflen);
    (void) bzero(outbuf, buflen);
    op = (struct opacket *)outbuf;
    ip = &op->ip;
    up = &op->udp;
    ic = (struct icmp *)op;
    if (udp) { 
      ip->ip_hl = SIZEOFstructip >> 2;
      ip->ip_v = IPVERSION;
      ip->ip_tos = tos;
      /*  ip->ip_len = ?? see below */
      ip->ip_off = (short) IP_DF;			/* always DF */
      ip->ip_ttl = ttl;
      ip->ip_p = IPPROTO_UDP;
#ifndef CRAY
      if (setfrom) ip->ip_src = FromHost.sin_addr;
      ip->ip_dst = ToHost.sin_addr;
      up->uh_sport = htons(ident);
#else
      if (setfrom) ip->ip_src = FromHost.sin_addr.s_addr;
      ip->ip_dst = ToHost.sin_addr.s_addr;
      ((struct udpiphdr *)ip)->ui_sport = htons(ident);
#endif
      /* up->uh_ulen = ?? see below */
      up->uh_sum = 0;
    } else {
      /* set up ICMP packet */
      ic->icmp_type = ICMP_ECHO;
      ic->icmp_seq = 1; /* Must match sseq initialization elsewhere */
      ic->icmp_id = ident;
      ic->icmp_cksum = 0;
      /* cksm = checksum(outbuf, probelen); see below */
    }
#ifdef sgi
#define MAXTRAN 100
    { int i;
      bufflist = (char **) malloc((MAXTRAN+2)*sizeof(char *));
      bufflist[0] = outbuf;
      for (i = 1; i <= MAXTRAN; i++) {
	bufflist[i] = (char *)malloc(buflen);
	bcopy(outbuf, bufflist[i], buflen);
      }
      bufflist[MAXTRAN+1] = 0;
      bfl = bufflist;
    }
#endif /* sgi */
  }

  /* "reload buffers" and put in the final touches */
  if (udp) {
    probelen = maxseg+tcpiplen-optlen;   /* probe size  w/o options */
#ifdef sgi
    for (bfl = bufflist; *bfl; ) {
      outbuf = *bfl++;
      op = (struct opacket *)outbuf;
      ip = &op->ip;
      up = &op->udp;
      ic = (struct icmp *)op;
#endif /* sgi */
      ip->ip_len = probelen;
      ip->ip_ttl = ttl;
      up->uh_ulen = htons((u_short)maxseg+tcpiplen-SIZEOFstructip);
/*sizeof(struct opacket));*/
#ifdef sgi
    }
#endif /* sgi */
  } else {
    probelen = maxseg+tcpiplen-optlen-SIZEOFstructip;
    ic = (struct icmp *)outbuf;
    ic->icmp_cksum = 0;
    cksm = checksum(outbuf, probelen);
  }
}

/* Search for the next smaller valid mtu */
int drop_mtu(os)
int os;
{
  static int mtulist[] =	/* from RFC1191 */
    {32000, 17914, 8166, 4352, 2002, 1492, 1006, 508, 296, 68, 0};
  int i=0, ns;

  while ((os <= (ns = mtulist[i++])) && ns) ;
  return (ns);
}

int recvseq(sq)
u_short *sq;
{
  int fromlen, bcnt;
  int seq, type, code, rident;
  struct udphdr32o *up;

 again:
  fromlen = sizeof(from);
  bcnt = recvfrom(is, inbuf, buflen, 0,
		  (struct sockaddr *) &from, &fromlen);
  if (bcnt<0) {
    if (errno != EINTR) perror("recvfrom");
    return 0;
  }
  type = code = rident = -1;
  buf = inbuf;
  crackip(inbuf);
  up = STRUCTALIGN(struct udphdr32o *, cipclient, 0);
  if (cicmp) {
    type = cicmp->icmp_type;
    code = cicmp->icmp_code;
  };
  if (udp && cip2 && up &&
      (type == ICMP_TIMXCEED || type == ICMP_UNREACH) &&
      (cip2->ip_p == IPPROTO_UDP) ){
    seq = SHORT(ntohs(up->uh_dport) - port);
    rident = ntohs(up->uh_sport);
    if ((type == ICMP_UNREACH) && (rident == ident)) {
      if (code == ICMP_UNREACH_NEEDFRAG) {
	int m = ntohl(cicmp->icmp_void) & 0xFFFF;
	if (!m) m = drop_mtu(mtu);	/* XXX */
	if (m != mtu) setbuffers(m, 0);
	goto again;			/* XXX */
      }
      autottl = -1;			/* quit soon */
    }
  } else {
    if (!udp &&
	(ToHost.sin_addr.s_addr == from.sin_addr.s_addr) &&
	(type == ICMP_ECHOREPLY) ) {
      seq = SHORT(cicmp->icmp_seq);
      rident = cicmp->icmp_id;
    }
  }
  if (!cicmp || (rident != ident)) {
    if (debug || (verbose >= 6)) {
      printf ("Unexpected message:\n");
      printf("Ident?, %x != %x\n", rident, ident);
      netprintip(&buf, &bcnt, npdepth);
    }
    else {
      Event('u', rident);
      if (verbose >= 4) putchar('u');
    }
    goto again;
  } else {
    goodfrom = from;
    if (verbose >= 8) {
      printf ("Received message:\n");
      netprintip(&buf, &bcnt, npdepth);
    }
  }
  *sq = seq;
  return 1;
}
 
main(argc, argv)
char *argv[];
{
  char *sv, **av = argv;
  int nropts= -1;
	/* ladding - route and data size */
  int rlen=0;				/* The current route length */
  int nbix;

  /* sequence numbers.  Unlike TCP, these are always "forward sequence" - 
     they are never resent (except when wrapped). */
  u_short sseq;			/* current send sequence */
  u_short rseq;			/* current receive sequence */
  u_short erseq;		/* expected (in sequence) receive sequence */
  u_short rrseq;		/* retran sequence */
  u_short mrseq;		/* maximum receive sequence */

  /* TCP state variables */
  u_long cwnd;
  u_long ssthresh;
  int dupacks, tcprexmtthresh = 3;

  /* statistics */
  int in, out;
  u_short rttseq;
  u_int minrtt;
  struct timeval rttstamp;

  /* no buffering on stdout */
  setvbuf(stdout, 0, _IONBF, 0);
  numeric=0; /* Since this is for civilians, we format names by default */

  /* general switch cracker */
  argc--, av++;
  while (argc > 0 && *av[0] == '-') {
    sv = *av++; argc--;
    while (*++sv) switch (*sv) {
    case 'p':
      if ((argc > 0) &&
	  ((duration = atoi(*av++)) > 0) ) {
	argc--;
	break;
      }
      goto punt;
    case 't':
      udp++;
      if ((argc > 0) &&
	  ((ttl = atoi(*av++)) > 0) ) {
	argc--;
	break;
      }
      goto punt;
    case 'a':
      if ((argc > 0) &&
	  ((autottl = atoi(*av++)) > 0) ) {
	argc--;
	break;
      }
      goto punt;
    case 'b':
      if (argc > 0) {
	bbytes = atoi(*av++); argc--;
	/* check for legal later */
	break;
      }
      goto punt;
    case 'T':
      thrust=1;
      break;
    case 'D':
      dack=0;
      break;
    case 'n':
      numeric=1;
      break;
    case 'v':
      if (isdigit(sv[1])) 
	verbose = *++sv - '0';
      else
	verbose = DEF_VERB;
      break;	/* No -v is verbose = 0 */
    case 'E':
      evfd = open("event.log", O_WRONLY|O_CREAT|O_TRUNC, 0666);	/* lazy */
      break;
    case 'V':
      printversion();
      exit(0);
    case 'd':
      debug++;
      break;
    case 'F':
      if (argc > 0) {
	setfrom++;
	setsin(&FromHost, *av++); argc--;
	break;
      }
punt:
      printf(usage);
      printf("Invalid argument to -%c\n", *sv);
      exit(1);
    default:
      printf(usage);
      printf("Unknown switch: -%c\n", *sv);
      exit(1);
    }
  }
  
  /* Parse and arrange the route */
  while (argc > 0) {
    setsin(&route[rlen++], *av++); argc--;
  }
  if (rlen < 1)  {
    printf(usage);
    printf("You must supply at least one host name\n");
    exit(1);
  }
  bcopy(&route[rlen-1], &ToHost, sizeof(struct sockaddr_in));  /* ICMP */

  /* misc configuration */
  Event('V', 950308);	/* Event logger version */
  if (thrust) setthrust();
  ident = (getpid() & 0xffff) | 0x8000;
  npdepth = debug?-1:(udp?3:2);
  if (autottl && !udp) {
    udp = ttl = 1;
  }
  if (!autottl) autottl = ttl;
  if (!udp) printf("ICMP mode!/n");

  /* set up the icmp socket */
  NOERROR(is = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), "icmp socket");
  if (debug) setsockopt(is, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof(on));

  /* set up the send socket - UDP if we are using it */
  if (udp) {
    NOERROR(sndsock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW), "sndsock");
    if (debug) setsockopt(sndsock, SOL_SOCKET, SO_DEBUG,
			  (char *)&on, sizeof(on));
#ifdef IP_HDRINCL
    NOERROR(setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL,
		       (char *)&on, sizeof(on)), "IP_HDRINCL");
#endif
  }
  else sndsock = is;	/* use the ICMP socket for sending */

#define BIGBUF 524288
#define TINYBUF 32768
  { int sz;
    for (sz=BIGBUF; sz >= TINYBUF; sz >>= 1) {
      if (setsockopt(is, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) >= 0) break;
    }
    if (sz != BIGBUF) printf("RCVBUF=%d\n", sz);
    for (sz=BIGBUF; sz >= TINYBUF; sz >>= 1) {
      if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, &sz, sizeof(sz)) >= 0) break;
    }
    if (sz != BIGBUF) printf("SNDBUF=%d\n", sz);
  }

#ifndef IP_HDRINCL
  if (setfrom) {
    NOERROR(bind(sndsock, (struct sockaddr *)&FromHost, sizeof(FromHost)),
	    "Bind from");
  }
#endif

  /* set the route and check the MTU */
  optlen = setroute(sndsock, IPOPT_LSRR, rlen, route, 0);
  { int minlen = optlen + SIZEOFstructip + (udp?UDP_MINLEN:ICMP_MINLEN);
    if (bbytes) {
      if (bbytes<minlen) {
	printf(usage);
	printf("MTU (-b) must be greater than %d\n", minlen);
	exit(1);
      }
    } else
      bbytes = udp?BBYTES:576;	/* udp default is FDDI, ICMP is 576 */
  }

  /* pre loop setup */
  MP_SIGNAL(SIGALRM,ring);
  sseq = SHORT(1);
  haltf = 0;
  printf((verbose >= 4)?lbanner:sbanner);  

  /* Set up input and output buffers (pre compute output packets) */
  for (; ttl <= autottl; ttl++) {
    Event('H', ttl);		/* which hop */
    setbuffers(bbytes, ttl);
    bbytes = 0;
    in = out = 0;
      
    /* sync to the system clock */
    gettimeofday(&now, 0);
    tick = now.tv_sec;
    while(tick == now.tv_sec) gettimeofday(&now, 0);
    starttick = tick = now.tv_sec;
    EventTime('T', &now);	/* starting time stamp */

    /* TCP's initial state and first packet */
#define RETRAN {sendseq(&sseq); out++; rrseq = sseq;}
    mrseq = sseq;
    RETRAN;		/* If mtu discovery, this sets maxseg */
    rttseq = erseq = sseq;
    rttstamp = now;
    minrtt = (u_int) -1;
    RETRAN;		/* cwnd = 2 packets */
    dupacks = 0;
    cwnd = 2 * maxseg;
    ssthresh = ((u_long) -1) >> 1;

    /* loop for the test duration */
    while ((++tick <= (starttick+duration)) && ! haltf) {
      alarm(2);		/* XXX fixed timout: 2 sec HEADWAY */
      if (verbose < 2) putchar('.');	/* minimal chatter */
	
      /* loop for one second */
      while (tick > now.tv_sec) {
	u_short tseq;
	int ev = recvseq(&rseq);

	gettimeofday(&now, 0);
	EventTime('T', &now);	/* one timestamp per iteration */

	/* emulate the receiver's reassembly queue
	       and the sender's dupack counter */

	if (!ev) {			/* timout */
	  dupacks = -1;
	  if (verbose >= 2) putchar('T');
	} else {
	  Event('I', rseq);			/* packet input */
	  if (NEG(sseq - rseq)){
	    Event('s', rseq);		/* way out of sequence */
	    if (debug) printf ("%d:%d:%d", mrseq, rseq, sseq);
	    continue;
	  }
	  in++;
	  if (NEG(mrseq - rseq)) mrseq = rseq;

	  /* do the rtt estimator */
	  if (rseq == rttseq) {
	    u_int rtt = (now.tv_sec - rttstamp.tv_sec) * 1000000 +
			(now.tv_usec - rttstamp.tv_usec);
	    if (rtt < minrtt) minrtt = rtt;
	  }

	  if (dupacks) {			/* already out of order */
	    dupacks++;
	    if (dupacks < tcprexmtthresh) {
	      if ((rseq == erseq) &&
		  (SHORT(mrseq - erseq) == (dupacks - 1))) {
		dupacks = 0;	    /* self repaired simple reordering */
		erseq = SHORT(mrseq + 1);
	      }
	    } else if (dupacks > tcprexmtthresh) {
	      if (!NEG(mrseq - rrseq)) {		/* full recovery */
		int loss = SHORT(mrseq - erseq) - dupacks +1;
		if (verbose >= 6) printf("%d ",loss);
		dupacks = 0;
		erseq = SHORT(mrseq + 1);
	      }
	    } else { /* dupacks == tcprexmtthresh */
	    }
	  } else {
	    if (rseq == erseq) {			/* no holes */
	      erseq = SHORT(mrseq + 1);
	      /* emulate the receivers delayed ACK behavior */
	      if (dack && (mrseq & 0x1)) continue;
	    } else if (NEG(rseq-erseq)) {	      /* miss-ordered ACK... */
	      /* XXX */
	      continue;
	    } else {
	      dupacks++;				/* a new hole */
	      if (verbose >= 2) putchar('X');
	    }
	  }
	}

	/* At this point dupacks != 0 if there is a problem,
					 otherwise all is well */

	/* Emulate the sender's window dynamics
	 * This code is is a composite of several TCP implementation.
	 */
	ev = '?';		/* unknown Event */
	if (dupacks) {
	  if (dupacks < 0) {
	    u_int win=cwnd / 2 / maxseg;
	    ev = 't';				/* Event: timeout */
	    if (win < 2) win = 2;
	    cwnd = win * maxseg;		/* use old ssthresh */
	    dupacks = 0;
	    RETRAN;
	    goto drop;
	  }

	  if (dupacks == tcprexmtthresh) {
	    u_int win;
	    ev = 'R';				/* Event: fast retran */
	    if (cwnd <= (ssthresh+maxseg)) cwnd /= 2; /* NEW feature */
	    win=cwnd / 2 / maxseg;
	    if (win < 2) win = 2;
	    cwnd = ssthresh = win * maxseg;
	    RETRAN;
	    goto drop;
	  }

	  if (dupacks > tcprexmtthresh) { 
	    ev = 'r';				/* Event: fast recovery */
	    goto send;
	  }

	  ev = 'd';				/* Event: misc dup ack */
	  goto drop;

	} else {
	  if (cwnd > ssthresh) {
	    ev = 'a';			/* Event: congestion avoidance  */
	    cwnd += maxseg * maxseg / cwnd;
	  } else {
	    ev = 'e';		/* Event: exponential open (aka slow start) */
	    cwnd += maxseg;
	  }
	}

	/* send out to cwnd beyond mrseq */
      send:
	Event(ev, cwnd);
	tseq = SHORT(mrseq + (cwnd/maxseg));
	while (NEG(sseq - tseq)) {
	  sendseq(&sseq);
	  if (NEG(rttseq - mrseq)) {
	    rttstamp = now;
	    rttseq = sseq;
	  }
	  out++;
	}
	continue;
      drop:
	Event(ev, cwnd);
      } /* loop for a second */

    } /* loop for the duration */
    Event2('A', ttl, goodfrom.sin_addr.s_addr);

    /* drain outstanding packets and report final stats */
    alarm(2);
    while(recvseq(&rseq)) in++;
    {
      float rtt, bw;
      char *name="";
      unsigned char *fp = (unsigned char *) &goodfrom.sin_addr;
      struct hostent *hp;

      if (!numeric) {
	hp = gethostbyaddr((char *) &goodfrom.sin_addr, 4, AF_INET);
	if (hp) name = hp->h_name;
      }

      rtt = minrtt / 1000.0;
/*    pct = out? (in*100.0)/out: 0.0; */
      bw = ((float) in)*maxseg*8 / duration / 1000;
      if (verbose >= 4) {
	printf(lfmt, ttl,
	     in, out, rtt,
	     bw, fp[0], fp[1], fp[2], fp[3], name); }
      else
	printf(sfmt, ttl,
	       bw, fp[0], fp[1], fp[2], fp[3], name);
    }
  } /* ttl loop */

  /* close out the event log */
  evflush();
  close(evfd);
}

void ring(signo)
{
  MP_RESIGNAL(SIGALRM,ring);
  alarm(2);
}
