Skip to content

Instantly share code, notes, and snippets.

@rofl0r
Last active May 30, 2022 08:56
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rofl0r/8260823 to your computer and use it in GitHub Desktop.
Save rofl0r/8260823 to your computer and use it in GitHub Desktop.
ping utility ripped off from busybox rev 1.22.0, turned into a standalone program + added SOCK_DGRAM functionality for root-free ping and ping6. [ http://lists.busybox.net/pipermail/busybox/2014-January/080206.html rejected SOCK_DGRAM patch as taken from busybox ml]
/* vi: set sw=4 ts=4: */
/*
* Mini ping implementation for busybox
*
* Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
*
* Adapted from the ping in netkit-base 0.10:
* Copyright (c) 1989 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Mike Muuss.
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
/* from ping6.c:
* Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
*
* This version of ping is adapted from the ping in netkit-base 0.10,
* which is:
*
* Original copyright notice is retained at the end of this file.
*
* This version is an adaptation of ping.c from busybox.
* The code was modified by Bart Visscher <magick@linux-fan.com>
*/
#include <time.h>
#include <stdarg.h>
#include <unistd.h>
#include <limits.h>
#include <net/if.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <stddef.h>
#include <netinet/icmp6.h>
#include <netdb.h>
#include <stdint.h>
#include <endian.h>
#include <ctype.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <syscall.h>
#if (__BYTE_ORDER == __BIG_ENDIAN) || (__BYTE_ORDER__ == __BIG_ENDIAN__)
#define BB_LITTLE_ENDIAN 0
#else
#define BB_LITTLE_ENDIAN 1
#endif
#define FAST_FUNC
#define ENABLE_PING6 1
#define ENABLE_FEATURE_IPV6 1
#define ENABLE_FEATURE_CLEAN_UP 1
#define ENABLE_FEATURE_FANCY_PING 1
#define IF_PING6(X) X
#define NORETURN
#define UNUSED_PARAM
#define EXIT_FAILURE 1
#define EXIT_SUCCESS 0
#define ENABLE_FEATURE_PREFER_IPV4_ADDRESS 1
#define ENABLE_FEATURE_UNIX_LOCAL 0
#define MAIN_EXTERNALLY_VISIBLE
#define ALIGNED(X) __attribute__((aligned(X)))
#define bb_msg_can_not_create_raw_socket "can't create raw socket"
#define bb_msg_perm_denied_are_you_root "permission denied (are you root?)"
#define bb_msg_memory_exhausted "out of memory"
#define bb_msg_write_error "write error"
#define bb_perror_msg(...) do { dprintf(2, __VA_ARGS__); perror(""); } while(0)
#define bb_error_msg(...) dprintf(2, __VA_ARGS__)
#define move_from_unaligned_int(v, intp) (memcpy(&(v), (intp), sizeof(int)))
#define bb__aliased_uint32_t uint32_t
typedef signed char smallint;
const int const_int_1 = 1;
void FAST_FUNC xfunc_die(void){ exit(1); }
void bb_show_usage(void) { printf("usage:\n"); exit(1); }
void bb_perror_msg_and_die(const char *s) { perror(s); exit(1); }
#define bb_error_msg_and_die(...) do { dprintf(2, __VA_ARGS__); exit(1); } while(0)
/* libc has incredibly messy way of doing this,
* typically requiring -lrt. We just skip all this mess */
static void get_mono(struct timespec *ts)
{
if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, ts))
bb_error_msg_and_die("clock_gettime(MONOTONIC) failed");
}
unsigned long long FAST_FUNC monotonic_us(void)
{
struct timespec ts;
get_mono(&ts);
return ts.tv_sec * 1000000ULL + ts.tv_nsec/1000;
}
int FAST_FUNC fflush_all(void)
{
return fflush(NULL);
}
void FAST_FUNC xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen)
{
if (bind(sockfd, my_addr, addrlen)) bb_perror_msg_and_die("bind");
}
static unsigned long long ret_ERANGE(void)
{
errno = ERANGE; /* this ain't as small as it looks (on glibc) */
return ULLONG_MAX;
}
static unsigned long long handle_errors(unsigned long long v, char **endp)
{
char next_ch = **endp;
/* errno is already set to ERANGE by strtoXXX if value overflowed */
if (next_ch) {
/* "1234abcg" or out-of-range? */
if (isalnum(next_ch) || errno)
return ret_ERANGE();
/* good number, just suspicious terminator */
errno = EINVAL;
}
return v;
}
unsigned FAST_FUNC bb_strtou(const char *arg, char **endp, int base)
{
unsigned long v;
char *endptr;
if (!endp) endp = &endptr;
*endp = (char*) arg;
/* strtoul(" -4200000000") returns 94967296, errno 0 (!) */
/* I don't think that this is right. Preventing this... */
if (!isalnum(arg[0])) return ret_ERANGE();
/* not 100% correct for lib func, but convenient for the caller */
errno = 0;
v = strtoul(arg, endp, base);
if (v > UINT_MAX) return ret_ERANGE();
return handle_errors(v, endp);
}
void FAST_FUNC set_nport(struct sockaddr *sa, unsigned port)
{
#if ENABLE_FEATURE_IPV6
if (sa->sa_family == AF_INET6) {
struct sockaddr_in6 *sin6 = (void*) sa;
sin6->sin6_port = port;
return;
}
#endif
if (sa->sa_family == AF_INET) {
struct sockaddr_in *sin = (void*) sa;
sin->sin_port = port;
return;
}
/* What? UNIX socket? IPX?? :) */
}
/* Like strncpy but make sure the resulting string is always 0 terminated. */
char* FAST_FUNC safe_strncpy(char *dst, const char *src, size_t size)
{
if (!size) return dst;
dst[--size] = '\0';
return strncpy(dst, src, size);
}
char* FAST_FUNC strncpy_IFNAMSIZ(char *dst, const char *src)
{
#ifndef IFNAMSIZ
enum { IFNAMSIZ = 16 };
#endif
return strncpy(dst, src, IFNAMSIZ);
}
// Die if we can't allocate size bytes of memory.
void* FAST_FUNC xmalloc(size_t size)
{
void *ptr = malloc(size);
if (ptr == NULL && size != 0)
bb_error_msg_and_die(bb_msg_memory_exhausted);
return ptr;
}
// Die if we can't allocate and zero size bytes of memory.
void* FAST_FUNC xzalloc(size_t size)
{
void *ptr = xmalloc(size);
memset(ptr, 0, size);
return ptr;
}
enum {
PARAM_STRING,
PARAM_LIST,
PARAM_INT,
};
typedef struct llist_t {
struct llist_t *link;
char *data;
} llist_t;
/* Add data to the end of the linked list. */
void FAST_FUNC llist_add_to_end(llist_t **list_head, void *data)
{
while (*list_head)
list_head = &(*list_head)->link;
*list_head = xzalloc(sizeof(llist_t));
(*list_head)->data = data;
/*(*list_head)->link = NULL;*/
}
int FAST_FUNC setsockopt_broadcast(int fd)
{
return setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &const_int_1, sizeof(const_int_1));
}
#ifdef SO_BINDTODEVICE
int FAST_FUNC setsockopt_bindtodevice(int fd, const char *iface)
{
int r;
struct ifreq ifr;
strncpy_IFNAMSIZ(ifr.ifr_name, iface);
/* NB: passing (iface, strlen(iface) + 1) does not work!
* (maybe it works on _some_ kernels, but not on 2.6.26)
* Actually, ifr_name is at offset 0, and in practice
* just giving char[IFNAMSIZ] instead of struct ifreq works too.
* But just in case it's not true on some obscure arch... */
r = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
if (r)
bb_perror_msg("can't bind to interface %s", iface);
return r;
}
#else
int FAST_FUNC setsockopt_bindtodevice(int fd UNUSED_PARAM,
const char *iface UNUSED_PARAM)
{
bb_error_msg("SO_BINDTODEVICE is not supported on this system");
return -1;
}
#endif
typedef struct {
unsigned char opt_char;
smallint param_type;
unsigned switch_on;
unsigned switch_off;
unsigned incongruously;
unsigned requires;
void **optarg; /* char**, llist_t** or int *. */
int *counter;
} t_complementary;
const char* opt_complementary;
uint32_t option_mask32;
int xatoi_positive(const char *);
uint32_t FAST_FUNC
getopt32(char **argv, const char *applet_opts, ...)
{
int argc;
unsigned flags = 0;
unsigned requires = 0;
t_complementary complementary[33]; /* last stays zero-filled */
char first_char;
int c;
const unsigned char *s;
t_complementary *on_off;
va_list p;
#if ENABLE_LONG_OPTS || ENABLE_FEATURE_GETOPT_LONG
const struct option *l_o;
struct option *long_options = (struct option *) &bb_null_long_options;
#endif
unsigned trigger;
char **pargv;
int min_arg = 0;
int max_arg = -1;
#define SHOW_USAGE_IF_ERROR 1
#define ALL_ARGV_IS_OPTS 2
#define FIRST_ARGV_IS_OPT 4
int spec_flgs = 0;
/* skip 0: some applets cheat: they do not actually HAVE argv[0] */
argc = 1;
while (argv[argc])
argc++;
va_start(p, applet_opts);
c = 0;
on_off = complementary;
memset(on_off, 0, sizeof(complementary));
/* skip bbox extension */
first_char = applet_opts[0];
if (first_char == '!')
applet_opts++;
/* skip GNU extension */
s = (const unsigned char *)applet_opts;
if (*s == '+' || *s == '-')
s++;
while (*s) {
if (c >= 32)
break;
on_off->opt_char = *s;
on_off->switch_on = (1 << c);
if (*++s == ':') {
on_off->optarg = va_arg(p, void **);
while (*++s == ':')
continue;
}
on_off++;
c++;
}
#if ENABLE_LONG_OPTS || ENABLE_FEATURE_GETOPT_LONG
if (applet_long_options) {
const char *optstr;
unsigned i, count;
count = 1;
optstr = applet_long_options;
while (optstr[0]) {
optstr += strlen(optstr) + 3; /* skip NUL, has_arg, val */
count++;
}
/* count == no. of longopts + 1 */
long_options = alloca(count * sizeof(*long_options));
memset(long_options, 0, count * sizeof(*long_options));
i = 0;
optstr = applet_long_options;
while (--count) {
long_options[i].name = optstr;
optstr += strlen(optstr) + 1;
long_options[i].has_arg = (unsigned char)(*optstr++);
/* long_options[i].flag = NULL; */
long_options[i].val = (unsigned char)(*optstr++);
i++;
}
for (l_o = long_options; l_o->name; l_o++) {
if (l_o->flag)
continue;
for (on_off = complementary; on_off->opt_char; on_off++)
if (on_off->opt_char == l_o->val)
goto next_long;
if (c >= 32)
break;
on_off->opt_char = l_o->val;
on_off->switch_on = (1 << c);
if (l_o->has_arg != no_argument)
on_off->optarg = va_arg(p, void **);
c++;
next_long: ;
}
/* Make it unnecessary to clear applet_long_options
* by hand after each call to getopt32
*/
applet_long_options = NULL;
}
#endif /* ENABLE_LONG_OPTS || ENABLE_FEATURE_GETOPT_LONG */
for (s = (const unsigned char *)opt_complementary; s && *s; s++) {
t_complementary *pair;
unsigned *pair_switch;
if (*s == ':')
continue;
c = s[1];
if (*s == '?') {
if (c < '0' || c > '9') {
spec_flgs |= SHOW_USAGE_IF_ERROR;
} else {
max_arg = c - '0';
s++;
}
continue;
}
if (*s == '-') {
if (c < '0' || c > '9') {
if (c == '-') {
spec_flgs |= FIRST_ARGV_IS_OPT;
s++;
} else
spec_flgs |= ALL_ARGV_IS_OPTS;
} else {
min_arg = c - '0';
s++;
}
continue;
}
if (*s == '=') {
min_arg = max_arg = c - '0';
s++;
continue;
}
for (on_off = complementary; on_off->opt_char; on_off++)
if (on_off->opt_char == *s)
goto found_opt;
/* Without this, diagnostic of such bugs is not easy */
bb_error_msg_and_die("NO OPT %c!", *s);
found_opt:
if (c == ':' && s[2] == ':') {
on_off->param_type = PARAM_LIST;
continue;
}
if (c == '+' && (s[2] == ':' || s[2] == '\0')) {
on_off->param_type = PARAM_INT;
s++;
continue;
}
if (c == ':' || c == '\0') {
requires |= on_off->switch_on;
continue;
}
if (c == '-' && (s[2] == ':' || s[2] == '\0')) {
flags |= on_off->switch_on;
on_off->incongruously |= on_off->switch_on;
s++;
continue;
}
if (c == *s) {
on_off->counter = va_arg(p, int *);
s++;
}
pair = on_off;
pair_switch = &pair->switch_on;
for (s++; *s && *s != ':'; s++) {
if (*s == '?') {
pair_switch = &pair->requires;
} else if (*s == '-') {
if (pair_switch == &pair->switch_off)
pair_switch = &pair->incongruously;
else
pair_switch = &pair->switch_off;
} else {
for (on_off = complementary; on_off->opt_char; on_off++)
if (on_off->opt_char == *s) {
*pair_switch |= on_off->switch_on;
break;
}
}
}
s--;
}
opt_complementary = NULL;
va_end(p);
if (spec_flgs & (FIRST_ARGV_IS_OPT | ALL_ARGV_IS_OPTS)) {
pargv = argv + 1;
while (*pargv) {
if (pargv[0][0] != '-' && pargv[0][0] != '\0') {
/* Can't use alloca: opts with params will
* return pointers to stack!
* NB: we leak these allocations... */
char *pp = xmalloc(strlen(*pargv) + 2);
*pp = '-';
strcpy(pp + 1, *pargv);
*pargv = pp;
}
if (!(spec_flgs & ALL_ARGV_IS_OPTS))
break;
pargv++;
}
}
/* In case getopt32 was already called:
* reset the libc getopt() function, which keeps internal state.
* run_nofork_applet() does this, but we might end up here
* also via gunzip_main() -> gzip_main(). Play safe.
*/
#ifdef __GLIBC__
optind = 0;
#else /* BSD style */
optind = 1;
/* optreset = 1; */
#endif
/* optarg = NULL; opterr = 0; optopt = 0; - do we need this?? */
/* Note: just "getopt() <= 0" will not work well for
* "fake" short options, like this one:
* wget $'-\203' "Test: test" http://kernel.org/
* (supposed to act as --header, but doesn't) */
#if ENABLE_LONG_OPTS || ENABLE_FEATURE_GETOPT_LONG
while ((c = getopt_long(argc, argv, applet_opts,
long_options, NULL)) != -1) {
#else
while ((c = getopt(argc, argv, applet_opts)) != -1) {
#endif
/* getopt prints "option requires an argument -- X"
* and returns '?' if an option has no arg, but one is reqd */
c &= 0xff; /* fight libc's sign extension */
for (on_off = complementary; on_off->opt_char != c; on_off++) {
/* c can be NUL if long opt has non-NULL ->flag,
* but we construct long opts so that flag
* is always NULL (see above) */
if (on_off->opt_char == '\0' /* && c != '\0' */) {
/* c is probably '?' - "bad option" */
goto error;
}
}
if (flags & on_off->incongruously)
goto error;
trigger = on_off->switch_on & on_off->switch_off;
flags &= ~(on_off->switch_off ^ trigger);
flags |= on_off->switch_on ^ trigger;
flags ^= trigger;
if (on_off->counter)
(*(on_off->counter))++;
if (optarg) {
if (on_off->param_type == PARAM_LIST) {
llist_add_to_end((llist_t **)(on_off->optarg), optarg);
} else if (on_off->param_type == PARAM_INT) {
//TODO: xatoi_positive indirectly pulls in printf machinery
*(unsigned*)(on_off->optarg) = xatoi_positive(optarg);
} else if (on_off->optarg) {
*(char **)(on_off->optarg) = optarg;
}
}
}
/* check depending requires for given options */
for (on_off = complementary; on_off->opt_char; on_off++) {
if (on_off->requires
&& (flags & on_off->switch_on)
&& (flags & on_off->requires) == 0
) {
goto error;
}
}
if (requires && (flags & requires) == 0)
goto error;
argc -= optind;
if (argc < min_arg || (max_arg >= 0 && argc > max_arg))
goto error;
option_mask32 = flags;
return flags;
error:
if (first_char != '!')
bb_show_usage();
return (int32_t)-1;
}
enum { COMMON_BUFSIZE = (BUFSIZ >= 256*sizeof(void*) ? BUFSIZ+1 : 256*sizeof(void*)) };
char bb_common_bufsiz1[COMMON_BUFSIZE] ALIGNED(sizeof(long long));
typedef struct len_and_sockaddr {
socklen_t len;
union {
struct sockaddr sa;
struct sockaddr_in sin;
#if ENABLE_FEATURE_IPV6
struct sockaddr_in6 sin6;
#endif
} u;
} len_and_sockaddr;
enum {
LSA_LEN_SIZE = offsetof(len_and_sockaddr, u),
LSA_SIZEOF_SA = sizeof(
union {
struct sockaddr sa;
struct sockaddr_in sin;
#if ENABLE_FEATURE_IPV6
struct sockaddr_in6 sin6;
#endif
}
)
};
/* We hijack this constant to mean something else */
/* It doesn't hurt because we will remove this bit anyway */
#define DIE_ON_ERROR AI_CANONNAME
/* host: "1.2.3.4[:port]", "www.google.com[:port]"
* port: if neither of above specifies port # */
static len_and_sockaddr* str2sockaddr(
const char *host, int port,
sa_family_t af,
int ai_flags)
{
//IF_NOT_FEATURE_IPV6(sa_family_t af = AF_INET;)
int rc;
len_and_sockaddr *r;
struct addrinfo *result = NULL;
struct addrinfo *used_res;
const char *org_host = host; /* only for error msg */
const char *cp;
struct addrinfo hint;
if (ENABLE_FEATURE_UNIX_LOCAL && strncmp(host, "local:", 6) == 0) {
struct sockaddr_un *sun;
r = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_un));
r->len = sizeof(struct sockaddr_un);
r->u.sa.sa_family = AF_UNIX;
sun = (struct sockaddr_un *)&r->u.sa;
safe_strncpy(sun->sun_path, host + 6, sizeof(sun->sun_path));
return r;
}
r = NULL;
/* Ugly parsing of host:addr */
if (ENABLE_FEATURE_IPV6 && host[0] == '[') {
/* Even uglier parsing of [xx]:nn */
host++;
cp = strchr(host, ']');
if (!cp || (cp[1] != ':' && cp[1] != '\0')) {
/* Malformed: must be [xx]:nn or [xx] */
bb_error_msg("bad address '%s'", org_host);
if (ai_flags & DIE_ON_ERROR)
xfunc_die();
return NULL;
}
} else {
cp = strrchr(host, ':');
if (ENABLE_FEATURE_IPV6 && cp && strchr(host, ':') != cp) {
/* There is more than one ':' (e.g. "::1") */
cp = NULL; /* it's not a port spec */
}
}
if (cp) { /* points to ":" or "]:" */
int sz = cp - host + 1;
host = safe_strncpy(alloca(sz), host, sz);
if (ENABLE_FEATURE_IPV6 && *cp != ':') {
cp++; /* skip ']' */
if (*cp == '\0') /* [xx] without port */
goto skip;
}
cp++; /* skip ':' */
port = bb_strtou(cp, NULL, 10);
if (errno || (unsigned)port > 0xffff) {
bb_error_msg("bad port spec '%s'", org_host);
if (ai_flags & DIE_ON_ERROR)
xfunc_die();
return NULL;
}
skip: ;
}
/* Next two if blocks allow to skip getaddrinfo()
* in case host name is a numeric IP(v6) address.
* getaddrinfo() initializes DNS resolution machinery,
* scans network config and such - tens of syscalls.
*/
/* If we were not asked specifically for IPv6,
* check whether this is a numeric IPv4 */
//IF_FEATURE_IPV6(if(af != AF_INET6)) {
if(af != AF_INET6) {
struct in_addr in4;
if (inet_aton(host, &in4) != 0) {
r = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_in));
r->len = sizeof(struct sockaddr_in);
r->u.sa.sa_family = AF_INET;
r->u.sin.sin_addr = in4;
goto set_port;
}
}
#if ENABLE_FEATURE_IPV6
/* If we were not asked specifically for IPv4,
* check whether this is a numeric IPv6 */
if (af != AF_INET) {
struct in6_addr in6;
if (inet_pton(AF_INET6, host, &in6) > 0) {
r = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_in6));
r->len = sizeof(struct sockaddr_in6);
r->u.sa.sa_family = AF_INET6;
r->u.sin6.sin6_addr = in6;
goto set_port;
}
}
#endif
memset(&hint, 0 , sizeof(hint));
hint.ai_family = af;
/* Need SOCK_STREAM, or else we get each address thrice (or more)
* for each possible socket type (tcp,udp,raw...): */
hint.ai_socktype = SOCK_STREAM;
hint.ai_flags = ai_flags & ~DIE_ON_ERROR;
rc = getaddrinfo(host, NULL, &hint, &result);
if (rc || !result) {
bb_error_msg("bad address '%s'", org_host);
if (ai_flags & DIE_ON_ERROR)
xfunc_die();
goto ret;
}
used_res = result;
#if ENABLE_FEATURE_PREFER_IPV4_ADDRESS
while (1) {
if (used_res->ai_family == AF_INET)
break;
used_res = used_res->ai_next;
if (!used_res) {
used_res = result;
break;
}
}
#endif
r = xmalloc(LSA_LEN_SIZE + used_res->ai_addrlen);
r->len = used_res->ai_addrlen;
memcpy(&r->u.sa, used_res->ai_addr, used_res->ai_addrlen);
set_port:
set_nport(&r->u.sa, htons(port));
ret:
if (result)
freeaddrinfo(result);
return r;
}
#if !ENABLE_FEATURE_IPV6
#define str2sockaddr(host, port, af, ai_flags) str2sockaddr(host, port, ai_flags)
#endif
len_and_sockaddr* FAST_FUNC xdotted2sockaddr(const char *host, int port)
{
return str2sockaddr(host, port, AF_UNSPEC, AI_NUMERICHOST | DIE_ON_ERROR);
}
len_and_sockaddr* FAST_FUNC xhost_and_af2sockaddr(const char *host, int port, sa_family_t af)
{
return str2sockaddr(host, port, af, DIE_ON_ERROR);
}
// Die if we can't copy a string to freshly allocated memory.
char* FAST_FUNC xstrdup(const char *s)
{
char *t;
if (s == NULL)
return NULL;
t = strdup(s);
if (t == NULL)
bb_error_msg_and_die(bb_msg_memory_exhausted);
return t;
}
// Die with an error message if we can't malloc() enough space and do an
// sprintf() into that space.
char* FAST_FUNC xasprintf(const char *format, ...)
{
va_list p;
int r;
char *string_ptr;
va_start(p, format);
r = vasprintf(&string_ptr, format, p);
va_end(p);
if (r < 0)
bb_error_msg_and_die(bb_msg_memory_exhausted);
return string_ptr;
}
/* We hijack this constant to mean something else */
/* It doesn't hurt because we will add this bit anyway */
#define IGNORE_PORT NI_NUMERICSERV
static char* FAST_FUNC sockaddr2str(const struct sockaddr *sa, int flags)
{
char host[128];
char serv[16];
int rc;
socklen_t salen;
if (ENABLE_FEATURE_UNIX_LOCAL && sa->sa_family == AF_UNIX) {
struct sockaddr_un *sun = (struct sockaddr_un *)sa;
return xasprintf("local:%.*s",
(int) sizeof(sun->sun_path),
sun->sun_path);
}
salen = LSA_SIZEOF_SA;
#if ENABLE_FEATURE_IPV6
if (sa->sa_family == AF_INET)
salen = sizeof(struct sockaddr_in);
if (sa->sa_family == AF_INET6)
salen = sizeof(struct sockaddr_in6);
#endif
rc = getnameinfo(sa, salen,
host, sizeof(host),
/* can do ((flags & IGNORE_PORT) ? NULL : serv) but why bother? */
serv, sizeof(serv),
/* do not resolve port# into service _name_ */
flags | NI_NUMERICSERV
);
if (rc)
return NULL;
if (flags & IGNORE_PORT)
return xstrdup(host);
#if ENABLE_FEATURE_IPV6
if (sa->sa_family == AF_INET6) {
if (strchr(host, ':')) /* heh, it's not a resolved hostname */
return xasprintf("[%s]:%s", host, serv);
/*return xasprintf("%s:%s", host, serv);*/
/* - fall through instead */
}
#endif
/* For now we don't support anything else, so it has to be INET */
/*if (sa->sa_family == AF_INET)*/
return xasprintf("%s:%s", host, serv);
/*return xstrdup(host);*/
}
char* FAST_FUNC xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa)
{
return sockaddr2str(sa, NI_NUMERICHOST | IGNORE_PORT);
}
/* Die with an error message if sendto failed.
* Return bytes sent otherwise */
ssize_t FAST_FUNC xsendto(int s, const void *buf, size_t len, const struct sockaddr *to,
socklen_t tolen)
{
ssize_t ret = sendto(s, buf, len, 0, to, tolen);
if (ret < 0) {
if (ENABLE_FEATURE_CLEAN_UP)
close(s);
bb_perror_msg_and_die("sendto");
}
return ret;
}
void FAST_FUNC xdup2(int from, int to)
{
if (dup2(from, to) != to)
bb_perror_msg_and_die("can't duplicate file descriptor");
}
// "Renumber" opened fd
void FAST_FUNC xmove_fd(int from, int to)
{
if (from == to)
return;
xdup2(from, to);
close(from);
}
uint16_t FAST_FUNC inet_cksum(uint16_t *addr, int nleft)
{
/*
* Our algorithm is simple, using a 32 bit accumulator,
* we add sequential 16 bit words to it, and at the end, fold
* back all the carry bits from the top 16 bits into the lower
* 16 bits.
*/
unsigned sum = 0;
while (nleft > 1) {
sum += *addr++;
nleft -= 2;
}
/* Mop up an odd byte, if necessary */
if (nleft == 1) {
if (BB_LITTLE_ENDIAN)
sum += *(uint8_t*)addr;
else
sum += *(uint8_t*)addr << 8;
}
/* Add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
return (uint16_t)~sum;
}
struct suffix_mult {
char suffix[4];
unsigned mult;
};
#define XSTR_STRTOU strtoul
#define XSTR_UTYPE_MAX UINT_MAX
unsigned int FAST_FUNC xstrtou_range_sfx(const char *numstr, int base,
unsigned int lower,
unsigned int upper,
const struct suffix_mult *suffixes)
{
unsigned int r;
int old_errno;
char *e;
/* Disallow '-' and any leading whitespace. */
if (*numstr == '-' || *numstr == '+' || isspace(*numstr))
goto inval;
/* Since this is a lib function, we're not allowed to reset errno to 0.
* Doing so could break an app that is deferring checking of errno.
* So, save the old value so that we can restore it if successful. */
old_errno = errno;
errno = 0;
r = XSTR_STRTOU(numstr, &e, base);
/* Do the initial validity check. Note: The standards do not
* guarantee that errno is set if no digits were found. So we
* must test for this explicitly. */
if (errno || numstr == e)
goto inval; /* error / no digits / illegal trailing chars */
errno = old_errno; /* Ok. So restore errno. */
/* Do optional suffix parsing. Allow 'empty' suffix tables.
* Note that we also allow nul suffixes with associated multipliers,
* to allow for scaling of the numstr by some default multiplier. */
if (suffixes) {
while (suffixes->mult) {
if (strcmp(suffixes->suffix, e) == 0) {
if (XSTR_UTYPE_MAX / suffixes->mult < r)
goto range; /* overflow! */
r *= suffixes->mult;
goto chk_range;
}
++suffixes;
}
}
/* Note: trailing space is an error.
* It would be easy enough to allow though if desired. */
if (*e)
goto inval;
chk_range:
/* Finally, check for range limits. */
if (r >= lower && r <= upper)
return r;
range:
bb_error_msg_and_die("number %s is not in %llu..%llu range",
numstr, (unsigned long long)lower,
(unsigned long long)upper);
inval:
bb_error_msg_and_die("invalid number '%s'", numstr);
}
unsigned int FAST_FUNC xatou_range(const char *numstr,
unsigned int lower,
unsigned int upper)
{
return xstrtou_range_sfx(numstr, 10, lower, upper, NULL);
}
int FAST_FUNC xatoi_positive(const char *numstr)
{
return xatou_range(numstr, 0, INT_MAX);
}
uint16_t FAST_FUNC xatou16(const char *numstr)
{
return xatou_range(numstr, 0, 0xffff);
}
//usage:#if !ENABLE_FEATURE_FANCY_PING
//usage:# define ping_trivial_usage
//usage: "HOST"
//usage:# define ping_full_usage "\n\n"
//usage: "Send ICMP ECHO_REQUEST packets to network hosts"
//usage:# define ping6_trivial_usage
//usage: "HOST"
//usage:# define ping6_full_usage "\n\n"
//usage: "Send ICMP ECHO_REQUEST packets to network hosts"
//usage:#else
//usage:# define ping_trivial_usage
//usage: "[OPTIONS] HOST"
//usage:# define ping_full_usage "\n\n"
//usage: "Send ICMP ECHO_REQUEST packets to network hosts\n"
//usage: IF_PING6(
//usage: "\n -4,-6 Force IP or IPv6 name resolution"
//usage: )
//usage: "\n -c CNT Send only CNT pings"
//usage: "\n -s SIZE Send SIZE data bytes in packets (default:56)"
//usage: "\n -t TTL Set TTL"
//usage: "\n -I IFACE/IP Use interface or IP address as source"
//usage: "\n -W SEC Seconds to wait for the first response (default:10)"
//usage: "\n (after all -c CNT packets are sent)"
//usage: "\n -w SEC Seconds until ping exits (default:infinite)"
//usage: "\n (can exit earlier with -c CNT)"
//usage: "\n -q Quiet, only displays output at start"
//usage: "\n and when finished"
//usage:
//usage:# define ping6_trivial_usage
//usage: "[OPTIONS] HOST"
//usage:# define ping6_full_usage "\n\n"
//usage: "Send ICMP ECHO_REQUEST packets to network hosts\n"
//usage: "\n -c CNT Send only CNT pings"
//usage: "\n -s SIZE Send SIZE data bytes in packets (default:56)"
//usage: "\n -I IFACE/IP Use interface or IP address as source"
//usage: "\n -q Quiet, only displays output at start"
//usage: "\n and when finished"
//usage:
//usage:#endif
//usage:
//usage:#define ping_example_usage
//usage: "$ ping localhost\n"
//usage: "PING slag (127.0.0.1): 56 data bytes\n"
//usage: "64 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=20.1 ms\n"
//usage: "\n"
//usage: "--- debian ping statistics ---\n"
//usage: "1 packets transmitted, 1 packets received, 0% packet loss\n"
//usage: "round-trip min/avg/max = 20.1/20.1/20.1 ms\n"
//usage:#define ping6_example_usage
//usage: "$ ping6 ip6-localhost\n"
//usage: "PING ip6-localhost (::1): 56 data bytes\n"
//usage: "64 bytes from ::1: icmp6_seq=0 ttl=64 time=20.1 ms\n"
//usage: "\n"
//usage: "--- ip6-localhost ping statistics ---\n"
//usage: "1 packets transmitted, 1 packets received, 0% packet loss\n"
//usage: "round-trip min/avg/max = 20.1/20.1/20.1 ms\n"
#if ENABLE_PING6
# include <netinet/icmp6.h>
/* I see RENUMBERED constants in bits/in.h - !!?
* What a fuck is going on with libc? Is it a glibc joke? */
# ifdef IPV6_2292HOPLIMIT
# undef IPV6_HOPLIMIT
# define IPV6_HOPLIMIT IPV6_2292HOPLIMIT
# endif
#endif
enum {
DEFDATALEN = 56,
MAXIPLEN = 60,
MAXICMPLEN = 76,
MAX_DUP_CHK = (8 * 128),
MAXWAIT = 10,
PINGINTERVAL = 1, /* 1 second */
pingsock = 0,
};
static int using_dgram;
static void
#if ENABLE_PING6
create_icmp_socket(len_and_sockaddr *lsa)
#else
create_icmp_socket(void)
#define create_icmp_socket(lsa) create_icmp_socket()
#endif
{
int sock;
#if ENABLE_PING6
if (lsa->u.sa.sa_family == AF_INET6)
sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
else
#endif
sock = socket(AF_INET, SOCK_RAW, 1); /* 1 == ICMP */
if (sock < 0) {
if (errno != EPERM)
bb_perror_msg_and_die(bb_msg_can_not_create_raw_socket);
#if defined(__linux__) || defined(__APPLE__)
/* We don't have root privileges. Try SOCK_DGRAM instead.
* Linux needs net.ipv4.ping_group_range for this to work.
* MacOSX allows ICMP_ECHO, ICMP_TSTAMP or ICMP_MASKREQ
*/
#if ENABLE_PING6
if (lsa->u.sa.sa_family == AF_INET6)
sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
else
#endif
sock = socket(AF_INET, SOCK_DGRAM, 1); /* 1 == ICMP */
if (sock < 0)
#endif
bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
using_dgram = 1;
}
xmove_fd(sock, pingsock);
}
#if !ENABLE_FEATURE_FANCY_PING
/* Simple version */
struct globals {
char *hostname;
char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
} FIX_ALIASING;
#define G (*(struct globals*)&bb_common_bufsiz1)
#define INIT_G() do { } while (0)
static void noresp(int ign UNUSED_PARAM)
{
printf("No response from %s\n", G.hostname);
exit(EXIT_FAILURE);
}
static void ping4(len_and_sockaddr *lsa)
{
struct icmp *pkt;
int c;
pkt = (struct icmp *) G.packet;
/*memset(pkt, 0, sizeof(G.packet)); already is */
pkt->icmp_type = ICMP_ECHO;
pkt->icmp_cksum = inet_cksum((uint16_t *) pkt, sizeof(G.packet));
xsendto(pingsock, G.packet, DEFDATALEN + ICMP_MINLEN, &lsa->u.sa, lsa->len);
/* listen for replies */
while (1) {
#if 0
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
c = recvfrom(pingsock, G.packet, sizeof(G.packet), 0,
(struct sockaddr *) &from, &fromlen);
#else
c = recv(pingsock, G.packet, sizeof(G.packet), 0);
#endif
if (c < 0) {
if (errno != EINTR)
bb_perror_msg("recvfrom");
continue;
}
if (c >= 76 || using_dgram && (c == 64)) { /* ip + icmp */
if(!using_dgram) {
struct iphdr *iphdr = (struct iphdr *) G.packet;
pkt = (struct icmp *) (G.packet + (iphdr->ihl << 2)); /* skip ip hdr */
} else pkt = (struct icmp *) G.packet;
if (pkt->icmp_type == ICMP_ECHOREPLY)
break;
}
}
if (ENABLE_FEATURE_CLEAN_UP)
close(pingsock);
}
#if ENABLE_PING6
static void ping6(len_and_sockaddr *lsa)
{
struct icmp6_hdr *pkt;
int c;
int sockopt;
pkt = (struct icmp6_hdr *) G.packet;
/*memset(pkt, 0, sizeof(G.packet)); already is */
pkt->icmp6_type = ICMP6_ECHO_REQUEST;
sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt));
xsendto(pingsock, G.packet, DEFDATALEN + sizeof(struct icmp6_hdr), &lsa->u.sa, lsa->len);
/* listen for replies */
while (1) {
#if 0
struct sockaddr_in6 from;
socklen_t fromlen = sizeof(from);
c = recvfrom(pingsock, G.packet, sizeof(G.packet), 0,
(struct sockaddr *) &from, &fromlen);
#else
c = recv(pingsock, G.packet, sizeof(G.packet), 0);
#endif
if (c < 0) {
if (errno != EINTR)
bb_perror_msg("recvfrom");
continue;
}
if (c >= ICMP_MINLEN) { /* icmp6_hdr */
if (pkt->icmp6_type == ICMP6_ECHO_REPLY)
break;
}
}
if (ENABLE_FEATURE_CLEAN_UP)
close(pingsock);
}
#endif
#if !ENABLE_PING6
# define common_ping_main(af, argv) common_ping_main(argv)
#endif
static int common_ping_main(sa_family_t af, char **argv)
{
len_and_sockaddr *lsa;
INIT_G();
#if ENABLE_PING6
while ((++argv)[0] && argv[0][0] == '-') {
if (argv[0][1] == '4') {
af = AF_INET;
continue;
}
if (argv[0][1] == '6') {
af = AF_INET6;
continue;
}
bb_show_usage();
}
#else
argv++;
#endif
G.hostname = *argv;
if (!G.hostname)
bb_show_usage();
#if ENABLE_PING6
lsa = xhost_and_af2sockaddr(G.hostname, 0, af);
#else
lsa = xhost_and_af2sockaddr(G.hostname, 0, AF_INET);
#endif
/* Set timer _after_ DNS resolution */
signal(SIGALRM, noresp);
// XXXX reenable alarm alarm(5); /* give the host 5000ms to respond */
create_icmp_socket(lsa);
#if ENABLE_PING6
if (lsa->u.sa.sa_family == AF_INET6)
ping6(lsa);
else
#endif
ping4(lsa);
printf("%s is alive!\n", G.hostname);
return EXIT_SUCCESS;
}
#else /* FEATURE_FANCY_PING */
/* Full(er) version */
#define OPT_STRING ("qvc:s:t:w:W:I:n4" IF_PING6("6"))
enum {
OPT_QUIET = 1 << 0,
OPT_VERBOSE = 1 << 1,
OPT_c = 1 << 2,
OPT_s = 1 << 3,
OPT_t = 1 << 4,
OPT_w = 1 << 5,
OPT_W = 1 << 6,
OPT_I = 1 << 7,
/*OPT_n = 1 << 8, - ignored */
OPT_IPV4 = 1 << 9,
OPT_IPV6 = (1 << 10) * ENABLE_PING6,
};
struct globals {
int if_index;
char *str_I;
len_and_sockaddr *source_lsa;
unsigned datalen;
unsigned pingcount; /* must be int-sized */
unsigned opt_ttl;
unsigned long ntransmitted, nreceived, nrepeats;
uint16_t myid;
unsigned tmin, tmax; /* in us */
unsigned long long tsum; /* in us, sum of all times */
unsigned deadline;
unsigned timeout;
unsigned total_secs;
unsigned sizeof_rcv_packet;
char *rcv_packet; /* [datalen + MAXIPLEN + MAXICMPLEN] */
void *snd_packet; /* [datalen + ipv4/ipv6_const] */
const char *hostname;
const char *dotted;
union {
struct sockaddr sa;
struct sockaddr_in sin;
#if ENABLE_PING6
struct sockaddr_in6 sin6;
#endif
} pingaddr;
unsigned char rcvd_tbl[MAX_DUP_CHK / 8];
} FIX_ALIASING;
#define G (*(struct globals*)&bb_common_bufsiz1)
#define if_index (G.if_index )
#define source_lsa (G.source_lsa )
#define str_I (G.str_I )
#define datalen (G.datalen )
#define pingcount (G.pingcount )
#define opt_ttl (G.opt_ttl )
#define myid (G.myid )
#define tmin (G.tmin )
#define tmax (G.tmax )
#define tsum (G.tsum )
#define deadline (G.deadline )
#define timeout (G.timeout )
#define total_secs (G.total_secs )
#define hostname (G.hostname )
#define dotted (G.dotted )
#define pingaddr (G.pingaddr )
#define rcvd_tbl (G.rcvd_tbl )
void BUG_ping_globals_too_big(void);
#define INIT_G() do { \
if (sizeof(G) > COMMON_BUFSIZE) \
BUG_ping_globals_too_big(); \
datalen = DEFDATALEN; \
timeout = MAXWAIT; \
tmin = UINT_MAX; \
} while (0)
#define BYTE(bit) rcvd_tbl[(bit)>>3]
#define MASK(bit) (1 << ((bit) & 7))
#define SET(bit) (BYTE(bit) |= MASK(bit))
#define CLR(bit) (BYTE(bit) &= (~MASK(bit)))
#define TST(bit) (BYTE(bit) & MASK(bit))
static void print_stats_and_exit(int junk) NORETURN;
static void print_stats_and_exit(int junk UNUSED_PARAM)
{
unsigned long ul;
unsigned long nrecv;
signal(SIGINT, SIG_IGN);
nrecv = G.nreceived;
printf("\n--- %s ping statistics ---\n"
"%lu packets transmitted, "
"%lu packets received, ",
hostname, G.ntransmitted, nrecv
);
if (G.nrepeats)
printf("%lu duplicates, ", G.nrepeats);
ul = G.ntransmitted;
if (ul != 0)
ul = (ul - nrecv) * 100 / ul;
printf("%lu%% packet loss\n", ul);
if (tmin != UINT_MAX) {
unsigned tavg = tsum / (nrecv + G.nrepeats);
printf("round-trip min/avg/max = %u.%03u/%u.%03u/%u.%03u ms\n",
tmin / 1000, tmin % 1000,
tavg / 1000, tavg % 1000,
tmax / 1000, tmax % 1000);
}
/* if condition is true, exit with 1 -- 'failure' */
exit(nrecv == 0 || (deadline && nrecv < pingcount));
}
static void sendping_tail(void (*sp)(int), int size_pkt)
{
int sz;
CLR((uint16_t)G.ntransmitted % MAX_DUP_CHK);
G.ntransmitted++;
size_pkt += datalen;
/* sizeof(pingaddr) can be larger than real sa size, but I think
* it doesn't matter */
sz = xsendto(pingsock, G.snd_packet, size_pkt, &pingaddr.sa, sizeof(pingaddr));
if (sz != size_pkt)
bb_error_msg_and_die(bb_msg_write_error);
if (pingcount == 0 || deadline || G.ntransmitted < pingcount) {
/* Didn't send all pings yet - schedule next in 1s */
signal(SIGALRM, sp);
if (deadline) {
total_secs += PINGINTERVAL;
if (total_secs >= deadline)
signal(SIGALRM, print_stats_and_exit);
}
alarm(PINGINTERVAL);
} else { /* -c NN, and all NN are sent (and no deadline) */
/* Wait for the last ping to come back.
* -W timeout: wait for a response in seconds.
* Affects only timeout in absense of any responses,
* otherwise ping waits for two RTTs. */
unsigned expire = timeout;
if (G.nreceived) {
/* approx. 2*tmax, in seconds (2 RTT) */
expire = tmax / (512*1024);
if (expire == 0)
expire = 1;
}
signal(SIGALRM, print_stats_and_exit);
alarm(expire);
}
}
static void sendping4(int junk UNUSED_PARAM)
{
struct icmp *pkt = G.snd_packet;
//memset(pkt, 0, datalen + ICMP_MINLEN + 4); - G.snd_packet was xzalloced
pkt->icmp_type = ICMP_ECHO;
/*pkt->icmp_code = 0;*/
pkt->icmp_cksum = 0; /* cksum is calculated with this field set to 0 */
pkt->icmp_seq = htons(G.ntransmitted); /* don't ++ here, it can be a macro */
pkt->icmp_id = myid;
/* If datalen < 4, we store timestamp _past_ the packet,
* but it's ok - we allocated 4 extra bytes in xzalloc() just in case.
*/
/*if (datalen >= 4)*/
/* No hton: we'll read it back on the same machine */
*(uint32_t*)&pkt->icmp_dun = monotonic_us();
pkt->icmp_cksum = inet_cksum((uint16_t *) pkt, datalen + ICMP_MINLEN);
sendping_tail(sendping4, ICMP_MINLEN);
}
#if ENABLE_PING6
static void sendping6(int junk UNUSED_PARAM)
{
struct icmp6_hdr *pkt = G.snd_packet;
//memset(pkt, 0, datalen + sizeof(struct icmp6_hdr) + 4);
pkt->icmp6_type = ICMP6_ECHO_REQUEST;
/*pkt->icmp6_code = 0;*/
/*pkt->icmp6_cksum = 0;*/
pkt->icmp6_seq = htons(G.ntransmitted); /* don't ++ here, it can be a macro */
pkt->icmp6_id = myid;
/*if (datalen >= 4)*/
*(bb__aliased_uint32_t*)(&pkt->icmp6_data8[4]) = monotonic_us();
//TODO? pkt->icmp_cksum = inet_cksum(...);
sendping_tail(sendping6, sizeof(struct icmp6_hdr));
}
#endif
static const char *icmp_type_name(int id)
{
switch (id) {
case ICMP_ECHOREPLY: return "Echo Reply";
case ICMP_DEST_UNREACH: return "Destination Unreachable";
case ICMP_SOURCE_QUENCH: return "Source Quench";
case ICMP_REDIRECT: return "Redirect (change route)";
case ICMP_ECHO: return "Echo Request";
case ICMP_TIME_EXCEEDED: return "Time Exceeded";
case ICMP_PARAMETERPROB: return "Parameter Problem";
case ICMP_TIMESTAMP: return "Timestamp Request";
case ICMP_TIMESTAMPREPLY: return "Timestamp Reply";
case ICMP_INFO_REQUEST: return "Information Request";
case ICMP_INFO_REPLY: return "Information Reply";
case ICMP_ADDRESS: return "Address Mask Request";
case ICMP_ADDRESSREPLY: return "Address Mask Reply";
default: return "unknown ICMP type";
}
}
#if ENABLE_PING6
/* RFC3542 changed some definitions from RFC2292 for no good reason, whee!
* the newer 3542 uses a MLD_ prefix where as 2292 uses ICMP6_ prefix */
#ifndef MLD_LISTENER_QUERY
# define MLD_LISTENER_QUERY ICMP6_MEMBERSHIP_QUERY
#endif
#ifndef MLD_LISTENER_REPORT
# define MLD_LISTENER_REPORT ICMP6_MEMBERSHIP_REPORT
#endif
#ifndef MLD_LISTENER_REDUCTION
# define MLD_LISTENER_REDUCTION ICMP6_MEMBERSHIP_REDUCTION
#endif
static const char *icmp6_type_name(int id)
{
switch (id) {
case ICMP6_DST_UNREACH: return "Destination Unreachable";
case ICMP6_PACKET_TOO_BIG: return "Packet too big";
case ICMP6_TIME_EXCEEDED: return "Time Exceeded";
case ICMP6_PARAM_PROB: return "Parameter Problem";
case ICMP6_ECHO_REPLY: return "Echo Reply";
case ICMP6_ECHO_REQUEST: return "Echo Request";
case MLD_LISTENER_QUERY: return "Listener Query";
case MLD_LISTENER_REPORT: return "Listener Report";
case MLD_LISTENER_REDUCTION: return "Listener Reduction";
default: return "unknown ICMP type";
}
}
#endif
static void unpack_tail(int sz, uint32_t *tp,
const char *from_str,
uint16_t recv_seq, int ttl)
{
unsigned char *b, m;
const char *dupmsg = " (DUP!)";
unsigned triptime = triptime; /* for gcc */
if (tp) {
/* (int32_t) cast is for hypothetical 64-bit unsigned */
/* (doesn't hurt 32-bit real-world anyway) */
triptime = (int32_t) ((uint32_t)monotonic_us() - *tp);
tsum += triptime;
if (triptime < tmin)
tmin = triptime;
if (triptime > tmax)
tmax = triptime;
}
b = &BYTE(recv_seq % MAX_DUP_CHK);
m = MASK(recv_seq % MAX_DUP_CHK);
/*if TST(recv_seq % MAX_DUP_CHK):*/
if (*b & m) {
++G.nrepeats;
} else {
/*SET(recv_seq % MAX_DUP_CHK):*/
*b |= m;
++G.nreceived;
dupmsg += 7;
}
if (option_mask32 & OPT_QUIET)
return;
printf("%d bytes from %s: seq=%u ttl=%d", sz,
from_str, recv_seq, ttl);
if (tp)
printf(" time=%u.%03u ms", triptime / 1000, triptime % 1000);
puts(dupmsg);
fflush_all();
}
static void unpack4(char *buf, int sz, struct sockaddr_in *from)
{
struct iphdr *iphdr;
struct icmp *icmppkt;
int hlen;
/* discard if too short */
if (sz < (datalen + ICMP_MINLEN))
return;
if(!using_dgram) {
/* check IP header */
iphdr = (struct iphdr *) buf;
hlen = iphdr->ihl << 2;
sz -= hlen;
icmppkt = (struct icmp *) (buf + hlen);
} else icmppkt = (struct icmp *) buf;
if (icmppkt->icmp_id != myid)
return; /* not our ping */
if (icmppkt->icmp_type == ICMP_ECHOREPLY) {
uint16_t recv_seq = ntohs(icmppkt->icmp_seq);
uint32_t *tp = NULL;
if (sz >= ICMP_MINLEN + sizeof(uint32_t))
tp = (uint32_t *) icmppkt->icmp_data;
unpack_tail(sz, tp,
inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr),
recv_seq, using_dgram ? 42 : iphdr->ttl);
} else if (icmppkt->icmp_type != ICMP_ECHO) {
bb_error_msg("warning: got ICMP %d (%s)",
icmppkt->icmp_type,
icmp_type_name(icmppkt->icmp_type));
}
}
#if ENABLE_PING6
static void unpack6(char *packet, int sz, struct sockaddr_in6 *from, int hoplimit)
{
struct icmp6_hdr *icmppkt;
char buf[INET6_ADDRSTRLEN];
/* discard if too short */
if (sz < (datalen + sizeof(struct icmp6_hdr)))
return;
icmppkt = (struct icmp6_hdr *) packet;
if (icmppkt->icmp6_id != myid)
return; /* not our ping */
if (icmppkt->icmp6_type == ICMP6_ECHO_REPLY) {
uint16_t recv_seq = ntohs(icmppkt->icmp6_seq);
uint32_t *tp = NULL;
if (sz >= sizeof(struct icmp6_hdr) + sizeof(uint32_t))
tp = (uint32_t *) &icmppkt->icmp6_data8[4];
unpack_tail(sz, tp,
inet_ntop(AF_INET6, &from->sin6_addr,
buf, sizeof(buf)),
recv_seq, hoplimit);
} else if (icmppkt->icmp6_type != ICMP6_ECHO_REQUEST) {
bb_error_msg("warning: got ICMP %d (%s)",
icmppkt->icmp6_type,
icmp6_type_name(icmppkt->icmp6_type));
}
}
#endif
static void ping4(len_and_sockaddr *lsa)
{
int sockopt;
pingaddr.sin = lsa->u.sin;
if (source_lsa && !using_dgram) {
if (setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_IF,
&source_lsa->u.sa, source_lsa->len))
bb_error_msg_and_die("can't set multicast source interface");
xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
} else if(using_dgram) {
struct sockaddr_in sa;
socklen_t sl;
sa.sin_family = AF_INET;
sa.sin_port = 0;
sa.sin_addr.s_addr = source_lsa ?
source_lsa->u.sin.sin_addr.s_addr : 0;
sl = sizeof(sa);
if (bind(pingsock, (struct sockaddr *) &sa, sl) == -1) {
perror("bind");
exit(2);
}
if (getsockname(pingsock, (struct sockaddr *) &sa, &sl) == -1) {
perror("getsockname");
exit(2);
}
myid = sa.sin_port;
}
/* enable broadcast pings */
setsockopt_broadcast(pingsock);
/* set recv buf (needed if we can get lots of responses: flood ping,
* broadcast ping etc) */
sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */
setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
if (opt_ttl != 0) {
setsockopt(pingsock, IPPROTO_IP, IP_TTL, &opt_ttl, sizeof(opt_ttl));
/* above doesnt affect packets sent to bcast IP, so... */
setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_TTL, &opt_ttl, sizeof(opt_ttl));
}
if(using_dgram) {
int hold = 65536;
if (setsockopt(pingsock, SOL_IP, IP_RECVTTL, (char *)&hold, sizeof(hold)))
perror("WARNING: setsockopt(IP_RECVTTL)");
if (setsockopt(pingsock, SOL_IP, IP_RETOPTS, (char *)&hold, sizeof(hold)))
perror("WARNING: setsockopt(IP_RETOPTS)");
}
signal(SIGINT, print_stats_and_exit);
/* start the ping's going ... */
sendping4(0);
/* listen for replies */
while (1) {
struct sockaddr_in from;
socklen_t fromlen = (socklen_t) sizeof(from);
int c;
c = recvfrom(pingsock, G.rcv_packet, G.sizeof_rcv_packet, 0,
(struct sockaddr *) &from, &fromlen);
if (c < 0) {
if (errno != EINTR)
bb_perror_msg("recvfrom");
continue;
}
unpack4(G.rcv_packet, c, &from);
if (pingcount && G.nreceived >= pingcount)
break;
}
}
#if ENABLE_PING6
extern int BUG_bad_offsetof_icmp6_cksum(void);
static void ping6(len_and_sockaddr *lsa)
{
int sockopt;
struct msghdr msg;
struct sockaddr_in6 from;
struct iovec iov;
char control_buf[CMSG_SPACE(36)];
pingaddr.sin6 = lsa->u.sin6;
if (source_lsa && !using_dgram)
xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
else if(using_dgram) {
struct sockaddr_in6 sa = {0};
socklen_t sl;
sa.sin6_family = AF_INET6;
sa.sin6_port = 0;
if(source_lsa) {
memcpy(&sa.sin6_addr, &source_lsa->u.sin6.sin6_addr, sizeof(struct in6_addr));
}
sl = sizeof(sa);
if (bind(pingsock, (struct sockaddr *) &sa, sl) == -1) {
perror("bind");
exit(2);
}
if (getsockname(pingsock, (struct sockaddr *) &sa, &sl) == -1) {
perror("getsockname");
exit(2);
}
myid = sa.sin6_port;
}
#ifdef ICMP6_FILTER
if(!using_dgram)
{
struct icmp6_filter filt;
if (!(option_mask32 & OPT_VERBOSE)) {
ICMP6_FILTER_SETBLOCKALL(&filt);
ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt);
} else {
ICMP6_FILTER_SETPASSALL(&filt);
}
if (setsockopt(pingsock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
sizeof(filt)) < 0)
bb_error_msg_and_die("setsockopt(ICMP6_FILTER)");
}
#endif /*ICMP6_FILTER*/
/* enable broadcast pings */
setsockopt_broadcast(pingsock);
/* set recv buf (needed if we can get lots of responses: flood ping,
* broadcast ping etc) */
sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */
setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
if (offsetof(struct icmp6_hdr, icmp6_cksum) != 2)
BUG_bad_offsetof_icmp6_cksum();
setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt));
/* request ttl info to be returned in ancillary data */
setsockopt(pingsock, SOL_IPV6, IPV6_HOPLIMIT, &const_int_1, sizeof(const_int_1));
if (if_index)
pingaddr.sin6.sin6_scope_id = if_index;
signal(SIGINT, print_stats_and_exit);
/* start the ping's going ... */
sendping6(0);
/* listen for replies */
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = control_buf;
iov.iov_base = G.rcv_packet;
iov.iov_len = G.sizeof_rcv_packet;
while (1) {
int c;
struct cmsghdr *mp;
int hoplimit = -1;
msg.msg_controllen = sizeof(control_buf);
c = recvmsg(pingsock, &msg, 0);
if (c < 0) {
if (errno != EINTR)
bb_perror_msg("recvfrom");
continue;
}
for (mp = CMSG_FIRSTHDR(&msg); mp; mp = CMSG_NXTHDR(&msg, mp)) {
if (mp->cmsg_level == SOL_IPV6
&& mp->cmsg_type == IPV6_HOPLIMIT
/* don't check len - we trust the kernel: */
/* && mp->cmsg_len >= CMSG_LEN(sizeof(int)) */
) {
/*hoplimit = *(int*)CMSG_DATA(mp); - unaligned access */
move_from_unaligned_int(hoplimit, CMSG_DATA(mp));
}
}
unpack6(G.rcv_packet, c, &from, hoplimit);
if (pingcount && G.nreceived >= pingcount)
break;
}
}
#endif
static void ping(len_and_sockaddr *lsa)
{
printf("PING %s (%s)", hostname, dotted);
if (source_lsa) {
printf(" from %s",
xmalloc_sockaddr2dotted_noport(&source_lsa->u.sa));
}
printf(": %d data bytes\n", datalen);
create_icmp_socket(lsa);
/* untested whether "-I addr" really works for IPv6: */
if (str_I)
setsockopt_bindtodevice(pingsock, str_I);
G.sizeof_rcv_packet = datalen + MAXIPLEN + MAXICMPLEN;
G.rcv_packet = xzalloc(G.sizeof_rcv_packet);
#if ENABLE_PING6
if (lsa->u.sa.sa_family == AF_INET6) {
/* +4 reserves a place for timestamp, which may end up sitting
* _after_ packet. Saves one if() - see sendping4/6() */
G.snd_packet = xzalloc(datalen + sizeof(struct icmp6_hdr) + 4);
ping6(lsa);
} else
#endif
{
G.snd_packet = xzalloc(datalen + ICMP_MINLEN + 4);
ping4(lsa);
}
}
static int common_ping_main(int opt, char **argv)
{
len_and_sockaddr *lsa;
char *str_s;
INIT_G();
/* exactly one argument needed; -v and -q don't mix; -c NUM, -t NUM, -w NUM, -W NUM */
opt_complementary = "=1:q--v:v--q:c+:t+:w+:W+";
opt |= getopt32(argv, OPT_STRING, &pingcount, &str_s, &opt_ttl, &deadline, &timeout, &str_I);
if (opt & OPT_s)
datalen = xatou16(str_s); // -s
if (opt & OPT_I) { // -I
if_index = if_nametoindex(str_I);
if (!if_index) {
/* TODO: I'm not sure it takes IPv6 unless in [XX:XX..] format */
source_lsa = xdotted2sockaddr(str_I, 0);
str_I = NULL; /* don't try to bind to device later */
}
}
if(!using_dgram) myid = (uint16_t) getpid();
hostname = argv[optind];
#if ENABLE_PING6
{
sa_family_t af = AF_UNSPEC;
if (opt & OPT_IPV4)
af = AF_INET;
if (opt & OPT_IPV6)
af = AF_INET6;
lsa = xhost_and_af2sockaddr(hostname, 0, af);
}
#else
lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET);
#endif
if (source_lsa && source_lsa->u.sa.sa_family != lsa->u.sa.sa_family)
/* leaking it here... */
source_lsa = NULL;
dotted = xmalloc_sockaddr2dotted_noport(&lsa->u.sa);
ping(lsa);
print_stats_and_exit(EXIT_SUCCESS);
/*return EXIT_SUCCESS;*/
}
#endif /* FEATURE_FANCY_PING */
int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ping_main(int argc UNUSED_PARAM, char **argv)
{
#if !ENABLE_FEATURE_FANCY_PING
return common_ping_main(AF_UNSPEC, argv);
#else
return common_ping_main(0, argv);
#endif
}
#if ENABLE_PING6
int ping6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ping6_main(int argc UNUSED_PARAM, char **argv)
{
# if !ENABLE_FEATURE_FANCY_PING
return common_ping_main(AF_INET6, argv);
# else
return common_ping_main(OPT_IPV6, argv);
# endif
}
#endif
int main(int argc, char** argv) {
if(argc > 1 && !strcmp(argv[1], "-6"))
return ping6_main(argc, argv);
else
return ping_main(argc, argv);
}
/* from ping6.c:
* Copyright (c) 1989 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Mike Muuss.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
* ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
*
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
@rofl0r
Copy link
Author

rofl0r commented Jan 14, 2014

in order to make it work for regular users, do

echo "1000 2147483647" > /proc/sys/net/ipv4/ping_group_range

as root

@bmmade
Copy link

bmmade commented Mar 5, 2016

Nice utility - can you give me a quick "usage". i want to ping icmpas well as normal ports

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment