/*
 * Send / receive TCP or UDP echos in any of a number of bizzare ways.
 *
 *   Joel P. Bion, March 1990
 *   Copyright (c) 1990 cisco Systems. All rights reserved.
 *
 * This "tuecho" program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Prompts as:
 *   Host: -- host to send echos to -- can be name or a.b.c.d --
 *   Enter protocol (0 = UDP, 1 = TCP) [0]: -- UDP or TCP
 *   Size of data portion (bytes) [100]: -- bytes in data, excluding headers --
 *   Number of bursts [5]: -- number of bursts of packets to send --
 *   Packets per burst [1]: -- packets per burst, all sent AT ONCE --
 *   Timeout (seconds) [2]:  -- how long to wait for data
 *   Pause interval (seconds) [0]: -- Pause interval between bursts of frames
 *   Type of pattern (specify = 0, increment = 1) [1]: 
 *	    -- if 0 specified, allow you to specify a 16bit pattern as four
 *	    -- hex digits (see below). If 1, will create a "incrementing",
 *	    -- cycling pattern from 0x0000 -> 0xffff ->.
 *   Enter pattern (hex value) [abcd]:	-- if "0" specified above
 */
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <arpa/inet.h>

extern void printf(), fflush(), exit(), bzero(), perror(), strcpy(),
	    setbuf(), sscanf(), bcopy(), close(), fprintf();
extern int write(), read(), select(), atoi(), socket(), connect();

/* Maximum packet size (integer) */
#define MAXBUF (2000)
#define MAXBYTES (MAXBUF * 4)

/* Special patterns... */
#define SPECIAL_NONE (0)
#define SPECIAL_COUNTER (SPECIAL_NONE + 1)
#define SPECIAL_MAX (SPECIAL_COUNTER)

/* Protocols */
#define PROTO_UDP (0)
#define PROTO_TCP (PROTO_UDP + 1)
#define PROTO_MAX (PROTO_TCP)

/* Global, because interrupt routine references them */
int
    lost,			/* Packets whose echos are not received */
    good,			/* We received a good reply */
    corrupted,			/* We received a bad reply */
    sentout,			/* How many we sent out */
    outc;			/* How many chars out */


/* Guarantee CRs at random times */
void cprint(c)
    char c;
{
    printf("%c", c);
    if (++outc == 70) {
	printf("\n");
	outc = 0;
    }
    fflush(stdout);
}

void cprintcr()
{
    if (outc != 0)
      printf("\n");
}

unsigned getint(prompt, defa, fc)
    char *prompt;
    unsigned defa;
    char fc;
{
    char s[1000];
    unsigned u;

    if (fc == 'x')
      printf("%s [%x]: ", prompt, defa);
    else
      printf("%s [%d]: ", prompt, defa);
    fgets(s, 999, stdin);
    if ((s[0] == '\0') || (s[0] == '\n'))
      return(defa);
    else {
	if (fc == 'x')
	  sscanf(s,"%x", &u);
	else
	  sscanf(s,"%d", &u);
	return(u);
    }
}
 


void outhere()
{
    cprintcr();
    printf("\nAt end, sent out = %d, good = %d, lost = %d, corrupted = %d.\n",
	   sentout, good, lost, corrupted);
}

void outhere_exit()
{
    outhere();
    exit(0);
}

void usage_exit()
{
    printf("usage: tuecho\n");
    printf("  with no parameters will prompt you for information, or another form:\n");
    printf("\n tuecho host proto size nbursts ppburst timeout pauseint ptype\n");
    printf("	    [pattern] (on same command line as above)\n");
    printf("\n");
    printf(" Where:\n");
    printf("	 host: host to receive your echos. (can be name or a.b.c.d form)\n");
    printf("	proto: 0 for UDP, 1 for TCP\n");
    printf("	 size: size of echo packet, excluding headers\n");
    printf("  nbursts: total number of \"bursts\" of echo packets to send\n");
    printf("  ppburst: how many echo packets should be in each burst\n");
    printf("  timeout: how many seconds to wait for the reply (allow more time for bursts)\n");
    printf(" pauseint: Pause interval, in seconds, between bursts\n");
    printf("	ptype: 0 to allow you to specify a hex pattern as the final \"pattern\" parameter\n");
    printf("	       1 for \"increasing cyclic\" 16-bit pattern going from 0x0000 to 0xffff\n");
    printf("		 and back again, until each echo packet is filled\n");
    printf("  pattern: four character hex pattern (no leading 0x), if ptype = 1, to insert repeatedly\n");
    printf("	       in the echo packets\n");
    exit(0);
}

