/*
 * Public Release 3
 * 
 * $Id: pimv6.c,v 1.1.2.1 1999/01/28 17:46:23 wfs Exp $
 */

/*
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1996, 1997 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, UC Berkeley's routing
 * daemon	 (routed), and DCN's HELLO routing Protocol.
 * 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.
 */

/* 
 * Ported by Jong Kann(ISI) and Yixin Jin(UCLA) to IPv6
 */

#define	INCLUDE_IF

#include "include.h"
#include "inet6.h"
#include "inet6_multi.h"
#include "mld6.h"
#include "krt_ipv6multi.h"
#include "targets.h"
#include "pimv6.h"

PROTOTYPE(pimv6_dr_election, static void, (if_addr *));
PROTOTYPE(pimv6_router_refresh, static void, (if_addr *, sockaddr_un *, time_t));
PROTOTYPE(pimv6_router_purge, static void, (if_addr *));
PROTOTYPE(pimv6_router_timeout, static void, (task_timer *, time_t));
PROTOTYPE(pimv6_graft_timeout, static void, (task_timer *, time_t));
PROTOTYPE(pimv6_join_timeout, static void, (task_timer *, time_t));
PROTOTYPE(pimv6_assert_timeout, static void, (task_timer *, time_t));
PROTOTYPE(pimv6_refresh_router_timer, static void, (if_addr *, time_t));
PROTOTYPE(pimv6_group_change, void, (int, if_addr *, struct in6_addr *));
PROTOTYPE(pimv6_send_graft, static void, (if_addr *, struct in6_addr *));
PROTOTYPE(pimv6_send_join_prune_graft, static void, (int, struct in6_addr *));
PROTOTYPE(pimv6_send_prune, static int, (mfc *));
PROTOTYPE(pimv6_queue_prune, static void, (mfc *, prune_list *));
PROTOTYPE(pimv6_clean_mfc, static void, (mfc *));
PROTOTYPE(pimv6_set_mfc_timer, static void, (mfc *, time_t));
PROTOTYPE(pimv6_reset_mfc_timer, static void, (mfc *));
PROTOTYPE(pimv6_mfc_add_ifap, static void, (mfc *, caddr_t));
PROTOTYPE(pimv6_mfc_delete_upifap, static void, (mfc *, caddr_t));
PROTOTYPE(pimv6_mfc_delete_ifap, static void, (mfc *, caddr_t));
PROTOTYPE(pimv6_mfc_delete_prune, static void, (mfc *, caddr_t));
PROTOTYPE(pimv6_recv_join_prune, static void, (if_addr *, struct pimv6hdr *, int));
PROTOTYPE(pimv6_recv_graft, static void, (if_addr *, struct pimv6hdr *, int));
PROTOTYPE(pimv6_recv_graft_ack, static void, (if_addr *, struct pimv6hdr *, int));
PROTOTYPE(pimv6_recv_assert, static void, (if_addr *, struct pimv6hdr *, int));
PROTOTYPE(pimv6_recv_hello, static void, (if_addr *, struct pimv6hdr *, int));
PROTOTYPE(pimv6_send, int, (if_addr *, struct pimv6hdr *, int, sockaddr_un *, int));
PROTOTYPE(pimv6_recv, static void, (task *));

/* Functions used to create encoded addresses */
void enc_eu6_addr(struct eu6_addr *, struct in6_addr *);
void enc_eg6_addr(struct eg6_addr *, u_int8, struct in6_addr *);
void enc_es6_addr(struct es6_addr *, u_int8, struct in6_addr *);

static task *pimv6_task = (task *) 0;
static task_timer *pimv6_timer_hello;	/* to send Hello */

#define	PIMV6_HELLO_OFFSET	1	/* don't send initial hello for 1 sec */

int pimv6_current_status = 0;		/* whether PIM is currently on */
int pimv6_config_status = 0;		/* whether PIM is on in new config */

/* All PIMV6 Routers Group - ff02::D */
#if BYTE_ORDER == LITTLE_ENDIAN
struct in6_addr all_pimv6_routers = {0x000002ff,0x0,0x0,0x0D000000};	
#endif
#if BYTE_ORDER == BIG_ENDIAN
struct in6_addr all_pimv6_routers = {0xff020000,0x0,0x0,0x0D};	
#endif

const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;

/*
 * distinguish different message types for pimv6_send_join_prune_graft()
 */

#define	PIMV6_MSG_JOIN	0
#define	PIMV6_MSG_PRUNE	1
#define	PIMV6_MSG_GRAFT	2

/*
 * pending prune can be caused by waiting hello or waiting join
 * 0 can not be used since it means false for pending
 */
#define PRUNE_WAIT_HELLO	1
#define PRUNE_WAIT_JOIN		2

/*
 * PIMv6 Router list (per interface)
 */

static block_t pimv6_router_block_index;

typedef struct _router_list {
    struct _router_list *dr_forw, *dr_back;	/* DR list */
    struct _router_list *tq_forw, *tq_back;	/* timer queue list */
    sockaddr_un *router_addr;		/* Router address */
    int mode;				/* Sparse or dense */
    time_t hold_time;			/* time to keep router active */
    time_t refresh_time;		/* Time of last refresh */
} router_list;

#define IFPS_NOT_LEAF		IFPS_KEEP1
#define IFPS_PIM_MODE		IFPS_POLICY1
					/* PIMv6 router list(per intf) */
#define pimv6_if_router_list	ifa_ps[RTPROTO_PIMV6].ips_datas[0]
#define pimv6_if_timer_timeout	ifa_ps[RTPROTO_PIMV6].ips_datas[1]
/*
 * Used to scan the pimv6 router list
 */

#define	ROUTER_DR_LIST(gp, list)	{ for (gp = (list)->dr_forw; gp != list; gp = gp->dr_forw)
#define ROUTER_DR_LIST_END(gp, list)	if (gp == list) gp = (router_list *) 0; }
#define	ROUTER_TQ_LIST(gp, list)	{ for (gp = (list)->tq_forw; gp != list; gp = gp->tq_forw)
#define ROUTER_TQ_LIST_END(gp, list)	if (gp == list) gp = (router_list *) 0; }

#define ROUTER_TQ_ENQ(elem, pred) { \
    register router_list *Xe = elem; \
    register router_list *Xp = pred; \
    Xp->tq_forw = (Xe->tq_forw = (Xe->tq_back = Xp)->tq_forw)->tq_back = Xe; \
}

#define ROUTER_TQ_DEQ(elem) { \
    register router_list *Xe = elem; \
    (Xe->tq_back->tq_forw = Xe->tq_forw)->tq_back = Xe->tq_back; \
}

#define ROUTER_DR_ENQ(elem, pred) { \
    register router_list *Xe = elem; \
    register router_list *Xp = pred; \
    Xp->dr_forw = (Xe->dr_forw = (Xe->dr_back = Xp)->dr_forw)->dr_back = Xe; \
}

#define ROUTER_DR_DEQ(elem) { \
    register router_list *Xe = elem; \
    (Xe->dr_back->dr_forw = Xe->dr_forw)->dr_back = Xe->dr_back; \
}

/*
 * Reverse Path Forwarding Lists for forming join/prune/graft messages
 */

typedef struct _rpf_list {
    struct	_rpf_list *forw, *back;
    struct in6_addr rpf_addr;
    struct in6_addr src_addr;
    if_addr	*ifap;
} rpf_list;

static rpf_list rpf_head =
	{ &rpf_head, &rpf_head, IN6ADDR_ANY_INIT, IN6ADDR_ANY_INIT, NULL };

static block_t pimv6_rpf_block_index;

#define	RPF_LIST(gp, list)	{ for (gp = (list)->forw; gp != list; gp = gp->forw)
#define RPF_LIST_END(gp, list)	if (gp == list) gp = (rpf_list *) 0; }

/*
 * Graft Ack List for timing out graft acks and retransmitting graft msgs
 */

typedef struct _graft_list {
    struct	_graft_list *forw, *back;
    struct in6_addr graft_group;
    time_t	graft_time;
    if_addr	*graft_ifap;
    sockaddr_un	graft_dst;
} graft_list;

static graft_list graft_head =
	{ &graft_head, &graft_head };

static block_t pimv6_graft_block_index;

static task_timer *pimv6_timer_graft;	/* for timing out graft acks */

#define	GRAFT_LIST(gp, list)	{ for (gp = (list)->forw; gp != list; gp = gp->forw)
#define GRAFT_LIST_END(gp, list)	if (gp == list) gp = (graft_list *) 0; }

/*
 * Prune List for sending and timing out prunes
 */

static prune_list prune_head =
	{ &prune_head, &prune_head };

static block_t pimv6_prune_block_index;

static task_timer *pimv6_timer_prune;	/* for timing out prunes */

/*
 * Join List for the join delay timer
 */

typedef struct _join_list {
    struct	_join_list *forw, *back;
    mfc		*mfcp;
    if_addr	*ifap;
    time_t	join_time;
} join_list;

static join_list join_head =
	{ &join_head, &join_head };

static block_t pimv6_join_block_index;

static task_timer *pimv6_timer_join;	/* for timing out join requests */

#define	JOIN_LIST(gp, list)	{ for (gp = (list)->forw; gp != list; gp = gp->forw)
#define JOIN_LIST_END(gp, list)	if (gp == list) gp = (join_list *) 0; }

/*
 *  Assert message queue
 */

static assert_list assert_head =
	{ &assert_head, &assert_head };

static block_t pimv6_assert_block_index;

static task_timer *pimv6_timer_assert;

/*
 * MFC timer queue
 */

typedef struct _mfc_list {
    struct	_mfc_list *forw, *back;
    time_t	mfc_timeout;
    mfc		*mfcp;
} mfc_list;

static mfc_list mfc_head =
	{ &mfc_head, &mfc_head };

static block_t pimv6_mfc_block_index;

static task_timer *pimv6_timer_mfc;	/* for purging inactive mfc entries */

#define	PIMV6_MFC_LIST(gp, list)	{ for (gp = (list)->forw; gp != list; gp = gp->forw) 
#define PIMV6_MFC_LIST_END(gp, list) if (gp == list) gp = (mfc_list *) 0; } 


/*
 * protocol defaults
 */

time_t pimv6_default_hellointerval;
time_t pimv6_default_routertimeout;
time_t pimv6_default_prunetimeout;
time_t pimv6_default_inactivitytimeout;
time_t pimv6_default_graftacktimeout;

/*
 * array of preferences for pimv6 asserts
 */

static pref_t pimv6_preference[RTPROTO_MAX];

/*
 * configuration interface lists
 */

adv_entry *pimv6_int_policy = 0;	/* PIMv6 control info */

/*
 * tracing details
 */

trace *pimv6_trace_options = { 0 };	/* Trace flags */

static const flag_t pimv6_trace_masks[] = {
    TR_PIMV6_DETAIL_HELLO,		/* 0 - HELLO */
    TR_PIMV6_DETAIL_REGISTER,		/* 1 - REGISTER */
    TR_PIMV6_DETAIL_REGISTER_STOP,	/* 2 - REGISTER_STOP */
    TR_PIMV6_DETAIL_JOIN_PRUNE,		/* 3 - JOIN_PRUNE */
    TR_PIMV6_DETAIL_BOOTSTRAP,		/* 4 - BOOTSTRAP */
    TR_PIMV6_DETAIL_ASSERT,		/* 5 - ASSERT */
    TR_PIMV6_DETAIL_GRAFT,		/* 6 - GRAFT */
    TR_PIMV6_DETAIL_GRAFT_ACK,		/* 7 - GRAFT_ACK */
    0
} ;

const bits pimv6_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 hello" },
    { TR_DETAIL_SEND_1,	"detail send hello" },
    { TR_DETAIL_RECV_1,	"detail recv hello" },
    { TR_PACKET_1,	"hello" },
    { TR_PACKET_SEND_1,	"send hello" },
    { TR_PACKET_RECV_1,	"recv hello" },
    { TR_DETAIL_2,	"detail register" },
    { TR_DETAIL_SEND_2,	"detail send register" },
    { TR_DETAIL_RECV_2,	"detail recv register" },
    { TR_PACKET_2,	"register" },
    { TR_PACKET_SEND_2,	"send register" },
    { TR_PACKET_RECV_2,	"recv register" },
    { TR_DETAIL_3,	"detail join" },
    { TR_DETAIL_SEND_3,	"detail send join" },
    { TR_DETAIL_RECV_3,	"detail recv join" },
    { TR_PACKET_3,	"join" },
    { TR_PACKET_SEND_3,	"send join" },
    { TR_PACKET_RECV_3,	"recv join" },
    { TR_DETAIL_4,	"detail assert" },
    { TR_DETAIL_SEND_4,	"detail send assert" },
    { TR_DETAIL_RECV_4,	"detail recv assert" },
    { TR_PACKET_4,	"assert" },
    { TR_PACKET_SEND_4,	"send assert" },
    { TR_PACKET_RECV_4,	"recv assert" },
    { TR_DETAIL_5,	"detail graft" },
    { TR_DETAIL_SEND_5,	"detail send graft" },
    { TR_DETAIL_RECV_5,	"detail recv graft" },
    { TR_PACKET_5,	"graft" },
    { TR_PACKET_SEND_5,	"send graft" },
    { TR_PACKET_RECV_5,	"recv graft" },
    { 0, NULL }
};

/* Create encoded unicast address eu6_addr */
void
enc_eu6_addr(struct eu6_addr *buff, struct in6_addr *addr)
{
	buff->addr_family = PIMV6_AF_IP6;
	buff->enc_type = 0;
	buff->u_6 = *addr;
}

/* Create encoded group address eg6_addr */
void
enc_eg6_addr(struct eg6_addr *buff, u_int8 mlen, struct in6_addr *addr)
{
	buff->addr_family = PIMV6_AF_IP6;
	buff->enc_type = 0;
	buff->reserved = 0;
	buff->mask_len = mlen;
	buff->g_6 = *addr;
}

/* Create encoded source address es6_addr */
void
enc_es6_addr(struct es6_addr *buff, u_int8 mlen, struct in6_addr *addr)
{
	buff->addr_family = PIMV6_AF_IP6;
	buff->enc_type = 0;
	buff->flags = 0;
	buff->mask_len = mlen;
	buff->s_6 = *addr;
}

/*
 *	Trace PIMV6 packets
 */
