/*	$NetBSD: mld6.c,v 1.26 2004/03/28 08:28:50 christos Exp $	*/
/*	$KAME: mld6.c,v 1.25 2001/01/16 14:14:18 itojun Exp $	*/

/*
 * Copyright (c) 2002 INRIA. All rights reserved.
 *
 * Implementation of Multicast Listener Discovery, Version 2.
 * Developed by Hitoshi Asaeda, INRIA, August 2002.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of INRIA nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Copyright (C) 1998 WIDE Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Stephen Deering of Stanford University.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)igmp.c	8.1 (Berkeley) 7/19/93
 */

/*
 * Copyright (c) 1988 Stephen Deering.
 *
 * This code is derived from software contributed to Berkeley by
 * Stephen Deering of Stanford University.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)igmp.c	8.1 (Berkeley) 7/19/93
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: mld6.c,v 1.26 2004/03/28 08:28:50 christos Exp $");

#include "opt_inet.h"
#include "opt_mcast.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/syslog.h>
#ifdef MLDV2
#include <sys/sysctl.h>
#endif

#include <net/if.h>

#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet/icmp6.h>
#include <netinet6/mld6_var.h>

#include <net/net_osdep.h>

/*
 * Protocol constants
 */

#ifdef MLDV2
struct router6_info *rt6i_head = NULL;
int mldmaxsrcfilter = IP_MAX_SOURCE_FILTER;
int mldsomaxsrc = SO_MAX_SOURCE_FILTER;
int mldalways_v2 = 0;
static int qhdrlen = MLD_V2_QUERY_MINLEN;
static int rhdrlen = 8; /* XXX */
static int ghdrlen = 20; /* XXX */
static int addrlen = sizeof(struct in6_addr);

#define	SOURCE_RECORD_LEN(numsrc)	(numsrc * addrlen)

#define	GET_REPORT_SOURCE_HEAD(in6m, type, iasl) {			\
	if ((type) == ALLOW_NEW_SOURCES)				\
		(iasl) = (in6m)->in6m_source->i6ms_alw;			\
	else if ((type) == BLOCK_OLD_SOURCES)				\
		(iasl) = (in6m)->in6m_source->i6ms_blk;			\
	else if ((type) == CHANGE_TO_INCLUDE_MODE)			\
		(iasl) = (in6m)->in6m_source->i6ms_toin;		\
	else if ((type) == CHANGE_TO_EXCLUDE_MODE)			\
		(iasl) = (in6m)->in6m_source->i6ms_toex;		\
	else {								\
		if ((in6m)->in6m_state == MLD_SG_QUERY_PENDING_MEMBER)	\
			(iasl) = (in6m)->in6m_source->i6ms_rec;		\
		else							\
			(iasl) = (in6m)->in6m_source->i6ms_cur;		\
	}								\
}
#endif

static struct ip6_pktopts ip6_opts;
static int mld_group_timers_are_running;
static int mld_interface_timers_are_running;
static int mld_state_change_timers_are_running;
static int ignflags = (IN6_IFF_NOTREADY|IN6_IFF_ANYCAST) & ~IN6_IFF_TENTATIVE;
/* XXX: These are necessary for KAME's link-local hack */
static struct in6_addr mld_all_nodes_linklocal = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
static struct in6_addr mld_all_routers_linklocal = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT;
#ifdef MLDV2
static struct in6_addr mld_all_v2routers_linklocal = IN6ADDR_LINKLOCAL_ALLV2ROUTERS_INIT;
#endif

static void mld_sendpkt(struct in6_multi *, int, const struct in6_addr *);
static struct mld_hdr *mld_allocbuf(struct mbuf **, int,
		struct in6_multi *, int);
#ifdef MLDV2
static struct router6_info *rt6i_find(struct ifnet *);
int mld_set_timer(struct ifnet *, struct router6_info *, struct mldv2_hdr *,
		int, u_int8_t);
void mld_set_hostcompat(struct ifnet *, struct router6_info *, int);
int mld_record_queried_source(struct in6_multi *, struct mldv2_hdr *,
		u_int16_t);
void mld_send_all_current_state_report(struct ifnet *);
int mld_send_current_state_report(struct mbuf **, int *, struct in6_multi *);
static int mld_create_group_record(struct mbuf *, int *, struct in6_multi *,
		u_int16_t, u_int16_t *, u_int8_t);
static void mld_cancel_pending_response(struct ifnet *, struct router6_info *);
#endif

#ifdef MLDV2
struct router6_info *
rt6i_init(ifp)
	struct ifnet *ifp;
{
	struct router6_info *rt6i;

	rt6i = (struct router6_info *)malloc(sizeof(struct router6_info),
					     M_MRTABLE, M_NOWAIT);
	if (rt6i == NULL)
		return NULL;

	rt6i->rt6i_ifp = ifp;
	rt6i->rt6i_timer1 = 0;
	rt6i->rt6i_timer2 = 0;
	rt6i->rt6i_qrv = MLD_DEF_RV;
	rt6i->rt6i_qqi = MLD_DEF_QI;
	rt6i->rt6i_qri = MLD_DEF_QRI / MLD_TIMER_SCALE;
	rt6i->rt6i_type = MLD_V2_ROUTER;
	rt6i->rt6i_next = rt6i_head;
	rt6i_head = rt6i;
	return (rt6i);
}

static struct router6_info *
rt6i_find(ifp)
	struct ifnet *ifp;
{
	struct router6_info *rt6i;

	for (rt6i = rt6i_head; rt6i != 0; rt6i = rt6i->rt6i_next) {
		if (rt6i->rt6i_ifp == ifp)
			return (rt6i);
	}

	if ((rt6i = rt6i_init(ifp)) == NULL)
		return NULL;

	return (rt6i);
}
#endif /* MLDV2 */

void
mld_init()
{
	static u_int8_t hbh_buf[8];
	struct ip6_hbh *hbh = (struct ip6_hbh *)hbh_buf;
	u_int16_t rtalert_code = htons((u_int16_t)IP6OPT_RTALERT_MLD);

	mld_group_timers_are_running = 0;
	mld_interface_timers_are_running = 0;
	mld_state_change_timers_are_running = 0;

	/* ip6h_nxt will be fill in later */
	hbh->ip6h_len = 0;	/* (8 >> 3) - 1 */

	/* XXX: grotty hard coding... */
	hbh_buf[2] = IP6OPT_PADN;	/* 2 byte padding */
	hbh_buf[3] = 0;
	hbh_buf[4] = IP6OPT_RTALERT;
	hbh_buf[5] = IP6OPT_RTALERT_LEN - 2;
	bcopy((caddr_t)&rtalert_code, &hbh_buf[6], sizeof(u_int16_t));

	ip6_opts.ip6po_hbh = hbh;
	/* We will specify the hoplimit by a multicast option. */
	ip6_opts.ip6po_hlim = -1;
}

void
#ifdef MLDV2
mld_start_listening(in6m, type)
	struct in6_multi *in6m;
	u_int8_t type;			/* State-Change report type */
#else
mld_start_listening(in6m)
	struct in6_multi *in6m;
#endif
{
#ifdef MLDV2
	struct mbuf *m = NULL;
	int buflen = 0;
	int timer_init = 1;		/* indicate timer initialization */
#endif

	/*
	 * RFC2710 page 10:
	 * The node never sends a Report or Done for the link-scope all-nodes
	 * address.
	 * MLD messages are never sent for multicast addresses whose scope is 0
	 * (reserved) or 1 (node-local).
	 */
	mld_all_nodes_linklocal.s6_addr16[1] =
		htons(in6m->in6m_ifp->if_index); /* XXX */
	if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &mld_all_nodes_linklocal) ||
	    IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < IPV6_ADDR_SCOPE_LINKLOCAL) {
		in6m->in6m_timer = 0;
		in6m->in6m_state = MLD_OTHERLISTENER;
	} else {
#ifdef MLDV2
		if (in6m->in6m_rti->rt6i_type == MLD_V2_ROUTER)
			mld_send_state_change_report(&m, &buflen, in6m,
						     type, timer_init);
		else
#endif
		{
			mld_sendpkt(in6m, MLD_LISTENER_REPORT, NULL);
			in6m->in6m_timer =
			    MLD_RANDOM_DELAY(MLD_UNSOL_INTVL * PR_FASTHZ);
			in6m->in6m_state = MLD_IREPORTEDLAST;
			mld_group_timers_are_running = 1;
		}
	}
}

void
#ifdef MLDV2
mld_stop_listening(in6m, type)
	struct in6_multi *in6m;
	u_int8_t type;			/* State-Change report type */
#else
mld_stop_listening(in6m)
	struct in6_multi *in6m;