void sendrecvechos(sock, ptype, pattern, bufsize, bursts, burstsize, timeout,
		   pause_int)
    int sock;			/* Socket to send / receive from */
    int ptype;			/* Pattern type (normal, or a special) */
    int pattern;		/* Pattern (if normal) */
    int bufsize;		/* Buffer size to send */
    int bursts;			/* Number of bursts */
    int burstsize;		/* Size of bursts */
    struct timeval timeout;	/* Timeout */
    int pause_int;		/* Pause interval */
{
    int i, j, k, ok, readin;
    int nb;
    unsigned int bufo[MAXBUF];	/* Packet to send out */
    unsigned int bufi[MAXBUF];	/* Packet to accept */
    fd_set readmask;		/* For "select" call */

    /* Set up select mask */
    FD_ZERO(&readmask);
    FD_SET(sock, &readmask);
    /* Set up the pattern. Check for specials... */
    switch (ptype) {
      case SPECIAL_COUNTER:
	for (i = 0; i < (bufsize / 4); i++)
	  bufo[i] = i;
	break;
      case SPECIAL_NONE:
      default:
	for (i = 0; i < (bufsize / 4); i++)
	  bufo[i] = pattern;
	break;
    }
    for (i = 0; (i < bursts); i++)	{
	if (pause_int)
	  sleep(pause_int);
	if (ptype == SPECIAL_COUNTER)
	  bufo[0] = i;
	for (j = 0; j < burstsize; j++) {
	    if (write(sock, (char *) bufo, bufsize) < 0) {
		perror("writing on datagram socket.");
		outhere();
		exit(1);
	    }
	    sentout++;
	}
	for (j = 0; j < burstsize; j++) {
	    nb = select(FD_SETSIZE, &readmask, (fd_set *) 0,
			(fd_set *) 0, &timeout);
	    if (nb <= 0) {
		lost++;
		cprint('.');
		FD_SET(sock, &readmask);
		continue;
	    }
	    if (FD_ISSET(sock, &readmask)) {
		bzero((char *) bufi, bufsize);
		if ((readin = read(sock, (char *) bufi, bufsize)) < 0) {
		    perror("Reading on datagram socket.");
		    outhere();
		    exit(1);
		} else {
		    ok = 1;
		    for (k = 0; ((k < (bufsize / 4)) && (ok)); k++)
		      ok = (bufo[k] == bufi[k]);
		    if (!(ok)) {
			corrupted++;
			cprint('*');
			continue;
		    } else {
			good++;
			cprint('!');
			continue;
		    }
		}
	    }
	}
    }
    outhere();
}

void printinfo(ptype, pattern, bufsize, bursts, burstsize, timeout, server,
	       proto, pause_int)
    int ptype;			/* Pattern type (normal, or a special) */
    int pattern;		/* Pattern (if normal) */
    int bufsize;		/* Buffer size to send */
    int bursts;			/* Number of bursts */
    int burstsize;		/* Size of bursts */
    struct timeval timeout;	/* Timeout */
    struct sockaddr_in server;	/* Remote server IP address */
    int proto;			/* Protocol */
    int pause_int;

{
    printf("\nSending %d bursts of %d %s packets, data portions being %d bytes,\n",
	   bursts, burstsize, ((proto == PROTO_TCP) ? ("TCP") : ("UDP")),
	   bufsize);
    printf("with a timeout of %d seconds,\nthe pattern being ",
	   timeout.tv_sec);
    switch (ptype) {
      case SPECIAL_COUNTER:
	printf("an incrementing value in the packet");
	break;
      case SPECIAL_NONE:
      default:
	printf("0x%x in each four bytes", pattern);
	break;
    }
    printf("\n");
    printf("Pause interval: %d seconds\n", pause_int);
    printf("Sending to IP address %u.%u.%u.%u...\n",
	   (unsigned) server.sin_addr.S_un.S_un_b.s_b1,
	   (unsigned) server.sin_addr.S_un.S_un_b.s_b2,
	   (unsigned)server.sin_addr.S_un.S_un_b.s_b3,
	   (unsigned) server.sin_addr.S_un.S_un_b.s_b4);
}

