/*	$KAME: mld6_proto.c,v 1.12 2003/09/02 09:57:04 itojun Exp $	*/

/*
 * 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) 1998 by the University of Oregon.
 *  All rights reserved.
 *
 *  Permission to use, copy, modify, and distribute this software and
 *  its documentation in source and binary forms for lawful
 *  purposes and without fee is hereby granted, provided
 *  that the above copyright notice appear in all copies and that both
 *  the copyright notice and this permission notice appear in supporting
 *  documentation, and that any documentation, advertising materials,
 *  and other materials related to such distribution and use acknowledge
 *  that the software was developed by the University of Oregon.
 *  The name of the University of Oregon may not be used to endorse or 
 *  promote products derived from this software without specific prior 
 *  written permission.
 *
 *  THE UNIVERSITY OF OREGON DOES NOT MAKE ANY REPRESENTATIONS
 *  ABOUT THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE.  THIS SOFTWARE IS
 *  PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
 *  INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND 
 *  NON-INFRINGEMENT.
 *
 *  IN NO EVENT SHALL UO, OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY
 *  SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, WHETHER IN CONTRACT,
 *  TORT, OR OTHER FORM OF ACTION, ARISING OUT OF OR IN CONNECTION WITH,
 *  THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *  Other copyrights might apply to parts of this software and are so
 *  noted when applicable.
 */
/*
 *  Questions concerning this software should be directed to 
 *  Kurt Windisch (kurtw@antc.uoregon.edu)
 */
/*
 * Part of this program has been derived from PIM sparse-mode pimd.
 * The pimd program is covered by the license in the accompanying file
 * named "LICENSE.pimd".
 *  
 * The pimd program is COPYRIGHT 1998 by University of Southern California.
 *
 * Part of this program has been derived from mrouted.
 * The mrouted program is covered by the license in the accompanying file
 * named "LICENSE.mrouted".
 * 
 * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
 * Leland Stanford Junior University.
 *
 */

#include "defs.h"

extern const struct in6_addr in6addr_any;

typedef struct {
	mifi_t  mifi;
	struct listaddr *g;
	int    q_time;
} cbk_t;


/*
 * Forward declarations.
 */
static void DelVif __P((void *arg));
static int SetTimer __P((int mifi, struct listaddr *g));
static int DeleteTimer __P((int id));
static void SendQuery __P((void *arg));
static int SetQueryTimer __P((struct listaddr *g, int mifi, int to_expire,
			      int q_time));

/*
 * Send group membership queries on that interface if I am querier.
 */
void
query_groups(v)
	register struct uvif *v;
{
	v->uv_gq_timer = MLD6_QUERY_INTERVAL;
	if (v->uv_flags & VIFF_QUERIER && (v->uv_flags & VIFF_NOLISTENER) == 0)
		send_mld6(MLD_LISTENER_QUERY, 0, &v->uv_linklocal->pa_addr,
			  NULL, (struct in6_addr *)&in6addr_any,
			  v->uv_ifindex, MLD6_QUERY_RESPONSE_INTERVAL, 0, 1);
}


/*
 * Process an incoming host membership query
 */
void
accept_listener_query(src, dst, group, tmo)
	struct sockaddr_in6 *src;
	struct in6_addr *dst, *group;
	int tmo;
{
	register int mifi;
	register struct uvif *v;
	struct sockaddr_in6 group_sa;

	init_sin6(&group_sa);

	/* Ignore my own membership query */
	if (local_address(src) != NO_VIF)
		return;

	if ((mifi = find_vif_direct(src)) == NO_VIF) {
		IF_DEBUG(DEBUG_MLD)
			log_msg(LOG_INFO, 0,
			    "accept_listener_query: can't find a mif");
		return;
	}

	v = &uvifs[mifi];

	if (!inet6_equal(&v->uv_querier->al_addr, src)) {
		/*
		 * This might be:
		 * - A query from a new querier, with a lower source address
		 *   than the current querier (who might be me)
		 * - A query from a new router that just started up and doesn't
		 *   know who the querier is.
		 * - A query from the current querier
		 */
		if (inet6_lessthan(src, (v->uv_querier ? &v->uv_querier->al_addr
					 : &v->uv_linklocal->pa_addr))) {
			IF_DEBUG(DEBUG_MLD)
				log_msg(LOG_DEBUG, 0, "new querier %s (was %s) "
				    "on mif %d",
				    inet6_fmt(&src->sin6_addr),
				    v->uv_querier ?
				    inet6_fmt(&v->uv_querier->al_addr.sin6_addr) :
				    "me", mifi);
			v->uv_flags &= ~VIFF_QUERIER;
			v->uv_querier->al_addr = *src;
			time(&v->uv_querier->al_ctime);
		}
	}
    
	/*
	 * Ignore the query if we're (still) the querier.
	 */
	if ((v->uv_flags & VIFF_QUERIER) != 0)
		return;

	/*
	 * Reset the timer since we've received a query.
	 */
	if (v->uv_querier && inet6_equal(src, &v->uv_querier->al_addr))
		v->uv_querier->al_timer = MLD6_OTHER_QUERIER_PRESENT_INTERVAL;

	/*
	 * If this is a Group-Specific query, we must set our membership timer
	 * to [Last Member Query Count] * the [Max Response Time] in the
	 * packet.
	 */
	if (!IN6_IS_ADDR_UNSPECIFIED(group)) {
		register struct listaddr *g;

		IF_DEBUG(DEBUG_MLD)
			log_msg(LOG_DEBUG, 0,
			    "%s for %s from %s on mif %d, timer %d",
			    "Group-specific membership query",
			    inet6_fmt(group),
			    inet6_fmt(&src->sin6_addr), mifi, tmo);

		group_sa.sin6_addr = *group;
		group_sa.sin6_scope_id = inet6_uvif2scopeid(&group_sa, v);
		for (g = v->uv_groups; g != NULL; g = g->al_next) {
			if (inet6_equal(&group_sa, &g->al_addr)
			    && g->al_query == 0) {
				/* setup a timeout to remove the group membership */
				if (g->al_timerid)
					g->al_timerid = DeleteTimer(g->al_timerid);
				g->al_timer = MLD6_LAST_LISTENER_QUERY_COUNT *
		                        tmo / MLD6_TIMER_SCALE;
				/*
				 * use al_query to record our presence
				 * in last-member state
				 */
				g->al_query = -1;
				g->al_timerid = SetTimer(mifi, g);
				IF_DEBUG(DEBUG_MLD)
					log_msg(LOG_DEBUG, 0,
					    "timer for grp %s on mif %d "
					    "set to %ld",
					    inet6_fmt(group),
					    mifi, g->al_timer);
				break;
			}
		}
	}
}


