#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <net/if.h>
#include <net/if_var.h>
#include <netinet/in.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet/icmp6.h>
#include <netinet6/mld6.h>
#include <netinet6/mld6_var.h>
static struct ip6_pktopts ip6_opts;
int mld6_timers_are_running;
int mld6_checktimer(struct ifnet *, struct mld6_pktlist *);
void
mld6_init(void)
{
static u_int8_t hbh_buf[8];
struct ip6_hbh *hbh = (struct ip6_hbh *)hbh_buf;
u_int16_t rtalert_code = htons((u_int16_t)IP6OPT_RTALERT_MLD);
mld6_timers_are_running = 0;
hbh->ip6h_len = 0;
hbh_buf[2] = IP6OPT_PADN;
hbh_buf[3] = 0;
hbh_buf[4] = IP6OPT_ROUTER_ALERT;
hbh_buf[5] = IP6OPT_RTALERT_LEN - 2;
memcpy(&hbh_buf[6], (caddr_t)&rtalert_code, sizeof(u_int16_t));
ip6_initpktopts(&ip6_opts);
ip6_opts.ip6po_hbh = hbh;
}
void
mld6_start_listening(struct in6_multi *in6m, struct ifnet *ifp,
struct mld6_pktinfo *pkt)
{
struct in6_addr all_nodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
int running = 0;
rw_assert_wrlock(&ifp->if_maddrlock);
all_nodes.s6_addr16[1] = htons(in6m->in6m_ifidx);
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_nodes) ||
__IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) <
__IPV6_ADDR_SCOPE_LINKLOCAL) {
in6m->in6m_state = MLD_OTHERLISTENER;
in6m->in6m_timer = 0;
} else {
in6m->in6m_state = MLD_IREPORTEDLAST;
in6m->in6m_timer =
MLD_RANDOM_DELAY(MLD_V1_MAX_RI * PR_FASTHZ);
pkt->mpi_addr = in6m->in6m_addr;
pkt->mpi_rdomain = ifp->if_rdomain;
pkt->mpi_ifidx = in6m->in6m_ifidx;
pkt->mpi_type = MLD_LISTENER_REPORT;
running = 1;
}
if (running)
atomic_store_int(&mld6_timers_are_running, 1);
}
void
mld6_stop_listening(struct in6_multi *in6m, struct ifnet *ifp,
struct mld6_pktinfo *pkt)
{
struct in6_addr all_nodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
struct in6_addr all_routers = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT;
rw_assert_anylock(&ifp->if_maddrlock);
all_nodes.s6_addr16[1] = htons(in6m->in6m_ifidx);
all_routers.s6_addr16[1] = htons(in6m->in6m_ifidx);
if (in6m->in6m_state == MLD_IREPORTEDLAST &&
(!IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_nodes)) &&
__IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) >
__IPV6_ADDR_SCOPE_INTFACELOCAL) {
pkt->mpi_addr = all_routers;
pkt->mpi_rdomain = ifp->if_rdomain;
pkt->mpi_ifidx = in6m->in6m_ifidx;
pkt->mpi_type = MLD_LISTENER_DONE;
}
}
void
mld6_input(struct mbuf *m, int off)
{
struct ip6_hdr *ip6;
struct mld_hdr *mldh;
struct ifnet *ifp;
struct in6_multi *in6m;
struct ifmaddr *ifma;
int timer;
int running = 0;
struct in6_addr all_nodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
mldh = ip6_exthdr_get(&m, off, sizeof(*mldh));
if (mldh == NULL) {
icmp6stat_inc(icp6s_tooshort);
return;
}
ip6 = mtod(m, struct ip6_hdr *);
if (!IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) {
#if 0
char src[INET6_ADDRSTRLEN], grp[INET6_ADDRSTRLEN];
log(LOG_ERR,
"mld_input: src %s is not link-local (grp=%s)\n",
inet_ntop(AF_INET6, &ip6->ip6_src, src, sizeof(src)),
inet_ntop(AF_INET6, &mldh->mld_addr, grp, sizeof(grp)));
#endif
m_freem(m);
return;
}
ifp = if_get(m->m_pkthdr.ph_ifidx);
if (ifp == NULL) {
m_freem(m);
return;
}
switch(mldh->mld_type) {
case MLD_LISTENER_QUERY: {
struct mld6_pktlist pktlist;
if (ifp->if_flags & IFF_LOOPBACK)
break;
if (!IN6_IS_ADDR_UNSPECIFIED(&mldh->mld_addr) &&
!IN6_IS_ADDR_MULTICAST(&mldh->mld_addr))
break;
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
mldh->mld_addr.s6_addr16[1] =
htons(ifp->if_index);
timer = ntohs(mldh->mld_maxdelay)*PR_FASTHZ/MLD_TIMER_SCALE;
if (timer == 0 && mldh->mld_maxdelay)
timer = 1;
all_nodes.s6_addr16[1] = htons(ifp->if_index);
rw_enter_write(&ifp->if_maddrlock);
STAILQ_INIT(&pktlist);
TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
if (ifma->ifma_addr->sa_family != AF_INET6)
continue;
in6m = ifmatoin6m(ifma);
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_nodes) ||
__IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) <
__IPV6_ADDR_SCOPE_LINKLOCAL)
continue;
if (IN6_IS_ADDR_UNSPECIFIED(&mldh->mld_addr) ||
IN6_ARE_ADDR_EQUAL(&mldh->mld_addr,
&in6m->in6m_addr))
{
if (timer == 0) {
struct mld6_pktinfo *pkt;
in6m->in6m_state = MLD_IREPORTEDLAST;
in6m->in6m_timer = 0;
pkt = malloc(sizeof(*pkt), M_MRTABLE,
M_NOWAIT);
if (pkt == NULL)
continue;
pkt->mpi_addr = in6m->in6m_addr;
pkt->mpi_rdomain = ifp->if_rdomain;
pkt->mpi_ifidx = in6m->in6m_ifidx;
pkt->mpi_type = MLD_LISTENER_REPORT;
STAILQ_INSERT_TAIL(&pktlist, pkt,
mpi_list);
} else if (in6m->in6m_timer == 0 ||
in6m->in6m_timer > timer) {
in6m->in6m_timer =
MLD_RANDOM_DELAY(timer);
running = 1;
}
}
}
rw_exit_write(&ifp->if_maddrlock);
while (!STAILQ_EMPTY(&pktlist)) {
struct mld6_pktinfo *pkt;
pkt = STAILQ_FIRST(&pktlist);
STAILQ_REMOVE_HEAD(&pktlist, mpi_list);
mld6_sendpkt(pkt);
free(pkt, M_MRTABLE, sizeof(*pkt));
}
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
mldh->mld_addr.s6_addr16[1] = 0;
break;
}
case MLD_LISTENER_REPORT:
if (m->m_flags & M_LOOP)
break;
if (!IN6_IS_ADDR_MULTICAST(&mldh->mld_addr))
break;
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
mldh->mld_addr.s6_addr16[1] =
htons(ifp->if_index);
rw_enter_write(&ifp->if_maddrlock);
in6m = in6_lookupmulti(&mldh->mld_addr, ifp);
if (in6m) {
in6m->in6m_state = MLD_OTHERLISTENER;
in6m->in6m_timer = 0;
}
rw_exit_write(&ifp->if_maddrlock);
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
mldh->mld_addr.s6_addr16[1] = 0;
break;
default:
#if 0
log(LOG_ERR, "mld_input: illegal type(%d)", mldh->mld_type);
#endif
break;
}
if (running)
atomic_store_int(&mld6_timers_are_running, 1);
if_put(ifp);
m_freem(m);
}
void
mld6_fasttimo(void)
{
struct mld6_pktlist pktlist;
struct ifnet *ifp;
int running = 0;
if (!atomic_load_int(&mld6_timers_are_running))
return;
atomic_store_int(&mld6_timers_are_running, 0);
NET_LOCK_SHARED();
STAILQ_INIT(&pktlist);
TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
if (mld6_checktimer(ifp, &pktlist))
running = 1;
}
while (!STAILQ_EMPTY(&pktlist)) {
struct mld6_pktinfo *pkt;
pkt = STAILQ_FIRST(&pktlist);
STAILQ_REMOVE_HEAD(&pktlist, mpi_list);
mld6_sendpkt(pkt);
free(pkt, M_MRTABLE, sizeof(*pkt));
}
NET_UNLOCK_SHARED();
if (running)
atomic_store_int(&mld6_timers_are_running, 1);
}
int
mld6_checktimer(struct ifnet *ifp, struct mld6_pktlist *pktlist)
{
struct in6_multi *in6m;
struct ifmaddr *ifma;
int running = 0;
rw_enter_write(&ifp->if_maddrlock);
TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
if (ifma->ifma_addr->sa_family != AF_INET6)
continue;
in6m = ifmatoin6m(ifma);
if (in6m->in6m_timer == 0) {
} else if (--in6m->in6m_timer == 0) {
struct mld6_pktinfo *pkt;
in6m->in6m_state = MLD_IREPORTEDLAST;
pkt = malloc(sizeof(*pkt), M_MRTABLE, M_NOWAIT);
if (pkt == NULL)
continue;
pkt->mpi_addr = in6m->in6m_addr;
pkt->mpi_rdomain = ifp->if_rdomain;
pkt->mpi_ifidx = in6m->in6m_ifidx;
pkt->mpi_type = MLD_LISTENER_REPORT;
STAILQ_INSERT_TAIL(pktlist, pkt, mpi_list);
} else {
running = 1;
}
}
rw_exit_write(&ifp->if_maddrlock);
return (running);
}
void
mld6_sendpkt(const struct mld6_pktinfo *pkt)
{
struct mbuf *mh, *md;
struct mld_hdr *mldh;
struct ip6_hdr *ip6;
struct ip6_moptions im6o;
struct in6_ifaddr *ia6;
struct ifnet *ifp;
int ignflags;
ifp = if_get(pkt->mpi_ifidx);
if (ifp == NULL)
return;
ignflags = IN6_IFF_DUPLICATED|IN6_IFF_ANYCAST;
if ((ia6 = in6ifa_ifpforlinklocal(ifp, ignflags)) == NULL) {
if_put(ifp);
return;
}
if ((ia6->ia6_flags & IN6_IFF_TENTATIVE))
ia6 = NULL;
MGETHDR(mh, M_DONTWAIT, MT_HEADER);
if (mh == NULL) {
if_put(ifp);
return;
}
MGET(md, M_DONTWAIT, MT_DATA);
if (md == NULL) {
m_free(mh);
if_put(ifp);
return;
}
mh->m_next = md;
mh->m_pkthdr.ph_rtableid = pkt->mpi_rdomain;
mh->m_pkthdr.len = sizeof(struct ip6_hdr) + sizeof(struct mld_hdr);
mh->m_len = sizeof(struct ip6_hdr);
m_align(mh, sizeof(struct ip6_hdr));
ip6 = mtod(mh, struct ip6_hdr *);
ip6->ip6_flow = 0;
ip6->ip6_vfc &= ~IPV6_VERSION_MASK;
ip6->ip6_vfc |= IPV6_VERSION;
ip6->ip6_nxt = IPPROTO_ICMPV6;
ip6->ip6_src = ia6 ? ia6->ia_addr.sin6_addr : in6addr_any;
ip6->ip6_dst = pkt->mpi_addr;
md->m_len = sizeof(struct mld_hdr);
mldh = mtod(md, struct mld_hdr *);
mldh->mld_type = pkt->mpi_type;
mldh->mld_code = 0;
mldh->mld_cksum = 0;
mldh->mld_maxdelay = 0;
mldh->mld_reserved = 0;
mldh->mld_addr = pkt->mpi_addr;
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
mldh->mld_addr.s6_addr16[1] = 0;
mh->m_pkthdr.csum_flags |= M_ICMP_CSUM_OUT;
bzero(&im6o, sizeof(im6o));
im6o.im6o_ifidx = pkt->mpi_ifidx;
im6o.im6o_hlim = 1;
#ifdef MROUTING
im6o.im6o_loop = (ip6_mrouter[pkt->mpi_rdomain] != NULL);
#endif
if_put(ifp);
icmp6stat_inc(icp6s_outhist + pkt->mpi_type);
ip6_output(mh, &ip6_opts, NULL, ia6 ? 0 : IPV6_UNSPECSRC, &im6o, NULL);
}