/*
 *  from rdisc.c,v 1.5.2.2 1995/01/23 12:41:39 jch Exp
 */

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


#include "include.h"
#include <netinet/ip6_icmp.h>
#include "icmpv6.h"
#include "ndp.h"
#include "inet6.h"

#define	NDP_SERVER
#define	NDP_CLIENT

/* Common data */
static task *ndp_task = (task *) 0;

int doing_ndp;
adv_entry *ndp_interface_policy = (adv_entry *) 0; /* List of interface policy */

trace *ndp_trace_options = { 0 };   /* Trace flags */
const bits ndp_trace_types[] = {
  { 0, NULL }
};

/**/

/* Server support */

#ifdef	NDP_SERVER

#define	NDP_NAME_LENGTH	64

typedef struct _ndp_entry {
    struct _ndp_entry *nde_forw, *nde_back;

    struct _ndp_group *nde_group;	/* Up pointer to the group */
    if_addr *nde_ifa;			/* Interface we belong to (except on deletes) */

    struct in6_addr nde_prefix;		/* Prefix to announce */
    byte nde_prefixlen;			/* Prefix length */
    byte nde_flags;			/* Prefix flags */
    time_t nde_invlife;			/* Prefix invalidation lifetime */
    time_t nde_deplife;			/* Prefix deprecation lifetime */
} ndp_entry;


typedef struct _ndp_response {
    struct _ndp_response *rdr_forw, *rdr_back;

    struct _ndp_group *rdr_group;		/* Group we belong to */
    sockaddr_un *rdr_dest;			/* Address to respond to */
    task_timer *rdr_timer;			/* Pointer to our timer */
#define	NDP_TIMER_RESPONSE_NAME	"Response"
    char rdr_name[sizeof NDP_TIMER_RESPONSE_NAME + NDP_NAME_LENGTH];
} ndp_response;


typedef struct _ndp_group {
    struct _ndp_group *ndg_forw, *ndg_back;
    
    if_link *ndg_link;			/* Physical interface we belong to */
    time_t ndg_adv_max;			/* Max advertisement interval */
    time_t ndg_adv_min;			/* Min advertisement interval */
    time_t ndg_lifetime;		/* Lifetime */
    byte ndg_maxhlim;			/* Max hop limit */
    byte ndg_flags;			/* Flags */

    if_addr *ndg_primary;		/* Primary address */

    task_timer *ndg_timer;		/* Timer for this group */
    task_job *ndg_job;			/* Foreground job for deletion announcments */

    struct _qelement ndg_prefixes;	/* Queue of active prefixes */
    struct _qelement ndg_deletes;	/* Queue of pending deletions */
    struct _qelement ndg_responses;	/* Queue of pending responses */

    u_int ndg_init;			/* Counts down to zero for each advertisement during init state */
    char ndg_name[NDP_NAME_LENGTH];
} ndp_group;

#define	ifa_nd_entry		ifa_ps[RTPROTO_NDP].ips_datas[0]


typedef struct _ndp_link {
    struct _ndp_link *ndl_forw, *ndl_back;

    if_link *ndl_link;		/* Physical interface we belong to */

    struct _qelement ndl_groups;	/* List of groups */
} ndp_link;

#define	ifl_nd_link	ifl_ps[RTPROTO_NDP]


#define	ND_LINKS_FIRST(ndlp)	(((ndlp) = (ndp_link *) ndp_links.q_forw) != (ndp_link *) &ndp_links)
#define	ND_NOLINKS()	(ndp_links.q_forw == &ndp_links)
#define	ND_LINKS(ndlp)	for ((ndlp) = (ndp_link *) ndp_links.q_forw; \
			     (ndlp) != (ndp_link *) &ndp_links; \
			     (ndlp) = (ndlp)->ndl_forw)
#define	ND_LINKS_END(ndlp)


#define	NDL_GROUPS_FIRST(ndlp, ndgp) \
	(((ndgp) = (ndp_group *) (ndlp)->ndl_groups.q_forw) != (ndp_group *) &(ndlp)->ndl_groups)
#define	NDL_NOGROUPS(ndlp)	((ndlp)->ndl_groups.q_forw == &(ndlp)->ndl_groups)
#define	NDL_GROUPS(ndlp, ndgp)	for ((ndgp) = (ndp_group *) (ndlp)->ndl_groups.q_forw; \
				     (ndgp) != (ndp_group *) &(ndlp)->ndl_groups; \
				     (ndgp) = (ndgp)->ndg_forw)
#define	NDL_GROUPS_END(ndlp, ndgp)


#define	ND_ENTRIES_FIRST(head, ndep)	(((ndep) = (ndp_entry *) (head)->q_forw) != (ndp_entry *) (head))
#define	ND_NOENTRIES(head)	((head)->q_forw == (head))
#define	ND_ENTRIES(head, ndep)	for ((ndep) = (ndp_entry *) (head)->q_forw; \
				     (ndep) != (ndp_entry *) (head); \
				     (ndep) = (ndep)->nde_forw)
#define	ND_ENTRIES_END(head, ndep)

				 
#define	NDG_RESPONSES_FIRST(ndgp, ndep) \
	(((rdrp) = (ndp_response *) (ndgp)->ndg_responses.q_forw) != (ndp_response *) &(ndgp)->ndg_responses)
#define	NDG_NORESPONSES(ndgp)	((ndgp)->ndg_responses.q_forw == &(ndgp)->ndg_responses)
#define	NDG_RESPONSES(ndgp, rdrp) for ((rdrp) = (ndp_response *) (ndgp)->ndg_responses.q_forw; \
				       (rdrp) != (ndp_response *) &(ndgp)->ndg_responses; \
				       (rdrp) = (rdrp)->rdr_forw)
#define	NDG_RESPONSES_END(ndgp, rdrp)

				 
static struct _qelement ndp_links = { &ndp_links, &ndp_links };
static block_t ndp_link_index;
static block_t ndp_group_index;
static block_t ndp_entry_index;
static block_t ndp_response_index;

adv_entry *ndp_server_address_policy = (adv_entry *) 0;	/* List of address policy */

/**/
/*
 *  Send an advertisement to a group
 */
static void
ndp_server_send __PF5(ndgp, ndp_group *,
		      head, qelement,
		      ifap, if_addr *,
		      dest, sockaddr_un *,
		      lifetime, time_t)
{
    sockaddr_un *src = ifap->ifa_addr_local;
    struct icmpv6 *icmp = (struct icmpv6 *) task_send_buffer; 
    register struct ndx6_pref *ndp;
    register ndp_entry *ndep;

    /* If not unicast */
    if (!dest) {
	dest = inet6_addr_allnodes;
    }
    
    /* Init our fields in the packet */
    icmp->icmp6_type = ICMP6_ADVERTISEMENT_RT;
    icmp->icmp6_code = 0;
    icmp->icmp6_mhlim = ndgp->ndg_maxhlim;
    icmp->icmp6_aflg = ndgp->ndg_flags;
    icmp->icmp6_life = ndgp->ndg_lifetime;
    ndp = (struct ndx6_pref *)((byte *) icmp + ICMP6_MINLEN);

    ND_ENTRIES(head, ndep) {
	if ((size_t) ((byte *) ndp - (byte *) icmp) > ifap->ifa_mtu) {
	    /* Packet is full */

	    /* Send the packet */
	    icmpv6_send(icmp,
			(size_t) ((byte *) ndp - (byte *) icmp),
			src,
			dest,
			ifap,
			MSG_DONTROUTE);

	    /* Reset fill pointer */
	    ndp = (struct ndx6_pref *)((byte *) icmp + ICMP6_MINLEN);
	}

	/* Add this prefix */
	bzero((caddr_t)ndp, sizeof(struct ndx6_pref));
        ndp->pref_ext = NDX6_PREF_INFO;
	ndp->pref_len = (sizeof(struct ndx6_pref) >> 3) - 1;
	ndp->pref_plen = ndep->nde_prefixlen;
	ndp->pref_flg = ndep->nde_flags;
	ndp->pref_ilife = htonl(ndep->nde_invlife);
	ndp->pref_dlife = htonl(ndep->nde_deplife);
	COPY_ADDR6(ndep->nde_prefix, ndp->pref_pref);
	ndp++;
    } ND_ENTRIES_END(head, ndep) ;

    /* Send the packet */
    icmpv6_send(icmp,
		(size_t) ((byte *) ndp - (byte *) icmp),
		src,
		dest,
		ifap,
		MSG_DONTROUTE);

}


/*
 *  When the timer fires, send a soliciation.
 *  New interval is calculated by ndp_send_advert().
 */
