/*	$NetBSD: igmp.c,v 1.45 2007/04/25 00:11:18 dyoung Exp $	*/

/*
 * Copyright (c) 2002 INRIA. All rights reserved.
 *
 * Implementation of Internet Group Management Protocol, Version 3.
 * Developed by Hitoshi Asaeda, INRIA, February 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) 1995, 1996, 1997, and 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.
 */

/*
 * Internet Group Management Protocol (IGMP) routines.
 *
 * Written by Steve Deering, Stanford, May 1988.
 * Modified by Rosen Sharma, Stanford, Aug 1994.
 * Modified by Bill Fenner, Xerox PARC, Feb 1995.
 *
 * MULTICAST Revision: 1.3
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: igmp.c,v 1.45 2007/04/25 00:11:18 dyoung Exp $");

#include "opt_mrouting.h"

#include <sys/param.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/systm.h>
#include <sys/sysctl.h>

#include <net/if.h>
#include <net/route.h>

#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/igmp_var.h>

#include <machine/stdarg.h>

#define IP_MULTICASTOPTS	0

POOL_INIT(igmp_rti_pool, sizeof(struct router_info), 0, 0, 0, "igmppl", NULL,
    IPL_SOFTNET);
struct igmpstat igmpstat;
struct rti_head rti_head = LIST_HEAD_INITIALIZER(rti_head);
int igmp_timers_are_running;
int interface_timers_are_running;
int state_change_timers_are_running;
int igmpsendwithra = 1;		/* send packets with Router Alert option */
int igmpdropwithnora = 0;	/* accept packets with no Router Alert option */
int igmpmaxsrcfilter = IP_MAX_SOURCE_FILTER;
int igmpsomaxsrc = SO_MAX_SOURCE_FILTER;
int igmpalways_v3 = 0;
static struct mbuf *router_alert;
static int qhdrlen = IGMP_v3_QUERY_MINLEN;
static int rhdrlen = IGMP_MINLEN;
static int ghdrlen = IGMP_MINLEN;
static int addrlen = sizeof(struct in_addr);

#define	SOURCE_RECORD_LEN(numsrc)	(numsrc * (sizeof(u_int32_t)))

#define	SET_REPORTHDR(m, numsrc) do {					\
	MGETHDR((m), M_DONTWAIT, MT_HEADER);				\
	if ((m) != NULL &&						\
		MHLEN - max_linkhdr < sizeof(struct ip)			\
				      + rhdrlen + ghdrlen		\
				      + SOURCE_RECORD_LEN((numsrc))) {	\
		MCLGET((m), M_DONTWAIT);				\
		if (((m)->m_flags & M_EXT) == 0) {			\
			m_freem((m));					\
			error = ENOBUFS;				\
			break;						\
		}							\
	}								\
	if ((m) == NULL) {						\
		error = ENOBUFS;					\
		break;							\
	}								\
	(m)->m_data += max_linkhdr;					\
	ip = mtod((m), struct ip *);					\
	buflen = sizeof(struct ip);					\
	ip->ip_len = htons(sizeof(struct ip) + rhdrlen);		\
	ip->ip_tos = 0xc0;						\
	ip->ip_off = htons(0);						\
	ip->ip_p = IPPROTO_IGMP;					\
	ip->ip_src = zeroin_addr;					\
	ip->ip_dst.s_addr = INADDR_NEW_ALLRTRS_GROUP;			\
	igmp_rhdr = (struct igmp_report_hdr *)((char *)ip + buflen);	\
	igmp_rhdr->igmp_type = IGMP_v3_HOST_MEMBERSHIP_REPORT;		\
	igmp_rhdr->igmp_reserved1 = 0;					\
	igmp_rhdr->igmp_reserved2 = 0;					\
	igmp_rhdr->igmp_grpnum = 0;					\
	buflen += rhdrlen;						\
	(m)->m_len = sizeof(struct ip) + rhdrlen;			\
	(m)->m_pkthdr.len = sizeof(struct ip) + rhdrlen;		\
	(m)->m_pkthdr.rcvif = (struct ifnet *)0;			\
	(m)->m_nextpkt = 0;						\
} while (0)

void igmp_sendpkt(struct in_multi *, int);
void igmp_set_timer(struct router_info *, struct igmp *, u_int8_t);
void igmp_set_hostcompat(struct router_info *, int);
int igmp_query_source(struct in_multi *, struct igmp *);
void igmp_send_all_curstate(struct ifnet *);
struct mbuf * igmp_send_curstate(struct mbuf *, struct in_multi *, int *);
void igmp_create_record(struct mbuf *, struct in_multi *, u_int8_t,
	struct in_addr_slist *, int *);
void igmp_cancel_report(struct router_info *);
static int rti_fill(struct in_multi *);
static struct router_info *rti_find(struct ifnet *);
static void rti_delete(struct ifnet *);

void
igmp_init(void)
{
	struct ipoption *ra;

	igmp_timers_are_running = 0;
	interface_timers_are_running = 0; /* used only by IGMPv3 */
	state_change_timers_are_running = 0; /* used only by IGMPv3 */

	/*
	 * Prepare Router Alert option to use in outgoing packets.
	 */
	MGET(router_alert, M_DONTWAIT, MT_DATA);
	ra = mtod(router_alert, struct ipoption *);
	ra->ipopt_dst.s_addr = 0;
	ra->ipopt_list[0] = IPOPT_RA;	/* Router Alert option */
	ra->ipopt_list[1] = 0x04;	/* 4 bytes long */
	ra->ipopt_list[2] = 0x00;
	ra->ipopt_list[3] = 0x00;
	router_alert->m_len = sizeof(ra->ipopt_dst) + ra->ipopt_list[1];
}

struct router_info *
rti_init(struct ifnet *ifp)
{
	struct router_info *rti;

	rti = pool_get(&igmp_rti_pool, PR_NOWAIT);
	if (rti == NULL)
		return NULL;

	rti->rti_ifp = ifp;
	rti->rti_timer1 = 0;
	rti->rti_timer2 = 0;
	rti->rti_timer3 = 0;
	rti->rti_qrv = IGMP_DEF_RV;
	rti->rti_qqi = IGMP_DEF_QI;
	rti->rti_qri = IGMP_DEF_QRI / IGMP_TIMER_SCALE;
	rti->rti_type = IGMP_v3_ROUTER;

	LIST_INSERT_HEAD(&rti_head, rti, rti_link);
	return (rti);
}

static int
rti_fill(struct in_multi *inm)
{
	struct router_info *rti;

	/* this function is called at splsoftnet() */
	LIST_FOREACH(rti, &rti_head, rti_link) {
		if (rti->rti_ifp == inm->inm_ifp) {
			inm->inm_rti = rti;
			if (rti->rti_type == IGMP_v1_ROUTER)
				return (IGMP_v1_HOST_MEMBERSHIP_REPORT);
			else
				return (IGMP_v2_HOST_MEMBERSHIP_REPORT);
		}
	}

	if ((rti = rti_init(inm->inm_ifp)) == NULL)
		return 0;
	else {
		/* v3 operations never call rti_fill() */
		rti->rti_type = IGMP_v2_ROUTER;
		inm->inm_rti = rti;
		return (IGMP_v2_HOST_MEMBERSHIP_REPORT);
	}
}

static struct router_info *
rti_find(struct ifnet *ifp)
{
	struct router_info *rti;
	int s = splsoftnet();

	LIST_FOREACH(rti, &rti_head, rti_link) {
		if (rti->rti_ifp == ifp) {
			splx(s);
			return (rti);
		}
	}

	if ((rti = rti_init(ifp)) == NULL) {
		splx(s);
		return NULL;
	} else {
		splx(s);
		return (rti);
	}
}

