1286 lines
32 KiB
C
1286 lines
32 KiB
C
/* $OpenBSD: ip6_mroute.c,v 1.137 2023/06/14 14:30:08 mvs 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 <sys/param.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/timeout.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/socketvar.h>
|
|
#include <sys/protosw.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/route.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet6/in6_var.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netinet/icmp6.h>
|
|
#include <netinet6/ip6_var.h>
|
|
#include <netinet6/ip6_mroute.h>
|
|
#include <netinet/in_pcb.h>
|
|
|
|
/* #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 ip6_mrouterq;
|
|
int ip6_mrouter_ver = 0;
|
|
int ip6_mrtproto; /* for netstat only */
|
|
struct mrt6stat mrt6stat;
|
|
|
|
#define NO_RTE_FOUND 0x1
|
|
#define RTE_FOUND 0x2
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* 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);
|
|
|
|
KERNEL_LOCK();
|
|
|
|
switch (cmd) {
|
|
case SIOCGETSGCNT_IN6:
|
|
NET_LOCK_SHARED();
|
|
error = get_sg6_cnt((struct sioc_sg_req6 *)data,
|
|
inp->inp_rtableid);
|
|
NET_UNLOCK_SHARED();
|
|
break;
|
|
case SIOCGETMIFCNT_IN6:
|
|
NET_LOCK_SHARED();
|
|
error = get_mif6_cnt((struct sioc_mif_req6 *)data,
|
|
inp->inp_rtableid);
|
|
NET_UNLOCK_SHARED();
|
|
break;
|
|
default:
|
|
error = ENOTTY;
|
|
break;
|
|
}
|
|
|
|
KERNEL_UNLOCK();
|
|
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;
|
|
memset(&minfo, 0, sizeof minfo);
|
|
TAILQ_FOREACH(ifp, &ifnetlist, 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;
|
|
|
|
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, &ifnetlist, if_list) {
|
|
if (ifp->if_rdomain != rtableid)
|
|
continue;
|
|
|
|
ip6_mrouter_detach(ifp);
|
|
}
|
|
|
|
ip6_mrouter[inp->inp_rtableid] = NULL;
|
|
ip6_mrouter_ver = 0;
|
|
|
|
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;
|
|
KERNEL_LOCK();
|
|
error = (*ifp->if_ioctl)(ifp, SIOCADDMULTI, (caddr_t)&ifr);
|
|
KERNEL_UNLOCK();
|
|
|
|
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, &ip6_mrouterq, 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 < getuptime()) {
|
|
char src[INET6_ADDRSTRLEN], dst[INET6_ADDRSTRLEN];
|
|
|
|
ip6_log_time = getuptime();
|
|
|
|
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, u_int rtableid)
|
|
{
|
|
struct mf6c *mf6c = (struct mf6c *)rt->rt_llinfo;
|
|
#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, &ip6_mrouterq, 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 divide
|
|
* 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, &ifnetlist, 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);
|
|
}
|