summaryrefslogtreecommitdiffstats
path: root/sys/netinet6
diff options
context:
space:
mode:
authorbms <bms@FreeBSD.org>2009-04-29 19:19:13 +0000
committerbms <bms@FreeBSD.org>2009-04-29 19:19:13 +0000
commit32a71137f08bc028578417de36a241d7e6011f58 (patch)
tree51d9a006ee48417962ce45f044b7e5603910fe13 /sys/netinet6
parent51a4d1c4a3d279a3638c0b40f351aa93f965c7df (diff)
downloadFreeBSD-src-32a71137f08bc028578417de36a241d7e6011f58.zip
FreeBSD-src-32a71137f08bc028578417de36a241d7e6011f58.tar.gz
Bite the bullet, and make the IPv6 SSM and MLDv2 mega-commit:
import from p4 bms_netdev. Summary of changes: * Connect netinet6/in6_mcast.c to build. The legacy KAME KPIs are mostly preserved. * Eliminate now dead code from ip6_output.c. Don't do mbuf bingo, we are not going to do RFC 2292 style CMSG tricks for multicast options as they are not required by any current IPv6 normative reference. * Refactor transports (UDP, raw_ip6) to do own mcast filtering. SCTP, TCP unaffected by this change. * Add ip6_msource, in6_msource structs to in6_var.h. * Hookup mld_ifinfo state to in6_ifextra, allocate from domifattach path. * Eliminate IN6_LOOKUP_MULTI(), it is no longer referenced. Kernel consumers which need this should use in6m_lookup(). * Refactor IPv6 socket group memberships to use a vector (like IPv4). * Update ifmcstat(8) for IPv6 SSM. * Add witness lock order for IN6_MULTI_LOCK. * Move IN6_MULTI_LOCK out of lower ip6_output()/ip6_input() paths. * Introduce IP6STAT_ADD/SUB/INC/DEC as per rwatson's IPv4 cleanup. * Update carp(4) for new IPv6 SSM KPIs. * Virtualize ip6_mrouter socket. Changes mostly localized to IPv6 MROUTING. * Don't do a local group lookup in MROUTING. * Kill unused KAME prototypes in6_purgemkludge(), in6_restoremkludge(). * Preserve KAME DAD timer jitter behaviour in MLDv1 compatibility mode. * Bump __FreeBSD_version to 800084. * Update UPDATING. NOTE WELL: * This code hasn't been tested against real MLDv2 queriers (yet), although the on-wire protocol has been verified in Wireshark. * There are a few unresolved issues in the socket layer APIs to do with scope ID propagation. * There is a LOR present in ip6_output()'s use of in6_setscope() which needs to be resolved. See comments in mld6.c. This is believed to be benign and can't be avoided for the moment without re-introducing an indirect netisr. This work was mostly derived from the IGMPv3 implementation, and has been sponsored by a third party.
Diffstat (limited to 'sys/netinet6')
-rw-r--r--sys/netinet6/icmp6.c64
-rw-r--r--sys/netinet6/in6.c43
-rw-r--r--sys/netinet6/in6_ifattach.c35
-rw-r--r--sys/netinet6/in6_mcast.c165
-rw-r--r--sys/netinet6/in6_pcb.c30
-rw-r--r--sys/netinet6/in6_proto.c1
-rw-r--r--sys/netinet6/in6_var.h293
-rw-r--r--sys/netinet6/ip6_input.c29
-rw-r--r--sys/netinet6/ip6_mroute.c34
-rw-r--r--sys/netinet6/ip6_output.c453
-rw-r--r--sys/netinet6/ip6_var.h24
-rw-r--r--sys/netinet6/mld6.c3339
-rw-r--r--sys/netinet6/mld6_var.h147
-rw-r--r--sys/netinet6/raw_ip6.c61
-rw-r--r--sys/netinet6/udp6_usrreq.c40
-rw-r--r--sys/netinet6/vinet6.h16
16 files changed, 3689 insertions, 1085 deletions
diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c
index 866fee1..1c8a132 100644
--- a/sys/netinet6/icmp6.c
+++ b/sys/netinet6/icmp6.c
@@ -147,8 +147,6 @@ icmp6_init(void)
INIT_VNET_INET6(curvnet);
V_icmp6errpps_count = 0;
-
- mld6_init();
}
static void
@@ -429,6 +427,23 @@ icmp6_input(struct mbuf **mp, int *offp, int proto)
}
/*
+ * Check multicast group membership.
+ * Note: SSM filters are not applied for ICMPv6 traffic.
+ */
+ if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+ struct ifnet *ifp;
+ struct in6_multi *inm;
+
+ ifp = m->m_pkthdr.rcvif;
+ inm = in6m_lookup(ifp, &ip6->ip6_dst);
+ if (inm == NULL) {
+ IP6STAT_INC(ip6s_notmember);
+ in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard);
+ goto freeit;
+ }
+ }
+
+ /*
* calculate the checksum
*/
#ifndef PULLDOWN_TEST
@@ -615,34 +630,20 @@ icmp6_input(struct mbuf **mp, int *offp, int proto)
case MLD_LISTENER_QUERY:
case MLD_LISTENER_REPORT:
- if (icmp6len < sizeof(struct mld_hdr))
- goto badlen;
- if (icmp6->icmp6_type == MLD_LISTENER_QUERY) /* XXX: ugly... */
- icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mldquery);
- else
- icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mldreport);
- if ((n = m_copym(m, 0, M_COPYALL, M_DONTWAIT)) == NULL) {
- /* give up local */
- mld6_input(m, off);
- m = NULL;
+ case MLD_LISTENER_DONE:
+ case MLDV2_LISTENER_REPORT:
+ /*
+ * Drop MLD traffic which is not link-local.
+ * XXX Should we also sanity check that these messages
+ * were directed to a link-local multicast prefix?
+ */
+ if (ip6->ip6_hlim != 1)
goto freeit;
- }
- mld6_input(n, off);
+ if (mld_input(m, off, icmp6len) != 0)
+ return (IPPROTO_DONE);
/* m stays. */
break;
- case MLD_LISTENER_DONE:
- icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mlddone);
- if (icmp6len < sizeof(struct mld_hdr)) /* necessary? */
- goto badlen;
- break; /* nothing to be done in kernel */
-
- case MLD_MTRACE_RESP:
- case MLD_MTRACE:
- /* XXX: these two are experimental. not officially defined. */
- /* XXX: per-interface statistics? */
- break; /* just pass it to applications */
-
case ICMP6_WRUREQUEST: /* ICMP6_FQDN_QUERY */
{
enum { WRU, FQDN } mode;
@@ -2050,7 +2051,7 @@ icmp6_rip6_input(struct mbuf **mp, int off)
INP_RUNLOCK(last);
} else {
m_freem(m);
- V_ip6stat.ip6s_delivered--;
+ IP6STAT_DEC(ip6s_delivered);
}
return IPPROTO_DONE;
}
@@ -2222,7 +2223,14 @@ void
icmp6_fasttimo(void)
{
- return;
+ mld_fasttimo();
+}
+
+void
+icmp6_slowtimo(void)
+{
+
+ mld_slowtimo();
}
static const char *
diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c
index d0caa7e..df57bf6 100644
--- a/sys/netinet6/in6.c
+++ b/sys/netinet6/in6.c
@@ -106,8 +106,6 @@ __FBSDID("$FreeBSD$");
#include <netinet6/in6_pcb.h>
#include <netinet6/vinet6.h>
-MALLOC_DEFINE(M_IP6MADDR, "in6_multi", "internet multicast address");
-
/*
* Definitions of some costant IP6 addresses.
*/
@@ -119,6 +117,8 @@ const struct in6_addr in6addr_linklocal_allnodes =
IN6ADDR_LINKLOCAL_ALLNODES_INIT;
const struct in6_addr in6addr_linklocal_allrouters =
IN6ADDR_LINKLOCAL_ALLROUTERS_INIT;
+const struct in6_addr in6addr_linklocal_allv2routers =
+ IN6ADDR_LINKLOCAL_ALLV2ROUTERS_INIT;
const struct in6_addr in6mask0 = IN6MASK0;
const struct in6_addr in6mask32 = IN6MASK32;
@@ -135,7 +135,6 @@ static int in6_ifinit __P((struct ifnet *, struct in6_ifaddr *,
struct sockaddr_in6 *, int));
static void in6_unlink_ifa(struct in6_ifaddr *, struct ifnet *);
-struct in6_multihead in6_multihead; /* XXX BSS initialization */
int (*faithprefix_p)(struct in6_addr *);
@@ -1110,10 +1109,12 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra,
* should be larger than the MLD delay (this could be
* relaxed a bit, but this simple logic is at least
* safe).
+ * XXX: Break data hiding guidelines and look at
+ * state for the solicited multicast group.
*/
mindelay = 0;
if (in6m_sol != NULL &&
- in6m_sol->in6m_state == MLD_REPORTPENDING) {
+ in6m_sol->in6m_state == MLD_REPORTING_MEMBER) {
mindelay = in6m_sol->in6m_timer;
}
maxdelay = MAX_RTR_SOLICITATION_DELAY * hz;
@@ -1590,36 +1591,6 @@ in6_ifinit(struct ifnet *ifp, struct in6_ifaddr *ia,
return (error);
}
-struct in6_multi_mship *
-in6_joingroup(struct ifnet *ifp, struct in6_addr *addr,
- int *errorp, int delay)
-{
- struct in6_multi_mship *imm;
-
- imm = malloc(sizeof(*imm), M_IP6MADDR, M_NOWAIT);
- if (!imm) {
- *errorp = ENOBUFS;
- return NULL;
- }
- imm->i6mm_maddr = in6_addmulti(addr, ifp, errorp, delay);
- if (!imm->i6mm_maddr) {
- /* *errorp is alrady set */
- free(imm, M_IP6MADDR);
- return NULL;
- }
- return imm;
-}
-
-int
-in6_leavegroup(struct in6_multi_mship *imm)
-{
-
- if (imm->i6mm_maddr)
- in6_delmulti(imm->i6mm_maddr);
- free(imm, M_IP6MADDR);
- return 0;
-}
-
/*
* Find an IPv6 interface link-local address specific to an interface.
*/
@@ -2328,6 +2299,9 @@ in6_domifattach(struct ifnet *ifp)
ext->lltable->llt_lookup = in6_lltable_lookup;
ext->lltable->llt_dump = in6_lltable_dump;
}
+
+ ext->mld_ifinfo = mld_domifattach(ifp);
+
return ext;
}
@@ -2336,6 +2310,7 @@ in6_domifdetach(struct ifnet *ifp, void *aux)
{
struct in6_ifextra *ext = (struct in6_ifextra *)aux;
+ mld_domifdetach(ifp);
scope6_ifdetach(ext->scope6_id);
nd6_ifdetach(ext->nd_ifinfo);
lltable_free(ext->lltable);
diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c
index 80b6d6b..077014e 100644
--- a/sys/netinet6/in6_ifattach.c
+++ b/sys/netinet6/in6_ifattach.c
@@ -63,6 +63,7 @@ __FBSDID("$FreeBSD$");
#include <netinet6/in6_ifattach.h>
#include <netinet6/ip6_var.h>
#include <netinet6/nd6.h>
+#include <netinet6/mld6_var.h>
#include <netinet6/scope6_var.h>
#include <netinet6/vinet6.h>
@@ -918,11 +919,35 @@ in6_tmpaddrtimer(void *ignored_arg)
static void
in6_purgemaddrs(struct ifnet *ifp)
{
- struct in6_multi *in6m;
- struct in6_multi *oin6m;
+ INIT_VNET_INET6(ifp->if_vnet);
+ LIST_HEAD(,in6_multi) purgeinms;
+ struct in6_multi *inm, *tinm;
+ struct ifmultiaddr *ifma;
+
+ LIST_INIT(&purgeinms);
+ IN6_MULTI_LOCK();
+
+ /*
+ * Extract list of in6_multi associated with the detaching ifp
+ * which the PF_INET6 layer is about to release.
+ * We need to do this as IF_ADDR_LOCK() may be re-acquired
+ * by code further down.
+ */
+ IF_ADDR_LOCK(ifp);
+ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ if (ifma->ifma_addr->sa_family != AF_INET6 ||
+ ifma->ifma_protospec == NULL)
+ continue;
+ inm = (struct in6_multi *)ifma->ifma_protospec;
+ LIST_INSERT_HEAD(&purgeinms, inm, in6m_entry);
+ }
+ IF_ADDR_UNLOCK(ifp);
- LIST_FOREACH_SAFE(in6m, &in6_multihead, in6m_entry, oin6m) {
- if (in6m->in6m_ifp == ifp)
- in6_delmulti(in6m);
+ LIST_FOREACH_SAFE(inm, &purgeinms, in6m_entry, tinm) {
+ LIST_REMOVE(inm, in6m_entry);
+ in6m_release_locked(inm);
}
+ mld_ifdetach(ifp);
+
+ IN6_MULTI_UNLOCK();
}
diff --git a/sys/netinet6/in6_mcast.c b/sys/netinet6/in6_mcast.c
index a4d435c..b3f272c 100644
--- a/sys/netinet6/in6_mcast.c
+++ b/sys/netinet6/in6_mcast.c
@@ -29,6 +29,7 @@
/*
* IPv6 multicast socket, group, and socket option processing module.
+ * Normative references: RFC 2292, RFC 3492, RFC 3542, RFC 3678, RFC 3810.
*/
#include <sys/cdefs.h>
@@ -142,6 +143,9 @@ static struct ip6_moptions *
static int in6p_get_source_filters(struct inpcb *, struct sockopt *);
static int in6p_join_group(struct inpcb *, struct sockopt *);
static int in6p_leave_group(struct inpcb *, struct sockopt *);
+static struct ifnet *
+ in6p_lookup_mcast_ifp(const struct inpcb *,
+ const struct sockaddr_in6 *);
static int in6p_block_unblock_source(struct inpcb *, struct sockopt *);
static int in6p_set_multicast_if(struct inpcb *, struct sockopt *);
static int in6p_set_source_filters(struct inpcb *, struct sockopt *);
@@ -1655,12 +1659,12 @@ int
ip6_getmoptions(struct inpcb *inp, struct sockopt *sopt)
{
INIT_VNET_INET6(curvnet);
- struct ip6_moptions *imo;
- int error, optval;
- u_char coptval;
+ struct ip6_moptions *im6o;
+ int error;
+ u_int optval;
INP_WLOCK(inp);
- imo = inp->in6p_moptions;
+ im6o = inp->in6p_moptions;
/*
* If socket is neither of type SOCK_RAW or SOCK_DGRAM,
* or is a divert socket, reject it.
@@ -1674,38 +1678,36 @@ ip6_getmoptions(struct inpcb *inp, struct sockopt *sopt)
error = 0;
switch (sopt->sopt_name) {
-#if 0 /* XXX FIXME */
case IPV6_MULTICAST_IF:
- if (imo == NULL || imo->im6o_multicast_ifp == NULL) {
+ if (im6o == NULL || im6o->im6o_multicast_ifp == NULL) {
optval = 0;
} else {
- optval = imo->im6o_multicast_ifp->if_index;
+ optval = im6o->im6o_multicast_ifp->if_index;
}
INP_WUNLOCK(inp);
- error = sooptcopyout(sopt, &ifindex, sizeof(u_int));
+ error = sooptcopyout(sopt, &optval, sizeof(u_int));
break;
-#endif
case IPV6_MULTICAST_HOPS:
- if (imo == 0)
- optval = coptval = V_ip6_defmcasthlim;
+ if (im6o == NULL)
+ optval = V_ip6_defmcasthlim;
else
- optval = coptval = imo->im6o_multicast_loop;
+ optval = im6o->im6o_multicast_loop;
INP_WUNLOCK(inp);
error = sooptcopyout(sopt, &optval, sizeof(u_int));
break;
case IPV6_MULTICAST_LOOP:
- if (imo == 0)
- optval = coptval = IPV6_DEFAULT_MULTICAST_LOOP;
+ if (im6o == NULL)
+ optval = in6_mcast_loop; /* XXX VIMAGE */
else
- optval = coptval = imo->im6o_multicast_loop;
+ optval = im6o->im6o_multicast_loop;
INP_WUNLOCK(inp);
error = sooptcopyout(sopt, &optval, sizeof(u_int));
break;
case IPV6_MSFILTER:
- if (imo == NULL) {
+ if (im6o == NULL) {
error = EADDRNOTAVAIL;
INP_WUNLOCK(inp);
} else {
@@ -1725,7 +1727,57 @@ ip6_getmoptions(struct inpcb *inp, struct sockopt *sopt)
}
/*
+ * Look up the ifnet to use for a multicast group membership,
+ * given the address of an IPv6 group.
+ *
+ * This routine exists to support legacy IPv6 multicast applications.
+ *
+ * If inp is non-NULL, use this socket's current FIB number for any
+ * required FIB lookup. Look up the group address in the unicast FIB,
+ * and use its ifp; usually, this points to the default next-hop.
+ * If the FIB lookup fails, return NULL.
+ *
+ * FUTURE: Support multiple forwarding tables for IPv6.
+ *
+ * Returns NULL if no ifp could be found.
+ */
+static struct ifnet *
+in6p_lookup_mcast_ifp(const struct inpcb *in6p __unused,
+ const struct sockaddr_in6 *gsin6)
+{
+ INIT_VNET_INET6(curvnet);
+ struct route_in6 ro6;
+ struct ifnet *ifp;
+
+ KASSERT(in6p->inp_vflag & INP_IPV6,
+ ("%s: not INP_IPV6 inpcb", __func__));
+ KASSERT(gsin6->sin6_family == AF_INET6,
+ ("%s: not AF_INET6 group", __func__));
+ KASSERT(IN6_IS_ADDR_MULTICAST(&gsin6->sin6_addr),
+ ("%s: not multicast", __func__));
+
+ ifp = NULL;
+ memset(&ro6, 0, sizeof(struct route_in6));
+ memcpy(&ro6.ro_dst, gsin6, sizeof(struct sockaddr_in6));
+#ifdef notyet
+ rtalloc_ign_fib(&ro6, 0, inp ? inp->inp_inc.inc_fibnum : 0);
+#else
+ rtalloc_ign((struct route *)&ro6, 0);
+#endif
+ if (ro6.ro_rt != NULL) {
+ ifp = ro6.ro_rt->rt_ifp;
+ KASSERT(ifp != NULL, ("%s: null ifp", __func__));
+ RTFREE(ro6.ro_rt);
+ }
+
+ return (ifp);
+}
+
+/*
* Join an IPv6 multicast group, possibly with a source.
+ *
+ * FIXME: The KAME use of the unspecified address (::)
+ * to join *all* multicast groups is currently unsupported.
*/
static int
in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
@@ -1765,8 +1817,14 @@ in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
gsa->sin6.sin6_len = sizeof(struct sockaddr_in6);
gsa->sin6.sin6_addr = mreq.ipv6mr_multiaddr;
- ifp = ifnet_byindex(mreq.ipv6mr_interface);
-
+ if (mreq.ipv6mr_interface == 0) {
+ ifp = in6p_lookup_mcast_ifp(inp, &gsa->sin6);
+ } else {
+ if (mreq.ipv6mr_interface < 0 ||
+ V_if_index < mreq.ipv6mr_interface)
+ return (EADDRNOTAVAIL);
+ ifp = ifnet_byindex(mreq.ipv6mr_interface);
+ }
CTR3(KTR_MLD, "%s: ipv6mr_interface = %d, ifp = %p",
__func__, mreq.ipv6mr_interface, ifp);
} break;
@@ -1813,12 +1871,35 @@ in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
break;
}
+#ifdef notyet
+ /*
+ * FIXME: Check for unspecified address (all groups).
+ * Do we have a normative reference for this 'feature'?
+ *
+ * We use the unspecified address to specify to accept
+ * all multicast addresses. Only super user is allowed
+ * to do this.
+ * XXX-BZ might need a better PRIV_NETINET_x for this
+ */
+ if (IN6_IS_ADDR_UNSPECIFIED(&gsa->sin6.sin6_addr)) {
+ error = priv_check(curthread, PRIV_NETINET_MROUTE);
+ if (error)
+ break;
+ } else
+#endif
if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
return (EINVAL);
if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0)
return (EADDRNOTAVAIL);
+#ifdef notyet
+ /*
+ * FIXME: Set interface scope in group address.
+ */
+ (void)in6_setscope(&gsa->sin6.sin_addr, ifp, NULL);
+#endif
+
/*
* MCAST_JOIN_SOURCE on an exclusive membership is an error.
* On an existing inclusive membership, it just adds the
@@ -1987,7 +2068,23 @@ in6p_leave_group(struct inpcb *inp, struct sockopt *sopt)
gsa->sin6.sin6_family = AF_INET6;
gsa->sin6.sin6_len = sizeof(struct sockaddr_in6);
gsa->sin6.sin6_addr = mreq.ipv6mr_multiaddr;
- ifp = ifnet_byindex(mreq.ipv6mr_interface);
+
+ if (mreq.ipv6mr_interface == 0) {
+#ifdef notyet
+ /*
+ * FIXME: Resolve scope ambiguity when interface
+ * index is unspecified.
+ */
+ ifp = in6p_lookup_mcast_ifp(inp, &gsa->sin6);
+#else
+ return (EADDRNOTAVAIL);
+#endif
+ } else {
+ if (mreq.ipv6mr_interface < 0 ||
+ V_if_index < mreq.ipv6mr_interface)
+ return (EADDRNOTAVAIL);
+ ifp = ifnet_byindex(mreq.ipv6mr_interface);
+ }
CTR3(KTR_MLD, "%s: ipv6mr_interface = %d, ifp = %p",
__func__, mreq.ipv6mr_interface, ifp);
@@ -2033,6 +2130,15 @@ in6p_leave_group(struct inpcb *inp, struct sockopt *sopt)
if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
return (EINVAL);
+#ifdef notyet
+ /*
+ * FIXME: Need to embed ifp's scope ID in the address
+ * handed down to MLD.
+ * See KAME IPV6_LEAVE_GROUP implementation.
+ */
+ (void)in6_setscope(&mreq->ipv6mr_multiaddr, ifp, NULL);
+#endif
+
/*
* Find the membership in the membership array.
*/
@@ -2348,7 +2454,7 @@ out_in6p_locked:
int
ip6_setmoptions(struct inpcb *inp, struct sockopt *sopt)
{
- struct ip6_moptions *imo;
+ struct ip6_moptions *im6o;
int error;
error = 0;
@@ -2364,7 +2470,6 @@ ip6_setmoptions(struct inpcb *inp, struct sockopt *sopt)
switch (sopt->sopt_name) {
case IPV6_MULTICAST_IF:
- /* XXX in v6 this one is far more involved */
error = in6p_set_multicast_if(inp, sopt);
break;
@@ -2381,9 +2486,11 @@ ip6_setmoptions(struct inpcb *inp, struct sockopt *sopt)
if (hlim < -1 || hlim > 255) {
error = EINVAL;
break;
+ } else if (hlim == -1) {
+ hlim = V_ip6_defmcasthlim;
}
- imo = in6p_findmoptions(inp);
- imo->im6o_multicast_hlim = hlim;
+ im6o = in6p_findmoptions(inp);
+ im6o->im6o_multicast_hlim = hlim;
INP_WUNLOCK(inp);
break;
}
@@ -2393,9 +2500,7 @@ ip6_setmoptions(struct inpcb *inp, struct sockopt *sopt)
/*
* Set the loopback flag for outgoing multicast packets.
- * Must be zero or one. The orimcaddrl multicast API required a
- * char argument, which is inconsistent with the rest
- * of the socket API. We allow either a char or an int.
+ * Must be zero or one.
*/
if (sopt->sopt_valsize != sizeof(u_int)) {
error = EINVAL;
@@ -2404,8 +2509,12 @@ ip6_setmoptions(struct inpcb *inp, struct sockopt *sopt)
error = sooptcopyin(sopt, &loop, sizeof(u_int), sizeof(u_int));
if (error)
break;
- imo = in6p_findmoptions(inp);
- imo->im6o_multicast_loop = loop;
+ if (loop > 1) {
+ error = EINVAL;
+ break;
+ }
+ im6o = in6p_findmoptions(inp);
+ im6o->im6o_multicast_loop = loop;
INP_WUNLOCK(inp);
break;
}
diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c
index 79e79cb..e446a05 100644
--- a/sys/netinet6/in6_pcb.c
+++ b/sys/netinet6/in6_pcb.c
@@ -733,36 +733,36 @@ in6_pcbpurgeif0(struct inpcbinfo *pcbinfo, struct ifnet *ifp)
{
struct inpcb *in6p;
struct ip6_moptions *im6o;
- struct in6_multi_mship *imm, *nimm;
+ int i, gap;
INP_INFO_RLOCK(pcbinfo);
LIST_FOREACH(in6p, pcbinfo->ipi_listhead, inp_list) {
INP_WLOCK(in6p);
im6o = in6p->in6p_moptions;
- if ((in6p->inp_vflag & INP_IPV6) &&
- im6o) {
+ if ((in6p->inp_vflag & INP_IPV6) && im6o != NULL) {
/*
- * Unselect the outgoing interface if it is being
- * detached.
+ * Unselect the outgoing ifp for multicast if it
+ * is being detached.
*/
if (im6o->im6o_multicast_ifp == ifp)
im6o->im6o_multicast_ifp = NULL;
-
/*
* Drop multicast group membership if we joined
* through the interface being detached.
- * XXX controversial - is it really legal for kernel
- * to force this?
*/
- for (imm = im6o->im6o_memberships.lh_first;
- imm != NULL; imm = nimm) {
- nimm = imm->i6mm_chain.le_next;
- if (imm->i6mm_maddr->in6m_ifp == ifp) {
- LIST_REMOVE(imm, i6mm_chain);
- in6_delmulti(imm->i6mm_maddr);
- free(imm, M_IP6MADDR);
+ gap = 0;
+ for (i = 0; i < im6o->im6o_num_memberships; i++) {
+ if (im6o->im6o_membership[i]->in6m_ifp ==
+ ifp) {
+ in6_mc_leave(im6o->im6o_membership[i],
+ NULL);
+ gap++;
+ } else if (gap != 0) {
+ im6o->im6o_membership[i - gap] =
+ im6o->im6o_membership[i];
}
}
+ im6o->im6o_num_memberships -= gap;
}
INP_WUNLOCK(in6p);
}
diff --git a/sys/netinet6/in6_proto.c b/sys/netinet6/in6_proto.c
index 8908d67..622777c 100644
--- a/sys/netinet6/in6_proto.c
+++ b/sys/netinet6/in6_proto.c
@@ -236,6 +236,7 @@ struct ip6protosw inet6sw[] = {
.pr_ctloutput = rip6_ctloutput,
.pr_init = icmp6_init,
.pr_fasttimo = icmp6_fasttimo,
+ .pr_slowtimo = icmp6_slowtimo,
.pr_usrreqs = &rip6_usrreqs
},
{
diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h
index a472a24..9a846c3 100644
--- a/sys/netinet6/in6_var.h
+++ b/sys/netinet6/in6_var.h
@@ -64,6 +64,12 @@
#ifndef _NETINET6_IN6_VAR_H_
#define _NETINET6_IN6_VAR_H_
+#include <sys/tree.h>
+
+#ifdef _KERNEL
+#include <sys/libkern.h>
+#endif
+
/*
* Interface address, Internet version. One of these structures
* is allocated for each interface with an Internet address.
@@ -89,12 +95,15 @@ struct in6_addrlifetime {
struct nd_ifinfo;
struct scope6_id;
struct lltable;
+struct mld_ifinfo;
+
struct in6_ifextra {
struct in6_ifstat *in6_ifstat;
struct icmp6_ifstat *icmp6_ifstat;
struct nd_ifinfo *nd_ifinfo;
struct scope6_id *scope6_id;
struct lltable *lltable;
+ struct mld_ifinfo *mld_ifinfo;
};
#define LLTABLE6(ifp) (((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->lltable)
@@ -489,9 +498,6 @@ do { \
extern struct in6_addr zeroin6_addr;
extern u_char inet6ctlerrmap[];
-#ifdef MALLOC_DECLARE
-MALLOC_DECLARE(M_IP6MADDR);
-#endif /* MALLOC_DECLARE */
/*
* Macro for finding the internet address structure (in6_ifaddr) corresponding
@@ -514,94 +520,243 @@ do { \
#endif /* _KERNEL */
/*
- * Multi-cast membership entry. One for each group/ifp that a PCB
- * belongs to.
+ * IPv6 multicast MLD-layer source entry.
+ */
+struct ip6_msource {
+ RB_ENTRY(ip6_msource) im6s_link; /* RB tree links */
+ struct in6_addr im6s_addr;
+ struct im6s_st {
+ uint16_t ex; /* # of exclusive members */
+ uint16_t in; /* # of inclusive members */
+ } im6s_st[2]; /* state at t0, t1 */
+ uint8_t im6s_stp; /* pending query */
+};
+RB_HEAD(ip6_msource_tree, ip6_msource);
+
+/*
+ * IPv6 multicast PCB-layer source entry.
+ *
+ * NOTE: overlapping use of struct ip6_msource fields at start.
+ */
+struct in6_msource {
+ RB_ENTRY(ip6_msource) im6s_link; /* Common field */
+ struct in6_addr im6s_addr; /* Common field */
+ uint8_t im6sl_st[2]; /* state before/at commit */
+};
+
+#ifdef _KERNEL
+/*
+ * IPv6 source tree comparison function.
+ *
+ * An ordered predicate is necessary; bcmp() is not documented to return
+ * an indication of order, memcmp() is, and is an ISO C99 requirement.
+ */
+static __inline int
+ip6_msource_cmp(const struct ip6_msource *a, const struct ip6_msource *b)
+{
+
+ return (memcmp(&a->im6s_addr, &b->im6s_addr, sizeof(struct in6_addr)));
+}
+RB_PROTOTYPE(ip6_msource_tree, ip6_msource, im6s_link, ip6_msource_cmp);
+#endif /* _KERNEL */
+
+/*
+ * IPv6 multicast PCB-layer group filter descriptor.
+ */
+struct in6_mfilter {
+ struct ip6_msource_tree im6f_sources; /* source list for (S,G) */
+ u_long im6f_nsrc; /* # of source entries */
+ uint8_t im6f_st[2]; /* state before/at commit */
+};
+
+/*
+ * Legacy KAME IPv6 multicast membership descriptor.
*/
struct in6_multi_mship {
- struct in6_multi *i6mm_maddr; /* Multicast address pointer */
- LIST_ENTRY(in6_multi_mship) i6mm_chain; /* multicast options chain */
+ struct in6_multi *i6mm_maddr;
+ LIST_ENTRY(in6_multi_mship) i6mm_chain;
};
-struct in6_multi {
+/*
+ * IPv6 group descriptor.
+ *
+ * For every entry on an ifnet's if_multiaddrs list which represents
+ * an IP multicast group, there is one of these structures.
+ *
+ * If any source filters are present, then a node will exist in the RB-tree
+ * to permit fast lookup by source whenever an operation takes place.
+ * This permits pre-order traversal when we issue reports.
+ * Source filter trees are kept separately from the socket layer to
+ * greatly simplify locking.
+ *
+ * When MLDv2 is active, in6m_timer is the response to group query timer.
+ * The state-change timer in6m_sctimer is separate; whenever state changes
+ * for the group the state change record is generated and transmitted,
+ * and kept if retransmissions are necessary.
+ *
+ * FUTURE: in6m_link is now only used when groups are being purged
+ * on a detaching ifnet. It could be demoted to a SLIST_ENTRY, but
+ * because it is at the very start of the struct, we can't do this
+ * w/o breaking the ABI for ifmcstat.
+ */
+struct in6_multi {
LIST_ENTRY(in6_multi) in6m_entry; /* list glue */
- struct in6_addr in6m_addr; /* IP6 multicast address */
+ struct in6_addr in6m_addr; /* IPv6 multicast address */
struct ifnet *in6m_ifp; /* back pointer to ifnet */
struct ifmultiaddr *in6m_ifma; /* back pointer to ifmultiaddr */
- u_int in6m_refcount; /* # membership claims by sockets */
+ u_int in6m_refcount; /* reference count */
u_int in6m_state; /* state of the membership */
u_int in6m_timer; /* MLD6 listener report timer */
- struct timeval in6m_timer_expire; /* when the timer expires */
- struct callout *in6m_timer_ch;
-};
-#define IN6M_TIMER_UNDEF -1
+ /* New fields for MLDv2 follow. */
+ struct mld_ifinfo *in6m_mli; /* MLD info */
+ SLIST_ENTRY(in6_multi) in6m_nrele; /* to-be-released by MLD */
+ struct ip6_msource_tree in6m_srcs; /* tree of sources */
+ u_long in6m_nsrc; /* # of tree entries */
-#ifdef _KERNEL
-/* flags to in6_update_ifa */
-#define IN6_IFAUPDATE_DADDELAY 0x1 /* first time to configure an address */
+ struct ifqueue in6m_scq; /* queue of pending
+ * state-change packets */
+ struct timeval in6m_lastgsrtv; /* last G-S-R query */
+ uint16_t in6m_sctimer; /* state-change timer */
+ uint16_t in6m_scrv; /* state-change rexmit count */
-extern LIST_HEAD(in6_multihead, in6_multi) in6_multihead;
+ /*
+ * SSM state counters which track state at T0 (the time the last
+ * state-change report's RV timer went to zero) and T1
+ * (time of pending report, i.e. now).
+ * Used for computing MLDv2 state-change reports. Several refcounts
+ * are maintained here to optimize for common use-cases.
+ */
+ struct in6m_st {
+ uint16_t iss_fmode; /* MLD filter mode */
+ uint16_t iss_asm; /* # of ASM listeners */
+ uint16_t iss_ex; /* # of exclusive members */
+ uint16_t iss_in; /* # of inclusive members */
+ uint16_t iss_rec; /* # of recorded sources */
+ } in6m_st[2]; /* state at t0, t1 */
+};
/*
- * Structure used by macros below to remember position when stepping through
- * all of the in6_multi records.
+ * Helper function to derive the filter mode on a source entry
+ * from its internal counters. Predicates are:
+ * A source is only excluded if all listeners exclude it.
+ * A source is only included if no listeners exclude it,
+ * and at least one listener includes it.
+ * May be used by ifmcstat(8).
*/
-struct in6_multistep {
- struct in6_ifaddr *i_ia;
- struct in6_multi *i_in6m;
-};
+static __inline uint8_t
+im6s_get_mode(const struct in6_multi *inm, const struct ip6_msource *ims,
+ uint8_t t)
+{
+
+ t = !!t;
+ if (inm->in6m_st[t].iss_ex > 0 &&
+ inm->in6m_st[t].iss_ex == ims->im6s_st[t].ex)
+ return (MCAST_EXCLUDE);
+ else if (ims->im6s_st[t].in > 0 && ims->im6s_st[t].ex == 0)
+ return (MCAST_INCLUDE);
+ return (MCAST_UNDEFINED);
+}
+
+#ifdef _KERNEL
/*
- * Macros for looking up the in6_multi record for a given IP6 multicast
- * address on a given interface. If no matching record is found, "in6m"
- * returns NULL.
+ * Lock macros for IPv6 layer multicast address lists. IPv6 lock goes
+ * before link layer multicast locks in the lock order. In most cases,
+ * consumers of IN_*_MULTI() macros should acquire the locks before
+ * calling them; users of the in_{add,del}multi() functions should not.
*/
+extern struct mtx in6_multi_mtx;
+#define IN6_MULTI_LOCK() mtx_lock(&in6_multi_mtx)
+#define IN6_MULTI_UNLOCK() mtx_unlock(&in6_multi_mtx)
+#define IN6_MULTI_LOCK_ASSERT() mtx_assert(&in6_multi_mtx, MA_OWNED)
+#define IN6_MULTI_UNLOCK_ASSERT() mtx_assert(&in6_multi_mtx, MA_NOTOWNED)
-#define IN6_LOOKUP_MULTI(addr, ifp, in6m) \
-/* struct in6_addr addr; */ \
-/* struct ifnet *ifp; */ \
-/* struct in6_multi *in6m; */ \
-do { \
- struct ifmultiaddr *ifma; \
- IF_ADDR_LOCK(ifp); \
- TAILQ_FOREACH(ifma, &(ifp)->if_multiaddrs, ifma_link) { \
- if (ifma->ifma_addr->sa_family == AF_INET6 \
- && IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)ifma->ifma_addr)->sin6_addr, \
- &(addr))) \
- break; \
- } \
- (in6m) = (struct in6_multi *)(ifma ? ifma->ifma_protospec : 0); \
- IF_ADDR_UNLOCK(ifp); \
-} while(0)
+/*
+ * Look up an in6_multi record for an IPv6 multicast address
+ * on the interface ifp.
+ * If no record found, return NULL.
+ *
+ * SMPng: The IN6_MULTI_LOCK and IF_ADDR_LOCK on ifp must be held.
+ */
+static __inline struct in6_multi *
+in6m_lookup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr)
+{
+ struct ifmultiaddr *ifma;
+ struct in6_multi *inm;
+
+ IN6_MULTI_LOCK_ASSERT();
+ IF_ADDR_LOCK_ASSERT(ifp);
+
+ inm = NULL;
+ TAILQ_FOREACH(ifma, &((ifp)->if_multiaddrs), ifma_link) {
+ if (ifma->ifma_addr->sa_family == AF_INET6) {
+ inm = (struct in6_multi *)ifma->ifma_protospec;
+ if (IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, mcaddr))
+ break;
+ inm = NULL;
+ }
+ }
+ return (inm);
+}
/*
- * Macro to step through all of the in6_multi records, one at a time.
- * The current position is remembered in "step", which the caller must
- * provide. IN6_FIRST_MULTI(), below, must be called to initialize "step"
- * and get the first record. Both macros return a NULL "in6m" when there
- * are no remaining records.
+ * Wrapper for in6m_lookup_locked().
+ *
+ * SMPng: Assumes that neithr the IN6_MULTI_LOCK() or IF_ADDR_LOCK() are held.
*/
-#define IN6_NEXT_MULTI(step, in6m) \
-/* struct in6_multistep step; */ \
-/* struct in6_multi *in6m; */ \
-do { \
- if (((in6m) = (step).i_in6m) != NULL) \
- (step).i_in6m = LIST_NEXT((step).i_in6m, in6m_entry); \
-} while(0)
-
-#define IN6_FIRST_MULTI(step, in6m) \
-/* struct in6_multistep step; */ \
-/* struct in6_multi *in6m */ \
-do { \
- (step).i_in6m = LIST_FIRST(&in6_multihead); \
- IN6_NEXT_MULTI((step), (in6m)); \
-} while(0)
-
-struct in6_multi *in6_addmulti __P((struct in6_addr *, struct ifnet *,
- int *, int));
-void in6_delmulti __P((struct in6_multi *));
-struct in6_multi_mship *in6_joingroup(struct ifnet *, struct in6_addr *, int *, int);
+static __inline struct in6_multi *
+in6m_lookup(struct ifnet *ifp, const struct in6_addr *mcaddr)
+{
+ struct in6_multi *inm;
+
+ IN6_MULTI_LOCK();
+ IF_ADDR_LOCK(ifp);
+ inm = in6m_lookup_locked(ifp, mcaddr);
+ IF_ADDR_UNLOCK(ifp);
+ IN6_MULTI_UNLOCK();
+
+ return (inm);
+}
+
+/* Acquire an in6_multi record. */
+static __inline void
+in6m_acquire_locked(struct in6_multi *inm)
+{
+
+ IN6_MULTI_LOCK_ASSERT();
+ ++inm->in6m_refcount;
+}
+
+struct ip6_moptions;
+struct sockopt;
+
+/* Multicast KPIs. */
+int im6o_mc_filter(const struct ip6_moptions *, const struct ifnet *,
+ const struct sockaddr *, const struct sockaddr *);
+int in6_mc_join(struct ifnet *, const struct in6_addr *,
+ struct in6_mfilter *, struct in6_multi **, int);
+int in6_mc_join_locked(struct ifnet *, const struct in6_addr *,
+ struct in6_mfilter *, struct in6_multi **, int);
+int in6_mc_leave(struct in6_multi *, struct in6_mfilter *);
+int in6_mc_leave_locked(struct in6_multi *, struct in6_mfilter *);
+void in6m_clear_recorded(struct in6_multi *);
+void in6m_commit(struct in6_multi *);
+void in6m_print(const struct in6_multi *);
+int in6m_record_source(struct in6_multi *, const struct in6_addr *);
+void in6m_release_locked(struct in6_multi *);
+void ip6_freemoptions(struct ip6_moptions *);
+int ip6_getmoptions(struct inpcb *, struct sockopt *);
+int ip6_setmoptions(struct inpcb *, struct sockopt *);
+
+/* Legacy KAME multicast KPIs. */
+struct in6_multi_mship *
+ in6_joingroup(struct ifnet *, struct in6_addr *, int *, int);
int in6_leavegroup(struct in6_multi_mship *);
+
+/* flags to in6_update_ifa */
+#define IN6_IFAUPDATE_DADDELAY 0x1 /* first time to configure an address */
+
int in6_mask2len __P((struct in6_addr *, u_char *));
int in6_control __P((struct socket *, u_long, caddr_t, struct ifnet *,
struct thread *));
@@ -615,8 +770,6 @@ void *in6_domifattach __P((struct ifnet *));
void in6_domifdetach __P((struct ifnet *, void *));
void in6_setmaxmtu __P((void));
int in6_if2idlen __P((struct ifnet *));
-void in6_restoremkludge __P((struct in6_ifaddr *, struct ifnet *));
-void in6_purgemkludge __P((struct ifnet *));
struct in6_ifaddr *in6ifa_ifpforlinklocal __P((struct ifnet *, int));
struct in6_ifaddr *in6ifa_ifpwithaddr __P((struct ifnet *, struct in6_addr *));
char *ip6_sprintf __P((char *, const struct in6_addr *));
diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c
index 5654c94..69ac45c 100644
--- a/sys/netinet6/ip6_input.c
+++ b/sys/netinet6/ip6_input.c
@@ -555,25 +555,12 @@ passin:
}
/*
- * Multicast check
+ * Multicast check. Assume packet is for us to avoid
+ * prematurely taking locks.
*/
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
- struct in6_multi *in6m = 0;
-
+ ours = 1;
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mcast);
- /*
- * See if we belong to the destination multicast group on the
- * arrival interface.
- */
- IN6_LOOKUP_MULTI(ip6->ip6_dst, m->m_pkthdr.rcvif, in6m);
- if (in6m)
- ours = 1;
- else if (!ip6_mrouter) {
- V_ip6stat.ip6s_notmember++;
- V_ip6stat.ip6s_cantforward++;
- in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard);
- goto bad;
- }
deliverifp = m->m_pkthdr.rcvif;
goto hbhcheck;
}
@@ -823,7 +810,8 @@ passin:
/*
* Forward if desirable.
*/
- if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+ if (V_ip6_mrouter &&
+ IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
/*
* If we are acting as a multicast router, all
* incoming multicast packets are passed to the
@@ -832,13 +820,12 @@ passin:
* ip6_mforward() returns a non-zero value, the packet
* must be discarded, else it may be accepted below.
*/
- if (ip6_mrouter && ip6_mforward &&
+ if (ip6_mforward &&
ip6_mforward(ip6, m->m_pkthdr.rcvif, m)) {
- V_ip6stat.ip6s_cantforward++;
+ IP6STAT_INC(ip6s_cantforward);
+ in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard);
goto bad;
}
- if (!ours)
- goto bad;
} else if (!ours) {
ip6_forward(m, srcrt);
goto out;
diff --git a/sys/netinet6/ip6_mroute.c b/sys/netinet6/ip6_mroute.c
index 29201d6..a88a9a1 100644
--- a/sys/netinet6/ip6_mroute.c
+++ b/sys/netinet6/ip6_mroute.c
@@ -93,6 +93,7 @@ __FBSDID("$FreeBSD$");
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
+#include <sys/domain.h>
#include <sys/protosw.h>
#include <sys/signalvar.h>
#include <sys/socket.h>
@@ -140,6 +141,7 @@ static int set_pim6(int *);
static int socket_send(struct socket *, struct mbuf *,
struct sockaddr_in6 *);
+extern int in6_mcast_loop;
extern struct domain inet6domain;
static const struct encaptab *pim6_encap_cookie;
@@ -367,7 +369,7 @@ X_ip6_mrouter_set(struct socket *so, struct sockopt *sopt)
struct mf6cctl mfcc;
mifi_t mifi;
- if (so != ip6_mrouter && sopt->sopt_name != MRT6_INIT)
+ if (so != V_ip6_mrouter && sopt->sopt_name != MRT6_INIT)
return (EACCES);
switch (sopt->sopt_name) {
@@ -432,7 +434,7 @@ X_ip6_mrouter_get(struct socket *so, struct sockopt *sopt)
INIT_VNET_INET6(curvnet);
int error = 0;
- if (so != ip6_mrouter)
+ if (so != V_ip6_mrouter)
return (EACCES);
switch (sopt->sopt_name) {
@@ -560,12 +562,12 @@ ip6_mrouter_init(struct socket *so, int v, int cmd)
MROUTER6_LOCK();
- if (ip6_mrouter != NULL) {
+ if (V_ip6_mrouter != NULL) {
MROUTER6_UNLOCK();
return (EADDRINUSE);
}
- ip6_mrouter = so;
+ V_ip6_mrouter = so;
V_ip6_mrouter_ver = cmd;
bzero((caddr_t)mf6ctable, sizeof(mf6ctable));
@@ -601,7 +603,7 @@ X_ip6_mrouter_done(void)
MROUTER6_LOCK();
- if (ip6_mrouter == NULL) {
+ if (V_ip6_mrouter == NULL) {
MROUTER6_UNLOCK();
return (EINVAL);
}
@@ -657,7 +659,7 @@ X_ip6_mrouter_done(void)
multicast_register_if6 = NULL;
}
- ip6_mrouter = NULL;
+ V_ip6_mrouter = NULL;
V_ip6_mrouter_ver = 0;
MROUTER6_UNLOCK();
@@ -1293,7 +1295,7 @@ X_ip6_mforward(struct ip6_hdr *ip6, struct ifnet *ifp, struct mbuf *m)
break;
}
- if (socket_send(ip6_mrouter, mm, &sin6) < 0) {
+ if (socket_send(V_ip6_mrouter, mm, &sin6) < 0) {
log(LOG_WARNING, "ip6_mforward: ip6_mrouter "
"socket queue full\n");
mrt6stat.mrt6s_upq_sockfull++;
@@ -1531,7 +1533,7 @@ ip6_mdq(struct mbuf *m, struct ifnet *ifp, struct mf6c *rt)
mrt6stat.mrt6s_upcalls++;
- if (socket_send(ip6_mrouter, mm, &sin6) < 0) {
+ if (socket_send(V_ip6_mrouter, mm, &sin6) < 0) {
#ifdef MRT6DEBUG
if (V_mrt6debug)
log(LOG_WARNING, "mdq, ip6_mrouter socket queue full\n");
@@ -1603,10 +1605,11 @@ phyint_send(struct ip6_hdr *ip6, struct mif6 *mifp, struct mbuf *m)
struct mbuf *mb_copy;
struct ifnet *ifp = mifp->m6_ifp;
int error = 0;
- struct in6_multi *in6m;
struct sockaddr_in6 *dst6;
u_long linkmtu;
+ dst6 = &mifp->m6_route.ro_dst;
+
/*
* Make a new reference to the packet; make sure that
* the IPv6 header is actually copied, not just referenced,
@@ -1648,17 +1651,16 @@ phyint_send(struct ip6_hdr *ip6, struct mif6 *mifp, struct mbuf *m)
}
/*
- * If we belong to the destination multicast group
- * on the outgoing interface, loop back a copy.
+ * If configured to loop back multicasts by default,
+ * loop back a copy now.
*/
- dst6 = &mifp->m6_route.ro_dst;
- IN6_LOOKUP_MULTI(ip6->ip6_dst, ifp, in6m);
- if (in6m != NULL) {
+ if (in6_mcast_loop) {
dst6->sin6_len = sizeof(struct sockaddr_in6);
dst6->sin6_family = AF_INET6;
dst6->sin6_addr = ip6->ip6_dst;
ip6_mloopback(ifp, m, &mifp->m6_route.ro_dst);
}
+
/*
* Put the packet into the sending queue of the outgoing interface
* if it would fit in the MTU of the interface.
@@ -1759,7 +1761,7 @@ register_send(struct ip6_hdr *ip6, struct mif6 *mif, struct mbuf *m)
/* iif info is not given for reg. encap.n */
mrt6stat.mrt6s_upcalls++;
- if (socket_send(ip6_mrouter, mm, &sin6) < 0) {
+ if (socket_send(V_ip6_mrouter, mm, &sin6) < 0) {
#ifdef MRT6DEBUG
if (V_mrt6debug)
log(LOG_WARNING,
@@ -2056,7 +2058,7 @@ ip6_mroute_modevent(module_t mod, int type, void *unused)
break;
case MOD_UNLOAD:
- if (ip6_mrouter != NULL)
+ if (V_ip6_mrouter != NULL)
return EINVAL;
if (pim6_encap_cookie) {
diff --git a/sys/netinet6/ip6_output.c b/sys/netinet6/ip6_output.c
index 787bc12..61804ec 100644
--- a/sys/netinet6/ip6_output.c
+++ b/sys/netinet6/ip6_output.c
@@ -110,7 +110,7 @@ __FBSDID("$FreeBSD$");
#include <netinet6/scope6_var.h>
#include <netinet6/vinet6.h>
-static MALLOC_DEFINE(M_IP6MOPTS, "ip6_moptions", "internet multicast options");
+extern int in6_mcast_loop;
struct ip6_exthdrs {
struct mbuf *ip6e_ip6;
@@ -128,8 +128,6 @@ static int ip6_getpcbopt(struct ip6_pktopts *, int, struct sockopt *);
static int ip6_setpktopt __P((int, u_char *, int, struct ip6_pktopts *,
struct ucred *, int, int, int));
-static int ip6_setmoptions(int, struct ip6_moptions **, struct mbuf *);
-static int ip6_getmoptions(int, struct ip6_moptions *, struct mbuf **);
static int ip6_copyexthdr(struct mbuf **, caddr_t, int);
static int ip6_insertfraghdr __P((struct mbuf *, struct mbuf *, int,
struct ip6_frag **));
@@ -692,12 +690,8 @@ again:
if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
m->m_flags &= ~(M_BCAST | M_MCAST); /* just in case */
} else {
- struct in6_multi *in6m;
-
m->m_flags = (m->m_flags & ~M_BCAST) | M_MCAST;
-
in6_ifstat_inc(ifp, ifs6_out_mcast);
-
/*
* Confirm that the outgoing interface supports multicast.
*/
@@ -707,13 +701,14 @@ again:
error = ENETUNREACH;
goto bad;
}
- IN6_LOOKUP_MULTI(ip6->ip6_dst, ifp, in6m);
- if (in6m != NULL &&
- (im6o == NULL || im6o->im6o_multicast_loop)) {
+ if ((im6o == NULL && in6_mcast_loop) ||
+ (im6o && im6o->im6o_multicast_loop)) {
/*
- * If we belong to the destination multicast group
- * on the outgoing interface, and the caller did not
- * forbid loopback, loop back a copy.
+ * Loop back multicast datagram if not expressly
+ * forbidden to do so, even if we have not joined
+ * the address; protocols will filter it later,
+ * thus deferring a hash lookup and lock acquisition
+ * at the expense of an m_copym().
*/
ip6_mloopback(ifp, m, dst);
} else {
@@ -729,7 +724,7 @@ again:
* above, will be forwarded by the ip6_input() routine,
* if necessary.
*/
- if (ip6_mrouter && (flags & IPV6_FORWARDING) == 0) {
+ if (V_ip6_mrouter && (flags & IPV6_FORWARDING) == 0) {
/*
* XXX: ip6_mforward expects that rcvif is NULL
* when it is called from the originating path.
@@ -1702,47 +1697,14 @@ do { \
case IPV6_MULTICAST_LOOP:
case IPV6_JOIN_GROUP:
case IPV6_LEAVE_GROUP:
- {
- if (sopt->sopt_valsize > MLEN) {
- error = EMSGSIZE;
- break;
- }
- /* XXX */
- }
- /* FALLTHROUGH */
- {
- struct mbuf *m;
-
- if (sopt->sopt_valsize > MCLBYTES) {
- error = EMSGSIZE;
- break;
- }
- /* XXX */
- MGET(m, sopt->sopt_td ? M_WAIT : M_DONTWAIT, MT_DATA);
- if (m == 0) {
- error = ENOBUFS;
- break;
- }
- if (sopt->sopt_valsize > MLEN) {
- MCLGET(m, sopt->sopt_td ? M_WAIT : M_DONTWAIT);
- if ((m->m_flags & M_EXT) == 0) {
- m_free(m);
- error = ENOBUFS;
- break;
- }
- }
- m->m_len = sopt->sopt_valsize;
- error = sooptcopyin(sopt, mtod(m, char *),
- m->m_len, m->m_len);
- if (error) {
- (void)m_free(m);
- break;
- }
- error = ip6_setmoptions(sopt->sopt_name,
- &in6p->in6p_moptions,
- m);
- (void)m_free(m);
- }
+ case IPV6_MSFILTER:
+ case MCAST_BLOCK_SOURCE:
+ case MCAST_UNBLOCK_SOURCE:
+ case MCAST_JOIN_GROUP:
+ case MCAST_LEAVE_GROUP:
+ case MCAST_JOIN_SOURCE_GROUP:
+ case MCAST_LEAVE_SOURCE_GROUP:
+ error = ip6_setmoptions(in6p, sopt);
break;
case IPV6_PORTRANGE:
@@ -1974,17 +1936,8 @@ do { \
case IPV6_MULTICAST_IF:
case IPV6_MULTICAST_HOPS:
case IPV6_MULTICAST_LOOP:
- case IPV6_JOIN_GROUP:
- case IPV6_LEAVE_GROUP:
- {
- struct mbuf *m;
- error = ip6_getmoptions(sopt->sopt_name,
- in6p->in6p_moptions, &m);
- if (error == 0)
- error = sooptcopyout(sopt,
- mtod(m, char *), m->m_len);
- m_freem(m);
- }
+ case IPV6_MSFILTER:
+ error = ip6_getmoptions(in6p, sopt);
break;
#ifdef IPSEC
@@ -2405,374 +2358,6 @@ ip6_freepcbopts(struct ip6_pktopts *pktopt)
}
/*
- * Set the IP6 multicast options in response to user setsockopt().
- */
-static int
-ip6_setmoptions(int optname, struct ip6_moptions **im6op, struct mbuf *m)
-{
- INIT_VNET_NET(curvnet);
- INIT_VNET_INET6(curvnet);
- int error = 0;
- u_int loop, ifindex;
- struct ipv6_mreq *mreq;
- struct ifnet *ifp;
- struct ip6_moptions *im6o = *im6op;
- struct route_in6 ro;
- struct in6_multi_mship *imm;
-
- if (im6o == NULL) {
- /*
- * No multicast option buffer attached to the pcb;
- * allocate one and initialize to default values.
- */
- im6o = (struct ip6_moptions *)
- malloc(sizeof(*im6o), M_IP6MOPTS, M_WAITOK);
-
- if (im6o == NULL)
- return (ENOBUFS);
- *im6op = im6o;
- im6o->im6o_multicast_ifp = NULL;
- im6o->im6o_multicast_hlim = V_ip6_defmcasthlim;
- im6o->im6o_multicast_loop = IPV6_DEFAULT_MULTICAST_LOOP;
- LIST_INIT(&im6o->im6o_memberships);
- }
-
- switch (optname) {
-
- case IPV6_MULTICAST_IF:
- /*
- * Select the interface for outgoing multicast packets.
- */
- if (m == NULL || m->m_len != sizeof(u_int)) {
- error = EINVAL;
- break;
- }
- bcopy(mtod(m, u_int *), &ifindex, sizeof(ifindex));
- if (ifindex < 0 || V_if_index < ifindex) {
- error = ENXIO; /* XXX EINVAL? */
- break;
- }
- ifp = ifnet_byindex(ifindex);
- if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) {
- error = EADDRNOTAVAIL;
- break;
- }
- im6o->im6o_multicast_ifp = ifp;
- break;
-
- case IPV6_MULTICAST_HOPS:
- {
- /*
- * Set the IP6 hoplimit for outgoing multicast packets.
- */
- int optval;
- if (m == NULL || m->m_len != sizeof(int)) {
- error = EINVAL;
- break;
- }
- bcopy(mtod(m, u_int *), &optval, sizeof(optval));
- if (optval < -1 || optval >= 256)
- error = EINVAL;
- else if (optval == -1)
- im6o->im6o_multicast_hlim = V_ip6_defmcasthlim;
- else
- im6o->im6o_multicast_hlim = optval;
- break;
- }
-
- case IPV6_MULTICAST_LOOP:
- /*
- * Set the loopback flag for outgoing multicast packets.
- * Must be zero or one.
- */
- if (m == NULL || m->m_len != sizeof(u_int)) {
- error = EINVAL;
- break;
- }
- bcopy(mtod(m, u_int *), &loop, sizeof(loop));
- if (loop > 1) {
- error = EINVAL;
- break;
- }
- im6o->im6o_multicast_loop = loop;
- break;
-
- case IPV6_JOIN_GROUP:
- /*
- * Add a multicast group membership.
- * Group must be a valid IP6 multicast address.
- */
- if (m == NULL || m->m_len != sizeof(struct ipv6_mreq)) {
- error = EINVAL;
- break;
- }
- mreq = mtod(m, struct ipv6_mreq *);
-
- if (IN6_IS_ADDR_UNSPECIFIED(&mreq->ipv6mr_multiaddr)) {
- /*
- * We use the unspecified address to specify to accept
- * all multicast addresses. Only super user is allowed
- * to do this.
- */
- /* XXX-BZ might need a better PRIV_NETINET_x for this */
- error = priv_check(curthread, PRIV_NETINET_MROUTE);
- if (error)
- break;
- } else if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) {
- error = EINVAL;
- break;
- }
-
- /*
- * If no interface was explicitly specified, choose an
- * appropriate one according to the given multicast address.
- */
- if (mreq->ipv6mr_interface == 0) {
- struct sockaddr_in6 *dst;
-
- /*
- * Look up the routing table for the
- * address, and choose the outgoing interface.
- * XXX: is it a good approach?
- */
- ro.ro_rt = NULL;
- dst = (struct sockaddr_in6 *)&ro.ro_dst;
- bzero(dst, sizeof(*dst));
- dst->sin6_family = AF_INET6;
- dst->sin6_len = sizeof(*dst);
- dst->sin6_addr = mreq->ipv6mr_multiaddr;
- rtalloc((struct route *)&ro);
- if (ro.ro_rt == NULL) {
- error = EADDRNOTAVAIL;
- break;
- }
- ifp = ro.ro_rt->rt_ifp;
- RTFREE(ro.ro_rt);
- } else {
- /*
- * If the interface is specified, validate it.
- */
- if (mreq->ipv6mr_interface < 0 ||
- V_if_index < mreq->ipv6mr_interface) {
- error = ENXIO; /* XXX EINVAL? */
- break;
- }
- ifp = ifnet_byindex(mreq->ipv6mr_interface);
- if (!ifp) {
- error = ENXIO; /* XXX EINVAL? */
- break;
- }
- }
-
- /*
- * See if we found an interface, and confirm that it
- * supports multicast
- */
- if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) {
- error = EADDRNOTAVAIL;
- break;
- }
-
- if (in6_setscope(&mreq->ipv6mr_multiaddr, ifp, NULL)) {
- error = EADDRNOTAVAIL; /* XXX: should not happen */
- break;
- }
-
- /*
- * See if the membership already exists.
- */
- for (imm = im6o->im6o_memberships.lh_first;
- imm != NULL; imm = imm->i6mm_chain.le_next)
- if (imm->i6mm_maddr->in6m_ifp == ifp &&
- IN6_ARE_ADDR_EQUAL(&imm->i6mm_maddr->in6m_addr,
- &mreq->ipv6mr_multiaddr))
- break;
- if (imm != NULL) {
- error = EADDRINUSE;
- break;
- }
- /*
- * Everything looks good; add a new record to the multicast
- * address list for the given interface.
- */
- imm = in6_joingroup(ifp, &mreq->ipv6mr_multiaddr, &error, 0);
- if (imm == NULL)
- break;
- LIST_INSERT_HEAD(&im6o->im6o_memberships, imm, i6mm_chain);
- break;
-
- case IPV6_LEAVE_GROUP:
- /*
- * Drop a multicast group membership.
- * Group must be a valid IP6 multicast address.
- */
- if (m == NULL || m->m_len != sizeof(struct ipv6_mreq)) {
- error = EINVAL;
- break;
- }
- mreq = mtod(m, struct ipv6_mreq *);
-
- /*
- * If an interface address was specified, get a pointer
- * to its ifnet structure.
- */
- if (mreq->ipv6mr_interface < 0 ||
- V_if_index < mreq->ipv6mr_interface) {
- error = ENXIO; /* XXX EINVAL? */
- break;
- }
- if (mreq->ipv6mr_interface == 0)
- ifp = NULL;
- else
- ifp = ifnet_byindex(mreq->ipv6mr_interface);
-
- /* Fill in the scope zone ID */
- if (ifp) {
- if (in6_setscope(&mreq->ipv6mr_multiaddr, ifp, NULL)) {
- /* XXX: should not happen */
- error = EADDRNOTAVAIL;
- break;
- }
- } else if (mreq->ipv6mr_interface != 0) {
- /*
- * This case happens when the (positive) index is in
- * the valid range, but the corresponding interface has
- * been detached dynamically (XXX).
- */
- error = EADDRNOTAVAIL;
- break;
- } else { /* ipv6mr_interface == 0 */
- struct sockaddr_in6 sa6_mc;
-
- /*
- * The API spec says as follows:
- * If the interface index is specified as 0, the
- * system may choose a multicast group membership to
- * drop by matching the multicast address only.
- * On the other hand, we cannot disambiguate the scope
- * zone unless an interface is provided. Thus, we
- * check if there's ambiguity with the default scope
- * zone as the last resort.
- */
- bzero(&sa6_mc, sizeof(sa6_mc));
- sa6_mc.sin6_family = AF_INET6;
- sa6_mc.sin6_len = sizeof(sa6_mc);
- sa6_mc.sin6_addr = mreq->ipv6mr_multiaddr;
- error = sa6_embedscope(&sa6_mc, V_ip6_use_defzone);
- if (error != 0)
- break;
- mreq->ipv6mr_multiaddr = sa6_mc.sin6_addr;
- }
-
- /*
- * Find the membership in the membership list.
- */
- for (imm = im6o->im6o_memberships.lh_first;
- imm != NULL; imm = imm->i6mm_chain.le_next) {
- if ((ifp == NULL || imm->i6mm_maddr->in6m_ifp == ifp) &&
- IN6_ARE_ADDR_EQUAL(&imm->i6mm_maddr->in6m_addr,
- &mreq->ipv6mr_multiaddr))
- break;
- }
- if (imm == NULL) {
- /* Unable to resolve interface */
- error = EADDRNOTAVAIL;
- break;
- }
- /*
- * Give up the multicast address record to which the
- * membership points.
- */
- LIST_REMOVE(imm, i6mm_chain);
- in6_delmulti(imm->i6mm_maddr);
- free(imm, M_IP6MADDR);
- break;
-
- default:
- error = EOPNOTSUPP;
- break;
- }
-
- /*
- * If all options have default values, no need to keep the mbuf.
- */
- if (im6o->im6o_multicast_ifp == NULL &&
- im6o->im6o_multicast_hlim == V_ip6_defmcasthlim &&
- im6o->im6o_multicast_loop == IPV6_DEFAULT_MULTICAST_LOOP &&
- im6o->im6o_memberships.lh_first == NULL) {
- free(*im6op, M_IP6MOPTS);
- *im6op = NULL;
- }
-
- return (error);
-}
-
-/*
- * Return the IP6 multicast options in response to user getsockopt().
- */
-static int
-ip6_getmoptions(int optname, struct ip6_moptions *im6o, struct mbuf **mp)
-{
- INIT_VNET_INET6(curvnet);
- u_int *hlim, *loop, *ifindex;
-
- *mp = m_get(M_WAIT, MT_HEADER); /* XXX */
-
- switch (optname) {
-
- case IPV6_MULTICAST_IF:
- ifindex = mtod(*mp, u_int *);
- (*mp)->m_len = sizeof(u_int);
- if (im6o == NULL || im6o->im6o_multicast_ifp == NULL)
- *ifindex = 0;
- else
- *ifindex = im6o->im6o_multicast_ifp->if_index;
- return (0);
-
- case IPV6_MULTICAST_HOPS:
- hlim = mtod(*mp, u_int *);
- (*mp)->m_len = sizeof(u_int);
- if (im6o == NULL)
- *hlim = V_ip6_defmcasthlim;
- else
- *hlim = im6o->im6o_multicast_hlim;
- return (0);
-
- case IPV6_MULTICAST_LOOP:
- loop = mtod(*mp, u_int *);
- (*mp)->m_len = sizeof(u_int);
- if (im6o == NULL)
- *loop = V_ip6_defmcasthlim;
- else
- *loop = im6o->im6o_multicast_loop;
- return (0);
-
- default:
- return (EOPNOTSUPP);
- }
-}
-
-/*
- * Discard the IP6 multicast options.
- */
-void
-ip6_freemoptions(struct ip6_moptions *im6o)
-{
- struct in6_multi_mship *imm;
-
- if (im6o == NULL)
- return;
-
- while ((imm = im6o->im6o_memberships.lh_first) != NULL) {
- LIST_REMOVE(imm, i6mm_chain);
- if (imm->i6mm_maddr)
- in6_delmulti(imm->i6mm_maddr);
- free(imm, M_IP6MADDR);
- }
- free(im6o, M_IP6MOPTS);
-}
-
-/*
* Set IPv6 outgoing packet options based on advanced API.
*/
int
diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h
index 9e8476c..313b6ca 100644
--- a/sys/netinet6/ip6_var.h
+++ b/sys/netinet6/ip6_var.h
@@ -98,11 +98,19 @@ struct ip6asfrag {
#define IP6_REASS_MBUF(ip6af) (*(struct mbuf **)&((ip6af)->ip6af_m))
-struct ip6_moptions {
+/*
+ * Structure attached to inpcb.in6p_moptions and
+ * passed to ip6_output when IPv6 multicast options are in use.
+ * This structure is lazy-allocated.
+ */
+struct ip6_moptions {
struct ifnet *im6o_multicast_ifp; /* ifp for outgoing multicasts */
u_char im6o_multicast_hlim; /* hoplimit for outgoing multicasts */
u_char im6o_multicast_loop; /* 1 >= hear sends if a member */
- LIST_HEAD(, in6_multi_mship) im6o_memberships;
+ u_short im6o_num_memberships; /* no. memberships this socket */
+ u_short im6o_max_memberships; /* max memberships this socket */
+ struct in6_multi **im6o_membership; /* group memberships */
+ struct in6_mfilter *im6o_mfilters; /* source filters */
};
/*
@@ -234,6 +242,13 @@ struct ip6stat {
};
#ifdef _KERNEL
+#define IP6STAT_ADD(name, val) V_ip6stat.name += (val)
+#define IP6STAT_SUB(name, val) V_ip6stat.name -= (val)
+#define IP6STAT_INC(name) IP6STAT_ADD(name, 1)
+#define IP6STAT_DEC(name) IP6STAT_SUB(name, 1)
+#endif
+
+#ifdef _KERNEL
/*
* IPv6 onion peeling state.
* it will be initialized when we come into ip6_input().
@@ -287,10 +302,7 @@ extern int ip6_rr_prune; /* router renumbering prefix
* walk list every 5 sec. */
extern int ip6_mcast_pmtu; /* enable pMTU discovery for multicast? */
extern int ip6_v6only;
-#endif /* VIMAGE_GLOBALS */
-
extern struct socket *ip6_mrouter; /* multicast routing daemon */
-#ifdef VIMAGE_GLOBALS
extern int ip6_sendredirects; /* send IP redirects when forwarding? */
extern int ip6_maxfragpackets; /* Maximum packets in reassembly queue */
extern int ip6_maxfrags; /* Maximum fragments in reassembly queue */
@@ -330,7 +342,7 @@ void ip6_init __P((void));
void ip6_input __P((struct mbuf *));
struct in6_ifaddr *ip6_getdstifaddr __P((struct mbuf *));
void ip6_freepcbopts __P((struct ip6_pktopts *));
-void ip6_freemoptions __P((struct ip6_moptions *));
+
int ip6_unknown_opt __P((u_int8_t *, struct mbuf *, int));
char * ip6_get_prevhdr __P((struct mbuf *, int));
int ip6_nexthdr __P((struct mbuf *, int, int, int *));
diff --git a/sys/netinet6/mld6.c b/sys/netinet6/mld6.c
index 0d05522..17b1df8 100644
--- a/sys/netinet6/mld6.c
+++ b/sys/netinet6/mld6.c
@@ -1,6 +1,5 @@
/*-
- * Copyright (C) 1998 WIDE Project.
- * All rights reserved.
+ * Copyright (c) 2009 Bruce Simpson.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -10,14 +9,14 @@
* 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.
+ * 3. The name of the author may not 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
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 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)
@@ -75,13 +74,16 @@ __FBSDID("$FreeBSD$");
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/protosw.h>
-#include <sys/syslog.h>
+#include <sys/sysctl.h>
#include <sys/kernel.h>
#include <sys/callout.h>
#include <sys/malloc.h>
+#include <sys/module.h>
#include <sys/vimage.h>
#include <net/if.h>
+#include <net/route.h>
+#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
@@ -90,568 +92,3113 @@ __FBSDID("$FreeBSD$");
#include <netinet6/ip6_var.h>
#include <netinet6/scope6_var.h>
#include <netinet/icmp6.h>
+#include <netinet6/mld6.h>
#include <netinet6/mld6_var.h>
+#include <netinet/vinet.h>
#include <netinet6/vinet6.h>
+#include <security/mac/mac_framework.h>
+
+#ifndef KTR_MLD
+#define KTR_MLD KTR_INET6
+#endif
+
+static struct mld_ifinfo *
+ mli_alloc_locked(struct ifnet *);
+static void mli_delete_locked(const struct ifnet *);
+static void mld_dispatch_packet(struct mbuf *);
+static void mld_dispatch_queue(struct ifqueue *, int);
+static void mld_final_leave(struct in6_multi *, struct mld_ifinfo *);
+static void mld_fasttimo_vnet(void);
+static int mld_handle_state_change(struct in6_multi *,
+ struct mld_ifinfo *);
+static int mld_initial_join(struct in6_multi *, struct mld_ifinfo *,
+ const int);
+#ifdef KTR
+static char * mld_rec_type_to_str(const int);
+#endif
+static void mld_set_version(struct mld_ifinfo *, const int);
+static void mld_slowtimo_vnet(void);
+static void mld_sysinit(void);
+static void mld_sysuninit(void);
+static int mld_v1_input_query(struct ifnet *, const struct ip6_hdr *,
+ const struct mld_hdr *);
+static int mld_v1_input_report(struct ifnet *, const struct ip6_hdr *,
+ const struct mld_hdr *);
+static void mld_v1_process_group_timer(struct in6_multi *, const int);
+static void mld_v1_process_querier_timers(struct mld_ifinfo *);
+static int mld_v1_transmit_report(struct in6_multi *, const int);
+static void mld_v1_update_group(struct in6_multi *, const int);
+static void mld_v2_cancel_link_timers(struct mld_ifinfo *);
+static void mld_v2_dispatch_general_query(struct mld_ifinfo *);
+static struct mbuf *
+ mld_v2_encap_report(struct ifnet *, struct mbuf *);
+static int mld_v2_enqueue_filter_change(struct ifqueue *,
+ struct in6_multi *);
+static int mld_v2_enqueue_group_record(struct ifqueue *,
+ struct in6_multi *, const int, const int, const int);
+static int mld_v2_input_query(struct ifnet *, const struct ip6_hdr *,
+ struct mbuf *, const int, const int);
+static int mld_v2_merge_state_changes(struct in6_multi *,
+ struct ifqueue *);
+static void mld_v2_process_group_timers(struct mld_ifinfo *,
+ struct ifqueue *, struct ifqueue *,
+ struct in6_multi *, const int);
+static int mld_v2_process_group_query(struct in6_multi *,
+ struct mld_ifinfo *mli, int, struct mbuf *, const int);
+static int sysctl_mld_gsr(SYSCTL_HANDLER_ARGS);
+static int sysctl_mld_ifinfo(SYSCTL_HANDLER_ARGS);
+
+#ifdef VIMAGE
+static vnet_attach_fn vnet_mld_iattach;
+static vnet_detach_fn vnet_mld_idetach;
+#else
+static int vnet_mld_iattach(const void *);
+static int vnet_mld_idetach(const void *);
+#endif /* VIMAGE */
+
/*
- * Protocol constants
+ * Normative references: RFC 2710, RFC 3590, RFC 3810.
+ *
+ * Locking:
+ * * The MLD subsystem lock ends up being system-wide for the moment,
+ * but could be per-VIMAGE later on.
+ * * The permitted lock order is: IN6_MULTI_LOCK, MLD_LOCK, IF_ADDR_LOCK.
+ * Any may be taken independently; if any are held at the same
+ * time, the above lock order must be followed.
+ * * IN6_MULTI_LOCK covers in_multi.
+ * * MLD_LOCK covers per-link state and any global variables in this file.
+ * * IF_ADDR_LOCK covers if_multiaddrs, which is used for a variety of
+ * per-link state iterators.
+ *
+ * XXX LOR PREVENTION
+ * A special case for IPv6 is the in6_setscope() routine. ip6_output()
+ * will not accept an ifp; it wants an embedded scope ID, unlike
+ * ip_output(), which happily takes the ifp given to it. The embedded
+ * scope ID is only used by MLD to select the outgoing interface.
+ *
+ * During interface attach and detach, MLD will take MLD_LOCK *after*
+ * the IF_AFDATA_LOCK.
+ * As in6_setscope() takes IF_AFDATA_LOCK then SCOPE_LOCK, we can't call
+ * it with MLD_LOCK held without triggering an LOR. A netisr with indirect
+ * dispatch could work around this, but we'd rather not do that, as it
+ * can introduce other races.
+ *
+ * As such, we exploit the fact that the scope ID is just the interface
+ * index, and embed it in the IPv6 destination address accordingly.
+ * This is potentially NOT VALID for MLDv1 reports, as they
+ * are always sent to the multicast group itself; as MLDv2
+ * reports are always sent to ff02::16, this is not an issue
+ * when MLDv2 is in use.
+ *
+ * This does not however eliminate the LOR when ip6_output() itself
+ * calls in6_setscope() internally whilst MLD_LOCK is held. This will
+ * trigger a LOR warning in WITNESS when the ifnet is detached.
+ *
+ * The right answer is probably to make IF_AFDATA_LOCK an rwlock, given
+ * how it's used across the network stack. Here we're simply exploiting
+ * the fact that MLD runs at a similar layer in the stack to scope6.c.
+ *
+ * VIMAGE:
+ * * Each in6_multi corresponds to an ifp, and each ifp corresponds
+ * to a vnet in ifp->if_vnet.
*/
+static struct mtx mld_mtx;
+MALLOC_DEFINE(M_MLD, "mld", "mld state");
+
+#define MLD_EMBEDSCOPE(pin6, zoneid) \
+ (pin6)->s6_addr16[1] = htons((zoneid) & 0xFFFF)
-/* denotes that the MLD max response delay field specifies time in milliseconds */
-#define MLD_TIMER_SCALE 1000
/*
- * time between repetitions of a node's initial report of interest in a
- * multicast address(in seconds)
+ * VIMAGE-wide globals.
*/
-#define MLD_UNSOLICITED_REPORT_INTERVAL 10
-
#ifdef VIMAGE_GLOBALS
-static struct ip6_pktopts ip6_opts;
-#endif
+struct timeval mld_gsrdelay;
+LIST_HEAD(, mld_ifinfo) mli_head;
+int interface_timers_running6;
+int state_change_timers_running6;
+int current_state_timers_running6;
+#endif /* VIMAGE_GLOBALS */
-static void mld6_sendpkt(struct in6_multi *, int, const struct in6_addr *);
-static void mld_starttimer(struct in6_multi *);
-static void mld_stoptimer(struct in6_multi *);
-static void mld_timeo(struct in6_multi *);
-static u_long mld_timerresid(struct in6_multi *);
+SYSCTL_DECL(_net_inet6); /* Note: Not in any common header. */
-void
-mld6_init(void)
+SYSCTL_NODE(_net_inet6, OID_AUTO, mld, CTLFLAG_RW, 0,
+ "IPv6 Multicast Listener Discovery");
+
+/*
+ * Virtualized sysctls.
+ */
+SYSCTL_V_PROC(V_NET, vnet_inet6, _net_inet6_mld, OID_AUTO, gsrdelay,
+ CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, mld_gsrdelay.tv_sec, 0,
+ sysctl_mld_gsr, "I",
+ "Rate limit for MLDv2 Group-and-Source queries in seconds");
+
+/*
+ * Non-virtualized sysctls.
+ */
+SYSCTL_NODE(_net_inet6_mld, OID_AUTO, ifinfo, CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sysctl_mld_ifinfo, "Per-interface MLDv2 state");
+
+/*
+ * Packed Router Alert option structure declaration.
+ */
+struct mld_raopt {
+ struct ip6_hbh hbh;
+ struct ip6_opt pad;
+ struct ip6_opt_router ra;
+} __packed;
+
+/*
+ * Router Alert hop-by-hop option header.
+ */
+static struct mld_raopt mld_ra = {
+ .hbh = { 0, 0 },
+ .pad = { .ip6o_type = IP6OPT_PADN, 0 },
+ .ra = {
+ .ip6or_type = IP6OPT_ROUTER_ALERT,
+ .ip6or_len = IP6OPT_RTALERT_LEN - 2,
+ .ip6or_value[0] = ((IP6OPT_RTALERT_MLD >> 8) & 0xFF),
+ .ip6or_value[1] = (IP6OPT_RTALERT_MLD & 0xFF)
+ }
+};
+static struct ip6_pktopts mld_po;
+
+static __inline void
+mld_save_context(struct mbuf *m, struct ifnet *ifp)
+{
+
+#ifdef VIMAGE
+ m->m_pkthdr.header = ifp->if_vnet;
+#endif /* VIMAGE */
+ m->m_pkthdr.flowid = ifp->if_index;
+}
+
+static __inline void
+mld_scrub_context(struct mbuf *m)
+{
+
+ m->m_pkthdr.header = NULL;
+ m->m_pkthdr.flowid = 0;
+}
+
+/*
+ * Restore context from a queued output chain.
+ * Return saved ifindex.
+ *
+ * VIMAGE: The assertion is there to make sure that we
+ * actually called CURVNET_SET() with what's in the mbuf chain.
+ */
+static __inline uint32_t
+mld_restore_context(struct mbuf *m)
+{
+
+#ifdef notyet
+#if defined(VIMAGE) && defined(INVARIANTS)
+ KASSERT(curvnet == (m->m_pkthdr.header),
+ ("%s: called when curvnet was not restored", __func__));
+#endif
+#endif
+ return (m->m_pkthdr.flowid);
+}
+
+/*
+ * Retrieve or set threshold between group-source queries in seconds.
+ *
+ * VIMAGE: Assume curvnet set by caller.
+ * SMPng: NOTE: Serialized by MLD lock.
+ */
+static int
+sysctl_mld_gsr(SYSCTL_HANDLER_ARGS)
{
INIT_VNET_INET6(curvnet);
- 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);
+ int error;
+ int i;
+
+ error = sysctl_wire_old_buffer(req, sizeof(int));
+ if (error)
+ return (error);
- /* ip6h_nxt will be fill in later */
- hbh->ip6h_len = 0; /* (8 >> 3) - 1 */
+ MLD_LOCK();
- /* XXX: grotty hard coding... */
- hbh_buf[2] = IP6OPT_PADN; /* 2 byte padding */
- hbh_buf[3] = 0;
- hbh_buf[4] = IP6OPT_ROUTER_ALERT;
- hbh_buf[5] = IP6OPT_RTALERT_LEN - 2;
- bcopy((caddr_t)&rtalert_code, &hbh_buf[6], sizeof(u_int16_t));
+ i = V_mld_gsrdelay.tv_sec;
- ip6_initpktopts(&V_ip6_opts);
- V_ip6_opts.ip6po_hbh = hbh;
+ error = sysctl_handle_int(oidp, &i, 0, req);
+ if (error || !req->newptr)
+ goto out_locked;
+
+ if (i < -1 || i >= 60) {
+ error = EINVAL;
+ goto out_locked;
+ }
+
+ CTR2(KTR_MLD, "change mld_gsrdelay from %d to %d",
+ V_mld_gsrdelay.tv_sec, i);
+ V_mld_gsrdelay.tv_sec = i;
+
+out_locked:
+ MLD_UNLOCK();
+ return (error);
}
-static void
-mld_starttimer(struct in6_multi *in6m)
+/*
+ * Expose struct mld_ifinfo to userland, keyed by ifindex.
+ * For use by ifmcstat(8).
+ *
+ * SMPng: NOTE: Does an unlocked ifindex space read.
+ * VIMAGE: Assume curvnet set by caller. The node handler itself
+ * is not directly virtualized.
+ */
+static int
+sysctl_mld_ifinfo(SYSCTL_HANDLER_ARGS)
{
- struct timeval now;
+ INIT_VNET_NET(curvnet);
+ INIT_VNET_INET6(curvnet);
+ int *name;
+ int error;
+ u_int namelen;
+ struct ifnet *ifp;
+ struct mld_ifinfo *mli;
+
+ name = (int *)arg1;
+ namelen = arg2;
+
+ if (req->newptr != NULL)
+ return (EPERM);
- microtime(&now);
- in6m->in6m_timer_expire.tv_sec = now.tv_sec + in6m->in6m_timer / hz;
- in6m->in6m_timer_expire.tv_usec = now.tv_usec +
- (in6m->in6m_timer % hz) * (1000000 / hz);
- if (in6m->in6m_timer_expire.tv_usec > 1000000) {
- in6m->in6m_timer_expire.tv_sec++;
- in6m->in6m_timer_expire.tv_usec -= 1000000;
+ if (namelen != 1)
+ return (EINVAL);
+
+ error = sysctl_wire_old_buffer(req, sizeof(struct mld_ifinfo));
+ if (error)
+ return (error);
+
+ IN6_MULTI_LOCK();
+ MLD_LOCK();
+
+ if (name[0] <= 0 || name[0] > V_if_index) {
+ error = ENOENT;
+ goto out_locked;
}
- /* start or restart the timer */
- callout_reset(in6m->in6m_timer_ch, in6m->in6m_timer,
- (void (*)(void *))mld_timeo, in6m);
+ error = ENOENT;
+
+ ifp = ifnet_byindex(name[0]);
+ if (ifp == NULL)
+ goto out_locked;
+
+ LIST_FOREACH(mli, &V_mli_head, mli_link) {
+ if (ifp == mli->mli_ifp) {
+ error = SYSCTL_OUT(req, mli,
+ sizeof(struct mld_ifinfo));
+ break;
+ }
+ }
+
+out_locked:
+ MLD_UNLOCK();
+ IN6_MULTI_UNLOCK();
+ return (error);
}
+/*
+ * Dispatch an entire queue of pending packet chains.
+ * VIMAGE: Assumes the vnet pointer has been set.
+ */
static void
-mld_stoptimer(struct in6_multi *in6m)
+mld_dispatch_queue(struct ifqueue *ifq, int limit)
{
- if (in6m->in6m_timer == IN6M_TIMER_UNDEF)
- return;
+ struct mbuf *m;
- callout_stop(in6m->in6m_timer_ch);
- in6m->in6m_timer = IN6M_TIMER_UNDEF;
+ for (;;) {
+ _IF_DEQUEUE(ifq, m);
+ if (m == NULL)
+ break;
+ CTR3(KTR_MLD, "%s: dispatch %p from %p", __func__, ifq, m);
+ mld_dispatch_packet(m);
+ if (--limit == 0)
+ break;
+ }
}
-static void
-mld_timeo(struct in6_multi *in6m)
+/*
+ * Filter outgoing MLD report state by group.
+ *
+ * Reports are ALWAYS suppressed for ALL-HOSTS (ff02::1)
+ * and node-local addresses. However, kernel and socket consumers
+ * always embed the KAME scope ID in the address provided, so strip it
+ * when performing comparison.
+ * Note: This is not the same as the *multicast* scope.
+ *
+ * Return zero if the given group is one for which MLD reports
+ * should be suppressed, or non-zero if reports should be issued.
+ */
+static __inline int
+mld_is_addr_reported(const struct in6_addr *addr)
{
- int s = splnet();
+ INIT_VNET_INET6(curvnet);
- in6m->in6m_timer = IN6M_TIMER_UNDEF;
+ KASSERT(IN6_IS_ADDR_MULTICAST(addr), ("%s: not multicast", __func__));
- callout_stop(in6m->in6m_timer_ch);
+ if (IPV6_ADDR_MC_SCOPE(addr) == IPV6_ADDR_SCOPE_NODELOCAL)
+ return (0);
- switch (in6m->in6m_state) {
- case MLD_REPORTPENDING:
- mld6_start_listening(in6m);
- break;
- default:
- mld6_sendpkt(in6m, MLD_LISTENER_REPORT, NULL);
- break;
+ if (IPV6_ADDR_MC_SCOPE(addr) == IPV6_ADDR_SCOPE_LINKLOCAL) {
+ struct in6_addr tmp = *addr;
+ in6_clearscope(&tmp);
+ if (IN6_ARE_ADDR_EQUAL(&tmp, &in6addr_linklocal_allnodes))
+ return (0);
}
- splx(s);
+ return (1);
}
-static u_long
-mld_timerresid(struct in6_multi *in6m)
+/*
+ * Attach MLD when PF_INET6 is attached to an interface.
+ *
+ * SMPng: Normally called with IF_AFDATA_LOCK held.
+ * VIMAGE: Currently we set the vnet pointer, although it is
+ * likely that it was already set by our caller.
+ */
+struct mld_ifinfo *
+mld_domifattach(struct ifnet *ifp)
{
- struct timeval now, diff;
+ struct mld_ifinfo *mli;
- microtime(&now);
+ CTR3(KTR_MLD, "%s: called for ifp %p(%s)",
+ __func__, ifp, ifp->if_xname);
- if (now.tv_sec > in6m->in6m_timer_expire.tv_sec ||
- (now.tv_sec == in6m->in6m_timer_expire.tv_sec &&
- now.tv_usec > in6m->in6m_timer_expire.tv_usec)) {
- return (0);
- }
- diff = in6m->in6m_timer_expire;
- diff.tv_sec -= now.tv_sec;
- diff.tv_usec -= now.tv_usec;
- if (diff.tv_usec < 0) {
- diff.tv_sec--;
- diff.tv_usec += 1000000;
- }
+ CURVNET_SET(ifp->if_vnet);
+ MLD_LOCK();
+
+ mli = mli_alloc_locked(ifp);
+ if (!(ifp->if_flags & IFF_MULTICAST))
+ mli->mli_flags |= MLIF_SILENT;
+
+ MLD_UNLOCK();
+ CURVNET_RESTORE();
- /* return the remaining time in milliseconds */
- return (diff.tv_sec * 1000 + diff.tv_usec / 1000);
+ return (mli);
}
-void
-mld6_start_listening(struct in6_multi *in6m)
+/*
+ * VIMAGE: assume curvnet set by caller.
+ */
+static struct mld_ifinfo *
+mli_alloc_locked(/*const*/ struct ifnet *ifp)
{
- struct in6_addr all_in6;
- int s = splnet();
+ INIT_VNET_INET6(ifp->if_vnet);
+ struct mld_ifinfo *mli;
+
+ MLD_LOCK_ASSERT();
+
+ mli = malloc(sizeof(struct mld_ifinfo), M_MLD, M_NOWAIT|M_ZERO);
+ if (mli == NULL)
+ goto out;
+
+ mli->mli_ifp = ifp;
+ mli->mli_version = MLD_VERSION_2;
+ mli->mli_flags = 0;
+ mli->mli_rv = MLD_RV_INIT;
+ mli->mli_qi = MLD_QI_INIT;
+ mli->mli_qri = MLD_QRI_INIT;
+ mli->mli_uri = MLD_URI_INIT;
+
+ SLIST_INIT(&mli->mli_relinmhead);
/*
- * RFC2710 page 10:
- * The node never sends a Report or Done for the link-scope all-nodes
- * address.
- * MLD messages are never sent for multicast addresses whose scope is 0
- * (reserved) or 1 (node-local).
+ * Responses to general queries are subject to bounds.
*/
- all_in6 = in6addr_linklocal_allnodes;
- if (in6_setscope(&all_in6, in6m->in6m_ifp, NULL)) {
- /* XXX: this should not happen! */
- in6m->in6m_timer = 0;
- in6m->in6m_state = MLD_OTHERLISTENER;
- }
- if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_in6) ||
- IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) <
- IPV6_ADDR_SCOPE_LINKLOCAL) {
- in6m->in6m_timer = 0;
- in6m->in6m_state = MLD_OTHERLISTENER;
- } else {
- mld6_sendpkt(in6m, MLD_LISTENER_REPORT, NULL);
- in6m->in6m_timer = arc4random() %
- MLD_UNSOLICITED_REPORT_INTERVAL * hz;
- in6m->in6m_state = MLD_IREPORTEDLAST;
+ IFQ_SET_MAXLEN(&mli->mli_gq, MLD_MAX_RESPONSE_PACKETS);
+
+ LIST_INSERT_HEAD(&V_mli_head, mli, mli_link);
+
+ CTR2(KTR_MLD, "allocate mld_ifinfo for ifp %p(%s)",
+ ifp, ifp->if_xname);
+
+out:
+ return (mli);
+}
+
+/*
+ * Hook for ifdetach.
+ *
+ * NOTE: Some finalization tasks need to run before the protocol domain
+ * is detached, but also before the link layer does its cleanup.
+ * Run before link-layer cleanup; cleanup groups, but do not free MLD state.
+ *
+ * SMPng: Caller must hold IN6_MULTI_LOCK().
+ * Must take IF_ADDR_LOCK() to cover if_multiaddrs iterator.
+ * XXX This routine is also bitten by unlocked ifma_protospec access.
+ *
+ * VIMAGE: curvnet should have been set by caller, but let's not assume
+ * that for now.
+ */
+void
+mld_ifdetach(struct ifnet *ifp)
+{
+ struct mld_ifinfo *mli;
+ struct ifmultiaddr *ifma;
+ struct in6_multi *inm, *tinm;
+
+ CTR3(KTR_MLD, "%s: called for ifp %p(%s)", __func__, ifp,
+ ifp->if_xname);
+
+ CURVNET_SET(ifp->if_vnet);
- mld_starttimer(in6m);
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK();
+
+ mli = MLD_IFINFO(ifp);
+ if (mli->mli_version == MLD_VERSION_2) {
+ IF_ADDR_LOCK(ifp);
+ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ if (ifma->ifma_addr->sa_family != AF_INET6 ||
+ ifma->ifma_protospec == NULL)
+ continue;
+ inm = (struct in6_multi *)ifma->ifma_protospec;
+ if (inm->in6m_state == MLD_LEAVING_MEMBER) {
+ SLIST_INSERT_HEAD(&mli->mli_relinmhead,
+ inm, in6m_nrele);
+ }
+ in6m_clear_recorded(inm);
+ }
+ IF_ADDR_UNLOCK(ifp);
+ SLIST_FOREACH_SAFE(inm, &mli->mli_relinmhead, in6m_nrele,
+ tinm) {
+ SLIST_REMOVE_HEAD(&mli->mli_relinmhead, in6m_nrele);
+ in6m_release_locked(inm);
+ }
}
- splx(s);
+
+ MLD_UNLOCK();
+ CURVNET_RESTORE();
}
+/*
+ * Hook for domifdetach.
+ * Runs after link-layer cleanup; free MLD state.
+ *
+ * SMPng: Normally called with IF_AFDATA_LOCK held.
+ * VIMAGE: curvnet should have been set by caller, but let's not assume
+ * that for now.
+ */
void
-mld6_stop_listening(struct in6_multi *in6m)
+mld_domifdetach(struct ifnet *ifp)
{
- struct in6_addr allnode, allrouter;
- allnode = in6addr_linklocal_allnodes;
- if (in6_setscope(&allnode, in6m->in6m_ifp, NULL)) {
- /* XXX: this should not happen! */
- return;
+ CTR3(KTR_MLD, "%s: called for ifp %p(%s)",
+ __func__, ifp, ifp->if_xname);
+
+ CURVNET_SET(ifp->if_vnet);
+
+ MLD_LOCK();
+ mli_delete_locked(ifp);
+ MLD_UNLOCK();
+
+ CURVNET_RESTORE();
+}
+
+static void
+mli_delete_locked(const struct ifnet *ifp)
+{
+ INIT_VNET_INET6(ifp->if_vnet);
+ struct mld_ifinfo *mli, *tmli;
+
+ CTR3(KTR_MLD, "%s: freeing mld_ifinfo for ifp %p(%s)",
+ __func__, ifp, ifp->if_xname);
+
+ MLD_LOCK_ASSERT();
+
+ LIST_FOREACH_SAFE(mli, &V_mli_head, mli_link, tmli) {
+ if (mli->mli_ifp == ifp) {
+ /*
+ * Free deferred General Query responses.
+ */
+ _IF_DRAIN(&mli->mli_gq);
+
+ LIST_REMOVE(mli, mli_link);
+
+ KASSERT(SLIST_EMPTY(&mli->mli_relinmhead),
+ ("%s: there are dangling in_multi references",
+ __func__));
+
+ free(mli, M_MLD);
+ return;
+ }
}
- allrouter = in6addr_linklocal_allrouters;
- if (in6_setscope(&allrouter, in6m->in6m_ifp, NULL)) {
- /* XXX impossible */
- return;
+#ifdef INVARIANTS
+ panic("%s: mld_ifinfo not found for ifp %p\n", __func__, ifp);
+#endif
+}
+
+/*
+ * Process a received MLDv1 general or address-specific query.
+ * Assumes that the query header has been pulled up to sizeof(mld_hdr).
+ */
+static int
+mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6,
+ const struct mld_hdr *mld)
+{
+ INIT_VNET_INET6(ifp->if_vnet);
+ struct ifmultiaddr *ifma;
+ struct mld_ifinfo *mli;
+ struct in6_multi *inm;
+ uint16_t timer;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+ IN6_MULTI_LOCK();
+ IF_ADDR_LOCK(ifp);
+ MLD_LOCK();
+
+ mli = MLD_IFINFO(ifp);
+ KASSERT(mli != NULL, ("%s: no mld_ifinfo for ifp %p", __func__, ifp));
+
+ /*
+ * Switch to MLDv1 host compatibility mode.
+ */
+ mld_set_version(mli, MLD_VERSION_1);
+
+ timer = ntohs(mld->mld_maxdelay) * PR_FASTHZ / MLD_TIMER_SCALE;
+ if (timer == 0)
+ timer = 1;
+
+ if (!IN6_IS_ADDR_UNSPECIFIED(&mld->mld_addr)) {
+ /*
+ * MLDv1 Group-Specific Query.
+ * If this is a group-specific MLDv1 query, we need only
+ * look up the single group to process it.
+ */
+ inm = in6m_lookup_locked(ifp, &mld->mld_addr);
+ if (inm != NULL) {
+ CTR3(KTR_MLD, "process v1 query %s on ifp %p(%s)",
+ ip6_sprintf(ip6tbuf, &mld->mld_addr),
+ ifp, ifp->if_xname);
+ mld_v1_update_group(inm, timer);
+ }
+ } else {
+ /*
+ * MLDv1 General Query.
+ * If this was not sent to the all-nodes group, ignore it.
+ *
+ * XXX Do we need to check for a scope ID in the destination
+ * address on input and strip it?
+ */
+ if (IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst,
+ &in6addr_linklocal_allnodes)) {
+ /*
+ * For each reporting group joined on this
+ * interface, kick the report timer.
+ */
+ CTR2(KTR_MLD,
+ "process v1 general query on ifp %p(%s)",
+ ifp, ifp->if_xname);
+
+ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ if (ifma->ifma_addr->sa_family != AF_INET6 ||
+ ifma->ifma_protospec == NULL)
+ continue;
+ inm = (struct in6_multi *)ifma->ifma_protospec;
+ mld_v1_update_group(inm, timer);
+ }
+ }
}
- if (in6m->in6m_state == MLD_IREPORTEDLAST &&
- !IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &allnode) &&
- IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) >
- IPV6_ADDR_SCOPE_INTFACELOCAL) {
- mld6_sendpkt(in6m, MLD_LISTENER_DONE, &allrouter);
+
+ MLD_UNLOCK();
+ IF_ADDR_UNLOCK(ifp);
+ IN6_MULTI_UNLOCK();
+
+ return (0);
+}
+
+/*
+ * Update the report timer on a group in response to an MLDv1 query.
+ *
+ * If we are becoming the reporting member for this group, start the timer.
+ * If we already are the reporting member for this group, and timer is
+ * below the threshold, reset it.
+ *
+ * We may be updating the group for the first time since we switched
+ * to MLDv2. If we are, then we must clear any recorded source lists,
+ * and transition to REPORTING state; the group timer is overloaded
+ * for group and group-source query responses.
+ *
+ * Unlike MLDv2, the delay per group should be jittered
+ * to avoid bursts of MLDv1 reports.
+ */
+static void
+mld_v1_update_group(struct in6_multi *inm, const int timer)
+{
+ INIT_VNET_INET6(curvnet);
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+ CTR4(KTR_MLD, "%s: %s/%s timer=%d", __func__,
+ ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname, timer);
+
+ IN6_MULTI_LOCK_ASSERT();
+
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ break;
+ case MLD_REPORTING_MEMBER:
+ if (inm->in6m_timer != 0 &&
+ inm->in6m_timer <= timer) {
+ CTR1(KTR_MLD, "%s: REPORTING and timer running, "
+ "skipping.", __func__);
+ break;
+ }
+ /* FALLTHROUGH */
+ case MLD_SG_QUERY_PENDING_MEMBER:
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_IDLE_MEMBER:
+ case MLD_LAZY_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ CTR1(KTR_MLD, "%s: ->REPORTING", __func__);
+ inm->in6m_state = MLD_REPORTING_MEMBER;
+ inm->in6m_timer = MLD_RANDOM_DELAY(timer);
+ V_current_state_timers_running6 = 1;
+ break;
+ case MLD_SLEEPING_MEMBER:
+ CTR1(KTR_MLD, "%s: ->AWAKENING", __func__);
+ inm->in6m_state = MLD_AWAKENING_MEMBER;
+ break;
+ case MLD_LEAVING_MEMBER:
+ break;
}
}
-void
-mld6_input(struct mbuf *m, int off)
+/*
+ * Process a received MLDv2 general, group-specific or
+ * group-and-source-specific query.
+ *
+ * Assumes that the query header has been pulled up to sizeof(mldv2_query).
+ *
+ * Return 0 if successful, otherwise an appropriate error code is returned.
+ */
+static int
+mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6,
+ struct mbuf *m, const int off, const int icmp6len)
{
INIT_VNET_INET6(curvnet);
- struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
- struct mld_hdr *mldh;
- struct ifnet *ifp = m->m_pkthdr.rcvif;
- struct in6_multi *in6m;
- struct in6_addr mld_addr, all_in6;
- struct in6_ifaddr *ia;
- struct ifmultiaddr *ifma;
- u_long timer; /* timer value in the MLD query header */
-
-#ifndef PULLDOWN_TEST
- IP6_EXTHDR_CHECK(m, off, sizeof(*mldh),);
- mldh = (struct mld_hdr *)(mtod(m, caddr_t) + off);
-#else
- IP6_EXTHDR_GET(mldh, struct mld_hdr *, m, off, sizeof(*mldh));
- if (mldh == NULL) {
- ICMP6STAT_INC(icp6s_tooshort);
- return;
+ struct mld_ifinfo *mli;
+ struct mldv2_query *mld;
+ struct in6_multi *inm;
+ uint32_t maxdelay, nsrc, qqi;
+ uint16_t timer;
+ uint8_t qrv;
+
+ CTR2(KTR_MLD, "process v2 query on ifp %p(%s)", ifp, ifp->if_xname);
+
+ mld = (struct mldv2_query *)(mtod(m, uint8_t *) + off);
+
+ maxdelay = ntohs(mld->mld_maxdelay); /* in 1/10ths of a second */
+ if (maxdelay >= 32678) {
+ maxdelay = (MLD_MRC_MANT(mld->mld_maxdelay) | 0x1000) <<
+ (MLD_MRC_EXP(mld->mld_maxdelay) + 3);
}
-#endif
- /* source address validation */
- ip6 = mtod(m, struct ip6_hdr *); /* in case mpullup */
- if (!IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) {
- char ip6bufs[INET6_ADDRSTRLEN], ip6bufg[INET6_ADDRSTRLEN];
- log(LOG_ERR,
- "mld6_input: src %s is not link-local (grp=%s)\n",
- ip6_sprintf(ip6bufs, &ip6->ip6_src),
- ip6_sprintf(ip6bufg, &mldh->mld_addr));
+ qrv = MLD_QRV(mld->mld_misc);
+ if (qrv < 2) {
+ CTR3(KTR_MLD, "%s: clamping qrv %d to %d", __func__,
+ qrv, MLD_RV_INIT);
+ qrv = MLD_RV_INIT;
+ }
+
+ qqi = mld->mld_qqi;
+ if (qqi >= 128) {
+ qqi = MLD_QQIC_MANT(mld->mld_qqi) <<
+ (MLD_QQIC_EXP(mld->mld_qqi) + 3);
+ }
+
+ timer = maxdelay * PR_FASTHZ / MLD_TIMER_SCALE;
+ if (timer == 0)
+ timer = 1;
+
+ nsrc = ntohs(mld->mld_numsrc);
+ if (nsrc > MLD_MAX_GS_SOURCES)
+ return (EMSGSIZE);
+ if (icmp6len < sizeof(struct mldv2_query) +
+ (nsrc * sizeof(struct in6_addr)))
+ return (EMSGSIZE);
+
+ IN6_MULTI_LOCK();
+ IF_ADDR_LOCK(ifp);
+ MLD_LOCK();
+
+ mli = MLD_IFINFO(ifp);
+ KASSERT(mli != NULL, ("%s: no mld_ifinfo for ifp %p", __func__, ifp));
+
+ mld_set_version(mli, MLD_VERSION_2);
+
+ mli->mli_rv = qrv;
+ mli->mli_qi = qqi;
+ mli->mli_qri = maxdelay;
+
+ CTR4(KTR_MLD, "%s: qrv %d qi %d qri %d", __func__, qrv, qqi,
+ maxdelay);
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&mld->mld_addr)) {
/*
- * spec (RFC2710) does not explicitly
- * specify to discard the packet from a non link-local
- * source address. But we believe it's expected to do so.
- * XXX: do we have to allow :: as source?
+ * MLDv2 General Query.
+ * Schedule a current-state report on this ifp for
+ * all groups, possibly containing source lists.
*/
- m_freem(m);
- return;
+ if (!IN6_ARE_ADDR_EQUAL(&in6addr_linklocal_allnodes,
+ &ip6->ip6_dst) || nsrc > 0) {
+ /*
+ * General Queries SHOULD be directed to ff02::1.
+ * A general query with a source list has undefined
+ * behaviour; discard it.
+ */
+ goto out_locked;
+ }
+
+ CTR2(KTR_MLD, "process v2 general query on ifp %p(%s)",
+ ifp, ifp->if_xname);
+
+ /*
+ * If there is a pending General Query response
+ * scheduled earlier than the selected delay, do
+ * not schedule any other reports.
+ * Otherwise, reset the interface timer.
+ */
+ if (mli->mli_v1_timer == 0 || mli->mli_v2_timer >= timer) {
+ mli->mli_v1_timer = MLD_RANDOM_DELAY(timer);
+ V_interface_timers_running6 = 1;
+ }
+ } else {
+ /*
+ * MLDv2 Group-specific or Group-and-source-specific Query.
+ *
+ * Group-source-specific queries are throttled on
+ * a per-group basis to defeat denial-of-service attempts.
+ * Queries for groups we are not a member of on this
+ * link are simply ignored.
+ */
+ inm = in6m_lookup_locked(ifp, &mld->mld_addr);
+ if (inm == NULL)
+ goto out_locked;
+ if (nsrc > 0) {
+ if (!ratecheck(&inm->in6m_lastgsrtv,
+ &V_mld_gsrdelay)) {
+ CTR1(KTR_MLD, "%s: GS query throttled.",
+ __func__);
+ goto out_locked;
+ }
+ }
+ CTR2(KTR_MLD, "process v2 group query on ifp %p(%s)",
+ ifp, ifp->if_xname);
+ /*
+ * If there is a pending General Query response
+ * scheduled sooner than the selected delay, no
+ * further report need be scheduled.
+ * Otherwise, prepare to respond to the
+ * group-specific or group-and-source query.
+ */
+ if (mli->mli_v1_timer == 0 || mli->mli_v2_timer >= timer)
+ mld_v2_process_group_query(inm, mli, timer, m, off);
+ }
+
+out_locked:
+ MLD_UNLOCK();
+ IF_ADDR_UNLOCK(ifp);
+ IN6_MULTI_UNLOCK();
+
+ return (0);
+}
+
+/*
+ * Process a recieved MLDv2 group-specific or group-and-source-specific
+ * query.
+ * Return <0 if any error occured. Currently this is ignored.
+ */
+static int
+mld_v2_process_group_query(struct in6_multi *inm, struct mld_ifinfo *mli,
+ int timer, struct mbuf *m0, const int off)
+{
+ INIT_VNET_INET6(curvnet);
+ struct mldv2_query *mld;
+ int retval;
+ uint16_t nsrc;
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ retval = 0;
+ mld = (struct mldv2_query *)(mtod(m0, uint8_t *) + off);
+
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ case MLD_SLEEPING_MEMBER:
+ case MLD_LAZY_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ case MLD_IDLE_MEMBER:
+ case MLD_LEAVING_MEMBER:
+ return (retval);
+ break;
+ case MLD_REPORTING_MEMBER:
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_SG_QUERY_PENDING_MEMBER:
+ break;
}
+ nsrc = ntohs(mld->mld_numsrc);
+
/*
- * make a copy for local work (in6_setscope() may modify the 1st arg)
+ * Deal with group-specific queries upfront.
+ * If any group query is already pending, purge any recorded
+ * source-list state if it exists, and schedule a query response
+ * for this group-specific query.
*/
- mld_addr = mldh->mld_addr;
- if (in6_setscope(&mld_addr, ifp, NULL)) {
- /* XXX: this should not happen! */
- m_free(m);
- return;
+ if (nsrc == 0) {
+ if (inm->in6m_state == MLD_G_QUERY_PENDING_MEMBER ||
+ inm->in6m_state == MLD_SG_QUERY_PENDING_MEMBER) {
+ in6m_clear_recorded(inm);
+ timer = min(inm->in6m_timer, timer);
+ }
+ inm->in6m_state = MLD_G_QUERY_PENDING_MEMBER;
+ inm->in6m_timer = MLD_RANDOM_DELAY(timer);
+ V_current_state_timers_running6 = 1;
+ return (retval);
}
/*
- * In the MLD6 specification, there are 3 states and a flag.
- *
- * In Non-Listener state, we simply don't have a membership record.
- * In Delaying Listener state, our timer is running (in6m->in6m_timer)
- * In Idle Listener state, our timer is not running
- * (in6m->in6m_timer==IN6M_TIMER_UNDEF)
- *
- * The flag is in6m->in6m_state, it is set to MLD_OTHERLISTENER if
- * we have heard a report from another member, or MLD_IREPORTEDLAST
- * if we sent the last report.
+ * Deal with the case where a group-and-source-specific query has
+ * been received but a group-specific query is already pending.
*/
- switch(mldh->mld_type) {
- case MLD_LISTENER_QUERY:
- if (ifp->if_flags & IFF_LOOPBACK)
- break;
-
- if (!IN6_IS_ADDR_UNSPECIFIED(&mld_addr) &&
- !IN6_IS_ADDR_MULTICAST(&mld_addr))
- break; /* print error or log stat? */
+ if (inm->in6m_state == MLD_G_QUERY_PENDING_MEMBER) {
+ timer = min(inm->in6m_timer, timer);
+ inm->in6m_timer = MLD_RANDOM_DELAY(timer);
+ V_current_state_timers_running6 = 1;
+ return (retval);
+ }
- all_in6 = in6addr_linklocal_allnodes;
- if (in6_setscope(&all_in6, ifp, NULL)) {
- /* XXX: this should not happen! */
- break;
+ /*
+ * Finally, deal with the case where a group-and-source-specific
+ * query has been received, where a response to a previous g-s-r
+ * query exists, or none exists.
+ * In this case, we need to parse the source-list which the Querier
+ * has provided us with and check if we have any source list filter
+ * entries at T1 for these sources. If we do not, there is no need
+ * schedule a report and the query may be dropped.
+ * If we do, we must record them and schedule a current-state
+ * report for those sources.
+ */
+ if (inm->in6m_nsrc > 0) {
+ struct mbuf *m;
+ uint8_t *sp;
+ int i, nrecorded;
+ int soff;
+
+ m = m0;
+ soff = off + sizeof(struct mldv2_query);
+ nrecorded = 0;
+ for (i = 0; i < nsrc; i++) {
+ sp = mtod(m, uint8_t *) + soff;
+ retval = in6m_record_source(inm,
+ (const struct in6_addr *)sp);
+ if (retval < 0)
+ break;
+ nrecorded += retval;
+ soff += sizeof(struct in6_addr);
+ if (soff >= m->m_len) {
+ soff = soff - m->m_len;
+ m = m->m_next;
+ if (m == NULL)
+ break;
+ }
+ }
+ if (nrecorded > 0) {
+ CTR1(KTR_MLD,
+ "%s: schedule response to SG query", __func__);
+ inm->in6m_state = MLD_SG_QUERY_PENDING_MEMBER;
+ inm->in6m_timer = MLD_RANDOM_DELAY(timer);
+ V_current_state_timers_running6 = 1;
}
+ }
+
+ return (retval);
+}
+
+/*
+ * Process a received MLDv1 host membership report.
+ * Assumes mld points to mld_hdr in pulled up mbuf chain.
+ */
+static int
+mld_v1_input_report(struct ifnet *ifp, const struct ip6_hdr *ip6,
+ const struct mld_hdr *mld)
+{
+ INIT_VNET_INET6(curvnet);
+ struct in6_ifaddr *ia;
+ struct in6_multi *inm;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+ if (ifp->if_flags & IFF_LOOPBACK)
+ return (0);
+
+ if (!IN6_IS_ADDR_MULTICAST(&mld->mld_addr) ||
+ !IN6_ARE_ADDR_EQUAL(&mld->mld_addr, &ip6->ip6_dst))
+ return (EINVAL);
+
+ /*
+ * Make sure we don't hear our own membership report, as fast
+ * leave requires knowing that we are the only member of a
+ * group. Assume we used the link-local address if available,
+ * otherwise look for ::.
+ */
+ ia = in6ifa_ifpforlinklocal(ifp, IN6_IFF_NOTREADY|IN6_IFF_ANYCAST);
+ if ((ia && IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, IA6_IN6(ia))) ||
+ (ia == NULL && IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)))
+ return (0);
+
+ CTR3(KTR_MLD, "process v1 report %s on ifp %p(%s)",
+ ip6_sprintf(ip6tbuf, &mld->mld_addr), ifp, ifp->if_xname);
+
+ IN6_MULTI_LOCK();
+ IF_ADDR_LOCK(ifp);
+
+ /*
+ * MLDv1 report suppression.
+ * If we are a member of this group, and our membership should be
+ * reported, and our group timer is pending or about to be reset,
+ * stop our group timer by transitioning to the 'lazy' state.
+ */
+ inm = in6m_lookup_locked(ifp, &mld->mld_addr);
+ if (inm != NULL) {
+ struct mld_ifinfo *mli;
+
+ mli = inm->in6m_mli;
+ KASSERT(mli != NULL,
+ ("%s: no mli for ifp %p", __func__, ifp));
/*
- * - Start the timers in all of our membership records
- * that the query applies to for the interface on
- * which the query arrived excl. those that belong
- * to the "all-nodes" group (ff02::1).
- * - Restart any timer that is already running but has
- * A value longer than the requested timeout.
- * - Use the value specified in the query message as
- * the maximum timeout.
+ * If we are in MLDv2 host mode, do not allow the
+ * other host's MLDv1 report to suppress our reports.
*/
- timer = ntohs(mldh->mld_maxdelay);
+ if (mli->mli_version == MLD_VERSION_2)
+ goto out_locked;
- IF_ADDR_LOCK(ifp);
- IFP_TO_IA6(ifp, ia);
- if (ia == NULL) {
- IF_ADDR_UNLOCK(ifp);
+ inm->in6m_timer = 0;
+
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ case MLD_SLEEPING_MEMBER:
+ break;
+ case MLD_REPORTING_MEMBER:
+ case MLD_IDLE_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ CTR3(KTR_MLD,
+ "report suppressed for %s on ifp %p(%s)",
+ ip6_sprintf(ip6tbuf, &mld->mld_addr),
+ ifp, ifp->if_xname);
+ case MLD_LAZY_MEMBER:
+ inm->in6m_state = MLD_LAZY_MEMBER;
+ break;
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_SG_QUERY_PENDING_MEMBER:
+ case MLD_LEAVING_MEMBER:
break;
}
+ }
- /*
- * XXX: System timer resolution is too low to handle Max
- * Response Delay, so set 1 to the internal timer even if
- * the calculated value equals to zero when Max Response
- * Delay is positive.
- */
- timer = ntohs(mldh->mld_maxdelay) * PR_FASTHZ / MLD_TIMER_SCALE;
- if (timer == 0 && mldh->mld_maxdelay)
- timer = 1;
+out_locked:
+ IF_ADDR_UNLOCK(ifp);
+ IN6_MULTI_UNLOCK();
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
- if (ifma->ifma_addr->sa_family != AF_INET6)
- continue;
- in6m = (struct in6_multi *)ifma->ifma_protospec;
+ return (0);
+}
- if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_in6) ||
- IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) <
- IPV6_ADDR_SCOPE_LINKLOCAL)
- continue;
+/*
+ * MLD input path.
+ *
+ * Assume query messages which fit in a single ICMPv6 message header
+ * have been pulled up.
+ * Assume that userland will want to see the message, even if it
+ * otherwise fails kernel input validation; do not free it.
+ * Pullup may however free the mbuf chain m if it fails.
+ *
+ * Return IPPROTO_DONE if we freed m. Otherwise, return 0.
+ */
+int
+mld_input(struct mbuf *m, int off, int icmp6len)
+{
+ struct ifnet *ifp;
+ struct ip6_hdr *ip6;
+ struct mld_hdr *mld;
+ int mldlen;
- if (IN6_IS_ADDR_UNSPECIFIED(&mld_addr) ||
- IN6_ARE_ADDR_EQUAL(&mld_addr, &in6m->in6m_addr)) {
- if (timer == 0) {
- /* send a report immediately */
- mld_stoptimer(in6m);
- mld6_sendpkt(in6m, MLD_LISTENER_REPORT,
- NULL);
- in6m->in6m_timer = 0; /* reset timer */
- in6m->in6m_state = MLD_IREPORTEDLAST;
- }
- else if (in6m->in6m_timer == IN6M_TIMER_UNDEF ||
- mld_timerresid(in6m) > timer) {
- in6m->in6m_timer =
- 1 + (arc4random() % timer) * hz / 1000;
- mld_starttimer(in6m);
- }
+ CTR3(KTR_MLD, "%s: called w/mbuf (%p,%d)", __func__, m, off);
+
+ ifp = m->m_pkthdr.rcvif;
+ INIT_VNET_INET6(ifp->if_vnet);
+
+ ip6 = mtod(m, struct ip6_hdr *);
+
+ /* Pullup to appropriate size. */
+ mld = (struct mld_hdr *)(mtod(m, uint8_t *) + off);
+ if (mld->mld_type == MLD_LISTENER_QUERY &&
+ icmp6len >= sizeof(struct mldv2_query)) {
+ mldlen = sizeof(struct mldv2_query);
+ } else {
+ mldlen = sizeof(struct mld_hdr);
+ }
+ IP6_EXTHDR_GET(mld, struct mld_hdr *, m, off, mldlen);
+ if (mld == NULL) {
+ ICMP6STAT_INC(icp6s_badlen);
+ return (IPPROTO_DONE);
+ }
+
+ switch (mld->mld_type) {
+ case MLD_LISTENER_QUERY:
+ icmp6_ifstat_inc(ifp, ifs6_in_mldquery);
+ if (icmp6len == sizeof(struct mld_hdr)) {
+ if (mld_v1_input_query(ifp, ip6, mld) != 0)
+ return (0);
+ } else if (icmp6len >= sizeof(struct mldv2_query)) {
+ if (mld_v2_input_query(ifp, ip6, m, off,
+ icmp6len) != 0)
+ return (0);
+ }
+ break;
+ case MLD_LISTENER_REPORT:
+ icmp6_ifstat_inc(ifp, ifs6_in_mldreport);
+ if (mld_v1_input_report(ifp, ip6, mld) != 0)
+ return (0); /* Userland needs to see it. */
+ break;
+ case MLDV2_LISTENER_REPORT:
+ icmp6_ifstat_inc(ifp, ifs6_in_mldreport);
+ break;
+ case MLD_LISTENER_DONE:
+ icmp6_ifstat_inc(ifp, ifs6_in_mlddone);
+ break;
+ default:
+ break;
+ }
+
+ return (0);
+}
+
+/*
+ * Fast timeout handler (global).
+ * VIMAGE: Timeout handlers are expected to service all vimages.
+ */
+void
+mld_fasttimo(void)
+{
+ VNET_ITERATOR_DECL(vnet_iter);
+
+ VNET_LIST_RLOCK();
+ VNET_FOREACH(vnet_iter) {
+ CURVNET_SET(vnet_iter);
+ mld_fasttimo_vnet();
+ CURVNET_RESTORE();
+ }
+ VNET_LIST_RUNLOCK();
+}
+
+/*
+ * Fast timeout handler (per-vnet).
+ *
+ * VIMAGE: Assume caller has set up our curvnet.
+ */
+static void
+mld_fasttimo_vnet(void)
+{
+ INIT_VNET_INET6(curvnet);
+ struct ifqueue scq; /* State-change packets */
+ struct ifqueue qrq; /* Query response packets */
+ struct ifnet *ifp;
+ struct mld_ifinfo *mli;
+ struct ifmultiaddr *ifma, *tifma;
+ struct in6_multi *inm;
+ int uri_fasthz;
+
+ uri_fasthz = 0;
+
+ /*
+ * Quick check to see if any work needs to be done, in order to
+ * minimize the overhead of fasttimo processing.
+ * SMPng: XXX Unlocked reads.
+ */
+ if (!V_current_state_timers_running6 &&
+ !V_interface_timers_running6 &&
+ !V_state_change_timers_running6)
+ return;
+
+ IN6_MULTI_LOCK();
+ MLD_LOCK();
+
+ /*
+ * MLDv2 General Query response timer processing.
+ */
+ if (V_interface_timers_running6) {
+ CTR1(KTR_MLD, "%s: interface timers running", __func__);
+
+ V_interface_timers_running6 = 0;
+ LIST_FOREACH(mli, &V_mli_head, mli_link) {
+ if (mli->mli_v2_timer == 0) {
+ /* Do nothing. */
+ } else if (--mli->mli_v2_timer == 0) {
+ mld_v2_dispatch_general_query(mli);
+ } else {
+ V_interface_timers_running6 = 1;
+ }
+ }
+ }
+
+ if (!V_current_state_timers_running6 &&
+ !V_state_change_timers_running6)
+ goto out_locked;
+
+ V_current_state_timers_running6 = 0;
+ V_state_change_timers_running6 = 0;
+
+ CTR1(KTR_MLD, "%s: state change timers running", __func__);
+
+ /*
+ * MLD host report and state-change timer processing.
+ * Note: Processing a v2 group timer may remove a node.
+ */
+ LIST_FOREACH(mli, &V_mli_head, mli_link) {
+ ifp = mli->mli_ifp;
+
+ if (mli->mli_version == MLD_VERSION_2) {
+ uri_fasthz = MLD_RANDOM_DELAY(mli->mli_uri *
+ PR_FASTHZ);
+
+ memset(&qrq, 0, sizeof(struct ifqueue));
+ IFQ_SET_MAXLEN(&qrq, MLD_MAX_G_GS_PACKETS);
+
+ memset(&scq, 0, sizeof(struct ifqueue));
+ IFQ_SET_MAXLEN(&scq, MLD_MAX_STATE_CHANGE_PACKETS);
+ }
+
+ IF_ADDR_LOCK(ifp);
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link,
+ tifma) {
+ if (ifma->ifma_addr->sa_family != AF_INET6 ||
+ ifma->ifma_protospec == NULL)
+ continue;
+ inm = (struct in6_multi *)ifma->ifma_protospec;
+ switch (mli->mli_version) {
+ case MLD_VERSION_1:
+ mld_v1_process_group_timer(inm,
+ mli->mli_version);
+ break;
+ case MLD_VERSION_2:
+ mld_v2_process_group_timers(mli, &qrq,
+ &scq, inm, uri_fasthz);
+ break;
}
}
IF_ADDR_UNLOCK(ifp);
+
+ if (mli->mli_version == MLD_VERSION_2) {
+ struct in6_multi *tinm;
+
+ mld_dispatch_queue(&qrq, 0);
+ mld_dispatch_queue(&scq, 0);
+
+ /*
+ * Free the in_multi reference(s) for
+ * this lifecycle.
+ */
+ SLIST_FOREACH_SAFE(inm, &mli->mli_relinmhead,
+ in6m_nrele, tinm) {
+ SLIST_REMOVE_HEAD(&mli->mli_relinmhead,
+ in6m_nrele);
+ in6m_release_locked(inm);
+ }
+ }
+ }
+
+out_locked:
+ MLD_UNLOCK();
+ IN6_MULTI_UNLOCK();
+}
+
+/*
+ * Update host report group timer.
+ * Will update the global pending timer flags.
+ */
+static void
+mld_v1_process_group_timer(struct in6_multi *inm, const int version)
+{
+ INIT_VNET_INET6(curvnet);
+ int report_timer_expired;
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ if (inm->in6m_timer == 0) {
+ report_timer_expired = 0;
+ } else if (--inm->in6m_timer == 0) {
+ report_timer_expired = 1;
+ } else {
+ V_current_state_timers_running6 = 1;
+ return;
+ }
+
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ case MLD_IDLE_MEMBER:
+ case MLD_LAZY_MEMBER:
+ case MLD_SLEEPING_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ break;
+ case MLD_REPORTING_MEMBER:
+ if (report_timer_expired) {
+ inm->in6m_state = MLD_IDLE_MEMBER;
+ (void)mld_v1_transmit_report(inm,
+ MLD_LISTENER_REPORT);
+ }
+ break;
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_SG_QUERY_PENDING_MEMBER:
+ case MLD_LEAVING_MEMBER:
break;
+ }
+}
- case MLD_LISTENER_REPORT:
+/*
+ * Update a group's timers for MLDv2.
+ * Will update the global pending timer flags.
+ * Note: Unlocked read from mli.
+ */
+static void
+mld_v2_process_group_timers(struct mld_ifinfo *mli,
+ struct ifqueue *qrq, struct ifqueue *scq,
+ struct in6_multi *inm, const int uri_fasthz)
+{
+ INIT_VNET_INET6(curvnet);
+ int query_response_timer_expired;
+ int state_change_retransmit_timer_expired;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ query_response_timer_expired = 0;
+ state_change_retransmit_timer_expired = 0;
+
+ /*
+ * During a transition from compatibility mode back to MLDv2,
+ * a group record in REPORTING state may still have its group
+ * timer active. This is a no-op in this function; it is easier
+ * to deal with it here than to complicate the slow-timeout path.
+ */
+ if (inm->in6m_timer == 0) {
+ query_response_timer_expired = 0;
+ } else if (--inm->in6m_timer == 0) {
+ query_response_timer_expired = 1;
+ } else {
+ V_current_state_timers_running6 = 1;
+ }
+
+ if (inm->in6m_sctimer == 0) {
+ state_change_retransmit_timer_expired = 0;
+ } else if (--inm->in6m_sctimer == 0) {
+ state_change_retransmit_timer_expired = 1;
+ } else {
+ V_state_change_timers_running6 = 1;
+ }
+
+ /* We are in fasttimo, so be quick about it. */
+ if (!state_change_retransmit_timer_expired &&
+ !query_response_timer_expired)
+ return;
+
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ case MLD_SLEEPING_MEMBER:
+ case MLD_LAZY_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ case MLD_IDLE_MEMBER:
+ break;
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_SG_QUERY_PENDING_MEMBER:
/*
- * For fast leave to work, we have to know that we are the
- * last person to send a report for this group. Reports
- * can potentially get looped back if we are a multicast
- * router, so discard reports sourced by me.
- * Note that it is impossible to check IFF_LOOPBACK flag of
- * ifp for this purpose, since ip6_mloopback pass the physical
- * interface to looutput.
+ * Respond to a previously pending Group-Specific
+ * or Group-and-Source-Specific query by enqueueing
+ * the appropriate Current-State report for
+ * immediate transmission.
*/
- if (m->m_flags & M_LOOP) /* XXX: grotty flag, but efficient */
- break;
+ if (query_response_timer_expired) {
+ int retval;
+
+ retval = mld_v2_enqueue_group_record(qrq, inm, 0, 1,
+ (inm->in6m_state == MLD_SG_QUERY_PENDING_MEMBER));
+ CTR2(KTR_MLD, "%s: enqueue record = %d",
+ __func__, retval);
+ inm->in6m_state = MLD_REPORTING_MEMBER;
+ in6m_clear_recorded(inm);
+ }
+ /* FALLTHROUGH */
+ case MLD_REPORTING_MEMBER:
+ case MLD_LEAVING_MEMBER:
+ if (state_change_retransmit_timer_expired) {
+ /*
+ * State-change retransmission timer fired.
+ * If there are any further pending retransmissions,
+ * set the global pending state-change flag, and
+ * reset the timer.
+ */
+ if (--inm->in6m_scrv > 0) {
+ inm->in6m_sctimer = uri_fasthz;
+ V_state_change_timers_running6 = 1;
+ }
+ /*
+ * Retransmit the previously computed state-change
+ * report. If there are no further pending
+ * retransmissions, the mbuf queue will be consumed.
+ * Update T0 state to T1 as we have now sent
+ * a state-change.
+ */
+ (void)mld_v2_merge_state_changes(inm, scq);
+
+ in6m_commit(inm);
+ CTR3(KTR_MLD, "%s: T1 -> T0 for %s/%s", __func__,
+ ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname);
+
+ /*
+ * If we are leaving the group for good, make sure
+ * we release MLD's reference to it.
+ * This release must be deferred using a SLIST,
+ * as we are called from a loop which traverses
+ * the in_ifmultiaddr TAILQ.
+ */
+ if (inm->in6m_state == MLD_LEAVING_MEMBER &&
+ inm->in6m_scrv == 0) {
+ inm->in6m_state = MLD_NOT_MEMBER;
+ SLIST_INSERT_HEAD(&mli->mli_relinmhead,
+ inm, in6m_nrele);
+ }
+ }
+ break;
+ }
+}
- if (!IN6_IS_ADDR_MULTICAST(&mld_addr))
- break;
+/*
+ * Switch to a different version on the given interface,
+ * as per Section 9.12.
+ */
+static void
+mld_set_version(struct mld_ifinfo *mli, const int version)
+{
+ MLD_LOCK_ASSERT();
+
+ CTR4(KTR_MLD, "%s: switching to v%d on ifp %p(%s)", __func__,
+ version, mli->mli_ifp, mli->mli_ifp->if_xname);
+
+ if (version == MLD_VERSION_1) {
+ int old_version_timer;
/*
- * If we belong to the group being reported, stop
- * our timer for that group.
+ * Compute the "Older Version Querier Present" timer as per
+ * Section 9.12.
*/
- IN6_LOOKUP_MULTI(mld_addr, ifp, in6m);
- if (in6m) {
- in6m->in6m_timer = 0; /* transit to idle state */
- in6m->in6m_state = MLD_OTHERLISTENER; /* clear flag */
+ old_version_timer = mli->mli_rv * mli->mli_qi + mli->mli_qri;
+ old_version_timer *= PR_SLOWHZ;
+
+ if (version == MLD_VERSION_1) {
+ mli->mli_v1_timer = old_version_timer;
}
- break;
- default: /* this is impossible */
- log(LOG_ERR, "mld6_input: illegal type(%d)", mldh->mld_type);
- break;
}
- m_freem(m);
+ if (mli->mli_v1_timer > 0) {
+ if (mli->mli_version != MLD_VERSION_1) {
+ mli->mli_version = MLD_VERSION_1;
+ mld_v2_cancel_link_timers(mli);
+ }
+ }
}
+/*
+ * Cancel pending MLDv2 timers for the given link and all groups
+ * joined on it; state-change, general-query, and group-query timers.
+ */
static void
-mld6_sendpkt(struct in6_multi *in6m, int type, const struct in6_addr *dst)
+mld_v2_cancel_link_timers(struct mld_ifinfo *mli)
{
INIT_VNET_INET6(curvnet);
- struct mbuf *mh, *md;
- struct mld_hdr *mldh;
- struct ip6_hdr *ip6;
- struct ip6_moptions im6o;
- struct in6_ifaddr *ia;
- struct ifnet *ifp = in6m->in6m_ifp;
- struct ifnet *outif = NULL;
+ struct ifmultiaddr *ifma;
+ struct ifnet *ifp;
+ struct in6_multi *inm;
+
+ CTR3(KTR_MLD, "%s: cancel v2 timers on ifp %p(%s)", __func__,
+ mli->mli_ifp, mli->mli_ifp->if_xname);
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
/*
- * At first, find a link local address on the outgoing interface
- * to use as the source address of the MLD packet.
+ * Fast-track this potentially expensive operation
+ * by checking all the global 'timer pending' flags.
*/
- if ((ia = in6ifa_ifpforlinklocal(ifp, IN6_IFF_NOTREADY|IN6_IFF_ANYCAST))
- == NULL)
+ if (!V_interface_timers_running6 &&
+ !V_state_change_timers_running6 &&
+ !V_current_state_timers_running6)
return;
- /*
- * Allocate mbufs to store ip6 header and MLD header.
- * We allocate 2 mbufs and make chain in advance because
- * it is more convenient when inserting the hop-by-hop option later.
- */
+ mli->mli_v2_timer = 0;
+
+ ifp = mli->mli_ifp;
+
+ IF_ADDR_LOCK(ifp);
+ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ if (ifma->ifma_addr->sa_family != AF_INET6)
+ continue;
+ inm = (struct in6_multi *)ifma->ifma_protospec;
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ case MLD_IDLE_MEMBER:
+ case MLD_LAZY_MEMBER:
+ case MLD_SLEEPING_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ break;
+ case MLD_LEAVING_MEMBER:
+ /*
+ * If we are leaving the group and switching
+ * version, we need to release the final
+ * reference held for issuing the INCLUDE {}.
+ *
+ * SMPNG: Must drop and re-acquire IF_ADDR_LOCK
+ * around in6m_release_locked(), as it is not
+ * a recursive mutex.
+ */
+ IF_ADDR_UNLOCK(ifp);
+ in6m_release_locked(inm);
+ IF_ADDR_LOCK(ifp);
+ /* FALLTHROUGH */
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_SG_QUERY_PENDING_MEMBER:
+ in6m_clear_recorded(inm);
+ /* FALLTHROUGH */
+ case MLD_REPORTING_MEMBER:
+ inm->in6m_sctimer = 0;
+ inm->in6m_timer = 0;
+ inm->in6m_state = MLD_REPORTING_MEMBER;
+ /*
+ * Free any pending MLDv2 state-change records.
+ */
+ _IF_DRAIN(&inm->in6m_scq);
+ break;
+ }
+ }
+ IF_ADDR_UNLOCK(ifp);
+}
+
+/*
+ * Global slowtimo handler.
+ * VIMAGE: Timeout handlers are expected to service all vimages.
+ */
+void
+mld_slowtimo(void)
+{
+ VNET_ITERATOR_DECL(vnet_iter);
+
+ VNET_LIST_RLOCK();
+ VNET_FOREACH(vnet_iter) {
+ CURVNET_SET(vnet_iter);
+ mld_slowtimo_vnet();
+ CURVNET_RESTORE();
+ }
+ VNET_LIST_RUNLOCK();
+}
+
+/*
+ * Per-vnet slowtimo handler.
+ */
+static void
+mld_slowtimo_vnet(void)
+{
+ INIT_VNET_INET6(curvnet);
+ struct mld_ifinfo *mli;
+
+ MLD_LOCK();
+
+ LIST_FOREACH(mli, &V_mli_head, mli_link) {
+ mld_v1_process_querier_timers(mli);
+ }
+
+ MLD_UNLOCK();
+}
+
+/*
+ * Update the Older Version Querier Present timers for a link.
+ * See Section 9.12 of RFC 3810.
+ */
+static void
+mld_v1_process_querier_timers(struct mld_ifinfo *mli)
+{
+ INIT_VNET_INET6(curvnet);
+
+ MLD_LOCK_ASSERT();
+
+ if (mli->mli_v1_timer == 0) {
+ /*
+ * MLDv1 Querier Present timers expired; revert to MLDv2.
+ */
+ if (mli->mli_version != MLD_VERSION_2) {
+ CTR5(KTR_MLD,
+ "%s: transition from v%d -> v%d on %p(%s)",
+ __func__, mli->mli_version, MLD_VERSION_2,
+ mli->mli_ifp, mli->mli_ifp->if_xname);
+ mli->mli_version = MLD_VERSION_2;
+ }
+ }
+}
+
+/*
+ * Transmit an MLDv1 report immediately.
+ */
+static int
+mld_v1_transmit_report(struct in6_multi *in6m, const int type)
+{
+ struct ifnet *ifp;
+ struct in6_ifaddr *ia;
+ struct ip6_hdr *ip6;
+ struct mbuf *mh, *md;
+ struct mld_hdr *mld;
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ ifp = in6m->in6m_ifp;
+ ia = in6ifa_ifpforlinklocal(ifp, IN6_IFF_NOTREADY|IN6_IFF_ANYCAST);
+ /* ia may be NULL if link-local address is tentative. */
+
MGETHDR(mh, M_DONTWAIT, MT_HEADER);
if (mh == NULL)
- return;
+ return (ENOMEM);
MGET(md, M_DONTWAIT, MT_DATA);
if (md == NULL) {
m_free(mh);
- return;
+ return (ENOMEM);
}
mh->m_next = md;
- mh->m_pkthdr.rcvif = NULL;
+ /*
+ * FUTURE: Consider increasing alignment by ETHER_HDR_LEN, so
+ * that ether_output() does not need to allocate another mbuf
+ * for the header in the most common case.
+ */
+ MH_ALIGN(mh, sizeof(struct ip6_hdr));
mh->m_pkthdr.len = sizeof(struct ip6_hdr) + sizeof(struct mld_hdr);
mh->m_len = sizeof(struct ip6_hdr);
- MH_ALIGN(mh, sizeof(struct ip6_hdr));
- /* fill in the ip6 header */
ip6 = mtod(mh, struct ip6_hdr *);
ip6->ip6_flow = 0;
ip6->ip6_vfc &= ~IPV6_VERSION_MASK;
ip6->ip6_vfc |= IPV6_VERSION;
- /* ip6_plen will be set later */
ip6->ip6_nxt = IPPROTO_ICMPV6;
- /* ip6_hlim will be set by im6o.im6o_multicast_hlim */
- ip6->ip6_src = ia->ia_addr.sin6_addr;
- ip6->ip6_dst = dst ? *dst : in6m->in6m_addr;
+ ip6->ip6_src = ia ? ia->ia_addr.sin6_addr : in6addr_any;
+ ip6->ip6_dst = in6m->in6m_addr;
- /* fill in the MLD header */
md->m_len = sizeof(struct mld_hdr);
- mldh = mtod(md, struct mld_hdr *);
- mldh->mld_type = type;
- mldh->mld_code = 0;
- mldh->mld_cksum = 0;
- /* XXX: we assume the function will not be called for query messages */
- mldh->mld_maxdelay = 0;
- mldh->mld_reserved = 0;
- mldh->mld_addr = in6m->in6m_addr;
- in6_clearscope(&mldh->mld_addr); /* XXX */
- mldh->mld_cksum = in6_cksum(mh, IPPROTO_ICMPV6, sizeof(struct ip6_hdr),
- sizeof(struct mld_hdr));
-
- /* construct multicast option */
- bzero(&im6o, sizeof(im6o));
- im6o.im6o_multicast_ifp = ifp;
- im6o.im6o_multicast_hlim = 1;
+ mld = mtod(md, struct mld_hdr *);
+ mld->mld_type = type;
+ mld->mld_code = 0;
+ mld->mld_cksum = 0;
+ mld->mld_maxdelay = 0;
+ mld->mld_reserved = 0;
+ mld->mld_addr = in6m->in6m_addr;
+ in6_clearscope(&mld->mld_addr);
+ mld->mld_cksum = in6_cksum(mh, IPPROTO_ICMPV6,
+ sizeof(struct ip6_hdr), sizeof(struct mld_hdr));
+
+ mld_save_context(mh, ifp);
+ mh->m_flags |= M_MLDV1;
+
+ mld_dispatch_packet(mh);
+
+ return (0);
+}
+
+/*
+ * Process a state change from the upper layer for the given IPv6 group.
+ *
+ * Each socket holds a reference on the in_multi in its own ip_moptions.
+ * The socket layer will have made the necessary updates to.the group
+ * state, it is now up to MLD to issue a state change report if there
+ * has been any change between T0 (when the last state-change was issued)
+ * and T1 (now).
+ *
+ * We use the MLDv2 state machine at group level. The MLd module
+ * however makes the decision as to which MLD protocol version to speak.
+ * A state change *from* INCLUDE {} always means an initial join.
+ * A state change *to* INCLUDE {} always means a final leave.
+ *
+ * If delay is non-zero, and the state change is an initial multicast
+ * join, the state change report will be delayed by 'delay' ticks
+ * in units of PR_FASTHZ if MLDv1 is active on the link; otherwise
+ * the initial MLDv2 state change report will be delayed by whichever
+ * is sooner, a pending state-change timer or delay itself.
+ *
+ * VIMAGE: curvnet should have been set by caller, as this routine
+ * is called from the socket option handlers.
+ */
+int
+mld_change_state(struct in6_multi *inm, const int delay)
+{
+ struct mld_ifinfo *mli;
+ struct ifnet *ifp;
+ int error;
+
+ IN6_MULTI_LOCK_ASSERT();
+
+ error = 0;
/*
- * Request loopback of the report if we are acting as a multicast
- * router, so that the process-level routing daemon can hear it.
+ * Try to detect if the upper layer just asked us to change state
+ * for an interface which has now gone away.
*/
- im6o.im6o_multicast_loop = (ip6_mrouter != NULL);
+ KASSERT(inm->in6m_ifma != NULL, ("%s: no ifma", __func__));
+ ifp = inm->in6m_ifma->ifma_ifp;
+ if (ifp != NULL) {
+ /*
+ * Sanity check that netinet6's notion of ifp is the
+ * same as net's.
+ */
+ KASSERT(inm->in6m_ifp == ifp, ("%s: bad ifp", __func__));
+ }
- /* increment output statictics */
- ICMP6STAT_INC(icp6s_outhist[type]);
+ MLD_LOCK();
- ip6_output(mh, &V_ip6_opts, NULL, 0, &im6o, &outif, NULL);
- if (outif) {
- icmp6_ifstat_inc(outif, ifs6_out_msg);
- switch (type) {
- case MLD_LISTENER_QUERY:
- icmp6_ifstat_inc(outif, ifs6_out_mldquery);
+ mli = MLD_IFINFO(ifp);
+ KASSERT(mli != NULL, ("%s: no mld_ifinfo for ifp %p", __func__, ifp));
+
+ /*
+ * If we detect a state transition to or from MCAST_UNDEFINED
+ * for this group, then we are starting or finishing an MLD
+ * life cycle for this group.
+ */
+ if (inm->in6m_st[1].iss_fmode != inm->in6m_st[0].iss_fmode) {
+ CTR3(KTR_MLD, "%s: inm transition %d -> %d", __func__,
+ inm->in6m_st[0].iss_fmode, inm->in6m_st[1].iss_fmode);
+ if (inm->in6m_st[0].iss_fmode == MCAST_UNDEFINED) {
+ CTR1(KTR_MLD, "%s: initial join", __func__);
+ error = mld_initial_join(inm, mli, delay);
+ goto out_locked;
+ } else if (inm->in6m_st[1].iss_fmode == MCAST_UNDEFINED) {
+ CTR1(KTR_MLD, "%s: final leave", __func__);
+ mld_final_leave(inm, mli);
+ goto out_locked;
+ }
+ } else {
+ CTR1(KTR_MLD, "%s: filter set change", __func__);
+ }
+
+ error = mld_handle_state_change(inm, mli);
+
+out_locked:
+ MLD_UNLOCK();
+ return (error);
+}
+
+/*
+ * Perform the initial join for an MLD group.
+ *
+ * When joining a group:
+ * If the group should have its MLD traffic suppressed, do nothing.
+ * MLDv1 starts sending MLDv1 host membership reports.
+ * MLDv2 will schedule an MLDv2 state-change report containing the
+ * initial state of the membership.
+ *
+ * If the delay argument is non-zero, then we must delay sending the
+ * initial state change for delay ticks (in units of PR_FASTHZ).
+ */
+static int
+mld_initial_join(struct in6_multi *inm, struct mld_ifinfo *mli,
+ const int delay)
+{
+ INIT_VNET_INET6(curvnet);
+ struct ifnet *ifp;
+ struct ifqueue *ifq;
+ int error, retval, syncstates;
+ int odelay;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+ CTR4(KTR_MLD, "%s: initial join %s on ifp %p(%s)",
+ __func__, ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp, inm->in6m_ifp->if_xname);
+
+ error = 0;
+ syncstates = 1;
+
+ ifp = inm->in6m_ifp;
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ KASSERT(mli && mli->mli_ifp == ifp, ("%s: inconsistent ifp", __func__));
+
+ /*
+ * Groups joined on loopback or marked as 'not reported',
+ * enter the MLD_SILENT_MEMBER state and
+ * are never reported in any protocol exchanges.
+ * All other groups enter the appropriate state machine
+ * for the version in use on this link.
+ * A link marked as MLIF_SILENT causes MLD to be completely
+ * disabled for the link.
+ */
+ if ((ifp->if_flags & IFF_LOOPBACK) ||
+ (mli->mli_flags & MLIF_SILENT) ||
+ !mld_is_addr_reported(&inm->in6m_addr)) {
+ CTR1(KTR_MLD,
+"%s: not kicking state machine for silent group", __func__);
+ inm->in6m_state = MLD_SILENT_MEMBER;
+ inm->in6m_timer = 0;
+ } else {
+ /*
+ * Deal with overlapping in_multi lifecycle.
+ * If this group was LEAVING, then make sure
+ * we drop the reference we picked up to keep the
+ * group around for the final INCLUDE {} enqueue.
+ */
+ if (mli->mli_version == MLD_VERSION_2 &&
+ inm->in6m_state == MLD_LEAVING_MEMBER)
+ in6m_release_locked(inm);
+
+ inm->in6m_state = MLD_REPORTING_MEMBER;
+
+ switch (mli->mli_version) {
+ case MLD_VERSION_1:
+ /*
+ * If a delay was provided, only use it if
+ * it is greater than the delay normally
+ * used for an MLDv1 state change report,
+ * and delay sending the initial MLDv1 report
+ * by not transitioning to the IDLE state.
+ */
+ odelay = MLD_RANDOM_DELAY(MLD_V1_MAX_RI * PR_FASTHZ);
+ if (delay) {
+ inm->in6m_timer = max(delay, odelay);
+ V_current_state_timers_running6 = 1;
+ } else {
+ inm->in6m_state = MLD_IDLE_MEMBER;
+ error = mld_v1_transmit_report(inm,
+ MLD_LISTENER_REPORT);
+ if (error == 0) {
+ inm->in6m_timer = odelay;
+ V_current_state_timers_running6 = 1;
+ }
+ }
break;
- case MLD_LISTENER_REPORT:
- icmp6_ifstat_inc(outif, ifs6_out_mldreport);
+
+ case MLD_VERSION_2:
+ /*
+ * Defer update of T0 to T1, until the first copy
+ * of the state change has been transmitted.
+ */
+ syncstates = 0;
+
+ /*
+ * Immediately enqueue a State-Change Report for
+ * this interface, freeing any previous reports.
+ * Don't kick the timers if there is nothing to do,
+ * or if an error occurred.
+ */
+ ifq = &inm->in6m_scq;
+ _IF_DRAIN(ifq);
+ retval = mld_v2_enqueue_group_record(ifq, inm, 1,
+ 0, 0);
+ CTR2(KTR_MLD, "%s: enqueue record = %d",
+ __func__, retval);
+ if (retval <= 0) {
+ error = retval * -1;
+ break;
+ }
+
+ /*
+ * Schedule transmission of pending state-change
+ * report up to RV times for this link. The timer
+ * will fire at the next mld_fasttimo (~200ms),
+ * giving us an opportunity to merge the reports.
+ *
+ * If a delay was provided to this function, only
+ * use this delay if sooner than the existing one.
+ */
+ KASSERT(mli->mli_rv > 1,
+ ("%s: invalid robustness %d", __func__,
+ mli->mli_rv));
+ inm->in6m_scrv = mli->mli_rv;
+ if (delay) {
+ if (inm->in6m_sctimer > 1) {
+ inm->in6m_sctimer =
+ min(inm->in6m_sctimer, delay);
+ } else
+ inm->in6m_sctimer = delay;
+ } else
+ inm->in6m_sctimer = 1;
+ V_state_change_timers_running6 = 1;
+
+ error = 0;
break;
- case MLD_LISTENER_DONE:
- icmp6_ifstat_inc(outif, ifs6_out_mlddone);
+ }
+ }
+
+ /*
+ * Only update the T0 state if state change is atomic,
+ * i.e. we don't need to wait for a timer to fire before we
+ * can consider the state change to have been communicated.
+ */
+ if (syncstates) {
+ in6m_commit(inm);
+ CTR3(KTR_MLD, "%s: T1 -> T0 for %s/%s", __func__,
+ ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname);
+ }
+
+ return (error);
+}
+
+/*
+ * Issue an intermediate state change during the life-cycle.
+ */
+static int
+mld_handle_state_change(struct in6_multi *inm, struct mld_ifinfo *mli)
+{
+ INIT_VNET_INET6(curvnet);
+ struct ifnet *ifp;
+ int retval;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+ CTR4(KTR_MLD, "%s: state change for %s on ifp %p(%s)",
+ __func__, ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp, inm->in6m_ifp->if_xname);
+
+ ifp = inm->in6m_ifp;
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ KASSERT(mli && mli->mli_ifp == ifp,
+ ("%s: inconsistent ifp", __func__));
+
+ if ((ifp->if_flags & IFF_LOOPBACK) ||
+ (mli->mli_flags & MLIF_SILENT) ||
+ !mld_is_addr_reported(&inm->in6m_addr) ||
+ (mli->mli_version != MLD_VERSION_2)) {
+ if (!mld_is_addr_reported(&inm->in6m_addr)) {
+ CTR1(KTR_MLD,
+"%s: not kicking state machine for silent group", __func__);
+ }
+ CTR1(KTR_MLD, "%s: nothing to do", __func__);
+ in6m_commit(inm);
+ CTR3(KTR_MLD, "%s: T1 -> T0 for %s/%s", __func__,
+ ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname);
+ return (0);
+ }
+
+ _IF_DRAIN(&inm->in6m_scq);
+
+ retval = mld_v2_enqueue_group_record(&inm->in6m_scq, inm, 1, 0, 0);
+ CTR2(KTR_MLD, "%s: enqueue record = %d", __func__, retval);
+ if (retval <= 0)
+ return (-retval);
+
+ /*
+ * If record(s) were enqueued, start the state-change
+ * report timer for this group.
+ */
+ inm->in6m_scrv = mli->mli_rv;
+ inm->in6m_sctimer = 1;
+ V_state_change_timers_running6 = 1;
+
+ return (0);
+}
+
+/*
+ * Perform the final leave for a multicast address.
+ *
+ * When leaving a group:
+ * MLDv1 sends a DONE message, if and only if we are the reporter.
+ * MLDv2 enqueues a state-change report containing a transition
+ * to INCLUDE {} for immediate transmission.
+ */
+static void
+mld_final_leave(struct in6_multi *inm, struct mld_ifinfo *mli)
+{
+ INIT_VNET_INET6(curvnet);
+ int syncstates;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+ syncstates = 1;
+
+ CTR4(KTR_MLD, "%s: final leave %s on ifp %p(%s)",
+ __func__, ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp, inm->in6m_ifp->if_xname);
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ case MLD_LEAVING_MEMBER:
+ /* Already leaving or left; do nothing. */
+ CTR1(KTR_MLD,
+"%s: not kicking state machine for silent group", __func__);
+ break;
+ case MLD_REPORTING_MEMBER:
+ case MLD_IDLE_MEMBER:
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_SG_QUERY_PENDING_MEMBER:
+ if (mli->mli_version == MLD_VERSION_1) {
+#ifdef INVARIANTS
+ if (inm->in6m_state == MLD_G_QUERY_PENDING_MEMBER ||
+ inm->in6m_state == MLD_SG_QUERY_PENDING_MEMBER)
+ panic("%s: MLDv2 state reached, not MLDv2 mode",
+ __func__);
+#endif
+ mld_v1_transmit_report(inm, MLD_LISTENER_DONE);
+ inm->in6m_state = MLD_NOT_MEMBER;
+ } else if (mli->mli_version == MLD_VERSION_2) {
+ /*
+ * Stop group timer and all pending reports.
+ * Immediately enqueue a state-change report
+ * TO_IN {} to be sent on the next fast timeout,
+ * giving us an opportunity to merge reports.
+ */
+ _IF_DRAIN(&inm->in6m_scq);
+ inm->in6m_timer = 0;
+ inm->in6m_scrv = mli->mli_rv;
+ CTR4(KTR_MLD, "%s: Leaving %s/%s with %d "
+ "pending retransmissions.", __func__,
+ ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname, inm->in6m_scrv);
+ if (inm->in6m_scrv == 0) {
+ inm->in6m_state = MLD_NOT_MEMBER;
+ inm->in6m_sctimer = 0;
+ } else {
+ int retval;
+
+ in6m_acquire_locked(inm);
+
+ retval = mld_v2_enqueue_group_record(
+ &inm->in6m_scq, inm, 1, 0, 0);
+ KASSERT(retval != 0,
+ ("%s: enqueue record = %d", __func__,
+ retval));
+
+ inm->in6m_state = MLD_LEAVING_MEMBER;
+ inm->in6m_sctimer = 1;
+ V_state_change_timers_running6 = 1;
+ syncstates = 0;
+ }
break;
}
+ break;
+ case MLD_LAZY_MEMBER:
+ case MLD_SLEEPING_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ /* Our reports are suppressed; do nothing. */
+ break;
+ }
+
+ if (syncstates) {
+ in6m_commit(inm);
+ CTR3(KTR_MLD, "%s: T1 -> T0 for %s/%s", __func__,
+ ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname);
+ inm->in6m_st[1].iss_fmode = MCAST_UNDEFINED;
+ CTR3(KTR_MLD, "%s: T1 now MCAST_UNDEFINED for %p/%s",
+ __func__, &inm->in6m_addr, inm->in6m_ifp->if_xname);
}
}
/*
- * Add an address to the list of IP6 multicast addresses for a given interface.
- * Add source addresses to the list also, if upstream router is MLDv2 capable
- * and the number of source is not 0.
+ * Enqueue an MLDv2 group record to the given output queue.
+ *
+ * If is_state_change is zero, a current-state record is appended.
+ * If is_state_change is non-zero, a state-change report is appended.
+ *
+ * If is_group_query is non-zero, an mbuf packet chain is allocated.
+ * If is_group_query is zero, and if there is a packet with free space
+ * at the tail of the queue, it will be appended to providing there
+ * is enough free space.
+ * Otherwise a new mbuf packet chain is allocated.
+ *
+ * If is_source_query is non-zero, each source is checked to see if
+ * it was recorded for a Group-Source query, and will be omitted if
+ * it is not both in-mode and recorded.
+ *
+ * The function will attempt to allocate leading space in the packet
+ * for the IPv6+ICMP headers to be prepended without fragmenting the chain.
+ *
+ * If successful the size of all data appended to the queue is returned,
+ * otherwise an error code less than zero is returned, or zero if
+ * no record(s) were appended.
*/
-struct in6_multi *
-in6_addmulti(struct in6_addr *maddr6, struct ifnet *ifp,
- int *errorp, int delay)
+static int
+mld_v2_enqueue_group_record(struct ifqueue *ifq, struct in6_multi *inm,
+ const int is_state_change, const int is_group_query,
+ const int is_source_query)
{
- struct in6_multi *in6m;
+ struct mldv2_record mr;
+ struct mldv2_record *pmr;
+ struct ifnet *ifp;
+ struct ip6_msource *ims, *nims;
+ struct mbuf *m0, *m, *md;
+ int error, is_filter_list_change;
+ int minrec0len, m0srcs, msrcs, nbytes, off;
+ int record_has_sources;
+ int now;
+ int type;
+ uint8_t mode;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
- *errorp = 0;
- in6m = NULL;
+ IN6_MULTI_LOCK_ASSERT();
+
+ error = 0;
+ ifp = inm->in6m_ifp;
+ is_filter_list_change = 0;
+ m = NULL;
+ m0 = NULL;
+ m0srcs = 0;
+ msrcs = 0;
+ nbytes = 0;
+ nims = NULL;
+ record_has_sources = 1;
+ pmr = NULL;
+ type = MLD_DO_NOTHING;
+ mode = inm->in6m_st[1].iss_fmode;
- /*IN6_MULTI_LOCK();*/
+ /*
+ * If we did not transition out of ASM mode during t0->t1,
+ * and there are no source nodes to process, we can skip
+ * the generation of source records.
+ */
+ if (inm->in6m_st[0].iss_asm > 0 && inm->in6m_st[1].iss_asm > 0 &&
+ inm->in6m_nsrc == 0)
+ record_has_sources = 0;
- IN6_LOOKUP_MULTI(*maddr6, ifp, in6m);
- if (in6m != NULL) {
+ if (is_state_change) {
/*
- * If we already joined this group, just bump the
- * refcount and return it.
+ * Queue a state change record.
+ * If the mode did not change, and there are non-ASM
+ * listeners or source filters present,
+ * we potentially need to issue two records for the group.
+ * If we are transitioning to MCAST_UNDEFINED, we need
+ * not send any sources.
+ * If there are ASM listeners, and there was no filter
+ * mode transition of any kind, do nothing.
*/
- KASSERT(in6m->in6m_refcount >= 1,
- ("%s: bad refcount %d", __func__, in6m->in6m_refcount));
- ++in6m->in6m_refcount;
- } else do {
- struct in6_multi *nin6m;
- struct ifmultiaddr *ifma;
- struct sockaddr_in6 sa6;
-
- bzero(&sa6, sizeof(sa6));
- sa6.sin6_family = AF_INET6;
- sa6.sin6_len = sizeof(struct sockaddr_in6);
- sa6.sin6_addr = *maddr6;
-
- *errorp = if_addmulti(ifp, (struct sockaddr *)&sa6, &ifma);
- if (*errorp)
- break;
+ if (mode != inm->in6m_st[0].iss_fmode) {
+ if (mode == MCAST_EXCLUDE) {
+ CTR1(KTR_MLD, "%s: change to EXCLUDE",
+ __func__);
+ type = MLD_CHANGE_TO_EXCLUDE_MODE;
+ } else {
+ CTR1(KTR_MLD, "%s: change to INCLUDE",
+ __func__);
+ type = MLD_CHANGE_TO_INCLUDE_MODE;
+ if (mode == MCAST_UNDEFINED)
+ record_has_sources = 0;
+ }
+ } else {
+ if (record_has_sources) {
+ is_filter_list_change = 1;
+ } else {
+ type = MLD_DO_NOTHING;
+ }
+ }
+ } else {
+ /*
+ * Queue a current state record.
+ */
+ if (mode == MCAST_EXCLUDE) {
+ type = MLD_MODE_IS_EXCLUDE;
+ } else if (mode == MCAST_INCLUDE) {
+ type = MLD_MODE_IS_INCLUDE;
+ KASSERT(inm->in6m_st[1].iss_asm == 0,
+ ("%s: inm %p is INCLUDE but ASM count is %d",
+ __func__, inm, inm->in6m_st[1].iss_asm));
+ }
+ }
+
+ /*
+ * Generate the filter list changes using a separate function.
+ */
+ if (is_filter_list_change)
+ return (mld_v2_enqueue_filter_change(ifq, inm));
+
+ if (type == MLD_DO_NOTHING) {
+ CTR3(KTR_MLD, "%s: nothing to do for %s/%s",
+ __func__, ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname);
+ return (0);
+ }
+
+ /*
+ * If any sources are present, we must be able to fit at least
+ * one in the trailing space of the tail packet's mbuf,
+ * ideally more.
+ */
+ minrec0len = sizeof(struct mldv2_record);
+ if (record_has_sources)
+ minrec0len += sizeof(struct in6_addr);
+
+ CTR4(KTR_MLD, "%s: queueing %s for %s/%s", __func__,
+ mld_rec_type_to_str(type),
+ ip6_sprintf(ip6tbuf, &inm->in6m_addr),
+ inm->in6m_ifp->if_xname);
+
+ /*
+ * Check if we have a packet in the tail of the queue for this
+ * group into which the first group record for this group will fit.
+ * Otherwise allocate a new packet.
+ * Always allocate leading space for IP6+RA+ICMPV6+REPORT.
+ * Note: Group records for G/GSR query responses MUST be sent
+ * in their own packet.
+ */
+ m0 = ifq->ifq_tail;
+ if (!is_group_query &&
+ m0 != NULL &&
+ (m0->m_pkthdr.PH_vt.vt_nrecs + 1 <= MLD_V2_REPORT_MAXRECS) &&
+ (m0->m_pkthdr.len + minrec0len) <
+ (ifp->if_mtu - MLD_MTUSPACE)) {
+ m0srcs = (ifp->if_mtu - m0->m_pkthdr.len -
+ sizeof(struct mldv2_record)) /
+ sizeof(struct in6_addr);
+ m = m0;
+ CTR1(KTR_MLD, "%s: use existing packet", __func__);
+ } else {
+ if (_IF_QFULL(ifq)) {
+ CTR1(KTR_MLD, "%s: outbound queue full", __func__);
+ return (-ENOMEM);
+ }
+ m = NULL;
+ m0srcs = (ifp->if_mtu - MLD_MTUSPACE -
+ sizeof(struct mldv2_record)) / sizeof(struct in6_addr);
+ if (!is_state_change && !is_group_query)
+ m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL)
+ m = m_gethdr(M_DONTWAIT, MT_DATA);
+ if (m == NULL)
+ return (-ENOMEM);
+
+ mld_save_context(m, ifp);
+
+ CTR1(KTR_MLD, "%s: allocated first packet", __func__);
+ }
+
+ /*
+ * Append group record.
+ * If we have sources, we don't know how many yet.
+ */
+ mr.mr_type = type;
+ mr.mr_datalen = 0;
+ mr.mr_numsrc = 0;
+ mr.mr_addr = inm->in6m_addr;
+ in6_clearscope(&mr.mr_addr);
+ if (!m_append(m, sizeof(struct mldv2_record), (void *)&mr)) {
+ if (m != m0)
+ m_freem(m);
+ CTR1(KTR_MLD, "%s: m_append() failed.", __func__);
+ return (-ENOMEM);
+ }
+ nbytes += sizeof(struct mldv2_record);
+
+ /*
+ * Append as many sources as will fit in the first packet.
+ * If we are appending to a new packet, the chain allocation
+ * may potentially use clusters; use m_getptr() in this case.
+ * If we are appending to an existing packet, we need to obtain
+ * a pointer to the group record after m_append(), in case a new
+ * mbuf was allocated.
+ * Only append sources which are in-mode at t1. If we are
+ * transitioning to MCAST_UNDEFINED state on the group, do not
+ * include source entries.
+ * Only report recorded sources in our filter set when responding
+ * to a group-source query.
+ */
+ if (record_has_sources) {
+ if (m == m0) {
+ md = m_last(m);
+ pmr = (struct mldv2_record *)(mtod(md, uint8_t *) +
+ md->m_len - nbytes);
+ } else {
+ md = m_getptr(m, 0, &off);
+ pmr = (struct mldv2_record *)(mtod(md, uint8_t *) +
+ off);
+ }
+ msrcs = 0;
+ RB_FOREACH_SAFE(ims, ip6_msource_tree, &inm->in6m_srcs,
+ nims) {
+ CTR2(KTR_MLD, "%s: visit node %s", __func__,
+ ip6_sprintf(ip6tbuf, &ims->im6s_addr));
+ now = im6s_get_mode(inm, ims, 1);
+ CTR2(KTR_MLD, "%s: node is %d", __func__, now);
+ if ((now != mode) ||
+ (now == mode && mode == MCAST_UNDEFINED)) {
+ CTR1(KTR_MLD, "%s: skip node", __func__);
+ continue;
+ }
+ if (is_source_query && ims->im6s_stp == 0) {
+ CTR1(KTR_MLD, "%s: skip unrecorded node",
+ __func__);
+ continue;
+ }
+ CTR1(KTR_MLD, "%s: append node", __func__);
+ if (!m_append(m, sizeof(struct in6_addr),
+ (void *)&ims->im6s_addr)) {
+ if (m != m0)
+ m_freem(m);
+ CTR1(KTR_MLD, "%s: m_append() failed.",
+ __func__);
+ return (-ENOMEM);
+ }
+ nbytes += sizeof(struct in6_addr);
+ ++msrcs;
+ if (msrcs == m0srcs)
+ break;
+ }
+ CTR2(KTR_MLD, "%s: msrcs is %d this packet", __func__,
+ msrcs);
+ pmr->mr_numsrc = htons(msrcs);
+ nbytes += (msrcs * sizeof(struct in6_addr));
+ }
+
+ if (is_source_query && msrcs == 0) {
+ CTR1(KTR_MLD, "%s: no recorded sources to report", __func__);
+ if (m != m0)
+ m_freem(m);
+ return (0);
+ }
+
+ /*
+ * We are good to go with first packet.
+ */
+ if (m != m0) {
+ CTR1(KTR_MLD, "%s: enqueueing first packet", __func__);
+ m->m_pkthdr.PH_vt.vt_nrecs = 1;
+ _IF_ENQUEUE(ifq, m);
+ } else
+ m->m_pkthdr.PH_vt.vt_nrecs++;
+
+ /*
+ * No further work needed if no source list in packet(s).
+ */
+ if (!record_has_sources)
+ return (nbytes);
+
+ /*
+ * Whilst sources remain to be announced, we need to allocate
+ * a new packet and fill out as many sources as will fit.
+ * Always try for a cluster first.
+ */
+ while (nims != NULL) {
+ if (_IF_QFULL(ifq)) {
+ CTR1(KTR_MLD, "%s: outbound queue full", __func__);
+ return (-ENOMEM);
+ }
+ m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL)
+ m = m_gethdr(M_DONTWAIT, MT_DATA);
+ if (m == NULL)
+ return (-ENOMEM);
+ mld_save_context(m, ifp);
+ md = m_getptr(m, 0, &off);
+ pmr = (struct mldv2_record *)(mtod(md, uint8_t *) + off);
+ CTR1(KTR_MLD, "%s: allocated next packet", __func__);
+
+ if (!m_append(m, sizeof(struct mldv2_record), (void *)&mr)) {
+ if (m != m0)
+ m_freem(m);
+ CTR1(KTR_MLD, "%s: m_append() failed.", __func__);
+ return (-ENOMEM);
+ }
+ m->m_pkthdr.PH_vt.vt_nrecs = 1;
+ nbytes += sizeof(struct mldv2_record);
+
+ m0srcs = (ifp->if_mtu - MLD_MTUSPACE -
+ sizeof(struct mldv2_record)) / sizeof(struct in6_addr);
+
+ msrcs = 0;
+ RB_FOREACH_FROM(ims, ip6_msource_tree, nims) {
+ CTR2(KTR_MLD, "%s: visit node %s",
+ __func__, ip6_sprintf(ip6tbuf, &ims->im6s_addr));
+ now = im6s_get_mode(inm, ims, 1);
+ if ((now != mode) ||
+ (now == mode && mode == MCAST_UNDEFINED)) {
+ CTR1(KTR_MLD, "%s: skip node", __func__);
+ continue;
+ }
+ if (is_source_query && ims->im6s_stp == 0) {
+ CTR1(KTR_MLD, "%s: skip unrecorded node",
+ __func__);
+ continue;
+ }
+ CTR1(KTR_MLD, "%s: append node", __func__);
+ if (!m_append(m, sizeof(struct in6_addr),
+ (void *)&ims->im6s_addr)) {
+ if (m != m0)
+ m_freem(m);
+ CTR1(KTR_MLD, "%s: m_append() failed.",
+ __func__);
+ return (-ENOMEM);
+ }
+ ++msrcs;
+ if (msrcs == m0srcs)
+ break;
+ }
+ pmr->mr_numsrc = htons(msrcs);
+ nbytes += (msrcs * sizeof(struct in6_addr));
+
+ CTR1(KTR_MLD, "%s: enqueueing next packet", __func__);
+ _IF_ENQUEUE(ifq, m);
+ }
+
+ return (nbytes);
+}
+
+/*
+ * Type used to mark record pass completion.
+ * We exploit the fact we can cast to this easily from the
+ * current filter modes on each ip_msource node.
+ */
+typedef enum {
+ REC_NONE = 0x00, /* MCAST_UNDEFINED */
+ REC_ALLOW = 0x01, /* MCAST_INCLUDE */
+ REC_BLOCK = 0x02, /* MCAST_EXCLUDE */
+ REC_FULL = REC_ALLOW | REC_BLOCK
+} rectype_t;
+
+/*
+ * Enqueue an MLDv2 filter list change to the given output queue.
+ *
+ * Source list filter state is held in an RB-tree. When the filter list
+ * for a group is changed without changing its mode, we need to compute
+ * the deltas between T0 and T1 for each source in the filter set,
+ * and enqueue the appropriate ALLOW_NEW/BLOCK_OLD records.
+ *
+ * As we may potentially queue two record types, and the entire R-B tree
+ * needs to be walked at once, we break this out into its own function
+ * so we can generate a tightly packed queue of packets.
+ *
+ * XXX This could be written to only use one tree walk, although that makes
+ * serializing into the mbuf chains a bit harder. For now we do two walks
+ * which makes things easier on us, and it may or may not be harder on
+ * the L2 cache.
+ *
+ * If successful the size of all data appended to the queue is returned,
+ * otherwise an error code less than zero is returned, or zero if
+ * no record(s) were appended.
+ */
+static int
+mld_v2_enqueue_filter_change(struct ifqueue *ifq, struct in6_multi *inm)
+{
+ static const int MINRECLEN =
+ sizeof(struct mldv2_record) + sizeof(struct in6_addr);
+ struct ifnet *ifp;
+ struct mldv2_record mr;
+ struct mldv2_record *pmr;
+ struct ip6_msource *ims, *nims;
+ struct mbuf *m, *m0, *md;
+ int m0srcs, nbytes, npbytes, off, rsrcs, schanged;
+ int nallow, nblock;
+ uint8_t mode, now, then;
+ rectype_t crt, drt, nrt;
+#ifdef KTR
+ char ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+ IN6_MULTI_LOCK_ASSERT();
+
+ if (inm->in6m_nsrc == 0 ||
+ (inm->in6m_st[0].iss_asm > 0 && inm->in6m_st[1].iss_asm > 0))
+ return (0);
+
+ ifp = inm->in6m_ifp; /* interface */
+ mode = inm->in6m_st[1].iss_fmode; /* filter mode at t1 */
+ crt = REC_NONE; /* current group record type */
+ drt = REC_NONE; /* mask of completed group record types */
+ nrt = REC_NONE; /* record type for current node */
+ m0srcs = 0; /* # source which will fit in current mbuf chain */
+ npbytes = 0; /* # of bytes appended this packet */
+ nbytes = 0; /* # of bytes appended to group's state-change queue */
+ rsrcs = 0; /* # sources encoded in current record */
+ schanged = 0; /* # nodes encoded in overall filter change */
+ nallow = 0; /* # of source entries in ALLOW_NEW */
+ nblock = 0; /* # of source entries in BLOCK_OLD */
+ nims = NULL; /* next tree node pointer */
+
+ /*
+ * For each possible filter record mode.
+ * The first kind of source we encounter tells us which
+ * is the first kind of record we start appending.
+ * If a node transitioned to UNDEFINED at t1, its mode is treated
+ * as the inverse of the group's filter mode.
+ */
+ while (drt != REC_FULL) {
+ do {
+ m0 = ifq->ifq_tail;
+ if (m0 != NULL &&
+ (m0->m_pkthdr.PH_vt.vt_nrecs + 1 <=
+ MLD_V2_REPORT_MAXRECS) &&
+ (m0->m_pkthdr.len + MINRECLEN) <
+ (ifp->if_mtu - MLD_MTUSPACE)) {
+ m = m0;
+ m0srcs = (ifp->if_mtu - m0->m_pkthdr.len -
+ sizeof(struct mldv2_record)) /
+ sizeof(struct in6_addr);
+ CTR1(KTR_MLD,
+ "%s: use previous packet", __func__);
+ } else {
+ m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL)
+ m = m_gethdr(M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ CTR1(KTR_MLD,
+ "%s: m_get*() failed", __func__);
+ return (-ENOMEM);
+ }
+ m->m_pkthdr.PH_vt.vt_nrecs = 0;
+ mld_save_context(m, ifp);
+ m0srcs = (ifp->if_mtu - MLD_MTUSPACE -
+ sizeof(struct mldv2_record)) /
+ sizeof(struct in6_addr);
+ npbytes = 0;
+ CTR1(KTR_MLD,
+ "%s: allocated new packet", __func__);
+ }
+ /*
+ * Append the MLD group record header to the
+ * current packet's data area.
+ * Recalculate pointer to free space for next
+ * group record, in case m_append() allocated
+ * a new mbuf or cluster.
+ */
+ memset(&mr, 0, sizeof(mr));
+ mr.mr_addr = inm->in6m_addr;
+ in6_clearscope(&mr.mr_addr);
+ if (!m_append(m, sizeof(mr), (void *)&mr)) {
+ if (m != m0)
+ m_freem(m);
+ CTR1(KTR_MLD,
+ "%s: m_append() failed", __func__);
+ return (-ENOMEM);
+ }
+ npbytes += sizeof(struct mldv2_record);
+ if (m != m0) {
+ /* new packet; offset in chain */
+ md = m_getptr(m, npbytes -
+ sizeof(struct mldv2_record), &off);
+ pmr = (struct mldv2_record *)(mtod(md,
+ uint8_t *) + off);
+ } else {
+ /* current packet; offset from last append */
+ md = m_last(m);
+ pmr = (struct mldv2_record *)(mtod(md,
+ uint8_t *) + md->m_len -
+ sizeof(struct mldv2_record));
+ }
+ /*
+ * Begin walking the tree for this record type
+ * pass, or continue from where we left off
+ * previously if we had to allocate a new packet.
+ * Only report deltas in-mode at t1.
+ * We need not report included sources as allowed
+ * if we are in inclusive mode on the group,
+ * however the converse is not true.
+ */
+ rsrcs = 0;
+ if (nims == NULL) {
+ nims = RB_MIN(ip6_msource_tree,
+ &inm->in6m_srcs);
+ }
+ RB_FOREACH_FROM(ims, ip6_msource_tree, nims) {
+ CTR2(KTR_MLD, "%s: visit node %s", __func__,
+ ip6_sprintf(ip6tbuf, &ims->im6s_addr));
+ now = im6s_get_mode(inm, ims, 1);
+ then = im6s_get_mode(inm, ims, 0);
+ CTR3(KTR_MLD, "%s: mode: t0 %d, t1 %d",
+ __func__, then, now);
+ if (now == then) {
+ CTR1(KTR_MLD,
+ "%s: skip unchanged", __func__);
+ continue;
+ }
+ if (mode == MCAST_EXCLUDE &&
+ now == MCAST_INCLUDE) {
+ CTR1(KTR_MLD,
+ "%s: skip IN src on EX group",
+ __func__);
+ continue;
+ }
+ nrt = (rectype_t)now;
+ if (nrt == REC_NONE)
+ nrt = (rectype_t)(~mode & REC_FULL);
+ if (schanged++ == 0) {
+ crt = nrt;
+ } else if (crt != nrt)
+ continue;
+ if (!m_append(m, sizeof(struct in6_addr),
+ (void *)&ims->im6s_addr)) {
+ if (m != m0)
+ m_freem(m);
+ CTR1(KTR_MLD,
+ "%s: m_append() failed", __func__);
+ return (-ENOMEM);
+ }
+ nallow += !!(crt == REC_ALLOW);
+ nblock += !!(crt == REC_BLOCK);
+ if (++rsrcs == m0srcs)
+ break;
+ }
+ /*
+ * If we did not append any tree nodes on this
+ * pass, back out of allocations.
+ */
+ if (rsrcs == 0) {
+ npbytes -= sizeof(struct mldv2_record);
+ if (m != m0) {
+ CTR1(KTR_MLD,
+ "%s: m_free(m)", __func__);
+ m_freem(m);
+ } else {
+ CTR1(KTR_MLD,
+ "%s: m_adj(m, -mr)", __func__);
+ m_adj(m, -((int)sizeof(
+ struct mldv2_record)));
+ }
+ continue;
+ }
+ npbytes += (rsrcs * sizeof(struct in6_addr));
+ if (crt == REC_ALLOW)
+ pmr->mr_type = MLD_ALLOW_NEW_SOURCES;
+ else if (crt == REC_BLOCK)
+ pmr->mr_type = MLD_BLOCK_OLD_SOURCES;
+ pmr->mr_numsrc = htons(rsrcs);
+ /*
+ * Count the new group record, and enqueue this
+ * packet if it wasn't already queued.
+ */
+ m->m_pkthdr.PH_vt.vt_nrecs++;
+ if (m != m0)
+ _IF_ENQUEUE(ifq, m);
+ nbytes += npbytes;
+ } while (nims != NULL);
+ drt |= crt;
+ crt = (~crt & REC_FULL);
+ }
+
+ CTR3(KTR_MLD, "%s: queued %d ALLOW_NEW, %d BLOCK_OLD", __func__,
+ nallow, nblock);
+
+ return (nbytes);
+}
+
+static int
+mld_v2_merge_state_changes(struct in6_multi *inm, struct ifqueue *ifscq)
+{
+ struct ifqueue *gq;
+ struct mbuf *m; /* pending state-change */
+ struct mbuf *m0; /* copy of pending state-change */
+ struct mbuf *mt; /* last state-change in packet */
+ int docopy, domerge;
+ u_int recslen;
+
+ docopy = 0;
+ domerge = 0;
+ recslen = 0;
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ /*
+ * If there are further pending retransmissions, make a writable
+ * copy of each queued state-change message before merging.
+ */
+ if (inm->in6m_scrv > 0)
+ docopy = 1;
+
+ gq = &inm->in6m_scq;
+#ifdef KTR
+ if (gq->ifq_head == NULL) {
+ CTR2(KTR_MLD, "%s: WARNING: queue for inm %p is empty",
+ __func__, inm);
+ }
+#endif
+
+ m = gq->ifq_head;
+ while (m != NULL) {
/*
- * If ifma->ifma_protospec is null, then if_addmulti() created
- * a new record. Otherwise, bump refcount, and we are done.
+ * Only merge the report into the current packet if
+ * there is sufficient space to do so; an MLDv2 report
+ * packet may only contain 65,535 group records.
+ * Always use a simple mbuf chain concatentation to do this,
+ * as large state changes for single groups may have
+ * allocated clusters.
*/
- if (ifma->ifma_protospec != NULL) {
- in6m = ifma->ifma_protospec;
- ++in6m->in6m_refcount;
- break;
+ domerge = 0;
+ mt = ifscq->ifq_tail;
+ if (mt != NULL) {
+ recslen = m_length(m, NULL);
+
+ if ((mt->m_pkthdr.PH_vt.vt_nrecs +
+ m->m_pkthdr.PH_vt.vt_nrecs <=
+ MLD_V2_REPORT_MAXRECS) &&
+ (mt->m_pkthdr.len + recslen <=
+ (inm->in6m_ifp->if_mtu - MLD_MTUSPACE)))
+ domerge = 1;
}
- nin6m = malloc(sizeof(*nin6m), M_IP6MADDR, M_NOWAIT | M_ZERO);
- if (nin6m == NULL) {
- if_delmulti_ifma(ifma);
- break;
+ if (!domerge && _IF_QFULL(gq)) {
+ CTR2(KTR_MLD,
+ "%s: outbound queue full, skipping whole packet %p",
+ __func__, m);
+ mt = m->m_nextpkt;
+ if (!docopy)
+ m_freem(m);
+ m = mt;
+ continue;
}
- nin6m->in6m_addr = *maddr6;
- nin6m->in6m_ifp = ifp;
- nin6m->in6m_refcount = 1;
- nin6m->in6m_ifma = ifma;
- ifma->ifma_protospec = nin6m;
+ if (!docopy) {
+ CTR2(KTR_MLD, "%s: dequeueing %p", __func__, m);
+ _IF_DEQUEUE(gq, m0);
+ m = m0->m_nextpkt;
+ } else {
+ CTR2(KTR_MLD, "%s: copying %p", __func__, m);
+ m0 = m_dup(m, M_NOWAIT);
+ if (m0 == NULL)
+ return (ENOMEM);
+ m0->m_nextpkt = NULL;
+ m = m->m_nextpkt;
+ }
- nin6m->in6m_timer_ch = malloc(sizeof(*nin6m->in6m_timer_ch),
- M_IP6MADDR, M_NOWAIT);
- if (nin6m->in6m_timer_ch == NULL) {
- free(nin6m, M_IP6MADDR);
- if_delmulti_ifma(ifma);
- break;
+ if (!domerge) {
+ CTR3(KTR_MLD, "%s: queueing %p to ifscq %p)",
+ __func__, m0, ifscq);
+ _IF_ENQUEUE(ifscq, m0);
+ } else {
+ struct mbuf *mtl; /* last mbuf of packet mt */
+
+ CTR3(KTR_MLD, "%s: merging %p with ifscq tail %p)",
+ __func__, m0, mt);
+
+ mtl = m_last(mt);
+ m0->m_flags &= ~M_PKTHDR;
+ mt->m_pkthdr.len += recslen;
+ mt->m_pkthdr.PH_vt.vt_nrecs +=
+ m0->m_pkthdr.PH_vt.vt_nrecs;
+
+ mtl->m_next = m0;
}
+ }
+
+ return (0);
+}
+
+/*
+ * Respond to a pending MLDv2 General Query.
+ */
+static void
+mld_v2_dispatch_general_query(struct mld_ifinfo *mli)
+{
+ INIT_VNET_INET6(curvnet);
+ struct ifmultiaddr *ifma, *tifma;
+ struct ifnet *ifp;
+ struct in6_multi *inm;
+ int retval;
+
+ IN6_MULTI_LOCK_ASSERT();
+ MLD_LOCK_ASSERT();
+
+ KASSERT(mli->mli_version == MLD_VERSION_2,
+ ("%s: called when version %d", __func__, mli->mli_version));
+
+ ifp = mli->mli_ifp;
+
+ IF_ADDR_LOCK(ifp);
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, tifma) {
+ if (ifma->ifma_addr->sa_family != AF_INET6 ||
+ ifma->ifma_protospec == NULL)
+ continue;
- LIST_INSERT_HEAD(&in6_multihead, nin6m, in6m_entry);
+ inm = (struct in6_multi *)ifma->ifma_protospec;
+ KASSERT(ifp == inm->in6m_ifp,
+ ("%s: inconsistent ifp", __func__));
- callout_init(nin6m->in6m_timer_ch, 0);
- nin6m->in6m_timer = delay;
- if (nin6m->in6m_timer > 0) {
- nin6m->in6m_state = MLD_REPORTPENDING;
- mld_starttimer(nin6m);
+ switch (inm->in6m_state) {
+ case MLD_NOT_MEMBER:
+ case MLD_SILENT_MEMBER:
+ break;
+ case MLD_REPORTING_MEMBER:
+ case MLD_IDLE_MEMBER:
+ case MLD_LAZY_MEMBER:
+ case MLD_SLEEPING_MEMBER:
+ case MLD_AWAKENING_MEMBER:
+ inm->in6m_state = MLD_REPORTING_MEMBER;
+ retval = mld_v2_enqueue_group_record(&mli->mli_gq,
+ inm, 0, 0, 0);
+ CTR2(KTR_MLD, "%s: enqueue record = %d",
+ __func__, retval);
+ break;
+ case MLD_G_QUERY_PENDING_MEMBER:
+ case MLD_SG_QUERY_PENDING_MEMBER:
+ case MLD_LEAVING_MEMBER:
+ break;
}
+ }
+ IF_ADDR_UNLOCK(ifp);
+
+ mld_dispatch_queue(&mli->mli_gq, MLD_MAX_RESPONSE_BURST);
+
+ /*
+ * Slew transmission of bursts over 500ms intervals.
+ */
+ if (mli->mli_gq.ifq_head != NULL) {
+ mli->mli_v2_timer = 1 + MLD_RANDOM_DELAY(
+ MLD_RESPONSE_BURST_INTERVAL);
+ V_interface_timers_running6 = 1;
+ }
+}
+
+/*
+ * Transmit the next pending message in the output queue.
+ *
+ * VIMAGE: Needs to store/restore vnet pointer on a per-mbuf-chain basis.
+ * MRT: Nothing needs to be done, as MLD traffic is always local to
+ * a link and uses a link-scope multicast address.
+ */
+static void
+mld_dispatch_packet(struct mbuf *m)
+{
+ struct ip6_moptions im6o;
+ struct ifnet *ifp;
+ struct ifnet *oifp;
+ struct mbuf *m0;
+ struct mbuf *md;
+ struct ip6_hdr *ip6;
+ struct mld_hdr *mld;
+ int error;
+ int off;
+ int type;
+ uint32_t ifindex;
+
+ CTR2(KTR_MLD, "%s: transmit %p", __func__, m);
+
+ /*
+ * Set VNET image pointer from enqueued mbuf chain
+ * before doing anything else. Whilst we use interface
+ * indexes to guard against interface detach, they are
+ * unique to each VIMAGE and must be retrieved.
+ */
+ CURVNET_SET(m->m_pkthdr.header);
+ INIT_VNET_NET(curvnet);
+ INIT_VNET_INET6(curvnet);
+ ifindex = mld_restore_context(m);
+
+ /*
+ * Check if the ifnet still exists. This limits the scope of
+ * any race in the absence of a global ifp lock for low cost
+ * (an array lookup).
+ */
+ ifp = ifnet_byindex(ifindex);
+ if (ifp == NULL) {
+ CTR3(KTR_MLD, "%s: dropped %p as ifindex %u went away.",
+ __func__, m, ifindex);
+ m_freem(m);
+ IP6STAT_INC(ip6s_noroute);
+ goto out;
+ }
- mld6_start_listening(nin6m);
+ im6o.im6o_multicast_hlim = 1;
+ im6o.im6o_multicast_loop = (V_ip6_mrouter != NULL);
+ im6o.im6o_multicast_ifp = ifp;
- in6m = nin6m;
+ if (m->m_flags & M_MLDV1) {
+ m0 = m;
+ } else {
+ m0 = mld_v2_encap_report(ifp, m);
+ if (m0 == NULL) {
+ CTR2(KTR_MLD, "%s: dropped %p", __func__, m);
+ m_freem(m);
+ IP6STAT_INC(ip6s_odropped);
+ goto out;
+ }
+ }
- } while (0);
+ mld_scrub_context(m0);
+ m->m_flags &= ~(M_PROTOFLAGS);
+ m0->m_pkthdr.rcvif = V_loif;
- /*IN6_MULTI_UNLOCK();*/
+ ip6 = mtod(m0, struct ip6_hdr *);
+#if 0
+ (void)in6_setscope(&ip6->ip6_dst, ifp, NULL); /* XXX LOR */
+#else
+ /*
+ * XXX XXX Break some KPI rules to prevent an LOR which would
+ * occur if we called in6_setscope() at transmission.
+ * See comments at top of file.
+ */
+ MLD_EMBEDSCOPE(&ip6->ip6_dst, ifp->if_index);
+#endif
- return (in6m);
+ /*
+ * Retrieve the ICMPv6 type before handoff to ip6_output(),
+ * so we can bump the stats.
+ */
+ md = m_getptr(m0, sizeof(struct ip6_hdr), &off);
+ mld = (struct mld_hdr *)(mtod(md, uint8_t *) + off);
+ type = mld->mld_type;
+
+ error = ip6_output(m0, &mld_po, NULL, IPV6_UNSPECSRC, &im6o,
+ &oifp, NULL);
+ if (error) {
+ CTR3(KTR_MLD, "%s: ip6_output(%p) = %d", __func__, m0, error);
+ goto out;
+ }
+ ICMP6STAT_INC(icp6s_outhist[type]);
+ if (oifp != NULL) {
+ icmp6_ifstat_inc(oifp, ifs6_out_msg);
+ switch (type) {
+ case MLD_LISTENER_REPORT:
+ case MLDV2_LISTENER_REPORT:
+ icmp6_ifstat_inc(oifp, ifs6_out_mldreport);
+ break;
+ case MLD_LISTENER_DONE:
+ icmp6_ifstat_inc(oifp, ifs6_out_mlddone);
+ break;
+ }
+ }
+out:
+ /*
+ * We must restore the existing vnet pointer before continuing.
+ */
+ CURVNET_RESTORE();
}
/*
- * Delete a multicast address record.
+ * Encapsulate an MLDv2 report.
+ *
+ * KAME IPv6 requires that hop-by-hop options be passed separately,
+ * and that the IPv6 header be prepended in a separate mbuf.
*
- * TODO: Locking, as per netinet.
+ * Returns a pointer to the new mbuf chain head, or NULL if the
+ * allocation failed.
*/
-void
-in6_delmulti(struct in6_multi *in6m)
+static struct mbuf *
+mld_v2_encap_report(struct ifnet *ifp, struct mbuf *m)
{
- struct ifmultiaddr *ifma;
+ INIT_VNET_INET6(curvnet);
+ struct mbuf *mh;
+ struct mldv2_report *mld;
+ struct ip6_hdr *ip6;
+ struct in6_ifaddr *ia;
+ int mldreclen;
+
+ KASSERT(ifp != NULL, ("%s: null ifp", __func__));
+ KASSERT((m->m_flags & M_PKTHDR),
+ ("%s: mbuf chain %p is !M_PKTHDR", __func__, m));
+
+ /*
+ * RFC3590: OK to send as :: or tentative during DAD.
+ */
+ ia = in6ifa_ifpforlinklocal(ifp, IN6_IFF_NOTREADY|IN6_IFF_ANYCAST);
+ if (ia == NULL)
+ CTR1(KTR_MLD, "%s: warning: ia is NULL", __func__);
- KASSERT(in6m->in6m_refcount >= 1, ("%s: freeing freed in6m", __func__));
+ MGETHDR(mh, M_DONTWAIT, MT_HEADER);
+ if (mh == NULL) {
+ m_freem(m);
+ return (NULL);
+ }
+ MH_ALIGN(mh, sizeof(struct ip6_hdr) + sizeof(struct mldv2_report));
- if (--in6m->in6m_refcount == 0) {
- mld_stoptimer(in6m);
- mld6_stop_listening(in6m);
+ mldreclen = m_length(m, NULL);
+ CTR2(KTR_MLD, "%s: mldreclen is %d", __func__, mldreclen);
- ifma = in6m->in6m_ifma;
- KASSERT(ifma->ifma_protospec == in6m,
- ("%s: ifma_protospec != in6m", __func__));
- ifma->ifma_protospec = NULL;
+ mh->m_len = sizeof(struct ip6_hdr) + sizeof(struct mldv2_report);
+ mh->m_pkthdr.len = sizeof(struct ip6_hdr) +
+ sizeof(struct mldv2_report) + mldreclen;
- LIST_REMOVE(in6m, in6m_entry);
- free(in6m->in6m_timer_ch, M_IP6MADDR);
- free(in6m, M_IP6MADDR);
+ 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 = ia ? ia->ia_addr.sin6_addr : in6addr_any;
+ ip6->ip6_dst = in6addr_linklocal_allv2routers;
+ /* scope ID will be set in netisr */
+
+ mld = (struct mldv2_report *)(ip6 + 1);
+ mld->mld_type = MLDV2_LISTENER_REPORT;
+ mld->mld_code = 0;
+ mld->mld_cksum = 0;
+ mld->mld_v2_reserved = 0;
+ mld->mld_v2_numrecs = htons(m->m_pkthdr.PH_vt.vt_nrecs);
+ m->m_pkthdr.PH_vt.vt_nrecs = 0;
+
+ mh->m_next = m;
+ mld->mld_cksum = in6_cksum(mh, IPPROTO_ICMPV6,
+ sizeof(struct ip6_hdr), sizeof(struct mldv2_report) + mldreclen);
+
+ return (mh);
+}
+
+#ifdef KTR
+static char *
+mld_rec_type_to_str(const int type)
+{
- if_delmulti_ifma(ifma);
+ switch (type) {
+ case MLD_CHANGE_TO_EXCLUDE_MODE:
+ return "TO_EX";
+ break;
+ case MLD_CHANGE_TO_INCLUDE_MODE:
+ return "TO_IN";
+ break;
+ case MLD_MODE_IS_EXCLUDE:
+ return "MODE_EX";
+ break;
+ case MLD_MODE_IS_INCLUDE:
+ return "MODE_IN";
+ break;
+ case MLD_ALLOW_NEW_SOURCES:
+ return "ALLOW_NEW";
+ break;
+ case MLD_BLOCK_OLD_SOURCES:
+ return "BLOCK_OLD";
+ break;
+ default:
+ break;
}
+ return "unknown";
+}
+#endif
+
+static void
+mld_sysinit(void)
+{
+
+ CTR1(KTR_MLD, "%s: initializing", __func__);
+ MLD_LOCK_INIT();
+
+ ip6_initpktopts(&mld_po);
+ mld_po.ip6po_hlim = 1;
+ mld_po.ip6po_hbh = &mld_ra.hbh;
+ mld_po.ip6po_prefer_tempaddr = IP6PO_TEMPADDR_NOTPREFER;
+ mld_po.ip6po_flags = IP6PO_DONTFRAG;
}
+
+static void
+mld_sysuninit(void)
+{
+
+ CTR1(KTR_MLD, "%s: tearing down", __func__);
+ MLD_LOCK_DESTROY();
+}
+
+/*
+ * Initialize an MLDv2 instance.
+ * VIMAGE: Assumes curvnet set by caller and called per vimage.
+ */
+static int
+vnet_mld_iattach(const void *unused __unused)
+{
+ INIT_VNET_INET6(curvnet);
+
+ CTR1(KTR_MLD, "%s: initializing", __func__);
+
+ LIST_INIT(&V_mli_head);
+
+ V_current_state_timers_running6 = 0;
+ V_interface_timers_running6 = 0;
+ V_state_change_timers_running6 = 0;
+
+ /*
+ * Initialize sysctls to default values.
+ */
+ V_mld_gsrdelay.tv_sec = 10;
+ V_mld_gsrdelay.tv_usec = 0;
+
+ return (0);
+}
+
+static int
+vnet_mld_idetach(const void *unused __unused)
+{
+ INIT_VNET_INET6(curvnet);
+
+ CTR1(KTR_MLD, "%s: tearing down", __func__);
+
+ KASSERT(LIST_EMPTY(&V_mli_head),
+ ("%s: mli list not empty; ifnets not detached?", __func__));
+
+ return (0);
+}
+
+#ifndef VIMAGE_GLOBALS
+static vnet_modinfo_t vnet_mld_modinfo = {
+ .vmi_id = VNET_MOD_MLD,
+ .vmi_name = "mld",
+ .vmi_dependson = VNET_MOD_INET6,
+ .vmi_iattach = vnet_mld_iattach,
+ .vmi_idetach = vnet_mld_idetach
+};
+#endif
+
+static int
+mld_modevent(module_t mod, int type, void *unused __unused)
+{
+
+ switch (type) {
+ case MOD_LOAD:
+ mld_sysinit();
+#ifndef VIMAGE_GLOBALS
+ vnet_mod_register(&vnet_mld_modinfo);
+#else
+ vnet_mld_iattach(NULL);
+#endif
+ break;
+ case MOD_UNLOAD:
+#ifndef VIMAGE_GLOBALS
+#ifdef NOTYET
+ vnet_mod_deregister(&vnet_mld_modinfo);
+#endif
+#else
+ vnet_mld_idetach(NULL);
+#endif
+ mld_sysuninit();
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+
+static moduledata_t mld_mod = {
+ "mld",
+ mld_modevent,
+ 0
+};
+DECLARE_MODULE(mld, mld_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
diff --git a/sys/netinet6/mld6_var.h b/sys/netinet6/mld6_var.h
index 4f51b99..efd01ab 100644
--- a/sys/netinet6/mld6_var.h
+++ b/sys/netinet6/mld6_var.h
@@ -1,6 +1,5 @@
/*-
- * Copyright (C) 1998 WIDE Project.
- * All rights reserved.
+ * Copyright (c) 2009 Bruce Simpson.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -10,14 +9,14 @@
* 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.
+ * 3. The name of the author may not 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
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 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)
@@ -26,29 +25,139 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $KAME: mld6_var.h,v 1.4 2000/03/25 07:23:54 sumikawa Exp $
* $FreeBSD$
*/
-
#ifndef _NETINET6_MLD6_VAR_H_
#define _NETINET6_MLD6_VAR_H_
+/*
+ * Multicast Listener Discovery (MLD)
+ * implementation-specific definitions.
+ */
+
#ifdef _KERNEL
-#define MLD_RANDOM_DELAY(X) (arc4random() % (X) + 1)
+/*
+ * Per-link MLD state.
+ */
+struct mld_ifinfo {
+ LIST_ENTRY(mld_ifinfo) mli_link;
+ struct ifnet *mli_ifp; /* interface this instance belongs to */
+ uint32_t mli_version; /* MLDv1 Host Compatibility Mode */
+ uint32_t mli_v1_timer; /* MLDv1 Querier Present timer (s) */
+ uint32_t mli_v2_timer; /* MLDv2 General Query (interface) timer (s)*/
+ uint32_t mli_flags; /* MLD per-interface flags */
+ uint32_t mli_rv; /* MLDv2 Robustness Variable */
+ uint32_t mli_qi; /* MLDv2 Query Interval (s) */
+ uint32_t mli_qri; /* MLDv2 Query Response Interval (s) */
+ uint32_t mli_uri; /* MLDv2 Unsolicited Report Interval (s) */
+ SLIST_HEAD(,in6_multi) mli_relinmhead; /* released groups */
+ struct ifqueue mli_gq; /* queue of general query responses */
+};
+#define MLIF_SILENT 0x00000001 /* Do not use MLD on this ifp */
+
+#define MLD_RANDOM_DELAY(X) (arc4random() % (X) + 1)
+#define MLD_MAX_STATE_CHANGES 24 /* Max pending changes per group */
/*
- * States for MLD stop-listening processing
+ * MLD per-group states.
*/
-#define MLD_OTHERLISTENER 0
-#define MLD_IREPORTEDLAST 1
-#define MLD_REPORTPENDING 2 /* implementation specific */
+#define MLD_NOT_MEMBER 0 /* Can garbage collect group */
+#define MLD_SILENT_MEMBER 1 /* Do not perform MLD for group */
+#define MLD_REPORTING_MEMBER 2 /* MLDv1 we are reporter */
+#define MLD_IDLE_MEMBER 3 /* MLDv1 we reported last */
+#define MLD_LAZY_MEMBER 4 /* MLDv1 other member reporting */
+#define MLD_SLEEPING_MEMBER 5 /* MLDv1 start query response */
+#define MLD_AWAKENING_MEMBER 6 /* MLDv1 group timer will start */
+#define MLD_G_QUERY_PENDING_MEMBER 7 /* MLDv2 group query pending */
+#define MLD_SG_QUERY_PENDING_MEMBER 8 /* MLDv2 source query pending */
+#define MLD_LEAVING_MEMBER 9 /* MLDv2 dying gasp (pending last */
+ /* retransmission of INCLUDE {}) */
+
+/*
+ * MLD version tag.
+ */
+#define MLD_VERSION_NONE 0 /* Invalid */
+#define MLD_VERSION_1 1
+#define MLD_VERSION_2 2 /* Default */
+
+/*
+ * MLDv2 protocol control variables.
+ */
+#define MLD_RV_INIT 2 /* Robustness Variable */
+#define MLD_RV_MIN 1
+#define MLD_RV_MAX 7
+
+#define MLD_QI_INIT 125 /* Query Interval (s) */
+#define MLD_QI_MIN 1
+#define MLD_QI_MAX 255
+
+#define MLD_QRI_INIT 10 /* Query Response Interval (s) */
+#define MLD_QRI_MIN 1
+#define MLD_QRI_MAX 255
+
+#define MLD_URI_INIT 3 /* Unsolicited Report Interval (s) */
+#define MLD_URI_MIN 0
+#define MLD_URI_MAX 10
+
+#define MLD_MAX_GS_SOURCES 256 /* # of sources in rx GS query */
+#define MLD_MAX_G_GS_PACKETS 8 /* # of packets to answer G/GS */
+#define MLD_MAX_STATE_CHANGE_PACKETS 8 /* # of packets per state change */
+#define MLD_MAX_RESPONSE_PACKETS 16 /* # of packets for general query */
+#define MLD_MAX_RESPONSE_BURST 4 /* # of responses to send at once */
+#define MLD_RESPONSE_BURST_INTERVAL (PR_FASTHZ / 2) /* 500ms */
+
+/*
+ * MLD-specific mbuf flags.
+ */
+#define M_MLDV1 M_PROTO1 /* Packet is MLDv1 */
+#define M_GROUPREC M_PROTO3 /* mbuf chain is a group record */
+
+/*
+ * Leading space for MLDv2 reports inside MTU.
+ *
+ * NOTE: This differs from IGMPv3 significantly. KAME IPv6 requires
+ * that a fully formed mbuf chain *without* the Router Alert option
+ * is passed to ip6_output(), however we must account for it in the
+ * MTU if we need to split an MLDv2 report into several packets.
+ *
+ * We now put the MLDv2 report header in the initial mbuf containing
+ * the IPv6 header.
+ */
+#define MLD_MTUSPACE (sizeof(struct ip6_hdr) + sizeof(struct mld_raopt) + \
+ sizeof(struct icmp6_hdr))
+
+/*
+ * Subsystem lock macros.
+ * The MLD lock is only taken with MLD. Currently it is system-wide.
+ * VIMAGE: The lock could be pushed to per-VIMAGE granularity in future.
+ */
+#define MLD_LOCK_INIT() mtx_init(&mld_mtx, "mld_mtx", NULL, MTX_DEF)
+#define MLD_LOCK_DESTROY() mtx_destroy(&mld_mtx)
+#define MLD_LOCK() mtx_lock(&mld_mtx)
+#define MLD_LOCK_ASSERT() mtx_assert(&mld_mtx, MA_OWNED)
+#define MLD_UNLOCK() mtx_unlock(&mld_mtx)
+#define MLD_UNLOCK_ASSERT() mtx_assert(&mld_mtx, MA_NOTOWNED)
+
+/*
+ * Per-link MLD context.
+ */
+#define MLD_IFINFO(ifp) \
+ (((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->mld_ifinfo)
+
+int mld_change_state(struct in6_multi *, const int);
+struct mld_ifinfo *
+ mld_domifattach(struct ifnet *);
+void mld_domifdetach(struct ifnet *);
+void mld_fasttimo(void);
+void mld_ifdetach(struct ifnet *);
+int mld_input(struct mbuf *, int, int);
+void mld_slowtimo(void);
+
+#ifdef SYSCTL_DECL
+SYSCTL_DECL(_net_inet6_mld);
+#endif
-void mld6_init(void);
-void mld6_input(struct mbuf *, int);
-void mld6_start_listening(struct in6_multi *);
-void mld6_stop_listening(struct in6_multi *);
-void mld6_fasttimeo(void);
#endif /* _KERNEL */
#endif /* _NETINET6_MLD6_VAR_H_ */
diff --git a/sys/netinet6/raw_ip6.c b/sys/netinet6/raw_ip6.c
index 2ac95e5..c340ffd 100644
--- a/sys/netinet6/raw_ip6.c
+++ b/sys/netinet6/raw_ip6.c
@@ -128,9 +128,20 @@ extern u_long rip_sendspace;
extern u_long rip_recvspace;
/*
- * Hooks for multicast forwarding.
+ * Hooks for multicast routing. They all default to NULL, so leave them not
+ * initialized and rely on BSS being set to 0.
+ */
+
+/*
+ * The socket used to communicate with the multicast routing daemon.
+ */
+#ifdef VIMAGE_GLOBALS
+struct socket *ip6_mrouter;
+#endif
+
+/*
+ * The various mrouter functions.
*/
-struct socket *ip6_mrouter = NULL;
int (*ip6_mrouter_set)(struct socket *, struct sockopt *);
int (*ip6_mrouter_get)(struct socket *, struct sockopt *);
int (*ip6_mrouter_done)(void);
@@ -149,6 +160,7 @@ rip6_input(struct mbuf **mp, int *offp, int proto)
#ifdef IPSEC
INIT_VNET_IPSEC(curvnet);
#endif
+ struct ifnet *ifp;
struct mbuf *m = *mp;
register struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
register struct inpcb *in6p;
@@ -166,6 +178,8 @@ rip6_input(struct mbuf **mp, int *offp, int proto)
init_sin6(&fromsa, m); /* general init */
+ ifp = m->m_pkthdr.rcvif;
+
INP_INFO_RLOCK(&V_ripcbinfo);
LIST_FOREACH(in6p, &V_ripcb, inp_list) {
/* XXX inp locking */
@@ -180,9 +194,17 @@ rip6_input(struct mbuf **mp, int *offp, int proto)
if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr) &&
!IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, &ip6->ip6_src))
continue;
- if (prison_check_ip6(in6p->inp_cred, &ip6->ip6_dst) != 0)
- continue;
- INP_RLOCK(in6p);
+ if (jailed(in6p->inp_cred)) {
+ /*
+ * Allow raw socket in jail to receive multicast;
+ * assume process had PRIV_NETINET_RAW at attach,
+ * and fall through into normal filter path if so.
+ */
+ if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) &&
+ prison_check_ip6(in6p->inp_cred,
+ &ip6->ip6_dst) != 0)
+ continue;
+ }
if (in6p->in6p_cksum != -1) {
V_rip6stat.rip6s_isum++;
if (in6_cksum(m, proto, *offp,
@@ -192,6 +214,31 @@ rip6_input(struct mbuf **mp, int *offp, int proto)
continue;
}
}
+ INP_RLOCK(in6p);
+ /*
+ * If this raw socket has multicast state, and we
+ * have received a multicast, check if this socket
+ * should receive it, as multicast filtering is now
+ * the responsibility of the transport layer.
+ */
+ if (in6p->in6p_moptions &&
+ IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+ struct sockaddr_in6 mcaddr;
+ int blocked;
+
+ bzero(&mcaddr, sizeof(struct sockaddr_in6));
+ mcaddr.sin6_len = sizeof(struct sockaddr_in6);
+ mcaddr.sin6_family = AF_INET6;
+ mcaddr.sin6_addr = ip6->ip6_dst;
+
+ blocked = im6o_mc_filter(in6p->in6p_moptions, ifp,
+ (struct sockaddr *)&mcaddr,
+ (struct sockaddr *)&fromsa);
+ if (blocked != MCAST_PASS) {
+ IP6STAT_INC(ip6s_notmember);
+ continue;
+ }
+ }
if (last != NULL) {
struct mbuf *n = m_copy(m, 0, (int)M_COPYALL);
@@ -604,13 +651,13 @@ rip6_attach(struct socket *so, int proto, struct thread *td)
static void
rip6_detach(struct socket *so)
{
- INIT_VNET_INET(so->so_vnet);
+ INIT_VNET_INET6(so->so_vnet);
struct inpcb *inp;
inp = sotoinpcb(so);
KASSERT(inp != NULL, ("rip6_detach: inp == NULL"));
- if (so == ip6_mrouter && ip6_mrouter_done)
+ if (so == V_ip6_mrouter && ip6_mrouter_done)
ip6_mrouter_done();
/* xxx: RSVP */
INP_INFO_WLOCK(&V_ripcbinfo);
diff --git a/sys/netinet6/udp6_usrreq.c b/sys/netinet6/udp6_usrreq.c
index 566cf92..5393740 100644
--- a/sys/netinet6/udp6_usrreq.c
+++ b/sys/netinet6/udp6_usrreq.c
@@ -177,6 +177,7 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
INIT_VNET_INET(curvnet);
INIT_VNET_INET6(curvnet);
struct mbuf *m = *mp;
+ struct ifnet *ifp;
struct ip6_hdr *ip6;
struct udphdr *uh;
struct inpcb *inp;
@@ -184,6 +185,7 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
int plen, ulen;
struct sockaddr_in6 fromsa;
+ ifp = m->m_pkthdr.rcvif;
ip6 = mtod(m, struct ip6_hdr *);
if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) {
@@ -239,6 +241,7 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
INP_INFO_RLOCK(&V_udbinfo);
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
struct inpcb *last;
+ struct ip6_moptions *imo;
/*
* In the event that laddr should be set to the link-local
@@ -261,12 +264,6 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
continue;
if (inp->inp_lport != uh->uh_dport)
continue;
- /*
- * XXX: Do not check source port of incoming datagram
- * unless inp_connect() has been called to bind the
- * fport part of the 4-tuple; the source could be
- * trying to talk to us with an ephemeral port.
- */
if (inp->inp_fport != 0 &&
inp->inp_fport != uh->uh_sport)
continue;
@@ -282,6 +279,35 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
continue;
}
+ INP_RLOCK(inp);
+
+ /*
+ * Handle socket delivery policy for any-source
+ * and source-specific multicast. [RFC3678]
+ */
+ imo = inp->in6p_moptions;
+ if (imo && IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+ struct sockaddr_in6 mcaddr;
+ int blocked;
+
+ bzero(&mcaddr, sizeof(struct sockaddr_in6));
+ mcaddr.sin6_len = sizeof(struct sockaddr_in6);
+ mcaddr.sin6_family = AF_INET6;
+ mcaddr.sin6_addr = ip6->ip6_dst;
+
+ blocked = im6o_mc_filter(imo, ifp,
+ (struct sockaddr *)&mcaddr,
+ (struct sockaddr *)&fromsa);
+ if (blocked != MCAST_PASS) {
+ if (blocked == MCAST_NOTGMEMBER)
+ IP6STAT_INC(ip6s_notmember);
+ if (blocked == MCAST_NOTSMEMBER ||
+ blocked == MCAST_MUTED)
+ UDPSTAT_INC(udps_filtermcast);
+ INP_RUNLOCK(inp);
+ continue;
+ }
+ }
if (last != NULL) {
struct mbuf *n;
@@ -397,6 +423,8 @@ udp6_input(struct mbuf **mp, int *offp, int proto)
return (IPPROTO_DONE);
badheadlocked:
+ if (inp)
+ INP_RUNLOCK(inp);
INP_INFO_RUNLOCK(&V_udbinfo);
badunlocked:
if (m)
diff --git a/sys/netinet6/vinet6.h b/sys/netinet6/vinet6.h
index 0920200..76bbec9 100644
--- a/sys/netinet6/vinet6.h
+++ b/sys/netinet6/vinet6.h
@@ -145,6 +145,7 @@ struct vnet_inet6 {
u_int32_t _ip6_temp_preferred_lifetime;
u_int32_t _ip6_temp_valid_lifetime;
+ struct socket * _ip6_mrouter;
int _ip6_mrouter_ver;
int _pim6;
u_int _mrt6debug;
@@ -153,6 +154,12 @@ struct vnet_inet6 {
int _ip6_use_defzone;
struct ip6_pktopts _ip6_opts;
+
+ struct timeval _mld_gsrdelay;
+ LIST_HEAD(, mld_ifinfo) _mli_head;
+ int _interface_timers_running6;
+ int _state_change_timers_running6;
+ int _current_state_timers_running6;
};
/* Size guard. See sys/vimage.h. */
@@ -173,6 +180,8 @@ extern struct vnet_inet6 vnet_inet6_0;
* Symbol translation macros
*/
#define V_addrsel_policytab VNET_INET6(addrsel_policytab)
+#define V_current_state_timers_running6 \
+ VNET_INET6(current_state_timers_running6)
#define V_dad_ignore_ns VNET_INET6(dad_ignore_ns)
#define V_dad_init VNET_INET6(dad_init)
#define V_dad_maxtry VNET_INET6(dad_maxtry)
@@ -190,6 +199,8 @@ extern struct vnet_inet6 vnet_inet6_0;
#define V_in6_ifaddr VNET_INET6(in6_ifaddr)
#define V_in6_maxmtu VNET_INET6(in6_maxmtu)
#define V_in6_tmpaddrtimer_ch VNET_INET6(in6_tmpaddrtimer_ch)
+#define V_interface_timers_running6 \
+ VNET_INET6(interface_timers_running6)
#define V_ip6_accept_rtadv VNET_INET6(ip6_accept_rtadv)
#define V_ip6_auto_flowlabel VNET_INET6(ip6_auto_flowlabel)
#define V_ip6_auto_linklocal VNET_INET6(ip6_auto_linklocal)
@@ -205,6 +216,7 @@ extern struct vnet_inet6 vnet_inet6_0;
#define V_ip6_maxfragpackets VNET_INET6(ip6_maxfragpackets)
#define V_ip6_maxfrags VNET_INET6(ip6_maxfrags)
#define V_ip6_mcast_pmtu VNET_INET6(ip6_mcast_pmtu)
+#define V_ip6_mrouter VNET_INET6(ip6_mrouter)
#define V_ip6_mrouter_ver VNET_INET6(ip6_mrouter_ver)
#define V_ip6_opts VNET_INET6(ip6_opts)
#define V_ip6_prefer_tempaddr VNET_INET6(ip6_prefer_tempaddr)
@@ -223,6 +235,8 @@ extern struct vnet_inet6 vnet_inet6_0;
#define V_ip6stealth VNET_INET6(ip6stealth)
#define V_llinfo_nd6 VNET_INET6(llinfo_nd6)
#define V_mrt6debug VNET_INET6(mrt6debug)
+#define V_mld_gsrdelay VNET_INET6(mld_gsrdelay)
+#define V_mli_head VNET_INET6(mli_head)
#define V_nd6_allocated VNET_INET6(nd6_allocated)
#define V_nd6_debug VNET_INET6(nd6_debug)
#define V_nd6_defifindex VNET_INET6(nd6_defifindex)
@@ -256,6 +270,8 @@ extern struct vnet_inet6 vnet_inet6_0;
#define V_rtq_timer6 VNET_INET6(rtq_timer6)
#define V_rtq_toomany6 VNET_INET6(rtq_toomany6)
#define V_sid_default VNET_INET6(sid_default)
+#define V_state_change_timers_running6 \
+ VNET_INET6(state_change_timers_running6)
#define V_udp6_recvspace VNET_INET6(udp6_recvspace)
#define V_udp6_sendspace VNET_INET6(udp6_sendspace)
OpenPOWER on IntegriCloud