static void
ndp_server_timer __PF2(tip, task_timer *,
		       real_interval, time_t)
{
    time_t max, min, offset;
    ndp_group *ndgp = (ndp_group *) tip->task_timer_data;

    /* Send an advertisement */
    ndp_server_send(ndgp,
		    &ndgp->ndg_prefixes,
		    ndgp->ndg_primary,
		    (sockaddr_un *) 0,
		    ndgp->ndg_lifetime);

    /* Recalculate timer */
    if (ndgp->ndg_init
	&& --ndgp->ndg_init) {
	/* Still in init mode */

	min = MIN(NDP_MAX_INITIAL_ADVERT_INTERVAL, ndgp->ndg_adv_min);
	max = MIN(NDP_MAX_INITIAL_ADVERT_INTERVAL, ndgp->ndg_adv_max);
    } else {
	min = ndgp->ndg_adv_min;
	max = ndgp->ndg_adv_max;
    }

    offset = min + grand((u_int32) (max - min));
    task_timer_set(ndgp->ndg_timer,
		   (time_t) 0,
		   offset);
    trace_tp(ndgp->ndg_timer->task_timer_task,
	     TR_STATE,
	     0,
	     ("ndp_server_timer: group %s timer set to %#T",
	      ndgp->ndg_name,
	      offset));
}


/*
 * Send a response to a query.
 */
static void
ndp_server_timer_response __PF2(tip, task_timer *,
				interval, time_t)
{
    ndp_response *rdrp = (ndp_response *) tip->task_timer_data;
    ndp_group *ndgp = rdrp->rdr_group;

    /* Send an advertisement */
    ndp_server_send(ndgp,
		    &ndgp->ndg_prefixes,
		    ndgp->ndg_primary,
		    rdrp->rdr_dest,
		    ndgp->ndg_lifetime);

    sockfree(rdrp->rdr_dest);
    REMQUE(rdrp);
    task_block_free(ndp_response_index, (void_t) rdrp);
}


/*
 * Send an indication of a deletion and delete the interface from
 * the group.  Delete the group and link if necessary.
 */
static void
ndp_server_delete __PF2(tp, task *,
			ndgp, ndp_group *)
{
    register ndp_entry *ndep;
    register if_addr *ifap = ndgp->ndg_primary;

    /* Send a deletion message if we can find an interface */
    if (!ifap) {
	/* Attempt to find an ifap */

	ND_ENTRIES(&ndgp->ndg_deletes, ndep) {
	    if (ndep->nde_ifa
		&& BIT_TEST(ndep->nde_ifa->ifa_state, IFS_UP)) {
		/* Found one */
		
		ifap = ndep->nde_ifa;
		break;
	    }
	} ND_ENTRIES_END(&ndgp->ndg_deletes, ndep) ;
    }
    if (ifap) {
	ndp_server_send(ndgp,
			&ndgp->ndg_deletes,
			ifap,
			(sockaddr_un *) 0,
			(time_t) 0);
    }


    /* Process deletions */
    while ((ndep = (ndp_entry *) ndgp->ndg_deletes.q_forw) != (ndp_entry *) &ndgp->ndg_deletes) {

	/* Remove from the list */
	REMQUE(ndep);

	if (!ndgp->ndg_primary) {
	    /* Remove ourselves from the all routers group */

	    task_set_option(icmpv6_task, 
			    TASKOPTION_GROUP_DROP,
			    ndep->nde_ifa,
			    inet6_addr_allrouters);
	}

	/* Release the interface */
	if (ndep->nde_ifa) {
	    ndep->nde_ifa->ifa_nd_entry = (void_t) 0;
	    IFA_FREE(ndep->nde_ifa);
	}
	task_block_free(ndp_entry_index, (void_t) ndep);
    }

    if (ND_NOENTRIES(&ndgp->ndg_prefixes)) {
	ndp_link *ndlp = ndgp->ndg_link->ifl_nd_link;
	ndp_response *rdrp;
	    
	/* Delete group */

	REMQUE(ndgp);

	if (ndgp->ndg_timer) {
	    task_timer_delete(ndgp->ndg_timer);
	}

	/* Delete any queued responses */
	while (NDG_RESPONSES_FIRST(ndgp, rdrp)) {
	    REMQUE(rdrp);
	    task_timer_delete(rdrp->rdr_timer);
	    sockfree(rdrp->rdr_dest);
	    task_block_free(ndp_response_index, (void_t) rdrp);
	}
	
	task_block_free(ndp_group_index, (void_t) ndgp);
	ndgp = (ndp_group *) 0;

	if (NDL_NOGROUPS(ndlp)) {
	    /* Delete interface */

	    REMQUE(ndlp);
	    ndlp->ndl_link->ifl_nd_link = (void_t) 0;
	    task_block_free(ndp_link_index, (void_t) ndlp);
	}
    }
}


static void
ndp_server_delete_job __PF1(jp, task_job *)
{
    register ndp_group *ndgp = (ndp_group *) jp->task_job_data;

    ndgp->ndg_job = (task_job *) 0;
    ndp_server_delete(jp->task_job_task, ndgp);
}


/**/

static int
ndp_server_group_policy __PF2(tp, task *,
			      ndgp, register ndp_group *)
{
    time_t max, min, lifetime;
    config_entry **ifl_list = config_resolv_ifl(ndp_interface_policy,
						ndgp->ndg_link,
						NDP_CONFIG_MAX);

    max = NDP_MAX_ADINTERVAL_DEFAULT;
    min = (time_t) 0;
    lifetime = (time_t) 0;
		
    if (ifl_list) {
	int type = NDP_CONFIG_MAX;
	config_entry *cp;

	/* Fill in the parameters */
	while (--type) {
	    if ((cp = ifl_list[type])) {
		switch (type) {
		case NDP_CONFIG_MAXADVINT:
		    max = (time_t) GA2S(cp->config_data);
		    break;
	  
		case NDP_CONFIG_MINADVINT:
		    min = (time_t) GA2S(cp->config_data);
		    break;
	  
		case NDP_CONFIG_LIFETIME:
		    lifetime = (time_t) GA2S(cp->config_data);
		    break;
		}
	    }
	}

	config_resolv_free(ifl_list, NDP_CONFIG_MAX);
    }

    if (!min) {
	/* Default is 3/4 * max */

	min = (max + (max << 1)) >> 2;
    } else if (min > max) {
	/* Out of range, use max valid value */
	
	min = max;
    }
    
    if (!lifetime) {
	/* Default is 3 * max */

	lifetime = (max + (max << 1));	/* 3 * max */
    } else if (lifetime < max) {
	/* Out of range, use min valid value */
	
	lifetime = max;
    }

    if (max != ndgp->ndg_adv_max
	|| min != ndgp->ndg_adv_min
	|| lifetime != ndgp->ndg_lifetime) {
	/* Something has changed */

	ndgp->ndg_adv_max = max;
	ndgp->ndg_adv_min = min;
	ndgp->ndg_lifetime = lifetime;

	trace_tp(tp,
		 TR_STATE, 
		 0, 
		 ("ndp_server_group_policy: group %s MaxAdvInt %#T  MinAdvInt = %#T  Lifetime %#T",
		  ndgp->ndg_name,
		  ndgp->ndg_adv_max,
		  ndgp->ndg_adv_min,
		  ndgp->ndg_lifetime));

	return TRUE;
    }    

    return FALSE;
}


