/* ICMP-related user commands
 * Copyright 1991 Phil Karn, KA9Q
 */
 /* Mods by PA0GRI */
#include <stdio.h>
#include <time.h>
#include "global.h"
#include "icmp.h"
#include "ip.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "timer.h"
#include "socket.h"
#include "proc.h"
#include "session.h"
#include "cmdparse.h"
#include "commands.h"

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

int currentPing;

static struct cmds Icmpcmds[] = {
	"echo",         doicmpec,       0, 0, NULLCHAR,
	"status",       doicmpstat,     0, 0, NULLCHAR,
	"trace",        doicmptr,       0, 0, NULLCHAR,
	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;
char *argv[];
void *p;
{
	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("(%2u)%-20s%10lu",i,Icmp_mib[i].name,
		 Icmp_mib[i].value.integer);
		tprintf("     (%2u)%-20s%10lu\n",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;
{
	if(argc < 2) {
		tprintf("ICMP Tracing is %d\n",Icmp_trace) ;
		return 0 ;
	}
	
	switch(argv[1][0]) {
		case '0':
		case '1':
		case '2': Icmp_trace=atoi(argv[1]);
			break;
		default:
			tprintf("Trace modes are: 0|1|2\n") ;
			return -1 ;
	}

	return 0 ;
}

static int
doicmpec(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Icmp_echo,"ICMP echo response accept",argc,argv);
}

/* Send ICMP Echo Request packets */
static int
pingcmd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
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) {
			keywait(NULLCHAR,1);
			freesession(sp);
		}
		return 1;
	}
       	if(usesession)
		sp->s = s;
	tprintf("Resolving %s... ",argv[1]);
	if((ping.target = resolve(argv[1])) == 0){
		tprintf(Badhost,argv[1]);
	        if(usesession) {
			keywait(NULLCHAR,1);
			freesession(sp);
		}
		return 1;
	}
	if(argc > 2)
		ping.len = 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 %s... Press <CR> to abort...\n",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))	{
					recv_mbuf(Curproc->input,NULL,0,NULLCHAR,0); /* flush */
					tprintf ("%s: ping aborted by user after %d seconds\n", argv[1], k);
					break;
				}
				mspause(1000L);
			}
			currentPing = 0;
			if (k == 120)
				tprintf ("%s: no response after 120 seconds\n", argv[1]);
		}
		return 0;
	}
	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;
		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,(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;
		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 {
			ping.srtt = (7*ping.srtt + rtt + 4) >> 3;
			ping.mdev = (3*ping.mdev + abserr + 2) >> 2;
		}
		if((ping.responses % 20) == 0)
			pinghdr(sp,&ping, &starttime);
		time (&thetime);
		cptr = ctime(&thetime);
		tprintf("%10lu%10lu%5lu%10lu%10lu%10lu     %8.8s\n",
		 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;
        newproc("ping",512,(void (*)())pingcmd,argc,(void *)pargv,p,1);
    } else
        pingcmd(argc,argv,p);
    return 0;
}


static void
pinghdr(sp,ping, start)
struct session *sp;
struct ping *ping;
time_t *start;
{
	tprintf("Pinging %s (%s); data %d interval %lu ms:\n    since %s",
	 sp->name,inet_ntoa(ping->target),ping->len,ping->interval, ctime(start));
	tprintf("      sent      rcvd    %%       rtt   avg rtt      mdev     received\n");
}

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

	if(Icmp_echo && icmp->args.echo.id == MAXINT16
	 && pullup(&bp,(char *)&timestamp,sizeof(timestamp))
	 == sizeof(timestamp)){
		/* Compute round trip time */
		rtt = msclock() - timestamp;
		usprintf((currentPing) ? currentPing : Curproc->output, "%s: rtt %lu\n",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;
{
	struct ping *ping;

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


/* Send ICMP Echo Request packet */
int
pingem(s,target,seq,id,len)
int s;          /* Raw socket on which to send ping */
int32 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 clock;

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

	/* Insert timestamp and build ICMP header */
	memcpy(data->data,(char *)&clock,sizeof(clock));
	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 0;
	}
	to.sin_family = AF_INET;
	to.sin_addr.s_addr = target;
	send_mbuf(s,bp,0,(char *)&to,sizeof(to));
	return 0;
}
