/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)ping.c	1.32	99/09/24 SMI"	/* SVr4.0 1.4	*/

/*
 * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * 		PROPRIETARY NOTICE (Combined)
 *
 * This source code is unpublished proprietary information
 * constituting, or derived under license from AT&T's UNIX(r) System V.
 * In addition, portions of such source code were derived from Berkeley
 * 4.3 BSD under license from the Regents of the University of
 * California.
 *
 *
 *
 * 		Copyright Notice
 *
 * Notice of copyright on this source code product does not indicate
 * publication.
 *
 * 	(c) 1986,1987,1988,1989,1998  Sun Microsystems, Inc
 * 	(c) 1983,1984,1985,1986,1987,1988,1989  AT&T.
 *		All rights reserved.
 *
 */

#include <stdio.h>
#include <strings.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

#include <sys/time.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/stropts.h>
#include <sys/file.h>
#include <sys/sysmacros.h>

#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_var.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet/udp.h>
#include <netdb.h>
#include <stdlib.h>

#include <ifaddrlist.h>
#include "ping.h"

/*
 * This macro is used to compare 16bit, wrapping sequence numbers. Inspired by
 * TCP's SEQ_LEQ macro.
 */
#define	PINGSEQ_LEQ(a, b)	((int16_t)((a)-(b)) <= 0)

#define	MAX_WAIT		10	/* max sec. to wait for response */
#define	MAX_TRAFFIC_CLASS	255	/* max traffic class for IPv6 */
#define	MAX_FLOW_LABEL		0xFFFFF	/* max flow label for IPv6 */
#define	MAX_TOS			255	/* max type-of-service for IPv4 */

#define	TIMEOUT			20	/* default timeout value */
#define	DEFAULT_DATALEN		56

#define	MULTICAST_NOLOOP	1	/* multicast options */
#define	MULTICAST_TTL		2
#define	MULTICAST_IF		4

#define	IF_INDEX		0	/* types of -i argument */
#define	IF_NAME			1
#define	IF_ADDR			2
#define	IF_ADDR6		3

#ifdef BSD
#define	setbuf(s, b)	setlinebuf((s))
#endif /* BSD */


/* interface identification */
union if_id {
	int index;		/* interface index (e.g., 1, 2) */
	char *name;		/* interface name (e.g., le0, hme0) */
	union any_in_addr addr;	/* interface address (e.g., 10.123.4.5) */
};

/* stores the interface supplied by the user */
struct if_entry {
	char *str;		/* unresolved, string input */
	int id_type;		/* type of ID (index, name, addr, addr6) */
	union if_id id;		/* ID */
};

char *progname;
char *targethost;

static int send_sock;			/* send sockets */
static int send_sock6;
static struct sockaddr_in to;		/* where to send */
static struct sockaddr_in6 to6;
static union any_in_addr gw_IP_list[MAX_GWS];	/* gateways */
static union any_in_addr gw_IP_list6[MAX_GWS6];
static int if_index = 0;		/* outgoing interface index */
boolean_t is_alive = _B_FALSE;		/* is target host alive */
struct targetaddr *current_targetaddr;	/* current target IP address to probe */
static struct targetaddr *targetaddr_list; /* list of IP addresses to probe */
static int num_targetaddrs;		/* no of target addresses to probe */
boolean_t verbose = _B_FALSE;		/* verbose output */
boolean_t stats = _B_FALSE;		/* display statistics */
static boolean_t settos = _B_FALSE;	/* set type-of-service value */
boolean_t rr_option = _B_FALSE;		/* true if using record route */
boolean_t send_reply = _B_FALSE;	/* Send an ICMP_{ECHO|TSTAMP}REPLY */
					/* that goes to target and comes back */
					/* to the the sender via src routing. */
boolean_t strict = _B_FALSE;		/* true if using strict source route */
boolean_t ts_option = _B_FALSE;		/* true if using timestamp option */
boolean_t use_icmp_ts = _B_FALSE;	/* Use ICMP timestamp request */
boolean_t use_udp = _B_FALSE;		/* Use UDP instead of ICMP */
boolean_t probe_all = _B_FALSE;		/* probe all the IP addresses */
boolean_t nflag = _B_FALSE;		/* do not reverse lookup addresses */
static int family_input = AF_UNSPEC;	/* address family supplied by user */
int datalen = DEFAULT_DATALEN;		/* How much data */
int ts_flag;				/* timestamp flag value */
static int num_gw;			/* number of gateways */
static int eff_num_gw;			/* effective number of gateways */
					/* if send_reply, it's 2*num_gw+1 */
static int num_wraps = -1;		/* no of times 64K icmp_seq wrapped */
static ushort_t dest_port = 32768 + 666; /* starting port for the UDP probes */
static char *gw_list[MAXMAX_GWS];	/* list of gateways as user enters */
static int interval = 1;		/* interval between transmissions */
static int options;			/* socket options */
static int moptions;			/* multicast options */
int npackets;				/* number of packets to send */
static ushort_t tos;			/* type-of-service value */
static int hoplimit = -1;		/* time-to-live value */
static int timeout = TIMEOUT;		/* timeout value (sec) for probes */
static struct if_entry out_if;		/* interface argument */
int ident;				/* ID for this ping run */
static struct timeval t_last_probe_sent; /* the time we sent the last probe */

/*
 * This buffer stores the received packets. Currently it needs to be 32 bit
 * aligned. In the future, we'll be using 64 bit alignment, so let's use 64 bit
 * alignment now.
 */
static uint64_t in_pkt[(IP_MAXPACKET + 1)/8];

/* Used to store the ancillary data that comes with the received packets */
static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8];

static int ntransmitted;	/* number of packet sent to single IP address */
int nreceived;			/* # of packets we got back from target host */
int nreceived_last_target;	/* received from last target IP */
/*
 * These are used for statistics. tmin is initialized to maximum integer value.
 */
int tmin = ((int)(((uint_t)~0) >> 1));
int tmax;
int tsum;			/* sum of all times, for doing average */

static struct targetaddr *build_targetaddr_list(struct hostinfo *,
    union any_in_addr *, struct hostinfo *, union any_in_addr *);
extern void check_reply(struct hostinfo *, struct msghdr *, int, ushort_t);
extern void check_reply6(struct hostinfo *, struct msghdr *, int, ushort_t);
static struct targetaddr *create_targetaddr_item(int, union any_in_addr,
    union any_in_addr);
void find_dstaddr(ushort_t, union any_in_addr *);
static struct ifaddrlist *find_if(struct ifaddrlist *, int);
static void finish();
static void free_hostinfo(struct hostinfo *);
static int get_gwaddrs(char *[], union any_in_addr *, int, int *);
static struct hostinfo *get_hostinfo(char *, int, int *);
static ushort_t in_cksum(ushort_t *, int);
static int int_arg(char *s, char *what);
boolean_t is_a_target(struct hostinfo *, union any_in_addr *);
static void mirror_gws(union any_in_addr *, int);
static void pinger(int, struct sockaddr *, struct msghdr *, int);
char *pr_name(char *, int);
char *pr_protocol(int);
static void print_unknown_host_msg(char *, char *, boolean_t);
static void recv_icmp_packet(struct hostinfo *, struct hostinfo *, int, int,
    ushort_t, ushort_t);