static void
pimv6_trace __PF7(trp, trace *,
		  dir, int,
		  ifap, if_addr *,
		  who, sockaddr_un *,
		  msg, register struct pimv6hdr *,
		  size, register size_t,
		  detail, int)
{
    struct in6_addr src_addr;
    caddr_t cp;
    u_int8 high_flags;
    u_int8 low_flags;

    if (dir) {
	/* Trace packet transmission */

	tracef("PIMV6 %sSENT %A -> %#A ",
	       dir > 0 ? "" : "*NOT* ",
	       ifap ? ifap->ifa_addr_local : sockbuild_str(""),
	       who);
    } else {
	/* Trace packet reception */
	tracef("PIMV6 RECV %#A ", who);
	if (task_recv_dstaddr) {
	    /* Some systems report the destination address */
	    tracef("-> %A ", task_recv_dstaddr);
	}
    }
    switch (msg->type) {
    case PIMV6_HELLO:
	{
	    struct pimv6hello *pq = (struct pimv6hello *) (msg + 1);

	    tracef("Hello: %s Mode ", "Dense");
	    if (detail) {
		tracef("Vers %d Holdtime %u Len %d",
		       (msg->version),
		       ntohl(pq->hello_value),
		       size);
	    }
	}
	break;
    case PIMV6_REGISTER:
	tracef("Register: ");
	break;
    case PIMV6_REGISTER_STOP:
	tracef("Register-Stop: ");
	break;
    case PIMV6_BOOTSTRAP:
	tracef("Bootstrap: ");
	break;
    case PIMV6_ASSERT:
	{
	    struct pimv6assert *pas = (struct pimv6assert *) (msg + 1);
	    tracef("Assert: Group %A Source %A ",
		   sockbuild_in6(0, (byte *)&(pas->group_addr).g_6),
		   sockbuild_in6(0, (byte *)&(pas->src_addr).u_6));
	    if (detail) {
		tracef("Vers %d RP %d Pref %u Metric %u Len %d",
		       (msg->version),
		       BIT_TEST(ntohl(pas->preference), PIMV6_ASSERT_RPBIT),
		       ntohl(pas->preference) & 0x7fffffff,
		       ntohl(pas->metric),
		       size);
	    }
	    break;
	}
    case PIMV6_JOIN_PRUNE:
    case PIMV6_GRAFT:
    case PIMV6_GRAFT_ACK:
	{
	    int i, j;
	    struct pimv6joinhdr *pjh = (struct pimv6joinhdr *) (msg + 1);
	    struct pimv6group *pg = (struct pimv6group *) (pjh + 1);

	    switch(msg->type) {
	    case PIMV6_JOIN_PRUNE:
		tracef("Join/Prune: ");
		break;
	    case PIMV6_GRAFT:
		tracef("Graft: ");
		break;
	    case PIMV6_GRAFT_ACK:
		tracef("Graft-Ack: ");
		break;
	    }
	    if (detail) {
		tracef("Vers %d Upstream %A Holdtime %u Groups %d ",
		       (msg->version),
		       sockbuild_in6(0, (byte *)&(pjh->rpf_addr.u_6)),
		       ntohs(pjh->holdtime),
		       pjh->num_groups);

		trace_only_tf(trp,
			      0,
			      (NULL));
		for (i = 0; i < pjh->num_groups; i++) {
		    cp = (caddr_t) &pg->source_data[0];

		    tracef("Group %A ",
			   sockbuild_in6(0,(byte *)&(pg->group_addr.g_6)));
		    trace_only_tf(trp,
				  0,
				  (NULL));
		    if (pg->num_join) {
			tracef("Join Sources: %d", ntohs(pg->num_join));
			trace_only_tf(trp,
				      0,
				      (NULL));
			for (j = 0; j < ntohs(pg->num_join); j++) {
				/* Skip addr_family */
			    cp++;
				/* Skip enc_type */
			    cp++;
			    high_flags = (u_int8) *cp++;
			    low_flags = (u_int8) *cp++;
			    bcopy(cp, (caddr_t) &src_addr, sizeof(struct in6_addr));
			    tracef("Source %A Masklen %d flags (",
				   sockbuild_in6(0, (byte *)&src_addr),
				   low_flags);
			    if (BIT_TEST(high_flags, PIMV6_SOURCE_S_BIT))
				tracef("S");
			    if (BIT_TEST(high_flags, PIMV6_SOURCE_W_BIT))
				tracef("W");
			    if (BIT_TEST(high_flags, PIMV6_SOURCE_R_BIT))
				tracef("R");
			    cp += sizeof(struct in6_addr);
			    tracef(")");
			    trace_only_tf(trp,
					  0,
					  (NULL));
			}
		    }
		    if (pg->num_prune) {
			tracef("Prune Sources: %d", ntohs(pg->num_prune));
			trace_only_tf(trp,
				      0,
				      (NULL));
			for (j = 0; j < ntohs(pg->num_prune); j++) {
				/* Skip addr_family */
			    cp++;
				/* Skip enc_type */
			    cp++;
			    high_flags = (u_int8) *cp++;
			    low_flags = (u_int8) *cp++;

			    bcopy(cp, (caddr_t) &src_addr, sizeof(struct in6_addr));
			    tracef("Source %A Masklen %d flags (",
				   sockbuild_in6(0, (byte *)&src_addr),
				   low_flags);
			    if (BIT_TEST(high_flags, PIMV6_SOURCE_S_BIT))
				tracef("S");
			    if (BIT_TEST(high_flags, PIMV6_SOURCE_W_BIT))
				tracef("W");
			    if (BIT_TEST(high_flags, PIMV6_SOURCE_R_BIT))
				tracef("R");
			    cp += sizeof(struct in6_addr);
			    tracef(")");
			    trace_only_tf(trp,
					  0,
					  (NULL));
			}
		    }
		    pg = (struct pimv6group *) cp;
		}
	    } else {
		tracef("Vers %d Upstream %A Groups %d",
		       (msg->version),
		       sockbuild_in6(0, (byte *)&(pjh->rpf_addr).u_6),
		       pjh->num_groups);
	    }
	    break;
	}
    }

    trace_only_tf(trp, 0, (NULL));
}

/*
 * Send a PIMV6 Hello to the specified interface.
 *
 */

static void
pimv6_send_hello __PF1(ifap, if_addr *)
{
    int rc = 0;
    size_t len;
    struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_PIMV6];
    struct pimv6hdr *pkt = task_get_send_buffer(struct pimv6hdr *);
    struct pimv6hello *pq = (struct pimv6hello *) (pkt + 1);

    pkt->version = PIMV6_VERSION;
    pkt->type    = PIMV6_HELLO;
    pkt->reserved = 0;

    pq->hello_type = htons(0x0001);	/* only type 1 is defined */
    pq->hello_len = htons(0x0002);	/* type 1 had length 2 */
    pq->hello_value = htonl(pimv6_default_routertimeout);

    /* Calculate packet checksum */
    len = sizeof(struct pimv6hdr) + sizeof(struct pimv6hello);
    pkt->cksum = 0;
    pkt->cksum = inet_cksum((void_t) pkt, len);

	/* SHOULD use link-local address for inter-router communication */
    if (IN6_IS_ADDR_LINKLOCAL(sock2in6(ifap->ifa_addr_local))) {
	rc = pimv6_send(ifap, pkt, len, sockbuild_in6(0, (byte *)&all_pimv6_routers), TRUE);
    } 

    /* Should we trace this packet? */

    if (TRACE_PACKET_SEND_TP(pimv6_task,
			     pkt->type,
			     PIMV6_HELLO,
			     pimv6_trace_masks)) {

	pimv6_trace(pimv6_task->task_trace,
		    rc,
		    ifap,
		    sockbuild_in6(0,(byte *)&all_pimv6_routers),
		    pkt,
		    len,
		    TRACE_DETAIL_SEND_TP(pimv6_task,
					 pkt->type,
					 PIMV6_CANDIDATE_RP_ADVERTISEMENT,
					 pimv6_trace_masks));
    }
}

/*ARGSUSED*/
static void
pimv6_graft_timeout __PF2(tip, task_timer *,
			  interval, time_t)
{
    graft_list	*lp, *aged;
    group_node	*gp;

    lp = graft_head.forw;
    while(lp != &graft_head && lp->graft_time <= time_sec) {

	trace_tp(pimv6_task,
		 TR_TIMER,
		 0,
		 ("pimv6_graft_timeout: Graft sent Upstream to %A for Group %A timed out before acknowledged",
		  &lp->graft_dst, sockbuild_in6(0, (byte *)&lp->graft_group)));

	gp = mfc_locate_group_v6(sockbuild_in6(0,(byte *)&(lp->graft_group)));
	if (gp) {
	    /* 
	     * It is possible that this timeout occurs after all MFCs with
	     * this group have been deleted
	     */
	    gp->graft_pending = FALSE;
	    pimv6_send_graft(lp->graft_ifap, &lp->graft_group);
	}

	aged = lp;
	lp = lp->forw;
	REMQUE(aged);
	task_block_free(pimv6_graft_block_index, (void_t) aged);
    }
    if (graft_head.forw != &graft_head) {
	task_timer_set(pimv6_timer_graft,
		       (time_t) 0,
		       graft_head.forw->graft_time - time_sec);
    }
}

static void
pimv6_graft_add __PF3(ifap, if_addr *,
		      group, struct in6_addr *,
		      dst, sockaddr_un *)
{
    graft_list *new = (graft_list *) task_block_alloc(pimv6_graft_block_index);
    /*
     * Insert at end of timer queue
     */
    new->graft_group = *group;
    new->graft_ifap = ifap;
    new->graft_time = time_sec + pimv6_default_graftacktimeout;
    new->graft_dst = *dst;

    INSQUE(new, graft_head.back);

    /* if no timer running, create one */

    if (!pimv6_timer_graft) {
	pimv6_timer_graft = task_timer_create(pimv6_task,
					      "GraftAck",
					      (flag_t) 0,
					      (time_t) 0,
					      pimv6_default_graftacktimeout,
					      pimv6_graft_timeout,
					      (void_t) 0);
    }
    /* if timer currently inactive, reactivate */
    else if (BIT_TEST(pimv6_timer_graft->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_set(pimv6_timer_graft,
		       (time_t) 0,
		       graft_head.forw->graft_time - time_sec);
    }
}

/* ARGSUSED */
static void
pimv6_scan_mfc_rpf  __PF2(mfcp, mfc *,
			  data, caddr_t)
{
    router_list *list = (router_list *) mfcp->upstream_ifap->pimv6_if_router_list;
    router_list *lp;
    rpf_list *rp, *new;
    upstream *up;
    if_addr	*ifap;

    /*
     * Don't send prunes or grafts to sources on local networks.
     */
    ifap = if_withsubnet(&mfcp->mfc_src);
    if (ifap)
	ifap = if_withindex(ifap->ifa_link->ifl_index, INET6_SCOPE_LINKLOCAL);
    if (ifap && (mfcp->upstream_ifap == ifap))
	return;
		
    new = (rpf_list *) task_block_alloc(pimv6_rpf_block_index);
    new->src_addr = sock2in6(&mfcp->mfc_src);
    new->ifap = mfcp->upstream_ifap;
    /*
     * Check if we received an assert,
     * if so, grafts, prunes, and joins go to the winner.
     */
    if (!SAME_ADDR6(mfcp->rpf_addr, in6addr_any)) {
	new->rpf_addr = mfcp->rpf_addr;
    } else {
	/*
	 * Could check to make sure upstream interfaces still
	 * match, but we'll do this later.
	 */
	up = krt_locate_upstream(&(mfcp->mfc_src), IPV6MULTI_PROTO_PIMV6);
	if(up && up->nbr)
	    new->rpf_addr = sock2in6(up->nbr);
	else{
	    tracef("May set RPF as all 0's\n");
	    new->rpf_addr = in6addr_any;
	}
    }

    /*
     * Next look in the pimv6 router list to see if this upstream
     * neighbor is configured for Dense or Sparse Mode.
     * If Dense mode, insert it in the list,
     * If Sparse mode, ignore it for now.
     */
    if(list) {
	ROUTER_DR_LIST(lp, list) {
	    if (SAME_ADDR6(sock2in6(lp->router_addr), new->rpf_addr)) {
		rp = rpf_head.forw;
		while(rp != &rpf_head &&
		      LESS_ADDR6(new->rpf_addr, rp->rpf_addr)) {
		    rp = rp->forw;
		}
		if (rp == &rpf_head) {
		    INSQUE(new, rp->back);
		} else {
		    INSQUE(new, rp);
		}
		return;
	    }
	} ROUTER_DR_LIST_END(lp, list);
    }
    /*
     * if we got this far, then we never found the upstream
     * pimv6 router. This means the upstream router is not
     * sending pimv6 queries. Log it and continue.
     */
    trace_log_tf(pimv6_trace_options,
		 0,
		 LOG_WARNING,
		 ("pimv6_scan_mfc_rpf: Upstream router %A not running PIMV6",
		  sockbuild_in6(0, (byte *)&new->rpf_addr)));

    task_block_free(pimv6_rpf_block_index, (void_t) new);
}

static void
pimv6_rpf_free __PF0(void)
{
    rpf_list	*rp, *old_rp;

    /*
     * Free up rpf list elements
     */

    rp = rpf_head.forw;
    while(rp != &rpf_head) {
	old_rp = rp;
	rp = rp->forw;
	REMQUE(old_rp);
	task_block_free(pimv6_rpf_block_index, (void_t) old_rp);
    }
}

/*
 * Send a PIMV6 Assert out the specified interface
 */

static void
pimv6_send_assert __PF3(ifap, if_addr *,
			group, struct in6_addr *,
			source, struct in6_addr *)
{
    int rc;
    size_t len;
    upstream *up = krt_locate_upstream(sockbuild_in6(0, (byte *)source),
				       IPV6MULTI_PROTO_PIMV6);
    struct pimv6hdr *pkt = task_get_send_buffer(struct pimv6hdr *);
    struct pimv6assert *pas = (struct pimv6assert *) (pkt + 1);

    pkt->version  = PIMV6_VERSION;
    pkt->type     = PIMV6_ASSERT;
    pkt->reserved = 0;

    enc_eg6_addr(&(pas->group_addr),PIMV6_MASK_LEN,group);
    enc_eu6_addr(&(pas->src_addr),source);
    pas->preference = htonl(pimv6_preference[up->protocol] & 0x7fffffff);
    pas->metric = htonl(up->metric);

    /* Calculate packet checksum */
    len = sizeof(struct pimv6hdr) + sizeof(struct pimv6assert);
    pkt->cksum = 0;
    pkt->cksum = inet_cksum((void_t) pkt, len);

    rc = pimv6_send(ifap, pkt, len, sockbuild_in6(0, (byte *)&all_pimv6_routers), FALSE);

    /* Should we trace this packet? */

    if (TRACE_PACKET_SEND_TP(pimv6_task,
			     pkt->type,
			     PIMV6_ASSERT,
			     pimv6_trace_masks)) {

	pimv6_trace(pimv6_task->task_trace,
		    rc,
		    ifap,
		    sockbuild_in6(0,(byte *)&all_pimv6_routers),
		    pkt,
		    len,
		    TRACE_DETAIL_SEND_TP(pimv6_task,
					 pkt->type,
					 PIMV6_CANDIDATE_RP_ADVERTISEMENT,
					 pimv6_trace_masks));
    }
}

/*ARGSUSED*/
static void
pimv6_assert_timeout  __PF2(tip, task_timer *,
			    interval, time_t)
{
    assert_list	*pas, *aged;

    pas = assert_head.forw;
    while(pas != &assert_head && (time_sec >= pas->assert_time)) {
	aged = pas;
	pas = pas->forw;
	REMQUE(aged);

	/*
	 * If no other asserts received before this timeout,
	 * set the RPF address to be the first assert we
	 * received.
	 */
	    
	aged->mfcp->rpf_addr = aged->rpf_addr;
	aged->mfcp->pimv6_assert = (assert_list *) 0;
	task_block_free(pimv6_assert_block_index, (void_t) aged);
    }
    if (assert_head.forw != &assert_head) {
	task_timer_set(pimv6_timer_assert,
		       (time_t) 0,
		       assert_head.forw->assert_time - time_sec);
    }
}


/*
 * Send a PIMV6 Graft for specified group to the specified interface.
 */

static void
pimv6_send_graft __PF2(join_ifap, if_addr *,
		       group, struct in6_addr *)
{
    struct in6_addr last_rpf;
    rpf_list	*rp;
    group_node	*gp = mfc_locate_group_v6(sockbuild_in6(0,(byte *)group));

    /*
     * Don't want to flood the network with graft messages
     */
    assert(gp);
    if (!gp || gp->graft_pending) {
	return;
    }

    /*
     * Only need to send grafts for sources which we already have state
     * Loop through existing sources and coallate rpf neighbors
     * This builds the rpf list.
     */
    
    mfc_source_visit_v6(gp, pimv6_scan_mfc_rpf, (caddr_t) 0);

    if (rpf_head.forw != &rpf_head) {

	pimv6_send_join_prune_graft(PIMV6_MSG_GRAFT, group);
	/*
	 * Insert each rpf_addr in Graft Ack List and reset timer
	 * if necessary. Only done once per rpf_addr.
	 */
	last_rpf = in6addr_any;
	RPF_LIST(rp, &rpf_head) {
	    if (!(SAME_ADDR6(rp->rpf_addr, last_rpf))) {

		pimv6_graft_add(join_ifap, group,
				sockbuild_in6(0, (byte *)&rp->rpf_addr));

		last_rpf = rp->rpf_addr;
	    }
	} RPF_LIST_END(rp, &rpf_head);

	/*
	 * Free up rpf list elements
	 */

	pimv6_rpf_free();

	gp->graft_pending = TRUE;
    }
}

/*
 * Send an PIMV6 message to the specified interface. 
 */

int
pimv6_send __PF5(ifap, if_addr *,
		 pkt, struct pimv6hdr *,
		 pktlen, int,
		 dst, sockaddr_un *,
		 loop, int)
{
    int             rc;
    static if_addr *last_ifap;

    if (IN6_IS_ADDR_MULTICAST(&sock2in6(dst))) {
	(void) task_set_option(pimv6_task, TASKOPTION_MULTI_TTL, 1);

	if (last_ifap != ifap)
	    (void) task_set_option(pimv6_task,
				   TASKOPTION_MULTI_IF,
				   last_ifap = ifap);
	if (task_set_option(pimv6_task, TASKOPTION_MULTI_LOOP,
			    loop) < 0) 
	    task_quit(errno);
    }

    rc = task_send_message(pimv6_task, (void_t) pkt, pktlen,
			   0, dst, ifap);

    return rc;
}

static void
pimv6_send_join_prune_graft __PF2(msgtype, int,
				  group, struct in6_addr *)
{
    int		rc;
    struct in6_addr last_rpf;
    size_t	len;
    struct	pimv6hdr *pkt = task_get_send_buffer(struct pimv6hdr *);
    struct	pimv6joinhdr *pjh = (struct pimv6joinhdr *) (pkt + 1);
    struct	pimv6group *pg = (struct pimv6group *) (pjh + 1);
    caddr_t	cp = (caddr_t) &pg->source_data[0];
    if_addr	*ifap = 0;
    rpf_list	*rp;
    sockaddr_un	*dst = 0;

    pkt->version = PIMV6_VERSION;
    pkt->reserved = 0;
    switch (msgtype) {
    case PIMV6_MSG_JOIN:
    case PIMV6_MSG_PRUNE:
	pkt->type = PIMV6_JOIN_PRUNE;
	break;
    case PIMV6_MSG_GRAFT:
	pkt->type = PIMV6_GRAFT;
	break;
    }

    pjh->reserved = 0;
    pjh->holdtime = htons(pimv6_default_prunetimeout);
    pjh->num_groups = 1;

    enc_eg6_addr(&(pg->group_addr), PIMV6_MASK_LEN, group);
    pg->num_join = pg->num_prune = 0;

    /*
     * Should be at least one rpf entry in the list
     */
    assert(rpf_head.forw != &rpf_head);

    last_rpf = rpf_head.forw->rpf_addr;
    RPF_LIST(rp, &rpf_head) {

	if (SAME_ADDR6(last_rpf, rp->rpf_addr)) {
	    /*
	     * add source to this message
	     */
	    switch (msgtype) {
	    case PIMV6_MSG_JOIN:
	    case PIMV6_MSG_GRAFT:
		pg->num_join++;
		break;
	    case PIMV6_MSG_PRUNE:
		pg->num_prune++;
		break;
	    }
	    ifap = rp->ifap;

	    /*
	     * set addr_family and enc_type
	     */
	    *cp++ = PIMV6_AF_IP6;
	    *cp++ = 0;

	    /*
	     * reset SWR flags in source
	     */
	    *cp = 0;
	    BIT_RESET(*cp,   PIMV6_SOURCE_S_BIT);
	    BIT_RESET(*cp,   PIMV6_SOURCE_W_BIT);
	    BIT_RESET(*cp++, PIMV6_SOURCE_R_BIT);

	    /*
	     * mask len is 128 bits for IPv6
	     */
	    *cp++ = PIMV6_MASK_LEN;

	    /*
	     * set source address in source
	     */

	    bcopy((caddr_t) &rp->src_addr, cp, sizeof(struct in6_addr));

	    cp += sizeof(struct in6_addr);


	} else {
	    /*
	     * send this message and begin new one
	     */
	    enc_eu6_addr(&(pjh->rpf_addr), &last_rpf);
	    pg->num_join = htons(pg->num_join);
	    pg->num_prune = htons(pg->num_prune);

	    /* Calculate packet checksum */
	    len = cp - (caddr_t) pkt;
	    pkt->cksum = 0;
	    pkt->cksum = inet_cksum((void_t) pkt, len);

	    switch (msgtype) {
	    case PIMV6_MSG_JOIN:
	    case PIMV6_MSG_PRUNE:
		dst = sockbuild_in6(0, (byte *)&all_pimv6_routers);
		break;
	    case PIMV6_MSG_GRAFT:
		dst = sockbuild_in6(0, (byte *)&last_rpf);
		break;
	    default:
		assert(0);
	    }
	    if (ifap) {
		rc = pimv6_send(ifap, pkt, len, dst, FALSE);

		/* Should we trace this packet? */

		if (TRACE_PACKET_SEND_TP(pimv6_task,
					 pkt->type,
					 PIMV6_JOIN_PRUNE,
					 pimv6_trace_masks)) {

		    pimv6_trace(pimv6_task->task_trace,
				rc,
				ifap,
				dst,
				pkt,
				len,
				TRACE_DETAIL_SEND_TP(pimv6_task,
						     pkt->type,
						     PIMV6_CANDIDATE_RP_ADVERTISEMENT,
						     pimv6_trace_masks));
		}
	    }
	    /*
	     *  now start the next message
	     */
	    switch (msgtype) {
	    case PIMV6_MSG_JOIN:
	    case PIMV6_MSG_GRAFT:
		pg->num_join = 1;
		break;
	    case PIMV6_MSG_PRUNE:
		pg->num_prune = 1;
		break;
	    default:
		assert(0);
	    }
	    last_rpf = rp->rpf_addr;

	    cp = (caddr_t) &pg->source_data[0];

	    /*
	     * set addr_family and enc_type
	     */
	    *cp++ = PIMV6_AF_IP6;
	    *cp++ = 0;

	    /*
	     * reset SWR flags in source
	     */
	    *cp = 0;
	    BIT_RESET(*cp,   PIMV6_SOURCE_S_BIT);
	    BIT_RESET(*cp,   PIMV6_SOURCE_W_BIT);
	    BIT_RESET(*cp++, PIMV6_SOURCE_R_BIT);

	    /*
	     * mask len is 128 bits for IPv6
	     */
	    *cp++ = PIMV6_MASK_LEN;

	    /*
	     * set source address in source
	     */

	    bcopy((caddr_t) &rp->src_addr, cp, sizeof(struct in6_addr));

	    cp += sizeof(struct in6_addr);

	}
    } RPF_LIST_END(rp, &rpf_head);

    /*
     * send this last (maybe only) graft message
     */
    enc_eu6_addr(&(pjh->rpf_addr), &last_rpf);
    pg->num_join = htons(pg->num_join);
    pg->num_prune = htons(pg->num_prune);

    /* Calculate packet checksum */
    len = cp - (caddr_t) pkt;
    pkt->cksum = 0;
    pkt->cksum = inet_cksum((void_t) pkt, len);

    if (ifap) {
	switch (msgtype) {
	case PIMV6_MSG_JOIN:
	case PIMV6_MSG_PRUNE:
	    dst = sockbuild_in6(0, (byte *)&all_pimv6_routers);
	    break;
	case PIMV6_MSG_GRAFT:
	    dst = sockbuild_in6(0, (byte *)&last_rpf);
	    break;
	default:
	    assert(0);
	}
	rc = pimv6_send(ifap, pkt, len, dst, FALSE);

	/* Should we trace this packet? */

	if (TRACE_PACKET_SEND_TP(pimv6_task,
				 pkt->type,
				 PIMV6_JOIN_PRUNE,
				 pimv6_trace_masks)) {

	    pimv6_trace(pimv6_task->task_trace,
			rc,
			ifap,
			dst,
			pkt,
			len,
			TRACE_DETAIL_SEND_TP(pimv6_task,
					     pkt->type,
					     PIMV6_CANDIDATE_RP_ADVERTISEMENT,
					     pimv6_trace_masks));
	}
    }
}

static void
pimv6_router_detect  __PF1(ifap, if_addr *)
{
    int rc = 0;
    struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_PIMV6];
    router_list *list = (router_list *) ifap->pimv6_if_router_list;
    router_list *gp;

    /*
     * List will contain at least our own address. If there is
     * more than one, other routers exist and it is not a leaf.
     */
    if (list) {
	gp = list->dr_forw;
	while(gp != list && rc < 2) {
	    rc++;
	    gp = gp->dr_forw;
	}
	rc--;
    }
    if (rc) {
	/*
	 * If no longer a leaf network, then add interface
	 * to existing forwarding cache entries
	 */
	if (!BIT_TEST(ips->ips_state, IFPS_NOT_LEAF)) {
	    mfc_visit_v6(pimv6_mfc_add_ifap, (caddr_t) ifap);
	    BIT_SET(ips->ips_state, IFPS_NOT_LEAF);
	}
    } else {
	/* 
	 * Now, it becomes a leaf router, then delete interface
	 * from existing forwarding cache entries
	 */
	if (BIT_TEST(ips->ips_state, IFPS_NOT_LEAF)) {
	    mfc_visit_v6(pimv6_mfc_delete_ifap, (caddr_t) ifap);
	    mfc_visit_v6(pimv6_mfc_delete_prune, (caddr_t) ifap);
	    BIT_RESET(ips->ips_state, IFPS_NOT_LEAF);
	}
    }
}

