/* $OpenBSD: ip6_mroute.c,v 1.124 2020/05/27 11:19:29 mpi Exp $ */ /* $NetBSD: ip6_mroute.c,v 1.59 2003/12/10 09:28:38 itojun Exp $ */ /* $KAME: ip6_mroute.c,v 1.45 2001/03/25 08:38:51 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. */ /* BSDI ip_mroute.c,v 2.10 1996/11/14 00:29:52 jch Exp */ /* * Copyright (c) 1989 Stephen Deering * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Stephen Deering of Stanford University. * * 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 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 BY THE REGENTS 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 REGENTS 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. * * @(#)ip_mroute.c 8.2 (Berkeley) 11/15/93 */ /* * IP multicast forwarding procedures * * Written by David Waitzman, BBN Labs, August 1988. * Modified by Steve Deering, Stanford, February 1989. * Modified by Mark J. Steiglitz, Stanford, May, 1991 * Modified by Van Jacobson, LBL, January 1993 * Modified by Ajit Thyagarajan, PARC, August 1993 * Modified by Bill Fenner, PARC, April 1994 * * MROUTING Revision: 3.5.1.2 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #define MCAST_DEBUG */ #ifdef MCAST_DEBUG int mcast6_debug = 1; #define DPRINTF(fmt, args...) \ do { \ if (mcast6_debug) \ printf("%s:%d " fmt "\n", \ __func__, __LINE__, ## args); \ } while (0) #else #define DPRINTF(fmt, args...) \ do { } while (0) #endif int ip6_mdq(struct mbuf *, struct ifnet *, struct rtentry *); void phyint_send6(struct ifnet *, struct ip6_hdr *, struct mbuf *); /* * Globals. All but ip6_mrouter, ip6_mrtproto and mrt6stat could be static, * except for netstat or debugging purposes. */ struct socket *ip6_mrouter[RT_TABLEID_MAX + 1]; struct rttimer_queue *mrouter6q[RT_TABLEID_MAX + 1]; int ip6_mrouter_ver = 0; int ip6_mrtproto; /* for netstat only */ struct mrt6stat mrt6stat; #define NO_RTE_FOUND 0x1 #define RTE_FOUND 0x2 #define MCAST_EXPIRE_TIMEOUT 30 /* seconds */ /* * Macros to compute elapsed time efficiently * Borrowed from Van Jacobson's scheduling code */ #define TV_DELTA(a, b, delta) do { \ int xxs; \ \ delta = (a).tv_usec - (b).tv_usec; \ if ((xxs = (a).tv_sec - (b).tv_sec)) { \ switch (xxs) { \ case 2: \ delta += 1000000; \ /* FALLTHROUGH */ \ case 1: \ delta += 1000000; \ break; \ default: \ delta += (1000000 * xxs); \ } \ } \ } while (0) #define TV_LT(a, b) (((a).tv_usec < (b).tv_usec && \ (a).tv_sec <= (b).tv_sec) || (a).tv_sec < (b).tv_sec) int get_sg6_cnt(struct sioc_sg_req6 *, unsigned int); int get_mif6_cnt(struct sioc_mif_req6 *, unsigned int); int ip6_mrouter_init(struct socket *, int, int); int add_m6if(struct socket *, struct mif6ctl *); int del_m6if(struct socket *, mifi_t *); int add_m6fc(struct socket *, struct mf6cctl *); int del_m6fc(struct socket *, struct mf6cctl *); struct ifnet *mrt6_iflookupbymif(mifi_t, unsigned int); struct rtentry *mf6c_find(struct ifnet *, struct in6_addr *, struct in6_addr *, unsigned int); struct rtentry *mrt6_mcast_add(struct ifnet *, struct sockaddr *, struct sockaddr *); void mrt6_mcast_del(struct rtentry *, unsigned int); void mf6c_expire_route(struct rtentry *, struct rttimer *); /* * Handle MRT setsockopt commands to modify the multicast routing tables. */ int ip6_mrouter_set(int cmd, struct socket *so, struct mbuf *m) { struct inpcb *inp = sotoinpcb(so); if (cmd != MRT6_INIT && so != ip6_mrouter[inp->inp_rtableid]) return (EPERM); switch (cmd) { case MRT6_INIT: if (m == NULL || m->m_len < sizeof(int)) return (EINVAL); return (ip6_mrouter_init(so, *mtod(m, int *), cmd)); case MRT6_DONE: return (ip6_mrouter_done(so)); case MRT6_ADD_MIF: if (m == NULL || m->m_len < sizeof(struct mif6ctl)) return (EINVAL); return (add_m6if(so, mtod(m, struct mif6ctl *))); case MRT6_DEL_MIF: if (m == NULL || m->m_len < sizeof(mifi_t)) return (EINVAL); return (del_m6if(so, mtod(m, mifi_t *))); case MRT6_ADD_MFC: if (m == NULL || m->m_len < sizeof(struct mf6cctl)) return (EINVAL); return (add_m6fc(so, mtod(m, struct mf6cctl *))); case MRT6_DEL_MFC: if (m == NULL || m->m_len < sizeof(struct mf6cctl)) return (EINVAL); return (del_m6fc(so, mtod(m, struct mf6cctl *))); default: return (EOPNOTSUPP); } } /* * Handle MRT getsockopt commands */ int ip6_mrouter_get(int cmd, struct socket *so, struct mbuf *m) { struct inpcb *inp = sotoinpcb(so); if (so != ip6_mrouter[inp->inp_rtableid]) return (EPERM); switch (cmd) { default: return EOPNOTSUPP; } } /* * Handle ioctl commands to obtain information from the cache */ int mrt6_ioctl(struct socket *so, u_long cmd, caddr_t data) { struct inpcb *inp = sotoinpcb(so); int error; if (inp == NULL) return (ENOTCONN); switch (cmd) { case SIOCGETSGCNT_IN6: NET_RLOCK_IN_IOCTL(); error = get_sg6_cnt((struct sioc_sg_req6 *)data, inp->inp_rtableid); NET_RUNLOCK_IN_IOCTL(); break; case SIOCGETMIFCNT_IN6: NET_RLOCK_IN_IOCTL(); error = get_mif6_cnt((struct sioc_mif_req6 *)data, inp->inp_rtableid); NET_RUNLOCK_IN_IOCTL(); break; default: error = ENOTTY; break; } return error; } /* * returns the packet, byte, rpf-failure count for the source group provided */ int get_sg6_cnt(struct sioc_sg_req6 *req, unsigned int rtableid) { struct rtentry *rt; struct mf6c *mf6c; rt = mf6c_find(NULL, &req->src.sin6_addr, &req->grp.sin6_addr, rtableid); if (rt == NULL) { req->pktcnt = req->bytecnt = req->wrong_if = 0xffffffff; return EADDRNOTAVAIL; } req->pktcnt = req->bytecnt = req->wrong_if = 0; do { mf6c = (struct mf6c *)rt->rt_llinfo; if (mf6c == NULL) continue; req->pktcnt += mf6c->mf6c_pkt_cnt; req->bytecnt += mf6c->mf6c_byte_cnt; req->wrong_if += mf6c->mf6c_wrong_if; } while ((rt = rtable_iterate(rt)) != NULL); return 0; } /* * returns the input and output packet and byte counts on the mif provided */ int get_mif6_cnt(struct sioc_mif_req6 *req, unsigned int rtableid) { struct ifnet *ifp; struct mif6 *m6; if ((ifp = mrt6_iflookupbymif(req->mifi, rtableid)) == NULL) return EINVAL; m6 = (struct mif6 *)ifp->if_mcast6; req->icount = m6->m6_pkt_in; req->ocount = m6->m6_pkt_out; req->ibytes = m6->m6_bytes_in; req->obytes = m6->m6_bytes_out; return 0; } int mrt6_sysctl_mif(void *oldp, size_t *oldlenp) { struct ifnet *ifp; caddr_t where = oldp; size_t needed, given; struct mif6 *mifp; struct mif6info minfo; given = *oldlenp; needed = 0; TAILQ_FOREACH(ifp, &ifnet, if_list) { if ((mifp = (struct mif6 *)ifp->if_mcast6) == NULL) continue; minfo.m6_mifi = mifp->m6_mifi; minfo.m6_flags = mifp->m6_flags; minfo.m6_lcl_addr = mifp->m6_lcl_addr; minfo.m6_ifindex = ifp->if_index; minfo.m6_pkt_in = mifp->m6_pkt_in; minfo.m6_pkt_out = mifp->m6_pkt_out; minfo.m6_bytes_in = mifp->m6_bytes_in; minfo.m6_bytes_out = mifp->m6_bytes_out; minfo.m6_rate_limit = mifp->m6_rate_limit; needed += sizeof(minfo); if (where && needed <= given) { int error; error = copyout(&minfo, where, sizeof(minfo)); if (error) return (error); where += sizeof(minfo); } } if (where) { *oldlenp = needed; if (given < needed) return (ENOMEM); } else *oldlenp = (11 * needed) / 10; return (0); } struct mf6csysctlarg { struct mf6cinfo *ms6a_minfos; size_t ms6a_len; size_t ms6a_needed; }; int mrt6_rtwalk_mf6csysctl(struct rtentry *rt, void *arg, unsigned int rtableid) { struct mf6c *mf6c = (struct mf6c *)rt->rt_llinfo; struct mf6csysctlarg *msa = arg; struct ifnet *ifp; struct mif6 *m6; struct mf6cinfo *minfo; int new = 0; /* Skip entries being removed. */ if (mf6c == NULL) return 0; /* Skip non-multicast routes. */ if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) != (RTF_HOST | RTF_MULTICAST)) return 0; /* User just asked for the output size. */ if (msa->ms6a_minfos == NULL) { msa->ms6a_needed += sizeof(*minfo); return 0; } /* Skip route with invalid interfaces. */ if ((ifp = if_get(rt->rt_ifidx)) == NULL) return 0; if ((m6 = (struct mif6 *)ifp->if_mcast6) == NULL) { if_put(ifp); return 0; } for (minfo = msa->ms6a_minfos; (uint8_t *)minfo < ((uint8_t *)msa->ms6a_minfos + msa->ms6a_len); minfo++) { /* Find a new entry or update old entry. */ if (!IN6_ARE_ADDR_EQUAL(&minfo->mf6c_origin.sin6_addr, &satosin6(rt->rt_gateway)->sin6_addr) || !IN6_ARE_ADDR_EQUAL(&minfo->mf6c_mcastgrp.sin6_addr, &satosin6(rt_key(rt))->sin6_addr)) { if (!IN6_IS_ADDR_UNSPECIFIED( &minfo->mf6c_origin.sin6_addr) || !IN6_IS_ADDR_UNSPECIFIED( &minfo->mf6c_mcastgrp.sin6_addr)) continue; new = 1; } minfo->mf6c_origin = *satosin6(rt->rt_gateway); minfo->mf6c_mcastgrp = *satosin6(rt_key(rt)); minfo->mf6c_parent = mf6c->mf6c_parent; minfo->mf6c_pkt_cnt += mf6c->mf6c_pkt_cnt; minfo->mf6c_byte_cnt += mf6c->mf6c_byte_cnt; IF_SET(m6->m6_mifi, &minfo->mf6c_ifset); break; } if (new != 0) msa->ms6a_needed += sizeof(*minfo); if_put(ifp); return 0; } int mrt6_sysctl_mfc(void *oldp, size_t *oldlenp) { unsigned int rtableid; int error; struct mf6csysctlarg msa; if (oldp != NULL && *oldlenp > MAXPHYS) return EINVAL; if (oldp != NULL) msa.ms6a_minfos = malloc(*oldlenp, M_TEMP, M_WAITOK | M_ZERO); else msa.ms6a_minfos = NULL; msa.ms6a_len = *oldlenp; msa.ms6a_needed = 0; for (rtableid = 0; rtableid <= RT_TABLEID_MAX; rtableid++) { rtable_walk(rtableid, AF_INET6, NULL, mrt6_rtwalk_mf6csysctl, &msa); } if (msa.ms6a_minfos != NULL && msa.ms6a_needed > 0 && (error = copyout(msa.ms6a_minfos, oldp, msa.ms6a_needed)) != 0) { free(msa.ms6a_minfos, M_TEMP, *oldlenp); return error; } free(msa.ms6a_minfos, M_TEMP, *oldlenp); *oldlenp = msa.ms6a_needed; return 0; } /* * Enable multicast routing */ int ip6_mrouter_init(struct socket *so, int v, int cmd) { struct inpcb *inp = sotoinpcb(so); unsigned int rtableid = inp->inp_rtableid; if (so->so_type != SOCK_RAW || so->so_proto->pr_protocol != IPPROTO_ICMPV6) return (EOPNOTSUPP); if (v != 1) return (ENOPROTOOPT); if (ip6_mrouter[rtableid] != NULL) return (EADDRINUSE); ip6_mrouter[rtableid] = so; ip6_mrouter_ver = cmd; mrouter6q[rtableid] = rt_timer_queue_create(MCAST_EXPIRE_TIMEOUT); return (0); } int mrouter6_rtwalk_delete(struct rtentry *rt, void *arg, unsigned int rtableid) { /* Skip non-multicast routes. */ if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) != (RTF_HOST | RTF_MULTICAST)) return 0; return EEXIST; } /* * Disable multicast routing */ int ip6_mrouter_done(struct socket *so) { struct inpcb *inp = sotoinpcb(so); struct ifnet *ifp; unsigned int rtableid = inp->inp_rtableid; int error; NET_ASSERT_LOCKED(); /* Delete all remaining installed multicast routes. */ do { struct rtentry *rt = NULL; error = rtable_walk(rtableid, AF_INET6, &rt, mrouter6_rtwalk_delete, NULL); if (rt != NULL && error == EEXIST) { mrt6_mcast_del(rt, rtableid); error = EAGAIN; } rtfree(rt); } while (error == EAGAIN); /* Unregister all interfaces in the domain. */ TAILQ_FOREACH(ifp, &ifnet, if_list) { if (ifp->if_rdomain != rtableid) continue; ip6_mrouter_detach(ifp); } rt_timer_queue_destroy(mrouter6q[rtableid]); ip6_mrouter[inp->inp_rtableid] = NULL; ip6_mrouter_ver = 0; mrouter6q[rtableid] = NULL; return 0; } void ip6_mrouter_detach(struct ifnet *ifp) { struct mif6 *m6 = (struct mif6 *)ifp->if_mcast6; struct in6_ifreq ifr; if (m6 == NULL) return; ifp->if_mcast6 = NULL; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sin6_family = AF_INET6; ifr.ifr_addr.sin6_addr = in6addr_any; KERNEL_LOCK(); (*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)&ifr); KERNEL_UNLOCK(); free(m6, M_MRTABLE, sizeof(*m6)); } /* * Add a mif to the mif table */ int add_m6if(struct socket *so, struct mif6ctl *mifcp) { struct inpcb *inp = sotoinpcb(so); struct mif6 *mifp; struct ifnet *ifp; struct in6_ifreq ifr; int error; unsigned int rtableid = inp->inp_rtableid; NET_ASSERT_LOCKED(); if (mifcp->mif6c_mifi >= MAXMIFS) return EINVAL; if (mrt6_iflookupbymif(mifcp->mif6c_mifi, rtableid) != NULL) return EADDRINUSE; /* XXX: is it appropriate? */ { ifp = if_get(mifcp->mif6c_pifi); if (ifp == NULL) return ENXIO; /* Make sure the interface supports multicast */ if ((ifp->if_flags & IFF_MULTICAST) == 0) { if_put(ifp); return EOPNOTSUPP; } /* * Enable promiscuous reception of all IPv6 multicasts * from the interface. */ memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sin6_family = AF_INET6; ifr.ifr_addr.sin6_addr = in6addr_any; error = (*ifp->if_ioctl)(ifp, SIOCADDMULTI, (caddr_t)&ifr); if (error) { if_put(ifp); return error; } } mifp = malloc(sizeof(*mifp), M_MRTABLE, M_WAITOK | M_ZERO); ifp->if_mcast6 = (caddr_t)mifp; mifp->m6_mifi = mifcp->mif6c_mifi; mifp->m6_flags = mifcp->mif6c_flags; #ifdef notyet /* scaling up here allows division by 1024 in critical code */ mifp->m6_rate_limit = mifcp->mif6c_rate_limit * 1024 / 1000; #endif if_put(ifp); return 0; } /* * Delete a mif from the mif table */ int del_m6if(struct socket *so, mifi_t *mifip) { struct inpcb *inp = sotoinpcb(so); struct ifnet *ifp; NET_ASSERT_LOCKED(); if (*mifip >= MAXMIFS) return EINVAL; if ((ifp = mrt6_iflookupbymif(*mifip, inp->inp_rtableid)) == NULL) return EINVAL; ip6_mrouter_detach(ifp); return 0; } int mf6c_add_route(struct ifnet *ifp, struct sockaddr *origin, struct sockaddr *group, struct mf6cctl *mf6cc, int wait) { struct rtentry *rt; struct mf6c *mf6c; unsigned int rtableid = ifp->if_rdomain; #ifdef MCAST_DEBUG char bsrc[INET6_ADDRSTRLEN], bdst[INET6_ADDRSTRLEN]; #endif /* MCAST_DEBUG */ rt = mrt6_mcast_add(ifp, origin, group); if (rt == NULL) return ENOENT; mf6c = malloc(sizeof(*mf6c), M_MRTABLE, wait | M_ZERO); if (mf6c == NULL) { DPRINTF("origin %s group %s parent %d (%s) malloc failed", inet_ntop(AF_INET6, origin, bsrc, sizeof(bsrc)), inet_ntop(AF_INET6, group, bdst, sizeof(bdst)), mf6cc->mf6cc_parent, ifp->if_xname); mrt6_mcast_del(rt, rtableid); rtfree(rt); return ENOMEM; } rt->rt_llinfo = (caddr_t)mf6c; rt_timer_add(rt, mf6c_expire_route, mrouter6q[rtableid], rtableid); mf6c->mf6c_parent = mf6cc->mf6cc_parent; rtfree(rt); return 0; } void mf6c_update(struct mf6cctl *mf6cc, int wait, unsigned int rtableid) { struct rtentry *rt; struct mf6c *mf6c; struct ifnet *ifp; struct sockaddr_in6 osin6, gsin6; mifi_t mifi; #ifdef MCAST_DEBUG char bdst[INET6_ADDRSTRLEN]; #endif /* MCAST_DEBUG */ memset(&osin6, 0, sizeof(osin6)); osin6.sin6_family = AF_INET6; osin6.sin6_len = sizeof(osin6); osin6.sin6_addr = mf6cc->mf6cc_origin.sin6_addr; memset(&gsin6, 0, sizeof(gsin6)); gsin6.sin6_family = AF_INET6; gsin6.sin6_len = sizeof(gsin6); gsin6.sin6_addr = mf6cc->mf6cc_mcastgrp.sin6_addr; for (mifi = 0; mifi < MAXMIFS; mifi++) { if (mifi == mf6cc->mf6cc_parent) continue; /* Test for mif existence and then update the entry. */ if ((ifp = mrt6_iflookupbymif(mifi, rtableid)) == NULL) continue; rt = mf6c_find(ifp, &mf6cc->mf6cc_origin.sin6_addr, &mf6cc->mf6cc_mcastgrp.sin6_addr, rtableid); /* mif not configured or removed. */ if (!IF_ISSET(mifi, &mf6cc->mf6cc_ifset)) { /* Route doesn't exist, nothing to do. */ if (rt == NULL) continue; DPRINTF("del route (group %s) for mif %d (%s)", inet_ntop(AF_INET6, &mf6cc->mf6cc_mcastgrp.sin6_addr, bdst, sizeof(bdst)), mifi, ifp->if_xname); mrt6_mcast_del(rt, rtableid); rtfree(rt); continue; } /* Route exists, look for changes. */ if (rt != NULL) { mf6c = (struct mf6c *)rt->rt_llinfo; /* Skip route being deleted. */ if (mf6c == NULL) { rtfree(rt); continue; } /* No new changes to apply. */ if (mf6cc->mf6cc_parent == mf6c->mf6c_parent) { rtfree(rt); continue; } DPRINTF("update route (group %s) for mif %d (%s)", inet_ntop(AF_INET6, &mf6cc->mf6cc_mcastgrp.sin6_addr, bdst, sizeof(bdst)), mifi, ifp->if_xname); mf6c->mf6c_parent = mf6cc->mf6cc_parent; rtfree(rt); continue; } DPRINTF("add route (group %s) for mif %d (%s)", inet_ntop(AF_INET6, &mf6cc->mf6cc_mcastgrp.sin6_addr, bdst, sizeof(bdst)), mifi, ifp->if_xname); mf6c_add_route(ifp, sin6tosa(&osin6), sin6tosa(&gsin6), mf6cc, wait); } /* Create route for the parent interface. */ if ((ifp = mrt6_iflookupbymif(mf6cc->mf6cc_parent, rtableid)) == NULL) { DPRINTF("failed to find upstream interface %d", mf6cc->mf6cc_parent); return; } /* We already have a route, nothing to do here. */ if ((rt = mf6c_find(ifp, &mf6cc->mf6cc_origin.sin6_addr, &mf6cc->mf6cc_mcastgrp.sin6_addr, rtableid)) != NULL) { rtfree(rt); return; } DPRINTF("add upstream route (group %s) for if %s", inet_ntop(AF_INET6, &mf6cc->mf6cc_mcastgrp.sin6_addr, bdst, sizeof(bdst)), ifp->if_xname); mf6c_add_route(ifp, sin6tosa(&osin6), sin6tosa(&gsin6), mf6cc, wait); } int mf6c_add(struct mf6cctl *mfccp, struct in6_addr *origin, struct in6_addr *group, int vidx, unsigned int rtableid, int wait) { struct ifnet *ifp; struct mif6 *m6; struct mf6cctl mf6cc; ifp = mrt6_iflookupbymif(vidx, rtableid); if (ifp == NULL || (m6 = (struct mif6 *)ifp->if_mcast6) == NULL) return ENOENT; memset(&mf6cc, 0, sizeof(mf6cc)); if (mfccp == NULL) { mf6cc.mf6cc_origin.sin6_family = AF_INET6; mf6cc.mf6cc_origin.sin6_len = sizeof(mf6cc.mf6cc_origin); mf6cc.mf6cc_origin.sin6_addr = *origin; mf6cc.mf6cc_mcastgrp.sin6_family = AF_INET6; mf6cc.mf6cc_mcastgrp.sin6_len = sizeof(mf6cc.mf6cc_mcastgrp); mf6cc.mf6cc_mcastgrp.sin6_addr = *group; mf6cc.mf6cc_parent = vidx; } else memcpy(&mf6cc, mfccp, sizeof(mf6cc)); mf6c_update(&mf6cc, wait, rtableid); return 0; } int add_m6fc(struct socket *so, struct mf6cctl *mfccp) { struct inpcb *inp = sotoinpcb(so); unsigned int rtableid = inp->inp_rtableid; NET_ASSERT_LOCKED(); return mf6c_add(mfccp, &mfccp->mf6cc_origin.sin6_addr, &mfccp->mf6cc_mcastgrp.sin6_addr, mfccp->mf6cc_parent, rtableid, M_WAITOK); } int del_m6fc(struct socket *so, struct mf6cctl *mfccp) { struct inpcb *inp = sotoinpcb(so); struct rtentry *rt; unsigned int rtableid = inp->inp_rtableid; NET_ASSERT_LOCKED(); while ((rt = mf6c_find(NULL, &mfccp->mf6cc_origin.sin6_addr, &mfccp->mf6cc_mcastgrp.sin6_addr, rtableid)) != NULL) { mrt6_mcast_del(rt, rtableid); rtfree(rt); } return 0; } int socket6_send(struct socket *s, struct mbuf *mm, struct sockaddr_in6 *src) { if (s) { if (sbappendaddr(s, &s->so_rcv, sin6tosa(src), mm, NULL) != 0) { sorwakeup(s); return 0; } } m_freem(mm); return -1; } /* * IPv6 multicast forwarding function. This function assumes that the packet * pointed to by "ip6" has arrived on (or is about to be sent to) the interface * pointed to by "ifp", and the packet is to be relayed to other networks * that have members of the packet's destination IPv6 multicast group. * * The packet is returned unscathed to the caller, unless it is * erroneous, in which case a non-zero return value tells the caller to * discard it. */ int ip6_mforward(struct ip6_hdr *ip6, struct ifnet *ifp, struct mbuf *m) { struct rtentry *rt; struct mif6 *mifp; struct mbuf *mm; struct sockaddr_in6 sin6; unsigned int rtableid = ifp->if_rdomain; NET_ASSERT_LOCKED(); /* * Don't forward a packet with Hop limit of zero or one, * or a packet destined to a local-only group. */ if (ip6->ip6_hlim <= 1 || IN6_IS_ADDR_MC_INTFACELOCAL(&ip6->ip6_dst) || IN6_IS_ADDR_MC_LINKLOCAL(&ip6->ip6_dst)) return 0; ip6->ip6_hlim--; /* * Source address check: do not forward packets with unspecified * source. It was discussed in July 2000, on ipngwg mailing list. * This is rather more serious than unicast cases, because some * MLD packets can be sent with the unspecified source address * (although such packets must normally set 1 to the hop limit field). */ if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) { ip6stat_inc(ip6s_cantforward); if (ip6_log_time + ip6_log_interval < time_uptime) { char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN]; ip6_log_time = time_uptime; inet_ntop(AF_INET6, &ip6->ip6_src, src, sizeof(src)); inet_ntop(AF_INET6, &ip6->ip6_dst, dst, sizeof(dst)); log(LOG_DEBUG, "cannot forward " "from %s to %s nxt %d received on interface %u\n", src, dst, ip6->ip6_nxt, m->m_pkthdr.ph_ifidx); } return 0; } /* * Determine forwarding mifs from the forwarding cache table */ rt = mf6c_find(NULL, &ip6->ip6_src, &ip6->ip6_dst, rtableid); /* Entry exists, so forward if necessary */ if (rt) { return (ip6_mdq(m, ifp, rt)); } else { /* * If we don't have a route for packet's origin, * Make a copy of the packet & * send message to routing daemon */ mrt6stat.mrt6s_no_route++; { struct mrt6msg *im; if ((mifp = (struct mif6 *)ifp->if_mcast6) == NULL) return EHOSTUNREACH; /* * Make a copy of the header to send to the user * level process */ mm = m_copym(m, 0, sizeof(struct ip6_hdr), M_NOWAIT); if (mm == NULL) return ENOBUFS; /* * Send message to routing daemon */ (void)memset(&sin6, 0, sizeof(sin6)); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; sin6.sin6_addr = ip6->ip6_src; im = NULL; switch (ip6_mrouter_ver) { case MRT6_INIT: im = mtod(mm, struct mrt6msg *); im->im6_msgtype = MRT6MSG_NOCACHE; im->im6_mbz = 0; im->im6_mif = mifp->m6_mifi; break; default: m_freem(mm); return EINVAL; } if (socket6_send(ip6_mrouter[rtableid], mm, &sin6) < 0) { log(LOG_WARNING, "ip6_mforward: ip6_mrouter " "socket queue full\n"); mrt6stat.mrt6s_upq_sockfull++; return ENOBUFS; } mrt6stat.mrt6s_upcalls++; mf6c_add(NULL, &ip6->ip6_src, &ip6->ip6_dst, mifp->m6_mifi, rtableid, M_NOWAIT); } return 0; } } void mf6c_expire_route(struct rtentry *rt, struct rttimer *rtt) { struct mf6c *mf6c = (struct mf6c *)rt->rt_llinfo; unsigned int rtableid = rtt->rtt_tableid; #ifdef MCAST_DEBUG char bsrc[INET6_ADDRSTRLEN], bdst[INET6_ADDRSTRLEN]; #endif /* MCAST_DEBUG */ /* Skip entry being deleted. */ if (mf6c == NULL) return; DPRINTF("origin %s group %s interface %d expire %s", inet_ntop(AF_INET6, &satosin6(rt->rt_gateway)->sin6_addr, bsrc, sizeof(bsrc)), inet_ntop(AF_INET6, &satosin6(rt_key(rt))->sin6_addr, bdst, sizeof(bdst)), rt->rt_ifidx, mf6c->mf6c_expire ? "yes" : "no"); if (mf6c->mf6c_expire == 0) { mf6c->mf6c_expire = 1; rt_timer_add(rt, mf6c_expire_route, mrouter6q[rtableid], rtableid); return; } mrt6_mcast_del(rt, rtableid); } /* * Packet forwarding routine once entry in the cache is made */ int ip6_mdq(struct mbuf *m, struct ifnet *ifp, struct rtentry *rt) { struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); struct mif6 *m6, *mifp = (struct mif6 *)ifp->if_mcast6; struct mf6c *mf6c = (struct mf6c *)rt->rt_llinfo; struct ifnet *ifn; int plen = m->m_pkthdr.len; if (mifp == NULL || mf6c == NULL) { rtfree(rt); return EHOSTUNREACH; } /* * Don't forward if it didn't arrive from the parent mif * for its origin. */ if (mifp->m6_mifi != mf6c->mf6c_parent) { /* came in the wrong interface */ mrt6stat.mrt6s_wrong_if++; mf6c->mf6c_wrong_if++; rtfree(rt); return 0; } /* if wrong iif */ /* If I sourced this packet, it counts as output, else it was input. */ if (m->m_pkthdr.ph_ifidx == 0) { /* XXX: is ph_ifidx really 0 when output?? */ mifp->m6_pkt_out++; mifp->m6_bytes_out += plen; } else { mifp->m6_pkt_in++; mifp->m6_bytes_in += plen; } /* * For each mif, forward a copy of the packet if there are group * members downstream on the interface. */ do { /* Don't consider non multicast routes. */ if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) != (RTF_HOST | RTF_MULTICAST)) continue; mf6c = (struct mf6c *)rt->rt_llinfo; if (mf6c == NULL) continue; mf6c->mf6c_pkt_cnt++; mf6c->mf6c_byte_cnt += m->m_pkthdr.len; /* Don't let this route expire. */ mf6c->mf6c_expire = 0; if ((ifn = if_get(rt->rt_ifidx)) == NULL) continue; /* Sanity check: did we configure this? */ if ((m6 = (struct mif6 *)ifn->if_mcast6) == NULL) { if_put(ifn); continue; } /* Don't send in the upstream interface. */ if (mf6c->mf6c_parent == m6->m6_mifi) { if_put(ifn); continue; } /* * check if the outgoing packet is going to break * a scope boundary. */ if ((mifp->m6_flags & MIFF_REGISTER) == 0 && (m6->m6_flags & MIFF_REGISTER) == 0 && (in6_addr2scopeid(ifp->if_index, &ip6->ip6_dst) != in6_addr2scopeid(ifn->if_index, &ip6->ip6_dst) || in6_addr2scopeid(ifp->if_index, &ip6->ip6_src) != in6_addr2scopeid(ifn->if_index, &ip6->ip6_src))) { if_put(ifn); ip6stat_inc(ip6s_badscope); continue; } m6->m6_pkt_out++; m6->m6_bytes_out += plen; phyint_send6(ifn, ip6, m); if_put(ifn); } while ((rt = rtable_iterate(rt)) != NULL); return 0; } void phyint_send6(struct ifnet *ifp, struct ip6_hdr *ip6, struct mbuf *m) { struct mbuf *mb_copy; struct sockaddr_in6 *dst6, sin6; int error = 0; NET_ASSERT_LOCKED(); /* * Make a new reference to the packet; make sure that * the IPv6 header is actually copied, not just referenced, * so that ip6_output() only scribbles on the copy. */ mb_copy = m_dup_pkt(m, max_linkhdr, M_NOWAIT); if (mb_copy == NULL) return; /* set MCAST flag to the outgoing packet */ mb_copy->m_flags |= M_MCAST; /* * If we sourced the packet, call ip6_output since we may devide * the packet into fragments when the packet is too big for the * outgoing interface. * Otherwise, we can simply send the packet to the interface * sending queue. */ if (m->m_pkthdr.ph_ifidx == 0) { struct ip6_moptions im6o; im6o.im6o_ifidx = ifp->if_index; /* XXX: ip6_output will override ip6->ip6_hlim */ im6o.im6o_hlim = ip6->ip6_hlim; im6o.im6o_loop = 1; error = ip6_output(mb_copy, NULL, NULL, IPV6_FORWARDING, &im6o, NULL); return; } /* * If we belong to the destination multicast group * on the outgoing interface, loop back a copy. */ dst6 = &sin6; memset(&sin6, 0, sizeof(sin6)); if (in6_hasmulti(&ip6->ip6_dst, ifp)) { dst6->sin6_len = sizeof(struct sockaddr_in6); dst6->sin6_family = AF_INET6; dst6->sin6_addr = ip6->ip6_dst; ip6_mloopback(ifp, m, dst6); } /* * Put the packet into the sending queue of the outgoing interface * if it would fit in the MTU of the interface. */ if (mb_copy->m_pkthdr.len <= ifp->if_mtu || ifp->if_mtu < IPV6_MMTU) { dst6->sin6_len = sizeof(struct sockaddr_in6); dst6->sin6_family = AF_INET6; dst6->sin6_addr = ip6->ip6_dst; error = ifp->if_output(ifp, mb_copy, sin6tosa(dst6), NULL); } else { if (ip6_mcast_pmtu) icmp6_error(mb_copy, ICMP6_PACKET_TOO_BIG, 0, ifp->if_mtu); else { m_freem(mb_copy); /* simply discard the packet */ } } } struct ifnet * mrt6_iflookupbymif(mifi_t mifi, unsigned int rtableid) { struct mif6 *m6; struct ifnet *ifp; TAILQ_FOREACH(ifp, &ifnet, if_list) { if (ifp->if_rdomain != rtableid) continue; if ((m6 = (struct mif6 *)ifp->if_mcast6) == NULL) continue; if (m6->m6_mifi != mifi) continue; return ifp; } return NULL; } struct rtentry * mf6c_find(struct ifnet *ifp, struct in6_addr *origin, struct in6_addr *group, unsigned int rtableid) { struct rtentry *rt; struct sockaddr_in6 msin6; memset(&msin6, 0, sizeof(msin6)); msin6.sin6_family = AF_INET6; msin6.sin6_len = sizeof(msin6); msin6.sin6_addr = *group; rt = rtalloc(sin6tosa(&msin6), 0, rtableid); do { if (!rtisvalid(rt)) { rtfree(rt); return NULL; } if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) != (RTF_HOST | RTF_MULTICAST)) continue; /* Return first occurrence if interface is not specified. */ if (ifp == NULL) return rt; if (rt->rt_ifidx == ifp->if_index) return rt; } while ((rt = rtable_iterate(rt)) != NULL); return NULL; } struct rtentry * mrt6_mcast_add(struct ifnet *ifp, struct sockaddr *origin, struct sockaddr *group) { struct ifaddr *ifa; int rv; unsigned int rtableid = ifp->if_rdomain; TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { if (ifa->ifa_addr->sa_family == AF_INET6) break; } if (ifa == NULL) { DPRINTF("ifa == NULL"); return NULL; } rv = rt_ifa_add(ifa, RTF_HOST | RTF_MULTICAST | RTF_MPATH, group, ifp->if_rdomain); if (rv != 0) { DPRINTF("rt_ifa_add failed %d", rv); return NULL; } return mf6c_find(ifp, NULL, &satosin6(group)->sin6_addr, rtableid); } void mrt6_mcast_del(struct rtentry *rt, unsigned int rtableid) { struct ifnet *ifp; int error; /* Remove all timers related to this route. */ rt_timer_remove_all(rt); free(rt->rt_llinfo, M_MRTABLE, sizeof(struct mf6c)); rt->rt_llinfo = NULL; ifp = if_get(rt->rt_ifidx); if (ifp == NULL) return; error = rtdeletemsg(rt, ifp, rtableid); if_put(ifp); if (error) DPRINTF("delete route error %d\n", error); }