/*
 * Copyright (c) 1996 The Regents of the University of Michigan
 * All Rights Reserved
 * 
 * License to use, copy, modify, and distribute this software and its
 * documentation can be obtained from Merit at the University of Michigan.
 * 
 * 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.
 * 
 * This copyright has ben automaticly added by the util/addcopyright.pl program.
 * __END_OF_COPYRIGHT__
 */

#define	INCLUDE_IF

#include "include.h"
#include "inet6.h"
#include "inet6_multi.h"
#include "icmpv6_group.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_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 ipv6 *, int, struct in6_addr *, 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 *);

/* Function to create IPv6 header */
void pimv6_ipv6_hdr(struct ipv6 *, u_int16, u_int8, u_int8,
	struct in6_addr *, struct in6_addr *);

/* Convert IPv6 address to printable (loggable) representation. */
static char digits[] = "0123456789abcdef";
static int ipv6round = 0;

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 IPv6 header to be used for PIMv6 raw socket */
void
pimv6_ipv6_hdr(struct ipv6 *ip, u_int16 len, u_int8 nh, u_int8 hop,
	struct in6_addr *src, struct in6_addr *dst)
{
	ip->ip6_head = htonl(0x60000000);	/* ver+pri+flow_label */
	ip->ip6_len = htons(len);		/* payload_length */
	ip->ip6_nh = nh;			/* next header */
	ip->ip6_hlim = hop;			/* hop limit */
	ip->ip6_src = *src;			/* source address */
	ip->ip6_dst = *dst;			/* destination address */
}