static void
ndp_server_ifachange __PF2(tp, task *,
			     ifap, if_addr *)
{
    int add = 0;
    int change = 0;
    int delete = 0;
    int policy = 0;
    metric_t preference = NDP_PREFERENCE_DEFAULT;
    sockaddr_un *group = (sockaddr_un *) 0;
    register ndp_entry *ndep = ifap->ifa_nd_entry;
    register ndp_group *ndgp = ndep ? ndep->nde_group : (ndp_group *) 0;

    if (!BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
	/* We only support interfaces where we can reach all members */

	return;
    }
  
    switch (ifap->ifa_change) {
    case IFC_NOCHANGE:
    case IFC_ADD:
	if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    policy++;
	}
	break;
    
    case IFC_DELETE:
	return;
    
    case IFC_DELETE|IFC_UPDOWN:
	if (!ndep) {
	    return;
	}
	delete = 2;
	break;
    
    default:
	/* Something has changed */
  
	if (BIT_TEST(ifap->ifa_change, IFC_UPDOWN)) {
	    if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
		/* Transition to UP */

		policy++;
	    } else {
		/* Transition to DOWN */
	
		if (!ndep) {
		    return;
		}
		delete = 2;
	    }
	    break;
	}
	/* METRIC, NETMASK, SEL - Don't care */
	/* MTU - will take effect on output */

	if (BIT_TEST(ifap->ifa_change, IFC_BROADCAST|IFC_ADDR)) {
	    /* These changes are handled by policy */

	    policy++;
	}
    }

    /* Recalculate parameters */
    if (policy) {
	config_entry **ifa_list = config_resolv_ifa(ndp_server_address_policy,
						    ifap,
						    NDP_CONFIG_MAX);
	/* Run policy */

	if (ifa_list) {
	    int type = NDP_IFA_CONFIG_MAX;
	    config_entry *cp;
    
	    /* Fill in the parameters */  
	    while (--type) {
		if ((cp = ifa_list[type])) {
		    switch (type) {
		    case NDP_CONFIG_IFA_IGNORE:
			if (GA2S(cp->config_data)) {
			    delete = 2;
			    trace_tp(tp,
				     TR_STATE,
				     0, 
				     ("ndp_ifa_change: Ignoring address %A on %s due to policy",
				      ifap->ifa_addr_local,
				      ifap->ifa_link->ifl_name));
			}
			break;

		    case NDP_CONFIG_IFA_PREFERENCE:
			preference = (metric_t) GA2S(cp->config_data);
			trace_tp(tp,
				 TR_STATE, 
				 0, 
				 ("ndp_ifa_change: Preference for address %A(%s) set to %d",
				  ifap->ifa_addr_local,
				  ifap->ifa_link->ifl_name,
				  preference));
			break;
		    }
		}

	    }
	    config_resolv_free(ifa_list, NDP_IFA_CONFIG_MAX);
	}

	if (delete) {
	    if (!ndep) {
		/* Can't delete if we don't have one */
		delete = 0;
	    }
	} else {
	    /* This is interface is valid, check for only changes */
	    
	    if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)
		&& (task_set_option(icmpv6_task,
				    TASKOPTION_GROUP_ADD,
				    ifap,
				    inet6_addr_allrouters) == 0
		    || errno == EADDRINUSE)) {
		/* This inteface supports multicast */
		
		group = inet6_addr_allnodes;
	    } else {
		trace_log_tp(tp,
			     0,
			     LOG_WARNING, 
			     ("ndp_server_ifachange: Ignoring interface %A on %s - multicast not available",
			      ifap->ifa_addr,
			      ifap->ifa_link->ifl_name));
	    }

	    /* Figure out what (if anything) changed */
	    if (!ndgp) {
		/* No existing group */
	    
		add++;
	    } else if (!SAME_ADDR6(sock2in6(ifap->ifa_addr_local), ndep->nde_prefix)) {
		/* We need to forget about the old entry */
		/* and create a new one */
		delete = 2;
		add++;
	    }
	}
    }

    if (delete) {
	/* Remove it */

	/* Remove from announce queue and add to the end of the delete queue */
	REMQUE(ndep);

	if (delete > 1) {
	    INSQUE(ndep, ndgp->ndg_deletes.q_back);

	    /* Save the interface pointer in case this interface comes back up */
	    if (add) {
		/* Except if we have been told not to */
	    
		IFA_FREE(ifap);
		ndep->nde_ifa = (if_addr *) 0;
	    }

	    /* forget about this entry */
	    ndep = (ndp_entry *) 0;
	    ifap->ifa_nd_entry = (void_t) 0;
	}

	if (ifap == ndgp->ndg_primary) {
	    /* Choose a new primary address */
	    
	    /* Free primary address */
	    IFA_FREE(ifap);

	    /* See if there is a new one */
	    if (ND_NOENTRIES(&ndgp->ndg_prefixes)) {
		ndgp->ndg_primary = (if_addr *) 0;
	    } else {
		IFA_ALLOC(ndgp->ndg_primary = ((ndp_entry *) ndgp->ndg_prefixes.q_forw)->nde_ifa);
	    }
	}

	/* If there are no entries for the group, delete the timer */
	if (ndgp->ndg_timer
	    && ND_NOENTRIES(&ndgp->ndg_prefixes)) {
	    ndp_response *rdrp;

	    task_timer_delete(ndgp->ndg_timer);
	    ndgp->ndg_timer = (task_timer *) 0;

	    /* Delete any queued responses */
	    while (NDG_RESPONSES_FIRST(ndgp, rdrp)) {
		REMQUE(rdrp);
		task_timer_delete(rdrp->rdr_timer);
		sockfree(rdrp->rdr_dest);
		task_block_free(ndp_response_index, (void_t) rdrp);
	    }
	}

	/* Create a job to process the deletion if necesary */
	if (!ndgp->ndg_job) {
	    ndgp->ndg_job = task_job_create(tp,
					    TASK_JOB_FG,
					    ndgp->ndg_name,
					    ndp_server_delete_job,
					    (void_t) ndgp);
	}

	ndgp = (ndp_group *) 0;
    }

    if (add) {
	register ndp_link *ndlp = ifap->ifa_link->ifl_nd_link;

	/* Find the right group */

	if (!ndlp) {
	    /* Create a link */

	    ndlp = (ndp_link *) task_block_alloc(ndp_link_index);
	    ifap->ifa_link->ifl_nd_link = ndlp;

	    ndlp->ndl_link = ifap->ifa_link;
	    ndlp->ndl_groups.q_forw = ndlp->ndl_groups.q_back = &ndlp->ndl_groups;

	    /* Add to our list of links */
	    INSQUE(ndlp, ndp_links.q_back);
	}

	/* Create a group */
	ndgp = (ndp_group *) task_block_alloc(ndp_group_index);
	ndgp->ndg_link = ifap->ifa_link;
	strcpy(ndgp->ndg_name, ndgp->ndg_link->ifl_name);
	ndgp->ndg_prefixes.q_forw = ndgp->ndg_prefixes.q_back = &ndgp->ndg_prefixes;
	ndgp->ndg_deletes.q_forw = ndgp->ndg_deletes.q_back = &ndgp->ndg_deletes;
	ndgp->ndg_responses.q_forw = ndgp->ndg_responses.q_back = &ndgp->ndg_responses;
	(void) ndp_server_group_policy(tp, ndgp) ;

	/* Insert on the end of the list */
	INSQUE(ndgp, ndlp->ndl_groups.q_forw->q_back);
    found_group:;

	/* Insert on address list */
	/* XXX - Should this be done in some order? */
	/* XXX - What about duplicates on a P2P interface? */
	if (!ndep) {
	    ndep = (ndp_entry *) task_block_alloc(ndp_entry_index);
	    COPY_ADDR6(sock2in6(ifap->ifa_addr_local), ndep->nde_prefix);
	}
	if (!ndep->nde_ifa) {
	    IFA_ALLOC(ndep->nde_ifa = ifap);
	}
	ndep->nde_group = ndgp;
	INSQUE(ndep, ndgp->ndg_prefixes.q_back);

	/* Link address to group and indicate it's new */
	ifap->ifa_nd_entry = ndep;

	/* If there is no primary address, we are it */
	if (!ndgp->ndg_primary) {
	    IFA_ALLOC(ndgp->ndg_primary = ifap);
	}
	
	/* Create or reset timer */
	if (ndgp->ndg_timer) {
	    /* Force a quick update, then go back to normal */

	    change++;
	} else {
	    /* Create a new timer */
	    
	    ndgp->ndg_init = NDP_MAX_INITIAL_ADVERTISEMENTS;
	    ndgp->ndg_timer = task_timer_create(tp, 
						ndgp->ndg_name,
						(flag_t) 0,
						(time_t) 0,
						NDP_MAX_RESPONSE_DELAY,
						ndp_server_timer,
						(void_t) ndgp);
	}
    }

    if (change) {
	/* Just a change, force an update */
	
	ndgp->ndg_init = MAX(ndgp->ndg_init, 1);
	task_timer_set(ndgp->ndg_timer,
		       (time_t) 0,
		       NDP_MAX_RESPONSE_DELAY);
    }
}


/*
 * ndp_server_recv
 * called from icmp recv routine
 */