#endif
{
#ifdef MLDV2
	struct mbuf *m = NULL;
	int buflen = 0;
	int timer_init = 1;		/* indicate timer initialization */
#endif

	mld_all_nodes_linklocal.s6_addr16[1] =
	    htons(in6m->in6m_ifp->if_index); /* XXX */
	mld_all_routers_linklocal.s6_addr16[1] =
	    htons(in6m->in6m_ifp->if_index); /* XXX: necessary when mrouting */

	if (in6m->in6m_state == MLD_OTHERLISTENER ||
	    (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &mld_all_nodes_linklocal) ||
	     IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < IPV6_ADDR_SCOPE_LINKLOCAL))
		return; /* no need to respond */
#ifdef MLDV2
	if (in6m->in6m_rti->rt6i_type == MLD_V2_ROUTER)
		mld_send_state_change_report(&m, &buflen, in6m,
					     type, timer_init);
	else
#endif
		mld_sendpkt(in6m, MLD_LISTENER_DONE,
			    &mld_all_routers_linklocal);
}

void
mld_input(m, off)
	struct mbuf *m;
	int off;
{
	struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
	struct mld_hdr *mldh;
	struct ifnet *ifp = m->m_pkthdr.rcvif;
	struct in6_multi *in6m;
	struct in6_ifaddr *ia;
	int timer;		/* timer value in the MLD query header */
#ifdef MLDV2
	struct mldv2_hdr *mldv2h;
	u_int8_t query_type;
	u_int16_t mldlen;
	struct router6_info *rt6i;
#endif

	IP6_EXTHDR_GET(mldh, struct mld_hdr *, m, off, sizeof(*mldh));
	if (mldh == NULL) {
		icmp6stat.icp6s_tooshort++;
		return;
	}

	/* source address validation */
	ip6 = mtod(m, struct ip6_hdr *);/* in case mpullup */
	if (!IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) {
#if 0
		log(LOG_ERR,
		    "mld_input: src %s is not link-local (grp=%s)\n",
		    ip6_sprintf(&ip6->ip6_src),
		    ip6_sprintf(&mldh->mld_addr));
#endif
		/*
		 * spec (RFC2710) does not explicitly
		 * specify to discard the packet from a non link-local
		 * source address. But we believe it's expected to do so.
		 * XXX: do we have to allow :: as source?
		 */
		m_freem(m);
		return;
	}

#ifdef MLDV2
	rt6i = rt6i_find(ifp);
	if (rt6i == NULL) {
#ifdef MLDV2_DEBUG
		printf("XXX: mld_input: cannot find router6_info at link#%d\n", ifp->if_index);
#endif
		m_freem(m);
		return; /* XXX */
	}
#endif

	/*
	 * In the MLD6 specification, there are 3 states and a flag.
	 *
	 * In Non-Listener state, we simply don't have a membership record.
	 * In Delaying Listener state, our timer is running (in6m->in6m_timer)
	 * In Idle Listener state, our timer is not running (in6m->in6m_timer==0)
	 *
	 * The flag is in6m->in6m_state, it is set to MLD_OTHERLISTENER if
	 * we have heard a report from another member, or MLD_IREPORTEDLAST
	 * if we sent the last report.
	 */
	switch (mldh->mld_type) {
	case MLD_LISTENER_QUERY:
		if (ifp->if_flags & IFF_LOOPBACK)
			break;

		if (!IN6_IS_ADDR_UNSPECIFIED(&mldh->mld_addr) &&
		    !IN6_IS_ADDR_MULTICAST(&mldh->mld_addr))
			break;	/* print error or log stat? */
		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
			mldh->mld_addr.s6_addr16[1] =
			    htons(ifp->if_index); /* XXX */

#ifdef MLDV2
		/*
		 * MLD version and Query type check.
		 * MLDv1 Query: length = 24 octets AND Max-Resp-Code = 0
		 * MLDv2 Query: length >= 28 octets AND Max-Resp-Code != 0
		 * (MLDv1 implementation must accept only the first 24
		 * octets of the query message)
		 */
		mldlen = m->m_pkthdr.len - off;
		if (mldlen == MLD_MINLEN)
			query_type = MLD_V1_QUERY;
		else if (mldlen >= MLD_V2_QUERY_MINLEN) {
			mldv2h = (struct mldv2_hdr *)mldh;
			if (IN6_ARE_ADDR_EQUAL(&mldv2h->mld_addr,
					       &in6addr_any) &&
			    mldv2h->mld_numsrc == 0)
				query_type = MLD_V2_GENERAL_QUERY;
			else if (IN6_IS_ADDR_MULTICAST(&mldv2h->mld_addr) &&
				 mldv2h->mld_numsrc == 0)
				query_type = MLD_V2_GROUP_QUERY;
			else if (IN6_IS_ADDR_MULTICAST(&mldv2h->mld_addr) &&
				 ntohs(mldv2h->mld_numsrc) > 0)
				query_type = MLD_V2_GROUP_SOURCE_QUERY;
			else {
#ifdef MLDV2_DEBUG
				printf("mld_input: ignores MLD packet with invalid format\n");
#endif
				m_freem(m);
				return;
			}

			if (rt6i->rt6i_type == MLD_V1_ROUTER)
				goto start_v1;
			if (mld_set_timer(ifp, rt6i, mldv2h, mldlen, query_type)
					!= 0) {
#ifdef MLDV2_DEBUG
				printf("XXX: mld_input: receive bad query\n");
#endif
				m_freem(m);
				return;
			}
			if (IN6_IS_ADDR_MC_LINKLOCAL(&mldv2h->mld_addr))
				mldv2h->mld_addr.s6_addr16[1] = 0; /* XXX */
			break;
		} else { /* 24 < packet length  < 28 */
#ifdef MLDV2_DEBUG
			printf("mld_input: ignores MLD packet with improper length (%d)\n", mldlen);
#endif
			m_freem(m);
			return;
		}
#endif

#ifdef MLDV2
start_v1:
#endif
		/*
		 * - Start the timers in all of our membership records
		 *   that the query applies to for the interface on
		 *   which the query arrived excl. those that belong
		 *   to the "all-nodes" group (ff02::1).
		 * - Restart any timer that is already running but has
		 *   A value longer than the requested timeout.
		 * - Use the value specified in the query message as
		 *   the maximum timeout.
		 */
		IFP_TO_IA6(ifp, ia);
		if (ia == NULL)
			break;

		/*
		 * XXX: System timer resolution is too low to handle Max
		 * Response Delay, so set 1 to the internal timer even if
		 * the calculated value equals to zero when Max Response
		 * Delay is positive.
		 */
		timer = ntohs(mldh->mld_maxdelay) * PR_FASTHZ / MLD_TIMER_SCALE;
		if (timer == 0 && mldh->mld_maxdelay)
			timer = 1;
		mld_all_nodes_linklocal.s6_addr16[1] =
			htons(ifp->if_index); /* XXX */

		for (in6m = ia->ia6_multiaddrs.lh_first;
		     in6m;
		     in6m = in6m->in6m_entry.le_next) {
			if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr,
						&mld_all_nodes_linklocal) ||
			    IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) <
						IPV6_ADDR_SCOPE_LINKLOCAL)
				continue;

			if (IN6_IS_ADDR_UNSPECIFIED(&mldh->mld_addr) ||
			    IN6_ARE_ADDR_EQUAL(&mldh->mld_addr,
						&in6m->in6m_addr)) {
				if (timer == 0) {
					/* send a report immediately */
					mld_sendpkt(in6m, MLD_LISTENER_REPORT,
						    NULL);
					in6m->in6m_timer = 0; /* reset timer */
					in6m->in6m_state = MLD_IREPORTEDLAST;
				}
				else if (in6m->in6m_timer == 0 || /*idle state*/
				    in6m->in6m_timer > timer) {
					in6m->in6m_timer =
					    MLD_RANDOM_DELAY(timer);
					mld_group_timers_are_running = 1;
				}
			}
		}

#ifdef MLDV2
		 /*
		  * MLDv1 Querier Present is set to Older Version Querier
		  * Present Timeout seconds whenever an MLDv1 General Query
		  * is received.
		  */
		if (mldalways_v2 == 0 &&
		    IN6_ARE_ADDR_EQUAL(&mldh->mld_addr, &in6addr_any))
			mld_set_hostcompat(ifp, rt6i, query_type);
