mirror of
https://git.savannah.gnu.org/git/inetutils.git
synced 2026-01-12 00:19:39 +08:00
1046 lines
24 KiB
C
1046 lines
24 KiB
C
/*
|
|
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012,
|
|
2013, 2014, 2015, 2016 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 3 of the License, 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 A 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 this program. If not, see `http://www.gnu.org/licenses/'. */
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef __sun
|
|
# define _XPG4_2 1 /* OpenSolaris: msg_control */
|
|
#endif /* __sun */
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <sys/uio.h>
|
|
#include <signal.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netinet/icmp6.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <argp.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#ifdef HAVE_LOCALE_H
|
|
# include <locale.h>
|
|
#endif
|
|
#ifdef HAVE_IDNA_H
|
|
# include <idna.h>
|
|
#endif
|
|
|
|
#include <unused-parameter.h>
|
|
#include <xalloc.h>
|
|
#include "ping6.h"
|
|
#include "libinetutils.h"
|
|
|
|
static PING *ping;
|
|
bool is_root = false;
|
|
unsigned char *data_buffer;
|
|
unsigned char *patptr;
|
|
int one = 1;
|
|
int pattern_len = MAXPATTERN;
|
|
size_t data_length = PING_DATALEN;
|
|
size_t count = DEFAULT_PING_COUNT;
|
|
size_t interval;
|
|
int socket_type;
|
|
int timeout = -1;
|
|
int hoplimit = 0;
|
|
unsigned int options;
|
|
static unsigned long preload = 0;
|
|
#ifdef IPV6_TCLASS
|
|
int tclass = -1; /* Kernel sets default: -1, RFC 3542. */
|
|
#endif
|
|
#ifdef IPV6_FLOWINFO
|
|
int flowinfo;
|
|
#endif
|
|
|
|
static int ping_echo (char *hostname);
|
|
static void ping_reset (PING * p);
|
|
static int send_echo (PING * ping);
|
|
|
|
const char args_doc[] = "HOST ...";
|
|
const char doc[] = "Send ICMP ECHO_REQUEST packets to network hosts."
|
|
"\vOptions marked with (root only) are available only to "
|
|
"superuser.";
|
|
const char *program_authors[] = {
|
|
"Jeroen Dekkers",
|
|
NULL
|
|
};
|
|
|
|
enum {
|
|
ARG_HOPLIMIT = 256,
|
|
};
|
|
|
|
static struct argp_option argp_options[] = {
|
|
#define GRP 0
|
|
{NULL, 0, NULL, 0, "Options valid for all request types:", GRP},
|
|
{"count", 'c', "NUMBER", 0, "stop after sending NUMBER packets", GRP+1},
|
|
{"debug", 'd', NULL, 0, "set the SO_DEBUG option", GRP+1},
|
|
#ifdef IPV6_FLOWINFO
|
|
{"flowinfo", 'F', "N", 0, "set N as flow identifier", GRP+1},
|
|
#endif
|
|
{"hoplimit", ARG_HOPLIMIT, "N", 0, "specify N as hop-limit", GRP+1},
|
|
{"interval", 'i', "NUMBER", 0, "wait NUMBER seconds between sending each "
|
|
"packet", GRP+1},
|
|
{"numeric", 'n', NULL, 0, "do not resolve host addresses", GRP+1},
|
|
{"ignore-routing", 'r', NULL, 0, "send directly to a host on an attached "
|
|
"network", GRP+1},
|
|
#ifdef IPV6_TCLASS
|
|
{"tos", 'T', "N", 0, "set traffic class to N", GRP+1},
|
|
#endif
|
|
{"timeout", 'w', "N", 0, "stop after N seconds", GRP+1},
|
|
{"ttl", ARG_HOPLIMIT, "N", 0, "synonym for --hoplimit", GRP+1},
|
|
{"verbose", 'v', NULL, 0, "verbose output", GRP+1},
|
|
#undef GRP
|
|
#define GRP 10
|
|
{NULL, 0, NULL, 0, "Options valid for --echo requests:", GRP},
|
|
{"flood", 'f', NULL, 0, "flood ping (root only)", GRP+1},
|
|
{"preload", 'l', "NUMBER", 0, "send NUMBER packets as fast as possible "
|
|
"before falling into normal mode of behavior (root only)", GRP+1},
|
|
{"pattern", 'p', "PATTERN", 0, "fill ICMP packet with given pattern (hex)",
|
|
GRP+1},
|
|
{"quiet", 'q', NULL, 0, "quiet output", GRP+1},
|
|
{"size", 's', "NUMBER", 0, "send NUMBER data octets", GRP+1},
|
|
#undef GRP
|
|
{NULL, 0, NULL, 0, NULL, 0}
|
|
};
|
|
|
|
static error_t
|
|
parse_opt (int key, char *arg, struct argp_state *state)
|
|
{
|
|
char *endptr;
|
|
static unsigned char pattern[MAXPATTERN];
|
|
|
|
switch (key)
|
|
{
|
|
case 'c':
|
|
count = ping_cvt_number (arg, 0, 0);
|
|
break;
|
|
|
|
case 'd':
|
|
socket_type |= SO_DEBUG;
|
|
break;
|
|
|
|
case 'f':
|
|
if (!is_root)
|
|
error (EXIT_FAILURE, 0, "flooding needs root privilege");
|
|
|
|
options |= OPT_FLOOD;
|
|
setbuf (stdout, (char *) NULL);
|
|
break;
|
|
|
|
#ifdef IPV6_FLOWINFO
|
|
case 'F':
|
|
options |= OPT_FLOWINFO;
|
|
flowinfo = ping_cvt_number (arg, 0, 0) & IPV6_FLOWINFO_FLOWLABEL;
|
|
break;
|
|
#endif
|
|
|
|
case 'i':
|
|
options |= OPT_INTERVAL;
|
|
interval = ping_cvt_number (arg, 0, 0);
|
|
break;
|
|
|
|
case 'l':
|
|
if (!is_root)
|
|
error (EXIT_FAILURE, 0, "preloading needs root privilege");
|
|
|
|
preload = strtoul (arg, &endptr, 0);
|
|
if (*endptr || preload > INT_MAX)
|
|
error (EXIT_FAILURE, 0, "preload size too large");
|
|
|
|
break;
|
|
|
|
case 'n':
|
|
options |= OPT_NUMERIC;
|
|
break;
|
|
|
|
case 'p':
|
|
decode_pattern (arg, &pattern_len, pattern);
|
|
patptr = pattern;
|
|
break;
|
|
|
|
case 'q':
|
|
options |= OPT_QUIET;
|
|
break;
|
|
|
|
case 'r':
|
|
socket_type |= SO_DONTROUTE;
|
|
break;
|
|
|
|
case 's':
|
|
data_length = ping_cvt_number (arg, PING_MAX_DATALEN, 1);
|
|
break;
|
|
|
|
#ifdef IPV6_TCLASS
|
|
case 'T':
|
|
options |= OPT_TCLASS;
|
|
tclass = ping_cvt_number (arg, 0, 0);
|
|
break;
|
|
#endif
|
|
|
|
case 'v':
|
|
options |= OPT_VERBOSE;
|
|
break;
|
|
|
|
case 'w':
|
|
timeout = ping_cvt_number (arg, INT_MAX, 0);
|
|
break;
|
|
|
|
case ARG_HOPLIMIT:
|
|
hoplimit = ping_cvt_number (arg, 255, 0);
|
|
break;
|
|
|
|
case ARGP_KEY_NO_ARGS:
|
|
argp_error (state, "missing host operand");
|
|
|
|
default:
|
|
return ARGP_ERR_UNKNOWN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct argp argp =
|
|
{argp_options, parse_opt, args_doc, doc, NULL, NULL, NULL};
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
int index;
|
|
int status = 0;
|
|
|
|
set_program_name (argv[0]);
|
|
|
|
# ifdef HAVE_SETLOCALE
|
|
setlocale (LC_ALL, "");
|
|
# endif
|
|
|
|
if (getuid () == 0)
|
|
is_root = true;
|
|
|
|
/* Parse command line */
|
|
iu_argp_init ("ping6", program_authors);
|
|
argp_parse (&argp, argc, argv, 0, &index, NULL);
|
|
|
|
ping = ping_init (0, getpid ());
|
|
if (ping == NULL)
|
|
/* ping_init() prints our error message. */
|
|
exit (EXIT_FAILURE);
|
|
|
|
setsockopt (ping->ping_fd, SOL_SOCKET, SO_BROADCAST, (char *) &one, sizeof (one));
|
|
|
|
/* Reset root privileges */
|
|
setuid (getuid ());
|
|
|
|
/* Force line buffering regardless of output device. */
|
|
setvbuf (stdout, NULL, _IOLBF, 0);
|
|
|
|
argc -= index;
|
|
argv += index;
|
|
|
|
if (count != 0)
|
|
ping_set_count (ping, count);
|
|
|
|
if (socket_type != 0)
|
|
ping_set_sockopt (ping, socket_type, &one, sizeof (one));
|
|
|
|
if (options & OPT_INTERVAL)
|
|
ping_set_interval (ping, interval);
|
|
|
|
if (hoplimit > 0)
|
|
if (setsockopt (ping->ping_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
|
|
&hoplimit, sizeof (hoplimit)) < 0)
|
|
error (0, errno, "setsockopt(IPV6_HOPLIMIT)");
|
|
|
|
#ifdef IPV6_TCLASS
|
|
if (options & OPT_TCLASS)
|
|
if (setsockopt (ping->ping_fd, IPPROTO_IPV6, IPV6_TCLASS,
|
|
&tclass, sizeof (tclass)) < 0)
|
|
error (EXIT_FAILURE, errno, "setsockopt(IPV6_TCLASS)");
|
|
#endif
|
|
|
|
#ifdef IPV6_FLOWINFO
|
|
if (options & OPT_FLOWINFO)
|
|
if (setsockopt (ping->ping_fd, IPPROTO_IPV6, IPV6_FLOWINFO,
|
|
&flowinfo, sizeof (flowinfo)) < 0)
|
|
error (EXIT_FAILURE, errno, "setsockopt(IPV6_FLOWINFO)");
|
|
#endif
|
|
|
|
init_data_buffer (patptr, pattern_len);
|
|
|
|
while (argc--)
|
|
{
|
|
status |= ping_echo (*argv++);
|
|
ping_reset (ping);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static volatile int stop = 0;
|
|
|
|
static void
|
|
sig_int (int signal _GL_UNUSED_PARAMETER)
|
|
{
|
|
stop = 1;
|
|
}
|
|
|
|
static int
|
|
ping_run (PING * ping, int (*finish) ())
|
|
{
|
|
fd_set fdset;
|
|
int fdmax;
|
|
struct timeval resp_time;
|
|
struct timeval last, intvl, now;
|
|
struct timeval *t = NULL;
|
|
int finishing = 0;
|
|
size_t nresp = 0;
|
|
unsigned long i;
|
|
|
|
signal (SIGINT, sig_int);
|
|
|
|
fdmax = ping->ping_fd + 1;
|
|
|
|
/* Some systems use `struct timeval' of size 16. As these are
|
|
* not initialising `timeval' properly by assignment alone, let
|
|
* us play safely here. gettimeofday() is always sufficient.
|
|
*/
|
|
memset (&resp_time, 0, sizeof (resp_time));
|
|
memset (&intvl, 0, sizeof (intvl));
|
|
memset (&now, 0, sizeof (now));
|
|
|
|
for (i = 0; i < preload; i++)
|
|
send_echo (ping);
|
|
|
|
if (options & OPT_FLOOD)
|
|
{
|
|
intvl.tv_sec = 0;
|
|
intvl.tv_usec = 10000;
|
|
}
|
|
else
|
|
PING_SET_INTERVAL (intvl, ping->ping_interval);
|
|
|
|
gettimeofday (&last, NULL);
|
|
send_echo (ping);
|
|
|
|
while (!stop)
|
|
{
|
|
int n;
|
|
|
|
FD_ZERO (&fdset);
|
|
FD_SET (ping->ping_fd, &fdset);
|
|
|
|
gettimeofday (&now, NULL);
|
|
resp_time.tv_sec = last.tv_sec + intvl.tv_sec - now.tv_sec;
|
|
resp_time.tv_usec = last.tv_usec + intvl.tv_usec - now.tv_usec;
|
|
|
|
while (resp_time.tv_usec < 0)
|
|
{
|
|
resp_time.tv_usec += 1000000;
|
|
resp_time.tv_sec--;
|
|
}
|
|
while (resp_time.tv_usec >= 1000000)
|
|
{
|
|
resp_time.tv_usec -= 1000000;
|
|
resp_time.tv_sec++;
|
|
}
|
|
|
|
if (resp_time.tv_sec < 0)
|
|
resp_time.tv_sec = resp_time.tv_usec = 0;
|
|
|
|
n = select (fdmax, &fdset, NULL, NULL, &resp_time);
|
|
if (n < 0)
|
|
{
|
|
if (errno != EINTR)
|
|
error (EXIT_FAILURE, errno, "select failed");
|
|
continue;
|
|
}
|
|
else if (n == 1)
|
|
{
|
|
if (ping_recv (ping) == 0)
|
|
nresp++;
|
|
if (t == 0)
|
|
{
|
|
gettimeofday (&now, NULL);
|
|
t = &now;
|
|
}
|
|
|
|
if (ping_timeout_p (&ping->ping_start_time, timeout))
|
|
break;
|
|
|
|
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 ('.');
|
|
}
|
|
|
|
if (ping_timeout_p (&ping->ping_start_time, timeout))
|
|
break;
|
|
}
|
|
else if (finishing)
|
|
break;
|
|
else
|
|
{
|
|
finishing = 1;
|
|
|
|
intvl.tv_sec = MAXWAIT;
|
|
}
|
|
gettimeofday (&last, NULL);
|
|
}
|
|
}
|
|
|
|
ping_unset_data (ping);
|
|
|
|
if (finish)
|
|
return (*finish) ();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
send_echo (PING * ping)
|
|
{
|
|
size_t off = 0;
|
|
int rc;
|
|
|
|
if (PING_TIMING (data_length))
|
|
{
|
|
struct timeval tv;
|
|
gettimeofday (&tv, NULL);
|
|
ping_set_data (ping, &tv, 0, sizeof (tv), USE_IPV6);
|
|
off += sizeof (tv);
|
|
}
|
|
if (data_buffer)
|
|
ping_set_data (ping, data_buffer, off,
|
|
data_length > off ? data_length - off : data_length,
|
|
USE_IPV6);
|
|
|
|
rc = ping_xmit (ping);
|
|
if (rc < 0)
|
|
error (EXIT_FAILURE, errno, "sending packet");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
ping_finish (void)
|
|
{
|
|
fflush (stdout);
|
|
printf ("--- %s ping statistics ---\n", ping->ping_hostname);
|
|
printf ("%zu packets transmitted, ", ping->ping_num_xmit);
|
|
printf ("%zu packets received, ", ping->ping_num_recv);
|
|
if (ping->ping_num_rept)
|
|
printf ("+%zu 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 (char *hostname)
|
|
{
|
|
int err;
|
|
char buffer[256];
|
|
struct ping_stat ping_stat;
|
|
int status;
|
|
|
|
if (options & OPT_FLOOD && options & OPT_INTERVAL)
|
|
error (EXIT_FAILURE, 0, "-f and -i incompatible options");
|
|
|
|
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, hostname))
|
|
error (EXIT_FAILURE, 0, "unknown host %s", hostname);
|
|
|
|
err = getnameinfo ((struct sockaddr *) &ping->ping_dest.ping_sockaddr6,
|
|
sizeof (ping->ping_dest.ping_sockaddr6), buffer,
|
|
sizeof (buffer), NULL, 0, NI_NUMERICHOST);
|
|
if (err)
|
|
{
|
|
const char *errmsg;
|
|
|
|
if (err == EAI_SYSTEM)
|
|
errmsg = strerror (errno);
|
|
else
|
|
errmsg = gai_strerror (err);
|
|
|
|
error (EXIT_FAILURE, 0, "getnameinfo: %s", errmsg);
|
|
}
|
|
|
|
printf ("PING %s (%s): %zu data bytes",
|
|
ping->ping_hostname, buffer, data_length);
|
|
if (options & OPT_VERBOSE)
|
|
printf (", id 0x%04x = %u", ping->ping_ident, ping->ping_ident);
|
|
|
|
printf ("\n");
|
|
|
|
status = ping_run (ping, echo_finish);
|
|
free (ping->ping_hostname);
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
ping_reset (PING * p)
|
|
{
|
|
p->ping_num_xmit = 0;
|
|
p->ping_num_recv = 0;
|
|
p->ping_num_rept = 0;
|
|
}
|
|
|
|
static int
|
|
print_echo (int dupflag, int hops, struct ping_stat *ping_stat,
|
|
struct sockaddr_in6 *dest _GL_UNUSED_PARAMETER,
|
|
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,
|
|
(options & OPT_NUMERIC) ? NI_NUMERICHOST
|
|
#ifdef NI_IDN
|
|
: NI_IDN
|
|
#else
|
|
: 0
|
|
#endif
|
|
);
|
|
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,
|
|
ntohs (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);
|
|
}
|
|
|
|
void
|
|
print_ip_data (struct icmp6_hdr *icmp6)
|
|
{
|
|
size_t j;
|
|
struct ip6_hdr *ip = (struct ip6_hdr *) ((char *) icmp6 + sizeof (*icmp6));
|
|
char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN];
|
|
|
|
(void) inet_ntop (AF_INET6, &ip->ip6_dst, dst, sizeof (dst));
|
|
(void) inet_ntop (AF_INET6, &ip->ip6_src, src, sizeof (src));
|
|
|
|
printf ("IP Header Dump:\n ");
|
|
for (j = 0; j < sizeof (*ip) - sizeof (ip->ip6_src) - sizeof (ip->ip6_dst); ++j)
|
|
printf ("%02x%s", *((unsigned char *) ip + j),
|
|
(j % 2) ? " " : ""); /* Group bytes two by two. */
|
|
printf ("(src) (dst)\n");
|
|
|
|
printf ("Vr TC Flow Plen Nxt Hop Src\t\t Dst\n");
|
|
printf (" %1x %02x %04x %4hu %3hhu %3hhu %s %s\n",
|
|
ntohl (ip->ip6_flow) >> 28,
|
|
(ntohl (ip->ip6_flow) & 0x0fffffff) >> 20,
|
|
ntohl (ip->ip6_flow) & 0x0fffff,
|
|
ntohs (ip->ip6_plen), ip->ip6_nxt, ip->ip6_hlim,
|
|
src, dst);
|
|
|
|
switch (ip->ip6_nxt)
|
|
{
|
|
case IPPROTO_ICMPV6:
|
|
{
|
|
struct icmp6_hdr *hdr =
|
|
(struct icmp6_hdr *) ((unsigned char *) ip + sizeof (*ip));
|
|
|
|
printf ("ICMP: type %hhu, code %hhu, size %hu",
|
|
hdr->icmp6_type, hdr->icmp6_code, ntohs (ip->ip6_plen));
|
|
switch (hdr->icmp6_type)
|
|
{
|
|
case ICMP6_ECHO_REQUEST:
|
|
case ICMP6_ECHO_REPLY:
|
|
printf (", id 0x%04x, seq 0x%04x",
|
|
ntohs (hdr->icmp6_id), ntohs (hdr->icmp6_seq));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
printf ("\n");
|
|
}
|
|
|
|
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 ((struct sockaddr *) from, sizeof (*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);
|
|
if (options & OPT_VERBOSE)
|
|
print_ip_data (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 (void)
|
|
{
|
|
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));
|
|
}
|
|
return (ping->ping_num_recv == 0);
|
|
}
|
|
|
|
static PING *
|
|
ping_init (int type _GL_UNUSED_PARAMETER, 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 || errno == EACCES)
|
|
error (EXIT_FAILURE, errno, "raw socket");
|
|
|
|
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 = DEFAULT_PING_COUNT;
|
|
p->ping_interval = PING_DEFAULT_INTERVAL;
|
|
p->ping_datalen = sizeof (struct timeval);
|
|
/* Make sure we use only 16 bits in this field, id for icmp is a unsigned short. */
|
|
p->ping_ident = ident & 0xFFFF;
|
|
p->ping_cktab_size = PING_CKTABSIZE;
|
|
gettimeofday (&p->ping_start_time, NULL);
|
|
return p;
|
|
}
|
|
|
|
static int
|
|
ping_xmit (PING * p)
|
|
{
|
|
int i, buflen;
|
|
struct icmp6_hdr *icmp6;
|
|
|
|
if (_ping_setbuf (p, USE_IPV6))
|
|
return -1;
|
|
|
|
buflen = p->ping_datalen + sizeof (struct icmp6_hdr);
|
|
|
|
/* Mark sequence number as sent */
|
|
_PING_CLR (p, p->ping_num_xmit);
|
|
|
|
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.ping_sockaddr6,
|
|
sizeof (p->ping_dest.ping_sockaddr6));
|
|
if (i < 0)
|
|
return -1;
|
|
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.ping_sockaddr6.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, USE_IPV6);
|
|
msg.msg_name = &p->ping_from.ping_sockaddr6;
|
|
msg.msg_namelen = sizeof (p->ping_from.ping_sockaddr6);
|
|
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)))
|
|
{
|
|
/* 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_num_recv++;
|
|
dupflag = 0;
|
|
}
|
|
|
|
print_echo (dupflag, hops, p->ping_closure, &p->ping_dest.ping_sockaddr6,
|
|
&p->ping_from.ping_sockaddr6, 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.ping_sockaddr6, icmp6, n);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ping_set_dest (PING * ping, char *host)
|
|
{
|
|
int err;
|
|
struct addrinfo *result, hints;
|
|
char *rhost;
|
|
|
|
#ifdef HAVE_IDN
|
|
err = idna_to_ascii_lz (host, &rhost, 0);
|
|
if (err)
|
|
return 1;
|
|
#else /* !HAVE_IDN */
|
|
rhost = host;
|
|
#endif
|
|
|
|
memset (&hints, 0, sizeof (hints));
|
|
hints.ai_family = AF_INET6;
|
|
hints.ai_flags = AI_CANONNAME;
|
|
#ifdef AI_IDN
|
|
hints.ai_flags |= AI_IDN;
|
|
#endif
|
|
#ifdef AI_CANONIDN
|
|
hints.ai_flags |= AI_CANONIDN;
|
|
#endif
|
|
|
|
err = getaddrinfo (rhost, NULL, &hints, &result);
|
|
if (err)
|
|
return 1;
|
|
|
|
memcpy (&ping->ping_dest.ping_sockaddr6, result->ai_addr, result->ai_addrlen);
|
|
|
|
if (result->ai_canonname)
|
|
ping->ping_hostname = strdup (result->ai_canonname);
|
|
else
|
|
ping->ping_hostname = strdup (rhost);
|
|
|
|
#if HAVE_IDN
|
|
free (rhost);
|
|
#endif
|
|
freeaddrinfo (result);
|
|
|
|
if (!ping->ping_hostname)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|