Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 27 May 2009 18:57:14 +0000 (UTC)
From:      Bruce M Simpson <bms@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r192923 - in head: share/man/man4 sys/netinet6 usr.sbin/ifmcstat
Message-ID:  <200905271857.n4RIvEJ2008462@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: bms
Date: Wed May 27 18:57:13 2009
New Revision: 192923
URL: http://svn.freebsd.org/changeset/base/192923

Log:
  Merge final round of MLD changes from p4:
   ip6_input.c, in6.h:
   * Add netinet6-specific mbuf flag M_RTALERT_MLD, shadowing M_PROTO6.
    * Always set this flag if HBH Router Alert option is present for MLD,
      even when not forwarding.
  
   icmp6.c:
   * In icmp6_input(), spell m->m_pkthdr.rcvif as ifp to be consistent.
   * Use scope ID for verifying input. Do not apply SSM filters here, no inpcb.
    * Check for M_RTALERT_MLD when validating MLD traffic, as we can't see
      IPv6 hop options outside of ip6_input().
  
   in6_mcast.c:
   * Use KAME scope/zone ID in in6_multi.
     * Update net.inet6.ip6.mcast.filters implementation to use scope IDs
       for comparisons.
   * Fix scope ID treatment in multicast socket option processing.
     Scope IDs passed in from userland will be ignored as other less
     ambiguous APIs exist for specifying the link.
   * Tighten userland input checks in IPv6 SSM delta and full-state ops.
     * Source filter embedded scope IDs need to be revisited, for now
       just clear them and ignore them on input.
   * Adapt KAME behaviour of looking up the scope ID in the default zone
     for multicast leaves, when the interface is ambiguous.
  
   mld6.c:
   * Tighten origin checks on MLD traffic as per RFC3810 Section 6.2:
    * ip6_src MAY be the unspecified address for MLDv1 reports.
    * ip6_src MAY have link-local address scope for MLDv1 reports,
      MLDv1 queries, and MLDv2 queries.
    * Perform address field validation *before* accepting queries.
   * Use KAME scope/zone ID in query/report processing.
     * Break const correctness for mld_v1_input_report(), mld_v1_input_query()
       as we temporarily modify the input mbuf chain.
     * Clear the scope ID before handoff to userland MLD daemon.
   * Fix MLDv1 old querier present timer processing.
     With the protocol defaults, hosts should revert to MLDv2 after 260s.
   * Add net.inet6.mld.v1enable sysctl, default to on.
  
   ifmcstat.c:
   * Use sysctl by default; -K requests kvm(3) if so compiled.
  
   mld.4:
   * Connect man page to build.
  
  Tested using PCS.

Modified:
  head/share/man/man4/Makefile
  head/share/man/man4/multicast.4
  head/sys/netinet6/icmp6.c
  head/sys/netinet6/in6.h
  head/sys/netinet6/in6_mcast.c
  head/sys/netinet6/ip6_input.c
  head/sys/netinet6/mld6.c
  head/usr.sbin/ifmcstat/ifmcstat.8
  head/usr.sbin/ifmcstat/ifmcstat.c

Modified: head/share/man/man4/Makefile
==============================================================================
--- head/share/man/man4/Makefile	Wed May 27 18:54:31 2009	(r192922)
+++ head/share/man/man4/Makefile	Wed May 27 18:57:13 2009	(r192923)
@@ -191,6 +191,7 @@ MAN=	aac.4 \
 	meteor.4 \
 	mfi.4 \
 	miibus.4 \
+	mld.4 \
 	mlx.4 \
 	mly.4 \
 	mmc.4 \

Modified: head/share/man/man4/multicast.4
==============================================================================
--- head/share/man/man4/multicast.4	Wed May 27 18:54:31 2009	(r192922)
+++ head/share/man/man4/multicast.4	Wed May 27 18:57:13 2009	(r192923)
@@ -25,7 +25,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd February 13, 2009
+.Dd May 27, 2009
 .Dt MULTICAST 4
 .Os
 .\"
@@ -962,6 +962,7 @@ after the previous upcall.
 .Xr intro 4 ,
 .Xr ip 4 ,
 .Xr ip6 4 ,
+.Xr mld 4 ,
 .Xr pim 4
 .\"
 .Sh HISTORY
@@ -1002,6 +1003,8 @@ monitoring were implemented by
 in collaboration with
 .An Chris Brown
 (NextHop).
+The IGMPv3 and MLDv2 multicast support was implemented by
+.An Bruce Simpson .
 .Pp
 This manual page was written by
 .An Pavlin Radoslavov

Modified: head/sys/netinet6/icmp6.c
==============================================================================
--- head/sys/netinet6/icmp6.c	Wed May 27 18:54:31 2009	(r192922)
+++ head/sys/netinet6/icmp6.c	Wed May 27 18:57:13 2009	(r192923)
@@ -403,6 +403,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 	INIT_VNET_INET6(curvnet);
 	INIT_VPROCG(TD_TO_VPROCG(curthread)); /* XXX V_hostname needs this */
 	struct mbuf *m = *mp, *n;
