/* $NetBSD: mld6.c,v 1.9 2000/12/04 07:05:48 itojun Exp $ */ /* $KAME: mld6.c,v 1.17 2000/12/04 06:33:10 itojun Exp $ */ /* * Copyright (C) 1998 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. */ /* * Copyright (c) 1998 by the University of Southern California. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and * its documentation in source and binary forms for lawful * purposes and without fee is hereby granted, provided * that the above copyright notice appear in all copies and that both * the copyright notice and this permission notice appear in supporting * documentation, and that any documentation, advertising materials, * and other materials related to such distribution and use acknowledge * that the software was developed by the University of Southern * California and/or Information Sciences Institute. * The name of the University of Southern California may not * be used to endorse or promote products derived from this software * without specific prior written permission. * * THE UNIVERSITY OF SOUTHERN CALIFORNIA DOES NOT MAKE ANY REPRESENTATIONS * ABOUT THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. 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, TITLE, AND * NON-INFRINGEMENT. * * IN NO EVENT SHALL USC, OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, WHETHER IN CONTRACT, * TORT, OR OTHER FORM OF ACTION, ARISING OUT OF OR IN CONNECTION WITH, * THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Other copyrights might apply to parts of this software and are so * noted when applicable. */ /* * Questions concerning this software should be directed to * Pavlin Ivanov Radoslavov (pavlin@catarina.usc.edu) */ /* * Part of this program has been derived from mrouted. * The mrouted program is covered by the license in the accompanying file * named "LICENSE.mrouted". * * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of * Leland Stanford Junior University. * */ #include "defs.h" #include /* * Exported variables. */ char *mld6_recv_buf; /* input packet buffer */ char *mld6_send_buf; /* output packet buffer */ int mld6_socket; /* socket for all network I/O */ struct sockaddr_in6 allrouters_group = {sizeof(struct sockaddr_in6), AF_INET6}; struct sockaddr_in6 allnodes_group = {sizeof(struct sockaddr_in6), AF_INET6}; /* Extenals */ extern struct in6_addr in6addr_linklocal_allnodes; /* local variables. */ static struct sockaddr_in6 dst = {sizeof(dst), AF_INET6}; static struct msghdr sndmh, rcvmh; static struct iovec sndiov[2]; static struct iovec rcviov[2]; static struct sockaddr_in6 from; static u_char *rcvcmsgbuf = NULL; static int rcvcmsglen; #ifndef USE_RFC2292BIS u_int8_t raopt[IP6OPT_RTALERT_LEN]; #endif static char *sndcmsgbuf; static int ctlbuflen = 0; static u_short rtalert_code; /* local functions */ static void mld6_read __P((int i, fd_set * fds)); static void accept_mld6 __P((int len)); static void make_mld6_msg __P((int, int, struct sockaddr_in6 *, struct sockaddr_in6 *, struct in6_addr *, int, int, int, int)); #ifndef IP6OPT_ROUTER_ALERT /* XXX to be compatible older systems */ #define IP6OPT_ROUTER_ALERT IP6OPT_RTALERT #endif /* * Open and initialize the MLD socket. */ void init_mld6() { struct icmp6_filter filt; int on; rtalert_code = htons(IP6OPT_RTALERT_MLD); if (!mld6_recv_buf && (mld6_recv_buf = malloc(RECV_BUF_SIZE)) == NULL) log(LOG_ERR, 0, "malloc failed"); if (!mld6_send_buf && (mld6_send_buf = malloc(RECV_BUF_SIZE)) == NULL) log(LOG_ERR, 0, "malloc failed"); rcvcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)); if (rcvcmsgbuf == NULL && (rcvcmsgbuf = malloc(rcvcmsglen)) == NULL) log(LOG_ERR, 0,"malloc failed"); IF_DEBUG(DEBUG_KERN) log(LOG_DEBUG,0,"%d octets allocated for the emit/recept buffer mld6",RECV_BUF_SIZE); if ((mld6_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) log(LOG_ERR, errno, "MLD6 socket"); k_set_rcvbuf(mld6_socket, SO_RECV_BUF_SIZE_MAX, SO_RECV_BUF_SIZE_MIN); /* lots of input buffering */ k_set_hlim(mld6_socket, MINHLIM); /* restrict multicasts to one hop */ #if 0 /* * Since we don't have to handle DMVRP messages via the MLD6 socket, * we can just let outgoing multicast packets be loop-backed. */ k_set_loop(mld6_socket, FALSE); /* disable multicast loopback */ #endif /* address initialization */ allnodes_group.sin6_addr = in6addr_linklocal_allnodes; if (inet_pton(AF_INET6, "ff02::2", (void *) &allrouters_group.sin6_addr) != 1) log(LOG_ERR, 0, "inet_pton failed for ff02::2"); /* filter all non-MLD ICMP messages */ ICMP6_FILTER_SETBLOCKALL(&filt); ICMP6_FILTER_SETPASS(ICMP6_MEMBERSHIP_QUERY, &filt); ICMP6_FILTER_SETPASS(ICMP6_MEMBERSHIP_REPORT, &filt); ICMP6_FILTER_SETPASS(ICMP6_MEMBERSHIP_REDUCTION, &filt); ICMP6_FILTER_SETPASS(MLD6_MTRACE_RESP, &filt); ICMP6_FILTER_SETPASS(MLD6_MTRACE, &filt); if (setsockopt(mld6_socket, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) < 0) log(LOG_ERR, errno, "setsockopt(ICMP6_FILTER)"); /* specify to tell receiving interface */ on = 1; #ifdef IPV6_RECVPKTINFO if (setsockopt(mld6_socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) log(LOG_ERR, errno, "setsockopt(IPV6_RECVPKTINFO)"); #else /* old adv. API */ if (setsockopt(mld6_socket, IPPROTO_IPV6, IPV6_PKTINFO, &on, sizeof(on)) < 0) log(LOG_ERR, errno, "setsockopt(IPV6_PKTINFO)"); #endif on = 1; /* specify to tell value of hoplimit field of received IP6 hdr */ #ifdef IPV6_RECVHOPLIMIT if (setsockopt(mld6_socket, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)) < 0) log(LOG_ERR, errno, "setsockopt(IPV6_RECVHOPLIMIT)"); #else /* old adv. API */ if (setsockopt(mld6_socket, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)) < 0) log(LOG_ERR, errno, "setsockopt(IPV6_HOPLIMIT)"); #endif /* initialize msghdr for receiving packets */ rcviov[0].iov_base = (caddr_t) mld6_recv_buf; rcviov[0].iov_len = RECV_BUF_SIZE; rcvmh.msg_name = (caddr_t) & from; rcvmh.msg_namelen = sizeof(from); rcvmh.msg_iov = rcviov; rcvmh.msg_iovlen = 1; rcvmh.msg_control = (caddr_t) rcvcmsgbuf; rcvmh.msg_controllen = rcvcmsglen; /* initialize msghdr for sending packets */ sndiov[0].iov_base = (caddr_t)mld6_send_buf; sndmh.msg_namelen = sizeof(struct sockaddr_in6); sndmh.msg_iov = sndiov; sndmh.msg_iovlen = 1; /* specifiy to insert router alert option in a hop-by-hop opt hdr. */ #ifndef USE_RFC2292BIS raopt[0] = IP6OPT_ROUTER_ALERT; raopt[1] = IP6OPT_RTALERT_LEN - 2; memcpy(&raopt[2], (caddr_t) & rtalert_code, sizeof(u_short)); #endif /* register MLD message handler */ if (register_input_handler(mld6_socket, mld6_read) < 0) log(LOG_ERR, 0, "Couldn't register mld6_read as an input handler"); } /* Read an MLD message */ static void mld6_read(i, rfd) int i; fd_set *rfd; { register int mld6_recvlen; mld6_recvlen = recvmsg(mld6_socket, &rcvmh, 0); if (mld6_recvlen < 0) { if (errno != EINTR) log(LOG_ERR, errno, "MLD6 recvmsg"); return; } /* TODO: make it as a thread in the future releases */ accept_mld6(mld6_recvlen); } /* * Process a newly received MLD6 packet that is sitting in the input packet * buffer. */ static void accept_mld6(recvlen) int recvlen; { struct in6_addr *group, *dst = NULL; struct mld6_hdr *mldh; struct cmsghdr *cm; struct in6_pktinfo *pi = NULL; int *hlimp = NULL; int ifindex = 0; struct sockaddr_in6 *src = (struct sockaddr_in6 *) rcvmh.msg_name; if (recvlen < sizeof(struct mld6_hdr)) { log(LOG_WARNING, 0, "received packet too short (%u bytes) for MLD header", recvlen); return; } mldh = (struct mld6_hdr *) rcvmh.msg_iov[0].iov_base; /* * Packets sent up from kernel to daemon have ICMPv6 type = 0. * Note that we set filters on the mld6_socket, so we should never * see a "normal" ICMPv6 packet with type 0 of ICMPv6 type. */ if (mldh->mld6_type == 0) { /* XXX: msg_controllen must be reset in this case. */ rcvmh.msg_controllen = rcvcmsglen; process_kernel_call(); return; } group = &mldh->mld6_addr; /* extract optional information via Advanced API */ for (cm = (struct cmsghdr *) CMSG_FIRSTHDR(&rcvmh); cm; cm = (struct cmsghdr *) CMSG_NXTHDR(&rcvmh, cm)) { if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_PKTINFO && cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) { pi = (struct in6_pktinfo *) (CMSG_DATA(cm)); ifindex = pi->ipi6_ifindex; dst = &pi->ipi6_addr; } if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_HOPLIMIT && cm->cmsg_len == CMSG_LEN(sizeof(int))) hlimp = (int *) CMSG_DATA(cm); } if (hlimp == NULL) { log(LOG_WARNING, 0, "failed to get receiving hop limit"); return; } /* TODO: too noisy. Remove it? */ //#define NOSUCHDEF #ifdef NOSUCHDEF IF_DEBUG(DEBUG_PKT | debug_kind(IPPROTO_ICMPV6, mldh->mld6_type, mldh->mld6_code)) log(LOG_DEBUG, 0, "RECV %s from %s to %s", packet_kind(IPPROTO_ICMPV6, mldh->mld6_type, mldh->mld6_code), inet6_fmt(&src->sin6_addr), inet6_fmt(dst)); #endif /* NOSUCHDEF */ /* for an mtrace message, we don't need strict checks */ if (mldh->mld6_type == MLD6_MTRACE) { accept_mtrace(src, dst, group, ifindex, (char *)(mldh + 1), mldh->mld6_code, recvlen - sizeof(struct mld6_hdr)); return; } /* hop limit check */ if (*hlimp != 1) { log(LOG_WARNING, 0, "received an MLD6 message with illegal hop limit(%d) from %s", *hlimp, inet6_fmt(&src->sin6_addr)); /* but accept the packet */ } if (ifindex == 0) { log(LOG_WARNING, 0, "failed to get receiving interface"); return; } /* scope check */ if (IN6_IS_ADDR_MC_NODELOCAL(&mldh->mld6_addr)) { log(LOG_INFO, 0, "RECV %s with an invalid scope: %s from %s", packet_kind(IPPROTO_ICMPV6, mldh->mld6_type, mldh->mld6_code), inet6_fmt(&mldh->mld6_addr), inet6_fmt(&src->sin6_addr)); return; /* discard */ } /* source address check */ if (!IN6_IS_ADDR_LINKLOCAL(&src->sin6_addr)) { log(LOG_INFO, 0, "RECV %s from a non link local address: %s", packet_kind(IPPROTO_ICMPV6, mldh->mld6_type, mldh->mld6_code), inet6_fmt(&src->sin6_addr)); return; } switch (mldh->mld6_type) { case MLD6_LISTENER_QUERY: accept_listener_query(src, dst, group, ntohs(mldh->mld6_maxdelay)); return; case MLD6_LISTENER_REPORT: accept_listener_report(src, dst, group); return; case MLD6_LISTENER_DONE: accept_listener_done(src, dst, group); return; default: /* This must be impossible since we set a type filter */ log(LOG_INFO, 0, "ignoring unknown ICMPV6 message type %x from %s to %s", mldh->mld6_type, inet6_fmt(&src->sin6_addr), inet6_fmt(dst)); return; } } static void make_mld6_msg(type, code, src, dst, group, ifindex, delay, datalen, alert) int type, code, ifindex, delay, datalen, alert; struct sockaddr_in6 *src, *dst; struct in6_addr *group; { static struct sockaddr_in6 dst_sa = {sizeof(dst_sa), AF_INET6}; struct mld6_hdr *mhp = (struct mld6_hdr *)mld6_send_buf; int ctllen, hbhlen = 0; switch(type) { case MLD6_MTRACE: case MLD6_MTRACE_RESP: sndmh.msg_name = (caddr_t)dst; break; default: if (IN6_IS_ADDR_UNSPECIFIED(group)) dst_sa.sin6_addr = allnodes_group.sin6_addr; else dst_sa.sin6_addr = *group; sndmh.msg_name = (caddr_t)&dst_sa; datalen = sizeof(struct mld6_hdr); break; } bzero(mhp, sizeof(*mhp)); mhp->mld6_type = type; mhp->mld6_code = code; mhp->mld6_maxdelay = htons(delay); mhp->mld6_addr = *group; sndiov[0].iov_len = datalen; /* estimate total ancillary data length */ ctllen = 0; if (ifindex != -1 || src) ctllen += CMSG_SPACE(sizeof(struct in6_pktinfo)); if (alert) { #ifdef USE_RFC2292BIS if ((hbhlen = inet6_opt_init(NULL, 0)) == -1) log(LOG_ERR, 0, "inet6_opt_init(0) failed"); if ((hbhlen = inet6_opt_append(NULL, 0, hbhlen, IP6OPT_ROUTER_ALERT, 2, 2, NULL)) == -1) log(LOG_ERR, 0, "inet6_opt_append(0) failed"); if ((hbhlen = inet6_opt_finish(NULL, 0, hbhlen)) == -1) log(LOG_ERR, 0, "inet6_opt_finish(0) failed"); ctllen += CMSG_SPACE(hbhlen); #else /* old advanced API */ hbhlen = inet6_option_space(sizeof(raopt)); ctllen += hbhlen; #endif } /* extend ancillary data space (if necessary) */ if (ctlbuflen < ctllen) { if (sndcmsgbuf) free(sndcmsgbuf); if ((sndcmsgbuf = malloc(ctllen)) == NULL) log(LOG_ERR, 0, "make_mld6_msg: malloc failed"); /* assert */ ctlbuflen = ctllen; } /* store ancillary data */ if ((sndmh.msg_controllen = ctllen) > 0) { struct cmsghdr *cmsgp; sndmh.msg_control = sndcmsgbuf; cmsgp = CMSG_FIRSTHDR(&sndmh); if (ifindex != -1 || src) { struct in6_pktinfo *pktinfo; cmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmsgp->cmsg_level = IPPROTO_IPV6; cmsgp->cmsg_type = IPV6_PKTINFO; pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsgp); memset((caddr_t)pktinfo, 0, sizeof(*pktinfo)); if (ifindex != -1) pktinfo->ipi6_ifindex = ifindex; if (src) pktinfo->ipi6_addr = src->sin6_addr; cmsgp = CMSG_NXTHDR(&sndmh, cmsgp); } if (alert) { #ifdef USE_RFC2292BIS int currentlen; void *hbhbuf, *optp = NULL; cmsgp->cmsg_len = CMSG_LEN(hbhlen); cmsgp->cmsg_level = IPPROTO_IPV6; cmsgp->cmsg_type = IPV6_HOPOPTS; hbhbuf = CMSG_DATA(cmsgp); if ((currentlen = inet6_opt_init(hbhbuf, hbhlen)) == -1) log(LOG_ERR, 0, "inet6_opt_init(len = %d) failed", hbhlen); if ((currentlen = inet6_opt_append(hbhbuf, hbhlen, currentlen, IP6OPT_ROUTER_ALERT, 2, 2, &optp)) == -1) log(LOG_ERR, 0, "inet6_opt_append(len = %d) failed", currentlen, hbhlen); (void)inet6_opt_set_val(optp, 0, &rtalert_code, sizeof(rtalert_code)); if (inet6_opt_finish(hbhbuf, hbhlen, currentlen) == -1) log(LOG_ERR, 0, "inet6_opt_finish(buf) failed"); #else /* old advanced API */ if (inet6_option_init((void *)cmsgp, &cmsgp, IPV6_HOPOPTS)) log(LOG_ERR, 0, /* assert */ "make_mld6_msg: inet6_option_init failed"); if (inet6_option_append(cmsgp, raopt, 4, 0)) log(LOG_ERR, 0, /* assert */ "make_mld6_msg: inet6_option_append failed"); #endif cmsgp = CMSG_NXTHDR(&sndmh, cmsgp); } } else sndmh.msg_control = NULL; /* clear for safety */ } void send_mld6(type, code, src, dst, group, index, delay, datalen, alert) int type; int code; /* for trace packets only */ struct sockaddr_in6 *src; struct sockaddr_in6 *dst; /* may be NULL */ struct in6_addr *group; int index, delay, alert; int datalen; /* for trace packets only */ { struct sockaddr_in6 *dstp; make_mld6_msg(type, code, src, dst, group, index, delay, datalen, alert); dstp = (struct sockaddr_in6 *)sndmh.msg_name; if (sendmsg(mld6_socket, &sndmh, 0) < 0) { if (errno == ENETDOWN) check_vif_state(); else log(log_level(IPPROTO_ICMPV6, type, 0), errno, "sendmsg to %s with src %s on %s", inet6_fmt(&dstp->sin6_addr), src ? inet6_fmt(&src->sin6_addr) : "(unspec)", ifindex2str(index)); return; } IF_DEBUG(DEBUG_PKT|debug_kind(IPPROTO_IGMP, type, 0)) log(LOG_DEBUG, 0, "SENT %s from %-15s to %s", packet_kind(IPPROTO_ICMPV6, type, 0), src ? inet6_fmt(&src->sin6_addr) : "unspec", inet6_fmt(&dstp->sin6_addr)); }