#endif

		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
			mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
		break;
	case MLD_LISTENER_REPORT:
		/*
		 * For fast leave to work, we have to know that we are the
		 * last person to send a report for this group.  Reports
		 * can potentially get looped back if we are a multicast
		 * router, so discard reports sourced by me.
		 * Note that it is impossible to check IFF_LOOPBACK flag of
		 * ifp for this purpose, since ip6_mloopback pass the physical
		 * interface to looutput.
		 */
		if (m->m_flags & M_LOOP) /* XXX: grotty flag, but efficient */
			break;

		if (!IN6_IS_ADDR_MULTICAST(&mldh->mld_addr))
			break;

		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
			mldh->mld_addr.s6_addr16[1] =
				htons(ifp->if_index); /* XXX */
		/*
		 * If we belong to the group being reported, stop
		 * our timer for that group.
		 */
		IN6_LOOKUP_MULTI(mldh->mld_addr, ifp, in6m);
		if (in6m) {
			in6m->in6m_timer = 0; /* transit to idle state */
			in6m->in6m_state = MLD_OTHERLISTENER; /* clear flag */
		}

		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
			mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
		break;
	default:		/* this is impossible */
#if 0
		/*
		 * this case should be impossible because of filtering in
		 * icmp6_input().  But we explicitly disabled this part
		 * just in case.
		 */
		log(LOG_ERR, "mld_input: illegal type(%d)", mldh->mld_type);
#endif
		break;
	}

	m_freem(m);
}

