/*
 * Public Release 3
 * 
 * $Id: dvmrp.c,v 1.2 1997/07/17 22:41:26 chopps Exp $
 *
 *  Author: Tom Pusateri <pusateri@netedge.com>
 */

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


#define	INCLUDE_IF

#include "include.h"
#include "inet.h"
#include "inet_multi.h"
#include "krt.h"
#include "krt_ipmulti.h"
#include "igmp.h"
#include "dvmrp.h"
#include "dvmrp_targets.h"

PROTOTYPE(dvmrp_recv_probe, static void, (task *, if_addr *, nbr_list *, struct igmp *, int, int));
PROTOTYPE(dvmrp_recv_report, static void, (task *, if_addr *, nbr_list *, struct igmp *, int));
PROTOTYPE(dvmrp_recv_prune, static void, (task *, if_addr *, nbr_list *, struct igmp *, int));
PROTOTYPE(dvmrp_recv_graft, static void, (task *, if_addr *, nbr_list *, struct igmp *, int));
PROTOTYPE(dvmrp_recv_graftack, static void, (task *, if_addr *, nbr_list *, struct igmp *, int));
PROTOTYPE(dvmrp_send_report, static void, (struct _dvmrp_target *, byte *, int len));
PROTOTYPE(dvmrp_send_graft, static void, (mfc *));
PROTOTYPE(dvmrp_add_graft, static void, (mfc *, sockaddr_un *));
PROTOTYPE(dvmrp_send_graftack, static void, (if_addr *, sockaddr_un *, u_int32, u_int32));
PROTOTYPE(dvmrp_group_change, void, (int, if_addr *, u_int32));
PROTOTYPE(dvmrp_nbr_timeout, static void, (task_timer *, time_t));
PROTOTYPE(dvmrp_age, static void, (task_timer *, time_t));
PROTOTYPE(dvmrp_refresh_router_timer, static void, (if_addr *, time_t));
PROTOTYPE(dvmrp_dr_election, static void, (if_addr *));
PROTOTYPE(dvmrp_nbr_purge, static void, (if_addr *));
PROTOTYPE(dvmrp_supply, static void, (struct _dvmrp_target *, int));
PROTOTYPE(dvmrp_mfc_add_ifap, static void, (mfc *, caddr_t));
PROTOTYPE(dvmrp_mfc_add_nbr, static void, (mfc *, caddr_t));
PROTOTYPE(dvmrp_mfc_delete_ifap, static void, (mfc *, caddr_t));
PROTOTYPE(dvmrp_mfc_delete_nbr, static void, (mfc *, caddr_t));
PROTOTYPE(dvmrp_queue_prune, static void, (mfc *, prune_list *));
PROTOTYPE(dvmrp_set_mfc_timer, static void, (mfc *, time_t));

static task *dvmrp_task = (task *) 0;
static flag_t dvmrp_flags = 0;

static u_int32 generation_id;		/* unique ID for prune reset */
					/* Default metric when propogating */
static metric_t dvmrp_default_metric = 0;

/*
 * Timers used by DVMRP
 */

static task_timer *dvmrp_timer_probe;	/* to send Neighbor Probes */
static task_timer *dvmrp_timer_update;	/* to send Route Reports */
static task_timer *dvmrp_timer_age;	/* to age routes */
static task_timer *dvmrp_timer_flash;	/* to send flash updates */

/*
 * protocol defaults
 */

int dvmrp_current_status = 0;		/* whether DVMRP is currently on */
int dvmrp_config_status = 0;		/* whether DVMRP is on in new config */

sockaddr_un *inaddr_dvmrp_group = 0;	/* dvmrp Group - 224.0.0.4 */

time_t dvmrp_default_prunetimeout;
time_t dvmrp_default_graftacktimeout;
time_t dvmrp_default_mfctimeout;

#define IFPS_NOT_LEAF		IFPS_KEEP1

typedef struct _dvmrp_ifparm {
    u_int32	rate_limit;
    u_int32	threshold;
} dvmrp_ifparm;

static block_t dvmrp_ifparam_block_index;
static block_t dvmrp_ifnbr_block_index;

#define dvmrp_if_nbr_list	ifa_ps[RTPROTO_DVMRP].ips_datas[0]
#define dvmrp_nbr_timer_timeout	ifa_ps[RTPROTO_DVMRP].ips_datas[1]
#define dvmrp_if_params		ifa_ps[RTPROTO_DVMRP].ips_datas[2]
#define DVMRP_IF_RATE_LIMIT(if)	(((dvmrp_ifparm *)(if->dvmrp_if_params))->rate_limit)
#define DVMRP_IF_THRESHOLD(if)	(((dvmrp_ifparm *)(if->dvmrp_if_params))->threshold)

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

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

static block_t dvmrp_prune_block_index;

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

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

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

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


/*
 * Neighbor List for tracking other local DVMRP routers
 */

static block_t dvmrp_nbr_block_index;

#define	NBR_DR_LIST(np, list)	{ for (np = (list)->dr_back; np != list; np = np->dr_back)
#define NBR_DR_LIST_END(np, list)	if (np == list) np = (nbr_list *) 0; }

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

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

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

#define NBR_DR_DEQ(elem) { \
    register nbr_list *Xe = elem; \
    (Xe->dr_back->dr_forw = Xe->dr_forw)->dr_back = Xe->dr_back; \
}
/*
 * configuration interface lists
 */

adv_entry *dvmrp_int_policy = 0;	/* DVMRP control info */
adv_entry *dvmrp_import_list;		/* List of nets to import from DVMRP */
adv_entry *dvmrp_export_list;		/* List of nets to exports routes to */


/*
 * DVMRP routing table
 */

/* Scan a change list */
#define	DVMRP_RTCHANGE_LIST(rte, rtl) \
    if (rtl) { \
	dvmrp_rt_list *rtl_root = (rtl)->rtl_root; \
	do { \
	    if ((rte = (rtl)->rtl_rt))

#define	DVMRP_RTCHANGE_LIST_END(rte, rtl) \
	    rte = (rt_entry *) 0; \
	} while (((rtl) = (rtl)->rtl_next)) ; \
	(rtl) = rtl_root; \
    }

#define DVMRP_RTCHANGE_LIST_ADD(root, rt) \
	{ \
	    dvmrp_rt_list *Xrtl = (dvmrp_rt_list *) task_block_alloc(dvmrp_rtlist_block_index); \
	    if (!root) { \
		Xrtl->rtl_next = root; \
		Xrtl->rtl_root = Xrtl; \
		root = Xrtl; \
	    } else { \
		Xrtl->rtl_next = root->rtl_next; \
		root->rtl_next = Xrtl; \
		Xrtl->rtl_root = root; \
	    } \
	    Xrtl->rtl_rt = rt; \
	}

/* Reset a list */
#define	DVMRP_RTCHANGE_LIST_RESET(rtl) \
	if (rtl) (rtl) = (rtl)->rtl_root; \
	    while (rtl) { \
		register dvmrp_rt_list *Xrtln = (rtl)->rtl_next; \
		task_block_free(dvmrp_rtlist_block_index, (void_t) (rtl)); \
		(rtl) = Xrtln; \
	    }

static dvmrp_rt_list *dvmrp_rtlist_root = 0;
static block_t dvmrp_rtlist_block_index = (block_t) 0;

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

typedef struct _graft_list {
    struct	_graft_list *forw, *back;
    mfc		*graft_mfc;
    time_t	graft_time;
    sockaddr_un	*graft_dst;
} graft_list;

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

static block_t dvmrp_graft_block_index;

static task_timer *dvmrp_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; }

/*
 * Global masks storage for sending DVMRP route reports
 */

static u_int32 sendmask, lastsendmask;

/*
 * Trace definitions
 */

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

static const flag_t dvmrp_trace_masks[] = {
    TR_ALL,			/* 0 - ALL types */
    TR_DVMRP_DETAIL_PROBE,	/* 1 - Probe */
    TR_DVMRP_DETAIL_REPORT,	/* 2 - Report */
    TR_DVMRP_DETAIL_ASK_NBRS,	/* 3 - Ask Neighbors */
    TR_DVMRP_DETAIL_NBRS,	/* 4 - Neighbors */
    TR_DVMRP_DETAIL_ASK_NBRS2,	/* 5 - Ask Neighbors2 */
    TR_DVMRP_DETAIL_NBRS2,	/* 6 - Neighbors */
    TR_DVMRP_DETAIL_PRUNE,	/* 7 - Prune */
    TR_DVMRP_DETAIL_GRAFT,	/* 8 - Graft */
    TR_DVMRP_DETAIL_GRAFT_ACK,	/* 9 - Graft Ack*/
} ;

const bits dvmrp_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 probe" },
    { TR_DETAIL_SEND_1,	"detail send probe" },
    { TR_DETAIL_RECV_1,	"detail recv probe" },
    { TR_PACKET_1,	"probe" },
    { TR_PACKET_SEND_1,	"send probe" },
    { TR_PACKET_RECV_1,	"recv probe" },
    { TR_DETAIL_2,	"detail report" },
    { TR_DETAIL_SEND_2,	"detail send report" },
    { TR_DETAIL_RECV_2,	"detail recv report" },
    { TR_PACKET_2,	"report" },
    { TR_PACKET_SEND_2,	"send report" },
    { TR_PACKET_RECV_2,	"recv report" },
    { TR_DETAIL_3,	"detail mapper" },
    { TR_DETAIL_SEND_3,	"detail send mapper" },
    { TR_DETAIL_RECV_3,	"detail recv mapper" },
    { TR_PACKET_3,	"mapper" },
    { TR_PACKET_SEND_3,	"send mapper" },
    { TR_PACKET_RECV_3,	"recv mapper" },
    { TR_DETAIL_4,	"detail prune" },
    { TR_DETAIL_SEND_4,	"detail send prune" },
    { TR_DETAIL_RECV_4,	"detail recv prune" },
    { TR_PACKET_4,	"prune" },
    { TR_PACKET_SEND_4,	"send prune" },
    { TR_PACKET_RECV_4,	"recv prune" },
    { 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 }
};

static const bits dvmrp_target_bits[] = {
    { DVMRPTF_TUNNEL,	"Tunnel" },
    { 0 }
} ;

					/* Target list */
static dvmrp_target dvmrp_targets = { &dvmrp_targets, &dvmrp_targets };

gw_entry *dvmrp_gw_list = 0;		/* List of DVMRP gateways */


/*
 * dvmrp_parse_version()
 *
 * By examining the version we can either determine the
 * capabilities of the neighbor or read the neighbor
 * flags directly.
 *
 * The flags we are currently trying to set are:
 * LEAF - we can't set this unless the neighbor tells us to
 * PRUNE - we will know this from the version or by the flags passed
 * GENID - same as PRUNE
 * MTRACE - same as PRUNE
 */

static flag_t
dvmrp_parse_version  __PF1(version, u_int32)
{
    u_int32 majorv;
    u_int32 minorv;
    flag_t flags;

    majorv = version & 0xff;
    minorv = (version >> 8) & 0xff;
    flags = (version >> 16) & 0xff;

    if (majorv < 3) {
	return 0;
    } else if (majorv == 3) {
	if (minorv < 3) {
		/*
		 * Prune Beta Release, no genid
		 */
	    return DVMRP_NBR_PRUNE;
	} else if (minorv == 3 || minorv == 4) {
		/*
		 * full featured releases
		 */
	    return DVMRP_NBR_PRUNE | DVMRP_NBR_GENID | DVMRP_NBR_MTRACE;
	} else {
		/*
		 * From here on, just look at flags
		 */
	    return flags;
	}
    } else if (majorv >= 10 && majorv < 20) {
	/*
	 * First Cisco release, no pruning
	 */
	return flags;
    } else if (majorv >= 20 && majorv < 30) {
	/*
	 * First GATED release
	 */
	return flags;
    } else {
	/*
	 * Unknown, try the flags
	 */
	return flags;
    }
}

/*
 *	Trace DVMRP packets
 */

static void
dvmrp_trace_version  __PF1(version, u_int32)
{
    u_int32 majorv = version & 0xff;
    u_int32 minorv = ((version >> 8) & 0xff);

    tracef("Vers: %d.%d ", majorv, minorv);
}

