cleanups and mods from Muus
[unix-history] / usr / src / sbin / ping / ping.c
#ifndef lint
static char sccsid[] = "@(#)ping.c 4.3 (Berkeley) %G%";
#endif
/*
* P I N G . C
*
* Using the InterNet Control Message Protocol (ICMP) "ECHO" facility,
* measure round-trip-delays and packet loss across network paths.
*
* Author -
* Mike Muuss
* U. S. Army Ballistic Research Laboratory
* December, 1983
* Modified at Uc Berkeley
*
* Status -
* Public Domain. Distribution Unlimited.
*
* Bugs -
* More statistics could always be gathered.
* This program has to run SUID to ROOT to access the ICMP socket.
*/
#include <stdio.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#define MAXWAIT 10 /* max time to wait for response, sec. */
char ttyobuf[4096];
int verbose;
u_char packet[1024];
int options;
extern int errno;
int s; /* Socket file descriptor */
struct hostent *hp; /* Pointer to host info */
struct timezone tz; /* leftover */
struct sockaddr whereto;/* Who to ping */
int datalen; /* How much data */
char usage[] = "Usage: ping [-drv] host [data size]\n";
char *hostname;
char hnamebuf[64];
int npackets;
int ntransmitted = 0; /* sequence # for outbound packets = #sent */
int ident;
int nreceived = 0; /* # of packets we got back */
int timing = 0;
int tmin = 999999999;
int tmax = 0;
int tsum = 0; /* sum of all times, for doing average */
int finish(), catcher();
/*
* M A I N
*/
main(argc, argv)
char *argv[];
{
struct sockaddr_in from;
char **av = argv;
struct sockaddr_in *to = (struct sockaddr_in *) &whereto;
int on = 1;
argc--, av++;
while (argc > 0 && *av[0] == '-') {
while (*++av[0]) switch (*av[0]) {
case 'd':
options |= SO_DEBUG;
break;
case 'r':
options |= SO_DONTROUTE;
break;
case 'v':
verbose++;
break;
}
argc--, av++;
}
if( argc < 1) {
printf(usage);
exit(1);
}
bzero( (char *)&whereto, sizeof(struct sockaddr) );
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 = hp->h_name;
} else {
to->sin_family = AF_INET;
to->sin_addr.s_addr = inet_addr(av[1]);
if (to->sin_addr.s_addr == -1) {
printf("%s: unknown host %s\n", argv[0], av[1]);
return;
}
strcpy(hnamebuf, av[1]);
hostname = hnamebuf;
}
if( argc >= 2 )
datalen = atoi( av[1] );
else
datalen = 64-8;
if (datalen >= sizeof(struct timeval))
timing = 1;
if (argc > 2)
npackets = atoi(av[2]);
ident = getpid() & 0xFFFF;
if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 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));
printf("PING %s: %d data bytes\n", hostname, datalen );
setbuffer( stdout, ttyobuf, sizeof(ttyobuf) );
signal( SIGINT, finish );
signal(SIGALRM, catcher);
catcher(); /* start things going */
for (;;) {
int len = sizeof (packet);
int fromlen = sizeof (from);
int cc;
if ( (cc=recvfrom(s, packet, len, 0, &from, &fromlen)) < 0) {
if( errno == EINTR )
continue;
perror("ping: recvfrom");
continue;
}
pr_pack( packet, cc, &from );
if (npackets && nreceived >= npackets)
finish();
}
/*NOTREACHED*/
}
/*
* C A T C H E R
*
* This routine causes another PING to be transmitted, and then
* schedules another SIGALRM for 1 second from now.
*
* Bug -
* Our sense of time will slowly skew (ie, packets will not be launched
* exactly at 1-second intervals). This does not affect the quality
* of the delay and loss statistics.
*/
catcher()
{
int waittime;
pinger();
if (npackets == 0 || ntransmitted < npackets)
alarm(1);
else {
if (nreceived) {
waittime = 2 * tmax / 1000;
if (waittime == 0)
waittime = 1;
} else
waittime = MAXWAIT;
signal(SIGALRM, finish);
alarm(waittime);
}
}
/*
* P I N G E R
*
* 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()
{
static u_char outpack[1024];
register struct icmp *icp = (struct icmp *) outpack;
int i, cc;
register struct timeval *tp = (struct timeval *) &outpack[8];
register u_char *datap = &outpack[8+sizeof(struct timeval)];
icp->icmp_type = ICMP_ECHO;
icp->icmp_code = 0;
icp->icmp_cksum = 0;
icp->icmp_seq = ntransmitted++;
icp->icmp_id = ident; /* ID */
cc = datalen+8; /* skips ICMP portion */
if (timing)
gettimeofday( tp, &tz );
for( i=8; i<datalen; i++) /* skip 8 for time */
*datap++ = i;
/* Compute ICMP checksum here */
icp->icmp_cksum = in_cksum( icp, cc );
/* cc = sendto(s, msg, len, flags, to, tolen) */
i = sendto( s, outpack, cc, 0, &whereto, sizeof(struct sockaddr) );
if( i < 0 || i != cc ) {
if( i<0 ) perror("sendto");
printf("ping: wrote %s %d chars, ret=%d\n",
hostname, cc, i );
fflush(stdout);
}
}
/*
* P R _ T Y P E
*
* Convert an ICMP "type" field to a printable string.
*/
char *
pr_type( t )
register int t;
{
static char *ttab[] = {
"Echo Reply",
"ICMP 1",
"ICMP 2",
"Dest Unreachable",
"Source Quence",
"Redirect",
"ICMP 6",
"ICMP 7",
"Echo",
"ICMP 9",
"ICMP 10",
"Time Exceeded",
"Parameter Problem",
"Timestamp",
"Timestamp Reply",
"Info Request",
"Info Reply"
};
if( t < 0 || t > 16 )
return("OUT-OF-RANGE");
return(ttab[t]);
}
/*
* P R _ P A C K
*
* 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( icp, cc, from )
register struct icmp *icp;
int cc;
struct sockaddr_in *from;
{
register long *lp = (long *) packet;
register int i;
struct timeval tv;
struct timeval *tp = (struct timeval *) &packet[8];
int triptime;
from->sin_addr.s_addr = ntohl( from->sin_addr.s_addr );
gettimeofday( &tv, &tz );
if( icp->icmp_type != ICMP_ECHOREPLY ) {
if (verbose) {
printf("%d bytes from x%x: ", cc, from->sin_addr.s_addr);
printf("icmp_type=%d (%s)\n",
icp->icmp_type, pr_type(icp->icmp_type) );
for( i=0; i<12; i++)
printf("x%2.2x: x%8.8x\n", i*sizeof(long), *lp++ );
printf("icmp_code=%d\n", icp->icmp_code );
fflush(stdout);
}
return;
}
if( icp->icmp_id != ident )
return; /* 'Twas not our ECHO */
printf("%d bytes from x%x: ", cc, from->sin_addr.s_addr);
printf("icmp_seq=%d. ", icp->icmp_seq );
if (timing) {
tvsub( &tv, tp );
triptime = tv.tv_sec*1000+(tv.tv_usec/1000);
printf("time=%d. ms\n", triptime );
tsum += triptime;
if( triptime < tmin )
tmin = triptime;
if( triptime > tmax )
tmax = triptime;
} else
putchar('\n');
nreceived++;
fflush(stdout);
}
/*
* I N _ C K S U M
*
* Checksum routine for Internet Protocol family headers (VAX Version).
*
* Shamelessly pilfered from /sys/vax/in_cksum.c, with all the MBUF stuff
* ripped out.
*/
#if vax
in_cksum(addr, len)
u_short *addr;
int len;
{
register int nleft = len; /* on vax, (user mode), r11 */
register int xxx; /* on vax, (user mode), r10 */
register u_short *w = addr; /* on vax, known to be r9 */
register int sum = 0; /* on vax, known to be r8 */
/*
* Force to long boundary so we do longword aligned
* memory operations. It is too hard to do byte
* adjustment, do only word adjustment.
*/
if (((int)w&0x2) && nleft >= 2) {
sum += *w++;
nleft -= 2;
}
/*
* Do as much of the checksum as possible 32 bits at at time.
* In fact, this loop is unrolled to make overhead from
* branches &c small.
*
* We can do a 16 bit ones complement sum 32 bits at a time
* because the 32 bit register is acting as two 16 bit
* registers for adding, with carries from the low added
* into the high (by normal carry-chaining) and carries
* from the high carried into the low on the next word
* by use of the adwc instruction. This lets us run
* this loop at almost memory speed.
*
* Here there is the danger of high order carry out, and
* we carefully use adwc.
*/
while ((nleft -= 32) >= 0) {
#undef ADD
asm("clrl r0"); /* clears carry */
#define ADD asm("adwc (r9)+,r8;");
ADD; ADD; ADD; ADD; ADD; ADD; ADD; ADD;
asm("adwc $0,r8");
}
nleft += 32;
while ((nleft -= 8) >= 0) {
asm("clrl r0");
ADD; ADD;
asm("adwc $0,r8");
}
nleft += 8;
/*
* Now eliminate the possibility of carry-out's by
* folding back to a 16 bit number (adding high and
* low parts together.) Then mop up trailing words
* and maybe an odd byte.
*/
{ asm("ashl $-16,r8,r0; addw2 r0,r8");
asm("adwc $0,r8; movzwl r8,r8"); }
while ((nleft -= 2) >= 0) {
asm("movzwl (r9)+,r0; addl2 r0,r8");
}
if (nleft == -1) {
sum += *(u_char *)w;
}
/*
* Add together high and low parts of sum
* and carry to get cksum.
* Have to be careful to not drop the last
* carry here.
*/
{ asm("ashl $-16,r8,r0; addw2 r0,r8; adwc $0,r8");
asm("mcoml r8,r8; movzwl r8,r8"); }
return (sum);
}
#endif vax
/*
* T V S U B
*
* Subtract 2 timeval structs: out = out - in.
*
* Out is assumed to be >= in.
*/
tvsub( out, in )
register struct timeval *out, *in;
{
if( (out->tv_usec -= in->tv_usec) < 0 ) {
out->tv_sec--;
out->tv_usec += 1000000;
}
out->tv_sec -= in->tv_sec;
}
/*
* F I N I S H
*
* 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.
*/
finish()
{
printf("\n----%s PING Statistics----\n", hostname );
printf("%d packets transmitted, ", ntransmitted );
printf("%d packets received, ", nreceived );
if (ntransmitted)
printf("%d%% packet loss",
(int) (((ntransmitted-nreceived)*100) / ntransmitted ) );
printf("\n");
if (nreceived && timing)
printf("round-trip (ms) min/avg/max = %d/%d/%d\n",
tmin,
tsum / nreceived,
tmax );
fflush(stdout);
exit(0);
}