blob: d541b796d8fc72290176e891c1268c9b1ca38387
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Mini ping implementation for busybox |
4 | * |
5 | * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> |
6 | * |
7 | * Adapted from the ping in netkit-base 0.10: |
8 | * Copyright (c) 1989 The Regents of the University of California. |
9 | * All rights reserved. |
10 | * |
11 | * This code is derived from software contributed to Berkeley by |
12 | * Mike Muuss. |
13 | * |
14 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
15 | */ |
16 | /* from ping6.c: |
17 | * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> |
18 | * |
19 | * This version of ping is adapted from the ping in netkit-base 0.10, |
20 | * which is: |
21 | * |
22 | * Original copyright notice is retained at the end of this file. |
23 | * |
24 | * This version is an adaptation of ping.c from busybox. |
25 | * The code was modified by Bart Visscher <magick@linux-fan.com> |
26 | */ |
27 | |
28 | #include <net/if.h> |
29 | #include <netinet/ip_icmp.h> |
30 | #include "libbb.h" |
31 | #include "common_bufsiz.h" |
32 | |
33 | #ifdef __BIONIC__ |
34 | /* should be in netinet/ip_icmp.h */ |
35 | # define ICMP_DEST_UNREACH 3 /* Destination Unreachable */ |
36 | # define ICMP_SOURCE_QUENCH 4 /* Source Quench */ |
37 | # define ICMP_REDIRECT 5 /* Redirect (change route) */ |
38 | # define ICMP_ECHO 8 /* Echo Request */ |
39 | # define ICMP_TIME_EXCEEDED 11 /* Time Exceeded */ |
40 | # define ICMP_PARAMETERPROB 12 /* Parameter Problem */ |
41 | # define ICMP_TIMESTAMP 13 /* Timestamp Request */ |
42 | # define ICMP_TIMESTAMPREPLY 14 /* Timestamp Reply */ |
43 | # define ICMP_INFO_REQUEST 15 /* Information Request */ |
44 | # define ICMP_INFO_REPLY 16 /* Information Reply */ |
45 | # define ICMP_ADDRESS 17 /* Address Mask Request */ |
46 | # define ICMP_ADDRESSREPLY 18 /* Address Mask Reply */ |
47 | #endif |
48 | |
49 | //config:config PING |
50 | //config: bool "ping" |
51 | //config: default y |
52 | //config: select PLATFORM_LINUX |
53 | //config: help |
54 | //config: ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to |
55 | //config: elicit an ICMP ECHO_RESPONSE from a host or gateway. |
56 | //config: |
57 | //config:config PING6 |
58 | //config: bool "ping6" |
59 | //config: default y |
60 | //config: depends on FEATURE_IPV6 |
61 | //config: help |
62 | //config: This will give you a ping that can talk IPv6. |
63 | //config: |
64 | //config:config FEATURE_FANCY_PING |
65 | //config: bool "Enable fancy ping output" |
66 | //config: default y |
67 | //config: depends on PING || PING6 |
68 | //config: help |
69 | //config: Make the output from the ping applet include statistics, and at the |
70 | //config: same time provide full support for ICMP packets. |
71 | |
72 | /* Needs socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), therefore BB_SUID_MAYBE: */ |
73 | //applet:IF_PING(APPLET(ping, BB_DIR_BIN, BB_SUID_MAYBE)) |
74 | //applet:IF_PING6(APPLET(ping6, BB_DIR_BIN, BB_SUID_MAYBE)) |
75 | |
76 | //kbuild:lib-$(CONFIG_PING) += ping.o |
77 | //kbuild:lib-$(CONFIG_PING6) += ping.o |
78 | |
79 | //usage:#if !ENABLE_FEATURE_FANCY_PING |
80 | //usage:# define ping_trivial_usage |
81 | //usage: "HOST" |
82 | //usage:# define ping_full_usage "\n\n" |
83 | //usage: "Send ICMP ECHO_REQUEST packets to network hosts" |
84 | //usage:# define ping6_trivial_usage |
85 | //usage: "HOST" |
86 | //usage:# define ping6_full_usage "\n\n" |
87 | //usage: "Send ICMP ECHO_REQUEST packets to network hosts" |
88 | //usage:#else |
89 | //usage:# define ping_trivial_usage |
90 | //usage: "[OPTIONS] HOST" |
91 | //usage:# define ping_full_usage "\n\n" |
92 | //usage: "Send ICMP ECHO_REQUEST packets to network hosts\n" |
93 | //usage: IF_PING6( |
94 | //usage: "\n -4,-6 Force IP or IPv6 name resolution" |
95 | //usage: ) |
96 | //usage: "\n -c CNT Send only CNT pings" |
97 | //usage: "\n -s SIZE Send SIZE data bytes in packets (default:56)" |
98 | //usage: "\n -t TTL Set TTL" |
99 | //usage: "\n -I IFACE/IP Use interface or IP address as source" |
100 | //usage: "\n -W SEC Seconds to wait for the first response (default:10)" |
101 | //usage: "\n (after all -c CNT packets are sent)" |
102 | //usage: "\n -w SEC Seconds until ping exits (default:infinite)" |
103 | //usage: "\n (can exit earlier with -c CNT)" |
104 | //usage: "\n -q Quiet, only display output at start" |
105 | //usage: "\n and when finished" |
106 | //usage: "\n -p Pattern to use for payload" |
107 | //usage: |
108 | //usage:# define ping6_trivial_usage |
109 | //usage: "[OPTIONS] HOST" |
110 | //usage:# define ping6_full_usage "\n\n" |
111 | //usage: "Send ICMP ECHO_REQUEST packets to network hosts\n" |
112 | //usage: "\n -c CNT Send only CNT pings" |
113 | //usage: "\n -s SIZE Send SIZE data bytes in packets (default:56)" |
114 | //usage: "\n -I IFACE/IP Use interface or IP address as source" |
115 | //usage: "\n -q Quiet, only display output at start" |
116 | //usage: "\n and when finished" |
117 | //usage: "\n -p Pattern to use for payload" |
118 | //usage: |
119 | //usage:#endif |
120 | //usage: |
121 | //usage:#define ping_example_usage |
122 | //usage: "$ ping localhost\n" |
123 | //usage: "PING slag (127.0.0.1): 56 data bytes\n" |
124 | //usage: "64 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=20.1 ms\n" |
125 | //usage: "\n" |
126 | //usage: "--- debian ping statistics ---\n" |
127 | //usage: "1 packets transmitted, 1 packets received, 0% packet loss\n" |
128 | //usage: "round-trip min/avg/max = 20.1/20.1/20.1 ms\n" |
129 | //usage:#define ping6_example_usage |
130 | //usage: "$ ping6 ip6-localhost\n" |
131 | //usage: "PING ip6-localhost (::1): 56 data bytes\n" |
132 | //usage: "64 bytes from ::1: icmp6_seq=0 ttl=64 time=20.1 ms\n" |
133 | //usage: "\n" |
134 | //usage: "--- ip6-localhost ping statistics ---\n" |
135 | //usage: "1 packets transmitted, 1 packets received, 0% packet loss\n" |
136 | //usage: "round-trip min/avg/max = 20.1/20.1/20.1 ms\n" |
137 | |
138 | #if ENABLE_PING6 |
139 | # include <netinet/icmp6.h> |
140 | /* I see RENUMBERED constants in bits/in.h - !!? |
141 | * What a fuck is going on with libc? Is it a glibc joke? */ |
142 | # ifdef IPV6_2292HOPLIMIT |
143 | # undef IPV6_HOPLIMIT |
144 | # define IPV6_HOPLIMIT IPV6_2292HOPLIMIT |
145 | # endif |
146 | #endif |
147 | |
148 | enum { |
149 | DEFDATALEN = 56, |
150 | MAXIPLEN = 60, |
151 | MAXICMPLEN = 76, |
152 | MAX_DUP_CHK = (8 * 128), |
153 | MAXWAIT = 10, |
154 | PINGINTERVAL = 1, /* 1 second */ |
155 | pingsock = 0, |
156 | }; |
157 | |
158 | static void |
159 | #if ENABLE_PING6 |
160 | create_icmp_socket(len_and_sockaddr *lsa) |
161 | #else |
162 | create_icmp_socket(void) |
163 | #define create_icmp_socket(lsa) create_icmp_socket() |
164 | #endif |
165 | { |
166 | int sock; |
167 | #if ENABLE_PING6 |
168 | if (lsa->u.sa.sa_family == AF_INET6) |
169 | sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); |
170 | else |
171 | #endif |
172 | sock = socket(AF_INET, SOCK_RAW, 1); /* 1 == ICMP */ |
173 | if (sock < 0) { |
174 | if (errno == EPERM) |
175 | bb_error_msg_and_die("%s", bb_msg_perm_denied_are_you_root); |
176 | bb_perror_msg_and_die("%s", bb_msg_can_not_create_raw_socket); |
177 | } |
178 | |
179 | xmove_fd(sock, pingsock); |
180 | } |
181 | |
182 | #if !ENABLE_FEATURE_FANCY_PING |
183 | |
184 | /* Simple version */ |
185 | |
186 | struct globals { |
187 | char *hostname; |
188 | char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; |
189 | uint16_t myid; |
190 | } FIX_ALIASING; |
191 | #define G (*(struct globals*)bb_common_bufsiz1) |
192 | #define INIT_G() do { setup_common_bufsiz(); } while (0) |
193 | |
194 | static void noresp(int ign UNUSED_PARAM) |
195 | { |
196 | printf("No response from %s\n", G.hostname); |
197 | exit(EXIT_FAILURE); |
198 | } |
199 | |
200 | static void ping4(len_and_sockaddr *lsa) |
201 | { |
202 | struct icmp *pkt; |
203 | int c; |
204 | |
205 | pkt = (struct icmp *) G.packet; |
206 | /*memset(pkt, 0, sizeof(G.packet)); already is */ |
207 | pkt->icmp_type = ICMP_ECHO; |
208 | pkt->icmp_id = G.myid; |
209 | pkt->icmp_cksum = inet_cksum((uint16_t *) pkt, sizeof(G.packet)); |
210 | |
211 | xsendto(pingsock, G.packet, DEFDATALEN + ICMP_MINLEN, &lsa->u.sa, lsa->len); |
212 | |
213 | /* listen for replies */ |
214 | while (1) { |
215 | #if 0 |
216 | struct sockaddr_in from; |
217 | socklen_t fromlen = sizeof(from); |
218 | |
219 | c = recvfrom(pingsock, G.packet, sizeof(G.packet), 0, |
220 | (struct sockaddr *) &from, &fromlen); |
221 | #else |
222 | c = recv(pingsock, G.packet, sizeof(G.packet), 0); |
223 | #endif |
224 | if (c < 0) { |
225 | if (errno != EINTR) |
226 | bb_perror_msg("recvfrom"); |
227 | continue; |
228 | } |
229 | if (c >= 76) { /* ip + icmp */ |
230 | struct iphdr *iphdr = (struct iphdr *) G.packet; |
231 | |
232 | pkt = (struct icmp *) (G.packet + (iphdr->ihl << 2)); /* skip ip hdr */ |
233 | if (pkt->icmp_id != G.myid) |
234 | continue; /* not our ping */ |
235 | if (pkt->icmp_type == ICMP_ECHOREPLY) |
236 | break; |
237 | } |
238 | } |
239 | if (ENABLE_FEATURE_CLEAN_UP) |
240 | close(pingsock); |
241 | } |
242 | |
243 | #if ENABLE_PING6 |
244 | static void ping6(len_and_sockaddr *lsa) |
245 | { |
246 | struct icmp6_hdr *pkt; |
247 | int c; |
248 | int sockopt; |
249 | |
250 | pkt = (struct icmp6_hdr *) G.packet; |
251 | /*memset(pkt, 0, sizeof(G.packet)); already is */ |
252 | pkt->icmp6_type = ICMP6_ECHO_REQUEST; |
253 | pkt->icmp6_id = G.myid; |
254 | |
255 | sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); |
256 | setsockopt_int(pingsock, SOL_RAW, IPV6_CHECKSUM, sockopt); |
257 | |
258 | xsendto(pingsock, G.packet, DEFDATALEN + sizeof(struct icmp6_hdr), &lsa->u.sa, lsa->len); |
259 | |
260 | /* listen for replies */ |
261 | while (1) { |
262 | #if 0 |
263 | struct sockaddr_in6 from; |
264 | socklen_t fromlen = sizeof(from); |
265 | |
266 | c = recvfrom(pingsock, G.packet, sizeof(G.packet), 0, |
267 | (struct sockaddr *) &from, &fromlen); |
268 | #else |
269 | c = recv(pingsock, G.packet, sizeof(G.packet), 0); |
270 | #endif |
271 | if (c < 0) { |
272 | if (errno != EINTR) |
273 | bb_perror_msg("recvfrom"); |
274 | continue; |
275 | } |
276 | if (c >= ICMP_MINLEN) { /* icmp6_hdr */ |
277 | if (pkt->icmp6_id != G.myid) |
278 | continue; /* not our ping */ |
279 | if (pkt->icmp6_type == ICMP6_ECHO_REPLY) |
280 | break; |
281 | } |
282 | } |
283 | if (ENABLE_FEATURE_CLEAN_UP) |
284 | close(pingsock); |
285 | } |
286 | #endif |
287 | |
288 | #if !ENABLE_PING6 |
289 | # define common_ping_main(af, argv) common_ping_main(argv) |
290 | #endif |
291 | static int common_ping_main(sa_family_t af, char **argv) |
292 | { |
293 | len_and_sockaddr *lsa; |
294 | |
295 | INIT_G(); |
296 | |
297 | #if ENABLE_PING6 |
298 | while ((++argv)[0] && argv[0][0] == '-') { |
299 | if (argv[0][1] == '4') { |
300 | af = AF_INET; |
301 | continue; |
302 | } |
303 | if (argv[0][1] == '6') { |
304 | af = AF_INET6; |
305 | continue; |
306 | } |
307 | bb_show_usage(); |
308 | } |
309 | #else |
310 | argv++; |
311 | #endif |
312 | |
313 | G.hostname = *argv; |
314 | if (!G.hostname) |
315 | bb_show_usage(); |
316 | |
317 | #if ENABLE_PING6 |
318 | lsa = xhost_and_af2sockaddr(G.hostname, 0, af); |
319 | #else |
320 | lsa = xhost_and_af2sockaddr(G.hostname, 0, AF_INET); |
321 | #endif |
322 | /* Set timer _after_ DNS resolution */ |
323 | signal(SIGALRM, noresp); |
324 | alarm(5); /* give the host 5000ms to respond */ |
325 | |
326 | create_icmp_socket(lsa); |
327 | G.myid = (uint16_t) getpid(); |
328 | #if ENABLE_PING6 |
329 | if (lsa->u.sa.sa_family == AF_INET6) |
330 | ping6(lsa); |
331 | else |
332 | #endif |
333 | ping4(lsa); |
334 | printf("%s is alive!\n", G.hostname); |
335 | return EXIT_SUCCESS; |
336 | } |
337 | |
338 | |
339 | #else /* FEATURE_FANCY_PING */ |
340 | |
341 | |
342 | /* Full(er) version */ |
343 | |
344 | #define OPT_STRING ("qvc:+s:t:+w:+W:+I:np:4" IF_PING6("6")) |
345 | enum { |
346 | OPT_QUIET = 1 << 0, |
347 | OPT_VERBOSE = 1 << 1, |
348 | OPT_c = 1 << 2, |
349 | OPT_s = 1 << 3, |
350 | OPT_t = 1 << 4, |
351 | OPT_w = 1 << 5, |
352 | OPT_W = 1 << 6, |
353 | OPT_I = 1 << 7, |
354 | /*OPT_n = 1 << 8, - ignored */ |
355 | OPT_p = 1 << 9, |
356 | OPT_IPV4 = 1 << 10, |
357 | OPT_IPV6 = (1 << 11) * ENABLE_PING6, |
358 | }; |
359 | |
360 | |
361 | struct globals { |
362 | int if_index; |
363 | char *str_I; |
364 | len_and_sockaddr *source_lsa; |
365 | unsigned datalen; |
366 | unsigned pingcount; /* must be int-sized */ |
367 | unsigned opt_ttl; |
368 | unsigned long ntransmitted, nreceived, nrepeats; |
369 | uint16_t myid; |
370 | uint8_t pattern; |
371 | unsigned tmin, tmax; /* in us */ |
372 | unsigned long long tsum; /* in us, sum of all times */ |
373 | unsigned deadline; |
374 | unsigned timeout; |
375 | unsigned total_secs; |
376 | unsigned sizeof_rcv_packet; |
377 | char *rcv_packet; /* [datalen + MAXIPLEN + MAXICMPLEN] */ |
378 | void *snd_packet; /* [datalen + ipv4/ipv6_const] */ |
379 | const char *hostname; |
380 | const char *dotted; |
381 | union { |
382 | struct sockaddr sa; |
383 | struct sockaddr_in sin; |
384 | #if ENABLE_PING6 |
385 | struct sockaddr_in6 sin6; |
386 | #endif |
387 | } pingaddr; |
388 | unsigned char rcvd_tbl[MAX_DUP_CHK / 8]; |
389 | } FIX_ALIASING; |
390 | #define G (*(struct globals*)bb_common_bufsiz1) |
391 | #define if_index (G.if_index ) |
392 | #define source_lsa (G.source_lsa ) |
393 | #define str_I (G.str_I ) |
394 | #define datalen (G.datalen ) |
395 | #define pingcount (G.pingcount ) |
396 | #define opt_ttl (G.opt_ttl ) |
397 | #define myid (G.myid ) |
398 | #define tmin (G.tmin ) |
399 | #define tmax (G.tmax ) |
400 | #define tsum (G.tsum ) |
401 | #define deadline (G.deadline ) |
402 | #define timeout (G.timeout ) |
403 | #define total_secs (G.total_secs ) |
404 | #define hostname (G.hostname ) |
405 | #define dotted (G.dotted ) |
406 | #define pingaddr (G.pingaddr ) |
407 | #define rcvd_tbl (G.rcvd_tbl ) |
408 | #define INIT_G() do { \ |
409 | setup_common_bufsiz(); \ |
410 | BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \ |
411 | datalen = DEFDATALEN; \ |
412 | timeout = MAXWAIT; \ |
413 | tmin = UINT_MAX; \ |
414 | } while (0) |
415 | |
416 | |
417 | #define BYTE(bit) rcvd_tbl[(bit)>>3] |
418 | #define MASK(bit) (1 << ((bit) & 7)) |
419 | #define SET(bit) (BYTE(bit) |= MASK(bit)) |
420 | #define CLR(bit) (BYTE(bit) &= (~MASK(bit))) |
421 | #define TST(bit) (BYTE(bit) & MASK(bit)) |
422 | |
423 | static void print_stats_and_exit(int junk) NORETURN; |
424 | static void print_stats_and_exit(int junk UNUSED_PARAM) |
425 | { |
426 | unsigned long ul; |
427 | unsigned long nrecv; |
428 | |
429 | signal(SIGINT, SIG_IGN); |
430 | |
431 | nrecv = G.nreceived; |
432 | printf("\n--- %s ping statistics ---\n" |
433 | "%lu packets transmitted, " |
434 | "%lu packets received, ", |
435 | hostname, G.ntransmitted, nrecv |
436 | ); |
437 | if (G.nrepeats) |
438 | printf("%lu duplicates, ", G.nrepeats); |
439 | ul = G.ntransmitted; |
440 | if (ul != 0) |
441 | ul = (ul - nrecv) * 100 / ul; |
442 | printf("%lu%% packet loss\n", ul); |
443 | if (tmin != UINT_MAX) { |
444 | unsigned tavg = tsum / (nrecv + G.nrepeats); |
445 | printf("round-trip min/avg/max = %u.%03u/%u.%03u/%u.%03u ms\n", |
446 | tmin / 1000, tmin % 1000, |
447 | tavg / 1000, tavg % 1000, |
448 | tmax / 1000, tmax % 1000); |
449 | } |
450 | /* if condition is true, exit with 1 -- 'failure' */ |
451 | exit(nrecv == 0 || (deadline && nrecv < pingcount)); |
452 | } |
453 | |
454 | static void sendping_tail(void (*sp)(int), int size_pkt) |
455 | { |
456 | int sz; |
457 | |
458 | CLR((uint16_t)G.ntransmitted % MAX_DUP_CHK); |
459 | G.ntransmitted++; |
460 | |
461 | size_pkt += datalen; |
462 | |
463 | /* sizeof(pingaddr) can be larger than real sa size, but I think |
464 | * it doesn't matter */ |
465 | sz = xsendto(pingsock, G.snd_packet, size_pkt, &pingaddr.sa, sizeof(pingaddr)); |
466 | if (sz != size_pkt) |
467 | bb_error_msg_and_die(bb_msg_write_error); |
468 | |
469 | if (pingcount == 0 || deadline || G.ntransmitted < pingcount) { |
470 | /* Didn't send all pings yet - schedule next in 1s */ |
471 | signal(SIGALRM, sp); |
472 | if (deadline) { |
473 | total_secs += PINGINTERVAL; |
474 | if (total_secs >= deadline) |
475 | signal(SIGALRM, print_stats_and_exit); |
476 | } |
477 | alarm(PINGINTERVAL); |
478 | } else { /* -c NN, and all NN are sent (and no deadline) */ |
479 | /* Wait for the last ping to come back. |
480 | * -W timeout: wait for a response in seconds. |
481 | * Affects only timeout in absense of any responses, |
482 | * otherwise ping waits for two RTTs. */ |
483 | unsigned expire = timeout; |
484 | |
485 | if (G.nreceived) { |
486 | /* approx. 2*tmax, in seconds (2 RTT) */ |
487 | expire = tmax / (512*1024); |
488 | if (expire == 0) |
489 | expire = 1; |
490 | } |
491 | signal(SIGALRM, print_stats_and_exit); |
492 | alarm(expire); |
493 | } |
494 | } |
495 | |
496 | static void sendping4(int junk UNUSED_PARAM) |
497 | { |
498 | struct icmp *pkt = G.snd_packet; |
499 | |
500 | memset(pkt, G.pattern, datalen + ICMP_MINLEN + 4); |
501 | pkt->icmp_type = ICMP_ECHO; |
502 | /*pkt->icmp_code = 0;*/ |
503 | pkt->icmp_cksum = 0; /* cksum is calculated with this field set to 0 */ |
504 | pkt->icmp_seq = htons(G.ntransmitted); /* don't ++ here, it can be a macro */ |
505 | pkt->icmp_id = myid; |
506 | |
507 | /* If datalen < 4, we store timestamp _past_ the packet, |
508 | * but it's ok - we allocated 4 extra bytes in xzalloc() just in case. |
509 | */ |
510 | /*if (datalen >= 4)*/ |
511 | /* No hton: we'll read it back on the same machine */ |
512 | *(uint32_t*)&pkt->icmp_dun = monotonic_us(); |
513 | |
514 | pkt->icmp_cksum = inet_cksum((uint16_t *) pkt, datalen + ICMP_MINLEN); |
515 | |
516 | sendping_tail(sendping4, ICMP_MINLEN); |
517 | } |
518 | #if ENABLE_PING6 |
519 | static void sendping6(int junk UNUSED_PARAM) |
520 | { |
521 | struct icmp6_hdr *pkt = G.snd_packet; |
522 | |
523 | memset(pkt, G.pattern, datalen + sizeof(struct icmp6_hdr) + 4); |
524 | pkt->icmp6_type = ICMP6_ECHO_REQUEST; |
525 | /*pkt->icmp6_code = 0;*/ |
526 | /*pkt->icmp6_cksum = 0;*/ |
527 | pkt->icmp6_seq = htons(G.ntransmitted); /* don't ++ here, it can be a macro */ |
528 | pkt->icmp6_id = myid; |
529 | |
530 | /*if (datalen >= 4)*/ |
531 | *(bb__aliased_uint32_t*)(&pkt->icmp6_data8[4]) = monotonic_us(); |
532 | |
533 | //TODO? pkt->icmp_cksum = inet_cksum(...); |
534 | |
535 | sendping_tail(sendping6, sizeof(struct icmp6_hdr)); |
536 | } |
537 | #endif |
538 | |
539 | static const char *icmp_type_name(int id) |
540 | { |
541 | switch (id) { |
542 | case ICMP_ECHOREPLY: return "Echo Reply"; |
543 | case ICMP_DEST_UNREACH: return "Destination Unreachable"; |
544 | case ICMP_SOURCE_QUENCH: return "Source Quench"; |
545 | case ICMP_REDIRECT: return "Redirect (change route)"; |
546 | case ICMP_ECHO: return "Echo Request"; |
547 | case ICMP_TIME_EXCEEDED: return "Time Exceeded"; |
548 | case ICMP_PARAMETERPROB: return "Parameter Problem"; |
549 | case ICMP_TIMESTAMP: return "Timestamp Request"; |
550 | case ICMP_TIMESTAMPREPLY: return "Timestamp Reply"; |
551 | case ICMP_INFO_REQUEST: return "Information Request"; |
552 | case ICMP_INFO_REPLY: return "Information Reply"; |
553 | case ICMP_ADDRESS: return "Address Mask Request"; |
554 | case ICMP_ADDRESSREPLY: return "Address Mask Reply"; |
555 | default: return "unknown ICMP type"; |
556 | } |
557 | } |
558 | #if ENABLE_PING6 |
559 | /* RFC3542 changed some definitions from RFC2292 for no good reason, whee! |
560 | * the newer 3542 uses a MLD_ prefix where as 2292 uses ICMP6_ prefix */ |
561 | #ifndef MLD_LISTENER_QUERY |
562 | # define MLD_LISTENER_QUERY ICMP6_MEMBERSHIP_QUERY |
563 | #endif |
564 | #ifndef MLD_LISTENER_REPORT |
565 | # define MLD_LISTENER_REPORT ICMP6_MEMBERSHIP_REPORT |
566 | #endif |
567 | #ifndef MLD_LISTENER_REDUCTION |
568 | # define MLD_LISTENER_REDUCTION ICMP6_MEMBERSHIP_REDUCTION |
569 | #endif |
570 | static const char *icmp6_type_name(int id) |
571 | { |
572 | switch (id) { |
573 | case ICMP6_DST_UNREACH: return "Destination Unreachable"; |
574 | case ICMP6_PACKET_TOO_BIG: return "Packet too big"; |
575 | case ICMP6_TIME_EXCEEDED: return "Time Exceeded"; |
576 | case ICMP6_PARAM_PROB: return "Parameter Problem"; |
577 | case ICMP6_ECHO_REPLY: return "Echo Reply"; |
578 | case ICMP6_ECHO_REQUEST: return "Echo Request"; |
579 | case MLD_LISTENER_QUERY: return "Listener Query"; |
580 | case MLD_LISTENER_REPORT: return "Listener Report"; |
581 | case MLD_LISTENER_REDUCTION: return "Listener Reduction"; |
582 | default: return "unknown ICMP type"; |
583 | } |
584 | } |
585 | #endif |
586 | |
587 | static void unpack_tail(int sz, uint32_t *tp, |
588 | const char *from_str, |
589 | uint16_t recv_seq, int ttl) |
590 | { |
591 | unsigned char *b, m; |
592 | const char *dupmsg = " (DUP!)"; |
593 | unsigned triptime = triptime; /* for gcc */ |
594 | |
595 | if (tp) { |
596 | /* (int32_t) cast is for hypothetical 64-bit unsigned */ |
597 | /* (doesn't hurt 32-bit real-world anyway) */ |
598 | triptime = (int32_t) ((uint32_t)monotonic_us() - *tp); |
599 | tsum += triptime; |
600 | if (triptime < tmin) |
601 | tmin = triptime; |
602 | if (triptime > tmax) |
603 | tmax = triptime; |
604 | } |
605 | |
606 | b = &BYTE(recv_seq % MAX_DUP_CHK); |
607 | m = MASK(recv_seq % MAX_DUP_CHK); |
608 | /*if TST(recv_seq % MAX_DUP_CHK):*/ |
609 | if (*b & m) { |
610 | ++G.nrepeats; |
611 | } else { |
612 | /*SET(recv_seq % MAX_DUP_CHK):*/ |
613 | *b |= m; |
614 | ++G.nreceived; |
615 | dupmsg += 7; |
616 | } |
617 | |
618 | if (option_mask32 & OPT_QUIET) |
619 | return; |
620 | |
621 | printf("%d bytes from %s: seq=%u ttl=%d", sz, |
622 | from_str, recv_seq, ttl); |
623 | if (tp) |
624 | printf(" time=%u.%03u ms", triptime / 1000, triptime % 1000); |
625 | puts(dupmsg); |
626 | fflush_all(); |
627 | } |
628 | static void unpack4(char *buf, int sz, struct sockaddr_in *from) |
629 | { |
630 | struct icmp *icmppkt; |
631 | struct iphdr *iphdr; |
632 | int hlen; |
633 | |
634 | /* discard if too short */ |
635 | if (sz < (datalen + ICMP_MINLEN)) |
636 | return; |
637 | |
638 | /* check IP header */ |
639 | iphdr = (struct iphdr *) buf; |
640 | hlen = iphdr->ihl << 2; |
641 | sz -= hlen; |
642 | icmppkt = (struct icmp *) (buf + hlen); |
643 | if (icmppkt->icmp_id != myid) |
644 | return; /* not our ping */ |
645 | |
646 | if (icmppkt->icmp_type == ICMP_ECHOREPLY) { |
647 | uint16_t recv_seq = ntohs(icmppkt->icmp_seq); |
648 | uint32_t *tp = NULL; |
649 | |
650 | if (sz >= ICMP_MINLEN + sizeof(uint32_t)) |
651 | tp = (uint32_t *) icmppkt->icmp_data; |
652 | unpack_tail(sz, tp, |
653 | inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr), |
654 | recv_seq, iphdr->ttl); |
655 | } else if (icmppkt->icmp_type != ICMP_ECHO) { |
656 | bb_error_msg("warning: got ICMP %d (%s)", |
657 | icmppkt->icmp_type, |
658 | icmp_type_name(icmppkt->icmp_type)); |
659 | } |
660 | } |
661 | #if ENABLE_PING6 |
662 | static void unpack6(char *packet, int sz, struct sockaddr_in6 *from, int hoplimit) |
663 | { |
664 | struct icmp6_hdr *icmppkt; |
665 | char buf[INET6_ADDRSTRLEN]; |
666 | |
667 | /* discard if too short */ |
668 | if (sz < (datalen + sizeof(struct icmp6_hdr))) |
669 | return; |
670 | |
671 | icmppkt = (struct icmp6_hdr *) packet; |
672 | if (icmppkt->icmp6_id != myid) |
673 | return; /* not our ping */ |
674 | |
675 | if (icmppkt->icmp6_type == ICMP6_ECHO_REPLY) { |
676 | uint16_t recv_seq = ntohs(icmppkt->icmp6_seq); |
677 | uint32_t *tp = NULL; |
678 | |
679 | if (sz >= sizeof(struct icmp6_hdr) + sizeof(uint32_t)) |
680 | tp = (uint32_t *) &icmppkt->icmp6_data8[4]; |
681 | unpack_tail(sz, tp, |
682 | inet_ntop(AF_INET6, &from->sin6_addr, |
683 | buf, sizeof(buf)), |
684 | recv_seq, hoplimit); |
685 | } else if (icmppkt->icmp6_type != ICMP6_ECHO_REQUEST) { |
686 | bb_error_msg("warning: got ICMP %d (%s)", |
687 | icmppkt->icmp6_type, |
688 | icmp6_type_name(icmppkt->icmp6_type)); |
689 | } |
690 | } |
691 | #endif |
692 | |
693 | static void ping4(len_and_sockaddr *lsa) |
694 | { |
695 | int sockopt; |
696 | |
697 | pingaddr.sin = lsa->u.sin; |
698 | if (source_lsa) { |
699 | if (setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_IF, |
700 | &source_lsa->u.sa, source_lsa->len)) |
701 | bb_error_msg_and_die("can't set multicast source interface"); |
702 | xbind(pingsock, &source_lsa->u.sa, source_lsa->len); |
703 | } |
704 | |
705 | /* enable broadcast pings */ |
706 | setsockopt_broadcast(pingsock); |
707 | |
708 | /* set recv buf (needed if we can get lots of responses: flood ping, |
709 | * broadcast ping etc) */ |
710 | sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */ |
711 | setsockopt_SOL_SOCKET_int(pingsock, SO_RCVBUF, sockopt); |
712 | |
713 | if (opt_ttl != 0) { |
714 | setsockopt_int(pingsock, IPPROTO_IP, IP_TTL, opt_ttl); |
715 | /* above doesnt affect packets sent to bcast IP, so... */ |
716 | setsockopt_int(pingsock, IPPROTO_IP, IP_MULTICAST_TTL, opt_ttl); |
717 | } |
718 | |
719 | signal(SIGINT, print_stats_and_exit); |
720 | |
721 | /* start the ping's going ... */ |
722 | sendping4(0); |
723 | |
724 | /* listen for replies */ |
725 | while (1) { |
726 | struct sockaddr_in from; |
727 | socklen_t fromlen = (socklen_t) sizeof(from); |
728 | int c; |
729 | |
730 | c = recvfrom(pingsock, G.rcv_packet, G.sizeof_rcv_packet, 0, |
731 | (struct sockaddr *) &from, &fromlen); |
732 | if (c < 0) { |
733 | if (errno != EINTR) |
734 | bb_perror_msg("recvfrom"); |
735 | continue; |
736 | } |
737 | unpack4(G.rcv_packet, c, &from); |
738 | if (pingcount && G.nreceived >= pingcount) |
739 | break; |
740 | } |
741 | } |
742 | #if ENABLE_PING6 |
743 | static void ping6(len_and_sockaddr *lsa) |
744 | { |
745 | int sockopt; |
746 | struct msghdr msg; |
747 | struct sockaddr_in6 from; |
748 | struct iovec iov; |
749 | char control_buf[CMSG_SPACE(36)]; |
750 | |
751 | pingaddr.sin6 = lsa->u.sin6; |
752 | if (source_lsa) |
753 | xbind(pingsock, &source_lsa->u.sa, source_lsa->len); |
754 | |
755 | #ifdef ICMP6_FILTER |
756 | { |
757 | struct icmp6_filter filt; |
758 | if (!(option_mask32 & OPT_VERBOSE)) { |
759 | ICMP6_FILTER_SETBLOCKALL(&filt); |
760 | ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt); |
761 | } else { |
762 | ICMP6_FILTER_SETPASSALL(&filt); |
763 | } |
764 | if (setsockopt(pingsock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, |
765 | sizeof(filt)) < 0) |
766 | bb_error_msg_and_die("setsockopt(%s)", "ICMP6_FILTER"); |
767 | } |
768 | #endif /*ICMP6_FILTER*/ |
769 | |
770 | /* enable broadcast pings */ |
771 | setsockopt_broadcast(pingsock); |
772 | |
773 | /* set recv buf (needed if we can get lots of responses: flood ping, |
774 | * broadcast ping etc) */ |
775 | sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */ |
776 | setsockopt_SOL_SOCKET_int(pingsock, SO_RCVBUF, sockopt); |
777 | |
778 | sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); |
779 | BUILD_BUG_ON(offsetof(struct icmp6_hdr, icmp6_cksum) != 2); |
780 | setsockopt_int(pingsock, SOL_RAW, IPV6_CHECKSUM, sockopt); |
781 | |
782 | /* request ttl info to be returned in ancillary data */ |
783 | setsockopt_1(pingsock, SOL_IPV6, IPV6_HOPLIMIT); |
784 | |
785 | if (if_index) |
786 | pingaddr.sin6.sin6_scope_id = if_index; |
787 | |
788 | signal(SIGINT, print_stats_and_exit); |
789 | |
790 | /* start the ping's going ... */ |
791 | sendping6(0); |
792 | |
793 | /* listen for replies */ |
794 | msg.msg_name = &from; |
795 | msg.msg_namelen = sizeof(from); |
796 | msg.msg_iov = &iov; |
797 | msg.msg_iovlen = 1; |
798 | msg.msg_control = control_buf; |
799 | iov.iov_base = G.rcv_packet; |
800 | iov.iov_len = G.sizeof_rcv_packet; |
801 | while (1) { |
802 | int c; |
803 | struct cmsghdr *mp; |
804 | int hoplimit = -1; |
805 | msg.msg_controllen = sizeof(control_buf); |
806 | |
807 | c = recvmsg(pingsock, &msg, 0); |
808 | if (c < 0) { |
809 | if (errno != EINTR) |
810 | bb_perror_msg("recvfrom"); |
811 | continue; |
812 | } |
813 | for (mp = CMSG_FIRSTHDR(&msg); mp; mp = CMSG_NXTHDR(&msg, mp)) { |
814 | if (mp->cmsg_level == SOL_IPV6 |
815 | && mp->cmsg_type == IPV6_HOPLIMIT |
816 | /* don't check len - we trust the kernel: */ |
817 | /* && mp->cmsg_len >= CMSG_LEN(sizeof(int)) */ |
818 | ) { |
819 | /*hoplimit = *(int*)CMSG_DATA(mp); - unaligned access */ |
820 | move_from_unaligned_int(hoplimit, CMSG_DATA(mp)); |
821 | } |
822 | } |
823 | unpack6(G.rcv_packet, c, &from, hoplimit); |
824 | if (pingcount && G.nreceived >= pingcount) |
825 | break; |
826 | } |
827 | } |
828 | #endif |
829 | |
830 | static void ping(len_and_sockaddr *lsa) |
831 | { |
832 | printf("PING %s (%s)", hostname, dotted); |
833 | if (source_lsa) { |
834 | printf(" from %s", |
835 | xmalloc_sockaddr2dotted_noport(&source_lsa->u.sa)); |
836 | } |
837 | printf(": %d data bytes\n", datalen); |
838 | |
839 | create_icmp_socket(lsa); |
840 | /* untested whether "-I addr" really works for IPv6: */ |
841 | if (str_I) |
842 | setsockopt_bindtodevice(pingsock, str_I); |
843 | |
844 | G.sizeof_rcv_packet = datalen + MAXIPLEN + MAXICMPLEN; |
845 | G.rcv_packet = xzalloc(G.sizeof_rcv_packet); |
846 | #if ENABLE_PING6 |
847 | if (lsa->u.sa.sa_family == AF_INET6) { |
848 | /* +4 reserves a place for timestamp, which may end up sitting |
849 | * _after_ packet. Saves one if() - see sendping4/6() */ |
850 | G.snd_packet = xzalloc(datalen + sizeof(struct icmp6_hdr) + 4); |
851 | ping6(lsa); |
852 | } else |
853 | #endif |
854 | { |
855 | G.snd_packet = xzalloc(datalen + ICMP_MINLEN + 4); |
856 | ping4(lsa); |
857 | } |
858 | } |
859 | |
860 | static int common_ping_main(int opt, char **argv) |
861 | { |
862 | len_and_sockaddr *lsa; |
863 | char *str_s, *str_p; |
864 | |
865 | INIT_G(); |
866 | |
867 | /* exactly one argument needed; -v and -q don't mix; -c NUM, -t NUM, -w NUM, -W NUM */ |
868 | opt_complementary = "=1:q--v:v--q"; |
869 | opt |= getopt32(argv, OPT_STRING, &pingcount, &str_s, &opt_ttl, &deadline, &timeout, &str_I, &str_p); |
870 | if (opt & OPT_s) |
871 | datalen = xatou16(str_s); // -s |
872 | if (opt & OPT_I) { // -I |
873 | if_index = if_nametoindex(str_I); |
874 | if (!if_index) { |
875 | /* TODO: I'm not sure it takes IPv6 unless in [XX:XX..] format */ |
876 | source_lsa = xdotted2sockaddr(str_I, 0); |
877 | str_I = NULL; /* don't try to bind to device later */ |
878 | } |
879 | } |
880 | if (opt & OPT_p) |
881 | G.pattern = xstrtou_range(str_p, 16, 0, 255); |
882 | |
883 | myid = (uint16_t) getpid(); |
884 | hostname = argv[optind]; |
885 | #if ENABLE_PING6 |
886 | { |
887 | sa_family_t af = AF_UNSPEC; |
888 | if (opt & OPT_IPV4) |
889 | af = AF_INET; |
890 | if (opt & OPT_IPV6) |
891 | af = AF_INET6; |
892 | lsa = xhost_and_af2sockaddr(hostname, 0, af); |
893 | } |
894 | #else |
895 | lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET); |
896 | #endif |
897 | |
898 | if (source_lsa && source_lsa->u.sa.sa_family != lsa->u.sa.sa_family) |
899 | /* leaking it here... */ |
900 | source_lsa = NULL; |
901 | |
902 | dotted = xmalloc_sockaddr2dotted_noport(&lsa->u.sa); |
903 | ping(lsa); |
904 | print_stats_and_exit(EXIT_SUCCESS); |
905 | /*return EXIT_SUCCESS;*/ |
906 | } |
907 | #endif /* FEATURE_FANCY_PING */ |
908 | |
909 | |
910 | #if ENABLE_PING |
911 | int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
912 | int ping_main(int argc UNUSED_PARAM, char **argv) |
913 | { |
914 | # if !ENABLE_FEATURE_FANCY_PING |
915 | return common_ping_main(AF_UNSPEC, argv); |
916 | # else |
917 | return common_ping_main(0, argv); |
918 | # endif |
919 | } |
920 | #endif |
921 | |
922 | #if ENABLE_PING6 |
923 | int ping6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
924 | int ping6_main(int argc UNUSED_PARAM, char **argv) |
925 | { |
926 | # if !ENABLE_FEATURE_FANCY_PING |
927 | return common_ping_main(AF_INET6, argv); |
928 | # else |
929 | return common_ping_main(OPT_IPV6, argv); |
930 | # endif |
931 | } |
932 | #endif |
933 | |
934 | /* from ping6.c: |
935 | * Copyright (c) 1989 The Regents of the University of California. |
936 | * All rights reserved. |
937 | * |
938 | * This code is derived from software contributed to Berkeley by |
939 | * Mike Muuss. |
940 | * |
941 | * Redistribution and use in source and binary forms, with or without |
942 | * modification, are permitted provided that the following conditions |
943 | * are met: |
944 | * 1. Redistributions of source code must retain the above copyright |
945 | * notice, this list of conditions and the following disclaimer. |
946 | * 2. Redistributions in binary form must reproduce the above copyright |
947 | * notice, this list of conditions and the following disclaimer in the |
948 | * documentation and/or other materials provided with the distribution. |
949 | * |
950 | * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change |
951 | * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> |
952 | * |
953 | * 4. Neither the name of the University nor the names of its contributors |
954 | * may be used to endorse or promote products derived from this software |
955 | * without specific prior written permission. |
956 | * |
957 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
958 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
959 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
960 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
961 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
962 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
963 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
964 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
965 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
966 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
967 | * SUCH DAMAGE. |
968 | */ |
969 |