static void
dvmrp_trace  __PF7(trp, trace *,
		   dir, int,
    		   ifap, if_addr *,
		   who, sockaddr_un *,
		   msg, register struct igmp *,
		   size, register size_t,
    		   detail, int)
{
    int i,
	maskbytelen;
    u_int32 origin,
	    group,
	    timeout,
	    mask = 0;
    byte *cp = 0;
    metric_t metric;

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

	tracef("DVMRP %sSENT %A -> %#A ",
	       dir > 0 ? "" : "*NOT* ",
	       ifap ? ifap->ifa_addr_local : sockbuild_str(""),
	       who);
    } else {
	/* Trace packet reception */
	tracef("DVMRP RECV %#A ",
	       who);
	if (task_recv_dstaddr) {
	    /* Some systems report the destination address */
	    tracef("-> %A ",
		   task_recv_dstaddr);
	}
    }

    cp = (byte *) (msg + 1);
    size -= sizeof(struct igmp);

    switch (msg->igmp_code) {
	case DVMRP_PROBE:
		tracef("Probe: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));
		if (detail) {
		    u_int32 v = dvmrp_parse_version(ntohl(msg->igmp_group));
		    if (v) {
			tracef("flags: %s%s%s%s ",
			       BIT_TEST(v, DVMRP_NBR_LEAF) ? "L" : "",
			       BIT_TEST(v, DVMRP_NBR_PRUNE) ? "P" : "",
			       BIT_TEST(v, DVMRP_NBR_GENID) ? "G" : "",
			       BIT_TEST(v, DVMRP_NBR_MTRACE) ? "M" : "");
		    }
		    if (BIT_TEST(v, DVMRP_NBR_GENID)) {
			u_int32 genid;

			bcopy((void_t) cp, (void_t) &genid, sizeof(genid));
			tracef("genid: 0x%0x ", ntohl(genid));
			cp += sizeof(genid);
			size -= sizeof(genid);

			if (size > 0) {
			    tracef("nbrs: ");
			    while(size > 0) {
				u_int32 addr;
				bcopy((void_t) cp, (void_t) &addr, sizeof(addr));
				tracef("%A ", sockbuild_in(0, addr));
				cp += sizeof(addr);
				size -= sizeof(addr);
			    }
			}
		    }
		}
		break;
	case DVMRP_REPORT:
		tracef("Report: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));
		while (size > 0) {
			/*
			 * Read the mask from the next 3 bytes.
			 * The first byte must always be 0xff so it
			 * is not included in the packet.
			 */
		    ((byte *) &mask)[0] = 0xff;
		    maskbytelen = 1;

		    for (i=1; i < 4; i++) {
			if ((((byte *) &mask)[i] = *cp++) != 0)
			    maskbytelen++;
		    }
		    size -= 3;

		    tracef("\n\tmask %-15A:", sockbuild_in(0, mask));
		    trace_only_tf(trp,
				  0,
				  (NULL));

			/*
			 * Now loop through the (origin, metric) pairs
			 */
		    do {
			if (size < maskbytelen + 1) {
			    trace_log_tf(dvmrp_trace_options,
					 0,
					 LOG_WARNING,
					 ("\ndvmrp_trace: received truncated route report from %A",
					  who));
			    
			    return;
			}
			origin = 0;
			for (i=0; i < maskbytelen; i++) {
			    ((byte *)&origin)[i] = *cp++;
			}
			size -= (maskbytelen + 1);
			metric = (metric_t) *cp++;

			tracef("\t\t%-15A   %d    ", sockbuild_in(0, origin), metric & 0x7f);
			trace_only_tf(trp,
				      TRC_NOSTAMP,
				      (NULL));
		    } while (!(metric & 0x80));
		}
		break;

	case DVMRP_ASK_NEIGHBORS:
		tracef("Ask Neighbors: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));
		break;

	case DVMRP_NEIGHBORS:
		tracef("Neighbors: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));
		break;

	case DVMRP_ASK_NEIGHBORS2:
		tracef("Ask Neighbors-2: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));
		break;

	case DVMRP_NEIGHBORS2:
		tracef("Neighbors-2: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));
		break;

	case DVMRP_PRUNE:
		tracef("Prune: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));

		if (size < sizeof(u_int32)) {
		    break;
		}
		for (i=0; i < sizeof(u_int32); i++)
		    ((byte *) &origin)[i] = *cp++;
		size -= sizeof(u_int32);

		if (size < sizeof(u_int32)) {
		    break;
		}
		for (i=0; i < sizeof(u_int32); i++)
		    ((byte *) &group)[i] = *cp++;
		size -= sizeof(u_int32);

		if (size < sizeof(u_int32)) {
		    break;
		}
		for (i=0; i < sizeof(u_int32); i++)
		    ((byte *) &timeout)[i] = *cp++;

		tracef("group %A source %A timeout %d",
		       sockbuild_in(0, origin),
		       sockbuild_in(0, group),
		       ntohl(timeout));

		break;

	case DVMRP_GRAFT:
		tracef("Graft: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));

		if (size < sizeof(u_int32)) {
		    break;
		}
		for (i=0; i < sizeof(u_int32); i++)
		    ((byte *) &origin)[i] = *cp++;
		size -= sizeof(u_int32);

		if (size < sizeof(u_int32)) {
		    break;
		}
		for (i=0; i < sizeof(u_int32); i++)
		    ((byte *) &group)[i] = *cp++;

		tracef("group %A source %A",
		       sockbuild_in(0, origin),
		       sockbuild_in(0, group));

		break;

	case DVMRP_GRAFT_ACK:
		tracef("Graft Ack: ");
		dvmrp_trace_version(ntohl(msg->igmp_group));

		if (size < sizeof(u_int32)) {
		    break;
		}
		for (i=0; i < sizeof(u_int32); i++)
		    ((byte *) &origin)[i] = *cp++;
		size -= sizeof(u_int32);

		if (size < sizeof(u_int32)) {
		    break;
		}
		for (i=0; i < sizeof(u_int32); i++)
		    ((byte *) &group)[i] = *cp++;

		tracef("group %A source %A",
		       sockbuild_in(0, origin),
		       sockbuild_in(0, group));

		break;
    }

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

static void
dvmrp_dr_election  __PF1(ifap, if_addr *)
{
    nbr_list *list = (nbr_list *) ifap->dvmrp_if_nbr_list;

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

    assert(list->dr_back->nbr_addr);

    if (sockaddrcmp_in(ifap->ifa_addr, list->dr_back->nbr_addr)) {
	igmp_enable_dr_status(ifap);
    } else {
	igmp_disable_dr_status(ifap);
    }
}

/*
 * update the neighbor list as we receive DVMRP packets
 * keep a timer queue of the last time we received packets from
 * each neighbor. If a neighbor goes away, it won't be refreshed
 * and the neighbor timeout routine will be reached.
 */

static nbr_list *
dvmrp_nbr_refresh __PF3(ifap, if_addr *,
			src_addr, sockaddr_un *,
			newp, int *)
{
    int found = FALSE;
    time_t expire;
    nbr_list *list = (nbr_list *) ifap->dvmrp_if_nbr_list;
    nbr_list *rlp, *rtr = list->dr_forw;

    while(rtr != list &&
	  rtr->nbr_addr->in.gin_addr.s_addr > src_addr->in.gin_addr.s_addr) {
	rtr = rtr->dr_forw;
    }
    if (rtr == list) {
	/*
	 * not found, insert at beginning of list
	 */
	rtr = (nbr_list *) task_block_alloc(dvmrp_nbr_block_index);
	rtr->nbr_addr = sockdup(src_addr);
	NBR_DR_ENQ(rtr, list->dr_back);
    } else {
	/*
	 * if match, remove from old position in timer list
	 */
	if (sockaddrcmp(rtr->nbr_addr, src_addr)) {
	    if (list->tq_forw == rtr &&
		ifap->dvmrp_nbr_timer_timeout &&
		!BIT_TEST(((task_timer *)ifap->dvmrp_nbr_timer_timeout)->task_timer_flags,
			  TIMERF_INACTIVE)) {
		task_timer_reset((task_timer *) ifap->dvmrp_nbr_timer_timeout);
	    }
	    NBR_TQ_DEQ(rtr);
	    found = TRUE;
	} else {
		/*
		 * got a new dvmrp router in the middle of the list
		 */
	    nbr_list *new = (nbr_list *)
				task_block_alloc(dvmrp_nbr_block_index);
	    NBR_DR_ENQ(new, rtr->dr_back);
	    rtr = new;
	    rtr->nbr_addr = sockdup(src_addr);
	}
    }
	/*
	 * update refresh time and insert it in the list
	 */
    rtr->refresh_time = time_sec;
    expire = rtr->refresh_time + NEIGHBOR_EXPIRE_TIME;

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

    dvmrp_refresh_router_timer(ifap,
	NEIGHBOR_EXPIRE_TIME - (time_sec - list->tq_forw->refresh_time));

    *newp = (found == FALSE);
    return rtr;
}


static void
dvmrp_refresh_router_timer  __PF2(ifap, if_addr *,
				  timeout, time_t)
{
	    /* if no timer running, create one */
    if (!ifap->dvmrp_nbr_timer_timeout) {

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

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

/*ARGSUSED*/
static void
dvmrp_nbr_timeout  __PF2(tip, task_timer *,
			 interval, time_t)
{
    if_addr *ifap = (if_addr *)(tip->task_timer_data);
    nbr_list *gp, *aged;
    nbr_list *list =  (nbr_list *) ifap->dvmrp_if_nbr_list;
    struct ifa_ps *ips = &ifap->ifa_ps[dvmrp_task->task_rtproto];

	/*
	 * 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) >= NEIGHBOR_EXPIRE_TIME) {
	aged = gp;
	gp = gp->tq_forw;
	NBR_TQ_DEQ(aged);
	NBR_DR_DEQ(aged);
	trace_tp(dvmrp_task,
		 TR_TIMER,
		 0,
		 ("dvmrp_nbr_timeout: DVMRP Router %A on interface %A(%s) timed out.",
		  aged->nbr_addr,
		  ifap->ifa_addr,
		  ifap->ifa_link->ifl_name));
	mfc_visit(dvmrp_mfc_delete_nbr, (caddr_t) aged);
	task_block_free(dvmrp_nbr_block_index, (void_t) aged);
    }

    dvmrp_refresh_router_timer(ifap,
	NEIGHBOR_EXPIRE_TIME - (time_sec - list->tq_forw->refresh_time));

    dvmrp_dr_election(ifap);

	/*
	 * If there is only one element on the list and its us,
	 * Update the interface leaf status.
	 */
    if (list->tq_forw->tq_forw == list) {
	BIT_RESET(ips->ips_state, IFPS_NOT_LEAF);
    }
}


#define	REJECT(p, m)	{ reject_msg = (m); pri = (p); goto Reject; }

/*
 * 	Called when an DVMRP packet is available for reading.
 */
static void
dvmrp_recv __PF3(ifap, if_addr *,
	         igmp, struct igmp *,
	         dvmrplen, int)
{
    task *tp = dvmrp_task;
    struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];
    nbr_list *nbr;
    gw_entry *gwp = (gw_entry *) 0;
    int pri = 0;
    int new = 0;
    const char *reject_msg = (char *) 0;


    if (dvmrplen < sizeof(struct igmp)) {
	trace_log_tf(dvmrp_trace_options,
		     0,
		     LOG_WARNING,
		     ("dvmrp_recv: ignoring dvmrp msg: short packet from %A",
		      task_recv_srcaddr));
	return;
    }

	/*
	 * Locate a gateway structure for this gateway
	 */
    gwp = gw_locate(&dvmrp_gw_list,
		    tp->task_rtproto,
		    tp,
		    (as_t) 0,
		    (as_t) 0,
		    task_recv_srcaddr,
		    0);

    if (!gwp) {
	REJECT(0, "unknown neighbor");
    }
	/*
	 * Determine if this is an already accepted neighbor.
	 * If not, see if we rejected it before.
	 * If not, see if it is on a directly connected multi access network
	 * where DVMRP is enabled or if they are a configured tunnel neighbor.
	 * Otherwise, ignore.
	 */

    if (!BIT_TEST(gwp->gw_flags, GWF_ACCEPT)) {

	if (BIT_TEST(gwp->gw_flags, GWF_REJECT)) {
	    REJECT(0, "not on same net");
	}

	if (!(ifap == if_withdst(gwp->gw_addr))) {
	    BIT_SET(gwp->gw_flags, GWF_REJECT);
	    REJECT(0, "not on same net");
	} else {
	    BIT_SET(gwp->gw_flags, GWF_ACCEPT);
	}
    }

    nbr = dvmrp_nbr_refresh(ifap, task_recv_srcaddr, &new);

    if (new) {
	    /*
	     * On broadcast nets, we do a DR election.
	     * No need to on p2p or nbma nets.
	     */
	if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST)) {
	    dvmrp_dr_election(ifap);
	}

	nbr->gwp = gwp;
	nbr->ifap = ifap;
	nbr->genid = 0;
	nbr->cap_flags = 0;
	nbr->nbr_flags = 0;

	mfc_visit(dvmrp_mfc_add_nbr, (caddr_t) nbr);
    }

    /* Should we trace this packet? */

    if (TRACE_PACKET_RECV_TP(tp,
			     igmp->igmp_code,
			     DVMRP_MAX_TYPE,
			     dvmrp_trace_masks)) {

	dvmrp_trace(tp->task_trace,
		    FALSE,
		    ifap,
		    task_recv_srcaddr,
		    igmp,
		    dvmrplen,
		    TRACE_DETAIL_RECV_TP(tp,
				         igmp->igmp_code,
				         DVMRP_MAX_TYPE,
				         dvmrp_trace_masks));
    }
    /*
     * update interface timer on interface that packet came in on.
     * also update the interface route in the dvmrp routing table.
     */

    if (!BIT_TEST(ifap->ifa_state, IFS_TUNNEL)) {
	if_rtupdate(ifap);

	/*
	 * If this packet is addressed from us ignore the packet.
	 */
	if (sockaddrcmp_in(gwp->gw_addr, ifap->ifa_addr)) {
	    return;
	}
    }

    if (BIT_TEST(ips->ips_state, IFPS_NOIN)) {
	REJECT(0, "interface marked for no DVMRP in");
    }

    if (dvmrplen > DVMRP_MAX_REPORT) {
	REJECT(0, "oversize route report");
    }

    gwp->gw_time = time_sec;

	/*
	 * If we thought this was a leaf network, then reset leaf bit
	 * and add this interface to all forwarding cache entries
	 * where it would be considered a downstream interface.
	 */
    if (!BIT_TEST(ips->ips_state, IFPS_NOT_LEAF)) {
	mfc_visit(dvmrp_mfc_add_ifap, (caddr_t) ifap);
	BIT_SET(ips->ips_state, IFPS_NOT_LEAF);
    }

    switch (igmp->igmp_code) {
	case DVMRP_PROBE:

		dvmrp_recv_probe(tp, ifap, nbr, igmp, dvmrplen, new);
		break;

	case DVMRP_REPORT:

		dvmrp_recv_report(tp, ifap, nbr, igmp, dvmrplen);
		break;

	case DVMRP_ASK_NEIGHBORS:
		break;

	case DVMRP_NEIGHBORS:

		REJECT(0, "neighbor list");
		break;

	case DVMRP_ASK_NEIGHBORS2:
		break;

	case DVMRP_NEIGHBORS2:

		REJECT(0, "neighbor list 2");
		break;

	case DVMRP_PRUNE:

		dvmrp_recv_prune(tp, ifap, nbr, igmp, dvmrplen);
		break;

	case DVMRP_GRAFT:

		dvmrp_recv_graft(tp, ifap, nbr, igmp, dvmrplen);
		break;

	case DVMRP_GRAFT_ACK:

		dvmrp_recv_graftack(tp, ifap, nbr, igmp, dvmrplen);
		break;

    }

    return;

Reject:

    tracef("dvmrp_recv: ignoring DVMRP ");
    trace_log_tp(tp,
		 0,
		 pri,
		 (" packet from %#A - %s",
		  task_recv_srcaddr,
		  reject_msg));
    trace_only_tp(tp,
		  0,
		  (NULL));
    return;
}

static void
dvmrp_recv_probe  __PF6(tp, task *,
			ifap, if_addr *,
			nbr, nbr_list *,
			igmp, struct igmp *,
			len, int,
			neighbor_change, int)
{
	/*
	 * Need to look at probe msgs and record capabilities of neighbor.
	 */
    u_int32 genid;
    u_int32 cap_flags;

    cap_flags = dvmrp_parse_version(igmp->igmp_group);

    if (BIT_TEST(cap_flags, DVMRP_NBR_GENID)) {
	byte *cp = (byte *) (igmp + 1);
	bcopy((void_t) cp, (caddr_t) &genid, sizeof(genid));
	genid = ntohl(genid);
	cp += sizeof(genid);
	len -= sizeof(genid);

	    /*
	     * Check for new instance of neighbor
	     */

	if (nbr->genid == 0) {
	    nbr->genid = genid;
	} else if (nbr->genid != genid) {
	    if (TRACE_TF(dvmrp_trace_options, TR_STATE)) {
		trace_only_tf(dvmrp_trace_options,
			      TRC_NL_AFTER,
			      ("DVMRP TRANSITION\tNeighbor %A EVENT GENERATION ID CHANGE",
			       (nbr)->nbr_addr));
	    }
	    nbr->genid = genid;
	    neighbor_change++;
	}

	    /*
	     * Each time through, mark neighbor as ONEWAY until
	     * we see our own address in his probe message
	     */
	BIT_SET(nbr->nbr_flags, DVMRP_NBR_ONEWAY);

	while(len > 0) {
	    u_int32 addr;
	    bcopy((void_t) cp, (caddr_t) &addr, sizeof(addr));
		/*
		 * If we see our own address, clear ONEWAY flag
		 */
	    if (addr == sock2ip(ifap->ifa_addr)) {
		BIT_RESET(nbr->nbr_flags, DVMRP_NBR_ONEWAY);
	    }
	    cp += sizeof(addr);
	    len -= sizeof(addr);
	}
	if (nbr->cap_flags != cap_flags) {
	    if (TRACE_TF(dvmrp_trace_options, TR_STATE)) {
		if (BIT_TEST(nbr->cap_flags, DVMRP_NBR_LEAF) !=
		    BIT_TEST(cap_flags, DVMRP_NBR_LEAF)) {
		
		    trace_only_tf(dvmrp_trace_options,
				  TRC_NL_AFTER,
				  ("DVMRP TRANSITION\tNeighbor %A EVENT LEAF STATUS CHANGE",
				   (nbr)->nbr_addr));
		}
		if (BIT_TEST(nbr->cap_flags, DVMRP_NBR_PRUNE) !=
		    BIT_TEST(cap_flags, DVMRP_NBR_PRUNE)) {
		
		    trace_only_tf(dvmrp_trace_options,
				  TRC_NL_AFTER,
				  ("DVMRP TRANSITION\tNeighbor %A EVENT PRUNE STATUS CHANGE",
				   (nbr)->nbr_addr));
		}
		if (BIT_TEST(nbr->cap_flags, DVMRP_NBR_GENID) !=
		    BIT_TEST(cap_flags, DVMRP_NBR_GENID)) {
		
		    trace_only_tf(dvmrp_trace_options,
				  TRC_NL_AFTER,
				  ("DVMRP TRANSITION\tNeighbor %A EVENT GENID STATUS CHANGE",
				   (nbr)->nbr_addr));
		}
		if (BIT_TEST(nbr->cap_flags, DVMRP_NBR_MTRACE) !=
		    BIT_TEST(cap_flags, DVMRP_NBR_MTRACE)) {
		
		    trace_only_tf(dvmrp_trace_options,
				  TRC_NL_AFTER,
				  ("DVMRP TRANSITION\tNeighbor %A EVENT MTRACE STATUS CHANGE",
				   (nbr)->nbr_addr));
		}
	    }
	    if (BIT_TEST(cap_flags, DVMRP_NBR_LEAF)) {
		/*
		 * If nbr is a leaf router but wasn't before,
		 * start leaf timeout timer
		 */
		if (!BIT_TEST(nbr->cap_flags, DVMRP_NBR_LEAF)) {
		}
	    } else {
		/*
		 * If nbr isn't a leaf router but was before,
		 * stop leaf timeout timer.
		 *
		 * We'll wait for a graft before we send any
		 * traffic from existing forwarding cache entries.
		 *
		 * But all new traffic will be sent out and then
		 * pruned back.
		 */
		if (BIT_TEST(nbr->cap_flags, DVMRP_NBR_LEAF)) {
		}
	    }
	    nbr->cap_flags = cap_flags;
	    neighbor_change++;
	}
    }

	/*
	 * Send a full route report if a new neighbor or change is detected
	 */
    if (neighbor_change) {
	dvmrp_target *tlp = (dvmrp_target *) 0;
	dvmrp_target *tlp2;

	/* Find the Target for this host */
	TARGET_LIST(tlp2, &dvmrp_targets) {
	    /* Look for target for this interface */

	    if (BIT_TEST(tlp2->target_flags, TARGETF_SUPPLY)) {
		if (tlp2->target_gwp == nbr->gwp) {
		    /* Found a target for this gateway! */

		    tlp = tlp2;
		    break;
		} else if (tlp2->target_ifap == ifap) {
		    /* Found a target for this interface */
		    /* remember it, but keep looking in case */
		    /* there is one for the gateway */

		    tlp = tlp2;
		}
	    }
	} TARGET_LIST_END(tlp2, &dvmrp_targets) ;

	if (tlp) {
	    dvmrp_supply(tlp, FALSE);
	}
    }
}

static void
dvmrp_recv_report  __PF5(tp, task *,
			 ifap, if_addr *,
			 nbr, nbr_list *,
			 igmp, struct igmp *,
			 len, int)
{
    int routes = 0;
    int i,
	maskbytelen;
    u_int32 origin,
	    mask = 0;
    rt_parms rtparms;
    rt_entry *rt;
    byte *cp = (byte *) igmp;
    void_t limit = (void_t) ((byte *) igmp + len);
    metric_t metric;


	/*
	 * Subtract header length from size
	 */
    len -= sizeof (struct igmp);
    cp += sizeof(struct igmp);
	

    bzero((caddr_t) &rtparms, sizeof (rtparms));
    rtparms.rtp_n_gw = 1;
    rtparms.rtp_gwp = nbr->gwp;
    rtparms.rtp_router = nbr->gwp->gw_addr;
    rtparms.rtp_state = (flag_t) 0;
    rtparms.rtp_preference = 50;

    rt_open(tp);

    while (cp < (byte *) limit) {
	if (len < 3) {
	    trace_log_tf(dvmrp_trace_options,
			 0,
			 LOG_WARNING,
			 ("dvmrp_recv_report: received truncated route report from %A",
			  nbr->gwp->gw_addr));
	    return;
	}

	    /*
	     * Read the mask from the next 3 bytes. The first byte must
	     * always be 0xff so it is not included. This does not
	     * allow for aggregating Class A nets but that shouldn't
	     * be a problem.
	     */
	((byte *) &mask)[0] = 0xff;
	maskbytelen = 1;

	for (i=1; i < 4; i++) {
	    if ((((byte *) &mask)[i] = *cp++) != 0)
		maskbytelen++;
	}
	len -= 3;


	    /*
	     * Now loop through the (origin, metric) pairs
	     */
	do {
	    if (len < maskbytelen + 1) {
		trace_log_tf(dvmrp_trace_options,
			     0,
			     LOG_WARNING,
			     ("dvmrp_recv_report: received truncated route report from %A",
			      nbr->gwp->gw_addr));
		
		return;
	    }
	    origin = 0;
	    for (i=0; i < maskbytelen; i++) {
		((byte *)&origin)[i] = *cp++;
	    }
	    len -= maskbytelen + 1;

	    metric = (metric_t) *cp++;
	    rtparms.rtp_metric = metric & 0x7f;
	    rtparms.rtp_metric += ifap->ifa_ps[tp->task_rtproto].ips_metric_in;
	    rtparms.rtp_state = RTS_NETROUTE|RTS_NOADVISE|RTS_NOTINSTALL;
	    rtparms.rtp_dest = sockbuild_in(0, origin);
	    rtparms.rtp_dest_mask = inet_mask_locate(mask);
	    if (rtparms.rtp_metric < 1 ||
		rtparms.rtp_metric >= 2 * DVMRP_UNREACHABLE) {
		trace_tp(tp,
			 TR_ROUTE,
			 0,
			 ("dvmrp_recv_report: %A/%A metric %d out of range",
			  rtparms.rtp_dest,
			  rtparms.rtp_dest_mask,
			  metric));
		continue;
	    }
	    if (if_myaddr(ifap, rtparms.rtp_dest, rtparms.rtp_dest_mask)) {
		/* Ignore route to interface or whole network */
		continue;
	    }

	    rt = rt_locate(rtparms.rtp_state,
			   rtparms.rtp_dest,
			   rtparms.rtp_dest_mask,
			   RTPROTO_BIT(tp->task_rtproto));

	    if (!rt) {

		/* New route */
		if (rtparms.rtp_metric < DVMRP_UNREACHABLE &&
		    import(rtparms.rtp_dest,
			   rtparms.rtp_dest_mask,
			   dvmrp_import_list,
			   ifap->ifa_ps[tp->task_rtproto].ips_import,
			   rtparms.rtp_gwp->gw_import,
			   &rtparms.rtp_preference,
			   ifap,
			   (void_t) 0)) {
		    /* Add new route */

		    rt = dvmrp_rt_add(&rtparms);
		} else {
		    BIT_SET(rtparms.rtp_gwp->gw_flags, GWF_IMPORT);
		}
	    } else if (rt->rt_gwp == nbr->gwp) {
		    /*
		     * This is the same route already in the routing table
		     */
		if (rtparms.rtp_metric >= DVMRP_UNREACHABLE) {
			/*
			 * No longer reachable, delete route
			 */
		    rt_delete(rt);
		} else if (rtparms.rtp_metric < rt->rt_metric) {
			/*
			 * Better metric, replace route
			 */
		    if ((rt = rt_change(rt,
					rtparms.rtp_metric,
					rtparms.rtp_metric2,
					rtparms.rtp_tag,
					rt->rt_preference,
					rt->rt_preference2,
					rt->rt_n_gw, rt->rt_routers))) {
			rt_refresh(rt);
		    }
		} else {
			/*
			 * No change in route
			 */
		    rt_refresh(rt);
		}
	    } else if ((rtparms.rtp_metric < rt->rt_metric) &&
		       import(rtparms.rtp_dest,
			      rtparms.rtp_dest_mask,
			      dvmrp_import_list,
			      ifap->ifa_ps[tp->task_rtproto].ips_import,
			      rtparms.rtp_gwp->gw_import,
			      &rtparms.rtp_preference,
			      ifap,
			      (void_t) 0)) {
		    /*
		     * New gateway has better metric
		     */

		rt_delete(rt);
		rt = dvmrp_rt_add(&rtparms);

	    }
	    routes++;
	} while (!(metric & 0x80));
    }
    rt_close(tp, rtparms.rtp_gwp, routes, NULL);

    if (dvmrp_rtlist_root) {
	dvmrp_flash(tp, dvmrp_rtlist_root);
    }
}

static void
dvmrp_recv_prune  __PF5(tp, task *,
			ifap, if_addr *,
			nbr, nbr_list *,
			igmp, struct igmp *,
			len, int)
{
    int i;
    int found = FALSE;
    time_t timeout;
    mfc *mfcp;
    u_int32 group, source;
    nbr_list *np;
    nbr_list *list = (nbr_list *) ifap->dvmrp_if_nbr_list;
    byte *cp = (byte *) igmp;

    NBR_DR_LIST(np, list) {
	if (sockaddrcmp_in(np->nbr_addr, nbr->nbr_addr)) {
	    found = TRUE;
	    break;
	}
    } NBR_DR_LIST_END(np, list);

	/*
	 * Do we know this neighbor?
	 */
    if (!found) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: received prune from unknown Neighbor %A",
		  np->nbr_addr));
	return;
    }
	/*
	 * If the neighbor can't prune, then ignore received prune
	 */
    if (!BIT_TEST(np->cap_flags, DVMRP_NBR_PRUNE)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: received prune from Neighbor %A not capable of pruning.",
		  np->nbr_addr));
	return;
    }
	/*
	 * Subtract header length from size
	 */
    len -= sizeof(struct igmp);
    cp += sizeof(struct igmp);


    if (len < sizeof(u_int32)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: source truncated from Neighbor %A",
		  np->nbr_addr));
	return;
    }
    for (i=0; i < sizeof(u_int32); i++)
	((byte *) &source)[i] = *cp++;
    len -= sizeof(u_int32);

    if (len < sizeof(u_int32)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: group truncated from Neighbor %A Source %A",
		  np->nbr_addr,
		  sockbuild_in(0, source)));
	return;
    }
    for (i=0; i < sizeof(u_int32); i++)
	((byte *) &group)[i] = *cp++;
    len -= sizeof(u_int32);

    if (len < sizeof(u_int32)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: timeout truncated from Neighbor %A Source %A Group %A",
		  np->nbr_addr,
		  sockbuild_in(0, group),
		  sockbuild_in(0, source)));
	return;
    }
    for (i=0; i < sizeof(u_int32); i++)
	((byte *) &timeout)[i] = *cp++;
    len -= sizeof(u_int32);

    timeout = ntohl(timeout);

    if (len != 0) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: warning: extra prune info from Neighbor %A Source %A Group %A",
		  np->nbr_addr,
		  sockbuild_in(0, group),
		  sockbuild_in(0, source)));
    }
    if (!(mfcp=mfc_locate_mfc(sockbuild_in(0, group),sockbuild_in(0, source)))) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: no MFC entry Source %A Group %A from Neighbor %A",
		  sockbuild_in(0, group),
		  sockbuild_in(0, source),
		  np->nbr_addr));
	return;
    }
	/*
	 * If this prune arrived on the upstream interface, ignore it.
	 */
    if (ifap == mfcp->upstream_ifap) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_prune: Prune from Upstream interface Source %A Group %A from Neighbor %A",
		  sockbuild_in(0, group),
		  sockbuild_in(0, source),
		  np->nbr_addr));
	return;
    }
	/*
	 * remove this neighbor from the downstream nbr list.
	 * If this is the last downstream neighbor on this interface,
	 * it will remove the entire interface from the mfc.
	 */
    dvmrp_mfc_delete_nbr(mfcp, (caddr_t) nbr);
}