static void
rti_delete(struct ifnet *ifp)  /* MUST be called at splsoftnet */
{
	struct router_info *rti;

	LIST_FOREACH(rti, &rti_head, rti_link) {
		if (rti->rti_ifp == ifp) {
			LIST_REMOVE(rti, rti_link);
			pool_put(&igmp_rti_pool, rti);
			return;
		}
	}
}

/*
 * Check whether IGMP message carries Router Alert option.
 */
int
igmp_get_routeralert(struct mbuf *m)
{
	struct ip *ip = mtod(m, struct ip *);
	struct igmp *igmp;
	int iphlen;
	u_int minlen, optlen;
	u_int16_t ip_len;

	++igmpstat.igps_rcv_total;

	/*
	 * Validate IP Time-to-Live
	 */
	if (ip->ip_ttl != 1) {
		++igmpstat.igps_rcv_badttl;
		return -1;
	}

	/*
	 * Validate lengths
	 */
#ifdef _IP_VHL
	iphlen = IP_VHL_HL(ip->ip_vhl) << 2;
#else
	iphlen = ip->ip_hl << 2;
#endif
	minlen = iphlen + IGMP_MINLEN;
	ip_len = ntohs(ip->ip_len);
	if (ip_len < minlen) {
		++igmpstat.igps_rcv_tooshort;
		return -1;
	}

	if (((m->m_flags & M_EXT) && (ip->ip_src.s_addr & IN_CLASSA_NET) == 0)
				|| m->m_len < minlen) {
		if ((m = m_pullup(m, minlen)) == 0) {
			++igmpstat.igps_rcv_tooshort;
			return -1;
		}
		ip = mtod(m, struct ip *);
	}

	optlen = iphlen - sizeof(struct ip);
	igmp = (struct igmp *)((char *)ip + optlen);

	/*
	 * Check IGMP message type and its Router Alert requirement.
	 */
	switch (igmp->igmp_type) {
	case IGMP_HOST_MEMBERSHIP_QUERY:
		if (igmp->igmp_code == 0) /* IGMPv1 */
			return 0;
		if (igmpdropwithnora && (ip_check_router_alert(ip) != 0)) {
			++igmpstat.igps_rcv_nora;
			return -1;
		} else
			return 0;
	case IGMP_v1_HOST_MEMBERSHIP_REPORT:
		return 0;
	case IGMP_v2_HOST_MEMBERSHIP_REPORT:
	case IGMP_HOST_LEAVE_MESSAGE:
		if (igmpdropwithnora && (ip_check_router_alert(ip) != 0)) {
			++igmpstat.igps_rcv_nora;
			return -1;
		} else
			return 0;
	}
	return 0;
}

