/*
 * Public Release 3
 * 
 * $Id: mld6.c,v 1.1.2.2 1999/02/10 14:15:06 wfs Exp $
 */

/*
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1996, 1997, 1998 The Regents of the University of Michigan
 * All Rights Reserved
 *  
 * Royalty-free licenses to redistribute GateD Release
 * 3 in whole or in part may be obtained by writing to:
 * 
 * 	Merit GateDaemon Project
 * 	4251 Plymouth Road, Suite C
 * 	Ann Arbor, MI 48105
 *  
 * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
 * UNIVERSITY OF MICHIGAN AND MERIT DO NOT WARRANT THAT THE
 * FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
 * THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
 * University of Michigan and Merit shall not be liable for
 * any special, indirect, incidental or consequential damages with respect
 * to any claim by Licensee or any third party arising from use of the
 * software. GateDaemon was originated and developed through release 3.0
 * by Cornell University and its collaborators.
 * 
 * Please forward bug fixes, enhancements and questions to the
 * gated mailing list: gated-people@gated.merit.edu.
 * 
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1990,1991,1992,1993,1994,1995 by Cornell University.
 *     All rights reserved.
 * 
 * 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.
 * 
 * GateD is based on Kirton's EGP and UC Berkeley's routing
 * daemon	 (routed).
 * Development of GateD has been supported in part by the
 * National Science Foundation.
 * 
 * ------------------------------------------------------------------------
 * 
 * Portions of this software may fall under the following
 * copyrights:
 * 
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms are
 * permitted provided that the above copyright notice and
 * this paragraph are duplicated in all such forms 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 California, Berkeley.  The name of the
 * University may not be used to endorse or promote
 * products derived from this software without specific
 * prior written permission.  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.
 */


#define INCLUDE_IOCTL
#define INCLUDE_IF
#define INCLUDE_IF_TYPES
#define INCLUDE_MROUTE

#include "include.h"
#include "inet6.h"
#include "inet6_multi.h"
#include "krt_ipv6multi.h"
#ifdef  IPV6_NETINET6
#include <netinet6/icmp6.h>
#else
#include <netinet/icmp6.h>
#endif
#include "icmpv6.h"
#include "mld6.h"

PROTOTYPE(mld6_job, static void, (task_timer *, time_t));
PROTOTYPE(mld6_query, static void, (if_addr *, sockaddr_un *));
PROTOTYPE(mld6_timeout, static void, (task_timer *, time_t));
PROTOTYPE(mld6_control_set, static void, (task *, if_addr *));
PROTOTYPE(mld6_control_reset, static void, (task *, if_addr *));

#define ICMPV6_QUERY_OFFSET	1 /* don't send initial query for 1 sec */

const bits mld6_proto_bits[] = 
{
    {IPV6MULTI_PROTO_MOSPF, "MOSPF(v6)"},
    {IPV6MULTI_PROTO_DVMRP, "DVMRP(v6)"},
    {IPV6MULTI_PROTO_PIMV6, "PIMV6"},
    {IPV6MULTI_PROTO_CBT, "CBT(v6)"},
    {0}
};

/*
 * protocol defaults
 */
int mld6_default_queryinterval;		/* default query interval */
int mld6_default_timeoutinterval;	/* default timeout interval */
static task_timer *mld6_timer_query = NULL; /* To send Host membership query */

/* 
 * local group database
 */

static block_t group_block_index_v6;

/*
 * configuration interface lists 
 */

adv_entry *mld6_int_policy = 0;			/* ICMPv6 group control info */

/* 
 * tracing details
 */

trace *mld6_trace_options = {0};		/* trace flags */

const bits mld6_trace_types[] = {
    {TR_DETAIL, "detail packets"},
    {TR_DETAIL_SEND, "detail send packets"},
    {TR_DETAIL_RECV, "detail recv packets"},
    {TR_PACKET, "packets"},
    {TR_PACKET_SEND, "send packets"},
    {TR_PACKET_RECV, "recv packets"},
    {TR_DETAIL_1, "detail query"},
    {TR_DETAIL_SEND_1, "detail send query"},
    {TR_DETAIL_RECV_1, "detail recv query"},
    {TR_PACKET_1, "query"},
    {TR_PACKET_SEND_1, "send query"},
    {TR_PACKET_RECV_1, "recv query"},
    {TR_DETAIL_2, "detail report"},
    {TR_DETAIL_SEND_2, "detail send report"},
    {TR_DETAIL_RECV_2, "detail recv report"},
    {TR_PACKET_2, "report"},
    {TR_PACKET_SEND_2, "send report"},
    {TR_PACKET_RECV_2, "recv report"},
    {TR_DETAIL_3, "detail leave"},
    {TR_DETAIL_SEND_3, "detail send leave"},
    {TR_DETAIL_RECV_3, "detail recv leave"},
    {TR_PACKET_3, "leave"},
    {TR_PACKET_SEND_3, "send leave"},
    {TR_PACKET_RECV_3, "recv leave"},
    {TR_DETAIL_4, "detail mtrace"},
    {TR_DETAIL_SEND_4, "detail send mtrace"},
    {TR_DETAIL_RECV_4, "detail recv mtrace"},
    {TR_PACKET_4, "mtrace"},
    {TR_PACKET_SEND_4, "send mtrace"},
    {TR_PACKET_RECV_4, "recv mtrace"},
    {0, NULL}
};   