static void
dvmrp_recv_graft  __PF5(tp, task *,
			ifap, if_addr *,
			nbr, nbr_list *,
			igmp, struct igmp *,
			len, int)
{
    int i;
    int found = FALSE;
    u_int32 group, source;
    nbr_list *np;
    nbr_list *list = (nbr_list *) ifap->dvmrp_if_nbr_list;
    prune_list *pp;
    byte *cp = (byte *) igmp;
    mfc *mfcp;

    NBR_DR_LIST(np, list) {
	if (sockaddrcmp_in(np->nbr_addr, nbr->nbr_addr)) {
	    found = TRUE;
	    break;
	}
    } NBR_DR_LIST_END(np, list);

	/*
	 * Do we know this neighbor?
	 */
    if (!found) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graft: received prune from unknown Neighbor %A",
		  np->nbr_addr));
	return;
    }
	/*
	 * If the neighbor can't prune, then ignore received prune
	 */
    if (!BIT_TEST(np->cap_flags, DVMRP_NBR_PRUNE)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graft: received graft from Neighbor %A not capable of pruning.",
		  np->nbr_addr));
	return;
    }
	/*
	 * Subtract header length from size
	 */
    len -= sizeof (struct igmp);
    cp += sizeof(struct igmp);


    if (len < sizeof(u_int32)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graft: source truncated from Neighbor %A",
		  np->nbr_addr));
	return;
    }
    for (i=0; i < sizeof(u_int32); i++)
	((byte *) &source)[i] = *cp++;
    len -= sizeof(u_int32);

    if (len < sizeof(u_int32)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graft: group truncated from Neighbor %A Source %A",
		  np->nbr_addr,
		  sockbuild_in(0, source)));
	return;
    }
    for (i=0; i < sizeof(u_int32); i++)
	((byte *) &group)[i] = *cp++;
    len -= sizeof(u_int32);

    if (len != 0) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graft: warning: extra graft info from Neighbor %A Source %A Group %A",
		  np->nbr_addr,
		  sockbuild_in(0, group),
		  sockbuild_in(0, source)));
    }
    if (!(mfcp=mfc_locate_mfc(sockbuild_in(0, group),sockbuild_in(0, source)))) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graft: no MFC entry Source %A Group %A from Neighbor %A",
		  sockbuild_in(0, group),
		  sockbuild_in(0, source),
		  np->nbr_addr));
    }
	/*
	 * Whether we have have a prune or not, send an ack to satisfy nbr
	 */
    dvmrp_send_graftack(ifap, np->nbr_addr, group, source);

    pp = mfcp->prune_down.if_forw;
    while (pp != &mfcp->prune_down) {
	if (sockaddrcmp_in(pp->nbr_addr, np->nbr_addr)) {
	    PRUNE_TQ_DEQ(pp);
	    PRUNE_IF_DEQ(pp);
	    task_block_free(dvmrp_prune_block_index, (void_t) pp);

		/*
		 * If we sent a prune upstream, then send graft upstream
		 */
	    if (mfcp->prune_up) {
		PRUNE_TQ_DEQ(mfcp->prune_up);
		dvmrp_send_graft(mfcp);
		task_block_free(dvmrp_prune_block_index, (void_t) mfcp->prune_up);
		mfcp->prune_up = (prune_list *) 0;
	    }
	    break;
	}
	pp = pp->if_forw;
    }
	/*
	 * add this neighbor to the downstream nbr list.
	 */
    dvmrp_mfc_add_nbr(mfcp, (caddr_t) nbr);
}

static void
dvmrp_recv_graftack  __PF5(tp, task *,
			   ifap, if_addr *,
			   nbr, nbr_list *,
			   igmp, struct igmp *,
			   len, int)
{
    int i;
    int found = FALSE;
    u_int32 group, source;
    nbr_list *np;
    nbr_list *list = (nbr_list *) ifap->dvmrp_if_nbr_list;
    byte *cp = (byte *) igmp;
    mfc *mfcp;

    NBR_DR_LIST(np, list) {
	if (sockaddrcmp_in(np->nbr_addr, nbr->nbr_addr)) {
	    found = TRUE;
	    break;
	}
    } NBR_DR_LIST_END(np, list);

	/*
	 * Do we know this neighbor?
	 */
    if (!found) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graftack: received prune from unknown Neighbor %A",
		  np->nbr_addr));
	return;
    }
	/*
	 * If the neighbor can't prune, then ignore received prune
	 */
    if (!BIT_TEST(np->cap_flags, DVMRP_NBR_PRUNE)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graftack: received graft from Neighbor %A not capable of pruning.",
		  np->nbr_addr));
	return;
    }
	/*
	 * Subtract header length from size
	 */
    len -= sizeof (struct igmp);
    cp += sizeof(struct igmp);


    if (len < sizeof(u_int32)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graftack: source truncated from Neighbor %A",
		  np->nbr_addr));
	return;
    }
    for (i=0; i < sizeof(u_int32); i++)
	((byte *) &source)[i] = *cp++;
    len -= sizeof(u_int32);

    if (len < sizeof(u_int32)) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graftack: group truncated from Neighbor %A Source %A",
		  np->nbr_addr,
		  sockbuild_in(0, source)));
	return;
    }
    for (i=0; i < sizeof(u_int32); i++)
	((byte *) &group)[i] = *cp++;
    len -= sizeof(u_int32);

    if (len != 0) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graftack: warning: extra graft info from Neighbor %A Source %A Group %A",
		  np->nbr_addr,
		  sockbuild_in(0, group),
		  sockbuild_in(0, source)));
    }
    if (!(mfcp=mfc_locate_mfc(sockbuild_in(0, group),sockbuild_in(0, source)))) {
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("dvmrp_recv_graftack: no MFC entry Source %A Group %A from Neighbor %A",
		  sockbuild_in(0, group),
		  sockbuild_in(0, source),
		  np->nbr_addr));
	return;
    }
	/*
	 * If graft is at the head of the list, need to reset the timer
	 */
    if (mfcp->mfc_graft == graft_head.forw) {
	task_timer_reset(dvmrp_timer_graft);
	REMQUE(mfcp->mfc_graft);
	if (graft_head.forw != &graft_head) {
	    task_timer_set(dvmrp_timer_graft,
			   (time_t) 0,
			   graft_head.forw->graft_time - time_sec);
	}
    } else {
	REMQUE(mfcp->mfc_graft);
    }
    task_block_free(dvmrp_graft_block_index, (void_t) mfcp->mfc_graft);
    mfcp->mfc_graft = (graft_list *) 0;
}