static void resolve_nodes(struct hostinfo **, struct hostinfo **,
    union any_in_addr **, union any_in_addr **, union any_in_addr *,
    union any_in_addr *, int);
void schedule_sigalrm();
static void select_all_src_addrs(union any_in_addr **, struct hostinfo *,
    union any_in_addr *);
static void select_src_addr(union any_in_addr *, int, union any_in_addr *);
void send_scheduled_probe();
boolean_t seq_match(ushort_t, int, ushort_t);
extern void set_ancillary_data(struct msghdr *, int, union any_in_addr *, int,
    uint_t);
extern void set_IPv4_options(int, union any_in_addr *, int, struct in_addr *,
    struct in_addr *);
static boolean_t setup_socket(int, int *, int *, int *, ushort_t *);
void sigalrm_handler();
void tvsub(struct timeval *, struct timeval *);
static void usage(char *);

/*
 * main()
 */
void
main(int argc, char *argv[])
{
	struct hostinfo *hi_dst = NULL;		/* target host information */
	struct hostinfo *hi_dst6 = NULL;
	union any_in_addr *src_addr_list = NULL;	/* src addrs to use */
	union any_in_addr *src_addr_list6 = NULL;
	int recv_sock = 0;				/* receive sockets */
	int recv_sock6 = 0;
	ushort_t udp_src_port;			/* src ports for UDP probes */
	ushort_t udp_src_port6;			/* used to identify replies */
	uint_t flowinfo = 0;
	uint_t class = 0;
	char tmp_buf[INET6_ADDRSTRLEN];
	int c;
	int i;

	/* we'll need the root privilege only when we open the sockets */
	(void) seteuid(getuid());

	progname = argv[0];

	setbuf(stdout, (char *)0);

	while ((c = getopt(argc, argv,
	    "aA:c:dF:G:g:I:i:LlnP:p:rRSsTt:UvX:x:Y0123?")) != -1) {
		switch ((char)c) {
		case 'A':
			if (strcmp(optarg, "inet") == 0) {
				family_input = AF_INET;
			} else if (strcmp(optarg, "inet6") == 0) {
				family_input = AF_INET6;
			} else {
				Fprintf(stderr,
				    "%s: unknown address family %s\n",
				    progname, optarg);
				exit(EXIT_FAILURE);
			}
			break;

		case 'a':
			probe_all = _B_TRUE;
			break;

		case 'c':
			i = int_arg(optarg, "traffic class");
			if (i > MAX_TRAFFIC_CLASS) {
				Fprintf(stderr, "%s: traffic class %d out of "
				    "range\n", progname, i);
				exit(EXIT_FAILURE);
			}
			class = (uint_t)i;
			break;

		case 'd':
			options |= SO_DEBUG;
			break;

		case 'F':
			i = int_arg(optarg, "flow label");
			if (i > MAX_FLOW_LABEL) {
				Fprintf(stderr, "%s: flow label %d out of "
				    "range\n", progname, i);
				exit(EXIT_FAILURE);
			}
			flowinfo = (uint_t)i;
			break;

		case 'I':
			stats = _B_TRUE;
			interval = int_arg(optarg, "interval");
			break;

		case 'i':
			/*
			 * this can accept interface index, interface name, and
			 * address configured on the interface
			 */
			moptions |= MULTICAST_IF;
			out_if.str = optarg;

			if (inet_pton(AF_INET6, optarg, &out_if.id.addr) > 0) {
				out_if.id_type = IF_ADDR6;
			} else if (inet_pton(AF_INET, optarg,
			    &out_if.id.addr) > 0) {
				out_if.id_type = IF_ADDR;
			} else if (strcmp(optarg, "0") == 0) {
				out_if.id_type = IF_INDEX;
				out_if.id.index = 0;
			} else if ((out_if.id.index = atoi(optarg)) != 0) {
				out_if.id_type = IF_INDEX;
			} else {
				out_if.id.name = optarg;
				out_if.id_type = IF_NAME;
			}
			break;

		case 'L':
			moptions |= MULTICAST_NOLOOP;
			break;

		case 'l':
			send_reply = _B_TRUE;
			strict = _B_FALSE;
			break;

		case 'n':
			nflag = _B_TRUE;
			break;

		case 'P':
			settos = _B_TRUE;
			i = int_arg(optarg, "type-of-service");
			if (i > MAX_TOS) {
				Fprintf(stderr, "%s: tos value %d out of "
				    "range\n", progname, i);
				exit(EXIT_FAILURE);
			}
			tos = (ushort_t)i;
			break;

		case 'p':
			i = int_arg(optarg, "port number");
			if (i > MAX_PORT) {
				Fprintf(stderr, "%s: port number %d out of "
				    "range\n", progname, i);
				exit(EXIT_FAILURE);
			}
			dest_port = (ushort_t)i;
			break;

		case 'r':
			options |= SO_DONTROUTE;
			break;

		case 'R':
			rr_option = _B_TRUE;
			break;

		case 'S':
			send_reply = _B_TRUE;
			strict = _B_TRUE;
			break;

		case 's':
			stats = _B_TRUE;
			break;

		case 'T':
			ts_option = _B_TRUE;
			break;

		case 't':
			moptions |= MULTICAST_TTL;
			hoplimit = int_arg(optarg, "ttl");
			if (hoplimit > MAXTTL) {
				Fprintf(stderr, "%s: ttl %d out of range\n",
				    progname, hoplimit);
				exit(EXIT_FAILURE);
			}
			break;

		case 'U':
			use_udp = _B_TRUE;
			use_icmp_ts = _B_FALSE;
			break;

		case 'v':
			verbose = _B_TRUE;
			break;
		/*
		 * 'x' and 'X' has been undocumented flags for source routing.
		 * Now we document loose source routing with the new flag 'g',
		 * which is same as in traceroute. We still keep x/X as
		 * as undocumented. 'G', which is for strict source routing is
		 * also undocumented.
		 */
		case 'x':
		case 'g':
			strict = _B_FALSE;
			if (num_gw > MAXMAX_GWS) {
				Fprintf(stderr, "%s: too many gateways\n",
				    progname);
				exit(EXIT_FAILURE);
			}
			gw_list[num_gw++] = optarg;
			break;

		case 'X':
		case 'G':
			strict = _B_TRUE;
			if (num_gw > MAXMAX_GWS) {
				Fprintf(stderr, "%s: too many gateways\n",
				    progname);
				exit(EXIT_FAILURE);
			}
			gw_list[num_gw++] = optarg;
			break;

		case 'Y':
			use_icmp_ts = _B_TRUE;
			use_udp = _B_FALSE;
			break;

		case '0':
		case '1':
		case '2':
		case '3':
			ts_flag = (char)c - '0';
			break;

		case '?':
			usage(progname);
			exit(EXIT_FAILURE);
			break;

		default:
			usage(progname);
			exit(EXIT_FAILURE);
			break;
		}
	}

	if (optind >= argc) {
		usage(progname);
		exit(EXIT_FAILURE);
	}

	/*
	 * send_reply, which sends the probe packet back to itself
	 * doesn't work with UDP
	 */
	if (use_udp)
		send_reply = _B_FALSE;

	targethost = argv[optind];
	optind++;
	if (optind < argc) {
		if (stats) {
			datalen = int_arg(argv[optind], "data size");
			optind++;
			if (optind < argc) {
				npackets = int_arg(argv[optind],
				    "packet count");
				if (npackets < 1) {
					Fprintf(stderr, "%s: packet count %d "
					    "out of range\n", progname,
					    npackets);
					exit(EXIT_FAILURE);
				}
			}
		} else {
			timeout = int_arg(argv[optind], "timeout");
		}
	}

	/*
	 * Let's prepare sockaddr_in* structures, cause we might need both of
	 * them.
	 */
	bzero((char *)&to, sizeof (struct sockaddr_in));
	to.sin_family = AF_INET;

	bzero((char *)&to6, sizeof (struct sockaddr_in6));
	to6.sin6_family = AF_INET6;
	to6.sin6_flowinfo = htonl((class << 20) | flowinfo);

	if (stats)
		(void) sigset(SIGINT, finish);

	ident = (int)getpid() & 0xFFFF;

	/* resolve the hostnames */
	resolve_nodes(&hi_dst, &hi_dst6, &src_addr_list, &src_addr_list6,
	    gw_IP_list, gw_IP_list6, family_input);

	/*
	 * 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 (hi_dst6 != NULL) {
		size_t exthdr_len = 0;

		if (send_reply) {
			/* struct ip6_rthdr0 includes one struct in6_addr */
			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 - 1) * 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);
			hi_dst6 = NULL;
		}
	}

	if (hi_dst != NULL) {
		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);
			hi_dst = NULL;
		}
	}

	/* setup the sockets */
	if (hi_dst6 != NULL) {
		if (!setup_socket(AF_INET6, &send_sock6, &recv_sock6,
		    &if_index, &udp_src_port6)) {
			free_hostinfo(hi_dst6);
			hi_dst6 = NULL;
		}
	}

	if (hi_dst != NULL) {
		if (!setup_socket(AF_INET, &send_sock, &recv_sock, &if_index,
		    &udp_src_port)) {
			free_hostinfo(hi_dst);
			hi_dst = NULL;
		}
	}

	if (hi_dst == NULL && hi_dst6 == NULL) {
		exit(EXIT_FAILURE);
	}

	/*
	 * 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 (hi_dst6 != NULL)
			mirror_gws(gw_IP_list6, AF_INET6);
		if (hi_dst != NULL)
			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(hi_dst6, src_addr_list6,
	    hi_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 (family_input == AF_INET ||
			    (family_input == AF_UNSPEC && hi_dst != NULL)) {
				Printf("PING %s (%s): %d data bytes\n",
				    targethost,
				    inet_ntop(AF_INET, (void *)hi_dst->addrs,
					tmp_buf, sizeof (tmp_buf)),
				    datalen);
			} else {
				Printf("PING %s (%s): %d data bytes\n",
				    targethost,
				    inet_ntop(AF_INET6, (void *)hi_dst6->addrs,
					tmp_buf, sizeof (tmp_buf)),
				    datalen);
			}
		}
	}

	/* Let's get things going */
	send_scheduled_probe();

	/* SIGALRM is used to send the next scheduled probe */
	(void) sigset(SIGALRM, sigalrm_handler);
	schedule_sigalrm();

	/*
	 * From now on, we'll always be listening to ICMP packets. As SIGALRM
	 * comes in, sigalrm_handler() will be invoked and send another
	 * probe.
	 */
	recv_icmp_packet(hi_dst6, hi_dst, recv_sock6, recv_sock, udp_src_port6,
	    udp_src_port);

	exit(EXIT_SUCCESS);	/* should never come here */
}

