/* $KAME: mtrace6.c,v 1.24 2005/04/14 06:22:34 suz Exp $ */ /* * Copyright (C) 1999 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project 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 BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #endif #include #ifdef HAVE_NETINET6_IN6_VAR_H #include #endif #include #include #include #include #include #include #include #include #include "trace.h" static char *gateway, *intface, *source, *group, *receiver, *destination; static char *linkstr; static int mldsoc, hops = 64, maxhops = 127, waittime = 3, querylen, opt_n; static struct sockaddr *gw_sock, *src_sock, *grp_sock, *dst_sock, *rcv_sock; static char *querypacket; static char frombuf[1024]; /* XXX: enough size? */ int main __P((int, char *[])); static char *proto_type __P((u_int)); static char *pr_addr __P((struct sockaddr *, int)); static void setqid __P((int, char *)); static void mtrace_loop __P((void)); static char *str_rflags __P((int)); static void show_ip6_result __P((struct sockaddr_in6 *, int)); static void show_result __P((struct sockaddr *, int)); static void set_sockaddr __P((char *, struct addrinfo *, struct sockaddr *, size_t)); static int is_multicast __P((struct sockaddr *)); static char *all_routers_str __P((int)); static int ip6_validaddr __P((char *, struct sockaddr_in6 *)); static int get_my_sockaddr __P((int, struct sockaddr *, size_t)); static void set_hlim __P((int, struct sockaddr *, int)); static void set_join __P((int, char *, struct sockaddr *)); static void set_filter __P((int, int)); static void open_socket __P((void)); static void make_ip6_packet __P((void)); static void make_packet __P((void)); static void usage __P((void)); static socklen_t get_sa_len(struct sockaddr *); int main(argc, argv) int argc; char *argv[]; { int op; /* get parameters */ while((op = getopt(argc, argv, "d:g:h:i:l:m:nr:w:")) != -1) { switch(op) { case 'd': destination = optarg; break; case 'g': gateway = optarg; break; case 'h': hops = atoi(optarg); if (hops < 0 || hops > 255) { warnx("query/response hops must be between 0 and 255"); usage(); } break; case 'i': intface = optarg; break; case 'l': linkstr = optarg; break; case 'm': maxhops = atoi(optarg); if (maxhops < 0 || maxhops > 255) { warnx("maxhops must be between 0 and 255"); usage(); } break; case 'n': opt_n = 1; break; case 'r': receiver = optarg; break; case 'w': waittime = atoi(optarg); break; case '?': default: usage(); break; } } argc -= optind; argv += optind; if (argc < 2) usage(); source = argv[0]; group = argv[1]; /* option consistency check */ if (gateway && linkstr) errx(1, "-g and -l are exclusive"); /* examine addresses and open a socket */ open_socket(); /* construct a query packet according to the specified parameters */ make_packet(); mtrace_loop(); exit(0); /*NOTREACHED*/ } static char * proto_type(type) u_int type; { static char buf[80]; switch (type) { case PROTO_DVMRP: return ("DVMRP"); case PROTO_MOSPF: return ("MOSPF"); case PROTO_PIM: return ("PIM"); case PROTO_CBT: return ("CBT"); case PROTO_PIM_SPECIAL: return ("PIM/Special"); case PROTO_PIM_STATIC: return ("PIM/Static"); case PROTO_DVMRP_STATIC: return ("DVMRP/Static"); case 0: return ("None"); default: (void) sprintf(buf, "Unknown protocol code %d", type); return (buf); } } static char * pr_addr(addr, numeric) struct sockaddr *addr; int numeric; { static char buf[MAXHOSTNAMELEN]; int flag = 0; if (numeric) flag |= NI_NUMERICHOST; getnameinfo(addr, get_sa_len(addr), buf, sizeof(buf), NULL, 0, flag); return (buf); } static void setqid(family, query) int family; char *query; { struct tr6_query *q6; switch(family) { case AF_INET6: q6 = (struct tr6_query *)((struct mld_hdr *)query + 1); q6->tr_qid = (u_int32_t)random(); } } static void mtrace_loop() { int nsoc, fromlen, rcvcc; struct timeval tv, tv_wait; struct fd_set *fdsp; size_t nfdsp; struct sockaddr_storage from_ss; struct sockaddr *from_sock = (struct sockaddr *)&from_ss; /* initializa random number of query ID */ gettimeofday(&tv, 0); srandom(tv.tv_usec); while(1) { /* XXX */ setqid(gw_sock->sa_family, querypacket); if (sendto(mldsoc, (void *)querypacket, querylen, 0, gw_sock, get_sa_len(gw_sock)) < 0) err(1, "sendto"); tv_wait.tv_sec = waittime; tv_wait.tv_usec = 0; nfdsp = howmany(mldsoc + 1, NFDBITS) * sizeof(fd_mask); fdsp = malloc(nfdsp); if (!fdsp) err(1, "malloc"); memset(fdsp, 0, nfdsp); FD_SET(mldsoc, fdsp); if ((nsoc = select(mldsoc + 1, fdsp, NULL, NULL, &tv_wait)) < 0) err(1, "select"); free(fdsp); if (nsoc == 0) { printf("Timeout\n"); exit(0); /* XXX try again? */ } fromlen = sizeof(from_ss); if ((rcvcc = recvfrom(mldsoc, frombuf, sizeof(frombuf), 0, from_sock, &fromlen)) < 0) err(1, "recvfrom"); show_result(from_sock, rcvcc); exit(0); /* XXX */ } } char *fwd_code[] = {"NOERR", "WRONGIF", "SPRUNE", "RPRUNE", "SCOPED", "NORT", "WRONGLH", "NOFWD", "RP", "RPFIF", "NOMC", "HIDDEN"}; char *fwd_errcode[] = {"", "NOSPC", "OLD", "ADMIN"}; static char * str_rflags(flag) int flag; { if (0x80 & flag) { /* fatal error */ flag &= ~0x80; if (flag >= sizeof(fwd_errcode) / sizeof(char *) || flag == 0) { warnx("unknown error code(%d)", flag); return("UNKNOWN"); } return(fwd_errcode[flag]); } /* normal code */ if (flag >= sizeof(fwd_code) / sizeof(char *)) { warnx("unknown forward code(%d)", flag); return("UNKNOWN"); } return(fwd_code[flag]); } static void show_ip6_result(from6, datalen) struct sockaddr_in6 *from6; int datalen; { struct mld_hdr *mld6_tr_resp = (struct mld_hdr *)frombuf; struct mld_hdr *mld6_tr_query = (struct mld_hdr *)querypacket; struct tr6_query *tr6_rquery = (struct tr6_query *)(mld6_tr_resp + 1); struct tr6_query *tr6_query = (struct tr6_query *)(mld6_tr_query + 1); struct tr6_resp *tr6_resp = (struct tr6_resp *)(tr6_rquery + 1), *rp, *rp_end; int i; if (datalen < sizeof(*mld6_tr_resp) + sizeof(*tr6_rquery) + sizeof(*tr6_resp)) { warnx("show_ip6_result: receive data length(%d) is short", datalen); return; } switch(mld6_tr_resp->mld_type) { case MLD_MTRACE_RESP: if ((datalen - sizeof(*mld6_tr_resp) - sizeof(*tr6_rquery)) % sizeof(*tr6_resp)) { warnx("show_ip6_result: incomplete response (%d bytes)", datalen); return; } rp_end = (struct tr6_resp *)((char *)mld6_tr_resp + datalen); /* sanity check for the response */ if (tr6_query->tr_qid != tr6_rquery->tr_qid || !IN6_ARE_ADDR_EQUAL(&tr6_query->tr_src, &tr6_rquery->tr_src) || !IN6_ARE_ADDR_EQUAL(&tr6_query->tr_dst, &tr6_rquery->tr_dst)) return; /* XXX: bark here? */ for (i = 0, rp = tr6_resp; rp < rp_end; i++, rp++) { struct sockaddr_in6 sa_resp, sa_upstream; /* reinitialize the sockaddr. paranoid? */ memset((void *)&sa_resp, 0, sizeof(sa_resp)); sa_resp.sin6_family = AF_INET6; #ifdef HAVE_SA_LEN sa_resp.sin6_len = sizeof(sa_resp); #endif memset((void *)&sa_upstream, 0, sizeof(sa_upstream)); sa_upstream.sin6_family = AF_INET6; #ifdef HAVE_SA_LEN sa_upstream.sin6_len = sizeof(sa_upstream); #endif sa_resp.sin6_addr = rp->tr_lcladdr; sa_upstream.sin6_addr = rp->tr_rmtaddr; /* print information for the router */ printf("%3d ", -i);/* index */ /* router address and incoming/outgoing interface */ printf("%s", pr_addr((struct sockaddr *)&sa_resp, opt_n)); printf("(%s/%ld->%ld) ", pr_addr((struct sockaddr *)&sa_upstream, 1), ntohl(rp->tr_inifid), ntohl(rp->tr_outifid)); /* multicast routing protocol type */ printf("%s ", proto_type(rp->tr_rproto)); /* forwarding error code */ printf("%s", str_rflags(rp->tr_rflags & 0xff)); putchar('\n'); } break; default: /* impossible... */ warnx("show_ip6_result: invalid ICMPv6 type(%d)", mld6_tr_resp->mld_type); /* assert? */ break; } } static void show_result(from, datalen) struct sockaddr *from; int datalen; { switch(from->sa_family) { case AF_INET6: show_ip6_result((struct sockaddr_in6 *)from, datalen); break; default: errx(1, "show_result: illegal AF(%d) on recv", from->sa_family); } } static void set_sockaddr(addrname, hints, sap, l) char *addrname; struct addrinfo *hints; struct sockaddr *sap; size_t l; { struct addrinfo *res; int ret_ga; ret_ga = getaddrinfo(addrname, NULL, hints, &res); if (ret_ga) errx(1, "getaddrinfo faild: %s", gai_strerror(ret_ga)); if (!res->ai_addr) errx(1, "getaddrinfo failed"); if (res->ai_addrlen > l) errx(1, "sockaddr too big"); memcpy((void *)sap, (void *)res->ai_addr, get_sa_len(res->ai_addr)); freeaddrinfo(res); } static int is_multicast(sa) struct sockaddr *sa; { switch(sa->sa_family) { case AF_INET6: if (IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)sa)->sin6_addr)) return 1; else return 0; break; default: return 0; /* XXX: support IPv4? */ } } static char * all_routers_str(family) int family; { static char s[NI_MAXHOST]; switch(family) { case AF_INET6: if (linkstr) snprintf(s, sizeof(s), "ff02::1%%%s", linkstr); else /* use the default link (assuming it) */ snprintf(s, sizeof(s), "ff02::1"); return (s); default: errx(1, "all_routers_str: unknown AF(%d)", family); } } static int ip6_validaddr(ifname, addr) char *ifname; struct sockaddr_in6 *addr; { int s; struct in6_ifreq ifr6; u_int32_t flags6; /* we need a global address only...XXX: should be flexible? */ if (IN6_IS_ADDR_LOOPBACK(&addr->sin6_addr) || IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr) || IN6_IS_ADDR_SITELOCAL(&addr->sin6_addr)) return(0); /* get IPv6 dependent flags and examine them */ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) err(1, "ip6_validaddr: socket"); #ifdef HAVE_STRLCPY strlcpy(ifr6.ifr_name, ifname, sizeof(ifr6.ifr_name)); #elif HAVE_STRNCPY strncpy(ifr6.ifr_name, ifname, sizeof(ifr6.ifr_name)); #else strcpy(ifr6.ifr_name, ifname); #endif ifr6.ifr_addr = *addr; if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) < 0) err(1, "ioctl(SIOCGIFAFLAG_IN6)"); close(s); flags6 = ifr6.ifr_ifru.ifru_flags6; if (flags6 & (IN6_IFF_ANYCAST | IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED)) return(0); return(1); } static int get_my_sockaddr(family, addrp, l) int family; struct sockaddr *addrp; size_t l; { struct ifaddrs *ifap, *ifa; if (getifaddrs(&ifap) != 0) { err(1, "getifaddrs"); /*NOTREACHED */ } for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr->sa_family != family) continue; if (get_sa_len(ifa->ifa_addr) > l) continue; switch(family) { case AF_INET6: if (ip6_validaddr(ifa->ifa_name, (struct sockaddr_in6 *)ifa->ifa_addr)) goto found; } } freeifaddrs(ifap); return (-1); /* not found */ found: memcpy((void *)addrp, (void *)ifa->ifa_addr, get_sa_len(ifa->ifa_addr)); freeifaddrs(ifap); return (0); } static void set_hlim(s, addr, hops) int s, hops; struct sockaddr *addr; { struct sockaddr_in6 *sin6; int opt; switch(addr->sa_family) { case AF_INET6: sin6 = (struct sockaddr_in6 *)addr; opt = IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr) ? IPV6_MULTICAST_HOPS : IPV6_UNICAST_HOPS; if (setsockopt(s, IPPROTO_IPV6, opt, (char *)&hops, sizeof(hops)) == -1) err(1, "setsockopt(%s)", (opt == IPV6_MULTICAST_HOPS) ? "IPV6_MULTICAST_HOPS" : "IPV6_UNICAST_HOPS"); break; } } static void set_join(s, ifname, group) int s; char *ifname; struct sockaddr *group; { struct ipv6_mreq mreq6; u_int ifindex; switch(group->sa_family) { case AF_INET6: if ((ifindex = if_nametoindex(ifname)) == 0) err(1, "set_join: if_nametoindex failed for %s", ifname); mreq6.ipv6mr_multiaddr = ((struct sockaddr_in6 *)group)->sin6_addr; mreq6.ipv6mr_interface = ifindex; if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)) < 0) err(1, "setsockopt(IPV6_JOIN_GROUP)"); break; } } static void set_filter(s, family) int s, family; { struct icmp6_filter filter6; switch(family) { case AF_INET6: ICMP6_FILTER_SETBLOCKALL(&filter6); ICMP6_FILTER_SETPASS(MLD_MTRACE_RESP, &filter6); if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter6, sizeof(filter6)) < 0) err(1, "setsockopt(ICMP6_FILTER)"); break; } } static void open_socket() { struct addrinfo hints; static struct sockaddr_storage gw_ss, src_ss, grp_ss, dst_ss, rcv_ss; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; /* to be independent of AF? */ hints.ai_socktype = SOCK_RAW; hints.ai_protocol = IPPROTO_ICMPV6; /* multicast group(must be specified) */ grp_sock = (struct sockaddr *)&grp_ss; set_sockaddr(group, &hints, grp_sock, sizeof(grp_ss)); if (!is_multicast(grp_sock)) errx(1, "group(%s) is not a multicast address", group); /* multicast source(must be specified) */ src_sock = (struct sockaddr *)&src_ss; set_sockaddr(source, &hints, src_sock, sizeof(src_ss)); if (is_multicast(src_sock)) errx(1, "source(%s) is not a unicast address", source); /* last hop gateway for the destination(if specified) */ gw_sock = (struct sockaddr *)&gw_ss; if (gateway) /* can be either multicast or unicast */ set_sockaddr(gateway, &hints, gw_sock, sizeof(gw_ss)); else { char *r = all_routers_str(grp_sock->sa_family); set_sockaddr(r, &hints, gw_sock, sizeof(gw_ss)); } /* destination address for the trace */ dst_sock = (struct sockaddr *)&dst_ss; if (destination) { set_sockaddr(destination, &hints, dst_sock, sizeof(dst_ss)); if (is_multicast(dst_sock)) errx(1, "destination(%s) is not a unicast address", destination); } else { /* XXX: consider interface? */ get_my_sockaddr(grp_sock->sa_family, dst_sock, sizeof(dst_ss)); } /* response receiver(if specified) */ rcv_sock = (struct sockaddr *)&rcv_ss; if (receiver) { /* can be either multicast or unicast */ set_sockaddr(receiver, &hints, rcv_sock, sizeof(rcv_ss)); if (is_multicast(rcv_sock) && intface == NULL) { #ifdef notyet warnx("receive I/F is not specified for multicast" "response(%s)", receiver); intface = default_intface; #else errx(1, "receive I/F is not specified for multicast" "response(%s)", receiver); #endif } } else { /* XXX: consider interface? */ get_my_sockaddr(grp_sock->sa_family, rcv_sock, sizeof(rcv_ss)); } if ((mldsoc = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol)) < 0) err(1, "socket"); /* set necessary socket options */ if (hops) set_hlim(mldsoc, gw_sock, hops); if (receiver && is_multicast(rcv_sock)) set_join(mldsoc, intface, rcv_sock); set_filter(mldsoc, grp_sock->sa_family); } static void make_ip6_packet() { struct mld_hdr *mld6_tr_query; struct tr6_query *tr6_query; querylen = sizeof(*mld6_tr_query) + sizeof(*tr6_query); if ((querypacket = malloc(querylen)) == NULL) errx(1, "make_ip6_packet: malloc failed"); memset(querypacket, 0, querylen); /* fill in MLD header */ mld6_tr_query = (struct mld_hdr *)querypacket; mld6_tr_query->mld_type = MLD_MTRACE; mld6_tr_query->mld_code = maxhops & 0xff; mld6_tr_query->mld_addr = ((struct sockaddr_in6 *)grp_sock)->sin6_addr; /* fill in mtrace query fields */ tr6_query = (struct tr6_query *)(mld6_tr_query + 1); tr6_query->tr_src = ((struct sockaddr_in6 *)src_sock)->sin6_addr; tr6_query->tr_dst = ((struct sockaddr_in6 *)dst_sock)->sin6_addr; tr6_query->tr_raddr = ((struct sockaddr_in6 *)rcv_sock)->sin6_addr; tr6_query->tr_rhlim = 0xff & hops; } static void make_packet() { switch(grp_sock->sa_family) { case AF_INET6: make_ip6_packet(); break; default: errx(1, "make_packet: unsupported AF(%d)", grp_sock->sa_family); } } static void usage() { fprintf(stderr, "usage: mtrace6 " "[-d destination] [-g gateway] [-h hops] [-i interface] " "[-l link] [-m maxhops] [-n] [-r response_addr] [-w waittime] " "source group\n"); exit(1); } static socklen_t get_sa_len(struct sockaddr *addr) { #ifdef HAVE_SA_LEN return addr->sa_len; #else switch (addr->sa_family) { case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); default: return sizeof(struct sockaddr); } #endif }