static void
dvmrp_send_probe  __PF1(tlp, dvmrp_target *)
{
    int rc;
    size_t len;
    struct ip *ip = task_get_send_buffer(struct ip *);
    struct igmp *igmp = (struct igmp *) (ip+1);
    u_int8 *pkt = (u_int8 *) (igmp+1);
    sockaddr_un *dst;
    nbr_list *nbrp = 0;
    nbr_list *list = (nbr_list *) tlp->target_ifap->dvmrp_if_nbr_list;
    if_addr *ifap;
    int leaf_router = TRUE;
    u_int32 capabilities = DVMRP_NBR_GENID|DVMRP_NBR_PRUNE;

	/*
	 * Go through the interface list and determine if we are
	 * a leaf router with respect to this target
	 */
    IF_ADDR(ifap) {
	struct ifa_ps *ips = &ifap->ifa_ps[dvmrp_task->task_rtproto];

	if (tlp->target_ifap != ifap &&
	    BIT_TEST(ips->ips_state, IFPS_NOT_LEAF)) {
	    leaf_router = FALSE;
	}
    } IF_ADDR_END(ifap) ;


    if (leaf_router) {
	 capabilities |= DVMRP_NBR_LEAF;
    }

	/*
	 * fill in igmp packet header
	 */
    len = sizeof(struct igmp);
    igmp->igmp_type = IGMP_PROTO_DVMRP;
    igmp->igmp_code = DVMRP_PROBE;
    igmp->igmp_group = htonl((capabilities << 16) |
			     (DVMRP_VERSION_MINOR << 8) |
			     DVMRP_VERSION_MAJOR);

	/*
	 * insert the unique ID into the packet (based on startup time)
	 */
    bcopy((void_t) &generation_id, (void_t) pkt, sizeof(generation_id));
    len += sizeof(generation_id);
    pkt += sizeof(generation_id);


	/*
	 * list all of the neighbors we've learned about over this interface
	 */

    NBR_DR_LIST(nbrp, list) {
	u_int32 addr = sock2ip(nbrp->nbr_addr);
	    /*
	     * Don't include our own address in the probe we send
	     */
	if (!sockaddrcmp_in(tlp->target_ifap->ifa_addr, nbrp->nbr_addr)) {
	    bcopy((void_t) &addr, (void_t) pkt, sizeof(addr));
	    len += sizeof(addr);
	    pkt += sizeof(addr);
	}
    } NBR_DR_LIST_END(nbrp, list);

	/*
	 * Set the destination address
	 */
    if (BIT_TEST(tlp->target_ifap->ifa_state, IFS_BROADCAST)) {
	dst = inaddr_dvmrp_group;
    } else {
	dst = *tlp->target_dst;
    }

    igmp->igmp_cksum = 0;
    igmp->igmp_cksum = inet_cksum((void_t) igmp, len);

    rc = igmp_send(tlp->target_ifap, ip, len + sizeof(struct ip), dst, TRUE);

    /* Should we trace this packet? */

    if (TRACE_PACKET_SEND_TP(dvmrp_task,
			     igmp->igmp_code,
			     DVMRP_MAX_TYPE,
			     dvmrp_trace_masks)) {

	dvmrp_trace(dvmrp_task->task_trace,
		    rc,
		    tlp->target_ifap,
		    dst,
		    igmp,
		    len,
		    TRACE_DETAIL_SEND_TP(dvmrp_task,
				         igmp->igmp_code,
				         DVMRP_MAX_TYPE,
				         dvmrp_trace_masks));
    }
}

static void
dvmrp_send_report  __PF3(tlp, dvmrp_target *,
			 start, byte *,
			 len, int)
{
    int rc;
    sockaddr_un *dst;
    struct igmp *igmp = (struct igmp *) (start + sizeof(struct ip));

    igmp->igmp_type = IGMP_PROTO_DVMRP;
    igmp->igmp_code = DVMRP_REPORT;
    igmp->igmp_group = htonl((DVMRP_VERSION_MINOR << 8) |
			     DVMRP_VERSION_MAJOR);

	/*
	 * Set the destination address
	 */
    if (BIT_TEST(tlp->target_ifap->ifa_state, IFS_BROADCAST)) {
	dst = inaddr_dvmrp_group;
    } else {
	dst = *tlp->target_dst;
    }

    igmp->igmp_cksum = 0;
    igmp->igmp_cksum = inet_cksum((void_t) igmp, len - sizeof(struct ip));

    rc = igmp_send(tlp->target_ifap, (struct ip *) start, len, dst, TRUE);

    /* Should we trace this packet? */

    if (TRACE_PACKET_SEND_TP(dvmrp_task,
			     igmp->igmp_code,
			     DVMRP_MAX_TYPE,
			     dvmrp_trace_masks)) {

	dvmrp_trace(dvmrp_task->task_trace,
		    rc,
		    tlp->target_ifap,
		    dst,
		    igmp,
		    len - sizeof(struct ip),
		    TRACE_DETAIL_SEND_TP(dvmrp_task,
				         igmp->igmp_code,
				         DVMRP_MAX_TYPE,
				         dvmrp_trace_masks));
    }
}

static void
dvmrp_supply_guts __PF6(tlp, dvmrp_target *,
			tdp, dvmrp_td_entry *,
			packet, byte *,
			start, byte *,
			fillpp, byte **,
			flash_update, int)
{
    int i, route_len;
    int cleanup = 0;
    int maskbytelen = 1;
    byte *fillp = *fillpp;
    int changes = 0;
    u_int32 dest;

    if (flash_update) {
	if (!BIT_TEST(tdp->td_flags, TDF_CHANGED)) {
	    /* End of changes for this target */
		
	    return;
	}
    } else {
	if (BIT_TEST(tdp->td_flags, TDF_HOLDDOWN|TDF_POISON)
	    && !--tdp->td_metric) {
	    /* Holddown is over - queue it to be released */

	    cleanup++;
	}
    }

    if (BIT_TEST(tdp->td_flags, TDF_CHANGED)) {
	BIT_RESET(tdp->td_flags, TDF_CHANGED);
    }
    
    sendmask = sock2ip(tdp->td_rt->rt_head->rth_dest_mask);
    for (i=1; i < 4; i++) {
	if ((((byte *) &sendmask)[i]) != 0)
	    maskbytelen++;
    }
    route_len = (sendmask == lastsendmask) ?
		maskbytelen + 1 : maskbytelen + sizeof(sendmask);
	/*
	 * If there is not room for this route, send what we have
	 * so far and start another message.
	 */
    if (fillp > route_len + packet + tlp->target_ifap->ifa_mtu) {
	dvmrp_send_report(tlp,
			  packet,
			  fillp-packet);
	fillp = start;
    }

    if (sendmask != lastsendmask) {
	    /*
	     * If we hit that last route associated with this mask,
	     * set the high order bit on the metric so the receiver
	     * knows to look for another mask.
	     */
	if (fillp > start) {
	    *(fillp-1) |= 0x80;
	}
	*fillp++ = ((byte *)&sendmask)[1];
	*fillp++ = ((byte *)&sendmask)[2];
	*fillp++ = ((byte *)&sendmask)[3];
    }

    dest = sock2ip(tdp->td_rt->rt_head->rth_dest);
    bcopy((void_t) &dest, (void_t) fillp, maskbytelen);
    fillp += maskbytelen;

    if (BIT_TEST(tdp->td_flags, TDF_HOLDDOWN|TDF_POISON)) {
	*fillp++ = DVMRP_UNREACHABLE;
    } else {
	*fillp++ = tdp->td_metric;
    }

    if (cleanup) {
	if (TRACE_TP(dvmrp_task, TR_POLICY)) {
	    if (!changes) {
		trace_only_tp(dvmrp_task,
			      TRC_NL_BEFORE,
			      ("dvmrp_supply_guts: Policy for target %A",
			       *tlp->target_dst));
	    }
	    trace_only_tp(dvmrp_task,
			  0,
			  ("\t%A/%A %s ended",
			   tdp->td_rt->rt_head->rth_dest,
			   tdp->td_rt->rt_head->rth_dest_mask,
			   BIT_TEST(tdp->td_flags, TDF_POISON) ? "poison" : "holddown"));
	    if (TRACE_TP(dvmrp_task, TR_POLICY)
		|| changes) {
		trace_only_tp(dvmrp_task,
			      0,
			      (NULL));
	    }
	}
	changes++;
	DVMRP_TD_CLEANUP(tlp, tdp);
    }
    lastsendmask = sendmask;
    *fillpp = fillp;
}

/* Send DVMRP updates to all targets on the list */

/*ARGSUSED*/
static void
dvmrp_supply __PF2(tlp, dvmrp_target *,
		   flash_update, int)
{
    byte *packet = task_get_send_buffer(byte *);
    byte *start = packet + sizeof(struct ip) + sizeof(struct igmp);
    byte *fillp = start;
    int maskindex, i, n, doing_right, total = 0;
    dvmrp_td_entry *stack[sizeof(u_int32) * NBBY];
    register dvmrp_td_entry *dp, *dprev;

    lastsendmask = 0;

    for (maskindex=0; maskindex < sizeof(u_int32) * NBBY; maskindex++) {
	total += tlp->target_td_count[maskindex];
    }

    for (maskindex=0; maskindex < sizeof(u_int32) * NBBY; maskindex++) {
	if (tlp->target_td_count[maskindex] == 0) {
	    continue;
	}
	dprev = tlp->target_td_head[maskindex];

	    /* catch the case where only one node in the tree. */
	if (dprev->dvmrp_tree_mask == DVMRP_TD_NOMASK) {
	    dvmrp_supply_guts(tlp, dprev, packet, start, &fillp, flash_update);
	    continue;
	}

	doing_right = 0;
	i = n = 0;
	for (;;) {
	    if (doing_right) {
		dp = dprev->right;
	    } else {
		dp = dprev->left;
	    }

	    if (dp->dvmrp_tree_mask >= dprev->dvmrp_tree_mask) {

		dvmrp_supply_guts(tlp, dp, packet, start, &fillp, flash_update);

		if (++n >= tlp->target_td_count[maskindex]) {
		    /* All found */
		    break;
		}

		if (doing_right) {
		    /*
		     * Finished right hand, back up stack for another
		     * node to be checked on the right.
		     */
		    assert(i != 0);
		    dprev = stack[--i];
		} else {
		    /*
		     * Finished left, try right on next pass
		     */
		    doing_right = 1;
		}
	    } else {
		if (doing_right) {
		    /*
		     * Node on right has children.  Step down the tree,
		     * starting on the left.  No need to stack the previous,
		     * we've already checked both children
		     */
		    doing_right = 0;
		} else {
		    /*
		     * Node on left has children, stack the previous node
		     * so we check its right child later.
		     */
		    stack[i++] = dprev;
		}
		dprev = dp;
	    }
	}
    }

    /* Send any packets with data remaining in them */
    if (fillp > start) {
	*(fillp-1) |= 0x80;
	dvmrp_send_report(tlp,
			  packet,
			  fillp-packet);
    }
}

/*ARGSUSED*/
static void
dvmrp_probe_job __PF2(tip, task_timer *,
		      interval, time_t)
{
    dvmrp_target *tlp;
    
    TARGET_LIST(tlp, &dvmrp_targets) {
	if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
		/*
		 * send a probe out to each target, but only send route
		 * reports at update interval
		 */
	    dvmrp_send_probe(tlp);
	}
    } TARGET_LIST_END(tlp, &dvmrp_targets);
}


/*ARGSUSED*/
static void
dvmrp_job __PF2(tip, task_timer *,
		interval, time_t)
{
    dvmrp_target *tlp;
    
    TARGET_LIST(tlp, &dvmrp_targets) {
	if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
	    dvmrp_supply(tlp, FALSE);
	}
    } TARGET_LIST_END(tlp, &dvmrp_targets);

    /* Indicate that flash updates are possible as soon as the timer fires */
    task_timer_set(dvmrp_timer_flash, DVMRP_T_FLASH, (time_t) 0);
    BIT_RESET(dvmrp_flags, DVMRPF_NOFLASH|DVMRPF_FLASHDUE);
}

/*
 *	send a flash update packet
 */
/*ARGSUSED*/
static void
dvmrp_do_flash __PF2(tip, task_timer *,
		     interval, time_t)
{
    task *tp = tip->task_timer_task;
    
    if (BIT_TEST(dvmrp_flags, DVMRPF_FLASHDUE)) {
	dvmrp_target *tlp;

	/* Do a flash update */

	trace_tp(tp,
		 TR_TASK,
		 0,
		 ("dvmrp_do_flash: Doing flash update for DVMRP"));

	TARGET_LIST(tlp, &dvmrp_targets) {
	    if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
		dvmrp_supply(tlp, TRUE);
	    }
	} TARGET_LIST_END(tlp, &dvmrp_targets);

	trace_tp(tp,
		 TR_TASK,
		 0,
		 ("dvmrp_do_flash: Flash update done"));

	/* Indicate no flash update is due */
	BIT_RESET(dvmrp_flags, DVMRPF_FLASHDUE);

	/* Schedule the next flash update */
	if (time_sec + DVMRP_T_MIN + DVMRP_T_MAX < dvmrp_timer_update->task_timer_next_time) {
	    /* We can squeeze another flash update in before the full update */

	    task_timer_set(tip, DVMRP_T_FLASH, (time_t) 0);
	} else {
	    /* The next flash update will be scheduled after the full update */

	    task_timer_reset(tip);
	    BIT_SET(dvmrp_flags, DVMRPF_NOFLASH);
	}
    } else {
	/* The next flash update can happen immediately */

	task_timer_reset(tip);
    }
}


/*
 *	Do or schedule a flash update
 */
static void
dvmrp_need_flash __PF1(tp, task *)
{
    task_timer *tip = dvmrp_timer_flash;

    if (!tip) {
	BIT_RESET(dvmrp_flags, DVMRPF_FLASHDUE);
	return;
    }
    
    /* Indicate we need a flash update */
    BIT_SET(dvmrp_flags, DVMRPF_FLASHDUE);

    /* And see if we can do it now */
    if (BIT_TEST(tip->task_timer_flags, TIMERF_INACTIVE)
	&& !BIT_TEST(dvmrp_flags, DVMRPF_NOFLASH)) {
	/* Do it now */

	dvmrp_do_flash(tip, (time_t) 0);
    }
}


/*
 *	Evaluate policy for changed routes.
 */
