/* ICMP-related user commands
 * Copyright 1991 Phil Karn, KA9Q
 */
/* Mods by PA0GRI */
#include "global.h"
#include "commands.h"
#ifndef MSDOS
#include <time.h>
#endif
#include "icmp.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: icmpcmd.c,v 1.20 1997/08/19 01:19:22 root Exp $";
#endif

static int pingcmd (int argc, char *argv[], void *p);
static int doicmpec (int argc, char *argv[], void *p);
static int doicmpstat (int argc, char *argv[], void *p);
static int doicmptr (int argc, char *argv[], void *p);
static int doicmptimeexceed (int argc, char *argv[], void *p);
static void pingtx (int s, void *ping1, void *p);
static void pinghdr (struct session * sp, struct ping * ping, time_t * start);

#ifdef CATALOG
#include "catalog.h"

#define CAT icmpcmd_catalog

#define tracingis	__STR(0)
#define tracemodes	__STR(1)
#define echostr		__STR(2)
#define ttlstr		__STR(3)
#define resolving	__STR(4)
#define pinging		__STR(5)
#define pingaborted	__STR(6)
#define pingtimeout	__STR(7)
#define pinghdrstr1	__STR(8)
#define pinghdrstr2	__STR(9)

#else /* CATALOG */
static const char tracingis[] = "ICMP Tracing is %d\n";
static const char tracemodes[] = "Trace modes are: 0|1|2\n";
static const char echostr[] = "ICMP echo response accept";
static const char ttlstr[] = "Ttl time exceed reply";
static const char resolving[] = "Resolving %s... ";
static const char pinging[] = "Pinging %s... Press <CR> to abort...\n";
static const char pingaborted[] = "%s: ping aborted by user after %d seconds\n";
static const char pingtimeout[] = "%s: no response after 120 seconds\n";
static const char pinghdrstr1[] = "Pinging %s (%s); data %d interval %lu ms:\n    since %s";
static const char pinghdrstr2[] = "      sent       rcvd     %%        rtt    avg rtt       mdev     received\n";

#endif /* CATALOG */


static const char statstr1[] = "(%2u)%-20s%10lu";
static const char statstr2[] = "     (%2u)%-20s%10lu\n";
static const char pingfmt[] = "%10lu %10lu %5lu %10lu %10lu %10lu     %8.8s\n";
static const char pingrtt[] = "%s: rtt %lu\n";

static int currentPing = 0;


static struct cmds Icmpcmds[] =
{
	{ "echo",	doicmpec,		0, 0, NULLCHAR},
	{ "status",	doicmpstat,		0, 0, NULLCHAR},
	{ "timeexceed",	doicmptimeexceed,	0, 0, NULLCHAR},
	{ "trace",	doicmptr,		0, 0, NULLCHAR},
	{ NULLCHAR,	NULL,			0, 0, NULLCHAR},
};


int Icmp_trace;
static int Icmp_echo = 1;


int
doicmp (argc, argv, p)
int argc;
char *argv[];
void *p;
{
	return subcmd (Icmpcmds, argc, argv, p);
}


static int
doicmpstat (argc, argv, p)
int argc OPTIONAL;
char *argv[] OPTIONAL;
void *p OPTIONAL;
{
register int i;
int lim;

	/* Note that the ICMP variables are shown in column order, because
	 * that lines up the In and Out variables on the same line
	 */
	lim = NUMICMPMIB / 2;
	for (i = 1; i <= lim; i++) {
		tprintf (statstr1, i, Icmp_mib[i].name, Icmp_mib[i].value.integer);
		tprintf (statstr2, i + lim, Icmp_mib[i + lim].name, Icmp_mib[i + lim].value.integer);
	}
	return 0;
}


static int
doicmptr (argc, argv, p)
int argc;
char *argv[];
void *p OPTIONAL;
{
	if (argc < 2) {
		tprintf (tracingis, Icmp_trace);
		return 0;
	}
	switch (argv[1][0]) {
		case '0':
		case '1':
		case '2':
			Icmp_trace = atoi (argv[1]);
			break;
		default:
			tputs (tracemodes);
			return -1;
	}

	return 0;
}


static int
doicmpec (argc, argv, p)
int argc;
char *argv[];
void *p OPTIONAL;
{
	return setbool (&Icmp_echo, echostr, argc, argv);
}