+	struct ifnet *ifp;
 	struct ip6_hdr *ip6, *nip6;
 	struct icmp6_hdr *icmp6, *nicmp6;
 	int off = *offp;
@@ -410,6 +411,8 @@ icmp6_input(struct mbuf **mp, int *offp,
 	int code, sum, noff;
 	char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
 
+	ifp = m->m_pkthdr.rcvif;
+
 #ifndef PULLDOWN_TEST
 	IP6_EXTHDR_CHECK(m, off, sizeof(struct icmp6_hdr), IPPROTO_DONE);
 	/* m might change if M_LOOP.  So, call mtod after this */
@@ -431,10 +434,8 @@ icmp6_input(struct mbuf **mp, int *offp,
 	 * Note: SSM filters are not applied for ICMPv6 traffic.
 	 */
 	if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
-		struct ifnet *ifp;
-		struct in6_multi *inm;
+		struct in6_multi	*inm;
 
-		ifp = m->m_pkthdr.rcvif;
 		inm = in6m_lookup(ifp, &ip6->ip6_dst);
 		if (inm == NULL) {
 			IP6STAT_INC(ip6s_notmember);
@@ -483,19 +484,19 @@ icmp6_input(struct mbuf **mp, int *offp,
 	}
 
 	ICMP6STAT_INC(icp6s_inhist[icmp6->icmp6_type]);
-	icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_msg);
+	icmp6_ifstat_inc(ifp, ifs6_in_msg);
 	if (icmp6->icmp6_type < ICMP6_INFOMSG_MASK)
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_error);
+		icmp6_ifstat_inc(ifp, ifs6_in_error);
 
 	switch (icmp6->icmp6_type) {
 	case ICMP6_DST_UNREACH:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_dstunreach);
+		icmp6_ifstat_inc(ifp, ifs6_in_dstunreach);
 		switch (code) {
 		case ICMP6_DST_UNREACH_NOROUTE:
 			code = PRC_UNREACH_NET;
 			break;
 		case ICMP6_DST_UNREACH_ADMIN:
-			icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_adminprohib);
+			icmp6_ifstat_inc(ifp, ifs6_in_adminprohib);
 			code = PRC_UNREACH_PROTOCOL; /* is this a good code? */
 			break;
 		case ICMP6_DST_UNREACH_ADDR:
@@ -515,7 +516,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ICMP6_PACKET_TOO_BIG:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_pkttoobig);
+		icmp6_ifstat_inc(ifp, ifs6_in_pkttoobig);
 
 		/* validation is made in icmp6_mtudisc_update */
 
@@ -529,7 +530,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ICMP6_TIME_EXCEEDED:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_timeexceed);
+		icmp6_ifstat_inc(ifp, ifs6_in_timeexceed);
 		switch (code) {
 		case ICMP6_TIME_EXCEED_TRANSIT:
 			code = PRC_TIMXCEED_INTRANS;
@@ -544,7 +545,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ICMP6_PARAM_PROB:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_paramprob);
+		icmp6_ifstat_inc(ifp, ifs6_in_paramprob);
 		switch (code) {
 		case ICMP6_PARAMPROB_NEXTHEADER:
 			code = PRC_UNREACH_PROTOCOL;
@@ -560,7 +561,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ICMP6_ECHO_REQUEST:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_echo);
+		icmp6_ifstat_inc(ifp, ifs6_in_echo);
 		if (code != 0)
 			goto badcode;
 		if ((n = m_copy(m, 0, M_COPYALL)) == NULL) {
@@ -623,7 +624,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ICMP6_ECHO_REPLY:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_echoreply);
+		icmp6_ifstat_inc(ifp, ifs6_in_echoreply);
 		if (code != 0)
 			goto badcode;
 		break;
@@ -633,11 +634,15 @@ icmp6_input(struct mbuf **mp, int *offp,
 	case MLD_LISTENER_DONE:
 	case MLDV2_LISTENER_REPORT:
 		/*
-		 * Drop MLD traffic which is not link-local.
+		 * Drop MLD traffic which is not link-local, has a hop limit
+		 * of greater than 1 hop, or which does not have the
+		 * IPv6 HBH Router Alert option.
+		 * As IPv6 HBH options are stripped in ip6_input() we must
+		 * check an mbuf header flag.
 		 * XXX Should we also sanity check that these messages
 		 * were directed to a link-local multicast prefix?
 		 */
-		if (ip6->ip6_hlim != 1)
+		if ((ip6->ip6_hlim != 1) || (m->m_flags & M_RTALERT_MLD) == 0)
 			goto freeit;
 		if (mld_input(m, off, icmp6len) != 0)
 			return (IPPROTO_DONE);
@@ -748,7 +753,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ND_ROUTER_SOLICIT:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_routersolicit);
+		icmp6_ifstat_inc(ifp, ifs6_in_routersolicit);
 		if (code != 0)
 			goto badcode;
 		if (icmp6len < sizeof(struct nd_router_solicit))
@@ -764,7 +769,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ND_ROUTER_ADVERT:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_routeradvert);
+		icmp6_ifstat_inc(ifp, ifs6_in_routeradvert);
 		if (code != 0)
 			goto badcode;
 		if (icmp6len < sizeof(struct nd_router_advert))