static int
dvmrp_policy __PF3(tp, task *,
		   tlp, dvmrp_target *,
		   change_list, dvmrp_rt_list *)
{
    if_addr *ifap = tlp->target_ifap;
    int changes = 0;
    int logged = 0;
    rt_entry *rte;

    DVMRP_RTCHANGE_LIST(rte, change_list) {
	rt_head *rth = rte->rt_head;
	register rt_entry *new_rt = rth->rth_dvmrp_active;
	adv_results result;
	dvmrp_td_entry *tdp;
	int exportable = FALSE;
	int changed = 0;
	int move_bit = 0;
	int holddown = 0;
	int poison = 0;
	int poison_reverse = 0;
	int set_metric = 0;

	tdp = dvmrp_td_locate(tlp, rth);

	trace_tp(tp,
		 TR_POLICY,
		 0,
		 ("dvmrp_policy: evaluating %A/%A",
		  rth->rth_dest,
		  rth->rth_dest_mask));

	/* Can we announce the new route (if there is one)? */
	if (new_rt) {
	    
	    if (socktype(rth->rth_dest) != AF_INET) {
		goto no_export;
	    }
	    
	    if (BIT_TEST(new_rt->rt_state, RTS_GROUP)) {
		/* Absolutely not */

		goto no_export;
	    }

	    if (RT_IFAP(new_rt) == ifap &&
		new_rt->rt_gwp->gw_proto == RTPROTO_DIRECT) {
		/* Do not send interface routes back to the same interface */

		goto no_export;
	    }

	    if (RT_IFAP(new_rt) == ifap
		&& sockaddrcmp(new_rt->rt_gwp->gw_addr,
			       *tlp->target_dst)) {
		/* Sending a route back to the router you are using could */
		/* cause a routing loop */

		poison_reverse++;
	    }

	    if ((new_rt->rt_gwp->gw_proto == tp->task_rtproto) &&
		(ifap == RT_IFAP(new_rt))) {
		poison_reverse++;
	    }

	    if (RT_IFAP(new_rt)
		&& !BIT_TEST(RT_IFAP(new_rt)->ifa_state, IFS_UP)) {
		/* The interface is down */
		goto no_export;
	    }

	    if (new_rt->rt_gwp->gw_proto == RTPROTO_DIRECT) {
		struct ifa_ps *ips = &(RT_IFAP(new_rt))->ifa_ps[tp->task_rtproto];
		/* Interface routes */
		if (BIT_TEST(ips->ips_state, IFPS_METRICIN)) {
		    result.res_metric = ips->ips_metric_in;
		} else {
		    result.res_metric = DVMRP_HOP;
		}
		if (result.res_metric < DVMRP_UNREACHABLE) {
		    exportable = TRUE;
		}
	    }

	    if (new_rt->rt_gwp->gw_proto == tp->task_rtproto) {
		result.res_metric = new_rt->rt_metric;
		    /*
		     * DVMRP has this funny scheme for keeping track of
		     * who is the upstream router by sending poison
		     * reverse metrics.
		     */
		if (poison_reverse && result.res_metric < DVMRP_UNREACHABLE) {
		    result.res_metric += DVMRP_UNREACHABLE;
		    exportable = TRUE;
		}
	    }

	no_export: ;
	}


	/* Now that we have determined the exportablility of the new */
	/* route we decide what changed need to be made. */

	/* There are two types of holddowns used, the first one is */
	/* called HOLDDOWN and is used when a route goes away or is */
	/* overridden by a route that is not suspected to be an echo */
	/* of a route we are announcing.  The second is called POISON */
	/* and is used when a route is overridden by a route suspected */
	/* to be an echo of a route we are announcing. */

	if (!tdp) {
	    /* New route */

	    if (exportable) {
		/* and it is exportable */
		
		/* Allocate new entry and fill it in */
		tdp = dvmrp_td_add(tlp, new_rt);

		set_metric++;
		changes++;
	    }
	} else if (!new_rt) {
	    /* No new route, just an old one */
	
	    if (!BIT_TEST(tdp->td_flags, TDF_POISON|TDF_HOLDDOWN)) {
		if (BIT_TEST(tdp->td_rt->rt_state, RTS_DELETE|RTS_HIDDEN)) {
		    /* Put into holddown */

		    holddown++;
		    changed++;
		} else {
		    /* Poison the old route */

		    poison++;
		    changed++;
		}
	    }
	} else if (new_rt == tdp->td_rt) {
	    /* Something has changed with the route we are announcing */

	    if (BIT_TEST(tdp->td_flags, TDF_POISON|TDF_HOLDDOWN)) {
		if (exportable) {
		    set_metric++;
		    changed++;
		}
	    } else {
		if (!exportable) {
		    poison++;
		    changed++;
		} else if (tdp->td_metric != result.res_metric) {
		    set_metric++;
		    changed++;
		}
	    }
	} else if (!BIT_TEST(new_rt->rt_state, RTS_PENDING)) {
	    /* The new route is not from a holddown protocol */
	    
	    if (BIT_TEST(tdp->td_flags, TDF_HOLDDOWN)
		&& !BIT_TEST(tdp->td_rt->rt_gwp->gw_flags, GWF_NEEDHOLD)
		&& !BIT_TEST(new_rt->rt_gwp->gw_flags, GWF_NEEDHOLD)) {
		/* No changes necessary */

		changed = move_bit = 0;
	    } else if (!BIT_TEST(tdp->td_flags, TDF_POISON|TDF_HOLDDOWN)) {
		/* Previously active, has been deleted */

		holddown++;
		changed++;
	    } else {
		if (exportable) {
		    set_metric++;
		    changed++;
		} else {
		    if (!BIT_TEST(tdp->td_flags, TDF_POISON)) {
			poison++;
			changed++;
		    }
		}
		move_bit++;
	    }
	} else {
	    /* New route is just better */

	    if (BIT_TEST(tdp->td_rt->rt_state, RTS_DELETE|RTS_HIDDEN)) {
		if (!BIT_TEST(tdp->td_flags, TDF_HOLDDOWN)) {
		    holddown++;
		    changed++;
		}
	    } else {
		if (!BIT_TEST(tdp->td_flags, TDF_POISON)) {
		    poison++;
		    changed++;
		}

		move_bit++;
	    }
	}

	if ((changed || set_metric)
	    && TRACE_TP(tp, TR_POLICY)) {
	    if (!logged) {
		logged++;
		trace_only_tp(tp,
			      TRC_NL_BEFORE,
			      ("dvmrp_policy: Policy for target %A",
			       *tlp->target_dst));
	    }
	    tracef("\t%A/%A ",
		   rth->rth_dest,
		   rth->rth_dest_mask);
	}
	if (set_metric) {
	    target_set_metric(tdp, result.res_metric);
	    trace_tp(tp,
		     TR_POLICY,
		     0,
		     ("metric %u",
		      tdp->td_metric));
	} else if (holddown) {
	    target_set_holddown(tdp, DVMRP_HOLDCOUNT);
	    trace_tp(tp,
		     TR_POLICY,
		     0,
		     ("starting holddown"));
	} else if (poison) {
	    target_set_poison(tdp, DVMRP_HOLDCOUNT);
	    trace_tp(tp,
		     TR_POLICY,
		     0,
		     ("starting poison"));
	} else if (TRACE_TP(tp, TR_POLICY)) {
	    trace_clear();
	}
	
	if (move_bit) {
	    /* Move lock to new route */

	    tdp->td_rt = new_rt;
	}
    } DVMRP_RTCHANGE_LIST_END(rte, change_list) ;

    if (logged) {
	trace_tp(tp,
		 TR_POLICY,
		 0,
		 (NULL));
    }
    return changes;
}

static time_t
dvmrp_send_prune  __PF1(mfcp, mfc *)
{
    int i;
    int found = FALSE;
    rt_entry *rt;
    nbr_list *nbrp;
    nbr_list *list = (nbr_list *) mfcp->upstream_ifap->dvmrp_if_nbr_list;

	/*
	 * Decide if upstream neighbor is capable of accepting
	 * prunes. If not, we`re out of luck.
	 */
    rt = rt_lookup((flag_t) 0,
		   (flag_t) 0,
		   sockbuild_in(0, mfcp->mfc_src),
		   RTPROTO_BIT(RTPROTO_DVMRP)|RTPROTO_BIT(RTPROTO_DIRECT));

	/*
	 * If we don't have a route, just return
	 */
    if (!rt) {
	return 0;
    }
    NBR_DR_LIST(nbrp, list) {
	if (sockaddrcmp_in(nbrp->nbr_addr, RT_ROUTER(rt))) {
	    found = TRUE;
	    break;
	}
    } NBR_DR_LIST_END(nbrp, list);

	/*
	 * If the neighbor can prune, then build the packet
	 */
    if (found && BIT_TEST(nbrp->cap_flags, DVMRP_NBR_PRUNE)) {
	int rc;
	time_t timeout;
	u_int32 net_timeout;
	byte *packet = task_get_send_buffer(byte *);
	byte *start = packet + sizeof(struct ip) + sizeof(struct igmp);
	struct igmp *igmp = (struct igmp *) (start + sizeof(struct ip));
	byte *fillp = start;

	    /*
	     * If a graft is pending, stop the graft timer
	     */
	if (mfcp->mfc_graft) {
	    graft_list	*gp;

	    GRAFT_LIST(gp, &graft_head) {
		if (sockaddrcmp_in(gp->graft_dst, nbrp->nbr_addr) &&
		    gp->graft_mfc->mfc_group->group_key == mfcp->mfc_group->group_key &&
		    gp->graft_mfc->mfc_src == mfcp->mfc_src) {
		    if (gp == graft_head.forw) {
			task_timer_reset(dvmrp_timer_graft);
		    }
		    REMQUE(gp);
		    task_block_free(dvmrp_graft_block_index, (void_t) gp);
			    /*
			     * if timer currently inactive, reactivate
			     */
		    if (BIT_TEST(dvmrp_timer_graft->task_timer_flags,
				 TIMERF_INACTIVE)) {
			task_timer_set(dvmrp_timer_graft,
				       (time_t) 0,
				       gp->graft_time - time_sec);
		    }
		    break;
		}
	    } GRAFT_LIST_END(gp, &graft_head);
	}
	igmp->igmp_type = IGMP_PROTO_DVMRP;
	igmp->igmp_code = DVMRP_PRUNE;
	igmp->igmp_group = htonl((DVMRP_VERSION_MINOR << 8) |
				 DVMRP_VERSION_MAJOR);

	    /*
	     * Determine the prune lifetime
	     * It is the minimum of the cache lifetime or
	     * the lifetime of any downstream prunes.
	     * The downstream prune with the shortest lifetime should be
	     * at the beginning of the downstream prune timeout queue.
	     */
	timeout = dvmrp_default_mfctimeout - (time_sec - mfcp->mfc_ctime);
	if (&mfcp->prune_down != mfcp->prune_down.tq_forw &&
	    mfcp->prune_down.tq_forw->holdtime < timeout) {
	    timeout = mfcp->prune_down.tq_forw->holdtime;
	}

	assert(timeout > 0);
	net_timeout = htonl(timeout);

	for (i=0; i < sizeof(u_int32); i++)
	    *fillp++ = ((byte *) &mfcp->mfc_src)[i];
	for (i=0; i < sizeof(u_int32); i++)
	    *fillp++ = ((byte *) &mfcp->mfc_group->group_key)[i];
	for (i=0; i < sizeof(u_int32); i++)
	    *fillp++ = ((byte *) &net_timeout)[i];

	igmp->igmp_cksum = 0;
	igmp->igmp_cksum = inet_cksum((void_t) igmp,
				      fillp-packet-sizeof(struct ip));
	rc = igmp_send(mfcp->upstream_ifap,
		       (struct ip *) packet,
		       fillp-packet,
		       nbrp->nbr_addr,
		       TRUE);

	/* Should we trace this packet? */

	if (TRACE_PACKET_SEND_TP(dvmrp_task,
				 igmp->igmp_code,
				 DVMRP_MAX_TYPE,
				 dvmrp_trace_masks)) {

	    dvmrp_trace(dvmrp_task->task_trace,
			rc,
			mfcp->upstream_ifap,
			nbrp->nbr_addr,
			igmp,
			fillp-packet-sizeof(struct ip),
			TRACE_DETAIL_SEND_TP(dvmrp_task,
					     igmp->igmp_code,
					     DVMRP_MAX_TYPE,
					     dvmrp_trace_masks));
	}
	return timeout;
    }
    return 0;
}

static void
dvmrp_send_graft  __PF1(mfcp, mfc *)
{
    int i,
	rc;
    int found = FALSE;
    rt_entry *rt;
    nbr_list *nbrp;
    nbr_list *list = (nbr_list *) mfcp->upstream_ifap->dvmrp_if_nbr_list;

	/*
	 * Decide if upstream neighbor is capable of accepting
	 * grafts. If not, we`re out of luck.
	 */
    rt = rt_lookup((flag_t) 0,
		   (flag_t) 0,
		   sockbuild_in(0, mfcp->mfc_src),
		   RTPROTO_BIT(RTPROTO_DVMRP)|RTPROTO_BIT(RTPROTO_DIRECT));

    NBR_DR_LIST(nbrp, list) {
	if (sockaddrcmp_in(nbrp->nbr_addr, RT_ROUTER(rt))) {
	    found = TRUE;
	    break;
	}
    } NBR_DR_LIST_END(nbrp, list);

	/*
	 * If the neighbor can prune, then build the packet
	 */
    if (found &&
	BIT_TEST(nbrp->cap_flags, DVMRP_NBR_PRUNE) &&
	!mfcp->mfc_graft) {

	byte *packet = task_get_send_buffer(byte *);
	byte *start = packet + sizeof(struct ip) + sizeof(struct igmp);
	struct igmp *igmp = (struct igmp *) (start + sizeof(struct ip));
	byte *fillp = start;

	igmp->igmp_type = IGMP_PROTO_DVMRP;
	igmp->igmp_code = DVMRP_GRAFT;
	igmp->igmp_group = htonl((DVMRP_VERSION_MINOR << 8) |
				 DVMRP_VERSION_MAJOR);

	for (i=0; i < sizeof(u_int32); i++)
	    *fillp++ = ((byte *) &mfcp->mfc_src)[i];
	for (i=0; i < sizeof(u_int32); i++)
	    *fillp++ = ((byte *) &mfcp->mfc_group->group_key)[i];

	igmp->igmp_cksum = 0;
	igmp->igmp_cksum = inet_cksum((void_t) igmp,
				      fillp-packet-sizeof(struct ip));
	rc = igmp_send(mfcp->upstream_ifap,
		       (struct ip *) packet,
		       fillp-packet,
		       nbrp->nbr_addr,
		       TRUE);

	/* Should we trace this packet? */

	if (TRACE_PACKET_SEND_TP(dvmrp_task,
				 igmp->igmp_code,
				 DVMRP_MAX_TYPE,
				 dvmrp_trace_masks)) {

	    dvmrp_trace(dvmrp_task->task_trace,
			rc,
			mfcp->upstream_ifap,
			nbrp->nbr_addr,
			igmp,
			fillp-packet-sizeof(struct ip),
			TRACE_DETAIL_SEND_TP(dvmrp_task,
					     igmp->igmp_code,
					     DVMRP_MAX_TYPE,
					     dvmrp_trace_masks));
	}
	dvmrp_add_graft(mfcp, nbrp->nbr_addr);
    }
    return;
}

static void
dvmrp_send_graftack  __PF4(ifap, if_addr *,
			   nbr, sockaddr_un *,
			   group, u_int32,
			   source, u_int32)
{
    int i, rc;
    byte *packet = task_get_send_buffer(byte *);
    byte *start = packet + sizeof(struct ip) + sizeof(struct igmp);
    struct igmp *igmp = (struct igmp *) (start + sizeof(struct ip));
    byte *fillp = start;

    igmp->igmp_type = IGMP_PROTO_DVMRP;
    igmp->igmp_code = DVMRP_GRAFT_ACK;
    igmp->igmp_group = htonl((DVMRP_VERSION_MINOR << 8) |
			     DVMRP_VERSION_MAJOR);

    for (i=0; i < sizeof(u_int32); i++)
	*fillp++ = ((byte *) &source)[i];
    for (i=0; i < sizeof(u_int32); i++)
	*fillp++ = ((byte *) &group)[i];

    igmp->igmp_cksum = 0;
    igmp->igmp_cksum = inet_cksum((void_t) igmp,
				  fillp-packet-sizeof(struct ip));
    rc = igmp_send(ifap,
		   (struct ip *) packet,
		   fillp-packet,
		   nbr,
		   TRUE);

    /* Should we trace this packet? */

    if (TRACE_PACKET_SEND_TP(dvmrp_task,
			     igmp->igmp_code,
			     DVMRP_MAX_TYPE,
			     dvmrp_trace_masks)) {

	dvmrp_trace(dvmrp_task->task_trace,
		    rc,
		    ifap,
		    nbr,
		    igmp,
		    fillp-packet-sizeof(struct ip),
		    TRACE_DETAIL_SEND_TP(dvmrp_task,
					 igmp->igmp_code,
					 DVMRP_MAX_TYPE,
					 dvmrp_trace_masks));
    }
}

/*
 * Add a nbr to the downstream interface neighbor list
 */

static void
dvmrp_mfc_add_nbr  __PF2(mfcp, mfc *,
			 data, caddr_t)
{
    nbr_list *nbr = (nbr_list *) data;
    downstream *dsp;

    DOWNSTREAM_LIST(dsp, mfcp->ds) {
        if (dsp->ds_proto == RTPROTO_DVMRP) {
	    if (sockaddrcmp_in(dsp->ds_addr, nbr->ifap->ifa_addr)) {
		ifnbr *intfnbr = dsp->ds_nbrlist.forw;
		while (intfnbr != &dsp->ds_nbrlist) {
		    if (sockaddrcmp_in(intfnbr->nbr_addr, nbr->nbr_addr)) {
			return;
		    }
		    intfnbr = intfnbr->forw;
		}
		intfnbr = task_block_alloc(dvmrp_ifnbr_block_index);
		intfnbr->nbr_addr = nbr->nbr_addr;
		INSQUE(intfnbr, dsp->ds_nbrlist.back);
	    }
	}
    } DOWNSTREAM_LIST_END(dsp, mfcp->ds);
}

/*
 * Delete a nbr from the downstream interface neighbor list
 */