/*
 * Create callback list of routines to be called when an MLD6
 * (Multicast Listener Discovery for IPv6) packet of a particular type
 * is requested. Useful since DVMRP and PIM piggy back on MLD6
 * messages.
 */

struct _mld6_recv_types {
    void (*recv_routine) ();
};

static struct _mld6_recv_types mld6_recv_types[] = { 
    {0},                    /* MLD6_LISTENER_QUERY */
    {0},                    /* MLD6_LISTENER_REPORT */
    {0},                    /* MLD6_LISTENER_DONE */
    {0},                    /* UNUSED */
    {0},                    /* UNUSED */
    {0},                    /* UNUSED */
    {0},                    /* UNUSED */
    {0},                    /* UNUSED */
    {0},                    /* UNUSED */
    {0},                    /* UNUSED */
    {0},                    /* UNUSED */
    {0}
};

/*
 * Request MLD6 packet types. This routine registers a callback
 * function to receive an MLD6 packet of a particular type. The type
 * should match the group_type field in the packet header. The common
 * group_type's are enumerated above.
 *
 * Assumptions: Only one routine protocol needs a particular
 * group_type. May not be true for MLD6_LISTENER_QUERY messages which
 * are used by multicast routing protocol for router discovery.
 */
int
mld6_register_recv(code, callback)
     int code;
     _PROTOTYPE(callback, 
		void,
		(if_addr *,
		 sockaddr_un *,
		 sockaddr_un *,
		 struct mld6_hdr *,
		 size_t));
{
    /*
     * ICMPv6 must be running before any protocol can register receive
     * routines
     */
	
    assert(icmpv6_task);

    trace_tp(icmpv6_task,
	     TR_NORMAL,
	     0,
	     ("mld6_register_recv: registering 0x%0x for packet type 0x%0x", callback, code));

    if (mld6_recv_types[code - MLD6_BASE_TYPE].recv_routine) {
	trace_only_tf(icmpv6_trace_options,
		      0,
		      ("mld6_register_recv: packet type 0x%0x already being received", 
		       code));
	return -1;
    } else {
	mld6_recv_types[code - MLD6_BASE_TYPE].recv_routine = callback;
	return 0;
    }
}

/*
 * No longer need MLD6 packet types. The routing protocol no longer
 * wished to receive packets of this particular type. Protocol may
 * have been disabled on this interface.
 */
int
mld6_unregister_recv __PF1(code, int)
{
    /*
     * ICMPv6 must be running before any protocol can register receive
     * routines
     */

    assert(icmpv6_task);

    trace_tp(icmpv6_task, 
	     TR_NORMAL,
	     0,
	     ("mld6_unregister_recv: removing receive routine for type 0x%0x", code));

    if (mld6_recv_types[code - MLD6_BASE_TYPE].recv_routine) {
	mld6_recv_types[code - MLD6_BASE_TYPE].recv_routine = (void_t) 0;
    } else {
	trace_only_tf(icmpv6_trace_options,
		      0,
		      ("mld6_unregister_recv: packet type 0x%0x not being received", code ));
	return -1;
    }
    return 0;
}

/*
 * Create callback list of routines to be called when a new group is added or
 * an existing group is timed out.
 */
  
typedef struct _mld6_change_notify {
        struct _mld6_change_notify *forw, *back;
        void            (*change_routine) ();
} mld6_change_notify;

static mld6_change_notify mld6_change_head =
{&mld6_change_head, &mld6_change_head};

static block_t  change_block_index_v6;

/*
 * Caller requests notification when a new group is added or an existing
 * group times out.
 */
  
#define CHANGE_LIST(cp, list)   { for (cp = (list)->forw; cp != list; cp = cp->forw)
#define CHANGE_LIST_END(cp, list)       if (cp == list) cp = (mld6_change_notify *) 0; }

int
mld6_register_group_change(callback)
     _PROTOTYPE(callback,
		void,
		(int,
		 if_addr *,
		 u_int32));
{
    mld6_change_notify *cp;

    /*
     * ICMPv6 must be running before any protocol can register group change
     * routines
     */

    assert(icmpv6_task);

    trace_tp(icmpv6_task,
	     TR_NORMAL,
	     0,
	     ("mld6_register_group_change: registering address 0x%0x",
	      callback));

    CHANGE_LIST(cp, &mld6_change_head) {
	if (cp->change_routine == callback) {
	    /*
	     * already registered
	     */
	    trace_tp(icmpv6_task,
		     TR_NORMAL,
		     0,
		     ("mld6_register_group_change: address 0x%0x already registered",
		      callback));
	    return -1;
	}
    } CHANGE_LIST_END(cp, &mld6_change_head);

    cp = (mld6_change_notify *) 
	task_block_alloc(change_block_index_v6);
    cp->change_routine = callback;

    INSQUE(cp, mld6_change_head.back);
    return 0;
}

