1191665Sbms/*
2191665Sbms * Copyright (c) 2009 Bruce Simpson.
3191665Sbms * All rights reserved.
4191665Sbms *
5191665Sbms * Redistribution and use in source and binary forms, with or without
6191665Sbms * modification, are permitted provided that the following conditions
7191665Sbms * are met:
8191665Sbms * 1. Redistributions of source code must retain the above copyright
9191665Sbms *    notice, this list of conditions and the following disclaimer.
10191665Sbms * 2. Redistributions in binary form must reproduce the above copyright
11191665Sbms *    notice, this list of conditions and the following disclaimer in the
12191665Sbms *    documentation and/or other materials provided with the distribution.
13191665Sbms * 3. The name of the author may not be used to endorse or promote
14191665Sbms *    products derived from this software without specific prior written
15191665Sbms *    permission.
16191665Sbms *
17191665Sbms * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18191665Sbms * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19191665Sbms * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20191665Sbms * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21191665Sbms * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22191665Sbms * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23191665Sbms * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24191665Sbms * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25191665Sbms * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26191665Sbms * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27191665Sbms * SUCH DAMAGE.
28191665Sbms */
29191665Sbms
30191665Sbms/*
31191665Sbms * IPv6 multicast socket, group, and socket option processing module.
32191672Sbms * Normative references: RFC 2292, RFC 3492, RFC 3542, RFC 3678, RFC 3810.
33191665Sbms */
34191665Sbms
35191665Sbms#include <sys/cdefs.h>
36191665Sbms__FBSDID("$FreeBSD: releng/10.3/sys/netinet6/in6_mcast.c 281911 2015-04-24 02:12:25Z ae $");
37191665Sbms
38191665Sbms#include "opt_inet6.h"
39191665Sbms
40191665Sbms#include <sys/param.h>
41191665Sbms#include <sys/systm.h>
42191665Sbms#include <sys/kernel.h>
43191665Sbms#include <sys/malloc.h>
44191665Sbms#include <sys/mbuf.h>
45191665Sbms#include <sys/protosw.h>
46191665Sbms#include <sys/socket.h>
47191665Sbms#include <sys/socketvar.h>
48191665Sbms#include <sys/protosw.h>
49191665Sbms#include <sys/sysctl.h>
50191665Sbms#include <sys/priv.h>
51191665Sbms#include <sys/ktr.h>
52191665Sbms#include <sys/tree.h>
53191665Sbms
54191665Sbms#include <net/if.h>
55191665Sbms#include <net/if_dl.h>
56191665Sbms#include <net/route.h>
57191665Sbms#include <net/vnet.h>
58191665Sbms
59191665Sbms#include <netinet/in.h>
60191665Sbms#include <netinet/in_var.h>
61191665Sbms#include <netinet6/in6_var.h>
62191665Sbms#include <netinet/ip6.h>
63191665Sbms#include <netinet/icmp6.h>
64191665Sbms#include <netinet6/ip6_var.h>
65191665Sbms#include <netinet/in_pcb.h>
66191665Sbms#include <netinet/tcp_var.h>
67191665Sbms#include <netinet6/nd6.h>
68191665Sbms#include <netinet6/mld6_var.h>
69195699Srwatson#include <netinet6/scope6_var.h>
70191665Sbms
71191665Sbms#ifndef KTR_MLD
72191665Sbms#define KTR_MLD KTR_INET6
73191665Sbms#endif
74191665Sbms
75191665Sbms#ifndef __SOCKUNION_DECLARED
76191665Sbmsunion sockunion {
77191665Sbms	struct sockaddr_storage	ss;
78191665Sbms	struct sockaddr		sa;
79191665Sbms	struct sockaddr_dl	sdl;
80191665Sbms	struct sockaddr_in6	sin6;
81191665Sbms};
82191665Sbmstypedef union sockunion sockunion_t;
83191665Sbms#define __SOCKUNION_DECLARED
84191665Sbms#endif /* __SOCKUNION_DECLARED */
85191665Sbms
86191665Sbmsstatic MALLOC_DEFINE(M_IN6MFILTER, "in6_mfilter",
87191665Sbms    "IPv6 multicast PCB-layer source filter");
88191665Sbmsstatic MALLOC_DEFINE(M_IP6MADDR, "in6_multi", "IPv6 multicast group");
89191665Sbmsstatic MALLOC_DEFINE(M_IP6MOPTS, "ip6_moptions", "IPv6 multicast options");
90191665Sbmsstatic MALLOC_DEFINE(M_IP6MSOURCE, "ip6_msource",
91191665Sbms    "IPv6 multicast MLD-layer source filter");
92191665Sbms
93191665SbmsRB_GENERATE(ip6_msource_tree, ip6_msource, im6s_link, ip6_msource_cmp);
94191665Sbms
95191665Sbms/*
96191665Sbms * Locking:
97191665Sbms * - Lock order is: Giant, INP_WLOCK, IN6_MULTI_LOCK, MLD_LOCK, IF_ADDR_LOCK.
98191665Sbms * - The IF_ADDR_LOCK is implicitly taken by in6m_lookup() earlier, however
99191665Sbms *   it can be taken by code in net/if.c also.
100191665Sbms * - ip6_moptions and in6_mfilter are covered by the INP_WLOCK.
101191665Sbms *
102191665Sbms * struct in6_multi is covered by IN6_MULTI_LOCK. There isn't strictly
103191665Sbms * any need for in6_multi itself to be virtualized -- it is bound to an ifp
104191665Sbms * anyway no matter what happens.
105191665Sbms */
106191665Sbmsstruct mtx in6_multi_mtx;
107191665SbmsMTX_SYSINIT(in6_multi_mtx, &in6_multi_mtx, "in6_multi_mtx", MTX_DEF);
108191665Sbms
109191665Sbmsstatic void	im6f_commit(struct in6_mfilter *);
110191665Sbmsstatic int	im6f_get_source(struct in6_mfilter *imf,
111191665Sbms		    const struct sockaddr_in6 *psin,
112191665Sbms		    struct in6_msource **);
113191665Sbmsstatic struct in6_msource *
114191665Sbms		im6f_graft(struct in6_mfilter *, const uint8_t,
115191665Sbms		    const struct sockaddr_in6 *);
116191665Sbmsstatic void	im6f_leave(struct in6_mfilter *);
117191665Sbmsstatic int	im6f_prune(struct in6_mfilter *, const struct sockaddr_in6 *);
118191665Sbmsstatic void	im6f_purge(struct in6_mfilter *);
119191665Sbmsstatic void	im6f_rollback(struct in6_mfilter *);
120191665Sbmsstatic void	im6f_reap(struct in6_mfilter *);
121191665Sbmsstatic int	im6o_grow(struct ip6_moptions *);
122191665Sbmsstatic size_t	im6o_match_group(const struct ip6_moptions *,
123191665Sbms		    const struct ifnet *, const struct sockaddr *);
124191665Sbmsstatic struct in6_msource *
125191665Sbms		im6o_match_source(const struct ip6_moptions *, const size_t,
126191665Sbms		    const struct sockaddr *);
127191665Sbmsstatic void	im6s_merge(struct ip6_msource *ims,
128191665Sbms		    const struct in6_msource *lims, const int rollback);
129191665Sbmsstatic int	in6_mc_get(struct ifnet *, const struct in6_addr *,
130191665Sbms		    struct in6_multi **);
131191665Sbmsstatic int	in6m_get_source(struct in6_multi *inm,
132191665Sbms		    const struct in6_addr *addr, const int noalloc,
133191665Sbms		    struct ip6_msource **pims);
134259983Sdim#ifdef KTR
135191665Sbmsstatic int	in6m_is_ifp_detached(const struct in6_multi *);
136259983Sdim#endif
137191665Sbmsstatic int	in6m_merge(struct in6_multi *, /*const*/ struct in6_mfilter *);
138191665Sbmsstatic void	in6m_purge(struct in6_multi *);
139191665Sbmsstatic void	in6m_reap(struct in6_multi *);
140191665Sbmsstatic struct ip6_moptions *
141191665Sbms		in6p_findmoptions(struct inpcb *);
142191665Sbmsstatic int	in6p_get_source_filters(struct inpcb *, struct sockopt *);
143191665Sbmsstatic int	in6p_join_group(struct inpcb *, struct sockopt *);
144191665Sbmsstatic int	in6p_leave_group(struct inpcb *, struct sockopt *);
145191672Sbmsstatic struct ifnet *
146191672Sbms		in6p_lookup_mcast_ifp(const struct inpcb *,
147191672Sbms		    const struct sockaddr_in6 *);
148191665Sbmsstatic int	in6p_block_unblock_source(struct inpcb *, struct sockopt *);
149191665Sbmsstatic int	in6p_set_multicast_if(struct inpcb *, struct sockopt *);
150191665Sbmsstatic int	in6p_set_source_filters(struct inpcb *, struct sockopt *);
151191665Sbmsstatic int	sysctl_ip6_mcast_filters(SYSCTL_HANDLER_ARGS);
152191665Sbms
153191665SbmsSYSCTL_DECL(_net_inet6_ip6);	/* XXX Not in any common header. */
154191665Sbms
155227309Sedstatic SYSCTL_NODE(_net_inet6_ip6, OID_AUTO, mcast, CTLFLAG_RW, 0,
156227309Sed    "IPv6 multicast");
157191665Sbms
158191665Sbmsstatic u_long in6_mcast_maxgrpsrc = IPV6_MAX_GROUP_SRC_FILTER;
159191665SbmsSYSCTL_ULONG(_net_inet6_ip6_mcast, OID_AUTO, maxgrpsrc,
160191665Sbms    CTLFLAG_RW | CTLFLAG_TUN, &in6_mcast_maxgrpsrc, 0,
161191665Sbms    "Max source filters per group");
162191665SbmsTUNABLE_ULONG("net.inet6.ip6.mcast.maxgrpsrc", &in6_mcast_maxgrpsrc);
163191665Sbms
164191665Sbmsstatic u_long in6_mcast_maxsocksrc = IPV6_MAX_SOCK_SRC_FILTER;
165191665SbmsSYSCTL_ULONG(_net_inet6_ip6_mcast, OID_AUTO, maxsocksrc,
166191665Sbms    CTLFLAG_RW | CTLFLAG_TUN, &in6_mcast_maxsocksrc, 0,
167191665Sbms    "Max source filters per socket");
168191665SbmsTUNABLE_ULONG("net.inet6.ip6.mcast.maxsocksrc", &in6_mcast_maxsocksrc);
169191665Sbms
170191665Sbms/* TODO Virtualize this switch. */
171191665Sbmsint in6_mcast_loop = IPV6_DEFAULT_MULTICAST_LOOP;
172191665SbmsSYSCTL_INT(_net_inet6_ip6_mcast, OID_AUTO, loop, CTLFLAG_RW | CTLFLAG_TUN,
173191665Sbms    &in6_mcast_loop, 0, "Loopback multicast datagrams by default");
174191665SbmsTUNABLE_INT("net.inet6.ip6.mcast.loop", &in6_mcast_loop);
175191665Sbms
176227309Sedstatic SYSCTL_NODE(_net_inet6_ip6_mcast, OID_AUTO, filters,
177191665Sbms    CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_ip6_mcast_filters,
178191665Sbms    "Per-interface stack-wide source filters");
179191665Sbms
180259983Sdim#ifdef KTR
181191665Sbms/*
182191665Sbms * Inline function which wraps assertions for a valid ifp.
183191665Sbms * The ifnet layer will set the ifma's ifp pointer to NULL if the ifp
184191665Sbms * is detached.
185191665Sbms */
186191665Sbmsstatic int __inline
187191665Sbmsin6m_is_ifp_detached(const struct in6_multi *inm)
188191665Sbms{
189191665Sbms	struct ifnet *ifp;
190191665Sbms
191191665Sbms	KASSERT(inm->in6m_ifma != NULL, ("%s: no ifma", __func__));
192191665Sbms	ifp = inm->in6m_ifma->ifma_ifp;
193191665Sbms	if (ifp != NULL) {
194191665Sbms		/*
195191665Sbms		 * Sanity check that network-layer notion of ifp is the
196191665Sbms		 * same as that of link-layer.
197191665Sbms		 */
198191665Sbms		KASSERT(inm->in6m_ifp == ifp, ("%s: bad ifp", __func__));
199191665Sbms	}
200191665Sbms
201191665Sbms	return (ifp == NULL);
202191665Sbms}
203259983Sdim#endif
204191665Sbms
205191665Sbms/*
206191665Sbms * Initialize an in6_mfilter structure to a known state at t0, t1
207191665Sbms * with an empty source filter list.
208191665Sbms */
209191665Sbmsstatic __inline void
210191665Sbmsim6f_init(struct in6_mfilter *imf, const int st0, const int st1)
211191665Sbms{
212191665Sbms	memset(imf, 0, sizeof(struct in6_mfilter));
213191665Sbms	RB_INIT(&imf->im6f_sources);
214191665Sbms	imf->im6f_st[0] = st0;
215191665Sbms	imf->im6f_st[1] = st1;
216191665Sbms}
217191665Sbms
218191665Sbms/*
219191665Sbms * Resize the ip6_moptions vector to the next power-of-two minus 1.
220191665Sbms * May be called with locks held; do not sleep.
221191665Sbms */
222191665Sbmsstatic int
223191665Sbmsim6o_grow(struct ip6_moptions *imo)
224191665Sbms{
225191665Sbms	struct in6_multi	**nmships;
226191665Sbms	struct in6_multi	**omships;
227191665Sbms	struct in6_mfilter	 *nmfilters;
228191665Sbms	struct in6_mfilter	 *omfilters;
229191665Sbms	size_t			  idx;
230191665Sbms	size_t			  newmax;
231191665Sbms	size_t			  oldmax;
232191665Sbms
233191665Sbms	nmships = NULL;
234191665Sbms	nmfilters = NULL;
235191665Sbms	omships = imo->im6o_membership;
236191665Sbms	omfilters = imo->im6o_mfilters;
237191665Sbms	oldmax = imo->im6o_max_memberships;
238191665Sbms	newmax = ((oldmax + 1) * 2) - 1;
239191665Sbms
240191665Sbms	if (newmax <= IPV6_MAX_MEMBERSHIPS) {
241191665Sbms		nmships = (struct in6_multi **)realloc(omships,
242191665Sbms		    sizeof(struct in6_multi *) * newmax, M_IP6MOPTS, M_NOWAIT);
243191665Sbms		nmfilters = (struct in6_mfilter *)realloc(omfilters,
244191665Sbms		    sizeof(struct in6_mfilter) * newmax, M_IN6MFILTER,
245191665Sbms		    M_NOWAIT);
246191665Sbms		if (nmships != NULL && nmfilters != NULL) {
247191665Sbms			/* Initialize newly allocated source filter heads. */
248191665Sbms			for (idx = oldmax; idx < newmax; idx++) {
249191665Sbms				im6f_init(&nmfilters[idx], MCAST_UNDEFINED,
250191665Sbms				    MCAST_EXCLUDE);
251191665Sbms			}
252191665Sbms			imo->im6o_max_memberships = newmax;
253191665Sbms			imo->im6o_membership = nmships;
254191665Sbms			imo->im6o_mfilters = nmfilters;
255191665Sbms		}
256191665Sbms	}
257191665Sbms
258191665Sbms	if (nmships == NULL || nmfilters == NULL) {
259191665Sbms		if (nmships != NULL)
260191665Sbms			free(nmships, M_IP6MOPTS);
261191665Sbms		if (nmfilters != NULL)
262191665Sbms			free(nmfilters, M_IN6MFILTER);
263191665Sbms		return (ETOOMANYREFS);
264191665Sbms	}
265191665Sbms
266191665Sbms	return (0);
267191665Sbms}
268191665Sbms
269191665Sbms/*
270191665Sbms * Find an IPv6 multicast group entry for this ip6_moptions instance
271191665Sbms * which matches the specified group, and optionally an interface.
272191665Sbms * Return its index into the array, or -1 if not found.
273191665Sbms */
274191665Sbmsstatic size_t
275191665Sbmsim6o_match_group(const struct ip6_moptions *imo, const struct ifnet *ifp,
276191665Sbms    const struct sockaddr *group)
277191665Sbms{
278191665Sbms	const struct sockaddr_in6 *gsin6;
279191665Sbms	struct in6_multi	**pinm;
280191665Sbms	int		  idx;
281191665Sbms	int		  nmships;
282191665Sbms
283191665Sbms	gsin6 = (const struct sockaddr_in6 *)group;
284191665Sbms
285191665Sbms	/* The im6o_membership array may be lazy allocated. */
286191665Sbms	if (imo->im6o_membership == NULL || imo->im6o_num_memberships == 0)
287191665Sbms		return (-1);
288191665Sbms
289191665Sbms	nmships = imo->im6o_num_memberships;
290191665Sbms	pinm = &imo->im6o_membership[0];
291191665Sbms	for (idx = 0; idx < nmships; idx++, pinm++) {
292191665Sbms		if (*pinm == NULL)
293191665Sbms			continue;
294191665Sbms		if ((ifp == NULL || ((*pinm)->in6m_ifp == ifp)) &&
295191665Sbms		    IN6_ARE_ADDR_EQUAL(&(*pinm)->in6m_addr,
296191665Sbms		    &gsin6->sin6_addr)) {
297191665Sbms			break;
298191665Sbms		}
299191665Sbms	}
300191665Sbms	if (idx >= nmships)
301191665Sbms		idx = -1;
302191665Sbms
303191665Sbms	return (idx);
304191665Sbms}
305191665Sbms
306191665Sbms/*
307191665Sbms * Find an IPv6 multicast source entry for this imo which matches
308191665Sbms * the given group index for this socket, and source address.
309191665Sbms *
310192923Sbms * XXX TODO: The scope ID, if present in src, is stripped before
311192923Sbms * any comparison. We SHOULD enforce scope/zone checks where the source
312192923Sbms * filter entry has a link scope.
313192923Sbms *
314191665Sbms * NOTE: This does not check if the entry is in-mode, merely if
315191665Sbms * it exists, which may not be the desired behaviour.
316191665Sbms */
317191665Sbmsstatic struct in6_msource *
318191665Sbmsim6o_match_source(const struct ip6_moptions *imo, const size_t gidx,
319191665Sbms    const struct sockaddr *src)
320191665Sbms{
321191665Sbms	struct ip6_msource	 find;
322191665Sbms	struct in6_mfilter	*imf;
323191665Sbms	struct ip6_msource	*ims;
324191665Sbms	const sockunion_t	*psa;
325191665Sbms
326191665Sbms	KASSERT(src->sa_family == AF_INET6, ("%s: !AF_INET6", __func__));
327191665Sbms	KASSERT(gidx != -1 && gidx < imo->im6o_num_memberships,
328191665Sbms	    ("%s: invalid index %d\n", __func__, (int)gidx));
329191665Sbms
330191665Sbms	/* The im6o_mfilters array may be lazy allocated. */
331191665Sbms	if (imo->im6o_mfilters == NULL)
332191665Sbms		return (NULL);
333191665Sbms	imf = &imo->im6o_mfilters[gidx];
334191665Sbms
335191665Sbms	psa = (const sockunion_t *)src;
336191665Sbms	find.im6s_addr = psa->sin6.sin6_addr;
337192923Sbms	in6_clearscope(&find.im6s_addr);		/* XXX */
338191665Sbms	ims = RB_FIND(ip6_msource_tree, &imf->im6f_sources, &find);
339191665Sbms
340191665Sbms	return ((struct in6_msource *)ims);
341191665Sbms}
342191665Sbms
343191665Sbms/*
344191665Sbms * Perform filtering for multicast datagrams on a socket by group and source.
345191665Sbms *
346191665Sbms * Returns 0 if a datagram should be allowed through, or various error codes
347191665Sbms * if the socket was not a member of the group, or the source was muted, etc.
348191665Sbms */
349191665Sbmsint
350191665Sbmsim6o_mc_filter(const struct ip6_moptions *imo, const struct ifnet *ifp,
351191665Sbms    const struct sockaddr *group, const struct sockaddr *src)
352191665Sbms{
353191665Sbms	size_t gidx;
354191665Sbms	struct in6_msource *ims;
355191665Sbms	int mode;
356191665Sbms
357191665Sbms	KASSERT(ifp != NULL, ("%s: null ifp", __func__));
358191665Sbms
359191665Sbms	gidx = im6o_match_group(imo, ifp, group);
360191665Sbms	if (gidx == -1)
361191665Sbms		return (MCAST_NOTGMEMBER);
362191665Sbms
363191665Sbms	/*
364191665Sbms	 * Check if the source was included in an (S,G) join.
365191665Sbms	 * Allow reception on exclusive memberships by default,
366191665Sbms	 * reject reception on inclusive memberships by default.
367191665Sbms	 * Exclude source only if an in-mode exclude filter exists.
368191665Sbms	 * Include source only if an in-mode include filter exists.
369191665Sbms	 * NOTE: We are comparing group state here at MLD t1 (now)
370191665Sbms	 * with socket-layer t0 (since last downcall).
371191665Sbms	 */
372191665Sbms	mode = imo->im6o_mfilters[gidx].im6f_st[1];
373191665Sbms	ims = im6o_match_source(imo, gidx, src);
374191665Sbms
375191665Sbms	if ((ims == NULL && mode == MCAST_INCLUDE) ||
376191665Sbms	    (ims != NULL && ims->im6sl_st[0] != mode))
377191665Sbms		return (MCAST_NOTSMEMBER);
378191665Sbms
379191665Sbms	return (MCAST_PASS);
380191665Sbms}
381191665Sbms
382191665Sbms/*
383191665Sbms * Find and return a reference to an in6_multi record for (ifp, group),
384191665Sbms * and bump its reference count.
385191665Sbms * If one does not exist, try to allocate it, and update link-layer multicast
386191665Sbms * filters on ifp to listen for group.
387191665Sbms * Assumes the IN6_MULTI lock is held across the call.
388191665Sbms * Return 0 if successful, otherwise return an appropriate error code.
389191665Sbms */
390191665Sbmsstatic int
391191665Sbmsin6_mc_get(struct ifnet *ifp, const struct in6_addr *group,
392191665Sbms    struct in6_multi **pinm)
393191665Sbms{
394191665Sbms	struct sockaddr_in6	 gsin6;
395191665Sbms	struct ifmultiaddr	*ifma;
396191665Sbms	struct in6_multi	*inm;
397191665Sbms	int			 error;
398191665Sbms
399191665Sbms	error = 0;
400191665Sbms
401191665Sbms	/*
402191665Sbms	 * XXX: Accesses to ifma_protospec must be covered by IF_ADDR_LOCK;
403191665Sbms	 * if_addmulti() takes this mutex itself, so we must drop and
404191665Sbms	 * re-acquire around the call.
405191665Sbms	 */
406191665Sbms	IN6_MULTI_LOCK_ASSERT();
407229621Sjhb	IF_ADDR_WLOCK(ifp);
408191665Sbms
409191665Sbms	inm = in6m_lookup_locked(ifp, group);
410191665Sbms	if (inm != NULL) {
411191665Sbms		/*
412191665Sbms		 * If we already joined this group, just bump the
413191665Sbms		 * refcount and return it.
414191665Sbms		 */
415191665Sbms		KASSERT(inm->in6m_refcount >= 1,
416191665Sbms		    ("%s: bad refcount %d", __func__, inm->in6m_refcount));
417191665Sbms		++inm->in6m_refcount;
418191665Sbms		*pinm = inm;
419191665Sbms		goto out_locked;
420191665Sbms	}
421191665Sbms
422191665Sbms	memset(&gsin6, 0, sizeof(gsin6));
423191665Sbms	gsin6.sin6_family = AF_INET6;
424191665Sbms	gsin6.sin6_len = sizeof(struct sockaddr_in6);
425191665Sbms	gsin6.sin6_addr = *group;
426191665Sbms
427191665Sbms	/*
428191665Sbms	 * Check if a link-layer group is already associated
429191665Sbms	 * with this network-layer group on the given ifnet.
430191665Sbms	 */
431229621Sjhb	IF_ADDR_WUNLOCK(ifp);
432191665Sbms	error = if_addmulti(ifp, (struct sockaddr *)&gsin6, &ifma);
433191665Sbms	if (error != 0)
434191665Sbms		return (error);
435229621Sjhb	IF_ADDR_WLOCK(ifp);
436191665Sbms
437191665Sbms	/*
438191665Sbms	 * If something other than netinet6 is occupying the link-layer
439191665Sbms	 * group, print a meaningful error message and back out of
440191665Sbms	 * the allocation.
441191665Sbms	 * Otherwise, bump the refcount on the existing network-layer
442191665Sbms	 * group association and return it.
443191665Sbms	 */
444191665Sbms	if (ifma->ifma_protospec != NULL) {
445191665Sbms		inm = (struct in6_multi *)ifma->ifma_protospec;
446191665Sbms#ifdef INVARIANTS
447191665Sbms		KASSERT(ifma->ifma_addr != NULL, ("%s: no ifma_addr",
448191665Sbms		    __func__));
449191665Sbms		KASSERT(ifma->ifma_addr->sa_family == AF_INET6,
450191665Sbms		    ("%s: ifma not AF_INET6", __func__));
451191665Sbms		KASSERT(inm != NULL, ("%s: no ifma_protospec", __func__));
452191665Sbms		if (inm->in6m_ifma != ifma || inm->in6m_ifp != ifp ||
453191665Sbms		    !IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, group))
454191665Sbms			panic("%s: ifma %p is inconsistent with %p (%p)",
455191665Sbms			    __func__, ifma, inm, group);
456191665Sbms#endif
457191665Sbms		++inm->in6m_refcount;
458191665Sbms		*pinm = inm;
459191665Sbms		goto out_locked;
460191665Sbms	}
461191665Sbms
462229621Sjhb	IF_ADDR_WLOCK_ASSERT(ifp);
463191665Sbms
464191665Sbms	/*
465191665Sbms	 * A new in6_multi record is needed; allocate and initialize it.
466191665Sbms	 * We DO NOT perform an MLD join as the in6_ layer may need to
467191665Sbms	 * push an initial source list down to MLD to support SSM.
468191665Sbms	 *
469191665Sbms	 * The initial source filter state is INCLUDE, {} as per the RFC.
470191665Sbms	 * Pending state-changes per group are subject to a bounds check.
471191665Sbms	 */
472191665Sbms	inm = malloc(sizeof(*inm), M_IP6MADDR, M_NOWAIT | M_ZERO);
473191665Sbms	if (inm == NULL) {
474191665Sbms		if_delmulti_ifma(ifma);
475191665Sbms		error = ENOMEM;
476191665Sbms		goto out_locked;
477191665Sbms	}
478191665Sbms	inm->in6m_addr = *group;
479191665Sbms	inm->in6m_ifp = ifp;
480191665Sbms	inm->in6m_mli = MLD_IFINFO(ifp);
481191665Sbms	inm->in6m_ifma = ifma;
482191665Sbms	inm->in6m_refcount = 1;
483191665Sbms	inm->in6m_state = MLD_NOT_MEMBER;
484191665Sbms	IFQ_SET_MAXLEN(&inm->in6m_scq, MLD_MAX_STATE_CHANGES);
485191665Sbms
486191665Sbms	inm->in6m_st[0].iss_fmode = MCAST_UNDEFINED;
487191665Sbms	inm->in6m_st[1].iss_fmode = MCAST_UNDEFINED;
488191665Sbms	RB_INIT(&inm->in6m_srcs);
489191665Sbms
490191665Sbms	ifma->ifma_protospec = inm;
491191665Sbms	*pinm = inm;
492191665Sbms
493191665Sbmsout_locked:
494229621Sjhb	IF_ADDR_WUNLOCK(ifp);
495191665Sbms	return (error);
496191665Sbms}
497191665Sbms
498191665Sbms/*
499191665Sbms * Drop a reference to an in6_multi record.
500191665Sbms *
501191665Sbms * If the refcount drops to 0, free the in6_multi record and
502191665Sbms * delete the underlying link-layer membership.
503191665Sbms */
504191665Sbmsvoid
505191665Sbmsin6m_release_locked(struct in6_multi *inm)
506191665Sbms{
507191665Sbms	struct ifmultiaddr *ifma;
508191665Sbms
509191665Sbms	IN6_MULTI_LOCK_ASSERT();
510191665Sbms
511191665Sbms	CTR2(KTR_MLD, "%s: refcount is %d", __func__, inm->in6m_refcount);
512191665Sbms
513191665Sbms	if (--inm->in6m_refcount > 0) {
514191665Sbms		CTR2(KTR_MLD, "%s: refcount is now %d", __func__,
515191665Sbms		    inm->in6m_refcount);
516191665Sbms		return;
517191665Sbms	}
518191665Sbms
519191665Sbms	CTR2(KTR_MLD, "%s: freeing inm %p", __func__, inm);
520191665Sbms
521191665Sbms	ifma = inm->in6m_ifma;
522191665Sbms
523191665Sbms	/* XXX this access is not covered by IF_ADDR_LOCK */
524191665Sbms	CTR2(KTR_MLD, "%s: purging ifma %p", __func__, ifma);
525191665Sbms	KASSERT(ifma->ifma_protospec == inm,
526191665Sbms	    ("%s: ifma_protospec != inm", __func__));
527191665Sbms	ifma->ifma_protospec = NULL;
528191665Sbms
529191665Sbms	in6m_purge(inm);
530191665Sbms
531191665Sbms	free(inm, M_IP6MADDR);
532191665Sbms
533191665Sbms	if_delmulti_ifma(ifma);
534191665Sbms}
535191665Sbms
536191665Sbms/*
537191665Sbms * Clear recorded source entries for a group.
538191665Sbms * Used by the MLD code. Caller must hold the IN6_MULTI lock.
539191665Sbms * FIXME: Should reap.
540191665Sbms */
541191665Sbmsvoid
542191665Sbmsin6m_clear_recorded(struct in6_multi *inm)
543191665Sbms{
544191665Sbms	struct ip6_msource	*ims;
545191665Sbms
546191665Sbms	IN6_MULTI_LOCK_ASSERT();
547191665Sbms
548191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &inm->in6m_srcs) {
549191665Sbms		if (ims->im6s_stp) {
550191665Sbms			ims->im6s_stp = 0;
551191665Sbms			--inm->in6m_st[1].iss_rec;
552191665Sbms		}
553191665Sbms	}
554191665Sbms	KASSERT(inm->in6m_st[1].iss_rec == 0,
555191665Sbms	    ("%s: iss_rec %d not 0", __func__, inm->in6m_st[1].iss_rec));
556191665Sbms}
557191665Sbms
558191665Sbms/*
559191665Sbms * Record a source as pending for a Source-Group MLDv2 query.
560191665Sbms * This lives here as it modifies the shared tree.
561191665Sbms *
562191665Sbms * inm is the group descriptor.
563191665Sbms * naddr is the address of the source to record in network-byte order.
564191665Sbms *
565191665Sbms * If the net.inet6.mld.sgalloc sysctl is non-zero, we will
566191665Sbms * lazy-allocate a source node in response to an SG query.
567191665Sbms * Otherwise, no allocation is performed. This saves some memory
568191665Sbms * with the trade-off that the source will not be reported to the
569191665Sbms * router if joined in the window between the query response and
570191665Sbms * the group actually being joined on the local host.
571191665Sbms *
572191665Sbms * VIMAGE: XXX: Currently the mld_sgalloc feature has been removed.
573191665Sbms * This turns off the allocation of a recorded source entry if
574191665Sbms * the group has not been joined.
575191665Sbms *
576191665Sbms * Return 0 if the source didn't exist or was already marked as recorded.
577191665Sbms * Return 1 if the source was marked as recorded by this function.
578191665Sbms * Return <0 if any error occured (negated errno code).
579191665Sbms */
580191665Sbmsint
581191665Sbmsin6m_record_source(struct in6_multi *inm, const struct in6_addr *addr)
582191665Sbms{
583191665Sbms	struct ip6_msource	 find;
584191665Sbms	struct ip6_msource	*ims, *nims;
585191665Sbms
586191665Sbms	IN6_MULTI_LOCK_ASSERT();
587191665Sbms
588191665Sbms	find.im6s_addr = *addr;
589191665Sbms	ims = RB_FIND(ip6_msource_tree, &inm->in6m_srcs, &find);
590191665Sbms	if (ims && ims->im6s_stp)
591191665Sbms		return (0);
592191665Sbms	if (ims == NULL) {
593191665Sbms		if (inm->in6m_nsrc == in6_mcast_maxgrpsrc)
594191665Sbms			return (-ENOSPC);
595191665Sbms		nims = malloc(sizeof(struct ip6_msource), M_IP6MSOURCE,
596191665Sbms		    M_NOWAIT | M_ZERO);
597191665Sbms		if (nims == NULL)
598191665Sbms			return (-ENOMEM);
599191665Sbms		nims->im6s_addr = find.im6s_addr;
600191665Sbms		RB_INSERT(ip6_msource_tree, &inm->in6m_srcs, nims);
601191665Sbms		++inm->in6m_nsrc;
602191665Sbms		ims = nims;
603191665Sbms	}
604191665Sbms
605191665Sbms	/*
606191665Sbms	 * Mark the source as recorded and update the recorded
607191665Sbms	 * source count.
608191665Sbms	 */
609191665Sbms	++ims->im6s_stp;
610191665Sbms	++inm->in6m_st[1].iss_rec;
611191665Sbms
612191665Sbms	return (1);
613191665Sbms}
614191665Sbms
615191665Sbms/*
616191665Sbms * Return a pointer to an in6_msource owned by an in6_mfilter,
617191665Sbms * given its source address.
618191665Sbms * Lazy-allocate if needed. If this is a new entry its filter state is
619191665Sbms * undefined at t0.
620191665Sbms *
621191665Sbms * imf is the filter set being modified.
622191665Sbms * addr is the source address.
623191665Sbms *
624191665Sbms * SMPng: May be called with locks held; malloc must not block.
625191665Sbms */
626191665Sbmsstatic int
627191665Sbmsim6f_get_source(struct in6_mfilter *imf, const struct sockaddr_in6 *psin,
628191665Sbms    struct in6_msource **plims)
629191665Sbms{
630191665Sbms	struct ip6_msource	 find;
631191665Sbms	struct ip6_msource	*ims, *nims;
632191665Sbms	struct in6_msource	*lims;
633191665Sbms	int			 error;
634191665Sbms
635191665Sbms	error = 0;
636191665Sbms	ims = NULL;
637191665Sbms	lims = NULL;
638191665Sbms
639191665Sbms	find.im6s_addr = psin->sin6_addr;
640191665Sbms	ims = RB_FIND(ip6_msource_tree, &imf->im6f_sources, &find);
641191665Sbms	lims = (struct in6_msource *)ims;
642191665Sbms	if (lims == NULL) {
643191665Sbms		if (imf->im6f_nsrc == in6_mcast_maxsocksrc)
644191665Sbms			return (ENOSPC);
645191665Sbms		nims = malloc(sizeof(struct in6_msource), M_IN6MFILTER,
646191665Sbms		    M_NOWAIT | M_ZERO);
647191665Sbms		if (nims == NULL)
648191665Sbms			return (ENOMEM);
649191665Sbms		lims = (struct in6_msource *)nims;
650191665Sbms		lims->im6s_addr = find.im6s_addr;
651191665Sbms		lims->im6sl_st[0] = MCAST_UNDEFINED;
652191665Sbms		RB_INSERT(ip6_msource_tree, &imf->im6f_sources, nims);
653191665Sbms		++imf->im6f_nsrc;
654191665Sbms	}
655191665Sbms
656191665Sbms	*plims = lims;
657191665Sbms
658191665Sbms	return (error);
659191665Sbms}
660191665Sbms
661191665Sbms/*
662191665Sbms * Graft a source entry into an existing socket-layer filter set,
663191665Sbms * maintaining any required invariants and checking allocations.
664191665Sbms *
665191665Sbms * The source is marked as being in the new filter mode at t1.
666191665Sbms *
667191665Sbms * Return the pointer to the new node, otherwise return NULL.
668191665Sbms */
669191665Sbmsstatic struct in6_msource *
670191665Sbmsim6f_graft(struct in6_mfilter *imf, const uint8_t st1,
671191665Sbms    const struct sockaddr_in6 *psin)
672191665Sbms{
673191665Sbms	struct ip6_msource	*nims;
674191665Sbms	struct in6_msource	*lims;
675191665Sbms
676191665Sbms	nims = malloc(sizeof(struct in6_msource), M_IN6MFILTER,
677191665Sbms	    M_NOWAIT | M_ZERO);
678191665Sbms	if (nims == NULL)
679191665Sbms		return (NULL);
680191665Sbms	lims = (struct in6_msource *)nims;
681191665Sbms	lims->im6s_addr = psin->sin6_addr;
682191665Sbms	lims->im6sl_st[0] = MCAST_UNDEFINED;
683191665Sbms	lims->im6sl_st[1] = st1;
684191665Sbms	RB_INSERT(ip6_msource_tree, &imf->im6f_sources, nims);
685191665Sbms	++imf->im6f_nsrc;
686191665Sbms
687191665Sbms	return (lims);
688191665Sbms}
689191665Sbms
690191665Sbms/*
691191665Sbms * Prune a source entry from an existing socket-layer filter set,
692191665Sbms * maintaining any required invariants and checking allocations.
693191665Sbms *
694191665Sbms * The source is marked as being left at t1, it is not freed.
695191665Sbms *
696191665Sbms * Return 0 if no error occurred, otherwise return an errno value.
697191665Sbms */
698191665Sbmsstatic int
699191665Sbmsim6f_prune(struct in6_mfilter *imf, const struct sockaddr_in6 *psin)
700191665Sbms{
701191665Sbms	struct ip6_msource	 find;
702191665Sbms	struct ip6_msource	*ims;
703191665Sbms	struct in6_msource	*lims;
704191665Sbms
705191665Sbms	find.im6s_addr = psin->sin6_addr;
706191665Sbms	ims = RB_FIND(ip6_msource_tree, &imf->im6f_sources, &find);
707191665Sbms	if (ims == NULL)
708191665Sbms		return (ENOENT);
709191665Sbms	lims = (struct in6_msource *)ims;
710191665Sbms	lims->im6sl_st[1] = MCAST_UNDEFINED;
711191665Sbms	return (0);
712191665Sbms}
713191665Sbms
714191665Sbms/*
715191665Sbms * Revert socket-layer filter set deltas at t1 to t0 state.
716191665Sbms */
717191665Sbmsstatic void
718191665Sbmsim6f_rollback(struct in6_mfilter *imf)
719191665Sbms{
720191665Sbms	struct ip6_msource	*ims, *tims;
721191665Sbms	struct in6_msource	*lims;
722191665Sbms
723191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &imf->im6f_sources, tims) {
724191665Sbms		lims = (struct in6_msource *)ims;
725191665Sbms		if (lims->im6sl_st[0] == lims->im6sl_st[1]) {
726191665Sbms			/* no change at t1 */
727191665Sbms			continue;
728191665Sbms		} else if (lims->im6sl_st[0] != MCAST_UNDEFINED) {
729191665Sbms			/* revert change to existing source at t1 */
730191665Sbms			lims->im6sl_st[1] = lims->im6sl_st[0];
731191665Sbms		} else {
732191665Sbms			/* revert source added t1 */
733191665Sbms			CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
734191665Sbms			RB_REMOVE(ip6_msource_tree, &imf->im6f_sources, ims);
735191665Sbms			free(ims, M_IN6MFILTER);
736191665Sbms			imf->im6f_nsrc--;
737191665Sbms		}
738191665Sbms	}
739191665Sbms	imf->im6f_st[1] = imf->im6f_st[0];
740191665Sbms}
741191665Sbms
742191665Sbms/*
743191665Sbms * Mark socket-layer filter set as INCLUDE {} at t1.
744191665Sbms */
745191665Sbmsstatic void
746191665Sbmsim6f_leave(struct in6_mfilter *imf)
747191665Sbms{
748191665Sbms	struct ip6_msource	*ims;
749191665Sbms	struct in6_msource	*lims;
750191665Sbms
751191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
752191665Sbms		lims = (struct in6_msource *)ims;
753191665Sbms		lims->im6sl_st[1] = MCAST_UNDEFINED;
754191665Sbms	}
755191665Sbms	imf->im6f_st[1] = MCAST_INCLUDE;
756191665Sbms}
757191665Sbms
758191665Sbms/*
759191665Sbms * Mark socket-layer filter set deltas as committed.
760191665Sbms */
761191665Sbmsstatic void
762191665Sbmsim6f_commit(struct in6_mfilter *imf)
763191665Sbms{
764191665Sbms	struct ip6_msource	*ims;
765191665Sbms	struct in6_msource	*lims;
766191665Sbms
767191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
768191665Sbms		lims = (struct in6_msource *)ims;
769191665Sbms		lims->im6sl_st[0] = lims->im6sl_st[1];
770191665Sbms	}
771191665Sbms	imf->im6f_st[0] = imf->im6f_st[1];
772191665Sbms}
773191665Sbms
774191665Sbms/*
775191665Sbms * Reap unreferenced sources from socket-layer filter set.
776191665Sbms */
777191665Sbmsstatic void
778191665Sbmsim6f_reap(struct in6_mfilter *imf)
779191665Sbms{
780191665Sbms	struct ip6_msource	*ims, *tims;
781191665Sbms	struct in6_msource	*lims;
782191665Sbms
783191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &imf->im6f_sources, tims) {
784191665Sbms		lims = (struct in6_msource *)ims;
785191665Sbms		if ((lims->im6sl_st[0] == MCAST_UNDEFINED) &&
786191665Sbms		    (lims->im6sl_st[1] == MCAST_UNDEFINED)) {
787191665Sbms			CTR2(KTR_MLD, "%s: free lims %p", __func__, ims);
788191665Sbms			RB_REMOVE(ip6_msource_tree, &imf->im6f_sources, ims);
789191665Sbms			free(ims, M_IN6MFILTER);
790191665Sbms			imf->im6f_nsrc--;
791191665Sbms		}
792191665Sbms	}
793191665Sbms}
794191665Sbms
795191665Sbms/*
796191665Sbms * Purge socket-layer filter set.
797191665Sbms */
798191665Sbmsstatic void
799191665Sbmsim6f_purge(struct in6_mfilter *imf)
800191665Sbms{
801191665Sbms	struct ip6_msource	*ims, *tims;
802191665Sbms
803191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &imf->im6f_sources, tims) {
804191665Sbms		CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
805191665Sbms		RB_REMOVE(ip6_msource_tree, &imf->im6f_sources, ims);
806191665Sbms		free(ims, M_IN6MFILTER);
807191665Sbms		imf->im6f_nsrc--;
808191665Sbms	}
809191665Sbms	imf->im6f_st[0] = imf->im6f_st[1] = MCAST_UNDEFINED;
810191665Sbms	KASSERT(RB_EMPTY(&imf->im6f_sources),
811191665Sbms	    ("%s: im6f_sources not empty", __func__));
812191665Sbms}
813191665Sbms
814191665Sbms/*
815191665Sbms * Look up a source filter entry for a multicast group.
816191665Sbms *
817191665Sbms * inm is the group descriptor to work with.
818191665Sbms * addr is the IPv6 address to look up.
819191665Sbms * noalloc may be non-zero to suppress allocation of sources.
820191665Sbms * *pims will be set to the address of the retrieved or allocated source.
821191665Sbms *
822191665Sbms * SMPng: NOTE: may be called with locks held.
823191665Sbms * Return 0 if successful, otherwise return a non-zero error code.
824191665Sbms */
825191665Sbmsstatic int
826191665Sbmsin6m_get_source(struct in6_multi *inm, const struct in6_addr *addr,
827191665Sbms    const int noalloc, struct ip6_msource **pims)
828191665Sbms{
829191665Sbms	struct ip6_msource	 find;
830191665Sbms	struct ip6_msource	*ims, *nims;
831191665Sbms#ifdef KTR
832191665Sbms	char			 ip6tbuf[INET6_ADDRSTRLEN];
833191665Sbms#endif
834191665Sbms
835191665Sbms	find.im6s_addr = *addr;
836191665Sbms	ims = RB_FIND(ip6_msource_tree, &inm->in6m_srcs, &find);
837191665Sbms	if (ims == NULL && !noalloc) {
838191665Sbms		if (inm->in6m_nsrc == in6_mcast_maxgrpsrc)
839191665Sbms			return (ENOSPC);
840191665Sbms		nims = malloc(sizeof(struct ip6_msource), M_IP6MSOURCE,
841191665Sbms		    M_NOWAIT | M_ZERO);
842191665Sbms		if (nims == NULL)
843191665Sbms			return (ENOMEM);
844191665Sbms		nims->im6s_addr = *addr;
845191665Sbms		RB_INSERT(ip6_msource_tree, &inm->in6m_srcs, nims);
846191665Sbms		++inm->in6m_nsrc;
847191665Sbms		ims = nims;
848191665Sbms		CTR3(KTR_MLD, "%s: allocated %s as %p", __func__,
849191665Sbms		    ip6_sprintf(ip6tbuf, addr), ims);
850191665Sbms	}
851191665Sbms
852191665Sbms	*pims = ims;
853191665Sbms	return (0);
854191665Sbms}
855191665Sbms
856191665Sbms/*
857191665Sbms * Merge socket-layer source into MLD-layer source.
858191665Sbms * If rollback is non-zero, perform the inverse of the merge.
859191665Sbms */
860191665Sbmsstatic void
861191665Sbmsim6s_merge(struct ip6_msource *ims, const struct in6_msource *lims,
862191665Sbms    const int rollback)
863191665Sbms{
864191665Sbms	int n = rollback ? -1 : 1;
865191665Sbms#ifdef KTR
866191665Sbms	char ip6tbuf[INET6_ADDRSTRLEN];
867191665Sbms
868191665Sbms	ip6_sprintf(ip6tbuf, &lims->im6s_addr);
869191665Sbms#endif
870191665Sbms
871191665Sbms	if (lims->im6sl_st[0] == MCAST_EXCLUDE) {
872191665Sbms		CTR3(KTR_MLD, "%s: t1 ex -= %d on %s", __func__, n, ip6tbuf);
873191665Sbms		ims->im6s_st[1].ex -= n;
874191665Sbms	} else if (lims->im6sl_st[0] == MCAST_INCLUDE) {
875191665Sbms		CTR3(KTR_MLD, "%s: t1 in -= %d on %s", __func__, n, ip6tbuf);
876191665Sbms		ims->im6s_st[1].in -= n;
877191665Sbms	}
878191665Sbms
879191665Sbms	if (lims->im6sl_st[1] == MCAST_EXCLUDE) {
880191665Sbms		CTR3(KTR_MLD, "%s: t1 ex += %d on %s", __func__, n, ip6tbuf);
881191665Sbms		ims->im6s_st[1].ex += n;
882191665Sbms	} else if (lims->im6sl_st[1] == MCAST_INCLUDE) {
883191665Sbms		CTR3(KTR_MLD, "%s: t1 in += %d on %s", __func__, n, ip6tbuf);
884191665Sbms		ims->im6s_st[1].in += n;
885191665Sbms	}
886191665Sbms}
887191665Sbms
888191665Sbms/*
889191665Sbms * Atomically update the global in6_multi state, when a membership's
890191665Sbms * filter list is being updated in any way.
891191665Sbms *
892191665Sbms * imf is the per-inpcb-membership group filter pointer.
893191665Sbms * A fake imf may be passed for in-kernel consumers.
894191665Sbms *
895191665Sbms * XXX This is a candidate for a set-symmetric-difference style loop
896191665Sbms * which would eliminate the repeated lookup from root of ims nodes,
897191665Sbms * as they share the same key space.
898191665Sbms *
899191665Sbms * If any error occurred this function will back out of refcounts
900191665Sbms * and return a non-zero value.
901191665Sbms */
902191665Sbmsstatic int
903191665Sbmsin6m_merge(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
904191665Sbms{
905191665Sbms	struct ip6_msource	*ims, *nims;
906191665Sbms	struct in6_msource	*lims;
907191665Sbms	int			 schanged, error;
908191665Sbms	int			 nsrc0, nsrc1;
909191665Sbms
910191665Sbms	schanged = 0;
911191665Sbms	error = 0;
912191665Sbms	nsrc1 = nsrc0 = 0;
913191665Sbms
914191665Sbms	/*
915191665Sbms	 * Update the source filters first, as this may fail.
916191665Sbms	 * Maintain count of in-mode filters at t0, t1. These are
917191665Sbms	 * used to work out if we transition into ASM mode or not.
918191665Sbms	 * Maintain a count of source filters whose state was
919191665Sbms	 * actually modified by this operation.
920191665Sbms	 */
921191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
922191665Sbms		lims = (struct in6_msource *)ims;
923191665Sbms		if (lims->im6sl_st[0] == imf->im6f_st[0]) nsrc0++;
924191665Sbms		if (lims->im6sl_st[1] == imf->im6f_st[1]) nsrc1++;
925191665Sbms		if (lims->im6sl_st[0] == lims->im6sl_st[1]) continue;
926191665Sbms		error = in6m_get_source(inm, &lims->im6s_addr, 0, &nims);
927191665Sbms		++schanged;
928191665Sbms		if (error)
929191665Sbms			break;
930191665Sbms		im6s_merge(nims, lims, 0);
931191665Sbms	}
932191665Sbms	if (error) {
933191665Sbms		struct ip6_msource *bims;
934191665Sbms
935191665Sbms		RB_FOREACH_REVERSE_FROM(ims, ip6_msource_tree, nims) {
936191665Sbms			lims = (struct in6_msource *)ims;
937191665Sbms			if (lims->im6sl_st[0] == lims->im6sl_st[1])
938191665Sbms				continue;
939191665Sbms			(void)in6m_get_source(inm, &lims->im6s_addr, 1, &bims);
940191665Sbms			if (bims == NULL)
941191665Sbms				continue;
942191665Sbms			im6s_merge(bims, lims, 1);
943191665Sbms		}
944191665Sbms		goto out_reap;
945191665Sbms	}
946191665Sbms
947191665Sbms	CTR3(KTR_MLD, "%s: imf filters in-mode: %d at t0, %d at t1",
948191665Sbms	    __func__, nsrc0, nsrc1);
949191665Sbms
950191665Sbms	/* Handle transition between INCLUDE {n} and INCLUDE {} on socket. */
951191665Sbms	if (imf->im6f_st[0] == imf->im6f_st[1] &&
952191665Sbms	    imf->im6f_st[1] == MCAST_INCLUDE) {
953191665Sbms		if (nsrc1 == 0) {
954191665Sbms			CTR1(KTR_MLD, "%s: --in on inm at t1", __func__);
955191665Sbms			--inm->in6m_st[1].iss_in;
956191665Sbms		}
957191665Sbms	}
958191665Sbms
959191665Sbms	/* Handle filter mode transition on socket. */
960191665Sbms	if (imf->im6f_st[0] != imf->im6f_st[1]) {
961191665Sbms		CTR3(KTR_MLD, "%s: imf transition %d to %d",
962191665Sbms		    __func__, imf->im6f_st[0], imf->im6f_st[1]);
963191665Sbms
964191665Sbms		if (imf->im6f_st[0] == MCAST_EXCLUDE) {
965191665Sbms			CTR1(KTR_MLD, "%s: --ex on inm at t1", __func__);
966191665Sbms			--inm->in6m_st[1].iss_ex;
967191665Sbms		} else if (imf->im6f_st[0] == MCAST_INCLUDE) {
968191665Sbms			CTR1(KTR_MLD, "%s: --in on inm at t1", __func__);
969191665Sbms			--inm->in6m_st[1].iss_in;
970191665Sbms		}
971191665Sbms
972191665Sbms		if (imf->im6f_st[1] == MCAST_EXCLUDE) {
973191665Sbms			CTR1(KTR_MLD, "%s: ex++ on inm at t1", __func__);
974191665Sbms			inm->in6m_st[1].iss_ex++;
975191665Sbms		} else if (imf->im6f_st[1] == MCAST_INCLUDE && nsrc1 > 0) {
976191665Sbms			CTR1(KTR_MLD, "%s: in++ on inm at t1", __func__);
977191665Sbms			inm->in6m_st[1].iss_in++;
978191665Sbms		}
979191665Sbms	}
980191665Sbms
981191665Sbms	/*
982191665Sbms	 * Track inm filter state in terms of listener counts.
983191665Sbms	 * If there are any exclusive listeners, stack-wide
984191665Sbms	 * membership is exclusive.
985191665Sbms	 * Otherwise, if only inclusive listeners, stack-wide is inclusive.
986191665Sbms	 * If no listeners remain, state is undefined at t1,
987191665Sbms	 * and the MLD lifecycle for this group should finish.
988191665Sbms	 */
989191665Sbms	if (inm->in6m_st[1].iss_ex > 0) {
990191665Sbms		CTR1(KTR_MLD, "%s: transition to EX", __func__);
991191665Sbms		inm->in6m_st[1].iss_fmode = MCAST_EXCLUDE;
992191665Sbms	} else if (inm->in6m_st[1].iss_in > 0) {
993191665Sbms		CTR1(KTR_MLD, "%s: transition to IN", __func__);
994191665Sbms		inm->in6m_st[1].iss_fmode = MCAST_INCLUDE;
995191665Sbms	} else {
996191665Sbms		CTR1(KTR_MLD, "%s: transition to UNDEF", __func__);
997191665Sbms		inm->in6m_st[1].iss_fmode = MCAST_UNDEFINED;
998191665Sbms	}
999191665Sbms
1000191665Sbms	/* Decrement ASM listener count on transition out of ASM mode. */
1001191665Sbms	if (imf->im6f_st[0] == MCAST_EXCLUDE && nsrc0 == 0) {
1002191665Sbms		if ((imf->im6f_st[1] != MCAST_EXCLUDE) ||
1003191665Sbms		    (imf->im6f_st[1] == MCAST_EXCLUDE && nsrc1 > 0))
1004191665Sbms			CTR1(KTR_MLD, "%s: --asm on inm at t1", __func__);
1005191665Sbms			--inm->in6m_st[1].iss_asm;
1006191665Sbms	}
1007191665Sbms
1008191665Sbms	/* Increment ASM listener count on transition to ASM mode. */
1009191665Sbms	if (imf->im6f_st[1] == MCAST_EXCLUDE && nsrc1 == 0) {
1010191665Sbms		CTR1(KTR_MLD, "%s: asm++ on inm at t1", __func__);
1011191665Sbms		inm->in6m_st[1].iss_asm++;
1012191665Sbms	}
1013191665Sbms
1014191665Sbms	CTR3(KTR_MLD, "%s: merged imf %p to inm %p", __func__, imf, inm);
1015191665Sbms	in6m_print(inm);
1016191665Sbms
1017191665Sbmsout_reap:
1018191665Sbms	if (schanged > 0) {
1019191665Sbms		CTR1(KTR_MLD, "%s: sources changed; reaping", __func__);
1020191665Sbms		in6m_reap(inm);
1021191665Sbms	}
1022191665Sbms	return (error);
1023191665Sbms}
1024191665Sbms
1025191665Sbms/*
1026191665Sbms * Mark an in6_multi's filter set deltas as committed.
1027191665Sbms * Called by MLD after a state change has been enqueued.
1028191665Sbms */
1029191665Sbmsvoid
1030191665Sbmsin6m_commit(struct in6_multi *inm)
1031191665Sbms{
1032191665Sbms	struct ip6_msource	*ims;
1033191665Sbms
1034191665Sbms	CTR2(KTR_MLD, "%s: commit inm %p", __func__, inm);
1035191665Sbms	CTR1(KTR_MLD, "%s: pre commit:", __func__);
1036191665Sbms	in6m_print(inm);
1037191665Sbms
1038191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &inm->in6m_srcs) {
1039191665Sbms		ims->im6s_st[0] = ims->im6s_st[1];
1040191665Sbms	}
1041191665Sbms	inm->in6m_st[0] = inm->in6m_st[1];
1042191665Sbms}
1043191665Sbms
1044191665Sbms/*
1045191665Sbms * Reap unreferenced nodes from an in6_multi's filter set.
1046191665Sbms */
1047191665Sbmsstatic void
1048191665Sbmsin6m_reap(struct in6_multi *inm)
1049191665Sbms{
1050191665Sbms	struct ip6_msource	*ims, *tims;
1051191665Sbms
1052191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &inm->in6m_srcs, tims) {
1053191665Sbms		if (ims->im6s_st[0].ex > 0 || ims->im6s_st[0].in > 0 ||
1054191665Sbms		    ims->im6s_st[1].ex > 0 || ims->im6s_st[1].in > 0 ||
1055191665Sbms		    ims->im6s_stp != 0)
1056191665Sbms			continue;
1057191665Sbms		CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
1058191665Sbms		RB_REMOVE(ip6_msource_tree, &inm->in6m_srcs, ims);
1059191665Sbms		free(ims, M_IP6MSOURCE);
1060191665Sbms		inm->in6m_nsrc--;
1061191665Sbms	}
1062191665Sbms}
1063191665Sbms
1064191665Sbms/*
1065191665Sbms * Purge all source nodes from an in6_multi's filter set.
1066191665Sbms */
1067191665Sbmsstatic void
1068191665Sbmsin6m_purge(struct in6_multi *inm)
1069191665Sbms{
1070191665Sbms	struct ip6_msource	*ims, *tims;
1071191665Sbms
1072191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &inm->in6m_srcs, tims) {
1073191665Sbms		CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
1074191665Sbms		RB_REMOVE(ip6_msource_tree, &inm->in6m_srcs, ims);
1075191665Sbms		free(ims, M_IP6MSOURCE);
1076191665Sbms		inm->in6m_nsrc--;
1077191665Sbms	}
1078274168Sae	/* Free state-change requests that might be queued. */
1079274168Sae	_IF_DRAIN(&inm->in6m_scq);
1080191665Sbms}
1081191665Sbms
1082191665Sbms/*
1083191665Sbms * Join a multicast address w/o sources.
1084191665Sbms * KAME compatibility entry point.
1085191665Sbms *
1086191665Sbms * SMPng: Assume no mc locks held by caller.
1087191665Sbms */
1088191665Sbmsstruct in6_multi_mship *
1089191665Sbmsin6_joingroup(struct ifnet *ifp, struct in6_addr *mcaddr,
1090191665Sbms    int *errorp, int delay)
1091191665Sbms{
1092191665Sbms	struct in6_multi_mship *imm;
1093191665Sbms	int error;
1094191665Sbms
1095191665Sbms	imm = malloc(sizeof(*imm), M_IP6MADDR, M_NOWAIT);
1096191665Sbms	if (imm == NULL) {
1097191665Sbms		*errorp = ENOBUFS;
1098191665Sbms		return (NULL);
1099191665Sbms	}
1100191665Sbms
1101191665Sbms	delay = (delay * PR_FASTHZ) / hz;
1102191665Sbms
1103191665Sbms	error = in6_mc_join(ifp, mcaddr, NULL, &imm->i6mm_maddr, delay);
1104191665Sbms	if (error) {
1105191665Sbms		*errorp = error;
1106191665Sbms		free(imm, M_IP6MADDR);
1107191665Sbms		return (NULL);
1108191665Sbms	}
1109191665Sbms
1110191665Sbms	return (imm);
1111191665Sbms}
1112191665Sbms
1113191665Sbms/*
1114191665Sbms * Leave a multicast address w/o sources.
1115191665Sbms * KAME compatibility entry point.
1116191665Sbms *
1117191665Sbms * SMPng: Assume no mc locks held by caller.
1118191665Sbms */
1119191665Sbmsint
1120191665Sbmsin6_leavegroup(struct in6_multi_mship *imm)
1121191665Sbms{
1122191665Sbms
1123191665Sbms	if (imm->i6mm_maddr != NULL)
1124191665Sbms		in6_mc_leave(imm->i6mm_maddr, NULL);
1125191665Sbms	free(imm,  M_IP6MADDR);
1126191665Sbms	return 0;
1127191665Sbms}
1128191665Sbms
1129191665Sbms/*
1130191665Sbms * Join a multicast group; unlocked entry point.
1131191665Sbms *
1132191665Sbms * SMPng: XXX: in6_mc_join() is called from in6_control() when upper
1133191665Sbms * locks are not held. Fortunately, ifp is unlikely to have been detached
1134191665Sbms * at this point, so we assume it's OK to recurse.
1135191665Sbms */
1136191665Sbmsint
1137191665Sbmsin6_mc_join(struct ifnet *ifp, const struct in6_addr *mcaddr,
1138191665Sbms    /*const*/ struct in6_mfilter *imf, struct in6_multi **pinm,
1139191665Sbms    const int delay)
1140191665Sbms{
1141191665Sbms	int error;
1142191665Sbms
1143191665Sbms	IN6_MULTI_LOCK();
1144191665Sbms	error = in6_mc_join_locked(ifp, mcaddr, imf, pinm, delay);
1145191665Sbms	IN6_MULTI_UNLOCK();
1146191665Sbms
1147191665Sbms	return (error);
1148191665Sbms}
1149191665Sbms
1150191665Sbms/*
1151191665Sbms * Join a multicast group; real entry point.
1152191665Sbms *
1153191665Sbms * Only preserves atomicity at inm level.
1154191665Sbms * NOTE: imf argument cannot be const due to sys/tree.h limitations.
1155191665Sbms *
1156191665Sbms * If the MLD downcall fails, the group is not joined, and an error
1157191665Sbms * code is returned.
1158191665Sbms */
1159191665Sbmsint
1160191665Sbmsin6_mc_join_locked(struct ifnet *ifp, const struct in6_addr *mcaddr,
1161191665Sbms    /*const*/ struct in6_mfilter *imf, struct in6_multi **pinm,
1162191665Sbms    const int delay)
1163191665Sbms{
1164191665Sbms	struct in6_mfilter	 timf;
1165191665Sbms	struct in6_multi	*inm;
1166191665Sbms	int			 error;
1167191665Sbms#ifdef KTR
1168191665Sbms	char			 ip6tbuf[INET6_ADDRSTRLEN];
1169191665Sbms#endif
1170191665Sbms
1171192923Sbms#ifdef INVARIANTS
1172192923Sbms	/*
1173192923Sbms	 * Sanity: Check scope zone ID was set for ifp, if and
1174192923Sbms	 * only if group is scoped to an interface.
1175192923Sbms	 */
1176192923Sbms	KASSERT(IN6_IS_ADDR_MULTICAST(mcaddr),
1177192923Sbms	    ("%s: not a multicast address", __func__));
1178192923Sbms	if (IN6_IS_ADDR_MC_LINKLOCAL(mcaddr) ||
1179192923Sbms	    IN6_IS_ADDR_MC_INTFACELOCAL(mcaddr)) {
1180192923Sbms		KASSERT(mcaddr->s6_addr16[1] != 0,
1181192923Sbms		    ("%s: scope zone ID not set", __func__));
1182192923Sbms	}
1183192923Sbms#endif
1184192923Sbms
1185191665Sbms	IN6_MULTI_LOCK_ASSERT();
1186191665Sbms
1187191665Sbms	CTR4(KTR_MLD, "%s: join %s on %p(%s))", __func__,
1188191665Sbms	    ip6_sprintf(ip6tbuf, mcaddr), ifp, ifp->if_xname);
1189191665Sbms
1190191665Sbms	error = 0;
1191191665Sbms	inm = NULL;
1192191665Sbms
1193191665Sbms	/*
1194191665Sbms	 * If no imf was specified (i.e. kernel consumer),
1195191665Sbms	 * fake one up and assume it is an ASM join.
1196191665Sbms	 */
1197191665Sbms	if (imf == NULL) {
1198191665Sbms		im6f_init(&timf, MCAST_UNDEFINED, MCAST_EXCLUDE);
1199191665Sbms		imf = &timf;
1200191665Sbms	}
1201191665Sbms
1202191665Sbms	error = in6_mc_get(ifp, mcaddr, &inm);
1203191665Sbms	if (error) {
1204191665Sbms		CTR1(KTR_MLD, "%s: in6_mc_get() failure", __func__);
1205191665Sbms		return (error);
1206191665Sbms	}
1207191665Sbms
1208191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
1209191665Sbms	error = in6m_merge(inm, imf);
1210191665Sbms	if (error) {
1211191665Sbms		CTR1(KTR_MLD, "%s: failed to merge inm state", __func__);
1212191665Sbms		goto out_in6m_release;
1213191665Sbms	}
1214191665Sbms
1215191665Sbms	CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
1216191665Sbms	error = mld_change_state(inm, delay);
1217191665Sbms	if (error) {
1218191665Sbms		CTR1(KTR_MLD, "%s: failed to update source", __func__);
1219191665Sbms		goto out_in6m_release;
1220191665Sbms	}
1221191665Sbms
1222191665Sbmsout_in6m_release:
1223191665Sbms	if (error) {
1224191665Sbms		CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm);
1225191665Sbms		in6m_release_locked(inm);
1226191665Sbms	} else {
1227191665Sbms		*pinm = inm;
1228191665Sbms	}
1229191665Sbms
1230191665Sbms	return (error);
1231191665Sbms}
1232191665Sbms
1233191665Sbms/*
1234191665Sbms * Leave a multicast group; unlocked entry point.
1235191665Sbms */
1236191665Sbmsint
1237191665Sbmsin6_mc_leave(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
1238191665Sbms{
1239191665Sbms	struct ifnet *ifp;
1240191665Sbms	int error;
1241191665Sbms
1242191665Sbms	ifp = inm->in6m_ifp;
1243191665Sbms
1244191665Sbms	IN6_MULTI_LOCK();
1245191665Sbms	error = in6_mc_leave_locked(inm, imf);
1246191665Sbms	IN6_MULTI_UNLOCK();
1247191665Sbms
1248191665Sbms	return (error);
1249191665Sbms}
1250191665Sbms
1251191665Sbms/*
1252191665Sbms * Leave a multicast group; real entry point.
1253191665Sbms * All source filters will be expunged.
1254191665Sbms *
1255191665Sbms * Only preserves atomicity at inm level.
1256191665Sbms *
1257191665Sbms * Holding the write lock for the INP which contains imf
1258191665Sbms * is highly advisable. We can't assert for it as imf does not
1259191665Sbms * contain a back-pointer to the owning inp.
1260191665Sbms *
1261191665Sbms * Note: This is not the same as in6m_release(*) as this function also
1262191665Sbms * makes a state change downcall into MLD.
1263191665Sbms */
1264191665Sbmsint
1265191665Sbmsin6_mc_leave_locked(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
1266191665Sbms{
1267191665Sbms	struct in6_mfilter	 timf;
1268191665Sbms	int			 error;
1269191665Sbms#ifdef KTR
1270191665Sbms	char			 ip6tbuf[INET6_ADDRSTRLEN];
1271191665Sbms#endif
1272191665Sbms
1273191665Sbms	error = 0;
1274191665Sbms
1275191665Sbms	IN6_MULTI_LOCK_ASSERT();
1276191665Sbms
1277191665Sbms	CTR5(KTR_MLD, "%s: leave inm %p, %s/%s, imf %p", __func__,
1278191665Sbms	    inm, ip6_sprintf(ip6tbuf, &inm->in6m_addr),
1279191665Sbms	    (in6m_is_ifp_detached(inm) ? "null" : inm->in6m_ifp->if_xname),
1280191665Sbms	    imf);
1281191665Sbms
1282191665Sbms	/*
1283191665Sbms	 * If no imf was specified (i.e. kernel consumer),
1284191665Sbms	 * fake one up and assume it is an ASM join.
1285191665Sbms	 */
1286191665Sbms	if (imf == NULL) {
1287191665Sbms		im6f_init(&timf, MCAST_EXCLUDE, MCAST_UNDEFINED);
1288191665Sbms		imf = &timf;
1289191665Sbms	}
1290191665Sbms
1291191665Sbms	/*
1292191665Sbms	 * Begin state merge transaction at MLD layer.
1293191665Sbms	 *
1294191665Sbms	 * As this particular invocation should not cause any memory
1295191665Sbms	 * to be allocated, and there is no opportunity to roll back
1296191665Sbms	 * the transaction, it MUST NOT fail.
1297191665Sbms	 */
1298191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
1299191665Sbms	error = in6m_merge(inm, imf);
1300191665Sbms	KASSERT(error == 0, ("%s: failed to merge inm state", __func__));
1301191665Sbms
1302191665Sbms	CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
1303191665Sbms	error = mld_change_state(inm, 0);
1304191665Sbms	if (error)
1305191665Sbms		CTR1(KTR_MLD, "%s: failed mld downcall", __func__);
1306191665Sbms
1307191665Sbms	CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm);
1308191665Sbms	in6m_release_locked(inm);
1309191665Sbms
1310191665Sbms	return (error);
1311191665Sbms}
1312191665Sbms
1313191665Sbms/*
1314191665Sbms * Block or unblock an ASM multicast source on an inpcb.
1315191665Sbms * This implements the delta-based API described in RFC 3678.
1316191665Sbms *
1317191665Sbms * The delta-based API applies only to exclusive-mode memberships.
1318191665Sbms * An MLD downcall will be performed.
1319191665Sbms *
1320191665Sbms * SMPng: NOTE: Must take Giant as a join may create a new ifma.
1321191665Sbms *
1322191665Sbms * Return 0 if successful, otherwise return an appropriate error code.
1323191665Sbms */
1324191665Sbmsstatic int
1325191665Sbmsin6p_block_unblock_source(struct inpcb *inp, struct sockopt *sopt)
1326191665Sbms{
1327191665Sbms	struct group_source_req		 gsr;
1328191665Sbms	sockunion_t			*gsa, *ssa;
1329191665Sbms	struct ifnet			*ifp;
1330191665Sbms	struct in6_mfilter		*imf;
1331191665Sbms	struct ip6_moptions		*imo;
1332191665Sbms	struct in6_msource		*ims;
1333191665Sbms	struct in6_multi			*inm;
1334191665Sbms	size_t				 idx;
1335191665Sbms	uint16_t			 fmode;
1336191665Sbms	int				 error, doblock;
1337191665Sbms#ifdef KTR
1338191665Sbms	char				 ip6tbuf[INET6_ADDRSTRLEN];
1339191665Sbms#endif
1340191665Sbms
1341191665Sbms	ifp = NULL;
1342191665Sbms	error = 0;
1343191665Sbms	doblock = 0;
1344191665Sbms
1345191665Sbms	memset(&gsr, 0, sizeof(struct group_source_req));
1346191665Sbms	gsa = (sockunion_t *)&gsr.gsr_group;
1347191665Sbms	ssa = (sockunion_t *)&gsr.gsr_source;
1348191665Sbms
1349191665Sbms	switch (sopt->sopt_name) {
1350191665Sbms	case MCAST_BLOCK_SOURCE:
1351191665Sbms	case MCAST_UNBLOCK_SOURCE:
1352191665Sbms		error = sooptcopyin(sopt, &gsr,
1353191665Sbms		    sizeof(struct group_source_req),
1354191665Sbms		    sizeof(struct group_source_req));
1355191665Sbms		if (error)
1356191665Sbms			return (error);
1357191665Sbms
1358191665Sbms		if (gsa->sin6.sin6_family != AF_INET6 ||
1359191665Sbms		    gsa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1360191665Sbms			return (EINVAL);
1361191665Sbms
1362191665Sbms		if (ssa->sin6.sin6_family != AF_INET6 ||
1363191665Sbms		    ssa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1364191665Sbms			return (EINVAL);
1365191665Sbms
1366191665Sbms		if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
1367191665Sbms			return (EADDRNOTAVAIL);
1368191665Sbms
1369191665Sbms		ifp = ifnet_byindex(gsr.gsr_interface);
1370191665Sbms
1371191665Sbms		if (sopt->sopt_name == MCAST_BLOCK_SOURCE)
1372191665Sbms			doblock = 1;
1373191665Sbms		break;
1374191665Sbms
1375191665Sbms	default:
1376191665Sbms		CTR2(KTR_MLD, "%s: unknown sopt_name %d",
1377191665Sbms		    __func__, sopt->sopt_name);
1378191665Sbms		return (EOPNOTSUPP);
1379191665Sbms		break;
1380191665Sbms	}
1381191665Sbms
1382191665Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
1383191665Sbms		return (EINVAL);
1384191665Sbms
1385192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
1386192923Sbms
1387191665Sbms	/*
1388191665Sbms	 * Check if we are actually a member of this group.
1389191665Sbms	 */
1390191665Sbms	imo = in6p_findmoptions(inp);
1391191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
1392191665Sbms	if (idx == -1 || imo->im6o_mfilters == NULL) {
1393191665Sbms		error = EADDRNOTAVAIL;
1394191665Sbms		goto out_in6p_locked;
1395191665Sbms	}
1396191665Sbms
1397191665Sbms	KASSERT(imo->im6o_mfilters != NULL,
1398191665Sbms	    ("%s: im6o_mfilters not allocated", __func__));
1399191665Sbms	imf = &imo->im6o_mfilters[idx];
1400191665Sbms	inm = imo->im6o_membership[idx];
1401191665Sbms
1402191665Sbms	/*
1403191665Sbms	 * Attempting to use the delta-based API on an
1404191665Sbms	 * non exclusive-mode membership is an error.
1405191665Sbms	 */
1406191665Sbms	fmode = imf->im6f_st[0];
1407191665Sbms	if (fmode != MCAST_EXCLUDE) {
1408191665Sbms		error = EINVAL;
1409191665Sbms		goto out_in6p_locked;
1410191665Sbms	}
1411191665Sbms
1412191665Sbms	/*
1413191665Sbms	 * Deal with error cases up-front:
1414191665Sbms	 *  Asked to block, but already blocked; or
1415191665Sbms	 *  Asked to unblock, but nothing to unblock.
1416191665Sbms	 * If adding a new block entry, allocate it.
1417191665Sbms	 */
1418191665Sbms	ims = im6o_match_source(imo, idx, &ssa->sa);
1419191665Sbms	if ((ims != NULL && doblock) || (ims == NULL && !doblock)) {
1420191665Sbms		CTR3(KTR_MLD, "%s: source %s %spresent", __func__,
1421191665Sbms		    ip6_sprintf(ip6tbuf, &ssa->sin6.sin6_addr),
1422191665Sbms		    doblock ? "" : "not ");
1423191665Sbms		error = EADDRNOTAVAIL;
1424191665Sbms		goto out_in6p_locked;
1425191665Sbms	}
1426191665Sbms
1427191665Sbms	INP_WLOCK_ASSERT(inp);
1428191665Sbms
1429191665Sbms	/*
1430191665Sbms	 * Begin state merge transaction at socket layer.
1431191665Sbms	 */
1432191665Sbms	if (doblock) {
1433191665Sbms		CTR2(KTR_MLD, "%s: %s source", __func__, "block");
1434191665Sbms		ims = im6f_graft(imf, fmode, &ssa->sin6);
1435191665Sbms		if (ims == NULL)
1436191665Sbms			error = ENOMEM;
1437191665Sbms	} else {
1438191665Sbms		CTR2(KTR_MLD, "%s: %s source", __func__, "allow");
1439191665Sbms		error = im6f_prune(imf, &ssa->sin6);
1440191665Sbms	}
1441191665Sbms
1442191665Sbms	if (error) {
1443191665Sbms		CTR1(KTR_MLD, "%s: merge imf state failed", __func__);
1444191665Sbms		goto out_im6f_rollback;
1445191665Sbms	}
1446191665Sbms
1447191665Sbms	/*
1448191665Sbms	 * Begin state merge transaction at MLD layer.
1449191665Sbms	 */
1450191665Sbms	IN6_MULTI_LOCK();
1451191665Sbms
1452191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
1453191665Sbms	error = in6m_merge(inm, imf);
1454264722Sae	if (error)
1455191665Sbms		CTR1(KTR_MLD, "%s: failed to merge inm state", __func__);
1456264722Sae	else {
1457264722Sae		CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
1458264722Sae		error = mld_change_state(inm, 0);
1459264722Sae		if (error)
1460264722Sae			CTR1(KTR_MLD, "%s: failed mld downcall", __func__);
1461191665Sbms	}
1462191665Sbms
1463191665Sbms	IN6_MULTI_UNLOCK();
1464191665Sbms
1465191665Sbmsout_im6f_rollback:
1466191665Sbms	if (error)
1467191665Sbms		im6f_rollback(imf);
1468191665Sbms	else
1469191665Sbms		im6f_commit(imf);
1470191665Sbms
1471191665Sbms	im6f_reap(imf);
1472191665Sbms
1473191665Sbmsout_in6p_locked:
1474191665Sbms	INP_WUNLOCK(inp);
1475191665Sbms	return (error);
1476191665Sbms}
1477191665Sbms
1478191665Sbms/*
1479191665Sbms * Given an inpcb, return its multicast options structure pointer.  Accepts
1480191665Sbms * an unlocked inpcb pointer, but will return it locked.  May sleep.
1481191665Sbms *
1482191665Sbms * SMPng: NOTE: Potentially calls malloc(M_WAITOK) with Giant held.
1483191665Sbms * SMPng: NOTE: Returns with the INP write lock held.
1484191665Sbms */
1485191665Sbmsstatic struct ip6_moptions *
1486191665Sbmsin6p_findmoptions(struct inpcb *inp)
1487191665Sbms{
1488191665Sbms	struct ip6_moptions	 *imo;
1489191665Sbms	struct in6_multi		**immp;
1490191665Sbms	struct in6_mfilter	 *imfp;
1491191665Sbms	size_t			  idx;
1492191665Sbms
1493191665Sbms	INP_WLOCK(inp);
1494191665Sbms	if (inp->in6p_moptions != NULL)
1495191665Sbms		return (inp->in6p_moptions);
1496191665Sbms
1497191665Sbms	INP_WUNLOCK(inp);
1498191665Sbms
1499191665Sbms	imo = malloc(sizeof(*imo), M_IP6MOPTS, M_WAITOK);
1500191665Sbms	immp = malloc(sizeof(*immp) * IPV6_MIN_MEMBERSHIPS, M_IP6MOPTS,
1501191665Sbms	    M_WAITOK | M_ZERO);
1502191665Sbms	imfp = malloc(sizeof(struct in6_mfilter) * IPV6_MIN_MEMBERSHIPS,
1503191665Sbms	    M_IN6MFILTER, M_WAITOK);
1504191665Sbms
1505191665Sbms	imo->im6o_multicast_ifp = NULL;
1506191665Sbms	imo->im6o_multicast_hlim = V_ip6_defmcasthlim;
1507191665Sbms	imo->im6o_multicast_loop = in6_mcast_loop;
1508191665Sbms	imo->im6o_num_memberships = 0;
1509191665Sbms	imo->im6o_max_memberships = IPV6_MIN_MEMBERSHIPS;
1510191665Sbms	imo->im6o_membership = immp;
1511191665Sbms
1512191665Sbms	/* Initialize per-group source filters. */
1513191665Sbms	for (idx = 0; idx < IPV6_MIN_MEMBERSHIPS; idx++)
1514191665Sbms		im6f_init(&imfp[idx], MCAST_UNDEFINED, MCAST_EXCLUDE);
1515191665Sbms	imo->im6o_mfilters = imfp;
1516191665Sbms
1517191665Sbms	INP_WLOCK(inp);
1518191665Sbms	if (inp->in6p_moptions != NULL) {
1519191665Sbms		free(imfp, M_IN6MFILTER);
1520191665Sbms		free(immp, M_IP6MOPTS);
1521191665Sbms		free(imo, M_IP6MOPTS);
1522191665Sbms		return (inp->in6p_moptions);
1523191665Sbms	}
1524191665Sbms	inp->in6p_moptions = imo;
1525191665Sbms	return (imo);
1526191665Sbms}
1527191665Sbms
1528191665Sbms/*
1529191665Sbms * Discard the IPv6 multicast options (and source filters).
1530191665Sbms *
1531191665Sbms * SMPng: NOTE: assumes INP write lock is held.
1532191665Sbms */
1533191665Sbmsvoid
1534191665Sbmsip6_freemoptions(struct ip6_moptions *imo)
1535191665Sbms{
1536191665Sbms	struct in6_mfilter	*imf;
1537191665Sbms	size_t			 idx, nmships;
1538191665Sbms
1539191665Sbms	KASSERT(imo != NULL, ("%s: ip6_moptions is NULL", __func__));
1540191665Sbms
1541191665Sbms	nmships = imo->im6o_num_memberships;
1542191665Sbms	for (idx = 0; idx < nmships; ++idx) {
1543191665Sbms		imf = imo->im6o_mfilters ? &imo->im6o_mfilters[idx] : NULL;
1544191665Sbms		if (imf)
1545191665Sbms			im6f_leave(imf);
1546191665Sbms		/* XXX this will thrash the lock(s) */
1547191665Sbms		(void)in6_mc_leave(imo->im6o_membership[idx], imf);
1548191665Sbms		if (imf)
1549191665Sbms			im6f_purge(imf);
1550191665Sbms	}
1551191665Sbms
1552191665Sbms	if (imo->im6o_mfilters)
1553191665Sbms		free(imo->im6o_mfilters, M_IN6MFILTER);
1554191665Sbms	free(imo->im6o_membership, M_IP6MOPTS);
1555191665Sbms	free(imo, M_IP6MOPTS);
1556191665Sbms}
1557191665Sbms
1558191665Sbms/*
1559191665Sbms * Atomically get source filters on a socket for an IPv6 multicast group.
1560191665Sbms * Called with INP lock held; returns with lock released.
1561191665Sbms */
1562191665Sbmsstatic int
1563191665Sbmsin6p_get_source_filters(struct inpcb *inp, struct sockopt *sopt)
1564191665Sbms{
1565191665Sbms	struct __msfilterreq	 msfr;
1566191665Sbms	sockunion_t		*gsa;
1567191665Sbms	struct ifnet		*ifp;
1568191665Sbms	struct ip6_moptions	*imo;
1569191665Sbms	struct in6_mfilter	*imf;
1570191665Sbms	struct ip6_msource	*ims;
1571191665Sbms	struct in6_msource	*lims;
1572191665Sbms	struct sockaddr_in6	*psin;
1573191665Sbms	struct sockaddr_storage	*ptss;
1574191665Sbms	struct sockaddr_storage	*tss;
1575191665Sbms	int			 error;
1576191665Sbms	size_t			 idx, nsrcs, ncsrcs;
1577191665Sbms
1578191665Sbms	INP_WLOCK_ASSERT(inp);
1579191665Sbms
1580191665Sbms	imo = inp->in6p_moptions;
1581191665Sbms	KASSERT(imo != NULL, ("%s: null ip6_moptions", __func__));
1582191665Sbms
1583191665Sbms	INP_WUNLOCK(inp);
1584191665Sbms
1585191665Sbms	error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq),
1586191665Sbms	    sizeof(struct __msfilterreq));
1587191665Sbms	if (error)
1588191665Sbms		return (error);
1589191665Sbms
1590192923Sbms	if (msfr.msfr_group.ss_family != AF_INET6 ||
1591192923Sbms	    msfr.msfr_group.ss_len != sizeof(struct sockaddr_in6))
1592191665Sbms		return (EINVAL);
1593191665Sbms
1594192923Sbms	gsa = (sockunion_t *)&msfr.msfr_group;
1595192923Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
1596192923Sbms		return (EINVAL);
1597192923Sbms
1598192923Sbms	if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
1599192923Sbms		return (EADDRNOTAVAIL);
1600191665Sbms	ifp = ifnet_byindex(msfr.msfr_ifindex);
1601191665Sbms	if (ifp == NULL)
1602192923Sbms		return (EADDRNOTAVAIL);
1603192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
1604191665Sbms
1605191665Sbms	INP_WLOCK(inp);
1606191665Sbms
1607191665Sbms	/*
1608191665Sbms	 * Lookup group on the socket.
1609191665Sbms	 */
1610191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
1611191665Sbms	if (idx == -1 || imo->im6o_mfilters == NULL) {
1612191665Sbms		INP_WUNLOCK(inp);
1613191665Sbms		return (EADDRNOTAVAIL);
1614191665Sbms	}
1615191665Sbms	imf = &imo->im6o_mfilters[idx];
1616191665Sbms
1617191665Sbms	/*
1618191665Sbms	 * Ignore memberships which are in limbo.
1619191665Sbms	 */
1620191665Sbms	if (imf->im6f_st[1] == MCAST_UNDEFINED) {
1621191665Sbms		INP_WUNLOCK(inp);
1622191665Sbms		return (EAGAIN);
1623191665Sbms	}
1624191665Sbms	msfr.msfr_fmode = imf->im6f_st[1];
1625191665Sbms
1626191665Sbms	/*
1627191665Sbms	 * If the user specified a buffer, copy out the source filter
1628191665Sbms	 * entries to userland gracefully.
1629191665Sbms	 * We only copy out the number of entries which userland
1630191665Sbms	 * has asked for, but we always tell userland how big the
1631191665Sbms	 * buffer really needs to be.
1632191665Sbms	 */
1633254629Sdelphij	if (msfr.msfr_nsrcs > in6_mcast_maxsocksrc)
1634254629Sdelphij		msfr.msfr_nsrcs = in6_mcast_maxsocksrc;
1635191665Sbms	tss = NULL;
1636191665Sbms	if (msfr.msfr_srcs != NULL && msfr.msfr_nsrcs > 0) {
1637191665Sbms		tss = malloc(sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs,
1638191665Sbms		    M_TEMP, M_NOWAIT | M_ZERO);
1639191665Sbms		if (tss == NULL) {
1640191665Sbms			INP_WUNLOCK(inp);
1641191665Sbms			return (ENOBUFS);
1642191665Sbms		}
1643191665Sbms	}
1644191665Sbms
1645191665Sbms	/*
1646191665Sbms	 * Count number of sources in-mode at t0.
1647191665Sbms	 * If buffer space exists and remains, copy out source entries.
1648191665Sbms	 */
1649191665Sbms	nsrcs = msfr.msfr_nsrcs;
1650191665Sbms	ncsrcs = 0;
1651191665Sbms	ptss = tss;
1652191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
1653191665Sbms		lims = (struct in6_msource *)ims;
1654191665Sbms		if (lims->im6sl_st[0] == MCAST_UNDEFINED ||
1655191665Sbms		    lims->im6sl_st[0] != imf->im6f_st[0])
1656191665Sbms			continue;
1657191665Sbms		++ncsrcs;
1658191665Sbms		if (tss != NULL && nsrcs > 0) {
1659191665Sbms			psin = (struct sockaddr_in6 *)ptss;
1660191665Sbms			psin->sin6_family = AF_INET6;
1661191665Sbms			psin->sin6_len = sizeof(struct sockaddr_in6);
1662191665Sbms			psin->sin6_addr = lims->im6s_addr;
1663191665Sbms			psin->sin6_port = 0;
1664191665Sbms			--nsrcs;
1665191665Sbms			++ptss;
1666191665Sbms		}
1667191665Sbms	}
1668191665Sbms
1669191665Sbms	INP_WUNLOCK(inp);
1670191665Sbms
1671191665Sbms	if (tss != NULL) {
1672191665Sbms		error = copyout(tss, msfr.msfr_srcs,
1673191665Sbms		    sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs);
1674191665Sbms		free(tss, M_TEMP);
1675191665Sbms		if (error)
1676191665Sbms			return (error);
1677191665Sbms	}
1678191665Sbms
1679191665Sbms	msfr.msfr_nsrcs = ncsrcs;
1680191665Sbms	error = sooptcopyout(sopt, &msfr, sizeof(struct __msfilterreq));
1681191665Sbms
1682191665Sbms	return (error);
1683191665Sbms}
1684191665Sbms
1685191665Sbms/*
1686191665Sbms * Return the IP multicast options in response to user getsockopt().
1687191665Sbms */
1688191665Sbmsint
1689191665Sbmsip6_getmoptions(struct inpcb *inp, struct sockopt *sopt)
1690191665Sbms{
1691191672Sbms	struct ip6_moptions	*im6o;
1692191672Sbms	int			 error;
1693191672Sbms	u_int			 optval;
1694191665Sbms
1695191665Sbms	INP_WLOCK(inp);
1696191672Sbms	im6o = inp->in6p_moptions;
1697191665Sbms	/*
1698191665Sbms	 * If socket is neither of type SOCK_RAW or SOCK_DGRAM,
1699191665Sbms	 * or is a divert socket, reject it.
1700191665Sbms	 */
1701191665Sbms	if (inp->inp_socket->so_proto->pr_protocol == IPPROTO_DIVERT ||
1702191665Sbms	    (inp->inp_socket->so_proto->pr_type != SOCK_RAW &&
1703191665Sbms	    inp->inp_socket->so_proto->pr_type != SOCK_DGRAM)) {
1704191665Sbms		INP_WUNLOCK(inp);
1705191665Sbms		return (EOPNOTSUPP);
1706191665Sbms	}
1707191665Sbms
1708191665Sbms	error = 0;
1709191665Sbms	switch (sopt->sopt_name) {
1710191665Sbms	case IPV6_MULTICAST_IF:
1711191672Sbms		if (im6o == NULL || im6o->im6o_multicast_ifp == NULL) {
1712191665Sbms			optval = 0;
1713191665Sbms		} else {
1714191672Sbms			optval = im6o->im6o_multicast_ifp->if_index;
1715191665Sbms		}
1716191665Sbms		INP_WUNLOCK(inp);
1717191672Sbms		error = sooptcopyout(sopt, &optval, sizeof(u_int));
1718191665Sbms		break;
1719191665Sbms
1720191665Sbms	case IPV6_MULTICAST_HOPS:
1721191672Sbms		if (im6o == NULL)
1722191672Sbms			optval = V_ip6_defmcasthlim;
1723191665Sbms		else
1724227481Sbz			optval = im6o->im6o_multicast_hlim;
1725191665Sbms		INP_WUNLOCK(inp);
1726191665Sbms		error = sooptcopyout(sopt, &optval, sizeof(u_int));
1727191665Sbms		break;
1728191665Sbms
1729191665Sbms	case IPV6_MULTICAST_LOOP:
1730191672Sbms		if (im6o == NULL)
1731191672Sbms			optval = in6_mcast_loop; /* XXX VIMAGE */
1732191665Sbms		else
1733191672Sbms			optval = im6o->im6o_multicast_loop;
1734191665Sbms		INP_WUNLOCK(inp);
1735191665Sbms		error = sooptcopyout(sopt, &optval, sizeof(u_int));
1736191665Sbms		break;
1737191665Sbms
1738191665Sbms	case IPV6_MSFILTER:
1739191672Sbms		if (im6o == NULL) {
1740191665Sbms			error = EADDRNOTAVAIL;
1741191665Sbms			INP_WUNLOCK(inp);
1742191665Sbms		} else {
1743191665Sbms			error = in6p_get_source_filters(inp, sopt);
1744191665Sbms		}
1745191665Sbms		break;
1746191665Sbms
1747191665Sbms	default:
1748191665Sbms		INP_WUNLOCK(inp);
1749191665Sbms		error = ENOPROTOOPT;
1750191665Sbms		break;
1751191665Sbms	}
1752191665Sbms
1753191665Sbms	INP_UNLOCK_ASSERT(inp);
1754191665Sbms
1755191665Sbms	return (error);
1756191665Sbms}
1757191665Sbms
1758191665Sbms/*
1759191672Sbms * Look up the ifnet to use for a multicast group membership,
1760191672Sbms * given the address of an IPv6 group.
1761191672Sbms *
1762191672Sbms * This routine exists to support legacy IPv6 multicast applications.
1763191672Sbms *
1764191672Sbms * If inp is non-NULL, use this socket's current FIB number for any
1765191672Sbms * required FIB lookup. Look up the group address in the unicast FIB,
1766191672Sbms * and use its ifp; usually, this points to the default next-hop.
1767191672Sbms * If the FIB lookup fails, return NULL.
1768191672Sbms *
1769191672Sbms * FUTURE: Support multiple forwarding tables for IPv6.
1770191672Sbms *
1771191672Sbms * Returns NULL if no ifp could be found.
1772191672Sbms */
1773191672Sbmsstatic struct ifnet *
1774231852Sbzin6p_lookup_mcast_ifp(const struct inpcb *in6p,
1775191672Sbms    const struct sockaddr_in6 *gsin6)
1776191672Sbms{
1777191672Sbms	struct route_in6	 ro6;
1778191672Sbms	struct ifnet		*ifp;
1779191672Sbms
1780191672Sbms	KASSERT(in6p->inp_vflag & INP_IPV6,
1781191672Sbms	    ("%s: not INP_IPV6 inpcb", __func__));
1782191672Sbms	KASSERT(gsin6->sin6_family == AF_INET6,
1783191672Sbms	    ("%s: not AF_INET6 group", __func__));
1784191672Sbms
1785191672Sbms	ifp = NULL;
1786191672Sbms	memset(&ro6, 0, sizeof(struct route_in6));
1787191672Sbms	memcpy(&ro6.ro_dst, gsin6, sizeof(struct sockaddr_in6));
1788231852Sbz	rtalloc_ign_fib((struct route *)&ro6, 0,
1789231852Sbz	    in6p ? in6p->inp_inc.inc_fibnum : RT_DEFAULT_FIB);
1790191672Sbms	if (ro6.ro_rt != NULL) {
1791191672Sbms		ifp = ro6.ro_rt->rt_ifp;
1792191672Sbms		KASSERT(ifp != NULL, ("%s: null ifp", __func__));
1793191672Sbms		RTFREE(ro6.ro_rt);
1794191672Sbms	}
1795191672Sbms
1796191672Sbms	return (ifp);
1797191672Sbms}
1798191672Sbms
1799191672Sbms/*
1800191665Sbms * Join an IPv6 multicast group, possibly with a source.
1801191672Sbms *
1802191672Sbms * FIXME: The KAME use of the unspecified address (::)
1803191672Sbms * to join *all* multicast groups is currently unsupported.
1804191665Sbms */
1805191665Sbmsstatic int
1806191665Sbmsin6p_join_group(struct inpcb *inp, struct sockopt *sopt)
1807191665Sbms{
1808191665Sbms	struct group_source_req		 gsr;
1809191665Sbms	sockunion_t			*gsa, *ssa;
1810191665Sbms	struct ifnet			*ifp;
1811191665Sbms	struct in6_mfilter		*imf;
1812191665Sbms	struct ip6_moptions		*imo;
1813191665Sbms	struct in6_multi		*inm;
1814191665Sbms	struct in6_msource		*lims;
1815191665Sbms	size_t				 idx;
1816191665Sbms	int				 error, is_new;
1817191665Sbms
1818191665Sbms	ifp = NULL;
1819191665Sbms	imf = NULL;
1820199528Sbms	lims = NULL;
1821191665Sbms	error = 0;
1822191665Sbms	is_new = 0;
1823191665Sbms
1824191665Sbms	memset(&gsr, 0, sizeof(struct group_source_req));
1825191665Sbms	gsa = (sockunion_t *)&gsr.gsr_group;
1826191665Sbms	gsa->ss.ss_family = AF_UNSPEC;
1827191665Sbms	ssa = (sockunion_t *)&gsr.gsr_source;
1828191665Sbms	ssa->ss.ss_family = AF_UNSPEC;
1829191665Sbms
1830192923Sbms	/*
1831192923Sbms	 * Chew everything into struct group_source_req.
1832192923Sbms	 * Overwrite the port field if present, as the sockaddr
1833192923Sbms	 * being copied in may be matched with a binary comparison.
1834192923Sbms	 * Ignore passed-in scope ID.
1835192923Sbms	 */
1836191665Sbms	switch (sopt->sopt_name) {
1837191665Sbms	case IPV6_JOIN_GROUP: {
1838191665Sbms		struct ipv6_mreq mreq;
1839191665Sbms
1840191665Sbms		error = sooptcopyin(sopt, &mreq, sizeof(struct ipv6_mreq),
1841191665Sbms		    sizeof(struct ipv6_mreq));
1842191665Sbms		if (error)
1843191665Sbms			return (error);
1844191665Sbms
1845191665Sbms		gsa->sin6.sin6_family = AF_INET6;
1846191665Sbms		gsa->sin6.sin6_len = sizeof(struct sockaddr_in6);
1847191665Sbms		gsa->sin6.sin6_addr = mreq.ipv6mr_multiaddr;
1848191665Sbms
1849191672Sbms		if (mreq.ipv6mr_interface == 0) {
1850191672Sbms			ifp = in6p_lookup_mcast_ifp(inp, &gsa->sin6);
1851191672Sbms		} else {
1852270044Sbz			if (V_if_index < mreq.ipv6mr_interface)
1853191672Sbms				return (EADDRNOTAVAIL);
1854191672Sbms			ifp = ifnet_byindex(mreq.ipv6mr_interface);
1855191672Sbms		}
1856191665Sbms		CTR3(KTR_MLD, "%s: ipv6mr_interface = %d, ifp = %p",
1857191665Sbms		    __func__, mreq.ipv6mr_interface, ifp);
1858191665Sbms	} break;
1859191665Sbms
1860191665Sbms	case MCAST_JOIN_GROUP:
1861191665Sbms	case MCAST_JOIN_SOURCE_GROUP:
1862191665Sbms		if (sopt->sopt_name == MCAST_JOIN_GROUP) {
1863191665Sbms			error = sooptcopyin(sopt, &gsr,
1864191665Sbms			    sizeof(struct group_req),
1865191665Sbms			    sizeof(struct group_req));
1866191665Sbms		} else if (sopt->sopt_name == MCAST_JOIN_SOURCE_GROUP) {
1867191665Sbms			error = sooptcopyin(sopt, &gsr,
1868191665Sbms			    sizeof(struct group_source_req),
1869191665Sbms			    sizeof(struct group_source_req));
1870191665Sbms		}
1871191665Sbms		if (error)
1872191665Sbms			return (error);
1873191665Sbms
1874191665Sbms		if (gsa->sin6.sin6_family != AF_INET6 ||
1875191665Sbms		    gsa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1876191665Sbms			return (EINVAL);
1877191665Sbms
1878191665Sbms		if (sopt->sopt_name == MCAST_JOIN_SOURCE_GROUP) {
1879191665Sbms			if (ssa->sin6.sin6_family != AF_INET6 ||
1880191665Sbms			    ssa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1881191665Sbms				return (EINVAL);
1882192923Sbms			if (IN6_IS_ADDR_MULTICAST(&ssa->sin6.sin6_addr))
1883192923Sbms				return (EINVAL);
1884192923Sbms			/*
1885192923Sbms			 * TODO: Validate embedded scope ID in source
1886192923Sbms			 * list entry against passed-in ifp, if and only
1887192923Sbms			 * if source list filter entry is iface or node local.
1888192923Sbms			 */
1889192923Sbms			in6_clearscope(&ssa->sin6.sin6_addr);
1890191665Sbms			ssa->sin6.sin6_port = 0;
1891192923Sbms			ssa->sin6.sin6_scope_id = 0;
1892191665Sbms		}
1893191665Sbms
1894191665Sbms		if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
1895191665Sbms			return (EADDRNOTAVAIL);
1896191665Sbms		ifp = ifnet_byindex(gsr.gsr_interface);
1897191665Sbms		break;
1898191665Sbms
1899191665Sbms	default:
1900191665Sbms		CTR2(KTR_MLD, "%s: unknown sopt_name %d",
1901191665Sbms		    __func__, sopt->sopt_name);
1902191665Sbms		return (EOPNOTSUPP);
1903191665Sbms		break;
1904191665Sbms	}
1905191665Sbms
1906191665Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
1907191665Sbms		return (EINVAL);
1908191665Sbms
1909191665Sbms	if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0)
1910191665Sbms		return (EADDRNOTAVAIL);
1911191665Sbms
1912192923Sbms	gsa->sin6.sin6_port = 0;
1913192923Sbms	gsa->sin6.sin6_scope_id = 0;
1914192923Sbms
1915191665Sbms	/*
1916192923Sbms	 * Always set the scope zone ID on memberships created from userland.
1917192923Sbms	 * Use the passed-in ifp to do this.
1918192923Sbms	 * XXX The in6_setscope() return value is meaningless.
1919192923Sbms	 * XXX SCOPE6_LOCK() is taken by in6_setscope().
1920191672Sbms	 */
1921192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
1922191672Sbms
1923191665Sbms	imo = in6p_findmoptions(inp);
1924191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
1925191665Sbms	if (idx == -1) {
1926191665Sbms		is_new = 1;
1927191665Sbms	} else {
1928191665Sbms		inm = imo->im6o_membership[idx];
1929191665Sbms		imf = &imo->im6o_mfilters[idx];
1930199526Sbms		if (ssa->ss.ss_family != AF_UNSPEC) {
1931199526Sbms			/*
1932199526Sbms			 * MCAST_JOIN_SOURCE_GROUP on an exclusive membership
1933199526Sbms			 * is an error. On an existing inclusive membership,
1934199526Sbms			 * it just adds the source to the filter list.
1935199526Sbms			 */
1936199526Sbms			if (imf->im6f_st[1] != MCAST_INCLUDE) {
1937199526Sbms				error = EINVAL;
1938199526Sbms				goto out_in6p_locked;
1939199526Sbms			}
1940199528Sbms			/*
1941199528Sbms			 * Throw out duplicates.
1942199528Sbms			 *
1943199528Sbms			 * XXX FIXME: This makes a naive assumption that
1944199528Sbms			 * even if entries exist for *ssa in this imf,
1945199528Sbms			 * they will be rejected as dupes, even if they
1946199528Sbms			 * are not valid in the current mode (in-mode).
1947199528Sbms			 *
1948199528Sbms			 * in6_msource is transactioned just as for anything
1949199528Sbms			 * else in SSM -- but note naive use of in6m_graft()
1950199528Sbms			 * below for allocating new filter entries.
1951199528Sbms			 *
1952199528Sbms			 * This is only an issue if someone mixes the
1953199528Sbms			 * full-state SSM API with the delta-based API,
1954199528Sbms			 * which is discouraged in the relevant RFCs.
1955199528Sbms			 */
1956199526Sbms			lims = im6o_match_source(imo, idx, &ssa->sa);
1957199528Sbms			if (lims != NULL /*&&
1958199528Sbms			    lims->im6sl_st[1] == MCAST_INCLUDE*/) {
1959199526Sbms				error = EADDRNOTAVAIL;
1960199526Sbms				goto out_in6p_locked;
1961199526Sbms			}
1962199526Sbms		} else {
1963199526Sbms			/*
1964199527Sbms			 * MCAST_JOIN_GROUP alone, on any existing membership,
1965199527Sbms			 * is rejected, to stop the same inpcb tying up
1966199527Sbms			 * multiple refs to the in_multi.
1967199527Sbms			 * On an existing inclusive membership, this is also
1968199527Sbms			 * an error; if you want to change filter mode,
1969199527Sbms			 * you must use the userland API setsourcefilter().
1970199527Sbms			 * XXX We don't reject this for imf in UNDEFINED
1971199527Sbms			 * state at t1, because allocation of a filter
1972199527Sbms			 * is atomic with allocation of a membership.
1973199526Sbms			 */
1974199527Sbms			error = EINVAL;
1975199527Sbms			goto out_in6p_locked;
1976191665Sbms		}
1977191665Sbms	}
1978191665Sbms
1979191665Sbms	/*
1980191665Sbms	 * Begin state merge transaction at socket layer.
1981191665Sbms	 */
1982191665Sbms	INP_WLOCK_ASSERT(inp);
1983191665Sbms
1984191665Sbms	if (is_new) {
1985191665Sbms		if (imo->im6o_num_memberships == imo->im6o_max_memberships) {
1986191665Sbms			error = im6o_grow(imo);
1987191665Sbms			if (error)
1988191665Sbms				goto out_in6p_locked;
1989191665Sbms		}
1990191665Sbms		/*
1991191665Sbms		 * Allocate the new slot upfront so we can deal with
1992191665Sbms		 * grafting the new source filter in same code path
1993191665Sbms		 * as for join-source on existing membership.
1994191665Sbms		 */
1995191665Sbms		idx = imo->im6o_num_memberships;
1996191665Sbms		imo->im6o_membership[idx] = NULL;
1997191665Sbms		imo->im6o_num_memberships++;
1998191665Sbms		KASSERT(imo->im6o_mfilters != NULL,
1999191665Sbms		    ("%s: im6f_mfilters vector was not allocated", __func__));
2000191665Sbms		imf = &imo->im6o_mfilters[idx];
2001191665Sbms		KASSERT(RB_EMPTY(&imf->im6f_sources),
2002191665Sbms		    ("%s: im6f_sources not empty", __func__));
2003191665Sbms	}
2004191665Sbms
2005191665Sbms	/*
2006191665Sbms	 * Graft new source into filter list for this inpcb's
2007191665Sbms	 * membership of the group. The in6_multi may not have
2008199526Sbms	 * been allocated yet if this is a new membership, however,
2009199526Sbms	 * the in_mfilter slot will be allocated and must be initialized.
2010199527Sbms	 *
2011199527Sbms	 * Note: Grafting of exclusive mode filters doesn't happen
2012199527Sbms	 * in this path.
2013199528Sbms	 * XXX: Should check for non-NULL lims (node exists but may
2014199528Sbms	 * not be in-mode) for interop with full-state API.
2015191665Sbms	 */
2016191665Sbms	if (ssa->ss.ss_family != AF_UNSPEC) {
2017191665Sbms		/* Membership starts in IN mode */
2018191665Sbms		if (is_new) {
2019191665Sbms			CTR1(KTR_MLD, "%s: new join w/source", __func__);
2020191665Sbms			im6f_init(imf, MCAST_UNDEFINED, MCAST_INCLUDE);
2021191665Sbms		} else {
2022191665Sbms			CTR2(KTR_MLD, "%s: %s source", __func__, "allow");
2023191665Sbms		}
2024191665Sbms		lims = im6f_graft(imf, MCAST_INCLUDE, &ssa->sin6);
2025191665Sbms		if (lims == NULL) {
2026191665Sbms			CTR1(KTR_MLD, "%s: merge imf state failed",
2027191665Sbms			    __func__);
2028191665Sbms			error = ENOMEM;
2029191665Sbms			goto out_im6o_free;
2030191665Sbms		}
2031199526Sbms	} else {
2032199526Sbms		/* No address specified; Membership starts in EX mode */
2033199526Sbms		if (is_new) {
2034199526Sbms			CTR1(KTR_MLD, "%s: new join w/o source", __func__);
2035199526Sbms			im6f_init(imf, MCAST_UNDEFINED, MCAST_EXCLUDE);
2036199526Sbms		}
2037191665Sbms	}
2038191665Sbms
2039191665Sbms	/*
2040191665Sbms	 * Begin state merge transaction at MLD layer.
2041191665Sbms	 */
2042191665Sbms	IN6_MULTI_LOCK();
2043191665Sbms
2044191665Sbms	if (is_new) {
2045191665Sbms		error = in6_mc_join_locked(ifp, &gsa->sin6.sin6_addr, imf,
2046191665Sbms		    &inm, 0);
2047264722Sae		if (error) {
2048264722Sae			IN6_MULTI_UNLOCK();
2049191665Sbms			goto out_im6o_free;
2050264722Sae		}
2051191665Sbms		imo->im6o_membership[idx] = inm;
2052191665Sbms	} else {
2053191665Sbms		CTR1(KTR_MLD, "%s: merge inm state", __func__);
2054191665Sbms		error = in6m_merge(inm, imf);
2055264722Sae		if (error)
2056191665Sbms			CTR1(KTR_MLD, "%s: failed to merge inm state",
2057191665Sbms			    __func__);
2058264722Sae		else {
2059264722Sae			CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
2060264722Sae			error = mld_change_state(inm, 0);
2061264722Sae			if (error)
2062264722Sae				CTR1(KTR_MLD, "%s: failed mld downcall",
2063264722Sae				    __func__);
2064191665Sbms		}
2065191665Sbms	}
2066191665Sbms
2067191665Sbms	IN6_MULTI_UNLOCK();
2068191665Sbms	INP_WLOCK_ASSERT(inp);
2069191665Sbms	if (error) {
2070191665Sbms		im6f_rollback(imf);
2071191665Sbms		if (is_new)
2072191665Sbms			im6f_purge(imf);
2073191665Sbms		else
2074191665Sbms			im6f_reap(imf);
2075191665Sbms	} else {
2076191665Sbms		im6f_commit(imf);
2077191665Sbms	}
2078191665Sbms
2079191665Sbmsout_im6o_free:
2080191665Sbms	if (error && is_new) {
2081191665Sbms		imo->im6o_membership[idx] = NULL;
2082191665Sbms		--imo->im6o_num_memberships;
2083191665Sbms	}
2084191665Sbms
2085191665Sbmsout_in6p_locked:
2086191665Sbms	INP_WUNLOCK(inp);
2087191665Sbms	return (error);
2088191665Sbms}
2089191665Sbms
2090191665Sbms/*
2091191665Sbms * Leave an IPv6 multicast group on an inpcb, possibly with a source.
2092191665Sbms */
2093191665Sbmsstatic int
2094191665Sbmsin6p_leave_group(struct inpcb *inp, struct sockopt *sopt)
2095191665Sbms{
2096192923Sbms	struct ipv6_mreq		 mreq;
2097191665Sbms	struct group_source_req		 gsr;
2098191665Sbms	sockunion_t			*gsa, *ssa;
2099191665Sbms	struct ifnet			*ifp;
2100191665Sbms	struct in6_mfilter		*imf;
2101191665Sbms	struct ip6_moptions		*imo;
2102191665Sbms	struct in6_msource		*ims;
2103191665Sbms	struct in6_multi		*inm;
2104192923Sbms	uint32_t			 ifindex;
2105191665Sbms	size_t				 idx;
2106191665Sbms	int				 error, is_final;
2107191665Sbms#ifdef KTR
2108191665Sbms	char				 ip6tbuf[INET6_ADDRSTRLEN];
2109191665Sbms#endif
2110191665Sbms
2111191665Sbms	ifp = NULL;
2112192923Sbms	ifindex = 0;
2113191665Sbms	error = 0;
2114191665Sbms	is_final = 1;
2115191665Sbms
2116191665Sbms	memset(&gsr, 0, sizeof(struct group_source_req));
2117191665Sbms	gsa = (sockunion_t *)&gsr.gsr_group;
2118191665Sbms	gsa->ss.ss_family = AF_UNSPEC;
2119191665Sbms	ssa = (sockunion_t *)&gsr.gsr_source;
2120191665Sbms	ssa->ss.ss_family = AF_UNSPEC;
2121191665Sbms
2122192923Sbms	/*
2123192923Sbms	 * Chew everything passed in up into a struct group_source_req
2124192923Sbms	 * as that is easier to process.
2125192923Sbms	 * Note: Any embedded scope ID in the multicast group passed
2126192923Sbms	 * in by userland is ignored, the interface index is the recommended
2127192923Sbms	 * mechanism to specify an interface; see below.
2128192923Sbms	 */
2129191665Sbms	switch (sopt->sopt_name) {
2130192923Sbms	case IPV6_LEAVE_GROUP:
2131191665Sbms		error = sooptcopyin(sopt, &mreq, sizeof(struct ipv6_mreq),
2132191665Sbms		    sizeof(struct ipv6_mreq));
2133191665Sbms		if (error)
2134191665Sbms			return (error);
2135191665Sbms		gsa->sin6.sin6_family = AF_INET6;
2136191665Sbms		gsa->sin6.sin6_len = sizeof(struct sockaddr_in6);
2137191665Sbms		gsa->sin6.sin6_addr = mreq.ipv6mr_multiaddr;
2138192923Sbms		gsa->sin6.sin6_port = 0;
2139192923Sbms		gsa->sin6.sin6_scope_id = 0;
2140192923Sbms		ifindex = mreq.ipv6mr_interface;
2141192923Sbms		break;
2142191665Sbms
2143191665Sbms	case MCAST_LEAVE_GROUP:
2144191665Sbms	case MCAST_LEAVE_SOURCE_GROUP:
2145191665Sbms		if (sopt->sopt_name == MCAST_LEAVE_GROUP) {
2146191665Sbms			error = sooptcopyin(sopt, &gsr,
2147191665Sbms			    sizeof(struct group_req),
2148191665Sbms			    sizeof(struct group_req));
2149191665Sbms		} else if (sopt->sopt_name == MCAST_LEAVE_SOURCE_GROUP) {
2150191665Sbms			error = sooptcopyin(sopt, &gsr,
2151191665Sbms			    sizeof(struct group_source_req),
2152191665Sbms			    sizeof(struct group_source_req));
2153191665Sbms		}
2154191665Sbms		if (error)
2155191665Sbms			return (error);
2156191665Sbms
2157191665Sbms		if (gsa->sin6.sin6_family != AF_INET6 ||
2158191665Sbms		    gsa->sin6.sin6_len != sizeof(struct sockaddr_in6))
2159191665Sbms			return (EINVAL);
2160191665Sbms		if (sopt->sopt_name == MCAST_LEAVE_SOURCE_GROUP) {
2161191665Sbms			if (ssa->sin6.sin6_family != AF_INET6 ||
2162191665Sbms			    ssa->sin6.sin6_len != sizeof(struct sockaddr_in6))
2163191665Sbms				return (EINVAL);
2164192923Sbms			if (IN6_IS_ADDR_MULTICAST(&ssa->sin6.sin6_addr))
2165192923Sbms				return (EINVAL);
2166192923Sbms			/*
2167192923Sbms			 * TODO: Validate embedded scope ID in source
2168192923Sbms			 * list entry against passed-in ifp, if and only
2169192923Sbms			 * if source list filter entry is iface or node local.
2170192923Sbms			 */
2171192923Sbms			in6_clearscope(&ssa->sin6.sin6_addr);
2172191665Sbms		}
2173192923Sbms		gsa->sin6.sin6_port = 0;
2174192923Sbms		gsa->sin6.sin6_scope_id = 0;
2175192923Sbms		ifindex = gsr.gsr_interface;
2176191665Sbms		break;
2177191665Sbms
2178191665Sbms	default:
2179191665Sbms		CTR2(KTR_MLD, "%s: unknown sopt_name %d",
2180191665Sbms		    __func__, sopt->sopt_name);
2181191665Sbms		return (EOPNOTSUPP);
2182191665Sbms		break;
2183191665Sbms	}
2184191665Sbms
2185191665Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
2186191665Sbms		return (EINVAL);
2187191665Sbms
2188191665Sbms	/*
2189192923Sbms	 * Validate interface index if provided. If no interface index
2190192923Sbms	 * was provided separately, attempt to look the membership up
2191192923Sbms	 * from the default scope as a last resort to disambiguate
2192192923Sbms	 * the membership we are being asked to leave.
2193192923Sbms	 * XXX SCOPE6 lock potentially taken here.
2194191672Sbms	 */
2195192923Sbms	if (ifindex != 0) {
2196270044Sbz		if (V_if_index < ifindex)
2197192923Sbms			return (EADDRNOTAVAIL);
2198192923Sbms		ifp = ifnet_byindex(ifindex);
2199192923Sbms		if (ifp == NULL)
2200192923Sbms			return (EADDRNOTAVAIL);
2201192923Sbms		(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
2202192923Sbms	} else {
2203192923Sbms		error = sa6_embedscope(&gsa->sin6, V_ip6_use_defzone);
2204192923Sbms		if (error)
2205192923Sbms			return (EADDRNOTAVAIL);
2206192923Sbms		/*
2207195755Sbms		 * Some badly behaved applications don't pass an ifindex
2208195755Sbms		 * or a scope ID, which is an API violation. In this case,
2209195755Sbms		 * perform a lookup as per a v6 join.
2210195755Sbms		 *
2211192923Sbms		 * XXX For now, stomp on zone ID for the corner case.
2212192923Sbms		 * This is not the 'KAME way', but we need to see the ifp
2213192923Sbms		 * directly until such time as this implementation is
2214192923Sbms		 * refactored, assuming the scope IDs are the way to go.
2215192923Sbms		 */
2216192923Sbms		ifindex = ntohs(gsa->sin6.sin6_addr.s6_addr16[1]);
2217195755Sbms		if (ifindex == 0) {
2218195755Sbms			CTR2(KTR_MLD, "%s: warning: no ifindex, looking up "
2219195755Sbms			    "ifp for group %s.", __func__,
2220195755Sbms			    ip6_sprintf(ip6tbuf, &gsa->sin6.sin6_addr));
2221195755Sbms			ifp = in6p_lookup_mcast_ifp(inp, &gsa->sin6);
2222195755Sbms		} else {
2223195755Sbms			ifp = ifnet_byindex(ifindex);
2224195755Sbms		}
2225192923Sbms		if (ifp == NULL)
2226192923Sbms			return (EADDRNOTAVAIL);
2227192923Sbms	}
2228191672Sbms
2229192923Sbms	CTR2(KTR_MLD, "%s: ifp = %p", __func__, ifp);
2230192923Sbms	KASSERT(ifp != NULL, ("%s: ifp did not resolve", __func__));
2231192923Sbms
2232191672Sbms	/*
2233191665Sbms	 * Find the membership in the membership array.
2234191665Sbms	 */
2235191665Sbms	imo = in6p_findmoptions(inp);
2236191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
2237191665Sbms	if (idx == -1) {
2238191665Sbms		error = EADDRNOTAVAIL;
2239191665Sbms		goto out_in6p_locked;
2240191665Sbms	}
2241191665Sbms	inm = imo->im6o_membership[idx];
2242191665Sbms	imf = &imo->im6o_mfilters[idx];
2243191665Sbms
2244191665Sbms	if (ssa->ss.ss_family != AF_UNSPEC)
2245191665Sbms		is_final = 0;
2246191665Sbms
2247191665Sbms	/*
2248191665Sbms	 * Begin state merge transaction at socket layer.
2249191665Sbms	 */
2250191665Sbms	INP_WLOCK_ASSERT(inp);
2251191665Sbms
2252191665Sbms	/*
2253191665Sbms	 * If we were instructed only to leave a given source, do so.
2254191665Sbms	 * MCAST_LEAVE_SOURCE_GROUP is only valid for inclusive memberships.
2255191665Sbms	 */
2256191665Sbms	if (is_final) {
2257191665Sbms		im6f_leave(imf);
2258191665Sbms	} else {
2259191665Sbms		if (imf->im6f_st[0] == MCAST_EXCLUDE) {
2260191665Sbms			error = EADDRNOTAVAIL;
2261191665Sbms			goto out_in6p_locked;
2262191665Sbms		}
2263191665Sbms		ims = im6o_match_source(imo, idx, &ssa->sa);
2264191665Sbms		if (ims == NULL) {
2265191665Sbms			CTR3(KTR_MLD, "%s: source %p %spresent", __func__,
2266191665Sbms			    ip6_sprintf(ip6tbuf, &ssa->sin6.sin6_addr),
2267191665Sbms			    "not ");
2268191665Sbms			error = EADDRNOTAVAIL;
2269191665Sbms			goto out_in6p_locked;
2270191665Sbms		}
2271191665Sbms		CTR2(KTR_MLD, "%s: %s source", __func__, "block");
2272191665Sbms		error = im6f_prune(imf, &ssa->sin6);
2273191665Sbms		if (error) {
2274191665Sbms			CTR1(KTR_MLD, "%s: merge imf state failed",
2275191665Sbms			    __func__);
2276191665Sbms			goto out_in6p_locked;
2277191665Sbms		}
2278191665Sbms	}
2279191665Sbms
2280191665Sbms	/*
2281191665Sbms	 * Begin state merge transaction at MLD layer.
2282191665Sbms	 */
2283191665Sbms	IN6_MULTI_LOCK();
2284191665Sbms
2285191665Sbms	if (is_final) {
2286191665Sbms		/*
2287191665Sbms		 * Give up the multicast address record to which
2288191665Sbms		 * the membership points.
2289191665Sbms		 */
2290191665Sbms		(void)in6_mc_leave_locked(inm, imf);
2291191665Sbms	} else {
2292191665Sbms		CTR1(KTR_MLD, "%s: merge inm state", __func__);
2293191665Sbms		error = in6m_merge(inm, imf);
2294264722Sae		if (error)
2295191665Sbms			CTR1(KTR_MLD, "%s: failed to merge inm state",
2296191665Sbms			    __func__);
2297264722Sae		else {
2298264722Sae			CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
2299264722Sae			error = mld_change_state(inm, 0);
2300264722Sae			if (error)
2301264722Sae				CTR1(KTR_MLD, "%s: failed mld downcall",
2302264722Sae				    __func__);
2303191665Sbms		}
2304191665Sbms	}
2305191665Sbms
2306191665Sbms	IN6_MULTI_UNLOCK();
2307191665Sbms
2308191665Sbms	if (error)
2309191665Sbms		im6f_rollback(imf);
2310191665Sbms	else
2311191665Sbms		im6f_commit(imf);
2312191665Sbms
2313191665Sbms	im6f_reap(imf);
2314191665Sbms
2315191665Sbms	if (is_final) {
2316191665Sbms		/* Remove the gap in the membership array. */
2317199522Sbms		for (++idx; idx < imo->im6o_num_memberships; ++idx) {
2318191665Sbms			imo->im6o_membership[idx-1] = imo->im6o_membership[idx];
2319199522Sbms			imo->im6o_mfilters[idx-1] = imo->im6o_mfilters[idx];
2320199522Sbms		}
2321191665Sbms		imo->im6o_num_memberships--;
2322191665Sbms	}
2323191665Sbms
2324191665Sbmsout_in6p_locked:
2325191665Sbms	INP_WUNLOCK(inp);
2326191665Sbms	return (error);
2327191665Sbms}
2328191665Sbms
2329191665Sbms/*
2330191665Sbms * Select the interface for transmitting IPv6 multicast datagrams.
2331191665Sbms *
2332191665Sbms * Either an instance of struct in6_addr or an instance of struct ipv6_mreqn
2333191665Sbms * may be passed to this socket option. An address of in6addr_any or an
2334191665Sbms * interface index of 0 is used to remove a previous selection.
2335191665Sbms * When no interface is selected, one is chosen for every send.
2336191665Sbms */
2337191665Sbmsstatic int
2338191665Sbmsin6p_set_multicast_if(struct inpcb *inp, struct sockopt *sopt)
2339191665Sbms{
2340191665Sbms	struct ifnet		*ifp;
2341191665Sbms	struct ip6_moptions	*imo;
2342191665Sbms	u_int			 ifindex;
2343191665Sbms	int			 error;
2344191665Sbms
2345191665Sbms	if (sopt->sopt_valsize != sizeof(u_int))
2346191665Sbms		return (EINVAL);
2347191665Sbms
2348191665Sbms	error = sooptcopyin(sopt, &ifindex, sizeof(u_int), sizeof(u_int));
2349191665Sbms	if (error)
2350191665Sbms		return (error);
2351270044Sbz	if (V_if_index < ifindex)
2352191665Sbms		return (EINVAL);
2353281911Sae	if (ifindex == 0)
2354281911Sae		ifp = NULL;
2355281911Sae	else {
2356281911Sae		ifp = ifnet_byindex(ifindex);
2357281911Sae		if (ifp == NULL)
2358281911Sae			return (EINVAL);
2359281911Sae		if ((ifp->if_flags & IFF_MULTICAST) == 0)
2360281911Sae			return (EADDRNOTAVAIL);
2361281911Sae	}
2362191665Sbms	imo = in6p_findmoptions(inp);
2363191665Sbms	imo->im6o_multicast_ifp = ifp;
2364191665Sbms	INP_WUNLOCK(inp);
2365191665Sbms
2366191665Sbms	return (0);
2367191665Sbms}
2368191665Sbms
2369191665Sbms/*
2370191665Sbms * Atomically set source filters on a socket for an IPv6 multicast group.
2371191665Sbms *
2372191665Sbms * SMPng: NOTE: Potentially calls malloc(M_WAITOK) with Giant held.
2373191665Sbms */
2374191665Sbmsstatic int
2375191665Sbmsin6p_set_source_filters(struct inpcb *inp, struct sockopt *sopt)
2376191665Sbms{
2377191665Sbms	struct __msfilterreq	 msfr;
2378191665Sbms	sockunion_t		*gsa;
2379191665Sbms	struct ifnet		*ifp;
2380191665Sbms	struct in6_mfilter	*imf;
2381191665Sbms	struct ip6_moptions	*imo;
2382191665Sbms	struct in6_multi		*inm;
2383191665Sbms	size_t			 idx;
2384191665Sbms	int			 error;
2385191665Sbms
2386191665Sbms	error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq),
2387191665Sbms	    sizeof(struct __msfilterreq));
2388191665Sbms	if (error)
2389191665Sbms		return (error);
2390191665Sbms
2391199523Sbms	if (msfr.msfr_nsrcs > in6_mcast_maxsocksrc)
2392199523Sbms		return (ENOBUFS);
2393199523Sbms
2394199523Sbms	if (msfr.msfr_fmode != MCAST_EXCLUDE &&
2395199523Sbms	    msfr.msfr_fmode != MCAST_INCLUDE)
2396191665Sbms		return (EINVAL);
2397191665Sbms
2398191665Sbms	if (msfr.msfr_group.ss_family != AF_INET6 ||
2399191665Sbms	    msfr.msfr_group.ss_len != sizeof(struct sockaddr_in6))
2400191665Sbms		return (EINVAL);
2401191665Sbms
2402191665Sbms	gsa = (sockunion_t *)&msfr.msfr_group;
2403191665Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
2404191665Sbms		return (EINVAL);
2405191665Sbms
2406191665Sbms	gsa->sin6.sin6_port = 0;	/* ignore port */
2407191665Sbms
2408191665Sbms	if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
2409191665Sbms		return (EADDRNOTAVAIL);
2410191665Sbms	ifp = ifnet_byindex(msfr.msfr_ifindex);
2411191665Sbms	if (ifp == NULL)
2412191665Sbms		return (EADDRNOTAVAIL);
2413192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
2414191665Sbms
2415191665Sbms	/*
2416191665Sbms	 * Take the INP write lock.
2417191665Sbms	 * Check if this socket is a member of this group.
2418191665Sbms	 */
2419191665Sbms	imo = in6p_findmoptions(inp);
2420191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
2421191665Sbms	if (idx == -1 || imo->im6o_mfilters == NULL) {
2422191665Sbms		error = EADDRNOTAVAIL;
2423191665Sbms		goto out_in6p_locked;
2424191665Sbms	}
2425191665Sbms	inm = imo->im6o_membership[idx];
2426191665Sbms	imf = &imo->im6o_mfilters[idx];
2427191665Sbms
2428191665Sbms	/*
2429191665Sbms	 * Begin state merge transaction at socket layer.
2430191665Sbms	 */
2431191665Sbms	INP_WLOCK_ASSERT(inp);
2432191665Sbms
2433191665Sbms	imf->im6f_st[1] = msfr.msfr_fmode;
2434191665Sbms
2435191665Sbms	/*
2436191665Sbms	 * Apply any new source filters, if present.
2437191665Sbms	 * Make a copy of the user-space source vector so
2438191665Sbms	 * that we may copy them with a single copyin. This
2439191665Sbms	 * allows us to deal with page faults up-front.
2440191665Sbms	 */
2441191665Sbms	if (msfr.msfr_nsrcs > 0) {
2442191665Sbms		struct in6_msource	*lims;
2443191665Sbms		struct sockaddr_in6	*psin;
2444191665Sbms		struct sockaddr_storage	*kss, *pkss;
2445191665Sbms		int			 i;
2446191665Sbms
2447191665Sbms		INP_WUNLOCK(inp);
2448191665Sbms
2449191665Sbms		CTR2(KTR_MLD, "%s: loading %lu source list entries",
2450191665Sbms		    __func__, (unsigned long)msfr.msfr_nsrcs);
2451191665Sbms		kss = malloc(sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs,
2452191665Sbms		    M_TEMP, M_WAITOK);
2453191665Sbms		error = copyin(msfr.msfr_srcs, kss,
2454191665Sbms		    sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs);
2455191665Sbms		if (error) {
2456191665Sbms			free(kss, M_TEMP);
2457191665Sbms			return (error);
2458191665Sbms		}
2459191665Sbms
2460191665Sbms		INP_WLOCK(inp);
2461191665Sbms
2462191665Sbms		/*
2463191665Sbms		 * Mark all source filters as UNDEFINED at t1.
2464191665Sbms		 * Restore new group filter mode, as im6f_leave()
2465191665Sbms		 * will set it to INCLUDE.
2466191665Sbms		 */
2467191665Sbms		im6f_leave(imf);
2468191665Sbms		imf->im6f_st[1] = msfr.msfr_fmode;
2469191665Sbms
2470191665Sbms		/*
2471191665Sbms		 * Update socket layer filters at t1, lazy-allocating
2472191665Sbms		 * new entries. This saves a bunch of memory at the
2473191665Sbms		 * cost of one RB_FIND() per source entry; duplicate
2474191665Sbms		 * entries in the msfr_nsrcs vector are ignored.
2475191665Sbms		 * If we encounter an error, rollback transaction.
2476191665Sbms		 *
2477191665Sbms		 * XXX This too could be replaced with a set-symmetric
2478191665Sbms		 * difference like loop to avoid walking from root
2479191665Sbms		 * every time, as the key space is common.
2480191665Sbms		 */
2481191665Sbms		for (i = 0, pkss = kss; i < msfr.msfr_nsrcs; i++, pkss++) {
2482191665Sbms			psin = (struct sockaddr_in6 *)pkss;
2483191665Sbms			if (psin->sin6_family != AF_INET6) {
2484191665Sbms				error = EAFNOSUPPORT;
2485191665Sbms				break;
2486191665Sbms			}
2487191665Sbms			if (psin->sin6_len != sizeof(struct sockaddr_in6)) {
2488191665Sbms				error = EINVAL;
2489191665Sbms				break;
2490191665Sbms			}
2491192923Sbms			if (IN6_IS_ADDR_MULTICAST(&psin->sin6_addr)) {
2492192923Sbms				error = EINVAL;
2493192923Sbms				break;
2494192923Sbms			}
2495192923Sbms			/*
2496192923Sbms			 * TODO: Validate embedded scope ID in source
2497192923Sbms			 * list entry against passed-in ifp, if and only
2498192923Sbms			 * if source list filter entry is iface or node local.
2499192923Sbms			 */
2500192923Sbms			in6_clearscope(&psin->sin6_addr);
2501191665Sbms			error = im6f_get_source(imf, psin, &lims);
2502191665Sbms			if (error)
2503191665Sbms				break;
2504191665Sbms			lims->im6sl_st[1] = imf->im6f_st[1];
2505191665Sbms		}
2506191665Sbms		free(kss, M_TEMP);
2507191665Sbms	}
2508191665Sbms
2509191665Sbms	if (error)
2510191665Sbms		goto out_im6f_rollback;
2511191665Sbms
2512191665Sbms	INP_WLOCK_ASSERT(inp);
2513191665Sbms	IN6_MULTI_LOCK();
2514191665Sbms
2515191665Sbms	/*
2516191665Sbms	 * Begin state merge transaction at MLD layer.
2517191665Sbms	 */
2518191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
2519191665Sbms	error = in6m_merge(inm, imf);
2520264722Sae	if (error)
2521191665Sbms		CTR1(KTR_MLD, "%s: failed to merge inm state", __func__);
2522264722Sae	else {
2523264722Sae		CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
2524264722Sae		error = mld_change_state(inm, 0);
2525264722Sae		if (error)
2526264722Sae			CTR1(KTR_MLD, "%s: failed mld downcall", __func__);
2527191665Sbms	}
2528191665Sbms
2529191665Sbms	IN6_MULTI_UNLOCK();
2530191665Sbms
2531191665Sbmsout_im6f_rollback:
2532191665Sbms	if (error)
2533191665Sbms		im6f_rollback(imf);
2534191665Sbms	else
2535191665Sbms		im6f_commit(imf);
2536191665Sbms
2537191665Sbms	im6f_reap(imf);
2538191665Sbms
2539191665Sbmsout_in6p_locked:
2540191665Sbms	INP_WUNLOCK(inp);
2541191665Sbms	return (error);
2542191665Sbms}
2543191665Sbms
2544191665Sbms/*
2545191665Sbms * Set the IP multicast options in response to user setsockopt().
2546191665Sbms *
2547191665Sbms * Many of the socket options handled in this function duplicate the
2548191665Sbms * functionality of socket options in the regular unicast API. However,
2549191665Sbms * it is not possible to merge the duplicate code, because the idempotence
2550191665Sbms * of the IPv6 multicast part of the BSD Sockets API must be preserved;
2551191665Sbms * the effects of these options must be treated as separate and distinct.
2552191665Sbms *
2553191665Sbms * SMPng: XXX: Unlocked read of inp_socket believed OK.
2554191665Sbms */
2555191665Sbmsint
2556191665Sbmsip6_setmoptions(struct inpcb *inp, struct sockopt *sopt)
2557191665Sbms{
2558191672Sbms	struct ip6_moptions	*im6o;
2559191665Sbms	int			 error;
2560191665Sbms
2561191665Sbms	error = 0;
2562191665Sbms
2563191665Sbms	/*
2564191665Sbms	 * If socket is neither of type SOCK_RAW or SOCK_DGRAM,
2565191665Sbms	 * or is a divert socket, reject it.
2566191665Sbms	 */
2567191665Sbms	if (inp->inp_socket->so_proto->pr_protocol == IPPROTO_DIVERT ||
2568191665Sbms	    (inp->inp_socket->so_proto->pr_type != SOCK_RAW &&
2569191665Sbms	     inp->inp_socket->so_proto->pr_type != SOCK_DGRAM))
2570191665Sbms		return (EOPNOTSUPP);
2571191665Sbms
2572191665Sbms	switch (sopt->sopt_name) {
2573191665Sbms	case IPV6_MULTICAST_IF:
2574191665Sbms		error = in6p_set_multicast_if(inp, sopt);
2575191665Sbms		break;
2576191665Sbms
2577191665Sbms	case IPV6_MULTICAST_HOPS: {
2578191665Sbms		int hlim;
2579191665Sbms
2580191665Sbms		if (sopt->sopt_valsize != sizeof(int)) {
2581191665Sbms			error = EINVAL;
2582191665Sbms			break;
2583191665Sbms		}
2584191665Sbms		error = sooptcopyin(sopt, &hlim, sizeof(hlim), sizeof(int));
2585191665Sbms		if (error)
2586191665Sbms			break;
2587191665Sbms		if (hlim < -1 || hlim > 255) {
2588191665Sbms			error = EINVAL;
2589191665Sbms			break;
2590191672Sbms		} else if (hlim == -1) {
2591191672Sbms			hlim = V_ip6_defmcasthlim;
2592191665Sbms		}
2593191672Sbms		im6o = in6p_findmoptions(inp);
2594191672Sbms		im6o->im6o_multicast_hlim = hlim;
2595191665Sbms		INP_WUNLOCK(inp);
2596191665Sbms		break;
2597191665Sbms	}
2598191665Sbms
2599191665Sbms	case IPV6_MULTICAST_LOOP: {
2600191665Sbms		u_int loop;
2601191665Sbms
2602191665Sbms		/*
2603191665Sbms		 * Set the loopback flag for outgoing multicast packets.
2604191672Sbms		 * Must be zero or one.
2605191665Sbms		 */
2606191665Sbms		if (sopt->sopt_valsize != sizeof(u_int)) {
2607191665Sbms			error = EINVAL;
2608191665Sbms			break;
2609191665Sbms		}
2610191665Sbms		error = sooptcopyin(sopt, &loop, sizeof(u_int), sizeof(u_int));
2611191665Sbms		if (error)
2612191665Sbms			break;
2613191672Sbms		if (loop > 1) {
2614191672Sbms			error = EINVAL;
2615191672Sbms			break;
2616191672Sbms		}
2617191672Sbms		im6o = in6p_findmoptions(inp);
2618191672Sbms		im6o->im6o_multicast_loop = loop;
2619191665Sbms		INP_WUNLOCK(inp);
2620191665Sbms		break;
2621191665Sbms	}
2622191665Sbms
2623191665Sbms	case IPV6_JOIN_GROUP:
2624191665Sbms	case MCAST_JOIN_GROUP:
2625191665Sbms	case MCAST_JOIN_SOURCE_GROUP:
2626191665Sbms		error = in6p_join_group(inp, sopt);
2627191665Sbms		break;
2628191665Sbms
2629191665Sbms	case IPV6_LEAVE_GROUP:
2630191665Sbms	case MCAST_LEAVE_GROUP:
2631191665Sbms	case MCAST_LEAVE_SOURCE_GROUP:
2632191665Sbms		error = in6p_leave_group(inp, sopt);
2633191665Sbms		break;
2634191665Sbms
2635191665Sbms	case MCAST_BLOCK_SOURCE:
2636191665Sbms	case MCAST_UNBLOCK_SOURCE:
2637191665Sbms		error = in6p_block_unblock_source(inp, sopt);
2638191665Sbms		break;
2639191665Sbms
2640191665Sbms	case IPV6_MSFILTER:
2641191665Sbms		error = in6p_set_source_filters(inp, sopt);
2642191665Sbms		break;
2643191665Sbms
2644191665Sbms	default:
2645191665Sbms		error = EOPNOTSUPP;
2646191665Sbms		break;
2647191665Sbms	}
2648191665Sbms
2649191665Sbms	INP_UNLOCK_ASSERT(inp);
2650191665Sbms
2651191665Sbms	return (error);
2652191665Sbms}
2653191665Sbms
2654191665Sbms/*
2655191665Sbms * Expose MLD's multicast filter mode and source list(s) to userland,
2656191665Sbms * keyed by (ifindex, group).
2657191665Sbms * The filter mode is written out as a uint32_t, followed by
2658191665Sbms * 0..n of struct in6_addr.
2659191665Sbms * For use by ifmcstat(8).
2660191665Sbms * SMPng: NOTE: unlocked read of ifindex space.
2661191665Sbms */
2662191665Sbmsstatic int
2663191665Sbmssysctl_ip6_mcast_filters(SYSCTL_HANDLER_ARGS)
2664191665Sbms{
2665192923Sbms	struct in6_addr			 mcaddr;
2666191665Sbms	struct in6_addr			 src;
2667191665Sbms	struct ifnet			*ifp;
2668191665Sbms	struct ifmultiaddr		*ifma;
2669191665Sbms	struct in6_multi		*inm;
2670191665Sbms	struct ip6_msource		*ims;
2671191665Sbms	int				*name;
2672191665Sbms	int				 retval;
2673191665Sbms	u_int				 namelen;
2674191665Sbms	uint32_t			 fmode, ifindex;
2675191665Sbms#ifdef KTR
2676191665Sbms	char				 ip6tbuf[INET6_ADDRSTRLEN];
2677191665Sbms#endif
2678191665Sbms
2679191665Sbms	name = (int *)arg1;
2680191665Sbms	namelen = arg2;
2681191665Sbms
2682191665Sbms	if (req->newptr != NULL)
2683191665Sbms		return (EPERM);
2684191665Sbms
2685191665Sbms	/* int: ifindex + 4 * 32 bits of IPv6 address */
2686191665Sbms	if (namelen != 5)
2687191665Sbms		return (EINVAL);
2688191665Sbms
2689191665Sbms	ifindex = name[0];
2690191665Sbms	if (ifindex <= 0 || ifindex > V_if_index) {
2691191665Sbms		CTR2(KTR_MLD, "%s: ifindex %u out of range",
2692191665Sbms		    __func__, ifindex);
2693191665Sbms		return (ENOENT);
2694191665Sbms	}
2695191665Sbms
2696192923Sbms	memcpy(&mcaddr, &name[1], sizeof(struct in6_addr));
2697192923Sbms	if (!IN6_IS_ADDR_MULTICAST(&mcaddr)) {
2698191665Sbms		CTR2(KTR_MLD, "%s: group %s is not multicast",
2699192923Sbms		    __func__, ip6_sprintf(ip6tbuf, &mcaddr));
2700191665Sbms		return (EINVAL);
2701191665Sbms	}
2702191665Sbms
2703191665Sbms	ifp = ifnet_byindex(ifindex);
2704191665Sbms	if (ifp == NULL) {
2705191665Sbms		CTR2(KTR_MLD, "%s: no ifp for ifindex %u",
2706191665Sbms		    __func__, ifindex);
2707191665Sbms		return (ENOENT);
2708191665Sbms	}
2709192923Sbms	/*
2710192923Sbms	 * Internal MLD lookups require that scope/zone ID is set.
2711192923Sbms	 */
2712192923Sbms	(void)in6_setscope(&mcaddr, ifp, NULL);
2713191665Sbms
2714191665Sbms	retval = sysctl_wire_old_buffer(req,
2715191665Sbms	    sizeof(uint32_t) + (in6_mcast_maxgrpsrc * sizeof(struct in6_addr)));
2716191665Sbms	if (retval)
2717191665Sbms		return (retval);
2718191665Sbms
2719191665Sbms	IN6_MULTI_LOCK();
2720191665Sbms
2721229621Sjhb	IF_ADDR_RLOCK(ifp);
2722191665Sbms	TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
2723191665Sbms		if (ifma->ifma_addr->sa_family != AF_INET6 ||
2724191665Sbms		    ifma->ifma_protospec == NULL)
2725191665Sbms			continue;
2726191665Sbms		inm = (struct in6_multi *)ifma->ifma_protospec;
2727192923Sbms		if (!IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, &mcaddr))
2728191665Sbms			continue;
2729191665Sbms		fmode = inm->in6m_st[1].iss_fmode;
2730191665Sbms		retval = SYSCTL_OUT(req, &fmode, sizeof(uint32_t));
2731191665Sbms		if (retval != 0)
2732191665Sbms			break;
2733191665Sbms		RB_FOREACH(ims, ip6_msource_tree, &inm->in6m_srcs) {
2734191665Sbms			CTR2(KTR_MLD, "%s: visit node %p", __func__, ims);
2735191665Sbms			/*
2736191665Sbms			 * Only copy-out sources which are in-mode.
2737191665Sbms			 */
2738191665Sbms			if (fmode != im6s_get_mode(inm, ims, 1)) {
2739191665Sbms				CTR1(KTR_MLD, "%s: skip non-in-mode",
2740191665Sbms				    __func__);
2741191665Sbms				continue;
2742191665Sbms			}
2743191665Sbms			src = ims->im6s_addr;
2744191665Sbms			retval = SYSCTL_OUT(req, &src,
2745191665Sbms			    sizeof(struct in6_addr));
2746191665Sbms			if (retval != 0)
2747191665Sbms				break;
2748191665Sbms		}
2749191665Sbms	}
2750229621Sjhb	IF_ADDR_RUNLOCK(ifp);
2751191665Sbms
2752191665Sbms	IN6_MULTI_UNLOCK();
2753191665Sbms
2754191665Sbms	return (retval);
2755191665Sbms}
2756191665Sbms
2757191665Sbms#ifdef KTR
2758191665Sbms
2759191665Sbmsstatic const char *in6m_modestrs[] = { "un", "in", "ex" };
2760191665Sbms
2761191665Sbmsstatic const char *
2762191665Sbmsin6m_mode_str(const int mode)
2763191665Sbms{
2764191665Sbms
2765191665Sbms	if (mode >= MCAST_UNDEFINED && mode <= MCAST_EXCLUDE)
2766191665Sbms		return (in6m_modestrs[mode]);
2767191665Sbms	return ("??");
2768191665Sbms}
2769191665Sbms
2770191665Sbmsstatic const char *in6m_statestrs[] = {
2771191665Sbms	"not-member",
2772191665Sbms	"silent",
2773191665Sbms	"idle",
2774191665Sbms	"lazy",
2775191665Sbms	"sleeping",
2776191665Sbms	"awakening",
2777191665Sbms	"query-pending",
2778191665Sbms	"sg-query-pending",
2779191665Sbms	"leaving"
2780191665Sbms};
2781191665Sbms
2782191665Sbmsstatic const char *
2783191665Sbmsin6m_state_str(const int state)
2784191665Sbms{
2785191665Sbms
2786191665Sbms	if (state >= MLD_NOT_MEMBER && state <= MLD_LEAVING_MEMBER)
2787191665Sbms		return (in6m_statestrs[state]);
2788191665Sbms	return ("??");
2789191665Sbms}
2790191665Sbms
2791191665Sbms/*
2792191665Sbms * Dump an in6_multi structure to the console.
2793191665Sbms */
2794191665Sbmsvoid
2795191665Sbmsin6m_print(const struct in6_multi *inm)
2796191665Sbms{
2797191665Sbms	int t;
2798191665Sbms	char ip6tbuf[INET6_ADDRSTRLEN];
2799191665Sbms
2800191828Skan	if ((ktr_mask & KTR_MLD) == 0)
2801191665Sbms		return;
2802191665Sbms
2803191665Sbms	printf("%s: --- begin in6m %p ---\n", __func__, inm);
2804191665Sbms	printf("addr %s ifp %p(%s) ifma %p\n",
2805191665Sbms	    ip6_sprintf(ip6tbuf, &inm->in6m_addr),
2806191665Sbms	    inm->in6m_ifp,
2807191665Sbms	    inm->in6m_ifp->if_xname,
2808191665Sbms	    inm->in6m_ifma);
2809191665Sbms	printf("timer %u state %s refcount %u scq.len %u\n",
2810191665Sbms	    inm->in6m_timer,
2811191665Sbms	    in6m_state_str(inm->in6m_state),
2812191665Sbms	    inm->in6m_refcount,
2813191665Sbms	    inm->in6m_scq.ifq_len);
2814191665Sbms	printf("mli %p nsrc %lu sctimer %u scrv %u\n",
2815191665Sbms	    inm->in6m_mli,
2816191665Sbms	    inm->in6m_nsrc,
2817191665Sbms	    inm->in6m_sctimer,
2818191665Sbms	    inm->in6m_scrv);
2819191665Sbms	for (t = 0; t < 2; t++) {
2820191665Sbms		printf("t%d: fmode %s asm %u ex %u in %u rec %u\n", t,
2821191665Sbms		    in6m_mode_str(inm->in6m_st[t].iss_fmode),
2822191665Sbms		    inm->in6m_st[t].iss_asm,
2823191665Sbms		    inm->in6m_st[t].iss_ex,
2824191665Sbms		    inm->in6m_st[t].iss_in,
2825191665Sbms		    inm->in6m_st[t].iss_rec);
2826191665Sbms	}
2827191665Sbms	printf("%s: --- end in6m %p ---\n", __func__, inm);
2828191665Sbms}
2829191665Sbms
2830191665Sbms#else /* !KTR */
2831191665Sbms
2832191665Sbmsvoid
2833191665Sbmsin6m_print(const struct in6_multi *inm)
2834191665Sbms{
2835191665Sbms
2836191665Sbms}
2837191665Sbms
2838191665Sbms#endif /* KTR */
2839