void
igmp_input(struct mbuf *m, ...)
{
	int proto;
	int iphlen, igmplen;
	struct ifnet *ifp = m->m_pkthdr.rcvif;
	struct ip *ip = mtod(m, struct ip *);
	struct igmp *igmp;
#ifdef m_pulldown
	struct mbuf *n;
	int *offp;
#endif
	int query_ver;
	struct in_multi *inm;
	struct in_multistep step;
	struct router_info *rti;
	struct in_ifaddr *ia;
	u_int timer;
	va_list ap;
	u_int16_t ip_len;

#if defined(MROUTING)
	extern struct socket *ip_mrouter;
#endif

	va_start(ap, m);
	iphlen = va_arg(ap, int);
	proto = va_arg(ap, int);
	va_end(ap);

	/*
	 * Check length and validate checksum
	 */
	ip_len = ntohs(ip->ip_len);
	if (ip_len > ifp->if_mtu) {
		++igmpstat.igps_rcv_toolong;
		m_freem(m);
		return;
	}
	igmplen = ip_len - iphlen;
#ifdef m_pulldown
	if ((n = m_pulldown(m, iphlen, igmplen, offp)) == NULL) {
		++igmpstat.igps_rcv_query_fails;
		m_freem(m);
		return;
	}

	igmp = (struct igmp *)(mtod(n, void *) + *offp);
	/* No need to assert alignment here. */
	if (in_cksum(n, igmplen)) {
		++igmpstat.igps_rcv_badsum;
		m_freem(m);
		return;
	}
#else
	m->m_data += iphlen;
	m->m_len -= iphlen;
	igmp = mtod(m, struct igmp *);
	/* No need to assert alignment here. */
	if (in_cksum(m, igmplen)) {
		++igmpstat.igps_rcv_badsum;
		m_freem(m);
		return;
	}
	m->m_data -= iphlen;
	m->m_len += iphlen;
#endif

	switch (igmp->igmp_type) {

	case IGMP_HOST_MEMBERSHIP_QUERY:
		/*
		 * IGMP version check.
		 * IGMPv1 Query: length = 8 octets AND Max-Resp-Code = 0
		 * IGMPv2 Query: length = 8 octets AND Max-Resp-Code != 0
		 * IGMPv3 Query: length >= 12 octets
		 * IGMPv1 and v2 implementation must accept only the first 8
		 * octets of the query message.
		 */
		if (igmplen == IGMP_MINLEN) {
			query_ver = IGMP_v2_QUERY; /* or IGMP_v1_QUERY */
		} else if (igmplen >= IGMP_v3_QUERY_MINLEN) {
			query_ver = IGMP_v3_QUERY;
		} else { /* igmplen > 8 && igmplen < 12 */
			++igmpstat.igps_rcv_badqueries; /* invalid query */
			m_freem(m);
			return;
		}

		if ((rti = rti_find(ifp)) == NULL) {
			++igmpstat.igps_rcv_query_fails;
			m_freem(m);
			return; /* no m_freem and break? */
		}

		if (ifp->if_flags & IFF_LOOPBACK)
			break;

		/*
		 * Note IGMPv1's igmp_code is *always* 0, and IGMPv2's
		 * igmp_code *must not* be 0. This means only correct v1 query
		 * must come through this "if" routine.
		 */
		if ((igmp->igmp_code == 0) && (query_ver != IGMP_v3_QUERY)) {

			query_ver = IGMP_v1_QUERY; /* overwrite */
			++igmpstat.igps_rcv_v1_queries;

			if (ip->ip_dst.s_addr != INADDR_ALLHOSTS_GROUP) {
				++igmpstat.igps_rcv_badqueries;
				m_freem(m);
				return;
			}

start_v1:
			/*
			 * Start the timers in all of our membership records
			 * for the interface on which the query arrived,
			 * except those that are already running and those
			 * that belong to a "local" group (224.0.0.X).
			 */
			IN_FIRST_MULTI(step, inm);
			while (inm != NULL) {
				if (inm->inm_ifp == ifp &&
				    inm->inm_timer == 0 &&
				    !IN_LOCAL_GROUP(inm->inm_addr.s_addr)) {
					inm->inm_state = IGMP_DELAYING_MEMBER;
					inm->inm_timer = IGMP_RANDOM_DELAY
						(IGMP_MAX_HOST_REPORT_DELAY *
						 PR_FASTHZ);
					igmp_timers_are_running = 1;
				}
				IN_NEXT_MULTI(step, inm);
			}
			if (igmpalways_v3 == 0)
				igmp_set_hostcompat(rti, query_ver);
		} else {
			/*
			 * Note if IGMPv2's igmp_code is 0, that message must
			 * not be correct query msg. This means correct v2
			 * query must come through this "else" routine.
			 */
			if (query_ver == IGMP_v2_QUERY) {
				++igmpstat.igps_rcv_v2_queries;
				if (!IN_MULTICAST(ip->ip_dst.s_addr)) {
					++igmpstat.igps_rcv_badqueries;
					m_freem(m);
					return;
				}
			}
		}

		/*
		 * Adjust timer for scheduling responses to IGMPv2 query.
		 */
		if (query_ver == IGMP_v2_QUERY) {
start_v2:
			/*
			 * Start the timers in all of our membership records
			 * for the interface on which the query arrived,
			 * except those that are already running and those
			 * that belong to a "local" group (224.0.0.X).  For
			 * timers already running, check if they need to be
			 * reset.
			 */
			timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
			if (timer == 0)
				timer = 1;
			IN_FIRST_MULTI(step, inm);
			while (inm != NULL) {
				if (inm->inm_ifp == ifp &&
				    !IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
				    (ip->ip_dst.s_addr == INADDR_ALLHOSTS_GROUP 
				    || in_hosteq(ip->ip_dst, inm->inm_addr)) &&
				    (in_hosteq(igmp->igmp_group, zeroin_addr)
				    || in_hosteq(igmp->igmp_group, inm->inm_addr))) {
					switch (inm->inm_state) {
					case IGMP_DELAYING_MEMBER:
						if (inm->inm_timer <= timer)
							break;
						/* FALLTHROUGH */
					case IGMP_IDLE_MEMBER:
					case IGMP_LAZY_MEMBER:
					case IGMP_AWAKENING_MEMBER:
					case IGMP_G_QUERY_PENDING_MEMBER:
					case IGMP_SG_QUERY_PENDING_MEMBER:
						inm->inm_state =
						    IGMP_DELAYING_MEMBER;
						inm->inm_timer =
						    IGMP_RANDOM_DELAY(timer);
						igmp_timers_are_running = 1;
						break;
					case IGMP_SLEEPING_MEMBER:
						inm->inm_state =
						    IGMP_AWAKENING_MEMBER;
						break;
					}
				}
				IN_NEXT_MULTI(step, inm);
			}
			/*
			 * IGMPv2 Querier Present is set to Older Version
			 * Querier Present Timeout seconds whenever an IGMPv2
			 * General Query is received.
			 */
			if (igmpalways_v3 == 0 &&
			    in_hosteq(igmp->igmp_group, zeroin_addr))
				igmp_set_hostcompat(rti, query_ver);
		} else if (query_ver == IGMP_v3_QUERY) {
			/*
			 * Adjust timer for scheduling responses to IGMPv3
			 * query.
			 */
			u_int8_t query_type;
			u_int16_t numsrc;

			++igmpstat.igps_rcv_v3_queries;

			/*
			 * Check query types and keep source list if needed.
			 */
			if (in_hosteq(igmp->igmp_group, zeroin_addr) &&
			    (igmp->igmp_numsrc == 0)) {
				if (ip->ip_dst.s_addr
						!= INADDR_ALLHOSTS_GROUP) {
					++igmpstat.igps_rcv_badqueries;
					m_freem(m);
					return;
				}
				query_type = IGMP_v3_GENERAL_QUERY;
			} else if (IN_MULTICAST(igmp->igmp_group.s_addr) &&
			    (igmp->igmp_numsrc == 0)) {
				query_type = IGMP_v3_GROUP_QUERY;
			} else if (IN_MULTICAST(igmp->igmp_group.s_addr) &&
			    (numsrc = ntohs(igmp->igmp_numsrc) > 0)) {
				igmplen -= qhdrlen; /* remaining source list */
				if (numsrc != igmplen / addrlen) {
					++igmpstat.igps_rcv_badqueries;
					m_freem(m);
					return;
				}
				query_type = IGMP_v3_GROUP_SOURCE_QUERY;
			} else {
				++igmpstat.igps_rcv_badqueries;
				m_freem(m);
				return;
			}

#ifdef MROUTING /* XXX */
			if (ip_mrouter != NULL) {
				if (IGMP_SFLAG(igmp->igmp_rtval) != 0) {
				/* XXX not yet */
				/* suppress the normal timer updates */
				}
			} else {
				/* XXX not yet */
				/* suppress the querier election or the normal
				 * "host-side" processing of a Query */
			}
#endif

			/*
			 * Dispatch this query to make an appropriate
			 * version's reply.
			 */
			if (rti->rti_type == IGMP_v1_ROUTER)
				goto start_v1;
			else if (rti->rti_type == IGMP_v2_ROUTER)
				goto start_v2;

			igmp_set_timer(rti, igmp, query_type);
		}

		break;

	case IGMP_v1_HOST_MEMBERSHIP_REPORT:
		++igmpstat.igps_rcv_reports;

		if (ifp->if_flags & IFF_LOOPBACK)
			break;

		if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
		    !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
			++igmpstat.igps_rcv_badreports;
			m_freem(m);
			return;
		}

		/*
		 * KLUDGE: if the IP source address of the report has an
		 * unspecified (i.e., zero) subnet number, as is allowed for
		 * a booting host, replace it with the correct subnet number
		 * so that a process-level multicast routing daemon can
		 * determine which subnet it arrived from.  This is necessary
		 * to compensate for the lack of any way for a process to
		 * determine the arrival interface of an incoming packet.
		 */
		if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
			IFP_TO_IA(ifp, ia);		/* XXX */
			if (ia)
				ip->ip_src.s_addr = ia->ia_subnet;
		}

		/*
		 * If we belong to the group being reported, stop
		 * our timer for that group.
		 */
		IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
		if (inm != NULL) {
			inm->inm_timer = 0;
			++igmpstat.igps_rcv_ourreports;

			switch (inm->inm_state) {
			case IGMP_IDLE_MEMBER:
			case IGMP_LAZY_MEMBER:
			case IGMP_AWAKENING_MEMBER:
			case IGMP_SLEEPING_MEMBER:
				inm->inm_state = IGMP_SLEEPING_MEMBER;
				break;
			case IGMP_DELAYING_MEMBER:
				if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
					inm->inm_state = IGMP_LAZY_MEMBER;
				else /* IGMP_v2_ROUTER */
					inm->inm_state = IGMP_SLEEPING_MEMBER;
				break;
			}
		}

		break;

	case IGMP_v2_HOST_MEMBERSHIP_REPORT:
#ifdef MROUTING
		/*
		 * Make sure we don't hear our own membership report.  Fast
		 * leave requires knowing that we are the only member of a
		 * group.
		 */
		IFP_TO_IA(ifp, ia);			/* XXX */
		if (ia && in_hosteq(ip->ip_src, ia->ia_addr.sin_addr))
			break;