/*
 * Process an incoming group membership report.
 */
void
accept_listener_report(src, dst, group)
	struct sockaddr_in6 *src;
	struct in6_addr *dst, *group;
{
	register mifi_t mifi;
	register struct uvif *v;
	register struct listaddr *g;
	struct sockaddr_in6 group_sa;

	init_sin6(&group_sa);

	if (IN6_IS_ADDR_MC_LINKLOCAL(group)) {
		IF_DEBUG(DEBUG_MLD)
			log_msg(LOG_DEBUG, 0,
			    "accept_listener_report: group(%s) has the "
			    "link-local scope. discard", inet6_fmt(group));
		return;
	}

	if ((mifi = find_vif_direct_local(src)) == NO_VIF) {
		IF_DEBUG(DEBUG_MLD)
			log_msg(LOG_INFO, 0,
			    "accept_listener_report: can't find a mif");
		return;
	}
    
	IF_DEBUG(DEBUG_MLD)
		log_msg(LOG_INFO, 0,
		    "accepting multicast listener report: "
		    "src %s, dst %s, grp %s",
		    inet6_fmt(&src->sin6_addr), inet6_fmt(dst),
		    inet6_fmt(group));

	v = &uvifs[mifi];
    
	/*
	 * Look for the group in our group list; if found, reset its timer.
	 */
	group_sa.sin6_addr = *group;
	group_sa.sin6_scope_id = inet6_uvif2scopeid(&group_sa, v);
	for (g = v->uv_groups; g != NULL; g = g->al_next) {
		if (inet6_equal(&group_sa, &g->al_addr)) {
			g->al_reporter = *src;

			/* delete old timers, set a timer for expiration */
			g->al_timer = MLD6_LISTENER_INTERVAL;
			if (g->al_query)
				g->al_query = DeleteTimer(g->al_query);
			if (g->al_timerid)
				g->al_timerid = DeleteTimer(g->al_timerid);
			g->al_timerid = SetTimer(mifi, g);
			add_leaf(mifi, NULL, &group_sa);
			break;
		}
	}

	/*
	 * If not found, add it to the list and update kernel cache.
	 */
	if (g == NULL) {
		g = (struct listaddr *)malloc(sizeof(struct listaddr));
		if (g == NULL)
			log_msg(LOG_ERR, 0, "ran out of memory");    /* fatal */

		g->al_addr   = group_sa;

		/** set a timer for expiration **/
		g->al_query     = 0;
		g->al_timer     = MLD6_LISTENER_INTERVAL;
		g->al_reporter  = *src;
		g->al_timerid   = SetTimer(mifi, g);
		g->al_next      = v->uv_groups;
		v->uv_groups    = g;
		time(&g->al_ctime);

		add_leaf(mifi, NULL, &group_sa);
	}
}


/* TODO: send PIM prune message if the last member? */
void
accept_listener_done(src, dst, group)
	struct sockaddr_in6 *src;
	struct in6_addr *dst, *group;
{
	register mifi_t mifi;
	register struct uvif *v;
	register struct listaddr *g;
	struct sockaddr_in6 group_sa;

	init_sin6(&group_sa);

	if ((mifi = find_vif_direct_local(src)) == NO_VIF) {
		IF_DEBUG(DEBUG_MLD)
			log_msg(LOG_INFO, 0,
			    "accept_listener_done: can't find a mif");
		return;
	}

