diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aff75fe3721a3bf9def4a157ad63829df418b6b..f79e7baedc51a89a7896a4d18798ada368153350 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,10 @@ set(DAP_CHAIN_NET_HEADERS dap_chain_node_ctl.h ) -add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_NET_SRCS} ${DAP_CHAIN_NET_HEADERS}) +file(GLOB IPUTILS_SRCS iputils/*.c) +file(GLOB IPUTILSHEADERS iputils/*.h) + +add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_NET_SRCS} ${DAP_CHAIN_NET_HEADERS} ${IPUTILS_SRCS} ${IPUTILSHEADERS}) target_link_libraries(dap_chain_net dap_core dap_crypto dap_chain dap_chain_crypto ) diff --git a/iputils/ping.c b/iputils/ping.c new file mode 100644 index 0000000000000000000000000000000000000000..979dec0ad57700790be37899105a74293e360b65 --- /dev/null +++ b/iputils/ping.c @@ -0,0 +1,1597 @@ +/* + * 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. 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. + */ +/* + * P I N G . C + * + * Using the InterNet Control Message Protocol (ICMP) "ECHO" facility, + * measure round-trip-delays and packet loss across network paths. + * + * Author - + * Mike Muuss + * U. S. Army Ballistic Research Laboratory + * December, 1983 + * + * Status - + * Public Domain. Distribution Unlimited. + * Bugs - + * More statistics could always be gathered. + * If kernel does not support non-raw ICMP sockets, + * this program has to run SUID to ROOT or with + * net_cap_raw enabled. + */ + +#include "ping.h" + +#include <assert.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <ifaddrs.h> +#include <math.h> + +#ifndef ICMP_FILTER +#define ICMP_FILTER 1 +struct icmp_filter { + uint32_t data; +}; +#endif + +ping_func_set_st ping4_func_set = { + .send_probe = ping4_send_probe, + .receive_error_msg = ping4_receive_error_msg, + .parse_reply = ping4_parse_reply, + .install_filter = ping4_install_filter +}; + +#define MAXIPLEN 60 +#define MAXICMPLEN 76 +#define NROUTES 9 /* number of record route slots */ +#define TOS_MAX 255 /* 8-bit TOS field */ + +static int ts_type; +static int nroute = 0; +static uint32_t route[10]; + +static struct sockaddr_in whereto; /* who to ping */ +static int optlen = 0; +static int settos = 0; /* Set TOS, Precendence or other QOS options */ + +static int broadcast_pings = 0; + +static void pr_options(unsigned char * cp, int hlen); +static void pr_iph(struct iphdr *ip); +static unsigned short in_cksum(const unsigned short *addr, int len, unsigned short salt); +static void pr_icmph(uint8_t type, uint8_t code, uint32_t info, struct icmphdr *icp); +static int parsetos(char *str); +static int parseflow(char *str); + +static struct sockaddr_in source = { .sin_family = AF_INET }; +char *device; +int pmtudisc = -1; + +static void create_socket(socket_st *sock, int family, int socktype, int protocol, int requisite) +{ + int do_fallback = 0; + + errno = 0; + + assert(sock->fd == -1); + assert(socktype == SOCK_DGRAM || socktype == SOCK_RAW); + + /* Attempt to create a ping socket if requested. Attempt to create a raw + * socket otherwise or as a fallback. Well known errno values follow. + * + * 1) EACCES + * + * Kernel returns EACCES for all ping socket creation attempts when the + * user isn't allowed to use ping socket. A range of group ids is + * configured using the `net.ipv4.ping_group_range` sysctl. Fallback + * to raw socket is necessary. + * + * Kernel returns EACCES for all raw socket creation attempts when the + * process doesn't have the `CAP_NET_RAW` capability. + * + * 2) EAFNOSUPPORT + * + * Kernel returns EAFNOSUPPORT for IPv6 ping or raw socket creation + * attempts when run with IPv6 support disabled (e.g. via `ipv6.disable=1` + * kernel command-line option. + * + * https://github.com/iputils/iputils/issues/32 + * + * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return + * EAFNOSUPPORT for all IPv4 ping socket creation attempts due to lack + * of support in the kernel. Fallback to raw socket is necessary. + * + * https://github.com/iputils/iputils/issues/54 + * + * 3) EPROTONOSUPPORT + * + * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return + * EPROTONOSUPPORT for all IPv6 ping socket creation attempts due to lack + * of support in the kernel [1]. Debian 9.5 based container with kernel 4.10 + * returns EPROTONOSUPPORT also for IPv4 [2]. Fallback to raw socket is + * necessary. + * + * [1] https://github.com/iputils/iputils/issues/54 + * [2] https://github.com/iputils/iputils/issues/129 + */ + if (socktype == SOCK_DGRAM) + sock->fd = socket(family, socktype, protocol); + + /* Kernel doesn't support ping sockets. */ + if (sock->fd == -1 && errno == EAFNOSUPPORT && family == AF_INET) + do_fallback = 1; + if (sock->fd == -1 && errno == EPROTONOSUPPORT) + do_fallback = 1; + + /* User is not allowed to use ping sockets. */ + if (sock->fd == -1 && errno == EACCES) + do_fallback = 1; + + if (socktype == SOCK_RAW || do_fallback) { + socktype = SOCK_RAW; + sock->fd = socket(family, SOCK_RAW, protocol); + } + + if (sock->fd == -1) { + /* Report error related to disabled IPv6 only when IPv6 also failed or in + * verbose mode. Report other errors always. + */ + if ((errno == EAFNOSUPPORT && socktype == AF_INET6) || options & F_VERBOSE || requisite) + error(0, errno, "socket"); + if (requisite) + exit(2); + } else + sock->socktype = socktype; +} + +static void set_socket_option(socket_st *sock, int level, int optname, const void *optval, socklen_t olen) +{ + if (sock->fd == -1) + return; + + if (setsockopt(sock->fd, level, optname, optval, olen) == -1) + error(2, errno, "setsockopt"); +} + +/* Much like stdtod(3, but will fails if str is not valid number. */ +static double ping_strtod(const char *str, const char *err_msg) +{ + double num; + char *end = NULL; + + if (str == NULL || *str == '\0') + goto err; + errno = 0; +#ifdef USE_IDN + setlocale(LC_ALL, "C"); +#endif + num = strtod(str, &end); +#ifdef USE_IDN + setlocale(LC_ALL, ""); +#endif + if (errno || str == end || (end && *end)) + goto err; + switch (fpclassify(num)) { + case FP_NORMAL: + case FP_ZERO: + break; + default: + errno = ERANGE; + goto err; + } + return num; + err: + error(2, errno, "%s: %s", err_msg, str); + abort(); /* cannot be reached, above error() will exit */ + return 0.0; +} + +int +ping_util() +{ + return 0; +} + +int +main2(int argc, char **argv) +{ + struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_protocol = IPPROTO_UDP, .ai_socktype = SOCK_DGRAM, .ai_flags = getaddrinfo_flags }; + struct addrinfo *result, *ai; + int status; + int ch; + socket_st sock4 = { .fd = -1 }; + socket_st sock6 = { .fd = -1 }; + char *target; + + limit_capabilities(); + +#ifdef USE_IDN + setlocale(LC_ALL, ""); + if (!strcmp(setlocale(LC_ALL, NULL), "C")) + hints.ai_flags &= ~ AI_CANONIDN; +#endif + + /* Support being called using `ping4` or `ping6` symlinks */ + if (argv[0][strlen(argv[0])-1] == '4') + hints.ai_family = AF_INET; + else if (argv[0][strlen(argv[0])-1] == '6') + hints.ai_family = AF_INET6; + + /* Parse command line options */ + while ((ch = getopt(argc, argv, "h?" "4bRT:" "6F:N:" "aABc:dDfi:I:l:Lm:M:nOp:qQ:rs:S:t:UvVw:W:")) != EOF) { + switch(ch) { + /* IPv4 specific options */ + case '4': + if (hints.ai_family != AF_UNSPEC) + error(2, 0, "only one -4 or -6 option may be specified"); + hints.ai_family = AF_INET; + break; + case 'b': + broadcast_pings = 1; + break; + case 'R': + if (options & F_TIMESTAMP) + error(2, 0, "only one of -T or -R may be used"); + options |= F_RROUTE; + break; + case 'T': + if (options & F_RROUTE) + error(2, 0, "only one of -T or -R may be used"); + options |= F_TIMESTAMP; + if (strcmp(optarg, "tsonly") == 0) + ts_type = IPOPT_TS_TSONLY; + else if (strcmp(optarg, "tsandaddr") == 0) + ts_type = IPOPT_TS_TSANDADDR; + else if (strcmp(optarg, "tsprespec") == 0) + ts_type = IPOPT_TS_PRESPEC; + else + error(2, 0, "invalid timestamp type: %s", optarg); + break; + /* IPv6 specific options */ + case '6': + if (hints.ai_family != AF_UNSPEC) + error(2, 0, "only one -4 or -6 option may be specified"); + hints.ai_family = AF_INET6; + break; + case 'F': + flowlabel = parseflow(optarg); + options |= F_FLOWINFO; + break; + case 'N': + if (niquery_option_handler(optarg) < 0) + usage(); + hints.ai_socktype = SOCK_RAW; + break; + /* Common options */ + case 'a': + options |= F_AUDIBLE; + break; + case 'A': + options |= F_ADAPTIVE; + break; + case 'B': + options |= F_STRICTSOURCE; + break; + case 'c': + npackets = atoi(optarg); + if (npackets <= 0) + error(2, 0, "bad number of packets to transmit: %ld", npackets); + break; + case 'd': + options |= F_SO_DEBUG; + break; + case 'D': + options |= F_PTIMEOFDAY; + break; + case 'i': + { + double optval; + + optval = ping_strtod(optarg, "bad timing interval"); + if (isgreater(optval, (double)(INT_MAX / 1000))) + error(2, 0, "bad timing interval: %s", optarg); + interval = (int)(optval * 1000); + options |= F_INTERVAL; + } + break; + case 'I': + /* IPv6 */ + if (strchr(optarg, ':')) { + char *p, *addr = strdup(optarg); + + if (!addr) + error(2, errno, "cannot copy: %s", optarg); + + p = strchr(addr, SCOPE_DELIMITER); + if (p) { + *p = '\0'; + device = optarg + (p - addr) + 1; + } + + if (inet_pton(AF_INET6, addr, (char*)&source6.sin6_addr) <= 0) + error(2, 0, "invalid source address: %s", optarg); + + options |= F_STRICTSOURCE; + + free(addr); + } else if (inet_pton(AF_INET, optarg, &source.sin_addr) > 0) { + options |= F_STRICTSOURCE; + } else { + device = optarg; + } + break; + case 'l': + preload = atoi(optarg); + if (preload <= 0) + error(2, 0, "bad preload value: %s, should be 1..%d", optarg, MAX_DUP_CHK); + if (preload > MAX_DUP_CHK) + preload = MAX_DUP_CHK; + if (uid && preload > 3) + error(2, 0, "cannot set preload to value greater than 3: %d", preload); + break; + case 'L': + options |= F_NOLOOP; + break; + case 'm': + { + char *endp; + mark = (int)strtoul(optarg, &endp, 10); + if (mark < 0 || *endp != '\0') + error(2, 0, "mark cannot be negative: %s", optarg); + options |= F_MARK; + break; + } + case 'M': + if (strcmp(optarg, "do") == 0) + pmtudisc = IP_PMTUDISC_DO; + else if (strcmp(optarg, "dont") == 0) + pmtudisc = IP_PMTUDISC_DONT; + else if (strcmp(optarg, "want") == 0) + pmtudisc = IP_PMTUDISC_WANT; + else + error(2, 0, "invalid -M argument: %s", optarg); + break; + case 'n': + options |= F_NUMERIC; + break; + case 'O': + options |= F_OUTSTANDING; + break; + case 'f': + /* avoid `getaddrinfo()` during flood */ + options |= F_FLOOD | F_NUMERIC; + setbuf(stdout, (char *)NULL); + break; + case 'p': + options |= F_PINGFILLED; + fill(optarg, outpack, sizeof(outpack)); + break; + case 'q': + options |= F_QUIET; + break; + case 'Q': + settos = parsetos(optarg); /* IPv4 */ + tclass = settos; /* IPv6 */ + break; + case 'r': + options |= F_SO_DONTROUTE; + break; + case 's': + datalen = atoi(optarg); + if (datalen < 0) + error(2, 0, "illegal packet size: %d", datalen); + if (datalen > MAXPACKET - 8) + error(2, 0, "packet size too large: %d", datalen); + break; + case 'S': + sndbuf = atoi(optarg); + if (sndbuf <= 0) + error(2, 0, "bad sndbuf value: %s", optarg); + break; + case 't': + options |= F_TTL; + ttl = atoi(optarg); + if (ttl < 0 || ttl > 255) + error(2, 0, "ttl out of range: %s", optarg); + break; + case 'U': + options |= F_LATENCY; + break; + case 'v': + options |= F_VERBOSE; + break; + case 'V': + printf(IPUTILS_VERSION("ping")); + exit(0); + case 'w': + deadline = atoi(optarg); + if (deadline < 0) + error(2, 0, "bad wait time: %s", optarg); + break; + case 'W': + { + double optval; + + optval = ping_strtod(optarg, "bad linger time"); + if (isless(optval, 0.001) || isgreater(optval, (double)(INT_MAX / 1000))) + error(2, 0, "bad linger time: %s", optarg); + /* lingertime will be converted to usec later */ + lingertime = (int)(optval * 1000); + } + break; + default: + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + if (!argc) + error(1, EDESTADDRREQ, "usage error"); + + target = argv[argc-1]; + + /* Create sockets */ + enable_capability_raw(); + if (hints.ai_family != AF_INET6) + create_socket(&sock4, AF_INET, hints.ai_socktype, IPPROTO_ICMP, hints.ai_family == AF_INET); + if (hints.ai_family != AF_INET) { + create_socket(&sock6, AF_INET6, hints.ai_socktype, IPPROTO_ICMPV6, sock4.fd == -1); + /* This may not be needed if both protocol versions always had the same value, but + * since I don't know that, it's better to be safe than sorry. */ + pmtudisc = pmtudisc == IP_PMTUDISC_DO ? IPV6_PMTUDISC_DO : + pmtudisc == IP_PMTUDISC_DONT ? IPV6_PMTUDISC_DONT : + pmtudisc == IP_PMTUDISC_WANT ? IPV6_PMTUDISC_WANT : pmtudisc; + } + disable_capability_raw(); + + /* Limit address family on single-protocol systems */ + if (hints.ai_family == AF_UNSPEC) { + if (sock4.fd == -1) + hints.ai_family = AF_INET6; + else if (sock6.fd == -1) + hints.ai_family = AF_INET; + } + + /* Set socket options */ + if (settos) + set_socket_option(&sock4, IPPROTO_IP, IP_TOS, &settos, sizeof settos); + if (tclass) + set_socket_option(&sock6, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof tclass); + + status = getaddrinfo(target, NULL, &hints, &result); + if (status) + error(2, 0, "%s: %s", target, gai_strerror(status)); + + for (ai = result; ai; ai = ai->ai_next) { + switch (ai->ai_family) { + case AF_INET: + status = ping4_run(argc, argv, ai, &sock4); + break; + case AF_INET6: + status = ping6_run(argc, argv, ai, &sock6); + break; + default: + error(2, 0, "unknown protocol family: %d", ai->ai_family); + } + + if (status == 0) + break; + } + + freeaddrinfo(result); + + return status; +} + +int ping4_run(int argc, char **argv, struct addrinfo *ai, socket_st *sock) +{ + static const struct addrinfo hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_UDP, .ai_flags = getaddrinfo_flags }; + int hold, packlen; + unsigned char *packet; + char *target; + char hnamebuf[NI_MAXHOST]; + unsigned char rspace[3 + 4 * NROUTES + 1]; /* record route space */ + uint32_t *tmp_rspace; + + if (argc > 1) { + if (options & F_RROUTE) + usage(); + else if (options & F_TIMESTAMP) { + if (ts_type != IPOPT_TS_PRESPEC) + usage(); + if (argc > 5) + usage(); + } else { + if (argc > 10) + usage(); + options |= F_SOURCEROUTE; + } + } + while (argc > 0) { + target = *argv; + + memset((char *)&whereto, 0, sizeof(whereto)); + whereto.sin_family = AF_INET; + if (inet_aton(target, &whereto.sin_addr) == 1) { + hostname = target; + if (argc == 1) + options |= F_NUMERIC; + } else { + struct addrinfo *result = NULL; + int status; + + if (argc > 1 || !ai) { + status = getaddrinfo(target, NULL, &hints, &result); + if (status) + error(2, 0, "%s: %s", target, gai_strerror(status)); + ai = result; + } + + memcpy(&whereto, ai->ai_addr, sizeof whereto); + memset(hnamebuf, 0, sizeof hnamebuf); + if (ai->ai_canonname) + strncpy(hnamebuf, ai->ai_canonname, sizeof hnamebuf - 1); + hostname = hnamebuf; + + if (result) + freeaddrinfo(result); + } + if (argc > 1) + route[nroute++] = whereto.sin_addr.s_addr; + argc--; + argv++; + } + + if (source.sin_addr.s_addr == 0) { + socklen_t alen; + struct sockaddr_in dst = whereto; + int probe_fd = socket(AF_INET, SOCK_DGRAM, 0); + + if (probe_fd < 0) + error(2, errno, "socket"); + if (device) { + struct ifreq ifr; + int i; + int fds[2] = {probe_fd, sock->fd}; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, device, IFNAMSIZ-1); + + for (i = 0; i < 2; i++) { + int fd = fds[i]; + int rc; + int errno_save; + + enable_capability_raw(); + rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device)+1); + errno_save = errno; + disable_capability_raw(); + + if (rc == -1) { + if (IN_MULTICAST(ntohl(dst.sin_addr.s_addr))) { + struct ip_mreqn imr; + if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) + error(2, 0, "unknown iface: %s", device); + memset(&imr, 0, sizeof(imr)); + imr.imr_ifindex = ifr.ifr_ifindex; + if (setsockopt(fd, SOL_IP, IP_MULTICAST_IF, &imr, sizeof(imr)) == -1) + error(2, errno, "IP_MULTICAST_IF"); + } else + error(2, errno_save, "SO_BINDTODEVICE %s", device); + } + } + } + + if (settos && + setsockopt(probe_fd, IPPROTO_IP, IP_TOS, (char *)&settos, sizeof(int)) < 0) + error(0, errno, "warning: QOS sockopts"); + + dst.sin_port = htons(1025); + if (nroute) + dst.sin_addr.s_addr = route[0]; + if (connect(probe_fd, (struct sockaddr*)&dst, sizeof(dst)) == -1) { + if (errno == EACCES) { + if (broadcast_pings == 0) + error(2, 0, + "Do you want to ping broadcast? Then -b. If not, check your local firewall rules"); + fprintf(stderr, "WARNING: pinging broadcast address\n"); + if (setsockopt(probe_fd, SOL_SOCKET, SO_BROADCAST, + &broadcast_pings, sizeof(broadcast_pings)) < 0) + error(2, errno, "cannot set broadcasting"); + if (connect(probe_fd, (struct sockaddr*)&dst, sizeof(dst)) == -1) + error(2, errno, "connect"); + } else + error(2, errno, "connect"); + } + alen = sizeof(source); + if (getsockname(probe_fd, (struct sockaddr*)&source, &alen) == -1) + error(2, errno, "getsockname"); + source.sin_port = 0; + + if (device) { + struct ifaddrs *ifa0, *ifa; + int ret; + + ret = getifaddrs(&ifa0); + if (ret) + error(2, errno, "gatifaddrs failed"); + for (ifa = ifa0; ifa; ifa = ifa->ifa_next) { + if (!ifa->ifa_name || !ifa->ifa_addr || + ifa->ifa_addr->sa_family != AF_INET) + continue; + if (!strcmp(ifa->ifa_name, device) && + !memcmp(&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, + &source.sin_addr, sizeof(source.sin_addr))) + break; + } + if (ifa && !memcmp(&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, + &dst.sin_addr, sizeof(source.sin_addr))) { + enable_capability_raw(); + setsockopt(sock->fd, SOL_SOCKET, SO_BINDTODEVICE, "", 0); + disable_capability_raw(); + } + freeifaddrs(ifa0); + if (!ifa) + error(0, 0, "Warning: source address might be selected on device other than: %s", device); + } + close(probe_fd); + } while (0); + + if (whereto.sin_addr.s_addr == 0) + whereto.sin_addr.s_addr = source.sin_addr.s_addr; + + if (device) { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, device, IFNAMSIZ-1); + if (ioctl(sock->fd, SIOCGIFINDEX, &ifr) < 0) + error(2, 0, "unknown iface: %s", device); + } + + if (broadcast_pings || IN_MULTICAST(ntohl(whereto.sin_addr.s_addr))) { + if (uid) { + if (interval < 1000) + error(2, 0, "broadcast ping with too short interval: %d", interval); + if (pmtudisc >= 0 && pmtudisc != IP_PMTUDISC_DO) + error(2, 0, "broadcast ping does not fragment"); + } + if (pmtudisc < 0) + pmtudisc = IP_PMTUDISC_DO; + } + + if (pmtudisc >= 0) { + if (setsockopt(sock->fd, SOL_IP, IP_MTU_DISCOVER, &pmtudisc, sizeof pmtudisc) == -1) + error(2, errno, "IP_MTU_DISCOVER"); + } + + if ((options&F_STRICTSOURCE) && + bind(sock->fd, (struct sockaddr *) &source, sizeof source) == -1) + error(2, errno, "bind"); + + if (sock->socktype == SOCK_RAW) { + struct icmp_filter filt; + filt.data = ~((1<<ICMP_SOURCE_QUENCH)| + (1<<ICMP_DEST_UNREACH)| + (1<<ICMP_TIME_EXCEEDED)| + (1<<ICMP_PARAMETERPROB)| + (1<<ICMP_REDIRECT)| + (1<<ICMP_ECHOREPLY)); + if (setsockopt(sock->fd, SOL_RAW, ICMP_FILTER, &filt, sizeof filt) == -1) + error(0, errno, "WARNING: setsockopt(ICMP_FILTER)"); + } + + hold = 1; + if (setsockopt(sock->fd, SOL_IP, IP_RECVERR, &hold, sizeof hold)) + error(0, 0, "WARNING: your kernel is veeery old. No problems."); + + if (sock->socktype == SOCK_DGRAM) { + if (setsockopt(sock->fd, SOL_IP, IP_RECVTTL, &hold, sizeof hold)) + error(0, errno, "WARNING: setsockopt(IP_RECVTTL)"); + if (setsockopt(sock->fd, SOL_IP, IP_RETOPTS, &hold, sizeof hold)) + error(0, errno, "WARNING: setsockopt(IP_RETOPTS)"); + } + + /* record route option */ + if (options & F_RROUTE) { + memset(rspace, 0, sizeof(rspace)); + rspace[0] = IPOPT_NOP; + rspace[1+IPOPT_OPTVAL] = IPOPT_RR; + rspace[1+IPOPT_OLEN] = sizeof(rspace)-1; + rspace[1+IPOPT_OFFSET] = IPOPT_MINOFF; + optlen = 40; + if (setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, sizeof rspace) < 0) + error(2, errno, "record route"); + } + if (options & F_TIMESTAMP) { + memset(rspace, 0, sizeof(rspace)); + rspace[0] = IPOPT_TIMESTAMP; + rspace[1] = (ts_type==IPOPT_TS_TSONLY ? 40 : 36); + rspace[2] = 5; + rspace[3] = ts_type; + if (ts_type == IPOPT_TS_PRESPEC) { + int i; + rspace[1] = 4+nroute*8; + for (i = 0; i < nroute; i++) { + tmp_rspace = (uint32_t*)&rspace[4+i*8]; + *tmp_rspace = route[i]; + } + } + if (setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, rspace[1]) < 0) { + rspace[3] = 2; + if (setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, rspace[1]) < 0) + error(2, errno, "ts option"); + } + optlen = 40; + } + if (options & F_SOURCEROUTE) { + int i; + memset(rspace, 0, sizeof(rspace)); + rspace[0] = IPOPT_NOOP; + rspace[1+IPOPT_OPTVAL] = (options & F_SO_DONTROUTE) ? IPOPT_SSRR + : IPOPT_LSRR; + rspace[1+IPOPT_OLEN] = 3 + nroute*4; + rspace[1+IPOPT_OFFSET] = IPOPT_MINOFF; + for (i = 0; i < nroute; i++) { + tmp_rspace = (uint32_t*)&rspace[4+i*4]; + *tmp_rspace = route[i]; + } + + if (setsockopt(sock->fd, IPPROTO_IP, IP_OPTIONS, rspace, 4 + nroute*4) < 0) + error(2, errno, "record route"); + optlen = 40; + } + + /* Estimate memory eaten by single packet. It is rough estimate. + * Actually, for small datalen's it depends on kernel side a lot. */ + hold = datalen + 8; + hold += ((hold+511)/512)*(optlen + 20 + 16 + 64 + 160); + sock_setbufs(sock, hold); + + if (broadcast_pings) { + if (setsockopt(sock->fd, SOL_SOCKET, SO_BROADCAST, &broadcast_pings, sizeof broadcast_pings) < 0) + error(2, errno, "cannot set broadcasting"); + } + + if (options & F_NOLOOP) { + int loop = 0; + if (setsockopt(sock->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof loop) == -1) + error(2, errno, "cannot disable multicast loopback"); + } + if (options & F_TTL) { + int ittl = ttl; + if (setsockopt(sock->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof ttl) == -1) + error(2, errno, "cannot set multicast time-to-live"); + if (setsockopt(sock->fd, IPPROTO_IP, IP_TTL, &ittl, sizeof ittl) == -1) + error(2, errno, "cannot set unicast time-to-live"); + } + + if (datalen > 0xFFFF - 8 - optlen - 20) + error(2, 0, "packet size %d is too large. Maximum is %d", + datalen, 0xFFFF - 8 - 20 - optlen); + + if (datalen >= (int) sizeof(struct timeval)) /* can we time transfer */ + timing = 1; + packlen = datalen + MAXIPLEN + MAXICMPLEN; + if (!(packet = (unsigned char *)malloc((unsigned int)packlen))) + error(2, errno, "memory allocation failed"); + + printf("PING %s (%s) ", hostname, inet_ntoa(whereto.sin_addr)); + if (device || (options&F_STRICTSOURCE)) + printf("from %s %s: ", inet_ntoa(source.sin_addr), device ? device : ""); + printf("%d(%d) bytes of data.\n", datalen, datalen+8+optlen+20); + + setup(sock); + + main_loop(&ping4_func_set, sock, packet, packlen); +} + + +int ping4_receive_error_msg(socket_st *sock) +{ + ssize_t res; + char cbuf[512]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsgh; + struct sock_extended_err *e; + struct icmphdr icmph; + struct sockaddr_in target; + int net_errors = 0; + int local_errors = 0; + int saved_errno = errno; + + iov.iov_base = &icmph; + iov.iov_len = sizeof(icmph); + msg.msg_name = (void*)⌖ + msg.msg_namelen = sizeof(target); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + + res = recvmsg(sock->fd, &msg, MSG_ERRQUEUE|MSG_DONTWAIT); + if (res < 0) + goto out; + + e = NULL; + for (cmsgh = CMSG_FIRSTHDR(&msg); cmsgh; cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { + if (cmsgh->cmsg_level == SOL_IP) { + if (cmsgh->cmsg_type == IP_RECVERR) + e = (struct sock_extended_err *)CMSG_DATA(cmsgh); + } + } + if (e == NULL) + abort(); + + if (e->ee_origin == SO_EE_ORIGIN_LOCAL) { + local_errors++; + if (options & F_QUIET) + goto out; + if (options & F_FLOOD) + write_stdout("E", 1); + else if (e->ee_errno != EMSGSIZE) + error(0, 0, "local error: %s", strerror(e->ee_errno)); + else + error(0, 0, "local error: message too long, mtu=%u", e->ee_info); + nerrors++; + } else if (e->ee_origin == SO_EE_ORIGIN_ICMP) { + struct sockaddr_in *sin = (struct sockaddr_in*)(e+1); + + if (res < (ssize_t) sizeof(icmph) || + target.sin_addr.s_addr != whereto.sin_addr.s_addr || + icmph.type != ICMP_ECHO || + !is_ours(sock, icmph.un.echo.id)) { + /* Not our error, not an error at all. Clear. */ + saved_errno = 0; + goto out; + } + + acknowledge(ntohs(icmph.un.echo.sequence)); + + net_errors++; + nerrors++; + if (options & F_QUIET) + goto out; + if (options & F_FLOOD) { + write_stdout("\bE", 2); + } else { + print_timestamp(); + printf("From %s icmp_seq=%u ", pr_addr(sin, sizeof *sin), ntohs(icmph.un.echo.sequence)); + pr_icmph(e->ee_type, e->ee_code, e->ee_info, NULL); + fflush(stdout); + } + } + +out: + errno = saved_errno; + return net_errors ? net_errors : -local_errors; +} + +/* + * 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 several bytes + * of the data portion are used to hold a UNIX "timeval" struct in VAX + * byte-order, to compute the round-trip time. + */ +int ping4_send_probe(socket_st *sock, void *packet, unsigned packet_size __attribute__((__unused__))) +{ + struct icmphdr *icp; + int cc; + int i; + + icp = (struct icmphdr *)packet; + icp->type = ICMP_ECHO; + icp->code = 0; + icp->checksum = 0; + icp->un.echo.sequence = htons(ntransmitted+1); + icp->un.echo.id = ident; /* ID */ + + rcvd_clear(ntransmitted+1); + + if (timing) { + if (options&F_LATENCY) { + struct timeval tmp_tv; + gettimeofday(&tmp_tv, NULL); + memcpy(icp+1, &tmp_tv, sizeof(tmp_tv)); + } else { + memset(icp+1, 0, sizeof(struct timeval)); + } + } + + cc = datalen + 8; /* skips ICMP portion */ + + /* compute ICMP checksum here */ + icp->checksum = in_cksum((unsigned short *)icp, cc, 0); + + if (timing && !(options&F_LATENCY)) { + struct timeval tmp_tv; + gettimeofday(&tmp_tv, NULL); + memcpy(icp+1, &tmp_tv, sizeof(tmp_tv)); + icp->checksum = in_cksum((unsigned short *)&tmp_tv, sizeof(tmp_tv), ~icp->checksum); + } + + i = sendto(sock->fd, icp, cc, 0, (struct sockaddr*)&whereto, sizeof(whereto)); + + return (cc == i ? 0 : i); +} + +/* + * parse_reply -- + * Print out the packet, if it came from us. This logic is necessary + * because ALL readers of the ICMP socket get a copy of ALL ICMP packets + * which arrive ('tis only fair). This permits multiple copies of this + * program to be run without having intermingled output (or statistics!). + */ +static +void pr_echo_reply(uint8_t *_icp, int len __attribute__((__unused__))) +{ + struct icmphdr *icp = (struct icmphdr *)_icp; + printf(" icmp_seq=%u", ntohs(icp->un.echo.sequence)); +} + +int +ping4_parse_reply(struct socket_st *sock, struct msghdr *msg, int cc, void *addr, struct timeval *tv) +{ + struct sockaddr_in *from = addr; + uint8_t *buf = msg->msg_iov->iov_base; + struct icmphdr *icp; + struct iphdr *ip; + int hlen; + int csfailed; + struct cmsghdr *cmsgh; + int reply_ttl; + uint8_t *opts, *tmp_ttl; + int olen; + + /* Check the IP header */ + ip = (struct iphdr *)buf; + if (sock->socktype == SOCK_RAW) { + hlen = ip->ihl*4; + if (cc < hlen + 8 || ip->ihl < 5) { + if (options & F_VERBOSE) + error(0, 0, "packet too short (%d bytes) from %s", cc, + pr_addr(from, sizeof *from)); + return 1; + } + reply_ttl = ip->ttl; + opts = buf + sizeof(struct iphdr); + olen = hlen - sizeof(struct iphdr); + } else { + hlen = 0; + reply_ttl = 0; + opts = buf; + olen = 0; + for (cmsgh = CMSG_FIRSTHDR(msg); cmsgh; cmsgh = CMSG_NXTHDR(msg, cmsgh)) { + if (cmsgh->cmsg_level != SOL_IP) + continue; + if (cmsgh->cmsg_type == IP_TTL) { + if (cmsgh->cmsg_len < sizeof(int)) + continue; + tmp_ttl = (uint8_t *) CMSG_DATA(cmsgh); + reply_ttl = (int)*tmp_ttl; + } else if (cmsgh->cmsg_type == IP_RETOPTS) { + opts = (uint8_t *) CMSG_DATA(cmsgh); + olen = cmsgh->cmsg_len; + } + } + } + + /* Now the ICMP part */ + cc -= hlen; + icp = (struct icmphdr *)(buf + hlen); + csfailed = in_cksum((unsigned short *)icp, cc, 0); + + if (icp->type == ICMP_ECHOREPLY) { + if (!is_ours(sock, icp->un.echo.id)) + return 1; /* 'Twas not our ECHO */ + if (!contains_pattern_in_payload((uint8_t*)(icp+1))) + return 1; /* 'Twas really not our ECHO */ + if (gather_statistics((uint8_t*)icp, sizeof(*icp), cc, + ntohs(icp->un.echo.sequence), + reply_ttl, 0, tv, pr_addr(from, sizeof *from), + pr_echo_reply)) { + fflush(stdout); + return 0; + } + } else { + /* We fall here when a redirect or source quench arrived. */ + + switch (icp->type) { + case ICMP_ECHO: + /* MUST NOT */ + return 1; + case ICMP_SOURCE_QUENCH: + case ICMP_REDIRECT: + case ICMP_DEST_UNREACH: + case ICMP_TIME_EXCEEDED: + case ICMP_PARAMETERPROB: + { + struct iphdr * iph = (struct iphdr *)(&icp[1]); + struct icmphdr *icp1 = (struct icmphdr*)((unsigned char *)iph + iph->ihl*4); + int error_pkt; + if (cc < (int) (8 + sizeof(struct iphdr) + 8) || + cc < 8+iph->ihl*4+8) + return 1; + if (icp1->type != ICMP_ECHO || + iph->daddr != whereto.sin_addr.s_addr || + !is_ours(sock, icp1->un.echo.id)) + return 1; + error_pkt = (icp->type != ICMP_REDIRECT && + icp->type != ICMP_SOURCE_QUENCH); + if (error_pkt) { + acknowledge(ntohs(icp1->un.echo.sequence)); + return 0; + } + if (options & (F_QUIET | F_FLOOD)) + return 1; + print_timestamp(); + printf("From %s: icmp_seq=%u ", + pr_addr(from, sizeof *from), + ntohs(icp1->un.echo.sequence)); + if (csfailed) + printf("(BAD CHECKSUM)"); + pr_icmph(icp->type, icp->code, ntohl(icp->un.gateway), icp); + return 1; + } + default: + /* MUST NOT */ + break; + } + if ((options & F_FLOOD) && !(options & (F_VERBOSE|F_QUIET))) { + if (!csfailed) + write_stdout("!E", 2); + else + write_stdout("!EC", 3); + return 0; + } + if (!(options & F_VERBOSE) || uid) + return 0; + if (options & F_PTIMEOFDAY) { + struct timeval recv_time; + gettimeofday(&recv_time, NULL); + printf("%lu.%06lu ", (unsigned long)recv_time.tv_sec, (unsigned long)recv_time.tv_usec); + } + printf("From %s: ", pr_addr(from, sizeof *from)); + if (csfailed) { + printf("(BAD CHECKSUM)\n"); + return 0; + } + pr_icmph(icp->type, icp->code, ntohl(icp->un.gateway), icp); + return 0; + } + + if (options & F_AUDIBLE) { + putchar('\a'); + if(options & F_FLOOD) + fflush(stdout); + } + if (!(options & F_FLOOD)) { + pr_options(opts, olen + sizeof(struct iphdr)); + + putchar('\n'); + fflush(stdout); + } + return 0; +} + + +#if BYTE_ORDER == LITTLE_ENDIAN +# define ODDBYTE(v) (v) +#elif BYTE_ORDER == BIG_ENDIAN +# define ODDBYTE(v) ((unsigned short)(v) << 8) +#else +# define ODDBYTE(v) htons((unsigned short)(v) << 8) +#endif + +unsigned short +in_cksum(const unsigned short *addr, int len, unsigned short csum) +{ + int nleft = len; + const unsigned short *w = addr; + unsigned short answer; + int sum = csum; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), + * 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. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) + sum += ODDBYTE(*(unsigned char *)w); /* le16toh() may be unavailable on old systems */ + + /* + * 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 */ + answer = ~sum; /* truncate to 16 bits */ + return (answer); +} + +/* + * pr_icmph -- + * Print a descriptive string about an ICMP header. + */ +void pr_icmph(uint8_t type, uint8_t code, uint32_t info, struct icmphdr *icp) +{ + switch(type) { + case ICMP_ECHOREPLY: + printf("Echo Reply\n"); + /* XXX ID + Seq + Data */ + break; + case ICMP_DEST_UNREACH: + switch(code) { + case ICMP_NET_UNREACH: + printf("Destination Net Unreachable\n"); + break; + case ICMP_HOST_UNREACH: + printf("Destination Host Unreachable\n"); + break; + case ICMP_PROT_UNREACH: + printf("Destination Protocol Unreachable\n"); + break; + case ICMP_PORT_UNREACH: + printf("Destination Port Unreachable\n"); + break; + case ICMP_FRAG_NEEDED: + printf("Frag needed and DF set (mtu = %u)\n", info); + break; + case ICMP_SR_FAILED: + printf("Source Route Failed\n"); + break; + case ICMP_NET_UNKNOWN: + printf("Destination Net Unknown\n"); + break; + case ICMP_HOST_UNKNOWN: + printf("Destination Host Unknown\n"); + break; + case ICMP_HOST_ISOLATED: + printf("Source Host Isolated\n"); + break; + case ICMP_NET_ANO: + printf("Destination Net Prohibited\n"); + break; + case ICMP_HOST_ANO: + printf("Destination Host Prohibited\n"); + break; + case ICMP_NET_UNR_TOS: + printf("Destination Net Unreachable for Type of Service\n"); + break; + case ICMP_HOST_UNR_TOS: + printf("Destination Host Unreachable for Type of Service\n"); + break; + case ICMP_PKT_FILTERED: + printf("Packet filtered\n"); + break; + case ICMP_PREC_VIOLATION: + printf("Precedence Violation\n"); + break; + case ICMP_PREC_CUTOFF: + printf("Precedence Cutoff\n"); + break; + default: + printf("Dest Unreachable, Bad Code: %d\n", code); + break; + } + if (icp && (options & F_VERBOSE)) + pr_iph((struct iphdr*)(icp + 1)); + break; + case ICMP_SOURCE_QUENCH: + printf("Source Quench\n"); + if (icp && (options & F_VERBOSE)) + pr_iph((struct iphdr*)(icp + 1)); + break; + case ICMP_REDIRECT: + switch(code) { + case ICMP_REDIR_NET: + printf("Redirect Network"); + break; + case ICMP_REDIR_HOST: + printf("Redirect Host"); + break; + case ICMP_REDIR_NETTOS: + printf("Redirect Type of Service and Network"); + break; + case ICMP_REDIR_HOSTTOS: + printf("Redirect Type of Service and Host"); + break; + default: + printf("Redirect, Bad Code: %d", code); + break; + } + { + struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { icp ? icp->un.gateway : info } }; + + printf("(New nexthop: %s)\n", pr_addr(&sin, sizeof sin)); + } + if (icp && (options & F_VERBOSE)) + pr_iph((struct iphdr*)(icp + 1)); + break; + case ICMP_ECHO: + printf("Echo Request\n"); + /* XXX ID + Seq + Data */ + break; + case ICMP_TIME_EXCEEDED: + switch(code) { + case ICMP_EXC_TTL: + printf("Time to live exceeded\n"); + break; + case ICMP_EXC_FRAGTIME: + printf("Frag reassembly time exceeded\n"); + break; + default: + printf("Time exceeded, Bad Code: %d\n", code); + break; + } + if (icp && (options & F_VERBOSE)) + pr_iph((struct iphdr*)(icp + 1)); + break; + case ICMP_PARAMETERPROB: + printf("Parameter problem: pointer = %u\n", icp ? (ntohl(icp->un.gateway)>>24) : info); + if (icp && (options & F_VERBOSE)) + pr_iph((struct iphdr*)(icp + 1)); + break; + case ICMP_TIMESTAMP: + printf("Timestamp\n"); + /* XXX ID + Seq + 3 timestamps */ + break; + case ICMP_TIMESTAMPREPLY: + printf("Timestamp Reply\n"); + /* XXX ID + Seq + 3 timestamps */ + break; + case ICMP_INFO_REQUEST: + printf("Information Request\n"); + /* XXX ID + Seq */ + break; + case ICMP_INFO_REPLY: + printf("Information Reply\n"); + /* XXX ID + Seq */ + break; +#ifdef ICMP_MASKREQ + case ICMP_MASKREQ: + printf("Address Mask Request\n"); + break; +#endif +#ifdef ICMP_MASKREPLY + case ICMP_MASKREPLY: + printf("Address Mask Reply\n"); + break; +#endif + default: + printf("Bad ICMP type: %d\n", type); + } +} + +void pr_options(unsigned char * cp, int hlen) +{ + int i, j; + int olen, totlen; + unsigned char * optptr; + static int old_rrlen; + static char old_rr[MAX_IPOPTLEN]; + + totlen = hlen-sizeof(struct iphdr); + optptr = cp; + + while (totlen > 0) { + if (*optptr == IPOPT_EOL) + break; + if (*optptr == IPOPT_NOP) { + totlen--; + optptr++; + printf("\nNOP"); + continue; + } + cp = optptr; + olen = optptr[1]; + if (olen < 2 || olen > totlen) + break; + + switch (*cp) { + case IPOPT_SSRR: + case IPOPT_LSRR: + printf("\n%cSRR: ", *cp==IPOPT_SSRR ? 'S' : 'L'); + j = *++cp; + cp++; + if (j > IPOPT_MINOFF) { + for (;;) { + uint32_t address; + memcpy(&address, cp, 4); + cp += 4; + if (address == 0) + printf("\t0.0.0.0"); + else { + struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { address } }; + + printf("\t%s", pr_addr(&sin, sizeof sin)); + } + j -= 4; + putchar('\n'); + if (j <= IPOPT_MINOFF) + break; + } + } + break; + case IPOPT_RR: + j = *++cp; /* get length */ + i = *++cp; /* and pointer */ + if (i > j) + i = j; + i -= IPOPT_MINOFF; + if (i <= 0) + break; + if (i == old_rrlen + && !memcmp(cp, old_rr, i) + && !(options & F_FLOOD)) { + printf("\t(same route)"); + break; + } + old_rrlen = i; + memcpy(old_rr, (char *)cp, i); + printf("\nRR: "); + cp++; + for (;;) { + uint32_t address; + memcpy(&address, cp, 4); + cp += 4; + if (address == 0) + printf("\t0.0.0.0"); + else { + struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { address } }; + + printf("\t%s", pr_addr(&sin, sizeof sin)); + } + i -= 4; + putchar('\n'); + if (i <= 0) + break; + } + break; + case IPOPT_TS: + { + int stdtime = 0, nonstdtime = 0; + uint8_t flags; + j = *++cp; /* get length */ + i = *++cp; /* and pointer */ + if (i > j) + i = j; + i -= 5; + if (i <= 0) + break; + flags = *++cp; + printf("\nTS: "); + cp++; + for (;;) { + long l; + + if ((flags&0xF) != IPOPT_TS_TSONLY) { + uint32_t address; + memcpy(&address, cp, 4); + cp += 4; + if (address == 0) + printf("\t0.0.0.0"); + else { + struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { address } }; + + printf("\t%s", pr_addr(&sin, sizeof sin)); + } + i -= 4; + if (i <= 0) + break; + } + l = *cp++; + l = (l<<8) + *cp++; + l = (l<<8) + *cp++; + l = (l<<8) + *cp++; + + if (l & 0x80000000) { + if (nonstdtime==0) + printf("\t%ld absolute not-standard", l&0x7fffffff); + else + printf("\t%ld not-standard", (l&0x7fffffff) - nonstdtime); + nonstdtime = l&0x7fffffff; + } else { + if (stdtime==0) + printf("\t%ld absolute", l); + else + printf("\t%ld", l - stdtime); + stdtime = l; + } + i -= 4; + putchar('\n'); + if (i <= 0) + break; + } + if (flags>>4) + printf("Unrecorded hops: %d\n", flags>>4); + break; + } + default: + printf("\nunknown option %x", *cp); + break; + } + totlen -= olen; + optptr += olen; + } +} + + +/* + * pr_iph -- + * Print an IP header with options. + */ +void pr_iph(struct iphdr *ip) +{ + int hlen; + unsigned char *cp; + + hlen = ip->ihl << 2; + cp = (unsigned char *)ip + 20; /* point to options */ + + printf("Vr HL TOS Len ID Flg off TTL Pro cks Src Dst Data\n"); + printf(" %1x %1x %02x %04x %04x", + ip->version, ip->ihl, ip->tos, ip->tot_len, ip->id); + printf(" %1x %04x", ((ip->frag_off) & 0xe000) >> 13, + (ip->frag_off) & 0x1fff); + printf(" %02x %02x %04x", ip->ttl, ip->protocol, ip->check); + printf(" %s ", inet_ntoa(*(struct in_addr *)&ip->saddr)); + printf(" %s ", inet_ntoa(*(struct in_addr *)&ip->daddr)); + printf("\n"); + pr_options(cp, hlen); +} + +/* + * pr_addr -- + * + * Return an ascii host address optionally with a hostname. + */ +char * +pr_addr(void *sa, socklen_t salen) +{ + static char buffer[4096] = ""; + static struct sockaddr_storage last_sa = { 0, {0}, 0 }; + static socklen_t last_salen = 0; + char name[NI_MAXHOST] = ""; + char address[NI_MAXHOST] = ""; + + if (salen == last_salen && !memcmp(sa, &last_sa, salen)) + return buffer; + + memcpy(&last_sa, sa, (last_salen = salen)); + + in_pr_addr = !setjmp(pr_addr_jmp); + + getnameinfo(sa, salen, address, sizeof address, NULL, 0, getnameinfo_flags | NI_NUMERICHOST); + if (!exiting && !(options & F_NUMERIC)) + getnameinfo(sa, salen, name, sizeof name, NULL, 0, getnameinfo_flags); + + if (*name) + snprintf(buffer, sizeof buffer, "%s (%s)", name, address); + else + snprintf(buffer, sizeof buffer, "%s", address); + + in_pr_addr = 0; + + return(buffer); +} + + +/* Set Type of Service (TOS) and other Quality of Service relating bits */ +int parsetos(char *str) +{ + const char *cp; + int tos; + char *ep; + + /* handle both hex and decimal values */ + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + cp = str + 2; + tos = (int)strtol(cp, &ep, 16); + } else + tos = (int)strtol(str, &ep, 10); + + /* doesn't look like decimal or hex, eh? */ + if (*ep != '\0') + error(2, 0, "bad TOS value: %s", str); + + if (tos > TOS_MAX) + error(2, 0, "the decimal value of TOS bits must be in range 0-255: %d", tos); + return(tos); +} + +int parseflow(char *str) +{ + const char *cp; + unsigned long val; + char *ep; + + /* handle both hex and decimal values */ + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + cp = str + 2; + val = (int)strtoul(cp, &ep, 16); + } else + val = (int)strtoul(str, &ep, 10); + + /* doesn't look like decimal or hex, eh? */ + if (*ep != '\0') + error(2, 0, "bad value for flowinfo: %s", str); + + if (val & ~IPV6_FLOWINFO_FLOWLABEL) + error(2, 0, "flow value is greater than 20 bits: %s", str); + return(val); +} + + + +void ping4_install_filter(socket_st *sock) +{ + static int once; + static struct sock_filter insns[] = { + BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), /* Skip IP header. F..g BSD... Look into ping6. */ + BPF_STMT(BPF_LD|BPF_H|BPF_IND, 4), /* Load icmp echo ident */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0xAAAA, 0, 1), /* Ours? */ + BPF_STMT(BPF_RET|BPF_K, ~0U), /* Yes, it passes. */ + BPF_STMT(BPF_LD|BPF_B|BPF_IND, 0), /* Load icmp type */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */ + BPF_STMT(BPF_RET|BPF_K, 0xFFFFFFF), /* No. It passes. */ + BPF_STMT(BPF_RET|BPF_K, 0) /* Echo with wrong ident. Reject. */ + }; + static struct sock_fprog filter = { + sizeof insns / sizeof(insns[0]), + insns + }; + + if (once) + return; + once = 1; + + /* Patch bpflet for current identifier. */ + insns[2] = (struct sock_filter)BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, htons(ident), 0, 1); + + if (setsockopt(sock->fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) + error(0, errno, "WARNING: failed to install socket filter"); +} diff --git a/iputils/ping.h b/iputils/ping.h new file mode 100644 index 0000000000000000000000000000000000000000..91cff9bd7682d0332ef5b623c9235f70715f31c5 --- /dev/null +++ b/iputils/ping.h @@ -0,0 +1,422 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <signal.h> +#include <poll.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <linux/types.h> +#include <linux/sockios.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <sys/uio.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <netdb.h> +#include <setjmp.h> +#include <netinet/icmp6.h> +#include <asm/byteorder.h> +#include <sched.h> +#include <math.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> +#include <linux/filter.h> +#include <resolv.h> + +#ifdef HAVE_ERROR_H +#include <error.h> +#else +#include <stdarg.h> +#endif + +#ifdef HAVE_LIBCAP +#include <sys/prctl.h> +#include <sys/capability.h> +#endif + +#ifdef USE_IDN +#include <locale.h> +#include <idn2.h> + +#ifndef AI_IDN +#define AI_IDN 0x0040 +#endif +#ifndef AI_CANONIDN +#define AI_CANONIDN 0x0080 +#endif +#ifndef NI_IDN +#define NI_IDN 32 +#endif + +#define getaddrinfo_flags (AI_CANONNAME | AI_IDN | AI_CANONIDN) +#define getnameinfo_flags NI_IDN +#else +#define getaddrinfo_flags (AI_CANONNAME) +#define getnameinfo_flags 0 +#endif + +#include <ifaddrs.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <linux/types.h> +#include <linux/errqueue.h> +#include <linux/in6.h> + +#ifndef SCOPE_DELIMITER +#define SCOPE_DELIMITER '%' +#endif + +#define DEFDATALEN (64 - 8) /* default data length */ + +#define MAXWAIT 10 /* max seconds to wait for response */ +#define MININTERVAL 10 /* Minimal interpacket gap */ +#define MINUSERINTERVAL 200 /* Minimal allowed interval for non-root */ + +#define SCHINT(a) (((a) <= MININTERVAL) ? MININTERVAL : (a)) + +/* various options */ +extern int options; +#define F_FLOOD 0x001 +#define F_INTERVAL 0x002 +#define F_NUMERIC 0x004 +#define F_PINGFILLED 0x008 +#define F_QUIET 0x010 +#define F_RROUTE 0x020 +#define F_SO_DEBUG 0x040 +#define F_SO_DONTROUTE 0x080 +#define F_VERBOSE 0x100 +#define F_TIMESTAMP 0x200 +#define F_SOURCEROUTE 0x400 +#define F_FLOOD_POLL 0x800 +#define F_LATENCY 0x1000 +#define F_AUDIBLE 0x2000 +#define F_ADAPTIVE 0x4000 +#define F_STRICTSOURCE 0x8000 +#define F_NOLOOP 0x10000 +#define F_TTL 0x20000 +#define F_MARK 0x40000 +#define F_PTIMEOFDAY 0x80000 +#define F_OUTSTANDING 0x100000 +#define F_FLOWINFO 0x200000 +#define F_TCLASS 0x400000 + +/* + * MAX_DUP_CHK is the number of bits in received table, i.e. the maximum + * number of received sequence numbers we can keep track of. + */ +#define MAX_DUP_CHK 0x10000 + +#if defined(__WORDSIZE) && __WORDSIZE == 64 +# define USE_BITMAP64 +#endif + +#ifdef USE_BITMAP64 +typedef uint64_t bitmap_t; +# define BITMAP_SHIFT 6 +#else +typedef uint32_t bitmap_t; +# define BITMAP_SHIFT 5 +#endif + +#if ((MAX_DUP_CHK >> (BITMAP_SHIFT + 3)) << (BITMAP_SHIFT + 3)) != MAX_DUP_CHK +# error Please MAX_DUP_CHK and/or BITMAP_SHIFT +#endif + +struct rcvd_table { + bitmap_t bitmap[MAX_DUP_CHK / (sizeof(bitmap_t) * 8)]; +}; + +extern struct rcvd_table rcvd_tbl; + +#define A(bit) (rcvd_tbl.bitmap[(bit) >> BITMAP_SHIFT]) /* identify word in array */ +#define B(bit) (((bitmap_t)1) << ((bit) & ((1 << BITMAP_SHIFT) - 1))) /* identify bit in word */ + +static inline void rcvd_set(uint16_t seq) +{ + unsigned bit = seq % MAX_DUP_CHK; + A(bit) |= B(bit); +} + +static inline void rcvd_clear(uint16_t seq) +{ + unsigned bit = seq % MAX_DUP_CHK; + A(bit) &= ~B(bit); +} + +static inline bitmap_t rcvd_test(uint16_t seq) +{ + unsigned bit = seq % MAX_DUP_CHK; + return A(bit) & B(bit); +} + +#ifndef HAVE_ERROR_H +static void error(int status, int errnum, const char *format, ...) +{ + va_list ap; + + fprintf(stderr, "ping: "); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + if (errnum) + fprintf(stderr, ": %s\n", strerror(errnum)); + else + fprintf(stderr, "\n"); + if (status) + exit(status); +} +#endif + +extern int datalen; +extern char *hostname; +extern int uid; +extern int ident; /* process id to identify our packets */ + +extern int sndbuf; +extern int ttl; + +extern long npackets; /* max packets to transmit */ +extern long nreceived; /* # of packets we got back */ +extern long nrepeats; /* number of duplicates */ +extern long ntransmitted; /* sequence # for outbound packets = #sent */ +extern long nchecksum; /* replies with bad checksum */ +extern long nerrors; /* icmp errors */ +extern int interval; /* interval between packets (msec) */ +extern int preload; +extern int deadline; /* time to die */ +extern int lingertime; +extern struct timeval start_time, cur_time; +extern volatile int exiting; +extern volatile int status_snapshot; +extern int confirm; +extern int confirm_flag; +extern char *device; +extern int pmtudisc; + +extern volatile int in_pr_addr; /* pr_addr() is executing */ +extern jmp_buf pr_addr_jmp; + +#ifndef MSG_CONFIRM +#define MSG_CONFIRM 0 +#endif + + +/* timing */ +extern int timing; /* flag to do timing */ +extern long tmin; /* minimum round trip time */ +extern long tmax; /* maximum round trip time */ +extern long long tsum; /* sum of all times, for doing average */ +extern long long tsum2; +extern int rtt; +extern uint16_t acked; +extern int pipesize; + +/* + * Write to stdout + */ +static inline void write_stdout(const char *str, size_t len) +{ + size_t o = 0; + ssize_t cc; + do { + cc = write(STDOUT_FILENO, str + o, len - o); + o += cc; + } while (len > o || cc < 0); +} + +/* + * tvsub -- + * Subtract 2 timeval structs: out = out - in. Out is assumed to + * be >= in. + */ +static inline void tvsub(struct timeval *out, struct timeval *in) +{ + if ((out->tv_usec -= in->tv_usec) < 0) { + --out->tv_sec; + out->tv_usec += 1000000; + } + out->tv_sec -= in->tv_sec; +} + +static inline void set_signal(int signo, void (*handler)(int)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + + sa.sa_handler = (void (*)(int))handler; + sigaction(signo, &sa, NULL); +} + +extern int __schedule_exit(int next); + +static inline int schedule_exit(int next) +{ + if (npackets && ntransmitted >= npackets && !deadline) + next = __schedule_exit(next); + return next; +} + +static inline int in_flight(void) +{ + uint16_t diff = (uint16_t)ntransmitted - acked; + return (diff<=0x7FFF) ? diff : ntransmitted-nreceived-nerrors; +} + +static inline void acknowledge(uint16_t seq) +{ + uint16_t diff = (uint16_t)ntransmitted - seq; + if (diff <= 0x7FFF) { + if ((int)diff+1 > pipesize) + pipesize = (int)diff+1; + if ((int16_t)(seq - acked) > 0 || + (uint16_t)ntransmitted - acked > 0x7FFF) + acked = seq; + } +} + +static inline void advance_ntransmitted(void) +{ + ntransmitted++; + /* Invalidate acked, if 16 bit seq overflows. */ + if ((uint16_t)ntransmitted - acked > 0x7FFF) + acked = (uint16_t)ntransmitted + 1; +} + +extern void usage(void) __attribute__((noreturn)); +extern void limit_capabilities(void); +static int enable_capability_raw(void); +static int disable_capability_raw(void); +static int enable_capability_admin(void); +static int disable_capability_admin(void); +#ifdef HAVE_LIBCAP +extern int modify_capability(cap_value_t, cap_flag_value_t); +static inline int enable_capability_raw(void) { return modify_capability(CAP_NET_RAW, CAP_SET); } +static inline int disable_capability_raw(void) { return modify_capability(CAP_NET_RAW, CAP_CLEAR); } +static inline int enable_capability_admin(void) { return modify_capability(CAP_NET_ADMIN, CAP_SET); } +static inline int disable_capability_admin(void) { return modify_capability(CAP_NET_ADMIN, CAP_CLEAR); } +#else +extern int modify_capability(int); +static inline int enable_capability_raw(void) { return modify_capability(1); } +static inline int disable_capability_raw(void) { return modify_capability(0); } +static inline int enable_capability_admin(void) { return modify_capability(1); } +static inline int disable_capability_admin(void) { return modify_capability(0); } +#endif +extern void drop_capabilities(void); + +typedef struct socket_st { + int fd; + int socktype; +} socket_st; + +char *pr_addr(void *sa, socklen_t salen); + +int is_ours(socket_st *sock, uint16_t id); + +int ping4_run(int argc, char **argv, struct addrinfo *ai, socket_st *sock); +int ping4_send_probe(socket_st *, void *packet, unsigned packet_size); +int ping4_receive_error_msg(socket_st *); +int ping4_parse_reply(socket_st *, struct msghdr *msg, int len, void *addr, struct timeval *); +void ping4_install_filter(socket_st *); + +typedef struct ping_func_set_st { + int (*send_probe)(socket_st *, void *packet, unsigned packet_size); + int (*receive_error_msg)(socket_st *sock); + int (*parse_reply)(socket_st *, struct msghdr *msg, int len, void *addr, struct timeval *); + void (*install_filter)(socket_st *); +} ping_func_set_st; + +#define MAXPACKET 128000 /* max packet size */ +extern ping_func_set_st ping4_func_set; + +extern int pinger(ping_func_set_st *fset, socket_st *sock); +extern void sock_setbufs(socket_st*, int alloc); +extern void setup(socket_st *); +extern int contains_pattern_in_payload(uint8_t *ptr); +extern void main_loop(ping_func_set_st *fset, socket_st*, uint8_t *buf, int buflen) __attribute__((noreturn)); +extern void finish(void) __attribute__((noreturn)); +extern void status(void); +extern void common_options(int ch); +extern int gather_statistics(uint8_t *ptr, int icmplen, + int cc, uint16_t seq, int hops, + int csfailed, struct timeval *tv, char *from, + void (*pr_reply)(uint8_t *ptr, int cc)); +extern void print_timestamp(void); +void fill(char *patp, unsigned char *packet, unsigned packet_size); + +extern int mark; +extern unsigned char outpack[MAXPACKET]; + +/* IPv6 */ + +int ping6_run(int argc, char **argv, struct addrinfo *ai, socket_st *sock); +void ping6_usage(unsigned from_ping); + +int ping6_send_probe(socket_st *sockets, void *packet, unsigned packet_size); +int ping6_receive_error_msg(socket_st *sockets); +int ping6_parse_reply(socket_st *, struct msghdr *msg, int len, void *addr, struct timeval *); +void ping6_install_filter(socket_st *sockets); + +extern ping_func_set_st ping6_func_set; + +int niquery_option_handler(const char *opt_arg); + +extern uint32_t tclass; +extern uint32_t flowlabel; +extern struct sockaddr_in6 source6; +extern struct sockaddr_in6 whereto6; +extern struct sockaddr_in6 firsthop6; + +/* IPv6 node information query */ + +#define NI_NONCE_SIZE 8 + +struct ni_hdr { + struct icmp6_hdr ni_u; + uint8_t ni_nonce[NI_NONCE_SIZE]; +}; + +#define ni_type ni_u.icmp6_type +#define ni_code ni_u.icmp6_code +#define ni_cksum ni_u.icmp6_cksum +#define ni_qtype ni_u.icmp6_data16[0] +#define ni_flags ni_u.icmp6_data16[1] + +/* Types */ +#ifndef ICMPV6_NI_QUERY +# define ICMPV6_NI_QUERY 139 +# define ICMPV6_NI_REPLY 140 +#endif + +/* Query Codes */ +#define NI_SUBJ_IPV6 0 +#define NI_SUBJ_NAME 1 +#define NI_SUBJ_IPV4 2 + +/* Reply Codes */ +#define NI_SUCCESS 0 +#define NI_REFUSED 1 +#define NI_UNKNOWN 2 + +/* Qtypes */ +#define NI_QTYPE_NOOP 0 +#define NI_QTYPE_NAME 2 +#define NI_QTYPE_IPV6ADDR 3 +#define NI_QTYPE_IPV4ADDR 4 + +/* Flags */ +#define NI_IPV6ADDR_F_TRUNCATE __constant_cpu_to_be16(0x0001) +#define NI_IPV6ADDR_F_ALL __constant_cpu_to_be16(0x0002) +#define NI_IPV6ADDR_F_COMPAT __constant_cpu_to_be16(0x0004) +#define NI_IPV6ADDR_F_LINKLOCAL __constant_cpu_to_be16(0x0008) +#define NI_IPV6ADDR_F_SITELOCAL __constant_cpu_to_be16(0x0010) +#define NI_IPV6ADDR_F_GLOBAL __constant_cpu_to_be16(0x0020) + +#define NI_IPV4ADDR_F_TRUNCATE NI_IPV6ADDR_F_TRUNCATE +#define NI_IPV4ADDR_F_ALL NI_IPV6ADDR_F_ALL diff --git a/iputils/ping_common.c b/iputils/ping_common.c new file mode 100755 index 0000000000000000000000000000000000000000..3e092f104f46502582c08670fe52985a904b5eb2 --- /dev/null +++ b/iputils/ping_common.c @@ -0,0 +1,1013 @@ +/* + * 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. 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. + */ + +#include "ping.h" + +#ifndef HZ +#define HZ sysconf(_SC_CLK_TCK) +#endif + +int options; + +int mark; +int sndbuf; +int ttl; +int rtt; +int rtt_addend; +uint16_t acked; + +unsigned char outpack[MAXPACKET]; +struct rcvd_table rcvd_tbl; + +/* 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 = 1; +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; + +/* 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; /* process id to identify our packets */ + +static int screen_width = INT_MAX; + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#ifdef HAVE_LIBCAP +static cap_value_t cap_raw = CAP_NET_RAW; +static cap_value_t cap_admin = CAP_NET_ADMIN; +#endif + +void usage(void) +{ + fprintf(stderr, + "\nUsage\n" + " ping [options] <destination>\n" + "\nOptions:\n" + " <destination> dns name or ip address\n" + " -a use audible ping\n" + " -A use adaptive ping\n" + " -B sticky source address\n" + " -c <count> stop after <count> replies\n" + " -D print timestamps\n" + " -d use SO_DEBUG socket option\n" + " -f flood ping\n" + " -h print help and exit\n" + " -I <interface> either interface name or address\n" + " -i <interval> seconds between sending each packet\n" + " -L suppress loopback of multicast packets\n" + " -l <preload> send <preload> number of packages while waiting replies\n" + " -m <mark> tag the packets going out\n" + " -M <pmtud opt> define mtu discovery, can be one of <do|dont|want>\n" + " -n no dns name resolution\n" + " -O report outstanding replies\n" + " -p <pattern> contents of padding byte\n" + " -q quiet output\n" + " -Q <tclass> use quality of service <tclass> bits\n" + " -s <size> use <size> as number of data bytes to be sent\n" + " -S <size> use <size> as SO_SNDBUF socket option value\n" + " -t <ttl> define time to live\n" + " -U print user-to-user latency\n" + " -v verbose output\n" + " -V print version and exit\n" + " -w <deadline> reply wait <deadline> in seconds\n" + " -W <timeout> time to wait for response\n" + "\nIPv4 options:\n" + " -4 use IPv4\n" + " -b allow pinging broadcast\n" + " -R record route\n" + " -T <timestamp> define timestamp, can be one of <tsonly|tsandaddr|tsprespec>\n" + "\nIPv6 options:\n" + " -6 use IPv6\n" + " -F <flowlabel> define flow label, default is random\n" + " -N <nodeinfo opt> use icmp6 node info query, try <help> as argument\n" + "\nFor more details see ping(8).\n" + ); + exit(2); +} + +void limit_capabilities(void) +{ +#ifdef HAVE_LIBCAP + 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) + error(-1, errno, "cap_get_proc"); + cap_p = cap_init(); + if (!cap_p) + error(-1, errno, "cap_init"); + 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) + error(-1, errno, "cap_set_proc"); + if (prctl(PR_SET_KEEPCAPS, 1) < 0) + error(-1, errno, "prctl"); + if (setuid(getuid()) < 0) + error(-1, errno, "setuid"); + if (prctl(PR_SET_KEEPCAPS, 0) < 0) + error(-1, errno, "prctl"); + cap_free(cap_p); + cap_free(cap_cur_p); +#endif + uid = getuid(); + euid = geteuid(); +#ifndef HAVE_LIBCAP + if (seteuid(uid)) + error(-1, errno, "setuid"); +#endif +} + +#ifdef HAVE_LIBCAP +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) { + error(0, errno, "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) { + error(0, errno, "cap_set_proc"); + goto out; + } + + cap_free(cap_p); + cap_p = NULL; + + rc = 0; +out: + if (cap_p) + cap_free(cap_p); + return rc; +} +#else +int modify_capability(int on) +{ + if (seteuid(on ? euid : getuid())) { + error(0, errno, "seteuid"); + return -1; + } + + return 0; +} +#endif + +void drop_capabilities(void) +{ +#ifdef HAVE_LIBCAP + cap_t cap = cap_init(); + if (cap_set_proc(cap) < 0) + error(-1, errno, "cap_set_proc"); + cap_free(cap); +#else + if (setuid(getuid())) + error(-1, errno, "setuid"); +#endif +} + +/* Fills all the outpack, excluding ICMP header, but _including_ + * timestamp area with supplied pattern. + */ +void fill(char *patp, unsigned char *packet, unsigned packet_size) +{ + int ii, jj; + unsigned int pat[16]; + char *cp; + unsigned char *bp = packet+8; + +#ifdef USE_IDN + setlocale(LC_ALL, "C"); +#endif + + for (cp = patp; *cp; cp++) { + if (!isxdigit(*cp)) + error(2, 0, "patterns must be specified as hex digits: %s", cp); + } + 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) { + unsigned kk; + for (kk = 0; kk <= packet_size - (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 +} + +static void sigexit(int signo __attribute__((__unused__))) +{ + exiting = 1; + if (in_pr_addr) + longjmp(pr_addr_jmp, 0); +} + +static void sigstatus(int signo __attribute__((__unused__))) +{ + 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 < (unsigned long) (1000*interval)) + waittime = 1000*interval; + } else + waittime = lingertime*1000; + + if (next < 0 || (unsigned long)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 several 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(ping_func_set_st *fset, socket_st *sock) +{ + 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 = fset->send_probe(sock, outpack, sizeof(outpack)); + + 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=fset->receive_error_msg(sock)) > 0) { + /* An ICMP error arrived. In this case, we've received + * an error from sendto(), but we've also received an + * ICMP message, which means the packet did in fact + * send in some capacity. So, in this odd case, report + * the more specific errno as the error, and treat this + * as a hard local error. */ + i = 0; + goto hard_local_error; + } + /* Compatibility with old linuces. */ + if (i == 0 && confirm_flag && errno == EINVAL) { + confirm_flag = 0; + errno = 0; + } + if (!errno) + goto resend; + } + +hard_local_error: + /* 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(socket_st *sock, int alloc) +{ + int rcvbuf, hold; + socklen_t tmplen = sizeof(hold); + + if (!sndbuf) + sndbuf = alloc; + setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf, sizeof(sndbuf)); + + rcvbuf = hold = alloc * preload; + if (hold < 65536) + hold = 65536; + setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (char *)&hold, sizeof(hold)); + if (getsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (char *)&hold, &tmplen) == 0) { + if (hold < rcvbuf) + error(0, 0, "WARNING: probably, rcvbuf is not enough to hold preload"); + } +} + +/* Protocol independent setup and parameter checks. */ + +void setup(socket_st *sock) +{ + int hold; + struct timeval tv; + sigset_t sset; + + if ((options & F_FLOOD) && !(options & F_INTERVAL)) + interval = 0; + + if (uid && interval < MINUSERINTERVAL) + error(2, 0, "cannot flood; minimal interval allowed for user is %dms", MINUSERINTERVAL); + + if (interval >= INT_MAX/preload) + error(2, 0, "illegal preload and/or interval: %d", interval); + + hold = 1; + if (options & F_SO_DEBUG) + setsockopt(sock->fd, SOL_SOCKET, SO_DEBUG, (char *)&hold, sizeof(hold)); + if (options & F_SO_DONTROUTE) + setsockopt(sock->fd, SOL_SOCKET, SO_DONTROUTE, (char *)&hold, sizeof(hold)); + +#ifdef SO_TIMESTAMP + if (!(options&F_LATENCY)) { + int on = 1; + if (setsockopt(sock->fd, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on))) + error(0, 0, "Warning: no SO_TIMESTAMP support, falling back to SIOCGSTAMP"); + } +#endif +#ifdef SO_MARK + if (options & F_MARK) { + int ret; + int errno_save; + + enable_capability_admin(); + ret = setsockopt(sock->fd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)); + errno_save = errno; + disable_capability_admin(); + + if (ret == -1) { + /* we probably dont wanna exit since old kernels + * dont support mark .. + */ + error(0, errno_save, "Warning: Failed to set mark: %d", mark); + } + } +#endif + + /* 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(sock->fd, 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(sock->fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv))) + options |= F_FLOOD_POLL; + + if (!(options & F_PINGFILLED)) { + int i; + unsigned 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 (sock->socktype == SOCK_RAW) + 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; + } + } +} + +/* + * Return 0 if pattern in payload point to be ptr did not match the pattern that was sent + */ +int contains_pattern_in_payload(uint8_t *ptr) +{ + int i; + uint8_t *cp, *dp; + + /* 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) + return 0; + } + return 1; +} + +void main_loop(ping_func_set_st *fset, socket_st *sock, uint8_t *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; + int recv_error; + + 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(fset, sock); + 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; + recv_error = 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 = sock->fd; + pset.events = POLLIN; + pset.revents = 0; + if (poll(&pset, 1, next) < 1 || + !(pset.revents&(POLLIN|POLLERR))) + continue; + polling = MSG_DONTWAIT; + recv_error = pset.revents&POLLERR; + } + } + + 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(sock->fd, &msg, polling); + polling = MSG_DONTWAIT; + + if (cc < 0) { + /* If there was a POLLERR and there is no packet + * on the socket, try to read the error queue. + * Otherwise, give up. + */ + if ((errno == EAGAIN && !recv_error) || + errno == EINTR) + break; + recv_error = 0; + if (!fset->receive_error_msg(sock)) { + if (errno) { + error(0, errno, "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(sock->fd, SIOCGSTAMP, &recv_time)) + gettimeofday(&recv_time, NULL); + recv_timep = &recv_time; + } + + not_ours = fset->parse_reply(sock, &msg, cc, addrbuf, recv_timep); + } + + /* See? ... someone runs another ping on this host. */ + if (not_ours && sock->socktype == SOCK_RAW) + fset->install_filter(sock); + + /* 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(uint8_t *icmph, int icmplen, + int cc, uint16_t seq, int hops, + int csfailed, struct timeval *tv, char *from, + void (*pr_reply)(uint8_t *icmph, int cc)) +{ + int dupflag = 0; + long triptime = 0; + uint8_t *ptr = icmph + icmplen; + + ++nreceived; + if (!csfailed) + acknowledge(seq); + + if (timing && cc >= (int) (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) { + error(0, 0, "Warning: time of day goes back (%ldus), taking countermeasures", 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; + uint8_t *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+500)/1000); + else if (triptime >= 10000) + printf(" time=%ld.%01ld ms", (triptime+50)/1000, + ((triptime+50)%1000)/100); + else if (triptime >= 1000) + printf(" time=%ld.%02ld ms", (triptime+5)/1000, + ((triptime+5)%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 = ((unsigned 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 = (unsigned 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 = LLONG_MAX; + 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) { +#ifdef USE_IDN + setlocale(LC_ALL, "C"); +#endif + printf(", %g%% packet loss", + (float) ((((long long)(ntransmitted - nreceived)) * 100.0) / + ntransmitted)); + printf(", time %ldms", 1000*tv.tv_sec+(tv.tv_usec+500)/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", nreceived, ntransmitted, 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(socket_st *sock, uint16_t id) { + return sock->socktype == SOCK_DGRAM || id == ident; +}