You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1093 lines
24 KiB
1093 lines
24 KiB
#include "ping_common.h" |
|
#include <ctype.h> |
|
#include <sched.h> |
|
#include <math.h> |
|
|
|
int options; |
|
|
|
__u32 mark; |
|
int sndbuf; |
|
int ttl; |
|
int rtt; |
|
int rtt_addend; |
|
__u16 acked; |
|
|
|
struct rcvd_table rcvd_tbl; |
|
int using_ping_socket = 1; |
|
|
|
|
|
/* counters */ |
|
long npackets; /* max packets to transmit */ |
|
long nreceived; /* # of packets we got back */ |
|
long nrepeats; /* number of duplicates */ |
|
long ntransmitted; /* sequence # for outbound packets = #sent */ |
|
long nchecksum; /* replies with bad checksum */ |
|
long nerrors; /* icmp errors */ |
|
int interval = 1000; /* interval between packets (msec) */ |
|
int preload; |
|
int deadline = 0; /* time to die */ |
|
int lingertime = MAXWAIT*1000; |
|
struct timeval start_time, cur_time; |
|
volatile int exiting; |
|
volatile int status_snapshot; |
|
int confirm = 0; |
|
volatile int in_pr_addr = 0; /* pr_addr() is executing */ |
|
jmp_buf pr_addr_jmp; |
|
|
|
/* Stupid workarounds for bugs/missing functionality in older linuces. |
|
* confirm_flag fixes refusing service of kernels without MSG_CONFIRM. |
|
* i.e. for linux-2.2 */ |
|
int confirm_flag = MSG_CONFIRM; |
|
/* And this is workaround for bug in IP_RECVERR on raw sockets which is present |
|
* in linux-2.2.[0-19], linux-2.4.[0-7] */ |
|
int working_recverr; |
|
|
|
/* timing */ |
|
int timing; /* flag to do timing */ |
|
long tmin = LONG_MAX; /* minimum round trip time */ |
|
long tmax; /* maximum round trip time */ |
|
/* Message for rpm maintainers: have _shame_. If you want |
|
* to fix something send the patch to me for sanity checking. |
|
* "sparcfix" patch is a complete non-sense, apparenly the person |
|
* prepared it was stoned. |
|
*/ |
|
long long tsum; /* sum of all times, for doing average */ |
|
long long tsum2; |
|
int pipesize = -1; |
|
|
|
int datalen = DEFDATALEN; |
|
|
|
char *hostname; |
|
int uid; |
|
uid_t euid; |
|
int ident = 0; /* process id to identify our packets */ |
|
|
|
static int screen_width = INT_MAX; |
|
|
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) |
|
|
|
#ifdef CAPABILITIES |
|
static cap_value_t cap_raw = CAP_NET_RAW; |
|
static cap_value_t cap_admin = CAP_NET_ADMIN; |
|
#endif |
|
|
|
void limit_capabilities(void) |
|
{ |
|
#ifdef CAPABILITIES |
|
cap_t cap_cur_p; |
|
cap_t cap_p; |
|
cap_flag_value_t cap_ok; |
|
|
|
cap_cur_p = cap_get_proc(); |
|
if (!cap_cur_p) { |
|
perror("ping: cap_get_proc"); |
|
exit(-1); |
|
} |
|
|
|
cap_p = cap_init(); |
|
if (!cap_p) { |
|
perror("ping: cap_init"); |
|
exit(-1); |
|
} |
|
|
|
cap_ok = CAP_CLEAR; |
|
cap_get_flag(cap_cur_p, CAP_NET_ADMIN, CAP_PERMITTED, &cap_ok); |
|
|
|
if (cap_ok != CAP_CLEAR) |
|
cap_set_flag(cap_p, CAP_PERMITTED, 1, &cap_admin, CAP_SET); |
|
|
|
cap_ok = CAP_CLEAR; |
|
cap_get_flag(cap_cur_p, CAP_NET_RAW, CAP_PERMITTED, &cap_ok); |
|
|
|
if (cap_ok != CAP_CLEAR) |
|
cap_set_flag(cap_p, CAP_PERMITTED, 1, &cap_raw, CAP_SET); |
|
|
|
if (cap_set_proc(cap_p) < 0) { |
|
perror("ping: cap_set_proc"); |
|
exit(-1); |
|
} |
|
|
|
if (prctl(PR_SET_KEEPCAPS, 1) < 0) { |
|
perror("ping: prctl"); |
|
exit(-1); |
|
} |
|
|
|
if (setuid(getuid()) < 0) { |
|
perror("setuid"); |
|
exit(-1); |
|
} |
|
|
|
if (prctl(PR_SET_KEEPCAPS, 0) < 0) { |
|
perror("ping: prctl"); |
|
exit(-1); |
|
} |
|
|
|
cap_free(cap_p); |
|
cap_free(cap_cur_p); |
|
#endif |
|
uid = getuid(); |
|
euid = geteuid(); |
|
#ifndef CAPABILITIES |
|
if (seteuid(uid)) { |
|
perror("ping: setuid"); |
|
exit(-1); |
|
} |
|
#endif |
|
} |
|
|
|
#ifdef CAPABILITIES |
|
int modify_capability(cap_value_t cap, cap_flag_value_t on) |
|
{ |
|
cap_t cap_p = cap_get_proc(); |
|
cap_flag_value_t cap_ok; |
|
int rc = -1; |
|
|
|
if (!cap_p) { |
|
perror("ping: cap_get_proc"); |
|
goto out; |
|
} |
|
|
|
cap_ok = CAP_CLEAR; |
|
cap_get_flag(cap_p, cap, CAP_PERMITTED, &cap_ok); |
|
if (cap_ok == CAP_CLEAR) { |
|
rc = on ? -1 : 0; |
|
goto out; |
|
} |
|
|
|
cap_set_flag(cap_p, CAP_EFFECTIVE, 1, &cap, on); |
|
|
|
if (cap_set_proc(cap_p) < 0) { |
|
perror("ping: cap_set_proc"); |
|
goto out; |
|
} |
|
|
|
cap_free(cap_p); |
|
|
|
rc = 0; |
|
out: |
|
if (cap_p) |
|
cap_free(cap_p); |
|
return rc; |
|
} |
|
#else |
|
int modify_capability(int on) |
|
{ |
|
if (seteuid(on ? euid : getuid())) { |
|
perror("seteuid"); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
void drop_capabilities(void) |
|
{ |
|
#ifdef CAPABILITIES |
|
cap_t cap = cap_init(); |
|
if (cap_set_proc(cap) < 0) { |
|
perror("ping: cap_set_proc"); |
|
exit(-1); |
|
} |
|
cap_free(cap); |
|
#else |
|
if (setuid(getuid())) { |
|
perror("ping: setuid"); |
|
exit(-1); |
|
} |
|
#endif |
|
} |
|
|
|
void android_check_security(void) |
|
{ |
|
if (getauxval(AT_SECURE) != 0) { |
|
fprintf(stderr, "This version of ping should NOT run with privileges. Aborting\n"); |
|
exit(1); |
|
} |
|
} |
|
|
|
/* Fills all the outpack, excluding ICMP header, but _including_ |
|
* timestamp area with supplied pattern. |
|
*/ |
|
static void fill(char *patp) |
|
{ |
|
int ii, jj, kk; |
|
int pat[16]; |
|
char *cp; |
|
u_char *bp = outpack+8; |
|
|
|
#ifdef USE_IDN |
|
setlocale(LC_ALL, "C"); |
|
#endif |
|
|
|
for (cp = patp; *cp; cp++) { |
|
if (!isxdigit(*cp)) { |
|
fprintf(stderr, |
|
"ping: patterns must be specified as hex digits.\n"); |
|
exit(2); |
|
} |
|
} |
|
ii = sscanf(patp, |
|
"%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x", |
|
&pat[0], &pat[1], &pat[2], &pat[3], &pat[4], &pat[5], &pat[6], |
|
&pat[7], &pat[8], &pat[9], &pat[10], &pat[11], &pat[12], |
|
&pat[13], &pat[14], &pat[15]); |
|
|
|
if (ii > 0) { |
|
for (kk = 0; kk <= maxpacket - (8 + ii); kk += ii) |
|
for (jj = 0; jj < ii; ++jj) |
|
bp[jj + kk] = pat[jj]; |
|
} |
|
if (!(options & F_QUIET)) { |
|
printf("PATTERN: 0x"); |
|
for (jj = 0; jj < ii; ++jj) |
|
printf("%02x", bp[jj] & 0xFF); |
|
printf("\n"); |
|
} |
|
|
|
#ifdef USE_IDN |
|
setlocale(LC_ALL, ""); |
|
#endif |
|
} |
|
|
|
void common_options(int ch) |
|
{ |
|
switch(ch) { |
|
case 'a': |
|
options |= F_AUDIBLE; |
|
break; |
|
case 'A': |
|
options |= F_ADAPTIVE; |
|
break; |
|
case 'c': |
|
npackets = atoi(optarg); |
|
if (npackets <= 0) { |
|
fprintf(stderr, "ping: bad number of packets to transmit.\n"); |
|
exit(2); |
|
} |
|
break; |
|
case 'd': |
|
options |= F_SO_DEBUG; |
|
break; |
|
case 'D': |
|
options |= F_PTIMEOFDAY; |
|
break; |
|
case 'i': /* wait between sending packets */ |
|
{ |
|
double dbl; |
|
char *ep; |
|
|
|
errno = 0; |
|
dbl = strtod(optarg, &ep); |
|
|
|
if (errno || *ep != '\0' || |
|
!finite(dbl) || dbl < 0.0 || dbl >= (double)INT_MAX / 1000 - 1.0) { |
|
fprintf(stderr, "ping: bad timing interval\n"); |
|
exit(2); |
|
} |
|
|
|
interval = (int)(dbl * 1000); |
|
|
|
options |= F_INTERVAL; |
|
break; |
|
} |
|
case 'm': |
|
{ |
|
char *endp; |
|
mark = strtoul(optarg, &endp, 0); |
|
if (*endp != '\0') { |
|
fprintf(stderr, "ping: invalid mark %s\n", optarg); |
|
exit(2); |
|
} |
|
options |= F_MARK; |
|
break; |
|
} |
|
case 'w': |
|
deadline = atoi(optarg); |
|
if (deadline < 0) { |
|
fprintf(stderr, "ping: bad wait time.\n"); |
|
exit(2); |
|
} |
|
break; |
|
case 'l': |
|
preload = atoi(optarg); |
|
if (preload <= 0) { |
|
fprintf(stderr, "ping: bad preload value, should be 1..%d\n", MAX_DUP_CHK); |
|
exit(2); |
|
} |
|
if (preload > MAX_DUP_CHK) |
|
preload = MAX_DUP_CHK; |
|
if (uid && preload > 3) { |
|
fprintf(stderr, "ping: cannot set preload to value > 3\n"); |
|
exit(2); |
|
} |
|
break; |
|
case 'O': |
|
options |= F_OUTSTANDING; |
|
break; |
|
case 'S': |
|
sndbuf = atoi(optarg); |
|
if (sndbuf <= 0) { |
|
fprintf(stderr, "ping: bad sndbuf value.\n"); |
|
exit(2); |
|
} |
|
break; |
|
case 'f': |
|
options |= F_FLOOD; |
|
setbuf(stdout, (char *)NULL); |
|
/* fallthrough to numeric - avoid gethostbyaddr during flood */ |
|
case 'n': |
|
options |= F_NUMERIC; |
|
break; |
|
case 'p': /* fill buffer with user pattern */ |
|
options |= F_PINGFILLED; |
|
fill(optarg); |
|
break; |
|
case 'q': |
|
options |= F_QUIET; |
|
break; |
|
case 'r': |
|
options |= F_SO_DONTROUTE; |
|
break; |
|
case 's': /* size of packet to send */ |
|
datalen = atoi(optarg); |
|
if (datalen < 0) { |
|
fprintf(stderr, "ping: illegal negative packet size %d.\n", datalen); |
|
exit(2); |
|
} |
|
if (datalen > maxpacket - 8) { |
|
fprintf(stderr, "ping: packet size too large: %d\n", |
|
datalen); |
|
exit(2); |
|
} |
|
break; |
|
case 'v': |
|
options |= F_VERBOSE; |
|
break; |
|
case 'L': |
|
options |= F_NOLOOP; |
|
break; |
|
case 't': |
|
options |= F_TTL; |
|
ttl = atoi(optarg); |
|
if (ttl < 0 || ttl > 255) { |
|
fprintf(stderr, "ping: ttl %u out of range\n", ttl); |
|
exit(2); |
|
} |
|
break; |
|
case 'U': |
|
options |= F_LATENCY; |
|
break; |
|
case 'B': |
|
options |= F_STRICTSOURCE; |
|
break; |
|
case 'W': |
|
lingertime = atoi(optarg); |
|
if (lingertime < 0 || lingertime > INT_MAX/1000000) { |
|
fprintf(stderr, "ping: bad linger time.\n"); |
|
exit(2); |
|
} |
|
lingertime *= 1000; |
|
break; |
|
case 'V': |
|
printf("ping utility, iputils-%s\n", SNAPSHOT); |
|
exit(0); |
|
default: |
|
abort(); |
|
} |
|
} |
|
|
|
|
|
static void sigexit(int signo) |
|
{ |
|
exiting = 1; |
|
if (in_pr_addr) |
|
longjmp(pr_addr_jmp, 0); |
|
} |
|
|
|
static void sigstatus(int signo) |
|
{ |
|
status_snapshot = 1; |
|
} |
|
|
|
|
|
int __schedule_exit(int next) |
|
{ |
|
static unsigned long waittime; |
|
struct itimerval it; |
|
|
|
if (waittime) |
|
return next; |
|
|
|
if (nreceived) { |
|
waittime = 2 * tmax; |
|
if (waittime < 1000*interval) |
|
waittime = 1000*interval; |
|
} else |
|
waittime = lingertime*1000; |
|
|
|
if (next < 0 || next < waittime/1000) |
|
next = waittime/1000; |
|
|
|
it.it_interval.tv_sec = 0; |
|
it.it_interval.tv_usec = 0; |
|
it.it_value.tv_sec = waittime/1000000; |
|
it.it_value.tv_usec = waittime%1000000; |
|
setitimer(ITIMER_REAL, &it, NULL); |
|
return next; |
|
} |
|
|
|
static inline void update_interval(void) |
|
{ |
|
int est = rtt ? rtt/8 : interval*1000; |
|
|
|
interval = (est+rtt_addend+500)/1000; |
|
if (uid && interval < MINUSERINTERVAL) |
|
interval = MINUSERINTERVAL; |
|
} |
|
|
|
/* |
|
* Print timestamp |
|
*/ |
|
void print_timestamp(void) |
|
{ |
|
if (options & F_PTIMEOFDAY) { |
|
struct timeval tv; |
|
gettimeofday(&tv, NULL); |
|
printf("[%lu.%06lu] ", |
|
(unsigned long)tv.tv_sec, (unsigned long)tv.tv_usec); |
|
} |
|
} |
|
|
|
/* |
|
* pinger -- |
|
* Compose and transmit an ICMP ECHO REQUEST packet. The IP packet |
|
* will be added on by the kernel. The ID field is our UNIX process ID, |
|
* and the sequence number is an ascending integer. The first 8 bytes |
|
* of the data portion are used to hold a UNIX "timeval" struct in VAX |
|
* byte-order, to compute the round-trip time. |
|
*/ |
|
int pinger(void) |
|
{ |
|
static int oom_count; |
|
static int tokens; |
|
int i; |
|
|
|
/* Have we already sent enough? If we have, return an arbitrary positive value. */ |
|
if (exiting || (npackets && ntransmitted >= npackets && !deadline)) |
|
return 1000; |
|
|
|
/* Check that packets < rate*time + preload */ |
|
if (cur_time.tv_sec == 0) { |
|
gettimeofday(&cur_time, NULL); |
|
tokens = interval*(preload-1); |
|
} else { |
|
long ntokens; |
|
struct timeval tv; |
|
|
|
gettimeofday(&tv, NULL); |
|
ntokens = (tv.tv_sec - cur_time.tv_sec)*1000 + |
|
(tv.tv_usec-cur_time.tv_usec)/1000; |
|
if (!interval) { |
|
/* Case of unlimited flood is special; |
|
* if we see no reply, they are limited to 100pps */ |
|
if (ntokens < MININTERVAL && in_flight() >= preload) |
|
return MININTERVAL-ntokens; |
|
} |
|
ntokens += tokens; |
|
if (ntokens > interval*preload) |
|
ntokens = interval*preload; |
|
if (ntokens < interval) |
|
return interval - ntokens; |
|
|
|
cur_time = tv; |
|
tokens = ntokens - interval; |
|
} |
|
|
|
if (options & F_OUTSTANDING) { |
|
if (ntransmitted > 0 && !rcvd_test(ntransmitted)) { |
|
print_timestamp(); |
|
printf("no answer yet for icmp_seq=%lu\n", (ntransmitted % MAX_DUP_CHK)); |
|
fflush(stdout); |
|
} |
|
} |
|
|
|
resend: |
|
i = send_probe(); |
|
|
|
if (i == 0) { |
|
oom_count = 0; |
|
advance_ntransmitted(); |
|
if (!(options & F_QUIET) && (options & F_FLOOD)) { |
|
/* Very silly, but without this output with |
|
* high preload or pipe size is very confusing. */ |
|
if ((preload < screen_width && pipesize < screen_width) || |
|
in_flight() < screen_width) |
|
write_stdout(".", 1); |
|
} |
|
return interval - tokens; |
|
} |
|
|
|
/* And handle various errors... */ |
|
if (i > 0) { |
|
/* Apparently, it is some fatal bug. */ |
|
abort(); |
|
} else if (errno == ENOBUFS || errno == ENOMEM) { |
|
int nores_interval; |
|
|
|
/* Device queue overflow or OOM. Packet is not sent. */ |
|
tokens = 0; |
|
/* Slowdown. This works only in adaptive mode (option -A) */ |
|
rtt_addend += (rtt < 8*50000 ? rtt/8 : 50000); |
|
if (options&F_ADAPTIVE) |
|
update_interval(); |
|
nores_interval = SCHINT(interval/2); |
|
if (nores_interval > 500) |
|
nores_interval = 500; |
|
oom_count++; |
|
if (oom_count*nores_interval < lingertime) |
|
return nores_interval; |
|
i = 0; |
|
/* Fall to hard error. It is to avoid complete deadlock |
|
* on stuck output device even when dealine was not requested. |
|
* Expected timings are screwed up in any case, but we will |
|
* exit some day. :-) */ |
|
} else if (errno == EAGAIN) { |
|
/* Socket buffer is full. */ |
|
tokens += interval; |
|
return MININTERVAL; |
|
} else { |
|
if ((i=receive_error_msg()) > 0) { |
|
/* An ICMP error arrived. */ |
|
tokens += interval; |
|
return MININTERVAL; |
|
} |
|
/* Compatibility with old linuces. */ |
|
if (i == 0 && confirm_flag && errno == EINVAL) { |
|
confirm_flag = 0; |
|
errno = 0; |
|
} |
|
if (!errno) |
|
goto resend; |
|
} |
|
|
|
/* Hard local error. Pretend we sent packet. */ |
|
advance_ntransmitted(); |
|
|
|
if (i == 0 && !(options & F_QUIET)) { |
|
if (options & F_FLOOD) |
|
write_stdout("E", 1); |
|
else |
|
perror("ping: sendmsg"); |
|
} |
|
tokens = 0; |
|
return SCHINT(interval); |
|
} |
|
|
|
/* Set socket buffers, "alloc" is an estimate of memory taken by single packet. */ |
|
|
|
void sock_setbufs(int icmp_sock, int alloc) |
|
{ |
|
int rcvbuf, hold; |
|
socklen_t tmplen = sizeof(hold); |
|
|
|
if (!sndbuf) |
|
sndbuf = alloc; |
|
setsockopt(icmp_sock, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)); |
|
|
|
rcvbuf = hold = alloc * preload; |
|
if (hold < 65536) |
|
hold = 65536; |
|
setsockopt(icmp_sock, SOL_SOCKET, SO_RCVBUF, (char *)&hold, sizeof(hold)); |
|
if (getsockopt(icmp_sock, SOL_SOCKET, SO_RCVBUF, (char *)&hold, &tmplen) == 0) { |
|
if (hold < rcvbuf) |
|
fprintf(stderr, "WARNING: probably, rcvbuf is not enough to hold preload.\n"); |
|
} |
|
} |
|
|
|
void sock_setmark(int icmp_sock) { |
|
#ifdef SO_MARK |
|
if (options & F_MARK) { |
|
int ret; |
|
|
|
enable_capability_admin(); |
|
ret = setsockopt(icmp_sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)); |
|
disable_capability_admin(); |
|
|
|
if (ret == -1) { |
|
/* we probably dont wanna exit since old kernels |
|
* dont support mark .. |
|
*/ |
|
fprintf(stderr, "Warning: Failed to set mark %d\n", mark); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
/* Protocol independent setup and parameter checks. */ |
|
|
|
void setup(int icmp_sock) |
|
{ |
|
int hold; |
|
struct timeval tv; |
|
sigset_t sset; |
|
|
|
if ((options & F_FLOOD) && !(options & F_INTERVAL)) |
|
interval = 0; |
|
|
|
if (uid && interval < MINUSERINTERVAL) { |
|
fprintf(stderr, "ping: cannot flood; minimal interval, allowed for user, is %dms\n", MINUSERINTERVAL); |
|
exit(2); |
|
} |
|
|
|
if (interval >= INT_MAX/preload) { |
|
fprintf(stderr, "ping: illegal preload and/or interval\n"); |
|
exit(2); |
|
} |
|
|
|
hold = 1; |
|
if (options & F_SO_DEBUG) |
|
setsockopt(icmp_sock, SOL_SOCKET, SO_DEBUG, (char *)&hold, sizeof(hold)); |
|
if (options & F_SO_DONTROUTE) |
|
setsockopt(icmp_sock, SOL_SOCKET, SO_DONTROUTE, (char *)&hold, sizeof(hold)); |
|
|
|
#ifdef SO_TIMESTAMP |
|
if (!(options&F_LATENCY)) { |
|
int on = 1; |
|
if (setsockopt(icmp_sock, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on))) |
|
fprintf(stderr, "Warning: no SO_TIMESTAMP support, falling back to SIOCGSTAMP\n"); |
|
} |
|
#endif |
|
|
|
sock_setmark(icmp_sock); |
|
|
|
/* Set some SNDTIMEO to prevent blocking forever |
|
* on sends, when device is too slow or stalls. Just put limit |
|
* of one second, or "interval", if it is less. |
|
*/ |
|
tv.tv_sec = 1; |
|
tv.tv_usec = 0; |
|
if (interval < 1000) { |
|
tv.tv_sec = 0; |
|
tv.tv_usec = 1000 * SCHINT(interval); |
|
} |
|
setsockopt(icmp_sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv)); |
|
|
|
/* Set RCVTIMEO to "interval". Note, it is just an optimization |
|
* allowing to avoid redundant poll(). */ |
|
tv.tv_sec = SCHINT(interval)/1000; |
|
tv.tv_usec = 1000*(SCHINT(interval)%1000); |
|
if (setsockopt(icmp_sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv))) |
|
options |= F_FLOOD_POLL; |
|
|
|
if (!(options & F_PINGFILLED)) { |
|
int i; |
|
u_char *p = outpack+8; |
|
|
|
/* Do not forget about case of small datalen, |
|
* fill timestamp area too! |
|
*/ |
|
for (i = 0; i < datalen; ++i) |
|
*p++ = i; |
|
} |
|
|
|
if (!using_ping_socket) |
|
ident = htons(getpid() & 0xFFFF); |
|
|
|
set_signal(SIGINT, sigexit); |
|
set_signal(SIGALRM, sigexit); |
|
set_signal(SIGQUIT, sigstatus); |
|
|
|
sigemptyset(&sset); |
|
sigprocmask(SIG_SETMASK, &sset, NULL); |
|
|
|
gettimeofday(&start_time, NULL); |
|
|
|
if (deadline) { |
|
struct itimerval it; |
|
|
|
it.it_interval.tv_sec = 0; |
|
it.it_interval.tv_usec = 0; |
|
it.it_value.tv_sec = deadline; |
|
it.it_value.tv_usec = 0; |
|
setitimer(ITIMER_REAL, &it, NULL); |
|
} |
|
|
|
if (isatty(STDOUT_FILENO)) { |
|
struct winsize w; |
|
|
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) { |
|
if (w.ws_col > 0) |
|
screen_width = w.ws_col; |
|
} |
|
} |
|
} |
|
|
|
void main_loop(int icmp_sock, __u8 *packet, int packlen) |
|
{ |
|
char addrbuf[128]; |
|
char ans_data[4096]; |
|
struct iovec iov; |
|
struct msghdr msg; |
|
struct cmsghdr *c; |
|
int cc; |
|
int next; |
|
int polling; |
|
|
|
iov.iov_base = (char *)packet; |
|
|
|
for (;;) { |
|
/* Check exit conditions. */ |
|
if (exiting) |
|
break; |
|
if (npackets && nreceived + nerrors >= npackets) |
|
break; |
|
if (deadline && nerrors) |
|
break; |
|
/* Check for and do special actions. */ |
|
if (status_snapshot) |
|
status(); |
|
|
|
/* Send probes scheduled to this time. */ |
|
do { |
|
next = pinger(); |
|
next = schedule_exit(next); |
|
} while (next <= 0); |
|
|
|
/* "next" is time to send next probe, if positive. |
|
* If next<=0 send now or as soon as possible. */ |
|
|
|
/* Technical part. Looks wicked. Could be dropped, |
|
* if everyone used the newest kernel. :-) |
|
* Its purpose is: |
|
* 1. Provide intervals less than resolution of scheduler. |
|
* Solution: spinning. |
|
* 2. Avoid use of poll(), when recvmsg() can provide |
|
* timed waiting (SO_RCVTIMEO). */ |
|
polling = 0; |
|
if ((options & (F_ADAPTIVE|F_FLOOD_POLL)) || next<SCHINT(interval)) { |
|
int recv_expected = in_flight(); |
|
|
|
/* If we are here, recvmsg() is unable to wait for |
|
* required timeout. */ |
|
if (1000 % HZ == 0 ? next <= 1000 / HZ : (next < INT_MAX / HZ && next * HZ <= 1000)) { |
|
/* Very short timeout... So, if we wait for |
|
* something, we sleep for MININTERVAL. |
|
* Otherwise, spin! */ |
|
if (recv_expected) { |
|
next = MININTERVAL; |
|
} else { |
|
next = 0; |
|
/* When spinning, no reasons to poll. |
|
* Use nonblocking recvmsg() instead. */ |
|
polling = MSG_DONTWAIT; |
|
/* But yield yet. */ |
|
sched_yield(); |
|
} |
|
} |
|
|
|
if (!polling && |
|
((options & (F_ADAPTIVE|F_FLOOD_POLL)) || interval)) { |
|
struct pollfd pset; |
|
pset.fd = icmp_sock; |
|
pset.events = POLLIN|POLLERR; |
|
pset.revents = 0; |
|
if (poll(&pset, 1, next) < 1 || |
|
!(pset.revents&(POLLIN|POLLERR))) |
|
continue; |
|
polling = MSG_DONTWAIT; |
|
} |
|
} |
|
|
|
for (;;) { |
|
struct timeval *recv_timep = NULL; |
|
struct timeval recv_time; |
|
int not_ours = 0; /* Raw socket can receive messages |
|
* destined to other running pings. */ |
|
|
|
iov.iov_len = packlen; |
|
memset(&msg, 0, sizeof(msg)); |
|
msg.msg_name = addrbuf; |
|
msg.msg_namelen = sizeof(addrbuf); |
|
msg.msg_iov = &iov; |
|
msg.msg_iovlen = 1; |
|
msg.msg_control = ans_data; |
|
msg.msg_controllen = sizeof(ans_data); |
|
|
|
cc = recvmsg(icmp_sock, &msg, polling); |
|
polling = MSG_DONTWAIT; |
|
|
|
if (cc < 0) { |
|
if (errno == EAGAIN || errno == EINTR) |
|
break; |
|
if (!receive_error_msg()) { |
|
if (errno) { |
|
perror("ping: recvmsg"); |
|
break; |
|
} |
|
not_ours = 1; |
|
} |
|
} else { |
|
|
|
#ifdef SO_TIMESTAMP |
|
for (c = CMSG_FIRSTHDR(&msg); c; c = CMSG_NXTHDR(&msg, c)) { |
|
if (c->cmsg_level != SOL_SOCKET || |
|
c->cmsg_type != SO_TIMESTAMP) |
|
continue; |
|
if (c->cmsg_len < CMSG_LEN(sizeof(struct timeval))) |
|
continue; |
|
recv_timep = (struct timeval*)CMSG_DATA(c); |
|
} |
|
#endif |
|
|
|
if ((options&F_LATENCY) || recv_timep == NULL) { |
|
if ((options&F_LATENCY) || |
|
ioctl(icmp_sock, SIOCGSTAMP, &recv_time)) |
|
gettimeofday(&recv_time, NULL); |
|
recv_timep = &recv_time; |
|
} |
|
|
|
not_ours = parse_reply(&msg, cc, addrbuf, recv_timep); |
|
} |
|
|
|
/* See? ... someone runs another ping on this host. */ |
|
if (not_ours && !using_ping_socket) |
|
install_filter(); |
|
|
|
/* If nothing is in flight, "break" returns us to pinger. */ |
|
if (in_flight() == 0) |
|
break; |
|
|
|
/* Otherwise, try to recvmsg() again. recvmsg() |
|
* is nonblocking after the first iteration, so that |
|
* if nothing is queued, it will receive EAGAIN |
|
* and return to pinger. */ |
|
} |
|
} |
|
finish(); |
|
} |
|
|
|
int gather_statistics(__u8 *icmph, int icmplen, |
|
int cc, __u16 seq, int hops, |
|
int csfailed, struct timeval *tv, char *from, |
|
void (*pr_reply)(__u8 *icmph, int cc)) |
|
{ |
|
int dupflag = 0; |
|
long triptime = 0; |
|
__u8 *ptr = icmph + icmplen; |
|
|
|
++nreceived; |
|
if (!csfailed) |
|
acknowledge(seq); |
|
|
|
if (timing && cc >= 8+sizeof(struct timeval)) { |
|
struct timeval tmp_tv; |
|
memcpy(&tmp_tv, ptr, sizeof(tmp_tv)); |
|
|
|
restamp: |
|
tvsub(tv, &tmp_tv); |
|
triptime = tv->tv_sec * 1000000 + tv->tv_usec; |
|
if (triptime < 0) { |
|
fprintf(stderr, "Warning: time of day goes back (%ldus), taking countermeasures.\n", triptime); |
|
triptime = 0; |
|
if (!(options & F_LATENCY)) { |
|
gettimeofday(tv, NULL); |
|
options |= F_LATENCY; |
|
goto restamp; |
|
} |
|
} |
|
if (!csfailed) { |
|
tsum += triptime; |
|
tsum2 += (long long)triptime * (long long)triptime; |
|
if (triptime < tmin) |
|
tmin = triptime; |
|
if (triptime > tmax) |
|
tmax = triptime; |
|
if (!rtt) |
|
rtt = triptime*8; |
|
else |
|
rtt += triptime-rtt/8; |
|
if (options&F_ADAPTIVE) |
|
update_interval(); |
|
} |
|
} |
|
|
|
if (csfailed) { |
|
++nchecksum; |
|
--nreceived; |
|
} else if (rcvd_test(seq)) { |
|
++nrepeats; |
|
--nreceived; |
|
dupflag = 1; |
|
} else { |
|
rcvd_set(seq); |
|
dupflag = 0; |
|
} |
|
confirm = confirm_flag; |
|
|
|
if (options & F_QUIET) |
|
return 1; |
|
|
|
if (options & F_FLOOD) { |
|
if (!csfailed) |
|
write_stdout("\b \b", 3); |
|
else |
|
write_stdout("\bC", 2); |
|
} else { |
|
int i; |
|
__u8 *cp, *dp; |
|
|
|
print_timestamp(); |
|
printf("%d bytes from %s:", cc, from); |
|
|
|
if (pr_reply) |
|
pr_reply(icmph, cc); |
|
|
|
if (hops >= 0) |
|
printf(" ttl=%d", hops); |
|
|
|
if (cc < datalen+8) { |
|
printf(" (truncated)\n"); |
|
return 1; |
|
} |
|
if (timing) { |
|
if (triptime >= 100000) |
|
printf(" time=%ld ms", triptime/1000); |
|
else if (triptime >= 10000) |
|
printf(" time=%ld.%01ld ms", triptime/1000, |
|
(triptime%1000)/100); |
|
else if (triptime >= 1000) |
|
printf(" time=%ld.%02ld ms", triptime/1000, |
|
(triptime%1000)/10); |
|
else |
|
printf(" time=%ld.%03ld ms", triptime/1000, |
|
triptime%1000); |
|
} |
|
if (dupflag) |
|
printf(" (DUP!)"); |
|
if (csfailed) |
|
printf(" (BAD CHECKSUM!)"); |
|
|
|
/* check the data */ |
|
cp = ((u_char*)ptr) + sizeof(struct timeval); |
|
dp = &outpack[8 + sizeof(struct timeval)]; |
|
for (i = sizeof(struct timeval); i < datalen; ++i, ++cp, ++dp) { |
|
if (*cp != *dp) { |
|
printf("\nwrong data byte #%d should be 0x%x but was 0x%x", |
|
i, *dp, *cp); |
|
cp = (u_char*)ptr + sizeof(struct timeval); |
|
for (i = sizeof(struct timeval); i < datalen; ++i, ++cp) { |
|
if ((i % 32) == sizeof(struct timeval)) |
|
printf("\n#%d\t", i); |
|
printf("%x ", *cp); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
static long llsqrt(long long a) |
|
{ |
|
long long prev = ~((long long)1 << 63); |
|
long long x = a; |
|
|
|
if (x > 0) { |
|
while (x < prev) { |
|
prev = x; |
|
x = (x+(a/x))/2; |
|
} |
|
} |
|
|
|
return (long)x; |
|
} |
|
|
|
/* |
|
* finish -- |
|
* Print out statistics, and give up. |
|
*/ |
|
void finish(void) |
|
{ |
|
struct timeval tv = cur_time; |
|
char *comma = ""; |
|
|
|
tvsub(&tv, &start_time); |
|
|
|
putchar('\n'); |
|
fflush(stdout); |
|
printf("--- %s ping statistics ---\n", hostname); |
|
printf("%ld packets transmitted, ", ntransmitted); |
|
printf("%ld received", nreceived); |
|
if (nrepeats) |
|
printf(", +%ld duplicates", nrepeats); |
|
if (nchecksum) |
|
printf(", +%ld corrupted", nchecksum); |
|
if (nerrors) |
|
printf(", +%ld errors", nerrors); |
|
if (ntransmitted) { |
|
printf(", %d%% packet loss", |
|
(int) ((((long long)(ntransmitted - nreceived)) * 100) / |
|
ntransmitted)); |
|
printf(", time %ldms", 1000*tv.tv_sec+tv.tv_usec/1000); |
|
} |
|
putchar('\n'); |
|
|
|
if (nreceived && timing) { |
|
long tmdev; |
|
|
|
tsum /= nreceived + nrepeats; |
|
tsum2 /= nreceived + nrepeats; |
|
tmdev = llsqrt(tsum2 - tsum * tsum); |
|
|
|
printf("rtt min/avg/max/mdev = %ld.%03ld/%lu.%03ld/%ld.%03ld/%ld.%03ld ms", |
|
(long)tmin/1000, (long)tmin%1000, |
|
(unsigned long)(tsum/1000), (long)(tsum%1000), |
|
(long)tmax/1000, (long)tmax%1000, |
|
(long)tmdev/1000, (long)tmdev%1000 |
|
); |
|
comma = ", "; |
|
} |
|
if (pipesize > 1) { |
|
printf("%spipe %d", comma, pipesize); |
|
comma = ", "; |
|
} |
|
if (nreceived && (!interval || (options&(F_FLOOD|F_ADAPTIVE))) && ntransmitted > 1) { |
|
int ipg = (1000000*(long long)tv.tv_sec+tv.tv_usec)/(ntransmitted-1); |
|
printf("%sipg/ewma %d.%03d/%d.%03d ms", |
|
comma, ipg/1000, ipg%1000, rtt/8000, (rtt/8)%1000); |
|
} |
|
putchar('\n'); |
|
exit(!nreceived || (deadline && nreceived < npackets)); |
|
} |
|
|
|
|
|
void status(void) |
|
{ |
|
int loss = 0; |
|
long tavg = 0; |
|
|
|
status_snapshot = 0; |
|
|
|
if (ntransmitted) |
|
loss = (((long long)(ntransmitted - nreceived)) * 100) / ntransmitted; |
|
|
|
fprintf(stderr, "\r%ld/%ld packets, %d%% loss", ntransmitted, nreceived, loss); |
|
|
|
if (nreceived && timing) { |
|
tavg = tsum / (nreceived + nrepeats); |
|
|
|
fprintf(stderr, ", min/avg/ewma/max = %ld.%03ld/%lu.%03ld/%d.%03d/%ld.%03ld ms", |
|
(long)tmin/1000, (long)tmin%1000, |
|
tavg/1000, tavg%1000, |
|
rtt/8000, (rtt/8)%1000, |
|
(long)tmax/1000, (long)tmax%1000 |
|
); |
|
} |
|
fprintf(stderr, "\n"); |
|
} |
|
|
|
inline int is_ours(uint16_t id) { |
|
return using_ping_socket || id == ident; |
|
} |
|
|
|
|