/*ARGSUSED*/
static void
pimv6_hello_job __PF2(tip, task_timer *,
		      interval, time_t)
{
    register if_addr *ifap;

    IF_ADDR(ifap) {
	struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_PIMV6];

	if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST) &&
	    BIT_TEST(ifap->ifa_state, IFS_UP) &&
	    BIT_TEST(ifap->ifa_state, IFS_BROADCAST | IFS_POINTOPOINT) &&
	    !BIT_TEST(ips->ips_state, IFPS_NOOUT)) {

	    if (mld6_get_ifproto(ifap) == IPV6MULTI_PROTO_PIMV6) 
		/* SHOULD use link-local address */
		if (IS_LINKLADDR6(sock2in6(ifap->ifa_addr_local)))
		    pimv6_send_hello(ifap);
	}
    } IF_ADDR_END(ifap) ;
}

/*ARGSUSED*/
static void
pimv6_router_timeout  __PF2(tip, task_timer *,
			    interval, time_t)
{
    if_addr *ifap = (if_addr *)(tip->task_timer_data);
    router_list *gp, *aged;
    router_list *list =  (router_list *) ifap->pimv6_if_router_list;

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

    gp = list->tq_forw;
    while(gp != list && (time_sec - gp->refresh_time) >= gp->hold_time) {
	aged = gp;
	gp = gp->tq_forw;
	ROUTER_TQ_DEQ(aged);
	ROUTER_DR_DEQ(aged);
	trace_tp(pimv6_task,
		 TR_TIMER,
		 0,
		 ("pimv6_router_timeout: PIMV6 Router %A on interface %A(%s) timed out.",
		  aged->router_addr,
		  ifap->ifa_addr_local,
		  ifap->ifa_link->ifl_name));
	task_block_free(pimv6_router_block_index, (void_t) aged);
    }

    pimv6_refresh_router_timer(ifap,
			       list->tq_forw->hold_time - (time_sec - list->tq_forw->refresh_time));

    /*       
     * update leaf status   
     */      
    pimv6_router_detect(ifap);
    
    /*
     * perform Designated Router Election
     * and update interface status if necessary
     */
    pimv6_dr_election(ifap);
}

static void
pimv6_commence_hello __PF1(ifap, if_addr *)
{
    router_list **listp = (router_list **) &ifap->pimv6_if_router_list;
    router_list *list;

    if (!*listp) {
	/* This node is dummy, only show the end of the list */
	list = (router_list *) task_block_alloc(pimv6_router_block_index);
	assert(list);
	list->dr_forw = list;
	list->dr_back = list;
	list->tq_forw = list;
	list->tq_back = list;

	*listp = list;
    }
    /* if no timer running, create one */
    if (!pimv6_timer_hello) {
	pimv6_timer_hello = task_timer_create(pimv6_task,
					      "Hello",
					      (flag_t) 0,
					      pimv6_default_hellointerval,
					      PIMV6_HELLO_OFFSET,
					      pimv6_hello_job,
					      (void_t) 0);
    }
    /* if timer currently inactive, reactivate */
    else if (BIT_TEST(pimv6_timer_hello->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_set(pimv6_timer_hello,
		       pimv6_default_hellointerval,
		       PIMV6_HELLO_OFFSET);
    }
}

static void
pimv6_mfc_check_use __PF1(mfcp, mfc *)
{

    if ((mfcp->mfc_lastuse == mfcp->mfc_use) || (mfcp->ds_count == 0)) { 
	/*
	 * This MFC is not used for 3 minutes, delete it Or this MFC
	 * is negative cache, delete it, which means something goes
	 * wrong in the implementation, most likely the downstream(me)
	 * sending prune failed, e.g. PRUNE packet sent throught
	 * tunnel was lost.  
	 */
	trace_tp(pimv6_task,
		 TR_TIMER,
		 0,
		 ("pimv6_mfc_check_use: MFC inactivity timeout: group %A source %A",
		  &mfcp->mfc_group->group_key,
		  &mfcp->mfc_src));
	pimv6_clean_mfc(mfcp);
	pimv6_reset_mfc_timer(mfcp);
	mfc_source_unlink_unicast_v6(pimv6_task, mfcp);
	(void) krt_delete_cache(&mfcp->mfc_group->group_key,
				&mfcp->mfc_src);
	mfc_delete_node_v6(mfcp);
	return;
    }
    if (BIT_TEST(mfcp->mfc_proto, IPV6MULTI_BIT(IPV6MULTI_PROTO_PIMV6))) {
	pimv6_set_mfc_timer(mfcp, pimv6_default_inactivitytimeout);
    }
}

/*ARGSUSED*/
static void
pimv6_mfc_timeout __PF2(tip, task_timer *,
			interval, time_t)
{
    mfc_list *aged, *mp = mfc_head.forw;

    while(mp != &mfc_head && mp->mfc_timeout <= time_sec) {
	aged = mp;
	mp = mp->forw;
	REMQUE(aged);
	if (krt_request_cache(aged->mfcp, pimv6_mfc_check_use)) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_mfc_timeout: check use count failed group %A source %A",
			  &aged->mfcp->mfc_group->group_key,
			  &aged->mfcp->mfc_src));
	}

	trace_tp(pimv6_task,
		 TR_TIMER,
		 0,
		 ("pimv6_mfc_timeout: refreshing MFC: group %A source %A",
		  &aged->mfcp->mfc_group->group_key,
		  &aged->mfcp->mfc_src));
	task_block_free(pimv6_mfc_block_index, (void_t) aged);
    }

    if (mfc_head.forw != &mfc_head) {
	task_timer_set(pimv6_timer_mfc,
		       (time_t) 0,
		       mfc_head.forw->mfc_timeout - time_sec);
    }
}

static void
pimv6_set_mfc_timer __PF2(mfcp, mfc *,
			  timeout, time_t)
{
    mfc_list *mp, *new;

    trace_tp(pimv6_task,
	     TR_TIMER,
	     0,
	     ("pimv6_set_mfc_timer: adding %d sec timer for MFC: group %A source %A",
	      timeout,
	      &mfcp->mfc_group->group_key,
	      &mfcp->mfc_src));

    new = (mfc_list *) task_block_alloc(pimv6_mfc_block_index);
    new->mfc_timeout = time_sec + timeout;
    new->mfcp = mfcp;

    mp = mfc_head.back;
    while (mp != &mfc_head && new->mfc_timeout < mp->mfc_timeout) {
	mp = mp->back;
    }

    INSQUE(new, mp);

    timeout = mfc_head.forw->mfc_timeout - time_sec;
    /*
     * If no timer running, create one
     */
    if (!pimv6_timer_mfc) {
	pimv6_timer_mfc = task_timer_create(pimv6_task,
					    "MFCTimeout",
					    (flag_t) 0,
					    (time_t) 0,
					    timeout,
					    pimv6_mfc_timeout,
					    (void_t) 0);
    } else {
	/*
	 * If new entry now at head of list, restart timer
	 */
	if (mfc_head.forw == new) {
	    task_timer_reset(pimv6_timer_mfc);
	}
	/*
	 * Or if timer isn't running
	 */
	if (BIT_TEST(pimv6_timer_mfc->task_timer_flags, TIMERF_INACTIVE)) {
	    task_timer_set(pimv6_timer_mfc,
			   (time_t) 0,
			   timeout);
	}
    }
}

static void
pimv6_reset_mfc_timer __PF1(mfcp, mfc *)
{
    mfc_list *mp;

    trace_tp(pimv6_task,
	     TR_TIMER,
	     0,
	     ("pimv6_reset_mfc_timer: removing MFC: group %A source %A",
	      &mfcp->mfc_group->group_key,
	      &mfcp->mfc_src));

    mp = mfc_head.forw;
    while (mp != &mfc_head && mfcp != mp->mfcp) {
	mp = mp->forw;
    }

    if (mfcp == mp->mfcp) {
	if (mp == mfc_head.forw) {
	    task_timer_reset(pimv6_timer_mfc);
	}
	REMQUE(mp);
	task_block_free(pimv6_mfc_block_index, (void_t) mp);
    }

    /*
     * If timer has been stopped, see if it needs restarted
     */
    if (mfc_head.forw != &mfc_head &&
	BIT_TEST(pimv6_timer_mfc->task_timer_flags, TIMERF_INACTIVE)) {

	task_timer_set(pimv6_timer_mfc,
		       (time_t) 0,
		       mfc_head.forw->mfc_timeout - time_sec);
    }
}

/*
 *	Fill in Multicast Forwarding Cache entry
 */
