# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # PROG= ping OBJS= ping.o ping_aux.o ping_aux6.o # Hammerhead: Install to /sbin ROOTFS_PROG = $(PROG) include ../../../Makefile.cmd # Hammerhead: /usr/sbin compat symlink ROOTUSRSBINLINKS = $(PROG:%=$(ROOTUSRSBIN)/%) $(ROOTSBIN)/ping : FILEMODE= 04555 # Ping uses the ancillary data feature which is available only through # UNIX 98 standards version of Socket interface. This interface is supposed to # be accessed by -lxnet. In addition -lsocket and is used to # capture new not-yet-standard interfaces. Someday -lxnet alone should be enough # when IPv6 inspired new interfaces are part of standards. LDLIBS += -lxnet -lsocket -lm -linetutil # These #defines are required to use SUSv3 interfaces CPPFLAGS += -D_XOPEN_SOURCE=600 -D__EXTENSIONS__ CSTD= $(CSTD_GNU99) # Hammerhead: Suppress socklen_t type mismatch warnings in legacy code CERRWARN += -Wno-incompatible-pointer-types .KEEP_STATE: .PARALLEL: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) install: all $(ROOTSBINPROG) $(ROOTUSRSBINLINKS) $(ROOTUSRSBINLINKS): -$(RM) $@; $(SYMLINK) ../../sbin/$(@F) $@ clean: $(RM) $(OBJS) include ../../../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2009 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. */ /* * Copyright (c) 2018, Joyent, Inc. * Copyright 2023 Oxide computer Company * Copyright 2024 Bill Sommerfeld */ #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 #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; char *nexthop; 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 */ boolean_t bypass = _B_FALSE; /* bypass IPsec policy */ 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 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 dontfrag; /* IP*_DONTFRAG */ 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 */ static timer_t timer; /* timer for waiting */ static volatile boolean_t timer_done = _B_FALSE; /* timer finished? */ static struct itimerspec interval = { { 0, 0 }, { 1, 0 } }; /* Interval for */ /* -I. The default interval is 1s. */ static hrtime_t mintime = NSEC2MSEC(500); /* minimum time between pings */ /* * Globals for our name services warning. See ns_warning_thr() for more on why * this exists. */ static mutex_t ns_lock = ERRORCHECKMUTEX; /* Protects the following data */ static boolean_t ns_active = _B_FALSE; /* Lookup is going on */ static hrtime_t ns_starttime; /* Time the lookup started */ static int ns_sleeptime = 2; /* Time in seconds between checks */ static int ns_warntime = 2; /* Time in seconds before warning */ /* * 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 *); static struct targetaddr *create_targetaddr_item(struct sockaddr *, 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); static void mirror_gws(union any_in_addr *, int); static void *ns_warning_thr(void *); static void parse_interval(const char *s); static void pinger(int, struct sockaddr *, struct msghdr *, 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 **, struct addrinfo **, union any_in_addr **); 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 *); static void set_nexthop(int, struct addrinfo *, int); static boolean_t setup_socket(int, int *, int *, int *, ushort_t *, struct addrinfo *); static void usage(char *); /* * main() */ int main(int argc, char *argv[]) { struct addrinfo *ai_dst = NULL; /* addrinfo host list */ struct addrinfo *ai_nexthop = NULL; /* addrinfo nexthop */ 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 abuf[INET6_ADDRSTRLEN]; int c; int i; boolean_t has_sys_ip_config; progname = argv[0]; (void) setlocale(LC_ALL, ""); /* * This program needs the net_icmpaccess privilege for creating * raw ICMP sockets. It needs sys_ip_config for using the * IP_NEXTHOP socket option (IPv4 only). We'll fail * on the socket call and report the error there when we have * insufficient privileges. * * Shared-IP zones don't have the sys_ip_config privilege, so * we need to check for it in our limit set before trying * to set it. */ has_sys_ip_config = priv_ineffect(PRIV_SYS_IP_CONFIG); (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_NET_ICMPACCESS, has_sys_ip_config ? PRIV_SYS_IP_CONFIG : (char *)NULL, (char *)NULL); setbuf(stdout, (char *)0); while ((c = getopt(argc, argv, "abA:c:dDF:G:g:I:i:LlnN:P: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 'D': dontfrag = 1; break; case 'b': bypass = _B_TRUE; 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; parse_interval(optarg); 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 'N': if (nexthop != NULL) { Fprintf(stderr, "%s: only one next hop gateway" " allowed\n", progname); exit(EXIT_FAILURE); } nexthop = 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; if (getenv("MACHINE_THAT_GOES_PING") != NULL) stats = _B_TRUE; 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, &ai_nexthop, &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, ai_nexthop)) exit(EXIT_FAILURE); } if (num_v4 != 0) { if (!setup_socket(AF_INET, &send_sock, &recv_sock, &if_index, &udp_src_port, ai_nexthop)) 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) { (void) inet_ntop(AF_INET, &((struct sockaddr_in *)(void *) ai_dst->ai_addr)->sin_addr, abuf, sizeof (abuf)); } else { (void) inet_ntop(AF_INET6, &((struct sockaddr_in6 *)(void *) ai_dst->ai_addr)->sin6_addr, abuf, sizeof (abuf)); } Printf("PING %s (%s): %d data bytes\n", targethost, abuf, datalen); } } /* Create our timer for future use */ if (timer_create(CLOCK_REALTIME, NULL, &timer) != 0) { Fprintf(stderr, "%s: failed to create timer: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } /* * Finally start up the name services warning thread. */ if (thr_create(NULL, 0, ns_warning_thr, NULL, THR_DETACHED | THR_DAEMON, NULL) != 0) { Fprintf(stderr, "%s: failed to create name services " "thread: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } /* 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); return (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_addr, &src_addr_list[i]); } else if (aip->ai_family == AF_INET6 && num_v6 != 0) { targetaddr = create_targetaddr_item(aip->ai_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 a destination sockaddr (containing address family, address, and * perhaps address scope) and a source address, this function creates a * targetaddr structure that will become part of the global targetaddr * list. Each targetaddr tracks the probes sent to a specific destination. */ static struct targetaddr * create_targetaddr_item(struct sockaddr *dst_addr, union any_in_addr *src_addr) { struct targetaddr *targetaddr; struct sockaddr_in *dst4 = (struct sockaddr_in *)dst_addr; struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst_addr; targetaddr = (struct targetaddr *)malloc(sizeof (struct targetaddr)); if (targetaddr == NULL) { Fprintf(stderr, "%s: malloc %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } targetaddr->family = dst_addr->sa_family; switch (dst_addr->sa_family) { case AF_INET: memset(&targetaddr->dst_addr, 0, sizeof (targetaddr->dst_addr)); targetaddr->dst_addr.addr = dst4->sin_addr; targetaddr->dst_scope = 0; break; case AF_INET6: memset(&targetaddr->dst_addr, 0, sizeof (targetaddr->dst_addr)); targetaddr->dst_addr.addr6 = dst6->sin6_addr; targetaddr->dst_scope = dst6->sin6_scope_id; if (if_index != 0 && targetaddr->dst_scope != 0 && if_index != targetaddr->dst_scope) { Fprintf(stderr, "%s: warning: conflicting scopes; using" " %s\n", progname, pr_if(targetaddr->dst_scope)); } break; } 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, struct addrinfo **ai_nexthopp, union any_in_addr **src_addr_listp) { struct addrinfo *ai_dst = NULL; struct addrinfo *ai_nexthop = 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); } if (nexthop != NULL) { get_hostinfo(nexthop, family_input, &ai_nexthop); if (ai_nexthop == NULL) { print_unknown_host_msg("", nexthop); 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; *ai_nexthopp = ai_nexthop; *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 = 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 = NULL; struct sockaddr_in6 *sin6 = NULL; 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) { assert(sin != NULL); src_addr->addr = sin->sin_addr; } else { assert(sin6 != NULL); src_addr->addr6 = sin6->sin6_addr; } (void) close(tmp_fd); free(sock); } /* * Set the IP_NEXTHOP/IPV6_NEXTHOP socket option. * exits on failure */ static void set_nexthop(int family, struct addrinfo *ai_nexthop, int sock) { if (family == AF_INET) { ipaddr_t nh; /* LINTED E_BAD_PTR_CAST_ALIGN */ nh = ((struct sockaddr_in *)ai_nexthop-> ai_addr)->sin_addr.s_addr; /* now we need the sys_ip_config privilege */ (void) __priv_bracket(PRIV_ON); if (setsockopt(sock, IPPROTO_IP, IP_NEXTHOP, &nh, sizeof (ipaddr_t)) < 0) { if (errno == EPERM) Fprintf(stderr, "%s: Insufficient privilege " "to specify IPv4 nexthop router.\n", progname); else Fprintf(stderr, "%s: setsockopt %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } (void) __priv_bracket(PRIV_OFF); /* revert to non-privileged user */ } else { struct sockaddr_in6 *nh; /* LINTED E_BAD_PTR_CAST_ALIGN */ nh = (struct sockaddr_in6 *)ai_nexthop-> ai_addr; if (setsockopt(sock, IPPROTO_IPV6, IPV6_NEXTHOP, nh, sizeof (struct sockaddr_in6)) < 0) { Fprintf(stderr, "%s: setsockopt %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } } /* * 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, struct addrinfo *ai_nexthop) { int send_sock; int recv_sock; struct sockaddr_in6 sin6; struct sockaddr_in sin; struct sockaddr *sp; struct ipsec_req req; 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); if (bypass) { (void) memset(&req, 0, sizeof (req)); req.ipsr_ah_req = IPSEC_PREF_NEVER; req.ipsr_esp_req = IPSEC_PREF_NEVER; if (setsockopt(recv_sock, (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6, IP_SEC_OPT, &req, sizeof (req)) < 0) { switch (errno) { case EPROTONOSUPPORT: /* * No IPsec subsystem or policy loaded. * Bypass implicitly allowed. */ break; case EPERM: Fprintf(stderr, "%s: Insufficient privilege " "to bypass IPsec policy.\n", progname); exit(EXIT_FAILURE); break; default: Fprintf(stderr, "%s: setsockopt %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); break; } } } /* * 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); } if (bypass) { if (setsockopt(send_sock, (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6, IP_SEC_OPT, &req, sizeof (req)) < 0) { switch (errno) { case EPROTONOSUPPORT: /* * No IPsec subsystem or policy loaded. * Bypass implicitly allowed. */ break; case EPERM: Fprintf(stderr, "%s: Insufficient " "privilege to bypass IPsec " "policy.\n", progname); exit(EXIT_FAILURE); break; default: Fprintf(stderr, "%s: setsockopt %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); break; } } } /* * 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; } if (nexthop != NULL) set_nexthop(family, ai_nexthop, send_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; /* Applies to unicast and multicast. */ 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? * Applies to unicast, broadcast and multicast. */ 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, LIFC_UNDER_IPMP, 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) { struct in_pktinfo pktinfo; 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); } bzero(&pktinfo, sizeof (pktinfo)); pktinfo.ipi_ifindex = my_if->index; if (setsockopt(send_sock, IPPROTO_IP, IP_PKTINFO, (char *)&pktinfo, sizeof (pktinfo)) == -1) { Fprintf(stderr, "%s: setsockopt " "IP_PKTINFO %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); } } /* We enable or disable to not depend on the kernel default */ if (family == AF_INET) { if (setsockopt(send_sock, IPPROTO_IP, IP_DONTFRAG, (char *)&dontfrag, sizeof (dontfrag)) == -1) { Fprintf(stderr, "%s: setsockopt IP_DONTFRAG %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } else { if (setsockopt(send_sock, IPPROTO_IPV6, IPV6_DONTFRAG, (char *)&dontfrag, sizeof (dontfrag)) == -1) { Fprintf(stderr, "%s: setsockopt IPV6_DONTFRAG %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); } } /* Ensure that timestamping is requested on the receive socket */ if (setsockopt(recv_sock, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof (on)) == -1) { Fprintf(stderr, "%s: warning: timing accuracy diminished -- " "setsockopt SO_TIMESTAMP failed %s", progname, strerror(errno)); } *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) { /* * If we've been told that we're done, the timer should be cancelled * and not rescheduled, just return. */ if (timer_done == _B_TRUE) return; /* * Guard against denial-of-service attacks. Make sure ping doesn't send * probes for every SIGALRM it receives in the case of errant SIGALRMs. * ping will ignore those which are received too soon (the smaller of * 0.5 sec and the ping interval, if in effect) 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) < mintime) { return; } send_scheduled_probe(); schedule_sigalrm(); } /* * Schedule next SIGALRM. */ void schedule_sigalrm(void) { int waittime; struct itimerspec it; bzero(&it, sizeof (struct itimerspec)); if (npackets == 0 || current_targetaddr->num_sent < current_targetaddr->num_probes) { it = interval; } else { if (current_targetaddr->got_reply) { waittime = 2 * tmax / MICROSEC; if (waittime == 0) waittime = 1; } else { waittime = MAX_WAIT; } it.it_value.tv_sec = waittime; } if (timer_settime(timer, TIMER_RELTIME, &it, NULL) != 0) { Fprintf(stderr, "%s: unexpected error updating time: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } /* * 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) { timer_done = _B_TRUE; 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; } to6.sin6_scope_id = current_targetaddr->dst_scope; /* * 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)) { int scope = if_index; if (scope != 0 && to6.sin6_scope_id != 0) /* * don't bother setting scope in ancillary data * as sin6_scope_id will override it. */ scope = 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, scope); 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; } else 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)) { timer_done = _B_TRUE; 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; switch (family) { case AF_INET: (void) memset(&sin, 0, sizeof (sin)); /* 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)); /* LINTED E_BAD_PTR_CAST_ALIGN */ sin6.sin6_addr = *(struct in6_addr *)addr; sin6.sin6_port = 0; sa = (struct sockaddr *)&sin6; break; default: sa = (struct sockaddr *)&sin6; break; } sa->sa_family = family; return (pr_name_sa(sa)); } char * pr_name_sa(const struct sockaddr *sa) { const struct sockaddr_in *sin = (struct sockaddr_in *)sa; const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; int family = sa->sa_family; static struct in6_addr prev_addr = IN6ADDR_ANY_INIT; static int prev_scope = 0; char *cp; char abuf[INET6_ADDRSTRLEN]; static char buf[NI_MAXHOST + INET6_ADDRSTRLEN + 3]; uint_t slen, alen, hlen; char *addr; int scope; switch (family) { case AF_INET: slen = sizeof (struct sockaddr_in); alen = sizeof (struct in_addr); addr = (char *)&sin->sin_addr; scope = 0; break; case AF_INET6: slen = sizeof (struct sockaddr_in6); alen = sizeof (struct in6_addr); addr = (char *)&sin6->sin6_addr; scope = sin6->sin6_scope_id; break; default: (void) snprintf(buf, sizeof (buf), ""); return (buf); } /* compare with the buffered (previous) lookup */ if (scope != prev_scope || memcmp(addr, &prev_addr, alen) != 0) { int flags = (nflag) ? NI_NUMERICHOST : NI_NAMEREQD; mutex_enter(&ns_lock); ns_active = _B_TRUE; ns_starttime = gethrtime(); mutex_exit(&ns_lock); if (getnameinfo(sa, slen, buf, sizeof (buf), NULL, 0, flags) != 0) { /* getnameinfo() failed; return just the address */ if (getnameinfo(sa, slen, buf, sizeof (buf), NULL, 0, NI_NUMERICHOST) != 0) { 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))); } mutex_enter(&ns_lock); ns_active = _B_FALSE; mutex_exit(&ns_lock); /* LINTED E_BAD_PTR_CAST_ALIGN */ prev_addr = *(struct in6_addr *)addr; prev_scope = scope; } return (buf); } char * pr_name4(const struct sockaddr_in *sin) { return (pr_name_sa((const struct sockaddr *)sin)); } char * pr_name6(const struct sockaddr_in6 *sin6) { return (pr_name_sa((const struct sockaddr *)sin6)); } /* * 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); } char * pr_if(int ifindex) { static struct ifaddrlist *al = NULL; static int count = 0; static char errbuf[ERRBUFSIZE]; int i; if (al == NULL) { count = ifaddrlist(&al, AF_INET6, 0, errbuf); if (count < 0) count = 0; } for (i = 0; i < count; i++) { if (al[i].index == ifindex) return (al[i].device); } (void) sprintf(errbuf, "%d", ifindex); return (errbuf); } /* * 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)) { 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 "/" TIMEFORMAT "/" TIMEFORMAT "\n", (double)tmin / 1000, mean / 1000, (double)tmax / 1000, 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] [-abdDLnRrv] [-A addr_family] [-c traffic_class]\n\t" "[-g gateway [-g gateway ...]] [-N nexthop] [-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) { Fprintf(stderr, "%s: bad %s: %s\n", progname, what, s); exit(EXIT_FAILURE); } return (num); } /* * Parse the interval into a itimerspec. The interval used to originally be * parsed as an integer argument. That means that one used to be able to specify * an interval in hex. The strtod() family honors that at times, with strtod * sometimes doing so depending on the compilation environment and strtof() and * srtold() always doing that. To facilitiate that and not worry about a * careless Makefile change breaking us, we instead just use strtold here, even * though we really don't need the precision. */ static void parse_interval(const char *s) { long double val; char *end; errno = 0; val = strtold(s, &end); if (errno != 0 || *end != '\0') { Fprintf(stderr, "%s: bad interval: %s\n", progname, s); exit(EXIT_FAILURE); } /* * Check values that we know are going to be bad. Anything greater than * INT_MAX, anything less than 0, look for specific NaNs. Also, clamp * the value at 0.01 seconds. */ if (val == NAN || val <= 0.0 || val >= INT_MAX) { Fprintf(stderr, "%s: bad interval: %s\n", progname, s); exit(EXIT_FAILURE); } if (val < 0.01L) { Fprintf(stderr, "%s: interval too small: %Lf\n", progname, val); exit(EXIT_FAILURE); } interval.it_value.tv_sec = (long)val; interval.it_value.tv_nsec = (long)((val - interval.it_value.tv_sec) * NANOSEC); if (interval.it_value.tv_sec == 0 && interval.it_value.tv_nsec < mintime) { mintime = interval.it_value.tv_nsec; } } /* * We should have an SO_TIMESTAMP message for this socket to indicate * the actual time that the message took. If we don't we'll fall back to * gettimeofday(); however, that can cause any delays due to DNS * resolution and the like to end up wreaking havoc on us. */ void ping_gettime(struct msghdr *msg, struct timeval *tv) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP && cmsg->cmsg_len == CMSG_LEN(sizeof (*tv))) { bcopy(CMSG_DATA(cmsg), tv, sizeof (*tv)); return; } } (void) gettimeofday(tv, (struct timezone *)NULL); } /* * The purpose of this thread is to try and inform a user that we're blocked * doing name lookups. For various reasons, ping has to try and look up the IP * addresses it receives via name services unless the -n flag is specified. The * irony of this is that when trying to use ping to actually diagnose a broken * network, name services are unlikely to be available and that will result in a * lot of confusion as to why pings seem like they're not working. As such, we * basically wake up every 2 seconds and check whether or not we've hit such a * condition where we should inform the user via stderr. * * Once they've been informed, we do not inform them again until approximately a * minute of time has passed, in case that things are working intermittently. */ /*ARGSUSED*/ static void * ns_warning_thr(void *unused) { for (;;) { hrtime_t now; (void) sleep(ns_sleeptime); now = gethrtime(); mutex_enter(&ns_lock); if (ns_active == _B_TRUE && now - ns_starttime >= ns_warntime * NANOSEC) { Fprintf(stderr, "%s: warning: ICMP responses " "received, but name service lookups are " "taking a while. Use ping -n to disable " "name service lookups.\n", progname); mutex_exit(&ns_lock); return (NULL); } mutex_exit(&ns_lock); } /* LINTED: E_STMT_NOT_REACHED */ return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, (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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2024 Bill Sommerfeld */ #ifndef _PING_H #define _PING_H #include #include #include #ifdef __cplusplus extern "C" { #endif #define MAX_PORT 65535 /* max port number for UDP probes */ #define MAX_ICMP_SEQ 65535 /* max icmp sequence value */ /* * Maximum number of source route space. Please note that because of the API, * we can only specify 8 gateways, the last address has to be the target * address. */ #define MAX_GWS 9 /* * This is the max it can be. But another limiting factor is the PMTU, * so in any instance, it can be less than 127. */ #define MAX_GWS6 127 /* maximum of above two */ #define MAXMAX_GWS MAX(MAX_GWS, MAX_GWS6) /* size of buffer to store the IPv4 gateway addresses */ #define ROUTE_SIZE (IPOPT_OLEN + IPOPT_OFFSET + \ MAX_GWS * sizeof (struct in_addr)) #define A_CNT(ARRAY) (sizeof (ARRAY) / sizeof ((ARRAY)[0])) #define Printf (void) printf #define Fprintf (void) fprintf #define TIMEFORMAT "%#.3f" /* * For each target IP address we are going to probe, we store required info, * such as address family, IP address of target, source IP address to use * for that target address, and number of probes to send in the targetaddr * structure. * All target addresses are also linked to each other and used in * scheduling probes. Each targetaddr structure identifies a batch of probes to * send : where to send, how many to send. We capture state information, such as * number of probes already sent (in this batch only), whether target replied * as we probe it, whether we are done with probing this address (can happen * in regular (!stats) mode when we get a reply for a probe sent in current * batch), and starting sequence number which is used together with number of * probes sent to determine if the incoming reply is for a probe we sent in * current batch. */ struct targetaddr { int family; union any_in_addr dst_addr; /* dst address for the probe */ union any_in_addr src_addr; /* src addr to use for this dst addr */ int dst_scope; /* scope of dst addr or zero */ int num_probes; /* num of probes to send to this dst */ int num_sent; /* number of probes already sent */ boolean_t got_reply; /* received a reply from dst while */ /* still probing it */ boolean_t probing_done; /* skip without sending all probes */ ushort_t starting_seq_num; /* initial icmp_seq/UDP port, used */ /* for authenticating replies */ struct targetaddr *next; /* next targetaddr item in the list */ }; struct hostinfo { char *name; /* hostname */ int family; /* address family */ int num_addr; /* number of addresses */ union any_in_addr *addrs; /* address list */ }; struct icmptype_table { int type; /* ICMP type */ char *message; /* corresponding string message */ }; extern struct targetaddr *current_targetaddr; extern int nreceived; extern int nreceived_last_target; extern int npackets; extern boolean_t is_alive; extern int datalen; extern boolean_t nflag; extern int ident; extern boolean_t probe_all; extern char *progname; extern boolean_t rr_option; extern boolean_t stats; extern boolean_t strict; extern char *targethost; extern long long tmax; extern long long tmin; extern int ts_flag; extern boolean_t ts_option; extern int64_t tsum; extern int64_t tsum2; extern boolean_t use_icmp_ts; extern boolean_t use_udp; extern boolean_t verbose; extern boolean_t send_reply; extern void ping_gettime(struct msghdr *, struct timeval *); extern void set_ancillary_data(struct msghdr *, int, union any_in_addr *, int, uint_t); extern void check_reply(struct addrinfo *, struct msghdr *, int, ushort_t); extern void check_reply6(struct addrinfo *, struct msghdr *, int, ushort_t); extern void find_dstaddr(ushort_t, union any_in_addr *); extern boolean_t is_a_target(struct addrinfo *, union any_in_addr *); extern char *pr_if(int); extern char *pr_name(char *, int); extern char *pr_name_sa(const struct sockaddr *); extern char *pr_name4(const struct sockaddr_in *); extern char *pr_name6(const struct sockaddr_in6 *); extern char *pr_protocol(int); extern void schedule_sigalrm(void); extern void send_scheduled_probe(void); extern boolean_t seq_match(ushort_t, int, ushort_t); extern void set_IPv4_options(int, union any_in_addr *, int, struct in_addr *, struct in_addr *); extern void sigalrm_handler(); extern void tvsub(struct timeval *, struct timeval *); #ifdef __cplusplus } #endif #endif /* _PING_H */ /* * 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 2006 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 */ /* * Copyright 2015, Joyent, Inc. * Copyright 2024 Bill Sommerfeld */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #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" /* * IPv4 source routing option. * In order to avoid padding for the alignment of IPv4 addresses, ipsr_addrs * is defined as a 2-D array of uint8_t, instead of 1-D array of struct in_addr. */ struct ip_sourceroute { uint8_t ipsr_code; uint8_t ipsr_len; uint8_t ipsr_ptr; /* up to 9 IPv4 addresses */ uint8_t ipsr_addrs[1][sizeof (struct in_addr)]; }; static void pr_options(uchar_t *, int); static void pr_rropt(uchar_t *, int, boolean_t); static void pr_tsopt(uchar_t *, int); static char *pr_type(int); /* * Set IPv4 options */ void set_IPv4_options(int sock, union any_in_addr *gw_IP_list, int gw_count, struct in_addr *src, struct in_addr *dst) { int req_size; char srr[ROUTE_SIZE + 1]; char *bufp; int optsize = ROUTE_SIZE; struct ip_sourceroute *srp; struct ip_timestamp *tsp; int i; if (rr_option || ts_option || gw_count > 0) { bzero(srr, sizeof (srr)); bufp = srr; if (gw_count > 0) { /* 3 = 1 (code) + 1 (len) + 1 (ptr) of src route opt. */ req_size = 3 + (sizeof (struct in_addr)) * gw_count; if (optsize < req_size) { Fprintf(stderr, "%s: too many IPv4 gateways\n", progname); exit(EXIT_FAILURE); } srp = (struct ip_sourceroute *)bufp; srp->ipsr_code = strict ? IPOPT_SSRR : IPOPT_LSRR; srp->ipsr_len = req_size; srp->ipsr_ptr = IPOPT_MINOFF; for (i = 0; i < gw_count; i++) { bcopy((char *)&gw_IP_list[i].addr, &srp->ipsr_addrs[i], sizeof (struct in_addr)); } optsize -= srp->ipsr_len; bufp += srp->ipsr_len; } /* do we send a timestamp option? */ if (ts_option) { if (optsize < IPOPT_MINOFF) { Fprintf(stderr, "%s: no room for timestamp option\n", progname); exit(EXIT_FAILURE); } /* LINTED */ tsp = (struct ip_timestamp *)bufp; tsp->ipt_code = IPOPT_TS; tsp->ipt_len = optsize; tsp->ipt_ptr = IPOPT_MINOFF + 1; tsp->ipt_flg = ts_flag & 0x0f; if (tsp->ipt_flg > IPOPT_TS_TSANDADDR) { req_size = 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. */ req_size++; if (optsize < req_size) { Fprintf(stderr, "%s: no room for " "timestamp option\n", progname); exit(EXIT_FAILURE); } bcopy((char *)dst, &tsp->ipt_timestamp.ipt_ta[0].ipt_addr, sizeof (struct in_addr)); bcopy((char *)src, &tsp->ipt_timestamp.ipt_ta[1].ipt_addr, sizeof (struct in_addr)); tsp->ipt_len = req_size; } optsize -= tsp->ipt_len; bufp += tsp->ipt_len; } /* do we send a record route option? */ if (rr_option) { if (optsize < IPOPT_MINOFF) { Fprintf(stderr, "%s: no room for record route option\n", progname); exit(EXIT_FAILURE); } /* * Format of record route option is same as source * route option. */ srp = (struct ip_sourceroute *)bufp; srp->ipsr_code = IPOPT_RR; srp->ipsr_len = optsize; srp->ipsr_ptr = IPOPT_MINOFF; optsize -= srp->ipsr_len; bufp += srp->ipsr_len; } optsize = bufp - srr; /* Round up to 4 byte boundary */ if (optsize & 0x3) optsize = (optsize & ~0x3) + 4; if (setsockopt(sock, IPPROTO_IP, IP_OPTIONS, srr, optsize) < 0) { Fprintf(stderr, "%s: setsockopt IP_OPTIONS %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } } /* * Check out the packet to see 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!). */ void check_reply(struct addrinfo *ai_dst, struct msghdr *msg, int cc, ushort_t udp_src_port) { struct ip *ip; struct icmp *icp; struct udphdr *up; union any_in_addr dst_addr; uchar_t *buf; int32_t *intp; struct sockaddr_in *from; struct timeval *tp; struct timeval tv; int hlen, hlen1; int64_t triptime; boolean_t valid_reply = _B_FALSE; boolean_t reply_matched_current_target = _B_FALSE; /* Is the source */ /* address of this reply same */ /* as where we're sending */ /* currently? */ boolean_t last_reply_from_targetaddr = _B_FALSE; /* Is this stats, */ /* probe all with npackets>0 */ /* and we received reply for */ /* the last probe sent to */ /* targetaddr */ int cc_left; char tmp_buf[INET6_ADDRSTRLEN]; static char *unreach[] = { "Net Unreachable", "Host Unreachable", "Protocol Unreachable", "Port Unreachable", "Fragmentation needed and DF set", "Source Route Failed", /* The following are from RFC1700 */ "Net Unknown", "Host Unknown", "Source Host Isolated", "Dest Net Prohibited", "Dest Host Prohibited", "Net Unreachable for TOS", "Host Unreachable for TOS", "Communication Administratively Prohibited", "Host Precedence Violation", "Precedence Cutoff in Effect" }; static char *redirect[] = { "Net", "Host", "TOS Net", "TOS Host" }; static char *timexceed[] = { "Time exceeded in transit", "Time exceeded during reassembly" }; boolean_t print_newline = _B_FALSE; int i; /* decompose msghdr into useful pieces */ buf = (uchar_t *)msg->msg_iov->iov_base; from = (struct sockaddr_in *)msg->msg_name; /* LINTED */ intp = (int32_t *)buf; ping_gettime(msg, &tv); /* LINTED */ ip = (struct ip *)buf; hlen = ip->ip_hl << 2; if ((cc < sizeof (struct ip)) || (cc < hlen + ICMP_MINLEN)) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, pr_name4(from)); } return; } cc -= hlen; /* LINTED */ icp = (struct icmp *)(buf + hlen); if (ip->ip_p == 0) { /* * Assume that we are running on a pre-4.3BSD system * such as SunOS before 4.0 */ /* LINTED */ icp = (struct icmp *)buf; } cc_left = cc - ICMP_MINLEN; switch (icp->icmp_type) { case ICMP_UNREACH: ip = &icp->icmp_ip; hlen1 = ip->ip_hl << 2; /* check if we have enough of the packet to work on */ if ((cc_left < sizeof (struct ip)) || (cc_left < hlen1 + sizeof (struct udphdr))) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, pr_name4(from)); } return; } /* get the UDP packet */ cc_left -= hlen1 + sizeof (struct udphdr); /* LINTED */ up = (struct udphdr *)((uchar_t *)ip + hlen1); /* check to see if this is what we sent */ if (icp->icmp_code == ICMP_UNREACH_PORT && ip->ip_p == IPPROTO_UDP && udp_src_port == up->uh_sport && use_udp) { valid_reply = _B_TRUE; } else { valid_reply = _B_FALSE; } if (valid_reply) { /* * For this valid reply, if we are still sending to * this target IP address, we'd like to do some * updates to targetaddr, so hold SIGALRMs. */ (void) sighold(SIGALRM); is_alive = _B_TRUE; nreceived++; reply_matched_current_target = seq_match(current_targetaddr->starting_seq_num, current_targetaddr->num_sent, ntohs(up->uh_dport)); if (reply_matched_current_target) { current_targetaddr->got_reply = _B_TRUE; nreceived_last_target++; /* * Determine if stats, probe-all, and * npackets != 0, and this is the reply for * the last probe we sent to current target * address. */ if (stats && probe_all && npackets > 0 && ((current_targetaddr->starting_seq_num + current_targetaddr->num_probes - 1) % (MAX_PORT + 1) == ntohs(up->uh_dport)) && (current_targetaddr->num_probes == current_targetaddr->num_sent)) last_reply_from_targetaddr = _B_TRUE; } else { /* * If it's just probe_all and we just received * a reply from a target address we were * probing and had timed out (now we are probing * some other target address), we ignore * this reply. */ if (probe_all && !stats) { valid_reply = _B_FALSE; /* * Only if it's verbose, we get a * message regarding this reply, * otherwise we are done here. */ if (!verbose) { (void) sigrelse(SIGALRM); return; } } } } /* stats mode doesn't print 'alive' messages */ if (valid_reply && !stats) { /* * if we are still sending to the same target address, * then stop it, because we know it's alive. */ if (reply_matched_current_target) { (void) alarm(0); /* cancel alarm */ (void) sigset(SIGALRM, SIG_IGN); current_targetaddr->probing_done = _B_TRUE; } (void) sigrelse(SIGALRM); if (!probe_all) { Printf("%s is alive\n", targethost); } else { (void) inet_ntop(AF_INET, (void *)&ip->ip_dst, tmp_buf, sizeof (tmp_buf)); if (nflag) { Printf("%s is alive\n", tmp_buf); } else { Printf("%s (%s) is alive\n", targethost, tmp_buf); } } if (reply_matched_current_target) { /* * Let's get things going again, but now * ping will start sending to next target IP * address. */ send_scheduled_probe(); (void) sigset(SIGALRM, sigalrm_handler); schedule_sigalrm(); } return; } else { /* * If we are not moving to next targetaddr, let's * release the SIGALRM now. We don't want to stall in * the middle of probing a targetaddr if the pr_name() * call (see below) takes longer. */ if (!last_reply_from_targetaddr) (void) sigrelse(SIGALRM); /* else, we'll release it later */ } dst_addr.addr = ip->ip_dst; if (valid_reply) { Printf("%d bytes from %s: ", cc, pr_name4(from)); Printf("udp_port=%d. ", ntohs(up->uh_dport)); print_newline = _B_TRUE; } else if (is_a_target(ai_dst, &dst_addr) || verbose) { if (icp->icmp_code >= A_CNT(unreach)) { Printf("ICMP %d Unreachable from gateway %s\n", icp->icmp_code, pr_name4(from)); } else { Printf("ICMP %s from gateway %s\n", unreach[icp->icmp_code], pr_name4(from)); } Printf(" for %s from %s", pr_protocol(ip->ip_p), pr_name((char *)&ip->ip_src, AF_INET)); Printf(" to %s", pr_name((char *)&ip->ip_dst, AF_INET)); if (ip->ip_p == IPPROTO_TCP || ip->ip_p == IPPROTO_UDP) { Printf(" port %d ", ntohs(up->uh_dport)); } print_newline = _B_TRUE; } /* if we are timing and the reply has a timeval */ if (valid_reply && datalen >= sizeof (struct timeval) && cc_left >= sizeof (struct timeval)) { /* LINTED */ tp = (struct timeval *)((char *)up + sizeof (struct udphdr)); (void) tvsub(&tv, tp); triptime = (int64_t)tv.tv_sec * MICROSEC + tv.tv_usec; Printf("time=" TIMEFORMAT " ms", triptime/1000.0); tsum += triptime; tsum2 += triptime*triptime; if (triptime < tmin) tmin = triptime; if (triptime > tmax) tmax = triptime; print_newline = _B_TRUE; } if (print_newline) (void) putchar('\n'); /* * If it's stats, probe-all, npackets > 0, and we received reply * for the last probe sent to this target address, then we * don't need to wait anymore, let's move on to next target * address, now! */ if (last_reply_from_targetaddr) { (void) alarm(0); /* cancel alarm */ current_targetaddr->probing_done = _B_TRUE; (void) sigrelse(SIGALRM); send_scheduled_probe(); schedule_sigalrm(); } break; case ICMP_REDIRECT: if (cc_left < sizeof (struct ip)) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, pr_name4(from)); } return; } ip = &icp->icmp_ip; dst_addr.addr = ip->ip_dst; if (is_a_target(ai_dst, &dst_addr) || verbose) { if (icp->icmp_code >= A_CNT(redirect)) { Printf("ICMP %d redirect from gateway %s\n", icp->icmp_code, pr_name4(from)); } else { Printf("ICMP %s redirect from gateway %s\n", redirect[icp->icmp_code], pr_name4(from)); } Printf(" to %s", pr_name((char *)&icp->icmp_gwaddr, AF_INET)); Printf(" for %s\n", pr_name((char *)&ip->ip_dst, AF_INET)); } break; case ICMP_ECHOREPLY: if (ntohs(icp->icmp_id) == ident) { if (!use_udp && !use_icmp_ts) valid_reply = _B_TRUE; else valid_reply = _B_FALSE; } else { return; } if (valid_reply) { /* * For this valid reply, if we are still sending to * this target IP address, we'd like to do some * updates to targetaddr, so hold SIGALRMs. */ (void) sighold(SIGALRM); is_alive = _B_TRUE; nreceived++; reply_matched_current_target = seq_match(current_targetaddr->starting_seq_num, current_targetaddr->num_sent, ntohs(icp->icmp_seq)); if (reply_matched_current_target) { current_targetaddr->got_reply = _B_TRUE; nreceived_last_target++; /* * Determine if stats, probe-all, and * npackets != 0, and this is the reply for * the last probe we sent to current target * address. */ if (stats && probe_all && npackets > 0 && ((current_targetaddr->starting_seq_num + current_targetaddr->num_probes - 1) % (MAX_ICMP_SEQ + 1) == ntohs(icp->icmp_seq)) && (current_targetaddr->num_probes == current_targetaddr->num_sent)) last_reply_from_targetaddr = _B_TRUE; } else { /* * If it's just probe_all and we just received * a reply from a target address we were * probing and had timed out (now we are probing * some other target address), we ignore * this reply. */ if (probe_all && !stats) { valid_reply = _B_FALSE; /* * Only if it's verbose, we get a * message regarding this reply, * otherwise we are done here. */ if (!verbose) { (void) sigrelse(SIGALRM); return; } } } } if (!stats && valid_reply) { /* * if we are still sending to the same target address, * then stop it, because we know it's alive. */ if (reply_matched_current_target) { (void) alarm(0); /* cancel alarm */ (void) sigset(SIGALRM, SIG_IGN); current_targetaddr->probing_done = _B_TRUE; } (void) sigrelse(SIGALRM); if (!probe_all) { Printf("%s is alive\n", targethost); } else { /* * If we are using send_reply, the real * target address is not the src address of the * replies. Use icmp_seq to find out where this * probe was sent to. */ if (send_reply) { (void) find_dstaddr( ntohs(icp->icmp_seq), &dst_addr); (void) inet_ntop(AF_INET, (void *)&dst_addr.addr, tmp_buf, sizeof (tmp_buf)); } else { (void) inet_ntop(AF_INET, (void *)&from->sin_addr, tmp_buf, sizeof (tmp_buf)); } if (nflag) { Printf("%s is alive\n", tmp_buf); } else { Printf("%s (%s) is alive\n", targethost, tmp_buf); } } if (reply_matched_current_target) { /* * Let's get things going again, but now * ping will start sending to next target IP * address. */ send_scheduled_probe(); (void) sigset(SIGALRM, sigalrm_handler); schedule_sigalrm(); } return; } else { /* * If we are not moving to next targetaddr, let's * release the SIGALRM now. We don't want to stall in * the middle of probing a targetaddr if the pr_name() * call (see below) takes longer. */ if (!last_reply_from_targetaddr) (void) sigrelse(SIGALRM); /* else, we'll release it later */ } /* * If we are using send_reply, the real target address is * not the src address of the replies. Use icmp_seq to find out * where this probe was sent to. */ if (send_reply) { (void) find_dstaddr(ntohs(icp->icmp_seq), &dst_addr); Printf("%d bytes from %s: ", cc, pr_name((char *)&dst_addr.addr, AF_INET)); } else { Printf("%d bytes from %s: ", cc, pr_name4(from)); } Printf("icmp_seq=%d. ", ntohs(icp->icmp_seq)); if (valid_reply && datalen >= sizeof (struct timeval) && cc_left >= sizeof (struct timeval)) { /* LINTED */ tp = (struct timeval *)&icp->icmp_data[0]; (void) tvsub(&tv, tp); triptime = (int64_t)tv.tv_sec * MICROSEC + tv.tv_usec; Printf("time=" TIMEFORMAT " ms", triptime/1000.0); tsum += triptime; tsum2 += triptime*triptime; if (triptime < tmin) tmin = triptime; if (triptime > tmax) tmax = triptime; } (void) putchar('\n'); /* * If it's stats, probe-all, npackets > 0, and we received reply * for the last probe sent to this target address, then we * don't need to wait anymore, let's move on to next target * address, now! */ if (last_reply_from_targetaddr) { (void) alarm(0); /* cancel alarm */ current_targetaddr->probing_done = _B_TRUE; (void) sigrelse(SIGALRM); send_scheduled_probe(); schedule_sigalrm(); } break; case ICMP_SOURCEQUENCH: if (cc_left < sizeof (struct ip)) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, pr_name4(from)); } return; } ip = &icp->icmp_ip; hlen1 = ip->ip_hl << 2; dst_addr.addr = ip->ip_dst; if (is_a_target(ai_dst, &dst_addr) || verbose) { Printf("ICMP Source Quench from %s\n", pr_name4(from)); Printf(" for %s from %s", pr_protocol(ip->ip_p), pr_name((char *)&ip->ip_src, AF_INET)); Printf(" to %s", pr_name((char *)&ip->ip_dst, AF_INET)); /* * if it's a UDP or TCP packet, we need at least first * 4 bytes of it to see the src/dst ports */ if ((ip->ip_p == IPPROTO_TCP || ip->ip_p == IPPROTO_UDP) && (cc_left >= hlen1 + 4)) { /* LINTED */ up = (struct udphdr *)((uchar_t *)ip + hlen1); Printf(" port %d", ntohs(up->uh_dport)); } (void) putchar('\n'); } break; case ICMP_PARAMPROB: if (cc_left < sizeof (struct ip)) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, pr_name4(from)); } return; } ip = &icp->icmp_ip; hlen1 = ip->ip_hl << 2; dst_addr.addr = ip->ip_dst; if (is_a_target(ai_dst, &dst_addr) || verbose) { switch (icp->icmp_code) { case ICMP_PARAMPROB_OPTABSENT: Printf("ICMP Missing a Required Option " "parameter problem from %s\n", pr_name4(from)); Printf(" option type = %d", icp->icmp_pptr); break; case ICMP_PARAMPROB_BADLENGTH: Printf("ICMP Bad Length parameter problem " "from %s\n", pr_name4(from)); Printf(" in byte %d", icp->icmp_pptr); if (icp->icmp_pptr <= hlen1) { Printf(" (value 0x%x)", *((char *)ip + icp->icmp_pptr)); } break; case 0: default: Printf("ICMP Parameter Problem from %s\n", pr_name4(from)); Printf(" in byte %d", icp->icmp_pptr); if (icp->icmp_pptr <= hlen1) { Printf(" (value 0x%x)", *((char *)ip + icp->icmp_pptr)); } break; } Printf(" for %s from %s", pr_protocol(ip->ip_p), pr_name((char *)&ip->ip_src, AF_INET)); Printf(" to %s", pr_name((char *)&ip->ip_dst, AF_INET)); /* * if it's a UDP or TCP packet, we need at least first * 4 bytes of it to see the src/dst ports */ if ((ip->ip_p == IPPROTO_TCP || ip->ip_p == IPPROTO_UDP) && (cc_left >= hlen1 + 4)) { /* LINTED */ up = (struct udphdr *)((uchar_t *)ip + hlen1); Printf(" port %d", ntohs(up->uh_dport)); } (void) putchar('\n'); } break; case ICMP_TIMXCEED: if (cc_left < sizeof (struct ip)) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, pr_name4(from)); } return; } ip = &icp->icmp_ip; hlen1 = ip->ip_hl << 2; dst_addr.addr = ip->ip_dst; if (is_a_target(ai_dst, &dst_addr) || verbose) { if (icp->icmp_code >= A_CNT(timexceed)) { Printf("ICMP %d time exceeded from %s\n", icp->icmp_code, pr_name4(from)); } else { Printf("ICMP %s from %s\n", timexceed[icp->icmp_code], pr_name4(from)); } Printf(" for %s from %s", pr_protocol(ip->ip_p), pr_name((char *)&ip->ip_src, AF_INET)); Printf(" to %s", pr_name((char *)&ip->ip_dst, AF_INET)); if ((ip->ip_p == IPPROTO_TCP || ip->ip_p == IPPROTO_UDP) && (cc_left >= hlen1 + 4)) { /* LINTED */ up = (struct udphdr *)((uchar_t *)ip + hlen1); Printf(" port %d", ntohs(up->uh_dport)); } (void) putchar('\n'); } break; case ICMP_TSTAMPREPLY: /* the packet should have enough space to store timestamps */ if (cc_left < sizeof (struct id_ts)) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, pr_name4(from)); } return; } if (ntohs(icp->icmp_id) == ident) { if (use_icmp_ts) valid_reply = _B_TRUE; else valid_reply = _B_FALSE; } else { return; } if (valid_reply) { /* * For this valid reply, if we are still sending to * this target IP address, we'd like to do some * updates to targetaddr, so hold SIGALRMs. */ (void) sighold(SIGALRM); is_alive = _B_TRUE; nreceived++; reply_matched_current_target = seq_match(current_targetaddr->starting_seq_num, current_targetaddr->num_sent, ntohs(icp->icmp_seq)); if (reply_matched_current_target) { current_targetaddr->got_reply = _B_TRUE; nreceived_last_target++; /* * Determine if stats, probe-all, and * npackets != 0, and this is the reply for * the last probe we sent to current target * address. */ if (stats && probe_all && npackets > 0 && ((current_targetaddr->starting_seq_num + current_targetaddr->num_probes - 1) % (MAX_ICMP_SEQ + 1) == ntohs(icp->icmp_seq)) && (current_targetaddr->num_probes == current_targetaddr->num_sent)) last_reply_from_targetaddr = _B_TRUE; } else { /* * If it's just probe_all and we just received * a reply from a target address we were * probing and had timed out (now we are probing * some other target address), we ignore * this reply. */ if (probe_all && !stats) { valid_reply = _B_FALSE; /* * Only if it's verbose, we get a * message regarding this reply, * otherwise we are done here. */ if (!verbose) { (void) sigrelse(SIGALRM); return; } } } } if (!stats && valid_reply) { /* * if we are still sending to the same target address, * then stop it, because we know it's alive. */ if (reply_matched_current_target) { (void) alarm(0); /* cancel alarm */ (void) sigset(SIGALRM, SIG_IGN); current_targetaddr->probing_done = _B_TRUE; } (void) sigrelse(SIGALRM); if (!probe_all) { Printf("%s is alive\n", targethost); } else { /* * If we are using send_reply, the real * target address is not the src address of the * replies. Use icmp_seq to find out where this * probe was sent to. */ if (send_reply) { (void) find_dstaddr( ntohs(icp->icmp_seq), &dst_addr); (void) inet_ntop(AF_INET, (void *)&dst_addr.addr, tmp_buf, sizeof (tmp_buf)); } else { (void) inet_ntop(AF_INET, (void *)&from->sin_addr, tmp_buf, sizeof (tmp_buf)); } if (nflag) { Printf("%s is alive\n", tmp_buf); } else { Printf("%s (%s) is alive\n", targethost, tmp_buf); } } if (reply_matched_current_target) { /* * Let's get things going again, but now * ping will start sending to next target IP * address. */ send_scheduled_probe(); (void) sigset(SIGALRM, sigalrm_handler); schedule_sigalrm(); } return; } else { /* * If we are not moving to next targetaddr, let's * release the SIGALRM now. We don't want to stall in * the middle of probing a targetaddr if the pr_name() * call (see below) takes longer. */ if (!last_reply_from_targetaddr) (void) sigrelse(SIGALRM); /* else, we'll release it later */ } /* * If we are using send_reply, the real target address is * not the src address of the replies. Use icmp_seq to find out * where this probe was sent to. */ if (send_reply) { (void) find_dstaddr(ntohs(icp->icmp_seq), &dst_addr); Printf("%d bytes from %s: ", cc, pr_name((char *)&dst_addr.addr, AF_INET)); } else { Printf("%d bytes from %s: ", cc, pr_name4(from)); } Printf("icmp_seq=%d. ", ntohs(icp->icmp_seq)); Printf("orig = %lu, recv = %lu, xmit = %lu ", (ulong_t)ntohl(icp->icmp_otime), (ulong_t)ntohl(icp->icmp_rtime), (ulong_t)ntohl(icp->icmp_ttime)); if (valid_reply) { /* * icp->icmp_otime is the time passed since midnight. * Therefore we need to adjust tv value, which is * the time passed since Jan 1, 1970. */ triptime = (tv.tv_sec % (24LL * 60 * 60)) * MILLISEC + (tv.tv_usec / (MICROSEC/MILLISEC)); triptime -= ntohl(icp->icmp_otime); if (triptime < 0) triptime += 24LL * 60 * 60 * MILLISEC; Printf("time=%d. ms", (int)triptime); triptime *= (MICROSEC/MILLISEC); tsum += triptime; tsum2 += triptime*triptime; if (triptime < tmin) tmin = triptime; if (triptime > tmax) tmax = triptime; } (void) putchar('\n'); /* * If it's stats, probe-all, npackets > 0, and we received reply * for the last probe sent to this target address, then we * don't need to wait anymore, let's move on to next target * address, now! */ if (last_reply_from_targetaddr) { (void) alarm(0); /* cancel alarm */ current_targetaddr->probing_done = _B_TRUE; (void) sigrelse(SIGALRM); send_scheduled_probe(); schedule_sigalrm(); } break; case ICMP_ROUTERADVERT: case ICMP_ROUTERSOLICIT: /* Router discovery messages */ return; case ICMP_ECHO: case ICMP_TSTAMP: case ICMP_IREQ: case ICMP_MASKREQ: /* These were never passed out from the SunOS 4.X kernel. */ return; case ICMP_IREQREPLY: case ICMP_MASKREPLY: /* Replies for information and address mask requests */ return; default: if (verbose) { Printf("%d bytes from %s:\n", cc, pr_name4(from)); Printf("icmp_type=%d (%s) ", icp->icmp_type, pr_type(icp->icmp_type)); Printf("icmp_code=%d\n", icp->icmp_code); for (i = 0; i < 12; i++) { Printf("x%2.2x: x%8.8x\n", i * sizeof (int32_t), *intp++); } } break; } buf += sizeof (struct ip); hlen -= sizeof (struct ip); /* if verbose and there exists IP options */ if (verbose && hlen > 0) pr_options((uchar_t *)buf, hlen); } /* * Print out the ip options. */ static void pr_options(uchar_t *opt, int optlength) { int curlength; Printf(" IP options: "); while (optlength > 0) { curlength = opt[1]; switch (*opt) { case IPOPT_EOL: optlength = 0; break; case IPOPT_NOP: opt++; optlength--; continue; case IPOPT_RR: Printf(" "); pr_rropt(opt, curlength, _B_TRUE); break; case IPOPT_TS: Printf("