static void
dvmrp_mfc_delete_nbr  __PF2(mfcp, mfc *,
			    data, caddr_t)
{
    nbr_list *nbr = (nbr_list *) data;
    downstream *dsp;

    DOWNSTREAM_LIST(dsp, mfcp->ds) {
        if (dsp->ds_proto == RTPROTO_DVMRP) {
	    if (sockaddrcmp_in(dsp->ds_addr, nbr->ifap->ifa_addr)) {
		ifnbr *intfnbr = dsp->ds_nbrlist.forw;
		while (intfnbr != &dsp->ds_nbrlist) {
		    if (sockaddrcmp_in(intfnbr->nbr_addr, nbr->nbr_addr)) {
			REMQUE(intfnbr);
			task_block_free(dvmrp_ifnbr_block_index, (void_t) intfnbr);
			break;
		    }
		    intfnbr = intfnbr->forw;
		}
		    /*
		     * No nbrs are left. Time to prune interface
		     */
		if (dsp->ds_nbrlist.forw == &dsp->ds_nbrlist) {
		    struct group_list *gp;
		    struct group_list *glist = (struct group_list *)
						nbr->ifap->igmp_if_group_list;
		    GROUP_LIST(gp, glist) {
			/*
			 * Has someone requested this group ?
			 * If so, we must keep sending data
			 */
			if (mfcp->mfc_group->group_key == gp->group_addr.s_addr) {
			    return;
			}
		    } GROUP_LIST_END(gp, glist);

		    dvmrp_mfc_delete_ifap(mfcp, (caddr_t) nbr->ifap);

		    return;
		}
	    }
	}
    } DOWNSTREAM_LIST_END(dsp, mfcp->ds);
}

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

    trace_tp(dvmrp_task,
	     TR_TIMER,
	     0,
	     ("dvmrp_reset_mfc_timer: removing MFC: group %A source %A",
	      sockbuild_in(0, mfcp->mfc_group->group_key),
	      sockbuild_in(0, 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(dvmrp_timer_mfc);
	}
	REMQUE(mp);
	task_block_free(dvmrp_mfc_block_index, (void_t) mp);
    }

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

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


/*ARGSUSED*/
static void
dvmrp_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;
	pp = pp->tq_forw;
	PRUNE_TQ_DEQ(aged);
	if (aged == aged->mfcp->prune_up) {
	    aged->mfcp->prune_up = (prune_list *) 0;
	} else {
	    PRUNE_IF_DEQ(aged);
	}

	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, IPMULTI_BIT(IPMULTI_PROTO_DVMRP));

		/*
		 * If this is the last protocol to want this cache entry,
		 * time to blow it away
		 */
	    if (!aged->mfcp->mfc_proto) {
		dvmrp_reset_mfc_timer(aged->mfcp);
		(void) krt_delete_cache(sockbuild_in(0, aged->mfcp->mfc_group->group_key),
					sockbuild_in(0, aged->mfcp->mfc_src));
		mfc_delete_node(aged->mfcp);
	    }

	    task_block_free(dvmrp_prune_block_index, aged);
	} else {
		/*
		 * Time to start sending traffic down this interface again
		 * and wait for the next prune
		 */
	    dvmrp_mfc_add_ifap(aged->mfcp, (caddr_t) aged->ifap);

	    task_block_free(dvmrp_prune_block_index, aged);
	}
    }
    if (prune_head.tq_forw != &prune_head) {
	task_timer_set(dvmrp_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
}

static void
dvmrp_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 (!dvmrp_timer_prune) {
	dvmrp_timer_prune = task_timer_create(dvmrp_task,
					    "Prune",
					    (flag_t) 0,
					    (time_t) 0,
					    new->prune_time - time_sec,
					    dvmrp_prune_timeout,
					    (void_t) 0);
    }
	/*
	 * if new head of list, reset timer
	 */
    else if (new == prune_head.tq_forw &&
	     !BIT_TEST(dvmrp_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_reset(dvmrp_timer_prune);
    }
	/*
	 * if timer currently inactive,
	 * reactivate
	 */
    if (BIT_TEST(dvmrp_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_set(dvmrp_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
}

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

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

	trace_tp(dvmrp_task,
		 TR_TIMER,
		 0,
		 ("dvmrp_graft_timeout: Graft sent Upstream to %A for Group %A Source %A timed out before acknowledged",
		  lp->graft_dst,
		  sockbuild_in(0, lp->graft_mfc->mfc_group->group_key),
		  sockbuild_in(0, lp->graft_mfc->mfc_src)));

	assert(lp->graft_mfc);
	lp->graft_mfc->mfc_graft = 0;

	aged = lp;
	lp = lp->forw;
	REMQUE(aged);
	task_block_free(dvmrp_graft_block_index, (void_t) aged);

	dvmrp_send_graft(lp->graft_mfc);
    }
    if (graft_head.forw != &graft_head) {
	task_timer_set(dvmrp_timer_graft,
		       (time_t) 0,
		       graft_head.forw->graft_time - time_sec);
    }
}

static void
dvmrp_add_graft  __PF2(mfcp, mfc *,
		       dst, sockaddr_un *)
{
    graft_list *new = (graft_list *) task_block_alloc(dvmrp_graft_block_index);
	/*
	 * Insert at end of timer queue
	 */
    new->graft_mfc = mfcp;
    new->graft_time = time_sec + dvmrp_default_graftacktimeout;
    new->graft_dst = dst;

    INSQUE(new, graft_head.back);

	    /* if no timer running, create one */
    if (!dvmrp_timer_graft) {
	dvmrp_timer_graft = task_timer_create(dvmrp_task,
					     "GraftAck",
					     (flag_t) 0,
					     (time_t) 0,
					     dvmrp_default_graftacktimeout,
					     dvmrp_graft_timeout,
					     (void_t) 0);
    }
	    /* if timer currently inactive, reactivate */
    else if (BIT_TEST(dvmrp_timer_graft->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_set(dvmrp_timer_graft,
		       (time_t) 0,
		       dvmrp_default_graftacktimeout);
    }
    mfcp->mfc_graft = new;
}

static void
dvmrp_mfc_add_ifap  __PF2(mfcp, mfc *,
			  data, caddr_t)
{
    prune_list *pp;
    downstream *dsp, *dpos;
    if_addr *ifap = (if_addr *) data;
    nbr_list *lp, *list = (nbr_list *) ifap->dvmrp_if_nbr_list;

	/*
	 * 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_DVMRP) {
	    if (sockaddrcmp_in(dsp->ds_addr, ifap->ifa_addr)) {
		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) {
	    if (sock2ip(dpos->ds_addr) < sock2ip(ifap->ifa_addr))
		    dpos = dpos->forw;
    }

    dsp = mfc_alloc_downstream();

    dsp->ds_proto = RTPROTO_DVMRP;
    dsp->ds_addr = ifap->ifa_addr;
    dsp->ds_ttl = DVMRP_IF_THRESHOLD(ifap);
    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 */

	/*
	 * Now add all of the neighbors we know about on this
	 * interface. We can't prune until all of these
	 * neighbors send us a prune.
	 */
    dsp->ds_nbrlist.forw = dsp->ds_nbrlist.back = &dsp->ds_nbrlist;
    NBR_DR_LIST(lp, list) {
	ifnbr *nbr = (ifnbr *)
		     task_block_alloc(dvmrp_ifnbr_block_index);
	nbr->nbr_addr = lp->nbr_addr;
	INSQUE(nbr, dsp->ds_nbrlist.back);
    } NBR_DR_LIST_END(lp, list);

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

    mfcp->ds_count++;

    trace_tp(dvmrp_task,
	     TR_ROUTE,
	     0,
	     ("dvmrp_mfc_add_ifap: DVMRP adding downstream interface %A(%s)",
	      ifap->ifa_addr,
	      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(dvmrp_timer_prune->task_timer_flags,
		      TIMERF_INACTIVE)) {
	    task_timer_reset(dvmrp_timer_prune);
	}

	PRUNE_TQ_DEQ(pp);

	task_block_free(dvmrp_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.
	     */
	if (mfcp->upstream_ifap !=
	    if_withsubnet(sockbuild_in (0, mfcp->mfc_src))) {
	    dvmrp_send_graft(mfcp);
	}
    }

    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(dvmrp_timer_prune->task_timer_flags,
			  TIMERF_INACTIVE)) {
		task_timer_reset(dvmrp_timer_prune);
	    }
	    PRUNE_TQ_DEQ(pp);
	    PRUNE_IF_DEQ(pp);
	    task_block_free(dvmrp_prune_block_index, pp);
	    break;
	}
	pp = pp->if_forw;
    }
	/*
	 * if timer currently inactive, reactivate
	 */
    if (prune_head.tq_forw != &prune_head &&
	BIT_TEST(dvmrp_timer_prune->task_timer_flags,
		 TIMERF_INACTIVE)) {
	task_timer_set(dvmrp_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
}

static void
dvmrp_mfc_delete_ifap  __PF2(mfcp, mfc *,
			     data, caddr_t)
{
    int count = 0;
    time_t timeout;
    prune_list *new;
    downstream *dsp, *delete = 0;
    if_addr *ifap = (if_addr *) data;

    DOWNSTREAM_LIST(dsp, mfcp->ds) {
        if (dsp->ds_proto == RTPROTO_DVMRP) {
	    if (sockaddrcmp_in(dsp->ds_addr, ifap->ifa_addr)) {
		    /*
		     * should never happen more than once!
		     */
		delete = dsp;
	    } else {
		count++;
	    }
	}
    } DOWNSTREAM_LIST_END(dsp, mfcp->ds);

    if (delete) {
	ifnbr *ifn;
	trace_tp(dvmrp_task,
		 TR_ROUTE,
		 0,
		 ("dvmrp_mfc_delete_ifap: DVMRP removing downstream interface %A(%s) for group %A source %A",
		  ifap->ifa_addr,
		  ifap->ifa_link->ifl_name,
		  sockbuild_in(0, mfcp->mfc_group->group_key),
		  sockbuild_in(0, mfcp->mfc_src)));
	    /*
	     * Go through the nbr list and free up the memory
	     */
	ifn = delete->ds_nbrlist.forw;
	while (ifn != &delete->ds_nbrlist) {
	    REMQUE(ifn);
	    task_block_free(dvmrp_ifnbr_block_index, ifn);
	    ifn = delete->ds_nbrlist.forw;
	}

	REMQUE(delete);
	mfc_free_downstream(delete);

	assert(mfcp->ds_count);

	mfcp->ds_count--;


	krt_update_mfc(mfcp);
	    /*
	     * If no more downstream interfaces, time to prune upstream
	     */
	if (!count && (timeout = dvmrp_send_prune(mfcp))) {
		/*
		 * If send ok, add prune to timeout queue
		 */
	    new = (prune_list *) task_block_alloc(dvmrp_prune_block_index);
	    new->ifap = mfcp->upstream_ifap;
	    new->mfcp = mfcp;
	    new->pending = FALSE;
	    new->holdtime = timeout;
	    new->prune_time = time_sec + timeout;

	    dvmrp_queue_prune(mfcp, new);
	}
    }
}


static void
dvmrp_nbr_purge __PF1(ifap, if_addr *)
{
    nbr_list *list = (nbr_list *) ifap->dvmrp_if_nbr_list;
    nbr_list *gp, *delete;

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

	task_timer_reset((task_timer *)ifap->dvmrp_nbr_timer_timeout);

	if (sockaddrcmp(ifap->ifa_addr, list->dr_forw->nbr_addr)) {
	    igmp_disable_dr_status(ifap);
	}

	/*
	 *  Remove all the routers from the list
	 */
	gp = list->dr_forw;
	while(gp != list ) {
	    delete = gp;
	    gp = gp->dr_forw;
	    NBR_TQ_DEQ(delete);
	    NBR_DR_DEQ(delete);
	    task_block_free(dvmrp_nbr_block_index, (void_t) delete);
	}
	task_block_free(dvmrp_nbr_block_index, (void_t) list);
	ifap->dvmrp_if_nbr_list = (void_t) 0;
    }
}


/*
 *	Update the target list
 */
static void
dvmrp_target_list __PF1(tp, task *)
{
    int targets = 0;
    flag_t target_flags = 0;
    static int n_targets;
    if_addr *ifap;

    /* figure out if we need to broadcast packets */
    IF_ADDR(ifap) {
	struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];
	if (!BIT_TEST(ips->ips_state, IFPS_NOOUT) &&
	    BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    targets++;
	}
    } IF_ADDR_END(ifap) ;

    if (targets > 1) {

	BIT_SET(dvmrp_flags, DVMRPF_BROADCAST);
    } else {

	BIT_RESET(dvmrp_flags, DVMRPF_BROADCAST);
    }

    if (!dvmrp_timer_age) {
	/* Create route age timer */

	dvmrp_timer_age = task_timer_create(tp,
					    "Age",
					    (flag_t) 0,
					    (time_t) 0,
					    DVMRP_T_EXPIRE,
					    dvmrp_age,
					    (void_t) 0);
    }

    if (BIT_TEST(dvmrp_flags, DVMRPF_BROADCAST)) {
	/* We are supplying updates */

	/* Make sure the timers are active */
	if (!dvmrp_timer_probe) {
	    /* Create the probe timer */

	    dvmrp_timer_probe = task_timer_create(tp,
						   "Probe",
						   0,
						   (time_t) DVMRP_T_PROBE,
						   (time_t) DVMRP_T_MAX,
						   dvmrp_probe_job,
						   (void_t) 0);
	}

	if (!dvmrp_timer_update) {
	    /* Create the update timer */

	    dvmrp_timer_update = task_timer_create(tp,
						   "Update",
						   0,
						   (time_t) DVMRP_T_UPDATE,
						   (time_t) DVMRP_T_MAX,
						   dvmrp_job,
						   (void_t) 0);
	}

	if (!dvmrp_timer_flash) {
	    /* Create flash update timer */

	    dvmrp_timer_flash = task_timer_create(tp,
						  "Flash",
						  (flag_t) 0,
						  (time_t) DVMRP_T_FLASH,
						  (time_t) DVMRP_T_MAX,
						  dvmrp_do_flash,
						  (void_t) 0);
	}
    } else {
	/* We are just listening */

	/* Make sure the timers do not exist */
	if (dvmrp_timer_probe) {
	    task_timer_delete(dvmrp_timer_probe);
	    dvmrp_timer_probe = (task_timer *) 0;
	}
	if (dvmrp_timer_update) {
	    task_timer_delete(dvmrp_timer_update);
	    dvmrp_timer_update = (task_timer *) 0;
	}
	if (dvmrp_timer_flash) {
	    task_timer_delete(dvmrp_timer_flash);
	    dvmrp_timer_flash = (task_timer *) 0;
	}
	if (dvmrp_timer_age) {
	    task_timer_delete(dvmrp_timer_age);
	    dvmrp_timer_age = (task_timer *) 0;
	}
    }


    /* Set flags for target list build */
    if (BIT_TEST(dvmrp_flags, DVMRPF_BROADCAST)) {
	BIT_SET(target_flags, TARGETF_BROADCAST);
    }

    /* Build or update target list */
    targets = dvmrp_target_build(tp,
			         &dvmrp_targets,
			         dvmrp_gw_list,
			         target_flags,
			         (void_t) 0);

    /* Evaluate policy for new targets */
    {
	int changes = 0;
	int have_list = 0;
	dvmrp_rt_list *rthl = (dvmrp_rt_list *) 0;
	dvmrp_target *tlp;

	TARGET_LIST(tlp, &dvmrp_targets) {
	    if (BIT_TEST(tlp->target_flags, TARGETF_BROADCAST)) {
		if (BIT_TEST(tlp->target_ifap->ifa_ps[tp->task_rtproto].ips_state, IFPS_NOIN)) {
			/*
			 * Interface no longer is running DVMRP.
			 * Go through all routes, rm routes out this interface
			 */
		    register rt_entry *rt;
		    register gw_entry *gwp;

		    GW_LIST(dvmrp_gw_list, gwp) {
			RTQ_LIST(&gwp->gw_rtq, rt) {
			    if (RT_IFAP(rt) == tlp->target_ifap) {
				rt_delete(rt);
				changes++;
			    }
			} RTQ_LIST_END(&gwp->gw_rtq, rt) ;
		    } GW_LIST(dvmrp_gw_list, gwp) ;
		}
	    }
	    switch (BIT_TEST(tlp->target_flags, TARGETF_POLICY|TARGETF_SUPPLY)) {
	    case TARGETF_SUPPLY:
		/* Need to run policy for this target */

		if (!have_list) {
		    /* Get update list */
		    rthl = dvmrp_rtlist_root;
		    have_list++;
		}

		if (rthl) {
		    /* and run policy */
		    changes += dvmrp_policy(tp, tlp, rthl);
		}

		/* Indicate policy has been run */
		BIT_SET(tlp->target_flags, TARGETF_POLICY);
		break;

	    case TARGETF_POLICY:
		/* Indicate policy run on this target */

		BIT_RESET(tlp->target_flags, TARGETF_POLICY);
		break;

	    default:
		break;
	    }
	} TARGET_LIST_END(tlp, &dvmrp_targets) ;

	if (dvmrp_rtlist_root) {
	    rthl = dvmrp_rtlist_root;
	    while (rthl) {
		register dvmrp_rt_list *Xrtln = rthl->rtl_next;
		task_block_free(dvmrp_rtlist_block_index, (void_t) rthl);
		rthl = Xrtln;
	    }
	    dvmrp_rtlist_root = (dvmrp_rt_list *) 0;
	}

	if (changes
	    && !BIT_TEST(dvmrp_flags, DVMRPF_RECONFIG)) {
	    dvmrp_need_flash(tp);
	}
    }

    if (targets != n_targets) {

	tracef("dvmrp_target_list: ");
	if (targets) {
	    tracef("supplying updates to");
	    if (targets) {
		tracef(" %d interface%s",
		       targets,
		       targets > 1 ? "s" : "");
	    }
	} else {
	    tracef("just listening");
	}	
	n_targets = targets;
	trace_log_tp(tp,
		     TRC_NL_AFTER,
		     LOG_INFO,
		     (NULL));
    }
}


void
dvmrp_group_change  __PF3(change, int,
			  ifap, if_addr *,
			  group, u_int32)
{
    group_node *gp = mfc_locate_group(group);

    switch(change) {
    case IGMP_GROUP_ADDED:
	trace_tp(dvmrp_task,
		 TR_NORMAL,
		 0,
		 ("dvmrp_group_change: group %A ADDED",
		  sockbuild_in(0, 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(gp, dvmrp_mfc_add_ifap, (caddr_t) ifap);
	}
	break;
    case IGMP_GROUP_REMOVED:
	trace_tp(dvmrp_task,
		 TR_NORMAL,
		 0,
		 ("dvmrp_group_change: group %A REMOVED",
		  sockbuild_in(0, 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(gp, dvmrp_mfc_delete_ifap, (caddr_t) ifap);
	}
	break;
    }
}


static void
dvmrp_clean_mfc  __PF1(mfcp, mfc *)
{
    prune_list *next, *down, *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(dvmrp_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
		    task_timer_reset(dvmrp_timer_prune);
		}
		next = pp->tq_forw;
		PRUNE_TQ_DEQ(pp);
		task_block_free(dvmrp_prune_block_index, pp);
		pp = next;
	    } else {
		pp = pp->tq_forw;
	    }
	}
    }
    if (&mfcp->prune_down != mfcp->prune_down.if_forw) {
	down = mfcp->prune_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(dvmrp_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
			task_timer_reset(dvmrp_timer_prune);
		    }
		    next = pp->tq_forw;
		    PRUNE_TQ_DEQ(pp);
		    PRUNE_IF_DEQ(pp);
		    task_block_free(dvmrp_prune_block_index, pp);
		    pp = next;
		} else {
		    pp = pp->tq_forw;
		}
	    }
	    down = down->if_forw;
	}
    }
    if (prune_head.tq_forw != &prune_head &&
	BIT_TEST(dvmrp_timer_prune->task_timer_flags, TIMERF_INACTIVE)) {
	task_timer_set(dvmrp_timer_prune,
		       (time_t) 0,
		       prune_head.tq_forw->prune_time - time_sec);
    }
}

