Last active
May 30, 2022 08:56
-
-
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]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* vi: set sw=4 ts=4: */ | |
/* | |
* Mini ping implementation for busybox | |
* | |
* Copyright (C) 1999 by Randolph Chung <[email protected]> | |
* | |
* 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 <[email protected]> | |
* | |
* 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 <[email protected]> | |
*/ | |
#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. | |
*/ |
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
in order to make it work for regular users, do
as root