void
mld_fasttimo()
{
	struct in6_multi *in6m;
	struct in6_multistep step;
	struct ifnet *ifp = NULL;
#ifdef MLDV2
	/*
	 * Both of Current-State Record timer and State-Change Record timer
	 * are controled.
	 */
	struct router6_info *rt6i;
	struct mbuf *cm, *sm;
	int cbuflen, sbuflen;
#endif
	int s;

	/*
	 * Quick check to see if any work needs to be done, in order
	 * to minimize the overhead of fasttimo processing.
	 */
	if (!mld_group_timers_are_running && !mld_interface_timers_are_running
			&& !mld_state_change_timers_are_running)
		return;

	s = splsoftnet();

#ifdef MLDV2
	if (mld_interface_timers_are_running) {
		mld_interface_timers_are_running = 0;
		for (rt6i = rt6i_head; rt6i; rt6i = rt6i->rt6i_next) {
			if (rt6i->rt6i_timer2 == 0)
				; /* do nothing */
			else if (--rt6i->rt6i_timer2 == 0)
				mld_send_all_current_state_report
						(rt6i->rt6i_ifp);
			else
				mld_interface_timers_are_running = 1;
		}
	}
#endif

#ifndef MLDV2
	if (!mld_group_timers_are_running) {
#else
	if (!mld_group_timers_are_running &&
	    !mld_state_change_timers_are_running) {
#endif
		splx(s);
		return;
	}
	mld_group_timers_are_running = 0;
#ifdef MLDV2
	mld_state_change_timers_are_running = 0;
	cm = sm = NULL;
	cbuflen = sbuflen = 0;
#endif
	IN6_FIRST_MULTI(step, in6m);
	if (in6m == NULL) {
		splx(s);
		return;
	}
	ifp = in6m->in6m_ifp;
	while (in6m != NULL) {
		if (in6m->in6m_timer == 0)
			; /* do nothing */
		else if (--in6m->in6m_timer == 0) {
#ifdef MLDV2
			if (in6m->in6m_rti->rt6i_type == MLD_V1_ROUTER) {
#endif
				mld_sendpkt(in6m, MLD_LISTENER_REPORT, NULL);
				in6m->in6m_state = MLD_IREPORTEDLAST;
#ifdef MLDV2
			} else if ((in6m->in6m_state
					== MLD_G_QUERY_PENDING_MEMBER) ||
				   (in6m->in6m_state
					== MLD_SG_QUERY_PENDING_MEMBER)) {
				if ((cm != NULL) && (ifp != in6m->in6m_ifp)) {
					mld_sendbuf(cm, ifp);
					cm = NULL;
				}
				(void)mld_send_current_state_report
					(&cm, &cbuflen, in6m);
				ifp = in6m->in6m_ifp;
			}
#endif
		} else
			mld_group_timers_are_running = 1;

#ifdef MLDV2
		if (!in6_is_mld_target(&in6m->in6m_addr))
			; /* skip */
		else if (in6m->in6m_source->i6ms_timer == 0)
			; /* do nothing */
		else if (--in6m->in6m_source->i6ms_timer == 0) {
			if ((sm != NULL) && (ifp != in6m->in6m_ifp)) {
				mld_sendbuf(sm, ifp);
				sm = NULL;
			}
			/* Check if this report was pending Source-List-Change
			 * report or not. It is only the case that robvar was
			 * not reduced here. (XXX rarely, QRV may be changed
			 * in a same timing.) */
			if (in6m->in6m_source->i6ms_robvar
					== in6m->in6m_rti->rt6i_qrv) {
				mld_send_state_change_report(&sm, &sbuflen,
						in6m, (u_int8_t)0, (int)1);
				sm = NULL;
			} else if (in6m->in6m_source->i6ms_robvar > 0) {
				mld_send_state_change_report(&sm, &sbuflen,
						in6m, (u_int8_t)0, (int)0);
				ifp = in6m->in6m_ifp;
			}
			if (in6m->in6m_source->i6ms_robvar != 0) {
				in6m->in6m_source->i6ms_timer = MLD_RANDOM_DELAY
						(MLDV2_UNSOL_INTVL * PR_FASTHZ);
				mld_state_change_timers_are_running = 1;
			}
		} else
			mld_state_change_timers_are_running = 1;
#endif
		IN6_NEXT_MULTI(step, in6m);
	}

#ifdef MLDV2
	if (cm != NULL)
		mld_sendbuf(cm, ifp);
	if (sm != NULL)
		mld_sendbuf(sm, ifp);
#endif
	splx(s);
}

#ifdef MLDV2
void
mld_slowtimo()
{
	struct router6_info *rt6i;
	int s;

	s = splsoftnet();

	for (rt6i = rt6i_head; rt6i != 0; rt6i = rt6i->rt6i_next) {
		if (rt6i->rt6i_timer1 == 0) {
			if (rt6i->rt6i_type != MLD_V2_ROUTER)
				rt6i->rt6i_type = MLD_V2_ROUTER;
		} else if (rt6i->rt6i_timer1 > 0) {
			--rt6i->rt6i_timer1;
			if (rt6i->rt6i_timer2 > 0)
				rt6i->rt6i_timer2 = 0;
		}
	}
	splx(s);
}
#endif

static void
mld_sendpkt(in6m, type, dst)
	struct in6_multi *in6m;
	int type;
	const struct in6_addr *dst;
{
	struct mbuf *mh;
	struct mld_hdr *mldh;
	struct ip6_hdr *ip6;
	struct ip6_moptions im6o;
	struct in6_ifaddr *ia = NULL;
	struct ifnet *ifp = in6m->in6m_ifp;

	/*
	 * At first, find a link local address on the outgoing interface
	 * to use as the source address of the MLD packet.
	 * We do not reject tentative addresses for MLD report to deal with
	 * the case where we first join a link-local address.
	 */
	if ((ia = in6ifa_ifpforlinklocal(ifp, ignflags)) == NULL)
		return;
	if ((ia->ia6_flags & IN6_IFF_TENTATIVE))
		ia = NULL;

	mldh = mld_allocbuf(&mh, MLD_MINLEN, in6m, type);
	if (mldh == NULL)
		return;

	/* fill in the ip6 header */
	ip6 = mtod(mh, struct ip6_hdr *);
	ip6->ip6_src = ia ? ia->ia_addr.sin6_addr : in6addr_any;
	ip6->ip6_dst = dst ? *dst : in6m->in6m_addr;

#ifdef __FreeBSD__ /* ifdef KAME ? */
	/* set packet addresses in a full sockaddr_in6 form */
	bzero(&src_sa, sizeof(struct sockaddr_in6));
	bzero(&dst_sa, sizeof(struct sockaddr_in6));
	src_sa.sin6_family = dst_sa.sin6_family = AF_INET6;
	src_sa.sin6_len = dst_sa.sin6_len = sizeof(struct sockaddr_in6);
	src_sa.sin6_addr = ip6->ip6_src;
	dst_sa.sin6_addr = ip6->ip6_dst;
	if (in6_addr2zoneid(ifp, &src_sa.sin6_addr, &src_sa.sin6_scope_id) ||
	    in6_addr2zoneid(ifp, &dst_sa.sin6_addr, &dst_sa.sin6_scope_id)) {
		/* XXX: impossible */
		m_free(*mh);
		*mh = NULL;
		return NULL;
	}
	if (!ip6_setpktaddrs(*mh, &src_sa, &dst_sa)) {
		m_free(*mh);
		*mh = NULL;
		return NULL;
	}
#endif
	mldh->mld_addr = in6m->in6m_addr;
	if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
		mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
	mldh->mld_cksum = in6_cksum(mh, IPPROTO_ICMPV6,
				    sizeof(struct ip6_hdr), MLD_MINLEN);

	/* construct multicast option */
	bzero(&im6o, sizeof(im6o));
	im6o.im6o_multicast_ifp = ifp;
	im6o.im6o_multicast_hlim = 1;

	/*
	 * Request loopback of the report if we are acting as a multicast
	 * router, so that the process-level routing daemon can hear it.
	 */
	im6o.im6o_multicast_loop = (ip6_mrouter != NULL);

	/* increment output statictics */
	icmp6stat.icp6s_outhist[type]++;
	icmp6_ifstat_inc(ifp, ifs6_out_msg);
	switch (type) {
	case MLD_LISTENER_QUERY:
		icmp6_ifstat_inc(ifp, ifs6_out_mldquery);
		break;
	case MLD_LISTENER_REPORT:
		icmp6_ifstat_inc(ifp, ifs6_out_mldreport);
		break;
	case MLD_LISTENER_DONE:
		icmp6_ifstat_inc(ifp, ifs6_out_mlddone);
		break;
	}

	ip6_output(mh, &ip6_opts, NULL, ia ? 0 : IPV6_UNSPECSRC, 
	    &im6o, (struct socket *)NULL, NULL);
}

static struct mld_hdr *
mld_allocbuf(mh, len, in6m, type)
	struct mbuf **mh;
	int len;
	struct in6_multi *in6m;
	int type;
{
	struct mbuf *md;
	struct mld_hdr *mldh;
	struct ip6_hdr *ip6;

	/*
	 * Allocate mbufs to store ip6 header and MLD header.
	 * We allocate 2 mbufs and make chain in advance because
	 * it is more convenient when inserting the hop-by-hop option later.
	 */
	MGETHDR(*mh, M_DONTWAIT, MT_HEADER);
	if (*mh == NULL)
		return NULL;
	MGET(md, M_DONTWAIT, MT_DATA);

	/* Use cluster in case of MLDv2 */
	if (md && (len > MLD_MINLEN || type == MLDV2_LISTENER_REPORT)) {
		/* XXX: assumes len is less than 2K Byte */
		MCLGET(md, M_DONTWAIT);
		if ((md->m_flags & M_EXT) == 0) {
			m_free(md);
			md = NULL;
		}
	}
	if (md == NULL) {
		m_free(*mh);
		*mh = NULL;
		return NULL;
	}
	(*mh)->m_next = md;
	md->m_next = NULL;

	(*mh)->m_pkthdr.rcvif = NULL;
	(*mh)->m_pkthdr.len = sizeof(struct ip6_hdr) + len;
	(*mh)->m_len = sizeof(struct ip6_hdr);
	MH_ALIGN(*mh, sizeof(struct ip6_hdr));

	/* fill in the ip6 header */
	ip6 = mtod(*mh, struct ip6_hdr *);
	bzero(ip6, sizeof(*ip6));
	ip6->ip6_flow = 0;
	ip6->ip6_vfc &= ~IPV6_VERSION_MASK;
	ip6->ip6_vfc |= IPV6_VERSION;
	/* ip6_plen will be set later */
	ip6->ip6_nxt = IPPROTO_ICMPV6;
	/* ip6_hlim will be set by im6o.im6o_multicast_hlim */
	/* ip6_src/dst will be set by mld_sendpkt() or mld_sendbuf() */

	/* fill in the MLD header */
	md->m_len = len;
	mldh = mtod(md, struct mld_hdr *);
	bzero(mldh, len);
	mldh->mld_type = type;

	return mldh;
}

#ifdef MLDV2
void
mld_sendbuf(mh, ifp)
	struct mbuf *mh;
	struct ifnet *ifp;
{
	struct ip6_hdr *ip6;
	struct mld_report_hdr *mld_rhdr;
	struct mld_group_record_hdr *mld_ghdr;
	int len;
	u_int16_t i;
	struct ip6_moptions im6o;
	struct mbuf *m;
	struct in6_ifaddr *ia;

	if (mh == NULL) {
#ifdef MLDV2_DEBUG
		printf("mld_sendbuf: mbuf is NULL\n");
#endif
		return;
	}

	/*
	 * At first, find a link local address on the outgoing interface
	 * to use as the source address of the MLD packet.
	 * We do not reject tentative addresses for MLD report to deal with
	 * the case where we first join a link-local address.
	 */
	if ((ia = in6ifa_ifpforlinklocal(ifp, ignflags)) == NULL) {
#ifdef MLDV2_DEBUG
		printf("mld_sendbuf: cannot send pkt due to a lack of linklocal address at link#%d", ifp->if_index);
#endif
		return;
	}
	if (ia->ia6_flags & IN6_IFF_TENTATIVE)
		ia = NULL;

	/* fill src/dst here */
	ip6 = mtod(mh, struct ip6_hdr *);
	ip6->ip6_src = ia ? ia->ia_addr.sin6_addr : in6addr_any;
	ip6->ip6_dst = mld_all_v2routers_linklocal;

#ifdef __FreeBSD__ /* ifdef KAME ? */
	/* set packet addresses in a full sockaddr_in6 form */
	bzero(&src_sa, sizeof(struct sockaddr_in6));
	bzero(&dst_sa, sizeof(struct sockaddr_in6));
	src_sa.sin6_family = dst_sa.sin6_family = AF_INET6;
	src_sa.sin6_len = dst_sa.sin6_len = sizeof(struct sockaddr_in6);
	src_sa.sin6_addr = ip6->ip6_src;
	dst_sa.sin6_addr = ip6->ip6_dst;
	if (in6_addr2zoneid(ifp, &src_sa.sin6_addr, &src_sa.sin6_scope_id) ||
	    in6_addr2zoneid(ifp, &dst_sa.sin6_addr, &dst_sa.sin6_scope_id)) {
		/* XXX: impossible */
		m_free(*mh);
		*mh = NULL;
		return NULL;
	}
	if (!ip6_setpktaddrs(*mh, &src_sa, &dst_sa)) {
		m_free(*mh);
		*mh = NULL;
		return NULL;
	}
#endif /*  XXX How about other OSes? */

	/* Assumes IPv6 header and MLD data are separeted into different mbuf */
	m = mh->m_next;

	mld_rhdr = mtod(m, struct mld_report_hdr *);
	len = rhdrlen;
	for (i = 0; i < mld_rhdr->mld_grpnum; i++) {
		mld_ghdr = (struct mld_group_record_hdr *)
					((char *)mld_rhdr + len);
		len += ghdrlen + SOURCE_RECORD_LEN(mld_ghdr->numsrc);
		HTONS(mld_ghdr->numsrc);
	}
	HTONS(mld_rhdr->mld_grpnum);
	mld_rhdr->mld_cksum = 0;
	mld_rhdr->mld_cksum = in6_cksum(mh, IPPROTO_ICMPV6,
					sizeof(struct ip6_hdr), len);

	bzero(&im6o, sizeof(im6o));
	im6o.im6o_multicast_ifp = ifp;
	im6o.im6o_multicast_hlim = IPV6_DEFAULT_MULTICAST_HOPS;
	im6o.im6o_multicast_loop = (ip6_mrouter != NULL);

	icmp6_ifstat_inc(ifp, ifs6_out_mldreport);
#ifdef MLDV2_DEBUG
	printf("MLDv2 Report sent from %s.\n", ifp->if_xname);
#endif
	ip6_output(mh, &ip6_opts, NULL, ia ? 0 : IPV6_UNSPECSRC, &im6o,
		   (struct socket *)NULL, NULL);
}

/*
 * Timer adjustment on reception of an MLDv2 Query.
 */
int
mld_set_timer(ifp, rt6i, mldh, mldlen, query_type)
	struct ifnet *ifp;
	struct router6_info *rt6i;
	struct mldv2_hdr *mldh;
	u_int16_t mldlen;
	u_int8_t query_type;
{
	struct in6_multi *in6m;
	struct in6_ifaddr *ia;
	int timer;			/* Max-Resp-Timer */
	int timer_i = 0;		/* interface timer */
	int timer_g = 0;		/* group timer */
	u_int16_t maxdelay = ntohs(mldh->mld_maxdelay);
	int error;
	int s = splsoftnet();

	/*
	 * Parse QRV, QQI, and QRI timer values.
	 */
	if ((rt6i->rt6i_qrv = MLD_QRV(mldh->mld_rtval)) == 0 ||
	    rt6i->rt6i_qrv > 7)
		rt6i->rt6i_qrv = MLD_DEF_RV;
	if (mldh->mld_qqi > 0 && mldh->mld_qqi < 128)
		rt6i->rt6i_qqi = mldh->mld_qqi;
	else if (mldh->mld_qqi >= 128)
		rt6i->rt6i_qqi = ((MLD_QQIC_MANT(mldh->mld_qqi) | 0x10)
				 << (MLD_QQIC_EXP(mldh->mld_qqi) + 3));
	else
		rt6i->rt6i_qqi = MLD_DEF_QI;
	rt6i->rt6i_qri = maxdelay;
	if (rt6i->rt6i_qri >= rt6i->rt6i_qqi * MLD_TIMER_SCALE)
		rt6i->rt6i_qri = rt6i->rt6i_qqi - 1;
		/* XXX tentatively adjusted */
	else
		rt6i->rt6i_qri /= MLD_TIMER_SCALE;

	if ((maxdelay > 0) && (maxdelay < 32768))
		timer = maxdelay;
	else
		timer = (MLD_MRC_MANT(mldh->mld_maxdelay) | 0x1000)
			<< (MLD_MRC_EXP(mldh->mld_maxdelay) + 3);

	/*
	 * Set interface timer if the query is Generic Query.
	 * Get group timer if the query is not Generic Query.
	 */
	if (query_type == MLD_V2_GENERAL_QUERY) {
		timer_i = timer * PR_FASTHZ / MLD_TIMER_SCALE;
		if (timer_i == 0)
			timer_i = 1;
		timer_i = MLD_RANDOM_DELAY(timer_i);
		if (mld_interface_timers_are_running &&
		    (rt6i->rt6i_timer2 != 0) && (rt6i->rt6i_timer2 < timer_i))
			; /* don't need to update interface timer */
		else {
			rt6i->rt6i_timer2 = timer_i;
			mld_interface_timers_are_running = 1;
		}
	} else { /* G or SG query */
		timer_g = timer * PR_FASTHZ / MLD_TIMER_SCALE;
		if (timer_g == 0)
			timer_g = 1;
		timer_g = MLD_RANDOM_DELAY(timer_g);
	}

	IFP_TO_IA6(ifp, ia);
	if (ia == NULL) {
		splx(s);
		return 0; /* XXX just ignore? */
	}
	LIST_FOREACH(in6m, &ia->ia6_multiaddrs, in6m_entry) {
		if (!in6_is_mld_target(&in6m->in6m_addr))
			continue;

		if ((in6m->in6m_source->i6ms_grpjoin == 0) &&
		    (in6m->in6m_source->i6ms_mode == MCAST_INCLUDE) &&
		    (in6m->in6m_source->i6ms_cur->numsrc == 0))
			continue;

		if (query_type == MLD_V2_GENERAL_QUERY) {
			/* Any previously pending response to Group- or
			 * Group-and-Source-Specific Query is canceled, if
			 * pending group timer is not sooner than new
			 * interface timer. */
			if (!mld_group_timers_are_running)
				continue;
			if (in6m->in6m_timer <= rt6i->rt6i_timer2)
				continue;
			in6m->in6m_state = MLD_IDLELISTENER;
			in6m->in6m_timer = 0;
			in6_free_msf_source_list
				(in6m->in6m_source->i6ms_rec->head);
			in6m->in6m_source->i6ms_rec->numsrc = 0;
			continue;
		} else if (!IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr,
					       &mldh->mld_addr))
			continue;

		/*
		 * If interface timer is sooner than new group timer,
		 * just ignore this Query for this group address.
		 */
		if (mld_interface_timers_are_running &&
		    rt6i->rt6i_timer2 < timer_g) {
			in6m->in6m_state = MLD_IDLELISTENER;
			in6m->in6m_timer = 0;
			break;
		}

		/* Receive Group-Specific Query */
		if (query_type == MLD_V2_GROUP_QUERY) {
			/*
			 * Group-Source list is cleared and a single response
			 * is scheduled, and group timer is set the earliest
			 * of the remaining time for the pending report and
			 * the selected delay.
			 */
			if ((in6m->in6m_state != MLD_G_QUERY_PENDING_MEMBER) &&
			    (in6m->in6m_state != MLD_SG_QUERY_PENDING_MEMBER)) {
				mld_group_timers_are_running = 1;
				in6m->in6m_timer = timer_g;
			} else {
				in6_free_msf_source_list
					(in6m->in6m_source->i6ms_rec->head);
				in6m->in6m_source->i6ms_rec->numsrc = 0;
				in6m->in6m_timer = min(in6m->in6m_timer,
						       timer_g);
			}
			in6m->in6m_state = MLD_G_QUERY_PENDING_MEMBER;
			break;
		}

		/* Receive Group-and-Source-Specific Query */
		if (in6m->in6m_state == MLD_G_QUERY_PENDING_MEMBER) {
			/*
			 * If there is a pending response for this group's
			 * Group-Specific Query, then queried sources are not
			 * recorded and pending status is not changed. Only the
			 * timer may be changed.
			 */
			in6m->in6m_timer = min(in6m->in6m_timer, timer_g);
			break;
		}
		/* Queried sources are augmented. */
		if ((error = mld_record_queried_source(in6m, mldh, mldlen)) > 0) {
			; /* XXX what kinds of statistics? */
			splx(s);
			return error;
		} else if (error == 0) {
			if (in6m->in6m_timer != 0)
				in6m->in6m_timer = min(in6m->in6m_timer,
						       timer_g);
			else {
				mld_group_timers_are_running = 1;
				in6m->in6m_timer = timer_g;
			}
			in6m->in6m_state = MLD_SG_QUERY_PENDING_MEMBER;
		}
		break;
	} /* for */

	splx(s);
	return 0;
}

