:1static char sccs_id[]="@(#)ping - John Chambers' version - %Dy";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* ping -i<N> -l[dvr] machine [datasize [npackets]] :
*	 Using the InterNet Control Message Protocol (ICMP) "ECHO" facility,
*	 measure round-trip-delays and packet loss across network paths.
*
* ping -i<N> pings at <N>-second intervals; default is 1 sec.
*
* ping machine:  attempts to see if machine is alive by pinging it for
*	  20 seconds
*
* This program has to run SUID to ROOT to access the ICMP socket.
*
* 06/09/88  Mo. Parenti		Changed signal handlers to void.
*
* 1/18/88   L. Gottfredsen	Merged 2 versions of ping into this one
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "V.h"	/* jc's debug package replaces stdio.h and errno.h */
#include "pktime.h"	/* jc's packet-timing module */
#include "sys_param.h"
#include "sys_socket.h"
#include "sys_file.h"
#include "sys_in_systm.h"
#include "sys_in.h"
#include "sys_ip.h"
#include "sys_ip_icmp.h"
#include "sys_wait.h"
#include "sys_signal.h"
#include "sys_netdb.h"
#if defined(Linux)
/*#include "icmp.h"*/
#endif
#ifndef ICMP
#define ICMP struct icmp
#endif
#ifndef IPKT
#define IPKT struct ip
#endif
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
#define DATALEN 8	/* Was 68-4 */
#define	MAXWAIT		5	/* m time to wait for response, sec. */
#define	MAXPACKET	4096	/* m packet size */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN	64
#endif
#define DEFTIMEOUT 20
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
byte	packet[MAXPACKET];


int   datalen = -1;		/* How much data */
char  hnamebuf[MAXHOSTNAMELEN];
char* hostname = 0;
HOST* hp = 0;			/* Pointer to host info */
int   ident = 0;
char* inet_ntoa();
int   interval  = 1;
short lflg      = 0;	/* long version, more stats */
int   npackets  = 0;
int   nreceived = 0;   	/* # of packets we got back */
int   nsent     = 0;	/* sequence # for outbound packets = #sent */
int   options   = 0;
int   s = -1;			/* Socket file descriptor */
int   timing    = 0;
int   tmax = 0;
int   tmin = 999999999;
int   tsum = 0;			/* sum of all times, for doing average */
struct timezone tz;		/* leftover */
char  usage[] = "Usage:  ping [-ldrv] host [data size [npackets]]\n";
SKAD  whereto;			/* Who to ping */

