/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* This is the ICMP module for the SLIP daemon,  shamelessly  plagiarized  from *
* the  BSD  ping.c program by John Chambers.  Instead of futzing around with a *
* separate process, analyzing the command line, generating printed output, and *
* all  that stuff, this ping routine just sends a single ICMP packet to a host *
* and waits around for the response.  The return value (1 or 0) will  indicate *
* whether the ping succeeded.                                                  *
*                                                                              *
* There's probably a lot of stuff here that can be deleted. We should ask lint *
* to finger the dead stuff, and get rid of it.                                 *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "V.h"	/* jc's debug package */
/*#include "slipd.h"	/* This may not be needed any more */
#include "icmp.h"	/* Portable ICMP definitions */
#include "sys_netdb.h"
#include "sys_socket.h"
#include "sys_syslog.h"	/* Not everyone has this ... */
/*
* Next, some symbolic constants:
*/
#define DATALEN    256	/* Size of data portion of packet */
#define	MINWAIT		10	/* Min time to wait for response, sec. */
#define	MAXWAIT		60	/* Max time to wait for response, sec. */
#define	MAXPACKET 4096	/* Max packet size */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN	64
#endif

global byte packet[MAXPACKET];
extern int   errno;

global SKAD whereto;			/* Who to ping */
global struct protoent *proto = 0;
extern struct protoent *getprotobyname();