/*
 * Set MLD Host Compatibility Mode.
 */
void
mld_set_hostcompat(ifp, rt6i, query_type)
	struct ifnet *ifp;
	struct router6_info *rt6i;
	u_int8_t query_type;
{
	int s = splsoftnet();

	/*
	 * Keep Older Version Querier Present timer.
	 */
	if (query_type == MLD_V1_QUERY) {
		rt6i->rt6i_timer1 = rt6i->rt6i_qrv * rt6i->rt6i_qqi
				    + rt6i->rt6i_qri;
		rt6i->rt6i_timer1 *= PR_SLOWHZ;
	}

	/*
	 * Check/set host compatibility mode. Whenever a host changes
	 * its compatability mode, cancel all its pending response and
	 * retransmission timers.
	 */
	if (rt6i->rt6i_timer1 > 0) {
		if (rt6i->rt6i_type != MLD_V1_ROUTER) {
			rt6i->rt6i_type = MLD_V1_ROUTER;
			mld_cancel_pending_response(ifp, rt6i);
		}
	}

	splx(s);
}

/*
 * Parse source addresses from MLDv2 Group-and-Source-Specific Query message
 * and merge them in a recorded source list.
 * If no pending source was recorded, return -1.
 * If some source was recorded as a reply for Group-and-Source-Specific Query,
 * return 0.
 */