static void 
ndp_server_recv __PF5(icmp_tp, task *,
		      src, sockaddr_un *,
		      dst, sockaddr_un *,
		      icmp, struct icmpv6 *,
		      len, size_t)
{
    if_addr *ifap;
    ndp_group *ndgp;
    ndp_entry *ndep;
    task *tp = ndp_task;

    /* first determine the interface on which the message was received */
    if (dst && !IS_MULTIADDR6(sock2in6(dst))) {
	/* Try to find interface by looking up the destination address */

	ifap = if_withlcladdr(dst, TRUE);
    } else {
	/* Try to find interface by looking up the source address */

	ifap = if_withdst(src);
    }

    if (!ifap) {
	trace_log_tp(tp,
		     0,
		     LOG_WARNING, 
		     ("ndp_server_recv: Can not locate interface for Router Solicitation from %A to %A",
		      src,
		      dst));
	return;
    }

    if (sockaddrcmp_in6(src, ifap->ifa_addr_local)) {
	/* Ignore our packets */

	return;
    }

    /* icmp code is 0 */
    if (icmp->icmp6_code) {
	trace_log_tp(tp, 
		     0,
		     LOG_WARNING, 
		     ("ndp_server_recv: ICMPv6 code not zero (%u) for Router Solicitation from %A to %A",
		      icmp->icmp6_code,
		      src,
		      dst));
	return;
    }

    /* Verify length */
    if (len < ICMP6_MINLEN) {
	trace_log_tp(tp,
		     0,
		     LOG_WARNING,
		     ("ndp_server_recv: Insufficient length (%u) for Router Solicitation from %A to %A",
		      len,
		      src,
		      dst));
	return;
    }    

    ndep = (ndp_entry *) ifap->ifa_nd_entry;
    if (!ndep) {
	/* Not running router discovery on this interface */

	return;
    }
    ndgp = ndep->nde_group;

    if (!ndgp->ndg_primary) {
	/* Group is being deleted */

	return;
    }
    
    if (dst) {
	/* Hmm, he's supposed to send to the multicast address */

	trace_log_tp(tp,
		      0,
		      LOG_WARNING,
		      ("ndp_server_recv: Expected multicast (%A) for Router Solicitation from %A to %A",
		       inet6_addr_allrouters,
		       src,
		       dst));

	/* We should send a response to the broadcast address, but */
	/* that would require pruning the responses to those one the */
	/* same network.  Instead, we'll response directly to the */
	/* router if we can. */
	if (!IS_ANYADDR6(sock2in6(src))) {
	    goto Unicast;
	} else {
	    goto Respond;
	}
    }
    
    if (!IS_ANYADDR6(sock2in6(src))) {
	ndp_response *rdrp;

	if (ndgp->ndg_timer->task_timer_next_time - time_sec <= NDP_MAX_RESPONSE_DELAY) {
	    /* We will be sending an advertisement within MAX_RESPONSE_DELAY */

	    return;
	}

    Unicast:;

	/* First see if one is already queued */
	NDG_RESPONSES(ndgp, rdrp) {
	    if (sockaddrcmp_in6(src, rdrp->rdr_dest)) {
		/* Already queued */
		goto Duplicate;
	    }
	} NDG_RESPONSES_END(ndgp, rdrp) ;

	/* Not queued, create one */
	rdrp = (ndp_response *) task_block_alloc(ndp_response_index);
	INSQUE(rdrp, ndgp->ndg_responses.q_back);
	rdrp->rdr_group = ndgp;
	rdrp->rdr_dest = sockdup(src);
	(void) sprintf(rdrp->rdr_name, "%s%A",
		       NDP_TIMER_RESPONSE_NAME,
		       src);

	rdrp->rdr_timer = task_timer_create(tp, 
					    rdrp->rdr_name,
					    (flag_t) 0,
					    (time_t) 0,
					    NDP_MAX_RESPONSE_DELAY,
					    ndp_server_timer_response,
					    (void_t) rdrp);

    Duplicate:;
    } else {
	/* Reply after a short random interval */

    Respond:;
	ndgp->ndg_init = MAX(ndgp->ndg_init, 1);
	task_timer_set(ndgp->ndg_timer,
		       (time_t) 0,
		       NDP_MAX_RESPONSE_DELAY);
    }
}


/**/

static void
ndp_server_interface_dump __PF2(fp, FILE *,
				list, config_entry *)
{
    register config_entry *cp;

    CONFIG_LIST(cp, list) {
	switch (cp->config_type) {
	case NDP_CONFIG_MAXADVINT:
	    (void) fprintf(fp, " MaxAdvInt %#T",
			   (time_t) GA2S(cp->config_data));
	    break;

	case NDP_CONFIG_MINADVINT:
	    (void) fprintf(fp, " MinAdvInt %#T",
			   (time_t) GA2S(cp->config_data));
	    break;
	    
	case NDP_CONFIG_LIFETIME:
	    (void) fprintf(fp, " Lifetime %#T",
			   (time_t) GA2S(cp->config_data));
	    break;
	    
	default:
	    assert(FALSE);
	    break;
	}
    } CONFIG_LIST_END(cp, list) ;
}


static void
ndp_server_address_dump __PF2(fp, FILE *,
			      list, config_entry *)
{
    register config_entry *cp;

    CONFIG_LIST(cp, list) {
	switch (cp->config_type) {
	case NDP_CONFIG_IFA_IGNORE:
	    (void) fprintf(fp, (int) GA2S(cp->config_data) ? " Ignore" : " Advertise");
	    break;
	    
	case NDP_CONFIG_IFA_PREFERENCE:
	    (void) fprintf(fp, " Preference %d",
			   (metric_t) GA2S(cp->config_data));
	    break;
	    
	default:
	    assert(FALSE);
	    break;
	}
    } CONFIG_LIST_END(cp, list) ;
}


static void
ndp_server_dump __PF2(tp, task *,
		      fp, FILE *)
{
    register ndp_link *ndlp;
    register if_link *ifl = (if_link *) 0;

    (void) fprintf(fp, "\tInterfaces:\n");

    ND_LINKS(ndlp) {
	register ndp_group *ndgp;

	if (!ifl
	    || ifl != ndlp->ndl_link) {
	    /* New interface */

	    ifl = ndlp->ndl_link;
	    
	    (void) fprintf(fp, "\t\tInterface %s:\n",
			   ifl->ifl_name);
	}

	NDL_GROUPS(ndlp, ndgp) {
	    register ndp_entry *ndep;
	    
	    (void) fprintf(fp, "\t\t\tAddr %A:\n\t\t\t\tminadvint %#T maxadvint %#T lifetime %#T\n\n",
			   ndgp->ndg_primary,
			   ndgp->ndg_adv_min,
			   ndgp->ndg_adv_max,
			   ndgp->ndg_lifetime);

	    ND_ENTRIES(&ndgp->ndg_prefixes, ndep) {
		(void) fprintf(fp, "\t\t\t\tPrefix %A\n",
			       sockbuild_in6(0, (byte *) &ndep->nde_prefix));
	    } ND_ENTRIES_END(&ndgp->ndg_prefixes, ndep) ;

	    if (!NDG_NORESPONSES(ndgp)) {
		register ndp_response *rdrp;
		
		(void) fprintf(fp, "\t\t\tResponses pending to:\n");

		NDG_RESPONSES(ndgp, rdrp) {
		    (void) fprintf(fp, "\t\t\t\t%A\n",
				   rdrp->rdr_dest);
		} NDG_RESPONSES_END(ndgp, rdrp) ;
	     }

	    if (!ND_NOENTRIES(&ndgp->ndg_deletes)) {

		(void) fprintf(fp, "\t\t\tDeletions pending for:\n");

		ND_ENTRIES(&ndgp->ndg_deletes, ndep) {
		    (void) fprintf(fp, "\t\t\t\t%-15A\n",
				   ndep->nde_prefix);
		} ND_ENTRIES_END(&ndgp->ndg_deletes, ndep) ;
	    } 
	} NDL_GROUPS_END(ndlp, ndgp) ;
    } ND_LINKS_END(ndlp) ;

    /* Print policy */
    if (ndp_interface_policy) {
	(void) fprintf(fp, "\n\tInterface policy:\n");
	control_interface_dump(fp, 2, ndp_interface_policy, ndp_server_interface_dump);
    }

    if (ndp_server_address_policy) {
	(void) fprintf(fp, "\n\tAddress policy:\n");
	control_interface_dump(fp, 2, ndp_server_address_policy, ndp_server_address_dump);
    }
}


static void
ndp_server_reinit __PF1(tp, task *)
{
    register ndp_link *ndlp;
    
    trace_set(tp->task_trace, ndp_trace_options);

    /* Re-evaluate group policy */
    ND_LINKS(ndlp) {
	register ndp_group *ndgp;

	NDL_GROUPS(ndlp, ndgp) {
	    if (ndp_server_group_policy(tp, ndgp)) {
		/* Schedule a quick update if something changed */
	
		ndgp->ndg_init = MAX(ndgp->ndg_init, 1);
		task_timer_set(ndgp->ndg_timer,
			       (time_t) 0,
			       NDP_MAX_RESPONSE_DELAY);
	    }
	} NDL_GROUPS_END(ndlp, ndgp) ;
    } ND_LINKS_END(ndlp) ;
}


static void
ndp_server_cleanup __PF1(tp, task *)
{
    adv_free_list(ndp_server_address_policy);
    ndp_server_address_policy = (adv_entry *) 0;
    adv_free_list(ndp_interface_policy);
    ndp_interface_policy = (adv_entry *) 0;

    trace_freeup(ndp_trace_options);
    trace_freeup(tp->task_trace);
}

static void
ndp_server_terminate __PF1(tp, task *)
{
    register ndp_link *ndlp;
    register ndp_group *ndgp;

    ND_LINKS(ndlp) {

	/* First clean up a bit */
	NDL_GROUPS(ndlp, ndgp) {
	    register ndp_entry *ndep;
	    register ndp_response *rdrp;

	    /* Delete the job */
	    if (ndgp->ndg_job) {
		task_job_delete(ndgp->ndg_job);
		ndgp->ndg_job = (task_job *) 0;
	    }

	    while (ND_ENTRIES_FIRST(&ndgp->ndg_prefixes, ndep)) {
		REMQUE(ndep);
		INSQUE(ndep, ndgp->ndg_deletes.q_back);
	    }

	    /* Delete any queued responses */
	    while (NDG_RESPONSES_FIRST(ndgp, rdrp)) {
		REMQUE(rdrp);
		task_timer_delete(rdrp->rdr_timer);
		sockfree(rdrp->rdr_dest);
		task_block_free(ndp_response_index, (void_t) rdrp);
	    }
	} NDL_GROUPS_END(ndlp, ndgp) ;
    } ND_LINKS_END(ndlp) ;

    /* Call ndp_server_delete() to send a notification and delete each */
    /* group and link */
    while (ND_LINKS_FIRST(ndlp)
	   && NDL_GROUPS_FIRST(ndlp, ndgp)) {
	ndp_server_delete(tp, ndgp);
    }
    
    ndp_server_cleanup(tp);

    krt_multicast6_delete(inet6_addr_allrouters);

    task_delete(tp);
    ndp_task = (task *) 0;
}
#endif	/* NDP_SERVER */

