in6_mcast.c revision 199528
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: head/sys/netinet6/in6_mcast.c 199528 2009-11-19 13:39:07Z bms $");
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);
134191665Sbmsstatic int	in6m_is_ifp_detached(const struct in6_multi *);
135191665Sbmsstatic int	in6m_merge(struct in6_multi *, /*const*/ struct in6_mfilter *);
136191665Sbmsstatic void	in6m_purge(struct in6_multi *);
137191665Sbmsstatic void	in6m_reap(struct in6_multi *);
138191665Sbmsstatic struct ip6_moptions *
139191665Sbms		in6p_findmoptions(struct inpcb *);
140191665Sbmsstatic int	in6p_get_source_filters(struct inpcb *, struct sockopt *);
141191665Sbmsstatic int	in6p_join_group(struct inpcb *, struct sockopt *);
142191665Sbmsstatic int	in6p_leave_group(struct inpcb *, struct sockopt *);
143191672Sbmsstatic struct ifnet *
144191672Sbms		in6p_lookup_mcast_ifp(const struct inpcb *,
145191672Sbms		    const struct sockaddr_in6 *);
146191665Sbmsstatic int	in6p_block_unblock_source(struct inpcb *, struct sockopt *);
147191665Sbmsstatic int	in6p_set_multicast_if(struct inpcb *, struct sockopt *);
148191665Sbmsstatic int	in6p_set_source_filters(struct inpcb *, struct sockopt *);
149191665Sbmsstatic int	sysctl_ip6_mcast_filters(SYSCTL_HANDLER_ARGS);
150191665Sbms
151191665SbmsSYSCTL_DECL(_net_inet6_ip6);	/* XXX Not in any common header. */
152191665Sbms
153191665SbmsSYSCTL_NODE(_net_inet6_ip6, OID_AUTO, mcast, CTLFLAG_RW, 0, "IPv6 multicast");
154191665Sbms
155191665Sbmsstatic u_long in6_mcast_maxgrpsrc = IPV6_MAX_GROUP_SRC_FILTER;
156191665SbmsSYSCTL_ULONG(_net_inet6_ip6_mcast, OID_AUTO, maxgrpsrc,
157191665Sbms    CTLFLAG_RW | CTLFLAG_TUN, &in6_mcast_maxgrpsrc, 0,
158191665Sbms    "Max source filters per group");
159191665SbmsTUNABLE_ULONG("net.inet6.ip6.mcast.maxgrpsrc", &in6_mcast_maxgrpsrc);
160191665Sbms
161191665Sbmsstatic u_long in6_mcast_maxsocksrc = IPV6_MAX_SOCK_SRC_FILTER;
162191665SbmsSYSCTL_ULONG(_net_inet6_ip6_mcast, OID_AUTO, maxsocksrc,
163191665Sbms    CTLFLAG_RW | CTLFLAG_TUN, &in6_mcast_maxsocksrc, 0,
164191665Sbms    "Max source filters per socket");
165191665SbmsTUNABLE_ULONG("net.inet6.ip6.mcast.maxsocksrc", &in6_mcast_maxsocksrc);
166191665Sbms
167191665Sbms/* TODO Virtualize this switch. */
168191665Sbmsint in6_mcast_loop = IPV6_DEFAULT_MULTICAST_LOOP;
169191665SbmsSYSCTL_INT(_net_inet6_ip6_mcast, OID_AUTO, loop, CTLFLAG_RW | CTLFLAG_TUN,
170191665Sbms    &in6_mcast_loop, 0, "Loopback multicast datagrams by default");
171191665SbmsTUNABLE_INT("net.inet6.ip6.mcast.loop", &in6_mcast_loop);
172191665Sbms
173191665SbmsSYSCTL_NODE(_net_inet6_ip6_mcast, OID_AUTO, filters,
174191665Sbms    CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_ip6_mcast_filters,
175191665Sbms    "Per-interface stack-wide source filters");
176191665Sbms
177191665Sbms/*
178191665Sbms * Inline function which wraps assertions for a valid ifp.
179191665Sbms * The ifnet layer will set the ifma's ifp pointer to NULL if the ifp
180191665Sbms * is detached.
181191665Sbms */
182191665Sbmsstatic int __inline
183191665Sbmsin6m_is_ifp_detached(const struct in6_multi *inm)
184191665Sbms{
185191665Sbms	struct ifnet *ifp;
186191665Sbms
187191665Sbms	KASSERT(inm->in6m_ifma != NULL, ("%s: no ifma", __func__));
188191665Sbms	ifp = inm->in6m_ifma->ifma_ifp;
189191665Sbms	if (ifp != NULL) {
190191665Sbms		/*
191191665Sbms		 * Sanity check that network-layer notion of ifp is the
192191665Sbms		 * same as that of link-layer.
193191665Sbms		 */
194191665Sbms		KASSERT(inm->in6m_ifp == ifp, ("%s: bad ifp", __func__));
195191665Sbms	}
196191665Sbms
197191665Sbms	return (ifp == NULL);
198191665Sbms}
199191665Sbms
200191665Sbms/*
201191665Sbms * Initialize an in6_mfilter structure to a known state at t0, t1
202191665Sbms * with an empty source filter list.
203191665Sbms */
204191665Sbmsstatic __inline void
205191665Sbmsim6f_init(struct in6_mfilter *imf, const int st0, const int st1)
206191665Sbms{
207191665Sbms	memset(imf, 0, sizeof(struct in6_mfilter));
208191665Sbms	RB_INIT(&imf->im6f_sources);
209191665Sbms	imf->im6f_st[0] = st0;
210191665Sbms	imf->im6f_st[1] = st1;
211191665Sbms}
212191665Sbms
213191665Sbms/*
214191665Sbms * Resize the ip6_moptions vector to the next power-of-two minus 1.
215191665Sbms * May be called with locks held; do not sleep.
216191665Sbms */
217191665Sbmsstatic int
218191665Sbmsim6o_grow(struct ip6_moptions *imo)
219191665Sbms{
220191665Sbms	struct in6_multi	**nmships;
221191665Sbms	struct in6_multi	**omships;
222191665Sbms	struct in6_mfilter	 *nmfilters;
223191665Sbms	struct in6_mfilter	 *omfilters;
224191665Sbms	size_t			  idx;
225191665Sbms	size_t			  newmax;
226191665Sbms	size_t			  oldmax;
227191665Sbms
228191665Sbms	nmships = NULL;
229191665Sbms	nmfilters = NULL;
230191665Sbms	omships = imo->im6o_membership;
231191665Sbms	omfilters = imo->im6o_mfilters;
232191665Sbms	oldmax = imo->im6o_max_memberships;
233191665Sbms	newmax = ((oldmax + 1) * 2) - 1;
234191665Sbms
235191665Sbms	if (newmax <= IPV6_MAX_MEMBERSHIPS) {
236191665Sbms		nmships = (struct in6_multi **)realloc(omships,
237191665Sbms		    sizeof(struct in6_multi *) * newmax, M_IP6MOPTS, M_NOWAIT);
238191665Sbms		nmfilters = (struct in6_mfilter *)realloc(omfilters,
239191665Sbms		    sizeof(struct in6_mfilter) * newmax, M_IN6MFILTER,
240191665Sbms		    M_NOWAIT);
241191665Sbms		if (nmships != NULL && nmfilters != NULL) {
242191665Sbms			/* Initialize newly allocated source filter heads. */
243191665Sbms			for (idx = oldmax; idx < newmax; idx++) {
244191665Sbms				im6f_init(&nmfilters[idx], MCAST_UNDEFINED,
245191665Sbms				    MCAST_EXCLUDE);
246191665Sbms			}
247191665Sbms			imo->im6o_max_memberships = newmax;
248191665Sbms			imo->im6o_membership = nmships;
249191665Sbms			imo->im6o_mfilters = nmfilters;
250191665Sbms		}
251191665Sbms	}
252191665Sbms
253191665Sbms	if (nmships == NULL || nmfilters == NULL) {
254191665Sbms		if (nmships != NULL)
255191665Sbms			free(nmships, M_IP6MOPTS);
256191665Sbms		if (nmfilters != NULL)
257191665Sbms			free(nmfilters, M_IN6MFILTER);
258191665Sbms		return (ETOOMANYREFS);
259191665Sbms	}
260191665Sbms
261191665Sbms	return (0);
262191665Sbms}
263191665Sbms
264191665Sbms/*
265191665Sbms * Find an IPv6 multicast group entry for this ip6_moptions instance
266191665Sbms * which matches the specified group, and optionally an interface.
267191665Sbms * Return its index into the array, or -1 if not found.
268191665Sbms */
269191665Sbmsstatic size_t
270191665Sbmsim6o_match_group(const struct ip6_moptions *imo, const struct ifnet *ifp,
271191665Sbms    const struct sockaddr *group)
272191665Sbms{
273191665Sbms	const struct sockaddr_in6 *gsin6;
274191665Sbms	struct in6_multi	**pinm;
275191665Sbms	int		  idx;
276191665Sbms	int		  nmships;
277191665Sbms
278191665Sbms	gsin6 = (const struct sockaddr_in6 *)group;
279191665Sbms
280191665Sbms	/* The im6o_membership array may be lazy allocated. */
281191665Sbms	if (imo->im6o_membership == NULL || imo->im6o_num_memberships == 0)
282191665Sbms		return (-1);
283191665Sbms
284191665Sbms	nmships = imo->im6o_num_memberships;
285191665Sbms	pinm = &imo->im6o_membership[0];
286191665Sbms	for (idx = 0; idx < nmships; idx++, pinm++) {
287191665Sbms		if (*pinm == NULL)
288191665Sbms			continue;
289191665Sbms		if ((ifp == NULL || ((*pinm)->in6m_ifp == ifp)) &&
290191665Sbms		    IN6_ARE_ADDR_EQUAL(&(*pinm)->in6m_addr,
291191665Sbms		    &gsin6->sin6_addr)) {
292191665Sbms			break;
293191665Sbms		}
294191665Sbms	}
295191665Sbms	if (idx >= nmships)
296191665Sbms		idx = -1;
297191665Sbms
298191665Sbms	return (idx);
299191665Sbms}
300191665Sbms
301191665Sbms/*
302191665Sbms * Find an IPv6 multicast source entry for this imo which matches
303191665Sbms * the given group index for this socket, and source address.
304191665Sbms *
305192923Sbms * XXX TODO: The scope ID, if present in src, is stripped before
306192923Sbms * any comparison. We SHOULD enforce scope/zone checks where the source
307192923Sbms * filter entry has a link scope.
308192923Sbms *
309191665Sbms * NOTE: This does not check if the entry is in-mode, merely if
310191665Sbms * it exists, which may not be the desired behaviour.
311191665Sbms */
312191665Sbmsstatic struct in6_msource *
313191665Sbmsim6o_match_source(const struct ip6_moptions *imo, const size_t gidx,
314191665Sbms    const struct sockaddr *src)
315191665Sbms{
316191665Sbms	struct ip6_msource	 find;
317191665Sbms	struct in6_mfilter	*imf;
318191665Sbms	struct ip6_msource	*ims;
319191665Sbms	const sockunion_t	*psa;
320191665Sbms
321191665Sbms	KASSERT(src->sa_family == AF_INET6, ("%s: !AF_INET6", __func__));
322191665Sbms	KASSERT(gidx != -1 && gidx < imo->im6o_num_memberships,
323191665Sbms	    ("%s: invalid index %d\n", __func__, (int)gidx));
324191665Sbms
325191665Sbms	/* The im6o_mfilters array may be lazy allocated. */
326191665Sbms	if (imo->im6o_mfilters == NULL)
327191665Sbms		return (NULL);
328191665Sbms	imf = &imo->im6o_mfilters[gidx];
329191665Sbms
330191665Sbms	psa = (const sockunion_t *)src;
331191665Sbms	find.im6s_addr = psa->sin6.sin6_addr;
332192923Sbms	in6_clearscope(&find.im6s_addr);		/* XXX */
333191665Sbms	ims = RB_FIND(ip6_msource_tree, &imf->im6f_sources, &find);
334191665Sbms
335191665Sbms	return ((struct in6_msource *)ims);
336191665Sbms}
337191665Sbms
338191665Sbms/*
339191665Sbms * Perform filtering for multicast datagrams on a socket by group and source.
340191665Sbms *
341191665Sbms * Returns 0 if a datagram should be allowed through, or various error codes
342191665Sbms * if the socket was not a member of the group, or the source was muted, etc.
343191665Sbms */
344191665Sbmsint
345191665Sbmsim6o_mc_filter(const struct ip6_moptions *imo, const struct ifnet *ifp,
346191665Sbms    const struct sockaddr *group, const struct sockaddr *src)
347191665Sbms{
348191665Sbms	size_t gidx;
349191665Sbms	struct in6_msource *ims;
350191665Sbms	int mode;
351191665Sbms
352191665Sbms	KASSERT(ifp != NULL, ("%s: null ifp", __func__));
353191665Sbms
354191665Sbms	gidx = im6o_match_group(imo, ifp, group);
355191665Sbms	if (gidx == -1)
356191665Sbms		return (MCAST_NOTGMEMBER);
357191665Sbms
358191665Sbms	/*
359191665Sbms	 * Check if the source was included in an (S,G) join.
360191665Sbms	 * Allow reception on exclusive memberships by default,
361191665Sbms	 * reject reception on inclusive memberships by default.
362191665Sbms	 * Exclude source only if an in-mode exclude filter exists.
363191665Sbms	 * Include source only if an in-mode include filter exists.
364191665Sbms	 * NOTE: We are comparing group state here at MLD t1 (now)
365191665Sbms	 * with socket-layer t0 (since last downcall).
366191665Sbms	 */
367191665Sbms	mode = imo->im6o_mfilters[gidx].im6f_st[1];
368191665Sbms	ims = im6o_match_source(imo, gidx, src);
369191665Sbms
370191665Sbms	if ((ims == NULL && mode == MCAST_INCLUDE) ||
371191665Sbms	    (ims != NULL && ims->im6sl_st[0] != mode))
372191665Sbms		return (MCAST_NOTSMEMBER);
373191665Sbms
374191665Sbms	return (MCAST_PASS);
375191665Sbms}
376191665Sbms
377191665Sbms/*
378191665Sbms * Find and return a reference to an in6_multi record for (ifp, group),
379191665Sbms * and bump its reference count.
380191665Sbms * If one does not exist, try to allocate it, and update link-layer multicast
381191665Sbms * filters on ifp to listen for group.
382191665Sbms * Assumes the IN6_MULTI lock is held across the call.
383191665Sbms * Return 0 if successful, otherwise return an appropriate error code.
384191665Sbms */
385191665Sbmsstatic int
386191665Sbmsin6_mc_get(struct ifnet *ifp, const struct in6_addr *group,
387191665Sbms    struct in6_multi **pinm)
388191665Sbms{
389191665Sbms	struct sockaddr_in6	 gsin6;
390191665Sbms	struct ifmultiaddr	*ifma;
391191665Sbms	struct in6_multi	*inm;
392191665Sbms	int			 error;
393191665Sbms
394191665Sbms	error = 0;
395191665Sbms
396191665Sbms	/*
397191665Sbms	 * XXX: Accesses to ifma_protospec must be covered by IF_ADDR_LOCK;
398191665Sbms	 * if_addmulti() takes this mutex itself, so we must drop and
399191665Sbms	 * re-acquire around the call.
400191665Sbms	 */
401191665Sbms	IN6_MULTI_LOCK_ASSERT();
402191665Sbms	IF_ADDR_LOCK(ifp);
403191665Sbms
404191665Sbms	inm = in6m_lookup_locked(ifp, group);
405191665Sbms	if (inm != NULL) {
406191665Sbms		/*
407191665Sbms		 * If we already joined this group, just bump the
408191665Sbms		 * refcount and return it.
409191665Sbms		 */
410191665Sbms		KASSERT(inm->in6m_refcount >= 1,
411191665Sbms		    ("%s: bad refcount %d", __func__, inm->in6m_refcount));
412191665Sbms		++inm->in6m_refcount;
413191665Sbms		*pinm = inm;
414191665Sbms		goto out_locked;
415191665Sbms	}
416191665Sbms
417191665Sbms	memset(&gsin6, 0, sizeof(gsin6));
418191665Sbms	gsin6.sin6_family = AF_INET6;
419191665Sbms	gsin6.sin6_len = sizeof(struct sockaddr_in6);
420191665Sbms	gsin6.sin6_addr = *group;
421191665Sbms
422191665Sbms	/*
423191665Sbms	 * Check if a link-layer group is already associated
424191665Sbms	 * with this network-layer group on the given ifnet.
425191665Sbms	 */
426191665Sbms	IF_ADDR_UNLOCK(ifp);
427191665Sbms	error = if_addmulti(ifp, (struct sockaddr *)&gsin6, &ifma);
428191665Sbms	if (error != 0)
429191665Sbms		return (error);
430191665Sbms	IF_ADDR_LOCK(ifp);
431191665Sbms
432191665Sbms	/*
433191665Sbms	 * If something other than netinet6 is occupying the link-layer
434191665Sbms	 * group, print a meaningful error message and back out of
435191665Sbms	 * the allocation.
436191665Sbms	 * Otherwise, bump the refcount on the existing network-layer
437191665Sbms	 * group association and return it.
438191665Sbms	 */
439191665Sbms	if (ifma->ifma_protospec != NULL) {
440191665Sbms		inm = (struct in6_multi *)ifma->ifma_protospec;
441191665Sbms#ifdef INVARIANTS
442191665Sbms		KASSERT(ifma->ifma_addr != NULL, ("%s: no ifma_addr",
443191665Sbms		    __func__));
444191665Sbms		KASSERT(ifma->ifma_addr->sa_family == AF_INET6,
445191665Sbms		    ("%s: ifma not AF_INET6", __func__));
446191665Sbms		KASSERT(inm != NULL, ("%s: no ifma_protospec", __func__));
447191665Sbms		if (inm->in6m_ifma != ifma || inm->in6m_ifp != ifp ||
448191665Sbms		    !IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, group))
449191665Sbms			panic("%s: ifma %p is inconsistent with %p (%p)",
450191665Sbms			    __func__, ifma, inm, group);
451191665Sbms#endif
452191665Sbms		++inm->in6m_refcount;
453191665Sbms		*pinm = inm;
454191665Sbms		goto out_locked;
455191665Sbms	}
456191665Sbms
457191665Sbms	IF_ADDR_LOCK_ASSERT(ifp);
458191665Sbms
459191665Sbms	/*
460191665Sbms	 * A new in6_multi record is needed; allocate and initialize it.
461191665Sbms	 * We DO NOT perform an MLD join as the in6_ layer may need to
462191665Sbms	 * push an initial source list down to MLD to support SSM.
463191665Sbms	 *
464191665Sbms	 * The initial source filter state is INCLUDE, {} as per the RFC.
465191665Sbms	 * Pending state-changes per group are subject to a bounds check.
466191665Sbms	 */
467191665Sbms	inm = malloc(sizeof(*inm), M_IP6MADDR, M_NOWAIT | M_ZERO);
468191665Sbms	if (inm == NULL) {
469191665Sbms		if_delmulti_ifma(ifma);
470191665Sbms		error = ENOMEM;
471191665Sbms		goto out_locked;
472191665Sbms	}
473191665Sbms	inm->in6m_addr = *group;
474191665Sbms	inm->in6m_ifp = ifp;
475191665Sbms	inm->in6m_mli = MLD_IFINFO(ifp);
476191665Sbms	inm->in6m_ifma = ifma;
477191665Sbms	inm->in6m_refcount = 1;
478191665Sbms	inm->in6m_state = MLD_NOT_MEMBER;
479191665Sbms	IFQ_SET_MAXLEN(&inm->in6m_scq, MLD_MAX_STATE_CHANGES);
480191665Sbms
481191665Sbms	inm->in6m_st[0].iss_fmode = MCAST_UNDEFINED;
482191665Sbms	inm->in6m_st[1].iss_fmode = MCAST_UNDEFINED;
483191665Sbms	RB_INIT(&inm->in6m_srcs);
484191665Sbms
485191665Sbms	ifma->ifma_protospec = inm;
486191665Sbms	*pinm = inm;
487191665Sbms
488191665Sbmsout_locked:
489191665Sbms	IF_ADDR_UNLOCK(ifp);
490191665Sbms	return (error);
491191665Sbms}
492191665Sbms
493191665Sbms/*
494191665Sbms * Drop a reference to an in6_multi record.
495191665Sbms *
496191665Sbms * If the refcount drops to 0, free the in6_multi record and
497191665Sbms * delete the underlying link-layer membership.
498191665Sbms */
499191665Sbmsvoid
500191665Sbmsin6m_release_locked(struct in6_multi *inm)
501191665Sbms{
502191665Sbms	struct ifmultiaddr *ifma;
503191665Sbms
504191665Sbms	IN6_MULTI_LOCK_ASSERT();
505191665Sbms
506191665Sbms	CTR2(KTR_MLD, "%s: refcount is %d", __func__, inm->in6m_refcount);
507191665Sbms
508191665Sbms	if (--inm->in6m_refcount > 0) {
509191665Sbms		CTR2(KTR_MLD, "%s: refcount is now %d", __func__,
510191665Sbms		    inm->in6m_refcount);
511191665Sbms		return;
512191665Sbms	}
513191665Sbms
514191665Sbms	CTR2(KTR_MLD, "%s: freeing inm %p", __func__, inm);
515191665Sbms
516191665Sbms	ifma = inm->in6m_ifma;
517191665Sbms
518191665Sbms	/* XXX this access is not covered by IF_ADDR_LOCK */
519191665Sbms	CTR2(KTR_MLD, "%s: purging ifma %p", __func__, ifma);
520191665Sbms	KASSERT(ifma->ifma_protospec == inm,
521191665Sbms	    ("%s: ifma_protospec != inm", __func__));
522191665Sbms	ifma->ifma_protospec = NULL;
523191665Sbms
524191665Sbms	in6m_purge(inm);
525191665Sbms
526191665Sbms	free(inm, M_IP6MADDR);
527191665Sbms
528191665Sbms	if_delmulti_ifma(ifma);
529191665Sbms}
530191665Sbms
531191665Sbms/*
532191665Sbms * Clear recorded source entries for a group.
533191665Sbms * Used by the MLD code. Caller must hold the IN6_MULTI lock.
534191665Sbms * FIXME: Should reap.
535191665Sbms */
536191665Sbmsvoid
537191665Sbmsin6m_clear_recorded(struct in6_multi *inm)
538191665Sbms{
539191665Sbms	struct ip6_msource	*ims;
540191665Sbms
541191665Sbms	IN6_MULTI_LOCK_ASSERT();
542191665Sbms
543191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &inm->in6m_srcs) {
544191665Sbms		if (ims->im6s_stp) {
545191665Sbms			ims->im6s_stp = 0;
546191665Sbms			--inm->in6m_st[1].iss_rec;
547191665Sbms		}
548191665Sbms	}
549191665Sbms	KASSERT(inm->in6m_st[1].iss_rec == 0,
550191665Sbms	    ("%s: iss_rec %d not 0", __func__, inm->in6m_st[1].iss_rec));
551191665Sbms}
552191665Sbms
553191665Sbms/*
554191665Sbms * Record a source as pending for a Source-Group MLDv2 query.
555191665Sbms * This lives here as it modifies the shared tree.
556191665Sbms *
557191665Sbms * inm is the group descriptor.
558191665Sbms * naddr is the address of the source to record in network-byte order.
559191665Sbms *
560191665Sbms * If the net.inet6.mld.sgalloc sysctl is non-zero, we will
561191665Sbms * lazy-allocate a source node in response to an SG query.
562191665Sbms * Otherwise, no allocation is performed. This saves some memory
563191665Sbms * with the trade-off that the source will not be reported to the
564191665Sbms * router if joined in the window between the query response and
565191665Sbms * the group actually being joined on the local host.
566191665Sbms *
567191665Sbms * VIMAGE: XXX: Currently the mld_sgalloc feature has been removed.
568191665Sbms * This turns off the allocation of a recorded source entry if
569191665Sbms * the group has not been joined.
570191665Sbms *
571191665Sbms * Return 0 if the source didn't exist or was already marked as recorded.
572191665Sbms * Return 1 if the source was marked as recorded by this function.
573191665Sbms * Return <0 if any error occured (negated errno code).
574191665Sbms */
575191665Sbmsint
576191665Sbmsin6m_record_source(struct in6_multi *inm, const struct in6_addr *addr)
577191665Sbms{
578191665Sbms	struct ip6_msource	 find;
579191665Sbms	struct ip6_msource	*ims, *nims;
580191665Sbms
581191665Sbms	IN6_MULTI_LOCK_ASSERT();
582191665Sbms
583191665Sbms	find.im6s_addr = *addr;
584191665Sbms	ims = RB_FIND(ip6_msource_tree, &inm->in6m_srcs, &find);
585191665Sbms	if (ims && ims->im6s_stp)
586191665Sbms		return (0);
587191665Sbms	if (ims == NULL) {
588191665Sbms		if (inm->in6m_nsrc == in6_mcast_maxgrpsrc)
589191665Sbms			return (-ENOSPC);
590191665Sbms		nims = malloc(sizeof(struct ip6_msource), M_IP6MSOURCE,
591191665Sbms		    M_NOWAIT | M_ZERO);
592191665Sbms		if (nims == NULL)
593191665Sbms			return (-ENOMEM);
594191665Sbms		nims->im6s_addr = find.im6s_addr;
595191665Sbms		RB_INSERT(ip6_msource_tree, &inm->in6m_srcs, nims);
596191665Sbms		++inm->in6m_nsrc;
597191665Sbms		ims = nims;
598191665Sbms	}
599191665Sbms
600191665Sbms	/*
601191665Sbms	 * Mark the source as recorded and update the recorded
602191665Sbms	 * source count.
603191665Sbms	 */
604191665Sbms	++ims->im6s_stp;
605191665Sbms	++inm->in6m_st[1].iss_rec;
606191665Sbms
607191665Sbms	return (1);
608191665Sbms}
609191665Sbms
610191665Sbms/*
611191665Sbms * Return a pointer to an in6_msource owned by an in6_mfilter,
612191665Sbms * given its source address.
613191665Sbms * Lazy-allocate if needed. If this is a new entry its filter state is
614191665Sbms * undefined at t0.
615191665Sbms *
616191665Sbms * imf is the filter set being modified.
617191665Sbms * addr is the source address.
618191665Sbms *
619191665Sbms * SMPng: May be called with locks held; malloc must not block.
620191665Sbms */
621191665Sbmsstatic int
622191665Sbmsim6f_get_source(struct in6_mfilter *imf, const struct sockaddr_in6 *psin,
623191665Sbms    struct in6_msource **plims)
624191665Sbms{
625191665Sbms	struct ip6_msource	 find;
626191665Sbms	struct ip6_msource	*ims, *nims;
627191665Sbms	struct in6_msource	*lims;
628191665Sbms	int			 error;
629191665Sbms
630191665Sbms	error = 0;
631191665Sbms	ims = NULL;
632191665Sbms	lims = NULL;
633191665Sbms
634191665Sbms	find.im6s_addr = psin->sin6_addr;
635191665Sbms	ims = RB_FIND(ip6_msource_tree, &imf->im6f_sources, &find);
636191665Sbms	lims = (struct in6_msource *)ims;
637191665Sbms	if (lims == NULL) {
638191665Sbms		if (imf->im6f_nsrc == in6_mcast_maxsocksrc)
639191665Sbms			return (ENOSPC);
640191665Sbms		nims = malloc(sizeof(struct in6_msource), M_IN6MFILTER,
641191665Sbms		    M_NOWAIT | M_ZERO);
642191665Sbms		if (nims == NULL)
643191665Sbms			return (ENOMEM);
644191665Sbms		lims = (struct in6_msource *)nims;
645191665Sbms		lims->im6s_addr = find.im6s_addr;
646191665Sbms		lims->im6sl_st[0] = MCAST_UNDEFINED;
647191665Sbms		RB_INSERT(ip6_msource_tree, &imf->im6f_sources, nims);
648191665Sbms		++imf->im6f_nsrc;
649191665Sbms	}
650191665Sbms
651191665Sbms	*plims = lims;
652191665Sbms
653191665Sbms	return (error);
654191665Sbms}
655191665Sbms
656191665Sbms/*
657191665Sbms * Graft a source entry into an existing socket-layer filter set,
658191665Sbms * maintaining any required invariants and checking allocations.
659191665Sbms *
660191665Sbms * The source is marked as being in the new filter mode at t1.
661191665Sbms *
662191665Sbms * Return the pointer to the new node, otherwise return NULL.
663191665Sbms */
664191665Sbmsstatic struct in6_msource *
665191665Sbmsim6f_graft(struct in6_mfilter *imf, const uint8_t st1,
666191665Sbms    const struct sockaddr_in6 *psin)
667191665Sbms{
668191665Sbms	struct ip6_msource	*nims;
669191665Sbms	struct in6_msource	*lims;
670191665Sbms
671191665Sbms	nims = malloc(sizeof(struct in6_msource), M_IN6MFILTER,
672191665Sbms	    M_NOWAIT | M_ZERO);
673191665Sbms	if (nims == NULL)
674191665Sbms		return (NULL);
675191665Sbms	lims = (struct in6_msource *)nims;
676191665Sbms	lims->im6s_addr = psin->sin6_addr;
677191665Sbms	lims->im6sl_st[0] = MCAST_UNDEFINED;
678191665Sbms	lims->im6sl_st[1] = st1;
679191665Sbms	RB_INSERT(ip6_msource_tree, &imf->im6f_sources, nims);
680191665Sbms	++imf->im6f_nsrc;
681191665Sbms
682191665Sbms	return (lims);
683191665Sbms}
684191665Sbms
685191665Sbms/*
686191665Sbms * Prune a source entry from an existing socket-layer filter set,
687191665Sbms * maintaining any required invariants and checking allocations.
688191665Sbms *
689191665Sbms * The source is marked as being left at t1, it is not freed.
690191665Sbms *
691191665Sbms * Return 0 if no error occurred, otherwise return an errno value.
692191665Sbms */
693191665Sbmsstatic int
694191665Sbmsim6f_prune(struct in6_mfilter *imf, const struct sockaddr_in6 *psin)
695191665Sbms{
696191665Sbms	struct ip6_msource	 find;
697191665Sbms	struct ip6_msource	*ims;
698191665Sbms	struct in6_msource	*lims;
699191665Sbms
700191665Sbms	find.im6s_addr = psin->sin6_addr;
701191665Sbms	ims = RB_FIND(ip6_msource_tree, &imf->im6f_sources, &find);
702191665Sbms	if (ims == NULL)
703191665Sbms		return (ENOENT);
704191665Sbms	lims = (struct in6_msource *)ims;
705191665Sbms	lims->im6sl_st[1] = MCAST_UNDEFINED;
706191665Sbms	return (0);
707191665Sbms}
708191665Sbms
709191665Sbms/*
710191665Sbms * Revert socket-layer filter set deltas at t1 to t0 state.
711191665Sbms */
712191665Sbmsstatic void
713191665Sbmsim6f_rollback(struct in6_mfilter *imf)
714191665Sbms{
715191665Sbms	struct ip6_msource	*ims, *tims;
716191665Sbms	struct in6_msource	*lims;
717191665Sbms
718191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &imf->im6f_sources, tims) {
719191665Sbms		lims = (struct in6_msource *)ims;
720191665Sbms		if (lims->im6sl_st[0] == lims->im6sl_st[1]) {
721191665Sbms			/* no change at t1 */
722191665Sbms			continue;
723191665Sbms		} else if (lims->im6sl_st[0] != MCAST_UNDEFINED) {
724191665Sbms			/* revert change to existing source at t1 */
725191665Sbms			lims->im6sl_st[1] = lims->im6sl_st[0];
726191665Sbms		} else {
727191665Sbms			/* revert source added t1 */
728191665Sbms			CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
729191665Sbms			RB_REMOVE(ip6_msource_tree, &imf->im6f_sources, ims);
730191665Sbms			free(ims, M_IN6MFILTER);
731191665Sbms			imf->im6f_nsrc--;
732191665Sbms		}
733191665Sbms	}
734191665Sbms	imf->im6f_st[1] = imf->im6f_st[0];
735191665Sbms}
736191665Sbms
737191665Sbms/*
738191665Sbms * Mark socket-layer filter set as INCLUDE {} at t1.
739191665Sbms */
740191665Sbmsstatic void
741191665Sbmsim6f_leave(struct in6_mfilter *imf)
742191665Sbms{
743191665Sbms	struct ip6_msource	*ims;
744191665Sbms	struct in6_msource	*lims;
745191665Sbms
746191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
747191665Sbms		lims = (struct in6_msource *)ims;
748191665Sbms		lims->im6sl_st[1] = MCAST_UNDEFINED;
749191665Sbms	}
750191665Sbms	imf->im6f_st[1] = MCAST_INCLUDE;
751191665Sbms}
752191665Sbms
753191665Sbms/*
754191665Sbms * Mark socket-layer filter set deltas as committed.
755191665Sbms */
756191665Sbmsstatic void
757191665Sbmsim6f_commit(struct in6_mfilter *imf)
758191665Sbms{
759191665Sbms	struct ip6_msource	*ims;
760191665Sbms	struct in6_msource	*lims;
761191665Sbms
762191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
763191665Sbms		lims = (struct in6_msource *)ims;
764191665Sbms		lims->im6sl_st[0] = lims->im6sl_st[1];
765191665Sbms	}
766191665Sbms	imf->im6f_st[0] = imf->im6f_st[1];
767191665Sbms}
768191665Sbms
769191665Sbms/*
770191665Sbms * Reap unreferenced sources from socket-layer filter set.
771191665Sbms */
772191665Sbmsstatic void
773191665Sbmsim6f_reap(struct in6_mfilter *imf)
774191665Sbms{
775191665Sbms	struct ip6_msource	*ims, *tims;
776191665Sbms	struct in6_msource	*lims;
777191665Sbms
778191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &imf->im6f_sources, tims) {
779191665Sbms		lims = (struct in6_msource *)ims;
780191665Sbms		if ((lims->im6sl_st[0] == MCAST_UNDEFINED) &&
781191665Sbms		    (lims->im6sl_st[1] == MCAST_UNDEFINED)) {
782191665Sbms			CTR2(KTR_MLD, "%s: free lims %p", __func__, ims);
783191665Sbms			RB_REMOVE(ip6_msource_tree, &imf->im6f_sources, ims);
784191665Sbms			free(ims, M_IN6MFILTER);
785191665Sbms			imf->im6f_nsrc--;
786191665Sbms		}
787191665Sbms	}
788191665Sbms}
789191665Sbms
790191665Sbms/*
791191665Sbms * Purge socket-layer filter set.
792191665Sbms */
793191665Sbmsstatic void
794191665Sbmsim6f_purge(struct in6_mfilter *imf)
795191665Sbms{
796191665Sbms	struct ip6_msource	*ims, *tims;
797191665Sbms
798191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &imf->im6f_sources, tims) {
799191665Sbms		CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
800191665Sbms		RB_REMOVE(ip6_msource_tree, &imf->im6f_sources, ims);
801191665Sbms		free(ims, M_IN6MFILTER);
802191665Sbms		imf->im6f_nsrc--;
803191665Sbms	}
804191665Sbms	imf->im6f_st[0] = imf->im6f_st[1] = MCAST_UNDEFINED;
805191665Sbms	KASSERT(RB_EMPTY(&imf->im6f_sources),
806191665Sbms	    ("%s: im6f_sources not empty", __func__));
807191665Sbms}
808191665Sbms
809191665Sbms/*
810191665Sbms * Look up a source filter entry for a multicast group.
811191665Sbms *
812191665Sbms * inm is the group descriptor to work with.
813191665Sbms * addr is the IPv6 address to look up.
814191665Sbms * noalloc may be non-zero to suppress allocation of sources.
815191665Sbms * *pims will be set to the address of the retrieved or allocated source.
816191665Sbms *
817191665Sbms * SMPng: NOTE: may be called with locks held.
818191665Sbms * Return 0 if successful, otherwise return a non-zero error code.
819191665Sbms */
820191665Sbmsstatic int
821191665Sbmsin6m_get_source(struct in6_multi *inm, const struct in6_addr *addr,
822191665Sbms    const int noalloc, struct ip6_msource **pims)
823191665Sbms{
824191665Sbms	struct ip6_msource	 find;
825191665Sbms	struct ip6_msource	*ims, *nims;
826191665Sbms#ifdef KTR
827191665Sbms	char			 ip6tbuf[INET6_ADDRSTRLEN];
828191665Sbms#endif
829191665Sbms
830191665Sbms	find.im6s_addr = *addr;
831191665Sbms	ims = RB_FIND(ip6_msource_tree, &inm->in6m_srcs, &find);
832191665Sbms	if (ims == NULL && !noalloc) {
833191665Sbms		if (inm->in6m_nsrc == in6_mcast_maxgrpsrc)
834191665Sbms			return (ENOSPC);
835191665Sbms		nims = malloc(sizeof(struct ip6_msource), M_IP6MSOURCE,
836191665Sbms		    M_NOWAIT | M_ZERO);
837191665Sbms		if (nims == NULL)
838191665Sbms			return (ENOMEM);
839191665Sbms		nims->im6s_addr = *addr;
840191665Sbms		RB_INSERT(ip6_msource_tree, &inm->in6m_srcs, nims);
841191665Sbms		++inm->in6m_nsrc;
842191665Sbms		ims = nims;
843191665Sbms		CTR3(KTR_MLD, "%s: allocated %s as %p", __func__,
844191665Sbms		    ip6_sprintf(ip6tbuf, addr), ims);
845191665Sbms	}
846191665Sbms
847191665Sbms	*pims = ims;
848191665Sbms	return (0);
849191665Sbms}
850191665Sbms
851191665Sbms/*
852191665Sbms * Merge socket-layer source into MLD-layer source.
853191665Sbms * If rollback is non-zero, perform the inverse of the merge.
854191665Sbms */
855191665Sbmsstatic void
856191665Sbmsim6s_merge(struct ip6_msource *ims, const struct in6_msource *lims,
857191665Sbms    const int rollback)
858191665Sbms{
859191665Sbms	int n = rollback ? -1 : 1;
860191665Sbms#ifdef KTR
861191665Sbms	char ip6tbuf[INET6_ADDRSTRLEN];
862191665Sbms
863191665Sbms	ip6_sprintf(ip6tbuf, &lims->im6s_addr);
864191665Sbms#endif
865191665Sbms
866191665Sbms	if (lims->im6sl_st[0] == MCAST_EXCLUDE) {
867191665Sbms		CTR3(KTR_MLD, "%s: t1 ex -= %d on %s", __func__, n, ip6tbuf);
868191665Sbms		ims->im6s_st[1].ex -= n;
869191665Sbms	} else if (lims->im6sl_st[0] == MCAST_INCLUDE) {
870191665Sbms		CTR3(KTR_MLD, "%s: t1 in -= %d on %s", __func__, n, ip6tbuf);
871191665Sbms		ims->im6s_st[1].in -= n;
872191665Sbms	}
873191665Sbms
874191665Sbms	if (lims->im6sl_st[1] == MCAST_EXCLUDE) {
875191665Sbms		CTR3(KTR_MLD, "%s: t1 ex += %d on %s", __func__, n, ip6tbuf);
876191665Sbms		ims->im6s_st[1].ex += n;
877191665Sbms	} else if (lims->im6sl_st[1] == MCAST_INCLUDE) {
878191665Sbms		CTR3(KTR_MLD, "%s: t1 in += %d on %s", __func__, n, ip6tbuf);
879191665Sbms		ims->im6s_st[1].in += n;
880191665Sbms	}
881191665Sbms}
882191665Sbms
883191665Sbms/*
884191665Sbms * Atomically update the global in6_multi state, when a membership's
885191665Sbms * filter list is being updated in any way.
886191665Sbms *
887191665Sbms * imf is the per-inpcb-membership group filter pointer.
888191665Sbms * A fake imf may be passed for in-kernel consumers.
889191665Sbms *
890191665Sbms * XXX This is a candidate for a set-symmetric-difference style loop
891191665Sbms * which would eliminate the repeated lookup from root of ims nodes,
892191665Sbms * as they share the same key space.
893191665Sbms *
894191665Sbms * If any error occurred this function will back out of refcounts
895191665Sbms * and return a non-zero value.
896191665Sbms */
897191665Sbmsstatic int
898191665Sbmsin6m_merge(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
899191665Sbms{
900191665Sbms	struct ip6_msource	*ims, *nims;
901191665Sbms	struct in6_msource	*lims;
902191665Sbms	int			 schanged, error;
903191665Sbms	int			 nsrc0, nsrc1;
904191665Sbms
905191665Sbms	schanged = 0;
906191665Sbms	error = 0;
907191665Sbms	nsrc1 = nsrc0 = 0;
908191665Sbms
909191665Sbms	/*
910191665Sbms	 * Update the source filters first, as this may fail.
911191665Sbms	 * Maintain count of in-mode filters at t0, t1. These are
912191665Sbms	 * used to work out if we transition into ASM mode or not.
913191665Sbms	 * Maintain a count of source filters whose state was
914191665Sbms	 * actually modified by this operation.
915191665Sbms	 */
916191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
917191665Sbms		lims = (struct in6_msource *)ims;
918191665Sbms		if (lims->im6sl_st[0] == imf->im6f_st[0]) nsrc0++;
919191665Sbms		if (lims->im6sl_st[1] == imf->im6f_st[1]) nsrc1++;
920191665Sbms		if (lims->im6sl_st[0] == lims->im6sl_st[1]) continue;
921191665Sbms		error = in6m_get_source(inm, &lims->im6s_addr, 0, &nims);
922191665Sbms		++schanged;
923191665Sbms		if (error)
924191665Sbms			break;
925191665Sbms		im6s_merge(nims, lims, 0);
926191665Sbms	}
927191665Sbms	if (error) {
928191665Sbms		struct ip6_msource *bims;
929191665Sbms
930191665Sbms		RB_FOREACH_REVERSE_FROM(ims, ip6_msource_tree, nims) {
931191665Sbms			lims = (struct in6_msource *)ims;
932191665Sbms			if (lims->im6sl_st[0] == lims->im6sl_st[1])
933191665Sbms				continue;
934191665Sbms			(void)in6m_get_source(inm, &lims->im6s_addr, 1, &bims);
935191665Sbms			if (bims == NULL)
936191665Sbms				continue;
937191665Sbms			im6s_merge(bims, lims, 1);
938191665Sbms		}
939191665Sbms		goto out_reap;
940191665Sbms	}
941191665Sbms
942191665Sbms	CTR3(KTR_MLD, "%s: imf filters in-mode: %d at t0, %d at t1",
943191665Sbms	    __func__, nsrc0, nsrc1);
944191665Sbms
945191665Sbms	/* Handle transition between INCLUDE {n} and INCLUDE {} on socket. */
946191665Sbms	if (imf->im6f_st[0] == imf->im6f_st[1] &&
947191665Sbms	    imf->im6f_st[1] == MCAST_INCLUDE) {
948191665Sbms		if (nsrc1 == 0) {
949191665Sbms			CTR1(KTR_MLD, "%s: --in on inm at t1", __func__);
950191665Sbms			--inm->in6m_st[1].iss_in;
951191665Sbms		}
952191665Sbms	}
953191665Sbms
954191665Sbms	/* Handle filter mode transition on socket. */
955191665Sbms	if (imf->im6f_st[0] != imf->im6f_st[1]) {
956191665Sbms		CTR3(KTR_MLD, "%s: imf transition %d to %d",
957191665Sbms		    __func__, imf->im6f_st[0], imf->im6f_st[1]);
958191665Sbms
959191665Sbms		if (imf->im6f_st[0] == MCAST_EXCLUDE) {
960191665Sbms			CTR1(KTR_MLD, "%s: --ex on inm at t1", __func__);
961191665Sbms			--inm->in6m_st[1].iss_ex;
962191665Sbms		} else if (imf->im6f_st[0] == MCAST_INCLUDE) {
963191665Sbms			CTR1(KTR_MLD, "%s: --in on inm at t1", __func__);
964191665Sbms			--inm->in6m_st[1].iss_in;
965191665Sbms		}
966191665Sbms
967191665Sbms		if (imf->im6f_st[1] == MCAST_EXCLUDE) {
968191665Sbms			CTR1(KTR_MLD, "%s: ex++ on inm at t1", __func__);
969191665Sbms			inm->in6m_st[1].iss_ex++;
970191665Sbms		} else if (imf->im6f_st[1] == MCAST_INCLUDE && nsrc1 > 0) {
971191665Sbms			CTR1(KTR_MLD, "%s: in++ on inm at t1", __func__);
972191665Sbms			inm->in6m_st[1].iss_in++;
973191665Sbms		}
974191665Sbms	}
975191665Sbms
976191665Sbms	/*
977191665Sbms	 * Track inm filter state in terms of listener counts.
978191665Sbms	 * If there are any exclusive listeners, stack-wide
979191665Sbms	 * membership is exclusive.
980191665Sbms	 * Otherwise, if only inclusive listeners, stack-wide is inclusive.
981191665Sbms	 * If no listeners remain, state is undefined at t1,
982191665Sbms	 * and the MLD lifecycle for this group should finish.
983191665Sbms	 */
984191665Sbms	if (inm->in6m_st[1].iss_ex > 0) {
985191665Sbms		CTR1(KTR_MLD, "%s: transition to EX", __func__);
986191665Sbms		inm->in6m_st[1].iss_fmode = MCAST_EXCLUDE;
987191665Sbms	} else if (inm->in6m_st[1].iss_in > 0) {
988191665Sbms		CTR1(KTR_MLD, "%s: transition to IN", __func__);
989191665Sbms		inm->in6m_st[1].iss_fmode = MCAST_INCLUDE;
990191665Sbms	} else {
991191665Sbms		CTR1(KTR_MLD, "%s: transition to UNDEF", __func__);
992191665Sbms		inm->in6m_st[1].iss_fmode = MCAST_UNDEFINED;
993191665Sbms	}
994191665Sbms
995191665Sbms	/* Decrement ASM listener count on transition out of ASM mode. */
996191665Sbms	if (imf->im6f_st[0] == MCAST_EXCLUDE && nsrc0 == 0) {
997191665Sbms		if ((imf->im6f_st[1] != MCAST_EXCLUDE) ||
998191665Sbms		    (imf->im6f_st[1] == MCAST_EXCLUDE && nsrc1 > 0))
999191665Sbms			CTR1(KTR_MLD, "%s: --asm on inm at t1", __func__);
1000191665Sbms			--inm->in6m_st[1].iss_asm;
1001191665Sbms	}
1002191665Sbms
1003191665Sbms	/* Increment ASM listener count on transition to ASM mode. */
1004191665Sbms	if (imf->im6f_st[1] == MCAST_EXCLUDE && nsrc1 == 0) {
1005191665Sbms		CTR1(KTR_MLD, "%s: asm++ on inm at t1", __func__);
1006191665Sbms		inm->in6m_st[1].iss_asm++;
1007191665Sbms	}
1008191665Sbms
1009191665Sbms	CTR3(KTR_MLD, "%s: merged imf %p to inm %p", __func__, imf, inm);
1010191665Sbms	in6m_print(inm);
1011191665Sbms
1012191665Sbmsout_reap:
1013191665Sbms	if (schanged > 0) {
1014191665Sbms		CTR1(KTR_MLD, "%s: sources changed; reaping", __func__);
1015191665Sbms		in6m_reap(inm);
1016191665Sbms	}
1017191665Sbms	return (error);
1018191665Sbms}
1019191665Sbms
1020191665Sbms/*
1021191665Sbms * Mark an in6_multi's filter set deltas as committed.
1022191665Sbms * Called by MLD after a state change has been enqueued.
1023191665Sbms */
1024191665Sbmsvoid
1025191665Sbmsin6m_commit(struct in6_multi *inm)
1026191665Sbms{
1027191665Sbms	struct ip6_msource	*ims;
1028191665Sbms
1029191665Sbms	CTR2(KTR_MLD, "%s: commit inm %p", __func__, inm);
1030191665Sbms	CTR1(KTR_MLD, "%s: pre commit:", __func__);
1031191665Sbms	in6m_print(inm);
1032191665Sbms
1033191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &inm->in6m_srcs) {
1034191665Sbms		ims->im6s_st[0] = ims->im6s_st[1];
1035191665Sbms	}
1036191665Sbms	inm->in6m_st[0] = inm->in6m_st[1];
1037191665Sbms}
1038191665Sbms
1039191665Sbms/*
1040191665Sbms * Reap unreferenced nodes from an in6_multi's filter set.
1041191665Sbms */
1042191665Sbmsstatic void
1043191665Sbmsin6m_reap(struct in6_multi *inm)
1044191665Sbms{
1045191665Sbms	struct ip6_msource	*ims, *tims;
1046191665Sbms
1047191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &inm->in6m_srcs, tims) {
1048191665Sbms		if (ims->im6s_st[0].ex > 0 || ims->im6s_st[0].in > 0 ||
1049191665Sbms		    ims->im6s_st[1].ex > 0 || ims->im6s_st[1].in > 0 ||
1050191665Sbms		    ims->im6s_stp != 0)
1051191665Sbms			continue;
1052191665Sbms		CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
1053191665Sbms		RB_REMOVE(ip6_msource_tree, &inm->in6m_srcs, ims);
1054191665Sbms		free(ims, M_IP6MSOURCE);
1055191665Sbms		inm->in6m_nsrc--;
1056191665Sbms	}
1057191665Sbms}
1058191665Sbms
1059191665Sbms/*
1060191665Sbms * Purge all source nodes from an in6_multi's filter set.
1061191665Sbms */
1062191665Sbmsstatic void
1063191665Sbmsin6m_purge(struct in6_multi *inm)
1064191665Sbms{
1065191665Sbms	struct ip6_msource	*ims, *tims;
1066191665Sbms
1067191665Sbms	RB_FOREACH_SAFE(ims, ip6_msource_tree, &inm->in6m_srcs, tims) {
1068191665Sbms		CTR2(KTR_MLD, "%s: free ims %p", __func__, ims);
1069191665Sbms		RB_REMOVE(ip6_msource_tree, &inm->in6m_srcs, ims);
1070191665Sbms		free(ims, M_IP6MSOURCE);
1071191665Sbms		inm->in6m_nsrc--;
1072191665Sbms	}
1073191665Sbms}
1074191665Sbms
1075191665Sbms/*
1076191665Sbms * Join a multicast address w/o sources.
1077191665Sbms * KAME compatibility entry point.
1078191665Sbms *
1079191665Sbms * SMPng: Assume no mc locks held by caller.
1080191665Sbms */
1081191665Sbmsstruct in6_multi_mship *
1082191665Sbmsin6_joingroup(struct ifnet *ifp, struct in6_addr *mcaddr,
1083191665Sbms    int *errorp, int delay)
1084191665Sbms{
1085191665Sbms	struct in6_multi_mship *imm;
1086191665Sbms	int error;
1087191665Sbms
1088191665Sbms	imm = malloc(sizeof(*imm), M_IP6MADDR, M_NOWAIT);
1089191665Sbms	if (imm == NULL) {
1090191665Sbms		*errorp = ENOBUFS;
1091191665Sbms		return (NULL);
1092191665Sbms	}
1093191665Sbms
1094191665Sbms	delay = (delay * PR_FASTHZ) / hz;
1095191665Sbms
1096191665Sbms	error = in6_mc_join(ifp, mcaddr, NULL, &imm->i6mm_maddr, delay);
1097191665Sbms	if (error) {
1098191665Sbms		*errorp = error;
1099191665Sbms		free(imm, M_IP6MADDR);
1100191665Sbms		return (NULL);
1101191665Sbms	}
1102191665Sbms
1103191665Sbms	return (imm);
1104191665Sbms}
1105191665Sbms
1106191665Sbms/*
1107191665Sbms * Leave a multicast address w/o sources.
1108191665Sbms * KAME compatibility entry point.
1109191665Sbms *
1110191665Sbms * SMPng: Assume no mc locks held by caller.
1111191665Sbms */
1112191665Sbmsint
1113191665Sbmsin6_leavegroup(struct in6_multi_mship *imm)
1114191665Sbms{
1115191665Sbms
1116191665Sbms	if (imm->i6mm_maddr != NULL)
1117191665Sbms		in6_mc_leave(imm->i6mm_maddr, NULL);
1118191665Sbms	free(imm,  M_IP6MADDR);
1119191665Sbms	return 0;
1120191665Sbms}
1121191665Sbms
1122191665Sbms/*
1123191665Sbms * Join a multicast group; unlocked entry point.
1124191665Sbms *
1125191665Sbms * SMPng: XXX: in6_mc_join() is called from in6_control() when upper
1126191665Sbms * locks are not held. Fortunately, ifp is unlikely to have been detached
1127191665Sbms * at this point, so we assume it's OK to recurse.
1128191665Sbms */
1129191665Sbmsint
1130191665Sbmsin6_mc_join(struct ifnet *ifp, const struct in6_addr *mcaddr,
1131191665Sbms    /*const*/ struct in6_mfilter *imf, struct in6_multi **pinm,
1132191665Sbms    const int delay)
1133191665Sbms{
1134191665Sbms	int error;
1135191665Sbms
1136191665Sbms	IN6_MULTI_LOCK();
1137191665Sbms	error = in6_mc_join_locked(ifp, mcaddr, imf, pinm, delay);
1138191665Sbms	IN6_MULTI_UNLOCK();
1139191665Sbms
1140191665Sbms	return (error);
1141191665Sbms}
1142191665Sbms
1143191665Sbms/*
1144191665Sbms * Join a multicast group; real entry point.
1145191665Sbms *
1146191665Sbms * Only preserves atomicity at inm level.
1147191665Sbms * NOTE: imf argument cannot be const due to sys/tree.h limitations.
1148191665Sbms *
1149191665Sbms * If the MLD downcall fails, the group is not joined, and an error
1150191665Sbms * code is returned.
1151191665Sbms */
1152191665Sbmsint
1153191665Sbmsin6_mc_join_locked(struct ifnet *ifp, const struct in6_addr *mcaddr,
1154191665Sbms    /*const*/ struct in6_mfilter *imf, struct in6_multi **pinm,
1155191665Sbms    const int delay)
1156191665Sbms{
1157191665Sbms	struct in6_mfilter	 timf;
1158191665Sbms	struct in6_multi	*inm;
1159191665Sbms	int			 error;
1160191665Sbms#ifdef KTR
1161191665Sbms	char			 ip6tbuf[INET6_ADDRSTRLEN];
1162191665Sbms#endif
1163191665Sbms
1164192923Sbms#ifdef INVARIANTS
1165192923Sbms	/*
1166192923Sbms	 * Sanity: Check scope zone ID was set for ifp, if and
1167192923Sbms	 * only if group is scoped to an interface.
1168192923Sbms	 */
1169192923Sbms	KASSERT(IN6_IS_ADDR_MULTICAST(mcaddr),
1170192923Sbms	    ("%s: not a multicast address", __func__));
1171192923Sbms	if (IN6_IS_ADDR_MC_LINKLOCAL(mcaddr) ||
1172192923Sbms	    IN6_IS_ADDR_MC_INTFACELOCAL(mcaddr)) {
1173192923Sbms		KASSERT(mcaddr->s6_addr16[1] != 0,
1174192923Sbms		    ("%s: scope zone ID not set", __func__));
1175192923Sbms	}
1176192923Sbms#endif
1177192923Sbms
1178191665Sbms	IN6_MULTI_LOCK_ASSERT();
1179191665Sbms
1180191665Sbms	CTR4(KTR_MLD, "%s: join %s on %p(%s))", __func__,
1181191665Sbms	    ip6_sprintf(ip6tbuf, mcaddr), ifp, ifp->if_xname);
1182191665Sbms
1183191665Sbms	error = 0;
1184191665Sbms	inm = NULL;
1185191665Sbms
1186191665Sbms	/*
1187191665Sbms	 * If no imf was specified (i.e. kernel consumer),
1188191665Sbms	 * fake one up and assume it is an ASM join.
1189191665Sbms	 */
1190191665Sbms	if (imf == NULL) {
1191191665Sbms		im6f_init(&timf, MCAST_UNDEFINED, MCAST_EXCLUDE);
1192191665Sbms		imf = &timf;
1193191665Sbms	}
1194191665Sbms
1195191665Sbms	error = in6_mc_get(ifp, mcaddr, &inm);
1196191665Sbms	if (error) {
1197191665Sbms		CTR1(KTR_MLD, "%s: in6_mc_get() failure", __func__);
1198191665Sbms		return (error);
1199191665Sbms	}
1200191665Sbms
1201191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
1202191665Sbms	error = in6m_merge(inm, imf);
1203191665Sbms	if (error) {
1204191665Sbms		CTR1(KTR_MLD, "%s: failed to merge inm state", __func__);
1205191665Sbms		goto out_in6m_release;
1206191665Sbms	}
1207191665Sbms
1208191665Sbms	CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
1209191665Sbms	error = mld_change_state(inm, delay);
1210191665Sbms	if (error) {
1211191665Sbms		CTR1(KTR_MLD, "%s: failed to update source", __func__);
1212191665Sbms		goto out_in6m_release;
1213191665Sbms	}
1214191665Sbms
1215191665Sbmsout_in6m_release:
1216191665Sbms	if (error) {
1217191665Sbms		CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm);
1218191665Sbms		in6m_release_locked(inm);
1219191665Sbms	} else {
1220191665Sbms		*pinm = inm;
1221191665Sbms	}
1222191665Sbms
1223191665Sbms	return (error);
1224191665Sbms}
1225191665Sbms
1226191665Sbms/*
1227191665Sbms * Leave a multicast group; unlocked entry point.
1228191665Sbms */
1229191665Sbmsint
1230191665Sbmsin6_mc_leave(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
1231191665Sbms{
1232191665Sbms	struct ifnet *ifp;
1233191665Sbms	int error;
1234191665Sbms
1235191665Sbms	ifp = inm->in6m_ifp;
1236191665Sbms
1237191665Sbms	IN6_MULTI_LOCK();
1238191665Sbms	error = in6_mc_leave_locked(inm, imf);
1239191665Sbms	IN6_MULTI_UNLOCK();
1240191665Sbms
1241191665Sbms	return (error);
1242191665Sbms}
1243191665Sbms
1244191665Sbms/*
1245191665Sbms * Leave a multicast group; real entry point.
1246191665Sbms * All source filters will be expunged.
1247191665Sbms *
1248191665Sbms * Only preserves atomicity at inm level.
1249191665Sbms *
1250191665Sbms * Holding the write lock for the INP which contains imf
1251191665Sbms * is highly advisable. We can't assert for it as imf does not
1252191665Sbms * contain a back-pointer to the owning inp.
1253191665Sbms *
1254191665Sbms * Note: This is not the same as in6m_release(*) as this function also
1255191665Sbms * makes a state change downcall into MLD.
1256191665Sbms */
1257191665Sbmsint
1258191665Sbmsin6_mc_leave_locked(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
1259191665Sbms{
1260191665Sbms	struct in6_mfilter	 timf;
1261191665Sbms	int			 error;
1262191665Sbms#ifdef KTR
1263191665Sbms	char			 ip6tbuf[INET6_ADDRSTRLEN];
1264191665Sbms#endif
1265191665Sbms
1266191665Sbms	error = 0;
1267191665Sbms
1268191665Sbms	IN6_MULTI_LOCK_ASSERT();
1269191665Sbms
1270191665Sbms	CTR5(KTR_MLD, "%s: leave inm %p, %s/%s, imf %p", __func__,
1271191665Sbms	    inm, ip6_sprintf(ip6tbuf, &inm->in6m_addr),
1272191665Sbms	    (in6m_is_ifp_detached(inm) ? "null" : inm->in6m_ifp->if_xname),
1273191665Sbms	    imf);
1274191665Sbms
1275191665Sbms	/*
1276191665Sbms	 * If no imf was specified (i.e. kernel consumer),
1277191665Sbms	 * fake one up and assume it is an ASM join.
1278191665Sbms	 */
1279191665Sbms	if (imf == NULL) {
1280191665Sbms		im6f_init(&timf, MCAST_EXCLUDE, MCAST_UNDEFINED);
1281191665Sbms		imf = &timf;
1282191665Sbms	}
1283191665Sbms
1284191665Sbms	/*
1285191665Sbms	 * Begin state merge transaction at MLD layer.
1286191665Sbms	 *
1287191665Sbms	 * As this particular invocation should not cause any memory
1288191665Sbms	 * to be allocated, and there is no opportunity to roll back
1289191665Sbms	 * the transaction, it MUST NOT fail.
1290191665Sbms	 */
1291191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
1292191665Sbms	error = in6m_merge(inm, imf);
1293191665Sbms	KASSERT(error == 0, ("%s: failed to merge inm state", __func__));
1294191665Sbms
1295191665Sbms	CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
1296191665Sbms	error = mld_change_state(inm, 0);
1297191665Sbms	if (error)
1298191665Sbms		CTR1(KTR_MLD, "%s: failed mld downcall", __func__);
1299191665Sbms
1300191665Sbms	CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm);
1301191665Sbms	in6m_release_locked(inm);
1302191665Sbms
1303191665Sbms	return (error);
1304191665Sbms}
1305191665Sbms
1306191665Sbms/*
1307191665Sbms * Block or unblock an ASM multicast source on an inpcb.
1308191665Sbms * This implements the delta-based API described in RFC 3678.
1309191665Sbms *
1310191665Sbms * The delta-based API applies only to exclusive-mode memberships.
1311191665Sbms * An MLD downcall will be performed.
1312191665Sbms *
1313191665Sbms * SMPng: NOTE: Must take Giant as a join may create a new ifma.
1314191665Sbms *
1315191665Sbms * Return 0 if successful, otherwise return an appropriate error code.
1316191665Sbms */
1317191665Sbmsstatic int
1318191665Sbmsin6p_block_unblock_source(struct inpcb *inp, struct sockopt *sopt)
1319191665Sbms{
1320191665Sbms	struct group_source_req		 gsr;
1321191665Sbms	sockunion_t			*gsa, *ssa;
1322191665Sbms	struct ifnet			*ifp;
1323191665Sbms	struct in6_mfilter		*imf;
1324191665Sbms	struct ip6_moptions		*imo;
1325191665Sbms	struct in6_msource		*ims;
1326191665Sbms	struct in6_multi			*inm;
1327191665Sbms	size_t				 idx;
1328191665Sbms	uint16_t			 fmode;
1329191665Sbms	int				 error, doblock;
1330191665Sbms#ifdef KTR
1331191665Sbms	char				 ip6tbuf[INET6_ADDRSTRLEN];
1332191665Sbms#endif
1333191665Sbms
1334191665Sbms	ifp = NULL;
1335191665Sbms	error = 0;
1336191665Sbms	doblock = 0;
1337191665Sbms
1338191665Sbms	memset(&gsr, 0, sizeof(struct group_source_req));
1339191665Sbms	gsa = (sockunion_t *)&gsr.gsr_group;
1340191665Sbms	ssa = (sockunion_t *)&gsr.gsr_source;
1341191665Sbms
1342191665Sbms	switch (sopt->sopt_name) {
1343191665Sbms	case MCAST_BLOCK_SOURCE:
1344191665Sbms	case MCAST_UNBLOCK_SOURCE:
1345191665Sbms		error = sooptcopyin(sopt, &gsr,
1346191665Sbms		    sizeof(struct group_source_req),
1347191665Sbms		    sizeof(struct group_source_req));
1348191665Sbms		if (error)
1349191665Sbms			return (error);
1350191665Sbms
1351191665Sbms		if (gsa->sin6.sin6_family != AF_INET6 ||
1352191665Sbms		    gsa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1353191665Sbms			return (EINVAL);
1354191665Sbms
1355191665Sbms		if (ssa->sin6.sin6_family != AF_INET6 ||
1356191665Sbms		    ssa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1357191665Sbms			return (EINVAL);
1358191665Sbms
1359191665Sbms		if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
1360191665Sbms			return (EADDRNOTAVAIL);
1361191665Sbms
1362191665Sbms		ifp = ifnet_byindex(gsr.gsr_interface);
1363191665Sbms
1364191665Sbms		if (sopt->sopt_name == MCAST_BLOCK_SOURCE)
1365191665Sbms			doblock = 1;
1366191665Sbms		break;
1367191665Sbms
1368191665Sbms	default:
1369191665Sbms		CTR2(KTR_MLD, "%s: unknown sopt_name %d",
1370191665Sbms		    __func__, sopt->sopt_name);
1371191665Sbms		return (EOPNOTSUPP);
1372191665Sbms		break;
1373191665Sbms	}
1374191665Sbms
1375191665Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
1376191665Sbms		return (EINVAL);
1377191665Sbms
1378192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
1379192923Sbms
1380191665Sbms	/*
1381191665Sbms	 * Check if we are actually a member of this group.
1382191665Sbms	 */
1383191665Sbms	imo = in6p_findmoptions(inp);
1384191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
1385191665Sbms	if (idx == -1 || imo->im6o_mfilters == NULL) {
1386191665Sbms		error = EADDRNOTAVAIL;
1387191665Sbms		goto out_in6p_locked;
1388191665Sbms	}
1389191665Sbms
1390191665Sbms	KASSERT(imo->im6o_mfilters != NULL,
1391191665Sbms	    ("%s: im6o_mfilters not allocated", __func__));
1392191665Sbms	imf = &imo->im6o_mfilters[idx];
1393191665Sbms	inm = imo->im6o_membership[idx];
1394191665Sbms
1395191665Sbms	/*
1396191665Sbms	 * Attempting to use the delta-based API on an
1397191665Sbms	 * non exclusive-mode membership is an error.
1398191665Sbms	 */
1399191665Sbms	fmode = imf->im6f_st[0];
1400191665Sbms	if (fmode != MCAST_EXCLUDE) {
1401191665Sbms		error = EINVAL;
1402191665Sbms		goto out_in6p_locked;
1403191665Sbms	}
1404191665Sbms
1405191665Sbms	/*
1406191665Sbms	 * Deal with error cases up-front:
1407191665Sbms	 *  Asked to block, but already blocked; or
1408191665Sbms	 *  Asked to unblock, but nothing to unblock.
1409191665Sbms	 * If adding a new block entry, allocate it.
1410191665Sbms	 */
1411191665Sbms	ims = im6o_match_source(imo, idx, &ssa->sa);
1412191665Sbms	if ((ims != NULL && doblock) || (ims == NULL && !doblock)) {
1413191665Sbms		CTR3(KTR_MLD, "%s: source %s %spresent", __func__,
1414191665Sbms		    ip6_sprintf(ip6tbuf, &ssa->sin6.sin6_addr),
1415191665Sbms		    doblock ? "" : "not ");
1416191665Sbms		error = EADDRNOTAVAIL;
1417191665Sbms		goto out_in6p_locked;
1418191665Sbms	}
1419191665Sbms
1420191665Sbms	INP_WLOCK_ASSERT(inp);
1421191665Sbms
1422191665Sbms	/*
1423191665Sbms	 * Begin state merge transaction at socket layer.
1424191665Sbms	 */
1425191665Sbms	if (doblock) {
1426191665Sbms		CTR2(KTR_MLD, "%s: %s source", __func__, "block");
1427191665Sbms		ims = im6f_graft(imf, fmode, &ssa->sin6);
1428191665Sbms		if (ims == NULL)
1429191665Sbms			error = ENOMEM;
1430191665Sbms	} else {
1431191665Sbms		CTR2(KTR_MLD, "%s: %s source", __func__, "allow");
1432191665Sbms		error = im6f_prune(imf, &ssa->sin6);
1433191665Sbms	}
1434191665Sbms
1435191665Sbms	if (error) {
1436191665Sbms		CTR1(KTR_MLD, "%s: merge imf state failed", __func__);
1437191665Sbms		goto out_im6f_rollback;
1438191665Sbms	}
1439191665Sbms
1440191665Sbms	/*
1441191665Sbms	 * Begin state merge transaction at MLD layer.
1442191665Sbms	 */
1443191665Sbms	IN6_MULTI_LOCK();
1444191665Sbms
1445191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
1446191665Sbms	error = in6m_merge(inm, imf);
1447191665Sbms	if (error) {
1448191665Sbms		CTR1(KTR_MLD, "%s: failed to merge inm state", __func__);
1449191665Sbms		goto out_im6f_rollback;
1450191665Sbms	}
1451191665Sbms
1452191665Sbms	CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
1453191665Sbms	error = mld_change_state(inm, 0);
1454191665Sbms	if (error)
1455191665Sbms		CTR1(KTR_MLD, "%s: failed mld downcall", __func__);
1456191665Sbms
1457191665Sbms	IN6_MULTI_UNLOCK();
1458191665Sbms
1459191665Sbmsout_im6f_rollback:
1460191665Sbms	if (error)
1461191665Sbms		im6f_rollback(imf);
1462191665Sbms	else
1463191665Sbms		im6f_commit(imf);
1464191665Sbms
1465191665Sbms	im6f_reap(imf);
1466191665Sbms
1467191665Sbmsout_in6p_locked:
1468191665Sbms	INP_WUNLOCK(inp);
1469191665Sbms	return (error);
1470191665Sbms}
1471191665Sbms
1472191665Sbms/*
1473191665Sbms * Given an inpcb, return its multicast options structure pointer.  Accepts
1474191665Sbms * an unlocked inpcb pointer, but will return it locked.  May sleep.
1475191665Sbms *
1476191665Sbms * SMPng: NOTE: Potentially calls malloc(M_WAITOK) with Giant held.
1477191665Sbms * SMPng: NOTE: Returns with the INP write lock held.
1478191665Sbms */
1479191665Sbmsstatic struct ip6_moptions *
1480191665Sbmsin6p_findmoptions(struct inpcb *inp)
1481191665Sbms{
1482191665Sbms	struct ip6_moptions	 *imo;
1483191665Sbms	struct in6_multi		**immp;
1484191665Sbms	struct in6_mfilter	 *imfp;
1485191665Sbms	size_t			  idx;
1486191665Sbms
1487191665Sbms	INP_WLOCK(inp);
1488191665Sbms	if (inp->in6p_moptions != NULL)
1489191665Sbms		return (inp->in6p_moptions);
1490191665Sbms
1491191665Sbms	INP_WUNLOCK(inp);
1492191665Sbms
1493191665Sbms	imo = malloc(sizeof(*imo), M_IP6MOPTS, M_WAITOK);
1494191665Sbms	immp = malloc(sizeof(*immp) * IPV6_MIN_MEMBERSHIPS, M_IP6MOPTS,
1495191665Sbms	    M_WAITOK | M_ZERO);
1496191665Sbms	imfp = malloc(sizeof(struct in6_mfilter) * IPV6_MIN_MEMBERSHIPS,
1497191665Sbms	    M_IN6MFILTER, M_WAITOK);
1498191665Sbms
1499191665Sbms	imo->im6o_multicast_ifp = NULL;
1500191665Sbms	imo->im6o_multicast_hlim = V_ip6_defmcasthlim;
1501191665Sbms	imo->im6o_multicast_loop = in6_mcast_loop;
1502191665Sbms	imo->im6o_num_memberships = 0;
1503191665Sbms	imo->im6o_max_memberships = IPV6_MIN_MEMBERSHIPS;
1504191665Sbms	imo->im6o_membership = immp;
1505191665Sbms
1506191665Sbms	/* Initialize per-group source filters. */
1507191665Sbms	for (idx = 0; idx < IPV6_MIN_MEMBERSHIPS; idx++)
1508191665Sbms		im6f_init(&imfp[idx], MCAST_UNDEFINED, MCAST_EXCLUDE);
1509191665Sbms	imo->im6o_mfilters = imfp;
1510191665Sbms
1511191665Sbms	INP_WLOCK(inp);
1512191665Sbms	if (inp->in6p_moptions != NULL) {
1513191665Sbms		free(imfp, M_IN6MFILTER);
1514191665Sbms		free(immp, M_IP6MOPTS);
1515191665Sbms		free(imo, M_IP6MOPTS);
1516191665Sbms		return (inp->in6p_moptions);
1517191665Sbms	}
1518191665Sbms	inp->in6p_moptions = imo;
1519191665Sbms	return (imo);
1520191665Sbms}
1521191665Sbms
1522191665Sbms/*
1523191665Sbms * Discard the IPv6 multicast options (and source filters).
1524191665Sbms *
1525191665Sbms * SMPng: NOTE: assumes INP write lock is held.
1526191665Sbms */
1527191665Sbmsvoid
1528191665Sbmsip6_freemoptions(struct ip6_moptions *imo)
1529191665Sbms{
1530191665Sbms	struct in6_mfilter	*imf;
1531191665Sbms	size_t			 idx, nmships;
1532191665Sbms
1533191665Sbms	KASSERT(imo != NULL, ("%s: ip6_moptions is NULL", __func__));
1534191665Sbms
1535191665Sbms	nmships = imo->im6o_num_memberships;
1536191665Sbms	for (idx = 0; idx < nmships; ++idx) {
1537191665Sbms		imf = imo->im6o_mfilters ? &imo->im6o_mfilters[idx] : NULL;
1538191665Sbms		if (imf)
1539191665Sbms			im6f_leave(imf);
1540191665Sbms		/* XXX this will thrash the lock(s) */
1541191665Sbms		(void)in6_mc_leave(imo->im6o_membership[idx], imf);
1542191665Sbms		if (imf)
1543191665Sbms			im6f_purge(imf);
1544191665Sbms	}
1545191665Sbms
1546191665Sbms	if (imo->im6o_mfilters)
1547191665Sbms		free(imo->im6o_mfilters, M_IN6MFILTER);
1548191665Sbms	free(imo->im6o_membership, M_IP6MOPTS);
1549191665Sbms	free(imo, M_IP6MOPTS);
1550191665Sbms}
1551191665Sbms
1552191665Sbms/*
1553191665Sbms * Atomically get source filters on a socket for an IPv6 multicast group.
1554191665Sbms * Called with INP lock held; returns with lock released.
1555191665Sbms */
1556191665Sbmsstatic int
1557191665Sbmsin6p_get_source_filters(struct inpcb *inp, struct sockopt *sopt)
1558191665Sbms{
1559191665Sbms	struct __msfilterreq	 msfr;
1560191665Sbms	sockunion_t		*gsa;
1561191665Sbms	struct ifnet		*ifp;
1562191665Sbms	struct ip6_moptions	*imo;
1563191665Sbms	struct in6_mfilter	*imf;
1564191665Sbms	struct ip6_msource	*ims;
1565191665Sbms	struct in6_msource	*lims;
1566191665Sbms	struct sockaddr_in6	*psin;
1567191665Sbms	struct sockaddr_storage	*ptss;
1568191665Sbms	struct sockaddr_storage	*tss;
1569191665Sbms	int			 error;
1570191665Sbms	size_t			 idx, nsrcs, ncsrcs;
1571191665Sbms
1572191665Sbms	INP_WLOCK_ASSERT(inp);
1573191665Sbms
1574191665Sbms	imo = inp->in6p_moptions;
1575191665Sbms	KASSERT(imo != NULL, ("%s: null ip6_moptions", __func__));
1576191665Sbms
1577191665Sbms	INP_WUNLOCK(inp);
1578191665Sbms
1579191665Sbms	error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq),
1580191665Sbms	    sizeof(struct __msfilterreq));
1581191665Sbms	if (error)
1582191665Sbms		return (error);
1583191665Sbms
1584192923Sbms	if (msfr.msfr_group.ss_family != AF_INET6 ||
1585192923Sbms	    msfr.msfr_group.ss_len != sizeof(struct sockaddr_in6))
1586191665Sbms		return (EINVAL);
1587191665Sbms
1588192923Sbms	gsa = (sockunion_t *)&msfr.msfr_group;
1589192923Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
1590192923Sbms		return (EINVAL);
1591192923Sbms
1592192923Sbms	if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
1593192923Sbms		return (EADDRNOTAVAIL);
1594191665Sbms	ifp = ifnet_byindex(msfr.msfr_ifindex);
1595191665Sbms	if (ifp == NULL)
1596192923Sbms		return (EADDRNOTAVAIL);
1597192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
1598191665Sbms
1599191665Sbms	INP_WLOCK(inp);
1600191665Sbms
1601191665Sbms	/*
1602191665Sbms	 * Lookup group on the socket.
1603191665Sbms	 */
1604191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
1605191665Sbms	if (idx == -1 || imo->im6o_mfilters == NULL) {
1606191665Sbms		INP_WUNLOCK(inp);
1607191665Sbms		return (EADDRNOTAVAIL);
1608191665Sbms	}
1609191665Sbms	imf = &imo->im6o_mfilters[idx];
1610191665Sbms
1611191665Sbms	/*
1612191665Sbms	 * Ignore memberships which are in limbo.
1613191665Sbms	 */
1614191665Sbms	if (imf->im6f_st[1] == MCAST_UNDEFINED) {
1615191665Sbms		INP_WUNLOCK(inp);
1616191665Sbms		return (EAGAIN);
1617191665Sbms	}
1618191665Sbms	msfr.msfr_fmode = imf->im6f_st[1];
1619191665Sbms
1620191665Sbms	/*
1621191665Sbms	 * If the user specified a buffer, copy out the source filter
1622191665Sbms	 * entries to userland gracefully.
1623191665Sbms	 * We only copy out the number of entries which userland
1624191665Sbms	 * has asked for, but we always tell userland how big the
1625191665Sbms	 * buffer really needs to be.
1626191665Sbms	 */
1627191665Sbms	tss = NULL;
1628191665Sbms	if (msfr.msfr_srcs != NULL && msfr.msfr_nsrcs > 0) {
1629191665Sbms		tss = malloc(sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs,
1630191665Sbms		    M_TEMP, M_NOWAIT | M_ZERO);
1631191665Sbms		if (tss == NULL) {
1632191665Sbms			INP_WUNLOCK(inp);
1633191665Sbms			return (ENOBUFS);
1634191665Sbms		}
1635191665Sbms	}
1636191665Sbms
1637191665Sbms	/*
1638191665Sbms	 * Count number of sources in-mode at t0.
1639191665Sbms	 * If buffer space exists and remains, copy out source entries.
1640191665Sbms	 */
1641191665Sbms	nsrcs = msfr.msfr_nsrcs;
1642191665Sbms	ncsrcs = 0;
1643191665Sbms	ptss = tss;
1644191665Sbms	RB_FOREACH(ims, ip6_msource_tree, &imf->im6f_sources) {
1645191665Sbms		lims = (struct in6_msource *)ims;
1646191665Sbms		if (lims->im6sl_st[0] == MCAST_UNDEFINED ||
1647191665Sbms		    lims->im6sl_st[0] != imf->im6f_st[0])
1648191665Sbms			continue;
1649191665Sbms		++ncsrcs;
1650191665Sbms		if (tss != NULL && nsrcs > 0) {
1651191665Sbms			psin = (struct sockaddr_in6 *)ptss;
1652191665Sbms			psin->sin6_family = AF_INET6;
1653191665Sbms			psin->sin6_len = sizeof(struct sockaddr_in6);
1654191665Sbms			psin->sin6_addr = lims->im6s_addr;
1655191665Sbms			psin->sin6_port = 0;
1656191665Sbms			--nsrcs;
1657191665Sbms			++ptss;
1658191665Sbms		}
1659191665Sbms	}
1660191665Sbms
1661191665Sbms	INP_WUNLOCK(inp);
1662191665Sbms
1663191665Sbms	if (tss != NULL) {
1664191665Sbms		error = copyout(tss, msfr.msfr_srcs,
1665191665Sbms		    sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs);
1666191665Sbms		free(tss, M_TEMP);
1667191665Sbms		if (error)
1668191665Sbms			return (error);
1669191665Sbms	}
1670191665Sbms
1671191665Sbms	msfr.msfr_nsrcs = ncsrcs;
1672191665Sbms	error = sooptcopyout(sopt, &msfr, sizeof(struct __msfilterreq));
1673191665Sbms
1674191665Sbms	return (error);
1675191665Sbms}
1676191665Sbms
1677191665Sbms/*
1678191665Sbms * Return the IP multicast options in response to user getsockopt().
1679191665Sbms */
1680191665Sbmsint
1681191665Sbmsip6_getmoptions(struct inpcb *inp, struct sockopt *sopt)
1682191665Sbms{
1683191672Sbms	struct ip6_moptions	*im6o;
1684191672Sbms	int			 error;
1685191672Sbms	u_int			 optval;
1686191665Sbms
1687191665Sbms	INP_WLOCK(inp);
1688191672Sbms	im6o = inp->in6p_moptions;
1689191665Sbms	/*
1690191665Sbms	 * If socket is neither of type SOCK_RAW or SOCK_DGRAM,
1691191665Sbms	 * or is a divert socket, reject it.
1692191665Sbms	 */
1693191665Sbms	if (inp->inp_socket->so_proto->pr_protocol == IPPROTO_DIVERT ||
1694191665Sbms	    (inp->inp_socket->so_proto->pr_type != SOCK_RAW &&
1695191665Sbms	    inp->inp_socket->so_proto->pr_type != SOCK_DGRAM)) {
1696191665Sbms		INP_WUNLOCK(inp);
1697191665Sbms		return (EOPNOTSUPP);
1698191665Sbms	}
1699191665Sbms
1700191665Sbms	error = 0;
1701191665Sbms	switch (sopt->sopt_name) {
1702191665Sbms	case IPV6_MULTICAST_IF:
1703191672Sbms		if (im6o == NULL || im6o->im6o_multicast_ifp == NULL) {
1704191665Sbms			optval = 0;
1705191665Sbms		} else {
1706191672Sbms			optval = im6o->im6o_multicast_ifp->if_index;
1707191665Sbms		}
1708191665Sbms		INP_WUNLOCK(inp);
1709191672Sbms		error = sooptcopyout(sopt, &optval, sizeof(u_int));
1710191665Sbms		break;
1711191665Sbms
1712191665Sbms	case IPV6_MULTICAST_HOPS:
1713191672Sbms		if (im6o == NULL)
1714191672Sbms			optval = V_ip6_defmcasthlim;
1715191665Sbms		else
1716191672Sbms			optval = im6o->im6o_multicast_loop;
1717191665Sbms		INP_WUNLOCK(inp);
1718191665Sbms		error = sooptcopyout(sopt, &optval, sizeof(u_int));
1719191665Sbms		break;
1720191665Sbms
1721191665Sbms	case IPV6_MULTICAST_LOOP:
1722191672Sbms		if (im6o == NULL)
1723191672Sbms			optval = in6_mcast_loop; /* XXX VIMAGE */
1724191665Sbms		else
1725191672Sbms			optval = im6o->im6o_multicast_loop;
1726191665Sbms		INP_WUNLOCK(inp);
1727191665Sbms		error = sooptcopyout(sopt, &optval, sizeof(u_int));
1728191665Sbms		break;
1729191665Sbms
1730191665Sbms	case IPV6_MSFILTER:
1731191672Sbms		if (im6o == NULL) {
1732191665Sbms			error = EADDRNOTAVAIL;
1733191665Sbms			INP_WUNLOCK(inp);
1734191665Sbms		} else {
1735191665Sbms			error = in6p_get_source_filters(inp, sopt);
1736191665Sbms		}
1737191665Sbms		break;
1738191665Sbms
1739191665Sbms	default:
1740191665Sbms		INP_WUNLOCK(inp);
1741191665Sbms		error = ENOPROTOOPT;
1742191665Sbms		break;
1743191665Sbms	}
1744191665Sbms
1745191665Sbms	INP_UNLOCK_ASSERT(inp);
1746191665Sbms
1747191665Sbms	return (error);
1748191665Sbms}
1749191665Sbms
1750191665Sbms/*
1751191672Sbms * Look up the ifnet to use for a multicast group membership,
1752191672Sbms * given the address of an IPv6 group.
1753191672Sbms *
1754191672Sbms * This routine exists to support legacy IPv6 multicast applications.
1755191672Sbms *
1756191672Sbms * If inp is non-NULL, use this socket's current FIB number for any
1757191672Sbms * required FIB lookup. Look up the group address in the unicast FIB,
1758191672Sbms * and use its ifp; usually, this points to the default next-hop.
1759191672Sbms * If the FIB lookup fails, return NULL.
1760191672Sbms *
1761191672Sbms * FUTURE: Support multiple forwarding tables for IPv6.
1762191672Sbms *
1763191672Sbms * Returns NULL if no ifp could be found.
1764191672Sbms */
1765191672Sbmsstatic struct ifnet *
1766191672Sbmsin6p_lookup_mcast_ifp(const struct inpcb *in6p __unused,
1767191672Sbms    const struct sockaddr_in6 *gsin6)
1768191672Sbms{
1769191672Sbms	struct route_in6	 ro6;
1770191672Sbms	struct ifnet		*ifp;
1771191672Sbms
1772191672Sbms	KASSERT(in6p->inp_vflag & INP_IPV6,
1773191672Sbms	    ("%s: not INP_IPV6 inpcb", __func__));
1774191672Sbms	KASSERT(gsin6->sin6_family == AF_INET6,
1775191672Sbms	    ("%s: not AF_INET6 group", __func__));
1776191672Sbms	KASSERT(IN6_IS_ADDR_MULTICAST(&gsin6->sin6_addr),
1777191672Sbms	    ("%s: not multicast", __func__));
1778191672Sbms
1779191672Sbms	ifp = NULL;
1780191672Sbms	memset(&ro6, 0, sizeof(struct route_in6));
1781191672Sbms	memcpy(&ro6.ro_dst, gsin6, sizeof(struct sockaddr_in6));
1782191672Sbms#ifdef notyet
1783191672Sbms	rtalloc_ign_fib(&ro6, 0, inp ? inp->inp_inc.inc_fibnum : 0);
1784191672Sbms#else
1785191672Sbms	rtalloc_ign((struct route *)&ro6, 0);
1786191672Sbms#endif
1787191672Sbms	if (ro6.ro_rt != NULL) {
1788191672Sbms		ifp = ro6.ro_rt->rt_ifp;
1789191672Sbms		KASSERT(ifp != NULL, ("%s: null ifp", __func__));
1790191672Sbms		RTFREE(ro6.ro_rt);
1791191672Sbms	}
1792191672Sbms
1793191672Sbms	return (ifp);
1794191672Sbms}
1795191672Sbms
1796191672Sbms/*
1797191665Sbms * Join an IPv6 multicast group, possibly with a source.
1798191672Sbms *
1799191672Sbms * FIXME: The KAME use of the unspecified address (::)
1800191672Sbms * to join *all* multicast groups is currently unsupported.
1801191665Sbms */
1802191665Sbmsstatic int
1803191665Sbmsin6p_join_group(struct inpcb *inp, struct sockopt *sopt)
1804191665Sbms{
1805191665Sbms	struct group_source_req		 gsr;
1806191665Sbms	sockunion_t			*gsa, *ssa;
1807191665Sbms	struct ifnet			*ifp;
1808191665Sbms	struct in6_mfilter		*imf;
1809191665Sbms	struct ip6_moptions		*imo;
1810191665Sbms	struct in6_multi		*inm;
1811191665Sbms	struct in6_msource		*lims;
1812191665Sbms	size_t				 idx;
1813191665Sbms	int				 error, is_new;
1814191665Sbms
1815191665Sbms	ifp = NULL;
1816191665Sbms	imf = NULL;
1817199528Sbms	lims = NULL;
1818191665Sbms	error = 0;
1819191665Sbms	is_new = 0;
1820191665Sbms
1821191665Sbms	memset(&gsr, 0, sizeof(struct group_source_req));
1822191665Sbms	gsa = (sockunion_t *)&gsr.gsr_group;
1823191665Sbms	gsa->ss.ss_family = AF_UNSPEC;
1824191665Sbms	ssa = (sockunion_t *)&gsr.gsr_source;
1825191665Sbms	ssa->ss.ss_family = AF_UNSPEC;
1826191665Sbms
1827192923Sbms	/*
1828192923Sbms	 * Chew everything into struct group_source_req.
1829192923Sbms	 * Overwrite the port field if present, as the sockaddr
1830192923Sbms	 * being copied in may be matched with a binary comparison.
1831192923Sbms	 * Ignore passed-in scope ID.
1832192923Sbms	 */
1833191665Sbms	switch (sopt->sopt_name) {
1834191665Sbms	case IPV6_JOIN_GROUP: {
1835191665Sbms		struct ipv6_mreq mreq;
1836191665Sbms
1837191665Sbms		error = sooptcopyin(sopt, &mreq, sizeof(struct ipv6_mreq),
1838191665Sbms		    sizeof(struct ipv6_mreq));
1839191665Sbms		if (error)
1840191665Sbms			return (error);
1841191665Sbms
1842191665Sbms		gsa->sin6.sin6_family = AF_INET6;
1843191665Sbms		gsa->sin6.sin6_len = sizeof(struct sockaddr_in6);
1844191665Sbms		gsa->sin6.sin6_addr = mreq.ipv6mr_multiaddr;
1845191665Sbms
1846191672Sbms		if (mreq.ipv6mr_interface == 0) {
1847191672Sbms			ifp = in6p_lookup_mcast_ifp(inp, &gsa->sin6);
1848191672Sbms		} else {
1849191672Sbms			if (mreq.ipv6mr_interface < 0 ||
1850191672Sbms			    V_if_index < mreq.ipv6mr_interface)
1851191672Sbms				return (EADDRNOTAVAIL);
1852191672Sbms			ifp = ifnet_byindex(mreq.ipv6mr_interface);
1853191672Sbms		}
1854191665Sbms		CTR3(KTR_MLD, "%s: ipv6mr_interface = %d, ifp = %p",
1855191665Sbms		    __func__, mreq.ipv6mr_interface, ifp);
1856191665Sbms	} break;
1857191665Sbms
1858191665Sbms	case MCAST_JOIN_GROUP:
1859191665Sbms	case MCAST_JOIN_SOURCE_GROUP:
1860191665Sbms		if (sopt->sopt_name == MCAST_JOIN_GROUP) {
1861191665Sbms			error = sooptcopyin(sopt, &gsr,
1862191665Sbms			    sizeof(struct group_req),
1863191665Sbms			    sizeof(struct group_req));
1864191665Sbms		} else if (sopt->sopt_name == MCAST_JOIN_SOURCE_GROUP) {
1865191665Sbms			error = sooptcopyin(sopt, &gsr,
1866191665Sbms			    sizeof(struct group_source_req),
1867191665Sbms			    sizeof(struct group_source_req));
1868191665Sbms		}
1869191665Sbms		if (error)
1870191665Sbms			return (error);
1871191665Sbms
1872191665Sbms		if (gsa->sin6.sin6_family != AF_INET6 ||
1873191665Sbms		    gsa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1874191665Sbms			return (EINVAL);
1875191665Sbms
1876191665Sbms		if (sopt->sopt_name == MCAST_JOIN_SOURCE_GROUP) {
1877191665Sbms			if (ssa->sin6.sin6_family != AF_INET6 ||
1878191665Sbms			    ssa->sin6.sin6_len != sizeof(struct sockaddr_in6))
1879191665Sbms				return (EINVAL);
1880192923Sbms			if (IN6_IS_ADDR_MULTICAST(&ssa->sin6.sin6_addr))
1881192923Sbms				return (EINVAL);
1882192923Sbms			/*
1883192923Sbms			 * TODO: Validate embedded scope ID in source
1884192923Sbms			 * list entry against passed-in ifp, if and only
1885192923Sbms			 * if source list filter entry is iface or node local.
1886192923Sbms			 */
1887192923Sbms			in6_clearscope(&ssa->sin6.sin6_addr);
1888191665Sbms			ssa->sin6.sin6_port = 0;
1889192923Sbms			ssa->sin6.sin6_scope_id = 0;
1890191665Sbms		}
1891191665Sbms
1892191665Sbms		if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
1893191665Sbms			return (EADDRNOTAVAIL);
1894191665Sbms		ifp = ifnet_byindex(gsr.gsr_interface);
1895191665Sbms		break;
1896191665Sbms
1897191665Sbms	default:
1898191665Sbms		CTR2(KTR_MLD, "%s: unknown sopt_name %d",
1899191665Sbms		    __func__, sopt->sopt_name);
1900191665Sbms		return (EOPNOTSUPP);
1901191665Sbms		break;
1902191665Sbms	}
1903191665Sbms
1904191665Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
1905191665Sbms		return (EINVAL);
1906191665Sbms
1907191665Sbms	if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0)
1908191665Sbms		return (EADDRNOTAVAIL);
1909191665Sbms
1910192923Sbms	gsa->sin6.sin6_port = 0;
1911192923Sbms	gsa->sin6.sin6_scope_id = 0;
1912192923Sbms
1913191665Sbms	/*
1914192923Sbms	 * Always set the scope zone ID on memberships created from userland.
1915192923Sbms	 * Use the passed-in ifp to do this.
1916192923Sbms	 * XXX The in6_setscope() return value is meaningless.
1917192923Sbms	 * XXX SCOPE6_LOCK() is taken by in6_setscope().
1918191672Sbms	 */
1919192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
1920191672Sbms
1921191665Sbms	imo = in6p_findmoptions(inp);
1922191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
1923191665Sbms	if (idx == -1) {
1924191665Sbms		is_new = 1;
1925191665Sbms	} else {
1926191665Sbms		inm = imo->im6o_membership[idx];
1927191665Sbms		imf = &imo->im6o_mfilters[idx];
1928199526Sbms		if (ssa->ss.ss_family != AF_UNSPEC) {
1929199526Sbms			/*
1930199526Sbms			 * MCAST_JOIN_SOURCE_GROUP on an exclusive membership
1931199526Sbms			 * is an error. On an existing inclusive membership,
1932199526Sbms			 * it just adds the source to the filter list.
1933199526Sbms			 */
1934199526Sbms			if (imf->im6f_st[1] != MCAST_INCLUDE) {
1935199526Sbms				error = EINVAL;
1936199526Sbms				goto out_in6p_locked;
1937199526Sbms			}
1938199528Sbms			/*
1939199528Sbms			 * Throw out duplicates.
1940199528Sbms			 *
1941199528Sbms			 * XXX FIXME: This makes a naive assumption that
1942199528Sbms			 * even if entries exist for *ssa in this imf,
1943199528Sbms			 * they will be rejected as dupes, even if they
1944199528Sbms			 * are not valid in the current mode (in-mode).
1945199528Sbms			 *
1946199528Sbms			 * in6_msource is transactioned just as for anything
1947199528Sbms			 * else in SSM -- but note naive use of in6m_graft()
1948199528Sbms			 * below for allocating new filter entries.
1949199528Sbms			 *
1950199528Sbms			 * This is only an issue if someone mixes the
1951199528Sbms			 * full-state SSM API with the delta-based API,
1952199528Sbms			 * which is discouraged in the relevant RFCs.
1953199528Sbms			 */
1954199526Sbms			lims = im6o_match_source(imo, idx, &ssa->sa);
1955199528Sbms			if (lims != NULL /*&&
1956199528Sbms			    lims->im6sl_st[1] == MCAST_INCLUDE*/) {
1957199526Sbms				error = EADDRNOTAVAIL;
1958199526Sbms				goto out_in6p_locked;
1959199526Sbms			}
1960199526Sbms		} else {
1961199526Sbms			/*
1962199527Sbms			 * MCAST_JOIN_GROUP alone, on any existing membership,
1963199527Sbms			 * is rejected, to stop the same inpcb tying up
1964199527Sbms			 * multiple refs to the in_multi.
1965199527Sbms			 * On an existing inclusive membership, this is also
1966199527Sbms			 * an error; if you want to change filter mode,
1967199527Sbms			 * you must use the userland API setsourcefilter().
1968199527Sbms			 * XXX We don't reject this for imf in UNDEFINED
1969199527Sbms			 * state at t1, because allocation of a filter
1970199527Sbms			 * is atomic with allocation of a membership.
1971199526Sbms			 */
1972199527Sbms			error = EINVAL;
1973199527Sbms			goto out_in6p_locked;
1974191665Sbms		}
1975191665Sbms	}
1976191665Sbms
1977191665Sbms	/*
1978191665Sbms	 * Begin state merge transaction at socket layer.
1979191665Sbms	 */
1980191665Sbms	INP_WLOCK_ASSERT(inp);
1981191665Sbms
1982191665Sbms	if (is_new) {
1983191665Sbms		if (imo->im6o_num_memberships == imo->im6o_max_memberships) {
1984191665Sbms			error = im6o_grow(imo);
1985191665Sbms			if (error)
1986191665Sbms				goto out_in6p_locked;
1987191665Sbms		}
1988191665Sbms		/*
1989191665Sbms		 * Allocate the new slot upfront so we can deal with
1990191665Sbms		 * grafting the new source filter in same code path
1991191665Sbms		 * as for join-source on existing membership.
1992191665Sbms		 */
1993191665Sbms		idx = imo->im6o_num_memberships;
1994191665Sbms		imo->im6o_membership[idx] = NULL;
1995191665Sbms		imo->im6o_num_memberships++;
1996191665Sbms		KASSERT(imo->im6o_mfilters != NULL,
1997191665Sbms		    ("%s: im6f_mfilters vector was not allocated", __func__));
1998191665Sbms		imf = &imo->im6o_mfilters[idx];
1999191665Sbms		KASSERT(RB_EMPTY(&imf->im6f_sources),
2000191665Sbms		    ("%s: im6f_sources not empty", __func__));
2001191665Sbms	}
2002191665Sbms
2003191665Sbms	/*
2004191665Sbms	 * Graft new source into filter list for this inpcb's
2005191665Sbms	 * membership of the group. The in6_multi may not have
2006199526Sbms	 * been allocated yet if this is a new membership, however,
2007199526Sbms	 * the in_mfilter slot will be allocated and must be initialized.
2008199527Sbms	 *
2009199527Sbms	 * Note: Grafting of exclusive mode filters doesn't happen
2010199527Sbms	 * in this path.
2011199528Sbms	 * XXX: Should check for non-NULL lims (node exists but may
2012199528Sbms	 * not be in-mode) for interop with full-state API.
2013191665Sbms	 */
2014191665Sbms	if (ssa->ss.ss_family != AF_UNSPEC) {
2015191665Sbms		/* Membership starts in IN mode */
2016191665Sbms		if (is_new) {
2017191665Sbms			CTR1(KTR_MLD, "%s: new join w/source", __func__);
2018191665Sbms			im6f_init(imf, MCAST_UNDEFINED, MCAST_INCLUDE);
2019191665Sbms		} else {
2020191665Sbms			CTR2(KTR_MLD, "%s: %s source", __func__, "allow");
2021191665Sbms		}
2022191665Sbms		lims = im6f_graft(imf, MCAST_INCLUDE, &ssa->sin6);
2023191665Sbms		if (lims == NULL) {
2024191665Sbms			CTR1(KTR_MLD, "%s: merge imf state failed",
2025191665Sbms			    __func__);
2026191665Sbms			error = ENOMEM;
2027191665Sbms			goto out_im6o_free;
2028191665Sbms		}
2029199526Sbms	} else {
2030199526Sbms		/* No address specified; Membership starts in EX mode */
2031199526Sbms		if (is_new) {
2032199526Sbms			CTR1(KTR_MLD, "%s: new join w/o source", __func__);
2033199526Sbms			im6f_init(imf, MCAST_UNDEFINED, MCAST_EXCLUDE);
2034199526Sbms		}
2035191665Sbms	}
2036191665Sbms
2037191665Sbms	/*
2038191665Sbms	 * Begin state merge transaction at MLD layer.
2039191665Sbms	 */
2040191665Sbms	IN6_MULTI_LOCK();
2041191665Sbms
2042191665Sbms	if (is_new) {
2043191665Sbms		error = in6_mc_join_locked(ifp, &gsa->sin6.sin6_addr, imf,
2044191665Sbms		    &inm, 0);
2045191665Sbms		if (error)
2046191665Sbms			goto out_im6o_free;
2047191665Sbms		imo->im6o_membership[idx] = inm;
2048191665Sbms	} else {
2049191665Sbms		CTR1(KTR_MLD, "%s: merge inm state", __func__);
2050191665Sbms		error = in6m_merge(inm, imf);
2051191665Sbms		if (error) {
2052191665Sbms			CTR1(KTR_MLD, "%s: failed to merge inm state",
2053191665Sbms			    __func__);
2054191665Sbms			goto out_im6f_rollback;
2055191665Sbms		}
2056191665Sbms		CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
2057191665Sbms		error = mld_change_state(inm, 0);
2058191665Sbms		if (error) {
2059191665Sbms			CTR1(KTR_MLD, "%s: failed mld downcall",
2060191665Sbms			    __func__);
2061191665Sbms			goto out_im6f_rollback;
2062191665Sbms		}
2063191665Sbms	}
2064191665Sbms
2065191665Sbms	IN6_MULTI_UNLOCK();
2066191665Sbms
2067191665Sbmsout_im6f_rollback:
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) {
2196192923Sbms		if (ifindex < 0 || 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);
2294191665Sbms		if (error) {
2295191665Sbms			CTR1(KTR_MLD, "%s: failed to merge inm state",
2296191665Sbms			    __func__);
2297191665Sbms			goto out_im6f_rollback;
2298191665Sbms		}
2299191665Sbms
2300191665Sbms		CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
2301191665Sbms		error = mld_change_state(inm, 0);
2302191665Sbms		if (error) {
2303191665Sbms			CTR1(KTR_MLD, "%s: failed mld downcall",
2304191665Sbms			    __func__);
2305191665Sbms		}
2306191665Sbms	}
2307191665Sbms
2308191665Sbms	IN6_MULTI_UNLOCK();
2309191665Sbms
2310191665Sbmsout_im6f_rollback:
2311191665Sbms	if (error)
2312191665Sbms		im6f_rollback(imf);
2313191665Sbms	else
2314191665Sbms		im6f_commit(imf);
2315191665Sbms
2316191665Sbms	im6f_reap(imf);
2317191665Sbms
2318191665Sbms	if (is_final) {
2319191665Sbms		/* Remove the gap in the membership array. */
2320199522Sbms		for (++idx; idx < imo->im6o_num_memberships; ++idx) {
2321191665Sbms			imo->im6o_membership[idx-1] = imo->im6o_membership[idx];
2322199522Sbms			imo->im6o_mfilters[idx-1] = imo->im6o_mfilters[idx];
2323199522Sbms		}
2324191665Sbms		imo->im6o_num_memberships--;
2325191665Sbms	}
2326191665Sbms
2327191665Sbmsout_in6p_locked:
2328191665Sbms	INP_WUNLOCK(inp);
2329191665Sbms	return (error);
2330191665Sbms}
2331191665Sbms
2332191665Sbms/*
2333191665Sbms * Select the interface for transmitting IPv6 multicast datagrams.
2334191665Sbms *
2335191665Sbms * Either an instance of struct in6_addr or an instance of struct ipv6_mreqn
2336191665Sbms * may be passed to this socket option. An address of in6addr_any or an
2337191665Sbms * interface index of 0 is used to remove a previous selection.
2338191665Sbms * When no interface is selected, one is chosen for every send.
2339191665Sbms */
2340191665Sbmsstatic int
2341191665Sbmsin6p_set_multicast_if(struct inpcb *inp, struct sockopt *sopt)
2342191665Sbms{
2343191665Sbms	struct ifnet		*ifp;
2344191665Sbms	struct ip6_moptions	*imo;
2345191665Sbms	u_int			 ifindex;
2346191665Sbms	int			 error;
2347191665Sbms
2348191665Sbms	if (sopt->sopt_valsize != sizeof(u_int))
2349191665Sbms		return (EINVAL);
2350191665Sbms
2351191665Sbms	error = sooptcopyin(sopt, &ifindex, sizeof(u_int), sizeof(u_int));
2352191665Sbms	if (error)
2353191665Sbms		return (error);
2354191665Sbms	if (ifindex < 0 || V_if_index < ifindex)
2355191665Sbms		return (EINVAL);
2356191665Sbms
2357191665Sbms	ifp = ifnet_byindex(ifindex);
2358191665Sbms	if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0)
2359191665Sbms		return (EADDRNOTAVAIL);
2360191665Sbms
2361191665Sbms	imo = in6p_findmoptions(inp);
2362191665Sbms	imo->im6o_multicast_ifp = ifp;
2363191665Sbms	INP_WUNLOCK(inp);
2364191665Sbms
2365191665Sbms	return (0);
2366191665Sbms}
2367191665Sbms
2368191665Sbms/*
2369191665Sbms * Atomically set source filters on a socket for an IPv6 multicast group.
2370191665Sbms *
2371191665Sbms * SMPng: NOTE: Potentially calls malloc(M_WAITOK) with Giant held.
2372191665Sbms */
2373191665Sbmsstatic int
2374191665Sbmsin6p_set_source_filters(struct inpcb *inp, struct sockopt *sopt)
2375191665Sbms{
2376191665Sbms	struct __msfilterreq	 msfr;
2377191665Sbms	sockunion_t		*gsa;
2378191665Sbms	struct ifnet		*ifp;
2379191665Sbms	struct in6_mfilter	*imf;
2380191665Sbms	struct ip6_moptions	*imo;
2381191665Sbms	struct in6_multi		*inm;
2382191665Sbms	size_t			 idx;
2383191665Sbms	int			 error;
2384191665Sbms
2385191665Sbms	error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq),
2386191665Sbms	    sizeof(struct __msfilterreq));
2387191665Sbms	if (error)
2388191665Sbms		return (error);
2389191665Sbms
2390199523Sbms	if (msfr.msfr_nsrcs > in6_mcast_maxsocksrc)
2391199523Sbms		return (ENOBUFS);
2392199523Sbms
2393199523Sbms	if (msfr.msfr_fmode != MCAST_EXCLUDE &&
2394199523Sbms	    msfr.msfr_fmode != MCAST_INCLUDE)
2395191665Sbms		return (EINVAL);
2396191665Sbms
2397191665Sbms	if (msfr.msfr_group.ss_family != AF_INET6 ||
2398191665Sbms	    msfr.msfr_group.ss_len != sizeof(struct sockaddr_in6))
2399191665Sbms		return (EINVAL);
2400191665Sbms
2401191665Sbms	gsa = (sockunion_t *)&msfr.msfr_group;
2402191665Sbms	if (!IN6_IS_ADDR_MULTICAST(&gsa->sin6.sin6_addr))
2403191665Sbms		return (EINVAL);
2404191665Sbms
2405191665Sbms	gsa->sin6.sin6_port = 0;	/* ignore port */
2406191665Sbms
2407191665Sbms	if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
2408191665Sbms		return (EADDRNOTAVAIL);
2409191665Sbms	ifp = ifnet_byindex(msfr.msfr_ifindex);
2410191665Sbms	if (ifp == NULL)
2411191665Sbms		return (EADDRNOTAVAIL);
2412192923Sbms	(void)in6_setscope(&gsa->sin6.sin6_addr, ifp, NULL);
2413191665Sbms
2414191665Sbms	/*
2415191665Sbms	 * Take the INP write lock.
2416191665Sbms	 * Check if this socket is a member of this group.
2417191665Sbms	 */
2418191665Sbms	imo = in6p_findmoptions(inp);
2419191665Sbms	idx = im6o_match_group(imo, ifp, &gsa->sa);
2420191665Sbms	if (idx == -1 || imo->im6o_mfilters == NULL) {
2421191665Sbms		error = EADDRNOTAVAIL;
2422191665Sbms		goto out_in6p_locked;
2423191665Sbms	}
2424191665Sbms	inm = imo->im6o_membership[idx];
2425191665Sbms	imf = &imo->im6o_mfilters[idx];
2426191665Sbms
2427191665Sbms	/*
2428191665Sbms	 * Begin state merge transaction at socket layer.
2429191665Sbms	 */
2430191665Sbms	INP_WLOCK_ASSERT(inp);
2431191665Sbms
2432191665Sbms	imf->im6f_st[1] = msfr.msfr_fmode;
2433191665Sbms
2434191665Sbms	/*
2435191665Sbms	 * Apply any new source filters, if present.
2436191665Sbms	 * Make a copy of the user-space source vector so
2437191665Sbms	 * that we may copy them with a single copyin. This
2438191665Sbms	 * allows us to deal with page faults up-front.
2439191665Sbms	 */
2440191665Sbms	if (msfr.msfr_nsrcs > 0) {
2441191665Sbms		struct in6_msource	*lims;
2442191665Sbms		struct sockaddr_in6	*psin;
2443191665Sbms		struct sockaddr_storage	*kss, *pkss;
2444191665Sbms		int			 i;
2445191665Sbms
2446191665Sbms		INP_WUNLOCK(inp);
2447191665Sbms
2448191665Sbms		CTR2(KTR_MLD, "%s: loading %lu source list entries",
2449191665Sbms		    __func__, (unsigned long)msfr.msfr_nsrcs);
2450191665Sbms		kss = malloc(sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs,
2451191665Sbms		    M_TEMP, M_WAITOK);
2452191665Sbms		error = copyin(msfr.msfr_srcs, kss,
2453191665Sbms		    sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs);
2454191665Sbms		if (error) {
2455191665Sbms			free(kss, M_TEMP);
2456191665Sbms			return (error);
2457191665Sbms		}
2458191665Sbms
2459191665Sbms		INP_WLOCK(inp);
2460191665Sbms
2461191665Sbms		/*
2462191665Sbms		 * Mark all source filters as UNDEFINED at t1.
2463191665Sbms		 * Restore new group filter mode, as im6f_leave()
2464191665Sbms		 * will set it to INCLUDE.
2465191665Sbms		 */
2466191665Sbms		im6f_leave(imf);
2467191665Sbms		imf->im6f_st[1] = msfr.msfr_fmode;
2468191665Sbms
2469191665Sbms		/*
2470191665Sbms		 * Update socket layer filters at t1, lazy-allocating
2471191665Sbms		 * new entries. This saves a bunch of memory at the
2472191665Sbms		 * cost of one RB_FIND() per source entry; duplicate
2473191665Sbms		 * entries in the msfr_nsrcs vector are ignored.
2474191665Sbms		 * If we encounter an error, rollback transaction.
2475191665Sbms		 *
2476191665Sbms		 * XXX This too could be replaced with a set-symmetric
2477191665Sbms		 * difference like loop to avoid walking from root
2478191665Sbms		 * every time, as the key space is common.
2479191665Sbms		 */
2480191665Sbms		for (i = 0, pkss = kss; i < msfr.msfr_nsrcs; i++, pkss++) {
2481191665Sbms			psin = (struct sockaddr_in6 *)pkss;
2482191665Sbms			if (psin->sin6_family != AF_INET6) {
2483191665Sbms				error = EAFNOSUPPORT;
2484191665Sbms				break;
2485191665Sbms			}
2486191665Sbms			if (psin->sin6_len != sizeof(struct sockaddr_in6)) {
2487191665Sbms				error = EINVAL;
2488191665Sbms				break;
2489191665Sbms			}
2490192923Sbms			if (IN6_IS_ADDR_MULTICAST(&psin->sin6_addr)) {
2491192923Sbms				error = EINVAL;
2492192923Sbms				break;
2493192923Sbms			}
2494192923Sbms			/*
2495192923Sbms			 * TODO: Validate embedded scope ID in source
2496192923Sbms			 * list entry against passed-in ifp, if and only
2497192923Sbms			 * if source list filter entry is iface or node local.
2498192923Sbms			 */
2499192923Sbms			in6_clearscope(&psin->sin6_addr);
2500191665Sbms			error = im6f_get_source(imf, psin, &lims);
2501191665Sbms			if (error)
2502191665Sbms				break;
2503191665Sbms			lims->im6sl_st[1] = imf->im6f_st[1];
2504191665Sbms		}
2505191665Sbms		free(kss, M_TEMP);
2506191665Sbms	}
2507191665Sbms
2508191665Sbms	if (error)
2509191665Sbms		goto out_im6f_rollback;
2510191665Sbms
2511191665Sbms	INP_WLOCK_ASSERT(inp);
2512191665Sbms	IN6_MULTI_LOCK();
2513191665Sbms
2514191665Sbms	/*
2515191665Sbms	 * Begin state merge transaction at MLD layer.
2516191665Sbms	 */
2517191665Sbms	CTR1(KTR_MLD, "%s: merge inm state", __func__);
2518191665Sbms	error = in6m_merge(inm, imf);
2519191665Sbms	if (error) {
2520191665Sbms		CTR1(KTR_MLD, "%s: failed to merge inm state", __func__);
2521191665Sbms		goto out_im6f_rollback;
2522191665Sbms	}
2523191665Sbms
2524191665Sbms	CTR1(KTR_MLD, "%s: doing mld downcall", __func__);
2525191665Sbms	error = mld_change_state(inm, 0);
2526191665Sbms	if (error)
2527191665Sbms		CTR1(KTR_MLD, "%s: failed mld downcall", __func__);
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
2721191665Sbms	IF_ADDR_LOCK(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	}
2750191665Sbms	IF_ADDR_UNLOCK(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