/*
 * Build the target IP address list. Use command line options and
 * name lookup results returned from name server to determine which addresses
 * to probe, how many times, in which order.
 */
static struct targetaddr *
build_targetaddr_list(struct hostinfo *hi_dst6,
    union any_in_addr *src_addr_list6, struct hostinfo *hi_dst,
    union any_in_addr *src_addr_list)
{
	struct targetaddr *head = NULL;
	struct targetaddr *targetaddr;
	struct targetaddr **nextp;
	int num_dst;
	int i;

	nextp = &head;
	if (hi_dst6 != NULL) {
		if (probe_all)
			num_dst = hi_dst6->num_addr;
		else
			num_dst = 1;
		num_targetaddrs = num_dst;
		for (i = 0; i < num_dst; i++) {
			targetaddr = create_targetaddr_item(AF_INET6,
			    hi_dst6->addrs[i], src_addr_list6[i]);
			targetaddr->next = NULL;
			*nextp = targetaddr;
			nextp = &targetaddr->next;
		}
	}
	if (hi_dst != NULL) {
		if (probe_all)
			num_dst = hi_dst->num_addr;
		else
			num_dst = 1;
		num_targetaddrs += num_dst;
		for (i = 0; i < num_dst; i++) {
			targetaddr = create_targetaddr_item(AF_INET,
			    hi_dst->addrs[i], src_addr_list[i]);
			targetaddr->next = NULL;
			*nextp = targetaddr;
			nextp = &targetaddr->next;
		}
	}
	if (npackets == 0 && stats)
		*nextp = head;	/* keep going indefinitely */

	return (head);
}

/*
 * Given an address family, dst and src addresses, by also looking at the
 * options provided at the command line, this function creates a targetaddr
 * to be linked with others, forming a global targetaddr list. Each targetaddr
 * item contains information about probes sent to a specific IP address.
 */
static struct targetaddr *
create_targetaddr_item(int family, union any_in_addr dst_addr,
    union any_in_addr src_addr)
{
	struct targetaddr *targetaddr;

	targetaddr = (struct targetaddr *)malloc(sizeof (struct targetaddr));
	if (targetaddr == NULL) {
		Fprintf(stderr, "%s: malloc %s\n", progname, strerror(errno));
		exit(EXIT_FAILURE);
	}
	targetaddr->family = family;
	targetaddr->dst_addr = dst_addr;
	targetaddr->src_addr = src_addr;
	if (stats) {
		/*
		 * npackets is only defined if we are in stats mode.
		 * npackets determines how many probes to send to each target
		 * IP address. npackets == 0 means send only 1 and move on to
		 * next target IP.
		 */
		if (npackets > 0)
			targetaddr->num_probes = npackets;
		else
			targetaddr->num_probes = 1;
	} else {
		targetaddr->num_probes = timeout;
	}
	targetaddr->num_sent = 0;
	targetaddr->got_reply = _B_FALSE;
	targetaddr->probing_done = _B_FALSE;
	targetaddr->starting_seq_num = 0; /* actual value will be set later */
	targetaddr->next = NULL;	/* actual value will be set later */

	return (targetaddr);
}

/*
 * print "unknown host" message
 */
static void
print_unknown_host_msg(char *protocol, char *hostname, boolean_t try_again)
{
	Fprintf(stderr, "%s: unknown%s host %s", progname, protocol, hostname);
	if (try_again)
		Fprintf(stderr, " (try again later)\n");
	else
	Fprintf(stderr, "\n");
}

/*
 * Resolve hostnames for the target host and gateways. Also, determine source
 * addresses to use for each target address.
 */
static void
resolve_nodes(struct hostinfo **hi_dstp, struct hostinfo **hi_dstp6,
    union any_in_addr **src_addr_listp, union any_in_addr **src_addr_listp6,
    union any_in_addr *gw_IP_list, union any_in_addr *gw_IP_list6, int family)
{
	struct hostinfo *hi_dst = NULL;
	struct hostinfo *hi_dst6 = NULL;
	union any_in_addr *src_addr_list = NULL;
	union any_in_addr *src_addr_list6 = NULL;
	int num_resolved_gw = 0;
	int num_resolved_gw6 = 0;
	int error_num;
	int error_num6;

