fixes from Yann E. MORIN <yann.morin.1998@anciens.enib.fr>
[oweals/busybox.git] / networking / ping.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * $Id: ping.c,v 1.56 2004/03/15 08:28:48 andersen Exp $
4  * Mini ping implementation for busybox
5  *
6  * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
7  *
8  * Adapted from the ping in netkit-base 0.10:
9  * Copyright (c) 1989 The Regents of the University of California.
10  * Derived from software contributed to Berkeley by Mike Muuss.
11  *
12  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
13  */
14
15 #include <sys/param.h>
16 #include <sys/socket.h>
17 #include <sys/file.h>
18 #include <sys/times.h>
19 #include <signal.h>
20
21 #include <netinet/in.h>
22 #include <netinet/ip.h>
23 #include <netinet/ip_icmp.h>
24 #include <arpa/inet.h>
25 #include <netdb.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include "busybox.h"
33
34 enum {
35         DEFDATALEN = 56,
36         MAXIPLEN = 60,
37         MAXICMPLEN = 76,
38         MAXPACKET = 65468,
39         MAX_DUP_CHK = (8 * 128),
40         MAXWAIT = 10,
41         PINGINTERVAL = 1                /* second */
42 };
43
44 static void ping(const char *host);
45
46 /* common routines */
47
48 static int in_cksum(unsigned short *buf, int sz)
49 {
50         int nleft = sz;
51         int sum = 0;
52         unsigned short *w = buf;
53         unsigned short ans = 0;
54
55         while (nleft > 1) {
56                 sum += *w++;
57                 nleft -= 2;
58         }
59
60         if (nleft == 1) {
61                 *(unsigned char *) (&ans) = *(unsigned char *) w;
62                 sum += ans;
63         }
64
65         sum = (sum >> 16) + (sum & 0xFFFF);
66         sum += (sum >> 16);
67         ans = ~sum;
68         return ans;
69 }
70
71 #ifndef CONFIG_FEATURE_FANCY_PING
72
73 /* simple version */
74
75 static char *hostname;
76
77 static void noresp(int ign)
78 {
79         printf("No response from %s\n", hostname);
80         exit(EXIT_FAILURE);
81 }
82
83 static void ping(const char *host)
84 {
85         struct hostent *h;
86         struct sockaddr_in pingaddr;
87         struct icmp *pkt;
88         int pingsock, c;
89         char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
90
91         pingsock = create_icmp_socket();
92
93         memset(&pingaddr, 0, sizeof(struct sockaddr_in));
94
95         pingaddr.sin_family = AF_INET;
96         h = xgethostbyname(host);
97         memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr));
98         hostname = h->h_name;
99
100         pkt = (struct icmp *) packet;
101         memset(pkt, 0, sizeof(packet));
102         pkt->icmp_type = ICMP_ECHO;
103         pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet));
104
105         c = sendto(pingsock, packet, DEFDATALEN + ICMP_MINLEN, 0,
106                            (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in));
107
108         if (c < 0) {
109                 if (ENABLE_FEATURE_CLEAN_UP) close(pingsock);
110                 bb_perror_msg_and_die("sendto");
111         }
112
113         signal(SIGALRM, noresp);
114         alarm(5);                                       /* give the host 5000ms to respond */
115         /* listen for replies */
116         while (1) {
117                 struct sockaddr_in from;
118                 socklen_t fromlen = sizeof(from);
119
120                 c = recvfrom(pingsock, packet, sizeof(packet), 0,
121                                 (struct sockaddr *) &from, &fromlen);
122                 if (c < 0) {
123                         if (errno == EINTR)
124                                 continue;
125                         bb_perror_msg("recvfrom");
126                         continue;
127                 }
128                 if (c >= 76) {                  /* ip + icmp */
129                         struct iphdr *iphdr = (struct iphdr *) packet;
130
131                         pkt = (struct icmp *) (packet + (iphdr->ihl << 2));     /* skip ip hdr */
132                         if (pkt->icmp_type == ICMP_ECHOREPLY)
133                                 break;
134                 }
135         }
136         if (ENABLE_FEATURE_CLEAN_UP) close(pingsock);
137         printf("%s is alive!\n", hostname);
138         return;
139 }
140
141 int ping_main(int argc, char **argv)
142 {
143         argc--;
144         argv++;
145         if (argc < 1)
146                 bb_show_usage();
147         ping(*argv);
148         return EXIT_SUCCESS;
149 }
150
151 #else /* ! CONFIG_FEATURE_FANCY_PING */
152
153 /* full(er) version */
154
155 #define OPT_STRING "qc:s:I:"
156 enum {
157         OPT_QUIET = 1 << 0,
158 };
159
160 static struct sockaddr_in pingaddr;
161 static struct sockaddr_in sourceaddr;
162 static int pingsock = -1;
163 static unsigned datalen; /* intentionally uninitialized to work around gcc bug */
164
165 static unsigned long ntransmitted, nreceived, nrepeats, pingcount;
166 static int myid;
167 static unsigned long tmin = ULONG_MAX, tmax, tsum;
168 static char rcvd_tbl[MAX_DUP_CHK / 8];
169
170 static struct hostent *hostent;
171
172 static void sendping(int);
173 static void pingstats(int);
174 static void unpack(char *, int, struct sockaddr_in *);
175
176 #define A(bit)          rcvd_tbl[(bit)>>3]      /* identify byte in array */
177 #define B(bit)          (1 << ((bit) & 0x07))   /* identify bit in byte */
178 #define SET(bit)        (A(bit) |= B(bit))
179 #define CLR(bit)        (A(bit) &= (~B(bit)))
180 #define TST(bit)        (A(bit) & B(bit))
181
182 /**************************************************************************/
183
184 static void pingstats(int junk)
185 {
186         int status;
187
188         signal(SIGINT, SIG_IGN);
189
190         printf("\n--- %s ping statistics ---\n", hostent->h_name);
191         printf("%lu packets transmitted, ", ntransmitted);
192         printf("%lu packets received, ", nreceived);
193         if (nrepeats)
194                 printf("%lu duplicates, ", nrepeats);
195         if (ntransmitted)
196                 printf("%lu%% packet loss\n",
197                            (ntransmitted - nreceived) * 100 / ntransmitted);
198         if (nreceived)
199                 printf("round-trip min/avg/max = %lu.%lu/%lu.%lu/%lu.%lu ms\n",
200                            tmin / 10, tmin % 10,
201                            (tsum / (nreceived + nrepeats)) / 10,
202                            (tsum / (nreceived + nrepeats)) % 10, tmax / 10, tmax % 10);
203         if (nreceived != 0)
204                 status = EXIT_SUCCESS;
205         else
206                 status = EXIT_FAILURE;
207         exit(status);
208 }
209
210 static void sendping(int junk)
211 {
212         struct icmp *pkt;
213         int i;
214         char packet[datalen + ICMP_MINLEN];
215
216         pkt = (struct icmp *) packet;
217
218         pkt->icmp_type = ICMP_ECHO;
219         pkt->icmp_code = 0;
220         pkt->icmp_cksum = 0;
221         pkt->icmp_seq = htons(ntransmitted++);
222         pkt->icmp_id = myid;
223         CLR(ntohs(pkt->icmp_seq) % MAX_DUP_CHK);
224
225         gettimeofday((struct timeval *) &pkt->icmp_dun, NULL);
226         pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet));
227
228         i = sendto(pingsock, packet, sizeof(packet), 0,
229                            (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in));
230
231         if (i < 0)
232                 bb_perror_msg_and_die("sendto");
233         else if ((size_t)i != sizeof(packet))
234                 bb_error_msg_and_die("ping wrote %d chars; %d expected", i,
235                            (int)sizeof(packet));
236
237         signal(SIGALRM, sendping);
238         if (pingcount == 0 || ntransmitted < pingcount) {       /* schedule next in 1s */
239                 alarm(PINGINTERVAL);
240         } else {                                        /* done, wait for the last ping to come back */
241                 /* todo, don't necessarily need to wait so long... */
242                 signal(SIGALRM, pingstats);
243                 alarm(MAXWAIT);
244         }
245 }
246
247 static char *icmp_type_name(int id)
248 {
249         switch (id) {
250         case ICMP_ECHOREPLY:            return "Echo Reply";
251         case ICMP_DEST_UNREACH:         return "Destination Unreachable";
252         case ICMP_SOURCE_QUENCH:        return "Source Quench";
253         case ICMP_REDIRECT:                     return "Redirect (change route)";
254         case ICMP_ECHO:                         return "Echo Request";
255         case ICMP_TIME_EXCEEDED:        return "Time Exceeded";
256         case ICMP_PARAMETERPROB:        return "Parameter Problem";
257         case ICMP_TIMESTAMP:            return "Timestamp Request";
258         case ICMP_TIMESTAMPREPLY:       return "Timestamp Reply";
259         case ICMP_INFO_REQUEST:         return "Information Request";
260         case ICMP_INFO_REPLY:           return "Information Reply";
261         case ICMP_ADDRESS:                      return "Address Mask Request";
262         case ICMP_ADDRESSREPLY:         return "Address Mask Reply";
263         default:                                        return "unknown ICMP type";
264         }
265 }
266
267 static void unpack(char *buf, int sz, struct sockaddr_in *from)
268 {
269         struct icmp *icmppkt;
270         struct iphdr *iphdr;
271         struct timeval tv, *tp;
272         int hlen, dupflag;
273         unsigned long triptime;
274
275         gettimeofday(&tv, NULL);
276
277         /* discard if too short */
278         if (sz < (datalen + ICMP_MINLEN))
279                 return;
280
281         /* check IP header */
282         iphdr = (struct iphdr *) buf;
283         hlen = iphdr->ihl << 2;
284         sz -= hlen;
285         icmppkt = (struct icmp *) (buf + hlen);
286         if (icmppkt->icmp_id != myid)
287                 return;                         /* not our ping */
288
289         if (icmppkt->icmp_type == ICMP_ECHOREPLY) {
290                 u_int16_t recv_seq = ntohs(icmppkt->icmp_seq);
291                 ++nreceived;
292                 tp = (struct timeval *) icmppkt->icmp_data;
293
294                 if ((tv.tv_usec -= tp->tv_usec) < 0) {
295                         --tv.tv_sec;
296                         tv.tv_usec += 1000000;
297                 }
298                 tv.tv_sec -= tp->tv_sec;
299
300                 triptime = tv.tv_sec * 10000 + (tv.tv_usec / 100);
301                 tsum += triptime;
302                 if (triptime < tmin)
303                         tmin = triptime;
304                 if (triptime > tmax)
305                         tmax = triptime;
306
307                 if (TST(recv_seq % MAX_DUP_CHK)) {
308                         ++nrepeats;
309                         --nreceived;
310                         dupflag = 1;
311                 } else {
312                         SET(recv_seq % MAX_DUP_CHK);
313                         dupflag = 0;
314                 }
315
316                 if (option_mask32 & OPT_QUIET)
317                         return;
318
319                 printf("%d bytes from %s: icmp_seq=%u", sz,
320                            inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr),
321                            recv_seq);
322                 printf(" ttl=%d", iphdr->ttl);
323                 printf(" time=%lu.%lu ms", triptime / 10, triptime % 10);
324                 if (dupflag)
325                         printf(" (DUP!)");
326                 puts("");
327         } else
328                 if (icmppkt->icmp_type != ICMP_ECHO)
329                         bb_error_msg("warning: got ICMP %d (%s)",
330                                         icmppkt->icmp_type, icmp_type_name(icmppkt->icmp_type));
331         fflush(stdout);
332 }
333
334 static void ping(const char *host)
335 {
336         char packet[datalen + MAXIPLEN + MAXICMPLEN];
337         int sockopt;
338
339         pingsock = create_icmp_socket();
340
341         if (sourceaddr.sin_addr.s_addr) {
342                 xbind(pingsock, (struct sockaddr*)&sourceaddr, sizeof(sourceaddr));
343         }
344
345         memset(&pingaddr, 0, sizeof(struct sockaddr_in));
346
347         pingaddr.sin_family = AF_INET;
348         hostent = xgethostbyname(host);
349         if (hostent->h_addrtype != AF_INET)
350                 bb_error_msg_and_die("unknown address type; only AF_INET is currently supported");
351
352         memcpy(&pingaddr.sin_addr, hostent->h_addr, sizeof(pingaddr.sin_addr));
353
354         /* enable broadcast pings */
355         setsockopt_broadcast(pingsock);
356
357         /* set recv buf for broadcast pings */
358         sockopt = 48 * 1024;
359         setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, (char *) &sockopt,
360                            sizeof(sockopt));
361
362         printf("PING %s (%s)",
363                         hostent->h_name,
364                         inet_ntoa(*(struct in_addr *) &pingaddr.sin_addr.s_addr));
365         if (sourceaddr.sin_addr.s_addr) {
366                 printf(" from %s",
367                         inet_ntoa(*(struct in_addr *) &sourceaddr.sin_addr.s_addr));
368         }
369         printf(": %d data bytes\n", datalen);
370
371         signal(SIGINT, pingstats);
372
373         /* start the ping's going ... */
374         sendping(0);
375
376         /* listen for replies */
377         while (1) {
378                 struct sockaddr_in from;
379                 socklen_t fromlen = (socklen_t) sizeof(from);
380                 int c;
381
382                 if ((c = recvfrom(pingsock, packet, sizeof(packet), 0,
383                                                   (struct sockaddr *) &from, &fromlen)) < 0) {
384                         if (errno == EINTR)
385                                 continue;
386                         bb_perror_msg("recvfrom");
387                         continue;
388                 }
389                 unpack(packet, c, &from);
390                 if (pingcount > 0 && nreceived >= pingcount)
391                         break;
392         }
393         pingstats(0);
394 }
395
396 /* TODO: consolidate ether-wake.c, dnsd.c, ifupdown.c, nslookup.c
397  * versions of below thing. BTW we have far too many "%u.%u.%u.%u" too...
398 */
399 static int parse_nipquad(const char *str, struct sockaddr_in* addr)
400 {
401         char dummy;
402         unsigned i1, i2, i3, i4;
403         if (sscanf(str, "%u.%u.%u.%u%c",
404                            &i1, &i2, &i3, &i4, &dummy) == 4
405         && ( (i1|i2|i3|i4) <= 0xff )
406         ) {
407                 uint8_t* ptr = (uint8_t*)&addr->sin_addr;
408                 ptr[0] = i1;
409                 ptr[1] = i2;
410                 ptr[2] = i3;
411                 ptr[3] = i4;
412                 return 0;
413         }
414         return 1; /* error */
415 }
416
417 int ping_main(int argc, char **argv)
418 {
419         char *opt_c, *opt_s, *opt_I;
420
421         datalen = DEFDATALEN; /* initialized here rather than in global scope to work around gcc bug */
422
423         /* exactly one argument needed */
424         opt_complementary = "=1";
425         getopt32(argc, argv, OPT_STRING, &opt_c, &opt_s, &opt_I);
426         if (option_mask32 & 2) pingcount = xatoul(opt_c); // -c
427         if (option_mask32 & 4) datalen = xatou16(opt_s); // -s
428         if (option_mask32 & 8) { // -I
429 /* TODO: ping6 accepts iface too:
430                 if_index = if_nametoindex(*argv);
431                 if (!if_index) ...
432 make it true for ping. */
433                 if (parse_nipquad(opt_I, &sourceaddr))
434                         bb_show_usage();
435         }
436
437         myid = (int16_t) getpid();
438         ping(argv[optind]);
439         return EXIT_SUCCESS;
440 }
441 #endif /* ! CONFIG_FEATURE_FANCY_PING */