/*
 * The routing protocol no longer want notified of changes to group
 * membership. Protocol may have been disabled on this interface.
 */
int
mld6_unregister_group_change(callback)
     _PROTOTYPE(callback,
		void,
		(int,
		 if_addr *,
		 u_int32));
{
    mld6_change_notify *cp;

    /*
     * ICMPv6 must be running before any protocol can register group
     * change routines
     */

    assert(icmpv6_task);
    trace_tp(icmpv6_task,
	     TR_NORMAL,
	     0,
	     ("mld6_unregister_group_change: unregistering address 0x%0x",
	      callback));

    CHANGE_LIST(cp, &mld6_change_head) {
	if (cp->change_routine == callback) {
	    /* Found it! */

	    REMQUE(cp);

	    return 0;
	}
    } CHANGE_LIST_END(cp, &mld6_change_head);

    trace_tp(icmpv6_task,
	     TR_NORMAL,
	     0,
	     ("mld6_unregister_group_change: address 0x%0x not registered",
	      callback));
    return -1;
}

/*
 * Send an MLD6 Query to the specified interface.
 * 
 * Called when the query timer expires for all interfaces that this
 * router is the DR for.
 */
static void 
mld6_query __PF2(ifap, if_addr *,
		 dst, sockaddr_un *)
{
    int rc;
    struct mld6_hdr *mld6 = task_get_send_buffer(struct mld6_hdr *);
#ifdef MLD6_LISTENER_QUERY
    mld6->mld6_type = MLD6_LISTENER_QUERY;
#else
    mld6->mld6_type = ICMP6_MEMBERSHIP_QUERY;
#endif
    /*
     * the icmp6_code is useless so far
     */
    mld6->mld6_code = 0;

    mld6->mld6_maxdelay = ICMPV6_MAX_HOST_REPORT_DELAY;

    bzero(&mld6->mld6_addr, sizeof(struct in6_addr));

    mld6->mld6_cksum = 0;
    /* icmpv6_send() will compute the checksum */
    rc = icmpv6_send((struct icmp6_hdr *)mld6, sizeof(struct mld6_hdr),
		     ifap->ifa_addr_local, dst, ifap, MSG_DONTROUTE);
}

