/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T * All Rights Reserved. */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California. * All Rights Reserved. * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ping.h" /* * This macro is used to compare 16bit, wrapping sequence numbers. Inspired by * TCP's SEQ_LEQ macro. */ #define PINGSEQ_LEQ(a, b) ((int16_t)((a)-(b)) <= 0) #define MAX_WAIT 10 /* max sec. to wait for response */ #define MAX_TRAFFIC_CLASS 255 /* max traffic class for IPv6 */ #define MAX_FLOW_LABEL 0xFFFFF /* max flow label for IPv6 */ #define MAX_TOS 255 /* max type-of-service for IPv4 */ #define TIMEOUT 20 /* default timeout value */ #define DEFAULT_DATALEN 56 #define MULTICAST_NOLOOP 1 /* multicast options */ #define MULTICAST_TTL 2 #define MULTICAST_IF 4 #define IF_INDEX 0 /* types of -i argument */ #define IF_NAME 1 #define IF_ADDR 2 #define IF_ADDR6 3 #ifdef BSD #define setbuf(s, b) setlinebuf((s)) #endif /* BSD */ /* interface identification */ union if_id { int index; /* interface index (e.g., 1, 2) */ char *name; /* interface name (e.g., le0, hme0) */ union any_in_addr addr; /* interface address (e.g., 10.123.4.5) */ }; /* stores the interface supplied by the user */ struct if_entry { char *str; /* unresolved, string input */ int id_type; /* type of ID (index, name, addr, addr6) */ union if_id id; /* ID */ }; char *progname; char *targethost; static int send_sock; /* send sockets */ static int send_sock6; static struct sockaddr_in to; /* where to send */ static struct sockaddr_in6 to6; static union any_in_addr gw_IP_list[MAX_GWS]; /* gateways */ static union any_in_addr gw_IP_list6[MAX_GWS6]; static int if_index = 0; /* outgoing interface index */ boolean_t is_alive = _B_FALSE; /* is target host alive */ struct targetaddr *current_targetaddr; /* current target IP address to probe */ static struct targetaddr *targetaddr_list; /* list of IP addresses to probe */ static int num_targetaddrs; /* no of target addresses to probe */ static int num_v4 = 0; /* count of IPv4 addresses */ static int num_v6 = 0; /* count of IPv6 addresses */ boolean_t verbose = _B_FALSE; /* verbose output */ boolean_t stats = _B_FALSE; /* display statistics */ static boolean_t settos = _B_FALSE; /* set type-of-service value */ boolean_t rr_option = _B_FALSE; /* true if using record route */ boolean_t send_reply = _B_FALSE; /* Send an ICMP_{ECHO|TSTAMP}REPLY */ /* that goes to target and comes back */ /* to the the sender via src routing. */ boolean_t strict = _B_FALSE; /* true if using strict source route */ boolean_t ts_option = _B_FALSE; /* true if using timestamp option */ boolean_t use_icmp_ts = _B_FALSE; /* Use ICMP timestamp request */ boolean_t use_udp = _B_FALSE; /* Use UDP instead of ICMP */ boolean_t probe_all = _B_FALSE; /* probe all the IP addresses */ boolean_t nflag = _B_FALSE; /* do not reverse lookup addresses */ static int family_input = AF_UNSPEC; /* address family supplied by user */ int datalen = DEFAULT_DATALEN; /* How much data */ int ts_flag; /* timestamp flag value */ static int num_gw; /* number of gateways */ static int eff_num_gw; /* effective number of gateways */ /* if send_reply, it's 2*num_gw+1 */ static int num_wraps = -1; /* no of times 64K icmp_seq wrapped */ static ushort_t dest_port = 32768 + 666; /* starting port for the UDP probes */ static char *gw_list[MAXMAX_GWS]; /* list of gateways as user enters */ static int interval = 1; /* interval between transmissions */ static int options; /* socket options */ static int moptions; /* multicast options */ int npackets; /* number of packets to send */ static ushort_t tos; /* type-of-service value */ static int hoplimit = -1; /* time-to-live value */ static int timeout = TIMEOUT; /* timeout value (sec) for probes */ static struct if_entry out_if; /* interface argument */ int ident; /* ID for this ping run */ static hrtime_t t_last_probe_sent; /* the time we sent the last probe */ /* * This buffer stores the received packets. Currently it needs to be 32 bit * aligned. In the future, we'll be using 64 bit alignment, so let's use 64 bit * alignment now. */ static uint64_t in_pkt[(IP_MAXPACKET + 1)/8]; /* Used to store the ancillary data that comes with the received packets */ static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8]; static int ntransmitted; /* number of packet sent to single IP address */ int nreceived; /* # of packets we got back from target host */ int nreceived_last_target; /* received from last target IP */ /* * These are used for statistics. tmin is initialized to maximum longint value. * The max value is also used for timeouts. All times are in microseconds. */ long long tmin = LLONG_MAX; long long tmax; int64_t tsum; /* sum of all times, for doing average */ int64_t tsum2; /* sum of squared times, for std. dev. */ static struct targetaddr *build_targetaddr_list(struct addrinfo *, union any_in_addr *); extern void check_reply(struct addrinfo *, struct msghdr *, int, ushort_t); extern void check_reply6(struct addrinfo *, struct msghdr *, int, ushort_t); static struct targetaddr *create_targetaddr_item(int, union any_in_addr *, union any_in_addr *); void find_dstaddr(ushort_t, union any_in_addr *); static struct ifaddrlist *find_if(struct ifaddrlist *, int); static void finish(); static void get_gwaddrs(char *[], int, union any_in_addr *, union any_in_addr *, int *, int *); static void get_hostinfo(char *, int, struct addrinfo **); static ushort_t in_cksum(ushort_t *, int); static int int_arg(char *s, char *what); boolean_t is_a_target(struct addrinfo *, union any_in_addr *); static void mirror_gws(union any_in_addr *, int); static void pinger(int, struct sockaddr *, struct msghdr *, int); char *pr_name(char *, int); char *pr_protocol(int); static void print_unknown_host_msg(const char *, const char *); static void recv_icmp_packet(struct addrinfo *, int, int, ushort_t, ushort_t); static void resolve_nodes(struct addrinfo **, union any_in_addr **); void schedule_sigalrm(); static void select_all_src_addrs(union any_in_addr **, struct addrinfo *, union any_in_addr *, union any_in_addr *); static void select_src_addr(union any_in_addr *, int, union any_in_addr *); void send_scheduled_probe(); boolean_t seq_match(ushort_t, int, ushort_t); extern void set_ancillary_data(struct msghdr *, int, union any_in_addr *, int, uint_t); extern void set_IPv4_options(int, union any_in_addr *, int, struct in_addr *, struct in_addr *); static boolean_t setup_socket(int, int *, int *, int *, ushort_t *); void sigalrm_handler(); void tvsub(struct timeval *, struct timeval *); static void usage(char *); /* * main() */ void main(int argc, char *argv[]) { struct addrinfo *ai_dst = NULL; /* addrinfo host list */ union any_in_addr *src_addr_list = NULL; /* src addrs to use */ int recv_sock = -1; /* receive sockets */ int recv_sock6 = -1; ushort_t udp_src_port; /* src ports for UDP probes */ ushort_t udp_src_port6; /* used to identify replies */ uint_t flowinfo = 0; uint_t class = 0; char tmp_buf[INET6_ADDRSTRLEN]; int c; int i; progname = argv[0]; /* * This program needs the net_icmpaccess privileges. We'll fail * on the socket call and report the error there when we have * insufficient privileges. */ (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_NET_ICMPACCESS, (char *)NULL); setbuf(stdout, (char *)0); while ((c = getopt(argc, argv, "aA:c:dF:G:g:I:i:LlnP:p:rRSsTt:UvX:x:Y0123?")) != -1) { switch ((char)c) { case 'A': if (strcmp(optarg, "inet") == 0) { family_input = AF_INET; } else if (strcmp(optarg, "inet6") == 0) { family_input = AF_INET6; } else { Fprintf(stderr, "%s: unknown address family %s\n", progname, optarg); exit(EXIT_FAILURE); } break; case 'a': probe_all = _B_TRUE; break; case 'c': i = int_arg(optarg, "traffic class"); if (i > MAX_TRAFFIC_CLASS) { Fprintf(stderr, "%s: traffic class %d out of " "range\n", progname, i); exit(EXIT_FAILURE); } class = (uint_t)i; break; case 'd': options |= SO_DEBUG; break; case 'F': i = int_arg(optarg, "flow label"); if (i > MAX_FLOW_LABEL) { Fprintf(stderr, "%s: flow label %d out of " "range\n", progname, i); exit(EXIT_FAILURE); } flowinfo = (uint_t)i; break; case 'I': stats = _B_TRUE; interval = int_arg(optarg, "interval"); break; case 'i': /* * this can accept interface index, interface name, and * address configured on the interface */ moptions |= MULTICAST_IF; out_if.str = optarg; if (inet_pton(AF_INET6, optarg, &out_if.id.addr) > 0) { out_if.id_type = IF_ADDR6; } else if (inet_pton(AF_INET, optarg, &out_if.id.addr) > 0) { out_if.id_type = IF_ADDR; } else if (strcmp(optarg, "0") == 0) { out_if.id_type = IF_INDEX; out_if.id.index = 0; } else if ((out_if.id.index = atoi(optarg)) != 0) { out_if.id_type = IF_INDEX; } else { out_if.id.name = optarg; out_if.id_type = IF_NAME; } break; case 'L': moptions |= MULTICAST_NOLOOP; break; case 'l': send_reply = _B_TRUE; strict = _B_FALSE; break; case 'n': nflag = _B_TRUE; break; case 'P': settos = _B_TRUE; i = int_arg(optarg, "type-of-service"); if (i > MAX_TOS) { Fprintf(stderr, "%s: tos value %d out of " "range\n", progname, i); exit(EXIT_FAILURE); } tos = (ushort_t)i; break; case 'p': i = int_arg(optarg, "port number"); if (i > MAX_PORT) { Fprintf(stderr, "%s: port number %d out of " "range\n", progname, i); exit(EXIT_FAILURE); } dest_port = (ushort_t)i; break; case 'r': options |= SO_DONTROUTE; break; case 'R': rr_option = _B_TRUE; break; case 'S': send_reply = _B_TRUE; strict = _B_TRUE; break; case 's': stats = _B_TRUE; break; case 'T': ts_option = _B_TRUE; break; case 't': moptions |= MULTICAST_TTL; hoplimit = int_arg(optarg, "ttl"); if (hoplimit > MAXTTL) { Fprintf(stderr, "%s: ttl %d out of range\n", progname, hoplimit); exit(EXIT_FAILURE); } break; case 'U': use_udp = _B_TRUE; use_icmp_ts = _B_FALSE; break; case 'v': verbose = _B_TRUE; break; /* * 'x' and 'X' has been undocumented flags for source routing. * Now we document loose source routing with the new flag 'g', * which is same as in traceroute. We still keep x/X as * as undocumented. 'G', which is for strict source routing is * also undocumented. */ case 'x': case 'g': strict = _B_FALSE; if (num_gw > MAXMAX_GWS) { Fprintf(stderr, "%s: too many gateways\n", progname); exit(EXIT_FAILURE); } gw_list[num_gw++] = optarg; break; case 'X': case 'G': strict = _B_TRUE; if (num_gw > MAXMAX_GWS) { Fprintf(stderr, "%s: too many gateways\n", progname); exit(EXIT_FAILURE); } gw_list[num_gw++] = optarg; break; case 'Y': use_icmp_ts = _B_TRUE; use_udp = _B_FALSE; break; case '0': case '1': case '2': case '3': ts_flag = (char)c - '0'; break; case '?': usage(progname); exit(EXIT_FAILURE); break; default: usage(progname); exit(EXIT_FAILURE); break; } } if (optind >= argc) { usage(progname); exit(EXIT_FAILURE); } /* * send_reply, which sends the probe packet back to itself * doesn't work with UDP */ if (use_udp) send_reply = _B_FALSE; targethost = argv[optind]; optind++; if (optind < argc) { if (stats) { datalen = int_arg(argv[optind], "data size"); optind++; if (optind < argc) { npackets = int_arg(argv[optind], "packet count"); if (npackets < 1) { Fprintf(stderr, "%s: packet count %d " "out of range\n", progname, npackets); exit(EXIT_FAILURE); } } } else { timeout = int_arg(argv[optind], "timeout"); } } /* * Let's prepare sockaddr_in* structures, cause we might need both of * them. */ bzero((char *)&to, sizeof (struct sockaddr_in)); to.sin_family = AF_INET; bzero((char *)&to6, sizeof (struct sockaddr_in6)); to6.sin6_family = AF_INET6; to6.sin6_flowinfo = htonl((class << 20) | flowinfo); if (stats) (void) sigset(SIGINT, finish); ident = (int)getpid() & 0xFFFF; /* resolve the hostnames */ resolve_nodes(&ai_dst, &src_addr_list); /* * We should make sure datalen is reasonable. * IP_MAXPACKET >= IPv4/IPv6 header length + * IPv4 options/IPv6 routing header length + * ICMP/ICMP6/UDP header length + * datalen */ if (family_input == AF_INET6 || (family_input == AF_UNSPEC && num_v6 != 0)) { size_t exthdr_len = 0; if (send_reply) { exthdr_len = sizeof (struct ip6_rthdr0) + 2 * num_gw * sizeof (struct in6_addr); } else if (num_gw > 0) { exthdr_len = sizeof (struct ip6_rthdr0) + num_gw * sizeof (struct in6_addr); } /* * Size of ICMP6 header and UDP header are the same. Let's * use ICMP6_MINLEN. */ if (datalen > (IP_MAXPACKET - (sizeof (struct ip6_hdr) + exthdr_len + ICMP6_MINLEN))) { Fprintf(stderr, "%s: data size too large for IPv6 packet\n", progname); num_v6 = 0; } } if (family_input == AF_INET || (family_input == AF_UNSPEC && num_v4 != 0)) { size_t opt_len = 0; if (send_reply) { /* * Includes 3 bytes code+ptr+len, the intermediate * gateways, the actual and the effective target. */ opt_len = 3 + (2 * num_gw + 2) * sizeof (struct in_addr); } else if (num_gw > 0) { opt_len = 3 + (num_gw + 1) * sizeof (struct in_addr); } if (rr_option) { opt_len = MAX_IPOPTLEN; } else if (ts_option) { if ((ts_flag & 0x0f) <= IPOPT_TS_TSANDADDR) { opt_len = MAX_IPOPTLEN; } else { opt_len += IPOPT_MINOFF + 2 * sizeof (struct ipt_ta); /* * Note: BSD/4.X is broken in their check so we * have to bump up this number by at least one. */ opt_len++; } } /* Round up to 4 byte boundary */ if (opt_len & 0x3) opt_len = (opt_len & ~0x3) + 4; if (datalen > (IP_MAXPACKET - (sizeof (struct ip) + opt_len + ICMP_MINLEN))) { Fprintf(stderr, "%s: data size too large for IPv4 packet\n", progname); num_v4 = 0; } } if (num_v4 == 0 && num_v6 == 0) { exit(EXIT_FAILURE); } /* setup the sockets */ if (num_v6 != 0) { if (!setup_socket(AF_INET6, &send_sock6, &recv_sock6, &if_index, &udp_src_port6)) exit(EXIT_FAILURE); } if (num_v4 != 0) { if (!setup_socket(AF_INET, &send_sock, &recv_sock, &if_index, &udp_src_port)) exit(EXIT_FAILURE); } __priv_relinquish(); /* * If sending back to ourself, add the mirror image of current * gateways, so that the probes travel to and from the target * by visiting the same gateways in reverse order. */ if (send_reply) { if (num_v6 != 0) mirror_gws(gw_IP_list6, AF_INET6); if (num_v4 != 0) mirror_gws(gw_IP_list, AF_INET); /* We add 1 because we put the target as the middle gateway */ eff_num_gw = 2 * num_gw + 1; } else { eff_num_gw = num_gw; } targetaddr_list = build_targetaddr_list(ai_dst, src_addr_list); current_targetaddr = targetaddr_list; /* * Set the starting_seq_num for the first targetaddr. * If we are sending ICMP Echo Requests, the sequence number is same as * ICMP sequence number, and it starts from zero. If we are sending UDP * packets, the sequence number is the destination UDP port number, * which starts from dest_port. At each probe, this sequence number is * incremented by one. * We set the starting_seq_num for first targetaddr here. The * following ones will be set by looking at where we left with the last * targetaddr. */ current_targetaddr->starting_seq_num = use_udp ? dest_port : 0; if (stats) { if (probe_all || !nflag) { Printf("PING %s: %d data bytes\n", targethost, datalen); } else { if (ai_dst->ai_family == AF_INET) { Printf("PING %s (%s): %d data bytes\n", targethost, inet_ntop(AF_INET, /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in *) ai_dst->ai_addr)->sin_addr, tmp_buf, sizeof (tmp_buf)), datalen); } else { Printf("PING %s (%s): %d data bytes\n", targethost, inet_ntop(AF_INET6, /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in6 *) ai_dst->ai_addr)->sin6_addr, tmp_buf, sizeof (tmp_buf)), datalen); } } } /* Let's get things going */ send_scheduled_probe(); /* SIGALRM is used to send the next scheduled probe */ (void) sigset(SIGALRM, sigalrm_handler); schedule_sigalrm(); /* * From now on, we'll always be listening to ICMP packets. As SIGALRM * comes in, sigalrm_handler() will be invoked and send another * probe. */ recv_icmp_packet(ai_dst, recv_sock6, recv_sock, udp_src_port6, udp_src_port); exit(EXIT_SUCCESS); /* should never come here */ } /* * Build the target IP address list. Use command line options and * name lookup results returned from name server to determine which addresses * to probe, how many times, in which order. */ static struct targetaddr * build_targetaddr_list(struct addrinfo *ai_dst, union any_in_addr *src_addr_list) { struct targetaddr *head = NULL; struct targetaddr *targetaddr; struct targetaddr **nextp; int num_dst; int i; struct addrinfo *aip; aip = ai_dst; if (probe_all) num_dst = num_v4 + num_v6; else num_dst = 1; num_targetaddrs = num_dst; nextp = &head; for (aip = ai_dst, i = 0; aip != NULL; aip = aip->ai_next, i++) { if (aip->ai_family == AF_INET && num_v4 != 0) { targetaddr = create_targetaddr_item(aip->ai_family, (union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in *) aip->ai_addr)->sin_addr, &src_addr_list[i]); } else if (aip->ai_family == AF_INET6 && num_v6 != 0) { targetaddr = create_targetaddr_item(aip->ai_family, (union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in6 *) aip->ai_addr)->sin6_addr, &src_addr_list[i]); } else { continue; } *nextp = targetaddr; nextp = &targetaddr->next; if (num_targetaddrs == 1) break; } if (npackets == 0 && stats) *nextp = head; /* keep going indefinitely */ return (head); } /* * Given an address family, dst and src addresses, by also looking at the * options provided at the command line, this function creates a targetaddr * to be linked with others, forming a global targetaddr list. Each targetaddr * item contains information about probes sent to a specific IP address. */ static struct targetaddr * create_targetaddr_item(int family, union any_in_addr *dst_addr, union any_in_addr *src_addr) { struct targetaddr *targetaddr; targetaddr = (struct targetaddr *)malloc(sizeof (struct targetaddr)); if (targetaddr == NULL) { Fprintf(stderr, "%s: malloc %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } targetaddr->family = family; targetaddr->dst_addr = *dst_addr; targetaddr->src_addr = *src_addr; if (stats) { /* * npackets is only defined if we are in stats mode. * npackets determines how many probes to send to each target * IP address. npackets == 0 means send only 1 and move on to * next target IP. */ if (npackets > 0) targetaddr->num_probes = npackets; else targetaddr->num_probes = 1; } else { targetaddr->num_probes = timeout; } targetaddr->num_sent = 0; targetaddr->got_reply = _B_FALSE; targetaddr->probing_done = _B_FALSE; targetaddr->starting_seq_num = 0; /* actual value will be set later */ targetaddr->next = NULL; /* actual value will be set later */ return (targetaddr); } /* * print "unknown host" message */ static void print_unknown_host_msg(const char *protocol, const char *hostname) { Fprintf(stderr, "%s: unknown%s host %s\n", progname, protocol, hostname); } /* * Resolve hostnames for the target host and gateways. Also, determine source * addresses to use for each target address. */ static void resolve_nodes(struct addrinfo **ai_dstp, union any_in_addr **src_addr_listp) { struct addrinfo *ai_dst = NULL; struct addrinfo *aip = NULL; union any_in_addr *src_addr_list = NULL; int num_resolved_gw = 0; int num_resolved_gw6 = 0; get_hostinfo(targethost, family_input, &ai_dst); if (ai_dst == NULL) { print_unknown_host_msg("", targethost); exit(EXIT_FAILURE); } /* Get a count of the v4 & v6 addresses */ for (aip = ai_dst; aip != NULL; aip = aip->ai_next) { switch (aip->ai_family) { case AF_INET: num_v4++; break; case AF_INET6: num_v6++; break; } } if (family_input == AF_UNSPEC && !probe_all) { family_input = ai_dst->ai_family; } /* resolve gateways */ if (num_gw > 0) { get_gwaddrs(gw_list, family_input, gw_IP_list, gw_IP_list6, &num_resolved_gw, &num_resolved_gw6); /* we couldn't resolve a gateway as an IPv6 host */ if (num_resolved_gw6 != num_gw && num_v6 != 0 && (family_input == AF_INET6 || family_input == AF_UNSPEC)) { print_unknown_host_msg(" IPv6", gw_list[num_resolved_gw6]); num_v6 = 0; } /* we couldn't resolve a gateway as an IPv4 host */ if (num_resolved_gw != num_gw && num_v4 != 0 && (family_input == AF_INET || family_input == AF_UNSPEC)) { print_unknown_host_msg(" IPv4", gw_list[num_resolved_gw]); num_v4 = 0; } } if (num_v4 == 0 && num_v6 == 0) exit(EXIT_FAILURE); select_all_src_addrs(&src_addr_list, ai_dst, gw_IP_list, gw_IP_list6); *ai_dstp = ai_dst; *src_addr_listp = src_addr_list; } /* * Resolve the gateway names, splitting results into v4 and v6 lists. * Gateway addresses are added to the appropriate passed-in array; the * number of resolved gateways for each af is returned in resolved[6]. * Assumes that passed-in arrays are large enough for MAX_GWS[6] addrs * and resolved[6] ptrs are non-null; ignores array and counter if the * address family param makes them irrelevant. */ static void get_gwaddrs(char **gw_list, int family, union any_in_addr *gwIPlist, union any_in_addr *gwIPlist6, int *resolved, int *resolved6) { int i; boolean_t check_v4 = _B_TRUE, check_v6 = _B_TRUE; struct addrinfo *ai = NULL; struct addrinfo *aip = NULL; *resolved = *resolved6 = 0; switch (family) { case AF_UNSPEC: break; case AF_INET: check_v6 = _B_FALSE; break; case AF_INET6: check_v4 = _B_FALSE; break; default: return; } if (check_v4 && num_gw >= MAX_GWS) { check_v4 = _B_FALSE; Fprintf(stderr, "%s: too many IPv4 gateways\n", progname); } if (check_v6 && num_gw > MAX_GWS6) { check_v6 = _B_FALSE; Fprintf(stderr, "%s: too many IPv6 gateways\n", progname); } for (i = 0; i < num_gw; i++) { if (!check_v4 && !check_v6) return; get_hostinfo(gw_list[i], family, &ai); if (ai == NULL) return; if (check_v4 && num_v4 != 0) { for (aip = ai; aip != NULL; aip = aip->ai_next) { if (aip->ai_family == AF_INET) { /* LINTED E_BAD_PTR_CAST_ALIGN */ bcopy(&((struct sockaddr_in *) aip->ai_addr)->sin_addr, &gwIPlist[i].addr, aip->ai_addrlen); (*resolved)++; break; } } } else if (check_v4) { check_v4 = _B_FALSE; } if (check_v6 && num_v6 != 0) { for (aip = ai; aip != NULL; aip = aip->ai_next) { if (aip->ai_family == AF_INET6) { /* LINTED E_BAD_PTR_CAST_ALIGN */ bcopy(&((struct sockaddr_in6 *) aip->ai_addr)->sin6_addr, &gwIPlist6[i].addr6, aip->ai_addrlen); (*resolved6)++; break; } } } else if (check_v6) { check_v6 = _B_FALSE; } } freeaddrinfo(ai); } /* * Given the list of gateways, extends the list with its mirror image. This is * used when -l/-S is used. The middle gateway will be the target address. We'll * leave it blank for now. */ static void mirror_gws(union any_in_addr *gwIPlist, int family) { int effective_num_gw; int i; /* We add 1 because we put the target as the middle gateway */ effective_num_gw = 2 * num_gw + 1; if ((family == AF_INET && effective_num_gw >= MAX_GWS) || (family == AF_INET6 && effective_num_gw > MAX_GWS6)) { Fprintf(stderr, "%s: too many %s gateways\n", progname, (family == AF_INET) ? "IPv4" : "IPv6"); exit(EXIT_FAILURE); } for (i = 0; i < num_gw; i++) gwIPlist[num_gw + i + 1].addr6 = gwIPlist[num_gw - i - 1].addr6; } /* * Given IP address or hostname, return addrinfo list. * Assumes that addrinfo ** ptr is non-null. */ static void get_hostinfo(char *host, int family, struct addrinfo **aipp) { struct addrinfo hints, *ai; struct in6_addr addr6; struct in_addr addr; boolean_t broadcast; /* is this 255.255.255.255? */ char tmp_buf[INET6_ADDRSTRLEN]; int rc; /* check if broadcast */ if (strcmp(host, "255.255.255.255") == 0) broadcast = _B_TRUE; else broadcast = _B_FALSE; /* check if IPv4-mapped address or broadcast */ if (((inet_pton(AF_INET6, host, &addr6) > 0) && IN6_IS_ADDR_V4MAPPED(&addr6)) || broadcast) { if (!broadcast) { /* * Peel off the "mapping" stuff, leaving 32 bit IPv4 * address. */ IN6_V4MAPPED_TO_INADDR(&addr6, &addr); /* convert it back to a string */ (void) inet_ntop(AF_INET, (void *)&addr, tmp_buf, sizeof (tmp_buf)); /* * Now the host is an IPv4 address. * Since it previously was a v4 mapped v6 address * we can be sure that the size of buffer 'host' * is large enough to contain the associated v4 * address and so we don't need to use a strn/lcpy * here. */ (void) strcpy(host, tmp_buf); } /* * If it's a broadcast address, it cannot be an IPv6 address. * Also, if it's a mapped address, we convert it into IPv4 * address because ping will send and receive IPv4 packets for * that address. Therefore, it's a failure case to ask * get_hostinfo() to treat a broadcast or a mapped address * as an IPv6 address. */ if (family == AF_INET6) { return; } } (void) memset(&hints, 0, sizeof (hints)); hints.ai_family = family; hints.ai_flags = AI_ADDRCONFIG; rc = getaddrinfo(host, NULL, &hints, &ai); if (rc != 0) { if (rc != EAI_NONAME) Fprintf(stderr, "%s: getaddrinfo: %s\n", progname, gai_strerror(rc)); return; } *aipp = ai; } /* * For each IP address of the target host, determine a source address to use. */ static void select_all_src_addrs(union any_in_addr **src_addr_list, struct addrinfo *ai, union any_in_addr *gwv4, union any_in_addr *gwv6) { union any_in_addr *list; struct addrinfo *aip; int num_dst = 1; int i; if (probe_all) for (aip = ai; aip->ai_next != NULL; aip = aip->ai_next, num_dst++); list = (union any_in_addr *) calloc((size_t)num_dst, sizeof (union any_in_addr)); if (list == NULL) { Fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } /* * If there's a gateway, a routing header as a consequence, our kernel * picks the source address based on the first hop address, rather than * final destination address. */ if (num_gw > 0) { if (ai->ai_family == AF_INET) select_src_addr(gwv4, ai->ai_family, &list[0]); else select_src_addr(gwv6, ai->ai_family, &list[0]); /* * Since the first gateway address is fixed, we'll use the same * src address for every different final destination address * we send to. */ for (i = 1; i < num_dst; i++) list[i] = list[0]; } else { /* * Although something like 'ping -l host' results in a routing * header, the first gateway address is the target host's * address. Therefore, as far as src address selection goes, * the result is same as having no routing header. */ for (i = 0, aip = ai; i < num_dst && aip != NULL; i++, aip = aip->ai_next) { if (aip->ai_family == AF_INET) { if (num_v4 != 0) { select_src_addr((union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in *) aip->ai_addr)->sin_addr, aip->ai_family, &list[i]); } } else { if (num_v6 != 0) { select_src_addr((union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in6 *) aip->ai_addr)->sin6_addr, aip->ai_family, &list[i]); } } } } *src_addr_list = list; } /* * For a given destination address, determine a source address to use. * Returns wildcard address if it cannot determine the source address. */ static void select_src_addr(union any_in_addr *dst_addr, int family, union any_in_addr *src_addr) { struct sockaddr *sock; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; int tmp_fd; size_t sock_len; sock = (struct sockaddr *)malloc(sizeof (struct sockaddr_in6)); if (sock == NULL) { Fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } (void) bzero(sock, sizeof (struct sockaddr_in6)); if (family == AF_INET) { /* LINTED E_BAD_PTR_CAST_ALIGN */ sin = (struct sockaddr_in *)sock; sin->sin_family = AF_INET; sin->sin_addr = dst_addr->addr; sin->sin_port = IPPORT_ECHO; /* port shouldn't be 0 */ sock_len = sizeof (struct sockaddr_in); } else { /* LINTED E_BAD_PTR_CAST_ALIGN */ sin6 = (struct sockaddr_in6 *)sock; sin6->sin6_family = AF_INET6; sin6->sin6_addr = dst_addr->addr6; sin6->sin6_port = IPPORT_ECHO; /* port shouldn't be 0 */ sock_len = sizeof (struct sockaddr_in6); } /* open a UDP socket */ if ((tmp_fd = socket(family, SOCK_DGRAM, 0)) < 0) { Fprintf(stderr, "%s: udp socket: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } /* connect it */ if (connect(tmp_fd, sock, sock_len) < 0) { /* * If there's no route to the destination, this connect() call * fails. We just return all-zero (wildcard) as the source * address, so that user can get to see "no route to dest" * message, as it'll try to send the probe packet out and will * receive ICMP unreachable. */ if (family == AF_INET) src_addr->addr.s_addr = INADDR_ANY; else src_addr->addr6 = in6addr_any; free(sock); return; } /* get the local sock info */ if (getsockname(tmp_fd, sock, &sock_len) < 0) { Fprintf(stderr, "%s: getsockname: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (family == AF_INET) { src_addr->addr = sin->sin_addr; } else { src_addr->addr6 = sin6->sin6_addr; } (void) close(tmp_fd); free(sock); } /* * Setup the socket for the given address family. * Returns _B_TRUE on success, _B_FALSE on failure. Failure is the case when no * interface can be found, or the specified interface (-i) is not found. On * library call failures, it exit()s. */ static boolean_t setup_socket(int family, int *send_sockp, int *recv_sockp, int *if_index, ushort_t *udp_src_port) { int send_sock; int recv_sock; struct sockaddr_in6 sin6; struct sockaddr_in sin; struct sockaddr *sp; size_t slen; int on = 1; uchar_t char_op; int int_op; /* now we need the net_icmpaccess privilege */ (void) __priv_bracket(PRIV_ON); recv_sock = socket(family, SOCK_RAW, (family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6); if (recv_sock < 0) { Fprintf(stderr, "%s: socket %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } /* revert to non-privileged user after opening sockets */ (void) __priv_bracket(PRIV_OFF); /* * We always receive on raw icmp socket. But the sending socket can be * raw icmp or udp, depending on the use of -U flag. */ if (use_udp) { send_sock = socket(family, SOCK_DGRAM, IPPROTO_UDP); if (send_sock < 0) { Fprintf(stderr, "%s: socket %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } /* * In order to distinguish replies to our UDP probes from * other pings', we need to know our source port number. */ if (family == AF_INET) { sp = (struct sockaddr *)&sin; slen = sizeof (sin); } else { sp = (struct sockaddr *)&sin6; slen = sizeof (sin6); } bzero(sp, slen); sp->sa_family = family; /* Let's bind() send_sock to wildcard address and port */ if (bind(send_sock, sp, slen) < 0) { Fprintf(stderr, "%s: bind %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } /* .... and see what port kernel picked for us */ if (getsockname(send_sock, sp, &slen) < 0) { Fprintf(stderr, "%s: getsockname %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } *udp_src_port = (family == AF_INET) ? sin.sin_port : sin6.sin6_port; } else { send_sock = recv_sock; } int_op = 48 * 1024; if (int_op < datalen) int_op = datalen; if (setsockopt(recv_sock, SOL_SOCKET, SO_RCVBUF, (char *)&int_op, sizeof (int_op)) == -1) { Fprintf(stderr, "%s: setsockopt SO_RCVBUF %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (setsockopt(send_sock, SOL_SOCKET, SO_SNDBUF, (char *)&int_op, sizeof (int_op)) == -1) { Fprintf(stderr, "%s: setsockopt SO_SNDBUF %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (options & SO_DEBUG) { if (setsockopt(send_sock, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)) == -1) { Fprintf(stderr, "%s: setsockopt SO_DEBUG %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } if (options & SO_DONTROUTE) { if (setsockopt(send_sock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, sizeof (on)) == -1) { Fprintf(stderr, "%s: setsockopt SO_DONTROUTE %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } if (moptions & MULTICAST_NOLOOP) { if (family == AF_INET) { char_op = 0; /* used to turn off option */ if (setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&char_op, sizeof (char_op)) == -1) { Fprintf(stderr, "%s: setsockopt " "IP_MULTICAST_NOLOOP %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } else { int_op = 0; /* used to turn off option */ if (setsockopt(send_sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&int_op, sizeof (int_op)) == -1) { Fprintf(stderr, "%s: setsockopt " "IPV6_MULTICAST_NOLOOP %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } } if (moptions & MULTICAST_TTL) { char_op = hoplimit; if (family == AF_INET) { if (setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&char_op, sizeof (char)) == -1) { Fprintf(stderr, "%s: setsockopt " "IP_MULTICAST_TTL %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (setsockopt(send_sock, IPPROTO_IP, IP_TTL, (char *)&hoplimit, sizeof (hoplimit)) == -1) { Fprintf(stderr, "%s: setsockopt IP_TTL %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } /* * AF_INET6 case is handled in set_ancillary_data() function. * This is because when ancillary data is used (for routing * header and outgoing interface index), the hoplimit set using * setsockopt() is ignored. */ } /* did the user specify an interface? */ if (moptions & MULTICAST_IF) { struct ifaddrlist *al = NULL; /* interface list */ struct ifaddrlist *my_if; char errbuf[ERRBUFSIZE]; int num_ifs; int num_src_ifs; /* exclude down and loopback */ int i; /* pull out the interface list */ num_ifs = ifaddrlist(&al, family, errbuf); if (num_ifs == -1) { Fprintf(stderr, "%s: %s\n", progname, errbuf); exit(EXIT_FAILURE); } /* filter out down and loopback interfaces */ num_src_ifs = 0; for (i = 0; i < num_ifs; i++) { if (!(al[i].flags & IFF_LOOPBACK) && (al[i].flags & IFF_UP)) num_src_ifs++; } if (num_src_ifs == 0) { Fprintf(stderr, "%s: can't find any %s interface\n", progname, (family == AF_INET) ? "IPv4" : "IPv6"); return (_B_FALSE); /* failure */ } /* locate the specified interface */ my_if = find_if(al, num_ifs); if (my_if == NULL) { Fprintf(stderr, "%s: %s is an invalid %s interface\n", progname, out_if.str, (family == AF_INET) ? "IPv4" : "IPv6"); return (_B_FALSE); } if (family == AF_INET) { if (setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&my_if->addr.addr, sizeof (struct in_addr)) == -1) { Fprintf(stderr, "%s: setsockopt " "IP_MULTICAST_IF %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } else { /* * the outgoing interface is set in set_ancillary_data() * function */ *if_index = my_if->index; } free(al); } if (settos && family == AF_INET) { int_op = tos; if (setsockopt(send_sock, IPPROTO_IP, IP_TOS, (char *)&int_op, sizeof (int_op)) == -1) { Fprintf(stderr, "%s: setsockopt IP_TOS %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } /* receiving IPv6 extension headers in verbose mode */ if (verbose && family == AF_INET6) { if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVHOPOPTS, (char *)&on, sizeof (on)) == -1) { Fprintf(stderr, "%s: setsockopt IPV6_RECVHOPOPTS %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVDSTOPTS, (char *)&on, sizeof (on)) == -1) { Fprintf(stderr, "%s: setsockopt IPV6_RECVDSTOPTS %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVRTHDR, (char *)&on, sizeof (on)) == -1) { Fprintf(stderr, "%s: setsockopt IPV6_RECVRTHDR %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVRTHDRDSTOPTS, (char *)&on, sizeof (on)) == -1) { Fprintf(stderr, "%s: setsockopt IPV6_RECVRTHDRDSTOPTS %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } *send_sockp = send_sock; *recv_sockp = recv_sock; /* successful */ return (_B_TRUE); } /* * Pull out the record containing all the info about the interface specified by * `out_if'. Skips interfaces which are down or loopback. */ static struct ifaddrlist * find_if(struct ifaddrlist *al, int num_ifs) { static struct ifaddrlist tmp_if; boolean_t found; int i; i = 0; found = _B_FALSE; while (i < num_ifs && !found) { tmp_if = al[i]; /* skip down or loopback interfaces */ if ((tmp_if.flags & IFF_LOOPBACK) || !(tmp_if.flags & IFF_UP)) { i++; continue; } /* the type of interface id is variable */ switch (out_if.id_type) { case IF_INDEX: if (out_if.id.index == tmp_if.index) found = _B_TRUE; break; case IF_NAME: if (strcmp(out_if.id.name, tmp_if.device) == 0) found = _B_TRUE; break; case IF_ADDR: if (out_if.id.addr.addr.s_addr == tmp_if.addr.addr.s_addr) { found = _B_TRUE; } break; case IF_ADDR6: if (IN6_ARE_ADDR_EQUAL(&out_if.id.addr.addr6, &tmp_if.addr.addr6)) { found = _B_TRUE; } break; default: break; } i++; } if (found) return (&tmp_if); else return (NULL); } /* * Invoked by SIGALRM, sigalrm_handler() is, responsible for calling * send_scheduled_probe() to send next probe. */ void sigalrm_handler(void) { /* * Guard againist denial-of-service attacks. Make sure ping doesn't * send probes for every SIGALRM it receives. Evil hacker can generate * SIGALRMs as fast as it can, but ping will ignore those which are * received too soon (earlier than 0.5 sec) after it sent the last * probe. We use gethrtime() instead of gettimeofday() because * the latter is not linear and is prone to resetting or drifting */ if ((gethrtime() - t_last_probe_sent) < 500000000) { return; } send_scheduled_probe(); schedule_sigalrm(); } /* * Schedule next SIGALRM. */ void schedule_sigalrm(void) { int waittime; if (npackets == 0 || current_targetaddr->num_sent < current_targetaddr->num_probes) { (void) alarm(interval); } else { if (current_targetaddr->got_reply) { waittime = 2 * tmax / MICROSEC; if (waittime == 0) waittime = 1; } else { waittime = MAX_WAIT; } (void) alarm(waittime); } } /* * Called by sigalrm_handler(), check_reply() or check_reply6(), * send_scheduled_probe() looks at the current_targetaddr and determines what * should be sent next and calls pinger(). */ void send_scheduled_probe() { static struct msghdr msg6; static boolean_t first_probe = _B_TRUE; char tmp_buf[INET6_ADDRSTRLEN]; /* * We are about to move to next targetaddr if it's either we sent * all the probes, or somebody set the probing_done flag to * _B_TRUE prompting us to move on. */ if (current_targetaddr->num_sent == current_targetaddr->num_probes || current_targetaddr->probing_done) { /* * is this a dead target? */ if (!stats && !current_targetaddr->got_reply) { if (!probe_all) { Printf("no answer from %s\n", targethost); } else { Printf("no answer from %s(%s)\n", targethost, inet_ntop(current_targetaddr->family, ¤t_targetaddr->dst_addr, tmp_buf, sizeof (tmp_buf))); } } /* * Before we move onto next item, let's do some clean up. */ current_targetaddr->got_reply = _B_FALSE; current_targetaddr->probing_done = _B_FALSE; /* * If this is probe-all without stats mode, then we need to * preserve this count. This is needed when we try to map an * icmp_seq to IP address. Otherwise, clear it. */ if (stats || !probe_all) current_targetaddr->num_sent = 0; nreceived_last_target = 0; current_targetaddr = current_targetaddr->next; /* * Did we reach the end of road? */ if (current_targetaddr == NULL) { (void) alarm(0); /* cancel alarm */ if (stats) finish(); if (is_alive) exit(EXIT_SUCCESS); else exit(EXIT_FAILURE); } else { /* * We use starting_seq_num for authenticating replies. * Each time we move to a new targetaddr, which has * a different target IP address, we update this field. */ current_targetaddr->starting_seq_num = use_udp ? dest_port : (ntransmitted % (MAX_ICMP_SEQ + 1)); } } if (current_targetaddr->family == AF_INET6) { if (send_reply) { /* sending back to ourself */ to6.sin6_addr = current_targetaddr->src_addr.addr6; } else { to6.sin6_addr = current_targetaddr->dst_addr.addr6; } /* * Setting the ancillary data once is enough, if we are * not using source routing through target (-l/-S). In * case -l/-S used, the middle gateway will be the * IP address of the source, which can be different * for each target IP. */ if (first_probe || (send_reply && current_targetaddr->num_sent == 0)) { if (send_reply) { /* target is the middle gateway now */ gw_IP_list6[num_gw].addr6 = current_targetaddr->dst_addr.addr6; } set_ancillary_data(&msg6, hoplimit, gw_IP_list6, eff_num_gw, if_index); first_probe = _B_FALSE; } pinger(send_sock6, (struct sockaddr *)&to6, &msg6, AF_INET6); } else { to.sin_addr = current_targetaddr->dst_addr.addr; /* * Set IPv4 options when sending the first probe to a target * IP address. Some options change when the target address * changes. */ if (current_targetaddr->num_sent == 0) { if (eff_num_gw > 0) { gw_IP_list[num_gw].addr = current_targetaddr->dst_addr.addr; /* * If send_reply, the target becomes the * middle gateway, sender becomes the last * gateway. */ if (send_reply) { gw_IP_list[eff_num_gw].addr = current_targetaddr->src_addr.addr; } } /* * In IPv4, if source routing is used, the target * address shows up as the last gateway, hence +1. */ set_IPv4_options(send_sock, gw_IP_list, (eff_num_gw > 0) ? eff_num_gw + 1 : 0, ¤t_targetaddr->src_addr.addr, &to.sin_addr); } pinger(send_sock, (struct sockaddr *)&to, NULL, AF_INET); } current_targetaddr->num_sent++; } /* * recv_icmp_packet()'s job is to listen to icmp packets and filter out * those ping is interested in. */ static void recv_icmp_packet(struct addrinfo *ai_dst, int recv_sock6, int recv_sock, ushort_t udp_src_port6, ushort_t udp_src_port) { struct msghdr in_msg; struct iovec iov; struct sockaddr_in6 from6; fd_set fds; int result; int cc; boolean_t always_true = _B_TRUE; /* lint doesn't like while(_B_TRUE) */ while (always_true) { (void) FD_ZERO(&fds); if (recv_sock6 != -1) FD_SET(recv_sock6, &fds); if (recv_sock != -1) FD_SET(recv_sock, &fds); result = select(MAX(recv_sock6, recv_sock) + 1, &fds, (fd_set *)NULL, (fd_set *)NULL, (struct timeval *)NULL); if (result == -1) { if (errno == EINTR) { continue; } else { Fprintf(stderr, "%s: select %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } else if (result > 0) { in_msg.msg_name = &from6; in_msg.msg_namelen = sizeof (from6); iov.iov_base = in_pkt; iov.iov_len = sizeof (in_pkt); in_msg.msg_iov = &iov; in_msg.msg_iovlen = 1; in_msg.msg_control = ancillary_data; in_msg.msg_controllen = sizeof (ancillary_data); /* Do we have an ICMP6 packet waiting? */ if ((recv_sock6 != -1) && (FD_ISSET(recv_sock6, &fds))) { cc = recvmsg(recv_sock6, &in_msg, 0); if (cc < 0) { if (errno != EINTR) { Fprintf(stderr, "%s: recvmsg %s\n", progname, strerror(errno)); } continue; } else if (cc > 0) { check_reply6(ai_dst, &in_msg, cc, udp_src_port6); } } /* Do we have an ICMP packet waiting? */ if ((recv_sock != -1) && (FD_ISSET(recv_sock, &fds))) { cc = recvmsg(recv_sock, &in_msg, 0); if (cc < 0) { if (errno != EINTR) { Fprintf(stderr, "%s: recvmsg %s\n", progname, strerror(errno)); } continue; } if (cc > 0) { check_reply(ai_dst, &in_msg, cc, udp_src_port); } } } /* * If we were probing last IP address of the target host and * received a reply for each probe sent to this address, * then we are done! */ if ((npackets > 0) && (current_targetaddr->next == NULL) && (nreceived_last_target == npackets)) { (void) alarm(0); /* cancel alarm */ finish(); } } /* infinite loop */ } /* * Given a host (with possibly multiple IP addresses) and an IP address, this * function determines if this IP address is one of the host's addresses to * which we're sending probes. Used to determine if we are interested in a * packet. */ boolean_t is_a_target(struct addrinfo *ai, union any_in_addr *addr) { int num_addrs; int i; struct addrinfo *aip; aip = ai; if (probe_all) num_addrs = num_v4 + num_v6; else num_addrs = 1; for (i = 0; i < num_addrs && aip != NULL; i++) { if (aip->ai_family == AF_INET6) { /* LINTED E_BAD_PTR_CAST_ALIGN */ if (IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *) aip->ai_addr)->sin6_addr, &addr->addr6)) return (_B_TRUE); } else { /* LINTED E_BAD_PTR_CAST_ALIGN */ if (((struct sockaddr_in *) aip->ai_addr)->sin_addr.s_addr == addr->addr.s_addr) return (_B_TRUE); } } return (_B_FALSE); } /* * 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 network * byte-order, to compute the round-trip time. */ static void pinger(int send_sock, struct sockaddr *whereto, struct msghdr *msg6, int family) { static uint64_t out_pkt_buf[(IP_MAXPACKET + 1) / 8]; uchar_t *out_pkt = (uchar_t *)&out_pkt_buf; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct icmp *icp = (struct icmp *)out_pkt; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)whereto; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct sockaddr_in *to = (struct sockaddr_in *)whereto; struct timeval *tp; struct timeval t_snd; uchar_t *datap; struct iovec iov; int start = 0; int cc; int i; /* using UDP? */ if (use_udp) { cc = datalen; /* LINTED E_BAD_PTR_CAST_ALIGN */ tp = (struct timeval *)out_pkt; datap = &out_pkt[sizeof (struct timeval)]; /* * This sets the port whether we are handling a v4 or v6 * sockaddr structure. */ to->sin_port = htons(dest_port); dest_port = (dest_port + 1) % (MAX_PORT + 1); ntransmitted++; } else { /* using ICMP */ cc = datalen + ICMP_MINLEN; if (family == AF_INET6) { icp->icmp_type = send_reply ? ICMP6_ECHO_REPLY : ICMP6_ECHO_REQUEST; } else if (use_icmp_ts) { /* family is AF_INET */ icp->icmp_type = send_reply ? ICMP_TSTAMPREPLY : ICMP_TSTAMP; } else { icp->icmp_type = send_reply ? ICMP_ECHOREPLY : ICMP_ECHO; } icp->icmp_code = 0; icp->icmp_cksum = 0; icp->icmp_seq = htons(ntransmitted++ % (MAX_ICMP_SEQ + 1)); if (icp->icmp_seq == 0) num_wraps++; icp->icmp_id = htons(ident); /* ID */ /* LINTED E_BAD_PTR_CAST_ALIGN */ tp = (struct timeval *)&out_pkt[ICMP_MINLEN]; datap = &out_pkt[ICMP_MINLEN + sizeof (struct timeval)]; } start = sizeof (struct timeval); /* skip for time */ (void) gettimeofday(&t_snd, (struct timezone *)NULL); /* if packet is big enough to store timeval OR ... */ if ((datalen >= sizeof (struct timeval)) || (family == AF_INET && use_icmp_ts)) *tp = t_snd; if (family == AF_INET && use_icmp_ts) { start = sizeof (struct id_ts); /* skip for ICMP timestamps */ /* Number of milliseconds since midnight */ icp->icmp_otime = htonl((tp->tv_sec % (24*60*60)) * 1000 + tp->tv_usec / 1000); } for (i = start; i < datalen; i++) *datap++ = i; if (family == AF_INET) { if (!use_udp) icp->icmp_cksum = in_cksum((ushort_t *)icp, cc); i = sendto(send_sock, (char *)out_pkt, cc, 0, whereto, sizeof (struct sockaddr_in)); } else { /* * Fill in the rest of the msghdr structure. msg_control is set * in set_ancillary_data(). */ msg6->msg_name = to6; msg6->msg_namelen = sizeof (struct sockaddr_in6); iov.iov_base = out_pkt; iov.iov_len = cc; msg6->msg_iov = &iov; msg6->msg_iovlen = 1; i = sendmsg(send_sock, msg6, 0); } /* This is a more precise time (right after we send the packet) */ t_last_probe_sent = gethrtime(); if (i < 0 || i != cc) { if (i < 0) { Fprintf(stderr, "%s: sendto %s\n", progname, strerror(errno)); if (!stats) exit(EXIT_FAILURE); } Printf("ping: wrote %s %d chars, ret=%d\n", targethost, cc, i); (void) fflush(stdout); } } /* * Return a hostname for the given IP address. */ char * pr_name(char *addr, int family) { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr *sa; static struct in6_addr prev_addr = IN6ADDR_ANY_INIT; char *cp; char abuf[INET6_ADDRSTRLEN]; static char buf[NI_MAXHOST + INET6_ADDRSTRLEN + 3]; uint_t slen, alen, hlen; switch (family) { case AF_INET: (void) memset(&sin, 0, sizeof (sin)); slen = sizeof (struct sockaddr_in); alen = sizeof (struct in_addr); /* LINTED E_BAD_PTR_CAST_ALIGN */ sin.sin_addr = *(struct in_addr *)addr; sin.sin_port = 0; sa = (struct sockaddr *)&sin; break; case AF_INET6: (void) memset(&sin6, 0, sizeof (sin6)); slen = sizeof (struct sockaddr_in6); alen = sizeof (struct in6_addr); /* LINTED E_BAD_PTR_CAST_ALIGN */ sin6.sin6_addr = *(struct in6_addr *)addr; sin6.sin6_port = 0; sa = (struct sockaddr *)&sin6; break; default: (void) snprintf(buf, sizeof (buf), ""); return (buf); } sa->sa_family = family; /* compare with the buffered (previous) lookup */ if (memcmp(addr, &prev_addr, alen) != 0) { int flags = (nflag) ? NI_NUMERICHOST : NI_NAMEREQD; if (getnameinfo(sa, slen, buf, sizeof (buf), NULL, 0, flags) != 0) { /* getnameinfo() failed; return just the address */ if (inet_ntop(family, (const void*)addr, buf, sizeof (buf)) == NULL) buf[0] = 0; } else if (!nflag) { /* append numeric address to hostname string */ hlen = strlen(buf); cp = (char *)(buf + hlen); (void) snprintf(cp, sizeof (buf) - hlen, " (%s)", inet_ntop(family, (const void *)addr, abuf, sizeof (abuf))); } /* LINTED E_BAD_PTR_CAST_ALIGN */ prev_addr = *(struct in6_addr *)addr; } return (buf); } /* * Return the protocol string, given its protocol number. */ char * pr_protocol(int prot) { static char buf[20]; switch (prot) { case IPPROTO_ICMPV6: (void) strlcpy(buf, "icmp6", sizeof (buf)); break; case IPPROTO_ICMP: (void) strlcpy(buf, "icmp", sizeof (buf)); break; case IPPROTO_TCP: (void) strlcpy(buf, "tcp", sizeof (buf)); break; case IPPROTO_UDP: (void) strlcpy(buf, "udp", sizeof (buf)); break; default: (void) snprintf(buf, sizeof (buf), "prot %d", prot); break; } return (buf); } /* * Checks if value is between seq_begin and seq_begin+seq_len. Note that * sequence numbers wrap around after MAX_ICMP_SEQ (== MAX_PORT). */ boolean_t seq_match(ushort_t seq_begin, int seq_len, ushort_t value) { /* * If seq_len is too big, like some value greater than MAX_ICMP_SEQ/2, * truncate it down to MAX_ICMP_SEQ/2. We are not going to accept any * reply which come 83hr later! */ if (seq_len > MAX_ICMP_SEQ / 2) { seq_begin = (seq_begin + seq_len - MAX_ICMP_SEQ / 2) % (MAX_ICMP_SEQ + 1); seq_len = MAX_ICMP_SEQ / 2; } if (PINGSEQ_LEQ(seq_begin, value) && PINGSEQ_LEQ(value, (seq_begin + seq_len - 1) % (MAX_ICMP_SEQ + 1))) return (_B_TRUE); else return (_B_FALSE); } /* * For a given icmp_seq, find which destination address we must have sent this * to. */ void find_dstaddr(ushort_t icmpseq, union any_in_addr *ipaddr) { struct targetaddr *target = targetaddr_list; int real_seq; int targetaddr_index; int real_npackets; int i; ipaddr->addr6 = in6addr_any; /* * If this is probe_all and not stats, then the number of probes sent to * each IP address may be different (remember, we stop sending to one IP * address as soon as it replies). They are stored in target->num_sent * field. Since we don't wrap around the list (!stats), they are also * preserved. */ if (probe_all && !stats) { do { if (seq_match(target->starting_seq_num, target->num_sent, icmpseq)) { ipaddr->addr6 = target->dst_addr.addr6; /* * We are not immediately return()ing here. * Because of wrapping, we might find another * match later, which is more likely to be the * real one. */ } target = target->next; } while (target != NULL); } else { /* * Find the absolute (non-wrapped) seq number within the last * 64K */ if (icmpseq < (ntransmitted % (MAX_ICMP_SEQ + 1))) { real_seq = num_wraps * (MAX_ICMP_SEQ + 1) + icmpseq; } else { real_seq = (num_wraps - 1) * (MAX_ICMP_SEQ + 1) + icmpseq; } /* Make sure it's non-negative */ if (real_seq < 0) return; real_npackets = (npackets == 0) ? 1 : npackets; /* * We sent npackets many packets to each of those * num_targetaddrs many IP addresses. */ targetaddr_index = (real_seq % (num_targetaddrs * real_npackets)) / real_npackets; for (i = 0; i < targetaddr_index; i++) target = target->next; ipaddr->addr6 = target->dst_addr.addr6; } } /* * Checksum routine for Internet Protocol family headers (C Version) */ static ushort_t in_cksum(ushort_t *addr, int len) { int nleft = len; ushort_t *w = addr; ushort_t answer; ushort_t odd_byte = 0; int sum = 0; /* * 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) { *(uchar_t *)(&odd_byte) = *(uchar_t *)w; sum += odd_byte; } /* * 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); } /* * Subtract 2 timeval structs: out = out - in. * Out is assumed to be >= in. */ 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; } /* * Print out statistics, and give up. * Heavily buffered STDIO is used here, so that all the statistics * will be written with 1 sys-write call. This is nice when more * than one copy of the program is running on a terminal; it prevents * the statistics output from becoming intermingled. */ static void finish() { Printf("\n----%s PING Statistics----\n", targethost); Printf("%d packets transmitted, ", ntransmitted); Printf("%d packets received, ", nreceived); if (ntransmitted) { if (nreceived <= ntransmitted) { Printf("%d%% packet loss", (int)(((ntransmitted-nreceived)*100) / ntransmitted)); } else { Printf("%.2f times amplification", (double)nreceived / (double)ntransmitted); } } (void) putchar('\n'); /* if packet is big enough to store timeval AND ... */ if ((datalen >= sizeof (struct timeval)) && (nreceived > 0)) { int precision = nreceived < 10 ? 3 : nreceived < 100 ? 4 : 5; double mean = (double)tsum / nreceived; double smean = (double)tsum2 / nreceived; double sd = sqrt(((smean - mean*mean) * nreceived) / (nreceived-1)); Printf("round-trip (ms) min/avg/max/stddev = " TIMEFORMAT "/" TIMEFORMAT_V "/" TIMEFORMAT "/" TIMEFORMAT_V "\n", (double)tmin / 1000, precision, mean / 1000, (double)tmax / 1000, precision-1, sd / 1000); } (void) fflush(stdout); exit(is_alive ? EXIT_SUCCESS : EXIT_FAILURE); } /* * print the usage line */ static void usage(char *cmdname) { Fprintf(stderr, "usage: %s host [timeout]\n", cmdname); Fprintf(stderr, /* CSTYLED */ "usage: %s -s [-l | U] [adLnRrv] [-A addr_family] [-c traffic_class]\n\t" "[-g gateway [-g gateway ...]] [-F flow_label] [-I interval]\n\t" "[-i interface] [-P tos] [-p port] [-t ttl] host [data_size] [npackets]\n", cmdname); } /* * Parse integer argument; exit with an error if it's not a number. * Now it also accepts hex. values. */ static int int_arg(char *s, char *what) { char *cp; char *ep; int num; errno = 0; if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { cp = s + 2; num = (int)strtol(cp, &ep, 16); } else { num = (int)strtol(s, &ep, 10); } if (errno || *ep != '\0' || num < 0) { (void) Fprintf(stderr, "%s: bad %s: %s\n", progname, what, s); exit(EXIT_FAILURE); } return (num); }