	switch (family) {
	case AF_UNSPEC:
		/* try to resolve IPv6 first */
		hi_dst6 = get_hostinfo(targethost, AF_INET6, &error_num6);

		if (hi_dst6 == NULL || probe_all) {
			/* now try IPv4 */
			hi_dst = get_hostinfo(targethost, AF_INET, &error_num);
			if (hi_dst == NULL && hi_dst6 == NULL) {
				print_unknown_host_msg("", targethost,
				    error_num == TRY_AGAIN ||
				    error_num6 == TRY_AGAIN);
				exit(EXIT_FAILURE);
			}
		}
		break;

	case AF_INET6:
		hi_dst6 = get_hostinfo(targethost, AF_INET6, &error_num6);
		if (hi_dst6 == NULL) {
			print_unknown_host_msg(" IPv6", targethost,
			    error_num6 == TRY_AGAIN);
			exit(EXIT_FAILURE);
		}
		break;

	case AF_INET:
		hi_dst = get_hostinfo(targethost, AF_INET, &error_num);
		if (hi_dst == NULL) {
			print_unknown_host_msg(" IPv4", targethost,
			    error_num == TRY_AGAIN);
			exit(EXIT_FAILURE);
		}
		break;

	default:
		Fprintf(stderr, "%s: unknown address family %d\n",
		    progname, family);
		exit(EXIT_FAILURE);
	}

	if ((hi_dst6 != NULL) && (num_gw > 0)) {
		num_resolved_gw6 = get_gwaddrs(gw_list, gw_IP_list6,
		    AF_INET6, &error_num6);
		if (num_resolved_gw6 == -1) {
			free_hostinfo(hi_dst6);
			hi_dst6 = NULL;
		}
	}

	if (hi_dst6 != NULL)
		select_all_src_addrs(&src_addr_list6, hi_dst6, gw_IP_list6);

	if ((hi_dst != NULL) && (num_gw > 0)) {
		num_resolved_gw = get_gwaddrs(gw_list, gw_IP_list,
		    AF_INET, &error_num);
		if (num_resolved_gw == -1) {
			free_hostinfo(hi_dst);
			hi_dst = NULL;
		}
	}

	if (hi_dst != NULL)
		select_all_src_addrs(&src_addr_list, hi_dst, gw_IP_list);

	/* check if one of the gateways resolves neither IPv6 nor IPv4 */
	if (num_gw > 0 && num_resolved_gw == num_resolved_gw6 &&
	    num_resolved_gw != num_gw && hi_dst != NULL && hi_dst6 != NULL) {
		print_unknown_host_msg("", gw_list[num_resolved_gw],
		    error_num6 == TRY_AGAIN || error_num == TRY_AGAIN);
		exit(EXIT_FAILURE);
	}

	/* check if a gateway failed to resolve to IPv6 */
	if (num_resolved_gw6 != num_gw && hi_dst6 != NULL) {
		print_unknown_host_msg(" IPv6", gw_list[num_resolved_gw6],
		    error_num6 == TRY_AGAIN);
		free_hostinfo(hi_dst6);
		hi_dst6 = NULL;
	}

	/* check if a gateway failed to resolve to IPv4 */
	if (num_resolved_gw != num_gw && hi_dst != NULL) {
		print_unknown_host_msg(" IPv4", gw_list[num_resolved_gw],
		    error_num == TRY_AGAIN);
		free_hostinfo(hi_dst);
		hi_dst = NULL;
	}

	*hi_dstp = hi_dst;
	*hi_dstp6 = hi_dst6;
	*src_addr_listp = src_addr_list;
	*src_addr_listp6 = src_addr_list6;
}

/*
 * Resolve the gateway names.
 * Return number of gateway addresses resolved.
 */
static int
get_gwaddrs(char **gw_list, union any_in_addr *gwIPlist, int family,
    int *error_num)
{
	struct hostinfo *hi;
	int i;

	if ((family == AF_INET && num_gw >= MAX_GWS) ||
	    (family == AF_INET6 && num_gw > MAX_GWS6)) {
		Fprintf(stderr, "%s: too many %s gateways\n",
		    progname, (family == AF_INET) ? "IPv4" : "IPv6");
		return (-1);
	}

	for (i = 0; i < num_gw; i++) {
		hi = get_hostinfo(gw_list[i], family, error_num);
		if (hi == NULL) {
			return (i);
		} else {
			gwIPlist[i].addr6 = hi->addrs[0].addr6;
		}
		free_hostinfo(hi);
	}

	/*
	 * If we are here, that means we could resolve all the gateways. Value
	 * of i is equal to the number of gateways.
	 */
	return (i);
}

/*
 * 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 a hostinfo structure
 */
static struct hostinfo *
get_hostinfo(char *host, int family, int *error_num)
{
	struct hostent *hp;
	struct hostinfo *hi;
	char **p;
	union any_in_addr *ap;
	struct in6_addr addr6;
	struct in_addr addr;
	boolean_t broadcast;		/* is this 255.255.255.255? */
	char tmp_buf[INET6_ADDRSTRLEN];
	int n;

	hi = (struct hostinfo *)calloc(1, sizeof (struct hostinfo));
	if (hi == NULL) {
		Fprintf(stderr, "%s: calloc %s\n", progname, strerror(errno));
		exit(EXIT_FAILURE);
	}

	hi->family = family;

	/* 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 */
			(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) {
			free(hi);
			return (NULL);
		}

		hi->num_addr = 1;

		hi->addrs = (union any_in_addr *)malloc(sizeof (hi->addrs[0]));
		if (hi->addrs == NULL) {
			Fprintf(stderr, "%s: malloc %s\n", progname,
			    strerror(errno));
			exit(EXIT_FAILURE);
		}

		if (broadcast) {
			hi->name = (char *)strdup(host);
			hi->addrs[0].addr.s_addr = 0xFFFFFFFF;
		} else {
			hi->name = (char *)strdup(tmp_buf);
			hi->addrs[0].addr = addr;
		}

		if (hi->name == NULL) {
			Fprintf(stderr, "%s: strdup %s\n", progname,
			    strerror(errno));
			exit(EXIT_FAILURE);
		}
		return (hi);
	}

	hp = getipnodebyname(host, family, AI_ADDRCONFIG, error_num);
	if (hp == NULL) {
		free(hi);
		return (NULL);
	}

	if (!((hp->h_addrtype == AF_INET &&
	    hp->h_length == sizeof (struct in_addr)) ||
	    (hp->h_addrtype == AF_INET6 &&
	    hp->h_length == sizeof (struct in6_addr)))) {
		Fprintf(stderr, "%s: bad host %s\n", progname, host);
		exit(EXIT_FAILURE);
	}

	/* initialize host */
	hi->name = (char *)strdup(hp->h_name);
	if (hi->name == NULL) {
		Fprintf(stderr, "%s: strdup %s\n", progname, strerror(errno));
		exit(EXIT_FAILURE);
	}

	/* count addresses returned */
	n = 0;
	for (p = hp->h_addr_list; *p != NULL; ++p)
		n++;
	hi->num_addr = n;

	hi->addrs = (union any_in_addr *)calloc(n, sizeof (hi->addrs[0]));
	if (hi->addrs == NULL) {
		Fprintf(stderr, "%s: calloc %s\n", progname, strerror(errno));
		exit(EXIT_FAILURE);
	}

	/* fill in the IP addresses */
	for (ap = hi->addrs, p = hp->h_addr_list; *p != NULL; ++ap, ++p) {
		(void) bcopy(*p, (char *)ap,
		    ((family == AF_INET) ? sizeof (struct in_addr) :
			sizeof (struct in6_addr)));
	}

	freehostent(hp);

	return (hi);
}