/* ARGSUSED */
static void
mld6_timeout __PF2(tip, task_timer *,
		   interval, time_t)
{
    if_addr *ifap = (if_addr *) (tip->task_timer_data);
    struct v6_group_list *gp, *aged;
    struct v6_group_list *list = (struct v6_group_list *) ifap->mld6_if_group_list;
    mld6_change_notify *cp;

    /*
     * search for groups that aged out will all be at beginning of list
     */

    gp = list->forw;
    while (gp != list &&
	   (time_sec - gp->refresh_time) >= mld6_default_timeoutinterval) {
	aged = gp;
	gp = gp->forw;
	REMQUE(aged);
	trace_tp(icmpv6_task,
		 TR_NORMAL,
		 0,
		 ("mld6_timeout: Group %A on interface %A(%s) timed out.",
		  sockbuild_in6(0, (byte*)&aged->group_addr),
		  ifap->ifa_addr_local,
		  ifap->ifa_link->ifl_name)); 
	/*
	 * Notify other protocols of removed group for pruning
	 */
#ifdef  IPV6_ADDR_SCOPE_LINKLOCAL
	if (IPV6_ADDR_MC_SCOPE(&aged->group_addr) > IPV6_ADDR_SCOPE_LINKLOCAL) {
#else
	if (IPV6_ADDR_MC_SCOPE(&gp->group_addr) > MADDR6_SCP_LINK) {
#endif
	    CHANGE_LIST(cp, &mld6_change_head) {
		if (cp->change_routine)
		    (*cp->change_routine) (MLD6_REMOVED,
					   ifap,
					   &aged->group_addr);
	    } CHANGE_LIST_END(cp, &mld6_change_head);
	}
	task_block_free(group_block_index_v6, (void_t) aged);
    }
    /*
     * if there's more entries in the list, reset the timer
     */
    if (list != list->forw) {
	task_timer_set((task_timer *) ifap->mld6_if_timer_timeout,
		       (time_t) 0,
		       mld6_default_timeoutinterval -
		       (time_sec - list->forw->refresh_time));
    }
}

/* ARGSUSED */
static void
mld6_job __PF2(tip, task_timer *,
	       interval, time_t)
{
    int stop_timer = 1;
    register if_addr *ifap;

    IF_ADDR(ifap) {
	struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
	if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {
	    stop_timer = 0;
	    mld6_query(ifap, inet6_addr_allnodes);
	}
    } IF_ADDR_END(ifap);

    if (stop_timer) {
	task_timer_reset(mld6_timer_query);
    }
}

/*
 * Called when an ICMPv6 packet is available for reading.
 */
void
mld6_recv __PF5(tp, task *,
		src, sockaddr_un *,
		dst, sockaddr_un *,
		icmp, struct icmp6_hdr *,
		count, size_t)
{
    if_addr *ifap;
    struct mld6_hdr *mld6 = (struct mld6_hdr *) icmp;

    /* "verify checksum" processing by kernel */

    ifap = if_withindex(task_recv_ifindex, INET6_SCOPE_LINKLOCAL);

    if (!ifap) {
	trace_log_tf(icmpv6_trace_options,
		     0,
		     LOG_ERR,
		     ("mld6_recv: ignoring icmpv6 group msg from remote source: %A", src));
	return;
    }
    if (!IN6_IS_ADDR_LINKLOCAL(&sock2in6(ifap->ifa_addr_local))) {
	/* In case the host doesn't use LL address to do report */
	if_addr	*ifap2;

	IF_ADDR(ifap2) {
	    if ((ifap2->ifa_link == ifap->ifa_link)
		&& (socktype(ifap2->ifa_addr_local) == AF_INET6)
		&& (IN6_IS_ADDR_LINKLOCAL(&sock2in6(ifap2->ifa_addr_local)))) {
		ifap = ifap2;
		break;
	    }
	} IF_ADDR_END(ifap2) ;
    }
    /*
     * If someone has registered for this packet type, call their
     * receive routine.
     */
    if (mld6_recv_types[mld6->mld6_type - MLD6_BASE_TYPE].recv_routine)
	(*mld6_recv_types[mld6->mld6_type - MLD6_BASE_TYPE].recv_routine) (ifap, src, dst, mld6, count);
}

/*
 * Receive Multicast Listener Query Packets
 */
static void
mld6_recv_multicast_listener_query __PF5(ifap, if_addr *,
					 src, sockaddr_un *,
					 dst, sockaddr_un *,
					 mld6, struct mld6_hdr *,
					 count, size_t)
{
	/* Don't know why it does this */
/*	icmpv6_recv_trace(ifap, icmp6, icmp6len); */
}

/*
 * Receive Multicast Listener Report Packets
 */
static void
mld6_recv_multicast_listener_report __PF5(ifap, if_addr *,
					  src, sockaddr_un *,
					  dst, sockaddr_un *,
					  mld6, struct mld6_hdr *,
					  count, size_t)
{
    time_t          timeout;
    struct v6_group_list *gp;
    struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
    struct v6_group_list *list = (struct v6_group_list *) ifap->mld6_if_group_list;     
    mld6_change_notify *cp;

    if (task_recv_dstaddr && 
	!SAME_ADDR6(sock2in6(task_recv_dstaddr), mld6->mld6_addr)) {
	trace_log_tf(mld6_trace_options,
		     0,
		     LOG_WARNING,
		     ("mld6_recv_multicast_listener_report: group %A doesn't match dest ipv6 %A", 
		      sockbuild_in6(0, (byte*)&mld6->mld6_addr),
		      task_recv_dstaddr));
    }
    /*
     * If we are DR on this interface (set by the multicast routing
     * protocol running on this interface) then we will listen to Host
     * Muticast Listener Reports. Otherwise, we ignore them.
     */
    if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {

	/* Search if group is already in database */
	GROUP_LIST(gp, list) {
	    if (SAME_ADDR6(gp->group_addr, mld6->mld6_addr)) {
		/* Found it! */

		REMQUE(gp);
		goto Found;
	    }
	} GROUP_LIST_END(gp, list);

	/* If not, Allocate a block to store this packet */
	gp = (struct v6_group_list *) task_block_alloc(group_block_index_v6); 
	gp->group_addr = mld6->mld6_addr;

	/*
	 * Notify other protocols of new group for grafting
	 */
#ifdef IPV6_ADDR_SCOPE_LINKLOCAL
	if (IPV6_ADDR_MC_SCOPE(&gp->group_addr) > IPV6_ADDR_SCOPE_LINKLOCAL) {
#else
	if (IPV6_ADDR_MC_SCOPE(&gp->group_addr) > MADDR6_SCP_LINK) {
#endif
	    CHANGE_LIST(cp, &mld6_change_head) {
		if (cp->change_routine)
		    (*cp->change_routine) (MLD6_ADDED,
					   ifap,
					   &gp->group_addr);
	    } CHANGE_LIST_END(cp, &mld6_change_head);
	}
Found:
	/* Store as sorted list with oldest first */
	INSQUE(gp, list->back);
                                 
	gp->last_addr = sock2in6(task_recv_srcaddr);
	gp->refresh_time = time_sec;
         
	timeout = mld6_default_timeoutinterval - (time_sec - list->forw->refresh_time);
	/* if no timer running, create one */
	if (!ifap->mld6_if_timer_timeout) {
	    ifap->mld6_if_timer_timeout =  
		(void_t) task_timer_create(icmpv6_task,
					   "Timeout",
					   (flag_t) 0,
					   (time_t) 0,
					   timeout,
					   mld6_timeout,
					   (void_t) ifap);
	}
	/* set existing timer to new time */
	else {
	    /*
	     * No way to reset data without deleting timer and
	     * re-creating it. So for now, just cheat  
	     */
	    ((task_timer *) ifap->mld6_if_timer_timeout)->task_timer_data = (void_t) ifap;
	    task_timer_set((task_timer *) ifap->mld6_if_timer_timeout,               
			   (time_t) 0,
			   timeout); 
	}       
    }
}

/*      
 * Receive Multicast Listener Done
 */
static void              
mld6_recv_multicast_listener_done __PF5(ifap, if_addr *,
					src, sockaddr_un *,
					dst, sockaddr_un *,
					mld6, struct mld6_hdr *,
					count, size_t)
{                                      
    struct v6_group_list *gp, *ggp;
    struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
    struct v6_group_list *list = (struct v6_group_list *) ifap->mld6_if_group_list;

    if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {
	/* Search if group is already in database */

	GROUP_LIST(gp, list) {
	    if (SAME_ADDR6(gp->group_addr, mld6->mld6_addr)) {
		/* Found it! */
		goto Found;
	    }
	} GROUP_LIST_END(gp, list);

	return;
    Found:
     
	REMQUE(gp);

	/*
	 * set timer to go off in ICMPV6_MAX_HOST_REPORT_DELAY
	 * seconds to delete group unless we receive another
	 * membership report before that.
	 */
	gp->refresh_time = time_sec - mld6_default_timeoutinterval + ICMPV6_MAX_HOST_REPORT_DELAY;
	ggp = list->forw;
	while (ggp != list) {
	    if (gp->refresh_time < ggp->refresh_time) {
		INSQUE(gp, ggp->back);
		break;
	    }
	    ggp = ggp->forw;
	}
	/*
	 * reached end without inserting group, stick it at end
	 */
	if (ggp == list) {
	    INSQUE(gp, list->back);
	}
	/*
	 * send a group specific query
	 */
	mld6_query(ifap, sockbuild_in6(0, (byte*)&gp->group_addr));

	/*
	 * reset the timer
	 */
	task_timer_set((task_timer *) ifap->mld6_if_timer_timeout,   
		       (time_t) 0,
		       list->forw->refresh_time +
		       mld6_default_timeoutinterval - time_sec);
    }
}

/*              
 * Enable a group to be received on the specified interface
 */
                
int             
mld6_join __PF2(ifap, if_addr *,
		group, sockaddr_un *)
{
    krt_multicast6_add(group);
    return task_set_option(icmpv6_task, TASKOPTION_GROUP_ADD, ifap, group);
}

/* 
 * Disable a group being received on the specified interface
 */

int
mld6_leave __PF2(ifap, if_addr *,
		 group, sockaddr_un *)
{
    krt_multicast6_delete(group); 
    return task_set_option(icmpv6_task, TASKOPTION_GROUP_DROP, ifap, group);
}

#if notyet			/* Not Supported yet */
static void
icmpv6_recv_mtrace __PF3(ifap, if_addr *,
			 icmpv6, struct icmpv6 *,
			 icmp6len, int)
{
    struct timeval tp;
    struct mtrace_request *mreq;
    struct mtrace_record *mresp;
    sockaddr_un *dst;
    mfc	*mfcp;
    if_addr	*llifp = NULL;	/* if_addr with link-local address */
    if_addr	*nllifp= NULL;  /* if_addr with non-link-local address */
    if_addr	*ifp = NULL;    /* generic if_addr */
    int ours	= FALSE;

    mreq = (struct mtrace_request *)icmpv6;
    /* No link-local information is allowed */
    if (IS_LINKLADDR6(mreq->source)
	|| IS_LINKLADDR6(mreq->dest)
	||(IN6_IS_ADDR_MC_LINKLOCAL(&mreq->group)))
	return;
    mreq->max_resp_delay = htons(ntohs(mreq->max_resp_delay) - 1);
	
    switch (mreq->type) {
    case ICMP6_MTRACE_REQUEST:
	mreq->type = ICMP6_MTRACE_RESPONSE;
	ifp = if_withsubnet(sockbuild_in6(0, (byte*)&mreq->dest));
	if (ifp) {
	    llifp = LLIF_WITHINDEX(ifp->ifa_link->ifl_index);
	    nllifp = IF_WITHINDEX(ifp->ifa_link->ifl_index);
	} else {
	    return;
	}
	if (llifp) {
	    struct ifa_ps	*ips;
	    struct v6_group_list *gp;
	    struct v6_group_list *glist = (struct v6_group_list *)
		llifp->mld6_if_group_list;

	    ips = &llifp->ifa_ps[RTPROTO_ICMPV6];
	    if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {
		GROUP_LIST(gp, glist) {
		    if (SAME_ADDR6(gp->group_addr, mreq->group)) {
			ours = TRUE;
		    }
		} GROUP_LIST_END(gp, glist);
	    }
	}
	if (ours) {
	    /* I am the router that takes care of this dest */
	    goto response;
	} else {
	    return;
	}
    case ICMP6_MTRACE_RESPONSE:
	mresp = (struct mtrace_record*)((byte*)icmpv6
					+icmp6len-sizeof(struct mtrace_record));
	nllifp = IF_WITHINDEX(ifap->ifa_link->ifl_index);
	llifp = LLIF_WITHINDEX(ifap->ifa_link->ifl_index);
	if (!SAME_ADDR6(llifp->ifa_addr_local->g6_addr, mresp->rpf_router))
	    return;
	goto response;
    default:
	return;
    }
 response:
    mfcp = mfc_locate_mfc_v6(sockbuild_in6(0, (byte *)&mreq->group),
			     sockbuild_in6(0, (byte *)&mreq->source));
    if (mfcp) {
	struct ipv6 *ipv6 = task_get_send_buffer(struct ipv6 *);
	struct icmpv6   *pkt = (struct icmpv6 *)(ipv6 + 1);
	struct mtrace_record *new;
	downstream *dpos, *dp;
	int haveit = FALSE;
	int old_use, new_use;

	dpos = mfcp->ds->forw;
	while (dpos != mfcp->ds) {
	    if (!SAME_ADDR6(sock2in6(dpos->ds_addr), 
			    sock2in6(ifap->ifa_addr_local))){     
		dpos = dpos->forw;
	    } else {
		haveit = TRUE;
		break;
	    }
	}
	if (!haveit && !ours) {
	    /* Come in the wrong interface */
	    return;
	}

	/* Update the cache entry */
	old_use = mfcp->mfc_use;
	krt_request_mfc(mfcp);
	new_use = mfcp->mfc_use;
	mfcp->mfc_use = old_use;
	bcopy((void*)icmpv6, (void*)pkt, icmp6len);
	mreq = (struct mtrace_request *)pkt;
	new = (struct mtrace_record *)((char*)pkt + icmp6len);
	gettimeofday(&tp, 0);
	new->arrival = htonl((tp.tv_sec + JAN_1970) << 16) +
	    ((tp.tv_usec >> 4) & 0xffff);
	new->outgoing_intf = nllifp->ifa_addr_local->g6_addr;
	ifp = mfcp->upstream_ifap;
	if (ifp) {
	    upstream *up;
	    if_addr *tmpifp = NULL;

	    nllifp = IF_WITHINDEX(ifp->ifa_link->ifl_index);
	    llifp = LLIF_WITHINDEX(ifp->ifa_link->ifl_index);
	    new->incoming_intf = nllifp->ifa_addr_local->g6_addr;
	    new->rpf_router = mfcp->rpf_addr;
	    new->inpacket = htonl(new_use);
	    new->outpacket = htonl(new_use);
	    new->total = htonl(new_use);
	    new->mrt_proto = mfcp->mfc_proto;
	    new->fwd_hop = 1;
	    new->src_mask = mfcp->mfc_pfx;
	    new->fwd_err = 0;
	    pkt->icmp6_cksum = 0;
	    /* icmpv6_send() will compute the checksum for me */
	    ifp = if_withsubnet(sockbuild_in6(0, (byte*)&mreq->source));
	    if (ifp || (mreq->max_resp_delay == 0)) {
		dst = sockdup(sockbuild_in6(0, (byte*)&mreq->resp_addr));
		up = krt_locate_upstream(dst, mld6_get_ifproto(ifap));
		tmpifp = IF_WITHINDEX(up->ifap->ifa_link->ifl_index);
		icmpv6_send(pkt, icmp6len + sizeof(struct mtrace_record),
			    tmpifp->ifa_addr_local, dst, tmpifp, 0);
	    } else {
		if (!IS_ANYADDR6(mfcp->rpf_addr)) {
		    dst = sockdup(sockbuild_in6(0, (byte*)&mfcp->rpf_addr));
		    icmpv6_send(pkt, icmp6len + sizeof(struct mtrace_record),
				llifp->ifa_addr_local, dst, llifp, MSG_DONTROUTE);
		} else
		    return;
	    }
	    sockfree(dst);
	    dst = (sockaddr_un *)0;
	}
    }	
}
#endif /* Not Supported, yet */
/*
 * Enable DR status on interface. This routine is called by the multicast
 * routing protocol running on this interface. A DR election is performed
 * with other multicast routers on shared lans and if elected, this routine
 * is called to begin sending Host Membership Queries. It also starts
 * maintaining a Local Group Database of Host MLD6 Reports.
 */
void
mld6_enable_dr_status __PF1(ifap, if_addr *)
{
    struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
    struct v6_group_list **listp = (struct v6_group_list **) & ifap->mld6_if_group_list;
    struct v6_group_list *list;

    if (!BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {

	trace_tp(icmpv6_task,
		 TR_STATE,
		 0,
		 ("mld6_enable_dr_status: Elected DR on interface %A(%s)",
		  ifap->ifa_addr_local,
		  ifap->ifa_link->ifl_name));
	BIT_SET(ips->ips_state, IFPS_DR_STATUS);
	list = (struct v6_group_list *) task_block_alloc(group_block_index_v6);
	list->forw = list;
	list->back = list;

	*listp = list;

	/* if no timer running, create one */
	if (!mld6_timer_query) {
	    mld6_timer_query = task_timer_create(icmpv6_task,
						 "Query",
						 (flag_t) 0,
						 mld6_default_queryinterval,
						 ICMPV6_QUERY_OFFSET,
						 mld6_job,
						 (void_t) 0);
	} 
	/* if timer currently inactive, reactivate */
	else if (BIT_TEST(mld6_timer_query->task_timer_flags, TIMERF_INACTIVE)) {
	    task_timer_set(mld6_timer_query,
			   mld6_default_queryinterval,
			   ICMPV6_QUERY_OFFSET);
	} 
	krt_multicast6_add(inet6_addr_allnodes); 
    }
} 

/*                        
 * Disable DR status on interface. If the routing protocol determines this
 * multicast router is no longer DR on this interface, it disables DR status
 * and stops sending Host MLD6 Queries on this interface.
 */
void
mld6_disable_dr_status __PF1(ifap, if_addr *)
{
    struct v6_group_list *gp, *delete;
    struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
    struct v6_group_list *list = (struct v6_group_list *) ifap->mld6_if_group_list;

    if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {
	trace_tp(icmpv6_task,
		 TR_STATE, 
		 0,
		 ("mld6_disable_dr_status: Relinquishing DR status on in terface %A(%s)",
		  ifap->ifa_addr_local,
		  ifap->ifa_link->ifl_name));
 
	BIT_RESET(ips->ips_state, IFPS_DR_STATUS);
   
	/*
	 * stop timer from running
	 */ 
	if (ifap->mld6_if_timer_timeout) {
	    task_timer_reset((task_timer *) ifap->mld6_if_timer_timeout);   
	}
	assert(list);

	gp = list->forw;
	while (gp != list) {
	    delete = gp;
	    gp = gp->forw;
	    REMQUE(delete);
	    task_block_free(group_block_index_v6, (void_t) delete);
	}
	task_block_free(group_block_index_v6, (void_t) list);
	ifap->mld6_if_group_list = (void_t) 0;

	krt_multicast6_delete(inet6_addr_allnodes);
    }
}

/*
 * Since IPv6 Multicast is based on RPF, it is difficult to have more than a
 * single multicast routing protocol running on the same interface at the
 * same time. Therefore, the first protocol to grab the interface wins.
 */

int
mld6_set_ifproto __PF2(ifap, if_addr *,
		       proto, int)
{
    if (!ifap->mld6_if_proto) {
	ifap->mld6_if_proto = (void_t) proto;
	return TRUE;
    }
    trace_log_tf(mld6_trace_options,
		 0,
		 LOG_WARNING,
		 ("mld6_set_ifproto: can't enable proto %s, %s already configured",
		  mld6_proto_bits[proto].t_name,
		  mld6_proto_bits[(int) ifap->mld6_if_proto].t_name));
    return FALSE;
}


void 
mld6_reset_ifproto __PF2(ifap, if_addr *,
			 proto, int)
{
    if ((int) ifap->mld6_if_proto == proto) {
	ifap->mld6_if_proto = (void_t) 0;
    }
}

int
mld6_get_ifproto __PF1(ifap, if_addr *)
{
    /*
     * If the incoming interface is not specified, we assume DVMRP. This
     * is necessary for 3.3 and earlier kernels. It doesn't hurt anything
     * since if it came in on the wrong interface, it will be tossed
     * since the interface won't match.
     */
    if (!ifap)
	return IPV6MULTI_PROTO_DVMRP;
    else
	return (int) ifap->mld6_if_proto;
}

/* 
 * Initialize static variables 
 */
void
mld6_var_init()
{
    mld6_default_queryinterval = 125;
    mld6_default_timeoutinterval = 270; 

}

/* 
 * Initialize ICMPv6 Group Management
 */

static int mld6_mrouting_protos = 0;
static int mld6_assert_count = 0;

/* ARGSUSED */
void
mld6_init()
{
#ifdef MLD6_LISTENER_QUERY
    mld6_register_recv(MLD6_LISTENER_QUERY,
		       mld6_recv_multicast_listener_query);
    mld6_register_recv(MLD6_LISTENER_REPORT,
		       mld6_recv_multicast_listener_report);
    mld6_register_recv(MLD6_LISTENER_DONE,
		       mld6_recv_multicast_listener_done);
#else
    mld6_register_recv(ICMP6_MEMBERSHIP_QUERY,
                       mld6_recv_multicast_listener_query);
    mld6_register_recv(ICMP6_MEMBERSHIP_REPORT,
                       mld6_recv_multicast_listener_report);
    mld6_register_recv(ICMP6_MEMBERSHIP_REDUCTION,
                       mld6_recv_multicast_listener_done);
#endif
#if notyet
    mld6_register_recv(ICMP6_MTRACE_REQUEST,
		       icmpv6_recv_mtrace);
    mld6_register_recv(ICMP6_MTRACE_RESPONSE,
		       icmpv6_recv_mtrace);
#endif /* Not Supported yet */
    group_block_index_v6 = task_block_init(sizeof(struct v6_group_list), "icmpv6_group_list");
    change_block_index_v6 = task_block_init(sizeof(mld6_change_notify), "mld6_change_notify");
}

/*
 * Deal with an interface status change
 */
static void
mld6_ifachange __PF2(tp, task *,
			     ifap, if_addr *)
{
    struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];

    if (socktype(ifap->ifa_addr_local) != AF_INET6) {
	return;
    }
    switch (ifap->ifa_change) {
    case IFC_NOCHANGE:
    case IFC_ADD:
	if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    mld6_control_set(tp, ifap);
	    if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK) ||
		BIT_TEST(ifap->ifa_state, IFS_POINTOPOINT)) {
		BIT_SET(ips->ips_state, IFPS_NOIN);
		BIT_SET(ips->ips_state, IFPS_NOOUT);
	    }
	}
	break;

    case IFC_DELETE:
    case IFC_DELETE | IFC_UPDOWN:

	mld6_control_reset(tp, ifap);
	mld6_disable_dr_status(ifap);
	break;

    default:
	/* Something has changed */

	if (BIT_TEST(ifap->ifa_change, IFC_UPDOWN)) {
	    if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
		/* Down to Up transition */

		mld6_control_set(tp, ifap);
		if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK)) {
		    BIT_SET(ips->ips_state, IFPS_NOIN);
		    BIT_SET(ips->ips_state, IFPS_NOOUT);
		}
	    } else {
		/* UP to DOWN transition */

		mld6_control_reset(tp, ifap);
		mld6_disable_dr_status(ifap);
	    }
	}
	break;
    }
}