static void
dvmrp_mfc_check_use  __PF1(mfcp, mfc *)
{
    if (mfcp->mfc_lastuse == mfcp->mfc_use) {
	int count = mfcp->ds_count;

	trace_tp(dvmrp_task,
		 TR_TIMER,
		 0,
		 ("dvmrp_mfc_check_use: MFC inactivity timeout: group %A source %A",
		  sockbuild_in(0, mfcp->mfc_group->group_key),
		  sockbuild_in(0, mfcp->mfc_src)));
	    /*
	     * If there are no downstream interfaces already, just delete it.
	     */

	if (!count) {
	    dvmrp_clean_mfc(mfcp);
	    dvmrp_reset_mfc_timer(mfcp);
	    (void) krt_delete_cache(
			    sockbuild_in(0, mfcp->mfc_group->group_key),
			    sockbuild_in(0, mfcp->mfc_src));
	    mfc_delete_node(mfcp);
	    return;
	}
    } else {
	if (BIT_TEST(mfcp->mfc_proto, IPMULTI_BIT(IPMULTI_PROTO_DVMRP))) {
	    dvmrp_set_mfc_timer(mfcp, dvmrp_default_mfctimeout);
	}
    }
}


/*ARGSUSED*/
static void
dvmrp_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, dvmrp_mfc_check_use)) {
	    trace_log_tf(dvmrp_trace_options,
			 0,
			 LOG_WARNING,
			 ("dvmrp_mfc_timeout: check use count failed group %A source %A",
			  sockbuild_in(0, aged->mfcp->mfc_group->group_key),
			  sockbuild_in(0, aged->mfcp->mfc_src)));
	}

	trace_tp(dvmrp_task,
		 TR_TIMER,
		 0,
		 ("dvmrp_mfc_timeout: refreshing MFC: group %A source %A",
		  sockbuild_in(0, aged->mfcp->mfc_group->group_key),
		  sockbuild_in(0, aged->mfcp->mfc_src)));
	task_block_free(dvmrp_mfc_block_index, (void_t) aged);
    }

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

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

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

    new = (mfc_list *) task_block_alloc(dvmrp_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 (!dvmrp_timer_mfc) {
	dvmrp_timer_mfc = task_timer_create(dvmrp_task,
					  "MFCTimeout",
					  (flag_t) 0,
					  (time_t) 0,
					  timeout,
					  dvmrp_mfc_timeout,
					  (void_t) 0);
    } else {
		/*
		 * If new entry now at head of list, restart timer
		 */
	if (mfc_head.forw == new) {
	    task_timer_reset(dvmrp_timer_mfc);
	}
		/*
		 * Or if timer isn't running
		 */
	if (BIT_TEST(dvmrp_timer_mfc->task_timer_flags, TIMERF_INACTIVE)) {
	    task_timer_set(dvmrp_timer_mfc,
			   (time_t) 0,
			   timeout);
	}
    }
}



/*
 *	Fill in Multicast Forwarding Cache entry
 */
static void
dvmrp_mfc_request  __PF3(msgtype, int,
			 in_ifap, if_addr *,
			 mfcp, mfc *)
{
    register if_addr *ifap;

    trace_tp(dvmrp_task,
	     TR_ROUTE,
	     0,
	     ("dvmrp_mfc_request: Request for group %A source %A",
	      sockbuild_in(0, mfcp->mfc_group->group_key),
	      sockbuild_in(0, mfcp->mfc_src)));
    if (in_ifap) {
	trace_tp(dvmrp_task,
		 TR_ROUTE,
		 0,
		 ("dvmrp_mfc_request: interface %A(%s)",
		  in_ifap->ifa_addr,
		  in_ifap->ifa_link->ifl_name));
    }

    switch (msgtype) {

    case EADDRNOTAVAIL:
	/*
	 * form outgoing interface list
	 * if not a leaf network, add to list
	 * if it's a leaf, then check group membership
	 */

	IF_ADDR(ifap) {
	    int add = 0;
	    struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_DVMRP];
	    struct group_list *gp;
	    struct group_list *glist = (struct group_list *)
					ifap->igmp_if_group_list;

		/*
		 * Don't send it out a non-multicast interface,
		 * the loopback, or the incoming interface
		 */
	    if (!BIT_TEST(ifap->ifa_state, IFS_MULTICAST) ||
		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 (mfcp->mfc_group->group_key == gp->group_addr.s_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;
		nbr_list *lp, *list = (nbr_list *) ifap->dvmrp_if_nbr_list;

		dpos = mfcp->ds->forw;
		while (dpos != mfcp->ds) {
			if (sock2ip(dpos->ds_addr) < sock2ip(ifap->ifa_addr))
				dpos = dpos->forw;
		}

		dp = mfc_alloc_downstream();

		dp->ds_addr = ifap->ifa_addr;
		dp->ds_proto = RTPROTO_DVMRP;
		dp->ds_ttl = DVMRP_IF_THRESHOLD(ifap);
		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 */

		    /*
		     * Now add all of the neighbors we know about on this
		     * interface. We can't prune until all of these
		     * neighbors send us a prune.
		     */
		dp->ds_nbrlist.forw = dp->ds_nbrlist.back = &dp->ds_nbrlist;
		NBR_DR_LIST(lp, list) {
		    ifnbr *nbr = (ifnbr *)
				 task_block_alloc(dvmrp_ifnbr_block_index);
		    nbr->nbr_addr = lp->nbr_addr;
		    INSQUE(nbr, dp->ds_nbrlist.back);
		} NBR_DR_LIST_END(lp, list);


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

		mfcp->ds_count++;

		trace_tp(dvmrp_task,
			 TR_ROUTE,
			 0,
			 ("dvmrp_mfc_request: DVMRP adding downstream interface %A(%s)",
			  ifap->ifa_addr,
			  ifap->ifa_link->ifl_name));
	    }
	} IF_ADDR_END(ifap) ;
	    /*
	     * flag this mfc as needed by DVMRP
	     */
	BIT_SET(mfcp->mfc_proto, IPMULTI_BIT(IPMULTI_PROTO_DVMRP));

	dvmrp_set_mfc_timer(mfcp, dvmrp_default_mfctimeout);
	break;

    case EADDRINUSE:
	    /*
	     * Duplicate packet on incorrect interface.
	     */
	trace_tp(dvmrp_task,
		 TR_ROUTE,
		 0,
		 ("dvmrp_mfc_request: duplicate packet arrived on wrong interface for group %A source %A",
		  sockbuild_in(0, mfcp->mfc_group->group_key),
		  sockbuild_in(0, mfcp->mfc_src)));
	break;
    }
}



/*
 *	Process changes in the routing table.
 */
void
dvmrp_flash __PF2(tp, task *,
		  change_list, dvmrp_rt_list *)
{
    int changes = 0;
    dvmrp_target *tlp;
    
    /* Re-evaluate policy */

    TARGET_LIST(tlp, &dvmrp_targets) {
	if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
	    changes += dvmrp_policy(tp, tlp, change_list);
	}
    } TARGET_LIST_END(tlp, &dvmrp_targets) ;
    
    if (changes) {
	/* Schedule a flash update */

	dvmrp_need_flash(tp);
    }

    if (dvmrp_rtlist_root) {
	register dvmrp_rt_list *rthl = dvmrp_rtlist_root;
	while (rthl) {
	    register dvmrp_rt_list *Xrtln = rthl->rtl_next;
	    task_block_free(dvmrp_rtlist_block_index, (void_t) rthl);
	    rthl = Xrtln;
	}
	dvmrp_rtlist_root = (dvmrp_rt_list *) 0;
    }
}


/*
 *  Age out DVMRP routes
 */
static void
dvmrp_age __PF2(tip, task_timer *,
	        interval, time_t)
{
    time_t expire_to = time_sec - DVMRP_T_EXPIRE;
    time_t nexttime = time_sec + 1;

    if (expire_to > 0) {
	gw_entry *gwp;

	GW_LIST(dvmrp_gw_list, gwp) {
	    rt_entry *rt;

	    if (!gwp->gw_n_routes) {
		/* No routes for this gateway */

		if (!gwp->gw_import && !gwp->gw_export) {
		    /* No routes, delete this gateway */

		    /* XXX */
		}
		continue;
	    }

	    /* Age any routes for this gateway */
	    RTQ_LIST(&gwp->gw_rtq, rt) {
		if (rt->rt_time <= expire_to) {
		    /* This route has expired */
		
		    rt_delete(rt);
		} else {
		    /* This is the next route to expire */
		    if (rt->rt_time < nexttime) {
			nexttime = rt->rt_time;
		    }
		    break;
		}
	    } RTQ_LIST_END(&gwp->gw_rtq, rt) ;
	} GW_LIST_END(dvmrp_gw_list, gwp) ;
    }

    if (nexttime > time_sec) {
	/* No routes to expire */

	nexttime = time_sec;
    }

    task_timer_set(tip, (time_t) 0, nexttime + DVMRP_T_EXPIRE - time_sec);
}


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

    adv_cleanup(RTPROTO_DVMRP,
		(int *) 0,
		(int *) 0,
		dvmrp_gw_list,
		&dvmrp_int_policy,
		&dvmrp_import_list,
		&dvmrp_export_list);

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

/*
 *	re-init routine
 */
/*ARGSUSED*/
static void
dvmrp_reinit __PF1(tp, task *)
{
    int entries = 0;
    gw_entry *gwp;

    trace_set(dvmrp_task->task_trace, dvmrp_trace_options);

    GW_LIST(dvmrp_gw_list, gwp) {
	rt_entry *rt;

	RTQ_LIST(&gwp->gw_rtq, rt) {
	    /* Calculate preference of this route */
	    if (import(rt->rt_head->rth_dest,
		       rt->rt_head->rth_dest_mask,
		       dvmrp_import_list,
		       RT_IFAP(rt)->ifa_ps[tp->task_rtproto].ips_import,
		       rt->rt_gwp->gw_import,
		       &rt->rt_preference,
		       RT_IFAP(rt),
		       (void_t) 0)) {
		entries++;
	    } else {
		/* This route is now restricted */
		rt_delete(rt);
	    }
	} RTQ_LIST_END(&gwp->gw_rtq, rt) ;
    } GW_LIST_END(dvmrp_gw_list, gwp) ;

    /* Indicate a reconfig in process */
    BIT_SET(dvmrp_flags, DVMRPF_RECONFIG);
}


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

	/* Release the target list, bit assignments, and buffers */
    dvmrp_target_free_list(tp, &dvmrp_targets);

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

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

	    dvmrp_nbr_purge(ifap);

	    if (ifap->dvmrp_nbr_timer_timeout) {
		task_timer_delete((task_timer *) ifap->dvmrp_nbr_timer_timeout);
		ifap->dvmrp_nbr_timer_timeout = (void_t) 0;
	    }

	    if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
		krt_del_vif(ifap);
		if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST)) {
		    (void) igmp_group_drop(ifap, inaddr_dvmrp_group);
		}
	    }
	}
    } IF_ADDR_END(ifap) ;

	/* Clean up tracing */
    dvmrp_cleanup(tp);

    dvmrp_timer_probe = (task_timer *) 0;
    dvmrp_timer_update = (task_timer *) 0;
    dvmrp_timer_age = (task_timer *) 0;
    dvmrp_timer_flash = (task_timer *) 0;
    dvmrp_timer_prune = (task_timer *) 0;
    dvmrp_timer_mfc = (task_timer *) 0;

	/*
	 * After we are done with the socket,
	 * We tell IGMP so it can terminate
	 */
    krt_unregister_mfc( EADDRNOTAVAIL, dvmrp_mfc_request );
    igmp_unregister_group_change( dvmrp_group_change );
    igmp_disable_mrouting();

    igmp_unregister_recv( IGMP_PROTO_DVMRP );

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

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

    if (!ifap->dvmrp_if_params) {
	ifap->dvmrp_if_params = (dvmrp_ifparm *)
		task_block_alloc(dvmrp_ifparam_block_index);
    }

    BIT_RESET(ips->ips_state, IFPS_RESET);
    ips->ips_metric_in = ifap->ifa_metric + DVMRP_HOP;
    DVMRP_IF_THRESHOLD(ifap) = 1;
    DVMRP_IF_RATE_LIMIT(ifap) = 0;
}


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

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

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

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

		    break;

		case DVMRP_CONFIG_DISABLE:
		    BIT_SET(ips->ips_state, IFPS_NOIN);
		    BIT_SET(ips->ips_state, IFPS_NOOUT);
		    igmp_reset_ifproto(ifap, IPMULTI_PROTO_DVMRP);
		    break;

		case DVMRP_CONFIG_METRIC:
		    BIT_SET(ips->ips_state, IFPS_METRICIN);
		    ips->ips_metric_in = (metric_t) GA2S(cp->config_data);
		    break;

		case DVMRP_CONFIG_THRESHOLD:
		    DVMRP_IF_THRESHOLD(ifap) = (u_int32) GA2S(cp->config_data);
		    break;

		case DVMRP_CONFIG_RATELIMIT:
		    DVMRP_IF_RATE_LIMIT(ifap) = (u_int32) GA2S(cp->config_data);
		    break;

		}
	    }
	}

	config_resolv_free(list, DVMRP_CONFIG_MAX);
    }
}