void finish(), catcher();
void noanswer(), quit();
int pid = 0;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
main(argc, argv, envv)
	char **argv;
	char **envv;
{	char   c, *p;
	struct sockaddr_in from;
	char **av = argv;
	char *toaddr = NULL;
	struct sockaddr_in *to = (struct sockaddr_in *) &whereto;
	int on = 1;
	struct protoent *proto;
	int fromlen, size, timeout;
#if defined(USE_waitstruct)
	union wait status;
#elif defined(USE_waitint)
	int   status;
#else
	static char m_badwait[] = "### Don't know whether to use `union wait' or `int` for wait status ###";
:1	V1 m_badwait D;
#endif

	argc = Vinit(argc, argv);
	mypid = getpid();

	argc--, av++;
	while (argc > 0 && *av[0] == '-') {
:3		V3 "Option: `%s'",*av D;
		while (*++av[0]) switch (*av[0]) {
			case 'd':
				Vopt(*av+1);
:4				V4 "Vlvl=%d output file=`%s'",
:4					Vlvl,Vfile?Vfile:"[none]" D;
				options |= SO_DEBUG;
				lflg = 1;
				continue;
			case 'i':
				if (sscanf(*av+1,"%d",&interval) < 1)
					interval = 5;
				if (interval < 1)
					interval = 1;
:2				V2 "Interval: %d seconds.",interval D;
				lflg = 1;	/* This triggers looping */
				/*
				* At debug levels 3 and higher, Alarm(n) will delay for
				* n+Vlvl seconds.
				*/
:3				V3 "Actually: %d+%d seconds.",interval,Vlvl D;
				continue;
			case 'r':
				options |= SO_DONTROUTE;
				lflg = 1;
				break;
			case 'v':
				if (*av[1]){
					Vopt(*av+1);
:4					V4 "Vlvl=%d output file=`%s'",
:4						Vlvl,Vfile?Vfile:"[none]" D;
				}
				lflg = 1;
				break;
			case 'l':
				lflg = 1;
				break;
		}
		argc--, av++;
	}
:5	V5 "Done with options." D;
	if (argc < 1)  {
		fprintf(stderr,usage);
		exit(1);
	}
	BZero(&whereto, sizeof(struct sockaddr));

:3	V3 "Arg: `%s'",av[0] D;
	to->sin_family = AF_INET;
	to->sin_addr.s_addr = inet_addr(av[0]);
:3	V3 "family=%d addr=%08lX=%s [from inet_addr]"
:3		,to->sin_family,to->sin_addr.s_addr,ipdot(&to->sin_addr.s_addr) D;
	if (to->sin_addr.s_addr && (to->sin_addr.s_addr != 0xFFFFFFFF)) {
		strcpy(hnamebuf, av[0]);
		hostname = hnamebuf;
	} else {
		hp = Gethostbyname(av[0]);
		if (hp) {
			to->sin_family = hp->h_addrtype;
			BCopy(hp->h_addr, (caddr_t)&to->sin_addr, hp->h_length);
			hostname = (CP)hp->h_name;
			toaddr = inet_ntoa(to->sin_addr.s_addr);
:3			V3 "family=%d addr=%08lX=%s [from Gethostbyname+inet_ntoa]"
:3				,to->sin_family,to->sin_addr.s_addr,toaddr D;
		} else {
			fprintf(stderr,"%s: unknown host %s\n", argv[0], av[0]);
			exit(1);
		}
	}
	if ((datalen=((argc >= 2)?atoi(av[lflg=1]):(DATALEN))) > MAXPACKET) {
:1		V1 "Packet size %d too large.\n",datalen D;
		exit(1);
	}
	if (datalen >= sizeof(TIMV))
		timing = 1;
	if (argc > 2) {
		npackets = atoi(av[2]);
		lflg = 1;
	}
	ident = getpid() & 0xFFFF;

	if ((proto = getprotobyname("icmp")) == NULL) {
		fprintf(stderr, "icmp: unknown protocol\n");
		exit(10);
	}
	if ((s = socket(AF_INET, SOCK_RAW, proto->p_proto)) < 0) {
		perror("ping: socket");
		exit(5);
	}
	if (options & SO_DEBUG)
		setsockopt(s, SOL_SOCKET, SO_DEBUG, &on, sizeof(on));
	if (options & SO_DONTROUTE)
		setsockopt(s, SOL_SOCKET, SO_DONTROUTE, &on, sizeof(on));

	if (lflg) {		/* print out statistics */
		Vtime();
   		printf("%s Ping ", Vtimh);
		if (toaddr)
			   printf("%s=", toaddr);
   		printf("%s", hostname);
		printf(" [%d data bytes]\n", datalen);
#if defined(ULTRIX)
		setlinebuf(stdout);	/* Ultrix block-buffers by default */
#endif
		SignalM(SIGINT, finish ,"finish");
		SignalM(SIGALRM, catcher,"catcher");

		catcher();	/* start things going */

		for (;;)
		{	int l = sizeof (packet);
			fromlen = sizeof (from);

			if ((size=Recvfrom(s, packet, l, 0, &from, &fromlen)) < 0) {
				if (errno == EINTR)
					continue;
				perror("ping: Recvfrom");
				continue;
			}
			pr_pack(packet, size, &from);
			if (npackets && nreceived >= npackets)
				finish();
		}
		/*NOTREACHED*/
	} else {		/* host alive or no answer from host */
		timeout = DEFTIMEOUT;
		if ((pid = Fork()) < 0) {
			perror("ping: fork");
			exit(1);
		}
		mypid = getpid();
		if (pid != 0) {		 /* parent */
:3			V3 "Parent process %d, child=%d.",ident,pid D;
			SignalM(SIGINT, quit, "quit");
			forever {
				if (pinger() < 0) {
				   	printf("%s is unreachable [Err %d=%s]\n",hostname,Erreason);
					kill(pid, SIGKILL);
					exit(1);
				}
				Sleep(interval-1);
#if defined(USE_wait3)
				if (Wait3(&status, WNOHANG, 0) == pid)
					if (status.w_termsig == 0)
						 Exit(status.w_retcode);
					else Exit(-1);
#elif defined(USE_wait)
				if (Wait(&status) == pid) {
					Exit((status >> 8) & 0xFF);
				} else {
					Exit(-1);
				}
#else
				V2 "### Don't know whether to use wait() or wait3() here." D;
				Exit(-1);
#endif
			}
		}
		if (pid == 0) {		 /* child */
:3			V3 "Child process %d, parent=%d.",getpid(),ident D;
			Alarm(timeout);
			SignalM(SIGALRM, noanswer, "noanswer");
			forever {
				int l = sizeof(packet);
				fromlen = sizeof(from);
				if ((size = Recvfrom(s, packet, l, 0, &from, &fromlen)) < 0) {
					perror("ping: Recvfrom");
					continue;
				}
				pr_pack(packet,size,&from);
				if (nreceived >= 1) {
				   	printf("%s is alive.\n", hostname);
					exit(0);
				}
			}
		}
	}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* This routine causes another PING  to  be  transmitted,  and  then  schedules
* another SIGALRM for <interval> seconds from now.
*
* Bug -
*    Our sense of time will slowly skew (ie,  packets  will  not  be  launched
*    exactly  at  <interval>-seconds  intervals).   This  does  not affect the
*    quality of the delay and loss statistics.
*/
void
catcher()
{	int waittime;

	pinger();
	if (npackets == 0 || nsent < npackets) {
:3		V3 "Set ping alarm for %d+%d sec.",interval,Vlvl D;
		SignalM(SIGALRM, catcher,"catcher");
		Alarm(interval);
	} else {
		if (nreceived) {
			waittime = 2 * tmax / 1000;
			if (waittime < interval)
				waittime = interval;
		} else
			waittime = MAXWAIT;
:3		V3 "Set alarm for %d+%d sec.",waittime,Vlvl D;
		Alarm(waittime);
		SignalM(SIGALRM, finish,"finish");
	}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Compose and transmit an ICMP ECHO REQUEST packet.  The  IP  packet  will  be
* added  on  by  the  kernel.   The  ID  field is our UNIX process ID, and the
* sequence number is an ascending integer.  The first  8  bytes  of  the  data
* portion  are  used  to  hold  a  UNIX "timeval" struct in VAX byte-order, to
* compute the round-trip time.
*/
pinger()
{	int e, i;
	int cc = datalen + 8;			/* skips ICMP portion */
static byte outpack[MAXPACKET];		/* Buffer for building packet */
	ICMP *icp = (ICMP*)outpack;
	byte  *datap = &outpack[8+sizeof(TIMV)];
	TIMV  *tp = (TIMV*)&outpack[8];

:1	Fpush("pinger");
	icp->icmp_type = ICMP_ECHO;
	icp->icmp_code = 0;
	icp->icmp_cksum = 0;
	icp->icmp_seq = nsent++;
	icp->icmp_id = ident;		/* ID */
	if (timing)
		gettimeofday(tp, &tz);
	for (i=8; i<datalen; i++)	/* skip 8 for time */
		*datap++ = i;
	icp->icmp_cksum = in_cksum(icp, cc);	/* Compute ICMP checksum here */

	i = sendto(s, outpack, cc, 0, &whereto, sizeof(struct sockaddr));

:1	if (i < 0 || i != cc) {
:1		e = errno;
:1		P2 "%s\t%s ### Wrote %d chars to %s,",
:1			Vtime(),progname, cc ,hostname D;
:1		errno = e;
:1		P2 "%s\t%s ### sendto returned %d [Err %d=%s]",
:1			Vtimp,progname, i, Erreason D;
:1	}
:1	Fpop;
	return i;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Print out the packet, if it came from us.  This logic is  necessary  because
* ALL  readers  of the ICMP socket get a copy of ALL ICMP packets which arrive
* ('tis only fair).  This permits multiple copies of this program  to  be  run
* without having intermingled output (or statistics!).
*/
pr_pack(buf, cc, from)
	char *buf;
	int   cc;
	struct sockaddr_in *from;
{	int   i, ps;
	IPKT *ip;
	ICMP *icp;
	long *lp = (long *) packet;
	TIMV  tv;
	TIMV *tp;
	int hlen, triptime;
:1	Fpush("pr_pack");;
	from->sin_addr.s_addr = ntohl(from->sin_addr.s_addr);
	gettimeofday(&tv, &tz);
	ps = cc;
	ip = (IPKT*)buf;
	hlen = ip->ip_hl << 2;
	if (cc < hlen + ICMP_MINLEN) {
:2		V2 "%d-byte packet too short from %s.", cc,
:2			ipdot(&from->sin_addr.s_addr) D;
		Done;
	}
	cc -= hlen;
	icp = (ICMP*)(buf + hlen);
	if (icp->icmp_type != ICMP_ECHOREPLY)  {
:2		V3 "%d bytes hdr + %d bytes data from %s; type=%d=%s code=%d.",
:2			hlen, cc,
:2			ipdot(&from->sin_addr.s_addr),
:2			icp->icmp_type, ICMPtype(icp->icmp_type),
:2			icp->icmp_code D;
:3		if (Vlvl > 3) dumpICMPpkt(icp,cc,"ICMPpkt");
:2		if (!lflg)
:2			V2 "ICMP type %d=%s.",icp->icmp_type,ICMPtype(icp->icmp_type) D;
		Done;
	}
	if (icp->icmp_id != ident)
		Done;			/* 'Twas not our ECHO */
	tp = (TIMV*)&icp->icmp_dun;
	if (lflg) {
		Vtime();
		printf("%s",Vtimh);
	/*	printf(" %d bytes",cc); */
		printf(" Pkt %3d",icp->icmp_seq);
		printf(" from %s",ipdot(&from->sin_addr.s_addr));
		if (hostname != hnamebuf)
			printf("=%s",hostname);
	}
	if (timing) {
		tvsub(&tv, tp);
		triptime = tv.tv_sec*1000+(tv.tv_usec/1000);
		if (lflg) printf("	%3d.%03d sec.\n",
			triptime / 1000,
			triptime % 1000);
		tsum += triptime;
		if (triptime < tmin) tmin = triptime;
		if (triptime > tmax) tmax = triptime;
	} else {
		putchar('\n');
	}
	fflush(stdout);
	nreceived++;
done:
:1	Fpop;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Checksum routine for Internet Protocol family headers (C Version)
*/
in_cksum(addr, l)
	U16 *addr;
	int  l;
{	int  nleft = l;
	U16 *w = addr;
	U16  answer;
	int  sum = 0;
 	U16  odd_byte = 0;
	/*
	 *  Our algorithm is simple, using a 32 bit accumulator (sum),
	 *  we add sequential 16 bit words to it, and at the end, fold
	 *  back all the carry bits from the top 16 bits into the lower
	 *  16 bits.
	 */
	while(nleft > 1)  {
		sum += *w++;
		nleft -= 2;
	}
	/* mop up an odd byte, if necessary */
	if (nleft == 1) {
		*(BP)(&odd_byte) = *(BP)w;
		sum += odd_byte;
	}
	/*
	* add back carry outs from top 16 bits to low 16 bits
	*/
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */
	return (answer);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Subtract 2 timeval structs:  out = out - in.  Out is assumed to be >= in.
*/
tvsub(out, in)
	TIMV *out, *in;
{
	if ((out->tv_usec -= in->tv_usec) < 0)   {
		out->tv_sec--;
		out->tv_usec += 1000000;
	}
	out->tv_sec -= in->tv_sec;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Print out statistics, and give up.  Heavily buffered STDIO is used here,  so
* that all the statistics will be written with 1 sys-write call.  This is nice
* when more than one copy of the program is running on a terminal; it prevents
* the statistics output from becomming intermingled.
*/
void
finish()
{
	printf("\n----%s PING Statistics----\n", hostname);
	printf("%d packets transmitted, ", nsent);
	printf("%d packets received, ", nreceived);
	if (nsent)
		printf("%d%% packet loss",
		(int) (((nsent-nreceived)*100) / nsent));
	printf("\n");
	if (nreceived && timing)
		printf("round-trip (ms)  min/avg/m = %d/%d/%d\n",
			tmin, tsum / nreceived, tmax);
	fflush(stdout);
	exit(0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
void
noanswer()
{
	printf("%s does not answer.\n", hostname);
	exit(1);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
void
quit()
{
	if (pid != mypid) {Kill(pid, SIGKILL); pid = 0;}
	exit(1);
}