int Icmp_timeexceed = 1;
static int
doicmptimeexceed (argc, argv, p)
int argc;
char *argv[];
void *p OPTIONAL;
{
	return setbool (&Icmp_timeexceed, ttlstr, argc, argv);
}


/* Send ICMP Echo Request packets */
static int
pingcmd (argc, argv, p)
int argc;
char *argv[];
void *p OPTIONAL;
{
struct proc *pinger = NULLPROC;	/* Transmit process */
struct sockaddr_in from;
struct icmp icmp;
struct mbuf *bp;
int32 timestamp, rtt, abserr;
int s, fromlen;
struct ping ping;
struct session *sp = (struct session *) 0;
time_t starttime, thetime;
char *cptr;
int usesession = 0;

	/* Make sure this is a 'one-shot- ping
	 * if not coming from the console - WG7J
	 */
	if (argc > 3)
		if (Curproc->input != Command->input)
			return 0;

	memset ((char *) &ping, 0, sizeof (ping));
	/* Allocate a session descriptor if this comes from console */
	if (Curproc->input == Command->input) {
		usesession = 1;
		if ((sp = ping.sp = newsession (argv[1], PING, 0)) == NULLSESSION) {
			tputs (TooManySessions);
			return 1;
		}
	}
	if ((s = socket (AF_INET, SOCK_RAW, ICMP_PTCL)) == -1) {
		tputs (Nosock);
		if (usesession) {
			(void) keywait (NULLCHAR, 1);
			freesession (sp);
		}
		return 1;
	}
	if (usesession && sp != NULLSESSION)
		sp->s = s;

	tprintf (resolving, argv[1]);

	if ((ping.target = resolve (argv[1])) == 0) {
		tprintf (Badhost, argv[1]);
		if (usesession) {
			(void) keywait (NULLCHAR, 1);
			freesession (sp);
		}
		return 1;
	}
	if (argc > 2)
		ping.len = (int16) atoi (argv[2]);

	if (argc > 3)
		ping.interval = atol (argv[3]);


	/* Optionally ping a range of IP addresses */
	if (argc > 4)
		ping.incflag = 1;

	if (ping.interval != 0)
		pinger = newproc ("pingtx", 300, pingtx, s, &ping, NULL, 0);
	else {
		/* One shot ping; let echo_proc hook handle response.
		 * An ID of MAXINT16 will not be confused with a legal socket
		 * number, which is used to identify repeated pings
		 */
		if (usesession) {
			pingem (s, ping.target, 0, MAXINT16, ping.len);
			freesession (sp);
		} else {
			int k;

			tprintf (pinging, argv[1]);
			usflush (Curproc->output);
			currentPing = Curproc->output;
			pingem (s, ping.target, 0, MAXINT16, ping.len);
			for (k = 0; k < 120; k++) {
				if (!currentPing)
					break;
				if (socklen (Curproc->input, 0)) {
					(void) recv_mbuf (Curproc->input, NULL, 0, NULLCHAR, 0);	/* flush */
					tprintf (pingaborted, argv[1], k);
					break;
				}
				(void) kpause (1000L);
			}
			currentPing = 0;
			if (k == 120)
				tprintf (pingtimeout, argv[1]);
			close_s (s);
		}
		return 0;
	}
	(void) time (&starttime);
	/* Now collect the replies */
	pinghdr (sp, &ping, &starttime);
	for ( ; ; ) {
		fromlen = sizeof (from);
		if (recv_mbuf (s, &bp, 0, (char *) &from, &fromlen) == -1)
			break;
		(void) ntohicmp (&icmp, &bp);
		if (icmp.type != ICMP_ECHO_REPLY || icmp.args.echo.id != s) {
			/* Ignore other people's responses */
			free_p (bp);
			continue;
		}
		/* Get stamp */
		if (pullup (&bp, (unsigned char *) &timestamp, sizeof (timestamp))
		    != sizeof (timestamp)) {
			/* The timestamp is missing! */
			free_p (bp);	/* Probably not necessary */
			continue;
		}
		free_p (bp);

		ping.responses++;

		/* Compute round trip time, update smoothed estimates */
		rtt = msclock () - timestamp;
		if (rtt < 0)
			rtt = 0;
		abserr = (rtt > ping.srtt) ? (rtt - ping.srtt) : (ping.srtt - rtt);

		if (ping.responses == 1) {
			/* First response, base entire SRTT on it */
			ping.srtt = rtt;
			ping.mdev = 0;
		} else {	/*lint -save -e704 */
			ping.srtt = (7 * ping.srtt + rtt + 4) >> 3;
			ping.mdev = (3 * ping.mdev + abserr + 2) >> 2;
		}		/*lint -restore */
		if ((ping.responses % 20) == 0)
			pinghdr (sp, &ping, &starttime);
		(void) time (&thetime);
		cptr = ctime (&thetime);
		tprintf (pingfmt, ping.sent, ping.responses,
			 (ping.responses * 100 + ping.sent / 2) / ping.sent,
			 rtt, ping.srtt, ping.mdev, &cptr[11]);
	}
	if (pinger != NULLPROC)
		killproc (pinger);
	freesession (sp);
	return 0;
}