#endif

		++igmpstat.igps_rcv_reports;

		if (ifp->if_flags & IFF_LOOPBACK)
			break;

		if (!IN_MULTICAST(igmp->igmp_group.s_addr) ||
		    !in_hosteq(igmp->igmp_group, ip->ip_dst)) {
			++igmpstat.igps_rcv_badreports;
			m_freem(m);
			return;
		}

		/*
		 * KLUDGE: if the IP source address of the report has an
		 * unspecified (i.e., zero) subnet number, as is allowed for
		 * a booting host, replace it with the correct subnet number
		 * so that a process-level multicast routing daemon can
		 * determine which subnet it arrived from.  This is necessary
		 * to compensate for the lack of any way for a process to
		 * determine the arrival interface of an incoming packet.
		 */
		if ((ip->ip_src.s_addr & IN_CLASSA_NET) == 0) {
#ifndef MROUTING
			IFP_TO_IA(ifp, ia);		/* XXX */
#endif
			if (ia)
				ip->ip_src.s_addr = ia->ia_subnet;
		}

		/*
		 * If we belong to the group being reported, stop
		 * our timer for that group.
		 */
		IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm);
		if (inm != NULL) {
			inm->inm_timer = 0;
			++igmpstat.igps_rcv_ourreports;

			switch (inm->inm_state) {
			case IGMP_DELAYING_MEMBER:
			case IGMP_IDLE_MEMBER:
			case IGMP_AWAKENING_MEMBER:
				inm->inm_state = IGMP_LAZY_MEMBER;
				break;
			case IGMP_LAZY_MEMBER:
			case IGMP_SLEEPING_MEMBER:
				break;
			}
		}

		break;

	}

	/*
	 * Pass all valid IGMP packets up to any process(es) listening
	 * on a raw IGMP socket.
	 */
	rip_input(m, iphlen, proto);
	return;
}

int
igmp_joingroup(struct in_multi *inm)
{
	int report_type;
	int s = splsoftnet();

	inm->inm_state = IGMP_IDLE_MEMBER;

	if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
	    (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0) {
		report_type = rti_fill(inm);
		if (report_type == 0) {
			splx(s);
			return ENOMEM;
		}
		igmp_sendpkt(inm, report_type);
		inm->inm_state = IGMP_DELAYING_MEMBER;
		inm->inm_timer = IGMP_RANDOM_DELAY(
		    IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
		igmp_timers_are_running = 1;
	} else {
		inm->inm_timer = 0;
	}
	splx(s);
	return 0;
}

void
igmp_leavegroup(struct in_multi *inm)
{

	switch (inm->inm_state) {
	case IGMP_DELAYING_MEMBER:
	case IGMP_IDLE_MEMBER:
		if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
		    (inm->inm_ifp->if_flags & IFF_LOOPBACK) == 0)
			if (inm->inm_rti->rti_type == IGMP_v2_ROUTER)
				igmp_sendpkt(inm, IGMP_HOST_LEAVE_MESSAGE);
		break;
	case IGMP_LAZY_MEMBER:
	case IGMP_AWAKENING_MEMBER:
	case IGMP_SLEEPING_MEMBER:
		break;
	}
}

void
igmp_fasttimo(void)
{
	struct in_multi *inm;
	struct in_multistep step;
	struct ifnet *ifp = NULL;
	struct router_info *rti;
	struct mbuf *cm, *sm;
	int mlen = 0;
	u_int8_t notype = 0;
	int s;

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

	s = splsoftnet();

	/*
	 * Both of Current-State Record timer and State-Change Record timer
	 * are controled.
	 */
	if (interface_timers_are_running) {
		interface_timers_are_running = 0;
		LIST_FOREACH(rti, &rti_head, rti_link) {
			if (rti->rti_timer3 == 0)
				; /* do nothing */
			else if (--rti->rti_timer3 == 0)
				igmp_send_all_curstate(rti->rti_ifp);
			else
				interface_timers_are_running = 1;
		}
	}

	if (!igmp_timers_are_running && !state_change_timers_are_running) {
		splx(s);
		return;
	}
	igmp_timers_are_running = 0;
	state_change_timers_are_running = 0;
	cm = sm = NULL;
	IN_FIRST_MULTI(step, inm);
	if (inm == NULL) {
		splx(s);
		return;
	}
	ifp = inm->inm_ifp;
	while (inm != NULL) {
		if (inm->inm_timer == 0) {
			; /* do nothing */
		} else if (--inm->inm_timer == 0) {
			if (inm->inm_state == IGMP_DELAYING_MEMBER) {
				if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
					igmp_sendpkt(inm,
					    IGMP_v1_HOST_MEMBERSHIP_REPORT);
				else
					igmp_sendpkt(inm,
					    IGMP_v2_HOST_MEMBERSHIP_REPORT);
				inm->inm_state = IGMP_IDLE_MEMBER;
			} else if ((inm->inm_state
					== IGMP_G_QUERY_PENDING_MEMBER) ||
				   (inm->inm_state
					== IGMP_SG_QUERY_PENDING_MEMBER)) {
				if ((cm != NULL) && (ifp != inm->inm_ifp)) {
					igmp_sendmsg(cm, ifp);
					cm = NULL;
				}
				cm = igmp_send_curstate(cm, inm, &mlen);
				ifp = inm->inm_ifp; /* when ifp is changed */
			}
		} else {
			igmp_timers_are_running = 1;
		}

		if (IN_LOCAL_GROUP(inm->inm_addr.s_addr)) {
			; /* skip */
		} else if (inmm_src->ims_timer == 0) {
			; /* do nothing */
		} else if (--inmm_src->ims_timer == 0) {
			if ((sm != NULL) && (ifp != inm->inm_ifp)) {
				igmp_sendmsg(sm, ifp);
				sm = NULL;
			}
			if (inmm_src->ims_robvar > 0) {
				sm = igmp_send_statechange(sm, inm, notype);
				ifp = inm->inm_ifp;
			}
			if (inmm_src->ims_robvar != 0) {
				/* reset timer */
				inmm_src->ims_timer = IGMP_RANDOM_DELAY
						(IGMP_UNSOL_INTVL * PR_FASTHZ);
				state_change_timers_are_running = 1;
			}
		} else {
			state_change_timers_are_running = 1;
		}
		IN_NEXT_MULTI(step, inm);
	}
	if (cm != NULL)
		igmp_sendmsg(cm, ifp);
	if (sm != NULL)
		igmp_sendmsg(sm, ifp);
	splx(s);
}

void
igmp_slowtimo(void)
{
	struct router_info *rti;
	int s;

	s = splsoftnet();
	LIST_FOREACH(rti, &rti_head, rti_link) {
		if ((rti->rti_timer1 == 0) && (rti->rti_timer2 == 0)) {
			if (rti->rti_type != IGMP_v3_ROUTER)
				rti->rti_type = IGMP_v3_ROUTER;
		} else if ((rti->rti_timer1 == 0) && (rti->rti_timer2 > 0)) {
			--rti->rti_timer2;
			if (rti->rti_type != IGMP_v2_ROUTER)
				rti->rti_type = IGMP_v2_ROUTER;
		} else if (rti->rti_timer1 > 0) {
			--rti->rti_timer1;
			if (rti->rti_timer2 > 0)
				rti->rti_timer2 = 0;
		}
	}
	splx(s);
}

