/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2017, Joyent, Inc. */ /* * Copyright (c) 1988, 1989, 1991, 1994, 1995, 1996, 1997 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that: (1) source code distributions * retain the above copyright notice and this paragraph in its entirety, (2) * distributions including binary code include the above copyright notice and * this paragraph in its entirety in the documentation or other materials * provided with the distribution, and (3) all advertising materials mentioning * features or use of this software display the following acknowledgement: * ``This product includes software developed by the University of California, * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of * the University nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior * written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * * @(#)$Header: traceroute.c,v 1.49 97/06/13 02:30:23 leres Exp $ (LBL) */ #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 #include "traceroute.h" #define MAX_SEQ 65535 /* max sequence value for ICMP */ #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 STR_LEN 30 /* store the information about a host */ struct hostinfo { char *name; /* hostname */ int family; /* address family of the IP addresses */ int num_addr; /* number of IP addresses */ union any_in_addr *addrs; /* list of IP addresses */ }; /* used to store a bunch of protocol specific values */ struct pr_set { int family; /* AF_INET or AF_INET6 */ char name[STR_LEN]; /* "IPv4" or "IPv6" */ char icmp[STR_LEN]; /* "icmp" or "ipv6-icmp" */ int icmp_minlen; int addr_len; int ip_hdr_len; int packlen; int sock_size; /* size of sockaddr_in or sockaddr_in6 */ struct sockaddr *to; struct sockaddr *from; void *from_sin_addr; union any_in_addr *gwIPlist; /* pointers to v4/v6 functions */ struct ip *(*set_buffers_fn) (int); int (*check_reply_fn)(struct msghdr *, int, int, uchar_t *, uchar_t *); boolean_t (*print_icmp_other_fn)(uchar_t, uchar_t); void (*print_addr_fn)(uchar_t *, int, struct sockaddr *); }; /* * LBNL bug fixed: in LBNL traceroute 'uchar_t packet[512];' * Not sufficient to hold the complete packet for ECHO REPLY of a big probe. * Packet size is reported incorrectly in such a case. * Also this buffer needs to be 32 bit aligned. In the future the alignment * requirement will be increased to 64 bit. So, let's use 64 bit alignment now. */ static uint64_t packet[(IP_MAXPACKET + 1)/8]; /* received packet */ static struct ip *outip4; /* output buffer to send as an IPv4 datagram */ static struct ip *outip6; /* output buffer to send as an IPv6 datagram */ /* Used to store the ancillary data that comes with the received packets */ static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8]; /* first get the gw names, later you'll resolve them based on the family */ static char *gwlist[MAXMAX_GWS]; /* gateway names list */ static union any_in_addr gwIPlist[MAX_GWS]; /* gateway IPv4 address list */ static union any_in_addr gwIP6list[MAX_GWS6]; /* gateway IPv6 address list */ static int family_input = AF_UNSPEC; /* User supplied protocol family */ static int rcvsock4; /* receive (icmp) socket file descriptor */ static int sndsock4; /* send (udp/icmp) socket file descriptor */ static int rcvsock6; /* receive (icmp6) socket file descriptor */ static int sndsock6; /* send (udp6/icmp6) socket file descriptor */ int gw_count = 0; /* number of gateways */ static struct sockaddr_in whereto; /* Who to try to reach */ static struct sockaddr_in6 whereto6; static struct sockaddr_in wherefrom; /* Who we are */ static struct sockaddr_in6 wherefrom6; static int packlen_input = 0; /* user input for packlen */ char *prog; static char *source_input = NULL; /* this is user arg. source, doesn't change */ static char *source = NULL; /* this gets modified after name lookup */ char *hostname; static char *device = NULL; /* interface name */ static struct pr_set *pr4; /* protocol info for IPv4 */ static struct pr_set *pr6; /* protocol info for IPv6 */ static struct ifaddrlist *al4; /* list of interfaces */ static struct ifaddrlist *al6; /* list of interfaces */ static uint_t if_index = 0; /* interface index */ static int num_v4 = 0; /* count of IPv4 addresses */ static int num_v6 = 0; /* count of IPv6 addresses */ static int num_ifs4 = 0; /* count of local IPv4 interfaces */ static int num_ifs6 = 0; /* count of local IPv6 interfaces */ static int nprobes = 3; /* number of probes */ static int max_ttl = 30; /* max number of hops */ static int first_ttl = 1; /* initial number of hops */ ushort_t ident; /* used to authenticate replies */ ushort_t port = 32768 + 666; /* start udp dest port # for probe packets */ static int options = 0; /* socket options */ boolean_t verbose = _B_FALSE; /* verbose output */ static int waittime = 5; /* time to wait for response (in seconds) */ static struct timeval delay = {0, 0}; /* delay between consecutive probe */ boolean_t nflag = _B_FALSE; /* print addresses numerically */ static boolean_t showttl = _B_FALSE; /* print the ttl(hop limit) of recvd pkt */ boolean_t useicmp = _B_FALSE; /* use icmp echo instead of udp packets */ boolean_t docksum = _B_TRUE; /* calculate checksums */ static boolean_t collect_stat = _B_FALSE; /* print statistics */ boolean_t settos = _B_FALSE; /* set type-of-service field */ int dontfrag = 0; /* IP*_DONTFRAG */ static int max_timeout = 5; /* quit after this consecutive timeouts */ static boolean_t probe_all = _B_FALSE; /* probe all the IFs of the target */ static boolean_t pick_src = _B_FALSE; /* traceroute picks the src address */ /* * flow and class are specific to IPv6, tos and off are specific to IPv4. * Each protocol uses the ones that are specific to itself, and ignores * others. */ static uint_t flow = 0; /* IPv6 flow info */ static uint_t class = 0; /* IPv6 class */ uchar_t tos = 0; /* IPv4 type-of-service */ ushort_t off = 0; /* set DF bit */ static jmp_buf env; /* stack environment for longjmp() */ boolean_t raw_req; /* if sndsock for IPv4 must be raw */ /* * Name service lookup related data. */ static mutex_t tr_nslock = ERRORCHECKMUTEX; static boolean_t tr_nsactive = _B_FALSE; /* Lookup ongoing */ static hrtime_t tr_nsstarttime; /* Start time */ static int tr_nssleeptime = 2; /* Interval between checks */ static int tr_nswarntime = 2; /* Interval to warn after */ /* Forwards */ static uint_t calc_packetlen(int, struct pr_set *); extern int check_reply(struct msghdr *, int, int, uchar_t *, uchar_t *); extern int check_reply6(struct msghdr *, int, int, uchar_t *, uchar_t *); static double deltaT(struct timeval *, struct timeval *); static char *device_name(struct ifaddrlist *, int, union any_in_addr *, struct pr_set *); extern void *find_ancillary_data(struct msghdr *, int, int); static boolean_t has_addr(struct addrinfo *, union any_in_addr *); static struct ifaddrlist *find_device(struct ifaddrlist *, int, char *); static struct ifaddrlist *find_ifaddr(struct ifaddrlist *, int, union any_in_addr *, int); static void get_gwaddrs(char **, int, union any_in_addr *, union any_in_addr *, int *, int *); static void get_hostinfo(char *, int, struct addrinfo **); char *inet_name(union any_in_addr *, int); ushort_t in_cksum(ushort_t *, int); extern int ip_hdr_length_v6(ip6_t *, int, uint8_t *); extern char *pr_type(uchar_t); extern char *pr_type6(uchar_t); extern void print_addr(uchar_t *, int, struct sockaddr *); extern void print_addr6(uchar_t *, int, struct sockaddr *); extern boolean_t print_icmp_other(uchar_t, uchar_t); extern boolean_t print_icmp_other6(uchar_t, uchar_t); static void print_stats(int, int, double, double, double, double); static void print_unknown_host_msg(const char *, const char *); static void record_stats(double, int *, double *, double *, double *, double *); static void resolve_nodes(int *, struct addrinfo **); static void select_src_addr(union any_in_addr *, union any_in_addr *, int); extern void send_probe(int, struct sockaddr *, struct ip *, int, int, struct timeval *, int); extern void send_probe6(int, struct msghdr *, struct ip *, int, int, struct timeval *, int); extern void set_ancillary_data(struct msghdr *, int, union any_in_addr *, int, uint_t); extern struct ip *set_buffers(int); extern struct ip *set_buffers6(int); extern void set_IPv4opt_sourcerouting(int, union any_in_addr *, union any_in_addr *); static void set_sin(struct sockaddr *, union any_in_addr *, int); static int set_src_addr(struct pr_set *, struct ifaddrlist **); static void setup_protocol(struct pr_set *, int); static void setup_socket(struct pr_set *, int); static void sig_handler(int); static int str2int(const char *, const char *, int, int); static double str2dbl(const char *, const char *, double, double); static void trace_it(struct addrinfo *); static void traceroute(union any_in_addr *, struct msghdr *, struct pr_set *, int, struct ifaddrlist *); static void tv_sub(struct timeval *, struct timeval *); static void usage(void); static int wait_for_reply(int, struct msghdr *, struct timeval *); static double xsqrt(double); static void *ns_warning_thr(void *); /* * main */ int main(int argc, char **argv) { struct addrinfo *ai_dst = NULL; /* destination host */ /* * "probing_successful" indicates if we could successfully send probes, * not necessarily received reply from the target (this behavior is from * the original traceroute). It's _B_FALSE if packlen is invalid, or no * interfaces found. */ boolean_t probing_successful = _B_FALSE; int longjmp_return; /* return value from longjump */ int i = 0; char *cp; int op; char *ep; char temp_buf[INET6_ADDRSTRLEN]; /* use for inet_ntop() */ double pause; /* * A raw socket will be used for IPv4 if there is sufficient * privilege. */ raw_req = priv_ineffect(PRIV_NET_RAWACCESS); /* * We'll need the privilege only when we open the sockets; that's * when we'll fail if the program has insufficient privileges. */ (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_NET_ICMPACCESS, raw_req ? PRIV_NET_RAWACCESS : NULL, NULL); (void) setlinebuf(stdout); if ((cp = strrchr(argv[0], '/')) != NULL) prog = cp + 1; else prog = argv[0]; opterr = 0; while ((op = getopt(argc, argv, "adFIlnrSvxA:c:f:g:i:L:m:P:p:Q:q:s:" "t:w:")) != EOF) { switch (op) { 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", prog, optarg); exit(EXIT_FAILURE); } break; case 'a': probe_all = _B_TRUE; break; case 'c': class = str2int(optarg, "traffic class", 0, MAX_TRAFFIC_CLASS); break; case 'd': options |= SO_DEBUG; break; case 'f': first_ttl = str2int(optarg, "first ttl", 1, MAXTTL); break; case 'F': off = IP_DF; dontfrag = 1; break; case 'g': if (!raw_req) { Fprintf(stderr, "%s: privilege to specify a loose source " "route gateway is unavailable\n", prog); exit(EXIT_FAILURE); } if (gw_count >= MAXMAX_GWS) { Fprintf(stderr, "%s: Too many gateways\n", prog); exit(EXIT_FAILURE); } gwlist[gw_count] = strdup(optarg); if (gwlist[gw_count] == NULL) { Fprintf(stderr, "%s: strdup %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } ++gw_count; break; case 'l': showttl = _B_TRUE; break; case 'i': /* this can be IF name or IF index */ if_index = (uint_t)strtol(optarg, &ep, 10); /* convert IF index <--> IF name */ if (errno != 0 || *ep != '\0') { device = optarg; if_index = if_nametoindex((const char *)device); /* * In case it fails, check to see if the problem * is other than "IF not found". */ if (if_index == 0 && errno != ENXIO) { Fprintf(stderr, "%s: if_nametoindex:" "%s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } else { device = (char *)malloc(LIFNAMSIZ + 1); if (device == NULL) { Fprintf(stderr, "%s: malloc: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } device = if_indextoname(if_index, device); if (device != NULL) { device[LIFNAMSIZ] = '\0'; } else if (errno != ENXIO) { /* * The problem was other than "index * not found". */ Fprintf(stderr, "%s: if_indextoname:" "%s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } if (device == NULL || if_index == 0) { Fprintf(stderr, "%s: interface %s " "doesn't match any actual interfaces\n", prog, optarg); exit(EXIT_FAILURE); } break; case 'I': useicmp = _B_TRUE; break; case 'L': flow = str2int(optarg, "flow label", 0, MAX_FLOW_LABEL); break; case 'm': max_ttl = str2int(optarg, "max ttl(hop limit)", 1, MAXTTL); break; case 'n': nflag = _B_TRUE; break; case 'P': pause = str2dbl(optarg, "pause", 0, INT_MAX); delay.tv_sec = (time_t)pause; delay.tv_usec = (suseconds_t)((pause - delay.tv_sec) * 1000000); break; case 'p': port = str2int(optarg, "port", 1, MAX_PORT); break; case 'Q': max_timeout = str2int(optarg, "max timeout", 1, -1); break; case 'q': nprobes = str2int(optarg, "nprobes", 1, -1); break; case 'r': options |= SO_DONTROUTE; break; case 'S': collect_stat = _B_TRUE; break; case 's': /* * set the ip source address of the outbound * probe (e.g., on a multi-homed host). */ source_input = optarg; break; case 't': tos = (uchar_t)str2int(optarg, "tos", 0, MAX_TOS); settos = _B_TRUE; break; case 'v': verbose = _B_TRUE; break; case 'x': docksum = _B_FALSE; break; case 'w': waittime = str2int(optarg, "wait time", 2, -1); break; default: usage(); break; } } /* * If it's probe_all, SIGQUIT makes traceroute exit(). But we set the * address to jump back to in traceroute(). Until then, we'll need to * temporarily specify one. */ if (probe_all) { if ((longjmp_return = setjmp(env)) != 0) { if (longjmp_return == SIGQUIT) { Printf("(exiting)\n"); exit(EXIT_SUCCESS); } else { /* should never happen */ exit(EXIT_FAILURE); } } (void) signal(SIGQUIT, sig_handler); } if ((gw_count > 0) && (options & SO_DONTROUTE)) { Fprintf(stderr, "%s: loose source route gateways (-g)" " cannot be specified when probe packets are sent" " directly to a host on an attached network (-r)\n", prog); exit(EXIT_FAILURE); } i = argc - optind; if (i == 1 || i == 2) { hostname = argv[optind]; if (i == 2) { /* accept any length now, we'll check it later */ packlen_input = str2int(argv[optind + 1], "packet length", 0, -1); } } else { usage(); } if (first_ttl > max_ttl) { Fprintf(stderr, "%s: first ttl(hop limit) (%d) may not be greater" " than max ttl(hop limit) (%d)\n", prog, first_ttl, max_ttl); exit(EXIT_FAILURE); } /* * 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", prog, strerror(errno)); exit(EXIT_FAILURE); } /* resolve hostnames */ resolve_nodes(&family_input, &ai_dst); if (ai_dst == NULL) { exit(EXIT_FAILURE); } /* * If it's probe_all, SIGINT makes traceroute skip to probing next IP * address of the target. The new interrupt handler is assigned in * traceroute() function. Until then let's ignore the signal. */ if (probe_all) (void) signal(SIGINT, SIG_IGN); ident = (getpid() & 0xffff) | 0x8000; /* * We KNOW that probe_all == TRUE if family is AF_UNSPEC, * since family is set to the specific AF found unless it's * probe_all. So if family == AF_UNSPEC, we need to init pr4 and pr6. */ switch (family_input) { case AF_UNSPEC: pr4 = (struct pr_set *)malloc(sizeof (struct pr_set)); if (pr4 == NULL) { Fprintf(stderr, "%s: malloc %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } pr6 = (struct pr_set *)malloc(sizeof (struct pr_set)); if (pr6 == NULL) { Fprintf(stderr, "%s: malloc %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } setup_protocol(pr6, AF_INET6); setup_protocol(pr4, AF_INET); outip6 = (*pr6->set_buffers_fn)(pr6->packlen); setup_socket(pr6, pr6->packlen); outip4 = (*pr4->set_buffers_fn)(pr4->packlen); setup_socket(pr4, pr4->packlen); num_ifs6 = set_src_addr(pr6, &al6); num_ifs4 = set_src_addr(pr4, &al4); break; case AF_INET6: pr6 = (struct pr_set *)malloc(sizeof (struct pr_set)); if (pr6 == NULL) { Fprintf(stderr, "%s: malloc %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } setup_protocol(pr6, AF_INET6); outip6 = (*pr6->set_buffers_fn)(pr6->packlen); setup_socket(pr6, pr6->packlen); num_ifs6 = set_src_addr(pr6, &al6); break; case AF_INET: pr4 = (struct pr_set *)malloc(sizeof (struct pr_set)); if (pr4 == NULL) { Fprintf(stderr, "%s: malloc %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } setup_protocol(pr4, AF_INET); outip4 = (*pr4->set_buffers_fn)(pr4->packlen); setup_socket(pr4, pr4->packlen); num_ifs4 = set_src_addr(pr4, &al4); break; default: Fprintf(stderr, "%s: unknow address family.\n", prog); exit(EXIT_FAILURE); } if (num_v4 + num_v6 > 1 && !probe_all) { if (ai_dst->ai_family == AF_INET) { Fprintf(stderr, "%s: Warning: %s has multiple addresses;" " using %s\n", prog, hostname, inet_ntop(AF_INET, /* LINTED E_BAD_PTR_CAST_ALIGN */ (void *)&((struct sockaddr_in *) ai_dst->ai_addr)->sin_addr, temp_buf, sizeof (temp_buf))); } else { Fprintf(stderr, "%s: Warning: %s has multiple addresses;" " using %s\n", prog, hostname, inet_ntop(AF_INET6, /* LINTED E_BAD_PTR_CAST_ALIGN */ (void *)&((struct sockaddr_in6 *) ai_dst->ai_addr)->sin6_addr, temp_buf, sizeof (temp_buf))); } } if (num_ifs4 + num_ifs6 > 0) { trace_it(ai_dst); probing_successful = _B_TRUE; } (void) close(rcvsock4); (void) close(sndsock4); (void) close(rcvsock6); (void) close(sndsock6); /* * if we could probe any of the IP addresses of the target, that means * this was a successful operation */ if (probing_successful) return (EXIT_SUCCESS); else return (EXIT_FAILURE); } /* * print "unknown host" message */ static void print_unknown_host_msg(const char *protocol, const char *host) { Fprintf(stderr, "%s: unknown%s host %s\n", prog, protocol, host); } /* * resolve destination host and gateways */ static void resolve_nodes(int *family, struct addrinfo **ai_dstp) { struct addrinfo *ai_dst = NULL; struct addrinfo *aip = NULL; int num_resolved_gw = 0; int num_resolved_gw6 = 0; get_hostinfo(hostname, *family, &ai_dst); if (ai_dst == NULL) { print_unknown_host_msg("", hostname); 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 == AF_UNSPEC && !probe_all) { *family = ai_dst->ai_family; } /* resolve gateways */ if (gw_count > 0) { get_gwaddrs(gwlist, *family, gwIPlist, gwIP6list, &num_resolved_gw, &num_resolved_gw6); /* we couldn't resolve a gateway as an IPv6 host */ if (num_resolved_gw6 != gw_count && num_v6 != 0) { if (*family == AF_INET6 || *family == AF_UNSPEC) print_unknown_host_msg(" IPv6", gwlist[num_resolved_gw6]); num_v6 = 0; } /* we couldn't resolve a gateway as an IPv4 host */ if (num_resolved_gw != gw_count && num_v4 != 0) { if (*family == AF_INET || *family == AF_UNSPEC) print_unknown_host_msg(" IPv4", gwlist[num_resolved_gw]); num_v4 = 0; } } *ai_dstp = (num_v4 + num_v6 > 0) ? ai_dst : NULL; } /* * Given IP address or hostname, return v4 and v6 hostinfo lists. * Assumes that hostinfo ** ptrs are 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; char abuf[INET6_ADDRSTRLEN]; /* use for inet_ntop() */ int rc; /* * Take care of v4-mapped addresses. It should run same as v4, after * chopping off the prefix, leaving the IPv4 address */ if ((inet_pton(AF_INET6, host, &addr6) > 0) && IN6_IS_ADDR_V4MAPPED(&addr6)) { /* 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, &addr, abuf, sizeof (abuf)); /* now the host is an IPv4 address */ (void) strcpy(host, abuf); /* * If it's a mapped address, we convert it into IPv4 * address because traceroute will send and receive IPv4 * packets for that address. Therefore, it's a failure case to * ask get_hostinfo() to treat 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 | AI_CANONNAME; rc = getaddrinfo(host, NULL, &hints, &ai); if (rc != 0) { if (rc != EAI_NONAME) Fprintf(stderr, "%s: getaddrinfo: %s\n", prog, gai_strerror(rc)); *aipp = NULL; return; } *aipp = ai; } /* * Calculate the packet length to be used, and check against the valid range. * Returns -1 if range check fails. */ static uint_t calc_packetlen(int plen_input, struct pr_set *pr) { int minpacket; /* min ip packet size */ int optlen; /* length of ip options */ int plen; /* * LBNL bug fixed: miscalculation of optlen */ if (gw_count > 0) { /* * IPv4: * ---- * 5 (NO OPs) + 3 (code, len, ptr) + gateways * IP options field can hold up to 9 gateways. But the API * allows you to specify only 8, because the last one is the * destination host. When this packet is sent, on the wire * you see one gateway replaced by 4 NO OPs. The other 1 NO * OP is for alignment * * IPv6: * ---- * Well, formula is different, but the result is same. * 8 byte fixed part for Type 0 Routing header, followed by * gateway addresses */ optlen = 8 + gw_count * pr->addr_len; } else { optlen = 0; } /* take care of the packet length calculations and checks */ minpacket = pr->ip_hdr_len + sizeof (struct outdata) + optlen; if (useicmp) minpacket += pr->icmp_minlen; /* minimum ICMP header size */ else minpacket += sizeof (struct udphdr); plen = plen_input; if (plen == 0) { plen = minpacket; /* minimum sized packet */ } else if (minpacket > plen || plen > IP_MAXPACKET) { Fprintf(stderr, "%s: %s packet size must be >= %d and <= %d\n", prog, pr->name, minpacket, IP_MAXPACKET); return (0); } return (plen); } /* * Sets the source address by resolving -i and -s arguments, or if -i and -s * don't dictate any, it sets the pick_src to make sure traceroute uses the * kernel's pick of the source address. * Returns number of interfaces configured on the source host, 0 on error or * there's no interface which is up amd not a loopback. */ static int set_src_addr(struct pr_set *pr, struct ifaddrlist **alp) { union any_in_addr *ap; struct ifaddrlist *al = NULL; struct ifaddrlist *tmp1_al = NULL; struct ifaddrlist *tmp2_al = NULL; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct sockaddr_in *sin_from = (struct sockaddr_in *)pr->from; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct sockaddr_in6 *sin6_from = (struct sockaddr_in6 *)pr->from; struct addrinfo *aip; char errbuf[ERRBUFSIZE]; char abuf[INET6_ADDRSTRLEN]; /* use for inet_ntop() */ int num_ifs; /* all the interfaces */ int num_src_ifs; /* exclude loopback and down */ int i; uint_t ifaddrflags = 0; source = source_input; if (device != NULL) ifaddrflags |= LIFC_UNDER_IPMP; /* get the interface address list */ num_ifs = ifaddrlist(&al, pr->family, ifaddrflags, errbuf); if (num_ifs < 0) { Fprintf(stderr, "%s: ifaddrlist: %s\n", prog, errbuf); exit(EXIT_FAILURE); } 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 network interfaces\n", prog, pr->name); return (0); } /* verify the device */ if (device != NULL) { tmp1_al = find_device(al, num_ifs, device); if (tmp1_al == NULL) { Fprintf(stderr, "%s: %s (index %d) is an invalid %s" " interface\n", prog, device, if_index, pr->name); free(al); return (0); } } /* verify the source address */ if (source != NULL) { get_hostinfo(source, pr->family, &aip); if (aip == NULL) { Fprintf(stderr, "%s: %s is an invalid %s source address\n", prog, source, pr->name); free(al); return (0); } source = aip->ai_canonname; if (pr->family == AF_INET) ap = (union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in *)aip->ai_addr)->sin_addr; else ap = (union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in6 *)aip->ai_addr)->sin6_addr; /* * LBNL bug fixed: used to accept any src address */ tmp2_al = find_ifaddr(al, num_ifs, ap, pr->family); if (tmp2_al == NULL) { (void) inet_ntop(pr->family, ap, abuf, sizeof (abuf)); Fprintf(stderr, "%s: %s is not a local %s address\n", prog, abuf, pr->name); free(al); freeaddrinfo(aip); return (0); } } pick_src = _B_FALSE; if (source == NULL) { /* no -s used */ if (device == NULL) { /* no -i used, no -s used */ pick_src = _B_TRUE; } else { /* -i used, no -s used */ /* * -i used, but not -s, and it's IPv4: set the source * address to whatever the interface has configured on * it. */ if (pr->family == AF_INET) set_sin(pr->from, &(tmp1_al->addr), pr->family); else pick_src = _B_TRUE; } } else { /* -s used */ if (device == NULL) { /* no -i used, -s used */ set_sin(pr->from, ap, pr->family); if (aip->ai_next != NULL) { (void) inet_ntop(pr->family, pr->from_sin_addr, abuf, sizeof (abuf)); Fprintf(stderr, "%s: Warning: %s has multiple " "addresses; using %s\n", prog, source, abuf); } } else { /* -i and -s used */ /* * Make sure the source specified matches the * interface address. You only care about this for IPv4 * IPv6 can handle IF not matching src address */ if (pr->family == AF_INET) { if (!has_addr(aip, &tmp1_al->addr)) { Fprintf(stderr, "%s: %s is not on interface %s\n", prog, source, device); exit(EXIT_FAILURE); } /* * make sure we use the one matching the * interface's address */ *ap = tmp1_al->addr; } set_sin(pr->from, ap, pr->family); } } /* * Binding at this point will set the source address to be used * for both IPv4 (when raw IP datagrams are not required) and * IPv6. If the address being bound to is zero, then the kernel * will end up choosing the source address when the datagram is * sent. * * For raw IPv4 datagrams, the source address is initialized * within traceroute() along with the outbound destination * address. */ if (pr->family == AF_INET && !raw_req) { sin_from->sin_family = AF_INET; sin_from->sin_port = htons(ident); if (bind(sndsock4, (struct sockaddr *)pr->from, sizeof (struct sockaddr_in)) < 0) { Fprintf(stderr, "%s: bind: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } else if (pr->family == AF_INET6) { sin6_from->sin6_family = AF_INET6; sin6_from->sin6_port = htons(ident); if (bind(sndsock6, (struct sockaddr *)pr->from, sizeof (struct sockaddr_in6)) < 0) { Fprintf(stderr, "%s: bind: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } whereto6.sin6_flowinfo = htonl((class << 20) | flow); } *alp = al; return (num_ifs); } /* * Returns the complete ifaddrlist structure matching the desired interface * address. Ignores interfaces which are either down or loopback. */ static struct ifaddrlist * find_ifaddr(struct ifaddrlist *al, int len, union any_in_addr *addr, int family) { struct ifaddrlist *tmp_al = al; int i; size_t addr_len = (family == AF_INET) ? sizeof (struct in_addr) : sizeof (struct in6_addr); for (i = 0; i < len; i++, tmp_al++) { if ((!(tmp_al->flags & IFF_LOOPBACK) && (tmp_al->flags & IFF_UP)) && (memcmp(&tmp_al->addr, addr, addr_len) == 0)) break; } if (i < len) { return (tmp_al); } else { return (NULL); } } /* * Returns the complete ifaddrlist structure matching the desired interface name * Ignores interfaces which are either down or loopback. */ static struct ifaddrlist * find_device(struct ifaddrlist *al, int len, char *device) { struct ifaddrlist *tmp_al = al; int i; for (i = 0; i < len; i++, tmp_al++) { if ((!(tmp_al->flags & IFF_LOOPBACK) && (tmp_al->flags & IFF_UP)) && (strcmp(tmp_al->device, device) == 0)) break; } if (i < len) { return (tmp_al); } else { return (NULL); } } /* * returns _B_TRUE if given hostinfo contains the given address */ static boolean_t has_addr(struct addrinfo *ai, union any_in_addr *addr) { struct addrinfo *ai_tmp = NULL; union any_in_addr *ap; for (ai_tmp = ai; ai_tmp != NULL; ai_tmp = ai_tmp->ai_next) { if (ai_tmp->ai_family == AF_INET6) continue; ap = (union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in *)ai_tmp->ai_addr)->sin_addr; if (memcmp(ap, addr, sizeof (struct in_addr)) == 0) break; } if (ai_tmp != NULL) { return (_B_TRUE); } else { return (_B_FALSE); } } /* * 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 **gwlist, 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 && gw_count >= MAX_GWS) { check_v4 = _B_FALSE; Fprintf(stderr, "%s: too many IPv4 gateways\n", prog); num_v4 = 0; } if (check_v6 && gw_count >= MAX_GWS6) { check_v6 = _B_FALSE; Fprintf(stderr, "%s: too many IPv6 gateways\n", prog); num_v6 = 0; } for (i = 0; i < gw_count; i++) { if (!check_v4 && !check_v6) return; get_hostinfo(gwlist[i], family, &ai); if (ai == NULL) return; if (check_v4 && num_v4 != 0) { check_v4 = _B_FALSE; 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)++; check_v4 = _B_TRUE; break; } } } else if (check_v4) { check_v4 = _B_FALSE; } if (check_v6 && num_v6 != 0) { check_v6 = _B_FALSE; 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)++; check_v6 = _B_TRUE; break; } } } else if (check_v6) { check_v6 = _B_FALSE; } } freeaddrinfo(ai); } /* * set protocol specific values here */ static void setup_protocol(struct pr_set *pr, int family) { /* * Set the global variables for each AF. This is going to save us lots * of "if (family == AF_INET)... else .." */ pr->family = family; if (family == AF_INET) { if (!docksum) { Fprintf(stderr, "%s: Warning: checksums disabled\n", prog); } (void) strcpy(pr->name, "IPv4"); (void) strcpy(pr->icmp, "icmp"); pr->icmp_minlen = ICMP_MINLEN; pr->addr_len = sizeof (struct in_addr); pr->ip_hdr_len = sizeof (struct ip); pr->sock_size = sizeof (struct sockaddr_in); pr->to = (struct sockaddr *)&whereto; pr->from = (struct sockaddr *)&wherefrom; pr->from_sin_addr = (void *)&wherefrom.sin_addr; pr->gwIPlist = gwIPlist; pr->set_buffers_fn = set_buffers; pr->check_reply_fn = check_reply; pr->print_icmp_other_fn = print_icmp_other; pr->print_addr_fn = print_addr; pr->packlen = calc_packetlen(packlen_input, pr); } else { (void) strcpy(pr->name, "IPv6"); (void) strcpy(pr->icmp, "ipv6-icmp"); pr->icmp_minlen = ICMP6_MINLEN; pr->addr_len = sizeof (struct in6_addr); pr->ip_hdr_len = sizeof (struct ip6_hdr); pr->sock_size = sizeof (struct sockaddr_in6); pr->to = (struct sockaddr *)&whereto6; pr->from = (struct sockaddr *)&wherefrom6; pr->from_sin_addr = (void *)&wherefrom6.sin6_addr; pr->gwIPlist = gwIP6list; pr->set_buffers_fn = set_buffers6; pr->check_reply_fn = check_reply6; pr->print_icmp_other_fn = print_icmp_other6; pr->print_addr_fn = print_addr6; pr->packlen = calc_packetlen(packlen_input, pr); } if (pr->packlen == 0) exit(EXIT_FAILURE); } /* * setup the sockets for the given protocol's address family */ static void setup_socket(struct pr_set *pr, int packet_len) { int on = 1; struct protoent *pe; int type; int proto; int int_op; int rsock; int ssock; if ((pe = getprotobyname(pr->icmp)) == NULL) { Fprintf(stderr, "%s: unknown protocol %s\n", prog, pr->icmp); exit(EXIT_FAILURE); } /* privilege bracketing */ (void) __priv_bracket(PRIV_ON); if ((rsock = socket(pr->family, SOCK_RAW, pe->p_proto)) < 0) { Fprintf(stderr, "%s: icmp socket: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } if (options & SO_DEBUG) { if (setsockopt(rsock, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)) < 0) { Fprintf(stderr, "%s: SO_DEBUG: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } if (options & SO_DONTROUTE) { if (setsockopt(rsock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, sizeof (on)) < 0) { Fprintf(stderr, "%s: SO_DONTROUTE: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } if (pr->family == AF_INET6) { /* Enable receipt of destination address info */ if (setsockopt(rsock, IPPROTO_IPV6, IPV6_RECVPKTINFO, (char *)&on, sizeof (on)) < 0) { Fprintf(stderr, "%s: IPV6_RECVPKTINFO: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } /* Enable receipt of hoplimit info */ if (setsockopt(rsock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, (char *)&on, sizeof (on)) < 0) { Fprintf(stderr, "%s: IPV6_RECVHOPLIMIT: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } /* * Initialize the socket type and protocol based on the address * family, whether or not a raw IP socket is required (for IPv4) * or whether ICMP will be used instead of UDP. * * For historical reasons, the datagrams sent out by * traceroute(1M) do not have the "don't fragment" flag set. For * this reason as well as the ability to set the Loose Source and * Record Route (LSRR) option, a raw IP socket will be used for * IPv4 when run in the global zone. Otherwise, the actual * datagram that will be sent will be a regular UDP or ICMP echo * request packet. However for convenience and for future options * when other IP header information may be specified using * traceroute, the buffer including the raw IP and UDP or ICMP * header is always filled in. When the probe is actually sent, * the size of the request and the start of the packet is set * according to the type of datagram to send. */ if (pr->family == AF_INET && raw_req) { type = SOCK_RAW; proto = IPPROTO_RAW; } else if (useicmp) { type = SOCK_RAW; if (pr->family == AF_INET) proto = IPPROTO_ICMP; else proto = IPPROTO_ICMPV6; } else { type = SOCK_DGRAM; proto = IPPROTO_UDP; } ssock = socket(pr->family, type, proto); if (ssock < 0) { if (proto == IPPROTO_RAW) { Fprintf(stderr, "%s: raw socket: %s\n", prog, strerror(errno)); } else if (proto == IPPROTO_UDP) { Fprintf(stderr, "%s: udp socket: %s\n", prog, strerror(errno)); } else { Fprintf(stderr, "%s: icmp socket: %s\n", prog, strerror(errno)); } exit(EXIT_FAILURE); } if (setsockopt(ssock, SOL_SOCKET, SO_SNDBUF, (char *)&packet_len, sizeof (packet_len)) < 0) { Fprintf(stderr, "%s: SO_SNDBUF: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } if (pr->family == AF_INET && raw_req) { if (setsockopt(ssock, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof (on)) < 0) { Fprintf(stderr, "%s: IP_HDRINCL: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } if (options & SO_DEBUG) { if (setsockopt(ssock, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)) < 0) { Fprintf(stderr, "%s: SO_DEBUG: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } if (options & SO_DONTROUTE) { if (setsockopt(ssock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, sizeof (on)) < 0) { Fprintf(stderr, "%s: SO_DONTROUTE: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } /* * If a raw IPv4 packet is going to be sent, the Type of Service * field in the packet will be initialized in set_buffers(). * Otherwise, it is initialized here using the IPPROTO_IP level * socket option. */ if (settos && !raw_req) { int_op = tos; if (setsockopt(ssock, IPPROTO_IP, IP_TOS, (char *)&int_op, sizeof (int_op)) < 0) { Fprintf(stderr, "%s: IP_TOS: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } /* We enable or disable to not depend on the kernel default */ if (pr->family == AF_INET) { if (setsockopt(ssock, IPPROTO_IP, IP_DONTFRAG, (char *)&dontfrag, sizeof (dontfrag)) == -1) { Fprintf(stderr, "%s: IP_DONTFRAG %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } else { if (setsockopt(ssock, IPPROTO_IPV6, IPV6_DONTFRAG, (char *)&dontfrag, sizeof (dontfrag)) == -1) { Fprintf(stderr, "%s: IPV6_DONTFRAG %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } } if (pr->family == AF_INET) { rcvsock4 = rsock; sndsock4 = ssock; } else { rcvsock6 = rsock; sndsock6 = ssock; } /* Revert to non-privileged user after configuring sockets */ (void) __priv_bracket(PRIV_OFF); } /* * If we are "probing all", this function calls traceroute() for each IP address * of the target, otherwise calls only once. Returns _B_FALSE if traceroute() * fails. */ static void trace_it(struct addrinfo *ai_dst) { struct msghdr msg6; int num_dst_IPaddrs; struct addrinfo *aip; int i; if (!probe_all) num_dst_IPaddrs = 1; else num_dst_IPaddrs = num_v4 + num_v6; /* * Initialize the msg6 structure using the hoplimit for the first * probe packet, gateway addresses and the outgoing interface index. */ if (ai_dst->ai_family == AF_INET6 || (probe_all && num_v6)) { msg6.msg_control = NULL; msg6.msg_controllen = 0; set_ancillary_data(&msg6, first_ttl, pr6->gwIPlist, gw_count, if_index); } /* run traceroute for all the IP addresses of the multihomed dest */ for (aip = ai_dst, i = 0; i < num_dst_IPaddrs && aip != NULL; i++) { union any_in_addr *addrp; if (aip->ai_family == AF_INET) { addrp = (union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in *) aip->ai_addr)->sin_addr; set_sin((struct sockaddr *)pr4->to, addrp, aip->ai_family); traceroute(addrp, &msg6, pr4, num_ifs4, al4); } else { addrp = (union any_in_addr *) /* LINTED E_BAD_PTR_CAST_ALIGN */ &((struct sockaddr_in6 *) aip->ai_addr)->sin6_addr; set_sin((struct sockaddr *)pr6->to, addrp, aip->ai_family); traceroute(addrp, &msg6, pr6, num_ifs6, al6); } aip = aip->ai_next; if (i < (num_dst_IPaddrs - 1)) (void) putchar('\n'); } } /* * set the IP address in a sockaddr struct */ static void set_sin(struct sockaddr *sock, union any_in_addr *addr, int family) { sock->sa_family = family; if (family == AF_INET) /* LINTED E_BAD_PTR_CAST_ALIGN */ ((struct sockaddr_in *)sock)->sin_addr = addr->addr; else /* LINTED E_BAD_PTR_CAST_ALIGN */ ((struct sockaddr_in6 *)sock)->sin6_addr = addr->addr6; } /* * returns the IF name on which the given IP address is configured */ static char * device_name(struct ifaddrlist *al, int len, union any_in_addr *ip_addr, struct pr_set *pr) { int i; struct ifaddrlist *tmp_al; tmp_al = al; for (i = 0; i < len; i++, tmp_al++) { if (memcmp(&tmp_al->addr, ip_addr, pr->addr_len) == 0) { return (tmp_al->device); } } return (NULL); } /* * Trace the route to the host with given IP address. */ static void traceroute(union any_in_addr *ip_addr, struct msghdr *msg6, struct pr_set *pr, int num_ifs, struct ifaddrlist *al) { int ttl; int probe; uchar_t type; /* icmp type */ uchar_t code; /* icmp code */ int reply; int seq = 0; char abuf[INET6_ADDRSTRLEN]; /* use for inet_ntop() */ int longjmp_return; /* return value from longjump */ struct ip *ip = (struct ip *)packet; boolean_t got_there = _B_FALSE; /* we hit the destination */ static boolean_t first_pkt = _B_TRUE; int hoplimit; /* hoplimit for IPv6 packets */ struct in6_addr addr6; int num_src_ifs; /* excludes down and loopback */ struct msghdr in_msg; struct iovec iov; int *intp; int sndsock; int rcvsock; msg6->msg_name = pr->to; msg6->msg_namelen = sizeof (struct sockaddr_in6); sndsock = (pr->family == AF_INET) ? sndsock4 : sndsock6; rcvsock = (pr->family == AF_INET) ? rcvsock4 : rcvsock6; /* carry out the source address selection */ if (pick_src) { union any_in_addr src_addr; char *dev_name; int i; /* * 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 (gw_count > 0) { (void) select_src_addr(pr->gwIPlist, &src_addr, pr->family); } else { (void) select_src_addr(ip_addr, &src_addr, pr->family); } set_sin(pr->from, &src_addr, pr->family); /* 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 > 1) { dev_name = device_name(al, num_ifs, &src_addr, pr); if (dev_name == NULL) dev_name = "?"; (void) inet_ntop(pr->family, pr->from_sin_addr, abuf, sizeof (abuf)); Fprintf(stderr, "%s: Warning: Multiple interfaces found;" " using %s @ %s\n", prog, abuf, dev_name); } } if (pr->family == AF_INET) { outip4->ip_src = *(struct in_addr *)pr->from_sin_addr; outip4->ip_dst = ip_addr->addr; } /* * If the hostname is an IPv6 literal address, let's not print it twice. */ if (pr->family == AF_INET6 && inet_pton(AF_INET6, hostname, &addr6) > 0) { Fprintf(stderr, "%s to %s", prog, hostname); } else { Fprintf(stderr, "%s to %s (%s)", prog, hostname, inet_ntop(pr->family, ip_addr, abuf, sizeof (abuf))); } if (source) Fprintf(stderr, " from %s", source); Fprintf(stderr, ", %d hops max, %d byte packets\n", max_ttl, pr->packlen); (void) fflush(stderr); /* * Setup the source routing for IPv4. For IPv6, we did the required * setup in the caller function, trace_it(), because it's independent * from the IP address of target. */ if (pr->family == AF_INET && gw_count > 0) set_IPv4opt_sourcerouting(sndsock, ip_addr, pr->gwIPlist); if (probe_all) { /* interrupt handler sig_handler() jumps back to here */ if ((longjmp_return = setjmp(env)) != 0) { switch (longjmp_return) { case SIGINT: Printf("(skipping)\n"); return; case SIGQUIT: Printf("(exiting)\n"); exit(EXIT_SUCCESS); default: /* should never happen */ exit(EXIT_FAILURE); } } (void) signal(SIGINT, sig_handler); } for (ttl = first_ttl; ttl <= max_ttl; ++ttl) { union any_in_addr lastaddr; int timeouts = 0; double rtt; /* for statistics */ int nreceived = 0; double rttmin, rttmax; double rttsum, rttssq; int unreachable; got_there = _B_FALSE; unreachable = 0; /* * The following line clears both IPv4 and IPv6 address stored * in the union. */ lastaddr.addr6 = in6addr_any; if ((ttl == (first_ttl + 1)) && (options & SO_DONTROUTE)) { Fprintf(stderr, "%s: host %s is not on a directly-attached" " network\n", prog, hostname); break; } Printf("%2d ", ttl); (void) fflush(stdout); for (probe = 0; (probe < nprobes) && (timeouts < max_timeout); ++probe) { int cc; struct timeval t1, t2; /* * Put a delay before sending this probe packet. Don't * delay it if it's the very first packet. */ if (!first_pkt) { if (delay.tv_sec > 0) (void) sleep((uint_t)delay.tv_sec); if (delay.tv_usec > 0) (void) usleep(delay.tv_usec); } else { first_pkt = _B_FALSE; } (void) gettimeofday(&t1, NULL); if (pr->family == AF_INET) { send_probe(sndsock, pr->to, outip4, seq, ttl, &t1, pr->packlen); } else { send_probe6(sndsock, msg6, outip6, seq, ttl, &t1, pr->packlen); } /* prepare msghdr for recvmsg() */ in_msg.msg_name = pr->from; in_msg.msg_namelen = pr->sock_size; iov.iov_base = (char *)packet; iov.iov_len = sizeof (packet); in_msg.msg_iov = &iov; in_msg.msg_iovlen = 1; in_msg.msg_control = ancillary_data; in_msg.msg_controllen = sizeof (ancillary_data); while ((cc = wait_for_reply(rcvsock, &in_msg, &t1)) != 0) { (void) gettimeofday(&t2, NULL); reply = (*pr->check_reply_fn) (&in_msg, cc, seq, &type, &code); in_msg.msg_controllen = sizeof (ancillary_data); /* Skip short packet */ if (reply == REPLY_SHORT_PKT) { continue; } timeouts = 0; /* * if reply comes from a different host, print * the hostname */ if (memcmp(pr->from_sin_addr, &lastaddr, pr->addr_len) != 0) { (*pr->print_addr_fn) ((uchar_t *)packet, cc, pr->from); /* store the address response */ (void) memcpy(&lastaddr, pr->from_sin_addr, pr->addr_len); } rtt = deltaT(&t1, &t2); if (collect_stat) { record_stats(rtt, &nreceived, &rttmin, &rttmax, &rttsum, &rttssq); } else { Printf(" %.3f ms", rtt); } if (pr->family == AF_INET6) { intp = find_ancillary_data(&in_msg, IPPROTO_IPV6, IPV6_HOPLIMIT); if (intp == NULL) { Fprintf(stderr, "%s: can't find " "IPV6_HOPLIMIT ancillary " "data\n", prog); exit(EXIT_FAILURE); } hoplimit = *intp; } if (reply == REPLY_GOT_TARGET) { got_there = _B_TRUE; if (((pr->family == AF_INET) && (ip->ip_ttl <= 1)) || ((pr->family == AF_INET6) && (hoplimit <= 1))) Printf(" !"); } if (!collect_stat && showttl) { if (pr->family == AF_INET) { Printf(" (ttl=%d)", (int)ip->ip_ttl); } else if (hoplimit != -1) { Printf(" (hop limit=%d)", hoplimit); } } if (reply == REPLY_GOT_OTHER) { if ((*pr->print_icmp_other_fn) (type, code)) { unreachable++; } } /* special case */ if (pr->family == AF_INET && type == ICMP_UNREACH && code == ICMP_UNREACH_PROTOCOL) got_there = _B_TRUE; break; } seq = (seq + 1) % (MAX_SEQ + 1); if (cc == 0) { Printf(" *"); timeouts++; } (void) fflush(stdout); } if (collect_stat) { print_stats(probe, nreceived, rttmin, rttmax, rttsum, rttssq); } (void) putchar('\n'); /* either we hit the target or received too many unreachables */ if (got_there || (unreachable > 0 && unreachable >= nprobes - 1)) break; } /* Ignore the SIGINT between traceroute() runs */ if (probe_all) (void) signal(SIGINT, SIG_IGN); } /* * for a given destination address and address family, it finds out what * source address kernel is going to pick */ static void select_src_addr(union any_in_addr *dst_addr, union any_in_addr *src_addr, int family) { int tmp_fd; struct sockaddr *sock; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; size_t sock_len; sock = (struct sockaddr *)malloc(sizeof (struct sockaddr_in6)); if (sock == NULL) { Fprintf(stderr, "%s: malloc %s\n", prog, 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", prog, 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", prog, strerror(errno)); exit(EXIT_FAILURE); } if (family == AF_INET) { /* LINTED E_BAD_PTR_CAST_ALIGN */ sin = (struct sockaddr_in *)sock; src_addr->addr = sin->sin_addr; } else { /* LINTED E_BAD_PTR_CAST_ALIGN */ sin6 = (struct sockaddr_in6 *)sock; src_addr->addr6 = sin6->sin6_addr; } free(sock); (void) close(tmp_fd); } /* * Checksum routine for Internet Protocol family headers (C Version) */ ushort_t in_cksum(ushort_t *addr, int len) { int nleft = len; ushort_t *w = addr; ushort_t answer; 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) sum += *(uchar_t *)w; /* 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); } /* * Wait until a reply arrives or timeout occurs. If packet arrived, read it * return the size of the packet read. */ static int wait_for_reply(int sock, struct msghdr *msg, struct timeval *tp) { fd_set fds; struct timeval now, wait; int cc = 0; int result; (void) FD_ZERO(&fds); FD_SET(sock, &fds); wait.tv_sec = tp->tv_sec + waittime; wait.tv_usec = tp->tv_usec; (void) gettimeofday(&now, NULL); tv_sub(&wait, &now); if (wait.tv_sec < 0 || wait.tv_usec < 0) return (0); result = select(sock + 1, &fds, (fd_set *)NULL, (fd_set *)NULL, &wait); if (result == -1) { if (errno != EINTR) { Fprintf(stderr, "%s: select: %s\n", prog, strerror(errno)); } } else if (result > 0) cc = recvmsg(sock, msg, 0); return (cc); } /* * Construct an Internet address representation. If the nflag has been supplied, * give numeric value, otherwise try for symbolic name. */ char * inet_name(union any_in_addr *in, int family) { char *cp; static boolean_t first = _B_TRUE; static char domain[NI_MAXHOST + 1]; static char line[NI_MAXHOST + 1]; /* assuming */ /* (NI_MAXHOST + 1) >= INET6_ADDRSTRLEN */ char hbuf[NI_MAXHOST]; socklen_t slen; struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr *sa; int flags; switch (family) { case AF_INET: slen = sizeof (struct sockaddr_in); sin.sin_addr = in->addr; sin.sin_port = 0; sa = (struct sockaddr *)&sin; break; case AF_INET6: slen = sizeof (struct sockaddr_in6); sin6.sin6_addr = in->addr6; sin6.sin6_port = 0; sin6.sin6_scope_id = 0; sa = (struct sockaddr *)&sin6; break; default: (void) snprintf(line, sizeof (line), ""); return (line); } sa->sa_family = family; if (first && !nflag) { /* find out the domain name */ first = _B_FALSE; mutex_enter(&tr_nslock); tr_nsactive = _B_TRUE; tr_nsstarttime = gethrtime(); mutex_exit(&tr_nslock); if (gethostname(domain, MAXHOSTNAMELEN) == 0 && (cp = strchr(domain, '.')) != NULL) { (void) strncpy(domain, cp + 1, sizeof (domain) - 1); domain[sizeof (domain) - 1] = '\0'; } else { domain[0] = '\0'; } mutex_enter(&tr_nslock); tr_nsactive = _B_FALSE; mutex_exit(&tr_nslock); } flags = (nflag) ? NI_NUMERICHOST : NI_NAMEREQD; mutex_enter(&tr_nslock); tr_nsactive = _B_TRUE; tr_nsstarttime = gethrtime(); mutex_exit(&tr_nslock); if (getnameinfo(sa, slen, hbuf, sizeof (hbuf), NULL, 0, flags) != 0) { if (inet_ntop(family, (const void *)&in->addr6, hbuf, sizeof (hbuf)) == NULL) hbuf[0] = 0; } else if (!nflag && (cp = strchr(hbuf, '.')) != NULL && strcmp(cp + 1, domain) == 0) { *cp = '\0'; } mutex_enter(&tr_nslock); tr_nsactive = _B_FALSE; mutex_exit(&tr_nslock); (void) strlcpy(line, hbuf, sizeof (line)); return (line); } /* * return the difference (in msec) between two time values */ static double deltaT(struct timeval *t1p, struct timeval *t2p) { double dt; dt = (double)(t2p->tv_sec - t1p->tv_sec) * 1000.0 + (double)(t2p->tv_usec - t1p->tv_usec) / 1000.0; return (dt); } /* * Subtract 2 timeval structs: out = out - in. * Out is assumed to be >= in. */ static void tv_sub(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; } /* * record statistics */ static void record_stats(double rtt, int *nreceived, double *rttmin, double *rttmax, double *rttsum, double *rttssq) { if (*nreceived == 0) { *rttmin = rtt; *rttmax = rtt; *rttsum = rtt; *rttssq = rtt * rtt; } else { if (rtt < *rttmin) *rttmin = rtt; if (rtt > *rttmax) *rttmax = rtt; *rttsum += rtt; *rttssq += rtt * rtt; } (*nreceived)++; } /* * display statistics */ static void print_stats(int ntransmitted, int nreceived, double rttmin, double rttmax, double rttsum, double rttssq) { double rttavg; /* average round-trip time */ double rttstd; /* rtt standard deviation */ if (ntransmitted > 0 && ntransmitted >= nreceived) { int missed = ntransmitted - nreceived; double loss = 100 * (double)missed / (double)ntransmitted; if (nreceived > 0) { rttavg = rttsum / nreceived; rttstd = rttssq - (rttavg * rttsum); rttstd = xsqrt(rttstd / nreceived); Printf(" %.3f", rttmin); Printf("/%.3f", rttavg); Printf("/%.3f", rttmax); Printf(" (%.3f) ms ", rttstd); } Printf(" %d/%d pkts", nreceived, ntransmitted); if (nreceived == 0) Printf(" (100%% loss)"); else Printf(" (%.2g%% loss)", loss); } } /* * square root function */ double xsqrt(double y) { double t, x; if (y <= 0) { return (0.0); } x = (y < 1.0) ? 1.0 : y; do { t = x; x = (t + (y/t))/2.0; } while (0 < x && x < t); return (x); } /* * String to double with optional min and max. */ static double str2dbl(const char *str, const char *what, double mi, double ma) { double val; char *ep; errno = 0; val = strtod(str, &ep); if (errno != 0 || *ep != '\0') { Fprintf(stderr, "%s: \"%s\" bad value for %s \n", prog, str, what); exit(EXIT_FAILURE); } if (val < mi && mi >= 0) { Fprintf(stderr, "%s: %s must be >= %f\n", prog, what, mi); exit(EXIT_FAILURE); } if (val > ma && ma >= 0) { Fprintf(stderr, "%s: %s must be <= %f\n", prog, what, ma); exit(EXIT_FAILURE); } return (val); } /* * String to int with optional min and max. Handles decimal and hex. */ static int str2int(const char *str, const char *what, int mi, int ma) { const char *cp; int val; char *ep; errno = 0; if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { cp = str + 2; val = (int)strtol(cp, &ep, 16); } else { val = (int)strtol(str, &ep, 10); } if (errno != 0 || *ep != '\0') { Fprintf(stderr, "%s: \"%s\" bad value for %s \n", prog, str, what); exit(EXIT_FAILURE); } if (val < mi && mi >= 0) { if (mi == 0) { Fprintf(stderr, "%s: %s must be >= %d\n", prog, what, mi); } else { Fprintf(stderr, "%s: %s must be > %d\n", prog, what, mi - 1); } exit(EXIT_FAILURE); } if (val > ma && ma >= 0) { Fprintf(stderr, "%s: %s must be <= %d\n", prog, what, ma); exit(EXIT_FAILURE); } return (val); } /* * This is the interrupt handler for SIGINT and SIGQUIT. It's completely handled * where it jumps to. */ static void sig_handler(int sig) { longjmp(env, sig); } /* * display the usage of traceroute */ static void usage(void) { Fprintf(stderr, "Usage: %s [-adFIlnSvx] [-A address_family] " "[-c traffic_class]\n" "\t[-f first_hop] [-g gateway [-g gateway ...]| -r] [-i iface]\n" "\t[-L flow_label] [-m max_hop] [-P pause_sec] [-p port] " "[-Q max_timeout]\n" "\t[-q nqueries] [-s src_addr] [-t tos] [-w wait_time] host " "[packetlen]\n", prog); exit(EXIT_FAILURE); } /* ARGSUSED */ static void * ns_warning_thr(void *unused) { for (;;) { hrtime_t now; (void) sleep(tr_nssleeptime); now = gethrtime(); mutex_enter(&tr_nslock); if (tr_nsactive && now - tr_nsstarttime >= tr_nswarntime * NANOSEC) { Fprintf(stderr, "%s: warning: responses " "received, but name service lookups are " "taking a while. Use %s -n to disable " "name service lookups.\n", prog, prog); mutex_exit(&tr_nslock); return (NULL); } mutex_exit(&tr_nslock); } /* LINTED: E_STMT_NOT_REACHED */ return (NULL); }