static void
pimv6_mfc_request __PF3(msgtype, int,
			in_ifap, if_addr *,
			mfcp, mfc *)
{
    register if_addr *ifap;
    prune_list *new;	/* If no downstream found, send prune back */
    struct ifa_ps *upif_ips;

    trace_tp(pimv6_task,
	     TR_ROUTE,
	     0,
	     ("pimv6_mfc_request: Request for group %A source %A",
	      &mfcp->mfc_group->group_key,
	      &mfcp->mfc_src));
    if (in_ifap) {
	trace_tp(pimv6_task,
		 TR_ROUTE,
		 0,
		 ("pimv6_mfc_request: interface %A(%s)",
		  in_ifap->ifa_addr_local,
		  in_ifap->ifa_link->ifl_name));
    }

    switch (msgtype) {

    case EADDRNOTAVAIL:

	/*
	 * form outgoing interface list
	 * if not a leaf network, add to list
	 * if is a leaf, then check group membership
	 */

	IF_ADDR(ifap) {
	    int add = 0;
	    struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_PIMV6];
	    struct v6_group_list *gp;
	    struct v6_group_list *glist = (struct v6_group_list *)
		ifap->mld6_if_group_list;

	    /*
	     * Don't send it out a non-multicast interface,
	     * a sparse mode interface, the loopback,
	     * or the incoming interface
	     */
	    if (!BIT_TEST(ifap->ifa_state, IFS_MULTICAST) ||
		BIT_TEST(ips->ips_state,IFPS_PIM_MODE) ||
		BIT_TEST(ifap->ifa_state, IFS_LOOPBACK) ||
	    	ifap == mfcp->upstream_ifap) 
		continue;
	    if (!BIT_TEST(ips->ips_state, IFPS_NOT_LEAF)) {
		if (glist) {
		    GROUP_LIST(gp, glist) {
			/*
			 * Has someone requested this group ?
			 */
			if (SAME_ADDR6(sock2in6(&mfcp->mfc_group->group_key), 
				       gp->group_addr)) {
			    add++;
			    break;
			}
		    } GROUP_LIST_END(gp, glist);
		}
	    } else {
		/*
		 * There are other routers downstream
		 * Let them decide when to prune
		 */
		add++;
	    }
	    if (add) {
		downstream *dpos, *dp;

		dpos = mfcp->ds->forw;
		while ((dpos != mfcp->ds)
		       && LESS_ADDR6(sock2in6(dpos->ds_addr), 
				     sock2in6(ifap->ifa_addr_local))){
		    dpos = dpos->forw;
		}

		dp = mfc_alloc_downstream_v6();

		dp->ds_addr = ifap->ifa_addr_local;
		dp->ds_proto = RTPROTO_PIMV6;
		dp->ds_hoplimit = 0;
		dp->ds_flags = 0;
#ifdef	KRT_IPMULTI_TTL0
		dp->ds_ifindex = ifap->ifa_vif;
#else	/* KRT_IPMULTI_TTL0 */
		dp->ds_ifindex = 0;
#endif	/* KRT_IPMULTI_TTL0 */

		/*
		 * insert in the mfc downstream list
		 */
		INSQUE(dp, dpos->back);

		mfcp->ds_count++;

		trace_tp(pimv6_task,
			 TR_ROUTE,
			 0,
			 ("pimv6_mfc_request: PIMv6 adding downstream interface %A(%s)",
			  ifap->ifa_addr_local,
			  ifap->ifa_link->ifl_name));
	    }
	} IF_ADDR_END(ifap) ;
	/*
	 * flag this mfc as needed by PIMv6
	 */
	BIT_SET(mfcp->mfc_proto, IPV6MULTI_BIT(IPV6MULTI_PROTO_PIMV6));

	pimv6_set_mfc_timer(mfcp, pimv6_default_inactivitytimeout);
	/*
	 * link mfc to source unicast route to handle
	 * changes to unicast routing in the future
	 */
	mfc_source_link_unicast_v6(pimv6_task, mfcp);

	upif_ips = &mfcp->upstream_ifap->ifa_ps[RTPROTO_PIMV6];
	if (!mfcp->ds_count) {
	    new = (prune_list *) task_block_alloc(pimv6_prune_block_index);	
	    new->ifap = mfcp->upstream_ifap;
	    new->mfcp = mfcp;
	    new->holdtime = pimv6_default_prunetimeout;
	    if (BIT_TEST(upif_ips->ips_state, IFPS_NOT_LEAF)) {
		new->pending = FALSE;
		new->prune_time = time_sec + pimv6_default_prunetimeout;
		if(pimv6_send_prune(mfcp))
		    pimv6_queue_prune(mfcp, new);
		else 
		    task_block_free(pimv6_prune_block_index, new);
	    } else { 
		if ((time_sec - task_time_start.gt_current.ut_sec)
		    < pimv6_default_hellointerval ) {
				/* Wait for 30 seconds in case we have not 
				 * heard Hello yet 
				 */
		    new->pending = PRUNE_WAIT_HELLO;
		    new->prune_time = time_sec + pimv6_default_hellointerval;
		    pimv6_queue_prune(mfcp, new);
		}
	    }
	}

	break;

    case EADDRINUSE:
	/*
	 * Duplicate packet on incorrect interface, try and stop it.
	 */
	pimv6_send_assert(in_ifap, &sock2in6(&mfcp->mfc_group->group_key), 
			  &sock2in6(&mfcp->mfc_src));
	break;
    }
}

/*
 * Called when an PIMV6 packet is available for reading.
 */
static void
pimv6_recv __PF1(tp, task *)
{
    size_t          pimv6len;
    int             n_packets = TASK_PACKET_LIMIT;

    while (n_packets-- && !task_receive_packet(tp, &pimv6len)) {
	register struct pimv6hdr *pimv6;
	if_addr        *ifap;
	sockaddr_un 	*dst;

	pimv6 = task_get_recv_buffer(struct pimv6hdr *);

	/* Remove IPv6 header from length */
	pimv6len -= sizeof(struct pimv6hdr);

	if (task_recv_dstaddr
	    && !IS_ANYADDR6(sock2in6(task_recv_dstaddr))) {
	    /* Destination address is valid */

	    dst = task_recv_dstaddr;
	} else {
	    /* Destination address is not valid */

	    dst = (sockaddr_un *) 0;
	}

#if notdef
	if (pimv6len != ipv6->ip6_len) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_ERR,
			 ("pimv6_recv: length mismatch: read: %u, ip6_len: %u",
			  pimv6len,
			  ntohs(ipv6->ip6_len)));
	    continue;
	}
#endif
	/* verify checksum */
	if (inet_cksum((void_t) pimv6, pimv6len)) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv: bad PIMV6 checksum from %A",
			  task_recv_srcaddr));
	    continue;
	}
	ifap = if_withindex(task_recv_ifindex, INET6_SCOPE_LINKLOCAL);
	if (ifap == 0) {
	    trace_log_tf(pimv6_trace_options,
			 0, 
			 LOG_WARNING,
			 ("pimv6_recv: bad receiving ifindex %d",
			  task_recv_ifindex));
            continue; 
        }

	if (!ifap) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv: ignoring pimv6 msg from remote source: %A",
			  task_recv_srcaddr));
	    continue;
	}
	if (mld6_get_ifproto(ifap) != IPV6MULTI_PROTO_PIMV6)
	    continue;
	if (pimv6->version != PIMV6_VERSION) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv: Unsupported Version %d from %A",
			  pimv6->version,
			  task_recv_srcaddr));
	    continue;
	}
	
	/* should we trace incoming packets? */
	if (TRACE_DETAIL_RECV_TP(pimv6_task,
				 pimv6->type,
				 PIMV6_CANDIDATE_RP_ADVERTISEMENT,
				 pimv6_trace_masks)) {
	    pimv6_trace(pimv6_task->task_trace,
			0, 
			ifap, 
			task_recv_srcaddr,
			pimv6,
			pimv6len,
			TRACE_DETAIL_RECV_TP(pimv6_task,
					     pimv6->type,
					     PIMV6_CANDIDATE_RP_ADVERTISEMENT,
					     pimv6_trace_masks)); 
	}
					
	switch (pimv6->type) {
	case PIMV6_HELLO:
	    pimv6_recv_hello(ifap, pimv6, pimv6len);
	    break;
	case PIMV6_REGISTER:
	    break;
	case PIMV6_REGISTER_STOP:
	    break;
	case PIMV6_JOIN_PRUNE:
	    pimv6_recv_join_prune(ifap, pimv6, pimv6len);
	    break;
	case PIMV6_BOOTSTRAP:
	    break;
	case PIMV6_ASSERT:
	    pimv6_recv_assert(ifap, pimv6, pimv6len);
	    break;
	case PIMV6_GRAFT:
	    pimv6_recv_graft(ifap, pimv6, pimv6len);
	    break;
	case PIMV6_GRAFT_ACK:
	    pimv6_recv_graft_ack(ifap, pimv6, pimv6len);
	    break;
	}
    }
}

static void
pimv6_recv_hello __PF3(ifap, if_addr *,
		       pimv6hdr, struct pimv6hdr *,
		       pimv6len, int)
{
    struct pimv6hello *pq = (struct pimv6hello *) (pimv6hdr + 1);

    if ((u_int)pimv6len < (sizeof(struct pimv6hdr)+sizeof(struct pimv6hello))) {
	trace_log_tf(pimv6_trace_options,
		     0,
		     LOG_WARNING,
		     ("pimv6_recv_hello: ignoring pimv6 msg: short packet from %A",
		      task_recv_srcaddr));
	return;
    }

    /*
     * add to router list on this interface
     * or refresh if it already exists
     */
    pimv6_router_refresh(ifap,
			 task_recv_srcaddr,
			 ntohl(pq->hello_value));

    /*
     * update leaf status
     */
    pimv6_router_detect(ifap);

    /*
     * perform Designated Router Election
     * and update interface status if necessary
     */
    pimv6_dr_election(ifap);
}


static void
pimv6_recv_join_prune __PF3(ifap, if_addr *,
			    pimv6hdr, struct pimv6hdr *,
			    pimv6len, int)
{
    int i, j;
    int ours = 0;
    int duplicate = 0;		/* we've already seen this prune, ignore */
    struct in6_addr src_addr;
    mfc *mfcp;
    prune_list *pp, *new;
    struct pimv6joinhdr *pjh = (struct pimv6joinhdr *) (pimv6hdr + 1);
    struct pimv6group *pg = (struct pimv6group *) (pjh + 1);

    pimv6len -= sizeof(struct pimv6hdr)+sizeof(struct pimv6joinhdr);
    if (pimv6len < 0) {
	trace_log_tf(pimv6_trace_options,
		     0,
		     LOG_WARNING,
		     ("pimv6_recv_join_prune: ignoring pimv6 msg: short packet from %A",
		      task_recv_srcaddr));
	return;
    }

    if (SAME_ADDR6(pjh->rpf_addr.u_6, sock2in6(ifap->ifa_addr_local)) ||
	SAME_ADDR6(pjh->rpf_addr.u_6, in6addr_any)) {
	ours++;
    }

    for (i = 0; i < pjh->num_groups; i++) {
	caddr_t	cp = (caddr_t) &pg->source_data[0];

	pimv6len -= sizeof(struct pimv6group);
	if (pimv6len < 0) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv_join_prune: ignoring pimv6 msg: short packet from %A",
			  task_recv_srcaddr));
	    return;
	}

	for (j = 0; j < ntohs(pg->num_join); j++) {
	    /*
	     * skip over addr_family, enc_type, flags and mask_len in source
	     */
	    cp += 2*sizeof(u_int16);
	    bcopy(cp, (caddr_t) &src_addr, sizeof(struct in6_addr));
	    cp += sizeof(struct in6_addr);

	    mfcp = mfc_locate_mfc_v6(sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
				     sockbuild_in6(0, (byte *)&src_addr));

	    /* In sparse mode, we would propagate this
	     * join upstream when we didn't have any state
	     * on the (S,G) entry. For now, just ignore.
	     *
	     * If this happens in dense mode, then either
	     * 1. Something is seriously wrong with the
	     *    unicast routing convergence so we can
	     *    just ignore this one and wait for things
	     *    to converge, or
	     * 2. This router was reset and lost state.
	     *    in which case we can just ignore the
	     *    join since all subsequent packets will
	     *    be forwarded until a prune is received.
	     */
	    if (!mfcp) {
		trace_log_tf(pimv6_trace_options,
			     0,
			     LOG_WARNING,
			     ("pimv6_recv_join_prune: cannot locate mfc for Group %A, source %A",
			      sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
			      sockbuild_in6(0, (byte *)&src_addr)));
		continue;
	    }
	    /*
	     * if join arrives on the upstream interface
	     * and we have one pending, suppress our join
	     */
	    if (mfcp->upstream_ifap == ifap) {
		join_list *jp;

		JOIN_LIST(jp, &join_head) {
		    if (jp->mfcp == mfcp && jp->ifap == ifap) {
				/*
				 * if at head of list, restart timer
				 */
			if (jp == join_head.forw) {
			    task_timer_reset(pimv6_timer_join);
			}
			REMQUE(jp);
			if (join_head.forw != &join_head &&
			    BIT_TEST(pimv6_timer_join->task_timer_flags,
				     TIMERF_INACTIVE)) {
			    task_timer_set(pimv6_timer_join,
					   (time_t) 0,
					   join_head.forw->join_time-time_sec);
			}
			task_block_free(pimv6_join_block_index, (void_t) jp);
			break;
		    }
		} JOIN_LIST_END (jp, &join_head);
	    } else if (ours) {
		/*
		 * if join overrides received prune
		 * on downstream interface, then process it.
		 * Otherwise, until we do Sparse Mode,
		 * just ignore it
		 */
		pp = mfcp->prune_down.if_forw;
		while(pp != &mfcp->prune_down) {
		    if (pp->mfcp == mfcp && pp->ifap == ifap) {
			/*
			 * If at head of timer queue,
			 * restart timer
			 */
			if (pp == prune_head.tq_forw &&
			    !BIT_TEST(pimv6_timer_prune->task_timer_flags,
				      TIMERF_INACTIVE)) {
			    task_timer_reset(pimv6_timer_prune);
			}
			PRUNE_TQ_DEQ(pp);
			PRUNE_IF_DEQ(pp);
			task_block_free(pimv6_prune_block_index, pp);
			/*
			 * if timer currently inactive,
			 * reactivate
			 */
			if (BIT_TEST(pimv6_timer_prune->task_timer_flags,
				     TIMERF_INACTIVE)) {
			    task_timer_set(pimv6_timer_prune,
					   (time_t) 0,
					   prune_head.tq_forw->prune_time - time_sec);
			}
			break;
		    }
		    pp = pp->if_forw;
		}
	    }
	}
	for (j = 0; j < ntohs(pg->num_prune); j++) {
	    /*
	     * skip over addr_family, enc_type, flags and mask_len in source
	     */
	    cp += 2 * sizeof(u_int16);
	    bcopy(cp, (caddr_t) &src_addr, sizeof(struct in6_addr));
	    cp += sizeof(struct in6_addr);

	    mfcp = mfc_locate_mfc_v6(sockbuild_in6(0, (byte *)&pg->group_addr.g_6), 
				     sockbuild_in6(0, (byte *)&src_addr));

	    /*
	     * If we receive a prune for a (S,G) that we have no
	     * state, then we probably have just been restarted.
	     * Create state for the group and continue since
	     * we shouldn't ignore the prune.
	     */
	    if (!mfcp) {
		trace_log_tf(pimv6_trace_options,
			     0,
			     LOG_WARNING,
			     ("pimv6_recv_join_prune: cannot locate mfc for Group %A, source %A",
			      sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
			      sockbuild_in6(0, (byte *)&src_addr)));
		continue;
	    }
	    /*
	     * if prune arrived on upstream interface,
	     * see if we need to counteract with a join
	     */
	    if (mfcp->upstream_ifap == ifap) {
		int join = 0;
		downstream *dsp;

		DOWNSTREAM_LIST(dsp, mfcp->ds) {
		    if (dsp->ds_proto == RTPROTO_PIMV6) {
			join++;
			break;
		    }
		} DOWNSTREAM_LIST_END(dsp, mfcp->ds);
		if (join) {
		    int offset;
		    join_list *jp;

		    jp = (join_list *) task_block_alloc(pimv6_join_block_index);
		    jp->ifap = ifap;
		    jp->mfcp = mfcp;
		    offset = PIMV6_JOIN_DELAY_MIN +
			grand((u_int32) (PIMV6_JOIN_DELAY_MAX -
					 PIMV6_JOIN_DELAY_MIN));
		    jp->join_time = time_sec + offset;
		    INSQUE(jp, join_head.back);

		    if (!pimv6_timer_join) {
			pimv6_timer_join = task_timer_create(pimv6_task,
							     "Join",
							     (flag_t) 0,
							     (time_t) 0,
							     offset,
							     pimv6_join_timeout,
							     (void_t) 0);
		    }
		    /*
		     * if new head of list, reset timer
		     */
		    else if (jp == join_head.forw &&
			     !BIT_TEST(pimv6_timer_join->task_timer_flags,
				       TIMERF_INACTIVE)) {
			task_timer_reset(pimv6_timer_join);
		    }
		    /*
		     * if timer currently inactive,
		     * reactivate
		     */
		    if (BIT_TEST(pimv6_timer_join->task_timer_flags,
				 TIMERF_INACTIVE)) {
			task_timer_set(pimv6_timer_join,
				       (time_t) 0,
				       join_head.forw->join_time - time_sec);
		    }
		}
	    } else if (ours) {
		/*
		 * otherwise, if on downstream interface, check
		 * for duplicate prune.
		 *
		 * If not duplicate, queue up prune waiting for
		 * any joins to arrive before we really prune it.
		 */
		pp = mfcp->prune_down.if_forw;
		while(pp != &mfcp->prune_down) {
		    if (pp->mfcp == mfcp && pp->ifap == ifap) {
			duplicate++;
			break;
		    }
		    pp = pp->if_forw;
		}

		if (duplicate) {
		    duplicate = 0;
		    continue;
		}

		new = (prune_list *)
		    task_block_alloc(pimv6_prune_block_index);
		new->ifap = ifap;
		new->mfcp = mfcp;
		new->holdtime = ntohs(pjh->holdtime);

		/*
		 * Don't need to wait for join on p2p link
		 */
		if (BIT_TEST(ifap->ifa_state, IFS_POINTOPOINT)) {
		    /* 
		     * If it is ptp, don't wait JOIN, do what it should do
		     * after wait_join timeout.
		     */
		    new->pending = FALSE;
		    new->prune_time = time_sec + ntohs(pjh->holdtime);
		    pimv6_mfc_delete_ifap(mfcp, (caddr_t)ifap); 
		    pimv6_queue_prune(mfcp, new);
		} else {
		    new->pending = PRUNE_WAIT_JOIN;
		    new->prune_time = time_sec + PIMV6_JOIN_DELAY_MAX;
		    pimv6_queue_prune(mfcp, new);
		}
	    }
	}
	pg = (struct pimv6group *) cp;
    }
    return;
}


