/* $NetBSD: mtrace6.c,v 1.7 2002/03/04 01:38:32 wiz Exp $ */ /* $KAME: mtrace6.c,v 1.16 2000/12/04 06:45:34 itojun 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. */ #include #include #include #include #include #include #include #if defined(__FreeBSD__) && __FreeBSD__ >= 3 #include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETIFADDRS #include #endif #include "trace.h" static char *gateway, *intface, *source, *group, *receiver, *destination; 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 *)); 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 *)); 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)); int main(argc, argv) int argc; char *argv[]; { int op; /* get parameters */ while((op = getopt(argc, argv, "d:g:h:i: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 '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]; /* 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; flag |= NI_WITHSCOPEID; getnameinfo(addr, addr->sa_len, 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 mld6_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, gw_sock->sa_len) < 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 mld6_hdr *mld6_tr_resp = (struct mld6_hdr *)frombuf; struct mld6_hdr *mld6_tr_query = (struct mld6_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->mld6_type) { case MLD6_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; sa_resp.sin6_len = sizeof(sa_resp); memset((void *)&sa_upstream, 0, sizeof(sa_upstream)); sa_upstream.sin6_family = AF_INET6; sa_upstream.sin6_len = sizeof(sa_upstream); 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/%d->%d) ", 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->mld6_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) char *addrname; struct addrinfo *hints; struct sockaddr *sap; { struct addrinfo *res; int ret_ga; ret_ga = getaddrinfo(addrname, NULL, hints, &res); if (ret_ga) errx(1, "getaddrinfo failed: %s", gai_strerror(ret_ga)); if (!res->ai_addr) errx(1, "getaddrinfo failed"); memcpy((void *)sap, (void *)res->ai_addr, res->ai_addr->sa_len); 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; { switch(family) { case AF_INET6: return("ff02::1"); 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"); strncpy(ifr6.ifr_name, ifname, sizeof(ifr6.ifr_name)); 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) int family; struct sockaddr *addrp; { #ifdef HAVE_GETIFADDRS 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) { 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, ifa->ifa_addr->sa_len); freeifaddrs(ifap); return (0); #else #define IF_BUFSIZE 8192 /* XXX: adhoc...should be customizable? */ int i, s; struct ifconf ifconf; struct ifreq *ifrp; static char ifbuf[IF_BUFSIZE]; if ((s = socket(family, SOCK_DGRAM, 0)) < 0) err(1, "socket"); ifconf.ifc_buf = ifbuf; ifconf.ifc_len = sizeof(ifbuf); if (ioctl(s, SIOCGIFCONF, (char *)&ifconf) < 0) err(1, "ioctl(SIOCGIFCONF)"); close(s); for (i = 0; i < ifconf.ifc_len; ) { ifrp = (struct ifreq *)(ifbuf + i); if (ifrp->ifr_addr.sa_family == family) { switch(family) { case AF_INET6: if (ip6_validaddr(ifrp->ifr_name, (struct sockaddr_in6 *)&ifrp->ifr_addr)) goto found; } } i += IFNAMSIZ; /* i += max(sizeof(sockaddr), ifr_addr.sa_len) */ if (ifrp->ifr_addr.sa_len > sizeof(struct sockaddr)) i += ifrp->ifr_addr.sa_len; else i += sizeof(struct sockaddr); } return(-1); /* not found */ found: memcpy((void *)addrp, (void *)&ifrp->ifr_addr, ifrp->ifr_addr.sa_len); return(0); #undef IF_BUFSIZE #endif } 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(MLD6_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); 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); 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); else { char *r = all_routers_str(grp_sock->sa_family); set_sockaddr(r, &hints, gw_sock); } /* destination address for the trace */ dst_sock = (struct sockaddr *)&dst_ss; if (destination) { set_sockaddr(destination, &hints, dst_sock); 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); } /* response receiver(if specified) */ rcv_sock = (struct sockaddr *)&rcv_ss; if (receiver) { /* can be either multicast or unicast */ set_sockaddr(receiver, &hints, rcv_sock); 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); } 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 mld6_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 mld6_hdr *)querypacket; mld6_tr_query->mld6_type = MLD6_MTRACE; mld6_tr_query->mld6_code = maxhops & 0xff; mld6_tr_query->mld6_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 %s\n", "[-d destination] [-g gateway] [-h hops] [-i interface] " "[-m maxhops] [-n] [-r response_addr] [-w waittime] source group"); exit(1); }