/**
 ** TMETRIC - An aid to discovering the bandwidth on a route to a host
 **
 **	Copyright (C) 2000 Michael Bacarella
 **
 **	This program is available to you under the terms of the
 **	GNU General Public License.
 **
 **	Contact: mike@bacarella.com for bugfixes, comments, etc.
 **
 **	NOTE:	This program uses type 'double' for it's calculations.
 **		Weird errors occur on systems with really old FPUs.
 **		I will make no effort to do this support myself.
 **		Patches are welcome, of course.
 **
 **	VERSION: 0.5
 **/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

#include <netdb.h>

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

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

#define SEND_BUFFER_SIZE	64000
#define RECV_BUFFER_SIZE	64000

typedef unsigned char	ng_uchar;
typedef unsigned short	ng_ushort;
typedef unsigned long	ng_ulong;

char *progname;
extern char *optarg;

char *se(void)
{
	return strerror(errno);
}

int getproto(char *proto)
{
	struct protoent *pe = getprotobyname(proto);
	if (pe == NULL) {
		fprintf(stderr, "%s: getprotobyname(%s): %s\n",
			progname, proto, se());
		exit(1);
	}
	return pe->p_proto;
}

	/* chksum - shamelessly lifted from ping.c and butchered */

ng_ushort chksum(ng_ushort *addr, size_t len)
{
	size_t nleft = len;
	int sum = 0;
	ng_ushort *w = addr;
	ng_ushort answer;

	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1) {
		*(ng_uchar *)(&answer) = *(ng_uchar *)w;
		sum += answer;
	}

	sum = (sum >> 16) + (sum & 0xFFFF);
	sum += (sum >> 16);
	answer = ~sum;
	
	return answer;
}


	/* genpkts - sit in a loop generating icmp echo request
	 *	packets, size based on criteria */

void genpkts(int rs, pid_t cproc, struct sockaddr_in *sin,
	size_t range_start, size_t range_end, size_t range_res,
	size_t delay, size_t verbose)
{
	char sbuffer[SEND_BUFFER_SIZE];
	struct icmp *icmp;
	struct timeval *tv;
	size_t i, len = range_start + 8;

	size_t rstep = (range_end - range_start) / range_res;
	
	icmp = (struct icmp *)sbuffer;
	for (i = 0; i < range_res; i++) {
		memset(icmp, 0, sizeof(struct icmp));
		len += rstep;

		icmp->icmp_type = ICMP_ECHO;
		icmp->icmp_code = 0;
		icmp->icmp_id = cproc;
		icmp->icmp_seq = i;
		gettimeofday((struct timeval *)icmp->icmp_data, NULL);

		icmp->icmp_cksum = chksum((u_short *)icmp, len);

		if (verbose > 0)
			puts("genpkts(): sending packet");
		if (sendto(rs, sbuffer, len, 0, (struct sockaddr *)sin,
				sizeof(struct sockaddr)) == -1) {
			if (errno != EINTR) {
				printf("%s: sendto: %s\n", progname, se());
				break;
			}	
		}
		if (verbose > 2)
			printf("generate_packet(): sleeping "
				"for %d secs\n", delay);

		sleep(delay);
	}
	if (verbose > 2)
		printf("%s: terminating packet processor thread\n", progname);

	kill(getppid(), SIGINT);

	if (verbose > 2)
		printf("%s: exiting\n", progname);
	exit(0);
}

void pravg(double bps)
{
	bps = bps * 8.0;	/* convert it to bits-per-second before printing */

	if (bps <= 1024)
		printf("%5f bps",bps);
	else if (bps <= 1024*1024)
		printf("%5.2f kbps", bps/1024);
	else if (bps <= 1024*1024*1024)
		printf("%5.2f Mbps", bps/(1024*1024));
	else
		printf("%5.2f Gbps", bps/(1024*1024*1024));
}


	/* procpkt - receive icmp packet, update statistics */

void procpkt(char *rbuffer, size_t len, pid_t cproc, size_t verbose,
	double *highest, double *lowest, double *sum, size_t *total)
{
	struct ip *ip;
	struct icmp *icmp;	
	struct timeval tv, *ts;
	double elapsed, bps;
	sigset_t blockmask, allowmask;

	gettimeofday(&tv, NULL);

	if (verbose > 1)
		puts("procpkt(): handling icmp packet");

	ip = (struct ip *)rbuffer;
	icmp = (struct icmp *)(rbuffer + (ip->ip_hl << 2));
	
	if (icmp->icmp_type != ICMP_ECHOREPLY) {
		if (verbose > 1)
			puts("procpkt(): not one of our packets");
		return;
	}

	if (icmp->icmp_id != cproc) {
		if (verbose > 1)
			puts("procpkt(): not one of our packets");
		return;
	}

	if (verbose > 0)
		puts("procpkt(): received reply");

	ts = (struct timeval *)icmp->icmp_data;

	elapsed = (double)(tv.tv_sec + (double)tv.tv_usec/1000000)
		- (double)(ts->tv_sec + (double)ts->tv_usec/1000000);

	if (elapsed == 0.0) {
		puts("floating point error -- elapsed time cannot be zero!");
		return;
	}
	
	printf("%d byte probe: (seq %d ping: %2.4f secs): ",
		len, icmp->icmp_seq, elapsed);
	bps = (double)((double)len/(double)elapsed);

		/* block sigint-- */
	sigemptyset(&blockmask);
	sigaddset(&blockmask, SIGINT);
	sigprocmask(SIG_BLOCK, &blockmask, &allowmask);

		/* compute new stats for SIGINT handler */
	*sum += (double)bps;
	(*total)++;
	if (bps > *highest)
		*highest = bps;
	if (bps < *lowest)
		*lowest = bps;

		/* --unblock sigint (restore old behavior) */
	sigprocmask(SIG_SETMASK, &allowmask, NULL);
	
	pravg(bps);
	putchar('\r');
	fflush(stdout);
}