static void
pimv6_recv_assert __PF3(ifap, if_addr *,
			pimv6hdr, struct pimv6hdr *,
			pimv6len, int)
{
    mfc *mfcp;
    int rpbit;
    struct pimv6assert *pas = (struct pimv6assert *) (pimv6hdr + 1);

    mfcp = mfc_locate_mfc_v6(sockbuild_in6(0, (byte *)&pas->group_addr.g_6), 
			     sockbuild_in6(0, (byte *)&pas->src_addr.u_6));
    if (!mfcp) {
	return;
    }

    pas->preference = ntohl(pas->preference);
    pas->metric = ntohl(pas->metric);
    rpbit = BIT_TEST(pas->preference, PIMV6_ASSERT_RPBIT);
    pas->preference &= 0x7fffffff;

    /*
     * if assert arrives on the upstream interface
     */
    if (mfcp->upstream_ifap == ifap) {
	/*
	 * If assert already received, now we have to decide upon the two.
	 * But if this is the first just queue it and time it out while
	 * waiting for the possible second.
	 */
	if (mfcp->pimv6_assert) {
	    if  (mfcp->pimv6_assert->preference < pas->preference ||
		 (mfcp->pimv6_assert->preference == pas->preference &&
		  mfcp->pimv6_assert->metric < pas->metric) ||
		 (mfcp->pimv6_assert->preference == pas->preference &&
		  mfcp->pimv6_assert->metric == pas->metric &&
		  LESS_ADDR6(sock2in6(task_recv_srcaddr), mfcp->pimv6_assert->rpf_addr))) {
		mfcp->rpf_addr = mfcp->pimv6_assert->rpf_addr;
	    } else {
		mfcp->rpf_addr = sock2in6(task_recv_srcaddr);
	    }
	    REMQUE(mfcp->pimv6_assert);
	    task_block_free(pimv6_assert_block_index, 
			    (void_t) mfcp->pimv6_assert);
	    mfcp->pimv6_assert = (assert_list *) 0;
	} else {
	    /*
	     * This is the first assert we have received. Queue it up
	     * and see if we get another one. If not, then we have
	     * a winner!
	     */
	    assert_list *new = (assert_list *)
		task_block_alloc(pimv6_assert_block_index);
	    new->mfcp = mfcp;
	    new->ifap = ifap;
	    new->rpf_addr = sock2in6(task_recv_srcaddr);
	    new->assert_time = time_sec + PIMV6_ASSERT_TIMEOUT;
	    new->preference = pas->preference;
	    new->metric = pas->metric;
	    new->rpbit = rpbit;

	    mfcp->pimv6_assert = new;
	    INSQUE(new, assert_head.back);

	    if (!pimv6_timer_assert) {
		pimv6_timer_assert = task_timer_create(pimv6_task,
						       "Assert",
						       (flag_t) 0,
						       (time_t) 0,
						       PIMV6_ASSERT_TIMEOUT,
						       pimv6_assert_timeout,
						       (void_t) 0);
	    }
	    /*
	     * if new head of list, reset timer
	     */
	    else if (new == assert_head.forw &&
		     !BIT_TEST(pimv6_timer_assert->task_timer_flags, 
			       TIMERF_INACTIVE))
		task_timer_reset(pimv6_timer_assert);
	    /*
	     * if timer currently inactive,
	     * reactivate
	     */
	    if (BIT_TEST(pimv6_timer_assert->task_timer_flags, TIMERF_INACTIVE)) {
		task_timer_set(pimv6_timer_assert,
			       (time_t) 0,
			       assert_head.forw->assert_time - time_sec);
	    }
	}
    } else {
	/*
	 * Else assert arrived on a downstream interface.
	 * Compare assert metric with our metric
	 */
	int keep = 0;
	upstream *up = krt_locate_upstream(&mfcp->mfc_src, IPV6MULTI_PROTO_PIMV6);

	pref_t preference = pimv6_preference[up->protocol] & 0x7fffffff;

	if ((u_int)preference < pas->preference ||
	    ((u_int)preference == pas->preference &&
	     up->metric < pas->metric) ||
	    ((u_int)preference == pas->preference &&
	     up->metric == pas->metric &&
	     LESS_ADDR6(sock2in6(task_recv_srcaddr), 
			sock2in6(ifap->ifa_addr_local)))){

	    router_list *list = (router_list *) ifap->pimv6_if_router_list;
	    router_list *lp;
	    struct v6_group_list *glist = (struct v6_group_list *)
		ifap->mld6_if_group_list;
	    struct v6_group_list *gp;
	    /*
	     * We have won the election.
	     * Send assert back so other router knows we win.
	     */
	    pimv6_send_assert(ifap, &sock2in6(&mfcp->mfc_group->group_key), 
			      &sock2in6(&mfcp->mfc_src));
	    /*
	     * If no directly connected group members or ...
	     */
	    if (glist) {
		GROUP_LIST(gp, glist) {
		    /*
		     * Has someone requested this group ?
		     */
		    if (SAME_ADDR6(sock2in6(&mfcp->mfc_group->group_key), 
				   gp->group_addr)) {
			keep++;
			break;
		    }
		} GROUP_LIST_END(gp, glist);
	    }

	    /*
	     * ... no other downstream routers besides us and the
	     * router that sent the assert, prune interface.
	     */
	    if (!keep) {
		keep = -2;
		ROUTER_DR_LIST(lp, list) {
		    keep++;
		} ROUTER_DR_LIST_END(lp, list);
	    }
	}
	if (!keep) {
	    int old_count;
	    prune_list *dp;
	    struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_PIMV6];	

	    old_count = mfcp->ds_count;
	    pimv6_mfc_delete_ifap(mfcp, (caddr_t) ifap); 
	    if ((mfcp->ds_count < old_count) 
		&& BIT_TEST(ips->ips_state, IFPS_NOT_LEAF)) {
		/* Really delete something */
		dp = (prune_list *) task_block_alloc(pimv6_prune_block_index);
		dp->ifap = ifap;
		dp->mfcp = mfcp;
		dp->pending = FALSE;
		dp->holdtime = pimv6_default_prunetimeout;
		dp->prune_time = time_sec + pimv6_default_prunetimeout;
		pimv6_queue_prune(mfcp, dp);
	    }
	}
    }
}


static void
pimv6_recv_graft __PF3(ifap, if_addr *,
		       pimv6hdr, struct pimv6hdr *,
		       pimv6len, int)
{
    int rc, i, j;
    int len = pimv6len - sizeof(struct pimv6hdr);
    struct in6_addr src_addr;
    mfc *mfcp;
    struct pimv6joinhdr *pjh = (struct pimv6joinhdr *) (pimv6hdr + 1);
    struct pimv6group *pg = (struct pimv6group *) (pjh + 1);

    len -= sizeof(struct pimv6joinhdr);
    if (len < 0) {
	trace_log_tf(pimv6_trace_options,
		     0,
		     LOG_WARNING,
		     ("pimv6_recv_graft: pimv6joinhdr: short packet from %A",
		      task_recv_srcaddr));
	return;
    }

    if (!sockaddrcmp_in6(ifap->ifa_addr_local, 
			 sockbuild_in6(0, (byte *)&pjh->rpf_addr.u_6))) {
	return;
    }

    for (i = 0; i < pjh->num_groups; i++) {
	caddr_t	cp = (caddr_t) &pg->source_data[0];

	len -= sizeof(struct pimv6group);
	if (len < 0) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv_graft: pimv6group: short packet from %A",
			  task_recv_srcaddr));
	    return;
	}

	for (j = 0; j < ntohs(pg->num_join); j++) {
	    /*
	     * skip over addr_family, enc_type, flags and mask_len in source
	     */
	    cp += 2 * sizeof(u_int16);
	    bcopy(cp, (caddr_t) &src_addr, sizeof(struct in6_addr));
	    cp += sizeof(struct in6_addr);

	    mfcp = mfc_locate_mfc_v6(sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
				     sockbuild_in6(0, (byte *)&src_addr));

	    if (!mfcp) {
		trace_tp(pimv6_task,
			 TR_NORMAL,
			 0,
			 ("pimv6_recv_graft: no Forwarding Cache entry for Group %A Source %A",
			  sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
			  sockbuild_in6(0, (byte *)&src_addr)));
		continue;
	    }
	    /*
	     * if graft doesn't arrive on downstream interface,
	     * ignore it
	     */
	    if (mfcp->upstream_ifap != ifap) {
	       /*
		* add the downstream interface 
		* and send back a Graft Ack.
		*/
		pimv6_mfc_add_ifap(mfcp, (caddr_t) ifap);

		pimv6hdr->type = PIMV6_GRAFT_ACK;
		pimv6hdr->cksum = 0;
		pimv6hdr->cksum = inet_cksum((void_t)pimv6hdr, pimv6len);
		rc = pimv6_send(ifap, pimv6hdr, pimv6len, task_recv_srcaddr,
				FALSE);

		/* Should we trace this packet? */

		if (TRACE_PACKET_SEND_TP(pimv6_task,
					 pimv6hdr->type,
					 PIMV6_GRAFT_ACK,
					 pimv6_trace_masks)) {

		    pimv6_trace(pimv6_task->task_trace,
				rc,
				ifap,
				task_recv_srcaddr,
				pimv6hdr,
				pimv6len,
				TRACE_DETAIL_SEND_TP(pimv6_task,
						     pimv6hdr->type,
						     PIMV6_CANDIDATE_RP_ADVERTISEMENT,
						     pimv6_trace_masks));
		}
	    }
	}
	if (ntohs(pg->num_prune) != 0) {

	    /*
	     * skip over addr_family, enc_type, flags and mask_len in source
	     */
	    cp += 2 * sizeof(u_int16);

	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv_graft: graft message for Group %A Source %A from %A contains prune sources!",
			  sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
			  sockbuild_in6(0, (byte *)(struct in6_addr *) cp),
			  task_recv_srcaddr));

	    cp += sizeof(struct in6_addr);
	}
	pg = (struct pimv6group *) cp;
    }
    return;
}


static void
pimv6_recv_graft_ack __PF3(ifap, if_addr *,
			   pimv6hdr, struct pimv6hdr *,
			   pimv6len, int)
{
    int i, j;
    int len = pimv6len - sizeof(struct pimv6hdr);
    struct in6_addr src_addr;
    mfc *mfcp;
    struct pimv6joinhdr *pjh = (struct pimv6joinhdr *) (pimv6hdr + 1);
    struct pimv6group *pg = (struct pimv6group *) (pjh + 1);

    len -= sizeof(struct pimv6joinhdr);
    if (len < 0) {
	trace_log_tf(pimv6_trace_options,
		     0,
		     LOG_WARNING,
		     ("pimv6_recv_graft_ack: ignoring pimv6 msg: short packet from %A",
		      task_recv_srcaddr));
	return;
    }

    for (i = 0; i < pjh->num_groups; i++) {
	caddr_t	cp = (caddr_t) &pg->source_data[0];

	len -= 2 * sizeof(u_int16) + 2 * sizeof(struct in6_addr);
	if (len < 0) {
	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv_graft_ack: ignoring pimv6 msg: short packet from %A",
			  task_recv_srcaddr));
	    return;
	}

	for (j = 0; j < ntohs(pg->num_join); j++) {
	    /*
	     * skip over addr_family, enc_type, flags and mask_len in source
	     */
	    cp += 2 * sizeof(u_int16);
	    bcopy(cp, (caddr_t) &src_addr, sizeof(struct in6_addr));
	    cp += sizeof(struct in6_addr);

	    mfcp = mfc_locate_mfc_v6(sockbuild_in6(0, (byte *)&pg->group_addr.g_6), sockbuild_in6(0, (byte *)&src_addr));

	    if (!mfcp) {
		trace_tp(pimv6_task,
			 TR_NORMAL,
			 0,
			 ("pimv6_recv_graft: no Forwarding Cache entry for Group %A Source %A",
			  sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
			  sockbuild_in6(0, (byte *)&src_addr)));
		continue;
	    }
	    /*
	     * if graft ack doesn't arrive on upstream interface,
	     * or graft not pending, ignore it, otherwise
	     * take it off the graft retransmission queue.
	     */
	    if (mfcp->upstream_ifap == ifap &&
		mfcp->mfc_group->graft_pending) {
		graft_list	*gp;

		GRAFT_LIST(gp, &graft_head) {
		    if (sockaddrcmp_in6(&gp->graft_dst, task_recv_srcaddr) &&
			SAME_ADDR6(gp->graft_group,
				   sock2in6(&mfcp->mfc_group->group_key))) {
			REMQUE(gp);
			task_block_free(pimv6_graft_block_index, (void_t) gp);
			if (graft_head.back == graft_head.forw) {
			    /* No more graft record */
			    task_timer_reset(pimv6_timer_graft);
			} else {
			    /*
			     * if timer currently inactive, reactivate
			     */
			    if (BIT_TEST(pimv6_timer_graft->task_timer_flags,
					 TIMERF_INACTIVE)) {
				task_timer_set(pimv6_timer_graft,
					       (time_t) 0,
					       graft_head.forw->graft_time - time_sec);
			    }
			}
			break;
		    }
		} GRAFT_LIST_END(gp, &graft_head);
		mfcp->mfc_group->graft_pending = FALSE;
	    }
	}
	if (ntohs(pg->num_prune) != 0) {

	    /*
	     * skip over addr_family, enc_type, flags and mask_len in source
	     */
	    cp += 2 * sizeof(u_int16);

	    trace_log_tf(pimv6_trace_options,
			 0,
			 LOG_WARNING,
			 ("pimv6_recv_graft_ack: graft ack message for Group %A Source %A from %A contains prune sources!",
			  sockbuild_in6(0, (byte *)&pg->group_addr.g_6),
			  sockbuild_in6(0, (byte *)(struct in6_addr *) cp),
			  task_recv_srcaddr));
	    cp += sizeof(struct in6_addr);
	}
	pg = (struct pimv6group *) cp;
    }
    return;
}


static void
pimv6_router_purge __PF1(ifap, if_addr *)
{
    router_list *list = (router_list *) ifap->pimv6_if_router_list;
    router_list *gp, *delete;

    if (list) {
	/*
	 * If we think we should be DR on this interface,
	 * disable it before we go away
	 */

	if (ifap->pimv6_if_timer_timeout && 
	    !BIT_TEST(((task_timer *)ifap->pimv6_if_timer_timeout)->task_timer_flags, TIMERF_INACTIVE)) 
	    task_timer_reset((task_timer *)ifap->pimv6_if_timer_timeout);

	if ((list != list->dr_forw)
	    && sockaddrcmp_in6(ifap->ifa_addr_local, list->dr_forw->router_addr)) {
	    mld6_disable_dr_status(ifap);
	}

	/*
	 *  Remove all the routers from the list
	 */
	gp = list->dr_forw;
	while(gp != list ) {
	    delete = gp;
	    gp = gp->dr_forw;
	    ROUTER_TQ_DEQ(delete);
	    ROUTER_DR_DEQ(delete);
	    task_block_free(pimv6_router_block_index, (void_t) delete);
	}
	task_block_free(pimv6_router_block_index, (void_t) list);
	ifap->pimv6_if_router_list = (void_t) 0;
    }
}

static void
pimv6_router_refresh __PF3(ifap, if_addr *,
			 src_addr, sockaddr_un *,
			 holdtime, time_t)
{
    time_t expire;
    router_list *list = (router_list *) ifap->pimv6_if_router_list;
    router_list *rlp, *rtr;

    rtr = list->dr_forw;
    while(rtr != list &&
	  sockaddrless_in6(src_addr, rtr->router_addr)) {
		rtr = rtr->dr_forw;
    }
    if (rtr == list) {
	/*
	 * not found, insert at end of list
	 */
		rtr = (router_list *) task_block_alloc(pimv6_router_block_index);
		rtr->router_addr = sockdup(src_addr);
		ROUTER_DR_ENQ(rtr, list->dr_back);
    } else {
	/*
	 * if match, remove from old position in timer list
	 */
		if (sockaddrcmp_in6(rtr->router_addr, src_addr)) {
	    	ROUTER_TQ_DEQ(rtr);
		} else {
		/*
		 * got a new pimv6 router in the middle of the list
		 */
	    	router_list *new = (router_list *)
				task_block_alloc(pimv6_router_block_index);
	    	ROUTER_DR_ENQ(new, rtr->dr_back);
	    	rtr = new;
	    	rtr->router_addr = sockdup(src_addr);
		}
    }
	/*
	 * update refresh time and insert it in the list
	 */
    rtr->hold_time = holdtime;
    rtr->refresh_time = time_sec;
    expire = rtr->refresh_time + rtr->hold_time;

    rlp = list->tq_back;
   	while(rlp != list && (expire < (rlp->refresh_time + rlp->hold_time))) {
		rlp = rlp->tq_back;
    }
    ROUTER_TQ_ENQ(rtr, rlp);

    pimv6_refresh_router_timer(ifap,
		list->tq_forw->hold_time - (time_sec - list->tq_forw->refresh_time));
}