/**/

/* Client support */

#ifdef	NDP_CLIENT

/* For controlling who does solicitations */
typedef struct _ndp_solicit {
    struct _ndp_solicit *nds_forw, *nds_back;

    flag_t nds_flags;
#define	NDSF_QUIET	BIT(0x01)	/* Don't solicit */

    if_link *nds_link;		/* Pointer to physical interface */

    if_addr *nds_addrs;		/* List of interface prefixes */

    task_timer *nds_timer;	/* Solicitation timer */
    u_int nds_solicits;		/* Number of solicitations remaining */
    u_int nds_n_valid;		/* Number of routes via this interface */
} ndp_solicit;

#define	ND_SOLICITS_FIRST(ndsp)	(((ndsp) = (ndp_solicit *) ndp_solicits.q_forw) != (ndp_solicit *) &ndp_solicits)
#define	ND_NOSOLICITS()	(ndp_solicits.q_forw == &ndp_solicits)
#define	ND_SOLICITS(ndsp)	for ((ndsp) = (ndp_solicit *) ndp_solicits.q_forw; \
			     (ndsp) != (ndp_solicit *) &ndp_solicits; \
			     (ndsp) = (ndsp)->nds_forw)
#define	ND_SOLICITS_END(ndsp)


/* Pointer to solicitation entry */
#define	ifa_nd_solicit		ifa_ps[RTPROTO_NDP].ips_datas[0]
#define	ifa_to_solicit(ifap)	((ndp_solicit *) (ifap)->ifa_nd_solicit)
/* Pointer to next if_addr * for this solicitation entry */
#define	ifa_nd_next		ifa_ps[RTPROTO_NDP].ips_datas[1]

/* Pointer to solicitation entry for multicast interfaces */
#define	ifl_nd_solicit		ifl_ps[RTPROTO_NDP]
#define	ifl_to_solicit(iflp)	((ndp_solicit *) (iflp)->ifl_nd_solicit)

/* A place to save the lifetime */
#define	rt_nd_lifetime		rt_data
#define	rt_to_lifetime(rt)	((time_t) GA2S(rt->rt_nd_lifetime))

/* Which interface we were learned from */
#define	gw_nd_ifap		gw_data
#define	gw_to_ifa(gwp)		((if_addr *) (gwp)->gw_nd_ifap)

pref_t ndp_client_preference = RTPREF_NDP;

static gw_entry *ndp_client_gw_list = (gw_entry *) 0;
static block_t ndp_solicit_index = (block_t) 0;
static struct _qelement ndp_solicits = { &ndp_solicits, &ndp_solicits };
static task_timer *ndp_client_timer_age = (task_timer *) 0;
static const bits ndp_solicit_bits[] = {
    { NDSF_QUIET,	"Quiet" },
    { 0,	NULL }
} ;

/* Hack because rt_change() does not recalculate interfaces */
#define	RT_CHANGE(rt, preference) \
	do { \
	    sockaddr_un *Xrouter = sockdup(RT_ROUTER(rt)); \
	    (void) rt_change((rt), \
			     (rt)->rt_metric, \
			     (rt)->rt_metric2, \
			     (rt)->rt_tag, \
			     (rt)->rt_preference, \
			     (rt)->rt_preference2, \
			     0, (sockaddr_un **) 0); \
	    (void) rt_change((rt), \
			     (rt)->rt_metric, \
			     (rt)->rt_metric2, \
			     (rt)->rt_tag, \
			     (preference), \
			     (rt)->rt_preference2, \
			     1, &Xrouter); \
	    sockfree(Xrouter); \
	} while (0)

/**/

static void
ndp_client_send __PF2(ifap, if_addr *,
		      dest, sockaddr_un *)
{
    sockaddr_un *src = ifap->ifa_addr_local;
    sockaddr_un *phys = ifap->ifa_link->ifl_addr;
    struct icmpv6 *icmp = (struct icmpv6 *) task_send_buffer; 
    struct ndx6_lladdr *lp;

    /* Init our fields in the packet */
    icmp->icmp6_type = ICMP6_SOLICITATION_RT;
    icmp->icmp6_code = 0;
    /* zero the reserved field */
    icmp->icmp6_pptr = 0;

    lp = (struct ndx6_lladdr *)((byte *) icmp + ICMP6_MINLEN);
    lp->lla_ext = NDX6_LLADDR_SRC;
    lp->lla_len = 1;
    bcopy((caddr_t) phys->ll.gll_addr, (caddr_t) lp->lla_addr, 6);

    icmpv6_send(icmp,
		(size_t) (ICMP6_MINLEN + sizeof(struct ndx6_lladdr)),
		src,
		dest,
		ifap,
		MSG_DONTROUTE);

}


static void
ndp_client_solicit __PF2(tip, task_timer *,
			 interval, time_t)
{
    ndp_solicit *ndsp = (ndp_solicit *) tip->task_timer_data;
    if_addr *ifap = ndsp->nds_addrs;

    assert(ifap && ndsp->nds_solicits);

    /* Send a solicitation */
    ndp_client_send(ifap, inet6_addr_allrouters);

    if (--ndsp->nds_solicits) {
	/* Set next interval */

	task_timer_set(tip,
		       (time_t) 0,
		       NDP_SOLICITATION_INTERVAL);
    } else {
	/* Reset timer */

	task_timer_delete(tip);
	ndsp->nds_timer = (task_timer *) 0;
    }
}


/**/


static void
ndp_client_rt_dump __PF2(fp, FILE *,
			 rt, rt_entry *)
{
    (void) fprintf(fp, "\t\t\tLearned via interface %A  Lifetime %#T\n",
		   gw_to_ifa(rt->rt_gwp)->ifa_addr,
		   rt_to_lifetime(rt));
}


static void
ndp_client_interface_dump __PF2(fp, FILE *,
				list, config_entry *)
{
    register config_entry *cp;

    CONFIG_LIST(cp, list) {
	switch (cp->config_type) {
	case NDP_CONFIG_CLIENT_DISABLE:
	    (void) fprintf(fp,
			   GA2S(cp->config_data) ? " disable" : " enable");
	    break;

	case NDP_CONFIG_CLIENT_QUIET:
	    (void) fprintf(fp,
			   GA2S(cp->config_data) ? " quiet" : " solicit");
	    break;
	    
	default:
	    assert(FALSE);
	    break;
	}
    } CONFIG_LIST_END(cp, list) ;
}


static void
ndp_client_dump __PF2(tp, task *,
		      fp, FILE *)
{
    register ndp_solicit *ndsp;

    (void) fprintf(fp, "\tPreference: %u\n\n",
		   ndp_client_preference);

    (void) fprintf(fp, "\tSolicitation Ethers:\n");
    
    ND_SOLICITS(ndsp) {
	if_addr *ifap;
	
	(void) fprintf(fp, "\t\tflags %s  interface %s  valid routes %u  solicits %u\n",
		       trace_bits(ndp_solicit_bits, ndsp->nds_flags),
		       ndsp->nds_link->ifl_name,
		       ndsp->nds_n_valid,
		       ndsp->nds_solicits);

	for (ifap = ndsp->nds_addrs;
	     ifap;
	     ifap = (if_addr *) ifap->ifa_nd_next) {

	    (void) fprintf(fp, "\t\t\t%A\n",
			   ifap->ifa_addr);
	}
    } ND_SOLICITS_END(ndsp) ;
    
    if (ndp_client_gw_list) {
	(void) fprintf(fp, "\n\tActive gateways:\n");
	gw_dump(fp,
		"\t\t",
		ndp_client_gw_list,
		tp->task_rtproto);
	(void) fprintf(fp, "\n");
    }

    if (ndp_interface_policy) {
	(void) fprintf(fp, "\tInterface policy:\n");
	control_interface_dump(fp, 2, ndp_interface_policy, ndp_client_interface_dump);
    }
}

/**/