int
mld_record_queried_source(in6m, mldh, mldlen)
	struct in6_multi *in6m;
	struct mldv2_hdr *mldh;
	u_int16_t mldlen;
{
	struct in6_addr_source *curias;
	u_int16_t numsrc, i;
	int ref_count;
	struct sockaddr_in6 src;
	int recorded = 0;

	mldlen -= qhdrlen; /* remaining source list */
	numsrc = ntohs(mldh->mld_numsrc);
	if (numsrc != mldlen / addrlen)
	    return EOPNOTSUPP; /* XXX */

	bzero(&src, sizeof(src));
	src.sin6_family = AF_INET6;
	src.sin6_len = sizeof(src);
	for (i = 0; i < numsrc && mldlen >= addrlen; i++, mldlen -= addrlen) {
		bcopy(&mldh->mld_src[i], &src, sizeof(mldh->mld_src[i]));
		if (in6m->in6m_source->i6ms_grpjoin > 0) {
			if ((ref_count = in6_merge_msf_source_addr
						(in6m->in6m_source->i6ms_rec,
						 &src, IMS_ADD_SOURCE)) < 0) {
			in6_free_msf_source_list
				(in6m->in6m_source->i6ms_rec->head);
			in6m->in6m_source->i6ms_rec->numsrc = 0;
			return ENOBUFS;
			}
			if (ref_count == 1)
				++in6m->in6m_source->i6ms_rec->numsrc;
			recorded = 1;
			continue;
		}

		LIST_FOREACH(curias, in6m->in6m_source->i6ms_cur->head,
			     i6as_list) {
			if (SS_COMPARE(&curias->i6as_addr, <, &src))
				continue;
			if (SS_COMPARE(&curias->i6as_addr, ==, &src)) {
				if (in6m->in6m_source->i6ms_mode
							!= MCAST_INCLUDE)
					break;
				ref_count = in6_merge_msf_source_addr
						(in6m->in6m_source->i6ms_rec,
						 &src, IMS_ADD_SOURCE);
				if (ref_count < 0) {
					in6_free_msf_source_list
					    (in6m->in6m_source->i6ms_rec->head);
					in6m->in6m_source->i6ms_rec->numsrc = 0;
					return ENOBUFS;
				}
				if (ref_count == 1)
					++in6m->in6m_source->i6ms_rec->numsrc;
				recorded = 1;
				break;
			}

			/* curias->ias_addr > src */
			if (in6m->in6m_source->i6ms_mode == MCAST_EXCLUDE) {
				ref_count = in6_merge_msf_source_addr
						(in6m->in6m_source->i6ms_rec,
						 &src, IMS_ADD_SOURCE);
				if (ref_count < 0) {
					in6_free_msf_source_list
					    (in6m->in6m_source->i6ms_rec->head);
					in6m->in6m_source->i6ms_rec->numsrc = 0;
					return ENOBUFS;
				}
				if (ref_count == 1)
					++in6m->in6m_source->i6ms_rec->numsrc;
				recorded = 1;
			}

			break;
		}

		if (!curias) {
			if (in6m->in6m_source->i6ms_mode == MCAST_EXCLUDE) {
				ref_count = in6_merge_msf_source_addr
						(in6m->in6m_source->i6ms_rec,
						 &src, IMS_ADD_SOURCE);
			if (ref_count < 0) {
				in6_free_msf_source_list
					(in6m->in6m_source->i6ms_rec->head);
				in6m->in6m_source->i6ms_rec->numsrc = 0;
				return ENOBUFS;
			}
			if (ref_count == 1)
				++in6m->in6m_source->i6ms_rec->numsrc;
			recorded = 1;
			}
		}
	}

	if (i != numsrc) {
		in6_free_msf_source_list(in6m->in6m_source->i6ms_rec->head);
		in6m->in6m_source->i6ms_rec->numsrc = 0;
		return EOPNOTSUPP; /* XXX */
	}

	return ((recorded == 0) ? -1 : 0);
}

/*
 * Send Current-State Report for General Query response.
 */
void
mld_send_all_current_state_report(ifp)
	struct ifnet *ifp;
{
	struct mbuf *m = NULL;
	int buflen = 0;
	struct in6_multi *in6m;
	struct in6_ifaddr *ia;

	IFP_TO_IA6(ifp, ia);
	if (ia == NULL)
	    return; /* XXX */
	LIST_FOREACH(in6m, &ia->ia6_multiaddrs, in6m_entry) {
		if (!in6_is_mld_target(&in6m->in6m_addr))
			continue;

		if (mld_send_current_state_report(&m, &buflen, in6m) != 0)
			return;
	}
	if (m != NULL)
		mld_sendbuf(m, ifp);
}

/*
 * Send Current-State Report for Group- and Group-and-Source-Sepcific Query
 * response.
 */
int
mld_send_current_state_report(m0, buflenp, in6m)
	struct mbuf **m0;	/* mbuf is inherited to put multiple group
				 * records in one message */
	int *buflenp;
	struct in6_multi *in6m;
{
	struct mbuf *m = *m0;
	u_int16_t max_len;
	u_int16_t numsrc, src_once, src_done = 0;
	u_int8_t type = 0;

	if (!in6_is_mld_target(&in6m->in6m_addr) ||
	    (in6m->in6m_ifp->if_flags & IFF_LOOPBACK) != 0)
		return 0;

	/* MCLBYTES is the maximum length even if if_mtu is too big. */
	max_len = (in6m->in6m_ifp->if_mtu < MCLBYTES) ?
				in6m->in6m_ifp->if_mtu : MCLBYTES;

	if (in6m->in6m_source->i6ms_mode == MCAST_INCLUDE)
		type = MODE_IS_INCLUDE;
	else if (in6m->in6m_source->i6ms_mode == MCAST_EXCLUDE)
		type = MODE_IS_EXCLUDE;

	/*
	 * Prepare record for General, Group-Specific, and Group-and-Source-
	 * Specific Query.
	 */
	if (in6m->in6m_state == MLD_SG_QUERY_PENDING_MEMBER) {
		type = MODE_IS_INCLUDE; /* always */
		numsrc = in6m->in6m_source->i6ms_rec->numsrc;
	} else
		numsrc = in6m->in6m_source->i6ms_cur->numsrc;

	if (type == MODE_IS_INCLUDE && numsrc == 0)
		return 0; /* no need to send Current-State Report */

	/*
	 * If Report type is MODE_IS_EXCLUDE, a single Group Record is sent,
	 * containing as many source addresses as can fit, and the remaining
	 * source addresses are not reported.
	 */
	if (type == MODE_IS_EXCLUDE) {
		if (max_len < SOURCE_RECORD_LEN(numsrc)
				+ sizeof(struct ip6_hdr) + rhdrlen + ghdrlen)
			numsrc = (max_len - sizeof(struct ip6_hdr)
				 - rhdrlen - ghdrlen) / addrlen;
	}

	if (m == NULL) {
		if (mld_allocbuf(m0, rhdrlen, in6m, MLDV2_LISTENER_REPORT)
				== NULL) {
#ifdef MLDV2_DEBUG
			printf("mld_send_current_state_report: error preparing new report header 1.\n");
#endif
			return EOPNOTSUPP; /* XXX */
		}
		m = *m0;
		*buflenp = 0;
	} else {
		if (ghdrlen + SOURCE_RECORD_LEN(numsrc) > MCLBYTES - *buflenp) {
			/*
			 * When remaining buffer is not enough to insert new
			 * group record, send current buffer and create a new
			 * buffer for this record.
			 */
			mld_sendbuf(m, in6m->in6m_ifp);
			m = NULL;
			if (mld_allocbuf(m0, rhdrlen, in6m,
					 MLDV2_LISTENER_REPORT) == NULL) {
#ifdef MLDV2_DEBUG
				printf("mld_send_current_state_report: error preparing new report header 2.\n");
#endif
				return EOPNOTSUPP; /* XXX */
			}
			m = *m0;
			*buflenp = 0;
		}
	} /* m == NULL */

	if (type == MODE_IS_EXCLUDE) {
		/*
		 * The number of sources of MODE_IS_EXCLUDE record is already
		 * adjusted to fit in one buffer.
		 */
		if (mld_create_group_record(m, buflenp, in6m, numsrc,
					    &src_done, type) != numsrc) {
#ifdef MLDV2_DEBUG
			printf("mld_send_current_state_report: error of sending MODE_IS_EXCLUDE report?\n");
#endif
			m_freem(m);
			return EOPNOTSUPP; /* XXX source address insert didn't
					    * finished. strange... */
		}
	} else {
		while (1) {
			/* XXX Some security implication? */
			src_once = mld_create_group_record(m, buflenp, in6m,
							   numsrc, &src_done,
							   type);
			if (numsrc > src_done) {
				/*
				 * Source address insert didn't finished, so,
				 * send this MLD report here and try to make
				 * separate message with remaining sources.
				 */
				mld_sendbuf(m, in6m->in6m_ifp);
				m = NULL;
				if (mld_allocbuf(m0, rhdrlen, in6m,
						 MLDV2_LISTENER_REPORT)
						== NULL) {
#ifdef MLDV2_DEBUG
					printf("mld_send_current_state_report: error preparing additional report header.\n");
#endif
					return EOPNOTSUPP; /* XXX */
				}
				m = *m0;
				*buflenp = 0;
			} else { /* finish insertion */
				break;
			}
		} /* while */
	}

	/*
	 * If pending query was Group-and-Source-Specific Query, pending
	 * (merged) source list is cleared.
	 */
	if (in6m->in6m_state == MLD_SG_QUERY_PENDING_MEMBER) {
		in6_free_msf_source_list(in6m->in6m_source->i6ms_rec->head);
		in6m->in6m_source->i6ms_rec->numsrc = 0;
	}
	in6m->in6m_state = MLD_IDLELISTENER;
	in6m->in6m_timer = 0;

	return 0;
}

/*
 * Send State-Change Report (Filter-Mode-Change Report and Source-List-Change
 * Report).
 * If report type is not specified (i.e., "0"), it indicates that requested
 * State-Change report may consist of Source-List-Change record. However, if
 * there is also a pending Filter-Mode-Change report for the group, Source-
 * List-Change report is not sent and kept as a scheduled report.
 */
