#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/protosw.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>
#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 *, int);
void phyint_send6(struct ifnet *, struct ip6_hdr *, struct mbuf *, int, int);
struct socket *ip6_mrouter[RT_TABLEID_MAX + 1];
struct rttimer_queue ip6_mrouterq;
int ip6_mrouter_ver = 0;
int ip6_mrtproto;
struct cpumem *mrt6counters;
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 *);
void mf6c_expire_route(struct rtentry *, u_int);
struct ifnet *mrt6_iflookupbymif(mifi_t, unsigned int);
struct rtentry *mf6c_find(struct ifnet *, struct in6_addr *, unsigned int);
struct rtentry *mrt6_mcast_add(struct ifnet *, struct sockaddr *);
void mrt6_mcast_del(struct rtentry *, unsigned int);
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);
}
}
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;
}
}
void
mrt6_init(void)
{
mrt6counters = counters_alloc(mrt6s_ncounters);
rt_timer_queue_init(&ip6_mrouterq, MCAST_EXPIRE_TIMEOUT,
&mf6c_expire_route);
}
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;
}
int
get_sg6_cnt(struct sioc_sg_req6 *req, unsigned int rtableid)
{
struct rtentry *rt;
struct mf6c *mf6c;
rt = mf6c_find(NULL, &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;
}
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)
{
TAILQ_HEAD(, ifnet) if_tmplist =
TAILQ_HEAD_INITIALIZER(if_tmplist);
struct ifnet *ifp;
caddr_t where = oldp;
size_t needed, given;
struct mif6 *mifp;
struct mif6info minfo;
int error = 0;
given = *oldlenp;
needed = 0;
memset(&minfo, 0, sizeof minfo);
rw_enter_write(&if_tmplist_lock);
NET_LOCK_SHARED();
TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
if (ifp->if_mcast6 != NULL) {
if_ref(ifp);
TAILQ_INSERT_TAIL(&if_tmplist, ifp, if_tmplist);
}
}
NET_UNLOCK_SHARED();
TAILQ_FOREACH (ifp, &if_tmplist, if_tmplist) {
NET_LOCK_SHARED();
if ((mifp = (struct mif6 *)ifp->if_mcast6) == NULL) {
NET_UNLOCK_SHARED();
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;
NET_UNLOCK_SHARED();
needed += sizeof(minfo);
if (where && needed <= given) {
error = copyout(&minfo, where, sizeof(minfo));
if (error)
break;
where += sizeof(minfo);
}
}
while ((ifp = TAILQ_FIRST(&if_tmplist))) {
TAILQ_REMOVE(&if_tmplist, ifp, if_tmplist);
if_put(ifp);
}
rw_exit_write(&if_tmplist_lock);
if (error)
return (error);
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;
if (mf6c == NULL)
return 0;
if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) !=
(RTF_HOST | RTF_MULTICAST))
return 0;
if (msa->ms6a_minfos == NULL) {
msa->ms6a_needed += sizeof(*minfo);
return 0;
}
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 + 1) <=
(uint8_t *)msa->ms6a_minfos + msa->ms6a_len;
minfo++) {
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_mrt6stat(void *oldp, size_t *oldlenp, void *newp)
{
uint64_t counters[mrt6s_ncounters];
struct mrt6stat mrt6stat;
int i = 0;
#define ASSIGN(field) do { mrt6stat.field = counters[i++]; } while (0)
memset(&mrt6stat, 0, sizeof mrt6stat);
counters_read(mrt6counters, counters, nitems(counters), NULL);
ASSIGN(mrt6s_mfc_lookups);
ASSIGN(mrt6s_mfc_misses);
ASSIGN(mrt6s_upcalls);
ASSIGN(mrt6s_no_route);
ASSIGN(mrt6s_bad_tunnel);
ASSIGN(mrt6s_cant_tunnel);
ASSIGN(mrt6s_wrong_if);
ASSIGN(mrt6s_upq_ovflw);
ASSIGN(mrt6s_cache_cleanups);
ASSIGN(mrt6s_drop_sel);
ASSIGN(mrt6s_q_overflow);
ASSIGN(mrt6s_pkt2large);
ASSIGN(mrt6s_upq_sockfull);
#undef ASSIGN
return (sysctl_rdstruct(oldp, oldlenp, newp,
&mrt6stat, sizeof(mrt6stat)));
}
int
mrt6_sysctl_mfc(void *oldp, size_t *oldlenp)
{
unsigned int rtableid;
int error;
struct mf6csysctlarg msa;
if (oldp != NULL && *oldlenp > MAXPHYS)
return EINVAL;
memset(&msa, 0, sizeof(msa));
if (oldp != NULL && *oldlenp > 0) {
msa.ms6a_minfos = malloc(*oldlenp, M_TEMP, M_WAITOK | M_ZERO);
msa.ms6a_len = *oldlenp;
}
NET_LOCK();
for (rtableid = 0; rtableid <= RT_TABLEID_MAX; rtableid++) {
rtable_walk(rtableid, AF_INET6, NULL, mrt6_rtwalk_mf6csysctl,
&msa);
}
NET_UNLOCK();
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, msa.ms6a_len);
return error;
}
free(msa.ms6a_minfos, M_TEMP, msa.ms6a_len);
*oldlenp = msa.ms6a_needed;
return 0;
}
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)
{
if (ISSET(rt->rt_flags, RTF_HOST | RTF_MULTICAST) !=
(RTF_HOST | RTF_MULTICAST))
return 0;
return EEXIST;
}
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();
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);
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));
}
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;
{
ifp = if_get(mifcp->mif6c_pifi);
if (ifp == NULL)
return ENXIO;
if ((ifp->if_flags & IFF_MULTICAST) == 0) {
if_put(ifp);
return EOPNOTSUPP;
}
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
mifp->m6_rate_limit = mifcp->mif6c_rate_limit * 1024 / 1000;
#endif
if_put(ifp);
return 0;
}
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
rt = mrt6_mcast_add(ifp, 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
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;
if ((ifp = mrt6_iflookupbymif(mifi, rtableid)) == NULL)
continue;
rt = mf6c_find(ifp, &mf6cc->mf6cc_mcastgrp.sin6_addr, rtableid);
if (!IF_ISSET(mifi, &mf6cc->mf6cc_ifset)) {
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;
}
if (rt != NULL) {
mf6c = (struct mf6c *)rt->rt_llinfo;
if (mf6c == NULL) {
rtfree(rt);
continue;
}
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);
}
if ((ifp = mrt6_iflookupbymif(mf6cc->mf6cc_parent,
rtableid)) == NULL) {
DPRINTF("failed to find upstream interface %d",
mf6cc->mf6cc_parent);
return;
}
if ((rt = mf6c_find(ifp, &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_mcastgrp.sin6_addr,
rtableid)) != NULL) {
mrt6_mcast_del(rt, rtableid);
rtfree(rt);
}
return 0;
}
int
socket6_send(struct socket *so, struct mbuf *mm, struct sockaddr_in6 *src)
{
if (so != NULL) {
int ret;
mtx_enter(&so->so_rcv.sb_mtx);
ret = sbappendaddr(&so->so_rcv, sin6tosa(src), mm, NULL);
mtx_leave(&so->so_rcv.sb_mtx);
if (ret != 0) {
sorwakeup(so);
return 0;
}
}
m_freem(mm);
return -1;
}
int
ip6_mforward(struct ip6_hdr *ip6, struct ifnet *ifp, struct mbuf *m, int flags)
{
struct rtentry *rt;
struct mif6 *mifp;
struct mbuf *mm;
struct sockaddr_in6 sin6;
unsigned int rtableid = ifp->if_rdomain;
NET_ASSERT_LOCKED();
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--;
if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) {
ip6stat_inc(ip6s_cantforward);
return 0;
}
mrt6stat_inc(mrt6s_mfc_lookups);
rt = mf6c_find(NULL, &ip6->ip6_dst, rtableid);
if (rt) {
return (ip6_mdq(m, ifp, rt, flags));
} else {
mrt6stat_inc(mrt6s_mfc_misses);
mrt6stat_inc(mrt6s_no_route);
{
struct mrt6msg *im;
if ((mifp = (struct mif6 *)ifp->if_mcast6) == NULL)
return EHOSTUNREACH;
mm = m_copym(m, 0, sizeof(struct ip6_hdr), M_NOWAIT);
if (mm == NULL)
return ENOBUFS;
(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_inc(mrt6s_upq_sockfull);
return ENOBUFS;
}
mrt6stat_inc(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
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);
}
int
ip6_mdq(struct mbuf *m, struct ifnet *ifp, struct rtentry *rt, int flags)
{
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, ip6_mcast_pmtu_local;
if (mifp == NULL || mf6c == NULL) {
rtfree(rt);
return EHOSTUNREACH;
}
if (mifp->m6_mifi != mf6c->mf6c_parent) {
mrt6stat_inc(mrt6s_wrong_if);
mf6c->mf6c_wrong_if++;
rtfree(rt);
return 0;
}
if (m->m_pkthdr.ph_ifidx == 0) {
mifp->m6_pkt_out++;
mifp->m6_bytes_out += plen;
} else {
mifp->m6_pkt_in++;
mifp->m6_bytes_in += plen;
}
ip6_mcast_pmtu_local = atomic_load_int(&ip6_mcast_pmtu);
do {
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;
mf6c->mf6c_expire = 0;
if ((ifn = if_get(rt->rt_ifidx)) == NULL)
continue;
if ((m6 = (struct mif6 *)ifn->if_mcast6) == NULL) {
if_put(ifn);
continue;
}
if (mf6c->mf6c_parent == m6->m6_mifi) {
if_put(ifn);
continue;
}
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, flags, ip6_mcast_pmtu_local);
if_put(ifn);
} while ((rt = rtable_iterate(rt)) != NULL);
return 0;
}
void
phyint_send6(struct ifnet *ifp, struct ip6_hdr *ip6, struct mbuf *m,
int flags, int mcast_pmtu)
{
struct mbuf *mb_copy;
struct sockaddr_in6 *dst6, sin6;
int error = 0;
NET_ASSERT_LOCKED();
mb_copy = m_dup_pkt(m, max_linkhdr, M_NOWAIT);
if (mb_copy == NULL)
return;
mb_copy->m_flags |= M_MCAST;
if (m->m_pkthdr.ph_ifidx == 0) {
struct ip6_moptions im6o;
im6o.im6o_ifidx = ifp->if_index;
im6o.im6o_hlim = ip6->ip6_hlim;
im6o.im6o_loop = 1;
error = ip6_output(mb_copy, NULL, NULL, flags | IPV6_FORWARDING,
&im6o, NULL);
return;
}
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);
}
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 (mcast_pmtu)
icmp6_error(mb_copy, ICMP6_PACKET_TOO_BIG, 0,
ifp->if_mtu);
else {
m_freem(mb_copy);
}
}
}
struct ifnet *
mrt6_iflookupbymif(mifi_t mifi, unsigned int rtableid)
{
struct mif6 *m6;
struct ifnet *ifp;
NET_ASSERT_LOCKED();
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 *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;
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 *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, &satosin6(group)->sin6_addr, rtableid);
}
void
mrt6_mcast_del(struct rtentry *rt, unsigned int rtableid)
{
struct ifnet *ifp;
int error;
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);
}