	IF_DEBUG(DEBUG_MLD)
		log_msg(LOG_INFO, 0,
		    "accepting listener done message: src %s, dst %s, grp %s",
		    inet6_fmt(&src->sin6_addr),
		    inet6_fmt(dst), inet6_fmt(group));

	v = &uvifs[mifi];
    
	if (!(v->uv_flags & (VIFF_QUERIER | VIFF_DR)))
		return;

	/*
	 * Look for the group in our group list in order to set up a
	 * short-timeout query.
	 */
	group_sa.sin6_addr = *group;
	group_sa.sin6_scope_id = inet6_uvif2scopeid(&group_sa, v);
	for (g = v->uv_groups; g != NULL; g = g->al_next) {
		if (inet6_equal(&group_sa, &g->al_addr)) {
			IF_DEBUG(DEBUG_MLD)
				log_msg(LOG_DEBUG, 0,
				    "[accept_done_message] %ld\n",
				    g->al_query);

			/*
			 * still waiting for a reply to a query,
			 * ignore the done
			 */
			if (g->al_query)
				return;
	    
			/** delete old timer set a timer for expiration **/
			if (g->al_timerid)
				g->al_timerid = DeleteTimer(g->al_timerid);

			/** send a group specific querry **/
			g->al_timer = (MLD6_LAST_LISTENER_QUERY_INTERVAL/MLD6_TIMER_SCALE) *
				(MLD6_LAST_LISTENER_QUERY_COUNT + 1);
			if (v->uv_flags & VIFF_QUERIER &&
			    (v->uv_flags & VIFF_NOLISTENER) == 0)
				send_mld6(MLD_LISTENER_QUERY, 0,
					  &v->uv_linklocal->pa_addr, NULL,
					  &g->al_addr.sin6_addr,
					  v->uv_ifindex,
					  MLD6_LAST_LISTENER_QUERY_INTERVAL, 0, 1);
			g->al_query = SetQueryTimer(g, mifi,
						    MLD6_LAST_LISTENER_QUERY_INTERVAL/MLD6_TIMER_SCALE,
						    MLD6_LAST_LISTENER_QUERY_INTERVAL);
			g->al_timerid = SetTimer(mifi, g);
			break;
		}
	}
}


/*
 * Time out record of a group membership on a vif
 */
static void
DelVif(arg)
	void *arg;
{
	cbk_t *cbk = (cbk_t *)arg;
	mifi_t mifi = cbk->mifi;
	struct uvif *v = &uvifs[mifi];
	struct listaddr *a, **anp, *g = cbk->g;

	/*
	 * Group has expired
	 * delete all kernel cache entries with this group
	 */
	if (g->al_query)
		DeleteTimer(g->al_query);

	delete_leaf(mifi, NULL, &g->al_addr);

	anp = &(v->uv_groups);
	while ((a = *anp) != NULL) {
		if (a == g) {
			*anp = a->al_next;
			free((char *)a);
		} else {
			anp = &a->al_next;
		}
	}

	free(cbk);
}


/*
 * Set a timer to delete the record of a group membership on a vif.
 */
static int
SetTimer(mifi, g)
	mifi_t mifi;
	struct listaddr *g;
{
	cbk_t *cbk;
    
	cbk = (cbk_t *) malloc(sizeof(cbk_t));
	cbk->mifi = mifi;
	cbk->g = g;
	return timer_setTimer(g->al_timer, DelVif, cbk);
}


/*
 * Delete a timer that was set above.
 */
static int
DeleteTimer(id)
	int id;
{
	timer_clearTimer(id);
	return 0;
}


/*
 * Send a group-specific query.
 */
static void
SendQuery(arg)
	void *arg;
{
	cbk_t *cbk = (cbk_t *)arg;
	register struct uvif *v = &uvifs[cbk->mifi];

	if (v->uv_flags & VIFF_QUERIER && (v->uv_flags & VIFF_NOLISTENER) == 0)
		send_mld6(MLD_LISTENER_QUERY, 0, &v->uv_linklocal->pa_addr,
			  NULL, &cbk->g->al_addr.sin6_addr,
			  v->uv_ifindex, cbk->q_time, 0, 1);
	cbk->g->al_query = 0;
	free(cbk);
}


/*
 * Set a timer to send a group-specific query.
 */
static int
SetQueryTimer(g, mifi, to_expire, q_time)
	struct listaddr *g;
	mifi_t mifi;
	int to_expire;
	int q_time;
{
	cbk_t *cbk;

	cbk = (cbk_t *) malloc(sizeof(cbk_t));
	cbk->g = g;
	cbk->q_time = q_time;
	cbk->mifi = mifi;
	return timer_setTimer(to_expire, SendQuery, cbk);
}

/* Checks for MLD listener: returns TRUE if there is a receiver for the
 * group on the given uvif, or returns FALSE otherwise.
 */
int
check_multicast_listener(v, group)
	struct uvif *v;
	struct sockaddr_in6 *group;
{
	register struct listaddr *g;

	/*
	 * Look for the group in our listener list;
	 */
	for (g = v->uv_groups; g != NULL; g = g->al_next) {
		if (inet6_equal(group, &g->al_addr)) 
			return TRUE;
	}
	return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1