static void
ndp_client_ifachange __PF2(tp, task *,
			     ifap, if_addr *)
{
    int policy = 0;
    int delete = 0;
    int solicit = 0;
    int quiet = 0;
    int add = 0;
    int netmask = 0;
    int route_recalc = 0;
    ndp_solicit *ndsp = (ndp_solicit *) ifap->ifa_nd_solicit;
    
    if (!BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
	/* We only support interfaces where we can reach all members */

	return;
    }
  
    if (!IS_LINKLADDR6(sock2in6(ifap->ifa_addr_local))) {
	/* We are only interested by link-local interface address */

	return;
    }

    switch (ifap->ifa_change) {
    case IFC_NOCHANGE:
    case IFC_ADD:
	if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    policy++;
	}
	break;
    
    case IFC_DELETE:
	return;
    
    case IFC_DELETE|IFC_UPDOWN:
	delete++;
	break;
    
    default:
	/* Something has changed */
  
	if (BIT_TEST(ifap->ifa_change, IFC_UPDOWN)) {
	    if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
		/* Transition to UP */

		policy++;
	    } else {
		/* Transition to DOWN */

		delete++;
	    }
	    break;
	}
	/* METRIC, ADDR, SEL, BROADCAST - Don't care */
	/* MTU - will take effect on output */

	if (BIT_TEST(ifap->ifa_change, IFC_NETMASK)) {
	    /* Need to re-evaluate gateways and routes so do it the hard way */

	    netmask++;
	}
    }

    if (policy) {
	config_entry **ifl_list = config_resolv_ifl(ndp_interface_policy,
						    ifap->ifa_link,
						    NDP_CONFIG_MAX);

	if (ifl_list) {
	    int type = NDP_CONFIG_CLIENT_MAX;
	    config_entry *cp;

	    /* Fill in the parameters */
	    while (--type) {
		if ((cp = ifl_list[type])) {
		    switch (type) {
		    case NDP_CONFIG_CLIENT_DISABLE:
			if (GA2S(cp->config_data)) {
			    /* Ignore this interface */
			    
			    delete++;
			}
			break;
	  
		    case NDP_CONFIG_CLIENT_QUIET:
			if (GA2S(cp->config_data)) {
			    quiet = 1;
			}
			break;
		    }
		}
	    }

	    config_resolv_free(ifl_list, NDP_CONFIG_MAX);
	}
    }

    /* Now figure out what we need to do */
    if (delete) {
	if (!ndsp) {
	    delete = 0;
	}
    } else {
	/* This is interface is valid, check for only changes */
	    
	/* Figure out what (if anything) changed */
	if (!ndsp) {
	    add++;
	} else if (BIT_MATCH(ndsp->nds_flags, NDSF_QUIET) != quiet) {
	    if (quiet) {
		/* Make us quiet */
		
		BIT_SET(ndsp->nds_flags, NDSF_QUIET);
		if (ndsp->nds_timer) {
		    task_timer_delete(ndsp->nds_timer);
		    ndsp->nds_timer = (task_timer *) 0;
		    ndsp->nds_solicits = 0;
		}
	    } else {
		/* Make us noisy */

		solicit++;
	    }
	}
    }

    /* Handle a delete */
    if (delete) {
	int freeit = 0;

	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("ndp_client_ifachange: DELETE address %A interface %s",
		  ifap->ifa_addr,
		  ifap->ifa_link->ifl_name));

	if (freeit) {
	    gw_entry *gwp;
	    
	    trace_tp(tp,
		     TR_STATE,
		     0,
		     ("ndp_client_ifachange: DELETE interface %s",
		      ndsp->nds_link->ifl_name));

	    REMQUE(ndsp);
	    if (ndsp->nds_timer) {
		task_timer_delete(ndsp->nds_timer);
	    }
	    task_block_free(ndp_solicit_index, (void_t) ndsp);

	    if (add) {
		/* Cause a route recalculation to make sure */
		/* the counts are right */

		route_recalc++;
	    } else {
		rt_list *deletes = (rt_list *) 0;
		
		/* Delete any routes via this interface */

		rt_open(tp);
		
		GW_LIST(ndp_client_gw_list, gwp) {
		    rt_entry *rt;

		    if (gw_to_ifa(gwp) == ifap) {
			/* Gateway via this interface - delete it */

			RTQ_LIST(&gwp->gw_rtq, rt) {
			    if (rt->rt_preference > 0) {
				ifa_to_solicit(gw_to_ifa(gwp))->nds_n_valid--;
			    }
			    RTLIST_ADD(deletes, sockdup(RT_ROUTER(rt)));
			    rt_delete(rt);
			} RTQ_LIST_END(&gwp->gw_rtq, rt) ;

			gwp->gw_data = (void_t) 0;
		    } else {
			/* Check routes */

			route_recalc++;
		    }
		} GW_LIST_END(ndp_client_gw_list, gwp) ;

		rt_close(tp, (gw_entry *) 0, 0, NULL);

		if (deletes) {
		    sockaddr_un *router;
		    
		    redirect_delete_router(deletes);

		    RT_LIST(router,deletes, sockaddr_un) {
			sockfree(router);
		    } RT_LIST_END(router,deletes, sockaddr_un) ;

		    RTLIST_RESET(deletes);
		}
	    }
	}
	ifap->ifa_nd_solicit = ifap->ifa_nd_next = (void_t) 0;
	IFA_FREE(ifap);
    }

    /* Handle a new one */
    if (add) {

	if (!ndsp) {
	    /* Create a new one */

	    ndsp = (ndp_solicit *) task_block_alloc(ndp_solicit_index);
	}

	ndsp->nds_link = ifap->ifa_link;
	IFA_ALLOC(ndsp->nds_addrs = ifap);
	INSQUE(ndsp, ndp_solicits.q_back);
	if (!quiet) {
	    solicit++;
	}

	ifap->ifa_nd_solicit = (void_t) ndsp;
	
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("ndp_client_ifachange: ADD address %A interface %s",
		  ifap->ifa_addr,
		  ifap->ifa_link->ifl_name));

    }

    if (netmask
	&& ndsp
	&& (!add && !delete)) {
	gw_entry *gwp;
	rt_list *deletes = (rt_list *) 0;
	
	/* Just the netmask changed, re-evaluate the routes */

	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("ndp_client_ifachange: processing netmask change"));

	rt_open(tp);
	
	GW_LIST(ndp_client_gw_list, gwp) {
	    if_addr *gw_ifap = if_withdst(gwp->gw_addr);
	    rt_entry *rt;

	    if (gw_ifap != gw_to_ifa(gwp)
		&& (gw_to_ifa(gwp) == ifap
		    || gw_ifap == ifap)) {
		/* We changed either to or from this interface */

		if (!gw_ifap
		    || (gw_ifap->ifa_link != gw_to_ifa(gwp)->ifa_link)) {
		    /* Gateway no longer reachable the same physical interface */

		    RTQ_LIST(&gwp->gw_rtq, rt) {
			if (rt->rt_preference > 0) {
			    ifa_to_solicit(gw_to_ifa(gwp))->nds_n_valid--;
			}
			/* Delete any redirects */
			RTLIST_ADD(deletes, sockdup(RT_ROUTER(rt)));
			rt_delete(rt);
		    } RTQ_LIST_END(&gwp->gw_rtq, rt) ;

		    gwp->gw_data = (void_t) 0;
		} else {
		    if (ifa_to_solicit(ifap) != ifa_to_solicit(gw_to_ifa(gwp)))
		    {
			/* Fix counts */

			RTQ_LIST(&gwp->gw_rtq, rt) {
			    if (rt->rt_preference > 0) {
				ifa_to_solicit(gw_to_ifa(gwp))->nds_n_valid--;
				ifa_to_solicit(gw_ifap)->nds_n_valid++;
			    }
			} RTQ_LIST_END(&gwp->gw_rtq, rt) ;
		    }
		    gwp->gw_data = (void_t) gw_ifap;
		}

		route_recalc++;

	    } 
	} GW_LIST_END(ndp_client_gw_list, gwp) ;

	rt_close(tp, (gw_entry *) 0, 0, NULL);

	if (deletes) {
	    sockaddr_un *router;
	    
	    redirect_delete_router(deletes);

	    RT_LIST(router,deletes, sockaddr_un) {
		sockfree(router);
	    } RT_LIST_END(router,deletes, sockaddr_un) ;

	    RTLIST_RESET(deletes);
	}

	/* Send a query */
	solicit++;
    }

    /* See if any routes need to be changed */
    if (route_recalc) {
	int changes = 0;
	register gw_entry *gwp;
	register ndp_solicit *ndsp2;

	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("ndp_client_ifachange: recalculating routes"));

	rt_open(tp);

	/* Reset the route count on all ethers */
	ND_SOLICITS(ndsp2) {
	    ndsp2->nds_n_valid = 0;
	} ND_SOLICITS_END(ndsp2) ;
	
	GW_LIST(ndp_client_gw_list, gwp) {
	    register rt_entry *rt;

	    RTQ_LIST(&gwp->gw_rtq, rt) {
		if_addr *router_ifap = if_withdst(RT_ROUTER(rt));
		pref_t preference = ndp_client_preference;

		if (!router_ifap
		    || router_ifap->ifa_link != gw_to_ifa(gwp)->ifa_link
		    || rt->rt_metric2 == NDP_PREFERENCE_INELIGIBLE) {
		    preference = -preference;
		}

		if (preference > 0) {
		    ifa_to_solicit(router_ifap)->nds_n_valid++;
		}
		
		if (preference != rt->rt_preference
		    || router_ifap != RT_IFAP(rt)) {
		    RT_CHANGE(rt, preference);
		    changes++;
		}
	    
	    } RTQ_LIST_END(&gwp->gw_rtq, rt) ;
	} GW_LIST_END(ndp_client_gw_list, gwp) ;
	    
	rt_close(tp, (gw_entry *) 0, changes, NULL);
    }

    if (solicit
	&& !quiet
	&& (!ndsp
	    || !ndsp->nds_n_valid)) {
	utime_t offset;
	
	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("ndp_client_ifachange: scheduling solicitation"));

	ndsp->nds_solicits = NDP_MAX_SOLICITATIONS;
	offset.ut_sec = 0;
	offset.ut_usec = (time_t) grand((u_int32) 1000000);

	if (ndsp->nds_timer) {
	    /* Timer already exists */
	
	    task_timer_uset(ndsp->nds_timer,
			    &offset,
			    (utime_t *) 0,
			    (utime_t *) 0);
	} else {
	    /* Create timer */

	    ndsp->nds_timer = task_timer_ucreate(ndp_task,
						 ndsp->nds_link->ifl_name,
						 (flag_t) 0,
						 (utime_t *) 0,
						 &offset,
						 (utime_t *) 0,
						 ndp_client_solicit,
						 (void_t) ndsp);
	}
    }
}