void
igmp_sendpkt(struct in_multi *inm, int type)
{
	struct mbuf *m;
	struct igmp *igmp;
	struct ip *ip;
	struct ip_moptions imo;
#ifdef MROUTING
	extern struct socket *ip_mrouter;
#endif /* MROUTING */

	if (type < 0)
		return;
	MGETHDR(m, M_DONTWAIT, MT_HEADER);
	if (m == NULL)
		return;
	/*
	 * Assume max_linkhdr + sizeof(struct ip) + IGMP_MINLEN
	 * is smaller than mbuf size returned by MGETHDR.
	 */
	m->m_data += max_linkhdr;
	m->m_len = sizeof(struct ip) + IGMP_MINLEN;
	m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN;

	ip = mtod(m, struct ip *);
	ip->ip_tos = 0;
	ip->ip_len = htons(sizeof(struct ip) + IGMP_MINLEN);
	ip->ip_off = htons(0);
	ip->ip_p = IPPROTO_IGMP;
	ip->ip_src = zeroin_addr;
	if (type == IGMP_HOST_LEAVE_MESSAGE)
		ip->ip_dst.s_addr = INADDR_ALLRTRS_GROUP;
	else
		ip->ip_dst = inm->inm_addr;

	m->m_data += sizeof(struct ip);
	m->m_len -= sizeof(struct ip);
	igmp = mtod(m, struct igmp *);
	igmp->igmp_type = type;
	igmp->igmp_code = 0;
	igmp->igmp_group = inm->inm_addr;
	igmp->igmp_cksum = 0;
	igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
	m->m_data -= sizeof(struct ip);
	m->m_len += sizeof(struct ip);

	imo.imo_multicast_ifp = inm->inm_ifp;
	imo.imo_multicast_ttl = 1;
#ifdef RSVP_ISI
	imo.imo_multicast_vif = -1;
#endif
	/*
	 * Request loopback of the report if we are acting as a multicast
	 * router, so that the process-level routing daemon can hear it.
	 */
#ifdef MROUTING
	imo.imo_multicast_loop = (ip_mrouter != NULL);
#else
	imo.imo_multicast_loop = 0;
#endif /* MROUTING */

	if (igmpsendwithra && inm->inm_rti->rti_type == IGMP_v2_ROUTER) {
		ip_output(m, router_alert, NULL, IP_MULTICASTOPTS, &imo, NULL);
	} else { /* IGMPv1 or IGMPv2 with no RA restriction */
		ip_output(m, NULL, NULL, IP_MULTICASTOPTS, &imo, NULL);
	}
	++igmpstat.igps_snd_v1v2_reports;
}

void
igmp_sendmsg(struct mbuf *m, struct ifnet *ifp)
{
	struct igmp_report_hdr *igmp_rhdr;
	struct igmp_group_record_hdr *igmp_ghdr;
	int len;
	u_int16_t i;
	struct ip_moptions imo;
#ifdef MROUTING
	extern struct socket *ip_mrouter;
#endif /* MROUTING */

	/*
	 * Insert check sum and send the message.
	 */
	if (m == NULL)
		return;
	m->m_data += sizeof(struct ip);
	m->m_len -= sizeof(struct ip);
	igmp_rhdr = mtod(m, struct igmp_report_hdr *);
	len = sizeof(struct igmp_report_hdr);
	for (i = 0; i < igmp_rhdr->igmp_grpnum; i++) {
		igmp_ghdr = (struct igmp_group_record_hdr *)
					((char *)igmp_rhdr + len);
		len += ghdrlen + SOURCE_RECORD_LEN(igmp_ghdr->numsrc);
		HTONS(igmp_ghdr->numsrc);
	}
	HTONS(igmp_rhdr->igmp_grpnum);
	igmp_rhdr->igmp_cksum = 0;
	igmp_rhdr->igmp_cksum = in_cksum(m, len);
	m->m_data -= sizeof(struct ip);
	m->m_len += sizeof(struct ip);

	imo.imo_multicast_ifp = ifp;
	imo.imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL;
#ifdef RSVP_ISI
	imo.imo_multicast_vif = -1;
#endif
#ifdef MROUTING
	imo.imo_multicast_loop = (ip_mrouter != NULL);
#else
	imo.imo_multicast_loop = 0;
#endif
	if (igmpsendwithra)
		ip_output(m, router_alert, NULL, IP_MULTICASTOPTS, &imo, NULL);
	else
		ip_output(m, NULL, NULL, IP_MULTICASTOPTS, &imo, NULL);
	++igmpstat.igps_snd_v3_reports;
}

/*
 * Timer adjustment on reception of an IGMPv3 Query.
 */
void
igmp_set_timer(struct router_info *rti, struct igmp *igmp, u_int8_t query_type)
{
	struct ifnet *ifp;
	struct in_multi *inm;
	struct in_multistep step;
	int timer;			/* Max-Resp-Timer */
	int timer_i = 0;		/* interface timer */
	int timer_g = 0;		/* group timer */
	int s = splsoftnet();

	/*
	 * Parse QRV, QQI, and QRI timer values.
	 */
	if (((rti->rti_qrv = IGMP_QRV(igmp->igmp_rtval)) == 0) ||
				(rti->rti_qrv > 7))
		rti->rti_qrv = IGMP_DEF_RV;
	if ((igmp->igmp_qqi > 0) && (igmp->igmp_qqi < 128))
		rti->rti_qqi = igmp->igmp_qqi;
	else if (igmp->igmp_qqi >= 128)
		rti->rti_qqi = ((IGMP_MANT(igmp->igmp_qqi) | 0x10)
				<< (IGMP_EXP(igmp->igmp_qqi) + 3));
	else
		rti->rti_qqi = IGMP_DEF_QI;
	rti->rti_qri = igmp->igmp_code;
	if (rti->rti_qri >= rti->rti_qqi)
		/* XXX tentatively adjusted */
		rti->rti_qri = (rti->rti_qqi - 1) / IGMP_TIMER_SCALE;
	else
		rti->rti_qri /= IGMP_TIMER_SCALE;

	if ((igmp->igmp_code > 0) && (igmp->igmp_code < 128))
		timer = igmp->igmp_code;
	else
		timer = (IGMP_MANT(igmp->igmp_code) | 0x10)
			<< (IGMP_EXP(igmp->igmp_code) + 3);

	/*
	 * Set interface timer if the query is Generic Query.
	 * Get group timer if the query is not Generic Query.
	 */
	if (query_type == IGMP_v3_GENERAL_QUERY) {
		timer_i = timer * PR_FASTHZ / IGMP_TIMER_SCALE;
		if (timer_i == 0)
			timer_i = 1;
		timer_i = IGMP_RANDOM_DELAY(timer_i);
		if (interface_timers_are_running && (rti->rti_timer3 != 0) &&
		    (rti->rti_timer3 < timer_i)) {
			; /* no need to update interface timer */
		} else {
			rti->rti_timer3 = timer_i;
			interface_timers_are_running = 1;
		}
	} else { /* Group- or Group-and-Source-Specific Query */
		timer_g = timer * PR_FASTHZ / IGMP_TIMER_SCALE;
		if (timer_g == 0)
			timer_g = 1;
		timer_g = IGMP_RANDOM_DELAY(timer_g);
	}

	ifp = rti->rti_ifp;
	IN_FIRST_MULTI(step, inm);
	while (inm != NULL) {
		if (IN_LOCAL_GROUP(inm->inm_addr.s_addr) || inm->inm_ifp != ifp)
			goto next_multi;

		if ((inm->inm_source->ims_grpjoin == 0) &&
		    (inm->inm_source->ims_mode == MCAST_INCLUDE) &&
		    (inm->inm_source->ims_cur->numsrc == 0))
			goto next_multi; /* no need to consider any timer */

		if (query_type == IGMP_v3_GENERAL_QUERY) {
			/*
			 * Pending response to Group- or
			 * Group-and-Source-Specific Query is canceled,
			 * if pending group timer is not sooner than new
			 * interface timer.
			 */
			if (!igmp_timers_are_running)
				goto next_multi;
			if (inm->inm_timer <= rti->rti_timer3)
				goto next_multi;
			inm->inm_state = IGMP_IDLE_MEMBER;
			inm->inm_timer = 0;
			in_free_msflist(inm->inm_source->ims_rec);
			inm->inm_source->ims_rec->numsrc = 0;
			goto next_multi;
		} else if (!in_hosteq(inm->inm_addr, igmp->igmp_group)) {
			goto next_multi;
		}

		/*
		 * If interface timer is sooner than new group timer,
		 * ignore this Query for this group address.
		 */
		if (interface_timers_are_running
		    && (rti->rti_timer3 < timer_g)) {
			inm->inm_state = IGMP_IDLE_MEMBER;
			inm->inm_timer = 0;
			break;
		}

		/* Receive Group-Specific Query */
		if (query_type == IGMP_v3_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 ((inm->inm_state != IGMP_G_QUERY_PENDING_MEMBER) &&
			    (inm->inm_state != IGMP_SG_QUERY_PENDING_MEMBER)) {
				igmp_timers_are_running = 1;
				inm->inm_timer = timer_g;
			} else {
				in_free_msflist(inm->inm_source->ims_rec);
				inm->inm_source->ims_rec->numsrc = 0;
				inm->inm_timer = min(inm->inm_timer, timer_g);
			}
			inm->inm_state = IGMP_G_QUERY_PENDING_MEMBER;
			break;
		}

		/* Receive Group-and-Source-Specific Query */
		if (inm->inm_state == IGMP_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.
			 */
			inm->inm_timer = min(inm->inm_timer, timer_g);
			break;
		}
		/* Queried sources are augmented. */
		if (igmp_query_source(inm, igmp) != 0) {
			++igmpstat.igps_rcv_query_fails;
		} else if (inm->inm_source->ims_rec->numsrc == 0) {
			; /* do nothing because it was not joined source */
		} else {
			if (inm->inm_timer != 0) {
				inm->inm_timer = min(inm->inm_timer, timer_g);
			} else {
				igmp_timers_are_running = 1;
				inm->inm_timer = timer_g;
			}
			inm->inm_state = IGMP_SG_QUERY_PENDING_MEMBER;
		}
		break;

next_multi:
		IN_NEXT_MULTI(step, inm);
	} /* while */

	splx(s);
}