void askinfo(argc, argv, hostname, proto,
	     bufsize, bursts, burstsize, timeout, ptype, pattern, pause_int)
    int argc;
    char **argv, *hostname;
    int *proto, *bufsize, *bursts, *burstsize;
    struct timeval *timeout;
    int *ptype;
    unsigned *pattern;
    int *pause_int;
{
    if ((argc > 9) || ((argc == 2) && (argv[1][0] == '-') &&
		       (argv[1][1] == 'h')))
      usage_exit();

    if (argc > 1)
      strcpy(hostname, argv[1]);
    else {
	printf("Host: ");
	gets(hostname);
    }
    *proto = ((argc > 2) ? (atoi(argv[2])) :
	      (int) getint("Enter protocol (0 = UDP, 1 = TCP)", 0, 'd'));
    *bufsize = ((argc > 3) ? (atoi(argv[3])) :
		(int) getint("Size of data portion (bytes)", 100, 'd'));
    *bursts = ((argc > 4) ? (atoi(argv[4])) :
	       (int) getint("Number of bursts", 5, 'd'));
    *burstsize = ((argc > 5) ? (atoi(argv[5])) :
		  (int) getint("Packets per burst", 1, 'd'));
    timeout->tv_sec = ((argc > 6) ? (atoi(argv[6])) :
		       (int) getint("Timeout (seconds)", 2, 'd'));
    *pause_int = ((argc > 7) ? (atoi(argv[7])) :
		  (int) getint("Enter pause interval", 0, 'd'));
    *ptype = ((argc > 8) ? (atoi(argv[8])) :
	      (int) getint("Type of pattern (specify = 0, increment = 1)",
			   1, 'd'));
    if (*ptype == SPECIAL_NONE)
      if (argc > 9)
	sscanf(argv[9], "%x", pattern);
      else
	*pattern = getint("Enter pattern (hex value)", 0xabcd, 'x');
}

void init()
{
    setbuf(stdout, NULL);
    good = sentout = corrupted = lost = outc = 0;
}

void main(argc, argv)
    int argc;
    char **argv;
{
    char hostname[1000];
    int bufsize, bursts, proto, ptype, burstsize, sock;
    struct hostent *hp, *gethostbyname();
    struct in_addr ip;
    struct sockaddr_in server;
    struct timeval timeout;
    unsigned int pattern, pause_int;

    askinfo(argc, argv, hostname, &proto, &bufsize, &bursts, &burstsize,
	    &timeout, &ptype, &pattern, &pause_int);
    signal(SIGINT, outhere_exit);
    ip.s_addr = inet_addr(hostname);
    if (ip.s_addr == -1) {
	hp = gethostbyname(hostname);
	if (hp == NULL) {
	    fprintf(stderr, "%s: %s: unknown host\n", argv[0],
		    argv[1]);
	    exit(1);
	}
	bcopy(hp->h_addr, &server.sin_addr, hp->h_length);
    } else
      bcopy(&ip, &server.sin_addr, sizeof(ip));
    printinfo(ptype, pattern, bufsize, bursts, burstsize, timeout, server,
	      proto, pause_int);
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
	perror("Opening stream socket.");
	exit(1);
    }
    server.sin_family = AF_INET;
    server.sin_port = htons(((struct servent *)
			     getservbyname("echo",((proto == PROTO_UDP) ?
						   ("udp") : ("tcp")
						   )))->s_port);
    if (connect(sock, &server, sizeof(server)) < 0) {
	perror("connecting stream socket");
	exit(1);
    }
    sendrecvechos(sock, ptype, pattern, bufsize, bursts, burstsize, timeout,
		  pause_int);
    close(sock);
    exit(0);
}