static void
ndp_client_age __PF2(tip, task_timer *,
		     interval, time_t)
{
    time_t nexttime = time_sec + NDP_LIFETIME_MAX + 1;
    gw_entry *gwp;
    rt_list *deletes = (rt_list *) 0;

    rt_open(tip->task_timer_task);
    
    GW_LIST(ndp_client_gw_list, gwp) {
	rt_entry *rt;
	if (!gwp->gw_n_routes) {
	    /* No routes, delete this gateway */

	    /* XXX */
	    continue;
	}

	/* Age any routes for this gateway */
	RTQ_LIST(&gwp->gw_rtq, rt) {
	    time_t lifetime = rt_to_lifetime(rt);
	    time_t expire_to = time_sec - lifetime;

	    if (time_sec + lifetime < nexttime) {
		nexttime = time_sec + lifetime;
	    }
	
	    if (expire_to <= 0) {
		/* We have not been up long enough */
		
		continue;
	    }

	    if (rt->rt_time <= expire_to) {
		if (rt->rt_preference > 0) {
		    ifa_to_solicit(gw_to_ifa(gwp))->nds_n_valid--;
		}
		RTLIST_ADD(deletes, sockdup(RT_ROUTER(rt)));
		rt_delete(rt);
	    } else {
		/* This is the next route to expire */
		if (rt->rt_time + lifetime < nexttime) {
		    nexttime = rt->rt_time + lifetime;
		}
		break;
	    }
	} RTQ_LIST_END(&gwp->gw_rtq, rt) ;
    } GW_LIST_END(ndp_client_gw_list, gwp) ;

    rt_close(tip->task_timer_task, (gw_entry *) 0, 0, NULL);

    if (deletes) {
	sockaddr_un *router;
	
	redirect_delete_router(deletes);
	
	RT_LIST(router,deletes, sockaddr_un) {
	    sockfree(router);
	} RT_LIST_END(router,deletes, sockaddr_un) ;

	RTLIST_RESET(deletes);
    }

    if (nexttime > time_sec + NDP_LIFETIME_MAX) {
	/* No routes to expire, let timer idle */

	nexttime = time_sec;
    }

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


static void
ndp_client_recv __PF5(icmp_tp, task *,
		      src, sockaddr_un *,
		      dst, sockaddr_un *,
		      icmp, struct icmpv6 *,
		      len, size_t)
{
    if_addr *ifap;
    task *tp = ndp_task;
    ndp_solicit *ndsp;

    /* first determine the interface on which the message was received */
    if (dst && !IS_MULTIADDR6(sock2in6(dst))) {
	/* Try to find interface by looking up the destination address */

	ifap = if_withlcladdr(dst, TRUE);
    } else {
	/* Try to find interface by looking up the source address */

	ifap = if_withdst(src);
    }

    if (!ifap) {
	/* Not configured on that interface (changed on last rdisc!) */

	return;
    }

    if (sockaddrcmp_in6(src, ifap->ifa_addr_local)) {
	/* Ignore our packets */

	return;
    }

    /* icmp code is 0 */
    if (icmp->icmp6_code) {

	return;
    }

    /* Verify length */
    if (len < ICMP6_RALEN) {
	/* Insufficient length */

	return;
    }    

    ndsp = (ndp_solicit *) ifap->ifa_nd_solicit;
    if (ndsp) {
	int changes = 0;
	gw_entry *gwp;
	rt_list *deletes = (rt_list *) 0;
	time_t lifetime = ntohs(icmp->icmp6_life);
	time_t min_lifetime = NDP_LIFETIME_MAX;
	register struct ndx6_pref *ndp;
	if_addr *router_ifap = if_withdst(src);
	pref_t preference = ndp_client_preference;

	rt_open(tp);
	
	gwp = gw_timestamp(&ndp_client_gw_list,
			   RTPROTO_NDP,
			   tp,
			   (as_t) 0,
			   (as_t) 0,
			   src,
			   (flag_t) 0);
	if (!gwp->gw_data) {
	    /* A virgin! */
	    
	    gwp->gw_rtd_dump = ndp_client_rt_dump;
	    gwp->gw_data = (void_t) ifap;
	} else {
	    /* The ifa_change code should catch any changes that could cause */
	    /* the interface for this gateway to change */

	    assert(gw_to_ifa(gwp) == ifap);
	}

	
	if (!router_ifap
	    || router_ifap->ifa_link != ifap->ifa_link) {
	    /* Ineligible or not on the same interface */

	    preference = -preference;
	} else if (ndsp->nds_timer) {
	    /* Valid advertisement - stop sending solicitations */

	    task_timer_delete(ndsp->nds_timer);
	    ndsp->nds_timer = (void_t) 0;
	    ndsp->nds_solicits = 0;
	}

	for (ndp = (struct ndx6_pref *) ((byte *) icmp + ICMP6_RALEN);
	     (byte *) ndp < (byte *) icmp + len;
	     ndp = (struct ndx6_pref *)((byte *) ndp +
					((ndp->pref_len + 1) * 8))) {
	    sockaddr_un *prefix = sockbuild_in6(0, (byte *) &ndp->pref_pref);
	    rt_entry *rt;

	    RTQ_LIST(&gwp->gw_rtq, rt) {
		if (sockaddrcmp_in6(RT_ROUTER(rt), src)) {
		    if (lifetime) {

			/* Update route's lifetime */
			if (lifetime != rt_to_lifetime(rt)) {
			    rt->rt_nd_lifetime = GS2A(lifetime);
			    if (min_lifetime > lifetime) {
				min_lifetime = lifetime;
			    }
			}

			/* Update time stamp */
			rt_refresh(rt);
		    } else {
			/* It's history */

			if (rt->rt_preference > 0) {
			    ndsp->nds_n_valid--;
			}
			RTLIST_ADD(deletes, sockdup(RT_ROUTER(rt)));
			rt_delete(rt);
			changes++;
		    }
		    goto found;
		}
	    } RTQ_LIST_END(&gwp->gw_rtq, rt) ;

	    if (lifetime) {
		rt_parms rtparms;
		
		/* Create a new route */

		bzero((caddr_t) &rtparms, sizeof rtparms);
		
		rtparms.rtp_dest = inet6_addr_default;
		rtparms.rtp_dest_mask = inet6_mask_default;
		rtparms.rtp_n_gw = 1;
		rtparms.rtp_router = src;
		rtparms.rtp_gwp = gwp;
		rtparms.rtp_state = RTS_INTERIOR|RTS_GATEWAY|RTS_NOADVISE;
		if (preference < 0) {
		    BIT_SET(rtparms.rtp_state, RTS_NOTINSTALL);
		}
		rtparms.rtp_preference = preference;
		rtparms.rtp_rtd = (void_t) ifap;
		rt = rt_add(&rtparms);
		rt->rt_nd_lifetime = GS2A(lifetime);

		if (rt->rt_preference > 0) {
		    /* Count this route */
		    ndsp->nds_n_valid++;
		}
		changes++;
		if (min_lifetime > lifetime) {
		    min_lifetime = lifetime;
		}
	    }
	    
	found:;
	}

	rt_close(tp, gwp, changes, NULL);

	if (deletes) {
	    sockaddr_un *router;
	    
	    redirect_delete_router(deletes);

	    RT_LIST(router,deletes, sockaddr_un) {
		sockfree(router);
	    } RT_LIST_END(router,deletes, sockaddr_un) ;

	    RTLIST_RESET(deletes);
	}

	/* Make sure timer will fire in time to age these routes */
	if (!ndp_client_timer_age->task_timer_next_time
	    || ndp_client_timer_age->task_timer_next_time - time_sec > min_lifetime) {

	    task_timer_set(ndp_client_timer_age,
			   (time_t) 0,
			   min_lifetime);
	}

    }
}


/**/

static void
ndp_client_cleanup __PF1(tp, task *)
{
    adv_free_list(ndp_interface_policy);
    ndp_interface_policy = (adv_entry *) 0;

    trace_freeup(ndp_trace_options);
    trace_freeup(tp->task_trace);
}


static void
ndp_client_reinit __PF1(tp, task *)
{
    int changes = 0;
    register gw_entry *gwp;

    trace_set(tp->task_trace, ndp_trace_options);

    rt_open(tp);
    
    GW_LIST(ndp_client_gw_list, gwp) {
	register rt_entry *rt;

	RTQ_LIST(&gwp->gw_rtq, rt) {
	    if_addr *ifap = if_withdst(RT_ROUTER(rt));
	    pref_t preference = ndp_client_preference;

	    if (!ifap
		|| ifap->ifa_link != gw_to_ifa(gwp)->ifa_link
		|| rt->rt_metric2 == NDP_PREFERENCE_INELIGIBLE) {
		preference = -preference;
	    }

	    if (preference != rt->rt_preference) {
		(void) rt_change(rt,
				 rt->rt_metric,
				 rt->rt_metric2,
				 rt->rt_tag,
				 preference,
				 rt->rt_preference2,
				 1, &RT_ROUTER(rt));
		changes++;
	    }
	    
	} RTQ_LIST_END(&gwp->gw_rtq, rt) ;
    } GW_LIST_END(ndp_client_gw_list, gwp) ;
	    
    rt_close(tp, (gw_entry *) 0, changes, NULL);
}


static void
ndp_client_terminate __PF1(tp, task *)
{
    register gw_entry *gwp;
    register ndp_solicit *ndsp;
    rt_list *deletes = (rt_list *) 0;

    rt_open(tp);
    
    GW_LIST(ndp_client_gw_list, gwp) {
	register rt_entry *rt;

	RTQ_LIST(&gwp->gw_rtq, rt) {
	    if (rt->rt_preference > 0) {
		ifa_to_solicit(gw_to_ifa(gwp))->nds_n_valid--;
	    }
	    RTLIST_ADD(deletes, sockdup(RT_ROUTER(rt)));
	    rt_delete(rt);
	} RTQ_LIST_END(&gwp->gw_rtq, rt) ;
    } GW_LIST_END(ndp_client_gw_list, gwp) ;

    rt_close(tp, (gw_entry *) 0, 0, NULL);

    if (deletes) {
	sockaddr_un *router;
	
	redirect_delete_router(deletes);

	RT_LIST(router,deletes, sockaddr_un) {
	    sockfree(router);
	} RT_LIST_END(router,deletes, sockaddr_un) ;

	RTLIST_RESET(deletes);
    }

    while (ND_SOLICITS_FIRST(ndsp)) {
	if_addr *ifap;


	REMQUE(ndsp);

	while ((ifap = ndsp->nds_addrs)) {

	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("ndp_client_terminate: DELETE address %A interface %s",
		  ifap->ifa_addr,
		  ifap->ifa_link->ifl_name));

	    ndsp->nds_addrs = ifap->ifa_nd_next;
	    ifap->ifa_nd_solicit = ifap->ifa_nd_next = (void_t) 0;
	    IFA_FREE(ifap);
	}

	trace_tp(tp,
		 TR_STATE,
		 0,
		 ("ndp_client_terminate: DELETE interface %s",
		  ndsp->nds_link->ifl_name));

	if (ndsp->nds_timer) {
	    task_timer_delete(ndsp->nds_timer);
	}
	
	task_block_free(ndp_solicit_index, (void_t) ndsp);
    }

    ndp_client_cleanup(tp);

    task_delete(tp);
    ndp_task = (task *) 0;
    ndp_client_timer_age = (task_timer *) 0;
}
#endif	/* NDP_CLIENT */