int
doping (argc, argv, p)
int argc;
char *argv[];
void *p;
{
char **pargv;
int i;

	if (Curproc->input == Command->input) {
		/* Make private copy of argv and args,
		 * spawn off subprocess and return.
		 */
		pargv = (char **) callocw ((unsigned) argc + 1, sizeof (char *));

		for (i = 0; i < argc; i++)
			pargv[i] = strdup (argv[i]);
		pargv[i] = NULL;
		(void) newproc ("ping", 512, (void (*)(int, void *, void *)) pingcmd, argc, (void *) pargv, p, 1);
	} else
		(void) pingcmd (argc, argv, p);
	return 0;
}


static void
pinghdr (sp, ping, start)
struct session *sp;
struct ping *ping;
time_t *start;
{
	tprintf (pinghdrstr1, sp->name, inet_ntoa (ping->target), ping->len, ping->interval, ctime (start));
	tprintf (pinghdrstr2);
}


void
echo_proc (source, dest, icmp, bp)
uint32 source;
uint32 dest OPTIONAL;
struct icmp *icmp;
struct mbuf *bp;
{
int32 timestamp, rtt;

	if (Icmp_echo && icmp->args.echo.id == MAXINT16
	    && pullup (&bp, (unsigned char *) &timestamp, sizeof (timestamp))
	    == sizeof (timestamp)) {
		/* Compute round trip time */
		rtt = msclock () - timestamp;
		usprintf ((currentPing) ? currentPing : Command->output, pingrtt, inet_ntoa (source), rtt);
		currentPing = 0;
	}
	free_p (bp);
}


/* Ping transmit process. Runs until killed */
static void
pingtx (s, ping1, p)
int s;				/* Socket to use */
void *ping1;
void *p OPTIONAL;
{
struct ping *ping;

	ping = (struct ping *) ping1;
	ping->sent = 0;
	if (ping->incflag) {
		for (;;) {
			pingem (s, ping->target++, 0, MAXINT16, ping->len);
			ping->sent++;
			(void) kpause (ping->interval);
		}
	} else {
		for (;;) {
			pingem (s, ping->target, (int16) ping->sent++, (int16) s, ping->len);
			(void) kpause (ping->interval);
		}
	}
}


/* Send ICMP Echo Request packet */
void
pingem (s, target, seq, id, len)
int s;				/* Raw socket on which to send ping */
uint32 target;			/* Site to be pinged */
int16 seq;			/* ICMP Echo Request sequence number */
int16 id;			/* ICMP Echo Request ID */
int16 len;			/* Length of optional data field */
{
struct mbuf *data;
struct mbuf *bp;
struct icmp icmp;
struct sockaddr_in to;
int32 theclock;

	theclock = msclock ();
	data = ambufw ((int16) (len + sizeof (theclock)));
	data->cnt = len + sizeof (theclock);
	/* Set optional data field, if any, to all 55's */
	if (len != 0)
		memset (data->data + sizeof (theclock), 0x55, (size_t) len);

	/* Insert timestamp and build ICMP header */
	memcpy (data->data, (char *) &theclock, sizeof (theclock));
	icmpOutEchos++;
	icmpOutMsgs++;
	icmp.type = ICMP_ECHO;
	icmp.code = 0;
	icmp.args.echo.seq = seq;
	icmp.args.echo.id = id;
	if ((bp = htonicmp (&icmp, data)) == NULLBUF) {
		free_p (data);
		return;
	}
	to.sin_family = AF_INET;
	to.sin_addr.s_addr = target;
	(void) send_mbuf (s, bp, 0, (char *) &to, sizeof (to));
}