/*
 * Set IGMP Host Compatibility Mode.
 */
void
igmp_set_hostcompat(struct router_info *rti, int query_ver)
{
	int s = splsoftnet();

	/*
	 * Keep Older Version Querier Present timer.
	 */
	if (query_ver == IGMP_v1_QUERY) {
		rti->rti_timer1 = rti->rti_qrv * rti->rti_qqi + rti->rti_qri;
		rti->rti_timer1 *= PR_SLOWHZ;
	} else if (query_ver == IGMP_v2_QUERY) {
		rti->rti_timer2 = rti->rti_qrv * rti->rti_qqi + rti->rti_qri;
		rti->rti_timer2 *= PR_SLOWHZ;
	}

	/*
	 * Check/set host compatibility mode. Whenever a host changes
	 * its compatability mode, cancel all its pending response and
	 * retransmission timers.
	 */
	if ((rti->rti_timer1 == 0) && (rti->rti_timer2 > 0)) {
		if (rti->rti_type != IGMP_v2_ROUTER) {
			rti->rti_type = IGMP_v2_ROUTER;
			igmp_cancel_report(rti);
		}
	} else if (rti->rti_timer1 > 0) {
		if (rti->rti_type != IGMP_v1_ROUTER) {
			rti->rti_type = IGMP_v1_ROUTER;
			igmp_cancel_report(rti);
		}
	}

	splx(s);
}

/*
 * Parse queried source addresses from IGMPv3 Group-and-Source-Specific Query
 * message and merge them in a recorded source list.
 * If there is no pending source recorded, or if some source was recorded as
 * a reply for Group-and-Source-Specific Query, return 0.
 */
int
igmp_query_source(struct in_multi *inm, struct igmp *igmp)
{
	struct in_addr_source *curias;
	u_int16_t numsrc, i;
	struct sockaddr_in src;
	int error = 0;

	numsrc = ntohs(igmp->igmp_numsrc);
	for (i = 0; i < numsrc; i++) {
		bzero(&src, sizeof(src));
		src.sin_family = AF_INET;
		src.sin_len = sizeof(src);
		src.sin_addr = igmp->src[i];
		if (inm->inm_source->ims_grpjoin > 0) {
			error = in_merge_msfaddr(inm->inm_source->ims_rec,
						 &src, IMS_ADD_SOURCE);
			if (error != 0) {
				in_free_msflist(inm->inm_source->ims_rec);
				inm->inm_source->ims_rec->numsrc = 0;
				return error;
			} else {
				++inm->inm_source->ims_rec->numsrc;
			}
			continue;
		}

		LIST_FOREACH(curias, inm->inm_source->ims_cur->head, ias_list) {
			if (SS_COMPARE(&curias->ias_addr, <, &src))
				continue;

			if (SS_COMPARE(&curias->ias_addr, ==, &src)) {
				error = in_merge_msfaddr
					    (inm->inm_source->ims_rec,
					     &src, IMS_ADD_SOURCE);
				if (error != 0) {
					in_free_msflist
						(inm->inm_source->ims_rec);
					inm->inm_source->ims_rec->numsrc = 0;
					return error;
				} else {
					++inm->inm_source->ims_rec->numsrc;
				}
				break;
			}
			break;
		}
	}

	return 0;
}

/*
 * Send Current-State Report for General Query response.
 */
void
igmp_send_all_curstate(struct ifnet *ifp)
{
	struct mbuf *m = NULL;
	struct in_multi *inm;
	struct in_multistep step;
	int mlen = 0;

	IN_FIRST_MULTI(step, inm);
	while (inm != NULL) {
		if (inm->inm_ifp != ifp || IN_LOCAL_GROUP(inm->inm_addr.s_addr))
			goto next_multi;

		m = igmp_send_curstate(m, inm, &mlen);
next_multi:
		IN_NEXT_MULTI(step, inm);
	}
	if (m != NULL)
		igmp_sendmsg(m, ifp);
}

/*
 * Send Current-State Report for Group- and Group-and-Source-Sepcific Query
 * response.
 */
struct mbuf *
igmp_send_curstate(struct mbuf *m0, struct in_multi *inm, int *mlenp)
{
	struct mbuf *m;		/* mbuf is modified to put multiple group
				 * records in one message */
	struct ip *ip;
	struct igmp_report_hdr *igmp_rhdr;
	u_int16_t numsrc;
	int buflen = 0;
	u_int8_t type;
	struct in_addr_slist *iasl;
	int error = 0;

	if (inm->inm_source->ims_mode == MCAST_INCLUDE) {
		type = ALLOW_NEW_SOURCES;
		iasl = inm->inm_source->ims_cur;
		numsrc = inm->inm_source->ims_cur->numsrc;
	} else {
		type = CHANGE_TO_EXCLUDE_MODE;
		iasl = NULL;
		numsrc = 0; /* (*,G) join state */
	}

	/*
	 * Prepare record for Group-and-Source-Specific Query.
	 */
	if (inm->inm_state == IGMP_SG_QUERY_PENDING_MEMBER) {
		type = ALLOW_NEW_SOURCES;
		iasl = inm->inm_source->ims_rec;
		numsrc = inm->inm_source->ims_rec->numsrc;
	}

	if (m0 == NULL) {
		SET_REPORTHDR(m, numsrc);
		if (error != 0)
			return NULL; /* XXX should return error */
	} else {
		if (ghdrlen + SOURCE_RECORD_LEN(numsrc) > M_TRAILINGSPACE(m0)) {
			/*
			 * When remaining buffer is not enough to insert new
			 * group record, send current buffer and create a new
			 * buffer for this record.
			 */
			igmp_sendmsg(m0, inm->inm_ifp);
			SET_REPORTHDR(m, numsrc);
			if (error != 0)
				return NULL;
		} else {
			m = m0;
		}
	}

	/*
	 * Complete to create a group record.
	 */
	igmp_create_record(m, inm, type, iasl, mlenp);

	/*
	 * If pending query was Group-and-Source-Specific Query, pending
	 * (merged) source list is cleared.
	 */
	if (inm->inm_state == IGMP_SG_QUERY_PENDING_MEMBER) {
		in_free_msflist(inm->inm_source->ims_rec);
		inm->inm_source->ims_rec->numsrc = 0;
	}
	inm->inm_state = IGMP_IDLE_MEMBER;
	inm->inm_timer = 0;

	return m;
}