/**/

/*
 * router discovery initialization
 *
 * start timers for each advertising interface
 */
void
ndp_init __PF0(void)
{
    if (doing_ndp) {
	/* Set tracing */
	trace_inherit_global(ndp_trace_options, ndp_trace_types, (flag_t) 0);
    }
    
    switch (doing_ndp) {
#ifdef	NDP_SERVER
    case NDP_DOING_SERVER:
#ifdef	NDP_CLIENT
	if (ndp_task
	    && GA2S(ndp_task->task_data) != doing_ndp) {
	    /* Terminate client */

	    ndp_client_terminate(ndp_task);
	    ndp_task = (task *) 0;
	}
#endif	/* NDP_CLIENT */
	if (!ndp_task) {
	    /* Create us a task */
	    ndp_task = task_alloc("NeighborDiscoveryServer",
				    TASKPRI_ICMP,
				    ndp_trace_options);
	    task_set_cleanup(ndp_task, ndp_server_cleanup);
	    task_set_reinit(ndp_task, ndp_server_reinit);
	    task_set_terminate(ndp_task, ndp_server_terminate);
	    task_set_ifachange(ndp_task, ndp_server_ifachange);
	    task_set_dump(ndp_task, ndp_server_dump);
	    ndp_task->task_data = GS2A(doing_ndp);
	    if (!task_create(ndp_task)) {
		task_quit(EINVAL);
	    }

	    /* Tell kernel code about our address */
	    krt_multicast6_add(inet6_addr_allrouters);

	    /* Allocate a block index */
	    if (!ndp_group_index) {
		ndp_link_index = task_block_init(sizeof (ndp_link), "ndp_link");
		ndp_group_index = task_block_init(sizeof (ndp_group), "ndp_group");
		ndp_entry_index = task_block_init(sizeof (ndp_entry), "ndp_entry");
		ndp_response_index = task_block_init(sizeof (ndp_response), "ndp_response");
	    }
	}

	/* Tell ICMPv6 we want router discovery solicitations */
	icmpv6_methods[ICMP6_SOLICITATION_RT] = ndp_server_recv;
	break;
#endif	/* NDP_SERVER */

#ifdef	NDP_CLIENT
    case NDP_DOING_CLIENT:
#ifdef	NDP_SERVER
	if (ndp_task
	    && GA2S(ndp_task->task_data) != doing_ndp) {
	    /* Terminate server */
	    
	    ndp_server_terminate(ndp_task);
	    ndp_task = (task *) 0;
	}
#endif	/* NDP_SERVER */
	if (!ndp_task) {
	    /* Create us a task */

	    ndp_task = task_alloc("NeighborDiscoveryClient",
				    TASKPRI_ICMP,
				    ndp_trace_options);
	    task_set_cleanup(ndp_task, ndp_client_cleanup);
	    task_set_reinit(ndp_task, ndp_client_reinit);
	    task_set_terminate(ndp_task, ndp_client_terminate);
	    task_set_ifachange(ndp_task, ndp_client_ifachange);
	    task_set_dump(ndp_task, ndp_client_dump);
	    ndp_task->task_data = GS2A(doing_ndp);
	    if (!task_create(ndp_task)) {
		task_quit(EINVAL);
	    }

	    /* Create the age timer, leave it inactive for now */
	    ndp_client_timer_age = task_timer_create(ndp_task,
						       "Age",
						       (flag_t) 0,
						       (time_t) 0,
						       (time_t) 0,
						       ndp_client_age,
						       (void_t) 0);

	    if (!ndp_solicit_index) {
		ndp_solicit_index = task_block_init(sizeof (ndp_solicit), "ndp_solicit");
	    }
	}
	    
	/* Tell ICMPv6 we want router discovery advertisements */
	icmpv6_methods[ICMP6_ADVERTISEMENT_RT] = ndp_client_recv;
	break;
#endif	/* NDP_CLIENT */

    case NDP_DOING_OFF:
	if (ndp_task) {
	    /* Cleanup */

	    switch (GA2S(ndp_task->task_data)) {
#ifdef	NDP_SERVER
	    case NDP_DOING_SERVER:
		ndp_server_terminate(ndp_task);
		break;
#endif	/* NDP_SERVER */

#ifdef	NDP_CLIENT
	    case NDP_DOING_CLIENT:
		ndp_client_terminate(ndp_task);
		break;
#endif	/* NDP_CLIENT */

	    default:
		assert(FALSE);
	    }

	    ndp_task = (task *) 0;
	}
	break;

    default:
	assert(FALSE);
    }
}


void
ndp_var_init __PF0(void)
{
    doing_ndp = NDP_DOING_OFF;
#ifdef	NDP_CLIENT
    ndp_client_preference = RTPREF_NDP;
#endif	/* NDP_CLIENT */
}