/*
 * Free a hostinfo structure
 */
static void
free_hostinfo(struct hostinfo *hi)
{
	if (hi != NULL) {
		free(hi->name);
		free(hi->addrs);
		free(hi);
	}
}

/*
 * 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 hostinfo *hi,
    union any_in_addr *gw1)
{
	union any_in_addr *list;
	int num_dst;
	int i;

	if (probe_all)
		num_dst = hi->num_addr;
	else
		num_dst = 1;

	list = (union any_in_addr *)
	    calloc((size_t)num_dst, sizeof (union any_in_addr));
	if (list == NULL) {
		Fprintf(stderr, "%s: calloc: %s\n", progname, strerror(errno));
		exit(EXIT_FAILURE);
	}

	/*
	 * If there's a gateway, a routing header as a consequence, our kernel
	 * picks the source address based on the first hop address, rather than
	 * final destination address.
	 */
	if (num_gw > 0) {
		select_src_addr(gw1, hi->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; i < num_dst; i++)
			select_src_addr(&hi->addrs[i], hi->family, &list[i]);
	}

	*src_addr_list = list;
}

/*
 * For a given destination address, determine a source address to use.
 * Returns wildcard address if it cannot determine the source address.
 */
static void
select_src_addr(union any_in_addr *dst_addr, int family,
    union any_in_addr *src_addr)
{
	struct sockaddr *sock;
	struct sockaddr_in *sin;
	struct sockaddr_in6 *sin6;
	int tmp_fd;
	size_t sock_len;

	sock = (struct sockaddr *)malloc(sizeof (struct sockaddr_in6));
	if (sock == NULL) {
		Fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
		exit(EXIT_FAILURE);
	}
	(void) bzero(sock, sizeof (struct sockaddr_in6));

