/* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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 "traceroute.h" int check_reply6(struct msghdr *, int, int, uchar_t *, uchar_t *); void *find_ancillary_data(struct msghdr *, int, int); extern char *inet_name(union any_in_addr *, int); static int IPv6_hdrlen(ip6_t *, int, uint8_t *); static char *pr_type6(uchar_t); void print_addr6(uchar_t *, int, struct sockaddr *); boolean_t print_icmp_other6(uchar_t, uchar_t); void send_probe6(int, struct msghdr *, struct ip *, int, int, struct timeval *, int); void set_ancillary_data(struct msghdr *, int, union any_in_addr *, int, uint_t); struct ip *set_buffers6(int); static boolean_t update_hoplimit_ancillary_data(struct msghdr *, int); /* * prepares the buffer to be sent as an IP datagram */ struct ip * set_buffers6(int plen) { struct ip *outip; uchar_t *outp; struct udphdr *outudp; struct icmp *outicmp; int optlen = 0; outip = (struct ip *)malloc((size_t)plen); if (outip == NULL) { Fprintf(stderr, "%s: malloc: %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } if (gw_count > 0) { /* ip6_rthdr0 structure includes one gateway address */ optlen = sizeof (struct ip6_rthdr0) + gw_count * sizeof (struct in6_addr); } (void) memset((char *)outip, 0, (size_t)plen); outp = (uchar_t *)(outip + 1); if (useicmp) { /* LINTED E_BAD_PTR_CAST_ALIGN */ outicmp = (struct icmp *)outp; outicmp->icmp_type = ICMP6_ECHO_REQUEST; outicmp->icmp_id = htons(ident); } else { /* LINTED E_BAD_PTR_CAST_ALIGN */ outudp = (struct udphdr *)outp; /* * "source port" is set at bind() call, so we don't do it * again */ outudp->uh_ulen = htons((ushort_t)(plen - (sizeof (struct ip6_hdr) + optlen))); } return (outip); } /* * Initialize the msghdr for specifying hoplimit, outgoing interface and routing * header for the probe packets. */ void set_ancillary_data(struct msghdr *msgp, int hoplimit, union any_in_addr *gwIPlist, int gw_cnt, uint_t if_index) { size_t hoplimit_space; size_t rthdr_space; size_t pktinfo_space; size_t bufspace; struct cmsghdr *cmsgp; uchar_t *cmsg_datap; int i; msgp->msg_control = NULL; msgp->msg_controllen = 0; /* * Need to figure out size of buffer needed for ancillary data * containing routing header and packet info options. * * Portable heuristic to compute upper bound on space needed for * N ancillary data options. It assumes up to _MAX_ALIGNMENT padding * after both header and data as the worst possible upper bound on space * consumed by padding. * It also adds one extra "sizeof (struct cmsghdr)" for the last option. * This is needed because we would like to use CMSG_NXTHDR() while * composing the buffer. The CMSG_NXTHDR() macro is designed better for * parsing than composing the buffer. It requires the pointer it returns * to leave space in buffer for addressing a cmsghdr and we want to make * sure it works for us while we skip beyond the last ancillary data * option. * * bufspace[i] = sizeof(struct cmsghdr) + + * + ; * * total_bufspace = bufspace[0] + bufspace[1] + ... * ... + bufspace[N-1] + sizeof (struct cmsghdr); */ rthdr_space = 0; pktinfo_space = 0; /* We'll always set the hoplimit of the outgoing packets */ hoplimit_space = sizeof (int); bufspace = sizeof (struct cmsghdr) + _MAX_ALIGNMENT + hoplimit_space + _MAX_ALIGNMENT; if (gw_cnt > 0) { rthdr_space = inet6_rth_space(IPV6_RTHDR_TYPE_0, gw_cnt); bufspace += sizeof (struct cmsghdr) + _MAX_ALIGNMENT + rthdr_space + _MAX_ALIGNMENT; } if (if_index != 0) { pktinfo_space = sizeof (struct in6_pktinfo); bufspace += sizeof (struct cmsghdr) + _MAX_ALIGNMENT + pktinfo_space + _MAX_ALIGNMENT; } /* * We need to temporarily set the msgp->msg_controllen to bufspace * (we will later trim it to actual length used). This is needed because * CMSG_NXTHDR() uses it to check we have not exceeded the bounds. */ bufspace += sizeof (struct cmsghdr); msgp->msg_controllen = bufspace; msgp->msg_control = (struct cmsghdr *)malloc(bufspace); if (msgp->msg_control == NULL) { Fprintf(stderr, "%s: malloc %s\n", prog, strerror(errno)); exit(EXIT_FAILURE); } cmsgp = CMSG_FIRSTHDR(msgp); /* * Fill ancillary data. First hoplimit, then rthdr and pktinfo if * needed. */ /* set hoplimit ancillary data */ cmsgp->cmsg_level = IPPROTO_IPV6; cmsgp->cmsg_type = IPV6_HOPLIMIT; cmsg_datap = CMSG_DATA(cmsgp); /* LINTED E_BAD_PTR_CAST_ALIGN */ *(int *)cmsg_datap = hoplimit; cmsgp->cmsg_len = cmsg_datap + hoplimit_space - (uchar_t *)cmsgp; cmsgp = CMSG_NXTHDR(msgp, cmsgp); /* set rthdr ancillary data if needed */ if (gw_cnt > 0) { struct ip6_rthdr0 *rthdr0p; cmsgp->cmsg_level = IPPROTO_IPV6; cmsgp->cmsg_type = IPV6_RTHDR; cmsg_datap = CMSG_DATA(cmsgp); /* * Initialize rthdr structure */ /* LINTED E_BAD_PTR_CAST_ALIGN */ rthdr0p = (struct ip6_rthdr0 *)cmsg_datap; if (inet6_rth_init(rthdr0p, rthdr_space, IPV6_RTHDR_TYPE_0, gw_cnt) == NULL) { Fprintf(stderr, "%s: inet6_rth_init failed\n", prog); exit(EXIT_FAILURE); } /* * Stuff in gateway addresses */ for (i = 0; i < gw_cnt; i++) { if (inet6_rth_add(rthdr0p, &gwIPlist[i].addr6) == -1) { Fprintf(stderr, "%s: inet6_rth_add\n", prog); exit(EXIT_FAILURE); } } cmsgp->cmsg_len = cmsg_datap + rthdr_space - (uchar_t *)cmsgp; cmsgp = CMSG_NXTHDR(msgp, cmsgp); } /* set pktinfo ancillary data if needed */ if (if_index != 0) { struct in6_pktinfo *pktinfop; cmsgp->cmsg_level = IPPROTO_IPV6; cmsgp->cmsg_type = IPV6_PKTINFO; cmsg_datap = CMSG_DATA(cmsgp); /* LINTED E_BAD_PTR_CAST_ALIGN */ pktinfop = (struct in6_pktinfo *)cmsg_datap; /* * We don't know if pktinfop->ipi6_addr is aligned properly, * therefore let's use bcopy, instead of assignment. */ (void) bcopy(&in6addr_any, &pktinfop->ipi6_addr, sizeof (struct in6_addr)); /* * We can assume pktinfop->ipi6_ifindex is 32 bit aligned. */ pktinfop->ipi6_ifindex = if_index; cmsgp->cmsg_len = cmsg_datap + pktinfo_space - (uchar_t *)cmsgp; cmsgp = CMSG_NXTHDR(msgp, cmsgp); } msgp->msg_controllen = (char *)cmsgp - (char *)msgp->msg_control; } /* * Parses the given msg->msg_control to find the IPV6_HOPLIMIT ancillary data * and update the hoplimit. * Returns _B_FALSE if it can't find IPV6_HOPLIMIT ancillary data, _B_TRUE * otherwise. */ static boolean_t update_hoplimit_ancillary_data(struct msghdr *msg, int hoplimit) { struct cmsghdr *cmsg; int *intp; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) { /* LINTED E_BAD_PTR_CAST_ALIGN */ intp = (int *)(CMSG_DATA(cmsg)); *intp = hoplimit; return (_B_TRUE); } } return (_B_FALSE); } /* * send a probe packet to the destination */ void send_probe6(int sndsock, struct msghdr *msg6, struct ip *outip, int seq, int ttl, struct timeval *tp, int packlen) { uchar_t *outp; struct icmp *outicmp; struct outdata *outdata; struct iovec iov; int cc; int optlen = 0; int send_size; struct sockaddr_in6 *to6; if (gw_count > 0) { /* ip6_rthdr0 structure includes one gateway address */ optlen = sizeof (struct ip6_rthdr0) + gw_count * sizeof (struct in6_addr); } send_size = packlen - sizeof (struct ip6_hdr) - optlen; /* if using UDP, further discount UDP header size */ if (!useicmp) send_size -= sizeof (struct udphdr); /* initialize buffer pointers */ outp = (uchar_t *)(outip + 1); /* LINTED E_BAD_PTR_CAST_ALIGN */ outicmp = (struct icmp *)outp; /* LINTED E_BAD_PTR_CAST_ALIGN */ outdata = (struct outdata *)(outp + ICMP6_MINLEN); if (!update_hoplimit_ancillary_data(msg6, ttl)) { Fprintf(stderr, "%s: can't find IPV6_HOPLIMIT ancillary data\n", prog); exit(EXIT_FAILURE); } /* Payload */ outdata->seq = seq; outdata->ttl = ttl; outdata->tv = *tp; if (useicmp) { outicmp->icmp_seq = htons(seq); } else { to6 = (struct sockaddr_in6 *)msg6->msg_name; to6->sin6_port = htons((port + seq) % (MAX_PORT + 1)); } iov.iov_base = outp; iov.iov_len = send_size; msg6->msg_iov = &iov; msg6->msg_iovlen = 1; cc = sendmsg(sndsock, msg6, 0); if (cc < 0 || cc != send_size) { if (cc < 0) { Fprintf(stderr, "%s: sendmsg: %s\n", prog, strerror(errno)); } Printf("%s: wrote %s %d chars, ret=%d\n", prog, hostname, send_size, cc); (void) fflush(stdout); } } /* * Return a pointer to the ancillary data for the given cmsg_level and * cmsg_type. * If not found return NULL. */ void * find_ancillary_data(struct msghdr *msg, int cmsg_level, int cmsg_type) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == cmsg_level && cmsg->cmsg_type == cmsg_type) { return (CMSG_DATA(cmsg)); } } return (NULL); } /* * Check out the reply packet to see if it's what we were expecting. * Returns REPLY_GOT_TARGET if the reply comes from the target * REPLY_GOT_GATEWAY if an intermediate gateway sends TIME_EXCEEDED * REPLY_GOT_OTHER for other kinds of unreachables indicating none of * the above two cases * * It also sets the icmp type and icmp code values */ int check_reply6(struct msghdr *msg, int cc, int seq, uchar_t *type, uchar_t *code) { uchar_t *buf = msg->msg_iov->iov_base; struct sockaddr_in6 *from_in6 = (struct sockaddr_in6 *)msg->msg_name; icmp6_t *icp6; ulong_t ip6hdr_len; uint8_t last_hdr; int save_cc = cc; char temp_buf[INET6_ADDRSTRLEN]; /* use for inet_ntop() */ /* Ignore packets > 64k or control buffers that don't fit */ if (msg->msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { if (verbose) { Printf("Truncated message: msg_flags 0x%x from %s\n", msg->msg_flags, inet_ntop(AF_INET6, (void *)&(from_in6->sin6_addr), temp_buf, sizeof (temp_buf))); } return (REPLY_SHORT_PKT); } if (cc < ICMP6_MINLEN) { if (verbose) { Printf("packet too short (%d bytes) from %s\n", cc, inet_ntop(AF_INET6, (void *)&(from_in6->sin6_addr), temp_buf, sizeof (temp_buf))); } return (REPLY_SHORT_PKT); } /* LINTED E_BAD_PTR_CAST_ALIGN */ icp6 = (icmp6_t *)buf; *type = icp6->icmp6_type; *code = icp6->icmp6_code; /* * traceroute interprets only ICMP6_TIME_EXCEED_TRANSIT, * ICMP6_DST_UNREACH, ICMP6_ECHO_REPLY, ICMP6_PACKET_TOO_BIG and * ICMP6_PARAMPROB_NEXTHEADER, ignores others */ if ((*type == ICMP6_TIME_EXCEEDED && *code == ICMP6_TIME_EXCEED_TRANSIT) || *type == ICMP6_DST_UNREACH || *type == ICMP6_ECHO_REPLY || *type == ICMP6_PACKET_TOO_BIG || (*type == ICMP6_PARAM_PROB && *code == ICMP6_PARAMPROB_NEXTHEADER)) { ip6_t *hip6; struct udphdr *up; icmp6_t *hicmp6; cc -= ICMP6_MINLEN; hip6 = (ip6_t *)&(icp6->icmp6_data32[1]); last_hdr = hip6->ip6_nxt; ip6hdr_len = IPv6_hdrlen(hip6, cc, &last_hdr); cc -= ip6hdr_len; if (useicmp) { if (*type == ICMP6_ECHO_REPLY && icp6->icmp6_id == htons(ident) && icp6->icmp6_seq == htons(seq)) { return (REPLY_GOT_TARGET); } /* LINTED E_BAD_PTR_CAST_ALIGN */ hicmp6 = (icmp6_t *)((uchar_t *)hip6 + ip6hdr_len); if (ICMP6_MINLEN <= cc && last_hdr == IPPROTO_ICMPV6 && hicmp6->icmp6_id == htons(ident) && hicmp6->icmp6_seq == htons(seq)) { if (*type == ICMP6_TIME_EXCEEDED) { return (REPLY_GOT_GATEWAY); } else { return (REPLY_GOT_OTHER); } } } else { /* LINTED E_BAD_PTR_CAST_ALIGN */ up = (struct udphdr *)((uchar_t *)hip6 + ip6hdr_len); /* * at least 4 bytes of UDP header is required for this * check */ if (4 <= cc && last_hdr == IPPROTO_UDP && up->uh_sport == htons(ident) && up->uh_dport == htons((port + seq) % (MAX_PORT + 1))) { if (*type == ICMP6_DST_UNREACH && *code == ICMP6_DST_UNREACH_NOPORT) { return (REPLY_GOT_TARGET); } else if (*type == ICMP6_TIME_EXCEEDED) { return (REPLY_GOT_GATEWAY); } else { return (REPLY_GOT_OTHER); } } } } if (verbose) { int i, j; uchar_t *lp = (uchar_t *)icp6; struct in6_addr *dst; struct in6_pktinfo *pkti; pkti = (struct in6_pktinfo *)find_ancillary_data(msg, IPPROTO_IPV6, IPV6_PKTINFO); if (pkti == NULL) { Fprintf(stderr, "%s: can't find IPV6_PKTINFO ancillary data\n", prog); exit(EXIT_FAILURE); } dst = &pkti->ipi6_addr; cc = save_cc; Printf("\n%d bytes from %s to ", cc, inet_ntop(AF_INET6, (const void *)&(from_in6->sin6_addr), temp_buf, sizeof (temp_buf))); Printf("%s: icmp type %d (%s) code %d\n", inet_ntop(AF_INET6, (const void *)dst, temp_buf, sizeof (temp_buf)), *type, pr_type6(*type), *code); for (i = 0; i < cc; i += 4) { Printf("%2d: x", i); for (j = 0; ((j < 4) && ((i + j) < cc)); j++) Printf("%2.2x", *lp++); (void) putchar('\n'); } } return (REPLY_SHORT_PKT); } /* * Return the length of the IPv6 related headers (including extension headers) */ static int IPv6_hdrlen(ip6_t *ip6h, int pkt_len, uint8_t *last_hdr_rtrn) { int length; int exthdrlength; uint8_t nexthdr; uint8_t *whereptr; ip6_hbh_t *hbhhdr; ip6_dest_t *desthdr; ip6_rthdr_t *rthdr; ip6_frag_t *fraghdr; uint8_t *endptr; length = sizeof (ip6_t); whereptr = ((uint8_t *)&ip6h[1]); /* point to next hdr */ endptr = ((uint8_t *)ip6h) + pkt_len; nexthdr = ip6h->ip6_nxt; *last_hdr_rtrn = IPPROTO_NONE; if (whereptr >= endptr) return (length); while (whereptr < endptr) { *last_hdr_rtrn = nexthdr; switch (nexthdr) { case IPPROTO_HOPOPTS: hbhhdr = (ip6_hbh_t *)whereptr; exthdrlength = 8 * (hbhhdr->ip6h_len + 1); if ((uchar_t *)hbhhdr + exthdrlength > endptr) return (length); nexthdr = hbhhdr->ip6h_nxt; length += exthdrlength; break; case IPPROTO_DSTOPTS: desthdr = (ip6_dest_t *)whereptr; exthdrlength = 8 * (desthdr->ip6d_len + 1); if ((uchar_t *)desthdr + exthdrlength > endptr) return (length); nexthdr = desthdr->ip6d_nxt; length += exthdrlength; break; case IPPROTO_ROUTING: rthdr = (ip6_rthdr_t *)whereptr; exthdrlength = 8 * (rthdr->ip6r_len + 1); if ((uchar_t *)rthdr + exthdrlength > endptr) return (length); nexthdr = rthdr->ip6r_nxt; length += exthdrlength; break; case IPPROTO_FRAGMENT: /* LINTED E_BAD_PTR_CAST_ALIGN */ fraghdr = (ip6_frag_t *)whereptr; if ((uchar_t *)&fraghdr[1] > endptr) return (length); nexthdr = fraghdr->ip6f_nxt; length += sizeof (struct ip6_frag); break; case IPPROTO_NONE: default: return (length); } whereptr = (uint8_t *)ip6h + length; } *last_hdr_rtrn = nexthdr; return (length); } /* * convert an ICMP6 "type" field to a printable string. */ static char * pr_type6(uchar_t type) { static struct icmptype_table ttab6[] = { {ICMP6_DST_UNREACH, "Dest Unreachable"}, {ICMP6_PACKET_TOO_BIG, "Packet Too Big"}, {ICMP6_TIME_EXCEEDED, "Time Exceeded"}, {ICMP6_PARAM_PROB, "Param Problem"}, {ICMP6_ECHO_REQUEST, "Echo Request"}, {ICMP6_ECHO_REPLY, "Echo Reply"}, {MLD_LISTENER_QUERY, "Multicast Listener Query"}, {MLD_LISTENER_REPORT, "Multicast Listener Report"}, {MLD_LISTENER_REDUCTION, "Multicast Listener Done"}, {ND_ROUTER_SOLICIT, "Router Solicitation"}, {ND_ROUTER_ADVERT, "Router Advertisement"}, {ND_NEIGHBOR_SOLICIT, "Neighbor Solicitation"}, {ND_NEIGHBOR_ADVERT, "Neighbor Advertisement"}, {ND_REDIRECT, "Redirect Message"} }; int i = 0; for (i = 0; i < A_CNT(ttab6); i++) { if (ttab6[i].type == type) return (ttab6[i].message); } return ("OUT-OF-RANGE"); } /* * print the IPv6 src address of the reply packet */ void print_addr6(uchar_t *buf, int cc, struct sockaddr *from) { /* LINTED E_BAD_PTR_CAST_ALIGN */ struct sockaddr_in6 *from_in6 = (struct sockaddr_in6 *)from; ip6_t *ip; union any_in_addr ip_addr; char *resolved_name; char temp_buf[INET6_ADDRSTRLEN]; /* use for inet_ntop() */ ip_addr.addr6 = from_in6->sin6_addr; /* LINTED E_BAD_PTR_CAST_ALIGN */ ip = (ip6_t *)buf; (void) inet_ntop(AF_INET6, &(from_in6->sin6_addr), temp_buf, sizeof (temp_buf)); if (!nflag) resolved_name = inet_name(&ip_addr, AF_INET6); /* * If the IPv6 address cannot be resolved to hostname, inet_name() * returns the IPv6 address as a string. In that case, we choose not * to print it twice. This saves us space on display. */ if (nflag || (strcmp(temp_buf, resolved_name) == 0)) Printf(" %s", temp_buf); else Printf(" %s (%s)", resolved_name, temp_buf); if (verbose) { Printf(" %d bytes to %s", cc, inet_ntop(AF_INET6, (const void *) &(ip->ip6_dst), temp_buf, sizeof (temp_buf))); } } /* * ICMP6 messages which doesn't mean we got the target, or we got a gateway, are * processed here. It returns _B_TRUE if it's some sort of 'unreachable'. */ boolean_t print_icmp_other6(uchar_t type, uchar_t code) { boolean_t unreach = _B_FALSE; switch (type) { /* this corresponds to "ICMP_UNREACH_NEEDFRAG" in ICMP */ case ICMP6_PACKET_TOO_BIG: unreach = _B_TRUE; Printf(" !B"); break; case ICMP6_PARAM_PROB: /* this corresponds to "ICMP_UNREACH_PROTOCOL" in ICMP */ if (code == ICMP6_PARAMPROB_NEXTHEADER) { unreach = _B_TRUE; Printf(" !R"); } break; case ICMP6_DST_UNREACH: switch (code) { case ICMP6_DST_UNREACH_NOPORT: break; case ICMP6_DST_UNREACH_NOROUTE: unreach = _B_TRUE; Printf(" !H"); break; case ICMP6_DST_UNREACH_ADMIN: unreach = _B_TRUE; Printf(" !X"); break; case ICMP6_DST_UNREACH_ADDR: unreach = _B_TRUE; Printf(" !A"); break; case ICMP6_DST_UNREACH_NOTNEIGHBOR: unreach = _B_TRUE; Printf(" !E"); break; default: unreach = _B_TRUE; Printf(" !<%d>", code); break; } break; default: break; } return (unreach); }