void
mld_send_state_change_report(m0, buflenp, in6m, type, timer_init)
	struct mbuf **m0;
	int *buflenp;
	struct in6_multi *in6m;
	u_int8_t type;
	int timer_init;		/* set this when IPMulticastListen() invoked */
{
	struct mbuf *m = *m0;
	u_int16_t max_len;
	u_int16_t numsrc, src_once, src_done = 0;

	if (!in6_is_mld_target(&in6m->in6m_addr) ||
	    (in6m->in6m_ifp->if_flags & IFF_LOOPBACK) != 0)
		return;

	/*
	 * If there is pending Filter-Mode-Change report, Source-List-Change
	 * report will be merged in an another message and scheduled to be
	 * sent after Filter-Mode-Change report is sent.
	 */
	if (in6m->in6m_source->i6ms_toex != NULL) {
		/* Initial TO_EX request must specify "type". */
		if (type == 0) {
			if (timer_init &&
			    ((in6m->in6m_source->i6ms_alw != NULL &&
			      in6m->in6m_source->i6ms_alw->numsrc > 0) ||
			     (in6m->in6m_source->i6ms_blk != NULL &&
			      in6m->in6m_source->i6ms_blk->numsrc > 0)))
				return; /* scheduled later */
			type = CHANGE_TO_EXCLUDE_MODE;
		}
	}
	if (in6m->in6m_source->i6ms_toin != NULL) {
		/* Initial TO_IN request must specify "type". */
		if (type == 0) {
			if (timer_init &&
			    ((in6m->in6m_source->i6ms_alw != NULL &&
			      in6m->in6m_source->i6ms_alw->numsrc > 0) ||
			     (in6m->in6m_source->i6ms_blk != NULL &&
			      in6m->in6m_source->i6ms_blk->numsrc > 0)))
				return; /* scheduled later */

			type = CHANGE_TO_INCLUDE_MODE;
		}
	}
	if (timer_init) {
		in6m->in6m_source->i6ms_robvar = in6m->in6m_rti->rt6i_qrv;
		in6m->in6m_source->i6ms_timer
			= MLD_RANDOM_DELAY(MLDV2_UNSOL_INTVL * PR_FASTHZ);
	} else if (!(in6m->in6m_source->i6ms_robvar > 0))
		return;

	/* MCLBYTES is the maximum length even if if_mtu is too big. */
	max_len = (in6m->in6m_ifp->if_mtu < MCLBYTES) ?
				in6m->in6m_ifp->if_mtu : MCLBYTES;

	/*
	 * If Report type is CHANGE_TO_EXCLUDE_MODE, a single Group Record
	 * is sent, containing as many source addresses as can fit, and the
	 * remaining source addresses are not reported.
	 * Note that numsrc may or may not be 0.
	 */
	if (type == CHANGE_TO_EXCLUDE_MODE) {
		numsrc = in6m->in6m_source->i6ms_toex->numsrc;
		if (max_len < SOURCE_RECORD_LEN(numsrc)
				+ sizeof(struct ip6_hdr) + rhdrlen + ghdrlen)
			/* toex's numsrc should be fit in a single message. */
			numsrc = (max_len - sizeof(struct ip6_hdr)
				 - rhdrlen - ghdrlen) / addrlen;
	} else if (type == CHANGE_TO_INCLUDE_MODE) {
		numsrc = in6m->in6m_source->i6ms_toin->numsrc;
	} else { /* ALLOW_NEW_SOURCES and/or BLOCK_OLD_SOURCES */
		numsrc = 0;
		if (in6m->in6m_source->i6ms_alw != NULL)
			numsrc = in6m->in6m_source->i6ms_alw->numsrc;
		if (in6m->in6m_source->i6ms_blk != NULL)
			numsrc = max(numsrc,
				     in6m->in6m_source->i6ms_blk->numsrc);
		if (numsrc == 0) {
			/*
			 * XXX following is tentative process. this should not
			 * be executed. this is just to avoid "loop" by timer.
			 */
			if (*m0 != NULL) {
				mld_sendbuf(*m0, in6m->in6m_ifp);
				*m0 = NULL;
			} else if (in6m->in6m_source->i6ms_robvar > 0)
				--in6m->in6m_source->i6ms_robvar;
			return;
		}
	}

	if (m == NULL) {
		if (mld_allocbuf(m0, rhdrlen, in6m, MLDV2_LISTENER_REPORT)
				== NULL) {
#ifdef MLDV2_DEBUG
			printf("mld_send_state_change_report: error preparing new report header.\n");
#endif
			return; /* robvar is not reduced */
		}
		m = *m0;
		*buflenp = 0;
	} else {
		if (ghdrlen + SOURCE_RECORD_LEN(numsrc)
		    > MCLBYTES - *buflenp) {
			/*
			 * When remaining buffer is not enough to insert new
			 * group record, send current buffer and create a new
			 * buffer for this record.
			 */
			mld_sendbuf(m, in6m->in6m_ifp);
			m = NULL;
			if (mld_allocbuf(m0, rhdrlen, in6m,
					 MLDV2_LISTENER_REPORT) == NULL) {
#ifdef MLDV2_DEBUG
				printf("mld_send_state_change_report: error preparing new report header.\n");
#endif
				return;
			}
			m = *m0;
			*buflenp = 0;
		}
	}

	if (type == CHANGE_TO_EXCLUDE_MODE) {
		/*
		 * The number of sources of CHANGE_TO_EXCLUDE_MODE record is
		 * already adjusted to fit in one buffer.
		 */
		if (mld_create_group_record(m, buflenp, in6m, numsrc,
					    &src_done, type) != numsrc) {
#ifdef MLDV2_DEBUG
			printf("mld_send_state_change_report: error of sending CHANGE_TO_EXCLUDE_MODE report?\n");
#endif
			m_freem(m);
			return; /* XXX source address insert didn't finished.
				 * strange... robvar is not reduced */
		}
		if (timer_init) {
			mld_state_change_timers_are_running = 1;
			mld_sendbuf(m, in6m->in6m_ifp);
		}

		if (--in6m->in6m_source->i6ms_robvar != 0)
			return;

		if (in6m->in6m_source->i6ms_toex != NULL) {
			/* For TO_EX list, it MUST be deleted after
			 * retransmission is done. This is because
			 * mld_fasttimo() doesn't know if the pending TO_EX
			 * report exists or not. */
			in6_free_msf_source_list
					(in6m->in6m_source->i6ms_toex->head);
			FREE(in6m->in6m_source->i6ms_toex->head, M_MSFILTER);
			FREE(in6m->in6m_source->i6ms_toex, M_MSFILTER);
			in6m->in6m_source->i6ms_toex = NULL;
		}
		/* Prepare scheduled Source-List-Change Report */
		if ((in6m->in6m_source->i6ms_alw != NULL &&
				in6m->in6m_source->i6ms_alw->numsrc > 0) ||
		    (in6m->in6m_source->i6ms_blk != NULL &&
				in6m->in6m_source->i6ms_blk->numsrc > 0)) {
			mld_state_change_timers_are_running = 1;
			in6m->in6m_source->i6ms_robvar
			    = in6m->in6m_rti->rt6i_qrv;
			in6m->in6m_source->i6ms_timer
			    = MLD_RANDOM_DELAY(MLDV2_UNSOL_INTVL * PR_FASTHZ);
		} else
			in6m->in6m_source->i6ms_timer = 0;

		return;
	}

	if (type == CHANGE_TO_INCLUDE_MODE) {
		while (1) {
			/* XXX Some security implication? */
			src_once = mld_create_group_record
						(m, buflenp, in6m, numsrc,
						 &src_done, type);
			if (numsrc > src_done) {
				mld_sendbuf(m, in6m->in6m_ifp);
				m = NULL;
				if (mld_allocbuf(m0, rhdrlen, in6m,
						 MLDV2_LISTENER_REPORT)
						== NULL) {
#ifdef MLDV2_DEBUG
					printf("mld_send_state_change_report: error preparing additional report header.\n");
#endif
					return;
				}
				m = *m0;
				*buflenp = 0;
			} else { /* finish insertion */
				break;
			}
		}
		if (timer_init) {
			mld_state_change_timers_are_running = 1;
			mld_sendbuf(m, in6m->in6m_ifp);
		}

		if (--in6m->in6m_source->i6ms_robvar != 0)
			return;

		if (in6m->in6m_source->i6ms_toin != NULL) {
			/* For TO_IN list, it MUST be deleted after
			 * retransmission is done. This is because
			 * mld_fasttimo() doesn't know if the pending TO_IN
			 * report exists or not. */
			in6_free_msf_source_list
					(in6m->in6m_source->i6ms_toin->head);
			FREE(in6m->in6m_source->i6ms_toin->head, M_MSFILTER);
			FREE(in6m->in6m_source->i6ms_toin, M_MSFILTER);
			in6m->in6m_source->i6ms_toin = NULL;
		}
		/* Prepare scheduled Source-List-Change Report */
		if ((in6m->in6m_source->i6ms_alw != NULL &&
				in6m->in6m_source->i6ms_alw->numsrc > 0) ||
		    (in6m->in6m_source->i6ms_blk != NULL &&
				in6m->in6m_source->i6ms_blk->numsrc > 0)) {
			mld_state_change_timers_are_running = 1;
			in6m->in6m_source->i6ms_robvar
			    = in6m->in6m_rti->rt6i_qrv;
			in6m->in6m_source->i6ms_timer
			    = MLD_RANDOM_DELAY(MLDV2_UNSOL_INTVL * PR_FASTHZ);
		} else
			in6m->in6m_source->i6ms_timer = 0;

		return;
	}

	/* ALLOW_NEW_SOURCES and/or BLOCK_OLD_SOURCES */
	if ((in6m->in6m_source->i6ms_alw != NULL) &&
			(in6m->in6m_source->i6ms_alw->numsrc != 0))
		type = ALLOW_NEW_SOURCES;
	else if ((in6m->in6m_source->i6ms_blk != NULL) &&
			(in6m->in6m_source->i6ms_blk->numsrc != 0))
		type = BLOCK_OLD_SOURCES;
	else {
		return;
	}

	while (1) {
		/* XXX Some security implication? */
		if (type == ALLOW_NEW_SOURCES)
			numsrc = in6m->in6m_source->i6ms_alw->numsrc;
		else if (type == BLOCK_OLD_SOURCES)
			numsrc = in6m->in6m_source->i6ms_blk->numsrc;
		else /* finish group record insertion */
			break;
		src_once = mld_create_group_record
					(m, buflenp, in6m, numsrc,
					 &src_done, type);
		if (numsrc > src_done) {
			mld_sendbuf(m, in6m->in6m_ifp);
			m = NULL;
			if (mld_allocbuf(m0, rhdrlen, in6m,
					 MLDV2_LISTENER_REPORT) == NULL) {
#ifdef MLDV2_DEBUG
				printf("mld_send_state_change_report: error preparing additional report header.\n");
#endif
				return;
			}
			m = *m0;
			*buflenp = 0;
		} else { /* next group record */
			if ((type == ALLOW_NEW_SOURCES) &&
			    (in6m->in6m_source->i6ms_blk != NULL) &&
			    (in6m->in6m_source->i6ms_blk->numsrc != 0))
				type = BLOCK_OLD_SOURCES;
			else
				type = 0;
			src_done = 0;
		}
	}
	if (timer_init) {
		mld_state_change_timers_are_running = 1;
		mld_sendbuf(m, in6m->in6m_ifp);
	}

	if (--in6m->in6m_source->i6ms_robvar == 0) {
		if ((in6m->in6m_source->i6ms_alw != NULL) &&
		    (in6m->in6m_source->i6ms_alw->numsrc != 0)) {
			in6_free_msf_source_list
					(in6m->in6m_source->i6ms_alw->head);
			in6m->in6m_source->i6ms_alw->numsrc = 0;
		}
		if ((in6m->in6m_source->i6ms_blk != NULL) &&
		    (in6m->in6m_source->i6ms_blk->numsrc != 0)) {
			in6_free_msf_source_list
					(in6m->in6m_source->i6ms_blk->head);
			in6m->in6m_source->i6ms_blk->numsrc = 0;
		}
		in6m->in6m_source->i6ms_timer = 0;
	}

	return;
}

