| /* |
| * |
| * 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. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by the University of |
| * California, Berkeley and its contributors. |
| * 4. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #ifndef lint |
| char copyright[] = |
| "@(#) Copyright (c) 1989 The Regents of the University of California.\n\ |
| All rights reserved.\n"; |
| #endif /* not lint */ |
| |
| /* |
| * 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. |
| * This program has to run SUID to ROOT to access the ICMP socket. |
| */ |
| #include "ping_common.h" |
| |
| #include <linux/filter.h> |
| #include <netinet/ip6.h> |
| #include <netinet/icmp6.h> |
| #include <resolv.h> |
| #ifndef WITHOUT_IFADDRS |
| #include <ifaddrs.h> |
| #endif |
| |
| #ifdef USE_IDN |
| #include <stringprep.h> |
| #endif |
| |
| #include "ping6_niquery.h" |
| #include "in6_flowlabel.h" |
| |
| #ifndef SOL_IPV6 |
| #define SOL_IPV6 IPPROTO_IPV6 |
| #endif |
| |
| #ifndef SOL_ICMPV6 |
| #define SOL_ICMPV6 IPPROTO_ICMPV6 |
| #endif |
| |
| /* RFC3542 */ |
| #ifndef ICMP6_DST_UNREACH_BEYONDSCOPE |
| #define ICMP6_DST_UNREACH_BEYONDSCOPE ICMP6_DST_UNREACH_NOTNEIGHBOR |
| #endif |
| |
| #if defined(ENABLE_PING6_RTHDR) && !defined(ENABLE_PING6_RTHDR_RFC3542) |
| #ifndef IPV6_SRCRT_TYPE_0 |
| #define IPV6_SRCRT_TYPE_0 0 |
| #endif |
| #endif |
| |
| #ifndef MLD_LISTENER_QUERY |
| #define MLD_LISTENER_QUERY 130 |
| #define MLD_LISTENER_REPORT 131 |
| #define MLD_LISTENER_REDUCTION 132 |
| #endif |
| |
| #define BIT_CLEAR(nr, addr) do { ((__u32 *)(addr))[(nr) >> 5] &= ~(1U << ((nr) & 31)); } while(0) |
| #define BIT_SET(nr, addr) do { ((__u32 *)(addr))[(nr) >> 5] |= (1U << ((nr) & 31)); } while(0) |
| #define BIT_TEST(nr, addr) do { (__u32 *)(addr))[(nr) >> 5] & (1U << ((nr) & 31)); } while(0) |
| |
| #ifndef ICMP6_FILTER_WILLPASS |
| #define ICMP6_FILTER_WILLPASS(type, filterp) \ |
| (BIT_TEST((type), filterp) == 0) |
| |
| #define ICMP6_FILTER_WILLBLOCK(type, filterp) \ |
| BIT_TEST((type), filterp) |
| |
| #define ICMP6_FILTER_SETPASS(type, filterp) \ |
| BIT_CLEAR((type), filterp) |
| |
| #define ICMP6_FILTER_SETBLOCK(type, filterp) \ |
| BIT_SET((type), filterp) |
| |
| #define ICMP6_FILTER_SETPASSALL(filterp) \ |
| memset(filterp, 0, sizeof(struct icmp6_filter)); |
| |
| #define ICMP6_FILTER_SETBLOCKALL(filterp) \ |
| memset(filterp, 0xFF, sizeof(struct icmp6_filter)); |
| #endif |
| |
| #define MAXPACKET 128000 /* max packet size */ |
| |
| #ifdef SO_TIMESTAMP |
| #define HAVE_SIN6_SCOPEID 1 |
| #endif |
| |
| #ifndef SCOPE_DELIMITER |
| # define SCOPE_DELIMITER '%' |
| #endif |
| |
| __u32 flowlabel; |
| __u32 tclass; |
| #ifdef ENABLE_PING6_RTHDR |
| struct cmsghdr *srcrt; |
| #endif |
| |
| struct sockaddr_in6 whereto; /* who to ping */ |
| u_char outpack[MAXPACKET]; |
| int maxpacket = sizeof(outpack); |
| |
| static unsigned char cmsgbuf[4096]; |
| static int cmsglen = 0; |
| |
| static char * pr_addr(struct in6_addr *addr); |
| static char * pr_addr_n(struct in6_addr *addr); |
| static int pr_icmph(__u8 type, __u8 code, __u32 info); |
| static void usage(void) __attribute((noreturn)); |
| |
| struct sockaddr_in6 source; |
| char *device; |
| int pmtudisc=-1; |
| |
| static int icmp_sock; |
| |
| #ifdef USE_GNUTLS |
| # include <gnutls/openssl.h> |
| #else |
| # include <openssl/md5.h> |
| #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(__u16 *p) |
| { |
| __u16 v; |
| memcpy(&v, p, sizeof(v)); |
| return ntohs(v); |
| } |
| |
| #if defined(ENABLE_PING6_RTHDR) && !defined(ENABLE_PING6_RTHDR_RFC3542) |
| size_t inet6_srcrt_space(int type, int segments) |
| { |
| if (type != 0 || segments > 24) |
| return 0; |
| |
| return (sizeof(struct cmsghdr) + sizeof(struct ip6_rthdr0) + |
| segments * sizeof(struct in6_addr)); |
| } |
| |
| extern struct cmsghdr * inet6_srcrt_init(void *bp, int type) |
| { |
| struct cmsghdr *cmsg; |
| |
| if (type) |
| return NULL; |
| |
| memset(bp, 0, sizeof(struct cmsghdr) + sizeof(struct ip6_rthdr0)); |
| cmsg = (struct cmsghdr *) bp; |
| |
| cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(struct ip6_rthdr0); |
| cmsg->cmsg_level = SOL_IPV6; |
| cmsg->cmsg_type = IPV6_RTHDR; |
| |
| return cmsg; |
| } |
| |
| int inet6_srcrt_add(struct cmsghdr *cmsg, const struct in6_addr *addr) |
| { |
| struct ip6_rthdr0 *hdr; |
| |
| hdr = (struct ip6_rthdr0 *) CMSG_DATA(cmsg); |
| |
| cmsg->cmsg_len += sizeof(struct in6_addr); |
| hdr->ip6r0_len += sizeof(struct in6_addr) / 8; |
| |
| memcpy(&hdr->ip6r0_addr[hdr->ip6r0_segleft++], addr, |
| sizeof(struct in6_addr)); |
| |
| return 0; |
| } |
| #endif |
| |
| unsigned int if_name2index(const char *ifname) |
| { |
| unsigned int i = if_nametoindex(ifname); |
| if (!i) { |
| fprintf(stderr, "ping: unknown iface %s\n", ifname); |
| exit(2); |
| } |
| 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, const char *arg); |
| static int niquery_option_ipv6_handler(int index, const char *arg); |
| 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), |
| {}, |
| }; |
| |
| static inline int niquery_is_enabled(void) |
| { |
| return ni_query >= 0; |
| } |
| |
| #if PING6_NONCE_MEMORY |
| __u8 *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) { |
| perror("ping6: calloc"); |
| exit(2); |
| } |
| |
| 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(__u8 *nonce, int fill) |
| { |
| static __u8 digest[MD5_DIGEST_LENGTH]; |
| static int seq = -1; |
| |
| if (fill || seq != *(__u16 *)nonce || seq < 0) { |
| MD5_CTX ctxt; |
| |
| MD5_Init(&ctxt); |
| MD5_Update(&ctxt, &ni_nonce_secret, sizeof(ni_nonce_secret)); |
| MD5_Update(&ctxt, nonce, sizeof(__u16)); |
| MD5_Final(digest, &ctxt); |
| |
| seq = *(__u16 *)nonce; |
| } |
| |
| if (fill) { |
| memcpy(nonce + sizeof(__u16), digest, NI_NONCE_SIZE - sizeof(__u16)); |
| return 0; |
| } else { |
| if (memcmp(nonce + sizeof(__u16), digest, NI_NONCE_SIZE - sizeof(__u16))) |
| return -1; |
| return ntohsp((__u16 *)nonce); |
| } |
| } |
| #endif |
| |
| static inline void niquery_fill_nonce(__u16 seq, __u8 *nonce) |
| { |
| __u16 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(__u8 *nonce) |
| { |
| #if PING6_NONCE_MEMORY |
| __u16 seq = ntohsp((__u16 *)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, const char *arg) |
| { |
| if (niquery_set_qtype(NI_QTYPE_NAME) < 0) |
| return -1; |
| return 0; |
| } |
| |
| static int niquery_option_ipv6_handler(int index, const char *arg) |
| { |
| if (niquery_set_qtype(NI_QTYPE_IPV6ADDR) < 0) |
| return -1; |
| return 0; |
| } |
| |
| static int niquery_option_ipv6_flag_handler(int index, const char *arg) |
| { |
| 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, const char *arg) |
| { |
| if (niquery_set_qtype(NI_QTYPE_IPV4ADDR) < 0) |
| return -1; |
| return 0; |
| } |
| |
| static int niquery_option_ipv4_flag_handler(int index, const char *arg) |
| { |
| 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, *ai0, *ai; |
| int offset; |
| int gai; |
| |
| if (niquery_set_subject_type(niquery_options[index].data) < 0) |
| return -1; |
| |
| ni_subject_type = niquery_options[index].data; |
| |
| memset(&hints, 0, sizeof(hints)); |
| |
| 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; |
| } |
| |
| hints.ai_socktype = SOCK_DGRAM; |
| #ifdef USE_IDN |
| hints.ai_flags = AI_IDN; |
| #endif |
| |
| gai = getaddrinfo(arg, 0, &hints, &ai0); |
| if (gai) { |
| fprintf(stderr, "Unknown host: %s\n", arg); |
| return -1; |
| } |
| |
| for (ai = ai0; ai; ai = ai->ai_next) { |
| void *p = malloc(ni_subject_len); |
| if (!p) |
| continue; |
| memcpy(p, (__u8 *)ai->ai_addr + offset, ni_subject_len); |
| free(ni_subject); |
| ni_subject = p; |
| break; |
| } |
| freeaddrinfo(ai0); |
| |
| return 0; |
| } |
| |
| static int niquery_option_subject_name_handler(int index, const char *arg) |
| { |
| static char nigroup_buf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ]; |
| unsigned char *dnptrs[2], **dpp, **lastdnptr; |
| int n; |
| int i; |
| char *name, *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; |
| __u8 digest[MD5_DIGEST_LENGTH]; |
| #ifdef USE_IDN |
| int rc; |
| #endif |
| |
| if (niquery_set_subject_type(NI_SUBJ_NAME) < 0) |
| return -1; |
| |
| #ifdef USE_IDN |
| name = stringprep_locale_to_utf8(arg); |
| if (!name) { |
| fprintf(stderr, "ping6: IDN support failed.\n"); |
| exit(2); |
| } |
| #else |
| name = strdup(arg); |
| if (!name) |
| goto oomexit; |
| #endif |
| |
| p = strchr(name, SCOPE_DELIMITER); |
| if (p) { |
| *p = '\0'; |
| if (strlen(p + 1) >= IFNAMSIZ) { |
| fprintf(stderr, "ping6: too long scope name.\n"); |
| exit(1); |
| } |
| } |
| |
| #ifdef USE_IDN |
| rc = idna_to_ascii_8z(name, &idn, 0); |
| if (rc) { |
| fprintf(stderr, "ping6: IDN encoding error: %s\n", |
| idna_strerror(rc)); |
| exit(2); |
| } |
| #else |
| idn = strdup(name); |
| if (!idn) |
| goto oomexit; |
| #endif |
| |
| 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) { |
| fprintf(stderr, "ping6: out of memory.\n"); |
| 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) { |
| fprintf(stderr, "ping6: Inappropriate subject name: %s\n", canonname); |
| goto errexit; |
| } else if (n >= buflen) { |
| fprintf(stderr, "ping6: dn_comp() returned too long result.\n"); |
| 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); |
| free(name); |
| |
| return 0; |
| oomexit: |
| fprintf(stderr, "ping6: out of memory.\n"); |
| errexit: |
| free(buf); |
| free(canonname); |
| free(idn); |
| free(name); |
| exit(1); |
| } |
| |
| int niquery_option_help_handler(int index, const char *arg) |
| { |
| fprintf(stderr, "ping6 -N suboptions\n" |
| "\tHelp:\n" |
| "\t\thelp\n" |
| "\tQuery:\n" |
| "\t\tname,\n" |
| "\t\tipv6,ipv6-all,ipv6-compatible,ipv6-linklocal,ipv6-sitelocal,ipv6-global,\n" |
| "\t\tipv4,ipv4-all,\n" |
| "\tSubject:\n" |
| "\t\tsubject-ipv6=addr,subject-ipv4=addr,subject-name=name,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; |
| } |
| |
| static int hextoui(const char *str) |
| { |
| unsigned long val; |
| char *ep; |
| |
| errno = 0; |
| val = strtoul(str, &ep, 16); |
| if (*ep) { |
| if (!errno) |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (val > UINT_MAX) { |
| errno = ERANGE; |
| return UINT_MAX; |
| } |
| |
| return val; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int ch, hold, packlen; |
| u_char *packet; |
| char *target; |
| struct addrinfo hints, *ai; |
| int gai; |
| struct sockaddr_in6 firsthop; |
| int socket_errno = 0; |
| struct icmp6_filter filter; |
| int err; |
| #ifdef __linux__ |
| int csum_offset, sz_opt; |
| #endif |
| static uint32_t scope_id = 0; |
| |
| #ifdef ANDROID |
| android_check_security(); |
| #endif |
| |
| limit_capabilities(); |
| |
| #ifdef USE_IDN |
| setlocale(LC_ALL, ""); |
| #endif |
| |
| icmp_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); |
| if (icmp_sock < 0) { |
| enable_capability_raw(); |
| icmp_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); |
| socket_errno = errno; |
| disable_capability_raw(); |
| using_ping_socket = 0; |
| } |
| |
| source.sin6_family = AF_INET6; |
| memset(&firsthop, 0, sizeof(firsthop)); |
| firsthop.sin6_family = AF_INET6; |
| |
| preload = 1; |
| while ((ch = getopt(argc, argv, COMMON_OPTSTR "F:N:")) != EOF) { |
| switch(ch) { |
| case 'F': |
| flowlabel = hextoui(optarg); |
| if (errno || (flowlabel & ~IPV6_FLOWINFO_FLOWLABEL)) { |
| fprintf(stderr, "ping: Invalid flowinfo %s\n", optarg); |
| exit(2); |
| } |
| options |= F_FLOWINFO; |
| break; |
| case 'Q': |
| tclass = hextoui(optarg); |
| if (errno || (tclass & ~0xff)) { |
| fprintf(stderr, "ping: Invalid tclass %s\n", optarg); |
| exit(2); |
| } |
| options |= F_TCLASS; |
| break; |
| case 'I': |
| if (strchr(optarg, ':')) { |
| char *p, *addr = strdup(optarg); |
| |
| if (!addr) { |
| fprintf(stderr, "ping: out of memory\n"); |
| exit(2); |
| } |
| |
| p = strchr(addr, SCOPE_DELIMITER); |
| if (p) { |
| *p = '\0'; |
| device = optarg + (p - addr) + 1; |
| } |
| |
| if (inet_pton(AF_INET6, addr, (char*)&source.sin6_addr) <= 0) { |
| fprintf(stderr, "ping: invalid source address %s\n", optarg); |
| exit(2); |
| } |
| |
| options |= F_STRICTSOURCE; |
| |
| free(addr); |
| } else { |
| device = optarg; |
| } |
| break; |
| case 'M': |
| if (strcmp(optarg, "do") == 0) |
| pmtudisc = IPV6_PMTUDISC_DO; |
| else if (strcmp(optarg, "dont") == 0) |
| pmtudisc = IPV6_PMTUDISC_DONT; |
| else if (strcmp(optarg, "want") == 0) |
| pmtudisc = IPV6_PMTUDISC_WANT; |
| else { |
| fprintf(stderr, "ping: wrong value for -M: do, dont, want are valid ones.\n"); |
| exit(2); |
| } |
| break; |
| case 'V': |
| printf("ping6 utility, iputils-%s\n", SNAPSHOT); |
| exit(0); |
| case 'N': |
| if (using_ping_socket) { |
| fprintf(stderr, "ping: -N requires raw socket permissions\n"); |
| exit(2); |
| } |
| if (niquery_option_handler(optarg) < 0) { |
| usage(); |
| break; |
| } |
| break; |
| COMMON_OPTIONS |
| common_options(ch); |
| break; |
| default: |
| usage(); |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| #ifdef ENABLE_PING6_RTHDR |
| while (argc > 1) { |
| struct in6_addr *addr; |
| |
| if (srcrt == NULL) { |
| int space; |
| |
| fprintf(stderr, "ping6: Warning: " |
| "Source routing is deprecated by RFC5095.\n"); |
| |
| #ifdef ENABLE_PING6_RTHDR_RFC3542 |
| space = inet6_rth_space(IPV6_RTHDR_TYPE_0, argc - 1); |
| #else |
| space = inet6_srcrt_space(IPV6_SRCRT_TYPE_0, argc - 1); |
| #endif |
| if (space == 0) { |
| fprintf(stderr, "srcrt_space failed\n"); |
| exit(2); |
| } |
| #ifdef ENABLE_PING6_RTHDR_RFC3542 |
| if (cmsglen + CMSG_SPACE(space) > sizeof(cmsgbuf)) { |
| fprintf(stderr, "no room for options\n"); |
| exit(2); |
| } |
| #else |
| if (space + cmsglen > sizeof(cmsgbuf)) { |
| fprintf(stderr, "no room for options\n"); |
| exit(2); |
| } |
| #endif |
| srcrt = (struct cmsghdr*)(cmsgbuf+cmsglen); |
| #ifdef ENABLE_PING6_RTHDR_RFC3542 |
| memset(srcrt, 0, CMSG_SPACE(0)); |
| srcrt->cmsg_len = CMSG_LEN(space); |
| srcrt->cmsg_level = IPPROTO_IPV6; |
| srcrt->cmsg_type = IPV6_RTHDR; |
| inet6_rth_init(CMSG_DATA(srcrt), space, IPV6_RTHDR_TYPE_0, argc - 1); |
| cmsglen += CMSG_SPACE(space); |
| #else |
| cmsglen += CMSG_ALIGN(space); |
| inet6_srcrt_init(srcrt, IPV6_SRCRT_TYPE_0); |
| #endif |
| } |
| |
| target = *argv; |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_INET6; |
| #ifdef USE_IDN |
| hints.ai_flags = AI_IDN; |
| #endif |
| gai = getaddrinfo(target, NULL, &hints, &ai); |
| if (gai) { |
| fprintf(stderr, "unknown host\n"); |
| exit(2); |
| } |
| addr = &((struct sockaddr_in6 *)(ai->ai_addr))->sin6_addr; |
| #ifdef ENABLE_PING6_RTHDR_RFC3542 |
| inet6_rth_add(CMSG_DATA(srcrt), addr); |
| #else |
| inet6_srcrt_add(srcrt, addr); |
| #endif |
| if (IN6_IS_ADDR_UNSPECIFIED(&firsthop.sin6_addr)) { |
| memcpy(&firsthop.sin6_addr, addr, 16); |
| #ifdef HAVE_SIN6_SCOPEID |
| firsthop.sin6_scope_id = ((struct sockaddr_in6 *)(ai->ai_addr))->sin6_scope_id; |
| /* Verify scope_id is the same as previous nodes */ |
| if (firsthop.sin6_scope_id && scope_id && firsthop.sin6_scope_id != scope_id) { |
| fprintf(stderr, "scope discrepancy among the nodes\n"); |
| exit(2); |
| } else if (!scope_id) { |
| scope_id = firsthop.sin6_scope_id; |
| } |
| #endif |
| } |
| freeaddrinfo(ai); |
| |
| argv++; |
| argc--; |
| } |
| #endif |
| |
| 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) { |
| #ifndef ENABLE_PING6_RTHDR |
| fprintf(stderr, "ping6: Source routing is deprecated by RFC5095.\n"); |
| #endif |
| usage(); |
| } else if (argc == 1) { |
| target = *argv; |
| } else { |
| if (ni_query < 0 && ni_subject_type != NI_SUBJ_NAME) |
| usage(); |
| target = ni_group; |
| } |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = AF_INET6; |
| #ifdef USE_IDN |
| hints.ai_flags = AI_IDN; |
| #endif |
| gai = getaddrinfo(target, NULL, &hints, &ai); |
| if (gai) { |
| fprintf(stderr, "unknown host\n"); |
| exit(2); |
| } |
| |
| memcpy(&whereto, ai->ai_addr, sizeof(whereto)); |
| whereto.sin6_port = htons(IPPROTO_ICMPV6); |
| |
| if (memchr(target, ':', strlen(target))) |
| options |= F_NUMERIC; |
| |
| freeaddrinfo(ai); |
| |
| if (IN6_IS_ADDR_UNSPECIFIED(&firsthop.sin6_addr)) { |
| memcpy(&firsthop.sin6_addr, &whereto.sin6_addr, 16); |
| #ifdef HAVE_SIN6_SCOPEID |
| 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) { |
| fprintf(stderr, "scope discrepancy among the nodes\n"); |
| exit(2); |
| } else if (!scope_id) { |
| scope_id = firsthop.sin6_scope_id; |
| } |
| #endif |
| } |
| |
| hostname = target; |
| |
| if (IN6_IS_ADDR_UNSPECIFIED(&source.sin6_addr)) { |
| socklen_t alen; |
| int probe_fd = socket(AF_INET6, SOCK_DGRAM, 0); |
| |
| if (probe_fd < 0) { |
| perror("socket"); |
| exit(2); |
| } |
| if (device) { |
| #if defined(IPV6_RECVPKTINFO) || defined(HAVE_SIN6_SCOPEID) |
| unsigned int iface = if_name2index(device); |
| #endif |
| #ifdef IPV6_RECVPKTINFO |
| struct in6_pktinfo ipi; |
| |
| memset(&ipi, 0, sizeof(ipi)); |
| ipi.ipi6_ifindex = iface; |
| #endif |
| |
| #ifdef HAVE_SIN6_SCOPEID |
| if (IN6_IS_ADDR_LINKLOCAL(&firsthop.sin6_addr) || |
| IN6_IS_ADDR_MC_LINKLOCAL(&firsthop.sin6_addr)) |
| firsthop.sin6_scope_id = iface; |
| #endif |
| enable_capability_raw(); |
| if ( |
| #ifdef IPV6_RECVPKTINFO |
| setsockopt(probe_fd, IPPROTO_IPV6, IPV6_PKTINFO, &ipi, sizeof(ipi)) == -1 && |
| #endif |
| setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device)+1) == -1) { |
| perror("setsockopt(SO_BINDTODEVICE)"); |
| exit(2); |
| } |
| disable_capability_raw(); |
| } |
| firsthop.sin6_port = htons(1025); |
| |
| sock_setmark(probe_fd); |
| |
| if (connect(probe_fd, (struct sockaddr*)&firsthop, sizeof(firsthop)) == -1) { |
| perror("connect"); |
| exit(2); |
| } |
| alen = sizeof(source); |
| if (getsockname(probe_fd, (struct sockaddr*)&source, &alen) == -1) { |
| perror("getsockname"); |
| exit(2); |
| } |
| source.sin6_port = 0; |
| close(probe_fd); |
| |
| #ifndef WITHOUT_IFADDRS |
| if (device) { |
| struct ifaddrs *ifa0, *ifa; |
| |
| if (getifaddrs(&ifa0)) { |
| perror("getifaddrs"); |
| exit(2); |
| } |
| |
| for (ifa = ifa0; ifa; ifa = ifa->ifa_next) { |
| if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET6) |
| continue; |
| if (!strncmp(ifa->ifa_name, device, sizeof(device) - 1) && |
| IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, |
| &source.sin6_addr)) |
| break; |
| } |
| if (!ifa) |
| fprintf(stderr, "ping6: Warning: source address might be selected on device other than %s.\n", device); |
| |
| freeifaddrs(ifa0); |
| } |
| #endif |
| } |
| #ifdef HAVE_SIN6_SCOPEID |
| else if (device && (IN6_IS_ADDR_LINKLOCAL(&source.sin6_addr) || |
| IN6_IS_ADDR_MC_LINKLOCAL(&source.sin6_addr))) |
| source.sin6_scope_id = if_name2index(device); |
| #endif |
| |
| if (icmp_sock < 0) { |
| errno = socket_errno; |
| perror("ping: icmp open socket"); |
| exit(2); |
| } |
| |
| 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 = SOL_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) { |
| fprintf(stderr, "ping: multicast ping with too short interval.\n"); |
| exit(2); |
| } |
| if (pmtudisc >= 0 && pmtudisc != IPV6_PMTUDISC_DO) { |
| fprintf(stderr, "ping: multicast ping does not fragment.\n"); |
| exit(2); |
| } |
| } |
| if (pmtudisc < 0) |
| pmtudisc = IPV6_PMTUDISC_DO; |
| } |
| |
| if (pmtudisc >= 0) { |
| if (setsockopt(icmp_sock, SOL_IPV6, IPV6_MTU_DISCOVER, &pmtudisc, sizeof(pmtudisc)) == -1) { |
| perror("ping: IPV6_MTU_DISCOVER"); |
| exit(2); |
| } |
| } |
| |
| if ((options&F_STRICTSOURCE) && |
| bind(icmp_sock, (struct sockaddr*)&source, sizeof(source)) == -1) { |
| perror("ping: bind icmp socket"); |
| exit(2); |
| } |
| |
| if (datalen >= sizeof(struct timeval) && (ni_query < 0)) { |
| /* can we time transfer */ |
| timing = 1; |
| } |
| packlen = datalen + 8 + 4096 + 40 + 8; /* 4096 for rthdr */ |
| if (!(packet = (u_char *)malloc((u_int)packlen))) { |
| fprintf(stderr, "ping: out of memory.\n"); |
| exit(2); |
| } |
| |
| working_recverr = 1; |
| hold = 1; |
| if (setsockopt(icmp_sock, SOL_IPV6, IPV6_RECVERR, (char *)&hold, sizeof(hold))) { |
| fprintf(stderr, "WARNING: your kernel is veeery old. No problems.\n"); |
| working_recverr = 0; |
| } |
| |
| /* 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(icmp_sock, hold); |
| |
| if (!using_ping_socket) { |
| #ifdef __linux__ |
| csum_offset = 2; |
| sz_opt = sizeof(int); |
| |
| err = setsockopt(icmp_sock, SOL_RAW, IPV6_CHECKSUM, |
| &csum_offset, sz_opt); |
| if (err < 0) { |
| /* checksum should be enabled by default and setting |
| * this option might fail anyway. |
| */ |
| fprintf(stderr, "setsockopt(RAW_CHECKSUM) failed" |
| " - try to continue."); |
| } |
| #endif |
| |
| /* |
| * select icmp echo reply as icmp type to receive |
| */ |
| |
| ICMP6_FILTER_SETBLOCKALL(&filter); |
| |
| if (!working_recverr) { |
| 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(icmp_sock, IPPROTO_ICMPV6, ICMP6_FILTER, |
| &filter, sizeof(struct icmp6_filter)); |
| |
| if (err < 0) { |
| perror("setsockopt(ICMP6_FILTER)"); |
| exit(2); |
| } |
| } |
| |
| if (options & F_NOLOOP) { |
| int loop = 0; |
| if (setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, |
| &loop, sizeof(loop)) == -1) { |
| perror ("can't disable multicast loopback"); |
| exit(2); |
| } |
| } |
| if (options & F_TTL) { |
| if (setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, |
| &ttl, sizeof(ttl)) == -1) { |
| perror ("can't set multicast hop limit"); |
| exit(2); |
| } |
| if (setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, |
| &ttl, sizeof(ttl)) == -1) { |
| perror ("can't set unicast hop limit"); |
| exit(2); |
| } |
| } |
| |
| if (1) { |
| int on = 1; |
| if ( |
| #ifdef IPV6_RECVHOPLIMIT |
| setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, |
| &on, sizeof(on)) == -1 && |
| setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_2292HOPLIMIT, |
| &on, sizeof(on)) == -1 |
| #else |
| setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_HOPLIMIT, |
| &on, sizeof(on)) == -1 |
| #endif |
| ){ |
| perror ("can't receive hop limit"); |
| exit(2); |
| } |
| } |
| |
| if (options & F_TCLASS) { |
| #ifdef IPV6_TCLASS |
| if (setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_TCLASS, |
| &tclass, sizeof(tclass)) == -1) { |
| perror ("setsockopt(IPV6_TCLASS)"); |
| exit(2); |
| } |
| #else |
| fprintf(stderr, "Traffic class is not supported.\n"); |
| #endif |
| } |
| |
| if (options&F_FLOWINFO) { |
| #ifdef IPV6_FLOWINFO_SEND |
| int on = 1; |
| #endif |
| #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); |
| #ifdef ENABLE_PING6_RTHDR |
| if (srcrt) |
| freq_len = CMSG_ALIGN(sizeof(*freq)) + srcrt->cmsg_len; |
| #endif |
| 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); |
| #ifdef ENABLE_PING6_RTHDR |
| if (srcrt) |
| memcpy(freq_buf + CMSG_ALIGN(sizeof(*freq)), srcrt, srcrt->cmsg_len); |
| #endif |
| if (setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, |
| freq, freq_len) == -1) { |
| perror ("can't set flowlabel"); |
| exit(2); |
| } |
| flowlabel = freq->flr_label; |
| #ifdef ENABLE_PING6_RTHDR |
| if (srcrt) { |
| cmsglen = (char*)srcrt - (char*)cmsgbuf; |
| srcrt = NULL; |
| } |
| #endif |
| #else |
| fprintf(stderr, "Flow labels are not supported.\n"); |
| exit(2); |
| #endif |
| |
| #ifdef IPV6_FLOWINFO_SEND |
| whereto.sin6_flowinfo = flowlabel; |
| if (setsockopt(icmp_sock, IPPROTO_IPV6, IPV6_FLOWINFO_SEND, |
| &on, sizeof(on)) == -1) { |
| perror ("can't send flowinfo"); |
| exit(2); |
| } |
| #else |
| fprintf(stderr, "Flowinfo is not supported.\n"); |
| exit(2); |
| #endif |
| } |
| |
| printf("PING %s(%s) ", hostname, pr_addr(&whereto.sin6_addr)); |
| if (flowlabel) |
| printf(", flow 0x%05x, ", (unsigned)ntohl(flowlabel)); |
| if (device || (options&F_STRICTSOURCE)) { |
| printf("from %s %s: ", |
| pr_addr_n(&source.sin6_addr), device ? : ""); |
| } |
| printf("%d data bytes\n", datalen); |
| |
| setup(icmp_sock); |
| |
| drop_capabilities(); |
| |
| main_loop(icmp_sock, packet, packlen); |
| } |
| |
| int receive_error_msg() |
| { |
| int 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(icmp_sock, &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 == SOL_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) |
| fprintf(stderr, "ping: local error: %s\n", strerror(e->ee_errno)); |
| else |
| fprintf(stderr, "ping: local error: Message too long, mtu=%u\n", e->ee_info); |
| nerrors++; |
| } else if (e->ee_origin == SO_EE_ORIGIN_ICMP6) { |
| struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)(e+1); |
| |
| if (res < sizeof(icmph) || |
| memcmp(&target.sin6_addr, &whereto.sin6_addr, 16) || |
| icmph.icmp6_type != ICMP6_ECHO_REQUEST || |
| !is_ours(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->sin6_addr), 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 ? : -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 8 bytes |
| * of the data portion are used to hold a UNIX "timeval" struct in VAX |
| * byte-order, to compute the round-trip time. |
| */ |
| int build_echo(__u8 *_icmph) |
| { |
| 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 *)&outpack[8], |
| (struct timezone *)NULL); |
| |
| cc = datalen + 8; /* skips ICMP portion */ |
| |
| return cc; |
| } |
| |
| |
| int build_niquery(__u8 *_nih) |
| { |
| 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 send_probe(void) |
| { |
| int len, cc; |
| |
| rcvd_clear(ntransmitted + 1); |
| |
| if (niquery_is_enabled()) |
| len = build_niquery(outpack); |
| else |
| len = build_echo(outpack); |
| |
| if (cmsglen == 0) { |
| cc = sendto(icmp_sock, (char *)outpack, len, confirm, |
| (struct sockaddr *) &whereto, |
| sizeof(struct sockaddr_in6)); |
| } else { |
| struct msghdr mhdr; |
| struct iovec iov; |
| |
| iov.iov_len = len; |
| iov.iov_base = outpack; |
| |
| 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(icmp_sock, &mhdr, confirm); |
| } |
| confirm = 0; |
| |
| return (cc == len ? 0 : cc); |
| } |
| |
| void pr_echo_reply(__u8 *_icmph, int cc) |
| { |
| struct icmp6_hdr *icmph = (struct icmp6_hdr *) _icmph; |
| printf(" icmp_seq=%u", ntohs(icmph->icmp6_seq)); |
| }; |
| |
| static void putchar_safe(char c) |
| { |
| if (isprint(c)) |
| putchar(c); |
| else |
| printf("\\%03o", c); |
| } |
| |
| void pr_niquery_reply_name(struct ni_hdr *nih, int len) |
| { |
| __u8 *h = (__u8 *)(nih + 1); |
| __u8 *p = h + 4; |
| __u8 *end = (__u8 *)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; |
| int 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; |
| } |
| } |
| |
| void pr_niquery_reply_addr(struct ni_hdr *nih, int len) |
| { |
| __u8 *h = (__u8 *)(nih + 1); |
| __u8 *p = h + 4; |
| __u8 *end = (__u8 *)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(__u32) + aflen > end) { |
| printf(" parse error (truncated)"); |
| break; |
| } |
| if (!inet_ntop(af, p + sizeof(__u32), buf, sizeof(buf))) |
| printf(" unexpeced error in inet_ntop(%s)", |
| strerror(errno)); |
| else |
| printf(" %s", buf); |
| p += sizeof(__u32) + aflen; |
| |
| continued = 1; |
| } |
| if (truncated) |
| printf(" (truncated)"); |
| } |
| |
| void pr_niquery_reply(__u8 *_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((__u16*)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 |
| parse_reply(struct msghdr *msg, int cc, void *addr, struct timeval *tv) |
| { |
| struct sockaddr_in6 *from = addr; |
| __u8 *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 != SOL_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) |
| fprintf(stderr, "ping: packet too short (%d bytes)\n", cc); |
| return 1; |
| } |
| |
| if (icmph->icmp6_type == ICMP6_ECHO_REPLY) { |
| if (!is_ours(icmph->icmp6_id)) |
| return 1; |
| if (gather_statistics((__u8*)icmph, sizeof(*icmph), cc, |
| ntohs(icmph->icmp6_seq), |
| hops, 0, tv, pr_addr(&from->sin6_addr), |
| pr_echo_reply)) |
| 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((__u8*)icmph, sizeof(*icmph), cc, |
| seq, |
| hops, 0, tv, pr_addr(&from->sin6_addr), |
| 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 < 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 = *(__u8*)icmph1; |
| icmph1++; |
| } |
| if (nexthdr == IPPROTO_ICMPV6) { |
| if (icmph1->icmp6_type != ICMP6_ECHO_REQUEST || |
| !is_ours(icmph1->icmp6_id)) |
| return 1; |
| acknowledge(ntohs(icmph1->icmp6_seq)); |
| if (working_recverr) |
| return 0; |
| nerrors++; |
| if (options & F_FLOOD) { |
| write_stdout("\bE", 2); |
| return 0; |
| } |
| print_timestamp(); |
| printf("From %s: icmp_seq=%u ", pr_addr(&from->sin6_addr), 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->sin6_addr)); |
| } |
| pr_icmph(icmph->icmp6_type, icmph->icmp6_code, ntohl(icmph->icmp6_mtu)); |
| } |
| |
| if (!(options & F_FLOOD)) { |
| if (options & F_AUDIBLE) |
| putchar('\a'); |
| putchar('\n'); |
| fflush(stdout); |
| } else { |
| putchar('\a'); |
| fflush(stdout); |
| } |
| return 0; |
| } |
| |
| |
| int pr_icmph(__u8 type, __u8 code, __u32 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; |
| } |
| |
| #include <linux/filter.h> |
| |
| void install_filter(void) |
| { |
| 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(icmp_sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) |
| perror("WARNING: failed to install socket filter\n"); |
| } |
| |
| |
| /* |
| * pr_addr -- |
| * Return an ascii host address as a dotted quad and optionally with |
| * a hostname. |
| */ |
| char * pr_addr(struct in6_addr *addr) |
| { |
| struct hostent *hp = NULL; |
| static char *s; |
| |
| #ifdef USE_IDN |
| free(s); |
| #endif |
| |
| in_pr_addr = !setjmp(pr_addr_jmp); |
| |
| if (!(exiting || options&F_NUMERIC)) |
| hp = gethostbyaddr((__u8*)addr, sizeof(struct in6_addr), AF_INET6); |
| |
| in_pr_addr = 0; |
| |
| if (!hp |
| #ifdef USE_IDN |
| || idna_to_unicode_lzlz(hp->h_name, &s, 0) != IDNA_SUCCESS |
| #endif |
| ) |
| s = NULL; |
| |
| return hp ? (s ? s : hp->h_name) : pr_addr_n(addr); |
| } |
| |
| char * pr_addr_n(struct in6_addr *addr) |
| { |
| static char str[64]; |
| inet_ntop(AF_INET6, addr, str, sizeof(str)); |
| return str; |
| } |
| |
| #define USAGE_NEWLINE "\n " |
| |
| void usage(void) |
| { |
| fprintf(stderr, |
| "Usage: ping6" |
| " [-" |
| "aAbBdDfhLnOqrRUvV" |
| "]" |
| " [-c count]" |
| " [-i interval]" |
| " [-I interface]" |
| USAGE_NEWLINE |
| " [-l preload]" |
| " [-m mark]" |
| " [-M pmtudisc_option]" |
| USAGE_NEWLINE |
| " [-N nodeinfo_option]" |
| " [-p pattern]" |
| " [-Q tclass]" |
| " [-s packetsize]" |
| USAGE_NEWLINE |
| " [-S sndbuf]" |
| " [-t ttl]" |
| " [-T timestamp_option]" |
| " [-w deadline]" |
| USAGE_NEWLINE |
| " [-W timeout]" |
| #ifdef ENABLE_PING6_RTHDR |
| " [hop1 ...]" |
| #endif |
| " destination" |
| "\n" |
| ); |
| exit(2); |
| } |