/* 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 ipv6 *ip = task_get_send_buffer(struct ipv6 *);
    struct pimv6hdr *pkt = (struct pimv6hdr *) (ip+1);
    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 (IS_LINKLADDR6(sock2in6(ifap->ifa_addr_local))) {
		rc = pimv6_send(ifap, ip, len, &all_pimv6_routers, TRUE);
    } 

    /* Should we trace this packet? */

    if (TRACE_PACKET_SEND_TP(pimv6_task,
			     pkt->type,
			     PIMV6_CANDIDATE_RP_ADVERTISEMENT,
			     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)));
		assert(gp);
		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) {
		if_addr *ifap2;
                
		IF_ADDR(ifap2) {
			if ((ifap2->ifa_link == ifap->ifa_link)
			   && (ifap2->ifa_addr_local->in6.gin6_family == AF_INET6)
			   && (IS_LINKLADDR6(ifap2->ifa_addr_local->in6.gin6_addr))) {
					ifap = ifap2;
				   break; 
			}   
		} IF_ADDR_END(ifap2) ;
		if (mfcp->upstream_ifap == ifap) 
			return;
	}
		
    new = (rpf_list *) task_block_alloc(pimv6_rpf_block_index);
    new->src_addr = mfcp->mfc_src.g6_addr;
    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 = up->nbr->g6_addr;
		else
			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(lp->router_addr->g6_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 ipv6 *ip = task_get_send_buffer(struct ipv6 *);
    struct pimv6hdr *pkt = (struct pimv6hdr *) (ip+1);
    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, ip, len, &all_pimv6_routers, FALSE);

    /* Should we trace this packet? */

    if (TRACE_PACKET_SEND_TP(pimv6_task,
			     pkt->type,
			     PIMV6_CANDIDATE_RP_ADVERTISEMENT,
			     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 *,
	        ip, struct ipv6 *,
	        iplen, int,
	        dst, struct in6_addr *,
	        loop, int)
{
	int             rc;
	static if_addr *last_ifap;

	/*
	 * Include the IPV6 header
	 */

	pimv6_ipv6_hdr(ip, iplen, IPPROTO_PIMV6, IP_DEFAULT_MULTICAST_TTL, 
		&sock2in6(ifap->ifa_addr_local), dst);

	if (IS_MULTIADDR6(*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_packet(pimv6_task, (void_t) ip, iplen+sizeof(struct ipv6), 0, sockbuild_in6(htons(PIMV6_PORT), (byte *)dst));

	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	ipv6 *ip = task_get_send_buffer(struct ipv6 *);
    struct	pimv6hdr *pkt = (struct pimv6hdr *) (ip+1);
    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, ip, len, &dst->g6_addr, FALSE);

		/* Should we trace this packet? */

		if (TRACE_PACKET_SEND_TP(pimv6_task,
					 pkt->type,
					 PIMV6_CANDIDATE_RP_ADVERTISEMENT,
					 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, ip, len, &dst->g6_addr, FALSE);

	/* Should we trace this packet? */

	if (TRACE_PACKET_SEND_TP(pimv6_task,
				 pkt->type,
				 PIMV6_CANDIDATE_RP_ADVERTISEMENT,
				 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);
			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 (icmpv6_group_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) {
	    /*
	     * This MFC is not used for 3 minutes, delete it
	     */
		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->icmpv6_group_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(mfcp->mfc_group->group_key.g6_addr, 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 { 
			/* 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, &mfcp->mfc_group->group_key.g6_addr, 
			&mfcp->mfc_src.g6_addr);
	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;
		register struct ipv6 *ipv6;
		if_addr        *ifap;
		sockaddr_un 	*dst;

		ipv6 = task_get_recv_buffer(struct ipv6 *);
		pimv6 = (struct pimv6hdr *) ((caddr_t) ipv6 + sizeof(struct ipv6));

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

		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 (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;
		}

		/* 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_withdst(task_recv_srcaddr);
		if (ifap == 0)
			ifap = if_withlcladdr(task_recv_srcaddr, FALSE); 

		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 (icmpv6_group_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;
		}

		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 (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 (preference < pas->preference ||
	    (preference == pas->preference &&
	     up->metric < pas->metric) ||
	     (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->icmpv6_group_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, &mfcp->mfc_group->group_key.g6_addr, 
					&mfcp->mfc_src.g6_addr);
		/*
		 * If no directly connected group members or ...
		 */
	    if (glist) {
			GROUP_LIST(gp, glist) {
			/*
			 * Has someone requested this group ?
			 */
				if (SAME_ADDR6(mfcp->mfc_group->group_key.g6_addr, 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;
    struct ipv6 *ip = (struct ipv6 *) ((char*)pimv6hdr - sizeof(struct ipv6));
    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, ip, pimv6len, &task_recv_srcaddr->g6_addr, FALSE);
		    /* Should we trace this packet? */

		    if (TRACE_PACKET_SEND_TP(pimv6_task,
					     pimv6hdr->type,
					     PIMV6_CANDIDATE_RP_ADVERTISEMENT,
					     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)));
		}
		     /*
		      * 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, mfcp->mfc_group->group_key.g6_addr)) {
					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)) {
			icmpv6_group_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);
    }
	    /* set existing timer to new time */
    else {
	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)) {
		icmpv6_group_enable_dr_status(ifap);
    } else {
		icmpv6_group_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
			 */
			pimv6_send_join_prune_graft(PIMV6_MSG_JOIN,
				      &jp->mfcp->mfc_group->group_key.g6_addr);

		}
		/*
		 * 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;
		}
    }
    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;

    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);
    }
}

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, &mfcp->mfc_group->group_key.g6_addr);

	/*
	 * 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)",
	      ifap->ifa_addr_local,
	      ifap->ifa_link->ifl_name));

    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) {
			if_addr *ifap2;
 
			IF_ADDR(ifap2) {
				if ((ifap2->ifa_link == tmp_ifap->ifa_link)
				   && (ifap2->ifa_addr_local->in6.gin6_family == AF_INET6)
				   && (IS_LINKLADDR6(ifap2->ifa_addr_local->in6.gin6_addr))) {  
						tmp_ifap = ifap2;
					   break;
				}
			} IF_ADDR_END(ifap2) ;
		}
        if (mfcp->upstream_ifap != tmp_ifap) {
			pimv6_send_graft(mfcp->upstream_ifap, &mfcp->mfc_group->group_key.g6_addr);
		}
    }

    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);
	}
}

static void
pimv6_mfc_delete_ifap  __PF2(mfcp, mfc *,
			   data, caddr_t)
{
    int count = 0, 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;
			} else {
				count++;
			}
		}
    } 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->icmpv6_group_if_group_list;
		doit = 1;
		if (glist) {
			GROUP_LIST(gp, glist) {
				/*
				 * Has someone request this group ?
				 */
				if (SAME_ADDR6(mfcp->mfc_group->group_key.g6_addr, 
								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 (!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 { 
				/* 
				 * 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 ICMPV6_GROUP_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 ICMPV6_GROUP_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) icmpv6_group_drop(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 );
    icmpv6_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);
		    icmpv6_group_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 (icmpv6_group_set_ifproto(ifap, IPV6MULTI_PROTO_PIMV6) == TRUE) {
				icmpv6_group_add(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)) {
	        icmpv6_group_drop(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);
	icmpv6_group_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 (icmpv6_group_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) icmpv6_group_add(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) icmpv6_group_drop(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);
		icmpv6_group_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;
}


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");
}


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

    pimv6_default_hellointerval = 30;
    pimv6_default_routertimeout = 105;
    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()
{
    if (!pimv6_task) {
	trace_inherit_global(pimv6_trace_options, pimv6_trace_types, (flag_t) 0);
	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);
	}

	if (task_set_option(pimv6_task,
			    TASKOPTION_RCVDSTADDR,
			    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();

	icmpv6_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;
}