int
mld_create_group_record(mh, buflenp, in6m, numsrc, done, type)
	struct mbuf *mh;
	int *buflenp;
	struct in6_multi *in6m;
	u_int16_t numsrc;
	u_int16_t *done;
	u_int8_t type;
{
	/* Assumes IPv6 header and MLD data are separeted into different mbuf */
	struct mbuf *m = mh->m_next;
	struct ip6_hdr *ip6;
	struct mld_report_hdr *mld_rhdr;
	struct mld_group_record_hdr *mld_ghdr;
	struct in6_addr_source *ias;
	struct in6_addr_slist *iasl;
	u_int16_t i, total;
	int mfreelen;

	ip6 = mtod(mh, struct ip6_hdr *);
	mld_rhdr = mtod(m, struct mld_report_hdr *);
	++mld_rhdr->mld_grpnum;

	mld_ghdr = (struct mld_group_record_hdr *)
			((char *)(mld_rhdr + 1) + *buflenp);
	mld_ghdr->record_type = type;
	mld_ghdr->auxlen = 0;
	mld_ghdr->numsrc = 0;
	bcopy(&in6m->in6m_addr, &mld_ghdr->group, sizeof(mld_ghdr->group));
	if (IN6_IS_ADDR_MC_LINKLOCAL(&mld_ghdr->group))
		mld_ghdr->group.s6_addr16[1] = 0; /* XXX */
	*buflenp += ghdrlen;
	m->m_len += ghdrlen;
	mh->m_pkthdr.len += ghdrlen;
	mfreelen = MCLBYTES - *buflenp;

	GET_REPORT_SOURCE_HEAD(in6m, type, iasl);
	total = 0;
	i = 0;
	if (iasl != NULL) {
		for (ias = LIST_FIRST(iasl->head); total < *done;
				total++, ias = LIST_NEXT(ias, i6as_list))
			; /* adjust a source pointer. */
		/* Insert source address to mbuf */
		for (; i < numsrc && ias != NULL && mfreelen > addrlen;
				i++, total++, mfreelen -= addrlen,
				ias = LIST_NEXT(ias, i6as_list))
			bcopy(&ias->i6as_addr.sin6_addr,
			      &mld_ghdr->src[i], ias->i6as_addr.sin6_len);
	}

	*done = total;
	mld_ghdr->numsrc = i;
	*buflenp += SOURCE_RECORD_LEN(i);
	m->m_len += SOURCE_RECORD_LEN(i);
	mh->m_pkthdr.len += SOURCE_RECORD_LEN(i);

	return i;
}

/*
 * Cancel all MLDv2 pending responce and retransmission timers on an interface.
 */
void
mld_cancel_pending_response(ifp, rt6i)
	struct ifnet *ifp;
	struct router6_info *rt6i;
{
	struct in6_multi *in6m;
	struct in6_multistep step;

	rt6i->rt6i_timer2 = 0;
	IN6_FIRST_MULTI(step, in6m);
	while (in6m != NULL) {
	    if (in6m->in6m_ifp != ifp)
		goto next_multi;
	    if (!in6_is_mld_target(&in6m->in6m_addr))
		goto next_multi;
	    if (in6m->in6m_source == NULL)
		goto next_multi;

	    in6m->in6m_source->i6ms_robvar = 0;
	    in6m->in6m_source->i6ms_timer = 0;
	    in6_free_msf_source_list(in6m->in6m_source->i6ms_rec->head);
	    in6m->in6m_source->i6ms_rec->numsrc = 0;
	    if (in6m->in6m_source->i6ms_alw != NULL) {
		in6_free_msf_source_list(in6m->in6m_source->i6ms_alw->head);
		in6m->in6m_source->i6ms_alw->numsrc = 0;
	    }
	    if (in6m->in6m_source->i6ms_blk != NULL) {
		in6_free_msf_source_list(in6m->in6m_source->i6ms_blk->head);
		in6m->in6m_source->i6ms_blk->numsrc = 0;
	    }
	    if (in6m->in6m_source->i6ms_toin != NULL) {
		in6_free_msf_source_list(in6m->in6m_source->i6ms_toin->head);
		/* For TO_IN list, it MUST be deleted. */
		FREE(in6m->in6m_source->i6ms_toin->head, M_MSFILTER);
		FREE(in6m->in6m_source->i6ms_toin, M_MSFILTER);
		in6m->in6m_source->i6ms_toin = NULL;
	    }
	    if (in6m->in6m_source->i6ms_toex != NULL) {
		in6_free_msf_source_list(in6m->in6m_source->i6ms_toex->head);
		/* For TO_EX list, it MUST be deleted. */
		FREE(in6m->in6m_source->i6ms_toex->head, M_MSFILTER);
		FREE(in6m->in6m_source->i6ms_toex, M_MSFILTER);
		in6m->in6m_source->i6ms_toex = NULL;
	    }

next_multi:
	    IN6_NEXT_MULTI(step, in6m);
	}
}
#endif /* MLDV2 */
