/*	Taken in 1996 from FreeBSD:
 *
 *      tcpblast - test and estimate TCP throuput
 *
 *      Daniel Karrenberg   <dfk@nic.eu.net>
 */

/*
 *	Changes and extensions: 1996-2000 Rafal Maszkowski <rzm@icm.edu.pl>
 *
 *	ftp://ftp.6bone.pl/pub/blast/README
 */

char verstr[30]="FreeBSD + rzm ", *vdate=VERSION;

#include <sys/types.h>
#include <getopt.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>	/* for Solaris */
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>	/* fcntl() */
#include <signal.h>	/* signal() */
#include <limits.h>	/* for Solaris */
#include "compat.h"

#include <netinet/tcp.h> /* TCP_MAXSEG */

#if defined(__sunos__) || defined(__osf1__)
#define	strerror(x)     atoi(x)
char	*gettext(x)	char *x; { return x; }
#endif

#define	_(msg)		msg

/* to fit time difference in one variable we are scaling s*(1000*USCALE)+us/(1000/USCALE) */
#define	USCALE		100
#define DEFBLKSIZE	(1024)
#define MAXBLKSIZE	(32*1024)
#define	MAXBLOCKS	10000000

/* options to make printresult() arguments list shorter */
#define PRRES_VERBMASK	7
#define	PRRES_SCROLL	8
#define	PRRES_RD	16
#define	PRRES_WR	32
#define	PRRES_SHOW	64

/* circular buffer element containing time and written/read bytes */
typedef struct {
	long	t, b;
} cbelem;

/* unit conversion */
typedef struct {
	char	*unit;
	int	val;
} unitelem;

struct sockaddr_in	sock_in;
struct in_addr		source;
#ifdef HAVE_IN6_ADDR
struct in6_addr		source6;
#endif /* HAVE_IN6_ADDR */
struct			servent *sp;
struct			hostent *host;
struct timeval		t0;
struct timezone		tz;
char 			wbuff[MAXBLKSIZE], rbuff[MAXBLKSIZE], *ind, port[30]="9";
long			readall=0, writeall=0;
unsigned long		delay=0;
int 			af = AF_INET, f, bufsize=0, tcp=0, udp=0, randomb=0,
			blksize=DEFBLKSIZE, setbufsize=-1, dots=1,
			continuous=1, experimental=0, verbosity=0, lastnwb=1,
			lastnrb=1, reading=0, writing=1, comparing=0, scroll=0,
			maxwrblk=300, maxrdblk=0, maxtime=0, lport=0 /* any */,
			rate=0, family=PF_UNSPEC, maxseg=-1, opt_source=0;
			;

struct ratetime {
	struct timeval	rrate, time;
};

struct ratetime	*ratetab = NULL;

