xref: /original-bsd/sbin/ping/ping.c (revision 7d595439)
1 #ifndef lint
2 static char sccsid[] = "@(#)ping.c	4.6 (Berkeley) 10/30/86";
3 #endif
4 
5 /*
6  *			P I N G . C
7  *
8  * Using the InterNet Control Message Protocol (ICMP) "ECHO" facility,
9  * measure round-trip-delays and packet loss across network paths.
10  *
11  * Author -
12  *	Mike Muuss
13  *	U. S. Army Ballistic Research Laboratory
14  *	December, 1983
15  * Modified at Uc Berkeley
16  *
17  * Status -
18  *	Public Domain.  Distribution Unlimited.
19  *
20  * Bugs -
21  *	More statistics could always be gathered.
22  *	This program has to run SUID to ROOT to access the ICMP socket.
23  */
24 
25 #include <stdio.h>
26 #include <errno.h>
27 #include <sys/time.h>
28 
29 #include <sys/param.h>
30 #include <sys/socket.h>
31 #include <sys/file.h>
32 
33 #include <netinet/in_systm.h>
34 #include <netinet/in.h>
35 #include <netinet/ip.h>
36 #include <netinet/ip_icmp.h>
37 #include <netdb.h>
38 
39 #define	MAXWAIT		10	/* max time to wait for response, sec. */
40 #define	MAXPACKET	4096	/* max packet size */
41 #ifndef MAXHOSTNAMELEN
42 #define MAXHOSTNAMELEN	64
43 #endif
44 
45 int	verbose;
46 u_char	packet[MAXPACKET];
47 int	options;
48 extern	int errno;
49 
50 int s;			/* Socket file descriptor */
51 struct hostent *hp;	/* Pointer to host info */
52 struct timezone tz;	/* leftover */
53 
54 struct sockaddr whereto;/* Who to ping */
55 int datalen;		/* How much data */
56 
57 char usage[] = "Usage:  ping [-drv] host [data size] [npackets]\n";
58 
59 char *hostname;
60 char hnamebuf[MAXHOSTNAMELEN];
61 char *inet_ntoa();
62 
63 int npackets;
64 int ntransmitted = 0;		/* sequence # for outbound packets = #sent */
65 int ident;
66 
67 int nreceived = 0;		/* # of packets we got back */
68 int timing = 0;
69 int tmin = 999999999;
70 int tmax = 0;
71 int tsum = 0;			/* sum of all times, for doing average */
72 int finish(), catcher();
73 
74 /*
75  * 			M A I N
76  */
77 main(argc, argv)
78 char *argv[];
79 {
80 	struct sockaddr_in from;
81 	char **av = argv;
82 	char *toaddr = NULL;
83 	struct sockaddr_in *to = (struct sockaddr_in *) &whereto;
84 	int on = 1;
85 	struct protoent *proto;
86 
87 	argc--, av++;
88 	while (argc > 0 && *av[0] == '-') {
89 		while (*++av[0]) switch (*av[0]) {
90 			case 'd':
91 				options |= SO_DEBUG;
92 				break;
93 			case 'r':
94 				options |= SO_DONTROUTE;
95 				break;
96 			case 'v':
97 				verbose++;
98 				break;
99 		}
100 		argc--, av++;
101 	}
102 	if( argc < 1)  {
103 		printf(usage);
104 		exit(1);
105 	}
106 
107 	bzero( (char *)&whereto, sizeof(struct sockaddr) );
108 	to->sin_family = AF_INET;
109 	to->sin_addr.s_addr = inet_addr(av[0]);
110 	if (to->sin_addr.s_addr != -1) {
111 		strcpy(hnamebuf, av[0]);
112 		hostname = hnamebuf;
113 	} else {
114 		hp = gethostbyname(av[0]);
115 		if (hp) {
116 			to->sin_family = hp->h_addrtype;
117 			bcopy(hp->h_addr, (caddr_t)&to->sin_addr, hp->h_length);
118 			hostname = hp->h_name;
119 			toaddr = inet_ntoa(to->sin_addr.s_addr);
120 		} else {
121 			printf("%s: unknown host %s\n", argv[0], av[0]);
122 			exit(1);
123 		}
124 	}
125 
126 	if( argc >= 2 )
127 		datalen = atoi( av[1] );
128 	else
129 		datalen = 64-8;
130 	if (datalen > MAXPACKET) {
131 		fprintf(stderr, "ping: packet size too large\n");
132 		exit(1);
133 	}
134 	if (datalen >= sizeof(struct timeval))
135 		timing = 1;
136 	if (argc > 2)
137 		npackets = atoi(av[2]);
138 
139 	ident = getpid() & 0xFFFF;
140 
141 	if ((proto = getprotobyname("icmp")) == NULL) {
142 		fprintf(stderr, "icmp: unknown protocol\n");
143 		exit(10);
144 	}
145 	if ((s = socket(AF_INET, SOCK_RAW, proto->p_proto)) < 0) {
146 		perror("ping: socket");
147 		exit(5);
148 	}
149 	if (options & SO_DEBUG)
150 		setsockopt(s, SOL_SOCKET, SO_DEBUG, &on, sizeof(on));
151 	if (options & SO_DONTROUTE)
152 		setsockopt(s, SOL_SOCKET, SO_DONTROUTE, &on, sizeof(on));
153 
154 	printf("PING %s", hostname);
155 	if (toaddr)
156 		printf(" (%s)", toaddr);
157 	printf(": %d data bytes\n", datalen);
158 
159 
160 	setlinebuf( stdout );
161 
162 	signal( SIGINT, finish );
163 	signal(SIGALRM, catcher);
164 
165 	catcher();	/* start things going */
166 
167 	for (;;) {
168 		int len = sizeof (packet);
169 		int fromlen = sizeof (from);
170 		int cc;
171 
172 		if ( (cc=recvfrom(s, packet, len, 0, &from, &fromlen)) < 0) {
173 			if( errno == EINTR )
174 				continue;
175 			perror("ping: recvfrom");
176 			continue;
177 		}
178 		pr_pack( packet, cc, &from );
179 		if (npackets && nreceived >= npackets)
180 			finish();
181 	}
182 	/*NOTREACHED*/
183 }
184 
185 /*
186  * 			C A T C H E R
187  *
188  * This routine causes another PING to be transmitted, and then
189  * schedules another SIGALRM for 1 second from now.
190  *
191  * Bug -
192  * 	Our sense of time will slowly skew (ie, packets will not be launched
193  * 	exactly at 1-second intervals).  This does not affect the quality
194  *	of the delay and loss statistics.
195  */
196 catcher()
197 {
198 	int waittime;
199 
200 	pinger();
201 	if (npackets == 0 || ntransmitted < npackets)
202 		alarm(1);
203 	else {
204 		if (nreceived) {
205 			waittime = 2 * tmax / 1000;
206 			if (waittime == 0)
207 				waittime = 1;
208 		} else
209 			waittime = MAXWAIT;
210 		signal(SIGALRM, finish);
211 		alarm(waittime);
212 	}
213 }
214 
215 /*
216  * 			P I N G E R
217  *
218  * Compose and transmit an ICMP ECHO REQUEST packet.  The IP packet
219  * will be added on by the kernel.  The ID field is our UNIX process ID,
220  * and the sequence number is an ascending integer.  The first 8 bytes
221  * of the data portion are used to hold a UNIX "timeval" struct in VAX
222  * byte-order, to compute the round-trip time.
223  */
224 pinger()
225 {
226 	static u_char outpack[MAXPACKET];
227 	register struct icmp *icp = (struct icmp *) outpack;
228 	int i, cc;
229 	register struct timeval *tp = (struct timeval *) &outpack[8];
230 	register u_char *datap = &outpack[8+sizeof(struct timeval)];
231 
232 	icp->icmp_type = ICMP_ECHO;
233 	icp->icmp_code = 0;
234 	icp->icmp_cksum = 0;
235 	icp->icmp_seq = ntransmitted++;
236 	icp->icmp_id = ident;		/* ID */
237 
238 	cc = datalen+8;			/* skips ICMP portion */
239 
240 	if (timing)
241 		gettimeofday( tp, &tz );
242 
243 	for( i=8; i<datalen; i++)	/* skip 8 for time */
244 		*datap++ = i;
245 
246 	/* Compute ICMP checksum here */
247 	icp->icmp_cksum = in_cksum( icp, cc );
248 
249 	/* cc = sendto(s, msg, len, flags, to, tolen) */
250 	i = sendto( s, outpack, cc, 0, &whereto, sizeof(struct sockaddr) );
251 
252 	if( i < 0 || i != cc )  {
253 		if( i<0 )  perror("sendto");
254 		printf("ping: wrote %s %d chars, ret=%d\n",
255 			hostname, cc, i );
256 		fflush(stdout);
257 	}
258 }
259 
260 /*
261  * 			P R _ T Y P E
262  *
263  * Convert an ICMP "type" field to a printable string.
264  */
265 char *
266 pr_type( t )
267 register int t;
268 {
269 	static char *ttab[] = {
270 		"Echo Reply",
271 		"ICMP 1",
272 		"ICMP 2",
273 		"Dest Unreachable",
274 		"Source Quence",
275 		"Redirect",
276 		"ICMP 6",
277 		"ICMP 7",
278 		"Echo",
279 		"ICMP 9",
280 		"ICMP 10",
281 		"Time Exceeded",
282 		"Parameter Problem",
283 		"Timestamp",
284 		"Timestamp Reply",
285 		"Info Request",
286 		"Info Reply"
287 	};
288 
289 	if( t < 0 || t > 16 )
290 		return("OUT-OF-RANGE");
291 
292 	return(ttab[t]);
293 }
294 
295 /*
296  *			P R _ P A C K
297  *
298  * Print out the packet, if it came from us.  This logic is necessary
299  * because ALL readers of the ICMP socket get a copy of ALL ICMP packets
300  * which arrive ('tis only fair).  This permits multiple copies of this
301  * program to be run without having intermingled output (or statistics!).
302  */
303 pr_pack( buf, cc, from )
304 char *buf;
305 int cc;
306 struct sockaddr_in *from;
307 {
308 	struct ip *ip;
309 	register struct icmp *icp;
310 	register long *lp = (long *) packet;
311 	register int i;
312 	struct timeval tv;
313 	struct timeval *tp;
314 	int hlen, triptime;
315 
316 	from->sin_addr.s_addr = ntohl( from->sin_addr.s_addr );
317 	gettimeofday( &tv, &tz );
318 
319 	ip = (struct ip *) buf;
320 	hlen = ip->ip_hl << 2;
321 	if (cc < hlen + ICMP_MINLEN) {
322 		if (verbose)
323 			printf("packet too short (%d bytes) from %s\n", cc,
324 				inet_ntoa(ntohl(from->sin_addr.s_addr)));
325 		return;
326 	}
327 	cc -= hlen;
328 	icp = (struct icmp *)(buf + hlen);
329 	if( icp->icmp_type != ICMP_ECHOREPLY )  {
330 		if (verbose) {
331 			printf("%d bytes from %s: ", cc,
332 				inet_ntoa(ntohl(from->sin_addr.s_addr)));
333 			printf("icmp_type=%d (%s)\n",
334 				icp->icmp_type, pr_type(icp->icmp_type) );
335 			for( i=0; i<12; i++)
336 			    printf("x%2.2x: x%8.8x\n", i*sizeof(long), *lp++ );
337 			printf("icmp_code=%d\n", icp->icmp_code );
338 		}
339 		return;
340 	}
341 	if( icp->icmp_id != ident )
342 		return;			/* 'Twas not our ECHO */
343 
344 	tp = (struct timeval *)&icp->icmp_data[0];
345 	printf("%d bytes from %s: ", cc,
346 		inet_ntoa(ntohl(from->sin_addr.s_addr)));
347 	printf("icmp_seq=%d. ", icp->icmp_seq );
348 	if (timing) {
349 		tvsub( &tv, tp );
350 		triptime = tv.tv_sec*1000+(tv.tv_usec/1000);
351 		printf("time=%d. ms\n", triptime );
352 		tsum += triptime;
353 		if( triptime < tmin )
354 			tmin = triptime;
355 		if( triptime > tmax )
356 			tmax = triptime;
357 	} else
358 		putchar('\n');
359 	nreceived++;
360 }
361 
362 
363 /*
364  *			I N _ C K S U M
365  *
366  * Checksum routine for Internet Protocol family headers (C Version)
367  *
368  */
369 in_cksum(addr, len)
370 u_short *addr;
371 int len;
372 {
373 	register int nleft = len;
374 	register u_short *w = addr;
375 	register u_short answer;
376 	register int sum = 0;
377 
378 	/*
379 	 *  Our algorithm is simple, using a 32 bit accumulator (sum),
380 	 *  we add sequential 16 bit words to it, and at the end, fold
381 	 *  back all the carry bits from the top 16 bits into the lower
382 	 *  16 bits.
383 	 */
384 	while( nleft > 1 )  {
385 		sum += *w++;
386 		nleft -= 2;
387 	}
388 
389 	/* mop up an odd byte, if necessary */
390 	if( nleft == 1 )
391 		sum += *(u_char *)w;
392 
393 	/*
394 	 * add back carry outs from top 16 bits to low 16 bits
395 	 */
396 	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
397 	sum += (sum >> 16);			/* add carry */
398 	answer = ~sum;				/* truncate to 16 bits */
399 	return (answer);
400 }
401 
402 /*
403  * 			T V S U B
404  *
405  * Subtract 2 timeval structs:  out = out - in.
406  *
407  * Out is assumed to be >= in.
408  */
409 tvsub( out, in )
410 register struct timeval *out, *in;
411 {
412 	if( (out->tv_usec -= in->tv_usec) < 0 )   {
413 		out->tv_sec--;
414 		out->tv_usec += 1000000;
415 	}
416 	out->tv_sec -= in->tv_sec;
417 }
418 
419 /*
420  *			F I N I S H
421  *
422  * Print out statistics, and give up.
423  * Heavily buffered STDIO is used here, so that all the statistics
424  * will be written with 1 sys-write call.  This is nice when more
425  * than one copy of the program is running on a terminal;  it prevents
426  * the statistics output from becomming intermingled.
427  */
428 finish()
429 {
430 	printf("\n----%s PING Statistics----\n", hostname );
431 	printf("%d packets transmitted, ", ntransmitted );
432 	printf("%d packets received, ", nreceived );
433 	if (ntransmitted)
434 	    printf("%d%% packet loss",
435 		(int) (((ntransmitted-nreceived)*100) / ntransmitted ) );
436 	printf("\n");
437 	if (nreceived && timing)
438 	    printf("round-trip (ms)  min/avg/max = %d/%d/%d\n",
439 		tmin,
440 		tsum / nreceived,
441 		tmax );
442 	fflush(stdout);
443 	exit(0);
444 }
445