void prhelp(void)
{
	fprintf(stderr, "usage: %s [options] <hostname>\n"
			"\t-s	start of packet size range\n"
			"\t-e	end of packet size range\n"
			"\t-r	resolution of range\n"
			"\t-d	delay between each packet\n"
			"\t-v	verbosity. use many v's for more verbosity\n"
			"\t-h	this information\n"
			"example: %s -s 1000 -e 5000 -r 5 host\n"
			"\tsends 5 packets, stepping from 1000 "
				"to 5000 bytes at host\n",
			progname, progname);
	exit(0);
}


int main (int argc, char *argv[])
{
	int rs;
	struct sockaddr_in sin;
	struct hostent *he;
	pid_t cproc;
	char rbuffer[RECV_BUFFER_SIZE], ch, *host;
	size_t range_start, range_res, range_end,
		verbose, size, total, delay;
	double highest, lowest, sum;

	void sigint(int s) {
		printf("\ntmetric stats avg/min/max: ");
		if (total) {
			pravg((double)sum/total);
			putchar(' ');
			pravg(lowest);
			putchar(' ');
			pravg(highest);
			putchar('\n');
		} else
			puts("0/0.0/0.0/0.0");
		if (verbose > 2)
			printf("%s: packet processor terminating\n", progname);
		kill(cproc,SIGKILL);
		exit(0);	
	}
	total = verbose = 0; 
	highest = sum = 0.0;
	lowest = 1000000000;
	progname = argv[0];
	delay = 1;
	range_start = range_res = 256;
	range_end = SEND_BUFFER_SIZE;

	if (argc < 2)
		prhelp();

	while ((ch = getopt(argc, argv, "hs:e:r:d:v")) != -1) {
		switch (ch) {
		case 's':
			range_start = atol(optarg);
			break;
		case 'e':
			range_end = atol(optarg);
			break;
		case 'r':
			range_res = atol(optarg);
			break;
		case 'd':
			delay = atol(optarg);
			break;
		case 'v':
			verbose++;
			break;
		case 'h':
		default:
			prhelp();
			break;
		}
	}

	if (range_start+8 >= range_end) {
		fprintf(stderr, "error: %s: unusable ranges specified\n",
			progname);
		exit(1);
	}
	if (delay == 0) {
		fprintf(stderr, "error: %s: delay cannot be zero\n",
			progname);
		exit(1);
	}
	if (range_res < 2) {
		fprintf(stderr, "error: %s: resolution cannot be less than 2\n",
			progname);
		exit (1);
	}
	host = argv[argc-1];
	
	if ((rs = socket(AF_INET, SOCK_RAW, getproto("icmp"))) == -1) {
		fprintf(stderr, "%s: socket(): %s\n", progname, se());
		exit(1);
	}

	size = RECV_BUFFER_SIZE;
	setsockopt(rs, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size));
	setsockopt(rs, SOL_SOCKET, SO_SNDBUF, &size, sizeof (size));
	setuid(getuid());

	sin.sin_family = AF_INET;
	
	if ((he = gethostbyname(host)) == NULL) {
		perror("gethostbyname()");
		exit(1);
	}
	memcpy(&sin.sin_addr, he->h_addr, he->h_length);

	if ((cproc = fork()) == 0) {
		genpkts(rs, getpid(), &sin, range_start,
			range_end, range_res, delay, verbose);
		exit(0);	
	}
	signal(SIGINT, sigint);

	if (cproc == -1) {
		fprintf(stderr, "%s: fork(): %s\n", progname, se());
		exit(1);
	}

	while (1) {
		int n = recvfrom(rs, rbuffer, sizeof(rbuffer),
			0, (struct sockaddr *)&sin, &size);
		if (n < 0) {
			if (errno == EINTR)
				continue;
			fprintf(stderr, "%s: recvfrom(): %s\n", progname, se());
		}
		procpkt(rbuffer, n, cproc, verbose, &highest,
			&lowest, &sum, &total);
	}
	exit(0);
}



syntax highlighted by Code2HTML, v. 0.9.1