#ifdef  USE_ZLIB
#undef fprintf
#define fprintf	gzprintf
#endif
void
mld6_dump __PF2(tp, task *,
		fd, FILE *)
{
    register if_addr *ifap;

    IF_ADDR(ifap) {
	struct v6_group_list *gp;
	struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
	struct v6_group_list *list = 
	    (struct v6_group_list *)ifap->mld6_if_group_list;

	if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {
	    (void)fprintf(fd, "\n\tLocal Group Database for Interface %A(%s)\n",
			  ifap->ifa_addr_local,
			  ifap->ifa_link->ifl_name);
	    fprintf(fd, "\t\tGroup           Last Reported By Refreshed\n");
	    GROUP_LIST(gp, list) {
		fprintf(fd, "\t\t%-15A %-15A   %u seconds ago\n", 
			sockbuild_in6(0, (byte*)&gp->group_addr),
			sockbuild_in6(0, (byte*)&gp->last_addr),
			time_sec - gp->refresh_time);
	    } GROUP_LIST_END(gp, list);
	}
    } IF_ADDR_END(ifap);

    (void)fprintf(fd, "\n");
}
#ifdef  USE_ZLIB
#undef fprintf
#endif

static void
mld6_control_set __PF2(tp, task *,
			       ifap, if_addr *)
{
    struct ifa_ps  *ips = &ifap->ifa_ps[tp->task_rtproto];
    config_entry  **list = config_resolv_ifa(mld6_int_policy,
					     ifap,
					     MLD6_CONFIG_MAX);

    /* Init */
    mld6_control_reset(tp, ifap);

    if (list) {
	int type = MLD6_CONFIG_MAX;
	config_entry *cp;

	/* Fill in the parameters */
	while (--type) {
	    if ((cp = list[type])) {
		switch (type) {
		case MLD6_CONFIG_ENABLE:
		    if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK)) {
			trace_only_tf(icmpv6_trace_options,
				      0,
				      ("mld6_control_set: can't enable icmpv6 group on loopback"));
		    }
		    if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
			BIT_RESET(ips->ips_state, IFPS_NOIN);
			BIT_RESET(ips->ips_state, IFPS_NOOUT);
		    } else {
			trace_only_tf(icmpv6_trace_options ,
				      0,
				      ("mld6_control_set: interface %A(%s) not multicast capable",
				       ifap->ifa_addr_local,
				       ifap->ifa_link->ifl_name));
		    }
		    break;   
   
		case MLD6_CONFIG_DISABLE:
		    BIT_SET(ips->ips_state, IFPS_NOIN);
		    BIT_SET(ips->ips_state, IFPS_NOOUT);
		    break;
		}
	    }       
	}
	config_resolv_free(list, MLD6_CONFIG_MAX);
    }       
}                       

/*      
 * Deal with interface policy
 */
static void
mld6_control_reset __PF2(tp, task *,
				 ifap, if_addr *)
{               
    struct ifa_ps  *ips = &ifap->ifa_ps[tp->task_rtproto];
            
    BIT_RESET(ips->ips_state, IFPS_RESET);
}        