@@ -780,7 +785,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ND_NEIGHBOR_SOLICIT:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_neighborsolicit);
+		icmp6_ifstat_inc(ifp, ifs6_in_neighborsolicit);
 		if (code != 0)
 			goto badcode;
 		if (icmp6len < sizeof(struct nd_neighbor_solicit))
@@ -796,7 +801,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ND_NEIGHBOR_ADVERT:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_neighboradvert);
+		icmp6_ifstat_inc(ifp, ifs6_in_neighboradvert);
 		if (code != 0)
 			goto badcode;
 		if (icmp6len < sizeof(struct nd_neighbor_advert))
@@ -812,7 +817,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		break;
 
 	case ND_REDIRECT:
-		icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_redirect);
+		icmp6_ifstat_inc(ifp, ifs6_in_redirect);
 		if (code != 0)
 			goto badcode;
 		if (icmp6len < sizeof(struct nd_redirect))
@@ -840,7 +845,7 @@ icmp6_input(struct mbuf **mp, int *offp,
 		    "icmp6_input: unknown type %d(src=%s, dst=%s, ifid=%d)\n",
 		    icmp6->icmp6_type, ip6_sprintf(ip6bufs, &ip6->ip6_src),
 		    ip6_sprintf(ip6bufd, &ip6->ip6_dst),
-		    m->m_pkthdr.rcvif ? m->m_pkthdr.rcvif->if_index : 0));
+		    ifp ? ifp->if_index : 0));
 		if (icmp6->icmp6_type < ICMP6_ECHO_REQUEST) {
 			/* ICMPv6 error: MUST deliver it by spec... */
 			code = PRC_NCMDS;

Modified: head/sys/netinet6/in6.h
==============================================================================
--- head/sys/netinet6/in6.h	Wed May 27 18:54:31 2009	(r192922)
+++ head/sys/netinet6/in6.h	Wed May 27 18:57:13 2009	(r192923)
@@ -619,6 +619,7 @@ struct ip6_mtuinfo {
 #define	M_DECRYPTED	M_PROTO3
 #define	M_LOOP		M_PROTO4
 #define	M_AUTHIPDGM	M_PROTO5
+#define	M_RTALERT_MLD	M_PROTO6
 
 #ifdef _KERNEL
 struct cmsghdr;

Modified: head/sys/netinet6/in6_mcast.c
==============================================================================
--- head/sys/netinet6/in6_mcast.c	Wed May 27 18:54:31 2009	(r192922)
+++ head/sys/netinet6/in6_mcast.c	Wed May 27 18:57:13 2009	(r192923)
@@ -305,6 +305,10 @@ im6o_match_group(const struct ip6_moptio
  * Find an IPv6 multicast source entry for this imo which matches
  * the given group index for this socket, and source address.
  *
+ * XXX TODO: The scope ID, if present in src, is stripped before
+ * any comparison. We SHOULD enforce scope/zone checks where the source
+ * filter entry has a link scope.
+ *
  * NOTE: This does not check if the entry is in-mode, merely if
  * it exists, which may not be the desired behaviour.
  */
@@ -328,6 +332,7 @@ im6o_match_source(const struct ip6_mopti
 
 	psa = (const sockunion_t *)src;
 	find.im6s_addr = psa->sin6.sin6_addr;
+	in6_clearscope(&find.im6s_addr);		/* XXX */
 	ims = RB_FIND(ip6_msource_tree, &imf->im6f_sources, &find);
 
 	return ((struct in6_msource *)ims);
@@ -1159,6 +1164,20 @@ in6_mc_join_locked(struct ifnet *ifp, co
 	char			 ip6tbuf[INET6_ADDRSTRLEN];
 #endif
 
+#ifdef INVARIANTS
+	/*
+	 * Sanity: Check scope zone ID was set for ifp, if and
+	 * only if group is scoped to an interface.
+	 */
+	KASSERT(IN6_IS_ADDR_MULTICAST(mcaddr),
+	    ("%s: not a multicast address", __func__));
+	if (IN6_IS_ADDR_MC_LINKLOCAL(mcaddr) ||
+	    IN6_IS_ADDR_MC_INTFACELOCAL(mcaddr)) {
+		KASSERT(mcaddr->s6_addr16[1] != 0,
+		    ("%s: scope zone ID not set", __func__));
+	}
+#endif
+
 	IN6_MULTI_LOCK_ASSERT();
 
 	CTR4(KTR_MLD, "%s: join %s on %p(%s))", __func__,
@@ -1360,6 +1379,8 @@ in6p_block_unblock_source(struct inpcb *
 	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
 		return (EINVAL);
 
+	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
+
 	/*
 	 * Check if we are actually a member of this group.
 	 */
@@ -1566,19 +1587,26 @@ in6p_get_source_filters(struct inpcb *in
 	if (error)
 		return (error);
 
-	if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
+	if (msfr.msfr_group.ss_family != AF_INET6 ||
+	    msfr.msfr_group.ss_len != sizeof(struct sockaddr_in6))
+		return (EINVAL);
+
+	gsa = (sockunion_t *)&msfr.msfr_group;
+	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
 		return (EINVAL);
 
+	if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
+		return (EADDRNOTAVAIL);
 	ifp = ifnet_byindex(msfr.msfr_ifindex);
 	if (ifp == NULL)
-		return (EINVAL);
+		return (EADDRNOTAVAIL);
+	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
 
 	INP_WLOCK(inp);
 
 	/*
 	 * Lookup group on the socket.
 	 */
-	gsa = (sockunion_t *)&msfr.msfr_group;
 	idx = im6o_match_group(imo, ifp, &gsa->sa);
 	if (idx == -1 || imo->im6o_mfilters == NULL) {
 		INP_WUNLOCK(inp);
@@ -1803,6 +1831,12 @@ in6p_join_group(struct inpcb *inp, struc
 	ssa = (sockunion_t *)&gsr.gsr_source;
 	ssa->ss.ss_family = AF_UNSPEC;
 
+	/*
+	 * Chew everything into struct group_source_req.
+	 * Overwrite the port field if present, as the sockaddr
+	 * being copied in may be matched with a binary comparison.
+	 * Ignore passed-in scope ID.
+	 */
 	switch (sopt->sopt_name) {
 	case IPV6_JOIN_GROUP: {
 		struct ipv6_mreq mreq;
@@ -1846,16 +1880,20 @@ in6p_join_group(struct inpcb *inp, struc
 		    gsa->sin6.sin6_len != sizeof(struct sockaddr_in6))
 			return (EINVAL);
 
-		/*
-		 * Overwrite the port field if present, as the sockaddr
-		 * being copied in may be matched with a binary comparison.
-		 */
-		gsa->sin6.sin6_port = 0;
 		if (sopt->sopt_name == MCAST_JOIN_SOURCE_GROUP) {
 			if (ssa->sin6.sin6_family != AF_INET6 ||
 			    ssa->sin6.sin6_len != sizeof(struct sockaddr_in6))
 				return (EINVAL);
+			if (IN6_IS_ADDR_MULTICAST(&ssa->sin6.sin6_addr))
+				return (EINVAL);
+			/*
+			 * TODO: Validate embedded scope ID in source
+			 * list entry against passed-in ifp, if and only
+			 * if source list filter entry is iface or node local.
+			 */
+			in6_clearscope(&ssa->sin6.sin6_addr);
 			ssa->sin6.sin6_port = 0;
+			ssa->sin6.sin6_scope_id = 0;
 		}
 
 		if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
@@ -1870,34 +1908,22 @@ in6p_join_group(struct inpcb *inp, struc
 		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
+	gsa->sin6.sin6_port = 0;
+	gsa->sin6.sin6_scope_id = 0;
+
 	/*
-	 * FIXME: Set interface scope in group address.
+	 * Always set the scope zone ID on memberships created from userland.
+	 * Use the passed-in ifp to do this.
+	 * XXX The in6_setscope() return value is meaningless.
+	 * XXX SCOPE6_LOCK() is taken by in6_setscope().
 	 */
-	(void)in6_setscope(&gsa->sin6.sin_addr, ifp, NULL);
-#endif
+	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
 
 	/*
 	 * MCAST_JOIN_SOURCE on an exclusive membership is an error.
@@ -2031,6 +2057,8 @@ static int
 in6p_leave_group(struct inpcb *inp, struct sockopt *sopt)
 {
 	INIT_VNET_NET(curvnet);
+	INIT_VNET_INET6(curvnet);
+	struct ipv6_mreq		 mreq;
 	struct group_source_req		 gsr;
 	sockunion_t			*gsa, *ssa;
 	struct ifnet			*ifp;
@@ -2038,6 +2066,7 @@ in6p_leave_group(struct inpcb *inp, stru
 	struct ip6_moptions		*imo;
 	struct in6_msource		*ims;
 	struct in6_multi		*inm;
+	uint32_t			 ifindex;
 	size_t				 idx;
 	int				 error, is_final;
 #ifdef KTR
@@ -2045,6 +2074,7 @@ in6p_leave_group(struct inpcb *inp, stru
 #endif
 
 	ifp = NULL;
+	ifindex = 0;
 	error = 0;
 	is_final = 1;
 
@@ -2054,39 +2084,26 @@ in6p_leave_group(struct inpcb *inp, stru
 	ssa = (sockunion_t *)&gsr.gsr_source;
 	ssa->ss.ss_family = AF_UNSPEC;
 
+	/*
+	 * Chew everything passed in up into a struct group_source_req
+	 * as that is easier to process.
+	 * Note: Any embedded scope ID in the multicast group passed
+	 * in by userland is ignored, the interface index is the recommended
+	 * mechanism to specify an interface; see below.
+	 */
 	switch (sopt->sopt_name) {
-	case IPV6_LEAVE_GROUP: {
-		struct ipv6_mreq mreq;
-
+	case IPV6_LEAVE_GROUP:
 		error = sooptcopyin(sopt, &mreq, sizeof(struct ipv6_mreq),
 		    sizeof(struct ipv6_mreq));
 		if (error)
 			return (error);
-
 		gsa->sin6.sin6_family = AF_INET6;
 		gsa->sin6.sin6_len = sizeof(struct sockaddr_in6);
 		gsa->sin6.sin6_addr = mreq.ipv6mr_multiaddr;
-
-		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);
-	} break;
+		gsa->sin6.sin6_port = 0;
+		gsa->sin6.sin6_scope_id = 0;
+		ifindex = mreq.ipv6mr_interface;
+		break;
 
 	case MCAST_LEAVE_GROUP:
 	case MCAST_LEAVE_SOURCE_GROUP:
@@ -2105,17 +2122,22 @@ in6p_leave_group(struct inpcb *inp, stru
 		if (gsa->sin6.sin6_family != AF_INET6 ||
 		    gsa->sin6.sin6_len != sizeof(struct sockaddr_in6))
 			return (EINVAL);
-
 		if (sopt->sopt_name == MCAST_LEAVE_SOURCE_GROUP) {
 			if (ssa->sin6.sin6_family != AF_INET6 ||
 			    ssa->sin6.sin6_len != sizeof(struct sockaddr_in6))
 				return (EINVAL);
+			if (IN6_IS_ADDR_MULTICAST(&ssa->sin6.sin6_addr))
+				return (EINVAL);
+			/*
+			 * TODO: Validate embedded scope ID in source
+			 * list entry against passed-in ifp, if and only
+			 * if source list filter entry is iface or node local.
+			 */
+			in6_clearscope(&ssa->sin6.sin6_addr);
 		}
-
-		if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
-			return (EADDRNOTAVAIL);
-
-		ifp = ifnet_byindex(gsr.gsr_interface);
+		gsa->sin6.sin6_port = 0;
+		gsa->sin6.sin6_scope_id = 0;
+		ifindex = gsr.gsr_interface;
 		break;
 
 	default:
@@ -2128,14 +2150,39 @@ in6p_leave_group(struct inpcb *inp, stru
 	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.
+	 * Validate interface index if provided. If no interface index
+	 * was provided separately, attempt to look the membership up
+	 * from the default scope as a last resort to disambiguate
+	 * the membership we are being asked to leave.
+	 * XXX SCOPE6 lock potentially taken here.
 	 */
-	(void)in6_setscope(&mreq->ipv6mr_multiaddr, ifp, NULL);
-#endif
+	if (ifindex != 0) {
+		if (ifindex < 0 || V_if_index < ifindex)
+			return (EADDRNOTAVAIL);
+		ifp = ifnet_byindex(ifindex);
+		if (ifp == NULL)
+			return (EADDRNOTAVAIL);
+		(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
+	} else {
+		error = sa6_embedscope(&gsa->sin6, V_ip6_use_defzone);
+		if (error)
+			return (EADDRNOTAVAIL);
+		/*
+		 * XXX For now, stomp on zone ID for the corner case.
+		 * This is not the 'KAME way', but we need to see the ifp
+		 * directly until such time as this implementation is
+		 * refactored, assuming the scope IDs are the way to go.
+		 */
+		ifindex = ntohs(gsa->sin6.sin6_addr.s6_addr16[1]);
+		KASSERT(ifindex != 0, ("%s: bad zone ID", __func__));
+		ifp = ifnet_byindex(ifindex);
+		if (ifp == NULL)
+			return (EADDRNOTAVAIL);
+	}
+
+	CTR2(KTR_MLD, "%s: ifp = %p", __func__, ifp);
+	KASSERT(ifp != NULL, ("%s: ifp did not resolve", __func__));
 
 	/*
 	 * Find the membership in the membership array.
@@ -2312,10 +2359,10 @@ in6p_set_source_filters(struct inpcb *in
 
 	if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
 		return (EADDRNOTAVAIL);
-
 	ifp = ifnet_byindex(msfr.msfr_ifindex);
 	if (ifp == NULL)
 		return (EADDRNOTAVAIL);
+	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
 
 	/*
 	 * Take the INP write lock.
@@ -2393,6 +2440,16 @@ in6p_set_source_filters(struct inpcb *in
 				error = EINVAL;
 				break;
 			}
+			if (IN6_IS_ADDR_MULTICAST(&psin->sin6_addr)) {
+				error = EINVAL;
+				break;
+			}
+			/*
+			 * TODO: Validate embedded scope ID in source
+			 * list entry against passed-in ifp, if and only
+			 * if source list filter entry is iface or node local.
+			 */
+			in6_clearscope(&psin->sin6_addr);
 			error = im6f_get_source(imf, psin, &lims);
 			if (error)
 				break;
@@ -2560,7 +2617,7 @@ static int
 sysctl_ip6_mcast_filters(SYSCTL_HANDLER_ARGS)
 {
 	INIT_VNET_NET(curvnet);
-	struct in6_addr			*pgina;
+	struct in6_addr			 mcaddr;
 	struct in6_addr			 src;
 	struct ifnet			*ifp;
 	struct ifmultiaddr		*ifma;
@@ -2591,10 +2648,10 @@ sysctl_ip6_mcast_filters(SYSCTL_HANDLER_
 		return (ENOENT);
 	}
 
-	pgina = (struct in6_addr *)&name[1];
-	if (!IN6_IS_ADDR_MULTICAST(pgina)) {
+	memcpy(&mcaddr, &name[1], sizeof(struct in6_addr));
+	if (!IN6_IS_ADDR_MULTICAST(&mcaddr)) {
 		CTR2(KTR_MLD, "%s: group %s is not multicast",
-		    __func__, ip6_sprintf(ip6tbuf, pgina));
+		    __func__, ip6_sprintf(ip6tbuf, &mcaddr));
 		return (EINVAL);
 	}
 
@@ -2604,6 +2661,10 @@ sysctl_ip6_mcast_filters(SYSCTL_HANDLER_
 		    __func__, ifindex);
 		return (ENOENT);
 	}
+	/*
+	 * Internal MLD lookups require that scope/zone ID is set.
+	 */
+	(void)in6_setscope(&mcaddr, ifp, NULL);
 
 	retval = sysctl_wire_old_buffer(req,
 	    sizeof(uint32_t) + (in6_mcast_maxgrpsrc * sizeof(struct in6_addr)));
@@ -2618,7 +2679,7 @@ sysctl_ip6_mcast_filters(SYSCTL_HANDLER_
 		    ifma->ifma_protospec == NULL)
 			continue;
 		inm = (struct in6_multi *)ifma->ifma_protospec;
-		if (!IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, pgina))
+		if (!IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, &mcaddr))
 			continue;
 		fmode = inm->in6m_st[1].iss_fmode;
 		retval = SYSCTL_OUT(req, &fmode, sizeof(uint32_t));

Modified: head/sys/netinet6/ip6_input.c
==============================================================================
--- head/sys/netinet6/ip6_input.c	Wed May 27 18:54:31 2009	(r192922)
+++ head/sys/netinet6/ip6_input.c	Wed May 27 18:57:13 2009	(r192923)
@@ -773,10 +773,11 @@ passin:
 		 * case we should pass the packet to the multicast routing
 		 * daemon.
 		 */
-		if (rtalert != ~0 && V_ip6_forwarding) {
+		if (rtalert != ~0) {
 			switch (rtalert) {
 			case IP6OPT_RTALERT_MLD:
-				ours = 1;
+				if (V_ip6_forwarding)
+					ours = 1;
 				break;
 			default:
 				/*
@@ -820,6 +821,9 @@ passin:
 		 * The packet is returned (relatively) intact; if
 		 * ip6_mforward() returns a non-zero value, the packet
 		 * must be discarded, else it may be accepted below.
+		 *
+		 * XXX TODO: Check hlim and multicast scope here to avoid
+		 * unnecessarily calling into ip6_mforward().
 		 */
 		if (ip6_mforward &&
 		    ip6_mforward(ip6, m->m_pkthdr.rcvif, m)) {
@@ -882,6 +886,14 @@ passin:
 		if (ip6_ipsec_input(m, nxt))
 			goto bad;
 #endif /* IPSEC */
+
+		/*
+		 * Use mbuf flags to propagate Router Alert option to
+		 * ICMPv6 layer, as hop-by-hop options have been stripped.
+		 */
+		if (nxt == IPPROTO_ICMPV6 && rtalert != ~0)
+			m->m_flags |= M_RTALERT_MLD;
+
 		nxt = (*inet6sw[ip6_protox[nxt]].pr_input)(&m, &off, nxt);
 	}
 	goto out;

Modified: head/sys/netinet6/mld6.c
==============================================================================
--- head/sys/netinet6/mld6.c	Wed May 27 18:54:31 2009	(r192922)
+++ head/sys/netinet6/mld6.c	Wed May 27 18:57:13 2009	(r192923)
@@ -122,9 +122,9 @@ 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 *);
+		    /*const*/ struct mld_hdr *);
 static int	mld_v1_input_report(struct ifnet *, const struct ip6_hdr *,
-		    const struct mld_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);
@@ -239,6 +239,11 @@ SYSCTL_V_PROC(V_NET, vnet_inet6, _net_in
 SYSCTL_NODE(_net_inet6_mld, OID_AUTO, ifinfo, CTLFLAG_RD | CTLFLAG_MPSAFE,
     sysctl_mld_ifinfo, "Per-interface MLDv2 state");
 
+static int	mld_v1enable = 1;
+SYSCTL_INT(_net_inet6_mld, OID_AUTO, v1enable, CTLFLAG_RW,
+    &mld_v1enable, 0, "Enable fallback to MLDv1");
+TUNABLE_INT("net.inet6.mld.v1enable", &mld_v1enable);
+
 /*
  * Packed Router Alert option structure declaration.
  */
@@ -615,36 +620,97 @@ mli_delete_locked(const struct ifnet *if
 /*
  * Process a received MLDv1 general or address-specific query.
  * Assumes that the query header has been pulled up to sizeof(mld_hdr).
+ *
+ * NOTE: Can't be fully const correct as we temporarily embed scope ID in
+ * mld_addr. This is OK as we own the mbuf chain.
  */
 static int
 mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6,
-    const struct mld_hdr *mld)
+    /*const*/ struct mld_hdr *mld)
 {
 	struct ifmultiaddr	*ifma;
 	struct mld_ifinfo	*mli;
 	struct in6_multi	*inm;
+	int			 is_general_query;
 	uint16_t		 timer;
 #ifdef KTR
 	char			 ip6tbuf[INET6_ADDRSTRLEN];
 #endif
 
+	is_general_query = 0;
+
+	if (!mld_v1enable) {
+		CTR3(KTR_MLD, "ignore v1 query %s on ifp %p(%s)",
+		    ip6_sprintf(ip6tbuf, &mld->mld_addr),
+		    ifp, ifp->if_xname);
+		return (0);
+	}
+
+	/*
+	 * RFC3810 Section 6.2: MLD queries must originate from
+	 * a router's link-local address.
+	 */
+	if (!IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) {
+		CTR3(KTR_MLD, "ignore v1 query src %s on ifp %p(%s)",
+		    ip6_sprintf(ip6tbuf, &ip6->ip6_src),
+		    ifp, ifp->if_xname);
+		return (0);
+	}
+
+	/*
+	 * Do address field validation upfront before we accept
+	 * the query.
+	 */
+	if (IN6_IS_ADDR_UNSPECIFIED(&mld->mld_addr)) {
+		/*
+		 * MLDv1 General Query.
+		 * If this was not sent to the all-nodes group, ignore it.
+		 */
+		struct in6_addr		 dst;
+
+		dst = ip6->ip6_dst;
+		in6_clearscope(&dst);
+		if (!IN6_ARE_ADDR_EQUAL(&dst, &in6addr_linklocal_allnodes))
+			return (EINVAL);
+		is_general_query = 1;
+	} else {
+		/*
+		 * Embed scope ID of receiving interface in MLD query for
+		 * lookup whilst we don't hold other locks.
+		 */
+		in6_setscope(&mld->mld_addr, ifp, NULL);
+	}
+
 	IN6_MULTI_LOCK();
 	MLD_LOCK();
 	IF_ADDR_LOCK(ifp);
 
-	mli = MLD_IFINFO(ifp);
-	KASSERT(mli != NULL, ("%s: no mld_ifinfo for ifp %p", __func__, ifp));
-
 	/*
 	 * Switch to MLDv1 host compatibility mode.
 	 */
+	mli = MLD_IFINFO(ifp);
+	KASSERT(mli != NULL, ("%s: no mld_ifinfo for ifp %p", __func__, ifp));
 	mld_set_version(mli, MLD_VERSION_1);
 
-	timer = ntohs(mld->mld_maxdelay) * PR_FASTHZ / MLD_TIMER_SCALE;
+	timer = (ntohs(mld->mld_maxdelay) * PR_FASTHZ) / MLD_TIMER_SCALE;
 	if (timer == 0)
 		timer = 1;
 
-	if (!IN6_IS_ADDR_UNSPECIFIED(&mld->mld_addr)) {
+	if (is_general_query) {
+		/*
+		 * 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);
+		}
+	} else {
 		/*
 		 * MLDv1 Group-Specific Query.
 		 * If this is a group-specific MLDv1 query, we need only
@@ -657,32 +723,8 @@ mld_v1_input_query(struct ifnet *ifp, co
 			    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.
-		 */
-		struct in6_addr dst;
-
-		dst = ip6->ip6_dst;
-		in6_clearscope(&dst);
-		if (IN6_ARE_ADDR_EQUAL(&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);
-			}
-		}
+		/* XXX Clear embedded scope ID as userland won't expect it. */
+		in6_clearscope(&mld->mld_addr);
 	}
 
 	IF_ADDR_UNLOCK(ifp);
@@ -769,18 +811,38 @@ mld_v2_input_query(struct ifnet *ifp, co
 	struct mldv2_query	*mld;
 	struct in6_multi	*inm;
 	uint32_t		 maxdelay, nsrc, qqi;
+	int			 is_general_query;
 	uint16_t		 timer;
 	uint8_t			 qrv;
+#ifdef KTR
+	char			 ip6tbuf[INET6_ADDRSTRLEN];
+#endif
+
+	is_general_query = 0;
+
+	/*
+	 * RFC3810 Section 6.2: MLD queries must originate from
+	 * a router's link-local address.
+	 */
+	if (!IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) {
+		CTR3(KTR_MLD, "ignore v1 query src %s on ifp %p(%s)",
+		    ip6_sprintf(ip6tbuf, &ip6->ip6_src),
+		    ifp, ifp->if_xname);
+		return (0);
+	}
 
-	CTR2(KTR_MLD, "process v2 query on ifp %p(%s)", ifp, ifp->if_xname);
+	CTR2(KTR_MLD, "input 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);
+		maxdelay = (MLD_MRC_MANT(maxdelay) | 0x1000) <<
+			   (MLD_MRC_EXP(maxdelay) + 3);
 	}
+	timer = (maxdelay * PR_FASTHZ) / MLD_TIMER_SCALE;
+	if (timer == 0)
+		timer = 1;
 
 	qrv = MLD_QRV(mld->mld_misc);
 	if (qrv < 2) {
@@ -795,10 +857,6 @@ mld_v2_input_query(struct ifnet *ifp, co
 		     (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);
@@ -806,6 +864,33 @@ mld_v2_input_query(struct ifnet *ifp, co
 	    (nsrc * sizeof(struct in6_addr)))
 		return (EMSGSIZE);
 
+	/*
+	 * Do further input validation upfront to avoid resetting timers
+	 * should we need to discard this query.
+	 */
+	if (IN6_IS_ADDR_UNSPECIFIED(&mld->mld_addr)) {
+		/*
+		 * General Queries SHOULD be directed to ff02::1.
+		 * A general query with a source list has undefined
+		 * behaviour; discard it.
+		 */
+		struct in6_addr		 dst;
+
+		dst = ip6->ip6_dst;
+		in6_clearscope(&dst);
+		if (!IN6_ARE_ADDR_EQUAL(&dst, &in6addr_linklocal_allnodes) ||
+		    nsrc > 0)
+			return (EINVAL);
+		is_general_query = 1;
+	} else {
+		/*
+		 * Embed scope ID of receiving interface in MLD query for
+		 * lookup whilst we don't hold other locks (due to KAME
+		 * locking lameness). We own this mbuf chain just now.
+		 */
+		in6_setscope(&mld->mld_addr, ifp, NULL);
+	}
+
 	IN6_MULTI_LOCK();
 	MLD_LOCK();
 	IF_ADDR_LOCK(ifp);
@@ -813,8 +898,15 @@ mld_v2_input_query(struct ifnet *ifp, co
 	mli = MLD_IFINFO(ifp);
 	KASSERT(mli != NULL, ("%s: no mld_ifinfo for ifp %p", __func__, ifp));
 
-	mld_set_version(mli, MLD_VERSION_2);
+	/*
+	 * Discard the v2 query if we're in Compatibility Mode.
+	 * The RFC is pretty clear that hosts need to stay in MLDv1 mode
+	 * until the Old Version Querier Present timer expires.
+	 */
+	if (mli->mli_version != MLD_VERSION_2)
+		goto out_locked;
 
+	mld_set_version(mli, MLD_VERSION_2);
 	mli->mli_rv = qrv;
 	mli->mli_qi = qqi;
 	mli->mli_qri = maxdelay;
@@ -822,39 +914,20 @@ mld_v2_input_query(struct ifnet *ifp, co
 	CTR4(KTR_MLD, "%s: qrv %d qi %d maxdelay %d", __func__, qrv, qqi,
 	    maxdelay);
 
-	if (IN6_IS_ADDR_UNSPECIFIED(&mld->mld_addr)) {
+	if (is_general_query) {
 		/*
 		 * MLDv2 General Query.
 		 *
 		 * Schedule a current-state report on this ifp for
 		 * all groups, possibly containing source lists.
 		 *
-		 * Strip scope ID embedded by ip6_input(). We do not need
-		 * to do this for the MLD payload.
-		 */
-		struct in6_addr dst;
-
-		dst = ip6->ip6_dst;
-		in6_clearscope(&dst);
-		if (!IN6_ARE_ADDR_EQUAL(&dst, &in6addr_linklocal_allnodes) ||
-		    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.
 		 */
+		CTR2(KTR_MLD, "process v2 general query on ifp %p(%s)",
+		    ifp, ifp->if_xname);
 		if (mli->mli_v2_timer == 0 || mli->mli_v2_timer >= timer) {
 			mli->mli_v2_timer = MLD_RANDOM_DELAY(timer);
 			V_interface_timers_running6 = 1;
@@ -890,6 +963,9 @@ mld_v2_input_query(struct ifnet *ifp, co
 		 */
 		if (mli->mli_v2_timer == 0 || mli->mli_v2_timer >= timer)
 			mld_v2_process_group_query(inm, mli, timer, m, off);
+
+		/* XXX Clear embedded scope ID as userland won't expect it. */
+		in6_clearscope(&mld->mld_addr);
 	}
 
 out_locked:
@@ -1017,27 +1093,57 @@ mld_v2_process_group_query(struct in6_mu
 /*
  * Process a received MLDv1 host membership report.
  * Assumes mld points to mld_hdr in pulled up mbuf chain.
+ *
+ * NOTE: Can't be fully const correct as we temporarily embed scope ID in
+ * mld_addr. This is OK as we own the mbuf chain.
  */
 static int
 mld_v1_input_report(struct ifnet *ifp, const struct ip6_hdr *ip6,
-    const struct mld_hdr *mld)
+    /*const*/ struct mld_hdr *mld)
 {
+	struct in6_addr		 src, dst;
 	struct in6_ifaddr	*ia;
 	struct in6_multi	*inm;
-	struct in6_addr		 src, dst;
 #ifdef KTR
 	char			 ip6tbuf[INET6_ADDRSTRLEN];
 #endif
 
+	if (!mld_v1enable) {
+		CTR3(KTR_MLD, "ignore v1 report %s on ifp %p(%s)",
+		    ip6_sprintf(ip6tbuf, &mld->mld_addr),
+		    ifp, ifp->if_xname);
+		return (0);
+	}
+
 	if (ifp->if_flags & IFF_LOOPBACK)
 		return (0);

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200905271857.n4RIvEJ2008462>