static void
pimv6_refresh_router_timer __PF2(ifap, if_addr *,
				 timeout, time_t)
{

    /* if no timer running, create one */
    if (!ifap->pimv6_if_timer_timeout) {

	ifap->pimv6_if_timer_timeout =
	    (void_t) task_timer_create(pimv6_task,
				       "Router",
				       (flag_t) 0,
				       (time_t) 0,
				       timeout,
				       pimv6_router_timeout,
				       (void_t) ifap);
    } else {			/* set existing timer to new time */
	if (!BIT_TEST(((task_timer *)ifap->pimv6_if_timer_timeout)->task_timer_flags,
		      TIMERF_INACTIVE)) {
	    task_timer_reset((task_timer *)ifap->pimv6_if_timer_timeout);
	}
	/*
	 * No way to reset data without deleting timer and
	 * re-creating it. So for now, just cheat
	 */
	((task_timer *)ifap->pimv6_if_timer_timeout)->task_timer_data = (void_t) ifap;

	task_timer_set((task_timer *)ifap->pimv6_if_timer_timeout,
		       (time_t) 0,
		       timeout);
    }
}


static void
pimv6_dr_election __PF1(ifap, if_addr *)
{
    router_list *list = (router_list *) ifap->pimv6_if_router_list;

    /*
     * check if I am the DR for this interface
     * If so, let icmp6 check the interface status
     */

    assert(list);
    assert(list->dr_forw->router_addr);

    if (sockaddrcmp_in6(ifap->ifa_addr_local, list->dr_forw->router_addr)) {
	mld6_enable_dr_status(ifap);
    } else {
	mld6_disable_dr_status(ifap);
    }
}

/*
 * A join delay timer has expired, time to send a join
 * This means we don't want this (S,G) entry pruned and noone else
 * has responded yet so we will.
 */

/*ARGSUSED*/
static void
pimv6_join_timeout __PF2(tip, task_timer *,
			 interval, time_t)
{
    join_list	*jp, *aged;

    jp = join_head.forw;
    while(jp != &join_head && (time_sec >= jp->join_time)) {

	trace_tp(pimv6_task,
		 TR_TIMER,
		 0,
		 ("pimv6_join_timeout: join delay timer expired for Group %A Source %A Interface %A(%s)",
		  &jp->mfcp->mfc_group->group_key,
		  &jp->mfcp->mfc_src,
		  jp->ifap->ifa_addr_local,
		  jp->ifap->ifa_link->ifl_name));
	
	/*
	 * put this group,source pair in the rpf list
	 */
	pimv6_scan_mfc_rpf(jp->mfcp, (caddr_t) 0);

	if (rpf_head.forw != &rpf_head) {
	    /*
	     * send a prune upstream for this group,source pair
	     * Before do it, check ds_count since Join is useless
	     * when there is no downstream interfaces.
	     */
	    if (jp->mfcp->ds_count)
		pimv6_send_join_prune_graft(PIMV6_MSG_JOIN,
					    &sock2in6(&jp->mfcp->mfc_group->group_key));

	}
	/*
	 * clean up the rpf list
	 */
	pimv6_rpf_free();

	aged = jp;
	jp = jp->forw;
	REMQUE(aged);
	task_block_free(pimv6_join_block_index, (void_t) aged);
    }
    if (join_head.forw != &join_head) {
	task_timer_set(pimv6_timer_join,
		       (time_t) 0,
		       join_head.forw->join_time - time_sec);
    }
}

