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