	if (family == AF_INET) {
		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 {
		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) {
		sin = (struct sockaddr_in *)sock;
		src_addr->addr = sin->sin_addr;
	} else {
		sin6 = (struct sockaddr_in6 *)sock;
		src_addr->addr6 = sin6->sin6_addr;
	}

	(void) close(tmp_fd);
	free(sock);
}

/*
 * Setup the socket for the given address family.
 * Returns _B_TRUE on success, _B_FALSE on failure. Failure is the case when no
 * interface can be found, or the specified interface (-i) is not found. On
 * library call failures, it exit()s.
 */
static boolean_t
setup_socket(int family, int *send_sockp, int *recv_sockp, int *if_index,
    ushort_t *udp_src_port)
{
	int send_sock;
	int recv_sock;
	struct sockaddr_in6 sin6;
	struct sockaddr_in sin;
	struct sockaddr *sp;
	size_t slen;
	int on = 1;
	uchar_t char_op;
	int int_op;

	/* now we need root privilege */
	(void) seteuid((uid_t)0);

	recv_sock = socket(family, SOCK_RAW,
	    (family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6);

	/* revert to non-privileged user after opening sockets */
	(void) seteuid(getuid());

	if (recv_sock < 0) {
		Fprintf(stderr, "%s: socket %s\n", progname, strerror(errno));
		exit(EXIT_FAILURE);
	}

	/*
	 * We always receive on raw icmp socket. But the sending socket can be
	 * raw icmp or udp, depending on the use of -U flag.
	 */
	if (use_udp) {
		send_sock = socket(family, SOCK_DGRAM, IPPROTO_UDP);
		if (send_sock < 0) {
			Fprintf(stderr, "%s: socket %s\n", progname,
			    strerror(errno));
			exit(EXIT_FAILURE);
		}

		/*
		 * In order to distinguish replies to our UDP probes from
		 * other pings', we need to know our source port number.
		 */
		if (family == AF_INET) {
			sp = (struct sockaddr *)&sin;
			slen = sizeof (sin);
		} else {
			sp = (struct sockaddr *)&sin6;
			slen = sizeof (sin6);
		}
		bzero(sp, slen);
		sp->sa_family = family;

		/* Let's bind() send_sock to wildcard address and port */
		if (bind(send_sock, sp, slen) < 0) {
			Fprintf(stderr, "%s: bind %s\n", progname,
			    strerror(errno));
			exit(EXIT_FAILURE);
		}

		/* .... and see what port kernel picked for us */
		if (getsockname(send_sock, sp, &slen) < 0) {
			Fprintf(stderr, "%s: getsockname %s\n", progname,
			    strerror(errno));
			exit(EXIT_FAILURE);
		}
		*udp_src_port = (family == AF_INET) ? sin.sin_port :
		    sin6.sin6_port;
	} else {
		send_sock = recv_sock;
	}

	int_op = 48 * 1024;
	if (int_op < datalen)
		int_op = datalen;
	if (setsockopt(recv_sock, SOL_SOCKET, SO_RCVBUF, (char *)&int_op,
	    sizeof (int_op)) == -1) {
		Fprintf(stderr, "%s: setsockopt SO_RCVBUF %s\n", progname,
		    strerror(errno));
		exit(EXIT_FAILURE);
	}

	if (setsockopt(send_sock, SOL_SOCKET, SO_SNDBUF, (char *)&int_op,
	    sizeof (int_op)) == -1) {
		Fprintf(stderr, "%s: setsockopt SO_SNDBUF %s\n", progname,
		    strerror(errno));
		exit(EXIT_FAILURE);
	}

	if (options & SO_DEBUG) {
		if (setsockopt(send_sock, SOL_SOCKET, SO_DEBUG, (char *)&on,
		    sizeof (on)) == -1) {
			Fprintf(stderr, "%s: setsockopt SO_DEBUG %s\n",
			    progname, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	if (options & SO_DONTROUTE) {
		if (setsockopt(send_sock, SOL_SOCKET, SO_DONTROUTE, (char *)&on,
		    sizeof (on)) == -1) {
			Fprintf(stderr, "%s: setsockopt SO_DONTROUTE %s\n",
			    progname, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	if (moptions & MULTICAST_NOLOOP) {
		if (family == AF_INET) {
			char_op = 0;	/* used to turn off option */

			if (setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_LOOP,
			    (char *)&char_op, sizeof (char_op)) == -1) {
				Fprintf(stderr, "%s: setsockopt "
				    "IP_MULTICAST_NOLOOP %s\n", progname,
				    strerror(errno));
				exit(EXIT_FAILURE);
			}
		} else {
			int_op = 0;	/* used to turn off option */

			if (setsockopt(send_sock, IPPROTO_IPV6,
			    IPV6_MULTICAST_LOOP, (char *)&int_op,
			    sizeof (int_op)) == -1) {
				Fprintf(stderr, "%s: setsockopt "
				    "IPV6_MULTICAST_NOLOOP %s\n", progname,
				    strerror(errno));
				exit(EXIT_FAILURE);
			}
		}
	}

	if (moptions & MULTICAST_TTL) {
		char_op = hoplimit;

		if (family == AF_INET) {
			if (setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL,
			    (char *)&char_op, sizeof (char)) == -1) {
				Fprintf(stderr, "%s: setsockopt "
				    "IP_MULTICAST_TTL %s\n", progname,
				    strerror(errno));
				exit(EXIT_FAILURE);
			}
			if (setsockopt(send_sock, IPPROTO_IP, IP_TTL,
			    (char *)&hoplimit, sizeof (hoplimit)) == -1) {
				Fprintf(stderr, "%s: setsockopt IP_TTL %s\n",
				    progname, strerror(errno));
				exit(EXIT_FAILURE);
			}
		}
		/*
		 * AF_INET6 case is handled in set_ancillary_data() function.
		 * This is because when ancillary data is used (for routing
		 * header and outgoing interface index), the hoplimit set using
		 * setsockopt() is ignored.
		 */
	}

	/* did the user specify an interface? */
	if (moptions & MULTICAST_IF) {
		struct ifaddrlist *al = NULL;		/* interface list */
		struct ifaddrlist *my_if;
		char errbuf[ERRBUFSIZE];
		int num_ifs;
		int num_src_ifs;		/* exclude down and loopback */
		int i;

		/* pull out the interface list */
		num_ifs = ifaddrlist(&al, family, errbuf);
		if (num_ifs == -1) {
			Fprintf(stderr, "%s: %s\n", progname, errbuf);
			exit(EXIT_FAILURE);
		}

		/* filter out down and loopback interfaces */
		num_src_ifs = 0;
		for (i = 0; i < num_ifs; i++) {
			if (!(al[i].flags & IFF_LOOPBACK) &&
			    (al[i].flags & IFF_UP))
				num_src_ifs++;
		}

		if (num_src_ifs == 0) {
			Fprintf(stderr, "%s: can't find any %s interface\n",
			    progname, (family == AF_INET) ? "IPv4" : "IPv6");

			return (_B_FALSE);	/* failure */
		}

		/* locate the specified interface */
		my_if = find_if(al, num_ifs);
		if (my_if == NULL) {
			Fprintf(stderr, "%s: %s is an invalid %s interface\n",
			    progname, out_if.str,
			    (family == AF_INET) ? "IPv4" : "IPv6");

			return (_B_FALSE);
		}

		if (family == AF_INET) {
			if (setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_IF,
			    (char *)&my_if->addr.addr,
			    sizeof (struct in_addr)) == -1) {
				Fprintf(stderr, "%s: setsockopt "
				    "IP_MULTICAST_IF %s\n", progname,
				    strerror(errno));
				exit(EXIT_FAILURE);
			}
		} else {
			/*
			 * the outgoing interface is set in set_ancillary_data()
			 * function
			 */
			*if_index = my_if->index;
		}

		free(al);
	}

	if (settos && family == AF_INET) {
		int_op = tos;
		if (setsockopt(send_sock, IPPROTO_IP, IP_TOS, (char *)&int_op,
		    sizeof (int_op)) == -1) {
			Fprintf(stderr, "%s: setsockopt IP_TOS %s\n",
			    progname, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	/* receiving IPv6 extension headers in verbose mode */
	if (verbose && family == AF_INET6) {
		if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVHOPOPTS,
		    (char *)&on, sizeof (on)) == -1) {
			Fprintf(stderr, "%s: setsockopt IPV6_RECVHOPOPTS %s\n",
			    progname, strerror(errno));
			exit(EXIT_FAILURE);
		}

		if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVDSTOPTS,
		    (char *)&on, sizeof (on)) == -1) {
			Fprintf(stderr, "%s: setsockopt IPV6_RECVDSTOPTS %s\n",
			    progname, strerror(errno));
			exit(EXIT_FAILURE);
		}

		if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVRTHDR,
		    (char *)&on, sizeof (on)) == -1) {
			Fprintf(stderr, "%s: setsockopt IPV6_RECVRTHDR %s\n",
			    progname, strerror(errno));
			exit(EXIT_FAILURE);
		}
		if (setsockopt(recv_sock, IPPROTO_IPV6, IPV6_RECVRTHDRDSTOPTS,
		    (char *)&on, sizeof (on)) == -1) {
			Fprintf(stderr,
			    "%s: setsockopt IPV6_RECVRTHDRDSTOPTS %s\n",
			    progname, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}

	*send_sockp = send_sock;
	*recv_sockp = recv_sock;

	/* successful */
	return (_B_TRUE);
}

/*
 * Pull out the record containing all the info about the interface specified by
 * `out_if'. Skips interfaces which are down or loopback.
 */
static struct ifaddrlist *
find_if(struct ifaddrlist *al, int num_ifs)
{
	static struct ifaddrlist tmp_if;
	boolean_t found;
	int i;

	i = 0;
	found = _B_FALSE;

	while (i < num_ifs && !found) {
		tmp_if = al[i];

		/* skip down or loopback interfaces */
		if ((tmp_if.flags & IFF_LOOPBACK) || !(tmp_if.flags & IFF_UP)) {
			i++;
			continue;
		}

		/* the type of interface id is variable */
		switch (out_if.id_type) {
		case IF_INDEX:
			if (out_if.id.index == tmp_if.index)
				found = _B_TRUE;
			break;

		case IF_NAME:
			if (strcmp(out_if.id.name, tmp_if.device) == 0)
				found = _B_TRUE;
			break;

		case IF_ADDR:
			if (out_if.id.addr.addr.s_addr ==
			    tmp_if.addr.addr.s_addr) {
				found = _B_TRUE;
			}
			break;

		case IF_ADDR6:
			if (IN6_ARE_ADDR_EQUAL(&out_if.id.addr.addr6,
			    &tmp_if.addr.addr6)) {
				found = _B_TRUE;
			}
			break;

		default:
			break;
		}

		i++;
	}

	if (found)
		return (&tmp_if);
	else
		return (NULL);
}

/*
 * Invoked by SIGALRM, sigalrm_handler() is, responsible for calling
 * send_scheduled_probe() to send next probe.
 */
void
sigalrm_handler(void)
{
	struct timeval t_now;
	struct timeval t_diff;

	/*
	 * Guard againist denial-of-service attacks. Make sure ping doesn't
	 * send probes for every SIGALRM it receives. Evil hacker can generate
	 * SIGALRMs as fast as it can, but ping will ignore those which are
	 * received too soon (earlier than 0.5 sec) after it sent the last
	 * probe.
	 */
	(void) gettimeofday(&t_now, NULL);
	t_diff = t_now;
	tvsub(&t_diff, &t_last_probe_sent);
	if (t_diff.tv_sec == 0 && t_diff.tv_usec < 500000)
		return;

	send_scheduled_probe();
	schedule_sigalrm();
}

/*
 * Schedule next SIGALRM.
 */
void
schedule_sigalrm(void)
{
	int waittime;

	if (npackets == 0 ||
	    current_targetaddr->num_sent < current_targetaddr->num_probes) {
		(void) alarm(interval);
	} else {
		if (current_targetaddr->got_reply) {
			waittime = 2 * tmax / 1000;
			if (waittime == 0)
				waittime = 1;
		} else {
			waittime = MAX_WAIT;
		}
		(void) alarm(waittime);
	}
}

/*
 * Called by sigalrm_handler(), check_reply() or check_reply6(),
 * send_scheduled_probe() looks at the current_targetaddr and determines what
 * should be sent next and calls pinger().
 */
void
send_scheduled_probe()
{
	static struct msghdr msg6;
	static boolean_t first_probe = _B_TRUE;
	char tmp_buf[INET6_ADDRSTRLEN];

	/*
	 * We are about to move to next targetaddr if it's either we sent
	 * all the probes, or somebody set the probing_done flag to
	 * _B_TRUE prompting us to move on.
	 */
	if (current_targetaddr->num_sent == current_targetaddr->num_probes ||
	    current_targetaddr->probing_done) {
		/*
		 * is this a dead target?
		 */
		if (!stats && !current_targetaddr->got_reply) {
			if (!probe_all) {
				Printf("no answer from %s\n", targethost);
			} else {
				Printf("no answer from %s(%s)\n", targethost,
				    inet_ntop(current_targetaddr->family,
					&current_targetaddr->dst_addr,
					tmp_buf, sizeof (tmp_buf)));
			}
		}
		/*
		 * Before we move onto next item, let's do some clean up.
		 */
		current_targetaddr->got_reply = _B_FALSE;
		current_targetaddr->probing_done = _B_FALSE;
		/*
		 * If this is probe-all without stats mode, then we need to
		 * preserve this count. This is needed when we try to map an
		 * icmp_seq to IP address. Otherwise, clear it.
		 */
		if (stats || !probe_all)
			current_targetaddr->num_sent = 0;
		nreceived_last_target = 0;

		current_targetaddr = current_targetaddr->next;

		/*
		 * Did we reach the end of road?
		 */
		if (current_targetaddr == NULL) {
			(void) alarm(0);	/* cancel alarm */
			if (stats)
				finish();
			if (is_alive)
				exit(EXIT_SUCCESS);
			else
				exit(EXIT_FAILURE);
		} else {
			/*
			 * We use starting_seq_num for authenticating replies.
			 * Each time we move to a new targetaddr, which has
			 * a different target IP address, we update this field.
			 */
			current_targetaddr->starting_seq_num =
			    use_udp ? dest_port :
				(ntransmitted % (MAX_ICMP_SEQ + 1));
		}
	}

	if (current_targetaddr->family == AF_INET6) {
		if (send_reply) {
			/* sending back to ourself */
			to6.sin6_addr = current_targetaddr->src_addr.addr6;
		} else {
			to6.sin6_addr = current_targetaddr->dst_addr.addr6;
		}
		/*
		 * Setting the ancillary data once is enough, if we are
		 * not using source routing through target (-l/-S). In
		 * case -l/-S used, the middle gateway will be the
		 * IP address of the source, which can be different
		 * for each target IP.
		 */
		if (first_probe ||
		    (send_reply && current_targetaddr->num_sent == 0)) {
			if (send_reply) {
				/* target is the middle gateway now */
				gw_IP_list6[num_gw].addr6 =
				    current_targetaddr->dst_addr.addr6;
			}
			set_ancillary_data(&msg6, hoplimit, gw_IP_list6,
			    eff_num_gw, if_index);
			first_probe = _B_FALSE;
		}
		pinger(send_sock6, (struct sockaddr *)&to6, &msg6, AF_INET6);
	} else {
		to.sin_addr = current_targetaddr->dst_addr.addr;
		/*
		 * Set IPv4 options when sending the first probe to a target
		 * IP address. Some options change when the target address
		 * changes.
		 */
		if (current_targetaddr->num_sent == 0) {
			if (eff_num_gw > 0) {
				gw_IP_list[num_gw].addr =
				    current_targetaddr->dst_addr.addr;
				/*
				 * If send_reply, the target becomes the
				 * middle gateway, sender becomes the last
				 * gateway.
				 */
				if (send_reply) {
					gw_IP_list[eff_num_gw].addr =
					    current_targetaddr->src_addr.addr;
				}
			}
			/*
			 * In IPv4, if source routing is used, the target
			 * address shows up as the last gateway, hence +1.
			 */
			set_IPv4_options(send_sock, gw_IP_list,
			    (eff_num_gw > 0) ? eff_num_gw + 1 : 0,
			    &current_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 hostinfo *hi_dst6, struct hostinfo *hi_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 != 0)
			FD_SET(recv_sock6, &fds);
		if (recv_sock != 0)
			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 (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(hi_dst6, &in_msg, cc,
					    udp_src_port6);
				}
			}
			/* Do we have an ICMP packet waiting? */
			if (FD_ISSET(recv_sock, &fds)) {
				cc = recvmsg(recv_sock, &in_msg, 0);
				if (cc < 0) {
					if (errno != EINTR) {
						Fprintf(stderr,
						    "%s: recvmsg %s\n",
						    progname, strerror(errno));
					}
					continue;
				} if (cc > 0) {
					check_reply(hi_dst, &in_msg, cc,
					    udp_src_port);
				}
			}
		}
		/*
		 * If we were probing last IP address of the target host and
		 * received a reply for each probe sent to this address,
		 * then we are done!
		 */
		if ((npackets > 0) && (current_targetaddr->next == NULL) &&
		    (nreceived_last_target == npackets)) {
			(void) alarm(0);	/* cancel alarm */
			finish();
		}
	} /* infinite loop */
}

/*
 * Given a host (with possibly multiple IP addresses) and an IP address, this
 * function determines if this IP address is one of the host's addresses to
 * which we're sending probes. Used to determine if we are interested in a
 * packet.
 */
boolean_t
is_a_target(struct hostinfo *hi, union any_in_addr *addr)
{
	int num_addrs;
	int i;

	if (probe_all)
		num_addrs = hi->num_addr;
	else
		num_addrs = 1;
	for (i = 0; i < num_addrs; i++) {
		if (hi->family == AF_INET6) {
			if (IN6_ARE_ADDR_EQUAL(&hi->addrs[i].addr6,
			    &addr->addr6))
				return (_B_TRUE);
		} else {
			if (hi->addrs[i].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;
	struct icmp *icp = (struct icmp *)out_pkt;
	struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)whereto;
	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;

		tp = (struct timeval *)out_pkt;
		datap = &out_pkt[sizeof (struct timeval)];

		to->sin_port = htons(dest_port);
		to6->sin6_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 */

		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) */
	(void) gettimeofday(&t_last_probe_sent, (struct timezone *)NULL);

	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 hostent *phe;
	static struct in6_addr prev_addr = IN6ADDR_ANY_INIT;
	char tmp_buf[INET6_ADDRSTRLEN];
	static char buf[256];
	uint_t len;
	int error_num;

	len = (family == AF_INET) ? sizeof (struct in_addr) :
	    sizeof (struct in6_addr);

	/* compare with the buffered (previous) lookup */
	if (memcmp(addr, &prev_addr, len) != 0) {
		if (nflag ||
		    (phe = getipnodebyaddr(addr, len, family, &error_num)) ==
			NULL) {
			(void) sprintf(buf, "%s",
			    inet_ntop(family, (const void *)addr, tmp_buf,
				sizeof (tmp_buf)));
		} else {
			(void) sprintf(buf, "%s (%s)", phe->h_name,
			    inet_ntop(family, (const void *)addr, tmp_buf,
				sizeof (tmp_buf)));
		}

		prev_addr = *(struct in6_addr *)addr;
	}
	return (buf);
}

/*
 * Return the protocol string, given its protocol number.
 */
char *
pr_protocol(int prot)
{
	static char buf[20];

	switch (prot) {
	case IPPROTO_ICMPV6:
		(void) strcpy(buf, "icmp6");
		break;

	case IPPROTO_ICMP:
		(void) strcpy(buf, "icmp");
		break;

	case IPPROTO_TCP:
		(void) strcpy(buf, "tcp");
		break;

	case IPPROTO_UDP:
		(void) strcpy(buf, "udp");
		break;

	default:
		(void) sprintf(buf, "prot %d", prot);
		break;
	}

	return (buf);
}

/*
 * Checks if value is between seq_begin and seq_begin+seq_len. Note that
 * sequence numbers wrap around after MAX_ICMP_SEQ (== MAX_PORT).
 */
boolean_t
seq_match(ushort_t seq_begin, int seq_len, ushort_t value)
{
	/*
	 * If seq_len is too big, like some value greater than MAX_ICMP_SEQ/2,
	 * truncate it down to MAX_ICMP_SEQ/2. We are not going to accept any
	 * reply which come 83hr later!
	 */
	if (seq_len > MAX_ICMP_SEQ / 2) {
		seq_begin = (seq_begin + seq_len - MAX_ICMP_SEQ / 2) %
		    (MAX_ICMP_SEQ + 1);
		seq_len = MAX_ICMP_SEQ / 2;
	}

	if (PINGSEQ_LEQ(seq_begin, value) &&
	    PINGSEQ_LEQ(value, (seq_begin + seq_len - 1) % (MAX_ICMP_SEQ + 1)))
		return (_B_TRUE);
	else
		return (_B_FALSE);
}

/*
 * For a given icmp_seq, find which destination address we must have sent this
 * to.
 */
void
find_dstaddr(ushort_t icmpseq, union any_in_addr *ipaddr)
{
	struct targetaddr *target = targetaddr_list;
	int real_seq;
	int targetaddr_index;
	int real_npackets;
	int i;

	ipaddr->addr6 = in6addr_any;

	/*
	 * If this is probe_all and not stats, then the number of probes sent to
	 * each IP address may be different (remember, we stop sending to one IP
	 * address as soon as it replies). They are stored in target->num_sent
	 * field. Since we don't wrap around the list (!stats), they are also
	 * preserved.
	 */
	if (probe_all && !stats) {
		do {
			if (seq_match(target->starting_seq_num,
			    target->num_sent, icmpseq)) {
				ipaddr->addr6 = target->dst_addr.addr6;
				/*
				 * We are not immediately return()ing here.
				 * Because of wrapping, we might find another
				 * match later, which is more likely to be the
				 * real one.
				 */
			}
			target = target->next;
		} while (target != NULL);
	} else {
		/*
		 * Find the absolute (non-wrapped) seq number within the last
		 * 64K
		 */
		if (icmpseq < (ntransmitted % (MAX_ICMP_SEQ + 1))) {
			real_seq = num_wraps * (MAX_ICMP_SEQ + 1) + icmpseq;
		} else {
			real_seq = (num_wraps - 1) * (MAX_ICMP_SEQ + 1) +
			    icmpseq;
		}

		/* Make sure it's non-negative */
		if (real_seq < 0)
			return;
		real_npackets = (npackets == 0) ? 1 : npackets;

		/*
		 * We sent npackets many packets to each of those
		 * num_targetaddrs many IP addresses.
		 */
		targetaddr_index =
		    (real_seq % (num_targetaddrs * real_npackets)) /
		    real_npackets;
		for (i = 0; i < targetaddr_index; i++)
			target = target->next;
		ipaddr->addr6 = target->dst_addr.addr6;
	}
}

/*
 * Checksum routine for Internet Protocol family headers (C Version)
 */
static ushort_t
in_cksum(ushort_t *addr, int len)
{
	int nleft = len;
	ushort_t *w = addr;
	ushort_t answer;
	ushort_t odd_byte = 0;
	int sum = 0;

	/*
	 *  Our algorithm is simple, using a 32 bit accumulator (sum),
	 *  we add sequential 16 bit words to it, and at the end, fold
	 *  back all the carry bits from the top 16 bits into the lower
	 *  16 bits.
	 */
	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}

	/* mop up an odd byte, if necessary */
	if (nleft == 1) {
		*(uchar_t *)(&odd_byte) = *(uchar_t *)w;
		sum += odd_byte;
	}

	/*
	 * add back carry outs from top 16 bits to low 16 bits
	 */
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */
	return (answer);
}

/*
 * Subtract 2 timeval structs:  out = out - in.
 * Out is assumed to be >= in.
 */
void
tvsub(struct timeval *out, struct timeval *in)
{
	if ((out->tv_usec -= in->tv_usec) < 0) {
		out->tv_sec--;
		out->tv_usec += 1000000;
	}
	out->tv_sec -= in->tv_sec;
}

/*
 * Print out statistics, and give up.
 * Heavily buffered STDIO is used here, so that all the statistics
 * will be written with 1 sys-write call.  This is nice when more
 * than one copy of the program is running on a terminal;  it prevents
 * the statistics output from becoming intermingled.
 */
static void
finish()
{
	Printf("\n----%s PING Statistics----\n", targethost);
	Printf("%d packets transmitted, ", ntransmitted);
	Printf("%d packets received, ", nreceived);
	if (ntransmitted) {
		if (nreceived <= ntransmitted) {
			Printf("%d%% packet loss",
			    (int)(((ntransmitted-nreceived)*100) /
			    ntransmitted));
		} else {
			Printf("%.2f times amplification",
			    (double)nreceived / (double)ntransmitted);
		}
	}
	(void) putchar('\n');

	/* if packet is big enough to store timeval AND ... */
	if ((datalen >= sizeof (struct timeval)) && (nreceived > 0)) {
	    Printf("round-trip (ms)  min/avg/max = %d/%d/%d\n",
		tmin, tsum / nreceived, tmax);
	}
	(void) fflush(stdout);

	if (is_alive)
		exit(EXIT_SUCCESS);
	else
		exit(EXIT_FAILURE);
}

/*
 * print the usage line
 */
static void
usage(char *cmdname)
{
	Fprintf(stderr, "usage: %s host [timeout]\n", cmdname);
	Fprintf(stderr,
/* CSTYLED */
"usage: %s -s [-l | U] [adLnRrv] [-A addr_family] [-c traffic_class]\n\t"
"[-g gateway [-g gateway ...]] [-F flow_label] [-I interval]\n\t"
"[-i interface] [-P tos] [-p port] [-t ttl] host [data_size] [npackets]\n",
	    cmdname);
}

/*
 * Parse integer argument; exit with an error if it's not a number.
 * Now it also accepts hex. values.
 */
static int
int_arg(char *s, char *what)
{
	char *cp;
	char *ep;
	int num;

	if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
		cp = s + 2;
		num = (int)strtol(cp, &ep, 16);
	} else {
		num = (int)strtol(s, &ep, 10);
	}

	if (errno || *ep != '\0' || num < 0) {
		(void) Fprintf(stderr, "%s: bad %s: %s\n",
		    progname, what, s);
		exit(EXIT_FAILURE);
	}

	return (num);
}