/*
 *	Deal with an interface status change
 */
static void
dvmrp_ifachange __PF2(tp, task *,
		      ifap, if_addr *)
{
    int rtchanges = 0;
    int ifchanges = 0;
    dvmrp_target *tlp;
    gw_entry *gwp;
    struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];
    nbr_list *list, **listp = (nbr_list **) &ifap->dvmrp_if_nbr_list;
    rt_entry *rt;

    if (socktype(ifap->ifa_addr) != AF_INET) {
	return;
    }

    rt_open(tp);
    
    switch (ifap->ifa_change) {
    case IFC_NOCHANGE:
    case IFC_ADD:
	if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    dvmrp_control_set(tp, ifap);
	    if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK) ||
		!BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
		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 DVMRP,
		     * do so, otherwise, another multicast protocol has it
		     */
		if (igmp_set_ifproto(ifap, IPMULTI_PROTO_DVMRP) == TRUE) {

			/*
			 * Tunnel interfaces will not have an interface route
			 */
		    if (BIT_TEST(ifap->ifa_state, IFS_TUNNEL)) {
			BIT_SET(ifap->ifa_state_policy,
				(ifap->ifa_state ^ IFS_NOAGE) & IFS_NOAGE);
		    } else {
			rt = ifap->ifa_rt;
			if (!rt->rt_head->rth_dvmrp_active ||
			    rt->rt_metric < rt->rt_head->rth_dvmrp_active->rt_metric) {
			    rt->rt_head->rth_dvmrp_active = rt;

			    DVMRP_RTCHANGE_LIST_ADD(dvmrp_rtlist_root, rt);
			}

			TARGET_LIST(tlp, &dvmrp_targets) {
			    if (tlp->target_ifap != ifap
				&& BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
				BIT_RESET(tlp->target_flags, TARGETF_POLICY);
			    }
			} TARGET_LIST_END(tlp, &dvmrp_targets) ;
		    }

		    ifchanges++;

		    if (!*listp) {
			list = (nbr_list *) task_block_alloc(dvmrp_nbr_block_index);
			list->dr_forw = list;
			list->dr_back = list;
			list->tq_forw = list;
			list->tq_back = list;

			*listp = list;
		    }

		    if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
			krt_add_vif(ifap,
				    DVMRP_IF_THRESHOLD(ifap),
				    (u_int32) DVMRP_IF_RATE_LIMIT(ifap));

			if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST)) {
			    (void) igmp_group_add(ifap, inaddr_dvmrp_group);
			    igmp_enable_dr_status(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:

	GW_LIST(dvmrp_gw_list, gwp) {
	    RTQ_LIST(&gwp->gw_rtq, rt) {
		if (RT_IFAP(rt) == ifap) {
		    rt_delete(rt);
		    rtchanges++;
		}
	    } RTQ_LIST_END(&gwp->gw_rtq, rt) ;
	} GW_LIST_END(dvmrp_gw_list, gwp) ;

	ifchanges++;

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

	    krt_del_vif(ifap);

	    if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST)) {
		(void) igmp_group_drop(ifap, inaddr_dvmrp_group);
	    }
	}
	dvmrp_control_reset(tp, ifap);
	igmp_disable_dr_status(ifap);
	break;

    default:
	/* Something has changed */

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

		dvmrp_control_set(tp, ifap);
		if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK) ||
		    !BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
		    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 DVMRP,
			 * do so, otherwise, another multicast protocol has it
			 */
		    if (igmp_set_ifproto(ifap, IPMULTI_PROTO_DVMRP) == TRUE) {

			if (BIT_TEST(ifap->ifa_state, IFS_TUNNEL)) {
			    BIT_SET(ifap->ifa_state_policy,
				    (ifap->ifa_state ^ IFS_NOAGE) & IFS_NOAGE);
			} else {
			    rt = ifap->ifa_rt;
			    if (!rt->rt_head->rth_dvmrp_active ||
				rt->rt_metric < rt->rt_head->rth_dvmrp_active->rt_metric) {
				rt->rt_head->rth_dvmrp_active = rt;

				DVMRP_RTCHANGE_LIST_ADD(dvmrp_rtlist_root, rt);
			    }

			    TARGET_LIST(tlp, &dvmrp_targets) {
				if (tlp->target_ifap != ifap
				    && BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
				    BIT_RESET(tlp->target_flags, TARGETF_POLICY);
				}
			    } TARGET_LIST_END(tlp, &dvmrp_targets) ;
			}

			ifchanges++;

			if (!*listp) {
			    list = (nbr_list *) task_block_alloc(dvmrp_nbr_block_index);
			    list->dr_forw = list;
			    list->dr_back = list;
			    list->tq_forw = list;
			    list->tq_back = list;

			    *listp = list;
			}

			if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
			    krt_add_vif(ifap,
					DVMRP_IF_THRESHOLD(ifap),
					(u_int32) DVMRP_IF_RATE_LIMIT(ifap));

			    if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST)) {
				(void) igmp_group_add(ifap, inaddr_dvmrp_group);
				igmp_enable_dr_status(ifap);
			    }
			}
		    } else {
			BIT_SET(ips->ips_state, IFPS_NOIN);
			BIT_SET(ips->ips_state, IFPS_NOOUT);
		    }
		}
	    } else {
		/* UP to DOWN transition */

		GW_LIST(dvmrp_gw_list, gwp) {
		    RTQ_LIST(&gwp->gw_rtq, rt) {
			if (RT_IFAP(rt) == ifap) {
			    rt_delete(rt);
			    rtchanges++;
			}
		    } RTQ_LIST_END(&gwp->gw_rtq, rt) ;
		} GW_LIST_END(dvmrp_gw_list, gwp) ;

		ifchanges++;

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

		    krt_del_vif(ifap);

		    if (BIT_TEST(ifap->ifa_state, IFS_BROADCAST)) {
			(void) igmp_group_drop(ifap, inaddr_dvmrp_group);
		    }
		}
		dvmrp_control_reset(tp, ifap);
		igmp_disable_dr_status(ifap);
	    }
	}
	if (BIT_TEST(ifap->ifa_change, IFC_METRIC)) {

	    /* The metric has changed, reset the POLL bit */
	    /* on any targets using this interface so we'll */
	    /* send another POLL */

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

		ips->ips_metric_in = ifap->ifa_metric + DVMRP_HOP;

		TARGET_LIST(tlp, &dvmrp_targets) {
		    if (tlp->target_ifap == ifap) {
			BIT_RESET(tlp->target_flags, DVMRPTF_POLL);
		    }
		} TARGET_LIST_END(tlp, &dvmrp_targets) ;
	    }
	}
	if (BIT_TEST(ifap->ifa_change, IFC_NETMASK)) {
	    /* The netmask has changed, delete any routes that */
	    /* point at gateways that are no longer reachable */

	    GW_LIST(dvmrp_gw_list, gwp) {
		RTQ_LIST(&gwp->gw_rtq, rt) {
		    if (RT_IFAP(rt) == ifap
			&& (if_withdstaddr(rt->rt_gwp->gw_addr) != ifap
			    || BIT_TEST(rt->rt_state, RTS_IFSUBNETMASK))) {
			/* Interface for this route has changed or we derived the subnet mask */
		    
			rt_delete(rt);
			rtchanges++;
		    }
		} RTQ_LIST_END(&gwp->gw_rtq, rt) ;
	    } GW_LIST_END(dvmrp_gw_list, gwp) ;

	    TARGET_LIST(tlp, &dvmrp_targets) {
		if (tlp->target_ifap == ifap
		    && BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
		    /* Some subnet masks may have been derrived, indicate that policy needs to be rerun */

		    BIT_RESET(tlp->target_flags, TARGETF_POLICY);
		}
	    } TARGET_LIST_END(tlp, &dvmrp_targets) ;
	}
	if (BIT_TEST(ifap->ifa_change, IFC_BROADCAST)) {
	    /* The broadcast address has changed.  Since target_dst */
	    /* Is a pointer to the pointer to the broadcast address */
	    /* the change is automatic.  But we should reset the POLL */
	    /* bit so we'll POLL this new address in case there are */
	    /* routers we did not yet hear from */

	    tlp = dvmrp_target_locate(&dvmrp_targets, ifap, (gw_entry *) 0);

	    if (tlp) {
		BIT_RESET(tlp->target_flags, DVMRPTF_POLL);
	    }
	}
	    
	/* A LOCALADDR change will take effect when the peers notice that the */
	/* 	old address is no longer sending */
	/* An MTU change will take effece on output */
	/* A SEL change is not possible in IP */
	break;
    }

    /* Update target list */
    if (ifchanges) {
	dvmrp_target_list(tp);
    }
    rt_close(tp, (gw_entry *) 0, rtchanges, NULL);
}

static void
dvmrp_int_dump __PF2(fd, FILE *,
		    list, config_entry *)
{
    register config_entry *cp;

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

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

	case DVMRP_CONFIG_METRIC:
	    (void) fprintf(fd, " metric %u",
			   (metric_t) GA2S(cp->config_data));
	    break;

	default:
	    assert(FALSE);
	    break;
	}
    } CONFIG_LIST_END(cp, list) ;
}

static void
dvmrp_dump __PF2(tp, task *,
	         fd, FILE *)
{
    if_addr	*ifap;

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

	if (!BIT_TEST(ips->ips_state, IFPS_NOIN)) {
	    (void) fprintf(fd, "\tInterface %A(%s):  Leaf Status: %s\n",
		    ifap->ifa_addr,
		    ifap->ifa_link->ifl_name,
		    BIT_TEST(ips->ips_state,IFPS_NOT_LEAF) ? "False":"True");
	    if (list && BIT_TEST(ifap->ifa_state, IFS_BROADCAST)) {
		(void) fprintf(fd, "\t\t DVMRP Nbr        Last Refresh\n");

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

    dvmrp_target_dump(fd, &dvmrp_targets, dvmrp_target_bits);

    if (dvmrp_gw_list) {
	(void) fprintf(fd, "\tActive gateways:\n");
	gw_dump(fd,
		"\t\t",
		dvmrp_gw_list,
		tp->task_rtproto);
	(void) fprintf(fd, "\n");
    }
    if (dvmrp_int_policy) {
	(void) fprintf(fd, "\tInterface policy:\n");
	control_interface_dump(fd, 2, dvmrp_int_policy, dvmrp_int_dump);
    }

    control_import_dump(fd, 1, RTPROTO_DVMRP, dvmrp_import_list, dvmrp_gw_list);
    control_export_dump(fd, 1, RTPROTO_DVMRP, dvmrp_export_list, dvmrp_gw_list);
    (void) fprintf(fd, "\n");
}


/*
 *	Initialize static variables
 */
void
dvmrp_var_init()
{
    dvmrp_default_metric = DVMRP_UNREACHABLE;
    dvmrp_default_prunetimeout = DVMRP_PRUNE_TIMEOUT;
    dvmrp_default_graftacktimeout = DVMRP_GRAFT_RETRANS_TIMEOUT;
    dvmrp_default_mfctimeout = DVMRP_CACHE_TIMEOUT;

    BIT_RESET(dvmrp_config_status, DVMRP_ENABLED);

    if (!inaddr_dvmrp_group)
	inaddr_dvmrp_group=sockdup(sockbuild_in(0, htonl(INADDR_DVMRP_GROUP)));

    generation_id = htonl(utime_current.ut_sec + utime_boot.ut_sec);
    dvmrp_rtlist_root = (dvmrp_rt_list *) 0;
}

int
dvmrp_parse_tunnel  __PF4(remote, sockaddr_un *,
			  local, if_addr_entry *,
			  cp, config_entry *,
			  err_msg, char *)
{
    if_addr *ifap = if_withlcladdr(local->ifae_addr, FALSE);
    if (!ifap) {
	(void) sprintf(err_msg,
		       "tunnel %A has bad local address",
		       remote);
	return TRUE;
    }
    if (!BIT_TEST(task_state, TASKS_TEST)) {
	u_int32 threshold = 1; 
	u_int32 limit = DVMRP_TUNNEL_RATELIMIT;
	krt_add_tunnel(ifap, remote, threshold, limit);
    }
    return FALSE ;
}

/*
 * initialize DVMRP socket and DVMRP task
 */

/*ARGSUSED*/
void
dvmrp_init()
{
    if (BIT_TEST(dvmrp_current_status, DVMRP_ENABLED)) {
	if (BIT_TEST(dvmrp_config_status, DVMRP_ENABLED)) {
	/*
	 * was enabled before and still is now
	 *  - reconfig handled above
	 */

	    trace_inherit_global(dvmrp_trace_options, dvmrp_trace_types, (flag_t) 0);
	} else {
	/*
	 * was enabled before but isn't anymore
	 *  - cleanup and terminate dvmrp task
	 */

	    if (dvmrp_task) {
		dvmrp_terminate(dvmrp_task);

		dvmrp_task = (task *) 0;
	    }
	}
    } else {
	if (BIT_TEST(dvmrp_config_status, DVMRP_ENABLED)) {
	/*
	 * wasn't enabled before but is now
	 *  - start dvmrp task
	 */

	    trace_inherit_global(dvmrp_trace_options, dvmrp_trace_types, (flag_t) 0);
	    if (!dvmrp_task) {
		dvmrp_timer_probe = (task_timer *) 0;
		dvmrp_timer_update = (task_timer *) 0;
		dvmrp_timer_age = (task_timer *) 0;
		dvmrp_timer_flash = (task_timer *) 0;
		dvmrp_timer_prune = (task_timer *) 0;
		dvmrp_timer_mfc = (task_timer *) 0;
		dvmrp_task = task_alloc("DVMRP",
				        TASKPRI_PROTO,
				        dvmrp_trace_options);

		dvmrp_task->task_rtproto = RTPROTO_DVMRP;
		dvmrp_task->task_rtfamily = AF_INET;
		task_set_cleanup(dvmrp_task, dvmrp_cleanup);
		task_set_dump(dvmrp_task, dvmrp_dump);
		task_set_ifachange(dvmrp_task, dvmrp_ifachange);
		task_set_reinit(dvmrp_task, dvmrp_reinit);
		task_set_terminate(dvmrp_task, dvmrp_terminate);


		if (!task_create(dvmrp_task)) {
		    task_quit(EINVAL);
		}

		igmp_register_recv( IGMP_PROTO_DVMRP, dvmrp_recv );
		igmp_register_group_change( dvmrp_group_change );
		krt_register_mfc( EADDRNOTAVAIL, dvmrp_mfc_request );
		igmp_enable_mrouting();

		dvmrp_nbr_block_index =
			task_block_init(sizeof (nbr_list),
			"dvmrp_nbr_list");

		dvmrp_prune_block_index =
			task_block_init(sizeof (prune_list),
			"dvmrp_prune_list");

		dvmrp_mfc_block_index =
			task_block_init(sizeof (mfc_list),
			"dvmrp_mfc_list");

		dvmrp_ifparam_block_index =
			task_block_init(sizeof(dvmrp_ifparm),
			"dvmrp_ifparm");

		dvmrp_rtlist_block_index =
			task_block_init(sizeof(dvmrp_rt_list),
			"dvmrp_rt_list");

		dvmrp_graft_block_index =
			task_block_init(sizeof(graft_list),
			"dvmrp_graft_list");

		dvmrp_ifnbr_block_index =
			task_block_init(sizeof(ifnbr),
			"dvmrp_ifnbr");

	    }
    	}
    }
    dvmrp_current_status = dvmrp_config_status;
}

/*
 * dvmrp_rt_add - add a dvmrp route to the patricia tree
 */
rt_entry *
dvmrp_rt_add __PF1(rtparms, rt_parms *)
{
    rt_entry *rt = rt_add(rtparms);

	/*
	 * reset the active route if necessary
	 * and add to the change list
	 */
    if (!rt->rt_head->rth_dvmrp_active ||
	rt->rt_metric < rt->rt_head->rth_dvmrp_active->rt_metric) {
	rt->rt_head->rth_dvmrp_active = rt;

	DVMRP_RTCHANGE_LIST_ADD(dvmrp_rtlist_root, rt);
    }
    return rt;
}