global Flag  alarmfl = 1;		/* Set timeout alarms? */
global int   datalen;			/* How much data */
extern CP    hostname;
extern int   hostnaml;
global int   ident;
global int   ipackets = 0;		/* Number of packets we got back */
global CP    m_noanswer ="%s [%s] Set noanswer alarm for %d seconds.";
global CP    m_silent = "Exiting; %s=%s has been silent for %d seconds.";
global int   ntransmitted = 0;	/* sequence # for outbound packets = #sent */
global int   opackets = 0;		/* Number of packets sent since last one received */
global int	  options;
global int   selrmask = 0;		/* select() read mask */
global int   selemask = 0;		/* select() error mask */
global int   selwmask = 0;		/* select() write mask */
global int   pinginterval = MINWAIT;	/* Length of wait between pings */
global int   pingtimeout = MAXWAIT;	/* Length of each ping() loop */
global int   pingsock = -1;	/* Socket file descriptor */
global U32   gotping  = 0;		/* Timestamp of last-received ping */
global U32   putping  = 0;		/* Timestamp of last-sent ping */
global int   pid;
global U32   recvtime = 0;		/* Last time we did a recvfrom() */
extern CP    thatname;
extern CP    thisname;
extern IP32  thataddr;
extern IP32  thisaddr;
extern CP    thatipad;
extern CP    thisipad;
global int   timeout = MINWAIT;
global struct timout timout = {MINWAIT,0};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Interrupt routine triggered by the alarm clock.
*/
noanswer()
{	int t;

	Fpush("noanswer");
	Vtime();
	t = Vtiml - gotping;
	P3 "%s %d packets from %s=%s in %d seconds.",
		Vtimp,ipackets,thatipad,thatname,t D;
	if (pingtimeout > 0 && t > pingtimeout) {
		V1 m_silent,thatname,thatipad,t D;
		S1(LOG_ERR,m_silent,thatname,thatipad,t);
		quit(ETIMEDOUT);
	}
	if (alarmfl) {
		P3 m_noanswer,Vtimp,"noanswer",timeout D;
		Alarm(timeout);
		SignalM(SIGALRM, noanswer,"noanswer");
	}
	Fpop;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*	Call this routine to ping a specific IPKT address.  The host name is used
*	in debug and syslog messages.  The return value is the number of pings
*	that succeeded during the pingtimeout interval.  Note that addr is in
*	host byte order, not network byte order.
*/
ping(addr,name)
	U32  addr;
	char*name;
{	int  i, t;
	SKin from;
	SKin*to = (SKin*)&whereto;
	int  on = 1;
	int  l, fromlen, size;
	U32  calltime;
	Fpush("ping");
	Vtime();
	calltime = Vtiml;
	ipackets = 0;		/* Number of packets received this time around */
	P3 "%s Called with addr=%08lX=%s `%s'",Vtimp,addr,ipdot(&addr),name D;
	ipackets = 0;		/* This will be the return value */
	V3 "ipackets=%d thataddr=%08lX=%s name=`%s'",
		ipackets,thataddr,thatipad,thatname D;
	bzero(&whereto, sizeof(SKAD));
	to->sin_family = AF_INET;
	to->sin_addr.s_addr = htonl(addr);
	datalen = DATALEN + hostnaml;
	ident = mypid & 0x0FFFF;
	if (!(proto)) {
		if ((proto = getprotobyname("icmp")) == NULL) {
			S1("Unknown protocol `icmp'=%d.",proto);
			quit(10);
		}
		V3 "IMCP protocol number = %d.",proto->p_proto D;
	}
	if (pingsock < 0) {			/* Do we have a socket? */
		errno = 0;
		if ((pingsock = Socket(AF_INET, SOCK_RAW, proto->p_proto)) < 0) {
			S1(LOG_ERR,"%s Can't get RAW socket [Err %d=%s]",
				Vtime(),Erreason);
			quit(5);
		}
		V3 "Got ICMP socket %d.",pingsock D;
		if (options & SO_DEBUG) {
			setsockopt(pingsock, SOL_SOCKET, SO_DEBUG, &on, sizeof(on));
			V3 "Socket DEBUG option on." D;
		}
		if (options & SO_DONTROUTE) {
			setsockopt(pingsock, SOL_SOCKET, SO_DONTROUTE, &on, sizeof(on));
			V3 "Socket DONTROUTE option on." D;
		}
		Seti(5,"opackets",opackets,0);
	}
	Vtime();
	P4 "%s Ping loop ...",Vtimp D;
loop:
	/*
	* This loop is here basically to send one ping message and to read any
	* responses.  We hang around for pinginterval seconds, and then return.
	* Note that we check that it's been pinginterval seconds since the last
	* ping message.  We also set a wakeup call at now+pinginterval seconds.
	* It is quite possible that we'll take several extra seconds here, but
	* that doesn't really matter.  The important thing is that we send out
	* one ping message about every pinginterval seconds, and that we read
	* all the responses, returning only after pinginterval (and maybe a few
	* more) seconds have passed.
	*/
	Vtime();
	if (pinginterval)
		timeout = pinginterval;
	P4 "%s Vtiml=%u putping=%u interval=%d timeout=%d",
		Vtimp,Vtiml,putping,pinginterval,timeout D;
	if ((Vtiml - putping) >= pinginterval) {	/* Not too fast */
		i = icmp();				/* Send one ping packet */
		if (i < 0) {
			S2(LOG_ERR,"%s Can't ping %s=%s [Err %d=%s]"
			,	Vtime(),thatipad,thatname,Erreason);
			switch (errno) {
			  case ENOBUFS:		/* Likely during heavy traffic */
				V3 "Try later..." D;
				Sleep(5);		/* Give the kernel time to do some work */
				Loop;
			  default:			/* Anything else is a real failure */
				S2(LOG_ERR,"%s Can't ping %s=%s [Err %d=%s]"
				,	Vtime(),thatipad,thatname,Erreason);
				quit(errno);	/* Shut down the SLIP link */
	}	}	}
	/*
	* We return only after pinginterval seconds, and if we have sent out at
	* least one ping message.
	*/
	t = Vtiml - calltime;
	if (t > pinginterval && putping >= calltime) {
		V4 "It's been %d seconds since we were called.",t D;
		Done;
	}
	if (alarmfl && timeout) {
		P3 m_noanswer,Vtimp,"ping",timeout D;
		Alarm(timeout);
		SignalM(SIGALRM, noanswer,"noanswer");
	}
	/*
	* Here is where we check for incoming packets.  Part of the nature of
	* ICMP is that a reader gets all incoming traffic.  The pkt() routine
	* has the jobs of testing to see if a packet is a response to one of
	* our pings, or has some other meaning.
	*/
	recvtime = time(0L);
	l = sizeof(packet);
	fromlen = sizeof(from);
	size = Recvfrom(pingsock,packet,l,0,&from,&fromlen);
	Vtime();
	if ((t = Vtiml - putping) > pinginterval * 2) {
		/*
		* Clock jumped, or we aren't getting cpu.
		*/
		P2 "%s +++ Clock jumped by %d seconds.",Vtimp,Vtiml-putping D;
		Seti(4,"ipackets",ipackets,0);	/* To prevent shutdown */
		Done;
	}
	V4 "Recvfrom returned %d after %d seconds.",size,Vtiml-recvtime D;
	if (size <= 0) {
		switch (errno) {
		  case EINTR:
		  case EWOULDBLOCK:
			P4 "%s No more packets in %d seconds.",Vtimp,t D;
			if (t > pingtimeout) {
				P4 "%s Timeout=%d exceeded.",Vtimp,pingtimeout D;
				Done;
			}
			Sleep(1);
			Loop;		/* No input, haven't reached timeout */
		  default:
			P2 "%s Recvfrom returned error %d=%s.",Vtimp,Erreason D;
		}
		if (size <= 0) {
			V3 "Zero-size packet received, Err %d=%s",Erreason D;
			Loop;
		}
		V2 "Recvfrom(%d,...) failed [Err %d=%s]",pingsock,Erreason D;
		Done;			/* Give up on this batch of pings */
	}
	/*
	* If we get a response, we reset the count of (unanswered)
	* packets sent back to zero, and go check for more.
	*/
	if (pkt(packet,size,&from))
		Seti(4,"opackets",opackets,0);
	if (t > pingtimeout) {
		V4 "Timeout after %d seconds.",t D;
		Done; 			/* Once thru ping() series */
	}
/*	Sleep(1);	** Is this needed? */
	Loop;
fail:
	Fpop;
	return(0);
done:
	Fpop;
	return(ipackets);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 			I C M P
* Compose and send an ICMP ECHO REQUEST packet.  The packet UDP header 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.
*/
icmp()
{	int i;
	int cc;		/* Size of packet */
static byte icmpkt[MAXPACKET];
	ICMP *icp = (ICMP*)icmpkt;
	byte *datap;

	Fpush("icmp");
	cc = datalen+8;				/* skips ICMP portion */
	P3 "%s Send %d-byte ping packet to %s=%s.",Vtimp,cc,thatname,thatipad D;
	icp->icmp_type = ICMP_ECHO;
	icp->icmp_code = 0;
	icp->icmp_cksum = 0;
	icp->icmp_seq = ntransmitted++;
	icp->icmp_id = ident;		/* ID is our pid */
	/*
	* Fill in our hostname as the first part of the data,
	* followed by binary numbers:
	*/
	for (datap=icmpkt+8+sizeof(struct timeval), i=0;
		 datap < icmpkt+MAXPACKET;
		 datap++, i++)
			*datap = (i<hostnaml)? hostname[i]: i-hostnaml;
	icp->icmp_cksum = in_cksum(icp, cc);
	V5 "ICMP checksum: %04X",icp->icmp_cksum D;
	H5(icmpkt,cc,"PKTping");

	i = Sendto(pingsock, icmpkt, cc, 0, &whereto, sizeof(SKAD));

	if (i < 0 || i != cc)  {
		P2 "%s ### Can't send %d-byte packet to %s.",Vtime(),cc,thatname D;
		if (i<0) {
			P2 "%s ### Sendto failed [Err %d=%s]",Vtimp,Erreason D;
		} else {
			P2 "%s ### Sendto returned %d [Err %d=%s]",Vtimp,i,Erreason D;
		}
		fflush(stdout);
		Fpop;
		return(-1);
	}
	Vtime();
	Seti(5,"putping",putping,Vtiml);
	++opackets;
	P3 "%s opackets=%d putping=%d Vtiml=%d.",
		Vtimp,opackets,putping,Vtiml D;
	Fpop;
	return(0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Examine the packet, to see 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!). The return value is
* 1 if it's for us, 0 if not.
*/
pkt(buf, cc, from)
	char*buf;
	int	 cc;
	SKin *from;
{	int	  c, hlen, t;
	IPKT *ip;
	ICMP *pp;
	U32  *lp, ll;

	Fpush("pkt");
	from->sin_addr.s_addr = ntohl(from->sin_addr.s_addr);

	ip   = (IPKT*)buf;
	lp   = (U32 *)ip;
	hlen = ip->ip_hl << 2;
	if (Vlvl>5)
		dumpIPpkt(buf,cc,"Received");
	if (cc < hlen + ICMP_MINLEN) {
		if (Vlvl > 1)
			V2 "Packet too short (%d bytes) from %s."
			,	cc,inet_ntoa(ntohl(from->sin_addr.s_addr)) D;
		if (Vlvl>2) dumpIPpkt(buf,cc,"Short");
		Fpop;
		return(0);
	}
	cc -= hlen;
	pp  = (ICMP*)(buf + hlen);
	t   = pp->icmp_type;
	c   = pp->icmp_code;
	if (t != ICMP_ECHOREPLY)  {
		ll = ntohl(from->sin_addr.s_addr);
		Vtime();
		P3 "%s ICMP from %08X %08lX => %08l."
		,	Vtimp, ll, lp[3], lp[4] D;
		P2 "%s ICMP from %s %s => %s."
		,	Vtimp, ipdot(&ll), ipdot(lp+3), ipdot(lp+4) D;
		P2 "%s ICMP type %d=`%s' code %d=`%s'"
		,	Vtimp, t, ICMPtype(t), c, ICMPcode(c) D;
		if (Vlvl>1)
			dumpIPpkt(buf,hlen+cc,"Rejected");
		if (t == ICMP_REDIRECT	/* Bounced packet */
		&&  lp[3] == thisaddr	/* Came from here */
		&&  lp[4] == thataddr	/* Went to other end of SLIP link */
		&&  pp->icmp_id == mypid	/* Contains our pid */
		) {
			P2 "%s ### %s appears to be bouncing packets.",Vtimp,thatname D;
			quit(1);
		}
		Fpop;
		return(0);
	}
	if (pp->icmp_id != ident) {
		V3 "Rejected ICMP packet type %d=%s from %08lX=%s.",
			t,ICMPtype(t),
			pp->icmp_gwaddr.s_addr,
			inet_ntoa(pp->icmp_gwaddr.s_addr) D;
		Fpop;
		return(0);			/* 'Twas not our ECHO */
	}
	Seti(5,"gotping",gotping,Vtiml);	/* Note time of last-received ping */
	V4 "Got %d-byte ICMP packet after %d sec; seq=%d from %s.",
		cc,gotping-putping,pp->icmp_seq,
		inet_ntoa(ntohl(from->sin_addr.s_addr)) D;
	P3 "%s Link %s=%s --> %s=%s is alive.",Vtime(),
		thisipad,thisname,
		thatipad,thatname D;
	ipackets++;
	Fpop;
	return(1);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Checksum routine for Internet Protocol family headers (C Version)
*/
in_cksum(addr, l)
	u_short *addr;
	int l;
{	int nleft = l;
	u_short *w = addr;
	u_short answer;
	int sum = 0;
	u_short odd_byte = 0;

	Fpush("in_cksum");
	/*
		 *  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 */
	Fpop;
	return(answer);
}