static void
pimv6_clean_mfc __PF1(mfcp, mfc *)
{
    prune_list *next, *down, *down_forw, *pp = prune_head.tq_forw;

    if (mfcp->prune_up) {
	while(pp != &prune_head) {
	    if (mfcp->prune_up == pp) {
		if (pp == prune_head.tq_forw &&
		    !BIT_TEST(pimv6_timer_prune->task_timer_flags, 
			      TIMERF_INACTIVE)) {
		    task_timer_reset(pimv6_timer_prune);
		}
		next = pp->tq_forw;
		PRUNE_TQ_DEQ(pp);
		task_block_free(pimv6_prune_block_index, pp);
		pp = next;
	    } else {
		pp = pp->tq_forw;
	    }
	}
	mfcp->prune_up = (prune_list *) 0;
    }
    if (&mfcp->prune_down != mfcp->prune_down.if_forw) {
	down = mfcp->prune_down.if_forw;
	down_forw = down->if_forw;
	while (down != &mfcp->prune_down) {
	    pp = prune_head.tq_forw;
	    while(pp != &prune_head) {
		if (pp == down) {
		    if (pp == prune_head.tq_forw &&
			!BIT_TEST(pimv6_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
			task_timer_reset(pimv6_timer_prune);
		    }
		    next = pp->tq_forw;
		    PRUNE_TQ_DEQ(pp);
		    PRUNE_IF_DEQ(pp);
		    task_block_free(pimv6_prune_block_index, pp);
		    pp = next;
		} else {
		    pp = pp->tq_forw;
		}
	    }
	    down = down_forw;
	    down_forw = down->if_forw;
	}
    }
    if (prune_head.tq_forw != &prune_head &&
	BIT_TEST(pimv6_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_set(pimv6_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
    if (mfcp->pimv6_assert) {
	/*
	 * If we are the top of the assert queue, then we must stop
	 * the timer and possibly restart it.
	 */
	if (mfcp->pimv6_assert == assert_head.forw) {
	    task_timer_reset(pimv6_timer_assert);
	    REMQUE(mfcp->pimv6_assert);
	    if (assert_head.forw != &assert_head) {
		task_timer_set(pimv6_timer_assert,
			       (time_t) 0,
			       assert_head.forw->assert_time - time_sec);
	    }
	} else {
	    /*
	     * Otherwise, just take it out of the timer queue.
	     */
	    REMQUE(mfcp->pimv6_assert);
	}
	task_block_free(pimv6_assert_block_index, (void_t) mfcp->pimv6_assert);
	mfcp->pimv6_assert = (assert_list *) 0;
    }
}

/*ARGSUSED*/
static void
pimv6_prune_timeout __PF2(tip, task_timer *,
			  interval, time_t)
{
    prune_list *aged, *pp = prune_head.tq_forw;

    while(pp != &prune_head && pp->prune_time <= time_sec) {
	aged = pp;
	PRUNE_TQ_DEQ(aged);
	if (aged == aged->mfcp->prune_up) {
	    aged->mfcp->prune_up = (prune_list *) 0;
	} else {
	    PRUNE_IF_DEQ(aged);
	}

	/*
	 * If this is a pending prune, go ahead and send prune
	 * since noone sent a join to stop it.
	 */
	if (aged->pending) {
	    if (aged->pending == PRUNE_WAIT_JOIN) {
		pimv6_mfc_delete_ifap(aged->mfcp, (caddr_t)aged->ifap); 
		aged->pending = FALSE;
		aged->prune_time = time_sec + aged->holdtime;
		pimv6_queue_prune(aged->mfcp, aged); 
	    }
	    if (aged->pending == PRUNE_WAIT_HELLO) {
		struct ifa_ps *upif_ips;

		aged->pending = FALSE;
		aged->prune_time = time_sec + aged->holdtime;
				/*
				 * If send ok, add prune to timeout queue, else free it
				 */
		upif_ips = &aged->mfcp->upstream_ifap->ifa_ps[RTPROTO_PIMV6];
		if (BIT_TEST(upif_ips->ips_state, IFPS_NOT_LEAF)) {
		    if(pimv6_send_prune(aged->mfcp))
			pimv6_queue_prune(aged->mfcp, aged);
		    else
			task_block_free(pimv6_prune_block_index, aged);
		} else 
		    task_block_free(pimv6_prune_block_index, aged);
	    }
	
	    /*
	     * Determine if this prune was received from downstream
	     * or was sent upstream
	     */
	} else if (aged->mfcp->upstream_ifap == aged->ifap) {

	    /*
	     * pretend we don't know anything about this (S,G)
	     * anymore and build up new state.
	     */
	    BIT_RESET(aged->mfcp->mfc_proto, IPV6MULTI_BIT(IPV6MULTI_PROTO_PIMV6));

	    /*
	     * If this is the last protocol to want this cache entry,
	     * time to blow it away
	     */
	    if (!aged->mfcp->mfc_proto) {
		pimv6_clean_mfc(aged->mfcp);
		pimv6_reset_mfc_timer(aged->mfcp);
		mfc_source_unlink_unicast_v6(pimv6_task, aged->mfcp);
		(void) krt_delete_cache(&aged->mfcp->mfc_group->group_key, &aged->mfcp->mfc_src);
		mfc_delete_node_v6(aged->mfcp);
	    }

	    task_block_free(pimv6_prune_block_index, aged);
	} else {
	    /*
	     * Time to start sending traffic down this interface again
	     * and wait for the next prune
	     */
	    pimv6_mfc_add_ifap(aged->mfcp, (caddr_t) aged->ifap);
	    task_block_free(pimv6_prune_block_index, aged);
	}
	/* Since everytime prune timeout, there is at least 1 prune 
	 * removed from TQ. Note: pp = pp->tq_forw is dangerous since
	 * some prune timeout will cause other prunes deleted, e.g.
	 * upstream prune timeout. New added prune won't affect since
	 * it has not timeout yet.
	 */
	pp = prune_head.tq_forw;
    }
    if (prune_head.tq_forw != &prune_head) {
	task_timer_set(pimv6_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
}

static void
pimv6_queue_prune __PF2(mfcp, mfc *,
			new, prune_list *)
{
    prune_list *pp = prune_head.tq_forw;
    mfc_list *mp  = mfc_head.forw;

    while(pp != &prune_head &&
	  pp->prune_time < new->prune_time) {
	pp = pp->tq_forw;
    }
    PRUNE_TQ_ENQ(new, pp->tq_back);
    /*
     * We insert an upstream prune in a separate pointer.
     * A dowstream prune gets put at the end of the list.
     */
    if (new->ifap == mfcp->upstream_ifap) {
	mfcp->prune_up = new;
    } else {
	PRUNE_IF_ENQ(new, mfcp->prune_down.if_back);
    }

    if (!pimv6_timer_prune) {
	pimv6_timer_prune = task_timer_create(pimv6_task,
					      "Prune",
					      (flag_t) 0,
					      (time_t) 0,
					      new->prune_time - time_sec,
					      pimv6_prune_timeout,
					      (void_t) 0);
    }
    /*
     * if new head of list, reset timer
     */
    else if (new == prune_head.tq_forw &&
	     !BIT_TEST(pimv6_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_reset(pimv6_timer_prune);
    }
    /*
     * if timer currently inactive,
     * reactivate
     */
    if (BIT_TEST(pimv6_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_set(pimv6_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }

    /* 
     * Setup MFC's timer as the max prune timer among all its outgoing
     * interfaces.
     */
    while (mp != &mfc_head && mfcp != mp->mfcp) {
	mp = mp->forw;
    }
    if (mfcp == mp->mfcp) {
	if (mp->mfc_timeout < new->prune_time) {
	    pimv6_reset_mfc_timer(mfcp);
	    pimv6_set_mfc_timer(mfcp, new->prune_time - time_sec);
	}
    }
}

static int
pimv6_send_prune __PF1(mfcp, mfc *)
{

    /*
     * put this group,source pair in the rpf list
     * returns non-zero on error
     */
    pimv6_scan_mfc_rpf(mfcp, (caddr_t) 0);

    if (rpf_head.forw == &rpf_head)
	return(0);

    /*
     * send a prune upstream for this group,source pair
     */
    pimv6_send_join_prune_graft(PIMV6_MSG_PRUNE, 
				&sock2in6(&mfcp->mfc_group->group_key));

    /*
     * clean up the rpf list
     */
    pimv6_rpf_free();

    return(1);
}


static void
pimv6_mfc_add_ifap __PF2(mfcp, mfc *,
			 data, caddr_t)
{
    prune_list *pp;
    downstream *dsp, *dpos;
    if_addr *ifap = (if_addr *) data;
    if_addr *tmp_ifap;

    /*
	 * Don't want to add interface if it is the expecting incoming intf
	 */
    if (ifap == mfcp->upstream_ifap) {
	return;
    }
    /*
	 * Look to see if interface is already in downstream list
	 */
    DOWNSTREAM_LIST(dsp, mfcp->ds) {
        if (dsp->ds_proto == RTPROTO_PIMV6) {
	    if (sockaddrcmp_in6(dsp->ds_addr, ifap->ifa_addr_local)) {
		return;
	    }
	}
    } DOWNSTREAM_LIST_END(dsp, mfcp->ds);

    /*
	 * This interface is not part of the downstream interface list
	 * Add it.
	 */
    dpos = mfcp->ds->forw;
    while ((dpos != mfcp->ds) 
	   && LESS_ADDR6(sock2in6(dpos->ds_addr), sock2in6(ifap->ifa_addr_local))) {
	dpos = dpos->forw;
    }

    dsp = mfc_alloc_downstream_v6();

    dsp->ds_proto = RTPROTO_PIMV6;
    dsp->ds_addr = ifap->ifa_addr_local;
    dsp->ds_hoplimit = dsp->ds_flags = 0;
#ifdef	KRT_IPMULTI_TTL0
    dsp->ds_ifindex = ifap->ifa_vif;
#else	/* KRT_IPMULTI_TTL0 */
    dsp->ds_ifindex = 0;
#endif	/* KRT_IPMULTI_TTL0 */

    /*
	 * stick at the end of the list
	 */
    INSQUE(dsp, dpos->back);

    mfcp->ds_count++;

    trace_tp(pimv6_task,
	     TR_ROUTE,
	     0,
	     ("pimv6_mfc_add_ifap: PIMV6 adding downstream interface %A(%s) for group %A source %A",
	      ifap->ifa_addr_local,
	      ifap->ifa_link->ifl_name,
	      &mfcp->mfc_group->group_key,
	      &mfcp->mfc_src));

    krt_update_mfc(mfcp);

    /*
     * If there is a prune upstream, we may need to send a graft.
     */

    if ((pp=mfcp->prune_up)) {
	if (pp == prune_head.tq_forw &&
	    !BIT_TEST(pimv6_timer_prune->task_timer_flags,
		      TIMERF_INACTIVE)) {
	    task_timer_reset(pimv6_timer_prune);
	}

	PRUNE_TQ_DEQ(pp);

	task_block_free(pimv6_prune_block_index, pp);

	mfcp->prune_up = (prune_list *) 0;

	/*
	 * If source is on a directly attached subnet,
	 * no need to send a graft. We will automatically get the packets.
	 */
	tmp_ifap = if_withsubnet(&mfcp->mfc_src);
	if(tmp_ifap) tmp_ifap = if_withindex(tmp_ifap->ifa_link->ifl_index,
					     INET6_SCOPE_LINKLOCAL);
        if (mfcp->upstream_ifap != tmp_ifap) {
	    pimv6_send_graft(mfcp->upstream_ifap,
			     &sock2in6(&mfcp->mfc_group->group_key));
	}
    }

    pp = mfcp->prune_down.if_forw;
    while (pp != &mfcp->prune_down) {
	if (pp->ifap == ifap) {
	    /*
	     * if this prune is at the head of the timer queue,
	     * we must refresh the timer queue after deleting it.
	     */
	    if (pp == prune_head.tq_forw &&
		!BIT_TEST(pimv6_timer_prune->task_timer_flags,
			  TIMERF_INACTIVE)) {
		task_timer_reset(pimv6_timer_prune);
	    }
	    PRUNE_TQ_DEQ(pp);
	    PRUNE_IF_DEQ(pp);
	    task_block_free(pimv6_prune_block_index, pp);
	    break;
	}
	pp = pp->if_forw;
    }
    /*
     * if timer currently inactive,
     * reactivate
     */
    if (prune_head.tq_forw != &prune_head &&
	BIT_TEST(pimv6_timer_prune->task_timer_flags,
		 TIMERF_INACTIVE)) {
	task_timer_set(pimv6_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
}

/* 
 * Delete MFC if certain interface is its upstream interface
 * Used when an interface is down
 */
static void
pimv6_mfc_delete_upifap __PF2(mfcp, mfc *,
			      data, caddr_t)
{
    if_addr *ifap = (if_addr *) data;

    if(ifap == mfcp->upstream_ifap) {
	pimv6_clean_mfc(mfcp);
	pimv6_reset_mfc_timer(mfcp);
	krt_delete_cache(&mfcp->mfc_group->group_key, &mfcp->mfc_src);
	mfc_delete_node_v6(mfcp);
    }
}

/*
 * Delete any downstream prune associated with this interface.
 * when a router is detected as down.
 */
static void
pimv6_mfc_delete_prune __PF2(mfcp, mfc *,
			     data, caddr_t)
{
    if_addr 	*ifap = (if_addr *)data;
    prune_list	*down = mfcp->prune_down.if_forw;

    if (&mfcp->prune_down != mfcp->prune_down.if_forw) {
	while(down != &mfcp->prune_down) {
	    if (down->ifap == ifap) {
		PRUNE_TQ_DEQ(down);
		PRUNE_IF_DEQ(down);
		task_block_free(pimv6_prune_block_index, down);
		break;
	    }
	    down = down->if_forw;
	}
    }
    if (prune_head.tq_forw != &prune_head) {
        task_timer_set(pimv6_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
}

static void
pimv6_mfc_delete_ifap __PF2(mfcp, mfc *,
			    data, caddr_t)
{
    int doit = 0;
    prune_list *new;
    downstream *dsp, *delete = 0;
    if_addr *ifap = (if_addr *) data;

    DOWNSTREAM_LIST(dsp, mfcp->ds) {
        if (dsp->ds_proto == RTPROTO_PIMV6) {
	    if (sockaddrcmp_in6(dsp->ds_addr, ifap->ifa_addr_local)) {
				/*
				 * should never happen more than once!
				 */
		delete = dsp;
		break;
	    }
	}
    } DOWNSTREAM_LIST_END(dsp, mfcp->ds);

    /* 
     * If the interface being deleted are DR and has members, don't do it
     */

    if (delete) {
        struct v6_group_list *gp;
	struct v6_group_list *glist = (struct v6_group_list *)
	    ifap->mld6_if_group_list;
	doit = 1;
	if (glist) {
	    GROUP_LIST(gp, glist) {
				/*
				 * Has someone request this group ?
				 */
		if (SAME_ADDR6(sock2in6(&mfcp->mfc_group->group_key), 
			       gp->group_addr)) {
		    doit = 0;
		    break;
		}
	    } GROUP_LIST_END(gp, glist);
	}
    } else 
	doit = 0;

    if (doit) {
	trace_tp(pimv6_task,
		 TR_ROUTE,
		 0,
		 ("pimv6_mfc_delete_ifap: PIMv6 removing downstream interface %A(%s) for group %A source %A",
		  ifap->ifa_addr_local,
		  ifap->ifa_link->ifl_name,
		  &mfcp->mfc_group->group_key,
		  &mfcp->mfc_src));
	REMQUE(delete);
	mfc_free_downstream_v6(delete);

	assert(mfcp->ds_count);

	mfcp->ds_count--;

	krt_update_mfc(mfcp);

	if (!mfcp->ds_count) {
	    struct ifa_ps *upif_ips;

	    new = (prune_list *) task_block_alloc(pimv6_prune_block_index);
	    new->ifap = mfcp->upstream_ifap;
	    new->mfcp = mfcp;
	    new->holdtime = pimv6_default_prunetimeout; 
	    /*
	     * If no more downstream interfaces, time to prune upstream
	     */
	    upif_ips = &mfcp->upstream_ifap->ifa_ps[RTPROTO_PIMV6];
	    if (BIT_TEST(upif_ips->ips_state, IFPS_NOT_LEAF)) { 
		new->pending = FALSE;
		new->prune_time = time_sec + pimv6_default_prunetimeout; 
		if(pimv6_send_prune(mfcp))
		    pimv6_queue_prune(mfcp, new);
		else 
		    task_block_free(pimv6_prune_block_index, new);
	    } else { 
		if ((time_sec - task_time_start.gt_current.ut_sec)
		    < pimv6_default_hellointerval ) {
		    /* 
		     * Wait for 30 seconds in case we have not 
		     * heard Hello yet 
		     */
		    new->pending = PRUNE_WAIT_HELLO;
		    new->prune_time = time_sec + pimv6_default_hellointerval;
		    pimv6_queue_prune(mfcp, new);
		}
	    }
	} 
    }
}

/*
 * There are some cases when you only want to delete the interface
 * and not send a prune yet because you are in the process of changing
 * the upstream interface and will be adding another interface shortly.
 * Also, you only want to update the kernel once. Therefore, this routine
 * is the same as above except it only deletes the downstream interface
 * and nothing else.
 */

static void
pimv6_mfc_delete_only_ifap __PF2(mfcp, mfc *,
				 ifap, if_addr *)
{
    downstream *dsp, *delete = 0;

    DOWNSTREAM_LIST(dsp, mfcp->ds) {
        if (dsp->ds_proto == RTPROTO_PIMV6) {
	    if (sockaddrcmp_in6(dsp->ds_addr, ifap->ifa_addr_local)) {
		/*
		 * should never happen more than once!
		 */
		delete = dsp;
	    }
	}
    } DOWNSTREAM_LIST_END(dsp, mfcp->ds);

    if (delete) {
	trace_tp(pimv6_task,
		 TR_ROUTE,
		 0,
		 ("pimv6_mfc_delete_only_ifap: PIMv6 removing downstream interface %A(%s) for group %A source %A",
		  ifap->ifa_addr_local,
		  ifap->ifa_link->ifl_name,
		  &mfcp->mfc_group->group_key,
		  &mfcp->mfc_src));
	REMQUE(delete);
	mfc_free_downstream_v6(delete);

	assert(mfcp->ds_count);

	mfcp->ds_count--;
    }
}

void
pimv6_group_change __PF3(change, int,
			 ifap, if_addr *,
			 group, struct in6_addr *)
{
    group_node *gp = mfc_locate_group_v6(sockbuild_in6(0,(byte *)group));

    switch(change) {
    case MLD6_ADDED:
	trace_tp(pimv6_task,
		 TR_NORMAL,
		 0,
		 ("pimv6_group_change: group %A ADDED",
		  sockbuild_in6(0, (byte *)group)));
	if (gp) {
	    /*
	     * Visit each mfc entry for all sources in this group
	     * Check for pruned off leaf networks
	     * If found, update downstream interface list with
	     * joining interface.
	     *
	     * Look for existing prunes as well.
	     * If found, must send graft
	     */
	    mfc_source_visit_v6(gp, pimv6_mfc_add_ifap, (caddr_t) ifap);
	}
	break;

    case MLD6_REMOVED:
	trace_tp(pimv6_task,
		 TR_NORMAL,
		 0,
		 ("pimv6_group_change: group %A REMOVED",
		  sockbuild_in6(0, (byte *)group)));
	if (gp) {
	    /*
	     * Visit each mfc entry for all sources in this group
	     * Look to see if dropping interface can be pruned.
	     *
	     * If downstream interface becomes null as a result,
	     * can send prune upstream for particular source.
	     */
	    mfc_source_visit_v6(gp, pimv6_mfc_delete_ifap, (caddr_t) ifap);
	}
	break;
    }
}


/*
 *	Cleanup before re-init
 */
/*ARGSUSED*/
static void
pimv6_cleanup __PF1(tp, task *)
{
    /* Release policy list */

    adv_free_list(pimv6_int_policy);
    pimv6_int_policy = (adv_entry *) 0;

    if (tp) {
	trace_freeup(tp->task_trace);
    }
    trace_freeup(pimv6_trace_options);
}

/*
 *	re-init routine
 */
/*ARGSUSED*/
static void
pimv6_reinit __PF1(tp, task *)
{
    trace_set(pimv6_task->task_trace, pimv6_trace_options);
}


/*
 *	Terminating - clean up
 */
static void
pimv6_terminate __PF1(tp, task *)
{
    register if_addr *ifap;

    /*
     * Remove list of routers on each interface
     */

    IF_ADDR(ifap) {
	struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];
	if (!BIT_TEST(ips->ips_state, IFPS_NOOUT)) {
	    pimv6_router_purge(ifap);

	    if (ifap->pimv6_if_timer_timeout) {
		task_timer_delete((task_timer *) ifap->pimv6_if_timer_timeout);
		ifap->pimv6_if_timer_timeout = (void_t) 0;
	    }
	    if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
		if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST | IFS_POINTOPOINT)) {
		    (void) mld6_leave(ifap, sockbuild_in6(0,(byte *)&all_pimv6_routers));
		}
	    }
	}
    } IF_ADDR_END(ifap) ;

    pimv6_cleanup(tp);

    pimv6_timer_hello = (task_timer *) 0;
    pimv6_timer_graft = (task_timer *) 0;
    pimv6_timer_prune = (task_timer *) 0;
    pimv6_timer_mfc = (task_timer *) 0;

	/*
	 * After we are done with the socket,
	 * We tell ICMPV6 so it can terminate
	 */

	/* Remove receive routine */
    krt_unregister_mfc( EADDRNOTAVAIL, pimv6_mfc_request );
    krt_unregister_mfc( EADDRINUSE, pimv6_mfc_request );
    mld6_unregister_group_change( pimv6_group_change );

    /* And exit */
    task_delete(tp);
    tp = (task *) 0;
}

/*
 *	Deal with interface policy
 */
static void
pimv6_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);
}


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

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

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

	/* Fill in the parameters */
	while (--type) {
	    if ((cp = list[type])) {
		switch (type) {
		case PIMV6_CONFIG_ENABLE:
		    if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK)) {
			trace_only_tf(pimv6_trace_options,
				      0,
				      ("pimv6_control_set: can't enable pimv6 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(pimv6_trace_options,
				      0,
				      ("control_set: interface %A(%s) not multicast capable",
				       ifap->ifa_addr_local,
				       ifap->ifa_link->ifl_name));
		    }

		    break;

		case PIMV6_CONFIG_DISABLE:

		    BIT_SET(ips->ips_state, IFPS_NOIN);
		    BIT_SET(ips->ips_state, IFPS_NOOUT);
		    mld6_reset_ifproto(ifap, IPV6MULTI_PROTO_PIMV6);
		    break;

		case PIMV6_CONFIG_MODE:
		    if (cp->config_data)
			BIT_SET(ips->ips_state,IFPS_PIM_MODE);
		    else
			BIT_RESET(ips->ips_state,IFPS_PIM_MODE);
		    break;

		case PIMV6_CONFIG_PRUNETIMEOUT:
		    break;

		case PIMV6_CONFIG_INACTIVITYTIMEOUT:
		    break;
		}
	    }
	}

	config_resolv_free(list, PIMV6_CONFIG_MAX);
    }
}

/*
 *	Process changes in the routing table.
 */
static void
pimv6_flash __PF2(tp, task *,
		  change_list, rt_list *)
{
    rt_head *rth;

    rt_open(tp);

    RT_LIST(rth, change_list, rt_head) {
	mfc_src_list *msl = 0, *head = 0, *up_head = 0;
	rt_entry *new_rt = rth->rth_active;
	rt_entry *old_rt = (rt_entry *) 0;

	if (rth->rth_last_active
	    && rtbit_isset(rth->rth_last_active, tp->task_rtbit)) {
	    /* There was an MFC off the last active route */

	    old_rt = rth->rth_last_active;
	    rttsi_get(rth, tp->task_rtbit, (byte *) &head);
	    assert(head);
	}

	if (!old_rt && !new_rt) {
	    /* Close the table */
	    rt_close(tp, (gw_entry *) 0, 0, NULL);
	    return;
	}


	/*
	 * We have ADDED a new route. Look to see if there are
	 * any MFC's above that would benefit from this better route.
	 */
	if (!old_rt) {
	    rt_head *parent = rt_table_locate_parent(rth);
	    if (parent && rtbit_isset(parent->rth_active, tp->task_rtbit)) {
		rttsi_get(rth, tp->task_rtbit, (byte *) &up_head);
		if (up_head) {
		    msl = up_head->forw;
		    while(msl != up_head) {
			trace_tp(pimv6_task,
				 TR_ROUTE,
				 0,
				 ("pimv6_flash: checking group %A source %A for new route %A mask %A",
				  &msl->mfcp->mfc_group->group_key,
				  &msl->mfcp->mfc_src,
				  rth->rth_dest,
				  rth->rth_dest_mask));
			if (sockaddrcmp_mask(rth->rth_dest,
					     &msl->mfcp->mfc_src,
					     rth->rth_dest_mask)) {

			    mfc_src_list *next = msl->forw;
			    REMQUE(msl);
				/*
				 * If this route is better, alloc a new head
				 * and move the MFC to the new route
				 */
			    head = task_block_alloc(mfc_unicast_block_index_v6);
			    head->forw = head;
			    head->back = head;

				/*
				 * Check incoming interface for change.
				 */
			    if (RT_IFAP(new_rt) != msl->mfcp->upstream_ifap) {
				if_addr *save_ifap = msl->mfcp->upstream_ifap;
				pimv6_mfc_delete_only_ifap(msl->mfcp,
							   RT_IFAP(new_rt));
				msl->mfcp->upstream_ifap = RT_IFAP(new_rt);
				pimv6_mfc_add_ifap(msl->mfcp, (caddr_t) save_ifap);
			    }
			    trace_tp(pimv6_task,
				     TR_ROUTE,
				     0,
				     ("pimv6_flash: moving group %A source %A to new route %A mask %A",
				      &msl->mfcp->mfc_group->group_key,
				      &msl->mfcp->mfc_src,
				      rth->rth_dest,
				      rth->rth_dest_mask));
			    rttsi_set(rth, tp->task_rtbit, (byte *) &head);
			    rtbit_set(new_rt, tp->task_rtbit);
			    INSQUE(msl, head->forw);
			    msl = next;
			} else {
			    msl = msl->forw;
			}
		    }
		    /*
		     * If all of the MFC's got moved off parent,
		     * clean up parent and reset bits
		     */
		    if (up_head == up_head->forw) {
			task_block_free(mfc_unicast_block_index_v6, (void_t) up_head);
			rtbit_reset(parent->rth_active, tp->task_rtbit);
			rttsi_reset(parent, tp->task_rtbit);

		    }
		}
	    }
	    continue;
	}

	/*
	 * We have DELETED a route. See if we can move these MFC's
	 * to a less specific route above.
	 */
	if (!new_rt) {
	    mfc_src_list *next;
	    rt_head *parent = rt_table_locate_parent(rth);

	    if (parent) {
		msl = head->forw;
		while (msl != head) {
		    next = msl->forw;
		    REMQUE(msl);
		    /*
		     * If parent subsumes this route, move MFC's to parent.
		     * Also, perform upstream interface check.
		     */
		    if (sockaddrcmp_mask(parent->rth_dest,
					 &msl->mfcp->mfc_src,
					 parent->rth_dest_mask)) {
			if (RT_IFAP(parent->rth_active) !=
			    msl->mfcp->upstream_ifap) {
			    if_addr *save_ifap = msl->mfcp->upstream_ifap;
			    pimv6_mfc_delete_only_ifap(msl->mfcp,
						       RT_IFAP(parent->rth_active));
			    msl->mfcp->upstream_ifap = RT_IFAP(parent->rth_active);
			    pimv6_mfc_add_ifap(msl->mfcp, (caddr_t) save_ifap);
			}
			trace_tp(pimv6_task,
				 TR_ROUTE,
				 0,
				 ("pimv6_flash: moving group %A source %A to new route %A mask %A",
				  &msl->mfcp->mfc_group->group_key,
				  &msl->mfcp->mfc_src,
				  parent->rth_dest,
				  parent->rth_dest_mask));
			if (rtbit_isset(parent->rth_active, tp->task_rtbit)) {
			    rttsi_get(rth, tp->task_rtbit, (byte *) &up_head);
			} else {
			    up_head = task_block_alloc(mfc_unicast_block_index_v6);
			    up_head->forw = up_head;
			    up_head->back = up_head;
			}
			INSQUE(msl, up_head->forw);
		    } else {
			/*
			 * No route back to source. Must delete MFC.
			 */
			BIT_RESET(msl->mfcp->mfc_proto,
				  IPV6MULTI_BIT(IPV6MULTI_PROTO_PIMV6));
			if (!msl->mfcp->mfc_proto) {
			    pimv6_clean_mfc(msl->mfcp);
			    pimv6_reset_mfc_timer(msl->mfcp);
			    (void) krt_delete_cache(&msl->mfcp->mfc_group->group_key, &msl->mfcp->mfc_src);
			    mfc_delete_node_v6(msl->mfcp);
			}
			task_block_free(mfc_unicast_block_index_v6, (void_t) msl);
		    }
		    msl = next;
		}
	    } else {
		/*
		 * No route back to source. Must delete MFCs.
		 */
		msl = head->forw;
		while (msl != head) {
		    next = msl->forw;
		    REMQUE(msl);

		    BIT_RESET(msl->mfcp->mfc_proto,
			      IPV6MULTI_BIT(IPV6MULTI_PROTO_PIMV6));
		    if (!msl->mfcp->mfc_proto) {
			pimv6_clean_mfc(msl->mfcp);
			pimv6_reset_mfc_timer(msl->mfcp);
			(void) krt_delete_cache(&msl->mfcp->mfc_group->group_key, &msl->mfcp->mfc_src);
			mfc_delete_node_v6(msl->mfcp);
		    }
		    task_block_free(mfc_unicast_block_index_v6, (void_t) msl);
		    msl = next;
		}
	    }
	    rtbit_reset(old_rt, tp->task_rtbit);
	    rttsi_reset(rth, tp->task_rtbit);
	    task_block_free(mfc_unicast_block_index_v6, head);
	    continue;
	}
	/*
	 * We have CHANGED a route. Perform upstream interface check.
	 */
	if (old_rt && new_rt) {
	    /* If the upstream interface has changed,
	     * delete the new one from the downstream interface list
	     * and add it as the new upstream interface. Then add the
	     * old upstream interface list and a new downstream interface.
	     */
	    if ((RT_IFAP(old_rt) != RT_IFAP(new_rt))
		&& (msl != NULL)) {
		pimv6_mfc_delete_only_ifap(msl->mfcp, RT_IFAP(new_rt));

		msl->mfcp->upstream_ifap = RT_IFAP(new_rt);

		pimv6_mfc_add_ifap(msl->mfcp, (caddr_t) RT_IFAP(old_rt));
	    }
	    trace_tp(pimv6_task,
		     TR_ROUTE,
		     0,
		     ("pimv6_flash: route change %A mask %A",
		      rth->rth_dest,
		      rth->rth_dest_mask));
	    if (old_rt != new_rt)
		/*
		 * Reset old MFC indication
		 */
		rtbit_reset(old_rt, tp->task_rtbit);
	    continue;
	}
    } RT_LIST_END(rth, change_list, rt_head) ;

    /* Close the table */
    rt_close(tp, (gw_entry *) 0, 0, NULL);
}


/*
 *	Deal with an interface status change
 */
static void
pimv6_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;
    }
    
    /* Only link-local address SHOULD be used for inter-router
     * communication.
     */
    if (!IS_LINKLADDR6(sock2in6(ifap->ifa_addr_local))) {
	return;
    } 
    switch (ifap->ifa_change) {
    case IFC_NOCHANGE:
    case IFC_ADD:
	if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    pimv6_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);
	    }
	    if (!BIT_TEST(ips->ips_state, IFPS_NOOUT)) {
		/*
		 * If we can lock this interface for PIM,
		 * do so, otherwise, another multicast protocol has it
		 */
		if (BIT_TEST(ifap->ifa_state, 
			     IFS_MULTICAST | IFS_BROADCAST | IFS_POINTOPOINT)) {
		    if (mld6_set_ifproto(ifap, IPV6MULTI_PROTO_PIMV6) == TRUE) {
			mld6_join(ifap, sockbuild_in6(0,(byte *)&all_pimv6_routers));
			pimv6_commence_hello(ifap);
		    } 
		} else {
		    BIT_SET(ips->ips_state, IFPS_NOIN);
		    BIT_SET(ips->ips_state, IFPS_NOOUT);
		}
	    }
	}
	break;

    case IFC_DELETE:
    case IFC_DELETE|IFC_UPDOWN:

	if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST) &&
	    !BIT_TEST(ips->ips_state, IFPS_NOOUT)) {
	    if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST | IFS_POINTOPOINT)) {
	        mld6_leave(ifap, sockbuild_in6(0,(byte *)&all_pimv6_routers));
	    }
	}
	pimv6_router_purge(ifap);
	pimv6_control_reset(tp, ifap);
	mfc_visit_v6(pimv6_mfc_delete_upifap, (caddr_t)ifap);
	mfc_visit_v6(pimv6_mfc_delete_ifap, (caddr_t)ifap);
	mfc_visit_v6(pimv6_mfc_delete_prune, (caddr_t)ifap);
	mld6_reset_ifproto(ifap, IPV6MULTI_PROTO_PIMV6);
	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 */

		pimv6_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);
		}
		if (!BIT_TEST(ips->ips_state, IFPS_NOOUT)) {
		    /*
		     * If we can lock this interface for PIM,
		     * do so, otherwise, another multicast protocol has it
		     */
		    if (mld6_set_ifproto(ifap, IPV6MULTI_PROTO_PIMV6) == TRUE) {
			if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
			    if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST|IFS_POINTOPOINT)) {
				(void) mld6_join(ifap, sockbuild_in6(0,(byte *)&all_pimv6_routers));
				pimv6_commence_hello(ifap);
				if (pimv6_timer_hello) {
				    pimv6_send_hello(ifap);
				}
			    }
			}
		    } else {
			BIT_SET(ips->ips_state, IFPS_NOIN);
			BIT_SET(ips->ips_state, IFPS_NOOUT);
		    }
		}
	    } else {
		/* UP to DOWN transition */

		if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST) && 
		    !BIT_TEST(ips->ips_state, IFPS_NOOUT)) {
		    if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST|IFS_POINTOPOINT)) {
		    	(void) mld6_leave(ifap, sockbuild_in6(0,(byte *)&all_pimv6_routers));
		    }
		}
		pimv6_router_purge(ifap);
		pimv6_control_reset(tp, ifap);
		mfc_visit_v6(pimv6_mfc_delete_upifap, (caddr_t)ifap);
		mfc_visit_v6(pimv6_mfc_delete_ifap, (caddr_t)ifap);
		mfc_visit_v6(pimv6_mfc_delete_prune, (caddr_t)ifap);
		mld6_reset_ifproto(ifap, IPV6MULTI_PROTO_PIMV6);
	    }
	}
	break;
    }
}

