diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aff75fe3721a3bf9def4a157ad63829df418b6b..35b070543c130e17f09856dd803c8d8a84b7b5f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,16 @@ 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 iputils/traceroute/*.c) +file(GLOB IPUTILS_HEADERS iputils/*.h iputils/traceroute/*.h) +add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_NET_SRCS} ${DAP_CHAIN_NET_HEADERS} ${IPUTILS_SRCS} ${IPUTILS_HEADERS}) -target_link_libraries(dap_chain_net dap_core dap_crypto dap_chain dap_chain_crypto ) -target_include_directories(dap_chain_net INTERFACE .) +find_package(PkgConfig REQUIRED) +pkg_search_module(GLIB REQUIRED glib-2.0) + +target_link_libraries(dap_chain_net dap_core dap_crypto dap_chain dap_chain_crypto -lresolv ${GLIB_LDFLAGS}) +target_include_directories(dap_chain_net INTERFACE . PUBLIC ${GLIB_INCLUDE_DIRS}) set(${PROJECT_NAME}_DEFINITIONS CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE) diff --git a/iputils/iputils.c b/iputils/iputils.c new file mode 100644 index 0000000000000000000000000000000000000000..0e16de9e96a627a1a61d89a10493d12fb23424e8 --- /dev/null +++ b/iputils/iputils.c @@ -0,0 +1,48 @@ +/* + * Set utilities for networking + */ + +#include <stdio.h> +#include <stdbool.h> +#include <glib.h> + +static bool LOG_VERBOSE = false; + +/** + * Set verbose mode + */ +void iputils_set_verbose(void) +{ + LOG_VERBOSE = true; +} + +/** + * Reset verbose mode + */ +void iputils_reset_verbose(void) +{ + LOG_VERBOSE = false; +} + +// analog printf() +int log_printf(const char *format, ...) +{ + int ret = 0; + if(LOG_VERBOSE) + { + gchar *log_str = NULL; + va_list args; + + va_start(args, format); + log_str = g_strdup_vprintf(format, args); + va_end(args); + + if(log_str) + { + + ret = printf(log_str); + g_free(log_str); + } + } + return ret; +} diff --git a/iputils/iputils.h b/iputils/iputils.h new file mode 100644 index 0000000000000000000000000000000000000000..244f566801bf73861d27ff8955eba6575db13715 --- /dev/null +++ b/iputils/iputils.h @@ -0,0 +1,76 @@ +/* + * Set utilities for networking + */ + +#ifndef _IPUTILS_H +#define _IPUTILS_H + +#include <stdint.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Send ping for ipv4 + * + * @addr host name or IP address + * @count number of packets to transmit + * @return ping time in microsecond or -1 if error + */ +int ping_util(const char *addr, int count); + +/** + * Send ping for ipv6 + * + * @addr host name or IP address + * @count number of packets to transmit + * @return ping time in microsecond or -1 if error + */ +int ping_util6(const char *addr, int count); + + +/** + * Tracepath host + * + * @addr[in] host name or IP address + * @hops[out] hops count + * @time_usec[out] latency in microseconds + * @return 0 Ok, -1 error + */ +int tracepath_util(const char *addr, int *hops, int *time_usec); + +/** + * Traceroute host + * + * @addr[in] host name or IP address + * @hops[out] hops count + * @time_usec[out] latency in microseconds + * @return 0 Ok, -1 error + */ +int traceroute_util(const char *addr, int *hops, int *time_usec); + + +/** + * Set verbose mode + */ +void iputils_set_verbose(void); +/** + * Reset verbose mode + */ +void iputils_reset_verbose(void); + + +// analog printf() +int log_printf(const char *format, ...); + +#define PACKAGE_NAME "iputils" +#define PACKAGE_VERSION "0.1" +#define IPUTILS_VERSION(_prog) "%s from %s %s\n", _prog, PACKAGE_NAME, PACKAGE_VERSION +#define UNUSED(x) (void)(x) +#ifdef __cplusplus +} +#endif + +#endif // _IPUTILS_H diff --git a/iputils/ping.c b/iputils/ping.c new file mode 100644 index 0000000000000000000000000000000000000000..0c3ec725e875fc74aecc6554c3e617473612cb82 --- /dev/null +++ b/iputils/ping.c @@ -0,0 +1,1653 @@ +/* + * 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> +#include <glib.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; +} + +static int ping_main(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; + optind = 0; + + 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; +} + +/** + * Send ping + * + * @type for ipv4=4, for ipv6=6 + * @addr host name or IP address + * @count number of packets to transmit + * @return ping time in microsecond or -1 if error + */ +int ping_util_common(int type, const char *addr, int count) +{ + + /* + rights for /bin/ping: -rwsr-xr-x 1 root root + current parametr: + # sysctl net.ipv4.ping_group_range + net.ipv4.ping_group_range = 1 0 + Need change range for other users: + # sysctl net.ipv4.ping_group_range="1 65000" + */ + int argc = 3; + const char *argv[argc]; + if(type != 4) + argv[0] = "ping6"; + else + argv[0] = "ping4"; + argv[1] = g_strdup_printf("-c%d", count); + argv[2] = addr; + ping_main(argc, (char**) argv); + g_free((char*) argv[1]); + if(ntransmitted > 1 && nreceived > 1) + return tsum; + return -1; +} + +/** + * Send ping for ipv4 + * + * @addr host name or IP address + * @count number of packets to transmit + * @return ping time in microsecond or -1 if error + */ +int ping_util(const char *addr, int count) +{ + return ping_util_common(4, addr, count); +} + +/** + * Send ping for ipv6 + * + * @addr host name or IP address + * @count number of packets to transmit + * @return ping time in microsecond or -1 if error + */ +int ping_util6(const char *addr, int count) +{ + return ping_util_common(6, addr, count); +} + +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); + log_printf("main_loop start %s (%s)\n", hostname, inet_ntoa(whereto.sin_addr)); + main_loop(&ping4_func_set, sock, packet, packlen); + log_printf("main_loop end\n"); + return 0; +} + +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); + if(!sock) + return net_errors; + 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; + log_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) { + //log_printf("in ping4_parse_reply00\n"); + 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; + } + //log_printf("in ping4_parse_reply01\n"); + } 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(); + log_printf("From %s: icmp_seq=%u ", + pr_addr(from, sizeof *from), + ntohs(icp1->un.echo.sequence)); + if(csfailed) + log_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); + log_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) { + log_printf("(BAD CHECKSUM)\n"); + return 0; + } + pr_icmph(icp->type, icp->code, ntohl(icp->un.gateway), icp); + return 0; + } + + if(options & F_AUDIBLE) { + log_printf("\a"); //putchar('\a'); + if(options & F_FLOOD) + fflush(stdout); + } + if(!(options & F_FLOOD)) { + pr_options(opts, olen + sizeof(struct iphdr)); + + log_printf("\n"); //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..97a56ee261f2c99b4288950dc35558488592b7cf --- /dev/null +++ b/iputils/ping.h @@ -0,0 +1,426 @@ +#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> + +#include "iputils.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/ping6_common.c b/iputils/ping6_common.c new file mode 100755 index 0000000000000000000000000000000000000000..aa1ebc831d6c9c843bf12e9b85b647baabc6ac60 --- /dev/null +++ b/iputils/ping6_common.c @@ -0,0 +1,1390 @@ +/* + * + * Modified for AF_INET6 by Pedro Roque + * + * <roque@di.fc.ul.pt> + * + * Original copyright notice included bellow + */ + +/* + * 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 or + * if -N option is used, this program has to run SUID to ROOT or + * with net_cap_raw enabled. + */ +#include "ping.h" + +ping_func_set_st ping6_func_set = { + .send_probe = ping6_send_probe, + .receive_error_msg = ping6_receive_error_msg, + .parse_reply = ping6_parse_reply, + .install_filter = ping6_install_filter +}; + +#ifndef SCOPE_DELIMITER +# define SCOPE_DELIMITER '%' +#endif + +uint32_t flowlabel; +uint32_t tclass; + +static struct sockaddr_in6 whereto; +static struct sockaddr_in6 firsthop; + +static unsigned char cmsgbuf[4096]; +static size_t cmsglen = 0; + +static int pr_icmph(uint8_t type, uint8_t code, uint32_t info); + +struct sockaddr_in6 source6 = { .sin6_family = AF_INET6 }; +extern char *device; + +#if defined(USE_GCRYPT) || defined(USE_OPENSSL) || defined(USE_NETTLE) +#include "iputils_md5dig.h" +#define USE_CRYPTO +#endif + +/* Node Information query */ +int ni_query = -1; +int ni_flag = 0; +void *ni_subject = NULL; +int ni_subject_len = 0; +int ni_subject_type = -1; +char *ni_group; + +static inline int ntohsp(uint16_t *p) +{ + uint16_t v; + memcpy(&v, p, sizeof(v)); + return ntohs(v); +} + +unsigned int if_name2index(const char *ifname) +{ + unsigned int i = if_nametoindex(ifname); + if(!i) + error(2, 0, "unknown iface: %s", ifname); + return i; +} + +struct niquery_option { + char *name; + int namelen; + int has_arg; + int data; + int (*handler)(int index, const char *arg); +}; + +#define NIQUERY_OPTION(_name, _has_arg, _data, _handler) \ + { \ + .name = _name, \ + .namelen = sizeof(_name) - 1, \ + .has_arg = _has_arg, \ + .data = _data, \ + .handler = _handler \ + } + +static int niquery_option_name_handler(int index __attribute__((__unused__)), + const char *arg __attribute__((__unused__))); +static int niquery_option_ipv6_handler(int index __attribute__((__unused__)), + const char *arg __attribute__((__unused__))); +static int niquery_option_ipv6_flag_handler(int index, const char *arg); +static int niquery_option_ipv4_handler(int index, const char *arg); +static int niquery_option_ipv4_flag_handler(int index, const char *arg); +static int niquery_option_subject_addr_handler(int index, const char *arg); +static int niquery_option_subject_name_handler(int index, const char *arg); +static int niquery_option_help_handler(int index, const char *arg); + +struct niquery_option niquery_options[] = { +NIQUERY_OPTION("name", 0, 0, niquery_option_name_handler), +NIQUERY_OPTION("fqdn", 0, 0, niquery_option_name_handler), +NIQUERY_OPTION("ipv6", 0, 0, niquery_option_ipv6_handler), +NIQUERY_OPTION("ipv6-all", 0, NI_IPV6ADDR_F_ALL, niquery_option_ipv6_flag_handler), +NIQUERY_OPTION("ipv6-compatible", 0, NI_IPV6ADDR_F_COMPAT, niquery_option_ipv6_flag_handler), +NIQUERY_OPTION("ipv6-linklocal", 0, NI_IPV6ADDR_F_LINKLOCAL, niquery_option_ipv6_flag_handler), +NIQUERY_OPTION("ipv6-sitelocal", 0, NI_IPV6ADDR_F_SITELOCAL, niquery_option_ipv6_flag_handler), +NIQUERY_OPTION("ipv6-global", 0, NI_IPV6ADDR_F_GLOBAL, niquery_option_ipv6_flag_handler), +NIQUERY_OPTION("ipv4", 0, 0, niquery_option_ipv4_handler), +NIQUERY_OPTION("ipv4-all", 0, NI_IPV4ADDR_F_ALL, niquery_option_ipv4_flag_handler), +NIQUERY_OPTION("subject-ipv6", 1, NI_SUBJ_IPV6, niquery_option_subject_addr_handler), +NIQUERY_OPTION("subject-ipv4", 1, NI_SUBJ_IPV4, niquery_option_subject_addr_handler), +NIQUERY_OPTION("subject-name", 1, 0, niquery_option_subject_name_handler), +NIQUERY_OPTION("subject-fqdn", 1, -1, niquery_option_subject_name_handler), +NIQUERY_OPTION("help", 0, 0, niquery_option_help_handler), + { NULL, 0, 0, 0, NULL } +}; + +static inline int niquery_is_enabled(void) +{ + return ni_query >= 0; +} + +#if PING6_NONCE_MEMORY +uint8_t *ni_nonce_ptr; +#else +struct { + struct timeval tv; + pid_t pid; +} ni_nonce_secret; +#endif + +static void niquery_init_nonce(void) +{ +#if PING6_NONCE_MEMORY + struct timeval tv; + unsigned long seed; + + seed = (unsigned long)getpid(); + if (!gettimeofday(&tv, NULL)) + seed ^= tv.tv_usec; + srand(seed); + + ni_nonce_ptr = calloc(NI_NONCE_SIZE, MAX_DUP_CHK); + if (!ni_nonce_ptr) + error(2, errno, "calloc"); + + ni_nonce_ptr[0] = ~0; +#else + gettimeofday(&ni_nonce_secret.tv, NULL); + ni_nonce_secret.pid = getpid(); +#endif +} + +#if !PING6_NONCE_MEMORY +static int niquery_nonce(uint8_t *nonce, int fill) +{ +# ifdef USE_CRYPTO + static uint8_t digest[MD5_DIGEST_LENGTH]; + static int seq = -1; + + if (fill || seq != *(uint16_t *)nonce || seq < 0) { + MD5_CTX ctxt; + + MD5_Init(&ctxt); + MD5_Update(&ctxt, &ni_nonce_secret, sizeof(ni_nonce_secret)); + MD5_Update(&ctxt, nonce, sizeof(uint16_t)); + MD5_Final(digest, &ctxt); + + seq = *(uint16_t *)nonce; + } + + if (fill) { + memcpy(nonce + sizeof(uint16_t), digest, NI_NONCE_SIZE - sizeof(uint16_t)); + return 0; + } else { + if (memcmp(nonce + sizeof(uint16_t), digest, NI_NONCE_SIZE - sizeof(uint16_t))) + return -1; + return ntohsp((uint16_t *)nonce); + } +# else + error(3, ENOSYS, "niquery_nonce() crypto disabled"); +# endif + if(nonce || fill) + return -1; + return -1; +} +#endif + +static inline void niquery_fill_nonce(uint16_t seq, uint8_t *nonce) +{ + uint16_t v = htons(seq); +#if PING6_NONCE_MEMORY + int i; + + memcpy(&ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK)], &v, sizeof(v)); + + for (i = sizeof(v); i < NI_NONCE_SIZE; i++) + ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK) + i] = 0x100 * (rand() / (RAND_MAX + 1.0)); + + memcpy(nonce, &ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK)], NI_NONCE_SIZE); +#else + memcpy(nonce, &v, sizeof(v)); + niquery_nonce(nonce, 1); +#endif +} + +static inline int niquery_check_nonce(uint8_t *nonce) +{ +#if PING6_NONCE_MEMORY + uint16_t seq = ntohsp((uint16_t *)nonce); + if (memcmp(nonce, &ni_nonce_ptr[NI_NONCE_SIZE * (seq % MAX_DUP_CHK)], NI_NONCE_SIZE)) + return -1; + return seq; +#else + return niquery_nonce(nonce, 0); +#endif +} + +static int niquery_set_qtype(int type) +{ + if(niquery_is_enabled() && ni_query != type) { + printf("Qtype conflict\n"); + return -1; + } + ni_query = type; + return 0; +} + +static int niquery_option_name_handler(int index __attribute__((__unused__)), + const char *arg __attribute__((__unused__))) +{ + if(niquery_set_qtype(NI_QTYPE_NAME) < 0) + return -1; + return 0; +} + +static int niquery_option_ipv6_handler(int index __attribute__((__unused__)), + const char *arg __attribute__((__unused__))) +{ + if(niquery_set_qtype(NI_QTYPE_IPV6ADDR) < 0) + return -1; + return 0; +} + +static int niquery_option_ipv6_flag_handler(int index, const char *arg __attribute__((__unused__))) +{ + if(niquery_set_qtype(NI_QTYPE_IPV6ADDR) < 0) + return -1; + ni_flag |= niquery_options[index].data; + return 0; +} + +static int niquery_option_ipv4_handler(int index __attribute__((__unused__)), + const char *arg __attribute__((__unused__))) +{ + if(niquery_set_qtype(NI_QTYPE_IPV4ADDR) < 0) + return -1; + return 0; +} + +static int niquery_option_ipv4_flag_handler(int index, const char *arg __attribute__((__unused__))) +{ + if(niquery_set_qtype(NI_QTYPE_IPV4ADDR) < 0) + return -1; + ni_flag |= niquery_options[index].data; + return 0; +} + +static inline int niquery_is_subject_valid(void) +{ + return ni_subject_type >= 0 && ni_subject; +} + +static int niquery_set_subject_type(int type) +{ + if(niquery_is_subject_valid() && ni_subject_type != type) { + printf("Subject type conflict\n"); + return -1; + } + ni_subject_type = type; + return 0; +} + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#define OFFSET_OF(type,elem) ((size_t)&((type *)0)->elem) + +static int niquery_option_subject_addr_handler(int index, const char *arg) +{ + struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_flags = getaddrinfo_flags }; + struct addrinfo *result, *ai; + int status; + int offset; + + if(niquery_set_subject_type(niquery_options[index].data) < 0) + return -1; + + ni_subject_type = niquery_options[index].data; + + switch (niquery_options[index].data) { + case NI_SUBJ_IPV6: + ni_subject_len = sizeof(struct in6_addr); + offset = OFFSET_OF(struct sockaddr_in6, sin6_addr); + hints.ai_family = AF_INET6; + break; + case NI_SUBJ_IPV4: + ni_subject_len = sizeof(struct in_addr); + offset = OFFSET_OF(struct sockaddr_in, sin_addr); + hints.ai_family = AF_INET; + break; + default: + /* should not happen. */ + offset = -1; + } + + status = getaddrinfo(arg, 0, &hints, &result); + if(status) { + error(0, 0, "%s: %s", arg, gai_strerror(status)); + return -1; + } + + for(ai = result; ai; ai = ai->ai_next) { + void *p = malloc(ni_subject_len); + if(!p) + continue; + memcpy(p, (uint8_t *) ai->ai_addr + offset, ni_subject_len); + free(ni_subject); + ni_subject = p; + break; + } + freeaddrinfo(result); + + return 0; +} + +#ifdef USE_IDN +# if IDN2_VERSION_NUMBER >= 0x02000000 +# define IDN2_FLAGS IDN2_NONTRANSITIONAL +# else +# define IDN2_FLAGS 0 +# endif +#endif + +#ifdef USE_CRYPTO +static int niquery_option_subject_name_handler(int index, const char *name) +{ + static char nigroup_buf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ]; + unsigned char *dnptrs[2], **dpp, **lastdnptr; + int n; + size_t i; + char *p; + char *canonname = NULL, *idn = NULL; + unsigned char *buf = NULL; + size_t namelen; + size_t buflen; + int dots, fqdn = niquery_options[index].data; + MD5_CTX ctxt; + uint8_t digest[MD5_DIGEST_LENGTH]; +#ifdef USE_IDN + int rc; +#endif + + if (niquery_set_subject_type(NI_SUBJ_NAME) < 0) + return -1; + +#ifdef USE_IDN + rc = idn2_lookup_ul(name, &idn, IDN2_FLAGS); + if (rc) + error(2, 0, "IDN encoding error: %s", idn2_strerror(rc)); +#else + idn = strdup(name); + if (!idn) + goto oomexit; +#endif + + p = strchr(idn, SCOPE_DELIMITER); + if (p) { + *p = '\0'; + if (strlen(p + 1) >= IFNAMSIZ) + error(1, 0, "too long scope name"); + } + + namelen = strlen(idn); + canonname = malloc(namelen + 1); + if (!canonname) + goto oomexit; + + dots = 0; + for (i = 0; i < namelen + 1; i++) { + canonname[i] = isupper(idn[i]) ? tolower(idn[i]) : idn[i]; + if (idn[i] == '.') + dots++; + } + + if (fqdn == 0) { + /* guess if hostname is FQDN */ + fqdn = dots ? 1 : -1; + } + + buflen = namelen + 3 + 1; /* dn_comp() requrires strlen() + 3, + plus non-fqdn indicator. */ + buf = malloc(buflen); + if (!buf) { + error(0, errno, "memory allocation failed"); + goto errexit; + } + + dpp = dnptrs; + lastdnptr = &dnptrs[ARRAY_SIZE(dnptrs)]; + + *dpp++ = (unsigned char *)buf; + *dpp++ = NULL; + + n = dn_comp(canonname, (unsigned char *)buf, buflen, dnptrs, lastdnptr); + if (n < 0) { + error(0, 0, "inappropriate subject name: %s", canonname); + goto errexit; + } else if ((size_t) n >= buflen) { + error(0, 0, "dn_comp() returned too long result"); + goto errexit; + } + + MD5_Init(&ctxt); + MD5_Update(&ctxt, buf, buf[0]); + MD5_Final(digest, &ctxt); + + sprintf(nigroup_buf, "ff02::2:%02x%02x:%02x%02x%s%s", + digest[0], digest[1], digest[2], digest[3], + p ? "%" : "", + p ? p + 1 : ""); + + if (fqdn < 0) + buf[n] = 0; + + free(ni_subject); + + ni_group = nigroup_buf; + ni_subject = buf; + ni_subject_len = n + (fqdn < 0); + ni_group = nigroup_buf; + + free(canonname); + free(idn); + + return 0; + oomexit: + error(0, errno, "memory allocation failed"); + errexit: + free(buf); + free(canonname); + free(idn); + exit(1); +} +#else +static int niquery_option_subject_name_handler(int index __attribute__((__unused__)), + const char *name __attribute__((__unused__))) +{ + error(3, ENOSYS, "niquery_option_subject_name_handler() crypto disabled"); + return -1; +} +#endif + +int niquery_option_help_handler(int index __attribute__((__unused__)), const char *arg __attribute__((__unused__))) +{ + fprintf(stderr, "ping -6 -N <nodeinfo opt>\n" + "Help:\n" + " help\n" + "Query:\n" + " name\n" + " ipv6\n" + " ipv6-all\n" + " ipv6-compatible\n" + " ipv6-global\n" + " ipv6-linklocal\n" + " ipv6-sitelocal\n" + " ipv4\n" + " ipv4-all\n" + "Subject:\n" + " subject-ipv6=addr\n" + " subject-ipv4=addr\n" + " subject-name=name\n" + " subject-fqdn=name\n" + ); + exit(2); +} + +int niquery_option_handler(const char *opt_arg) +{ + struct niquery_option *p; + int i; + int ret = -1; + for(i = 0, p = niquery_options; p->name; i++, p++) { + if(strncmp(p->name, opt_arg, p->namelen)) + continue; + if(!p->has_arg) { + if(opt_arg[p->namelen] == '\0') { + ret = p->handler(i, NULL); + if(ret >= 0) + break; + } + } else { + if(opt_arg[p->namelen] == '=') { + ret = p->handler(i, &opt_arg[p->namelen] + 1); + if(ret >= 0) + break; + } + } + } + if(!p->name) + ret = niquery_option_help_handler(0, NULL); + return ret; +} + +int ping6_run(int argc, char **argv, struct addrinfo *ai, struct socket_st *sock) +{ + static const struct addrinfo hints = { .ai_family = AF_INET6, .ai_flags = getaddrinfo_flags }; + struct addrinfo *result = NULL; + int status; + int hold, packlen; + unsigned char *packet; + char *target; + struct icmp6_filter filter; + int err; + static uint32_t scope_id = 0; + + if(niquery_is_enabled()) { + niquery_init_nonce(); + + if(!niquery_is_subject_valid()) { + ni_subject = &whereto.sin6_addr; + ni_subject_len = sizeof(whereto.sin6_addr); + ni_subject_type = NI_SUBJ_IPV6; + } + } + + if(argc > 1) { + usage(); + } else if(argc == 1) { + target = *argv; + } else { + if(ni_query < 0 && ni_subject_type != NI_SUBJ_NAME) + usage(); + target = ni_group; + } + + if(!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)); + whereto.sin6_port = htons(IPPROTO_ICMPV6); + + if(result) + freeaddrinfo(result); + + if(memchr(target, ':', strlen(target))) + options |= F_NUMERIC; + + if(IN6_IS_ADDR_UNSPECIFIED(&firsthop.sin6_addr)) { + memcpy(&firsthop.sin6_addr, &whereto.sin6_addr, 16); + firsthop.sin6_scope_id = whereto.sin6_scope_id; + /* Verify scope_id is the same as intermediate nodes */ + if(firsthop.sin6_scope_id && scope_id && firsthop.sin6_scope_id != scope_id) + error(2, 0, "scope discrepancy among the nodes"); + else if(!scope_id) + scope_id = firsthop.sin6_scope_id; + } + + hostname = target; + + if(IN6_IS_ADDR_UNSPECIFIED(&source6.sin6_addr)) { + socklen_t alen; + int probe_fd = socket(AF_INET6, SOCK_DGRAM, 0); + + if(probe_fd < 0) + error(2, errno, "socket"); + if(device) { + unsigned int iface = if_name2index(device); +#ifdef IPV6_RECVPKTINFO + struct in6_pktinfo ipi; + + memset(&ipi, 0, sizeof(ipi)); + ipi.ipi6_ifindex = iface; +#endif + + if(IN6_IS_ADDR_LINKLOCAL(&firsthop.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&firsthop.sin6_addr)) + firsthop.sin6_scope_id = iface; + enable_capability_raw(); +#ifdef IPV6_RECVPKTINFO + if( + setsockopt(probe_fd, IPPROTO_IPV6, IPV6_PKTINFO, &ipi, sizeof ipi) == -1 || + setsockopt(sock->fd, IPPROTO_IPV6, IPV6_PKTINFO, &ipi, sizeof ipi) == -1) { + perror("setsockopt(IPV6_PKTINFO)"); + exit(2); + } +#endif + if( + setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) == -1 || + setsockopt(sock->fd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) == -1) { + error(2, errno, "setsockopt(SO_BINDTODEVICE) %s", device); + } + disable_capability_raw(); + } + + if(!IN6_IS_ADDR_LINKLOCAL(&firsthop.sin6_addr) && + !IN6_IS_ADDR_MC_LINKLOCAL(&firsthop.sin6_addr)) + firsthop.sin6_family = AF_INET6; + + firsthop.sin6_port = htons(1025); + if(connect(probe_fd, (struct sockaddr*) &firsthop, sizeof(firsthop)) == -1) + error(2, errno, "connect"); + alen = sizeof source6; + if(getsockname(probe_fd, (struct sockaddr *) &source6, &alen) == -1) + error(2, errno, "getsockname"); + source6.sin6_port = 0; + close(probe_fd); + + if(device) { + struct ifaddrs *ifa0, *ifa; + + if(getifaddrs(&ifa0)) + error(2, errno, "getifaddrs"); + + for(ifa = ifa0; ifa; ifa = ifa->ifa_next) { + if(!ifa->ifa_name || !ifa->ifa_addr || + ifa->ifa_addr->sa_family != AF_INET6) + continue; + if(!strcmp(ifa->ifa_name, device) && + IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 * )ifa->ifa_addr)->sin6_addr, + &source6.sin6_addr)) + break; + } + if(!ifa) + error(0, 0, "Warning: source address might be selected on device other than: %s", device); + + freeifaddrs(ifa0); + } + } + else if(device && (IN6_IS_ADDR_LINKLOCAL(&source6.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&source6.sin6_addr))) + source6.sin6_scope_id = if_name2index(device); + + if(device) { + struct cmsghdr *cmsg; + struct in6_pktinfo *ipi; + + cmsg = (struct cmsghdr*) (cmsgbuf + cmsglen); + cmsglen += CMSG_SPACE(sizeof(*ipi)); + cmsg->cmsg_len = CMSG_LEN(sizeof(*ipi)); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + ipi = (struct in6_pktinfo*) CMSG_DATA(cmsg); + memset(ipi, 0, sizeof(*ipi)); + ipi->ipi6_ifindex = if_name2index(device); + } + + if((whereto.sin6_addr.s6_addr16[0] & htons(0xff00)) == htons(0xff00)) { + if(uid) { + if(interval < 1000) + error(2, 0, "multicast ping with too short interval: %d", interval); + if(pmtudisc >= 0 && pmtudisc != IPV6_PMTUDISC_DO) + error(2, 0, "multicast ping does not fragment"); + } + if(pmtudisc < 0) + pmtudisc = IPV6_PMTUDISC_DO; + } + + if(pmtudisc >= 0) { + if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &pmtudisc, sizeof pmtudisc) == -1) + error(2, errno, "IPV6_MTU_DISCOVER"); + } + + if((options & F_STRICTSOURCE) && + bind(sock->fd, (struct sockaddr *) &source6, sizeof source6) == -1) + error(2, errno, "bind icmp socket"); + + if((ssize_t) datalen >= (ssize_t) sizeof(struct timeval) && (ni_query < 0)) { + /* can we time transfer */ + timing = 1; + } + packlen = datalen + 8 + 4096 + 40 + 8; /* 4096 for rthdr */ + if(!(packet = (unsigned char *) malloc((unsigned int) packlen))) + error(2, errno, "memory allocation failed"); + + hold = 1; + + /* 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) * (40 + 16 + 64 + 160); + sock_setbufs(sock, hold); + +#ifdef __linux__ + if(sock->socktype == SOCK_RAW) { + int csum_offset = 2; + int sz_opt = sizeof(int); + + err = setsockopt(sock->fd, SOL_RAW, IPV6_CHECKSUM, &csum_offset, sz_opt); + if(err < 0) { + /* checksum should be enabled by default and setting this + * option might fail anyway. + */ + error(0, errno, "setsockopt(RAW_CHECKSUM) failed - try to continue"); + } +#else + { +#endif + + /* + * select icmp echo reply as icmp type to receive + */ + + ICMP6_FILTER_SETBLOCKALL(&filter); + + ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &filter); + ICMP6_FILTER_SETPASS(ICMP6_PACKET_TOO_BIG, &filter); + ICMP6_FILTER_SETPASS(ICMP6_TIME_EXCEEDED, &filter); + ICMP6_FILTER_SETPASS(ICMP6_PARAM_PROB, &filter); + + if(niquery_is_enabled()) + ICMP6_FILTER_SETPASS(ICMPV6_NI_REPLY, &filter); + else + ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter); + + err = setsockopt(sock->fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof filter); + + if(err < 0) + error(2, errno, "setsockopt(ICMP6_FILTER)"); + } + + if(options & F_NOLOOP) { + int loop = 0; + if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof loop) == -1) + error(2, errno, "can't disable multicast loopback"); + } + if(options & F_TTL) { + if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof ttl) == -1) + error(2, errno, "can't set multicast hop limit"); + if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof ttl) == -1) + error(2, errno, "can't set unicast hop limit"); + } + + const int on = 1; + if( + #ifdef IPV6_RECVHOPLIMIT + setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof on) == -1 && + setsockopt(sock->fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof on) == -1 + #else + setsockopt(sock->fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof on) == -1 +#endif + ) + error(2, errno, "can't receive hop limit"); + + if(options & F_TCLASS) { +#ifdef IPV6_TCLASS + if(setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof tclass) == -1) + error(2, errno, "setsockopt(IPV6_TCLASS)"); +#else + error(0, 0, "traffic class is not supported"); +#endif + } + + if(options & F_FLOWINFO) { +#ifdef IPV6_FLOWLABEL_MGR + char freq_buf[CMSG_ALIGN(sizeof(struct in6_flowlabel_req)) + cmsglen]; + struct in6_flowlabel_req *freq = (struct in6_flowlabel_req *)freq_buf; + int freq_len = sizeof(*freq); + memset(freq, 0, sizeof(*freq)); + freq->flr_label = htonl(flowlabel & IPV6_FLOWINFO_FLOWLABEL); + freq->flr_action = IPV6_FL_A_GET; + freq->flr_flags = IPV6_FL_F_CREATE; + freq->flr_share = IPV6_FL_S_EXCL; + memcpy(&freq->flr_dst, &whereto.sin6_addr, 16); + if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, freq, freq_len) == -1) + error(2, errno, "can't set flowlabel"); + flowlabel = freq->flr_label; +#else + error(2, 0, "flow labels are not supported"); +#endif + +#ifdef IPV6_FLOWINFO_SEND + whereto.sin6_flowinfo = flowlabel; + if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_FLOWINFO_SEND, &on, sizeof on) == -1) + error(2, errno, "can't send flowinfo"); +#else + error(2, 0, "flowinfo is not supported"); +#endif + } + + printf("PING %s(%s) ", hostname, pr_addr(&whereto, sizeof whereto)); + if(flowlabel) + printf(", flow 0x%05x, ", (unsigned) ntohl(flowlabel)); + if(device || (options & F_STRICTSOURCE)) { + int saved_options = options; + + options |= F_NUMERIC; + printf("from %s %s: ", pr_addr(&source6, sizeof source6), device ? device : ""); + options = saved_options; + } + printf("%d data bytes\n", datalen); + + setup(sock); + + drop_capabilities(); + + main_loop(&ping6_func_set, sock, packet, packlen); + return 0; +} + +int ping6_receive_error_msg(socket_st *sock) +{ + ssize_t res; + char cbuf[512]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + struct sock_extended_err *e; + struct icmp6_hdr icmph; + struct sockaddr_in6 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(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if(cmsg->cmsg_level == IPPROTO_IPV6) { + if(cmsg->cmsg_type == IPV6_RECVERR) + e = (struct sock_extended_err *) CMSG_DATA(cmsg); + } + } + 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, e->ee_errno, "local error"); + else + error(0, 0, "local error: message too long, mtu: %u", e->ee_info); + nerrors++; + } else if(e->ee_origin == SO_EE_ORIGIN_ICMP6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*) (e + 1); + + if((size_t) res < sizeof(icmph) || + memcmp(&target.sin6_addr, &whereto.sin6_addr, 16) || + icmph.icmp6_type != ICMP6_ECHO_REQUEST || + !is_ours(sock, icmph.icmp6_id)) { + /* Not our error, not an error at all. Clear. */ + saved_errno = 0; + goto out; + } + + 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(sin6, sizeof *sin6), ntohs(icmph.icmp6_seq)); + pr_icmph(e->ee_type, e->ee_code, e->ee_info); + putchar('\n'); + 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 build_echo(uint8_t *_icmph, unsigned packet_size __attribute__((__unused__))) +{ + struct icmp6_hdr *icmph; + int cc; + + icmph = (struct icmp6_hdr *) _icmph; + icmph->icmp6_type = ICMP6_ECHO_REQUEST; + icmph->icmp6_code = 0; + icmph->icmp6_cksum = 0; + icmph->icmp6_seq= htons(ntransmitted+1); + icmph->icmp6_id= ident; + + if(timing) + gettimeofday((struct timeval *) &_icmph[8], + (struct timezone *) NULL); + + cc = datalen + 8; /* skips ICMP portion */ + + return cc; +} + +int build_niquery(uint8_t *_nih, unsigned packet_size __attribute__((__unused__))) +{ + struct ni_hdr *nih; + int cc; + + nih = (struct ni_hdr *) _nih; + nih->ni_cksum = 0; + + nih->ni_type = ICMPV6_NI_QUERY; + cc = sizeof(*nih); + datalen = 0; + + niquery_fill_nonce(ntransmitted + 1, nih->ni_nonce); + nih->ni_code = ni_subject_type; + nih->ni_qtype= htons(ni_query); + nih->ni_flags= ni_flag; + memcpy(nih + 1, ni_subject, ni_subject_len); + cc += ni_subject_len; + + return cc; +} + +int ping6_send_probe(socket_st *sock, void *packet, unsigned packet_size) +{ + int len, cc; + + rcvd_clear(ntransmitted + 1); + + if(niquery_is_enabled()) + len = build_niquery(packet, packet_size); + else + len = build_echo(packet, packet_size); + + if(cmsglen == 0) { + cc = sendto(sock->fd, (char *) packet, len, confirm, + (struct sockaddr *) &whereto, + sizeof(struct sockaddr_in6)); + } else { + struct msghdr mhdr; + struct iovec iov; + + iov.iov_len = len; + iov.iov_base = packet; + + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.msg_name = &whereto; + mhdr.msg_namelen = sizeof(struct sockaddr_in6); + mhdr.msg_iov = &iov; + mhdr.msg_iovlen = 1; + mhdr.msg_control = cmsgbuf; + mhdr.msg_controllen = cmsglen; + + cc = sendmsg(sock->fd, &mhdr, confirm); + } + confirm = 0; + + return (cc == len ? 0 : cc); +} + +void pr_echo_reply(uint8_t *_icmph, int cc __attribute__((__unused__))) +{ + struct icmp6_hdr *icmph = (struct icmp6_hdr *) _icmph; + log_printf(" icmp_seq=%u", ntohs(icmph->icmp6_seq)); +} + +static void putchar_safe(char c) +{ + if(isprint(c)) + putchar(c); + else + printf("\\%03o", c); +} + +static +void pr_niquery_reply_name(struct ni_hdr *nih, int len) +{ + uint8_t *h = (uint8_t *) (nih + 1); + uint8_t *p = h + 4; + uint8_t *end = (uint8_t *) nih + len; + int continued = 0; + char buf[1024]; + int ret; + + len -= sizeof(struct ni_hdr) + 4; + + if(len < 0) { + printf(" parse error (too short)"); + return; + } + while(p < end) { + int fqdn = 1; + size_t i; + + memset(buf, 0xff, sizeof(buf)); + + if(continued) + putchar(','); + + ret = dn_expand(h, end, p, buf, sizeof(buf)); + if(ret < 0) { + printf(" parse error (truncated)"); + break; + } + if(p + ret < end && *(p + ret) == '\0') + fqdn = 0; + + putchar(' '); + for(i = 0; i < strlen(buf); i++) + putchar_safe(buf[i]); + if(fqdn) + putchar('.'); + + p += ret + !fqdn; + + continued = 1; + } +} + +static +void pr_niquery_reply_addr(struct ni_hdr *nih, int len) +{ + uint8_t *h = (uint8_t *) (nih + 1); + uint8_t *p; + uint8_t *end = (uint8_t *) nih + len; + int af; + int aflen; + int continued = 0; + int truncated; + char buf[1024]; + + switch (ntohs(nih->ni_qtype)) { + case NI_QTYPE_IPV4ADDR: + af = AF_INET; + aflen = sizeof(struct in_addr); + truncated = nih->ni_flags & NI_IPV6ADDR_F_TRUNCATE; + break; + case NI_QTYPE_IPV6ADDR: + af = AF_INET6; + aflen = sizeof(struct in6_addr); + truncated = nih->ni_flags & NI_IPV4ADDR_F_TRUNCATE; + break; + default: + /* should not happen */ + af = aflen = truncated = 0; + } + p = h; + if(len < 0) { + printf(" parse error (too short)"); + return; + } + + while(p < end) { + if(continued) + putchar(','); + + if(p + sizeof(uint32_t) + aflen > end) { + printf(" parse error (truncated)"); + break; + } + if(!inet_ntop(af, p + sizeof(uint32_t), buf, sizeof(buf))) + printf(" unexpeced error in inet_ntop(%s)", + strerror(errno)); + else + printf(" %s", buf); + p += sizeof(uint32_t) + aflen; + + continued = 1; + } + if(truncated) + printf(" (truncated)"); +} + +static +void pr_niquery_reply(uint8_t *_nih, int len) +{ + struct ni_hdr *nih = (struct ni_hdr *) _nih; + + switch (nih->ni_code) { + case NI_SUCCESS: + switch (ntohs(nih->ni_qtype)) { + case NI_QTYPE_NAME: + pr_niquery_reply_name(nih, len); + break; + case NI_QTYPE_IPV4ADDR: + case NI_QTYPE_IPV6ADDR: + pr_niquery_reply_addr(nih, len); + break; + default: + printf(" unknown qtype(0x%02x)", ntohs(nih->ni_qtype)); + } + break; + case NI_REFUSED: + printf(" refused"); + break; + case NI_UNKNOWN: + printf(" unknown"); + break; + default: + printf(" unknown code(%02x)", ntohs(nih->ni_code)); + } + printf("; seq=%u;", ntohsp((uint16_t*) nih->ni_nonce)); +} + +/* + * 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!). + */ +int +ping6_parse_reply(socket_st *sock, struct msghdr *msg, int cc, void *addr, struct timeval *tv) +{ + struct sockaddr_in6 *from = addr; + uint8_t *buf = msg->msg_iov->iov_base; + struct cmsghdr *c; + struct icmp6_hdr *icmph; + int hops = -1; + + for(c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) { + if(c->cmsg_level != IPPROTO_IPV6) + continue; + switch (c->cmsg_type) { + case IPV6_HOPLIMIT: + #ifdef IPV6_2292HOPLIMIT + case IPV6_2292HOPLIMIT: + #endif + if(c->cmsg_len < CMSG_LEN(sizeof(int))) + continue; + memcpy(&hops, CMSG_DATA(c), sizeof(hops)); + } + } + + /* Now the ICMP part */ + + icmph = (struct icmp6_hdr *) buf; + if(cc < 8) { + if(options & F_VERBOSE) + error(0, 0, "packet too short: %d bytes", cc); + return 1; + } + + if(icmph->icmp6_type == ICMP6_ECHO_REPLY) { + if(!is_ours(sock, icmph->icmp6_id)) + return 1; + if (!contains_pattern_in_payload((uint8_t*)(icmph+1))) + return 1; /* 'Twas really not our ECHO */ + if (gather_statistics((uint8_t*)icmph, sizeof(*icmph), cc, + ntohs(icmph->icmp6_seq), + hops, 0, tv, pr_addr(from, sizeof *from), + pr_echo_reply)) { + fflush(stdout); + return 0; + } + } else if (icmph->icmp6_type == ICMPV6_NI_REPLY) { + struct ni_hdr *nih = (struct ni_hdr *)icmph; + int seq = niquery_check_nonce(nih->ni_nonce); + if (seq < 0) + return 1; + if (gather_statistics((uint8_t*)icmph, sizeof(*icmph), cc, + seq, + hops, 0, tv, pr_addr(from, sizeof *from), + pr_niquery_reply)) + return 0; + } else { + int nexthdr; + struct ip6_hdr *iph1 = (struct ip6_hdr*)(icmph+1); + struct icmp6_hdr *icmph1 = (struct icmp6_hdr *)(iph1+1); + + /* We must not ever fall here. All the messages but + * echo reply are blocked by filter and error are + * received with IPV6_RECVERR. Ugly code is preserved + * however, just to remember what crap we avoided + * using RECVRERR. :-) + */ + + if (cc < (int) (8 + sizeof(struct ip6_hdr) + 8)) + return 1; + + if (memcmp(&iph1->ip6_dst, &whereto.sin6_addr, 16)) + return 1; + + nexthdr = iph1->ip6_nxt; + + if (nexthdr == 44) { + nexthdr = *(uint8_t*)icmph1; + icmph1++; + } + if (nexthdr == IPPROTO_ICMPV6) { + if (icmph1->icmp6_type != ICMP6_ECHO_REQUEST || + !is_ours(sock, icmph1->icmp6_id)) + return 1; + acknowledge(ntohs(icmph1->icmp6_seq)); + nerrors++; + if (options & F_FLOOD) { + write_stdout("\bE", 2); + return 0; + } + print_timestamp(); + printf("From %s: icmp_seq=%u ", pr_addr(from, sizeof *from), ntohs(icmph1->icmp6_seq)); + } else { + /* We've got something other than an ECHOREPLY */ + if (!(options & F_VERBOSE) || uid) + return 1; + print_timestamp(); + printf("From %s: ", pr_addr(from, sizeof *from)); + } + pr_icmph(icmph->icmp6_type, icmph->icmp6_code, ntohl(icmph->icmp6_mtu)); + } + + if(options & F_AUDIBLE) { + putchar('\a'); + if(options & F_FLOOD) + fflush(stdout); + } + if(!(options & F_FLOOD)) { + putchar('\n'); + fflush(stdout); + } + return 0; +} + +int pr_icmph(uint8_t type, uint8_t code, uint32_t info) +{ + switch (type) { + case ICMP6_DST_UNREACH: + printf("Destination unreachable: "); + switch (code) { + case ICMP6_DST_UNREACH_NOROUTE: + printf("No route"); + break; + case ICMP6_DST_UNREACH_ADMIN: + printf("Administratively prohibited"); + break; + case ICMP6_DST_UNREACH_BEYONDSCOPE: + printf("Beyond scope of source address"); + break; + case ICMP6_DST_UNREACH_ADDR: + printf("Address unreachable"); + break; + case ICMP6_DST_UNREACH_NOPORT: + printf("Port unreachable"); + break; + default: + printf("Unknown code %d", code); + break; + } + break; + case ICMP6_PACKET_TOO_BIG: + printf("Packet too big: mtu=%u", info); + if(code) + printf(", code=%d", code); + break; + case ICMP6_TIME_EXCEEDED: + printf("Time exceeded: "); + if(code == ICMP6_TIME_EXCEED_TRANSIT) + printf("Hop limit"); + else if(code == ICMP6_TIME_EXCEED_REASSEMBLY) + printf("Defragmentation failure"); + else + printf("code %d", code); + break; + case ICMP6_PARAM_PROB: + printf("Parameter problem: "); + if(code == ICMP6_PARAMPROB_HEADER) + printf("Wrong header field "); + else if(code == ICMP6_PARAMPROB_NEXTHEADER) + printf("Unknown header "); + else if(code == ICMP6_PARAMPROB_OPTION) + printf("Unknown option "); + else + printf("code %d ", code); + printf("at %u", info); + break; + case ICMP6_ECHO_REQUEST: + printf("Echo request"); + break; + case ICMP6_ECHO_REPLY: + printf("Echo reply"); + break; + case MLD_LISTENER_QUERY: + printf("MLD Query"); + break; + case MLD_LISTENER_REPORT: + printf("MLD Report"); + break; + case MLD_LISTENER_REDUCTION: + printf("MLD Reduction"); + break; + default: + printf("unknown icmp type: %u", type); + + } + return 0; +} + +void ping6_install_filter(socket_st *sock) +{ + static int once; + static struct sock_filter insns[] = { + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 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_ABS, 0), /* Load icmp type */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ICMP6_ECHO_REPLY, 1, 0), /* Echo? */ + BPF_STMT(BPF_RET|BPF_K, ~0U), /* No. It passes. This must not happen. */ + 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[1] = (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_common.c b/iputils/ping_common.c new file mode 100755 index 0000000000000000000000000000000000000000..bf58d31faeb01991eff5bcf3f102e0e12f2d1f5a --- /dev/null +++ b/iputils/ping_common.c @@ -0,0 +1,1018 @@ +/* + * 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. */ + } + } + // here present exit() from app + 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(); + log_printf("%d bytes from %s:", cc, from); + + if(pr_reply) + pr_reply(icmph, cc); + + if(hops >= 0) + log_printf(" ttl=%d", hops); + + if(cc < datalen + 8) { + log_printf(" (truncated)\n"); + return 1; + } + if(timing) { + if(triptime >= 100000) + log_printf(" time=%ld ms", (triptime + 500) / 1000); + else if(triptime >= 10000) + log_printf(" time=%ld.%01ld ms", (triptime + 50) / 1000, + ((triptime + 50) % 1000) / 100); + else if(triptime >= 1000) + log_printf(" time=%ld.%02ld ms", (triptime + 5) / 1000, + ((triptime + 5) % 1000) / 10); + else + log_printf(" time=%ld.%03ld ms", triptime / 1000, + triptime % 1000); + } + if(dupflag) + log_printf(" (DUP!)"); + if(csfailed) + log_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) { + log_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)) + log_printf("\n#%d\t", i); + log_printf("%x ", *cp); + } + break; + } + } + } + return 0; +} +#ifdef PING_DBG +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; +} +#endif + +/* + * finish -- + * Print out statistics, and give up. + */ +void finish(void) +{ + struct timeval tv = cur_time; +#ifdef PING_DBG + char *comma = ""; +#endif + + tvsub(&tv, &start_time); +#ifdef PING_DBG + putchar('\n'); + fflush(stdout); + printf("--- %s ping statistics ---\n", hostname); + printf("%ld packets transmitted, ", ntransmitted); + printf("%ld received", nreceived); + if (nrepeats) + log_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'); +#endif + + if(nreceived && timing) { + + tsum /= nreceived + nrepeats; + tsum2 /= nreceived + nrepeats; +#ifdef PING_DBG + long tmdev; + 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 = ", "; +#endif + } +#ifdef PING_DBG + 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)); +#endif +} + +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; +} diff --git a/iputils/tracepath.c b/iputils/tracepath.c new file mode 100755 index 0000000000000000000000000000000000000000..7eda079a53be11edd3317acf6f2c9aee5c3ecf67 --- /dev/null +++ b/iputils/tracepath.c @@ -0,0 +1,782 @@ +/* + * tracepath.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ + +#include <arpa/inet.h> +#include <errno.h> +#include <limits.h> +#include <linux/errqueue.h> +#include <linux/icmp.h> +#include <linux/icmpv6.h> +#include <linux/types.h> +#include <netdb.h> +#include <netinet/in.h> +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <unistd.h> +#include <glib.h> + +#include "iputils.h" + +#ifdef USE_IDN +# include <locale.h> +# ifndef AI_IDN +# define AI_IDN 0x0040 +# endif +# ifndef NI_IDN +# define NI_IDN 32 +# endif +# define getnameinfo_flags NI_IDN +#else +# define getnameinfo_flags 0 +#endif + +#ifndef SOL_IPV6 +# define SOL_IPV6 IPPROTO_IPV6 +#endif + +#ifndef IP_PMTUDISC_DO +# define IP_PMTUDISC_DO 3 +#endif +#ifndef IPV6_PMTUDISC_DO +# define IPV6_PMTUDISC_DO 3 +#endif + +enum { + MAX_PROBES = 10, + + MAX_HOPS_DEFAULT = 30, + MAX_HOPS_LIMIT = 255, + + HOST_COLUMN_SIZE = 52, + + HIS_ARRAY_SIZE = 64, + + DEFAULT_OVERHEAD_IPV4 = 28, + DEFAULT_OVERHEAD_IPV6 = 48, + + DEFAULT_MTU_IPV4 = 65535, + DEFAULT_MTU_IPV6 = 128000, + + DEFAULT_BASEPORT = 44444, + + ANCILLARY_DATA_LEN = 512, +}; + +struct hhistory { + int hops; + struct timeval sendtime; + struct timeval deltatime; + int hop; + char *host_namea; + char *host_nameb; +}; + +struct probehdr { + uint32_t ttl; + struct timeval tv; +}; + +struct run_state { + struct hhistory his[HIS_ARRAY_SIZE]; + int hisptr; + struct sockaddr_storage target; + struct addrinfo *ai; + int socket_fd; + socklen_t targetlen; + uint16_t base_port; + uint8_t ttl; + int max_hops; + int overhead; + int mtu; + void *pktbuf; + int hops_to; + int hops_from; + unsigned int + no_resolve :1, + show_both :1, + mapped :1; +}; + +/* + * All includes, definitions, struct declarations, and global variables are + * above. After this comment all you can find is functions. + */ + +static void data_wait(struct run_state const * const ctl) +{ + fd_set fds; + struct timeval tv = { + .tv_sec = 1, + .tv_usec = 0 + }; + + FD_ZERO(&fds); + FD_SET(ctl->socket_fd, &fds); + select(ctl->socket_fd + 1, &fds, NULL, NULL, &tv); +} + +static void print_host(struct run_state const * const ctl, char const * const a, + char const * const b) +{ + int plen; + + plen = log_printf("%s", a); + if(ctl->show_both) + plen += log_printf(" (%s)", b); + if(plen >= HOST_COLUMN_SIZE) + plen = HOST_COLUMN_SIZE - 1; + log_printf("%*s", HOST_COLUMN_SIZE - plen, ""); +} + +static int recverr(struct run_state * const ctl) +{ + ssize_t recv_size; + struct probehdr rcvbuf; + char cbuf[ANCILLARY_DATA_LEN]; + struct cmsghdr *cmsg; + struct sock_extended_err *e; + struct sockaddr_storage addr; + struct timeval tv; + struct timeval *rettv; + struct timeval *deltatv; + int slot = 0; + int rethops; + int sndhops; + int progress = -1; + int broken_router; + char hnamebuf[NI_MAXHOST] = ""; + struct iovec iov = { + .iov_base = &rcvbuf, + .iov_len = sizeof(rcvbuf) + }; + struct msghdr msg; + const struct msghdr reset = { + .msg_name = (uint8_t *) &addr, + .msg_namelen = sizeof(addr), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf), + 0 + }; + + restart: + memset(&rcvbuf, -1, sizeof(rcvbuf)); + msg = reset; + + gettimeofday(&tv, NULL); + recv_size = recvmsg(ctl->socket_fd, &msg, MSG_ERRQUEUE); + if(recv_size < 0) { + if(errno == EAGAIN) + return progress; + goto restart; + } + + progress = ctl->mtu; + + rethops = -1; + sndhops = -1; + e = NULL; + rettv = NULL; + deltatv = NULL; + broken_router = 0; + + slot = -ctl->base_port; + switch (ctl->ai->ai_family) { + case AF_INET6: + slot += ntohs(((struct sockaddr_in6 *) &addr)->sin6_port); + break; + case AF_INET: + slot += ntohs(((struct sockaddr_in *) &addr)->sin_port); + break; + } + + if(slot >= 0 && slot < (HIS_ARRAY_SIZE - 1) && ctl->his[slot].hops) { + sndhops = ctl->his[slot].hops; + rettv = &ctl->his[slot].sendtime; + deltatv = &ctl->his[slot].deltatime; + ctl->his[slot].hop = sndhops; + ctl->his[slot].hops = 0; + } + if(recv_size == sizeof(rcvbuf)) { + if(rcvbuf.ttl == 0 || rcvbuf.tv.tv_sec == 0) + broken_router = 1; + else { + sndhops = rcvbuf.ttl; + rettv = &rcvbuf.tv; + } + } + + for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + switch (cmsg->cmsg_level) { + case SOL_IPV6: + switch (cmsg->cmsg_type) { + case IPV6_RECVERR: + e = (struct sock_extended_err *) CMSG_DATA(cmsg); + break; + case IPV6_HOPLIMIT: + #ifdef IPV6_2292HOPLIMIT + case IPV6_2292HOPLIMIT: + #endif + memcpy(&rethops, CMSG_DATA(cmsg), sizeof(rethops)); + break; + default: + log_printf("cmsg6:%d\n ", cmsg->cmsg_type); + } + break; + case SOL_IP: + switch (cmsg->cmsg_type) { + case IP_RECVERR: + e = (struct sock_extended_err *) CMSG_DATA(cmsg); + break; + case IP_TTL: + rethops = *(uint8_t *) CMSG_DATA(cmsg); + break; + default: + log_printf("cmsg4:%d\n ", cmsg->cmsg_type); + } + } + } + if(e == NULL) { + log_printf("no info\n"); + return 0; + } + if(e->ee_origin == SO_EE_ORIGIN_LOCAL) + log_printf("%2d?: %-32s ", ctl->ttl, "[LOCALHOST]"); + else if(e->ee_origin == SO_EE_ORIGIN_ICMP6 || + e->ee_origin == SO_EE_ORIGIN_ICMP) { + char abuf[NI_MAXHOST]; + struct sockaddr *sa = (struct sockaddr *) (e + 1); + socklen_t salen; + + if(sndhops > 0) + log_printf("%2d: ", sndhops); + else + log_printf("%2d?: ", ctl->ttl); + + switch (sa->sa_family) { + case AF_INET6: + salen = sizeof(struct sockaddr_in6); + break; + case AF_INET: + salen = sizeof(struct sockaddr_in); + break; + default: + salen = 0; + } + + if(ctl->no_resolve || ctl->show_both) + { + if(getnameinfo(sa, salen, abuf, sizeof(abuf), NULL, 0, + NI_NUMERICHOST)) + strcpy(abuf, "???"); + ctl->his[slot].host_namea = strdup(abuf); + } else + abuf[0] = 0; + + if(!ctl->no_resolve || ctl->show_both) + { + fflush(stdout); + if(getnameinfo(sa, salen, hnamebuf, sizeof hnamebuf, NULL, 0, + getnameinfo_flags)) + strcpy(hnamebuf, "???"); + ctl->his[slot].host_nameb = strdup(hnamebuf); + } else + hnamebuf[0] = 0; + + if(ctl->no_resolve) { + print_host(ctl, abuf, hnamebuf); + } + else { + print_host(ctl, hnamebuf, abuf); + } + } + + if(rettv) { + struct timeval res; + + timersub(&tv, rettv, &res); + if(deltatv) + memcpy(deltatv, &res, sizeof(struct timeval)); + log_printf("%3ld.%03ldms ", res.tv_sec * 1000 + res.tv_usec / 1000, res.tv_usec % 1000); + if(broken_router) + log_printf("(This broken router returned corrupted payload) "); + } + + if(rethops <= 64) + rethops = 65 - rethops; + else if(rethops <= 128) + rethops = 129 - rethops; + else + rethops = 256 - rethops; + + //log_printf("ERROR=%d ", e->ee_errno); + switch (e->ee_errno) { + case ETIMEDOUT: + log_printf("\n"); + break; + case EMSGSIZE: + log_printf("pmtu %d\n", e->ee_info); + ctl->mtu = e->ee_info; + progress = ctl->mtu; + break; + case ECONNREFUSED: + log_printf("reached\n"); + ctl->hops_to = sndhops < 0 ? ctl->ttl : sndhops; + ctl->hops_from = rethops; + return 0; + case EPROTO: + log_printf("!P\n"); + return 0; + case EHOSTUNREACH: + if((e->ee_origin == SO_EE_ORIGIN_ICMP && + e->ee_type == ICMP_TIME_EXCEEDED && + e->ee_code == ICMP_EXC_TTL) || + (e->ee_origin == SO_EE_ORIGIN_ICMP6 && + e->ee_type == ICMPV6_TIME_EXCEED && + e->ee_code == ICMPV6_EXC_HOPLIMIT)) { + if(rethops >= 0) { + if(sndhops >= 0 && rethops != sndhops) + log_printf("asymm %2d ", rethops); + else if(sndhops < 0 && rethops != ctl->ttl) + log_printf("asymm %2d ", rethops); + } + //log_printf("hops=->%2d, <-%2d ", sndhops, rethops); + log_printf("\n"); + break; + } + printf("!H\n"); + return 0; + case ENETUNREACH: + log_printf("!N\n"); + return 0; + case EACCES: + log_printf("!A\n"); + return 0; + default: + printf("\n"); + errno = e->ee_errno; + perror("NET ERROR"); + return 0; + } + goto restart; + return 0; +} + +static int probe_ttl(struct run_state * const ctl) +{ + int i; + struct probehdr *hdr = ctl->pktbuf; + + memset(ctl->pktbuf, 0, ctl->mtu); + restart: + for(i = 0; i < MAX_PROBES; i++) { + int res; + + hdr->ttl = ctl->ttl; + switch (ctl->ai->ai_family) { + case AF_INET6: + ((struct sockaddr_in6 *) &ctl->target)->sin6_port = + htons(ctl->base_port + ctl->hisptr); + break; + case AF_INET: + ((struct sockaddr_in *) &ctl->target)->sin_port = + htons(ctl->base_port + ctl->hisptr); + break; + } + gettimeofday(&hdr->tv, NULL); + ctl->his[ctl->hisptr].hops = ctl->ttl; + ctl->his[ctl->hisptr].sendtime = hdr->tv; + if(sendto(ctl->socket_fd, ctl->pktbuf, ctl->mtu - ctl->overhead, 0, + (struct sockaddr *) &ctl->target, ctl->targetlen) > 0) + break; + res = recverr(ctl); + ctl->his[ctl->hisptr].hops = 0; + if(res == 0) + return 0; + if(res > 0) + goto restart; + } + ctl->hisptr = (ctl->hisptr + 1) & (HIS_ARRAY_SIZE - 1); + + if(i < MAX_PROBES) { + data_wait(ctl); + if(recv(ctl->socket_fd, ctl->pktbuf, ctl->mtu, MSG_DONTWAIT) > 0) { + log_printf("%2d?: reply received 8)\n", ctl->ttl); + return 0; + } + return recverr(ctl); + } + + log_printf("%2d: send failed\n", ctl->ttl); + return 0; +} + +static void usage(void) +{ + fprintf(stderr, + "\nUsage\n" + " tracepath [options] <destination>\n" + "\nOptions:\n" + " -4 use IPv4\n" + " -6 use IPv6\n" + " -b print both name and ip\n" + " -l <length> use packet <length>\n" + " -m <hops> use maximum <hops>\n" + " -n no dns name resolution\n" + " -p <port> use destination <port>\n" + " -V print version and exit\n" + " <destination> dns name or ip address\n" + "\nFor more details see tracepath(8).\n"); + exit(-1); +} + +int tracepath_main(int argc, char **argv, struct run_state *ctl) +{ + int ret = -1; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, +#ifdef USE_IDN + .ai_flags = AI_IDN | AI_CANONNAME, +#endif + }; + struct addrinfo *result; + int ch; + int status; + int on; + char *p; + char pbuf[NI_MAXSERV]; + +#ifdef USE_IDN + setlocale(LC_ALL, ""); +#endif + + /* Support being called using `tracepath4` or `tracepath6` 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; + + while((ch = getopt(argc, argv, "46nbh?l:m:p:V")) != EOF) { + switch (ch) { + case '4': + if(hints.ai_family == AF_INET6) { + fprintf(stderr, + "tracepath: Only one -4 or -6 option may be specified\n"); + return -1; //exit(2); + } + hints.ai_family = AF_INET; + break; + case '6': + if(hints.ai_family == AF_INET) { + fprintf(stderr, + "tracepath: Only one -4 or -6 option may be specified\n"); + return -1; //exit(2); + } + hints.ai_family = AF_INET6; + break; + case 'n': + ctl->no_resolve = 1; + break; + case 'b': + ctl->show_both = 1; + break; + case 'l': + if((ctl->mtu = atoi(optarg)) <= ctl->overhead) { + fprintf(stderr, + "Error: pktlen must be > %d and <= %d.\n", + ctl->overhead, INT_MAX); + return -1; //exit(1); + } + break; + case 'm': + ctl->max_hops = atoi(optarg); + if(ctl->max_hops < 0 || ctl->max_hops > MAX_HOPS_LIMIT) { + fprintf(stderr, + "Error: max hops must be 0 .. %d (inclusive).\n", + MAX_HOPS_LIMIT); + return -1; //exit(1); + } + break; + case 'p': + ctl->base_port = atoi(optarg); + break; + case 'V': + printf(IPUTILS_VERSION("tracepath")); + return 0; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if(argc != 1) + usage(); + + /* Backward compatibility */ + if(!ctl->base_port) { + p = strchr(argv[0], '/'); + if(p) { + *p = 0; + ctl->base_port = atoi(p + 1); + } else + ctl->base_port = DEFAULT_BASEPORT; + } + sprintf(pbuf, "%u", ctl->base_port); + + status = getaddrinfo(argv[0], pbuf, &hints, &result); + if(status) { + fprintf(stderr, "tracepath: %s: %s\n", argv[0], + gai_strerror(status)); + exit(1); + } + + for(ctl->ai = result; ctl->ai; ctl->ai = ctl->ai->ai_next) { + if(ctl->ai->ai_family != AF_INET6 && ctl->ai->ai_family != AF_INET) + continue; + ctl->socket_fd = socket(ctl->ai->ai_family, ctl->ai->ai_socktype, ctl->ai->ai_protocol); + if(ctl->socket_fd < 0) + continue; + memcpy(&ctl->target, ctl->ai->ai_addr, ctl->ai->ai_addrlen); + ctl->targetlen = ctl->ai->ai_addrlen; + break; + } + if(ctl->socket_fd < 0) { + perror("socket/connect"); + exit(1); + } + + switch (ctl->ai->ai_family) { + case AF_INET6: + ctl->overhead = DEFAULT_OVERHEAD_IPV6; + if(!ctl->mtu) + ctl->mtu = DEFAULT_MTU_IPV6; + if(ctl->mtu <= ctl->overhead) + goto pktlen_error; + + on = IPV6_PMTUDISC_DO; + if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_MTU_DISCOVER, &on, sizeof(on)) && + (on = IPV6_PMTUDISC_DO, setsockopt(ctl->socket_fd, SOL_IPV6, + IPV6_MTU_DISCOVER, &on, sizeof(on)))) { + perror("IPV6_MTU_DISCOVER"); + exit(1); + } + on = 1; + if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_RECVERR, &on, sizeof(on))) { + perror("IPV6_RECVERR"); + exit(1); + } + if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)) + #ifdef IPV6_RECVHOPLIMIT + && setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on)) + #endif + ) { + perror("IPV6_HOPLIMIT"); + exit(1); + } + if(!IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6 * )&ctl->target)->sin6_addr))) + break; + ctl->mapped = 1; + /*FALLTHROUGH*/ + case AF_INET: + ctl->overhead = DEFAULT_OVERHEAD_IPV4; + if(!ctl->mtu) + ctl->mtu = DEFAULT_MTU_IPV4; + if(ctl->mtu <= ctl->overhead) + goto pktlen_error; + + on = IP_PMTUDISC_DO; + if(setsockopt(ctl->socket_fd, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on))) { + perror("IP_MTU_DISCOVER"); + exit(1); + } + on = 1; + if(setsockopt(ctl->socket_fd, SOL_IP, IP_RECVERR, &on, sizeof(on))) { + perror("IP_RECVERR"); + exit(1); + } + if(setsockopt(ctl->socket_fd, SOL_IP, IP_RECVTTL, &on, sizeof(on))) { + perror("IP_RECVTTL"); + exit(1); + } + } + + ctl->pktbuf = malloc(ctl->mtu); + if(!ctl->pktbuf) { + perror("malloc"); + return -1; //exit(1); + } + +// struct sockaddr sa; +// int salen6 = sizeof(struct sockaddr_in6); +// int salen = sizeof(struct sockaddr_in); + char target_ip[NI_MAXHOST]; + char *target_name = argv[0]; + memset(&target_ip, 0, sizeof(target_ip)); + getnameinfo((struct sockaddr *) &ctl->target, ctl->targetlen, target_ip, sizeof(target_ip), NULL, 0, + NI_NUMERICHOST); + log_printf("START target_name=%s target_ip=%s\n", target_name, target_ip); + + for(ctl->ttl = 1; ctl->ttl <= ctl->max_hops; ctl->ttl++) { + int res; + int i; + + on = ctl->ttl; + switch (ctl->ai->ai_family) { + case AF_INET6: + if(setsockopt(ctl->socket_fd, SOL_IPV6, IPV6_UNICAST_HOPS, &on, sizeof(on))) { + perror("IPV6_UNICAST_HOPS"); + return -1; //exit(1); + } + if(!ctl->mapped) + break; + /*FALLTHROUGH*/ + case AF_INET: + if(setsockopt(ctl->socket_fd, SOL_IP, IP_TTL, &on, sizeof(on))) { + perror("IP_TTL"); + return -1; //exit(1); + } + } + + restart: + for(i = 0; i < 2; i++) { + int old_mtu; + + old_mtu = ctl->mtu; + res = probe_ttl(ctl); + if(ctl->mtu != old_mtu) + goto restart; + if(res == 0) + goto done; + if(res > 0) + break; + } + // if already find need name, make ret>=0 and break + { + int i = ctl->hisptr - 1; + //for(int i = 0; i < MAX_HOPS_DEFAULT; i++) + { + char *host_namea = (ctl->his[i].host_namea); + char *host_nameb = (ctl->his[i].host_nameb); + if(host_namea) { + if(!strcmp(host_namea, target_name) || !strcmp(host_namea, target_ip)) { + ctl->ttl = ctl->max_hops; + ret = i; + break; + } + } + if(host_nameb) { + if(!strcmp(host_nameb, target_name) || !strcmp(host_nameb, target_ip)) { + ctl->ttl = ctl->max_hops; + ret = i; + break; + } + } + } + } + if(res < 0) + log_printf("%2d: no reply\n", ctl->ttl); + } + log_printf(" Too many hops: pmtu %d\n", ctl->mtu); + + done: + freeaddrinfo(result); + + log_printf(" Resume: pmtu %d ", ctl->mtu); + if(ctl->hops_to >= 0) + { + ret = ctl->hisptr - 1; + log_printf("hops %d ", ctl->hops_to); + } + if(ctl->hops_from >= 0) + log_printf("back %d ", ctl->hops_from); + log_printf("\n"); + return ret; //exit(0); + + pktlen_error: + fprintf(stderr, "Error: pktlen must be > %d and <= %d\n", + ctl->overhead, INT_MAX); + return -1; //exit(1); +} + +/** + * Tracepath host + * + * @addr[in] host name or IP address + * @hops[out] hops count + * @time_usec[out] latency in microseconds + * @return 0 Ok, -1 error + */ +int tracepath_util(const char *addr, int *hops, int *time_usec) +{ + int type = 4; // 4 or 6 + int total_hops = 0; + long int total_time_usec = 0; // latency in microseconds + int argc = 4; + const char *argv[argc]; + if(type != 4) + argv[0] = "tracepath6"; + else + argv[0] = "tracepath4"; + argv[1] = "-b"; // print both name and ip + argv[2] = "-m 26"; // -n -m 16 + argv[3] = addr; + struct run_state ctl = { + .max_hops = MAX_HOPS_DEFAULT, + .hops_to = -1, + .hops_from = -1, + 0 + }; + int ret = tracepath_main(argc, (char**) argv, &ctl); + for(int i = 0; i < MAX_HOPS_DEFAULT; i++) { + free(ctl.his[i].host_namea); + free(ctl.his[i].host_nameb); + } + if(ret >= 0) + { + struct timeval *deltatime = &(ctl.his[ret].deltatime); + total_hops = ctl.his[ret].hop; + total_time_usec = deltatime->tv_sec * 1000000 + deltatime->tv_usec; + } + else + for(int i = 0; i < MAX_HOPS_DEFAULT; i++) { + struct timeval *deltatime = &(ctl.his[i].deltatime); + if(ctl.his[i].hop) { //if(!ctl.his[i].hops && (deltatime->tv_sec > 0 || deltatime->tv_usec > 0)) { + total_hops = ctl.his[i].hop; + total_time_usec = deltatime->tv_sec * 1000000 + deltatime->tv_usec; + } + /*if(ctl.his[i].hop > 0) + { + char *host_name = (ctl.his[i].host_name) ? ctl.his[i].host_name : "-"; + printf("%d %d: %s %ld\n", ctl.his[i].hops, ctl.his[i].hop, host_name, + deltatime->tv_sec * 1000000 + deltatime->tv_usec); + }*/ + } + if(hops) { + *hops = total_hops; + } + if(time_usec) { + *time_usec = total_time_usec; + } + return (ret >= 0) ? 0 : -1; + +} + diff --git a/iputils/traceroute/as_lookups.c b/iputils/traceroute/as_lookups.c new file mode 100755 index 0000000000000000000000000000000000000000..873180375802bce154e491966ea68f81eab98522 --- /dev/null +++ b/iputils/traceroute/as_lookups.c @@ -0,0 +1,128 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include "traceroute.h" + + +#define DEF_RADB_SERVER "whois.radb.net" +#define DEF_RADB_SERVICE "nicname" + + +static sockaddr_any ra_addr = {{ 0, }, }; +static char ra_buf[512] = { 0, }; + + +const char *get_as_path (const char *query) { + int sk, n; + FILE *fp; + char buf[1024]; + int prefix = 0, best_prefix = 0; + char *rb, *re = &ra_buf[sizeof (ra_buf) / sizeof (*ra_buf) - 1]; + + + if (!ra_addr.sa.sa_family) { + const char *server, *service; + struct addrinfo *res; + int ret; + + server = getenv ("RA_SERVER"); + if (!server) server = DEF_RADB_SERVER; + + service = getenv ("RA_SERVICE"); + if (!service) service = DEF_RADB_SERVICE; + + + ret = getaddrinfo (server, service, NULL, &res); + if (ret) { + fprintf (stderr, "%s/%s: %s\n", server, service, + gai_strerror(ret)); + exit (2); + } + + memcpy (&ra_addr, res->ai_addr, res->ai_addrlen); + + freeaddrinfo (res); + } + + + sk = socket (ra_addr.sa.sa_family, SOCK_STREAM, 0); + if (sk < 0) error ("socket"); + + if (connect (sk, &ra_addr.sa, sizeof (ra_addr)) < 0) + goto err_sk; + + n = snprintf (buf, sizeof (buf), "%s\r\n", query); + if (n >= (int)sizeof (buf)) goto err_sk; + + if (write (sk, buf, n) < n) + goto err_sk; + + fp = fdopen (sk, "r"); + if (!fp) goto err_sk; + + + strcpy (ra_buf, "*"); + rb = ra_buf; + + while (fgets (buf, sizeof (buf), fp) != NULL) { + + if (!strncmp (buf, "route:", sizeof ("route:") - 1) || + !strncmp (buf, "route6:", sizeof ("route6:") - 1) + ) { + char *p = strchr (buf, '/'); + + if (p) prefix = strtoul (++p, NULL, 10); + else prefix = 0; /* Hmmm... */ + + } + else if (!strncmp (buf, "origin:", sizeof ("origin:") -1)) { + char *p, *as; + + p = buf + (sizeof ("origin:") - 1); + + while (isspace (*p)) p++; + as = p; + while (*p && !isspace (*p)) p++; + *p = '\0'; + + if (prefix > best_prefix) { + best_prefix = prefix; + + rb = ra_buf; + while (rb < re && (*rb++ = *as++)) ; + } + else if (prefix == best_prefix) { + char *q = strstr (ra_buf, as); + + if (!q || (*(q += strlen (as)) != '\0' && *q != '/')) { + if (rb > ra_buf) rb[-1] = '/'; + while (rb < re && (*rb++ = *as++)) ; + } + } + /* else just ignore it */ + } + } + + fclose (fp); + + return ra_buf; + + +err_sk: + close (sk); + return "!!"; +} diff --git a/iputils/traceroute/clif.c b/iputils/traceroute/clif.c new file mode 100755 index 0000000000000000000000000000000000000000..4380e8ecd061e5becee1f296c89a8c8bbf127b51 --- /dev/null +++ b/iputils/traceroute/clif.c @@ -0,0 +1,1284 @@ +/* + Copyright (c) 2000, 2003 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: LGPL v2.1 or any later + + See COPYING.LIB for the status of this software. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> + +#include "../iputils.h" +#include "clif.h" + +#if 1 /* Bad idea, anyway... */ +#define MAX_ARGC_NUMBER 256 +typedef unsigned char _CLIF_index; +#else +#define MAX_ARGC_NUMBER (4096 / 5 + 1) /* POSIX ARG_MAX >= 4096 ... */ +typedef unsigned short _CLIF_index; +#endif + +/* This is needed for some print info functions. + This is ugly for thread-safe (is it really actual on program invoking?), + and for several CLIF_parse_cmdline invoking... But foo on this. Yeah... + */ +static struct { + int argc; + char **argv; + CLIF_option *option_list; + CLIF_argument *argument_list; + unsigned int parse_flags; +} curr = { 0, }; + +static void err_report(const char *format, ...) { + va_list ap; + + if(curr.parse_flags & CLIF_SILENT) + return; + + va_start(ap, format); + + vfprintf(stderr, format, ap); + + va_end(ap); + + fprintf(stderr, "\n"); + + return; +} + +/* info generation stuff... */ + +#define SHORT_PLUS_MINUS "+/-" +#define LONG_PLUS_MINUS "++/--" +#define EXCL_DLM " | " + +static char *show_short(const CLIF_option *optn) { + static char buf[80]; + char *p = buf; + unsigned int flags = optn->flags | curr.parse_flags; + + if(optn->function_plus) { + if(!optn->function) + *p++ = '+'; + else { + strcpy(p, SHORT_PLUS_MINUS); + p += sizeof(SHORT_PLUS_MINUS) - 1; + } + } else + *p++ = '-'; + + *p++ = optn->short_opt[0]; + + if(optn->arg_name) { + char *endp = buf + sizeof(buf) - sizeof(",...]"); + const char *s; + + if(!(flags & _CLIF_STRICT_JOIN_ARG)) + *p++ = ' '; + if(flags & CLIF_OPTARG) + *p++ = '['; + + s = optn->arg_name; + while(*s && p < endp) + *p++ = *s++; + + if(flags & CLIF_SEVERAL) { + strcpy(p, ",..."); + p += sizeof(",...") - 1; /* last '\0' ... */ + } + + if(flags & CLIF_OPTARG) + *p++ = ']'; + } + + *p = '\0'; + + return buf; +} + +static char *show_long(const CLIF_option *optn) { + static char buf[80]; + char *p = buf; + char *endp; + const char *s; + unsigned int flags = optn->flags | curr.parse_flags; + + if(!(flags & _CLIF_STRICT_KEYWORD)) { + + if(!(flags & _CLIF_STRICT_ONEDASH)) { + if(optn->function_plus) { + if(!optn->function) { + *p++ = '+'; + *p++ = '+'; + } + else { + strcpy(p, LONG_PLUS_MINUS); + p += sizeof(LONG_PLUS_MINUS) - 1; + } + } else { + *p++ = '-'; + *p++ = '-'; + } + + } else { + if(optn->function_plus) { + if(!optn->function) + *p++ = '+'; + else { + strcpy(p, SHORT_PLUS_MINUS); + p += sizeof(SHORT_PLUS_MINUS) - 1; + } + } else + *p++ = '-'; + } + } + + s = optn->long_opt; + endp = buf + sizeof(buf) - sizeof(" ["); + while(*s && p < endp) + *p++ = *s++; + + if(optn->arg_name) { + + if(flags & _CLIF_STRICT_NOEQUAL) { + *p++ = ' '; + if(flags & CLIF_OPTARG) + *p++ = '['; + } else { + if(flags & CLIF_OPTARG) + *p++ = '['; + *p++ = '='; + } + + s = optn->arg_name; + endp = buf + sizeof(buf) - sizeof(",...]"); + while(*s && p < endp) + *p++ = *s++; + + if(flags & CLIF_SEVERAL) { + strcpy(p, ",..."); + p += sizeof(",...") - 1; /* last '\0' ... */ + } + + if(flags & CLIF_OPTARG) + *p++ = ']'; + } + + *p = '\0'; + + return buf; +} + +static char *show_excl(const CLIF_option *option_list, int *cnt_p) { + static char buf[256]; + const CLIF_option *optn; + char *p = buf; + char *endp = buf + sizeof(buf) - sizeof(EXCL_DLM); + int excl_cnt = 0; + + *p = '\0'; + if(cnt_p) + *cnt_p = 0; + if(!option_list) + return buf; + + for(optn = option_list; optn->short_opt || optn->long_opt; optn++) { + char *s; + + if(!(optn->flags & CLIF_EXCL)) + continue; + + if(optn->short_opt) + s = show_short(optn); + else + s = show_long(optn); + + if(excl_cnt > 0) { /* i.e., second etc... */ + strcpy(p, EXCL_DLM); + p += sizeof(EXCL_DLM) - 1; + } + + while(*s && p < endp) + *p++ = *s++; + + excl_cnt++; + } + + *p = '\0'; + + if(cnt_p) + *cnt_p = excl_cnt; + + return buf; +} + +static int is_keyword(const CLIF_option *optn) { + unsigned int flags = optn->flags | curr.parse_flags; + + return (flags & _CLIF_STRICT_KEYWORD) != 0; +} + +static void err_bad_opt(const char *arg, char c, int n) { + char sym = (*arg == '+') ? '+' : '-'; + + if(c) + err_report("Bad option `%c%c' (argc %d)", sym, c, n); + else { + char *p = strchr(arg, '='); + const char *type = (*arg == sym) ? "option" : "keyword"; + + if(p) + err_report("Bad %s `%s' (with arg `%s') (argc %d)", + type, arg, p + 1, n); + else + err_report("Bad %s `%s' (argc %d)", type, arg, n); + } +} + +static void err_bad_arg(const CLIF_option *optn, char c, int n) { + CLIF_option tmp = *optn; + char ss[80]; + char *s; + + tmp.arg_name = NULL; + + if(c) { + s = show_short(&tmp); /* always without arg... */ + strncpy(ss, s, sizeof(ss)); + s = show_short(optn); + } else { + s = show_long(&tmp); /* always without arg... */ + strncpy(ss, s, sizeof(ss)); + s = show_long(optn); + } + + err_report("%s `%s' (argc %d) requires an argument: `%s'", + (c || !is_keyword(optn)) ? "Option" : "Keyword", ss, n, s); +} + +static void err_bad_res(const CLIF_option *optn, char c, + const char *opt_arg, int n) { + CLIF_option tmp = *optn; + char *ss; + const char *type; + + tmp.arg_name = NULL; + + if(c) { + ss = show_short(&tmp); + type = "option"; + } else { + ss = show_long(&tmp); + type = is_keyword(optn) ? "keyword" : "option"; + } + + if(optn->arg_name) + err_report("Cannot handle `%s' %s with arg `%s' (argc %d)", + ss, type, opt_arg, n); + else + err_report("Cannot handle `%s' %s (argc %d)", ss, type, n); +} + +static void err_bad_excl(const CLIF_option *optn, char c, int n) { + CLIF_option tmp = *optn; + char *ss; + char *excl = show_excl(curr.option_list, 0); + /* Note: show_(short|long)() nested!!! */ + + tmp.arg_name = NULL; + + if(c) + ss = show_short(&tmp); + else + ss = show_long(&tmp); + + err_report("%s `%s' (argc %d): Only one of:\n %s\n" + "may be specified.", + (c || !is_keyword(optn)) ? "Option" : "Keyword", + ss, n, excl); +} + +static CLIF_option *find_long(char *arg, char **arg_p, + unsigned int match, unsigned int nomatch) { + CLIF_option *optn; + CLIF_option *abbrev = NULL; + char *abbrev_arg = NULL; + int abbrev_found = 0; + + for(optn = curr.option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + char *a; + const char *o; + unsigned int flags; + + if(!optn->long_opt) + continue; + + flags = curr.parse_flags | optn->flags; + if(flags & nomatch) + continue; + if(match && !(flags & match)) + continue; /* XXX: optimize it */ + + for(a = arg, o = optn->long_opt; *o && *a == *o; a++, o++) + ; + + if(*a == '\0' || + (*a == '=' && optn->arg_name && !(flags & _CLIF_STRICT_NOEQUAL)) + ) { /* looks like end of option... */ + + if(!*o) { /* explicit match found */ + if(*a == '=' && arg_p) + *arg_p = a + 1; + return optn; + } + + if((flags & CLIF_ABBREV) && + (a - arg >= CLIF_MIN_ABBREV) + ) { + if(!abbrev_found) { + abbrev_found = 1; + abbrev = optn; + if(*a == '=') + abbrev_arg = a + 1; + } else + /* several possibility case... */ + abbrev = NULL; + } + } + } + + if(abbrev) { /* implicit match found */ + if(abbrev_arg && arg_p) + *arg_p = abbrev_arg; + return abbrev; + } else + /* no match found */ + return NULL; +} + +static int check_sym(const CLIF_option *optn, char sym) { + + if(sym == '+') { + if(!optn->function_plus) + return -1; + } + else if(sym == '-') { + if(!optn->function && optn->function_plus) + return -1; + } + + return 0; +} + +static int call_function(CLIF_option *optn, char *opt_arg, char sym) { + int (*function)(CLIF_option *, char *); + + function = (sym == '+') ? optn->function_plus : optn->function; + + if(!function) + return 0; + + if(opt_arg && ((optn->flags | curr.parse_flags) & CLIF_SEVERAL)) { + char tmp[80]; + char *t; + char *endt = tmp + sizeof(tmp); + + while(*opt_arg) { + + t = tmp; + while(t < endt && *opt_arg && + *opt_arg != ' ' && *opt_arg != '\t' && *opt_arg != ',' + ) + *t++ = *opt_arg++; + + if(t >= endt) + return -1; + + *t = '\0'; + + if(function(optn, tmp) < 0) + return -1; + + while(*opt_arg == ' ' || *opt_arg == '\t' || *opt_arg == ',') + opt_arg++; + } + + return 0; + } + + return function(optn, opt_arg); +} + +int CLIF_parse_cmdline (int argc, char *argv[], + CLIF_option *option_list, + CLIF_argument *argument_list, + unsigned int parse_flags){ +int i, j; +CLIF_option *optn; +CLIF_argument *argm; +int num_args = 0; +int num_argm = 0, strict_beg = 0, strict_end = 0; +_CLIF_index arg_n[MAX_ARGC_NUMBER]; +unsigned int dirty_flags = 0; +int dirty_plus = 0; +int exclusive_cnt = 0; +int posix = getenv ("POSIXLY_CORRECT") != NULL || +(parse_flags & CLIF_POSIX); + +curr.argc = argc; +curr.argv = argv; +curr.option_list = option_list; +curr.argument_list = argument_list; +curr.parse_flags = parse_flags; + +if (argc <= 1 && (parse_flags & CLIF_HELP_EMPTY)) { + CLIF_current_help (); + exit (0); +} + +/* Scan argument_list for check and some info. */ + +if (argument_list) { + enum stages {STRICT_BEG, OPTIONAL, STRICT_END}; + int stage = STRICT_BEG; + + for (argm = argument_list; argm->name; argm++) { + + if (argm->flags & CLIF_STRICT) { + + if (stage == STRICT_BEG) strict_beg++; + else if (stage == OPTIONAL) { + stage = STRICT_END; + strict_end++; + } + else if (stage == STRICT_END) + strict_end++; + } else { + if (stage == STRICT_BEG) stage = OPTIONAL; + else if (stage == STRICT_END) { + err_report ("Incorrect argument list set in program " + "source: more than one optional area."); + return -1; + } + } + + num_argm++; + } +} + +/* Scan option_list for some info. */ +if (option_list) { + + dirty_flags = parse_flags; + + for (optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + dirty_flags |= optn->flags; + if (optn->function_plus) dirty_plus = 1; + } +} + +if (dirty_flags & CLIF_EXCL) +exclusive_cnt = 1; /* only one is allowed... */ + +/* Go ! Store arguments, parse options. */ + +for (i = 1; i < argc; i++) { + char *arg = argv[i]; + char *opt_arg = NULL; + char sym = '-'; + + if (!option_list) + goto handle_arg; + + if (*arg == '+' && dirty_plus) + sym = '+'; + + if (*arg != sym) { /* argument or keyword */ + + if (dirty_flags & CLIF_MAY_KEYWORD) { + optn = find_long (arg, &opt_arg, CLIF_MAY_KEYWORD, 0); + if (optn) goto long_found; + } + + if (num_args == 0 && (parse_flags & CLIF_FIRST_GROUP)) { + /* ugly... */ + parse_flags &= ~CLIF_FIRST_GROUP; + dirty_flags &= ~CLIF_FIRST_GROUP; /* to be correct */ + + goto handle_short; + } + + /* else it is an argument */ + goto handle_arg; + + } + else if (*++arg == sym) { /* `--' - long option */ + arg++; + + if (*arg == sym || /* `---' - let it be not option... */ + (parse_flags & (_CLIF_STRICT_KEYWORD|_CLIF_STRICT_ONEDASH)) + ) { + arg -= 2; + goto handle_arg; /* not option anyway */ + } + + optn = find_long (arg, &opt_arg, 0, + _CLIF_STRICT_KEYWORD | _CLIF_STRICT_ONEDASH); + if (optn) goto long_found; + + /* XXX: May be allow only for `--', not `++' too... */ + if (!*arg && sym == '-') { /* `--' and no empty longoption */ + option_list = NULL; /* POSIX way... */ + continue; + } + + /* XXX: or treat as an argument sometimes??? */ + err_bad_opt (argv[i], 0, i); + return -1; + } + else { /* short option, or several short options... */ + + if (dirty_flags & CLIF_MAY_ONEDASH) { + optn = find_long (arg, &opt_arg, CLIF_MAY_ONEDASH, 0); + if (optn) goto long_found; + } + + if (!*arg) { /* POSIX say: only "stdout specification"... */ + arg--; + goto handle_arg; + } + + goto handle_short; + } + + long_found: + if (check_sym (optn, sym) < 0) { /* Oops... */ + err_bad_opt (argv[i], 0, i); + return -1; + } + + if (optn->flags & CLIF_EXCL) { + if (!exclusive_cnt) { + err_bad_excl (optn, 0, i); + return -1; + } + exclusive_cnt--; + } + + if (optn->arg_name && !opt_arg) { + unsigned int flags = optn->flags | parse_flags; + + if (++i >= argc || + !(flags & CLIF_MAY_NOEQUAL) + ) { /* missing opt arg */ + i--; + + if (!(flags & CLIF_OPTARG)) { + err_bad_arg (optn, 0, i); + return -1; + } + + opt_arg = NULL; + } else + opt_arg = argv[i]; + + } + + if (call_function (optn, opt_arg, sym) < 0) { + err_bad_res (optn, 0, opt_arg, i); + return -1; + } + + if (optn->flags & CLIF_EXIT) + exit (0); + + continue; + + handle_arg: + if (argument_list) { + if (i < MAX_ARGC_NUMBER) /* XXX: ugly, better report */ + arg_n[num_args++] = i; + } else { + err_report ("`%s' (argc %d): arguments are not allowed", + argv[i], i); + return -1; + } + + /* POSIX say: No more options after args... */ + if (posix) option_list = NULL; /* geniously... */ + + continue; + + handle_short: + + opt_arg = NULL; + + do { + + for (optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + if (optn->short_opt && optn->short_opt[0] == *arg) + break; + } + if (!optn->short_opt || + check_sym (optn, sym) < 0 + ) { + err_bad_opt (argv[i], *arg, i); + return -1; + } + + if (optn->flags & CLIF_EXCL) { + if (!exclusive_cnt) { + err_bad_excl (optn, *arg, i); + return -1; + } + exclusive_cnt--; + } + + if (optn->arg_name) { + unsigned int flags = parse_flags | optn->flags; + + if (arg[1] == '\0') { /* a last one */ + + /* POSIX say: an option with arg cannot be grouped. */ + if (posix && arg != argv[i] && arg[-1] != sym) { + err_bad_arg (optn, *arg, i); /* good way? */ + return -1; + } + + if (++i >= argc || + (flags & _CLIF_STRICT_JOIN_ARG) + ) { + i--; + + if (!(flags & CLIF_OPTARG)) { + err_bad_arg (optn, *arg, i); + return -1; + } + + opt_arg = NULL; + } else + opt_arg = argv[i]; + } + else if ((arg == argv[i] || arg[-1] == sym) && + (flags & CLIF_MAY_JOIN_ARG) + ) { + opt_arg = ++arg; + } + else { /* inside a group... */ + if (!(flags & CLIF_OPTARG) || + (flags & CLIF_MAY_JOIN_ARG) + ) { + err_bad_arg (optn, *arg, i); + return -1; + } + + opt_arg = NULL; + } + } + + if (call_function (optn, opt_arg, sym) < 0) { + err_bad_res (optn, optn->short_opt[0], opt_arg, i); + return -1; + } + + if (optn->flags & CLIF_EXIT) + exit (0); + + }while (!opt_arg && *++arg); + +} /* for ( ... ) */ + +if ((parse_flags & CLIF_STRICT_EXCL) && exclusive_cnt != 0) { + err_report ("One of these must be specified:\n %s\n", + show_excl (option_list, 0)); + return -1; +} + +/* Now, after *ALL* options, handle arguments, if any. */ + +if (num_args < strict_beg + strict_end) { + /* Missing some needed arguments. */ + + if (num_args < strict_beg) argm = argument_list + num_args; + else + argm = argument_list + + ((num_args - strict_beg) + (num_argm - strict_end)); + + if (num_args == strict_beg + strict_end - 1) + err_report ("Specify \"%s\" missing argument.", argm->name); + else + err_report ("Specify \"%s\" and other missing arguments.", + argm->name); + return -1; +} + +if (num_args > 0) { + _CLIF_index argm_index[MAX_ARGC_NUMBER]; + + /* assing argm (by index) for each arg... */ + + for (i = 0, j = 0; i < strict_beg; i++, j++) + argm_index[i] = j; + for (i = num_args - strict_end, j = num_argm - strict_end; + i < num_args; i++, j++ + ) argm_index[i] = j; + for (i = strict_beg, j = strict_beg; + i < num_args - strict_end && j < num_argm - strict_end; + i++ + ) { + argm_index[i] = j; + if (!(argument_list[j].flags & CLIF_MORE)) + j++; + } + + if (i < num_args - strict_end) { /* there are extra args... */ + err_report ("Extra arg `%s' (position %d, argc %d)", + argv[arg_n[i]], i + 1, arg_n[i]); + return -1; + } + + if (j < num_argm - strict_end && + !(argument_list[j].flags & CLIF_MORE) && + /* ...i.e, there are some missing optional args... */ + (argument_list[j].flags & CLIF_ACC_PREV) + ) { + if (j == 0) + err_report ("Incorrect argument list set: first arg " + "cannot be `accompanied with previous'."); + else + err_report ("Arg \"%s\" must be specified because " + "\"%s\" `%s' is used.", argument_list[j].name, + argument_list[j - 1].name, argv[arg_n[i - 1]]); + return -1; + } + + if (argm_index[--i] == j && + /* above is true only after OPTIONAL area scan + and when `j' is stopped on CLIF_MORE */ + ++j < num_argm - strict_end + /* i.e: there is a *last* one (after CLIF_MORE) + in the OPTIONAL area */ + ) argm_index[i] = j; /* *last* is better than *more* */ + + /* ...and work now */ + + for (i = 0; i < num_args; i++) { + argm = argument_list + argm_index[i]; + + if (argm->function && + argm->function (argm, argv[arg_n[i]], i) < 0 + ) { + err_report ("Cannot handle \"%s\" cmdline arg `%s' " + "on position %d (argc %d)", + argm->name, argv[arg_n[i]], i + 1, arg_n[i]); + return -1; + } + } + + /* That`s all. */ +} + +return 0; +} + +static void box_output(int start, int left, int width, const char *str, + const char *arg_name) { + char *p, *endp, *s; + int l; + char buf[1024]; + char spacer[128]; /* assume it is enough */ + + if(left > (int) sizeof(spacer) - 2) + left = (int) sizeof(spacer) - 2; + if(width > (int) sizeof(buf) - 1) + width = (int) sizeof(buf) - 1; + + spacer[0] = '\n'; + memset(spacer + 1, ' ', left); + spacer[left + 1] = '\0'; + + l = left - start; + if(l > 0) { + memset(buf, ' ', l); + buf[l] = '\0'; + fprintf(stderr, "%s", buf); + } else + fprintf(stderr, "%s", spacer); + + endp = buf + width; + + p = buf; + + while(*str) { + + while(*str && p < endp) { + + if(*str == '%' && arg_name) { + if(str[1] == '%') { + *p++ = '%'; + str += 2; + continue; + } + else if(str[1] == 's') { + const char *a = arg_name; + + while(*a && p < endp) + *p++ = *a++; + str += 2; + continue; + } + } + + *p++ = *str++; + } + + *p = '\0'; + + if(p < endp) + break; + + while(p > buf && *p != ' ' && *p != '\t') + p--; + if(p <= buf) + return; /* foo on you */ + + *p = '\0'; + fprintf(stderr, "%s", buf); + fprintf(stderr, "%s", spacer); + + p++; + for(s = buf; *p; *s++ = *p++) + ; + *s = '\0'; + p = s; + } + + fprintf(stderr, "%s", buf); + + return; +} + +#define SHORT_LONG_DLM " " +#define OPT_START_DLM " " +#define OPT_FIELD_WIDTH 30 + +#define ARG_MARK_STRICT "+ " +#define ARG_MARK_GROUP0 " . " +#define ARG_MARK_GROUP " ' " +#define ARG_MARK_OPT " " +#define ARG_FIELD_WIDTH 20 + +#define SCREEN_WIDTH 80 + +void CLIF_print_options(const char *header, + const CLIF_option *option_list) { + const CLIF_option *optn; + char *excl; + int excl_cnt = 0; + + /* Print a header string, if present... */ + if(header) + fprintf(stderr, "%s\n", header); + + if(!option_list) + return; + + for(optn = option_list; optn->short_opt || optn->long_opt; optn++) { + int len; + + /* generate and print an option usage */ + + if(optn->short_opt) { + if(optn->long_opt) + len = fprintf(stderr, OPT_START_DLM "%s" + SHORT_LONG_DLM "%s", + show_short(optn), show_long(optn)); + else + len = fprintf(stderr, OPT_START_DLM "%s", + show_short(optn)); + } else + len = fprintf(stderr, OPT_START_DLM "%s", show_long(optn)); + + /* print a help string, if present */ + + if(optn->help_string) + box_output(len, OPT_FIELD_WIDTH, + SCREEN_WIDTH - OPT_FIELD_WIDTH, + optn->help_string, optn->arg_name); + + fprintf(stderr, "\n"); /* a last one */ + } + + excl = show_excl(option_list, &excl_cnt); + if(excl_cnt > 0) { + + if(excl_cnt == 1) { + if((curr.parse_flags & CLIF_STRICT_EXCL) && + curr.option_list == option_list + ) + fprintf(stderr, "Anyway `%s' must be specified.\n", excl); + //else /* simple ordinary option, because excl_cnt == 1 ... */ + } else + fprintf(stderr, "Only one of these may be specified:\n" + " %s\n", excl); + } + + return; +} + +void CLIF_print_arguments(const char *header, + const CLIF_argument *argument_list) { + const CLIF_argument *argm; + + if(!argument_list) + return; + + /* Print a header string, if present... */ + if(header) + fprintf(stderr, "%s\n", header); + + for(argm = argument_list; argm->name; argm++) { + int len; + + if(argm->flags & CLIF_STRICT) + len = fprintf(stderr, ARG_MARK_STRICT "%s", argm->name); + else if(argm->flags & CLIF_MORE) + len = fprintf(stderr, ARG_MARK_OPT "%s ...", argm->name); + else if(argm->flags & CLIF_ACC_PREV) + len = fprintf(stderr, ARG_MARK_GROUP "%s", argm->name); + else if((argm + 1)->name && ((argm + 1)->flags & CLIF_ACC_PREV)) + len = fprintf(stderr, ARG_MARK_GROUP0 "%s", argm->name); + else + len = fprintf(stderr, ARG_MARK_OPT "%s", argm->name); + + if(argm->help_string) + box_output(len, ARG_FIELD_WIDTH, + SCREEN_WIDTH - ARG_FIELD_WIDTH, + argm->help_string, argm->name); + + fprintf(stderr, "\n"); + } + + return; +} + +void CLIF_print_usage(const char *header, const char *progname, + const CLIF_option *option_list, + const CLIF_argument *argument_list) { + + if(!progname && curr.argv) + progname = curr.argv[0]; + + if(!header) { + if(progname) + fprintf(stderr, "Usage: %s", progname); + else + fprintf(stderr, "Command line options:"); + } else { + if(progname) + fprintf(stderr, "%s\n" OPT_START_DLM "%s", header, progname); + else + fprintf(stderr, "%s", header); + } + + if(option_list) { + const CLIF_option *optn; + char m_buf[256], p_buf[256], mp_buf[256]; + char *m = m_buf, *p = p_buf, *mp = mp_buf; + char *end_m = m_buf + sizeof(m_buf) - 1; + char *end_p = p_buf + sizeof(p_buf) - 1; + char *end_mp = mp_buf + sizeof(mp_buf) - 1; + char *excl; + int excl_cnt = 0; + + /* first, show exclusive option list, if any... */ + + excl = show_excl(option_list, &excl_cnt); + if(excl_cnt > 0) { + if((curr.parse_flags & CLIF_STRICT_EXCL) && + curr.option_list == option_list + ) { + if(excl_cnt == 1) + fprintf(stderr, " %s", excl); + else + fprintf(stderr, " { %s }", excl); + } else + fprintf(stderr, " [ %s ]", excl); + } + + /* second, find short options without arguments... */ + + for(optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + /* We don`t exclude CLIF_EXTRA hear: + simple one char don`t eat a lot of space... + */ + + if(!optn->short_opt || + optn->arg_name || + (optn->flags & CLIF_EXCL) + ) + continue; + + if(optn->function_plus) { + if(optn->function) { + if(mp < end_mp) + *mp++ = optn->short_opt[0]; + } else { + if(p < end_p) + *p++ = optn->short_opt[0]; + } + } else { + if(m < end_m) + *m++ = optn->short_opt[0]; + } + } + + if(m > (char *) m_buf) { + *m = '\0'; + fprintf(stderr, " [ -%s ]", m_buf); + } + if(p > (char *) p_buf) { + *p = '\0'; + fprintf(stderr, " [ +%s ]", p_buf); + } + if(mp > (char *) mp_buf) { + *mp = '\0'; + fprintf(stderr, " [ " SHORT_PLUS_MINUS "%s ]", mp_buf); + } + + /* third, print all another... */ + + for(optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + if(optn->flags & CLIF_EXTRA) + continue; + + if(optn->flags & CLIF_EXCL) + continue; /* already handled */ + + if(optn->short_opt) { + if(optn->arg_name) + fprintf(stderr, " [ %s ]", show_short(optn)); + //else + /* already handled */ + } else + fprintf(stderr, " [ %s ]", show_long(optn)); + } + } + + if(argument_list) { + const CLIF_argument *argm; + int deep = 0; + + for(argm = argument_list; argm->name; argm++) { + + if(argm->flags & CLIF_STRICT) { + if(deep > 0) { + fputc(' ', stderr); + while(deep--) + fputc(']', stderr); + deep = 0; + } + + fprintf(stderr, " %s", argm->name); + } else { + if(argm->flags & CLIF_MORE) + fprintf(stderr, " [ %s ...", argm->name); + else if(argm->flags & CLIF_ACC_PREV) { + fprintf(stderr, " %s", argm->name); + --deep; /* ugly, but easy */ + } else + fprintf(stderr, " [ %s", argm->name); + + deep++; + } + } + + if(deep > 0) { + fputc(' ', stderr); + while(deep--) + fputc(']', stderr); + } + } + + fprintf(stderr, "\n"); +} + +int CLIF_current_help(void) { + + if(!curr.argc) + return -1; /* i.e., not inited... */ + + CLIF_print_usage("Usage:", curr.argv[0], curr.option_list, + curr.argument_list); + + if(curr.option_list) + CLIF_print_options("Options:", curr.option_list); + + if(curr.argument_list) + CLIF_print_arguments("\nArguments:", curr.argument_list); + + return 0; +} + +/* Common useful option handlers. */ + +int CLIF_version_handler(CLIF_option *optn, char *arg) { + UNUSED(arg); + if(!optn->data) + return -1; + + fprintf(stderr, "%s\n", ((char *) optn->data)); + + return 0; /* be happy */ +} + +int CLIF_set_flag(CLIF_option *optn, char *arg) { + UNUSED(arg); + if(!optn->data) + return -1; + + *((int *) optn->data) = 1; + + return 0; +} + +int CLIF_unset_flag(CLIF_option *optn, char *arg) { + UNUSED(arg); + if(!optn->data) + return -1; + + *((int *) optn->data) = 0; + + return 0; +} + +static int set_string(char **data, char *arg) { + + if(!data) + return -1; + + *data = arg; + + return 0; +} + +int CLIF_set_string(CLIF_option *optn, char *arg) { + + return set_string(optn->data, arg); +} + +int CLIF_arg_string(CLIF_argument *argm, char *arg, int index) { + UNUSED(index); + return set_string(argm->data, arg); +} + +static int set_int(int *data, char *arg) { + char *q; + + if(!data) + return -1; + + *data = (int) strtol(arg, &q, 0); + + return (q == arg || *q) ? -1 : 0; +} + +static int set_uint(unsigned int *data, char *arg) { + char *q; + + if(!data) + return -1; + + *data = (unsigned int) strtoul(arg, &q, 0); + + return (q == arg || *q) ? -1 : 0; +} + +static int set_double(double *data, char *arg) { + char *q; + + if(!data) + return -1; + + *data = strtod(arg, &q); + + return (q == arg || *q) ? -1 : 0; +} + +int CLIF_set_int(CLIF_option *optn, char *arg) { + + return set_int(optn->data, arg); +} + +int CLIF_set_uint(CLIF_option *optn, char *arg) { + + return set_uint(optn->data, arg); +} + +int CLIF_set_double(CLIF_option *optn, char *arg) { + + return set_double(optn->data, arg); +} + +int CLIF_arg_int(CLIF_argument *argm, char *arg, int index) { + UNUSED(index); + return set_int(argm->data, arg); +} + +int CLIF_arg_uint(CLIF_argument *argm, char *arg, int index) { + UNUSED(index); + return set_uint(argm->data, arg); +} + +int CLIF_arg_double(CLIF_argument *argm, char *arg, int index) { + UNUSED(index); + return set_double(argm->data, arg); +} + +int CLIF_call_func(CLIF_option *optn, char *arg) { + + if(!optn->data) + return -1; + + if(optn->arg_name) { + int (*func)(char *) = optn->data; + + return func(arg); + } else { + int (*func)(void) = optn->data; + + return func(); + } +} + +int CLIF_arg_func(CLIF_argument *argm, char *arg, int index) { + int (*func)(char *, int); + + if(!argm->data) + return -1; + + func = (int (*)(char *, int)) argm->data; + + return func(arg, index); +} + diff --git a/iputils/traceroute/clif.h b/iputils/traceroute/clif.h new file mode 100755 index 0000000000000000000000000000000000000000..c798a8df8fbfee7b7450b6e6ec0a46c08443c66f --- /dev/null +++ b/iputils/traceroute/clif.h @@ -0,0 +1,121 @@ +/* + Copyright (c) 2000, 2003 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: LGPL v2.1 or any later + + See COPYING.LIB for the status of this software. +*/ + +#ifndef _CLIF_H +#define _CLIF_H + + +typedef struct CLIF_option_struct CLIF_option; +struct CLIF_option_struct { + const char *short_opt; + const char *long_opt; + const char *arg_name; + const char *help_string; + int (*function) (CLIF_option *optn, char *arg); + void *data; + int (*function_plus) (CLIF_option *optn, char *arg); + unsigned int flags; +}; +#define CLIF_END_OPTION { 0, 0, 0, 0, 0, 0, 0, 0 } + +typedef struct CLIF_argument_struct CLIF_argument; +struct CLIF_argument_struct { + const char *name; + const char *help_string; + int (*function) (CLIF_argument *argm, char *arg, int index); + void *data; + unsigned int flags; +}; +#define CLIF_END_ARGUMENT { 0, 0, 0, 0, 0 } + +/* Argument flag bits. */ +#define CLIF_MORE (0x01) /* null or several */ +#define CLIF_STRICT (0x02) /* arg must be present */ +#define CLIF_ACC_PREV (0x04) /* arg must be accompanied with previous */ + + +/* Option flag bits. */ + +/* affected only by per-option flags */ +#define CLIF_EXTRA (0x0001) /* don`t show in usage line */ +#define CLIF_EXIT (0x0002) /* exit after handler return */ +#define CLIF_EXCL (0x0004) /* at exclusive area */ + +/* affected by per-option flags and by common `parse_flags' argument + of CLIF_parse_cmdline(). In last case appropriate bits are translated + for all the options. +*/ +#define CLIF_MAY_JOIN_ARG (0x0010) +#define _CLIF_STRICT_JOIN_ARG (0x0020) +#define CLIF_JOIN_ARG (CLIF_MAY_JOIN_ARG|_CLIF_STRICT_JOIN_ARG) +#define CLIF_MAY_NOEQUAL (0x0040) +#define _CLIF_STRICT_NOEQUAL (0x0080) +#define CLIF_NOEQUAL (CLIF_MAY_NOEQUAL|_CLIF_STRICT_NOEQUAL) +#define CLIF_MAY_KEYWORD (0x0100) +#define _CLIF_STRICT_KEYWORD (0x0200) +#define CLIF_KEYWORD (CLIF_MAY_KEYWORD|_CLIF_STRICT_KEYWORD) +#define CLIF_MAY_ONEDASH (0x0400) +#define _CLIF_STRICT_ONEDASH (0x0800) +#define CLIF_ONEDASH (CLIF_MAY_ONEDASH|_CLIF_STRICT_ONEDASH) +#define CLIF_OPTARG (0x1000) /* allow missing optarg */ +#define CLIF_ABBREV (0x2000) /* allow long opt abbreviation */ +#define CLIF_SEVERAL (0x4000) /* several args in one opt`s arg */ + +/* affected only by common `parse_flags' arg of CLIF_parse_cmdline() . */ +#define CLIF_HELP_EMPTY (0x10000) /* print help on empty cmdline */ +#define CLIF_POSIX (0x20000) /* follow POSIX standard */ +#define CLIF_FIRST_GROUP (0x40000) /* first arg - options` group */ +#define CLIF_STRICT_EXCL (0x80000) /* at least one exclusive */ +#define CLIF_SILENT (0x100000) /* no errors on stderr */ + +#define CLIF_MIN_ABBREV 2 /* a minimal match length in abbrev */ + + +extern int CLIF_parse (int argc, char **argv, CLIF_option *option_list, + CLIF_argument *arg_list, unsigned int parse_flags); +/* history compatibility... */ +#define CLIF_parse_cmdline(ARGC,ARGV,OPTN,ARGS,FLAGS) \ + CLIF_parse (ARGC, ARGV, OPTN, ARGS, FLAGS) + +extern void CLIF_print_options (const char *header, + const CLIF_option *option_list); +extern void CLIF_print_arguments (const char *header, + const CLIF_argument *argument_list); +extern void CLIF_print_usage (const char *header, const char *progname, + const CLIF_option *option_list, + const CLIF_argument *argument_list); + +extern int CLIF_current_help (void); + +/* Common useful option handlers. */ +extern int CLIF_version_handler (CLIF_option *optn, char *arg); +extern int CLIF_set_flag (CLIF_option *optn, char *arg); +extern int CLIF_unset_flag (CLIF_option *optn, char *arg); +extern int CLIF_set_string (CLIF_option *optn, char *arg); +extern int CLIF_set_int (CLIF_option *optn, char *arg); +extern int CLIF_set_uint (CLIF_option *optn, char *arg); +extern int CLIF_set_double (CLIF_option *optn, char *arg); +extern int CLIF_call_func (CLIF_option *optn, char *arg); + +extern int CLIF_arg_string (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_int (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_uint (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_double (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_func (CLIF_argument *argm, char *arg, int index); + + +/* Some useful macros. */ + +#define CLIF_HELP_OPTION \ + { 0, "help", 0, "Read this help and exit", \ + CLIF_call_func, CLIF_current_help, 0, CLIF_EXTRA | CLIF_EXIT } +#define CLIF_VERSION_OPTION(STR) \ + { "V", "version", 0, "Print version info and exit", \ + CLIF_version_handler, STR, 0, CLIF_EXTRA | CLIF_EXIT } + +#endif /* _CLIF_H */ diff --git a/iputils/traceroute/csum.c b/iputils/traceroute/csum.c new file mode 100755 index 0000000000000000000000000000000000000000..ea3a50da0430d19582941d72cded69fb09e45127 --- /dev/null +++ b/iputils/traceroute/csum.c @@ -0,0 +1,34 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> + +#include "traceroute.h" + + +uint16_t in_csum (const void *ptr, size_t len) { + const uint16_t *p = (const uint16_t *) ptr; + size_t nw = len / 2; + unsigned int sum = 0; + uint16_t res; + + while (nw--) sum += *p++; + + if (len & 0x1) + sum += htons (*((unsigned char *) p) << 8); + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + res = ~sum; + if (!res) res = ~0; + + return res; +} + diff --git a/iputils/traceroute/extension.c b/iputils/traceroute/extension.c new file mode 100755 index 0000000000000000000000000000000000000000..515a8dbed7da85addd25543a3bbcfd303944f667 --- /dev/null +++ b/iputils/traceroute/extension.c @@ -0,0 +1,132 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "traceroute.h" + + +struct icmp_ext_header { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned int version:4; + unsigned int reserved:4; +#else + unsigned int reserved:4; + unsigned int version:4; +#endif + uint8_t reserved1; + uint16_t checksum; +} __attribute__ ((packed)); + + +struct icmp_ext_object { + uint16_t length; + uint8_t class; + uint8_t c_type; + uint8_t data[0]; +}; + +#define MPLS_CLASS 1 +#define MPLS_C_TYPE 1 + + +#define do_snprintf(CURR, END, FMT, ARGS...) \ + do { \ + CURR += snprintf (CURR, END - CURR, (FMT), ## ARGS);\ + if (CURR > END) CURR = END; \ + } while (0) + + +static int try_extension (probe *pb, char *buf, size_t len) { + struct icmp_ext_header *iext = (struct icmp_ext_header *) buf; + char str[1024]; + char *curr = str; + char *end = str + sizeof (str) / sizeof (*str); + + + /* a check for len >= 8 already done for all cases */ + + if (iext->version != 2) return -1; + + if (iext->checksum && + in_csum (iext, len) != (uint16_t) ~0 + ) return -1; + + buf += sizeof (*iext); + len -= sizeof (*iext); + + + while (len >= sizeof (struct icmp_ext_object)) { + struct icmp_ext_object *obj = (struct icmp_ext_object *) buf; + size_t objlen = ntohs (obj->length); + size_t data_len; + uint32_t *ui = (uint32_t *) obj->data; + int i, n; + + if (objlen < sizeof (*obj) || + objlen > len + ) return -1; + + data_len = objlen - sizeof (*obj); + if (data_len % sizeof (uint32_t)) + return -1; /* must be 32bit rounded... */ + + n = data_len / sizeof (*ui); + + + if (curr > (char *) str && curr < end) + *curr++ = ';'; /* a separator */ + + if (obj->class == MPLS_CLASS && + obj->c_type == MPLS_C_TYPE && + n >= 1 + ) { /* people prefer MPLS to be parsed... */ + + do_snprintf (curr, end, "MPLS:"); + + for (i = 0; i < n; i++, ui++) { + uint32_t mpls = ntohl (*ui); + + do_snprintf (curr, end, "%sL=%u,E=%u,S=%u,T=%u", + i ? "/" : "", + mpls >> 12, + (mpls >> 9) & 0x7, + (mpls >> 8) & 0x1, + mpls & 0xff); + } + + } + else { /* common case... */ + + do_snprintf (curr, end, "%u/%u:", obj->class, obj->c_type); + + for (i = 0; i < n && curr < end; i++, ui++) + do_snprintf (curr, end, "%s%08x", i ? "," : "", ntohl(*ui)); + } + + buf += objlen; + len -= objlen; + } + + if (len) return -1; + + + pb->ext = strdup (str); + + return 0; +} + + +void handle_extensions (probe *pb, char *buf, int len, int step) { + + if (!step) + try_extension (pb, buf, len); + else { + for ( ; len >= 8; buf += step, len -= step) + if (try_extension (pb, buf, len) == 0) + break; + } + + return; +} + diff --git a/iputils/traceroute/flowlabel.h b/iputils/traceroute/flowlabel.h new file mode 100755 index 0000000000000000000000000000000000000000..af5a65ae28c349c193388ca719ee2563d579b591 --- /dev/null +++ b/iputils/traceroute/flowlabel.h @@ -0,0 +1,40 @@ +/* + It is just a stripped copy of the kernel header "linux/in6.h" + + "Flow label" things are still not defined in "netinet/in*.h" headers, + but we cannot use "linux/in6.h" immediately because it currently + conflicts with "netinet/in.h" . +*/ + +struct in6_flowlabel_req +{ + struct in6_addr flr_dst; + __u32 flr_label; + __u8 flr_action; + __u8 flr_share; + __u16 flr_flags; + __u16 flr_expires; + __u16 flr_linger; + __u32 __flr_pad; + /* Options in format of IPV6_PKTOPTIONS */ +}; + +#define IPV6_FL_A_GET 0 +#define IPV6_FL_A_PUT 1 +#define IPV6_FL_A_RENEW 2 + +#define IPV6_FL_F_CREATE 1 +#define IPV6_FL_F_EXCL 2 + +#define IPV6_FL_S_NONE 0 +#define IPV6_FL_S_EXCL 1 +#define IPV6_FL_S_PROCESS 2 +#define IPV6_FL_S_USER 3 +#define IPV6_FL_S_ANY 255 + +#define IPV6_FLOWINFO_FLOWLABEL 0x000fffff +#define IPV6_FLOWINFO_PRIORITY 0x0ff00000 + +#define IPV6_FLOWLABEL_MGR 32 +#define IPV6_FLOWINFO_SEND 33 + diff --git a/iputils/traceroute/mod-dccp.c b/iputils/traceroute/mod-dccp.c new file mode 100755 index 0000000000000000000000000000000000000000..26f2c653bf0fd3af1faeb911cd943e493edd93e9 --- /dev/null +++ b/iputils/traceroute/mod-dccp.c @@ -0,0 +1,295 @@ +/* + Copyright (c) 2012 Samuel Jero <sj323707@ohio.edu> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <linux/dccp.h> + + +#include "traceroute.h" + + +#define DEF_SERVICE_CODE 1885957735 + +#define DCCP_HEADER_LEN (sizeof (struct dccp_hdr) + \ + sizeof (struct dccp_hdr_ext) \ + + sizeof (struct dccp_hdr_request)) + +static sockaddr_any dest_addr = {{ 0, }, }; +static unsigned int dest_port = 0; + +static int raw_sk = -1; +static int last_ttl = 0; + +static uint8_t buf[1024]; /* enough, enough... */ +static size_t csum_len = 0; +static struct dccp_hdr *dh = NULL; +static struct dccp_hdr_ext *dhe = NULL; +static struct dccp_hdr_request *dhr = NULL; +static unsigned int service_code = DEF_SERVICE_CODE; + + +static CLIF_option dccp_options[] = { + { 0, "service", "NUM", "Set DCCP service code to %s (default is " + _TEXT (DEF_SERVICE_CODE) ")", + CLIF_set_uint, &service_code, 0, CLIF_ABBREV }, + CLIF_END_OPTION +}; + + +static int dccp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int af = dest->sa.sa_family; + sockaddr_any src; + socklen_t len; + uint8_t *ptr; + uint16_t *lenp; + + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; /* raw sockets can be confused */ + + if (!port_seq) port_seq = DEF_DCCP_PORT; + dest_port = htons (port_seq); + + + /* Create raw socket for DCCP */ + raw_sk = socket (af, SOCK_RAW, IPPROTO_DCCP); + if (raw_sk < 0) + error_or_perm ("socket"); + + tune_socket (raw_sk); /* including bind, if any */ + + if (connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0) + error ("connect"); + + len = sizeof (src); + if (getsockname (raw_sk, &src.sa, &len) < 0) + error ("getsockname"); + + + if (!raw_can_connect ()) { /* work-around for buggy kernels */ + close (raw_sk); + raw_sk = socket (af, SOCK_RAW, IPPROTO_DCCP); + if (raw_sk < 0) error ("socket"); + tune_socket (raw_sk); + /* but do not connect it... */ + } + + + use_recverr (raw_sk); + + add_poll (raw_sk, POLLIN | POLLERR); + + + /* Now create the sample packet. */ + + /* For easy checksum computing: + saddr + daddr + length + protocol + dccphdr + */ + + ptr = buf; + + if (af == AF_INET) { + len = sizeof (src.sin.sin_addr); + memcpy (ptr, &src.sin.sin_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin.sin_addr, len); + ptr += len; + } else { + len = sizeof (src.sin6.sin6_addr); + memcpy (ptr, &src.sin6.sin6_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin6.sin6_addr, len); + ptr += len; + } + + lenp = (uint16_t *) ptr; + ptr += sizeof (uint16_t); + *((uint16_t *) ptr) = htons ((uint16_t) IPPROTO_DCCP); + ptr += sizeof (uint16_t); + + + /* Construct DCCP header */ + dh = (struct dccp_hdr *) ptr; + + dh->dccph_ccval = 0; + dh->dccph_checksum = 0; + dh->dccph_cscov = 0; + dh->dccph_dport = dest_port; + dh->dccph_reserved = 0; + dh->dccph_sport = 0; /* temporary */ + dh->dccph_x = 1; + dh->dccph_type = DCCP_PKT_REQUEST; + dh->dccph_seq2 = 0; /* reserved if using 48 bit sequence numbers */ + /* high 16 bits of sequence number. Always make 0 for simplicity. */ + dh->dccph_seq = 0; + ptr += sizeof (struct dccp_hdr); + + dhe = (struct dccp_hdr_ext *) ptr; + dhe->dccph_seq_low = 0; /* temporary */ + ptr += sizeof (struct dccp_hdr_ext); + + dhr = (struct dccp_hdr_request *) ptr; + dhr->dccph_req_service = htonl (service_code); + ptr += sizeof (struct dccp_hdr_request); + + + csum_len = ptr - buf; + + if (csum_len > sizeof (buf)) + error ("impossible"); /* paranoia */ + + len = ptr - (uint8_t *) dh; + if (len & 0x03) error ("impossible"); /* as >>2 ... */ + + *lenp = htons (len); + dh->dccph_doff = len >> 2; + + + *packet_len_p = len; + + return 0; +} + + +static void dccp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + sockaddr_any addr; + socklen_t len = sizeof (addr); + + + /* To make sure we have chosen a free unused "source port", + just create, (auto)bind and hold a socket while the port is needed. + */ + + sk = socket (af, SOCK_DCCP, IPPROTO_DCCP); + if (sk < 0) error ("socket"); + + bind_socket (sk); + + if (getsockname (sk, &addr.sa, &len) < 0) + error ("getsockname"); + + /* When we reach the target host, it can send us either Reset or Response. + For Reset all is OK (we and kernel just answer nothing), but + for Response we should reply with our Close. + It is well-known "half-open technique", used by port scanners etc. + This way we do not touch remote applications at all, unlike + the ordinary connect(2) call. + As the port-holding socket neither connect() nor listen(), + it means "no such port yet" for remote ends, and kernel always + send Reset in such a situation automatically (we have to do nothing). + */ + + + dh->dccph_sport = addr.sin.sin_port; + + dhe->dccph_seq_low = random_seq (); + + dh->dccph_checksum = 0; + dh->dccph_checksum = in_csum (buf, csum_len); + + + if (ttl != last_ttl) { + set_ttl (raw_sk, ttl); + last_ttl = ttl; + } + + + pb->send_time = get_time (); + + if (do_send (raw_sk, dh, dh->dccph_doff << 2, &dest_addr) < 0) { + close (sk); + pb->send_time = 0; + return; + } + + + pb->seq = dh->dccph_sport; + + pb->sk = sk; + + return; +} + + +static probe *dccp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + UNUSED(sk); + probe *pb; + struct dccp_hdr *ndh = (struct dccp_hdr *) buf; + uint16_t sport, dport; + + + if (len < 8) return NULL; /* too short */ + + + if (err) { + sport = ndh->dccph_sport; + dport = ndh->dccph_dport; + } else { + sport = ndh->dccph_dport; + dport = ndh->dccph_sport; + } + + + if (dport != dest_port) + return NULL; + + if (!equal_addr (&dest_addr, from)) + return NULL; + + pb = probe_by_seq (sport); + if (!pb) return NULL; + + if (!err) pb->final = 1; + + return pb; +} + + +static void dccp_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), dccp_check_reply); +} + + +static void dccp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module dccp_ops = { + .name = "dccp", + .init = dccp_init, + .send_probe = dccp_send_probe, + .recv_probe = dccp_recv_probe, + .expire_probe = dccp_expire_probe, + .options = dccp_options, +}; + +TR_MODULE (dccp_ops); + +void tr_module_dccp_insert() +{ + +} diff --git a/iputils/traceroute/mod-icmp.c b/iputils/traceroute/mod-icmp.c new file mode 100755 index 0000000000000000000000000000000000000000..e2194d9aa4563b22b48d66f324de9f8cedf61b00 --- /dev/null +++ b/iputils/traceroute/mod-icmp.c @@ -0,0 +1,240 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> + +#include "traceroute.h" + +static sockaddr_any dest_addr = { { 0, }, }; +static uint16_t seq = 1; +static uint16_t ident = 0; + +static char *data; +static size_t *length_p; + +static int icmp_sk = -1; +static int last_ttl = 0; + +static int raw = 0; +static int dgram = 0; + +static CLIF_option icmp_options[] = { + { 0, "raw", 0, "Use raw sockets way only. Default is try this way " + "first (probably not allowed for unprivileged users), " + "then try dgram", + CLIF_set_flag, &raw, 0, CLIF_EXCL }, + { 0, "dgram", 0, "Use dgram sockets way only. May be not implemented " + "by old kernels or restricted by sysadmins", + CLIF_set_flag, &dgram, 0, CLIF_EXCL }, + CLIF_END_OPTION + }; + +static int icmp_init(const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int i; + int af = dest->sa.sa_family; + int protocol; + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; + + if(port_seq) + seq = port_seq; + + length_p = packet_len_p; + if(*length_p < sizeof(struct icmphdr)) + *length_p = sizeof(struct icmphdr); + + data = malloc(*length_p); + if(!data) + error("malloc"); + + for(i = (int) sizeof(struct icmphdr); i < (int)*length_p; i++) + data[i] = 0x40 + (i & 0x3f); + + protocol = (af == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6; + + if(!raw) { + icmp_sk = socket(af, SOCK_DGRAM, protocol); + if(icmp_sk < 0 && dgram) + error("socket"); + } + + if(!dgram) { + int raw_sk = socket(af, SOCK_RAW, protocol); + if(raw_sk < 0) { + if(raw || icmp_sk < 0) + error_or_perm("socket"); + dgram = 1; + } else { + /* prefer the traditional "raw" way when possible */ + close(icmp_sk); + icmp_sk = raw_sk; + } + } + + tune_socket(icmp_sk); + + /* Don't want to catch packets from another hosts */ + if(raw_can_connect() && + connect(icmp_sk, &dest_addr.sa, sizeof(dest_addr)) < 0 + ) + error("connect"); + + use_recverr(icmp_sk); + + if(dgram) { + sockaddr_any addr; + socklen_t len = sizeof(addr); + + if(getsockname(icmp_sk, &addr.sa, &len) < 0) + error("getsockname"); + ident = ntohs(addr.sin.sin_port); /* both IPv4 and IPv6 */ + + } else + ident = getpid() & 0xffff; + + add_poll(icmp_sk, POLLIN | POLLERR); + + return 0; +} + +static void icmp_send_probe(probe *pb, int ttl) { + int af = dest_addr.sa.sa_family; + + if(ttl != last_ttl) { + + set_ttl(icmp_sk, ttl); + + last_ttl = ttl; + } + + if(af == AF_INET) { + struct icmp *icmp = (struct icmp *) data; + + icmp->icmp_type = ICMP_ECHO; + icmp->icmp_code = 0; + icmp->icmp_cksum = 0; + icmp->icmp_id = htons(ident); + icmp->icmp_seq = htons(seq); + + icmp->icmp_cksum = in_csum(data, *length_p); + } + else if(af == AF_INET6) { + struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) data; + + icmp6->icmp6_type = ICMP6_ECHO_REQUEST; + icmp6->icmp6_code = 0; + icmp6->icmp6_cksum = 0; + icmp6->icmp6_id= htons (ident); + icmp6->icmp6_seq= htons(seq); + + /* icmp6->icmp6_cksum always computed by kernel internally */ + } + + pb->send_time = get_time(); + + if(do_send(icmp_sk, data, *length_p, &dest_addr) < 0) { + pb->send_time = 0; + return; + } + + pb->seq = seq; + + seq++; + + return; +} + +static probe *icmp_check_reply(int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + UNUSED(sk); + UNUSED(from); + int af = dest_addr.sa.sa_family; + int type; + uint16_t recv_id, recv_seq; + probe *pb; + + if(len < sizeof(struct icmphdr)) + return NULL; + + if(af == AF_INET) { + struct icmp *icmp = (struct icmp *) buf; + + type = icmp->icmp_type; + + recv_id = ntohs(icmp->icmp_id); + recv_seq = ntohs(icmp->icmp_seq); + + } + else { /* AF_INET6 */ + struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) buf; + + type = icmp6->icmp6_type; + + recv_id = ntohs(icmp6->icmp6_id); + recv_seq = ntohs(icmp6->icmp6_seq); + } + + if(recv_id != ident) + return NULL; + + pb = probe_by_seq(recv_seq); + if(!pb) + return NULL; + + if(!err) { + + if(!(af == AF_INET && type == ICMP_ECHOREPLY) && + !(af == AF_INET6 && type == ICMP6_ECHO_REPLY) + ) + return NULL; + + pb->final = 1; + } + + return pb; +} + +static void icmp_recv_probe(int sk, int revents) { + + if(!(revents & (POLLIN | POLLERR))) + return; + + recv_reply(sk, !!(revents & POLLERR), icmp_check_reply); +} + +static void icmp_expire_probe(probe *pb) { + + probe_done(pb); +} + +static tr_module icmp_ops = { + .name = "icmp", + .init = icmp_init, + .send_probe = icmp_send_probe, + .recv_probe = icmp_recv_probe, + .expire_probe = icmp_expire_probe, + .options = icmp_options, +}; + +TR_MODULE(icmp_ops); + +void tr_module_icmp_insert() +{ + +} + diff --git a/iputils/traceroute/mod-raw.c b/iputils/traceroute/mod-raw.c new file mode 100755 index 0000000000000000000000000000000000000000..bd9f05ba612569e5fbfdb1a3aec01990a55dd8a0 --- /dev/null +++ b/iputils/traceroute/mod-raw.c @@ -0,0 +1,172 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netdb.h> + +#include "traceroute.h" + + +static sockaddr_any dest_addr = {{ 0, }, }; +static int protocol = DEF_RAW_PROT; + +static char *data = NULL; +static size_t *length_p; + +static int raw_sk = -1; +static int last_ttl = 0; +static int seq = 0; + + +static int set_protocol (CLIF_option *optn, char *arg) { + UNUSED(optn); + char *q; + + protocol = strtoul (arg, &q, 0); + if (q == arg) { + struct protoent *p = getprotobyname (arg); + + if (!p) return -1; + protocol = p->p_proto; + } + + return 0; +} + + +static CLIF_option raw_options[] = { + { 0, "protocol", "PROT", "Use protocol %s (default is " + _TEXT (DEF_RAW_PROT) ")", + set_protocol, 0, 0, CLIF_ABBREV }, + CLIF_END_OPTION +}; + + +static int raw_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int i; + int af = dest->sa.sa_family; + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; + + if (port_seq) protocol = port_seq; + + + length_p = packet_len_p; + + if (*length_p && + !(data = malloc (*length_p)) + ) error ("malloc"); + + for (i = 0; i < (int)*length_p; i++) + data[i] = 0x40 + (i & 0x3f); + + + raw_sk = socket (af, SOCK_RAW, protocol); + if (raw_sk < 0) + error_or_perm ("socket"); + + tune_socket (raw_sk); + + /* Don't want to catch packets from another hosts */ + if (raw_can_connect () && + connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0 + ) error ("connect"); + + use_recverr (raw_sk); + + + add_poll (raw_sk, POLLIN | POLLERR); + + return 0; +} + + +static void raw_send_probe (probe *pb, int ttl) { + + if (ttl != last_ttl) { + + set_ttl (raw_sk, ttl); + + last_ttl = ttl; + } + + + pb->send_time = get_time (); + + if (do_send (raw_sk, data, *length_p, &dest_addr) < 0) { + pb->send_time = 0; + return; + } + + + pb->seq = ++seq; + + return; +} + + +static probe *raw_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + UNUSED(sk); + UNUSED(len); + UNUSED(buf); + probe *pb; + + if (!equal_addr (&dest_addr, from)) + return NULL; + + pb = probe_by_seq (seq); + if (!pb) return NULL; + + if (!err) pb->final = 1; + + return pb; +} + + +static void raw_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), raw_check_reply); +} + + +static void raw_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module raw_ops = { + .name = "raw", + .init = raw_init, + .send_probe = raw_send_probe, + .recv_probe = raw_recv_probe, + .expire_probe = raw_expire_probe, + .options = raw_options, + .one_per_time = 1, +}; + +TR_MODULE (raw_ops); + +void tr_module_raw_insert() +{ + +} diff --git a/iputils/traceroute/mod-tcp.c b/iputils/traceroute/mod-tcp.c new file mode 100755 index 0000000000000000000000000000000000000000..32d2f1027e88db3ff623150cfcca5b7aad7ccf8d --- /dev/null +++ b/iputils/traceroute/mod-tcp.c @@ -0,0 +1,515 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/tcp.h> + + +#include "traceroute.h" + + +#ifndef IP_MTU +#define IP_MTU 14 +#endif + + +static sockaddr_any dest_addr = {{ 0, }, }; +static unsigned int dest_port = 0; + +static int raw_sk = -1; +static int last_ttl = 0; + +static uint8_t buf[1024]; /* enough, enough... */ +static size_t csum_len = 0; +static struct tcphdr *th = NULL; + +#define TH_FLAGS(TH) (((uint8_t *) (TH))[13]) +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#define TH_ECE 0x40 +#define TH_CWR 0x80 + + +static int flags = 0; /* & 0xff == tcp_flags ... */ +static int sysctl = 0; +static int reuse = 0; +static unsigned int mss = 0; +static int info = 0; + +#define FL_FLAGS 0x0100 +#define FL_ECN 0x0200 +#define FL_SACK 0x0400 +#define FL_TSTAMP 0x0800 +#define FL_WSCALE 0x1000 + + +static struct { + const char *name; + unsigned int flag; +} tcp_flags[] = { + { "fin", TH_FIN }, + { "syn", TH_SYN }, + { "rst", TH_RST }, + { "psh", TH_PSH }, + { "ack", TH_ACK }, + { "urg", TH_URG }, + { "ece", TH_ECE }, + { "cwr", TH_CWR }, +}; + +static char *names_by_flags (unsigned int flags) { + int i; + char str[64]; /* enough... */ + char *curr = str; + char *end = str + sizeof (str) / sizeof (*str); + + for (i = 0; i < (int)(sizeof (tcp_flags) / sizeof (*tcp_flags)); i++) { + const char *p; + + if (!(flags & tcp_flags[i].flag)) continue; + + if (curr > str && curr < end) *curr++ = ','; + for (p = tcp_flags[i].name; *p && curr < end; *curr++ = *p++) ; + } + + *curr = '\0'; + + return strdup (str); +} + +static int set_tcp_flag (CLIF_option *optn, char *arg) { + UNUSED(arg); + int i; + + for (i = 0; i < (int)(sizeof (tcp_flags) / sizeof (*tcp_flags)); i++) { + if (!strcmp (optn->long_opt, tcp_flags[i].name)) { + flags |= tcp_flags[i].flag; + return 0; + } + } + + return -1; +} + +static int set_tcp_flags (CLIF_option *optn, char *arg) { + UNUSED(optn); + char *q; + unsigned long value; + + value = strtoul (arg, &q, 0); + if (q == arg) return -1; + + flags = (flags & ~0xff) | (value & 0xff) | FL_FLAGS; + return 0; +} + +static int set_flag (CLIF_option *optn, char *arg) { + UNUSED(arg); + flags |= (unsigned long) optn->data; + + return 0; +} + +static CLIF_option tcp_options[] = { + { 0, "syn", 0, "Set tcp flag SYN (default if no other " + "tcp flags specified)", set_tcp_flag, 0, 0, 0 }, + { 0, "ack", 0, "Set tcp flag ACK,", set_tcp_flag, 0, 0, 0 }, + { 0, "fin", 0, "FIN,", set_tcp_flag, 0, 0, 0 }, + { 0, "rst", 0, "RST,", set_tcp_flag, 0, 0, 0 }, + { 0, "psh", 0, "PSH,", set_tcp_flag, 0, 0, 0 }, + { 0, "urg", 0, "URG,", set_tcp_flag, 0, 0, 0 }, + { 0, "ece", 0, "ECE,", set_tcp_flag, 0, 0, 0 }, + { 0, "cwr", 0, "CWR", set_tcp_flag, 0, 0, 0 }, + { 0, "flags", "NUM", "Set tcp flags exactly to value %s", + set_tcp_flags, 0, 0, CLIF_ABBREV }, + { 0, "ecn", 0, "Send syn packet with tcp flags ECE and CWR " + "(for Explicit Congestion Notification, rfc3168)", + set_flag, (void *) FL_ECN, 0, 0 }, + { 0, "sack", 0, "Use sack,", + set_flag, (void *) FL_SACK, 0, 0 }, + { 0, "timestamps", 0, "timestamps,", + set_flag, (void *) FL_TSTAMP, 0, CLIF_ABBREV }, + { 0, "window_scaling", 0, "window_scaling option for tcp", + set_flag, (void *) FL_WSCALE, 0, CLIF_ABBREV }, + { 0, "sysctl", 0, "Use current sysctl (/proc/sys/net/*) setting " + "for the tcp options and ecn. Always set by default " + "(with \"syn\") if nothing else specified", + CLIF_set_flag, &sysctl, 0, 0 }, + { 0, "reuse", 0, "Allow to reuse local port numbers " + "for the huge workloads (SO_REUSEADDR)", + CLIF_set_flag, &reuse, 0, 0 }, + { 0, "mss", "NUM", "Use value of %s for maxseg tcp option (when syn)", + CLIF_set_uint, &mss, 0, 0 }, + { 0, "info", 0, "Print tcp flags of final tcp replies when target " + "host is reached. Useful to determine whether " + "an application listens the port etc.", + CLIF_set_flag, &info, 0, 0 }, + CLIF_END_OPTION +}; + + +#define SYSCTL_PREFIX "/proc/sys/net/ipv4/tcp_" +static int check_sysctl (const char *name) { + int fd, res; + char buf[sizeof (SYSCTL_PREFIX) + strlen (name) + 1]; + uint8_t ch; + + strcpy (buf, SYSCTL_PREFIX); + strcat (buf, name); + + fd = open (buf, O_RDONLY, 0); + if (fd < 0) return 0; + + res = read (fd, &ch, sizeof (ch)); + close (fd); + + if (res != sizeof (ch)) + return 0; + + /* since kernel 2.6.31 "tcp_ecn" can have value of '2'... */ + if (ch == '1') return 1; + + return 0; +} + + +static int tcp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int af = dest->sa.sa_family; + sockaddr_any src; + int mtu; + socklen_t len; + uint8_t *ptr; + uint16_t *lenp; + + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; /* raw sockets can be confused */ + + if (!port_seq) port_seq = DEF_TCP_PORT; + dest_port = htons (port_seq); + + + /* Create raw socket for tcp */ + + raw_sk = socket (af, SOCK_RAW, IPPROTO_TCP); + if (raw_sk < 0) + error_or_perm ("socket"); + + tune_socket (raw_sk); /* including bind, if any */ + + if (connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0) + error ("connect"); + + len = sizeof (src); + if (getsockname (raw_sk, &src.sa, &len) < 0) + error ("getsockname"); + + + len = sizeof (mtu); + if (getsockopt (raw_sk, af == AF_INET ? SOL_IP : SOL_IPV6, + af == AF_INET ? IP_MTU : IPV6_MTU, + &mtu, &len) < 0 || mtu < 576 + ) mtu = 576; + + /* mss = mtu - headers */ + mtu -= af == AF_INET ? sizeof (struct iphdr) : sizeof (struct ip6_hdr); + mtu -= sizeof (struct tcphdr); + + + if (!raw_can_connect ()) { /* work-around for buggy kernels */ + close (raw_sk); + raw_sk = socket (af, SOCK_RAW, IPPROTO_TCP); + if (raw_sk < 0) error ("socket"); + tune_socket (raw_sk); + /* but do not connect it... */ + } + + + use_recverr (raw_sk); + + add_poll (raw_sk, POLLIN | POLLERR); + + + /* Now create the sample packet. */ + + if (!flags) sysctl = 1; + + if (sysctl) { + if (check_sysctl ("ecn")) flags |= FL_ECN; + if (check_sysctl ("sack")) flags |= FL_SACK; + if (check_sysctl ("timestamps")) flags |= FL_TSTAMP; + if (check_sysctl ("window_scaling")) flags |= FL_WSCALE; + } + + if (!(flags & (FL_FLAGS | 0xff))) { /* no any tcp flag set */ + flags |= TH_SYN; + if (flags & FL_ECN) + flags |= TH_ECE | TH_CWR; + } + + + /* For easy checksum computing: + saddr + daddr + length + protocol + tcphdr + tcpoptions + */ + + ptr = buf; + + if (af == AF_INET) { + len = sizeof (src.sin.sin_addr); + memcpy (ptr, &src.sin.sin_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin.sin_addr, len); + ptr += len; + } else { + len = sizeof (src.sin6.sin6_addr); + memcpy (ptr, &src.sin6.sin6_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin6.sin6_addr, len); + ptr += len; + } + + lenp = (uint16_t *) ptr; + ptr += sizeof (uint16_t); + *((uint16_t *) ptr) = htons ((uint16_t) IPPROTO_TCP); + ptr += sizeof (uint16_t); + + + /* Construct TCP header */ + + th = (struct tcphdr *) ptr; + + th->source = 0; /* temporary */ + th->dest = dest_port; + th->seq = 0; /* temporary */ + th->ack_seq = 0; + th->doff = 0; /* later... */ + TH_FLAGS(th) = flags & 0xff; + th->window = htons (4 * mtu); + th->check = 0; + th->urg_ptr = 0; + + + /* Build TCP options */ + + ptr = (uint8_t *) (th + 1); + + if (flags & TH_SYN) { + *ptr++ = TCPOPT_MAXSEG; /* 2 */ + *ptr++ = TCPOLEN_MAXSEG; /* 4 */ + *((uint16_t *) ptr) = htons (mss ? mss : (unsigned int)mtu); + ptr += sizeof (uint16_t); + } + + if (flags & FL_TSTAMP) { + + if (flags & FL_SACK) { + *ptr++ = TCPOPT_SACK_PERMITTED; /* 4 */ + *ptr++ = TCPOLEN_SACK_PERMITTED;/* 2 */ + } else { + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_NOP; /* 1 */ + } + *ptr++ = TCPOPT_TIMESTAMP; /* 8 */ + *ptr++ = TCPOLEN_TIMESTAMP; /* 10 */ + + *((uint32_t *) ptr) = random_seq (); /* really! */ + ptr += sizeof (uint32_t); + *((uint32_t *) ptr) = (flags & TH_ACK) ? random_seq () : 0; + ptr += sizeof (uint32_t); + } + else if (flags & FL_SACK) { + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_SACK_PERMITTED; /* 4 */ + *ptr++ = TCPOLEN_SACK_PERMITTED; /* 2 */ + } + + if (flags & FL_WSCALE) { + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_WINDOW; /* 3 */ + *ptr++ = TCPOLEN_WINDOW; /* 3 */ + *ptr++ = 2; /* assume some corect value... */ + } + + + csum_len = ptr - buf; + + if (csum_len > sizeof (buf)) + error ("impossible"); /* paranoia */ + + len = ptr - (uint8_t *) th; + if (len & 0x03) error ("impossible"); /* as >>2 ... */ + + *lenp = htons (len); + th->doff = len >> 2; + + + *packet_len_p = len; + + return 0; +} + + +static void tcp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + sockaddr_any addr; + socklen_t len = sizeof (addr); + + + /* To make sure we have chosen a free unused "source port", + just create, (auto)bind and hold a socket while the port is needed. + */ + + sk = socket (af, SOCK_STREAM, 0); + if (sk < 0) error ("socket"); + + if (reuse && setsockopt (sk, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) + error ("setsockopt SO_REUSEADDR"); + + bind_socket (sk); + + if (getsockname (sk, &addr.sa, &len) < 0) + error ("getsockname"); + + /* When we reach the target host, it can send us either RST or SYN+ACK. + For RST all is OK (we and kernel just answer nothing), but + for SYN+ACK we should reply with our RST. + It is well-known "half-open technique", used by port scanners etc. + This way we do not touch remote applications at all, unlike + the ordinary connect(2) call. + As the port-holding socket neither connect() nor listen(), + it means "no such port yet" for remote ends, and kernel always + send RST in such a situation automatically (we have to do nothing). + */ + + + th->source = addr.sin.sin_port; + + th->seq = random_seq (); + + th->check = 0; + th->check = in_csum (buf, csum_len); + + + if (ttl != last_ttl) { + + set_ttl (raw_sk, ttl); + + last_ttl = ttl; + } + + + pb->send_time = get_time (); + + if (do_send (raw_sk, th, th->doff << 2, &dest_addr) < 0) { + close (sk); + pb->send_time = 0; + return; + } + + + pb->seq = th->source; + + pb->sk = sk; + + return; +} + + +static probe *tcp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + UNUSED(sk); + probe *pb; + struct tcphdr *tcp = (struct tcphdr *) buf; + uint16_t sport, dport; + + + if (len < 8) return NULL; /* too short */ + + + if (err) { + sport = tcp->source; + dport = tcp->dest; + } else { + sport = tcp->dest; + dport = tcp->source; + } + + + if (dport != dest_port) + return NULL; + + if (!equal_addr (&dest_addr, from)) + return NULL; + + pb = probe_by_seq (sport); + if (!pb) return NULL; + + + if (!err) { + + pb->final = 1; + + if (info) + pb->ext = names_by_flags (TH_FLAGS(tcp)); + } + + return pb; +} + + +static void tcp_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), tcp_check_reply); +} + + +static void tcp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module tcp_ops = { + .name = "tcp", + .init = tcp_init, + .send_probe = tcp_send_probe, + .recv_probe = tcp_recv_probe, + .expire_probe = tcp_expire_probe, + .options = tcp_options, +}; + +TR_MODULE (tcp_ops); + +void tr_module_tcp_insert() +{ + +} diff --git a/iputils/traceroute/mod-tcpconn.c b/iputils/traceroute/mod-tcpconn.c new file mode 100755 index 0000000000000000000000000000000000000000..13e239864756de3639330189389933df570e8bf6 --- /dev/null +++ b/iputils/traceroute/mod-tcpconn.c @@ -0,0 +1,237 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/tcp.h> +#include <errno.h> + +#include "traceroute.h" + + +static sockaddr_any dest_addr = {{ 0, }, }; + +static int icmp_sk = -1; + + +static int tcp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + UNUSED(packet_len_p); + int af = dest->sa.sa_family; + + dest_addr = *dest; + dest_addr.sin.sin_port = htons (DEF_TCP_PORT); + + if (port_seq) + dest_addr.sin.sin_port = htons (port_seq); + + + /* Currently an ICMP socket is the only way + to obtain the needed info... + */ + icmp_sk = socket (af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP + : IPPROTO_ICMPV6); + if (icmp_sk < 0) + error_or_perm ("socket"); + + /* icmp_sk not need full tune_socket() here, just a receiving one */ + bind_socket (icmp_sk); + use_timestamp (icmp_sk); + use_recv_ttl (icmp_sk); + + add_poll (icmp_sk, POLLIN); + + return 0; +} + + +static void tcp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + sockaddr_any addr; + socklen_t length = sizeof (addr); + + + sk = socket (af, SOCK_STREAM, 0); + if (sk < 0) error ("socket"); + + tune_socket (sk); /* common stuff */ + + set_ttl (sk, ttl); + + + pb->send_time = get_time (); + + if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) { + if (errno != EINPROGRESS) + error ("connect"); + } + + + if (getsockname (sk, &addr.sa, &length) < 0) + error ("getsockname"); + + pb->seq = addr.sin.sin_port; /* both ipv4/ipv6 */ + + pb->sk = sk; + + add_poll (sk, POLLERR | POLLHUP | POLLOUT); + + return; +} + + +static probe *tcp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + UNUSED(sk); + UNUSED(err); + UNUSED(from); + int af = dest_addr.sa.sa_family; + int type, code, info; + probe *pb; + struct tcphdr *tcp; + + + if (len < sizeof (struct icmphdr)) + return NULL; + + + if (af == AF_INET) { + struct icmp *icmp = (struct icmp *) buf; + struct iphdr *ip; + int hlen; + + type = icmp->icmp_type; + code = icmp->icmp_code; + info = icmp->icmp_void; + + if (type != ICMP_TIME_EXCEEDED && type != ICMP_DEST_UNREACH) + return NULL; + + if (len < sizeof (struct icmphdr) + sizeof (struct iphdr) + 8) + /* `8' - rfc1122: 3.2.2 */ + return NULL; + + ip = (struct iphdr *) (((char *)icmp) + sizeof(struct icmphdr)); + hlen = ip->ihl << 2; + + if (len < sizeof (struct icmphdr) + hlen + 8) + return NULL; + if (ip->protocol != IPPROTO_TCP) + return NULL; + + tcp = (struct tcphdr *) (((char *) ip) + hlen); + + } + else { /* AF_INET6 */ + struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) buf; + struct ip6_hdr *ip6; + + type = icmp6->icmp6_type; + code = icmp6->icmp6_code; + info = icmp6->icmp6_mtu; + + if (type != ICMP6_TIME_EXCEEDED && + type != ICMP6_DST_UNREACH && + type != ICMP6_PACKET_TOO_BIG + ) return NULL; + + if (len < sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr) + 8) + return NULL; + + ip6 = (struct ip6_hdr *) (icmp6 + 1); + if (ip6->ip6_nxt != IPPROTO_TCP) + return NULL; + + tcp = (struct tcphdr *) (ip6 + 1); + + } + + + if (tcp->dest != dest_addr.sin.sin_port) + return NULL; + + pb = probe_by_seq (tcp->source); + if (!pb) return NULL; + + + /* here only, high level has no data to do this */ + parse_icmp_res (pb, type, code, info); + + return pb; +} + + +static void tcp_recv_probe (int sk, int revents) { + + if (sk != icmp_sk) { /* a tcp socket */ + probe *pb; + + pb = probe_by_sk (sk); + if (!pb) { + del_poll (sk); + return; + } + + + /* do connect() again and check errno, regardless of revents */ + if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) { + if (errno != EISCONN && errno != ECONNREFUSED) + return; /* ICMP say more */ + } + + /* we have reached the dest host (either connected or refused) */ + + memcpy (&pb->res, &dest_addr, sizeof (pb->res)); + + pb->final = 1; + + pb->recv_time = get_time (); + + probe_done (pb); + + return; + } + + + /* ICMP stuff */ + + if (!(revents & POLLIN)) + return; + + recv_reply (icmp_sk, 0, tcp_check_reply); +} + + +static void tcp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module tcp_ops = { + .name = "tcpconn", + .init = tcp_init, + .send_probe = tcp_send_probe, + .recv_probe = tcp_recv_probe, + .expire_probe = tcp_expire_probe, +}; + +TR_MODULE (tcp_ops); + +void tr_module_tcpconn_insert() +{ + +} diff --git a/iputils/traceroute/mod-udp.c b/iputils/traceroute/mod-udp.c new file mode 100755 index 0000000000000000000000000000000000000000..e24d0271c8c6e60f40592f0be18e3e7c7b482379 --- /dev/null +++ b/iputils/traceroute/mod-udp.c @@ -0,0 +1,241 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/in.h> +#include <netinet/udp.h> + +#include "traceroute.h" + + +#ifndef IPPROTO_UDPLITE +#define IPPROTO_UDPLITE 136 +#endif + +#ifndef UDPLITE_SEND_CSCOV +#define UDPLITE_SEND_CSCOV 10 +#define UDPLITE_RECV_CSCOV 11 +#endif + + +static sockaddr_any dest_addr = {{ 0, }, }; +static unsigned int curr_port = 0; +static unsigned int protocol = IPPROTO_UDP; + + +static char *data = NULL; +static size_t *length_p; + +static void fill_data (size_t *packet_len_p) { + int i; + + length_p = packet_len_p; + + if (*length_p && + !(data = malloc (*length_p)) + ) error ("malloc"); + + for (i = 0; i < (int)*length_p; i++) + data[i] = 0x40 + (i & 0x3f); + + return; +} + + +static int udp_default_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + + curr_port = port_seq ? port_seq : DEF_START_PORT; + + dest_addr = *dest; + dest_addr.sin.sin_port = htons (curr_port); + + fill_data (packet_len_p); + + return 0; +} + + +static int udp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + + dest_addr = *dest; + + if (!port_seq) port_seq = DEF_UDP_PORT; + dest_addr.sin.sin_port = htons ((uint16_t) port_seq); + + fill_data (packet_len_p); + + return 0; +} + + +static unsigned int coverage = 0; +#define MIN_COVERAGE (sizeof (struct udphdr)) + +static void set_coverage (int sk) { + int val = MIN_COVERAGE; + + if (setsockopt (sk, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, + &coverage, sizeof (coverage)) < 0 + ) error ("UDPLITE_SEND_CSCOV"); + + if (setsockopt (sk, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, + &val, sizeof (val)) < 0 + ) error ("UDPLITE_RECV_CSCOV"); +} + +static CLIF_option udplite_options[] = { + { 0, "coverage", "NUM", "Set udplite send coverage to %s (default is " + _TEXT(MIN_COVERAGE) ")", + CLIF_set_uint, &coverage, 0, CLIF_ABBREV }, + CLIF_END_OPTION +}; + +static int udplite_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + + dest_addr = *dest; + + if (!port_seq) port_seq = DEF_UDP_PORT; /* XXX: Hmmm... */ + dest_addr.sin.sin_port = htons ((uint16_t) port_seq); + + protocol = IPPROTO_UDPLITE; + + if (!coverage) coverage = MIN_COVERAGE; + + fill_data (packet_len_p); + + return 0; +} + + +static void udp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + + + sk = socket (af, SOCK_DGRAM, protocol); + if (sk < 0) error ("socket"); + + tune_socket (sk); /* common stuff */ + + if (coverage) set_coverage (sk); /* udplite case */ + + set_ttl (sk, ttl); + + + if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) + error ("connect"); + + use_recverr (sk); + + + pb->send_time = get_time (); + + if (do_send (sk, data, *length_p, NULL) < 0) { + close (sk); + pb->send_time = 0; + return; + } + + + pb->sk = sk; + + add_poll (sk, POLLIN | POLLERR); + + pb->seq = dest_addr.sin.sin_port; + + if (curr_port) { /* traditional udp method */ + curr_port++; + dest_addr.sin.sin_port = htons (curr_port); /* both ipv4 and ipv6 */ + } + + return; +} + + +static probe *udp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + UNUSED(buf); + UNUSED(len); + probe *pb; + + pb = probe_by_sk (sk); + if (!pb) return NULL; + + if (pb->seq != from->sin.sin_port) + return NULL; + + if (!err) pb->final = 1; + + return pb; +} + + +static void udp_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), udp_check_reply); +} + + +static void udp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +/* All three modules share the same methods except the init... */ + +static tr_module default_ops = { + .name = "default", + .init = udp_default_init, + .send_probe = udp_send_probe, + .recv_probe = udp_recv_probe, + .expire_probe = udp_expire_probe, + .header_len = sizeof (struct udphdr), +}; + +TR_MODULE (default_ops); + + +static tr_module udp_ops = { + .name = "udp", + .init = udp_init, + .send_probe = udp_send_probe, + .recv_probe = udp_recv_probe, + .expire_probe = udp_expire_probe, + .header_len = sizeof (struct udphdr), +}; + +TR_MODULE (udp_ops); + + +static tr_module udplite_ops = { + .name = "udplite", + .init = udplite_init, + .send_probe = udp_send_probe, + .recv_probe = udp_recv_probe, + .expire_probe = udp_expire_probe, + .header_len = sizeof (struct udphdr), + .options = udplite_options, +}; + +TR_MODULE (udplite_ops); + +void tr_module_udp_insert() +{ + +} diff --git a/iputils/traceroute/module.c b/iputils/traceroute/module.c new file mode 100755 index 0000000000000000000000000000000000000000..f8f228ef635758a62b036a87986cdfae27f210bb --- /dev/null +++ b/iputils/traceroute/module.c @@ -0,0 +1,51 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> + +#include "traceroute.h" + +void tr_module_icmp_insert(); +void tr_module_udp_insert(); +void tr_module_tcp_insert(); +void tr_module_tcpconn_insert(); +void tr_module_raw_insert(); +void tr_module_dccp_insert(); + +static tr_module *base = NULL; + +void tr_register_module(tr_module *ops) { + + ops->next = base; + base = ops; +// printf("tr_register_module name=%s\n", ops->name); +} + +const tr_module *tr_get_module(const char *name) { + const tr_module *ops; + + tr_module_icmp_insert(); + tr_module_udp_insert(); + tr_module_tcp_insert(); + tr_module_tcpconn_insert(); + tr_module_raw_insert(); + tr_module_dccp_insert(); + + if(!name) + return 0; + + for(ops = base; ops; ops = ops->next) { + if(!strcasecmp(name, ops->name)) + return ops; + } + + return NULL; +} diff --git a/iputils/traceroute/poll.c b/iputils/traceroute/poll.c new file mode 100755 index 0000000000000000000000000000000000000000..232a83395bc600ab54bddb1d8ea7c98e3ceb8526 --- /dev/null +++ b/iputils/traceroute/poll.c @@ -0,0 +1,93 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <poll.h> +#include <errno.h> +#include <math.h> + +#include "traceroute.h" + + +static struct pollfd *pfd = NULL; +static unsigned int num_polls = 0; + + +void add_poll (int fd, int events) { + int i; + + for (i = 0; i < (int)num_polls && pfd[i].fd > 0; i++) ; + + if (i == (int)num_polls) { + pfd = realloc (pfd, ++num_polls * sizeof (*pfd)); + if (!pfd) error ("realloc"); + } + + pfd[i].fd = fd; + pfd[i].events = events; +} + + +void del_poll (int fd) { + int i; + + for (i = 0; i < (int)num_polls && pfd[i].fd != fd; i++) ; + + if (i < (int)num_polls) pfd[i].fd = -1; /* or just zero it... */ +} + + +static int cleanup_polls (void) { + int i; + + for (i = 0; i < (int)num_polls && pfd[i].fd > 0; i++) ; + + if (i < (int)num_polls) { /* a hole have found */ + int j; + + for (j = i + 1; j < (int)num_polls; j++) { + if (pfd[j].fd > 0) { + pfd[i++] = pfd[j]; + pfd[j].fd = -1; + } + } + } + + return i; +} + + +void do_poll (double timeout, void (*callback) (int fd, int revents)) { + int nfds; + int msecs = ceil (timeout * 1000); + + while ((nfds = cleanup_polls ()) > 0) { + int i, n; + + n = poll (pfd, nfds, msecs); + + if (n <= 0) { + if (n == 0 || errno == EINTR) + return; + error ("poll"); + } + + for (i = 0; n && i < (int)num_polls; i++) { + if (pfd[i].revents) { + callback (pfd[i].fd, pfd[i].revents); + n--; + } + } + + msecs = 0; /* no more wait, just eat all the pending */ + } + + return; +} + diff --git a/iputils/traceroute/random.c b/iputils/traceroute/random.c new file mode 100755 index 0000000000000000000000000000000000000000..5a8f911e1463b797559b263b5d9cd7081a82a428 --- /dev/null +++ b/iputils/traceroute/random.c @@ -0,0 +1,28 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/times.h> + +#include "traceroute.h" + + +static void __init_random_seq (void) __attribute__ ((constructor)); +static void __init_random_seq (void) { + + srand (times (NULL) + getpid ()); +} + + +unsigned int random_seq (void) { + + /* To not worry about RANDOM_MAX and precision... */ + return (rand () << 16) ^ (rand () << 8) ^ rand () ^ (rand () >> 8); +} + diff --git a/iputils/traceroute/time.c b/iputils/traceroute/time.c new file mode 100755 index 0000000000000000000000000000000000000000..16ea82483cc3483ada3a62c81c8436f820667191 --- /dev/null +++ b/iputils/traceroute/time.c @@ -0,0 +1,27 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/time.h> + +#include "traceroute.h" + + +/* Just returns current time as double, with most possible precision... */ + +double get_time (void) { + struct timeval tv; + double d; + + gettimeofday (&tv, NULL); + + d = ((double) tv.tv_usec) / 1000000. + (unsigned long) tv.tv_sec; + + return d; +} diff --git a/iputils/traceroute/traceroute.c b/iputils/traceroute/traceroute.c new file mode 100755 index 0000000000000000000000000000000000000000..0142e70948b7c7ffb9645ed39decf915ee7346ae --- /dev/null +++ b/iputils/traceroute/traceroute.c @@ -0,0 +1,1754 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <netdb.h> +#include <errno.h> +#include <locale.h> +#include <sys/utsname.h> +#include <linux/types.h> +#include <linux/errqueue.h> + +/* XXX: Remove this when things will be defined properly in netinet/ ... */ +#include "flowlabel.h" + +#include <clif.h> +//#include "version.h" +#include <stdbool.h> +#include <glib.h> +#include "traceroute.h" + + +#ifndef ICMP6_DST_UNREACH_BEYONDSCOPE +#ifdef ICMP6_DST_UNREACH_NOTNEIGHBOR +#define ICMP6_DST_UNREACH_BEYONDSCOPE ICMP6_DST_UNREACH_NOTNEIGHBOR +#else +#define ICMP6_DST_UNREACH_BEYONDSCOPE 2 +#endif +#endif + +#ifndef IPV6_RECVHOPLIMIT +#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT +#endif + +#ifndef IP_PMTUDISC_PROBE +#define IP_PMTUDISC_PROBE 3 +#endif + +#ifndef IPV6_PMTUDISC_PROBE +#define IPV6_PMTUDISC_PROBE 3 +#endif + +#ifndef AI_IDN +#define AI_IDN 0 +#endif + +#ifndef NI_IDN +#define NI_IDN 0 +#endif + +#define MAX_HOPS 255 +#define MAX_PROBES 10 +#define MAX_GATEWAYS_4 8 +#define MAX_GATEWAYS_6 127 +#define DEF_HOPS 30 +#define DEF_SIM_PROBES 16 /* including several hops */ +#define DEF_NUM_PROBES 3 //1 +#define DEF_WAIT_SECS 5.0 +#define DEF_HERE_FACTOR 3 +#define DEF_NEAR_FACTOR 10 +#ifndef DEF_WAIT_PREC +#define DEF_WAIT_PREC 0.001 /* +1 ms to avoid precision issues */ +#endif +#define DEF_SEND_SECS 0 +#define DEF_DATA_LEN 40 /* all but IP header... */ +#define MAX_PACKET_LEN 65000 +#ifndef DEF_AF +#define DEF_AF AF_INET +#endif + +#define ttl2hops(X) (((X) <= 64 ? 65 : ((X) <= 128 ? 129 : 256)) - (X)) + +static char version_string[] = "Modern traceroute for Linux, " + "version " _TEXT(VERSION) +"\nCopyright (c) 2016 Dmitry Butskoy, " +" License: GPL v2 or any later"; +static int debug = 0; +static unsigned int first_hop = 1; +static unsigned int max_hops = DEF_HOPS; +static unsigned int sim_probes = DEF_SIM_PROBES; +static unsigned int probes_per_hop = DEF_NUM_PROBES; + +static char **gateways = NULL; +static int num_gateways = 0; +static unsigned char *rtbuf = NULL; +static size_t rtbuf_len = 0; +static unsigned int ipv6_rthdr_type = 2; /* IPV6_RTHDR_TYPE_2 */ + +static size_t header_len = 0; +static size_t data_len = 0; + +static int dontfrag = 0; +static int noresolve = 0; +static int extension = 0; +static int as_lookups = 0; +static unsigned int dst_port_seq = 0; +static unsigned int tos = 0; +static unsigned int flow_label = 0; +static int noroute = 0; +static unsigned int fwmark = 0; +static int packet_len = -1; +static double wait_secs = DEF_WAIT_SECS; +static double here_factor = DEF_HERE_FACTOR; +static double near_factor = DEF_NEAR_FACTOR; +static double send_secs = DEF_SEND_SECS; +static int mtudisc = 0; +static int backward = 0; + +static sockaddr_any dst_addr = { { 0, }, }; +static char *dst_name = NULL; +static char *device = NULL; +static sockaddr_any src_addr = { { 0, }, }; +static unsigned int src_port = 0; + +static const char *module = "default"; +static const tr_module *ops = NULL; + +static char *opts[16] = { NULL, }; /* assume enough */ +static unsigned int opts_idx = 1; /* first one reserved... */ + +static int af = 0; + +static probe *probes = NULL; +static unsigned int num_probes = 0; + +static void ex_error(const char *format, ...) { + va_list ap; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + + fprintf(stderr, "\n"); + + //exit(2); +} + +void error(const char *str) { + + fprintf(stderr, "\n"); + + perror(str); + + exit(1); +} + +void error_or_perm(const char *str) { + + if(errno == EPERM) + fprintf(stderr, "You do not have enough privileges to use " + "this traceroute method."); + error(str); +} + +/* Set initial parameters according to how we was called */ + +static void check_progname(const char *name) { + const char *p; + int l; + + p = strrchr(name, '/'); + if(p) + p++; + else + p = name; + + l = strlen(p); + if(l <= 0) + return; + l--; + + if(p[l] == '6') + af = AF_INET6; + else if(p[l] == '4') + af = AF_INET; + + if(!strncmp(p, "tcp", 3)) + module = "tcp"; + if(!strncmp(p, "tracert", 7)) + module = "icmp"; + + return; +} + +static int getaddr(const char *name, sockaddr_any *addr) { + int ret; + struct addrinfo hints, *ai, *res = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = af; + hints.ai_flags = AI_IDN; + + ret = getaddrinfo(name, NULL, &hints, &res); + if(ret) { + fprintf(stderr, "%s: %s\n", name, gai_strerror(ret)); + return -1; + } + + for(ai = res; ai; ai = ai->ai_next) { + if(ai->ai_family == af) + break; + /* when af not specified, choose DEF_AF if present */ + if(!af && ai->ai_family == DEF_AF) + break; + } + if(!ai) + ai = res; /* anything... */ + + if(ai->ai_addrlen > sizeof(*addr)) + return -1; /* paranoia */ + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + + freeaddrinfo(res); + + return 0; +} + +static void make_fd_used(int fd) { + int nfd; + + if(fcntl(fd, F_GETFL) != -1) + return; + + if(errno != EBADF) + error("fcntl F_GETFL"); + + nfd = open("/dev/null", O_RDONLY); + if(nfd < 0) + error("open /dev/null"); + + if(nfd != fd) { + dup2(nfd, fd); + close(nfd); + } + + return; +} + +static char addr2str_buf[INET6_ADDRSTRLEN]; + +static const char *addr2str(const sockaddr_any *addr) { + + getnameinfo(&addr->sa, sizeof(*addr), + addr2str_buf, sizeof(addr2str_buf), 0, 0, NI_NUMERICHOST); + + return addr2str_buf; +} + +/* IP options stuff */ + +static int init_ip_options(void) { + sockaddr_any *gates; + int i, max; + + if(!num_gateways) + return 0; + + /* check for TYPE,ADDR,ADDR... form */ + if(af == AF_INET6 && num_gateways > 1 && gateways[0]) { + char *q; + unsigned int value = strtoul(gateways[0], &q, 0); + + if(!*q) { + ipv6_rthdr_type = value; + num_gateways--; + for(i = 0; i < num_gateways; i++) + gateways[i] = gateways[i + 1]; + } + } + + max = af == AF_INET ? MAX_GATEWAYS_4 : MAX_GATEWAYS_6; + if(num_gateways > max) { + ex_error("Too many gateways specified. No more than %d", max); + return -1; + } + + gates = alloca(num_gateways * sizeof(*gates)); + + for(i = 0; i < num_gateways; i++) { + + if(!gateways[i]) + error("strdup"); + + if(getaddr(gateways[i], &gates[i]) < 0) { + ex_error(""); /* already reported */ + return -1; + } + if(gates[i].sa.sa_family != af) { + ex_error("IP versions mismatch in gateway addresses"); + return -1; + } + + free(gateways[i]); + } + + free(gateways); + gateways = NULL; + + if(af == AF_INET) { + struct in_addr *in; + + rtbuf_len = 4 + (num_gateways + 1) * sizeof(*in); + rtbuf = malloc(rtbuf_len); + if(!rtbuf) + error("malloc"); + + in = (struct in_addr *) &rtbuf[4]; + for(i = 0; i < num_gateways; i++) + memcpy(&in[i], &gates[i].sin.sin_addr, sizeof(*in)); + /* final hop */ + memcpy(&in[i], &dst_addr.sin.sin_addr, sizeof(*in)); + i++; + + rtbuf[0] = IPOPT_NOP; + rtbuf[1] = IPOPT_LSRR; + rtbuf[2] = (i * sizeof(*in)) + 3; + rtbuf[3] = IPOPT_MINOFF; + + } + else if(af == AF_INET6) { + struct in6_addr *in6; + struct ip6_rthdr *rth; + + /* IPV6_RTHDR_TYPE_0 length is 8 */ + rtbuf_len = 8 + num_gateways * sizeof(*in6); + rtbuf = malloc(rtbuf_len); + if(!rtbuf) + error("malloc"); + + rth = (struct ip6_rthdr *) rtbuf; + rth->ip6r_nxt = 0; + rth->ip6r_len = 2 * num_gateways; + rth->ip6r_type = ipv6_rthdr_type; + rth->ip6r_segleft = num_gateways; + + *((uint32_t *) (rth + 1)) = 0; + + in6 = (struct in6_addr *) (rtbuf + 8); + for(i = 0; i < num_gateways; i++) + memcpy(&in6[i], &gates[i].sin6.sin6_addr, sizeof(*in6)); + } + + return 0; +} + +/* Command line stuff */ +__attribute__((unused)) +static int set_af(CLIF_option *optn, char *arg) { + UNUSED(arg); + int vers = (long) optn->data; + + if(vers == 4) + af = AF_INET; + else if(vers == 6) + af = AF_INET6; + else + return -1; + + return 0; +} + +static int add_gateway(CLIF_option *optn, char *arg) { + UNUSED(optn); + if(num_gateways >= MAX_GATEWAYS_6) { /* 127 > 8 ... :) */ + fprintf(stderr, "Too many gateways specified."); + return -1; + } + + gateways = realloc(gateways, (num_gateways + 1) * sizeof(*gateways)); + if(!gateways) + error("malloc"); + gateways[num_gateways++] = strdup(arg); + + return 0; +} + +static int set_source(CLIF_option *optn, char *arg) { + UNUSED(optn); + return getaddr(arg, &src_addr); +} + +static int set_port(CLIF_option *optn, char *arg) { + unsigned int *up = (unsigned int *) optn->data; + char *q; + + *up = strtoul(arg, &q, 0); + if(q == arg) { + struct servent *s = getservbyname(arg, NULL); + + if(!s) + return -1; + *up = ntohs(s->s_port); + } + + return 0; +} + +static int set_module(CLIF_option *optn, char *arg) { + UNUSED(arg); + module = (char *) optn->data; + + return 0; +} + +static int set_mod_option(CLIF_option *optn, char *arg) { + UNUSED(optn); + if(!strcmp(arg, "help")) { + const tr_module *mod = tr_get_module(module); + + if(mod && mod->options) { + /* just to set common keyword flag... */ + CLIF_parse(1, &arg, 0, 0, CLIF_KEYWORD); + CLIF_print_options(NULL, mod->options); + } else + fprintf(stderr, "No options for module `%s'\n", module); + + exit(0); + } + + if(opts_idx >= sizeof(opts) / sizeof(*opts)) { + fprintf(stderr, "Too many module options\n"); + return -1; + } + + opts[opts_idx] = strdup(arg); + if(!opts[opts_idx]) + error("strdup"); + opts_idx++; + + return 0; +} + +static int set_raw(CLIF_option *optn, char *arg) { + char buf[1024]; + + module = "raw"; + + snprintf(buf, sizeof(buf), "protocol=%s", arg); + return set_mod_option(optn, buf); +} + +static int set_wait_specs(CLIF_option *optn, char *arg) { + UNUSED(optn); + char *p, *q; + + here_factor = near_factor = 0; + + wait_secs = strtod(p = arg, &q); + if(q == p) + return -1; + if(!*q++) + return 0; + + here_factor = strtod(p = q, &q); + if(q == p) + return -1; + if(!*q++) + return 0; + + near_factor = strtod(p = q, &q); + if(q == p || *q) + return -1; + + return 0; +} + +static int set_host(CLIF_argument *argm, char *arg, int index) { + UNUSED(argm); + UNUSED(index); + if(getaddr(arg, &dst_addr) < 0) + return -1; + + dst_name = arg; + + /* i.e., guess it by the addr in cmdline... */ + if(!af) + af = dst_addr.sa.sa_family; + + return 0; +} + +static CLIF_option option_list[] = { + { "4", 0, 0, "Use IPv4", set_af, (void *) 4, 0, CLIF_EXTRA }, + { "6", 0, 0, "Use IPv6", set_af, (void *) 6, 0, 0 }, + { "d", "debug", 0, "Enable socket level debugging", + CLIF_set_flag, &debug, 0, 0 }, + { "F", "dont-fragment", 0, "Do not fragment packets", + CLIF_set_flag, &dontfrag, 0, CLIF_ABBREV }, + { "f", "first", "first_ttl", "Start from the %s hop (instead from 1)", + CLIF_set_uint, &first_hop, 0, 0 }, + { "g", "gateway", "gate", "Route packets through the specified gateway " + "(maximum " _TEXT(MAX_GATEWAYS_4) " for IPv4 and " + _TEXT(MAX_GATEWAYS_6) " for IPv6)", + add_gateway, 0, 0, CLIF_SEVERAL }, + { "I", "icmp", 0, "Use ICMP ECHO for tracerouting", + set_module, "icmp", 0, 0 }, + { "T", "tcp", 0, "Use TCP SYN for tracerouting (default " + "port is " _TEXT(DEF_TCP_PORT) ")", + set_module, "tcp", 0, 0 }, + { "i", "interface", "device", "Specify a network interface " + "to operate with", + CLIF_set_string, &device, 0, 0 }, + { "m", "max-hops", "max_ttl", "Set the max number of hops (max TTL " + "to be reached). Default is " _TEXT(DEF_HOPS), + CLIF_set_uint, &max_hops, 0, 0 }, + { "N", "sim-queries", "squeries", "Set the number of probes " + "to be tried simultaneously (default is " + _TEXT(DEF_SIM_PROBES) ")", + CLIF_set_uint, &sim_probes, 0, 0 }, + { "n", 0, 0, "Do not resolve IP addresses to their domain names", + CLIF_set_flag, &noresolve, 0, 0 }, + { "p", "port", "port", "Set the destination port to use. " + "It is either initial udp port value for " + "\"default\" method (incremented by each probe, " + "default is " _TEXT(DEF_START_PORT) "), " + "or initial seq for \"icmp\" (incremented as well, " + "default from 1), or some constant destination port" + " for other methods (with default of " + _TEXT(DEF_TCP_PORT) " for \"tcp\", " + _TEXT(DEF_UDP_PORT) " for \"udp\", etc.)", + set_port, &dst_port_seq, 0, 0 }, + { "t", "tos", "tos", "Set the TOS (IPv4 type of service) or TC " + "(IPv6 traffic class) value for outgoing packets", + CLIF_set_uint, &tos, 0, 0 }, + { "l", "flowlabel", "flow_label", "Use specified %s for IPv6 packets", + CLIF_set_uint, &flow_label, 0, 0 }, + { "w", "wait", "MAX,HERE,NEAR", "Wait for a probe no more than HERE " + "(default " _TEXT(DEF_HERE_FACTOR) ") times longer " + "than a response from the same hop, or no more " + "than NEAR (default " _TEXT(DEF_NEAR_FACTOR) ") " + "times than some next hop, or MAX (default " + _TEXT(DEF_WAIT_SECS) ") seconds " + "(float point values allowed too)", + set_wait_specs, 0, 0, 0 }, + { "q", "queries", "nqueries", "Set the number of probes per each hop. " + "Default is " _TEXT(DEF_NUM_PROBES), + CLIF_set_uint, &probes_per_hop, 0, 0 }, + { "r", 0, 0, "Bypass the normal routing and send directly to a host " + "on an attached network", + CLIF_set_flag, &noroute, 0, 0 }, + { "s", "source", "src_addr", "Use source %s for outgoing packets", + set_source, 0, 0, 0 }, + { "z", "sendwait", "sendwait", "Minimal time interval between probes " + "(default " _TEXT(DEF_SEND_SECS) "). If the value " + "is more than 10, then it specifies a number " + "in milliseconds, else it is a number of seconds " + "(float point values allowed too)", + CLIF_set_double, &send_secs, 0, 0 }, + { "e", "extensions", 0, "Show ICMP extensions (if present), " + "including MPLS", + CLIF_set_flag, &extension, 0, CLIF_ABBREV }, + { "A", "as-path-lookups", 0, "Perform AS path lookups in routing " + "registries and print results directly after " + "the corresponding addresses", + CLIF_set_flag, &as_lookups, 0, 0 }, + { "M", "module", "name", "Use specified module (either builtin or " + "external) for traceroute operations. Most methods " + "have their shortcuts (`-I' means `-M icmp' etc.)", + CLIF_set_string, &module, 0, CLIF_EXTRA }, + { "O", "options", "OPTS", "Use module-specific option %s for the " + "traceroute module. Several %s allowed, separated " + "by comma. If %s is \"help\", print info about " + "available options", + set_mod_option, 0, 0, CLIF_SEVERAL | CLIF_EXTRA }, + { 0, "sport", "num", "Use source port %s for outgoing packets. " + "Implies `-N 1'", + set_port, &src_port, 0, CLIF_EXTRA }, + #ifdef SO_MARK + { 0, "fwmark", "num", "Set firewall mark for outgoing packets", + CLIF_set_uint, &fwmark, 0, 0 }, + #endif + { "U", "udp", 0, "Use UDP to particular port for tracerouting " + "(instead of increasing the port per each probe), " + "default port is " _TEXT(DEF_UDP_PORT), + set_module, "udp", 0, CLIF_EXTRA }, + { 0, "UL", 0, "Use UDPLITE for tracerouting (default dest port is " + _TEXT(DEF_UDP_PORT) ")", + set_module, "udplite", 0, CLIF_ONEDASH | CLIF_EXTRA }, + { "D", "dccp", 0, "Use DCCP Request for tracerouting (default " + "port is " _TEXT(DEF_DCCP_PORT) ")", + set_module, "dccp", 0, CLIF_EXTRA }, + { "P", "protocol", "prot", "Use raw packet of protocol %s " + "for tracerouting", + set_raw, 0, 0, CLIF_EXTRA }, + { 0, "mtu", 0, "Discover MTU along the path being traced. " + "Implies `-F -N 1'", + CLIF_set_flag, &mtudisc, 0, CLIF_EXTRA }, + { 0, "back", 0, "Guess the number of hops in the backward path " + "and print if it differs", + CLIF_set_flag, &backward, 0, CLIF_EXTRA }, + CLIF_VERSION_OPTION (version_string), + CLIF_HELP_OPTION, + CLIF_END_OPTION + }; + +static CLIF_argument arg_list[] = { + { "host", "The host to traceroute to", + set_host, 0, CLIF_STRICT }, + { "packetlen", "The full packet length (default is the length of " + "an IP header plus " _TEXT(DEF_DATA_LEN) "). Can be " + "ignored or increased to a minimal allowed value", + CLIF_arg_int, &packet_len, 0 }, + CLIF_END_ARGUMENT + }; + +static void do_it(void); + +int traceroute_main(int argc, char *argv[]) { + + setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); /* avoid commas in msec printed */ + + check_progname(argv[0]); + + if(CLIF_parse(argc, argv, option_list, arg_list, + CLIF_MAY_JOIN_ARG | CLIF_HELP_EMPTY) < 0 + ) + return -1; //exit(2); + + ops = tr_get_module(module); + if(!ops) { + ex_error("Unknown traceroute module %s", module); + return -1; + } + + if(!first_hop || first_hop > max_hops) { + ex_error("first hop out of range"); + return -1; + } + if(max_hops > MAX_HOPS) { + ex_error("max hops cannot be more than " _TEXT(MAX_HOPS)); + return -1; + } + if(!probes_per_hop || probes_per_hop > MAX_PROBES) { + ex_error("no more than " _TEXT(MAX_PROBES) " probes per hop"); + return -1; + } + if(wait_secs < 0 || here_factor < 0 || near_factor < 0) { + ex_error("bad wait specifications `%g,%g,%g' used", + wait_secs, here_factor, near_factor); + return -1; + } + if(packet_len > MAX_PACKET_LEN) { + ex_error("too big packetlen %d specified", packet_len); + return -1; + } + if(src_addr.sa.sa_family && src_addr.sa.sa_family != af) { + ex_error("IP version mismatch in addresses specified"); + return -1; + } + if(send_secs < 0) { + ex_error("bad sendtime `%g' specified", send_secs); + return -1; + } + if(send_secs >= 10) /* it is milliseconds */ + send_secs /= 1000; + + if(af == AF_INET6 && (tos || flow_label)) + dst_addr.sin6.sin6_flowinfo = + htonl(((tos & 0xff) << 20) | (flow_label & 0x000fffff)); + + if(src_port) { + src_addr.sin.sin_port = htons((uint16_t) src_port); + src_addr.sa.sa_family = af; + } + + if(src_port || ops->one_per_time) { + sim_probes = 1; + here_factor = near_factor = 0; + } + + /* make sure we don't std{in,out,err} to open sockets */ + make_fd_used(0); + make_fd_used(1); + make_fd_used(2); + + if(init_ip_options() == -1) + return -1; + + header_len = (af == AF_INET ? sizeof(struct iphdr) + : + sizeof(struct ip6_hdr)) + + rtbuf_len + ops->header_len; + + if(mtudisc) { + dontfrag = 1; + sim_probes = 1; + if(packet_len < 0) + packet_len = MAX_PACKET_LEN; + } + + if(packet_len < 0) { + if(DEF_DATA_LEN >= ops->header_len) + data_len = DEF_DATA_LEN - ops->header_len; + } else { + if(packet_len >= (int) header_len) + data_len = packet_len - header_len; + } + + num_probes = max_hops * probes_per_hop; + probes = calloc(num_probes, sizeof(*probes)); + if(!probes) + error("calloc"); + + if(ops->options && opts_idx > 1) { + opts[0] = strdup(module); /* aka argv[0] ... */ + if(CLIF_parse(opts_idx, opts, ops->options, 0, CLIF_KEYWORD) < 0) + return -1; //exit(2); + } + + if(ops->init(&dst_addr, dst_port_seq, &data_len) < 0) + ex_error("trace method's init failed"); + + do_it(); + return 0; +} + +/* PRINT STUFF */ + +static void print_header(void) { + + /* Note, without ending new-line! */ + log_printf("traceroute to %s (%s), %u hops max, %zu byte packets", + dst_name, addr2str(&dst_addr), max_hops, + header_len + data_len); + fflush(stdout); +} + +static bool print_addr(sockaddr_any *res) { + bool is_final = false; + const char *str; + + if(!res->sa.sa_family) + return is_final; + + str = addr2str(res); + + if(noresolve) { + log_printf("%s", str); + if(str && !strcmp(dst_name, str)) + is_final = true; + } + else { + char buf[1024]; + + buf[0] = '\0'; + getnameinfo(&res->sa, sizeof(*res), buf, sizeof(buf), + 0, 0, NI_IDN); + log_printf(" %s (%s)", buf[0] ? buf : str, str); + if(buf[0] && !strcmp(dst_name, buf)) + is_final = true; + else + if(str && !strcmp(dst_name, str)) + is_final = true; + } + + if(as_lookups) + log_printf(" [%s]", get_as_path(str)); + + return is_final; +} + +static bool print_probe(probe *pb) { + bool is_final = false; + unsigned int idx = (pb - probes); + unsigned int ttl = idx / probes_per_hop + 1; + unsigned int np = idx % probes_per_hop; + + if(np == 0) + log_printf("\n%2u ", ttl); + + if(!pb->res.sa.sa_family) + log_printf(" *"); + else { + int prn = !np; /* print if the first... */ + + if(np) { /* ...and if differs with previous */ + probe *p; + + /* skip expired */ + for(p = pb - 1; np && !p->res.sa.sa_family; p--, np--) + ; + + if(!np || + !equal_addr(&p->res, &pb->res) || + (p->ext != pb->ext && + !(p->ext && pb->ext && !strcmp(p->ext, pb->ext))) || + (backward && p->recv_ttl != pb->recv_ttl) + ) + prn = 1; + } + + if(prn) { + is_final = print_addr(&pb->res); + if(pb->ext) + log_printf(" <%s>", pb->ext); + + if(backward && pb->recv_ttl) { + int hops = ttl2hops(pb->recv_ttl); + if(hops != (int) ttl) + log_printf(" '-%d'", hops); + } + } + } + + if(pb->recv_time) { + double diff = pb->recv_time - pb->send_time; + + log_printf(" %.3f ms", diff * 1000); + } + + if(pb->err_str[0]) + log_printf(" %s", pb->err_str); + + fflush(stdout); + + return is_final; +} + +static void print_end(void) { + + log_printf("\n"); +} + +/* Compute timeout stuff */ + +static double get_timeout(probe *pb) { + double value; + + if(here_factor) { + /* check for already replied from the same hop */ + int i, idx = (pb - probes); + probe *p = &probes[idx - (idx % probes_per_hop)]; + + for(i = 0; i < (int) probes_per_hop; i++, p++) { + /* `p == pb' skipped since !pb->done */ + + if(p->done && (value = p->recv_time - p->send_time) > 0) { + value += DEF_WAIT_PREC; + value *= here_factor; + return value < wait_secs ? value : wait_secs; + } + } + } + + if(near_factor) { + /* check forward for already replied */ + probe *p, *endp = probes + num_probes; + + for(p = pb + 1; p < endp && p->send_time; p++) { + + if(p->done && (value = p->recv_time - p->send_time) > 0) { + value += DEF_WAIT_PREC; + value *= near_factor; + return value < wait_secs ? value : wait_secs; + } + } + } + + return wait_secs; +} + +/* Check expiration stuff */ + +static void check_expired(probe *pb) { + int idx = (pb - probes); + probe *p, *endp = probes + num_probes; + probe *fp = NULL, *pfp = NULL; + + if(!pb->done) /* an ops method still not release it */ + return; + + /* check all the previous in the same hop */ + for(p = &probes[idx - (idx % probes_per_hop)]; p < pb; p++) { + + if(!p->done || /* too early to decide something */ + !p->final /* already ttl-exceeded in the same hop */ + ) + return; + + pfp = p; /* some of the previous probes is final */ + } + + /* check forward all the sent probes */ + for(p = pb + 1; p < endp && p->send_time; p++) { + + if(p->done) { /* some next probe already done... */ + if(!p->final) /* ...was ttl-exceeded. OK, we are expired. */ + return; + else { + fp = p; + break; + } + } + } + + if(!fp) /* no any final probe found. Assume expired. */ + return; + + /* Well. There is a situation "*(this) * * * * ... * * final" + We cannot guarantee that "final" is in its right place. + We've sent "sim_probes" simultaneously, and the final hop + can drop some of them and answer only for latest ones. + If we can detect/assume that it so, then just put "final" + to the (pseudo-expired) "this" place. + */ + + /* It seems that the case of "answers for latest ones only" + occurs mostly with icmp_unreach error answers ("!H" etc.). + Icmp_echoreply, tcp_reset and even icmp_port_unreach looks + like going in the right order. + */ + if(!fp->err_str[0]) /* not an icmp_unreach error report... */ + return; + + if(pfp || + (idx % probes_per_hop) + (fp - pb) < probes_per_hop + ) { + /* Either some previous (pfp) or some next probe + in this hop is final. It means that the whole hop is final. + Do the replace (it also causes further "final"s to be shifted + here too). + */ + goto replace_by_final; + } + + /* If the final probe is an icmp_unreachable report + (either in a case of some error, like "!H", or just port_unreach), + it could follow the "time-exceed" report from the *same* hop. + */ + for(p = pb - 1; p >= probes; p--) { + if(equal_addr(&p->res, &fp->res)) { + /* ...Yes. Put "final" to the "this" place. */ + goto replace_by_final; + } + } + + if(fp->recv_ttl) { + /* Consider the ttl value of the report packet and guess where + the "final" should be. If it seems that it should be + in the same hop as "this", then do replace. + */ + int back_hops, ttl; + + /* We assume that the reporting one has an initial ttl value + of either 64, or 128, or 255. It is most widely used + in the modern routers and computers. + The idea comes from tracepath(1) routine. + */ + back_hops = ttl2hops(fp->recv_ttl); + + /* It is possible that the back path differs from the forward + and therefore has different number of hops. To minimize + such an influence, get the nearest previous time-exceeded + probe and compare with it. + */ + for(p = pb - 1; p >= probes; p--) { + if(p->done && !p->final && p->recv_ttl) { + int hops = ttl2hops(p->recv_ttl); + + if(hops < back_hops) { + ttl = (p - probes) / probes_per_hop + 1; + back_hops = (back_hops - hops) + ttl; + break; + } + } + } + + ttl = idx / probes_per_hop + 1; + if(back_hops == ttl) + /* Yes! It seems that "final" should be at "this" place */ + goto replace_by_final; + else if(back_hops < ttl) + /* Hmmm... Assume better to replace here too... */ + goto replace_by_final; + + } + + /* No idea what to do. Assume expired. */ + + return; + + replace_by_final: + + *pb = *fp; + + memset(fp, 0, sizeof(*fp)); + /* block extra re-send */ + fp->send_time = 1.; + + return; +} + +probe *probe_by_seq(int seq) { + int n; + + if(seq <= 0) + return NULL; + + for(n = 0; n < (int) num_probes; n++) { + if(probes[n].seq == seq) + return &probes[n]; + } + + return NULL; +} + +probe *probe_by_sk(int sk) { + int n; + + if(sk <= 0) + return NULL; + + for(n = 0; n < (int) num_probes; n++) { + if(probes[n].sk == sk) + return &probes[n]; + } + + return NULL; +} + +static void poll_callback(int fd, int revents) { + + ops->recv_probe(fd, revents); +} + +static void do_it(void) { + int start = (first_hop - 1) * probes_per_hop; + int end = num_probes; + double last_send = 0; + + print_header(); + + while(start < end) { + int n, num = 0; + double next_time = 0; + double now_time = get_time(); + + for(n = start; n < end; n++) { + probe *pb = &probes[n]; + + if(n == start && /* probably time to print... */ + !pb->done && pb->send_time /* ...but yet not replied */ + ) { + double expire_time = pb->send_time + get_timeout(pb); + + if(expire_time > now_time) + next_time = expire_time; + else { + ops->expire_probe(pb); + check_expired(pb); + } + } + + if(pb->done) { + + if(n == start) { /* can print it now */ + if(print_probe(pb)) { + pb->final = 1; + } + //log_printf("(host=%s,%s recv_ttl=%d)", dst_name, (host) ? host : "-", pb->recv_ttl); + start++; + } + + /* { + char buf[1024]; + buf[0] = '\0'; + getnameinfo(&(pb->res.sa), sizeof((pb->res)), buf, sizeof(buf),0, 0, NI_IDN); + if(buf[0] && !strcmp(dst_name, buf)) + pb->real_final = true; + //else + //if(str && !strcmp(dst_name, str)) + // pb->real_final = true; + }*/ + + if(pb->final) + end = (n / probes_per_hop + 1) * probes_per_hop; + + continue; + } + + if(!pb->send_time) { + int ttl; + double next; + + if(send_secs && (next = last_send + send_secs) > now_time) { + next_time = next; + break; + } + + ttl = n / probes_per_hop + 1; + + ops->send_probe(pb, ttl); + + if(!pb->send_time) { + if(next_time) + break; /* have chances later */ + else + error("send probe"); + } + + last_send = pb->send_time; + } + + if(!next_time) + next_time = pb->send_time + get_timeout(pb); + + num++; + if(num >= (int) sim_probes) + break; + } + + if(next_time) { + double timeout = next_time - get_time(); + + if(timeout < 0) + timeout = 0; + + do_poll(timeout, poll_callback); + } + + } + + print_end(); + + return; +} + +void tune_socket(int sk) { + int i = 0; + + if(debug) { + i = 1; + if(setsockopt(sk, SOL_SOCKET, SO_DEBUG, &i, sizeof(i)) < 0) + error("setsockopt SO_DEBUG"); + } + +#ifdef SO_MARK + if(fwmark) { + if(setsockopt(sk, SOL_SOCKET, SO_MARK, + &fwmark, sizeof(fwmark)) < 0 + ) + error("setsockopt SO_MARK"); + } +#endif + + if(rtbuf && rtbuf_len) { + if(af == AF_INET) { + if(setsockopt(sk, IPPROTO_IP, IP_OPTIONS, + rtbuf, rtbuf_len) < 0 + ) + error("setsockopt IP_OPTIONS"); + } + else if(af == AF_INET6) { + if(setsockopt(sk, IPPROTO_IPV6, IPV6_RTHDR, + rtbuf, rtbuf_len) < 0 + ) + error("setsockopt IPV6_RTHDR"); + } + } + + bind_socket(sk); + + if(af == AF_INET) { + + i = dontfrag ? IP_PMTUDISC_PROBE : IP_PMTUDISC_DONT; + if(setsockopt(sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0 && + (!dontfrag || (i = IP_PMTUDISC_DO, + setsockopt(sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0)) + ) + error("setsockopt IP_MTU_DISCOVER"); + + if(tos) { + i = tos; + if(setsockopt(sk, SOL_IP, IP_TOS, &i, sizeof(i)) < 0) + error("setsockopt IP_TOS"); + } + + } + else if(af == AF_INET6) { + + i = dontfrag ? IPV6_PMTUDISC_PROBE : IPV6_PMTUDISC_DONT; + if(setsockopt(sk, SOL_IPV6, IPV6_MTU_DISCOVER, &i, sizeof(i)) < 0 && + (!dontfrag || (i = IPV6_PMTUDISC_DO, + setsockopt(sk, SOL_IPV6, IPV6_MTU_DISCOVER, &i, sizeof(i)) < 0)) + ) + error("setsockopt IPV6_MTU_DISCOVER"); + + if(flow_label) { + struct in6_flowlabel_req flr; + + memset(&flr, 0, sizeof(flr)); + flr.flr_label = htonl(flow_label & 0x000fffff); + flr.flr_action = IPV6_FL_A_GET; + flr.flr_flags = IPV6_FL_F_CREATE; + flr.flr_share = IPV6_FL_S_ANY; + memcpy(&flr.flr_dst, &dst_addr.sin6.sin6_addr, + sizeof(flr.flr_dst)); + + if(setsockopt(sk, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, + &flr, sizeof(flr)) < 0 + ) + error("setsockopt IPV6_FLOWLABEL_MGR"); + } + + if(tos) { + i = tos; + if(setsockopt(sk, IPPROTO_IPV6, IPV6_TCLASS, + &i, sizeof(i)) < 0 + ) + error("setsockopt IPV6_TCLASS"); + } + + if(tos || flow_label) { + i = 1; + if(setsockopt(sk, IPPROTO_IPV6, IPV6_FLOWINFO_SEND, + &i, sizeof(i)) < 0 + ) + error("setsockopt IPV6_FLOWINFO_SEND"); + } + } + + if(noroute) { + i = noroute; + if(setsockopt(sk, SOL_SOCKET, SO_DONTROUTE, &i, sizeof(i)) < 0) + error("setsockopt SO_DONTROUTE"); + } + + use_timestamp(sk); + + use_recv_ttl(sk); + + fcntl(sk, F_SETFL, O_NONBLOCK); + + return; +} + +void parse_icmp_res(probe *pb, int type, int code, int info) { + char *str = NULL; + char buf[sizeof(pb->err_str)]; + + if(af == AF_INET) { + + if(type == ICMP_TIME_EXCEEDED) { + if(code == ICMP_EXC_TTL) + return; + } + else if(type == ICMP_DEST_UNREACH) { + + switch (code) { + case ICMP_UNREACH_NET: + case ICMP_UNREACH_NET_UNKNOWN: + case ICMP_UNREACH_ISOLATED: + case ICMP_UNREACH_TOSNET: + str = "!N"; + break; + + case ICMP_UNREACH_HOST: + case ICMP_UNREACH_HOST_UNKNOWN: + case ICMP_UNREACH_TOSHOST: + str = "!H"; + break; + + case ICMP_UNREACH_NET_PROHIB: + case ICMP_UNREACH_HOST_PROHIB: + case ICMP_UNREACH_FILTER_PROHIB: + str = "!X"; + break; + + case ICMP_UNREACH_PORT: + /* dest host is reached */ + str = ""; + break; + + case ICMP_UNREACH_PROTOCOL: + str = "!P"; + break; + + case ICMP_UNREACH_NEEDFRAG: + snprintf(buf, sizeof(buf), "!F-%d", info); + str = buf; + break; + + case ICMP_UNREACH_SRCFAIL: + str = "!S"; + break; + + case ICMP_UNREACH_HOST_PRECEDENCE: + str = "!V"; + break; + + case ICMP_UNREACH_PRECEDENCE_CUTOFF: + str = "!C"; + break; + + default: + snprintf(buf, sizeof(buf), "!<%u>", code); + str = buf; + break; + } + } + + } + else if(af == AF_INET6) { + + if(type == ICMP6_TIME_EXCEEDED) { + if(code == ICMP6_TIME_EXCEED_TRANSIT) + return; + } + else if(type == ICMP6_DST_UNREACH) { + + switch (code) { + + case ICMP6_DST_UNREACH_NOROUTE: + str = "!N"; + break; + + case ICMP6_DST_UNREACH_BEYONDSCOPE: + case ICMP6_DST_UNREACH_ADDR: + str = "!H"; + break; + + case ICMP6_DST_UNREACH_ADMIN: + str = "!X"; + break; + + case ICMP6_DST_UNREACH_NOPORT: + /* dest host is reached */ + str = ""; + break; + + default: + snprintf(buf, sizeof(buf), "!<%u>", code); + str = buf; + break; + } + } + else if(type == ICMP6_PACKET_TOO_BIG) { + snprintf(buf, sizeof(buf), "!F-%d", info); + str = buf; + } + } + + if(!str) { + snprintf(buf, sizeof(buf), "!<%u-%u>", type, code); + str = buf; + } + + if(*str) { + strncpy(pb->err_str, str, sizeof(pb->err_str)); + pb->err_str[sizeof(pb->err_str) - 1] = '\0'; + } + // final present + pb->final = 1; + + return; +} + +static void parse_local_res(probe *pb, int ee_errno, int info) { + + if(ee_errno == EMSGSIZE && info != 0) { + snprintf(pb->err_str, sizeof(pb->err_str) - 1, "!F-%d", info); + pb->final = 1; + return; + } + + errno = ee_errno; + error("local recverr"); +} + +void probe_done(probe *pb) { + + if(pb->sk) { + del_poll(pb->sk); + close(pb->sk); + pb->sk = 0; + } + + pb->seq = 0; + + pb->done = 1; +} + +void recv_reply(int sk, int err, check_reply_t check_reply) { + struct msghdr msg; + sockaddr_any from; + struct iovec iov; + int n; + probe *pb; + char buf[1280]; /* min mtu for ipv6 ( >= 576 for ipv4) */ + char *bufp = buf; + char control[1024]; + struct cmsghdr *cm; + double recv_time = 0; + int recv_ttl = 0; + struct sock_extended_err *ee = NULL; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + n = recvmsg(sk, &msg, err ? MSG_ERRQUEUE : 0); + if(n < 0) + return; + + /* when not MSG_ERRQUEUE, AF_INET returns full ipv4 header + on raw sockets... + */ + + if(!err && + af == AF_INET && + /* XXX: Assume that the presence of an extra header means + that it is not a raw socket... + */ + ops->header_len == 0 + ) { + struct iphdr *ip = (struct iphdr *) bufp; + int hlen; + + if(n < (int) sizeof(struct iphdr)) + return; + + hlen = ip->ihl << 2; + if(n < hlen) + return; + + bufp += hlen; + n -= hlen; + } + + pb = check_reply(sk, err, &from, bufp, n); + if(!pb) { + + /* for `frag needed' case at the local host, + kernel >= 3.13 sends local error (no more icmp) + */ + if(!n && err && dontfrag) { + pb = &probes[(first_hop - 1) * probes_per_hop]; + if(pb->done) + return; + } else + return; + } + + /* Parse CMSG stuff */ + + for(cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) { + void *ptr = CMSG_DATA(cm); + + if(cm->cmsg_level == SOL_SOCKET) { + + if(cm->cmsg_type == SO_TIMESTAMP) { + struct timeval *tv = (struct timeval *) ptr; + + recv_time = tv->tv_sec + tv->tv_usec / 1000000.; + } + } + else if(cm->cmsg_level == SOL_IP) { + + if(cm->cmsg_type == IP_TTL) + recv_ttl = *((int *) ptr); + else if(cm->cmsg_type == IP_RECVERR) { + + ee = (struct sock_extended_err *) ptr; + + if(ee->ee_origin != SO_EE_ORIGIN_ICMP && + ee->ee_origin != SO_EE_ORIGIN_LOCAL + ) + return; + + /* dgram icmp sockets might return extra things... */ + if(ee->ee_origin == SO_EE_ORIGIN_ICMP && + (ee->ee_type == ICMP_SOURCE_QUENCH || + ee->ee_type == ICMP_REDIRECT) + ) + return; + } + } + else if(cm->cmsg_level == SOL_IPV6) { + + if(cm->cmsg_type == IPV6_HOPLIMIT) + recv_ttl = *((int *) ptr); + else if(cm->cmsg_type == IPV6_RECVERR) { + + ee = (struct sock_extended_err *) ptr; + + if(ee->ee_origin != SO_EE_ORIGIN_ICMP6 && + ee->ee_origin != SO_EE_ORIGIN_LOCAL + ) + return; + } + } + } + + if(!recv_time) + recv_time = get_time(); + + if(!err) + memcpy(&pb->res, &from, sizeof(pb->res)); + + pb->recv_time = recv_time; + + pb->recv_ttl = recv_ttl; + + if(ee && ee->ee_origin != SO_EE_ORIGIN_LOCAL) { /* icmp or icmp6 */ + memcpy(&pb->res, SO_EE_OFFENDER(ee), sizeof(pb->res)); + parse_icmp_res(pb, ee->ee_type, ee->ee_code, ee->ee_info); + } + + if(ee && ee->ee_origin == SO_EE_ORIGIN_LOCAL) + parse_local_res(pb, ee->ee_errno, ee->ee_info); + + if(ee && + mtudisc && + ee->ee_info >= header_len && + ee->ee_info < header_len + data_len + ) { + data_len = ee->ee_info - header_len; + + probe_done(pb); + + /* clear this probe (as actually the previous hop answers here) + but fill its `err_str' by the info obtained. Ugly, but easy... + */ + memset(pb, 0, sizeof(*pb)); + snprintf(pb->err_str, sizeof(pb->err_str) - 1, "F=%d", ee->ee_info); + + return; + } + + if(ee && + extension && + header_len + n >= (128 + 8) && /* at least... (rfc4884) */ + header_len <= 128 && /* paranoia */ + ((af == AF_INET && (ee->ee_type == ICMP_TIME_EXCEEDED || + ee->ee_type == ICMP_DEST_UNREACH || + ee->ee_type == ICMP_PARAMETERPROB)) || + (af == AF_INET6 && (ee->ee_type == ICMP6_TIME_EXCEEDED || + ee->ee_type == ICMP6_DST_UNREACH)) + ) + ) { + int step; + int offs = 128 - header_len; + + if(n > (int) data_len) + step = 0; /* guaranteed at 128 ... */ + else + step = af == AF_INET ? 4 : 8; + + handle_extensions(pb, bufp + offs, n - offs, step); + } + + probe_done(pb); +} + +int equal_addr(const sockaddr_any *a, const sockaddr_any *b) { + + if(!a->sa.sa_family) + return 0; + + if(a->sa.sa_family != b->sa.sa_family) + return 0; + + if(a->sa.sa_family == AF_INET6) + return !memcmp(&a->sin6.sin6_addr, &b->sin6.sin6_addr, + sizeof(a->sin6.sin6_addr)); + else + return !memcmp(&a->sin.sin_addr, &b->sin.sin_addr, + sizeof(a->sin.sin_addr)); + return 0; /* not reached */ +} + +void bind_socket(int sk) { + sockaddr_any *addr, tmp; + + if(device) { + if(setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, + device, strlen(device) + 1) < 0 + ) + error("setsockopt SO_BINDTODEVICE"); + } + + if(!src_addr.sa.sa_family) { + memset(&tmp, 0, sizeof(tmp)); + tmp.sa.sa_family = af; + addr = &tmp; + } else + addr = &src_addr; + + if(bind(sk, &addr->sa, sizeof(*addr)) < 0) + error("bind"); + + return; +} + +void use_timestamp(int sk) { + int n = 1; + + setsockopt(sk, SOL_SOCKET, SO_TIMESTAMP, &n, sizeof(n)); + /* foo on errors... */ +} + +void use_recv_ttl(int sk) { + int n = 1; + + if(af == AF_INET) + setsockopt(sk, SOL_IP, IP_RECVTTL, &n, sizeof(n)); + else if(af == AF_INET6) + setsockopt(sk, SOL_IPV6, IPV6_RECVHOPLIMIT, &n, sizeof(n)); + /* foo on errors */ +} + +void use_recverr(int sk) { + int val = 1; + + if(af == AF_INET) { + if(setsockopt(sk, SOL_IP, IP_RECVERR, &val, sizeof(val)) < 0) + error("setsockopt IP_RECVERR"); + } + else if(af == AF_INET6) { + if(setsockopt(sk, SOL_IPV6, IPV6_RECVERR, &val, sizeof(val)) < 0) + error("setsockopt IPV6_RECVERR"); + } +} + +void set_ttl(int sk, int ttl) { + + if(af == AF_INET) { + if(setsockopt(sk, SOL_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) + error("setsockopt IP_TTL"); + } + else if(af == AF_INET6) { + if(setsockopt(sk, SOL_IPV6, IPV6_UNICAST_HOPS, + &ttl, sizeof(ttl)) < 0 + ) + error("setsockopt IPV6_UNICAST_HOPS"); + } +} + +int do_send(int sk, const void *data, size_t len, const sockaddr_any *addr) { + int res; + + if(!addr || raw_can_connect()) + res = send(sk, data, len, 0); + else + res = sendto(sk, data, len, 0, &addr->sa, sizeof(*addr)); + + if(res < 0) { + if(errno == ENOBUFS || errno == EAGAIN) + return res; + if(errno == EMSGSIZE) + return 0; /* recverr will say more... */ + error("send"); /* not recoverable */ + } + + return res; +} + +/* There is a bug in the kernel before 2.6.25, which prevents icmp errors + to be obtained by MSG_ERRQUEUE for ipv6 connected raw sockets. + */ +static int can_connect = -1; + +#define VER(A,B,C,D) (((((((A) << 8) | (B)) << 8) | (C)) << 8) | (D)) + +int raw_can_connect(void) { + + if(can_connect < 0) { + + if(af == AF_INET) + can_connect = 1; + else { /* AF_INET6 */ + struct utsname uts; + int n; + unsigned int a, b, c, d = 0; + + if(uname(&uts) < 0) + return 0; + + n = sscanf(uts.release, "%u.%u.%u.%u", &a, &b, &c, &d); + can_connect = (n >= 3 && VER (a, b, c, d) >= VER(2, 6, 25, 0)); + } + } + + return can_connect; +} + +/** + * Traceroute host + * + * @addr[in] host name or IP address + * @hops[out] hops count + * @time_usec[out] latency in microseconds + * @return 0 Ok, -1 error + */ +int traceroute_util(const char *addr, int *hops, int *time_usec) +{ + UNUSED(hops); + int type = 6; // ipv4 or ipv6 + int argc = 3; + const char *argv[argc]; + if(type != 4) + argv[0] = "traceroute6"; + else + argv[0] = "traceroute4"; + argv[1] = "-4"; // ipv4 + argv[2] = addr; + + *hops = 0; + *time_usec = 0; + + int ret = traceroute_main(argc, (char**) argv); + if(!ret) + for(int i = 0; i < (int) num_probes; i++) { + probe *one_probe = probes + i; + if(one_probe->done && one_probe->final && one_probe->recv_ttl) { + *hops = i / DEF_NUM_PROBES + 1; + *time_usec = (int) ((one_probe->recv_time - one_probe->send_time) * 1000000); + // if error -> not found host + if(!strlen(one_probe->err_str)) + ret = 1; + break; + } + /*if(one_probe->done) + printf("%d(%d) dseq=%d sk=%d done=%d final=%d recv_ttl=%d dt=%lf err='%s'\n", i + 1, + i / DEF_NUM_PROBES + 1, + one_probe->seq, one_probe->sk, one_probe->done, + one_probe->final, one_probe->recv_ttl, one_probe->recv_time - one_probe->send_time, + one_probe->err_str);*/ + } + free(probes); + + return (ret == 1) ? 0 : -1; +} diff --git a/iputils/traceroute/traceroute.h b/iputils/traceroute/traceroute.h new file mode 100755 index 0000000000000000000000000000000000000000..532b7b6f0912d67fb71e2bcac901d7f9da99bb37 --- /dev/null +++ b/iputils/traceroute/traceroute.h @@ -0,0 +1,107 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <buc@citadel.stu.neva.ru> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <netinet/in.h> + +#include <clif.h> +#include "../iputils.h" + +union common_sockaddr { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; +typedef union common_sockaddr sockaddr_any; + +struct probe_struct { + int done; + int final; + sockaddr_any res; + double send_time; + double recv_time; + int recv_ttl; + int sk; + int seq; + char *ext; + char err_str[16]; /* assume enough */ +}; +typedef struct probe_struct probe; + + +struct tr_module_struct { + struct tr_module_struct *next; + const char *name; + int (*init) (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len); + void (*send_probe) (probe *pb, int ttl); + void (*recv_probe) (int fd, int revents); + void (*expire_probe) (probe *pb); + CLIF_option *options; /* per module options, if any */ + int one_per_time; /* no simultaneous probes */ + size_t header_len; /* additional header length (aka for udp) */ +}; +typedef struct tr_module_struct tr_module; + + +#define __TEXT(X) #X +#define _TEXT(X) __TEXT(X) + +#define DEF_START_PORT 33434 /* start for traditional udp method */ +#define DEF_UDP_PORT 53 /* dns */ +#define DEF_TCP_PORT 80 /* web */ +#define DEF_DCCP_PORT DEF_START_PORT /* is it a good choice?... */ +#define DEF_RAW_PROT 253 /* for experimentation and testing, rfc3692 */ + + +void error (const char *str) __attribute__((noreturn)); +void error_or_perm (const char *str) __attribute__((noreturn)); + +double get_time (void); +void tune_socket (int sk); +void parse_icmp_res (probe *pb, int type, int code, int info); +void probe_done (probe *pb); + +typedef probe *(*check_reply_t) (int sk, int err, sockaddr_any *from, + char *buf, size_t len); +void recv_reply (int sk, int err, check_reply_t check_reply); + +int equal_addr (const sockaddr_any *a, const sockaddr_any *b); + +probe *probe_by_seq (int seq); +probe *probe_by_sk (int sk); + +void bind_socket (int sk); +void use_timestamp (int sk); +void use_recv_ttl (int sk); +void use_recverr (int sk); +void set_ttl (int sk, int ttl); +int do_send (int sk, const void *data, size_t len, const sockaddr_any *addr); + +void add_poll (int fd, int events); +void del_poll (int fd); +void do_poll (double timeout, void (*callback) (int fd, int revents)); + +void handle_extensions (probe *pb, char *buf, int len, int step); +const char *get_as_path (const char *query); + +int raw_can_connect (void); + +unsigned int random_seq (void); +uint16_t in_csum (const void *ptr, size_t len); + + +void tr_register_module (tr_module *module); +const tr_module *tr_get_module (const char *name); + +#define TR_MODULE(MOD) \ +static void __init_ ## MOD (void) __attribute__ ((constructor)); \ +static void __init_ ## MOD (void) { \ + \ + tr_register_module (&MOD); \ +} +