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