/*
 * Send State-Change Report (Filter-Mode-Change Report and Source-List-Change
 * Report).
 * If report type is not specified (i.e., "0"), the requested State-Change
 * report may consist of multiple records. However, if there is a pending
 * Filter-Mode-Change report for the group, Source-List-Change report is not
 * sent in the same message and scheduled as the different report transmitted
 * later.
 */
struct mbuf *
igmp_send_statechange(struct mbuf *m0, struct in_multi *inm, u_int8_t type)
{
	struct mbuf *m;
	struct ip *ip;
	struct igmp_report_hdr *igmp_rhdr;
	struct in_addr_slist *iasl;
	u_int16_t numsrc = 0;
	int buflen = 0;
	int mlen = 0;
	int error = 0;

	if (IN_LOCAL_GROUP(inm->inm_addr.s_addr) ||
	    (inm->inm_ifp->if_flags & IFF_LOOPBACK) != 0)
		return m0;

	if (!(inm->inm_source->ims_robvar > 0)) {
		return m0;
	}

	/*
	 * If there is pending Filter-Mode-Change report, Source-List-Change
	 * report will be merged in another message and scheduled to be sent
	 * after Filter-Mode-Change report is sent.
	 */
	if (inm->inm_source->ims_toin != NULL) {
		if (type == 0)
			type = CHANGE_TO_INCLUDE_MODE;
	} else if (inm->inm_source->ims_toex != NULL) {
		if (type == 0)
			type = CHANGE_TO_EXCLUDE_MODE;
	}

	if (type == CHANGE_TO_EXCLUDE_MODE) { /* (*,G) join */
		numsrc = 0;
	} else if (type == CHANGE_TO_INCLUDE_MODE) {
		if (inm->inm_source->ims_toin != NULL)
			/* 0 when (*,G) leave */
			numsrc = inm->inm_source->ims_toin->numsrc;
		else
			numsrc = 1;
	} else { /* ALLOW_NEW_SOURCES and/or BLOCK_OLD_SOURCES */
		if ((inm->inm_source->ims_alw != NULL) &&
		    (inm->inm_source->ims_alw->numsrc != 0))
			numsrc = inm->inm_source->ims_alw->numsrc;
		if ((inm->inm_source->ims_blk != NULL) &&
		    (inm->inm_source->ims_blk->numsrc != 0))
			numsrc = max(numsrc, inm->inm_source->ims_blk->numsrc);
	}

	if (m0 == NULL) {
		SET_REPORTHDR(m, numsrc);
		if (error != 0)
			return NULL; /* robvar is not reduced */
	} else {
		if (ghdrlen + SOURCE_RECORD_LEN(numsrc) > M_TRAILINGSPACE(m0)) {
			/*
			 * When remaining buffer is not enough to insert new
			 * group record, send current buffer and create a new
			 * buffer for this record.
			 */
			igmp_sendmsg(m0, inm->inm_ifp);
			SET_REPORTHDR(m, numsrc);
			if (error != 0)
				return NULL; /* XXX */
		} else {
			m = m0;
		}
	}

	/*
	 * Filter-Mode-Change report
	 */
	if ((type == CHANGE_TO_EXCLUDE_MODE)
	    || (type == CHANGE_TO_INCLUDE_MODE)) {

		if (type == CHANGE_TO_EXCLUDE_MODE)
			iasl = inm->inm_source->ims_toex;
		else
			iasl = inm->inm_source->ims_toin;
		igmp_create_record(m, inm, type, iasl, &mlen);

		/* If this request is for an initial join/leave request */
		if (inm->inm_source->ims_robvar == inm->inm_rti->rti_qrv) {
			state_change_timers_are_running = 1;
			igmp_sendmsg(m, inm->inm_ifp);
		}
		if (--inm->inm_source->ims_robvar == 0) {
			/*
			 * For TO_IN and TO_EX list, they MUST be deleted
			 * after retransmission is done, in order to make
			 * igmp_fasttimo() know pending TO_IN or TO_EX report
			 * does not exist any more.
			 */
			if (inm->inm_source->ims_toin != NULL) {
				in_free_msflist(inm->inm_source->ims_toin);
				FREE(inm->inm_source->ims_toin->head,
				    M_MSFILTER);
				FREE(inm->inm_source->ims_toin, M_MSFILTER);
				inm->inm_source->ims_toin = NULL;
			} else if (inm->inm_source->ims_toex != NULL) {
				in_free_msflist(inm->inm_source->ims_toex);
				FREE(inm->inm_source->ims_toex->head,
				    M_MSFILTER);
				FREE(inm->inm_source->ims_toex, M_MSFILTER);
				inm->inm_source->ims_toex = NULL;
			}
			/* Prepare scheduled Source-List-Change Report */
			if ((inm->inm_source->ims_alw != NULL &&
			     inm->inm_source->ims_alw->numsrc > 0) ||
			    (inm->inm_source->ims_blk != NULL &&
			     inm->inm_source->ims_blk->numsrc > 0)) {
				state_change_timers_are_running = 1;
				inm->inm_source->ims_robvar
				    = inm->inm_rti->rti_qrv;
				inm->inm_source->ims_timer
				    = IGMP_RANDOM_DELAY
				      (IGMP_UNSOL_INTVL * PR_FASTHZ);
			} else {
				inm->inm_source->ims_timer = 0;
			}
		}

	/*
	 * Source-List-Change report
	 */
	} else { /* ALLOW_NEW_SOURCES and/or BLOCK_OLD_SOURCES */
		if ((inm->inm_source->ims_alw != NULL) &&
		    (inm->inm_source->ims_alw->numsrc != 0)) {
			type = ALLOW_NEW_SOURCES;
			iasl = inm->inm_source->ims_alw;
			igmp_create_record(m, inm, type, iasl, &mlen);
		}
		if ((inm->inm_source->ims_blk != NULL) &&
		    (inm->inm_source->ims_blk->numsrc != 0)) {
			type = BLOCK_OLD_SOURCES;
			iasl = inm->inm_source->ims_blk;
			igmp_create_record(m, inm, type, iasl, &mlen);
		}

		/* If this request is for an initial join/leave request */
		if (inm->inm_source->ims_robvar == inm->inm_rti->rti_qrv) {
			state_change_timers_are_running = 1;
			igmp_sendmsg(m, inm->inm_ifp);
		}

		/*
		 * If state-change reports are transmitted robvar times,
		 * ALW or BLK record is cleared.
		 * Note that current-state reports are created by refering
		 * with ims_cur record.
		 */ 
		if (--inm->inm_source->ims_robvar == 0) {
			if ((inm->inm_source->ims_alw != NULL) &&
			    (inm->inm_source->ims_alw->numsrc != 0)) {
				in_free_msflist(inm->inm_source->ims_alw);
				inm->inm_source->ims_alw->numsrc = 0;
			}
			if ((inm->inm_source->ims_blk != NULL) &&
			    (inm->inm_source->ims_blk->numsrc != 0)) {
				in_free_msflist(inm->inm_source->ims_blk);
				inm->inm_source->ims_blk->numsrc = 0;
			}
			inm->inm_source->ims_timer = 0;
		}
	}

	return m;
}