void
usage(char *name) {	
	fprintf(stderr, "\n");
	fprintf(stderr, "Usage: %s [options] destination[:port]\n\n", name);
	fprintf(stderr, "tcpblast/udpblast is a simple tool for probing network and estimating its\n");
	fprintf(stderr, "throughput. By default it sends %d blocks (1024 bytes each) of data to\n", maxwrblk);
	fprintf(stderr, "specified destination host.\n");
#ifdef HAVE_GETADDRINFO
	fprintf(stderr, "Destination can be name or address, IPv4 or IPv6. When IPv6 address is specified\n");
	fprintf(stderr, "with port it should look like: '[3ffe:8010::1]:9' (with single quotes to prevent\n");
	fprintf(stderr, "shell expansion of []).\n");
#endif /* HAVE_GETADDRINFO */
	fprintf(stderr, "\n");
	fprintf(stderr, "Options:\n");
#ifdef HAVE_GETADDRINFO
	fprintf(stderr, "-4, --ipv4           use only ipv4 address\n");
	fprintf(stderr, "-6, --ipv6           use only ipv6 address\n");
#endif /* HAVE_GETADDRINFO */
	fprintf(stderr, "-a                   send random data\n");
	fprintf(stderr, "-b BUF_SIZE          socket buf size (default: %d == %s), with `-' to\n", setbufsize, setbufsize==-1 ? "don't change" : "change");
	fprintf(stderr, "                       be substracted from results\n");
	fprintf(stderr, "-c, --count BLOCKS   change default (%d) number of blocks, range: 1..%d\n", maxwrblk, MAXBLOCKS);
	fprintf(stderr, "-d DOTFREQ           print dot every DOTFREQ blocks, disables cont. speed disp. \n");
/*	fprintf(stderr, "-f FILE              send FILE instead of generated data\n") ;		*/
	fprintf(stderr, "-h, --help           this help\n");
	fprintf(stderr, "-i, --delay DELAY    write delay in microseconds (EXPERIMENTAL)\n");
	fprintf(stderr, "-l, --last BLKS      show also speed for last BLKS blocks\n");
	fprintf(stderr, "-m                   results for every block in separate line\n");
	fprintf(stderr, "-n, --nwrite         do not write, use e.g. with chargen port\n");
	fprintf(stderr, "-o                   switch from continuous speed displaying to dots printing\n");
	fprintf(stderr, "-p PORT              bind this local PORT\n");
	fprintf(stderr, "-q --quiet           show only final statistics\n");
	fprintf(stderr, "-r, --read           read data returned to us, switches default port to echo\n");
	fprintf(stderr, "-R, --rate RATESPEC  limit the speed according to the RATESPEC\n");
	fprintf(stderr, "-S, --source ADDRESS   source (locally bound) IPv4 or IPv6 ADDRESS\n");
	fprintf(stderr, "-s BLOCK_SIZE        block size (default %d bytes)\n", blksize);
	fprintf(stderr, "-t MAXTIME           limit time to MAXTIME (up to %.0f h)\n", 4.294967295e9/(1000*USCALE));
	fprintf(stderr, "    --tcp            use TCP (%s)\n", ind[0]=='t' ? "default" : "default if named tcpblast" );
	fprintf(stderr, "    --udp            use UDP (%s)\n", ind[0]=='u' ? "default" : "default if named udpblast" );
	fprintf(stderr, "-v, --verbosity      verbosity, default 0, maximum 3. -v adds time display, -vv\n");
	fprintf(stderr, "                       also speed in B/s, -v - speed in b/s.\n");
	fprintf(stderr, "-V, --version        version\n");
	fprintf(stderr, "-x, --maxseg SIZE    setting packet SIZE using TCP_MAXSEG\n");
	fprintf(stderr, "destination          host name or address\n");
	fprintf(stderr, "port                 use port #/name xyz instead of default port %s\n", port);
	fprintf(stderr, "\n");
	fprintf(stderr, "RATESPEC             RATE[,TIME][:RATE[,TIME]]...\n");
	fprintf(stderr, "RATE                 generating data at RATE speed in B/s\n");
	fprintf(stderr, "TIME                 for TIME seconds (can be floating point number), last can\n");
	fprintf(stderr, "                       be omitted and that time will be infinite\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "Options -b, -c, -R, -s and -t can use case insensitive unit multipliers and\n");
	fprintf(stderr, "specifiers:\n");
	fprintf(stderr, "  size (bytes):\n");
	fprintf(stderr, "    -b and -s argument without units is in bytes, can use [{k,m,g}]b (lower\n");
	fprintf(stderr, "    or upper case) for KB, MB or GB. {k,m,g} are powers of 1024.\n");
	fprintf(stderr, "  number:\n");
	fprintf(stderr, "    -c argument is just a number, can have k, m or g added (powers of 1024),\n");
	fprintf(stderr, "    no unit needed\n");
	fprintf(stderr, "  rate (bytes pes second):\n");
	fprintf(stderr, "    -R RATE is in B/s or Bps, the postfixes can be [{k,m,g}]{b/s,bps}\n");
	fprintf(stderr, "  time (seconds):\n");
	fprintf(stderr, "    -t and -R TIME argument is in seconds, can be postfixed with\n");
	fprintf(stderr, "    [{k,m,g}]{s,min,h,w,m,y}\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "Example:\n");
	fprintf(stderr, "  tcpblast -b 4KB -c 10k -R 10KBps,2:20kbps,3s target\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "%s version: %s\n", name, verstr);
	exit(0);
}

void usage_small(char *name) {	
	fprintf(stderr, "type %s --help for help\n", name);
	exit(0);
}

/* randomize the buffer */
void
randbuff(int blksize) {
	int i;
	for (i=0; i<blksize; i++) {
		wbuff[i]=rand() % 256;
	}
}

unsigned long
gettdiff(struct timeval t0) {
	struct timeval t1;
	struct timezone t1z;

	if (gettimeofday(&t1, &t1z) < 0)
	{
		perror("tcp/udpblast time");
		exit(11);
	}
	return (t1.tv_sec-t0.tv_sec)*(1000*USCALE) + (t1.tv_usec-t0.tv_usec)/(1000/USCALE);
}

/* should be ready for options = PRRES_RD|PRRES_WR */

/*
	t0			start time
	writeall/readall	current # of B written/read
	bufsize			buffer size substracted form the results
	lastn[wr]b		if >0 we additionally count speed fo last n blocks
	options			PRRES_VERBMASK - 3 bits, PRRES_SCROLL, PRRES_RD, PRRES_WR, PRRES_SHOW
	global: max{wr|rd}blk	loop limir for written/read blocks
*/

void
printresult(struct timeval t0, long writeall, long readall, int bufsize, int lastnwb, int lastnrb, int options) {
	static int circrbufset = 0, circwbufset = 0, cbrsize, cbwsize, cbrptr, cbwptr;
	static cbelem *circrbuf = NULL, *circwbuf = NULL;
	unsigned long wtime, rtime, dwtime=0, drtime=0, pwtime, prtime;		/* 0s to shut up -Wall */
	int rd, wr, vrb, show;

	wtime = rtime = gettdiff(t0);

	rd = options&PRRES_RD; wr = options&PRRES_WR;
	vrb = options&PRRES_VERBMASK; show = options&PRRES_SHOW;

	if (show && !(options&PRRES_SCROLL)) printf("\r");

	/* possible future expansion: reset the table if # of KBs changes (or shrink/extend in next version) */
	if (wr && circwbufset && (lastnwb != cbwsize)) {
		circwbufset = 0;
		if (circwbuf) free(circwbuf);
	}
	if (rd && circrbufset && (lastnrb != cbrsize)) {
		circrbufset = 0;
		if (circrbuf) free(circrbuf);
	}

	if (wr && (lastnwb>0)) { if (circwbufset) {
		/* we are not advancing the pointer if no new bytes were found;
		   comparing with b+blksize handles special case when we finish blocked
		   and # of bytes is uneven, we can avoid using a new time after long
		   blocking, we will still use the last # of bytes when calculating speed */
		if (writeall >= circwbuf[cbwptr % cbwsize].b + blksize) {
			cbwptr++;
			circwbuf[cbwptr % cbwsize].t = wtime;
			circwbuf[cbwptr % cbwsize].b = writeall;
		} else {
			/* instead of dissolving the rate we are using 'stale' style */
			wtime = circwbuf[cbwptr % cbwsize].t;
		}
		pwtime = circwbuf[(cbwptr + 1) % cbwsize].t;
		if (pwtime >= wtime) { dwtime = 0; } else { dwtime = wtime - pwtime; };
	} else {
		circwbuf = malloc(sizeof(cbelem)*lastnwb);
		if (circwbuf == NULL) {
			perror("malloc: no memory");
		} else {
			bzero(circwbuf, sizeof(cbelem)*lastnwb);
			cbwsize = lastnwb;
			cbwptr = 0;
			circwbufset = 1;
		}
	} }

	if (rd && (lastnrb>0) ) { if (circrbufset) {
		/* see comment for writing */
		if (readall >= circrbuf[cbrptr % cbrsize].b + blksize) {
			cbrptr++;
			circrbuf[cbrptr % cbrsize].t = rtime;
			circrbuf[cbrptr % cbrsize].b = readall;
		} else {
			/* instead of dissolving the rate we are using 'stale' style */
			rtime = circrbuf[cbrptr % cbrsize].t;
		}
		prtime = circrbuf[(cbrptr + 1) % cbrsize].t;
		if (prtime >= rtime) { drtime = 0; } else { drtime = rtime - prtime; };
	} else {
		circrbuf = malloc(sizeof(cbelem)*lastnrb);
		if (circrbuf == NULL) {
			perror("malloc: no memory");
		} else {
			bzero(circrbuf, sizeof(cbelem)*lastnrb);
			cbrsize = lastnrb;
			cbrptr = 0;
			circrbufset = 1;
		}
	} }

	if (show) {
		if (wr) printf("Written");
		if (rd) printf("/Read");
		printf(" ");
		if (wr) printf("%5ld", writeall/1024);
		if (rd) printf("/%ld", readall/1024);
		printf(" KB");
		if (vrb >= 1) {
			printf("  in ");
			if (wr) printf("%.1f", (double)wtime/USCALE);
			if (rd) printf("/%.1f", (double)rtime/USCALE);
			printf(" ms");
		}
		if (vrb >= 3) {
			printf("   ");
			if (wr) printf("%.1f", ((double)writeall-bufsize)/wtime*8000*USCALE);
			if (rd) printf("/%.1f", ((double)readall-bufsize)/rtime*8000*USCALE);
			printf(" b/s");
		}
		if (vrb >= 2) {
			printf("   ");
			if (wr) printf("%.1f", ((double)writeall-bufsize)/wtime*1000*USCALE);
			if (rd) printf("/%.1f", (double)(readall-bufsize)/rtime*USCALE*1000);
			printf(" B/s");
		}
		printf("   ");
		if (wr) printf("%.2f", ((double)writeall-bufsize) / (double) (wtime*(1024.0/USCALE)) * 1000 );
		if (rd) printf("/%.2f", ((double)readall-bufsize) / (double) (rtime*(1024.0/USCALE)) * 1000 );
		printf(" KB/s");
		if ( (lastnwb>1) || (lastnrb>1) ) {
			long dw=0, dr=0;		/* 0s to shup up -Wall */
			if (wr) dw = writeall-circwbuf[(cbwptr + 1) % cbwsize].b;
			if (rd) dr = readall -circrbuf[(cbrptr + 1) % cbrsize].b;
			printf("  (last ");
			if (wr) printf("%ld", dw/1024);
			if (rd) printf("/%ld", dr/1024);
			printf(" KB:");
			printf(" ");
			if (wr) printf("%.1f", (double)dw /dwtime*USCALE*1000 );
			if (rd) printf("/%.1f", (double)dr /drtime*USCALE*1000);
			printf(" B/s)");
		}
		printf("     ");
		if (options&PRRES_SCROLL) printf("\n");
		fflush(stdout);
	}
}

/*
 * finish -- (netkit-base-0.10-37, ping.c)
 *      Print out statistics, and give up.
 */

void
finish() {
        /* (void)signal(SIGINT, SIG_IGN);	*/

/* printf("\n"); */
	if (dots!=0) printf("\n");
	if (continuous) printf("\r");
	printresult(t0, writeall, readall, 0, lastnwb, lastnrb, verbosity|scroll|reading*PRRES_RD|writing*PRRES_WR|PRRES_SHOW);
	if (experimental && (bufsize>0) && (maxwrblk*blksize>bufsize)) {
		printf("\nExperimentally taking into account %d B socket buffer:\n", bufsize);
		printresult(t0, writeall, readall, bufsize, lastnwb, lastnrb,
			verbosity|scroll|reading*PRRES_RD|writing*PRRES_WR|PRRES_SHOW);
	}
	printf("\n");
	exit(0);
}

unitelem unitmult[] = { { "k", 1024 }, { "m", 1048576}, { "g", 1073741824 }, { NULL, 0} };
unitelem unitspeed[] = { { "b/s", 1}, { "bps", 1}, { NULL, 0} };
unitelem unitsize[] = { { "b", 1}, { NULL, 0} };
unitelem unittime[] = { { "s", 1}, { "min", 60 }, { "h", 3600 }, { "d", 86400 },
			{ "w", 604800 }, { "m", 2635200 }, { "y", 31557600 }, { NULL, 0 } };

#define	NEED_SMTH_YES	1
#define	NEED_SMTH_NO	0

double
atoval(char *tstr, unitelem *unitmult, unitelem *unitsmth, int needsmth) {
	char	*errptr;
	int	i, uleft;
	long	mult = 1, smth = 1, seensmth = 0;
	double	val;

	val = strtod(tstr, &errptr);
	if ( (errptr == NULL) || (strlen(errptr) == 0) ) return val;

	uleft = strlen(errptr);
	if (needsmth) for (i = 0; unitsmth[i].val != 0; i++) {
		/* comparing the unit name with last bytes of the string */
		if (strncasecmp(errptr+uleft-strlen(unitsmth[i].unit), unitsmth[i].unit, strlen(unitsmth[i].unit))==0) {
			smth = unitsmth[i].val;
			uleft -= strlen(unitsmth[i].unit);
			seensmth = 1;
			break;
		}
	}
	if (needsmth) {
		if (seensmth != 1) { printf("Unit error in <%s>\n", tstr); exit(1); }
		if (!uleft) return val*smth;	/* unit, no multipliers */
	}

	for (i = 0; unitmult[i].val != 0; i++) {
		if (strncasecmp(errptr, unitmult[i].unit, strlen(unitmult[i].unit)) == 0) {
			if (strlen(unitmult[i].unit) != uleft) continue;
			mult = unitmult[i].val;
			uleft -= strlen(unitmult[i].unit);
			break;
		}
	}
	if (uleft !=0 ) { printf("Unit error in <%s>\n", tstr); exit(1); }

	return val*mult*smth;
}

int
parse_rates(char *spec) {
	int	colons=0, i=0;
	char	*scan=spec, *rtoken, *comma;
	double	frate, ftime;

	while (*scan!='\0') if (*scan++==':') colons++;

	ratetab = calloc(colons+2, sizeof(struct ratetime));
	if (ratetab==NULL) { printf("calloc() failed\n"); exit(1); }

	while( (rtoken = strsep(&spec,":")) ) {
		comma = index(rtoken, ',');
		if (!comma) {
			if (i!=colons) {	/* both  i  and  colons  counted from 0 */
				printf("Only in the last section of RATESPEC it makes sense to omit TIME (default: infinity)\n");
				exit(1);
			}
			ratetab[i].time.tv_sec  = LONG_MAX; /* we mean infinity really, 68 y should suffice, gonna die earlier */
			ratetab[i].time.tv_usec = 0;
		} else {
			*comma = '\0';
			ftime = atoval(comma+1, unitmult, unittime, NEED_SMTH_YES);
			ratetab[i].time.tv_sec  = (long)(ftime);
			ratetab[i].time.tv_usec = (long) ((ftime - (long)(ftime)) * 1000000 + 0.5);
		}
		frate = atoval(rtoken, unitmult, unitspeed, NEED_SMTH_YES);
		ratetab[i].rrate.tv_sec  = (long) (1000000 /* us/s */ * ( blksize /* bytes-block */ / frate ) +0.5);
		ratetab[i].rrate.tv_usec = ratetab[i].rrate.tv_sec % 1000000;
		ratetab[i].rrate.tv_sec  = ratetab[i].rrate.tv_sec / 1000000;
		i++;
	}

	/* last one */
	ratetab[i].rrate.tv_sec  = 0;
	ratetab[i].rrate.tv_usec = 0;
	return 0;
}

void
sndbuf(int f, int setbufsize, char *name) {
	int	size=sizeof(int); 	/* getsockopt() should know how much space we have */

	/* get/setsockopt doesn't return any error really for SO_SNDBUF,
	   at least on Linux; it limits the buffer to [2048..65536]
	   (131070 for 2.1 (?) but you can manipulate with /proc/sys/net/core/wmem_max ) */
	if (getsockopt(f, SOL_SOCKET, SO_SNDBUF, (void *)&bufsize, &size)==-1)
		printf("%s getsockopt: %s", name, strerror(errno));
	printf("read SO_SNDBUF = %d\n", bufsize);
	if (setbufsize!=-1) {
		if (setsockopt(f, SOL_SOCKET, SO_SNDBUF, (void *)&setbufsize, sizeof(setbufsize))==-1)
			printf("%s getsockopt: %s", name, strerror(errno));
		if (getsockopt(f, SOL_SOCKET, SO_SNDBUF, (void *)&bufsize, &size)==-1) /* size value's been set b4 */
			printf("%s getsockopt: %s", name, strerror(errno));
		printf("set  SO_SNDBUF = %d\n", bufsize);
	}
}

void
setmaxseg(int f, int maxseg, char *name) {
	int	currseg, size=sizeof(int); 	/* getsockopt() should know how much space we have */

	if (getsockopt(f, IPPROTO_TCP, TCP_MAXSEG, (void *)&currseg, &size)==-1)
		printf("%s getsockopt: %s", name, strerror(errno));
	printf("read TCP_MAXSEG = %d\n", currseg);
	if (maxseg!=-1) {
		if (setsockopt(f, IPPROTO_TCP, TCP_MAXSEG, (void *)&maxseg, sizeof(maxseg))==-1)
			printf("%s getsockopt: %s", name, strerror(errno));
		if (getsockopt(f, IPPROTO_TCP, TCP_MAXSEG, (void *)&currseg, &size)==-1) /* size value's been set b4 */
			printf("%s getsockopt: %s", name, strerror(errno));
		printf("set  TCP_MAXSEG = %d\n", currseg);
	}
}


int
bind_local(int f, int af, int lport, struct in_addr laddr) {
	struct sockaddr_in	sock_in;

	bzero((char *)&sock_in, sizeof(sock_in));
	sock_in.sin_family = af;
	sock_in.sin_port = htons(lport);
	if (opt_source) sock_in.sin_addr = laddr;
	if (bind(f, (struct sockaddr*)&sock_in, sizeof (sock_in)) < 0) {
		perror("tcp/udpblast: bind");
		exit(7);
	}
	return 0;
}

#ifdef HAVE_IN6_ADDR
int
bind_local6(int f, int af, int lport, struct in6_addr laddr) {
	struct sockaddr_in6	sock_in;

	bzero((char *)&sock_in, sizeof (sock_in));
	sock_in.sin6_family = af;
	sock_in.sin6_port = htons(lport);
	if (opt_source) sock_in.sin6_addr = laddr;
	if (bind(f, (struct sockaddr*)&sock_in, sizeof (sock_in)) < 0) {
		perror("tcp/udpblast: bind 6");
		exit(7);
	}
	return 0;
}
#endif /* HAVE_IN6_ADDR */

struct timeval
getrate(struct timeval *time) {
	static int	i=0;

	*time = ratetab[i].time;
	return ratetab[i++].rrate;
}

int
main(int argc, char **argv) {
	int		ncol;
	char		hostname[100], *srate=NULL /* for gcc */, fname[256], *name, *rpar, *pport;
	long		written1, read1, tmpw, tmpr, wrblk, rdblk, flags;
	struct timeval	twrpass, twrcurr, tdiff, timecount, rrate;
#ifdef HAVE_GETADDRINFO
	struct addrinfo	*ai, *ai0, hints;
	int		errgai;
#else
	struct servent	*service;
#endif /* HAVE_GETADDRINFO */
#ifdef HAVE_INET_PTON
	int		errpton;
#endif /* HAVE_INET_PTON */

	/* changing default, based on program name */
	if (argv[0]) {	/* no tricks! */
		strncpy(fname, argv[0], sizeof(fname));
	} else {
		strcpy(fname, "tcpblast");
	}
	if ( (name = rindex(fname, '/')) ) {
		name++;
	} else {
		name = fname;
	}

	strcat(verstr, vdate);

	/* non-random data - is modem compressing it? */
	bzero(wbuff, MAXBLKSIZE);
	/*	memset(wbuff, 'a', MAXBLKSIZE); 	oops, tests */

	/* find first letter in the name - usage() needs it */
	ind = name;	/* degenerated after intr. argv->name proc. */

	while (1) {
		static const struct option long_options[] = {
			{ "count",	required_argument,	NULL,	'c' },
			{ "delay",	required_argument,	NULL,	'i' },
			{ "help",	no_argument,		NULL,	'h' },
			{ "ipv4",	no_argument,		NULL,	'4' },
			{ "ipv6",	no_argument,		NULL,	'6' },
			{ "nwrite",	no_argument,		NULL,	'n' },
			{ "quiet",	no_argument,		NULL,	'q' },
			{ "read",	no_argument,		NULL,	'r' },
			{ "rate",	required_argument,	NULL,	'R' },
			{ "source",	required_argument,	NULL,	'S' },
			{ "tcp",	no_argument,		NULL,	 2  },
			{ "udp",	no_argument,		NULL,	 3  },
			{ "version",	no_argument,		NULL,	'V' },
			{ "verbosity",	no_argument,		NULL,	'v' },
			{ "maxseg",	required_argument,	NULL,	'x' },
			{ NULL,		0,			NULL,	 0  } };
		int	optchar;
		if ((optchar = getopt_long (argc, argv, "46ab:c:d:hi:l:mop:qrR:s:S:t:vVx:", long_options, NULL)) == -1) break;
		switch (optchar) {
			case 2: tcp=1;								break;
			case 3: udp=1;								break;
#ifdef HAVE_GETADDRINFO
			case '4': family = PF_INET;						break;
			case '6': family = PF_INET6;						break;
#endif /* HAVE_GETADDRINFO */
			case 'a': randomb=1;							break;
			case 'b':
				/* negative BUFSIZE means substracting it from results */
				setbufsize = atoval(optarg, unitmult, unitsize, NEED_SMTH_YES);
				printf("%d\n", setbufsize);
				if (setbufsize < 0) bufsize = setbufsize = abs(setbufsize);	break;
			case 'c': maxwrblk = abs((int)(atoval(optarg, unitmult, NULL, NEED_SMTH_NO))); break;
			case 'd': dots=abs(atoi(optarg)); continuous=0;				break;
			case 'f': printf("-f option not yet implemented\n"); exit(1);		break;
			case 'h': usage(name);							break;
			case 'i': delay=abs(atoi(optarg));					break;
			case 'l': lastnwb=lastnrb=abs(atoi(optarg))+1; 				break; /* split later */
			case 'm': scroll=PRRES_SCROLL;						break;
			case 'n': writing=0;							break;
			case 'o': continuous=0;							break;
			case 'p': lport=atoi(optarg);
				if ((lport<0) || (lport>65535)) {
					printf("Local port must be in 0..65535 range\n"); exit(1);
				}								break;
			case 'q': dots=0; continuous=0;						break;
			case 'r': reading=1; strcpy(port,"7");					break;
			case 'R': rate=1; srate = malloc(strlen(optarg)+1); 
				if (!srate) { printf("srate = malloc() failed\n"); exit(1); }
				strcpy(srate, optarg);						break;
			case 's': blksize=abs((int)(atoval(optarg, unitmult, unitsize, NEED_SMTH_YES))); break;
			case 'S':
				  opt_source = 1;
#ifdef HAVE_INET_PTON
				  errpton = inet_pton(af, optarg, &source);
#ifdef HAVE_IN6_ADDR
				  if (errpton <= 0) {
					  af = AF_INET6;
					  errpton = inet_pton(af, optarg, &source6);
				  }
#endif /* HAVE_IN6_ADDR */
				  if (errpton <= 0) {
					  printf(_("error: inet_pton(%d, %s, )\n"), af, optarg);
					  exit(66);
				  }
#else
				  if ( (source.s_addr = inet_addr(optarg)) == INADDR_NONE ) {
					  printf(_("error: inet_addr(%s)\n"), optarg);
					  exit(66);
				  }
#endif /* HAVE_INET_PTON */
				  break;
			case 't': maxtime=abs((int)(atoval(optarg, unitmult, unittime, NEED_SMTH_YES))); break;
			case 'v': verbosity++;							break;
			case 'V': printf("%s version: %s\n", name, verstr);	return 0;	break;
			case 'x': maxseg=atoi(optarg);						break;
			default: ;
		}
	}

	/* for now parse_rates() exit()s on errors itself */
	if (rate && parse_rates(srate)) { printf("wrong RATESPEC\n"); exit(1); }

/* correctness */
	if (tcp && udp) {
		printf("cannot use both TCP and UDP\n");
		usage_small(name);
		exit(2);
	}

	/* if neither -t nor -u is chosen use first character of the program name */
	if ( (tcp==0) && (udp==0) && (ind[0]=='t') ) tcp=1;
	if ( (tcp==0) && (udp==0) && (ind[0]=='u') ) udp=1;

	if (!tcp && !udp) {
		printf("must use either TCP or UDP\n");
		usage_small(name);
		exit(3);
	}

	if (continuous) dots=0;

	/* after options processing we need one arg left */
	if (argc - optind != 1) {
		if (argc - optind == 0) printf("give a hostname[:port]\n");
		usage_small(name);
		exit(4);
	}
	strncpy(hostname, argv[optind], sizeof(hostname)-1);

	if (maxwrblk<=0 || maxwrblk>MAXBLOCKS) {
		fprintf(stderr, "%s: %d BLOCKS outside of range 1..%d \n", name, maxwrblk, MAXBLOCKS);
		exit(5);
	}
	if (!maxrdblk) maxrdblk = maxwrblk;
	if (!reading)  maxrdblk = 0;
	if (!writing)  maxwrblk = 0;


	/* parsing hostname; possible combinations:
	   hostname[:port]	# ipv4 or ipv6, -[46] switch to choose if both available
	   ipv4[:port]
	   ipv6
	   \[ipv6\]
	   \[ipv6\][:port]
	*/
	pport = NULL;
	ncol = 0; { char *p = hostname; while (*p) if (*p++ == ':') ncol++; }
	if (ncol >= 2) {		/* must be a v6 addr */
		rpar = index(hostname, ']');
		if (rpar) {
			/* if : follows ] set ptr and cut port from the hostname */
			if (rpar[1] == ':') {
				pport = rpar+2;
				rpar[0] = '\0';
			}
			/* assuming that ] has its [ we strip both */
			*rpar = '\0';
			memmove(hostname, hostname+1, strlen(hostname+1)+1);
		}
	} else if (ncol == 1) {		/* must be a v4:port */
		if ( (pport = rindex(hostname, ':')) != 0 ) {
			*pport = '\0';
			pport++;
		}
	}
	if (pport) strncpy(port, pport, sizeof(port)-1);

	printf("Sending %s %s data to %s, port %s using %d B blocks.\n",
		randomb ? "random":"non-random", tcp ? "TCP":"UDP", hostname, port, blksize);

#ifdef HAVE_GETADDRINFO
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = tcp? SOCK_STREAM:SOCK_DGRAM;
	hints.ai_family = family;
	/* if (index(hostname, ':')) hints.ai_flags |= AI_NUMERICHOST; */

	if ( (errgai=getaddrinfo(hostname, port, &hints, &ai0)) != 0 ) {
		printf( _("getaddrinfo(): %s (%d)\n"), gai_strerror(errgai), errgai);
		return 1;
	}

	f = -1;
	for (ai = ai0; ai; ai = ai->ai_next) {
		f = socket(ai->ai_family, ai->ai_socktype, 0);
		if (f < 0) continue;
		sndbuf(f, setbufsize, name);
		setmaxseg(f, maxseg, name);
		if (af == AF_INET)  bind_local (f, af, lport, source);
#ifdef HAVE_IN6_ADDR
		if (af == AF_INET6) bind_local6(f, af, lport, source6);
#endif /* HAVE_IN6_ADDR */
		if (connect(f, (struct sockaddr *) ai->ai_addr, ai->ai_addrlen) < 0) {
			close(f); f = -1; continue;
		}
		break;
	}
	if (ai0) freeaddrinfo(ai0);
	if (f == -1) {
		perror( _("connect"));
		exit(1);
	}

#else /* HAVE_GETADDRINFO */

	if (tcp) f = socket(AF_INET, SOCK_STREAM, 0);
	else     f = socket(AF_INET, SOCK_DGRAM,  0);
	if (f < 0) {
		perror("tcp/udpblast: socket");
		exit(6);
	}

	sndbuf(f, setbufsize, name);
	setmaxseg(f, maxseg, name);

	bind_local(f, af, lport, source);
	host = gethostbyname(hostname);
	if (host) {
		sock_in.sin_family = host->h_addrtype;
		bcopy(host->h_addr, &sock_in.sin_addr, host->h_length);
	} else {
		sock_in.sin_family = AF_INET;
		sock_in.sin_addr.s_addr = inet_addr(argv[optind]);
		if (sock_in.sin_addr.s_addr == -1) {
			fprintf(stderr, "%s: %s unknown host\n", name, argv[optind]);
			exit(8);
		}
	}

	/* port # or name can be used */
	service = getservbyname(port, tcp ? "tcp" : "udp");
	if (service==NULL) sock_in.sin_port = htons(abs(atoi(port)));
	else               sock_in.sin_port = service->s_port;

	if (connect(f, (struct sockaddr*)&sock_in, sizeof(sock_in)) <0) {
		perror("tcp/udpblast connect");
		exit(9);
	}

#endif /* HAVE_GETADDRINFO */

	if (gettimeofday(&t0, &tz) < 0) {
		perror("tcp/udpblast time");
		exit(10);
	}
	if (randomb) srand(t0.tv_usec);		/* more less... */

	/* single process, all non-blocking writes/reads */
	flags=fcntl(f, F_GETFL);
	fcntl(f, F_SETFL, flags|O_NONBLOCK);

	if (comparing) {
		/* allocate a table of pointers to all possible blocks */
	}

	signal(SIGINT, finish);		/* ^C stops */

	if (rate) {
		rrate = getrate(&timecount);
		gettimeofday(&twrpass, &tz);
	}

	written1 = read1 = wrblk = rdblk = 0;
	do {
		if (writing && (wrblk<maxwrblk)) {
			tmpw = written1;
			if (delay && !rate) usleep(delay);

			if (rate) {
				/* if reached 0 getting new RATE,TIME */
				if ( (timecount.tv_sec<0) || (timecount.tv_usec<0) ) {
					rrate = getrate(&timecount);
					/* end of the table */
					if ( (rrate.tv_sec==0) && (rrate.tv_usec==0)) finish();
				}

				/* checking if we used whole time up to twrpass;
				   if not we have to wait to dissolve the bytes in time
				*/
				gettimeofday(&twrcurr, &tz);
				timersub(&twrpass, &twrcurr, &tdiff);
				if ( (tdiff.tv_sec>=0) && (tdiff.tv_usec>0) ) {
					usleep(tdiff.tv_sec*1000000 + tdiff.tv_usec);
				}

				/* FIXME: what happens if we couldn't write a whole block?
				   the line below might have been taking it into account */
				timeradd(&twrpass, &rrate, &twrpass);		/* the block should be sent until twrpass time */
				timersub(&timecount, &rrate, &timecount);	/* count to 0 */
			}

			written1 = write(f, wbuff+written1, (size_t)blksize-written1);
			if (written1 != -1) {
				writeall += written1;
				written1 += tmpw;
			} else { written1 = tmpw; }
			if (written1 >= blksize) {
				written1 -= blksize; wrblk++;
				/* optimization: do not call printresult() at all when silent */
				if (dots||continuous) printresult(t0, writeall, readall, experimental ? bufsize : 0,
					lastnwb, lastnrb, verbosity|scroll|PRRES_WR|reading*PRRES_RD
					|writing*PRRES_WR|continuous*PRRES_SHOW);
				if ( (dots) && ( (dots==1) || (wrblk%dots==1) ) ) write(1, ".", 1);
				if (randomb) {
					randbuff(blksize);
					/* alloc space, copy */
					if (comparing) {
					}
				}
			}
		}

		if (reading && (rdblk<maxrdblk)) {
			tmpr = read1;		/* saving to cumulate after reading */
			read1=read(f, rbuff+read1, (size_t)blksize-read1);
			if (read1 != -1) {
				readall += read1;
				read1 += tmpr;
			} else { read1 = tmpr; }
			if (read1 >= blksize) {
				read1 -= blksize; rdblk++;
				/* optimization: do not call printresult() at all when silent */
				if (dots||continuous) printresult(t0, writeall, readall, experimental ? bufsize : 0,
					lastnwb, lastnrb, verbosity|scroll|PRRES_RD|reading*PRRES_RD
					|writing*PRRES_WR|continuous*PRRES_SHOW);
				/* when !writing we would get lots of BSs when reading so we use  ... & writing */
				if ( (dots) && ( (dots==1) || (rdblk%dots==1) ) && writing ) write(1, "\b \b", 1);
				if (comparing) {
					if (randomb) {
					} else {
					}
				}
			}
		}

		/* with -O3 not calling the function whem maxtime==0 ? */
		if (maxtime && ((gettdiff(t0)/(1000*USCALE)) > maxtime)) break;
	} while ( (wrblk<maxwrblk) || (rdblk<maxrdblk) );

	finish();
	return(1);	/* for the compiler mostly, never reached */
}
