/* Copyright (C) 1998, 2001, 2002, 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Inetutils; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ping_common.h" #include "ping6.h" static char short_options[] = "VLhc:dfi:l:np:qRrs:t:v"; static struct option long_options[] = { /* Help options */ {"version", no_argument, NULL, 'V'}, {"license", no_argument, NULL, 'L'}, {"help", no_argument, NULL, 'h'}, /* Common options */ {"count", required_argument, NULL, 'c'}, {"debug", no_argument, NULL, 'd'}, {"ignore-routing", no_argument, NULL, 'r'}, {"size", required_argument, NULL, 's'}, {"interval",required_argument, NULL, 'i'}, {"numeric", no_argument, NULL, 'n'}, /* echo-specific options */ {"flood", no_argument, NULL, 'f'}, {"preload", required_argument, NULL, 'l'}, {"pattern", required_argument, NULL, 'p'}, {"quiet", no_argument, NULL, 'q'}, {NULL, no_argument, NULL, 0} }; static PING *ping; unsigned char *data_buffer; size_t data_length = PING_DATALEN; static unsigned int options; static unsigned long preload = 0; static int ping_echo (int argc, char **argv); static void show_usage (void); static int send_echo (PING *ping); int main (int argc, char **argv) { int c; char *p; int one = 1; u_char pattern[16]; int pattern_len = 16; u_char *patptr = NULL; int is_root = getuid () == 0; if ((ping = ping_init (0, getpid ())) == NULL) { fprintf (stderr, "can't init ping: %s\n", strerror (errno)); exit (1); } setsockopt (ping->ping_fd, SOL_SOCKET, SO_BROADCAST, (char *)&one, sizeof (one)); /* Reset root privileges */ setuid (getuid ()); /* Parse command line */ while ((c = getopt_long (argc, argv, short_options, long_options, NULL)) != EOF) { switch (c) { case 'V': printf ("ping - %s %s\n", PACKAGE_NAME, PACKAGE_VERSION); printf ("Copyright (C) 2005 Free Software Foundation, Inc.\n"); printf ("%s comes with ABSOLUTELY NO WARRANTY.\n", PACKAGE_NAME); printf ("You may redistribute copies of %s\n", PACKAGE_NAME); printf ("under the terms of the GNU General Public License.\n"); printf ("For more information about these matters, "); printf ("see the files named COPYING.\n"); exit (0); break; case 'L': show_license (); exit (0); case 'h': show_usage (); exit (0); break; case 'c': ping->ping_count = ping_cvt_number (optarg, 0, 0); break; case 'd': setsockopt (ping->ping_fd, SOL_SOCKET, SO_DEBUG, &one, sizeof (one)); break; case 'r': setsockopt (ping->ping_fd, SOL_SOCKET, SO_DONTROUTE, &one, sizeof (one)); break; case 'i': options |= OPT_INTERVAL; ping->ping_interval = ping_cvt_number (optarg, 0, 0); break; case 'p': decode_pattern (optarg, &pattern_len, pattern); patptr = pattern; break; case 's': data_length = ping_cvt_number (optarg, PING_MAX_DATALEN, 1); break; case 'n': options |= OPT_NUMERIC; break; case 'q': options |= OPT_QUIET; break; case 'l': if (!is_root) { fprintf (stderr, "ping: option not allowed: --preload\n"); exit (1); } preload = strtoul (optarg, &p, 0); if (*p || preload > INT_MAX) { fprintf (stderr, "ping: invalid preload value (%s)\n", optarg); exit (1); } break; case 'f': if (!is_root) { fprintf (stderr, "ping: option not allowed: --flood\n"); exit (1); } options |= OPT_FLOOD; setbuf (stdout, (char *)NULL); break; default: fprintf (stderr, "%c: not implemented\n", c); exit (1); } } argc -= optind; argv += optind; if (argc == 0) { show_usage (); exit (0); } init_data_buffer (patptr, pattern_len); return ping_echo (argc, argv); } static char * ipaddr2str (struct sockaddr_in6 *from) { int err; size_t len; char *buf, ipstr[256], hoststr[256]; err = getnameinfo ((struct sockaddr *) from, sizeof (*from), ipstr, sizeof (ipstr), NULL, 0, NI_NUMERICHOST); if (err) { const char *errmsg; if (err == EAI_SYSTEM) errmsg = strerror (errno); else errmsg = gai_strerror (err); fprintf (stderr, "ping: getnameinfo: %s\n", errmsg); return xstrdup ("unknown"); } if (options & OPT_NUMERIC) return xstrdup (ipstr); err = getnameinfo ((struct sockaddr *) from, sizeof (*from), hoststr, sizeof (hoststr), NULL, 0, NI_NAMEREQD); if (err) return xstrdup (ipstr); len = strlen (ipstr) + strlen (hoststr) + 4; /* Pair of parentheses, a space and a NUL. */ buf = xmalloc (len); sprintf (buf, "%s (%s)", hoststr, ipstr); return buf; } static volatile int stop = 0; static RETSIGTYPE sig_int (int signal) { stop = 1; } static int ping_run (PING *ping, int (*finish)()) { fd_set fdset; int fdmax; struct timeval timeout; struct timeval last, intvl, now; struct timeval *t = NULL; int finishing = 0; int nresp = 0; signal (SIGINT, sig_int); fdmax = ping->ping_fd+1; while (preload--) send_echo (ping); if (options & OPT_FLOOD) { intvl.tv_sec = 0; intvl.tv_usec = 10000; } else { intvl.tv_sec = ping->ping_interval; intvl.tv_usec = 0; } gettimeofday (&last, NULL); send_echo (ping); while (!stop) { int n; FD_ZERO (&fdset); FD_SET (ping->ping_fd, &fdset); gettimeofday (&now, NULL); timeout.tv_sec = last.tv_sec + intvl.tv_sec - now.tv_sec; timeout.tv_usec = last.tv_usec + intvl.tv_usec - now.tv_usec; while (timeout.tv_usec < 0) { timeout.tv_usec += 1000000; timeout.tv_sec--; } while (timeout.tv_usec >= 1000000) { timeout.tv_usec -= 1000000; timeout.tv_sec++; } if (timeout.tv_sec < 0) timeout.tv_sec = timeout.tv_usec = 0; n = select (fdmax, &fdset, NULL, NULL, &timeout); if (n < 0) { if (errno != EINTR) perror ("select"); continue; } else if (n == 1) { if (ping_recv (ping) == 0) nresp++; if (t == 0) { gettimeofday (&now, NULL); t = &now; } if (ping->ping_count && nresp >= ping->ping_count) break; } else { if (!ping->ping_count || ping->ping_num_xmit < ping->ping_count) { send_echo (ping); if (!(options & OPT_QUIET) && options & OPT_FLOOD) { putchar ('.'); } } else if (finishing) break; else { finishing = 1; intvl.tv_sec = MAXWAIT; } gettimeofday (&last, NULL); } } if (finish) return (*finish)(); return 0; } static int send_echo (PING *ping) { int off = 0; if (PING_TIMING (data_length)) { struct timeval tv; gettimeofday (&tv, NULL); ping_set_data (ping, &tv, 0, sizeof (tv)); off += sizeof (tv); } if (data_buffer) ping_set_data (ping, data_buffer, off, data_length > PING_HEADER_LEN ? data_length - PING_HEADER_LEN : data_length); return ping_xmit (ping); } static int ping_finish () { fflush (stdout); printf ("--- %s ping statistics ---\n", ping->ping_hostname); printf ("%ld packets transmitted, ", ping->ping_num_xmit); printf ("%ld packets received, ", ping->ping_num_recv); if (ping->ping_num_rept) printf ("+%ld duplicates, ", ping->ping_num_rept); if (ping->ping_num_xmit) { if (ping->ping_num_recv > ping->ping_num_xmit) printf ("-- somebody's printing up packets!"); else printf ("%d%% packet loss", (int) (((ping->ping_num_xmit - ping->ping_num_recv) * 100) / ping->ping_num_xmit)); } printf ("\n"); return 0; } static int print_echo (int dup, int hops, struct ping_stat *stat, struct sockaddr_in6 *dest, struct sockaddr_in6 *from, struct icmp6_hdr *icmp6, int datalen); static void print_icmp_error (struct sockaddr_in6 *from, struct icmp6_hdr *icmp6, int len); static int echo_finish (void); static int ping_echo (int argc, char **argv) { int err; char buffer[256]; struct ping_stat ping_stat; if (options & OPT_FLOOD && options & OPT_INTERVAL) { fprintf (stderr, "ping: -f and -i incompatible options.\n"); return 2; } memset (&ping_stat, 0, sizeof (ping_stat)); ping_stat.tmin = 999999999.0; ping->ping_datalen = data_length; ping->ping_closure = &ping_stat; if (ping_set_dest (ping, *argv)) { fprintf (stderr, "ping: unknown host\n"); exit (1); } err = getnameinfo ((struct sockaddr *) &ping->ping_dest, sizeof (ping->ping_dest), buffer, sizeof (buffer), NULL, 0, NI_NUMERICHOST); if (err) { const char *errmsg; if (err == EAI_SYSTEM) errmsg = strerror (errno); else errmsg = gai_strerror (err); fprintf (stderr, "ping: getnameinfo: %s\n", errmsg); exit (1); } printf ("PING %s (%s): %d data bytes\n", ping->ping_hostname, buffer, data_length); return ping_run (ping, echo_finish); } static int print_echo (int dupflag, int hops, struct ping_stat *ping_stat, struct sockaddr_in6 *dest, struct sockaddr_in6 *from, struct icmp6_hdr *icmp6, int datalen) { int err; char buf[256]; struct timeval tv; int timing = 0; double triptime = 0.0; gettimeofday (&tv, NULL); /* Do timing */ if (PING_TIMING (datalen - sizeof (struct icmp6_hdr))) { struct timeval tv1, *tp; timing++; tp = (struct timeval *) icmp6 + 1; /* Avoid unaligned data: */ memcpy (&tv1, tp, sizeof (tv1)); tvsub (&tv, &tv1); triptime = ((double)tv.tv_sec) * 1000.0 + ((double)tv.tv_usec) / 1000.0; ping_stat->tsum += triptime; ping_stat->tsumsq += triptime*triptime; if (triptime < ping_stat->tmin) ping_stat->tmin = triptime; if (triptime > ping_stat->tmax) ping_stat->tmax = triptime; } if (options & OPT_QUIET) return 0; if (options & OPT_FLOOD) { putchar ('\b'); return 0; } err = getnameinfo ((struct sockaddr *) from, sizeof (*from), buf, sizeof (buf), NULL, 0, 0); if (err) { const char *errmsg; if (err == EAI_SYSTEM) errmsg = strerror (errno); else errmsg = gai_strerror (err); fprintf (stderr, "ping: getnameinfo: %s\n", errmsg); strcpy (buf, "unknown"); } printf ("%d bytes from %s: icmp_seq=%u", datalen, buf, htons (icmp6->icmp6_seq)); if (hops >= 0) printf (" ttl=%d", hops); if (timing) printf (" time=%.3f ms", triptime); if (dupflag) printf (" (DUP!)"); putchar ('\n'); return 0; } #define NITEMS(a) sizeof(a)/sizeof((a)[0]) struct icmp_code_descr { int code; char *diag; }; static struct icmp_code_descr icmp_dest_unreach_desc[] = { { ICMP6_DST_UNREACH_NOROUTE, "No route to destination" }, { ICMP6_DST_UNREACH_ADMIN, "Destination administratively prohibited" }, { ICMP6_DST_UNREACH_BEYONDSCOPE, "Beyond scope of source address" }, { ICMP6_DST_UNREACH_ADDR, "Address unreachable" }, { ICMP6_DST_UNREACH_NOPORT, "Port unreachable" }, }; static void print_dst_unreach (struct icmp6_hdr *icmp6) { struct icmp_code_descr *p; printf ("Destination unreachable: "); for (p = icmp_dest_unreach_desc; p < icmp_dest_unreach_desc + NITEMS(icmp_dest_unreach_desc); p++) { if (p->code == icmp6->icmp6_code) { puts (p->diag); return; } } printf ("Unknown code %d\n", icmp6->icmp6_code); } static void print_packet_too_big (struct icmp6_hdr *icmp6) { printf ("Packet too big, mtu=%d\n", icmp6->icmp6_mtu); } static struct icmp_code_descr icmp_time_exceeded_desc[] = { { ICMP6_TIME_EXCEED_TRANSIT, "Hop limit exceeded"}, { ICMP6_TIME_EXCEED_REASSEMBLY, "Fragment reassembly timeout"}, }; static void print_time_exceeded (struct icmp6_hdr *icmp6) { struct icmp_code_descr *p; printf ("Time exceeded: "); for (p = icmp_time_exceeded_desc; p < icmp_time_exceeded_desc + NITEMS(icmp_time_exceeded_desc); p++) { if (p->code == icmp6->icmp6_code) { puts (p->diag); return; } } printf ("Unknown code %d\n", icmp6->icmp6_code); } static struct icmp_code_descr icmp_param_prob_desc[] = { { ICMP6_PARAMPROB_HEADER, "Erroneous header field"}, { ICMP6_PARAMPROB_NEXTHEADER, "Unrecognized Next Header type"}, { ICMP6_PARAMPROB_OPTION, "Unrecognized IPv6 option"}, }; static void print_param_prob (struct icmp6_hdr *icmp6) { struct icmp_code_descr *p; printf ("Parameter problem: "); for (p = icmp_param_prob_desc; p < icmp_param_prob_desc + NITEMS(icmp_param_prob_desc); p++) { if (p->code == icmp6->icmp6_code) { puts (p->diag); return; } } printf ("Unknown code %d\n", icmp6->icmp6_code); } static struct icmp_diag { int type; void (*func) (struct icmp6_hdr *); } icmp_diag[] = { { ICMP6_DST_UNREACH, print_dst_unreach }, { ICMP6_PACKET_TOO_BIG, print_packet_too_big }, { ICMP6_TIME_EXCEEDED, print_time_exceeded }, { ICMP6_PARAM_PROB, print_param_prob }, }; static void print_icmp_error (struct sockaddr_in6 *from, struct icmp6_hdr *icmp6, int len) { char *s; struct icmp_diag *p; s = ipaddr2str (from); printf ("%d bytes from %s: ", len, s); free (s); for (p = icmp_diag; p < icmp_diag + NITEMS(icmp_diag); p++) { if (p->type == icmp6->icmp6_type) { p->func (icmp6); return; } } /* This should never happen because of the ICMP6_FILTER set in ping_init(). */ printf ("Unknown ICMP type: %d\n", icmp6->icmp6_type); } static int echo_finish () { ping_finish (); if (ping->ping_num_recv && PING_TIMING (data_length)) { struct ping_stat *ping_stat = (struct ping_stat*)ping->ping_closure; double total = ping->ping_num_recv + ping->ping_num_rept; double avg = ping_stat->tsum/total; double vari = ping_stat->tsumsq / total - avg * avg; printf ("round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms\n", ping_stat->tmin, avg, ping_stat->tmax, nsqrt (vari, 0.0005)); } exit (ping->ping_num_recv == 0); } static void show_usage (void) { printf ("\ Usage: ping6 [OPTION]... [ADDRESS]...\n\ \n\ Informational options:\n\ -h, --help display this help and exit\n\ -L, --license display license and exit\n\ -V, --version output version information and exit\n\ Options valid for all request types:\n\ -c, --count N stop after sending N packets\n\ -d, --debug set the SO_DEBUG option\n\ -i, --interval N wait N seconds between sending each packet\n\ -n, --numeric do not resolve host addresses\n\ -r, --ignore-routing send directly to a host on an attached network\n\ Options valid for --echo requests:\n\ * -f, --flood flood ping \n\ * -l, --preload N send N packets as fast as possible before falling into\n\ normal mode of behavior\n\ -p, --pattern PAT fill ICMP packet with given pattern (hex)\n\ -q, --quiet quiet output\n\ -s, --size N set number of data octets to send\n\ \n\ Options marked with an * are available only to super-user\n\ \n\ report bugs to " PACKAGE_BUGREPORT ".\n\ "); } static PING * ping_init (int type, int ident) { int fd, err; const int on = 1; PING *p; struct icmp6_filter filter; /* Initialize raw ICMPv6 socket */ fd = socket (PF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (fd < 0) { if (errno == EPERM) { fprintf (stderr, "ping: ping must run as root\n"); } return NULL; } /* Tell which ICMPs we are interested in. */ ICMP6_FILTER_SETBLOCKALL (&filter); ICMP6_FILTER_SETPASS (ICMP6_ECHO_REPLY, &filter); ICMP6_FILTER_SETPASS (ICMP6_DST_UNREACH, &filter); ICMP6_FILTER_SETPASS (ICMP6_PACKET_TOO_BIG, &filter); ICMP6_FILTER_SETPASS (ICMP6_TIME_EXCEEDED, &filter); ICMP6_FILTER_SETPASS (ICMP6_PARAM_PROB, &filter); err = setsockopt (fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof (filter)); if (err) { close (fd); return NULL; } err = setsockopt (fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof (on)); if (err) { close (fd); return NULL; } /* Allocate PING structure and initialize it to default values */ p = malloc (sizeof (*p)); if (!p) { close (fd); return NULL; } memset (p, 0, sizeof (*p)); p->ping_fd = fd; p->ping_count = 0; p->ping_interval = PING_INTERVAL; p->ping_datalen = sizeof (struct icmp6_hdr); /* Make sure we use only 16 bits in this field, id for icmp is a u_short. */ p->ping_ident = ident & 0xFFFF; p->ping_cktab_size = PING_CKTABSIZE; return p; } static int _ping_setbuf (PING *p) { if (!p->ping_buffer) { p->ping_buffer = malloc (_PING_BUFLEN (p)); if (!p->ping_buffer) return -1; } if (!p->ping_cktab) { p->ping_cktab = malloc (p->ping_cktab_size); if (!p->ping_cktab) return -1; memset (p->ping_cktab, 0, p->ping_cktab_size); } return 0; } static int ping_set_data (PING *p, void *data, size_t off, size_t len) { if (_ping_setbuf (p)) return -1; if (p->ping_datalen < off + len) return -1; memcpy (p->ping_buffer + sizeof (struct icmp6_hdr) + off, data, len); return 0; } static int ping_xmit (PING *p) { int i, buflen; struct icmp6_hdr *icmp6; if (_ping_setbuf (p)) return -1; buflen = p->ping_datalen + sizeof (struct icmp6_hdr); /* Mark sequence number as sent */ _PING_CLR (p, p->ping_num_xmit % p->ping_cktab_size); icmp6 = (struct icmp6_hdr *) p->ping_buffer; icmp6->icmp6_type = ICMP6_ECHO_REQUEST; icmp6->icmp6_code = 0; /* The checksum will be calculated by the TCP/IP stack. */ icmp6->icmp6_cksum = 0; icmp6->icmp6_id = htons (p->ping_ident); icmp6->icmp6_seq = htons (p->ping_num_xmit); i = sendto (p->ping_fd, (char *)p->ping_buffer, buflen, 0, (struct sockaddr*) &p->ping_dest, sizeof (p->ping_dest)); if (i < 0) perror ("ping: sendto"); else { p->ping_num_xmit++; if (i != buflen) printf ("ping: wrote %s %d chars, ret=%d\n", p->ping_hostname, buflen, i); } return 0; } static int my_echo_reply (PING *p, struct icmp6_hdr *icmp6) { struct ip6_hdr *orig_ip = (struct ip6_hdr *) (icmp6 + 1); struct icmp6_hdr *orig_icmp = (struct icmp6_hdr *)(orig_ip + 1); return IN6_ARE_ADDR_EQUAL (&orig_ip->ip6_dst, &ping->ping_dest.sin6_addr) && orig_ip->ip6_nxt == IPPROTO_ICMPV6 && orig_icmp->icmp6_type == ICMP6_ECHO_REQUEST && orig_icmp->icmp6_id == htons (p->ping_ident); } static int ping_recv (PING *p) { int dupflag, n; int hops = -1; struct msghdr msg; struct iovec iov; struct icmp6_hdr *icmp6; struct cmsghdr *cmsg; char cmsg_data[1024]; iov.iov_base = p->ping_buffer; iov.iov_len = _PING_BUFLEN (p); msg.msg_name = &p->ping_from; msg.msg_namelen = sizeof (p->ping_from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsg_data; msg.msg_controllen = sizeof (cmsg_data); msg.msg_flags = 0; n = recvmsg (p->ping_fd, &msg, 0); if (n < 0) return -1; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR (&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) { hops = *(int *)CMSG_DATA(cmsg); break; } } icmp6 = (struct icmp6_hdr *) p->ping_buffer; if (icmp6->icmp6_type == ICMP6_ECHO_REPLY) { /* We got an echo reply. */ if (ntohs (icmp6->icmp6_id) != p->ping_ident) return -1; /* It's not a response to us. */ if (_PING_TST (p, ntohs (icmp6->icmp6_seq) % p->ping_cktab_size)) { /* We already got the reply for this echo request. */ p->ping_num_rept++; dupflag = 1; } else { _PING_SET (p, ntohs (icmp6->icmp6_seq) % p->ping_cktab_size); p->ping_num_recv++; dupflag = 0; } print_echo (dupflag, hops, p->ping_closure, &p->ping_dest, &p->ping_from, icmp6, n); } else { /* We got an error reply. */ if (!my_echo_reply (p, icmp6)) return -1; /* It's not for us. */ print_icmp_error (&p->ping_from, icmp6, n); } return 0; } static int ping_set_dest (PING *ping, char *host) { int err; struct addrinfo *result, hints; memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_INET6; err = getaddrinfo (host, NULL, &hints, &result); if (err) return 1; memcpy (&ping->ping_dest, result->ai_addr, result->ai_addrlen); freeaddrinfo (result); ping->ping_hostname = strdup (host); if (!ping->ping_hostname) return 1; return 0; }