void
igmp_create_record(struct mbuf *m, struct in_multi *inm, u_int8_t type,
    struct in_addr_slist *iasl, int *mlen)
{
	struct ip *ip;
	struct igmp_report_hdr *igmp_rhdr;
	struct igmp_group_record_hdr *igmp_ghdr;
	struct in_addr_source *ias = NULL;
	u_int16_t numsrc, i;
	int mfreelen;
	u_int16_t ip_len;

	if (iasl != NULL) {
		numsrc = iasl->numsrc;
		ias = LIST_FIRST(iasl->head);
	} else {
		numsrc = 0;
	}
	ip = mtod(m, struct ip *);
	igmp_rhdr = (struct igmp_report_hdr *)((char *)ip + sizeof(*ip));
	++igmp_rhdr->igmp_grpnum;
	/* Check if this mbuf includes a packet header */
	if (*mlen == 0)
		*mlen = sizeof(struct ip) + rhdrlen;

	igmp_ghdr = (struct igmp_group_record_hdr *)((char *)ip + *mlen);
	igmp_ghdr->record_type = type;
	igmp_ghdr->auxlen = 0;
	igmp_ghdr->numsrc = 0;
	igmp_ghdr->group.s_addr = inm->inm_addr.s_addr;
	ip_len = ntohs(ip->ip_len);
	ip_len += ghdrlen;
	*mlen += ghdrlen;
	m->m_len += ghdrlen;
	m->m_pkthdr.len += ghdrlen;
	mfreelen = M_TRAILINGSPACE(m) - *mlen;

	/* Insert as many source addresses to mbuf as possible */
	for (i = 0; i < numsrc && ias != NULL && mfreelen > addrlen;
	     i++, mfreelen -= addrlen, ias = LIST_NEXT(ias, ias_list))
		bcopy(&ias->ias_addr.sin_addr, &igmp_ghdr->src[i],
		      sizeof(igmp_ghdr->src[i]));
	ip_len += SOURCE_RECORD_LEN(i);
	ip->ip_len = htons(ip_len);
	igmp_ghdr->numsrc = i;
	*mlen += SOURCE_RECORD_LEN(i);
	m->m_len += SOURCE_RECORD_LEN(i);
	m->m_pkthdr.len += SOURCE_RECORD_LEN(i);

#ifdef IGMPV3_DEBUG
	if (i < numsrc) {
		printf("igmp_create_record: every source address is not reported.\n"); /* XXX TODO: need to support this. */
	}
#endif

	return;
}

/*
 * Cancel all IGMPv3 pending response and retransmission timers on an
 * interface.
 */
void
igmp_cancel_report(struct router_info *rti)
{
	struct ifnet *ifp;
	struct in_multi *inm;
	struct in_multistep step;

	rti->rti_timer3 = 0;
	ifp = rti->rti_ifp;
	IN_FIRST_MULTI(step, inm);
	while (inm != NULL) {
		if (inm->inm_ifp != ifp)
			goto next_multi;
		if (IN_LOCAL_GROUP(inm->inm_addr.s_addr))
			goto next_multi;
		if (inm->inm_source == NULL)
			goto next_multi;

		inm->inm_source->ims_robvar = 0;
		inm->inm_source->ims_timer = 0;
		in_free_msflist(inm->inm_source->ims_rec);
		inm->inm_source->ims_rec->numsrc = 0;
		if (inm->inm_source->ims_alw != NULL) {
			in_free_msflist(inm->inm_source->ims_alw);
			inm->inm_source->ims_alw->numsrc = 0;
		}
		if (inm->inm_source->ims_blk != NULL) {
			in_free_msflist(inm->inm_source->ims_blk);
			inm->inm_source->ims_blk->numsrc = 0;
		}
		if (inm->inm_source->ims_toin != NULL) {
			in_free_msflist(inm->inm_source->ims_toin);
			/* For TO_IN list, it MUST be deleted. */
			FREE(inm->inm_source->ims_toin->head, M_MSFILTER);
			FREE(inm->inm_source->ims_toin, M_MSFILTER);
			inm->inm_source->ims_toin = NULL;
		}
		if (inm->inm_source->ims_toex != NULL) {
			in_free_msflist(inm->inm_source->ims_toex);
			/* For TO_EX list, it MUST be deleted. */
			FREE(inm->inm_source->ims_toex->head, M_MSFILTER);
			FREE(inm->inm_source->ims_toex, M_MSFILTER);
			inm->inm_source->ims_toex = NULL;
		}

next_multi:
		IN_NEXT_MULTI(step, inm);
	}
}

void
igmp_purgeif(struct ifnet *ifp)	/* MUST be called at splsoftnet() */
{
	rti_delete(ifp);	/* manipulates pools */
}

SYSCTL_SETUP(sysctl_net_inet_igmp_setup, "sysctl net.inet.igmp subtree setup")
{                       
	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT,
		       CTLTYPE_NODE, "net", NULL,
		       NULL, 0, NULL, 0,
		       CTL_NET, CTL_EOL);
	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT,
		       CTLTYPE_NODE, "inet", NULL,
		       NULL, 0, NULL, 0,
		       CTL_NET, PF_INET, CTL_EOL);
	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT,
		       CTLTYPE_NODE, "igmp",
		       SYSCTL_DESCR("IGMP related settings"),
		       NULL, 0, NULL, 0,
		       CTL_NET, PF_INET, IPPROTO_IGMP, CTL_EOL);

	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
		       CTLTYPE_INT, "sendwithra",
		       SYSCTL_DESCR("Send packet with Router Alert"),
		       NULL, 0, &igmpsendwithra, 0,
		       CTL_NET, PF_INET, IPPROTO_IGMP,
		       IGMPCTL_SENDWITHRA, CTL_EOL);
	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
		       CTLTYPE_INT, "dropwithnora",
		       SYSCTL_DESCR("Drop packet with no Router Alert"),
		       NULL, 0, &igmpdropwithnora, 0,
		       CTL_NET, PF_INET, IPPROTO_IGMP,
		       IGMPCTL_DROPWITHNORA, CTL_EOL);
	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
		       CTLTYPE_INT, "maxsrcfilter",
		       SYSCTL_DESCR("Maximum MSF source per kernel"),
		       NULL, 0, &igmpmaxsrcfilter, 0,
		       CTL_NET, PF_INET, IPPROTO_IGMP,
		       IGMPCTL_MAXSRCFILTER, CTL_EOL);
	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
		       CTLTYPE_INT, "somaxsrc",
		       SYSCTL_DESCR("Maximum MSF source per socket"),
		       NULL, 0, &igmpsomaxsrc, 0,
		       CTL_NET, PF_INET, IPPROTO_IGMP,
		       IGMPCTL_SOMAXSRC, CTL_EOL);
	sysctl_createv(clog, 0, NULL, NULL,
		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
		       CTLTYPE_INT, "always_v3",
		       SYSCTL_DESCR("Always behave as IGMPv3 node"),
		       NULL, 0, &igmpalways_v3, 0,
		       CTL_NET, PF_INET, IPPROTO_IGMP,
		       IGMPCTL_ALWAYS_V3, CTL_EOL);
}