/*
 * Here we initialize the protocol preferences for sending and receiving
 * Assert Messages. We have defined these to match the Cisco implementation.
 * Its not pretty but it will enhance the interoperability.
 */

static void
pimv6_pref_init __PF0(void)
{
    pimv6_preference[RTPROTO_DIRECT] = PIMV6_DIRECT_PREFERENCE;
    pimv6_preference[RTPROTO_KERNEL] = PIMV6_KERNEL_PREFERENCE;
    pimv6_preference[RTPROTO_STATIC] = PIMV6_STATIC_PREFERENCE;
    pimv6_preference[RTPROTO_OSPF] = PIMV6_OSPF_PREFERENCE;
    pimv6_preference[RTPROTO_OSPF_ASE] = PIMV6_OSPF_PREFERENCE;
    pimv6_preference[RTPROTO_RIPNG] = PIMV6_RIPNG_PREFERENCE;
    pimv6_preference[RTPROTO_HELLO] = PIMV6_HELLO_PREFERENCE;
    pimv6_preference[RTPROTO_BGP] = PIMV6_BGP_PREFERENCE;
    pimv6_preference[RTPROTO_EGP] = PIMV6_EGP_PREFERENCE;
    pimv6_preference[RTPROTO_IDRP] = PIMV6_IDRP_PREFERENCE;
    pimv6_preference[RTPROTO_ISIS] = PIMV6_ISIS_PREFERENCE;
    pimv6_preference[RTPROTO_SLSP] = PIMV6_SLSP_PREFERENCE;
}


void
pimv6_set_pref __PF2(rtproto, int,
		     pref, pref_t)
{
    pimv6_preference[rtproto] = pref;
}


#ifdef  USE_ZLIB
#undef fprintf
#define fprintf	gzprintf
#endif
static void
pimv6_int_dump __PF2(fd, FILE *,
		     list, config_entry *)
{
    register config_entry *cp;

    CONFIG_LIST(cp, list) {
	switch (cp->config_type) {
	case PIMV6_CONFIG_ENABLE:
	    (void) fprintf(fd, " %s enabled",
			   cp->config_data ? "" : "not");
	    break;

	case PIMV6_CONFIG_DISABLE:
	    (void) fprintf(fd, " %s disabled",
			   cp->config_data ? "" : "not");
	    break;

	case PIMV6_CONFIG_MODE:
	    break;

	case PIMV6_CONFIG_PRUNETIMEOUT:
	    (void) fprintf(fd, " prune-timeout %u secs", cp->config_data);
	    break;

	case PIMV6_CONFIG_INACTIVITYTIMEOUT:
	    (void) fprintf(fd, " inactivity-timeout %u secs", cp->config_data);
	    break;
	default:
	    assert(FALSE);
	    break;
	}
    } CONFIG_LIST_END(cp, list) ;
}

/* ARGSUSED */
static void
pimv6_tsi_dump __PF4(fp, FILE *,
		     rth, rt_head *,
		     data, void_t,
		     pfx, const char *)
{
    mfc_src_list *head, *msl;

    rttsi_get(rth, pimv6_task->task_rtbit, (byte *) &head);

    if (head) {
	msl = head->forw;
	while (msl != head) {
	    mfc *mfcp = msl->mfcp;

	    (void) fprintf(fp, "%sPIM %A, %A\n",
			   pfx,
			   &mfcp->mfc_group->group_key,
			   &mfcp->mfc_src);
	    msl = msl->forw;
	}
    }
}

static void
pimv6_dump __PF2(tp, task *,
		 fd, FILE *)
{
    if_addr	*ifap;
    mfc_list	*mp;
    join_list	*jp;
    prune_list	*pp;
    graft_list	*gp;

    if (pimv6_int_policy) {
	(void) fprintf(fd, "\tInterface policy:\n");
	control_interface_dump(fd, 2, pimv6_int_policy, pimv6_int_dump);
    }

    /*
     * Dump list of routers on each interface
     */
    IF_ADDR(ifap) {
	struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_PIMV6];
	router_list *list = (router_list *) ifap->pimv6_if_router_list;
	router_list *rp;

	if (!BIT_TEST(ips->ips_state, IFPS_NOIN)) {
	    (void) fprintf(fd, "\tInterface %A(%s):  Leaf Status: %s   Mode: %s\n",
			   ifap->ifa_addr_local,
			   ifap->ifa_link->ifl_name,
			   BIT_TEST(ips->ips_state,IFPS_NOT_LEAF) ? "False":"True",
			   BIT_TEST(ips->ips_state,IFPS_PIM_MODE) ? "Sparse":"Dense");
	    if (list && BIT_TEST(ifap->ifa_state, IFS_BROADCAST|IFS_POINTOPOINT)) {
		(void) fprintf(fd, "\t\t PIM Router        Last Refresh\n");

		(void) fprintf(fd, "\tDR");
		ROUTER_DR_LIST(rp, list) {
		    (void) fprintf(fd, "\t%-15A %5u secs ago\n\t",
				   rp->router_addr,
				   time_sec - rp->refresh_time);
		} ROUTER_DR_LIST_END(rp, list);
	    }
	    (void) fprintf(fd, "\n");
	}
    } IF_ADDR_END(ifap) ;

    (void) fprintf(fd, "\n\tCurrent Prune List:\n");
    (void) fprintf(fd, "\t\t   Group           Source       Times Out\n");
    PRUNE_LIST(pp, &prune_head) {
	(void) fprintf(fd, "\t\t%-15A %-15A  %4u secs\n",
		       &pp->mfcp->mfc_group->group_key,
		       &pp->mfcp->mfc_src,
		       pp->prune_time - time_sec);
    } PRUNE_LIST_END(pp, &prune_head);

    (void) fprintf(fd, "\n\tCurrent Join Delay List:\n");
    JOIN_LIST(jp, &join_head) {
	(void) fprintf(fd,
		       "\t\tWaiting %d more second(s) to send Join for Group %A Source %A out Interface %A(%s)\n",
		       jp->join_time - time_sec,
		       &jp->mfcp->mfc_group->group_key,
		       &jp->mfcp->mfc_src,
		       jp->ifap->ifa_addr_local,
		       jp->ifap->ifa_link->ifl_name);
    } JOIN_LIST_END(jp, &join_head);

    (void) fprintf(fd, "\n\tCurrent Unacknowleged Grafts:\n");
    GRAFT_LIST(gp, &graft_head) {
	(void) fprintf(fd,
		       "\t\tGroup %A added on interface %A(%s) sent to %A, %3d seconds ago\n",
		       sockbuild_in6(0, (byte *)&gp->graft_group),
		       gp->graft_ifap->ifa_addr_local,
		       gp->graft_ifap->ifa_link->ifl_name,
		       &gp->graft_dst,
		       time_sec - gp->graft_time);
    } GRAFT_LIST_END(gp, &graft_head);

    (void) fprintf(fd, "\n\tMFC Timeout Queue:\n");
    (void) fprintf(fd, "\t\t   Group           Source       Times Out\n");
    PIMV6_MFC_LIST(mp, &mfc_head) {
	(void) fprintf(fd, "\t\t%-15A %-15A  %4u secs\n",
		       &mp->mfcp->mfc_group->group_key,
		       &mp->mfcp->mfc_src,
		       mp->mfc_timeout - time_sec);
    } PIMV6_MFC_LIST_END(mp, &mfc_head);

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


/*
 *	Initialize static variables
 */
void
pimv6_var_init()
{
    BIT_RESET(pimv6_config_status, PIMV6_ENABLED);

    pimv6_default_hellointerval = 30;
    pimv6_default_routertimeout = (int)(3.5 * 
					(float)pimv6_default_hellointerval);
    pimv6_default_prunetimeout = 180;
    pimv6_default_inactivitytimeout = 180;
    pimv6_default_graftacktimeout = 60;

    /*
     * initialize preference values
     */
    pimv6_pref_init();
}


/*
 * initialize PIMv6 socket and PIMv6 task
 */

/*ARGSUSED*/
void
pimv6_init __PF0(void)
{
    if (!pimv6_task) {
	trace_inherit_global(pimv6_trace_options, 
			     pimv6_trace_types, (flag_t) TR_DETAIL);
	pimv6_timer_hello = (task_timer *) 0;
	pimv6_timer_graft = (task_timer *) 0;
	pimv6_timer_prune = (task_timer *) 0;
	pimv6_timer_mfc = (task_timer *) 0;
	pimv6_task = task_alloc("PIMV6",
				TASKPRI_PROTO,
				pimv6_trace_options);

	pimv6_task->task_proto = IPPROTO_PIMV6;
	pimv6_task->task_rtproto = RTPROTO_PIMV6;
	pimv6_task->task_rtfamily = AF_INET6;
	pimv6_task->task_rtbit = rtbit_alloc(pimv6_task,
					     FALSE,
					     sizeof (mfc_src_list *),
					     (void_t) 0,
					     pimv6_tsi_dump);

	task_set_recv(pimv6_task, pimv6_recv);
	task_set_cleanup(pimv6_task, pimv6_cleanup);
	task_set_dump(pimv6_task, pimv6_dump);
	task_set_ifachange(pimv6_task, pimv6_ifachange);
	task_set_reinit(pimv6_task, pimv6_reinit);
	task_set_terminate(pimv6_task, pimv6_terminate);
	task_set_flash(pimv6_task, pimv6_flash);

	if((pimv6_task->task_socket=task_get_socket(pimv6_task, 
						    AF_INET6, SOCK_RAW, IPPROTO_PIMV6))<0) {
	    task_quit(errno);
	}
	if (!task_create(pimv6_task)) {
	    task_quit(EINVAL);
	}

	/* 
	 * RECVPKTINFO option return dstaddr and receiving i/f,
	 * so we don't use TASKOPTION_RCVDSTADDR
	 */
	if (task_set_option(pimv6_task,
			    TASKOPTION_RCVIF,
			    TRUE ) < 0) {
	    task_quit(errno);
	}

	if (task_set_option(pimv6_task,
			    TASKOPTION_NONBLOCKING,
			    TRUE) < 0) {
	    task_quit(errno);
	}
	/* Disable reception of our own packets */
	if (task_set_option(pimv6_task,
			    TASKOPTION_MULTI_LOOP,
			    FALSE) < 0) {
	    task_quit(errno);
	}
	/* Assume for now that only local wire multicasts will be sent */
	if (task_set_option(pimv6_task,
			    TASKOPTION_MULTI_TTL,
			    1) < 0) {
	    task_quit(errno);
	} 
	if (task_set_option(pimv6_task,
			    TASKOPTION_IPHEADER_INC,
			    TRUE) < 0) {
	    task_quit(errno);
	}	

	if (task_set_option(pimv6_task, 
			    TASKOPTION_REUSEADDR, 
			    TRUE) < 0) {
	    task_quit(errno);
	}


	task_alloc_recv(pimv6_task, PIMV6_PACKET_MAX);
	task_alloc_send(pimv6_task, PIMV6_PACKET_MAX);

	/* Do all we can to avoid losing packets */
	if (task_set_option(pimv6_task,
			    TASKOPTION_RECVBUF,
			    task_maxpacket) < 0) {
	    task_quit(errno);
	}

	if (task_set_option(pimv6_task,
			    TASKOPTION_SENDBUF,
			    task_maxpacket) < 0) {
	    task_quit(errno);
	}

#ifdef  KRT_IPV6MULTI_RTSOCK
	/*
	 * Since the 3.3 release assumes we will
	 * include the IP header, we include it for
	 * the gated kernel as well so the code is
	 * more similar.
	 */
	if (task_set_option(pimv6_task,
			    TASKOPTION_IPHEADER_INC,
			    TRUE) < 0) {
	    task_quit(errno);
	}
#endif          /* KRT_IPV6MULTI_RTSOCK */

	/* Flush all old forwarding cache in kernel */
	krt_flush_cache();

	mld6_register_group_change( pimv6_group_change );
	krt_register_mfc( EADDRNOTAVAIL, pimv6_mfc_request );
	krt_register_mfc( EADDRINUSE, pimv6_mfc_request );

	pimv6_router_block_index =
	    task_block_init(sizeof (router_list),
			    "pimv6_router_list");
	pimv6_rpf_block_index =
	    task_block_init(sizeof (rpf_list),
			    "pimv6_rpf_list");
	pimv6_graft_block_index =
	    task_block_init(sizeof (graft_list),
			    "pimv6_graft_list");
	pimv6_prune_block_index =
	    task_block_init(sizeof (prune_list),
			    "pimv6_prune_list");
	pimv6_join_block_index =
	    task_block_init(sizeof (join_list),
			    "pimv6_join_list");
	pimv6_assert_block_index =
	    task_block_init(sizeof (assert_list),
			    "pimv6_assert_list");
	pimv6_mfc_block_index =
	    task_block_init(sizeof (mfc_list),
			    "pimv6_mfc_list");

    }
    pimv6_current_status = pimv6_config_status;
}

