File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / dhcp / client / dhc6.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Oct 9 09:06:54 2012 UTC (11 years, 8 months ago) by misho
Branches: dhcp, MAIN
CVS tags: v4_1_R7p0, v4_1_R7, v4_1_R4, HEAD
dhcp 4.1 r7

/* dhc6.c - DHCPv6 client routines. */

/*
 * Copyright (c) 2012 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 2006-2010 by Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *   Internet Systems Consortium, Inc.
 *   950 Charter Street
 *   Redwood City, CA 94063
 *   <info@isc.org>
 *   https://www.isc.org/
 */

#include "dhcpd.h"

#ifdef DHCPv6

struct sockaddr_in6 DHCPv6DestAddr;

/*
 * Option definition structures that are used by the software - declared
 * here once and assigned at startup to save lookups.
 */
struct option *clientid_option = NULL;
struct option *elapsed_option = NULL;
struct option *ia_na_option = NULL;
struct option *ia_ta_option = NULL;
struct option *ia_pd_option = NULL;
struct option *iaaddr_option = NULL;
struct option *iaprefix_option = NULL;
struct option *oro_option = NULL;
struct option *irt_option = NULL;

static struct dhc6_lease *dhc6_dup_lease(struct dhc6_lease *lease,
					 const char *file, int line);
static struct dhc6_ia *dhc6_dup_ia(struct dhc6_ia *ia,
				   const char *file, int line);
static struct dhc6_addr *dhc6_dup_addr(struct dhc6_addr *addr,
				       const char *file, int line);
static void dhc6_ia_destroy(struct dhc6_ia **src, const char *file, int line);
static isc_result_t dhc6_parse_ia_na(struct dhc6_ia **pia,
				     struct packet *packet,
				     struct option_state *options);
static isc_result_t dhc6_parse_ia_ta(struct dhc6_ia **pia,
				     struct packet *packet,
				     struct option_state *options);
static isc_result_t dhc6_parse_ia_pd(struct dhc6_ia **pia,
				     struct packet *packet,
				     struct option_state *options);
static isc_result_t dhc6_parse_addrs(struct dhc6_addr **paddr,
				     struct packet *packet,
				     struct option_state *options);
static isc_result_t dhc6_parse_prefixes(struct dhc6_addr **ppref,
					struct packet *packet,
					struct option_state *options);
static struct dhc6_ia *find_ia(struct dhc6_ia *head,
			       u_int16_t type, const char *id);
static struct dhc6_addr *find_addr(struct dhc6_addr *head,
				   struct iaddr *address);
static struct dhc6_addr *find_pref(struct dhc6_addr *head,
				   struct iaddr *prefix, u_int8_t plen);
void init_handler(struct packet *packet, struct client_state *client);
void info_request_handler(struct packet *packet, struct client_state *client);
void rapid_commit_handler(struct packet *packet, struct client_state *client);
void do_init6(void *input);
void do_info_request6(void *input);
void do_confirm6(void *input);
void reply_handler(struct packet *packet, struct client_state *client);
static isc_result_t dhc6_add_ia_na(struct client_state *client,
				   struct data_string *packet,
				   struct dhc6_lease *lease,
				   u_int8_t message);
static isc_result_t dhc6_add_ia_ta(struct client_state *client,
				   struct data_string *packet,
				   struct dhc6_lease *lease,
				   u_int8_t message);
static isc_result_t dhc6_add_ia_pd(struct client_state *client,
				   struct data_string *packet,
				   struct dhc6_lease *lease,
				   u_int8_t message);
static isc_boolean_t stopping_finished(void);
static void dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst);
void do_select6(void *input);
void do_refresh6(void *input);
static void do_release6(void *input);
static void start_bound(struct client_state *client);
static void start_informed(struct client_state *client);
void informed_handler(struct packet *packet, struct client_state *client);
void bound_handler(struct packet *packet, struct client_state *client);
void start_renew6(void *input);
void start_rebind6(void *input);
void do_depref(void *input);
void do_expire(void *input);
static void make_client6_options(struct client_state *client,
				 struct option_state **op,
				 struct dhc6_lease *lease, u_int8_t message);
static void script_write_params6(struct client_state *client,
				 const char *prefix,
				 struct option_state *options);
static isc_boolean_t active_prefix(struct client_state *client);

static int check_timing6(struct client_state *client, u_int8_t msg_type, 
		         char *msg_str, struct dhc6_lease *lease,
		         struct data_string *ds);

extern int onetry;
extern int stateless;

/*
 * The "best" default DUID, since we cannot predict any information
 * about the system (such as whether or not the hardware addresses are
 * integrated into the motherboard or similar), is the "LLT", link local
 * plus time, DUID. For real stateless "LL" is better.
 *
 * Once generated, this duid is stored into the state database, and
 * retained across restarts.
 *
 * For the time being, there is probably a different state database for
 * every daemon, so this winds up being a per-interface identifier...which
 * is not how it is intended.  Upcoming rearchitecting the client should
 * address this "one daemon model."
 */
void
form_duid(struct data_string *duid, const char *file, int line)
{
	struct interface_info *ip;
	int len;

	/* For now, just use the first interface on the list. */
	ip = interfaces;

	if (ip == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	if ((ip->hw_address.hlen == 0) ||
	    (ip->hw_address.hlen > sizeof(ip->hw_address.hbuf)))
		log_fatal("Impossible hardware address length at %s:%d.", MDL);

	/*
	 * 2 bytes for the 'duid type' field.
	 * 2 bytes for the 'htype' field.
	 * (not stateless) 4 bytes for the 'current time'.
	 * enough bytes for the hardware address (note that hw_address has
	 * the 'htype' on byte zero).
	 */
	len = 4 + (ip->hw_address.hlen - 1);
	if (!stateless)
		len += 4;
	if (!buffer_allocate(&duid->buffer, len, MDL))
		log_fatal("no memory for default DUID!");
	duid->data = duid->buffer->data;
	duid->len = len;

	/* Basic Link Local Address type of DUID. */
	if (!stateless) {
		putUShort(duid->buffer->data, DUID_LLT);
		putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]);
		putULong(duid->buffer->data + 4, cur_time - DUID_TIME_EPOCH);
		memcpy(duid->buffer->data + 8, ip->hw_address.hbuf + 1,
		       ip->hw_address.hlen - 1);
	} else {
		putUShort(duid->buffer->data, DUID_LL);
		putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]);
		memcpy(duid->buffer->data + 4, ip->hw_address.hbuf + 1,
		       ip->hw_address.hlen - 1);
	}
}

/*
 * Assign DHCPv6 port numbers as a client.
 */
void
dhcpv6_client_assignments(void)
{
	struct servent *ent;
	unsigned code;

	if (path_dhclient_pid == NULL)
		path_dhclient_pid = _PATH_DHCLIENT6_PID;
	if (path_dhclient_db == NULL)
		path_dhclient_db = _PATH_DHCLIENT6_DB;

	if (local_port == 0) {
		ent = getservbyname("dhcpv6-client", "udp");
		if (ent == NULL)
			local_port = htons(546);
		else
			local_port = ent->s_port;
	}

	if (remote_port == 0) {
		ent = getservbyname("dhcpv6-server", "udp");
		if (ent == NULL)
			remote_port = htons(547);
		else
			remote_port = ent->s_port;
	}

	memset(&DHCPv6DestAddr, 0, sizeof(DHCPv6DestAddr));
	DHCPv6DestAddr.sin6_family = AF_INET6;
	DHCPv6DestAddr.sin6_port = remote_port;
	inet_pton(AF_INET6, All_DHCP_Relay_Agents_and_Servers,
		  &DHCPv6DestAddr.sin6_addr);

	code = D6O_CLIENTID;
	if (!option_code_hash_lookup(&clientid_option,
				     dhcpv6_universe.code_hash, &code, 0, MDL))
		log_fatal("Unable to find the CLIENTID option definition.");

	code = D6O_ELAPSED_TIME;
	if (!option_code_hash_lookup(&elapsed_option,
				     dhcpv6_universe.code_hash, &code, 0, MDL))
		log_fatal("Unable to find the ELAPSED_TIME option definition.");

	code = D6O_IA_NA;
	if (!option_code_hash_lookup(&ia_na_option, dhcpv6_universe.code_hash,
				     &code, 0, MDL))
		log_fatal("Unable to find the IA_NA option definition.");

	code = D6O_IA_TA;
	if (!option_code_hash_lookup(&ia_ta_option, dhcpv6_universe.code_hash,
				     &code, 0, MDL))
		log_fatal("Unable to find the IA_TA option definition.");

	code = D6O_IA_PD;
	if (!option_code_hash_lookup(&ia_pd_option, dhcpv6_universe.code_hash,
				     &code, 0, MDL))
		log_fatal("Unable to find the IA_PD option definition.");

	code = D6O_IAADDR;
	if (!option_code_hash_lookup(&iaaddr_option, dhcpv6_universe.code_hash,
				     &code, 0, MDL))
		log_fatal("Unable to find the IAADDR option definition.");

	code = D6O_IAPREFIX;
	if (!option_code_hash_lookup(&iaprefix_option,
				     dhcpv6_universe.code_hash,
				     &code, 0, MDL))
		log_fatal("Unable to find the IAPREFIX option definition.");

	code = D6O_ORO;
	if (!option_code_hash_lookup(&oro_option, dhcpv6_universe.code_hash,
				     &code, 0, MDL))
		log_fatal("Unable to find the ORO option definition.");

	code = D6O_INFORMATION_REFRESH_TIME;
	if (!option_code_hash_lookup(&irt_option, dhcpv6_universe.code_hash,
				     &code, 0, MDL))
		log_fatal("Unable to find the IRT option definition.");

#ifndef __CYGWIN32__ /* XXX */
	endservent();
#endif
}

/*
 * Instead of implementing RFC3315 RAND (section 14) as a float "between"
 * -0.1 and 0.1 non-inclusive, we implement it as an integer.
 *
 * The result is expected to follow this table:
 *
 *		split range answer
 *		    - ERROR -		      base <= 0
 *		0	1   0..0	 1 <= base <= 10
 *		1	3  -1..1	11 <= base <= 20
 *		2	5  -2..2	21 <= base <= 30
 *		3	7  -3..3	31 <= base <= 40
 *		...
 *
 * XXX: For this to make sense, we really need to do timing on a
 * XXX: usec scale...we currently can assume zero for any value less than
 * XXX: 11, which are very common in early stages of transmission for most
 * XXX: messages.
 */
static TIME
dhc6_rand(TIME base)
{
	TIME rval;
	TIME range;
	TIME split;

	/*
	 * A zero or less timeout is a bad thing...we don't want to
	 * DHCP-flood anyone.
	 */
	if (base <= 0)
		log_fatal("Impossible condition at %s:%d.", MDL);

	/*
	 * The first thing we do is count how many random integers we want
	 * in either direction (best thought of as the maximum negative
	 * integer, as we will subtract this potentially from a random 0).
	 */
	split = (base - 1) / 10;

	/* Don't bother with the rest of the math if we know we'll get 0. */
	if (split == 0)
		return 0;

	/*
	 * Then we count the total number of integers in this set.  This
	 * is twice the number of integers in positive and negative
	 * directions, plus zero (-1, 0, 1 is 3, -2..2 adds 2 to 5, so forth).
	 */
	range = (split * 2) + 1;

	/* Take a random number from [0..(range-1)]. */
	rval = random();
	rval %= range;

	/* Offset it to uncover potential negative values. */
	rval -= split;

	return rval;
}

/* Initialize message exchange timers (set RT from Initial-RT). */
static void
dhc6_retrans_init(struct client_state *client)
{
	int xid;

	/* Initialize timers. */
	client->txcount = 0;
	client->RT = client->IRT + dhc6_rand(client->IRT);

	/* Generate a new random 24-bit transaction ID for this exchange. */

#if (RAND_MAX >= 0x00ffffff)
	xid = random();
#elif (RAND_MAX >= 0x0000ffff)
	xid = (random() << 16) ^ random();
#elif (RAND_MAX >= 0x000000ff)
	xid = (random() << 16) ^ (random() << 8) ^ random();
#else
# error "Random number generator of less than 8 bits not supported."
#endif

	client->dhcpv6_transaction_id[0] = (xid >> 16) & 0xff;
	client->dhcpv6_transaction_id[1] = (xid >>  8) & 0xff;
	client->dhcpv6_transaction_id[2] =  xid        & 0xff;
}

/* Advance the DHCPv6 retransmission state once. */
static void
dhc6_retrans_advance(struct client_state *client)
{
	struct timeval elapsed;

	/* elapsed = cur - start */
	elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec;
	elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec;
	if (elapsed.tv_usec < 0) {
		elapsed.tv_sec -= 1;
		elapsed.tv_usec += 1000000;
	}
	/* retrans_advance is called after consuming client->RT. */
	/* elapsed += RT */
	elapsed.tv_sec += client->RT / 100;
	elapsed.tv_usec += (client->RT % 100) * 10000;
	if (elapsed.tv_usec >= 1000000) {
		elapsed.tv_sec += 1;
		elapsed.tv_usec -= 1000000;
	}

	/*
	 * RT for each subsequent message transmission is based on the previous
	 * value of RT:
	 *
	 *    RT = 2*RTprev + RAND*RTprev
	 */
	client->RT += client->RT + dhc6_rand(client->RT);

	/*
	 * MRT specifies an upper bound on the value of RT (disregarding the
	 * randomization added by the use of RAND).  If MRT has a value of 0,
	 * there is no upper limit on the value of RT.  Otherwise:
	 *
	 *    if (RT > MRT)
	 *       RT = MRT + RAND*MRT
	 */
	if ((client->MRT != 0) && (client->RT > client->MRT))
		client->RT = client->MRT + dhc6_rand(client->MRT);

	/*
	 * Further, if there's an MRD, we should wake up upon reaching
	 * the MRD rather than at some point after it.
	 */
	if (client->MRD == 0) {
		/* Done. */
		client->txcount++;
		return;
	}
	/* elapsed += client->RT */
	elapsed.tv_sec += client->RT / 100;
	elapsed.tv_usec += (client->RT % 100) * 10000;
	if (elapsed.tv_usec >= 1000000) {
		elapsed.tv_sec += 1;
		elapsed.tv_usec -= 1000000;
	}
	if (elapsed.tv_sec >= client->MRD) {
		/*
		 * wake at RT + cur = start + MRD
		 */
		client->RT = client->MRD +
			(client->start_time.tv_sec - cur_tv.tv_sec);
		client->RT = client->RT * 100 +
			(client->start_time.tv_usec - cur_tv.tv_usec) / 10000;
	}
	client->txcount++;
}

/* Quick validation of DHCPv6 ADVERTISE packet contents. */
static int
valid_reply(struct packet *packet, struct client_state *client)
{
	struct data_string sid, cid;
	struct option_cache *oc;
	int rval = ISC_TRUE;

	memset(&sid, 0, sizeof(sid));
	memset(&cid, 0, sizeof(cid));

	if (!lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID)) {
		log_error("Response without a server identifier received.");
		rval = ISC_FALSE;
	}

	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_CLIENTID);
	if (!oc ||
	    !evaluate_option_cache(&sid, packet, NULL, client, packet->options,
				   client->sent_options, &global_scope, oc,
				   MDL)) {
		log_error("Response without a client identifier.");
		rval = ISC_FALSE;
	}

	oc = lookup_option(&dhcpv6_universe, client->sent_options,
			   D6O_CLIENTID);
	if (!oc ||
	    !evaluate_option_cache(&cid, packet, NULL, client,
				   client->sent_options, NULL, &global_scope,
				   oc, MDL)) {
		log_error("Local client identifier is missing!");
		rval = ISC_FALSE;
	}

	if (sid.len == 0 ||
	    sid.len != cid.len ||
	    memcmp(sid.data, cid.data, sid.len)) {
		log_error("Advertise with matching transaction ID, but "
			  "mismatching client id.");
		rval = ISC_FALSE;
	}

	return rval;
}

/*
 * Create a complete copy of a DHCPv6 lease structure.
 */
static struct dhc6_lease *
dhc6_dup_lease(struct dhc6_lease *lease, const char *file, int line)
{
	struct dhc6_lease *copy;
	struct dhc6_ia **insert_ia, *ia;

	copy = dmalloc(sizeof(*copy), file, line);
	if (copy == NULL) {
		log_error("Out of memory for v6 lease structure.");
		return NULL;
	}

	data_string_copy(&copy->server_id, &lease->server_id, file, line);
	copy->pref = lease->pref;

	memcpy(copy->dhcpv6_transaction_id, lease->dhcpv6_transaction_id,
	       sizeof(copy->dhcpv6_transaction_id));

	option_state_reference(&copy->options, lease->options, file, line);

	insert_ia = &copy->bindings;
	for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
		*insert_ia = dhc6_dup_ia(ia, file, line);

		if (*insert_ia == NULL) {
			dhc6_lease_destroy(&copy, file, line);
			return NULL;
		}

		insert_ia = &(*insert_ia)->next;
	}

	return copy;
}

/*
 * Duplicate an IA structure.
 */
static struct dhc6_ia *
dhc6_dup_ia(struct dhc6_ia *ia, const char *file, int line)
{
	struct dhc6_ia *copy;
	struct dhc6_addr **insert_addr, *addr;

	copy = dmalloc(sizeof(*ia), file, line);

	memcpy(copy->iaid, ia->iaid, sizeof(copy->iaid));

	copy->ia_type = ia->ia_type;
	copy->starts = ia->starts;
	copy->renew = ia->renew;
	copy->rebind = ia->rebind;

	insert_addr = &copy->addrs;
	for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
		*insert_addr = dhc6_dup_addr(addr, file, line);

		if (*insert_addr == NULL) {
			dhc6_ia_destroy(&copy, file, line);
			return NULL;
		}

		insert_addr = &(*insert_addr)->next;
	}

	if (ia->options != NULL)
		option_state_reference(&copy->options, ia->options,
				       file, line);

	return copy;
}

/*
 * Duplicate an IAADDR or IAPREFIX structure.
 */
static struct dhc6_addr *
dhc6_dup_addr(struct dhc6_addr *addr, const char *file, int line)
{
	struct dhc6_addr *copy;

	copy = dmalloc(sizeof(*addr), file, line);

	if (copy == NULL)
		return NULL;

	memcpy(&copy->address, &addr->address, sizeof(copy->address));

	copy->plen = addr->plen;
	copy->flags = addr->flags;
	copy->starts = addr->starts;
	copy->preferred_life = addr->preferred_life;
	copy->max_life = addr->max_life;

	if (addr->options != NULL)
		option_state_reference(&copy->options, addr->options,
				       file, line);

	return copy;
}

/*
 * Form a DHCPv6 lease structure based upon packet contents.  Creates and
 * populates IA's and any IAADDR/IAPREFIX's they contain.
 * Parsed options are deleted in order to not save them in the lease file.
 */
static struct dhc6_lease *
dhc6_leaseify(struct packet *packet)
{
	struct data_string ds;
	struct dhc6_lease *lease;
	struct option_cache *oc;

	lease = dmalloc(sizeof(*lease), MDL);
	if (lease == NULL) {
		log_error("Out of memory for v6 lease structure.");
		return NULL;
	}

	memcpy(lease->dhcpv6_transaction_id, packet->dhcpv6_transaction_id, 3);
	option_state_reference(&lease->options, packet->options, MDL);

	memset(&ds, 0, sizeof(ds));

	/* Determine preference (default zero). */
	oc = lookup_option(&dhcpv6_universe, lease->options, D6O_PREFERENCE);
	if (oc &&
	    evaluate_option_cache(&ds, packet, NULL, NULL, lease->options,
				  NULL, &global_scope, oc, MDL)) {
		if (ds.len != 1) {
			log_error("Invalid length of DHCPv6 Preference option "
				  "(%d != 1)", ds.len);
			data_string_forget(&ds, MDL);
			dhc6_lease_destroy(&lease, MDL);
			return NULL;
		} else {
			lease->pref = ds.data[0];
			log_debug("RCV:  X-- Preference %u.",
				  (unsigned)lease->pref);
		}

		data_string_forget(&ds, MDL);
	}
	delete_option(&dhcpv6_universe, lease->options, D6O_PREFERENCE);

	/*
	 * Dig into recursive DHCPv6 pockets for IA_NA and contained IAADDR
	 * options.
	 */
	if (dhc6_parse_ia_na(&lease->bindings, packet,
			     lease->options) != ISC_R_SUCCESS) {
		/* Error conditions are logged by the caller. */
		dhc6_lease_destroy(&lease, MDL);
		return NULL;
	}
	/*
	 * Dig into recursive DHCPv6 pockets for IA_TA and contained IAADDR
	 * options.
	 */
	if (dhc6_parse_ia_ta(&lease->bindings, packet,
			     lease->options) != ISC_R_SUCCESS) {
		/* Error conditions are logged by the caller. */
		dhc6_lease_destroy(&lease, MDL);
		return NULL;
	}
	/*
	 * Dig into recursive DHCPv6 pockets for IA_PD and contained IAPREFIX
	 * options.
	 */
	if (dhc6_parse_ia_pd(&lease->bindings, packet,
			     lease->options) != ISC_R_SUCCESS) {
		/* Error conditions are logged by the caller. */
		dhc6_lease_destroy(&lease, MDL);
		return NULL;
	}

	/*
	 * This is last because in the future we may want to make a different
	 * key based upon additional information from the packet (we may need
	 * to allow multiple leases in one client state per server, but we're
	 * not sure based on what additional keys now).
	 */
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
	if (!evaluate_option_cache(&lease->server_id, packet, NULL, NULL,
				   lease->options, NULL, &global_scope,
				   oc, MDL) ||
	    lease->server_id.len == 0) {
		/* This should be impossible due to validation checks earlier.
		 */
		log_error("Invalid SERVERID option cache.");
		dhc6_lease_destroy(&lease, MDL);
		return NULL;
	} else {
		log_debug("RCV:  X-- Server ID: %s",
			  print_hex_1(lease->server_id.len,
				      lease->server_id.data, 52));
	}

	return lease;
}

static isc_result_t
dhc6_parse_ia_na(struct dhc6_ia **pia, struct packet *packet,
		 struct option_state *options)
{
	struct data_string ds;
	struct dhc6_ia *ia;
	struct option_cache *oc;
	isc_result_t result;

	memset(&ds, 0, sizeof(ds));

	oc = lookup_option(&dhcpv6_universe, options, D6O_IA_NA);
	for ( ; oc != NULL ; oc = oc->next) {
		ia = dmalloc(sizeof(*ia), MDL);
		if (ia == NULL) {
			log_error("Out of memory allocating IA_NA structure.");
			return ISC_R_NOMEMORY;
		} else if (evaluate_option_cache(&ds, packet, NULL, NULL,
						 options, NULL,
						 &global_scope, oc, MDL) &&
			   ds.len >= 12) {
			memcpy(ia->iaid, ds.data, 4);
			ia->ia_type = D6O_IA_NA;
			ia->starts = cur_time;
			ia->renew = getULong(ds.data + 4);
			ia->rebind = getULong(ds.data + 8);

			log_debug("RCV:  X-- IA_NA %s",
				  print_hex_1(4, ia->iaid, 59));
			/* XXX: This should be the printed time I think. */
			log_debug("RCV:  | X-- starts %u",
				  (unsigned)ia->starts);
			log_debug("RCV:  | X-- t1 - renew  +%u", ia->renew);
			log_debug("RCV:  | X-- t2 - rebind +%u", ia->rebind);

			/*
			 * RFC3315 section 22.4, discard IA_NA's that
			 * have t1 greater than t2, and both not zero.
			 * Since RFC3315 defines this behaviour, it is not
			 * an error - just normal operation.
			 *
			 * Note that RFC3315 says we MUST honor these values
			 * if they are not zero.  So insane values are
			 * totally OK.
			 */
			if ((ia->renew > 0) && (ia->rebind > 0) &&
			    (ia->renew > ia->rebind)) {
				log_debug("RCV:  | !-- INVALID renew/rebind "
					  "times, IA_NA discarded.");
				dfree(ia, MDL);
				data_string_forget(&ds, MDL);
				continue;
			}

			if (ds.len > 12) {
				log_debug("RCV:  | X-- [Options]");

				if (!option_state_allocate(&ia->options,
							   MDL)) {
					log_error("Out of memory allocating "
						  "IA_NA option state.");
					dfree(ia, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_NOMEMORY;
				}

				if (!parse_option_buffer(ia->options,
							 ds.data + 12,
							 ds.len - 12,
							 &dhcpv6_universe)) {
					log_error("Corrupt IA_NA options.");
					option_state_dereference(&ia->options,
								 MDL);
					dfree(ia, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_BADPARSE;
				}
			}
			data_string_forget(&ds, MDL);

			if (ia->options != NULL) {
				result = dhc6_parse_addrs(&ia->addrs, packet,
							  ia->options);
				if (result != ISC_R_SUCCESS) {
					option_state_dereference(&ia->options,
								 MDL);
					dfree(ia, MDL);
					return result;
				}
			}

			while (*pia != NULL)
				pia = &(*pia)->next;
			*pia = ia;
			pia = &ia->next;
		} else {
			log_error("Invalid IA_NA option cache.");
			dfree(ia, MDL);
			if (ds.len != 0)
				data_string_forget(&ds, MDL);
			return ISC_R_UNEXPECTED;
		}
	}
	delete_option(&dhcpv6_universe, options, D6O_IA_NA);

	return ISC_R_SUCCESS;
}

static isc_result_t
dhc6_parse_ia_ta(struct dhc6_ia **pia, struct packet *packet,
		 struct option_state *options)
{
	struct data_string ds;
	struct dhc6_ia *ia;
	struct option_cache *oc;
	isc_result_t result;

	memset(&ds, 0, sizeof(ds));

	oc = lookup_option(&dhcpv6_universe, options, D6O_IA_TA);
	for ( ; oc != NULL ; oc = oc->next) {
		ia = dmalloc(sizeof(*ia), MDL);
		if (ia == NULL) {
			log_error("Out of memory allocating IA_TA structure.");
			return ISC_R_NOMEMORY;
		} else if (evaluate_option_cache(&ds, packet, NULL, NULL,
						 options, NULL,
						 &global_scope, oc, MDL) &&
			   ds.len >= 4) {
			memcpy(ia->iaid, ds.data, 4);
			ia->ia_type = D6O_IA_TA;
			ia->starts = cur_time;

			log_debug("RCV:  X-- IA_TA %s",
				  print_hex_1(4, ia->iaid, 59));
			/* XXX: This should be the printed time I think. */
			log_debug("RCV:  | X-- starts %u",
				  (unsigned)ia->starts);

			if (ds.len > 4) {
				log_debug("RCV:  | X-- [Options]");

				if (!option_state_allocate(&ia->options,
							   MDL)) {
					log_error("Out of memory allocating "
						  "IA_TA option state.");
					dfree(ia, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_NOMEMORY;
				}

				if (!parse_option_buffer(ia->options,
							 ds.data + 4,
							 ds.len - 4,
							 &dhcpv6_universe)) {
					log_error("Corrupt IA_TA options.");
					option_state_dereference(&ia->options,
								 MDL);
					dfree(ia, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_BADPARSE;
				}
			}
			data_string_forget(&ds, MDL);

			if (ia->options != NULL) {
				result = dhc6_parse_addrs(&ia->addrs, packet,
							  ia->options);
				if (result != ISC_R_SUCCESS) {
					option_state_dereference(&ia->options,
								 MDL);
					dfree(ia, MDL);
					return result;
				}
			}

			while (*pia != NULL)
				pia = &(*pia)->next;
			*pia = ia;
			pia = &ia->next;
		} else {
			log_error("Invalid IA_TA option cache.");
			dfree(ia, MDL);
			if (ds.len != 0)
				data_string_forget(&ds, MDL);
			return ISC_R_UNEXPECTED;
		}
	}
	delete_option(&dhcpv6_universe, options, D6O_IA_TA);

	return ISC_R_SUCCESS;
}

static isc_result_t
dhc6_parse_ia_pd(struct dhc6_ia **pia, struct packet *packet,
		 struct option_state *options)
{
	struct data_string ds;
	struct dhc6_ia *ia;
	struct option_cache *oc;
	isc_result_t result;

	memset(&ds, 0, sizeof(ds));

	oc = lookup_option(&dhcpv6_universe, options, D6O_IA_PD);
	for ( ; oc != NULL ; oc = oc->next) {
		ia = dmalloc(sizeof(*ia), MDL);
		if (ia == NULL) {
			log_error("Out of memory allocating IA_PD structure.");
			return ISC_R_NOMEMORY;
		} else if (evaluate_option_cache(&ds, packet, NULL, NULL,
						 options, NULL,
						 &global_scope, oc, MDL) &&
			   ds.len >= 12) {
			memcpy(ia->iaid, ds.data, 4);
			ia->ia_type = D6O_IA_PD;
			ia->starts = cur_time;
			ia->renew = getULong(ds.data + 4);
			ia->rebind = getULong(ds.data + 8);

			log_debug("RCV:  X-- IA_PD %s",
				  print_hex_1(4, ia->iaid, 59));
			/* XXX: This should be the printed time I think. */
			log_debug("RCV:  | X-- starts %u",
				  (unsigned)ia->starts);
			log_debug("RCV:  | X-- t1 - renew  +%u", ia->renew);
			log_debug("RCV:  | X-- t2 - rebind +%u", ia->rebind);

			/*
			 * RFC3633 section 9, discard IA_PD's that
			 * have t1 greater than t2, and both not zero.
			 * Since RFC3633 defines this behaviour, it is not
			 * an error - just normal operation.
			 */
			if ((ia->renew > 0) && (ia->rebind > 0) &&
			    (ia->renew > ia->rebind)) {
				log_debug("RCV:  | !-- INVALID renew/rebind "
					  "times, IA_PD discarded.");
				dfree(ia, MDL);
				data_string_forget(&ds, MDL);
				continue;
			}

			if (ds.len > 12) {
				log_debug("RCV:  | X-- [Options]");

				if (!option_state_allocate(&ia->options,
							   MDL)) {
					log_error("Out of memory allocating "
						  "IA_PD option state.");
					dfree(ia, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_NOMEMORY;
				}

				if (!parse_option_buffer(ia->options,
							 ds.data + 12,
							 ds.len - 12,
							 &dhcpv6_universe)) {
					log_error("Corrupt IA_PD options.");
					option_state_dereference(&ia->options,
								 MDL);
					dfree(ia, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_BADPARSE;
				}
			}
			data_string_forget(&ds, MDL);

			if (ia->options != NULL) {
				result = dhc6_parse_prefixes(&ia->addrs,
							     packet,
							     ia->options);
				if (result != ISC_R_SUCCESS) {
					option_state_dereference(&ia->options,
								 MDL);
					dfree(ia, MDL);
					return result;
				}
			}

			while (*pia != NULL)
				pia = &(*pia)->next;
			*pia = ia;
			pia = &ia->next;
		} else {
			log_error("Invalid IA_PD option cache.");
			dfree(ia, MDL);
			if (ds.len != 0)
				data_string_forget(&ds, MDL);
			return ISC_R_UNEXPECTED;
		}
	}
	delete_option(&dhcpv6_universe, options, D6O_IA_PD);

	return ISC_R_SUCCESS;
}


static isc_result_t
dhc6_parse_addrs(struct dhc6_addr **paddr, struct packet *packet,
		 struct option_state *options)
{
	struct data_string ds;
	struct option_cache *oc;
	struct dhc6_addr *addr;

	memset(&ds, 0, sizeof(ds));

	oc = lookup_option(&dhcpv6_universe, options, D6O_IAADDR);
	for ( ; oc != NULL ; oc = oc->next) {
		addr = dmalloc(sizeof(*addr), MDL);
		if (addr == NULL) {
			log_error("Out of memory allocating "
				  "address structure.");
			return ISC_R_NOMEMORY;
		} else if (evaluate_option_cache(&ds, packet, NULL, NULL,
						 options, NULL, &global_scope,
						 oc, MDL) &&
			   (ds.len >= 24)) {

			addr->address.len = 16;
			memcpy(addr->address.iabuf, ds.data, 16);
			addr->starts = cur_time;
			addr->preferred_life = getULong(ds.data + 16);
			addr->max_life = getULong(ds.data + 20);

			log_debug("RCV:  | | X-- IAADDR %s",
				  piaddr(addr->address));
			log_debug("RCV:  | | | X-- Preferred lifetime %u.",
				  addr->preferred_life);
			log_debug("RCV:  | | | X-- Max lifetime %u.",
				  addr->max_life);

			/*
			 * RFC 3315 section 22.6 says we must discard
			 * addresses whose pref is later than valid.
			 */
			if ((addr->preferred_life > addr->max_life)) {
				log_debug("RCV:  | | | !-- INVALID lifetimes, "
					  "IAADDR discarded.  Check your "
					  "server configuration.");
				dfree(addr, MDL);
				data_string_forget(&ds, MDL);
				continue;
			}

			/*
			 * Fortunately this is the last recursion in the
			 * protocol.
			 */
			if (ds.len > 24) {
				if (!option_state_allocate(&addr->options,
							   MDL)) {
					log_error("Out of memory allocating "
						  "IAADDR option state.");
					dfree(addr, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_NOMEMORY;
				}

				if (!parse_option_buffer(addr->options,
							 ds.data + 24,
							 ds.len - 24,
							 &dhcpv6_universe)) {
					log_error("Corrupt IAADDR options.");
					option_state_dereference(&addr->options,
								 MDL);
					dfree(addr, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_BADPARSE;
				}
			}

			if (addr->options != NULL)
				log_debug("RCV:  | | | X-- "
					  "[Options]");

			data_string_forget(&ds, MDL);

			*paddr = addr;
			paddr = &addr->next;
		} else {
			log_error("Invalid IAADDR option cache.");
			dfree(addr, MDL);
			if (ds.len != 0)
				data_string_forget(&ds, MDL);
			return ISC_R_UNEXPECTED;
		}
	}
	delete_option(&dhcpv6_universe, options, D6O_IAADDR);

	return ISC_R_SUCCESS;
}

static isc_result_t
dhc6_parse_prefixes(struct dhc6_addr **ppfx, struct packet *packet,
		    struct option_state *options)
{
	struct data_string ds;
	struct option_cache *oc;
	struct dhc6_addr *pfx;

	memset(&ds, 0, sizeof(ds));

	oc = lookup_option(&dhcpv6_universe, options, D6O_IAPREFIX);
	for ( ; oc != NULL ; oc = oc->next) {
		pfx = dmalloc(sizeof(*pfx), MDL);
		if (pfx == NULL) {
			log_error("Out of memory allocating "
				  "prefix structure.");
			return ISC_R_NOMEMORY;
		} else if (evaluate_option_cache(&ds, packet, NULL, NULL,
						 options, NULL, &global_scope,
						 oc, MDL) &&
			   (ds.len >= 25)) {

			pfx->preferred_life = getULong(ds.data);
			pfx->max_life = getULong(ds.data + 4);
			pfx->plen = getUChar(ds.data + 8);
			pfx->address.len = 16;
			memcpy(pfx->address.iabuf, ds.data + 9, 16);
			pfx->starts = cur_time;

			log_debug("RCV:  | | X-- IAPREFIX %s/%d",
				  piaddr(pfx->address), (int)pfx->plen);
			log_debug("RCV:  | | | X-- Preferred lifetime %u.",
				  pfx->preferred_life);
			log_debug("RCV:  | | | X-- Max lifetime %u.",
				  pfx->max_life);

			/* Sanity check over the prefix length */
			if ((pfx->plen < 4) || (pfx->plen > 128)) {
				log_debug("RCV:  | | | !-- INVALID prefix "
					  "length, IAPREFIX discarded.  "
					  "Check your server configuration.");
				dfree(pfx, MDL);
				data_string_forget(&ds, MDL);
				continue;
			}
			/*
			 * RFC 3633 section 10 says we must discard
			 * prefixes whose pref is later than valid.
			 */
			if ((pfx->preferred_life > pfx->max_life)) {
				log_debug("RCV:  | | | !-- INVALID lifetimes, "
					  "IAPREFIX discarded.  Check your "
					  "server configuration.");
				dfree(pfx, MDL);
				data_string_forget(&ds, MDL);
				continue;
			}

			/*
			 * Fortunately this is the last recursion in the
			 * protocol.
			 */
			if (ds.len > 25) {
				if (!option_state_allocate(&pfx->options,
							   MDL)) {
					log_error("Out of memory allocating "
						  "IAPREFIX option state.");
					dfree(pfx, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_NOMEMORY;
				}

				if (!parse_option_buffer(pfx->options,
							 ds.data + 25,
							 ds.len - 25,
							 &dhcpv6_universe)) {
					log_error("Corrupt IAPREFIX options.");
					option_state_dereference(&pfx->options,
								 MDL);
					dfree(pfx, MDL);
					data_string_forget(&ds, MDL);
					return ISC_R_BADPARSE;
				}
			}

			if (pfx->options != NULL)
				log_debug("RCV:  | | | X-- "
					  "[Options]");

			data_string_forget(&ds, MDL);

			*ppfx = pfx;
			ppfx = &pfx->next;
		} else {
			log_error("Invalid IAPREFIX option cache.");
			dfree(pfx, MDL);
			if (ds.len != 0)
				data_string_forget(&ds, MDL);
			return ISC_R_UNEXPECTED;
		}
	}
	delete_option(&dhcpv6_universe, options, D6O_IAPREFIX);

	return ISC_R_SUCCESS;
}

/* Clean up a lease object, deallocate all its parts, and set it to NULL. */
void
dhc6_lease_destroy(struct dhc6_lease **src, const char *file, int line)
{
	struct dhc6_ia *ia, *nia;
	struct dhc6_lease *lease;

	if (src == NULL || *src == NULL) {
		log_error("Attempt to destroy null lease.");
		return;
	}
	lease = *src;

	if (lease->server_id.len != 0)
		data_string_forget(&lease->server_id, file, line);

	for (ia = lease->bindings ; ia != NULL ; ia = nia) {
		nia = ia->next;

		dhc6_ia_destroy(&ia, file, line);
	}

	if (lease->options != NULL)
		option_state_dereference(&lease->options, file, line);

	dfree(lease, file, line);
	*src = NULL;
}

/*
 * Traverse the addresses list, and destroy their contents, and NULL the
 * list pointer.
 */
static void
dhc6_ia_destroy(struct dhc6_ia **src, const char *file, int line)
{
	struct dhc6_addr *addr, *naddr;
	struct dhc6_ia *ia;

	if (src == NULL || *src == NULL) {
		log_error("Attempt to destroy null IA.");
		return;
	}
	ia = *src;

	for (addr = ia->addrs ; addr != NULL ; addr = naddr) {
		naddr = addr->next;

		if (addr->options != NULL)
			option_state_dereference(&addr->options, file, line);

		dfree(addr, file, line);
	}

	if (ia->options != NULL)
		option_state_dereference(&ia->options, file, line);

	dfree(ia, file, line);
	*src = NULL;
}

/*
 * For a given lease, insert it into the tail of the lease list.  Upon
 * finding a duplicate by server id, remove it and take over its position.
 */
static void
insert_lease(struct dhc6_lease **head, struct dhc6_lease *new)
{
	while (*head != NULL) {
		if ((*head)->server_id.len == new->server_id.len &&
		    memcmp((*head)->server_id.data, new->server_id.data,
			   new->server_id.len) == 0) {
			new->next = (*head)->next;
			dhc6_lease_destroy(head, MDL);
			break;
		}

		head= &(*head)->next;
	}

	*head = new;
	return;
}

/*
 * Not really clear what to do here yet.
 */
static int
dhc6_score_lease(struct client_state *client, struct dhc6_lease *lease)
{
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;
	struct option **req;
	int i;

	if (lease->score)
		return lease->score;

	lease->score = 1;

	/* If this lease lacks a required option, dump it. */
	/* XXX: we should be able to cache the failure... */
	req = client->config->required_options;
	if (req != NULL) {
		for (i = 0 ; req[i] != NULL ; i++) {
			if (lookup_option(&dhcpv6_universe, lease->options,
					  req[i]->code) == NULL) {
				lease->score = 0;
				return lease->score;
			}
		}
	}

	/* If this lease contains a requested option, improve its score. */
	req = client->config->requested_options;
	if (req != NULL) {
		for (i = 0 ; req[i] != NULL ; i++) {
			if (lookup_option(&dhcpv6_universe, lease->options,
					  req[i]->code) != NULL)
				lease->score++;
		}
	}

	for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
		lease->score += 50;

		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			lease->score += 100;
		}
	}

	return lease->score;
}

/*
 * start_init6() kicks off the process, transmitting a packet and
 * scheduling a retransmission event.
 */
void
start_init6(struct client_state *client)
{
	struct timeval tv;

	log_debug("PRC: Soliciting for leases (INIT).");
	client->state = S_INIT;

	/* Initialize timers, RFC3315 section 17.1.2. */
	client->IRT = SOL_TIMEOUT * 100;
	client->MRT = SOL_MAX_RT * 100;
	client->MRC = 0;
	/* Default is 0 (no max) but -1 changes this. */
	if (!onetry)
		client->MRD = 0;
	else
		client->MRD = client->config->timeout;

	dhc6_retrans_init(client);

	/*
	 * RFC3315 section 17.1.2 goes out of its way:
	 * Also, the first RT MUST be selected to be strictly greater than IRT
	 * by choosing RAND to be strictly greater than 0.
	 */
	/* if RAND < 0 then RAND = -RAND */
	if (client->RT <= client->IRT)
		client->RT = client->IRT + (client->IRT - client->RT);
	/* if RAND == 0 then RAND = 1 */
	if (client->RT <= client->IRT)
		client->RT = client->IRT + 1;

	client->v6_handler = init_handler;

	/*
	 * RFC3315 section 17.1.2 says we MUST start the first packet
	 * between 0 and SOL_MAX_DELAY seconds.  The good news is
	 * SOL_MAX_DELAY is 1.
	 */
	tv.tv_sec = cur_tv.tv_sec;
	tv.tv_usec = cur_tv.tv_usec;
	tv.tv_usec += (random() % (SOL_MAX_DELAY * 100)) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_init6, client, NULL, NULL);

	if (nowait)
		go_daemon();
}

/*
 * start_info_request6() kicks off the process, transmitting an info
 * request packet and scheduling a retransmission event.
 */
void
start_info_request6(struct client_state *client)
{
	struct timeval tv;

	log_debug("PRC: Requesting information (INIT).");
	client->state = S_INIT;

	/* Initialize timers, RFC3315 section 18.1.5. */
	client->IRT = INF_TIMEOUT * 100;
	client->MRT = INF_MAX_RT * 100;
	client->MRC = 0;
	/* Default is 0 (no max) but -1 changes this. */
	if (!onetry)
		client->MRD = 0;
	else
		client->MRD = client->config->timeout;

	dhc6_retrans_init(client);

	client->v6_handler = info_request_handler;

	/*
	 * RFC3315 section 18.1.5 says we MUST start the first packet
	 * between 0 and INF_MAX_DELAY seconds.  The good news is
	 * INF_MAX_DELAY is 1.
	 */
	tv.tv_sec = cur_tv.tv_sec;
	tv.tv_usec = cur_tv.tv_usec;
	tv.tv_usec += (random() % (INF_MAX_DELAY * 100)) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_info_request6, client, NULL, NULL);

	if (nowait)
		go_daemon();
}

/*
 * start_confirm6() kicks off an "init-reboot" version of the process, at
 * startup to find out if old bindings are 'fair' and at runtime whenever
 * a link cycles state we'll eventually want to do this.
 */
void
start_confirm6(struct client_state *client)
{
	struct timeval tv;

	/* If there is no active lease, there is nothing to check. */
	if ((client->active_lease == NULL) ||
	    !active_prefix(client) ||
	    client->active_lease->released) {
		start_init6(client);
		return;
	}

	log_debug("PRC: Confirming active lease (INIT-REBOOT).");
	client->state = S_REBOOTING;

	/* Initialize timers, RFC3315 section 17.1.3. */
	client->IRT = CNF_TIMEOUT * 100;
	client->MRT = CNF_MAX_RT * 100;
	client->MRC = 0;
	client->MRD = CNF_MAX_RD;

	dhc6_retrans_init(client);

	client->v6_handler = reply_handler;

	/*
	 * RFC3315 section 18.1.2 says we MUST start the first packet
	 * between 0 and CNF_MAX_DELAY seconds.  The good news is
	 * CNF_MAX_DELAY is 1.
	 */
	tv.tv_sec = cur_tv.tv_sec;
	tv.tv_usec = cur_tv.tv_usec;
	tv.tv_usec += (random() % (CNF_MAX_DELAY * 100)) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	if (wanted_ia_pd != 0) {
		client->state = S_REBINDING;
		client->refresh_type = DHCPV6_REBIND;
		add_timeout(&tv, do_refresh6, client, NULL, NULL);
	} else
		add_timeout(&tv, do_confirm6, client, NULL, NULL);
}

/*
 * check_timing6() check on the timing for sending a v6 message
 * and then do the basic initialization for a v6 message.
 */
#define CHK_TIM_SUCCESS		0
#define CHK_TIM_MRC_EXCEEDED	1
#define CHK_TIM_MRD_EXCEEDED	2
#define CHK_TIM_ALLOC_FAILURE	3

int
check_timing6 (struct client_state *client, u_int8_t msg_type, 
	       char *msg_str, struct dhc6_lease *lease,
	       struct data_string *ds)
{
	struct timeval elapsed;

	/*
	 * Start_time starts at the first transmission.
	 */
	if (client->txcount == 0) {
		client->start_time.tv_sec = cur_tv.tv_sec;
		client->start_time.tv_usec = cur_tv.tv_usec;
	} else if ((client->MRC != 0) && (client->txcount > client->MRC)) {
		log_info("Max retransmission count exceeded.");
		return(CHK_TIM_MRC_EXCEEDED);
	}

	/* elapsed = cur - start */
	elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec;
	elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec;
	if (elapsed.tv_usec < 0) {
		elapsed.tv_sec -= 1;
		elapsed.tv_usec += 1000000;
	}

	/* Check if finished (-1 argument). */
	if ((client->MRD != 0) && (elapsed.tv_sec > client->MRD)) {
		log_info("Max retransmission duration exceeded.");
		return(CHK_TIM_MRD_EXCEEDED);
	}

	memset(ds, 0, sizeof(*ds));
	if (!buffer_allocate(&(ds->buffer), 4, MDL)) {
		log_error("Unable to allocate memory for %s.", msg_str);
		return(CHK_TIM_ALLOC_FAILURE);
	}
	ds->data = ds->buffer->data;
	ds->len = 4;

	ds->buffer->data[0] = msg_type;
	memcpy(ds->buffer->data + 1, client->dhcpv6_transaction_id, 3);

	/* Form an elapsed option. */
	/* Maximum value is 65535 1/100s coded as 0xffff. */
	if ((elapsed.tv_sec < 0) || (elapsed.tv_sec > 655) ||
	    ((elapsed.tv_sec == 655) && (elapsed.tv_usec > 350000))) {
		client->elapsed = 0xffff;
	} else {
		client->elapsed = elapsed.tv_sec * 100;
		client->elapsed += elapsed.tv_usec / 10000;
	}

	if (client->elapsed == 0)
		log_debug("XMT: Forming %s, 0 ms elapsed.", msg_str);
	else
		log_debug("XMT: Forming %s, %u0 ms elapsed.", msg_str,
			  (unsigned)client->elapsed);

	client->elapsed = htons(client->elapsed);

	make_client6_options(client, &client->sent_options, lease, msg_type);

	return(CHK_TIM_SUCCESS);
}

/*
 * do_init6() marshals and transmits a solicit.
 */
void
do_init6(void *input)
{
	struct client_state *client;
	struct dhc6_ia *old_ia;
	struct dhc6_addr *old_addr;
	struct data_string ds;
	struct data_string ia;
	struct data_string addr;
	struct timeval tv;
	u_int32_t t1, t2;
	int i, idx, len, send_ret;

	client = input;

	/*
	 * In RFC3315 section 17.1.2, the retransmission timer is
	 * used as the selecting timer.
	 */
	if (client->advertised_leases != NULL) {
		start_selecting6(client);
		return;
	}

	switch(check_timing6(client, DHCPV6_SOLICIT, "Solicit", NULL, &ds)) {
	      case CHK_TIM_MRC_EXCEEDED:
	      case CHK_TIM_ALLOC_FAILURE:
		return;
	      case CHK_TIM_MRD_EXCEEDED:
		client->state = S_STOPPED;
		if (client->active_lease != NULL) {
			dhc6_lease_destroy(&client->active_lease, MDL);
			client->active_lease = NULL;
		}
		/* Stop if and only if this is the last client. */
		if (stopping_finished())
			exit(2);
		return;
	}

	/*
	 * Fetch any configured 'sent' options (includes DUID) in wire format.
	 */
	dhcpv6_universe.encapsulate(&ds, NULL, NULL, client,
				    NULL, client->sent_options, &global_scope,
				    &dhcpv6_universe);

	/* Use a specific handler with rapid-commit. */
	if (lookup_option(&dhcpv6_universe, client->sent_options,
			  D6O_RAPID_COMMIT) != NULL) {
		client->v6_handler = rapid_commit_handler;
	}

	/* Append IA_NA. */
	for (i = 0; i < wanted_ia_na; i++) {
		/*
		 * XXX: maybe the IA_NA('s) should be put into the sent_options
		 * cache.  They'd have to be pulled down as they also contain
		 * different option caches in the same universe...
		 */
		memset(&ia, 0, sizeof(ia));
		if (!buffer_allocate(&ia.buffer, 12, MDL)) {
			log_error("Unable to allocate memory for IA_NA.");
			data_string_forget(&ds, MDL);
			return;
		}
		ia.data = ia.buffer->data;
		ia.len = 12;

		/*
		 * A simple IAID is the last 4 bytes
		 * of the hardware address.
		 */
		if (client->interface->hw_address.hlen > 4) {
			idx = client->interface->hw_address.hlen - 4;
			len = 4;
		} else {
			idx = 0;
			len = client->interface->hw_address.hlen;
		}
		memcpy(ia.buffer->data,
		       client->interface->hw_address.hbuf + idx,
		       len);
		if (i)
			ia.buffer->data[3] += i;

		t1 = client->config->requested_lease / 2;
		t2 = t1 + (t1 / 2);
		putULong(ia.buffer->data + 4, t1);
		putULong(ia.buffer->data + 8, t2);

		log_debug("XMT:  X-- IA_NA %s",
			  print_hex_1(4, ia.buffer->data, 55));
		log_debug("XMT:  | X-- Request renew in  +%u", (unsigned)t1);
		log_debug("XMT:  | X-- Request rebind in +%u", (unsigned)t2);

		if ((client->active_lease != NULL) &&
		    ((old_ia = find_ia(client->active_lease->bindings,
				       D6O_IA_NA,
				       (char *)ia.buffer->data)) != NULL)) {
			/*
			 * For each address in the old IA_NA,
			 * request a binding.
			 */
			memset(&addr, 0, sizeof(addr));
			for (old_addr = old_ia->addrs ; old_addr != NULL ;
			     old_addr = old_addr->next) {
				if (old_addr->address.len != 16) {
					log_error("Invalid IPv6 address "
						  "length %d.  "
						  "Ignoring.  (%s:%d)",
						  old_addr->address.len,
						  MDL);
					continue;
				}

				if (!buffer_allocate(&addr.buffer, 24, MDL)) {
					log_error("Unable to allocate memory "
						  "for IAADDR.");
					data_string_forget(&ia, MDL);
					data_string_forget(&ds, MDL);
					return;
				}
				addr.data = addr.buffer->data;
				addr.len = 24;

				memcpy(addr.buffer->data,
				       old_addr->address.iabuf,
				       16);

				t1 = client->config->requested_lease;
				t2 = t1 + (t1 / 2);
				putULong(addr.buffer->data + 16, t1);
				putULong(addr.buffer->data + 20, t2);

				log_debug("XMT:  | X-- Request address %s.",
					  piaddr(old_addr->address));
				log_debug("XMT:  | | X-- Request "
					  "preferred in +%u",
					  (unsigned)t1);
				log_debug("XMT:  | | X-- Request valid "
					  "in     +%u",
					  (unsigned)t2);

				append_option(&ia, &dhcpv6_universe,
					      iaaddr_option,
					      &addr);

				data_string_forget(&addr, MDL);
			}
		}

		append_option(&ds, &dhcpv6_universe, ia_na_option, &ia);
		data_string_forget(&ia, MDL);
	}

	/* Append IA_TA. */
	for (i = 0; i < wanted_ia_ta; i++) {
		/*
		 * XXX: maybe the IA_TA('s) should be put into the sent_options
		 * cache.  They'd have to be pulled down as they also contain
		 * different option caches in the same universe...
		 */
		memset(&ia, 0, sizeof(ia));
		if (!buffer_allocate(&ia.buffer, 4, MDL)) {
			log_error("Unable to allocate memory for IA_TA.");
			data_string_forget(&ds, MDL);
			return;
		}
		ia.data = ia.buffer->data;
		ia.len = 4;

		/*
		 * A simple IAID is the last 4 bytes
		 * of the hardware address.
		 */
		if (client->interface->hw_address.hlen > 4) {
			idx = client->interface->hw_address.hlen - 4;
			len = 4;
		} else {
			idx = 0;
			len = client->interface->hw_address.hlen;
		}
		memcpy(ia.buffer->data,
		       client->interface->hw_address.hbuf + idx,
		       len);
		if (i)
			ia.buffer->data[3] += i;

		log_debug("XMT:  X-- IA_TA %s",
			  print_hex_1(4, ia.buffer->data, 55));

		if ((client->active_lease != NULL) &&
		    ((old_ia = find_ia(client->active_lease->bindings,
				       D6O_IA_TA,
				       (char *)ia.buffer->data)) != NULL)) {
			/*
			 * For each address in the old IA_TA,
			 * request a binding.
			 */
			memset(&addr, 0, sizeof(addr));
			for (old_addr = old_ia->addrs ; old_addr != NULL ;
			     old_addr = old_addr->next) {
				if (old_addr->address.len != 16) {
					log_error("Invalid IPv6 address "
						  "length %d.  "
						  "Ignoring.  (%s:%d)",
						  old_addr->address.len,
						  MDL);
					continue;
				}

				if (!buffer_allocate(&addr.buffer, 24, MDL)) {
					log_error("Unable to allocate memory "
						  "for IAADDR.");
					data_string_forget(&ia, MDL);
					data_string_forget(&ds, MDL);
					return;
				}
				addr.data = addr.buffer->data;
				addr.len = 24;

				memcpy(addr.buffer->data,
				       old_addr->address.iabuf,
				       16);

				t1 = client->config->requested_lease;
				t2 = t1 + (t1 / 2);
				putULong(addr.buffer->data + 16, t1);
				putULong(addr.buffer->data + 20, t2);

				log_debug("XMT:  | X-- Request address %s.",
					  piaddr(old_addr->address));
				log_debug("XMT:  | | X-- Request "
					  "preferred in +%u",
					  (unsigned)t1);
				log_debug("XMT:  | | X-- Request valid "
					  "in     +%u",
					  (unsigned)t2);

				append_option(&ia, &dhcpv6_universe,
					      iaaddr_option,
					      &addr);

				data_string_forget(&addr, MDL);
			}
		}

		append_option(&ds, &dhcpv6_universe, ia_ta_option, &ia);
		data_string_forget(&ia, MDL);
	}

	/* Append IA_PD. */
	for (i = 0; i < wanted_ia_pd; i++) {
		/*
		 * XXX: maybe the IA_PD('s) should be put into the sent_options
		 * cache.  They'd have to be pulled down as they also contain
		 * different option caches in the same universe...
		 */
		memset(&ia, 0, sizeof(ia));
		if (!buffer_allocate(&ia.buffer, 12, MDL)) {
			log_error("Unable to allocate memory for IA_PD.");
			data_string_forget(&ds, MDL);
			return;
		}
		ia.data = ia.buffer->data;
		ia.len = 12;

		/*
		 * A simple IAID is the last 4 bytes
		 * of the hardware address.
		 */
		if (client->interface->hw_address.hlen > 4) {
			idx = client->interface->hw_address.hlen - 4;
			len = 4;
		} else {
			idx = 0;
			len = client->interface->hw_address.hlen;
		}
		memcpy(ia.buffer->data,
		       client->interface->hw_address.hbuf + idx,
		       len);
		if (i)
			ia.buffer->data[3] += i;

		t1 = client->config->requested_lease / 2;
		t2 = t1 + (t1 / 2);
		putULong(ia.buffer->data + 4, t1);
		putULong(ia.buffer->data + 8, t2);

		log_debug("XMT:  X-- IA_PD %s",
			  print_hex_1(4, ia.buffer->data, 55));
		log_debug("XMT:  | X-- Request renew in  +%u", (unsigned)t1);
		log_debug("XMT:  | X-- Request rebind in +%u", (unsigned)t2);

		if ((client->active_lease != NULL) &&
		    ((old_ia = find_ia(client->active_lease->bindings,
				       D6O_IA_PD,
				       (char *)ia.buffer->data)) != NULL)) {
			/*
			 * For each prefix in the old IA_PD,
			 * request a binding.
			 */
			memset(&addr, 0, sizeof(addr));
			for (old_addr = old_ia->addrs ; old_addr != NULL ;
			     old_addr = old_addr->next) {
				if (old_addr->address.len != 16) {
					log_error("Invalid IPv6 prefix, "
						  "Ignoring.  (%s:%d)",
						  MDL);
					continue;
				}

				if (!buffer_allocate(&addr.buffer, 25, MDL)) {
					log_error("Unable to allocate memory "
						  "for IAPREFIX.");
					data_string_forget(&ia, MDL);
					data_string_forget(&ds, MDL);
					return;
				}
				addr.data = addr.buffer->data;
				addr.len = 25;

				t1 = client->config->requested_lease;
				t2 = t1 + (t1 / 2);
				putULong(addr.buffer->data, t1);
				putULong(addr.buffer->data + 4, t2);

				putUChar(addr.buffer->data + 8,
					 old_addr->plen);
				memcpy(addr.buffer->data + 9,
				       old_addr->address.iabuf,
				       16);

				log_debug("XMT:  | X-- Request prefix %s/%u.",
					  piaddr(old_addr->address),
					  (unsigned) old_addr->plen);
				log_debug("XMT:  | | X-- Request "
					  "preferred in +%u",
					  (unsigned)t1);
				log_debug("XMT:  | | X-- Request valid "
					  "in     +%u",
					  (unsigned)t2);

				append_option(&ia, &dhcpv6_universe,
					      iaprefix_option,
					      &addr);

				data_string_forget(&addr, MDL);
			}
		}

		append_option(&ds, &dhcpv6_universe, ia_pd_option, &ia);
		data_string_forget(&ia, MDL);
	}

	/* Transmit and wait. */

	log_info("XMT: Solicit on %s, interval %ld0ms.",
		 client->name ? client->name : client->interface->name,
		 (long int)client->RT);

	send_ret = send_packet6(client->interface,
				ds.data, ds.len, &DHCPv6DestAddr);
	if (send_ret != ds.len) {
		log_error("dhc6: send_packet6() sent %d of %d bytes",
			  send_ret, ds.len);
	}

	data_string_forget(&ds, MDL);

	/* Wait RT */
	tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
	tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_init6, client, NULL, NULL);

	dhc6_retrans_advance(client);
}

/* do_info_request6() marshals and transmits an information-request. */
void
do_info_request6(void *input)
{
	struct client_state *client;
	struct data_string ds;
	struct timeval tv;
	int send_ret;

	client = input;

	switch(check_timing6(client, DHCPV6_INFORMATION_REQUEST,
			     "Info-Request", NULL, &ds)) {
	      case CHK_TIM_MRC_EXCEEDED:
	      case CHK_TIM_ALLOC_FAILURE:
		return;
	      case CHK_TIM_MRD_EXCEEDED:
		exit(2);
	      case CHK_TIM_SUCCESS:
		break;
	}

	/* Fetch any configured 'sent' options (includes DUID) in wire format.
	 */
	dhcpv6_universe.encapsulate(&ds, NULL, NULL, client,
				    NULL, client->sent_options, &global_scope,
				    &dhcpv6_universe);

	/* Transmit and wait. */

	log_info("XMT: Info-Request on %s, interval %ld0ms.",
		 client->name ? client->name : client->interface->name,
		 (long int)client->RT);

	send_ret = send_packet6(client->interface,
				ds.data, ds.len, &DHCPv6DestAddr);
	if (send_ret != ds.len) {
		log_error("dhc6: send_packet6() sent %d of %d bytes",
			  send_ret, ds.len);
	}

	data_string_forget(&ds, MDL);

	/* Wait RT */
	tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
	tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_info_request6, client, NULL, NULL);

	dhc6_retrans_advance(client);
}

/* do_confirm6() creates a Confirm packet and transmits it.  This function
 * is called on every timeout to (re)transmit.
 */
void
do_confirm6(void *input)
{
	struct client_state *client;
	struct data_string ds;
	int send_ret;
	struct timeval tv;

	client = input;

	if (client->active_lease == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	/* In section 17.1.3, it is said:
	 *
	 *   If the client receives no responses before the message
	 *   transmission process terminates, as described in section 14,
	 *   the client SHOULD continue to use any IP addresses, using the
	 *   last known lifetimes for those addresses, and SHOULD continue
	 *   to use any other previously obtained configuration parameters.
	 *
	 * So if confirm times out, we go active.
	 *
	 * XXX: Should we reduce all IA's t1 to 0, so that we renew and
	 * stick there until we get a reply?
	 */

	switch(check_timing6(client, DHCPV6_CONFIRM, "Confirm",
			     client->active_lease, &ds)) {
	      case CHK_TIM_MRC_EXCEEDED:
	      case CHK_TIM_MRD_EXCEEDED:
		start_bound(client);
		return;
	      case CHK_TIM_ALLOC_FAILURE:
		return;
	      case CHK_TIM_SUCCESS:
		break;
	}

	/* Fetch any configured 'sent' options (includes DUID') in wire format.
	 */
	dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL,
				    client->sent_options, &global_scope,
				    &dhcpv6_universe);

	/* Append IA's. */
	if (wanted_ia_na &&
	    dhc6_add_ia_na(client, &ds, client->active_lease,
			   DHCPV6_CONFIRM) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		return;
	}
	if (wanted_ia_ta &&
	    dhc6_add_ia_ta(client, &ds, client->active_lease,
			   DHCPV6_CONFIRM) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		return;
	}

	/* Transmit and wait. */

	log_info("XMT: Confirm on %s, interval %ld0ms.",
		 client->name ? client->name : client->interface->name,
		 (long int)client->RT);

	send_ret = send_packet6(client->interface, ds.data, ds.len,
				&DHCPv6DestAddr);
	if (send_ret != ds.len) {
		log_error("dhc6: sendpacket6() sent %d of %d bytes",
			  send_ret, ds.len);
	}

	data_string_forget(&ds, MDL);

	/* Wait RT */
	tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
	tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_confirm6, client, NULL, NULL);

	dhc6_retrans_advance(client);
}

/*
 * Release addresses.
 */
void
start_release6(struct client_state *client)
{
	/* Cancel any pending transmissions */
	cancel_timeout(do_confirm6, client);
	cancel_timeout(do_select6, client);
	cancel_timeout(do_refresh6, client);
	cancel_timeout(do_release6, client);
	client->state = S_STOPPED;

	/*
	 * It is written:  "The client MUST NOT use any of the addresses it
	 * is releasing as the source address in the Release message or in
	 * any subsequently transmitted message."  So unconfigure now.
	 */
	unconfigure6(client, "RELEASE6");

	/* Note this in the lease file. */
	if (client->active_lease == NULL)
		return;
	client->active_lease->released = ISC_TRUE;
	write_client6_lease(client, client->active_lease, 0, 1);

	/* Set timers per RFC3315 section 18.1.6. */
	client->IRT = REL_TIMEOUT * 100;
	client->MRT = 0;
	client->MRC = REL_MAX_RC;
	client->MRD = 0;

	dhc6_retrans_init(client);
	client->v6_handler = reply_handler;

	do_release6(client);
}
/*
 * do_release6() creates a Release packet and transmits it.
 */
static void
do_release6(void *input)
{
	struct client_state *client;
	struct data_string ds;
	int send_ret;
	struct timeval tv;

	client = input;

	if ((client->active_lease == NULL) || !active_prefix(client))
		return;

	switch(check_timing6(client, DHCPV6_RELEASE, "Release", 
			     client->active_lease, &ds)) {
	      case CHK_TIM_MRC_EXCEEDED:
	      case CHK_TIM_ALLOC_FAILURE:
	      case CHK_TIM_MRD_EXCEEDED:
		goto release_done;
	      case CHK_TIM_SUCCESS:
		break;
	}

	/*
	 * Don't use unicast as we don't know if we still have an
	 * available address with enough scope.
	 */

	dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL,
				    client->sent_options, &global_scope,
				    &dhcpv6_universe);

	/* Append IA's (but don't release temporary addresses). */
	if (wanted_ia_na &&
	    dhc6_add_ia_na(client, &ds, client->active_lease,
			   DHCPV6_RELEASE) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		goto release_done;
	}
	if (wanted_ia_pd &&
	    dhc6_add_ia_pd(client, &ds, client->active_lease,
			   DHCPV6_RELEASE) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		goto release_done;
	}

	/* Transmit and wait. */
	log_info("XMT: Release on %s, interval %ld0ms.",
		 client->name ? client->name : client->interface->name,
		 (long int)client->RT);

	send_ret = send_packet6(client->interface, ds.data, ds.len,
				&DHCPv6DestAddr);
	if (send_ret != ds.len) {
		log_error("dhc6: sendpacket6() sent %d of %d bytes",
			  send_ret, ds.len);
	}

	data_string_forget(&ds, MDL);

	/* Wait RT */
	tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
	tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_release6, client, NULL, NULL);
	dhc6_retrans_advance(client);
	return;

      release_done:
	dhc6_lease_destroy(&client->active_lease, MDL);
	client->active_lease = NULL;
	if (stopping_finished())
		exit(0);
}

/* status_log() just puts a status code into displayable form and logs it
 * to info level.
 */
static void
status_log(int code, const char *scope, const char *additional, int len)
{
	const char *msg = NULL;

	switch(code) {
	      case STATUS_Success:
		msg = "Success";
		break;

	      case STATUS_UnspecFail:
		msg = "UnspecFail";
		break;

	      case STATUS_NoAddrsAvail:
		msg = "NoAddrsAvail";
		break;

	      case STATUS_NoBinding:
		msg = "NoBinding";
		break;

	      case STATUS_NotOnLink:
		msg = "NotOnLink";
		break;

	      case STATUS_UseMulticast:
		msg = "UseMulticast";
		break;

	      case STATUS_NoPrefixAvail:
		msg = "NoPrefixAvail";
		break;

	      default:
		msg = "UNKNOWN";
		break;
	}

	if (len > 0)
		log_info("%s status code %s: %s", scope, msg,
			 print_hex_1(len,
				     (const unsigned char *)additional, 50));
	else
		log_info("%s status code %s.", scope, msg);
}

/* Acquire a status code.
 */
static isc_result_t
dhc6_get_status_code(struct option_state *options, unsigned *code,
		     struct data_string *msg)
{
	struct option_cache *oc;
	struct data_string ds;
	isc_result_t rval = ISC_R_SUCCESS;

	if ((options == NULL) || (code == NULL))
		return ISC_R_INVALIDARG;

	if ((msg != NULL) && (msg->len != 0))
		return ISC_R_INVALIDARG;

	memset(&ds, 0, sizeof(ds));

	/* Assume success if there is no option. */
	*code = STATUS_Success;

	oc = lookup_option(&dhcpv6_universe, options, D6O_STATUS_CODE);
	if ((oc != NULL) &&
	    evaluate_option_cache(&ds, NULL, NULL, NULL, options,
				  NULL, &global_scope, oc, MDL)) {
		if (ds.len < 2) {
			log_error("Invalid status code length %d.", ds.len);
			rval = ISC_R_FORMERR;
		} else
			*code = getUShort(ds.data);

		if ((msg != NULL) && (ds.len > 2)) {
			data_string_copy(msg, &ds, MDL);
			msg->data += 2;
			msg->len -= 2;
		}

		data_string_forget(&ds, MDL);
		return rval;
	}

	return ISC_R_NOTFOUND;
}

/* Look at status codes in an advertise, and reform the return value.
 */
static isc_result_t
dhc6_check_status(isc_result_t rval, struct option_state *options,
		  const char *scope, unsigned *code)
{
	struct data_string msg;
	isc_result_t status;

	if ((scope == NULL) || (code == NULL))
		return ISC_R_INVALIDARG;

	/* If we don't find a code, we assume success. */
	*code = STATUS_Success;

	/* If there is no options cache, then there is no code. */
	if (options != NULL) {
		memset(&msg, 0, sizeof(msg));
		status = dhc6_get_status_code(options, code, &msg);

		if (status == ISC_R_SUCCESS) {
			status_log(*code, scope, (char *)msg.data, msg.len);
			data_string_forget(&msg, MDL);

			if (*code != STATUS_Success)
				rval = ISC_R_FAILURE;

		} else if (status != ISC_R_NOTFOUND)
			rval = status;
	}

	return rval;
}

/* Look in the packet, any IA's, and any IAADDR's within those IA's to find
 * status code options that are not SUCCESS.
 */
static isc_result_t
dhc6_check_advertise(struct dhc6_lease *lease)
{
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;
	isc_result_t rval = ISC_R_SUCCESS;
	int have_addrs = ISC_FALSE;
	unsigned code;
	const char *scope;

	rval = dhc6_check_status(rval, lease->options, "message", &code);

	for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
		switch (ia->ia_type) {
			case D6O_IA_NA:
				scope = "IA_NA";
				break;
			case D6O_IA_TA:
				scope = "IA_TA";
				break;
			case D6O_IA_PD:
				scope = "IA_PD";
				break;
			default:
				log_error("dhc6_check_advertise: no type.");
				return ISC_R_FAILURE;
		}
		rval = dhc6_check_status(rval, ia->options, scope, &code);

		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			if (ia->ia_type != D6O_IA_PD)
				scope = "IAADDR";
			else
				scope = "IAPREFIX";
			rval = dhc6_check_status(rval, addr->options,
						 scope, &code);
			have_addrs = ISC_TRUE;
		}
	}

	if (have_addrs != ISC_TRUE)
		rval = ISC_R_ADDRNOTAVAIL;

	return rval;
}

/* status code <-> action matrix for the client in INIT state
 * (rapid/commit).  Returns always false as no action is defined.
 */
static isc_boolean_t
dhc6_init_action(struct client_state *client, isc_result_t *rvalp,
		 unsigned code)
{
	if (rvalp == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	if (client == NULL) {
		*rvalp = ISC_R_INVALIDARG;
		return ISC_FALSE;
	}

	if (*rvalp == ISC_R_SUCCESS)
		return ISC_FALSE;

	/* No possible action in any case... */
	return ISC_FALSE;
}

/* status code <-> action matrix for the client in SELECT state
 * (request/reply).  Returns true if action was taken (and the
 * packet should be ignored), or false if no action was taken.
 */
static isc_boolean_t
dhc6_select_action(struct client_state *client, isc_result_t *rvalp,
		   unsigned code)
{
	struct dhc6_lease *lease;
	isc_result_t rval;

	if (rvalp == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	if (client == NULL) {
		*rvalp = ISC_R_INVALIDARG;
		return ISC_FALSE;
	}
	rval = *rvalp;

	if (rval == ISC_R_SUCCESS)
		return ISC_FALSE;

	switch (code) {
		/* We may have an earlier failure status code (so no
		 * success rval), and a success code now.  This
		 * doesn't upgrade the rval to success, but it does
		 * mean we take no action here.
		 */
	      case STATUS_Success:
		/* Gimpy server, or possibly an attacker. */
	      case STATUS_NoBinding:
	      case STATUS_UseMulticast:
		/* Take no action. */
		return ISC_FALSE;

		/* If the server can't deal with us, either try the
		 * next advertised server, or continue retrying if there
		 * weren't any.
		 */
	      default:
	      case STATUS_UnspecFail:
		if (client->advertised_leases != NULL) {
			dhc6_lease_destroy(&client->selected_lease, MDL);
			client->selected_lease = NULL;

			start_selecting6(client);

			break;
		} else /* Take no action - continue to retry. */
			return ISC_FALSE;

		/* If the server has no addresses, try other servers if
		 * we got some, otherwise go to INIT to hope for more
		 * servers.
		 */
	      case STATUS_NoAddrsAvail:
	      case STATUS_NoPrefixAvail:
		if (client->state == S_REBOOTING)
			return ISC_FALSE;

		if (client->selected_lease == NULL)
			log_fatal("Impossible case at %s:%d.", MDL);

		dhc6_lease_destroy(&client->selected_lease, MDL);
		client->selected_lease = NULL;

		if (client->advertised_leases != NULL)
			start_selecting6(client);
		else
			start_init6(client);

		break;

		/* If we got a NotOnLink from a Confirm, then we're not
		 * on link.  Kill the old-active binding and start over.
		 *
		 * If we got a NotOnLink from our Request, something weird
		 * happened.  Start over from scratch anyway.
		 */
	      case STATUS_NotOnLink:
		if (client->state == S_REBOOTING) {
			if (client->active_lease == NULL)
				log_fatal("Impossible case at %s:%d.", MDL);

			dhc6_lease_destroy(&client->active_lease, MDL);
		} else {
			if (client->selected_lease == NULL)
				log_fatal("Impossible case at %s:%d.", MDL);

			dhc6_lease_destroy(&client->selected_lease, MDL);
			client->selected_lease = NULL;

			while (client->advertised_leases != NULL) {
				lease = client->advertised_leases;
				client->advertised_leases = lease->next;

				dhc6_lease_destroy(&lease, MDL);
			}
		}

		start_init6(client);
		break;
	}

	return ISC_TRUE;
}

static void
dhc6_withdraw_lease(struct client_state *client)
{
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;

	if ((client == NULL) || (client->active_lease == NULL))
		return;

	for (ia = client->active_lease->bindings ; ia != NULL ;
	     ia = ia->next) {
		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			addr->max_life = addr->preferred_life = 0;
		}
	}

	/* Perform expiry. */
	do_expire(client);
}

/* status code <-> action matrix for the client in BOUND state
 * (request/reply).  Returns true if action was taken (and the
 * packet should be ignored), or false if no action was taken.
 */
static isc_boolean_t
dhc6_reply_action(struct client_state *client, isc_result_t *rvalp,
		  unsigned code)
{
	isc_result_t rval;

	if (rvalp == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	if (client == NULL) {
		*rvalp = ISC_R_INVALIDARG;
		return ISC_FALSE;
	}
	rval = *rvalp;

	if (rval == ISC_R_SUCCESS)
		return ISC_FALSE;

	switch (code) {
		/* It's possible an earlier status code set rval to a failure
		 * code, and we've encountered a later success.
		 */
	      case STATUS_Success:
		/* In "refreshes" (where we get replies), we probably
		 * still have a valid lease.  So "take no action" and
		 * the upper levels will keep retrying until the lease
		 * expires (or we rebind).
		 */
	      case STATUS_UnspecFail:
		/* For unknown codes...it's a soft (retryable) error. */
	      default:
		return ISC_FALSE;

		/* The server is telling us to use a multicast address, so
		 * we have to delete the unicast option from the active
		 * lease, then allow retransmission to occur normally.
		 * (XXX: It might be preferable in this case to retransmit
		 * sooner than the current interval, but for now we don't.)
		 */
	      case STATUS_UseMulticast:
		if (client->active_lease != NULL)
			delete_option(&dhcp_universe,
				      client->active_lease->options,
				      D6O_UNICAST);
		return ISC_FALSE;

		/* "When the client receives a NotOnLink status from the
		 *  server in response to a Request, the client can either
		 *  re-issue the Request without specifying any addresses
		 *  or restart the DHCP server discovery process."
		 *
		 * This is strange.  If competing server evaluation is
		 * useful (and therefore in the protocol), then why would
		 * a client's first reaction be to request from the same
		 * server on a different link?  Surely you'd want to
		 * re-evaluate your server selection.
		 *
		 * Well, I guess that's the answer.
		 */
	      case STATUS_NotOnLink:
		/* In this case, we need to rescind all current active
		 * bindings (just 'expire' them all normally, if early).
		 * They're no use to us on the wrong link.  Then head back
		 * to init, redo server selection and get new addresses.
		 */
		dhc6_withdraw_lease(client);
		break;

		/* "If the status code is NoAddrsAvail, the client has
		 *  received no usable addresses in the IA and may choose
		 *  to try obtaining addresses for the IA from another
		 *  server."
		 */
	      case STATUS_NoAddrsAvail:
	      case STATUS_NoPrefixAvail:
		/* Head back to init, keeping any active bindings (!). */
		start_init6(client);
		break;

		/* -  sends a Request message if the IA contained a Status
		 *    Code option with the NoBinding status (and does not
		 *    send any additional Renew/Rebind messages)
		 */
	      case STATUS_NoBinding:
		if (client->advertised_leases != NULL)
			log_fatal("Impossible condition at %s:%d.", MDL);

		client->advertised_leases =
				dhc6_dup_lease(client->active_lease, MDL);
		start_selecting6(client);
		break;
	}

	return ISC_TRUE;
}

/* status code <-> action matrix for the client in STOPPED state
 * (release/decline).  Returns true if action was taken (and the
 * packet should be ignored), or false if no action was taken.
 * NoBinding is translated into Success.
 */
static isc_boolean_t
dhc6_stop_action(struct client_state *client, isc_result_t *rvalp,
		  unsigned code)
{
	isc_result_t rval;

	if (rvalp == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	if (client == NULL) {
		*rvalp = ISC_R_INVALIDARG;
		return ISC_FALSE;
	}
	rval = *rvalp;

	if (rval == ISC_R_SUCCESS)
		return ISC_FALSE;

	switch (code) {
		/* It's possible an earlier status code set rval to a failure
		 * code, and we've encountered a later success.
		 */
	      case STATUS_Success:
		/* For unknown codes...it's a soft (retryable) error. */
	      case STATUS_UnspecFail:
	      default:
		return ISC_FALSE;

		/* NoBinding is not an error */
	      case STATUS_NoBinding:
		if (rval == ISC_R_FAILURE)
			*rvalp = ISC_R_SUCCESS;
		return ISC_FALSE;

		/* Should not happen */
	      case STATUS_NoAddrsAvail:
	      case STATUS_NoPrefixAvail:
		break;

		/* Give up on it */
	      case STATUS_NotOnLink:
		break;

		/* The server is telling us to use a multicast address, so
		 * we have to delete the unicast option from the active
		 * lease, then allow retransmission to occur normally.
		 * (XXX: It might be preferable in this case to retransmit
		 * sooner than the current interval, but for now we don't.)
		 */
	      case STATUS_UseMulticast:
		if (client->active_lease != NULL)
			delete_option(&dhcp_universe,
				      client->active_lease->options,
				      D6O_UNICAST);
		return ISC_FALSE;
	}

	return ISC_TRUE;
}

/* Look at a new and old lease, and make sure the new information is not
 * losing us any state.
 */
static isc_result_t
dhc6_check_reply(struct client_state *client, struct dhc6_lease *new)
{
	isc_boolean_t (*action)(struct client_state *,
				isc_result_t *, unsigned);
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;
	isc_result_t rval = ISC_R_SUCCESS;
	unsigned code;
	const char *scope;
	int nscore, sscore;

	if ((client == NULL) || (new == NULL))
		return ISC_R_INVALIDARG;

	switch (client->state) {
	      case S_INIT:
		action = dhc6_init_action;
		break;

	      case S_SELECTING:
	      case S_REBOOTING:
		action = dhc6_select_action;
		break;

	      case S_RENEWING:
	      case S_REBINDING:
		action = dhc6_reply_action;
		break;

	      case S_STOPPED:
		action = dhc6_stop_action;
		break;

	      default:
		log_fatal("Impossible condition at %s:%d.", MDL);
		return ISC_R_CANCELED;
	}

	/* If there is a code to extract, and if there is some
	 * action to take based on that code, then take the action
	 * and do not continue.
	 */
	rval = dhc6_check_status(rval, new->options, "message", &code);
	if (action(client, &rval, code))
		return ISC_R_CANCELED;

	for (ia = new->bindings ; ia != NULL ; ia = ia->next) {
		switch (ia->ia_type) {
			case D6O_IA_NA:
				scope = "IA_NA";
				break;
			case D6O_IA_TA:
				scope = "IA_TA";
				break;
			case D6O_IA_PD:
				scope = "IA_PD";
				break;
			default:
				log_error("dhc6_check_reply: no type.");
				return ISC_R_INVALIDARG;
		}
		rval = dhc6_check_status(rval, ia->options,
					 scope, &code);
		if (action(client, &rval, code))
			return ISC_R_CANCELED;

		for (addr = ia->addrs ; addr != NULL ;
		     addr = addr->next) {
			if (ia->ia_type != D6O_IA_PD)
				scope = "IAADDR";
			else
				scope = "IAPREFIX";
			rval = dhc6_check_status(rval, addr->options,
						 scope, &code);
			if (action(client, &rval, code))
				return ISC_R_CANCELED;
		}
	}

	/* A Confirm->Reply is unsuitable for comparison to the old lease. */
	if (client->state == S_REBOOTING)
		return rval;

	/* No old lease in rapid-commit. */
	if (client->state == S_INIT)
		return rval;

	switch (client->state) {
	      case S_SELECTING:
		/* Compare the new lease with the selected lease to make
		 * sure there is no risky business.
		 */
		nscore = dhc6_score_lease(client, new);
		sscore = dhc6_score_lease(client, client->selected_lease);
		if ((client->advertised_leases != NULL) &&
		    (nscore < (sscore / 2))) {
			/* XXX: An attacker might reply this way to make
			 * XXX: sure we latch onto their configuration.
			 * XXX: We might want to ignore the packet and
			 * XXX: schedule re-selection at the next timeout?
			 */
			log_error("PRC: BAIT AND SWITCH detected.  Score of "
				  "supplied lease (%d) is substantially "
				  "smaller than the advertised score (%d).  "
				  "Trying other servers.",
				  nscore, sscore);

			dhc6_lease_destroy(&client->selected_lease, MDL);
			client->selected_lease = NULL;

			start_selecting6(client);

			return ISC_R_CANCELED;
		}
		break;

	      case S_RENEWING:
	      case S_REBINDING:
		/* This leaves one RFC3315 status check unimplemented:
		 *
		 * -  sends a Renew/Rebind if the IA is not in the Reply
		 *    message
		 *
		 * We rely on the scheduling system to note that the IA has
		 * not left Renewal/Rebinding/whatever since it still carries
		 * old times from the last successful binding.  So this is
		 * implemented actually, just not explicitly.
		 */
		break;

	      case S_STOPPED:
		/* Nothing critical to do at this stage. */
		break;

	      default:
		log_fatal("REALLY impossible condition at %s:%d.", MDL);
		return ISC_R_CANCELED;
	}

	return rval;
}

/* While in init state, we only collect advertisements.  If there happens
 * to be an advertisement with a preference option of 255, that's an
 * automatic exit.  Otherwise, we collect advertisements until our timeout
 * expires (client->RT).
 */
void
init_handler(struct packet *packet, struct client_state *client)
{
	struct dhc6_lease *lease;

	/* In INIT state, we send solicits, we only expect to get
	 * advertises (rapid commit has its own handler).
	 */
	if (packet->dhcpv6_msg_type != DHCPV6_ADVERTISE)
		return;

	/* RFC3315 section 15.3 validation (same as 15.10 since we
	 * always include a client id).
	 */
	if (!valid_reply(packet, client)) {
		log_error("Invalid Advertise - rejecting.");
		return;
	}

	lease = dhc6_leaseify(packet);

	if (dhc6_check_advertise(lease) != ISC_R_SUCCESS) {
		log_debug("PRC: Lease failed to satisfy.");
		dhc6_lease_destroy(&lease, MDL);
		return;
	}

	insert_lease(&client->advertised_leases, lease);

	/* According to RFC3315 section 17.1.2, the client MUST wait for
	 * the first RT before selecting a lease.  But on the 400th RT,
	 * we dont' want to wait the full timeout if we finally get an
	 * advertise.  We could probably wait a second, but ohwell,
	 * RFC3315 doesn't say so.
	 *
	 * If the lease is highest possible preference, 255, RFC3315 claims
	 * we should continue immediately even on the first RT.  We probably
	 * should not if the advertise contains less than one IA and address.
	 */
	if ((client->txcount > 1) ||
	    ((lease->pref == 255) &&
	    (dhc6_score_lease(client, lease) > 150))) {
		log_debug("RCV:  Advertisement immediately selected.");
		cancel_timeout(do_init6, client);
		start_selecting6(client);
	} else
		log_debug("RCV:  Advertisement recorded.");
}

/* info_request_handler() accepts a Reply to an Info-request.
 */
void
info_request_handler(struct packet *packet, struct client_state *client)
{
	isc_result_t check_status;
	unsigned code;

	if (packet->dhcpv6_msg_type != DHCPV6_REPLY)
		return;

	/* RFC3315 section 15.10 validation (same as 15.3 since we
	 * always include a client id).
	 */
	if (!valid_reply(packet, client)) {
		log_error("Invalid Reply - rejecting.");
		return;
	}

	check_status = dhc6_check_status(ISC_R_SUCCESS, packet->options,
					 "message", &code);
	if (check_status != ISC_R_SUCCESS) {
		/* If no action was taken, but there is an error, then
		 * we wait for a retransmission.
		 */
		if (check_status != ISC_R_CANCELED)
			return;
	}

	/* We're done retransmitting at this point. */
	cancel_timeout(do_info_request6, client);

	/* Action was taken, so now that we've torn down our scheduled
	 * retransmissions, return.
	 */
	if (check_status == ISC_R_CANCELED)
		return;

	/* Cleanup if a previous attempt to go bound failed. */
	if (client->old_lease != NULL) {
		dhc6_lease_destroy(&client->old_lease, MDL);
		client->old_lease = NULL;
	}

	/* Cache options in the active_lease. */
	if (client->active_lease != NULL)
		client->old_lease = client->active_lease;
	client->active_lease = dmalloc(sizeof(struct dhc6_lease), MDL);
	if (client->active_lease == NULL)
		log_fatal("Out of memory for v6 lease structure.");
	option_state_reference(&client->active_lease->options,
			       packet->options, MDL);

	start_informed(client);
}

/* Specific version of init_handler() for rapid-commit.
 */
void
rapid_commit_handler(struct packet *packet, struct client_state *client)
{
	struct dhc6_lease *lease;
	isc_result_t check_status;

	/* On ADVERTISE just fall back to the init_handler().
	 */
	if (packet->dhcpv6_msg_type == DHCPV6_ADVERTISE) {
		init_handler(packet, client);
		return;
	} else if (packet->dhcpv6_msg_type != DHCPV6_REPLY)
		return;

	/* RFC3315 section 15.10 validation (same as 15.3 since we
	 * always include a client id).
	 */
	if (!valid_reply(packet, client)) {
		log_error("Invalid Reply - rejecting.");
		return;
	}

	/* A rapid-commit option MUST be here. */
	if (lookup_option(&dhcpv6_universe, packet->options,
			  D6O_RAPID_COMMIT) == 0) {
		log_error("Reply without Rapid-Commit - rejecting.");
		return;
	}

	lease = dhc6_leaseify(packet);

	/* This is an out of memory condition...hopefully a temporary
	 * problem.  Returning now makes us try to retransmit later.
	 */
	if (lease == NULL)
		return;

	check_status = dhc6_check_reply(client, lease);
	if (check_status != ISC_R_SUCCESS) {
		dhc6_lease_destroy(&lease, MDL);
		return;
	}

	/* Jump to the selecting state. */
	cancel_timeout(do_init6, client);
	client->state = S_SELECTING;

	/* Merge any bindings in the active lease (if there is one) into
	 * the new active lease.
	 */
	dhc6_merge_lease(client->active_lease, lease);

	/* Cleanup if a previous attempt to go bound failed. */
	if (client->old_lease != NULL) {
		dhc6_lease_destroy(&client->old_lease, MDL);
		client->old_lease = NULL;
	}

	/* Make this lease active and BIND to it. */
	if (client->active_lease != NULL)
		client->old_lease = client->active_lease;
	client->active_lease = lease;

	/* We're done with the ADVERTISEd leases, if any. */
	while(client->advertised_leases != NULL) {
		lease = client->advertised_leases;
		client->advertised_leases = lease->next;

		dhc6_lease_destroy(&lease, MDL);
	}

	start_bound(client);
}

/* Find the 'best' lease in the cache of advertised leases (usually).  From
 * RFC3315 Section 17.1.3:
 *
 *   Upon receipt of one or more valid Advertise messages, the client
 *   selects one or more Advertise messages based upon the following
 *   criteria.
 *
 *   -  Those Advertise messages with the highest server preference value
 *      are preferred over all other Advertise messages.
 *
 *   -  Within a group of Advertise messages with the same server
 *      preference value, a client MAY select those servers whose
 *      Advertise messages advertise information of interest to the
 *      client.  For example, the client may choose a server that returned
 *      an advertisement with configuration options of interest to the
 *      client.
 *
 *   -  The client MAY choose a less-preferred server if that server has a
 *      better set of advertised parameters, such as the available
 *      addresses advertised in IAs.
 *
 * Note that the first and third contradict each other.  The third should
 * probably be taken to mean that the client should prefer answers that
 * offer bindings, even if that violates the preference rule.
 *
 * The above also isn't deterministic where there are ties.  So the final
 * tiebreaker we add, if all other values are equal, is to compare the
 * server identifiers and to select the numerically lower one.
 */
static struct dhc6_lease *
dhc6_best_lease(struct client_state *client, struct dhc6_lease **head)
{
	struct dhc6_lease **rpos, *rval, **candp, *cand;
	int cscore, rscore;

	if (head == NULL || *head == NULL)
		return NULL;

	rpos = head;
	rval = *rpos;
	rscore = dhc6_score_lease(client, rval);
	candp = &rval->next;
	cand = *candp;

	log_debug("PRC: Considering best lease.");
	log_debug("PRC:  X-- Initial candidate %s (s: %d, p: %u).",
		  print_hex_1(rval->server_id.len,
			      rval->server_id.data, 48),
		  rscore, (unsigned)rval->pref);

	for (; cand != NULL ; candp = &cand->next, cand = *candp) {
		cscore = dhc6_score_lease(client, cand);

		log_debug("PRC:  X-- Candidate %s (s: %d, p: %u).",
			  print_hex_1(cand->server_id.len,
				      cand->server_id.data, 48),
			  cscore, (unsigned)cand->pref);

		/* Above you'll find quoted RFC3315 Section 17.1.3.
		 *
		 * The third clause tells us to give up on leases that
		 * have no bindings even if their preference is better.
		 * So where our 'selected' lease's score is less than 150
		 * (1 ia + 1 addr), choose any candidate >= 150.
		 *
		 * The first clause tells us to make preference the primary
		 * deciding factor.  So if it's lower, reject, if it's
		 * higher, select.
		 *
		 * The second clause tells us where the preference is
		 * equal, we should use 'our judgement' of what we like
		 * to see in an advertisement primarily.
		 *
		 * But there can still be a tie.  To make this deterministic,
		 * we compare the server identifiers and select the binary
		 * lowest.
		 *
		 * Since server id's are unique in this list, there is
		 * no further tie to break.
		 */
		if ((rscore < 150) && (cscore >= 150)) {
			log_debug("PRC:  | X-- Selected, has bindings.");
		} else if (cand->pref < rval->pref) {
			log_debug("PRC:  | X-- Rejected, lower preference.");
			continue;
		} else if (cand->pref > rval->pref) {
			log_debug("PRC:  | X-- Selected, higher preference.");
		} else if (cscore > rscore) {
			log_debug("PRC:  | X-- Selected, equal preference, "
				  "higher score.");
		} else if (cscore < rscore) {
			log_debug("PRC:  | X-- Rejected, equal preference, "
				  "lower score.");
			continue;
		} else if ((cand->server_id.len < rval->server_id.len) ||
			   ((cand->server_id.len == rval->server_id.len) &&
			    (memcmp(cand->server_id.data,
				    rval->server_id.data,
				    cand->server_id.len) < 0))) {
			log_debug("PRC:  | X-- Selected, equal preference, "
				  "equal score, binary lesser server ID.");
		} else {
			log_debug("PRC:  | X-- Rejected, equal preference, "
				  "equal score, binary greater server ID.");
			continue;
		}

		rpos = candp;
		rval = cand;
		rscore = cscore;
	}

	/* Remove the selected lease from the chain. */
	*rpos = rval->next;

	return rval;
}

/* Select a lease out of the advertised leases and setup state to try and
 * acquire that lease.
 */
void
start_selecting6(struct client_state *client)
{
	struct dhc6_lease *lease;

	if (client->advertised_leases == NULL) {
		log_error("Can not enter DHCPv6 SELECTING state with no "
			  "leases to select from!");
		return;
	}

	log_debug("PRC: Selecting best advertised lease.");
	client->state = S_SELECTING;

	lease = dhc6_best_lease(client, &client->advertised_leases);

	if (lease == NULL)
		log_fatal("Impossible error at %s:%d.", MDL);

	client->selected_lease = lease;

	/* Set timers per RFC3315 section 18.1.1. */
	client->IRT = REQ_TIMEOUT * 100;
	client->MRT = REQ_MAX_RT * 100;
	client->MRC = REQ_MAX_RC;
	client->MRD = 0;

	dhc6_retrans_init(client);

	client->v6_handler = reply_handler;

	/* ("re")transmit the first packet. */
	do_select6(client);
}

/* Transmit a Request to select a lease offered in Advertisements.  In
 * the event of failure, either move on to the next-best advertised lease,
 * or head back to INIT state if there are none.
 */
void
do_select6(void *input)
{
	struct client_state *client;
	struct dhc6_lease *lease;
	struct data_string ds;
	struct timeval tv;
	int send_ret;

	client = input;

	/* 'lease' is fewer characters to type. */
	lease = client->selected_lease;
	if (lease == NULL || lease->bindings == NULL) {
		log_error("Illegal to attempt selection without selecting "
			  "a lease.");
		return;
	}

	switch(check_timing6(client, DHCPV6_REQUEST, "Request", lease, &ds)) {
	      case CHK_TIM_MRC_EXCEEDED:
	      case CHK_TIM_MRD_EXCEEDED:
		log_debug("PRC: Lease %s failed.",
			  print_hex_1(lease->server_id.len,
				      lease->server_id.data, 56));

		/* Get rid of the lease that timed/counted out. */
		dhc6_lease_destroy(&lease, MDL);
		client->selected_lease = NULL;

		/* If there are more leases great.  If not, get more. */
		if (client->advertised_leases != NULL)
			start_selecting6(client);
		else
			start_init6(client);
		return;
	      case CHK_TIM_ALLOC_FAILURE:
		return;
	      case CHK_TIM_SUCCESS:
		break;
	}

	/* Now make a packet that looks suspiciously like the one we
	 * got from the server.  But different.
	 *
	 * XXX: I guess IAID is supposed to be something the client
	 * indicates and uses as a key to its internal state.  It is
	 * kind of odd to ask the server for IA's whose IAID the client
	 * did not manufacture.  We first need a formal dhclient.conf
	 * construct for the iaid, then we can delve into this matter
	 * more properly.  In the time being, this will work.
	 */

	/* Fetch any configured 'sent' options (includes DUID) in wire format.
	 */
	dhcpv6_universe.encapsulate(&ds, NULL, NULL, client,
				    NULL, client->sent_options, &global_scope,
				    &dhcpv6_universe);

	/* Now append any IA's, and within them any IAADDR/IAPREFIXs. */
	if (wanted_ia_na &&
	    dhc6_add_ia_na(client, &ds, lease,
			   DHCPV6_REQUEST) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		return;
	}
	if (wanted_ia_ta &&
	    dhc6_add_ia_ta(client, &ds, lease,
			   DHCPV6_REQUEST) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		return;
	}
	if (wanted_ia_pd &&
	    dhc6_add_ia_pd(client, &ds, lease,
			   DHCPV6_REQUEST) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		return;
	}

	log_info("XMT: Request on %s, interval %ld0ms.",
		 client->name ? client->name : client->interface->name,
		 (long int)client->RT);

	send_ret = send_packet6(client->interface,
				ds.data, ds.len, &DHCPv6DestAddr);
	if (send_ret != ds.len) {
		log_error("dhc6: send_packet6() sent %d of %d bytes",
			  send_ret, ds.len);
	}

	data_string_forget(&ds, MDL);

	/* Wait RT */
	tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
	tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_select6, client, NULL, NULL);

	dhc6_retrans_advance(client);
}

/* For each IA_NA in the lease, for each address in the IA_NA,
 * append that information onto the packet-so-far.
 */
static isc_result_t
dhc6_add_ia_na(struct client_state *client, struct data_string *packet,
	       struct dhc6_lease *lease, u_int8_t message)
{
	struct data_string iads;
	struct data_string addrds;
	struct dhc6_addr *addr;
	struct dhc6_ia *ia;
	isc_result_t rval = ISC_R_SUCCESS;
	TIME t1, t2;

	memset(&iads, 0, sizeof(iads));
	memset(&addrds, 0, sizeof(addrds));
	for (ia = lease->bindings;
	     ia != NULL && rval == ISC_R_SUCCESS;
	     ia = ia->next) {
		if (ia->ia_type != D6O_IA_NA)
			continue;

		if (!buffer_allocate(&iads.buffer, 12, MDL)) {
			log_error("Unable to allocate memory for IA_NA.");
			rval = ISC_R_NOMEMORY;
			break;
		}

		/* Copy the IAID into the packet buffer. */
		memcpy(iads.buffer->data, ia->iaid, 4);
		iads.data = iads.buffer->data;
		iads.len = 12;

		switch (message) {
		      case DHCPV6_REQUEST:
		      case DHCPV6_RENEW:
		      case DHCPV6_REBIND:

			t1 = client->config->requested_lease / 2;
			t2 = t1 + (t1 / 2);
#if MAX_TIME > 0xffffffff
			if (t1 > 0xffffffff)
				t1 = 0xffffffff;
			if (t2 > 0xffffffff)
				t2 = 0xffffffff;
#endif
			putULong(iads.buffer->data + 4, t1);
			putULong(iads.buffer->data + 8, t2);

			log_debug("XMT:  X-- IA_NA %s",
				  print_hex_1(4, iads.data, 59));
			log_debug("XMT:  | X-- Requested renew  +%u",
				  (unsigned) t1);
			log_debug("XMT:  | X-- Requested rebind +%u",
				  (unsigned) t2);
			break;

		      case DHCPV6_CONFIRM:
		      case DHCPV6_RELEASE:
		      case DHCPV6_DECLINE:
			/* Set t1 and t2 to zero; server will ignore them */
			memset(iads.buffer->data + 4, 0, 8);
			log_debug("XMT:  X-- IA_NA %s",
				  print_hex_1(4, iads.buffer->data, 55));

			break;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}

		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			/*
			 * Do not confirm expired addresses, do not request
			 * expired addresses (but we keep them around for
			 * solicit).
			 */
			if (addr->flags & DHC6_ADDR_EXPIRED)
				continue;

			if (addr->address.len != 16) {
				log_error("Illegal IPv6 address length (%d), "
					  "ignoring.  (%s:%d)",
					  addr->address.len, MDL);
				continue;
			}

			if (!buffer_allocate(&addrds.buffer, 24, MDL)) {
				log_error("Unable to allocate memory for "
					  "IAADDR.");
				rval = ISC_R_NOMEMORY;
				break;
			}

			addrds.data = addrds.buffer->data;
			addrds.len = 24;

			/* Copy the address into the packet buffer. */
			memcpy(addrds.buffer->data, addr->address.iabuf, 16);

			/* Copy in additional information as appropriate */
			switch (message) {
			      case DHCPV6_REQUEST:
			      case DHCPV6_RENEW:
			      case DHCPV6_REBIND:
				t1 = client->config->requested_lease;
				t2 = t1 + 300;
				putULong(addrds.buffer->data + 16, t1);
				putULong(addrds.buffer->data + 20, t2);

				log_debug("XMT:  | | X-- IAADDR %s",
					  piaddr(addr->address));
				log_debug("XMT:  | | | X-- Preferred "
					  "lifetime +%u", (unsigned)t1);
				log_debug("XMT:  | | | X-- Max lifetime +%u",
					  (unsigned)t2);

				break;

			      case DHCPV6_CONFIRM:
				/*
				 * Set preferred and max life to zero,
				 * per 17.1.3.
				 */
				memset(addrds.buffer->data + 16, 0, 8);
				log_debug("XMT:  | X-- Confirm Address %s",
					  piaddr(addr->address));
				break;

			      case DHCPV6_RELEASE:
				/* Preferred and max life are irrelevant */
				memset(addrds.buffer->data + 16, 0, 8);
				log_debug("XMT:  | X-- Release Address %s",
					  piaddr(addr->address));
				break;

			      case DHCPV6_DECLINE:
				/* Preferred and max life are irrelevant */
				memset(addrds.buffer->data + 16, 0, 8);
				log_debug("XMT:  | X-- Decline Address %s",
					  piaddr(addr->address));
				break;

			      default:
				log_fatal("Impossible condition at %s:%d.",
					  MDL);
			}

			append_option(&iads, &dhcpv6_universe, iaaddr_option,
				      &addrds);
			data_string_forget(&addrds, MDL);
		}

		/*
		 * It doesn't make sense to make a request without an
		 * address.
		 */
		if (ia->addrs == NULL) {
			log_debug("!!!:  V IA_NA has no IAADDRs - removed.");
			rval = ISC_R_FAILURE;
		} else if (rval == ISC_R_SUCCESS) {
			log_debug("XMT:  V IA_NA appended.");
			append_option(packet, &dhcpv6_universe, ia_na_option,
				      &iads);
		}

		data_string_forget(&iads, MDL);
	}

	return rval;
}

/* For each IA_TA in the lease, for each address in the IA_TA,
 * append that information onto the packet-so-far.
 */
static isc_result_t
dhc6_add_ia_ta(struct client_state *client, struct data_string *packet,
	       struct dhc6_lease *lease, u_int8_t message)
{
	struct data_string iads;
	struct data_string addrds;
	struct dhc6_addr *addr;
	struct dhc6_ia *ia;
	isc_result_t rval = ISC_R_SUCCESS;
	TIME t1, t2;

	memset(&iads, 0, sizeof(iads));
	memset(&addrds, 0, sizeof(addrds));
	for (ia = lease->bindings;
	     ia != NULL && rval == ISC_R_SUCCESS;
	     ia = ia->next) {
		if (ia->ia_type != D6O_IA_TA)
			continue;

		if (!buffer_allocate(&iads.buffer, 4, MDL)) {
			log_error("Unable to allocate memory for IA_TA.");
			rval = ISC_R_NOMEMORY;
			break;
		}

		/* Copy the IAID into the packet buffer. */
		memcpy(iads.buffer->data, ia->iaid, 4);
		iads.data = iads.buffer->data;
		iads.len = 4;

		log_debug("XMT:  X-- IA_TA %s",
			  print_hex_1(4, iads.buffer->data, 55));

		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			/*
			 * Do not confirm expired addresses, do not request
			 * expired addresses (but we keep them around for
			 * solicit).
			 */
			if (addr->flags & DHC6_ADDR_EXPIRED)
				continue;

			if (addr->address.len != 16) {
				log_error("Illegal IPv6 address length (%d), "
					  "ignoring.  (%s:%d)",
					  addr->address.len, MDL);
				continue;
			}

			if (!buffer_allocate(&addrds.buffer, 24, MDL)) {
				log_error("Unable to allocate memory for "
					  "IAADDR.");
				rval = ISC_R_NOMEMORY;
				break;
			}

			addrds.data = addrds.buffer->data;
			addrds.len = 24;

			/* Copy the address into the packet buffer. */
			memcpy(addrds.buffer->data, addr->address.iabuf, 16);

			/* Copy in additional information as appropriate */
			switch (message) {
			      case DHCPV6_REQUEST:
			      case DHCPV6_RENEW:
			      case DHCPV6_REBIND:
				t1 = client->config->requested_lease;
				t2 = t1 + 300;
				putULong(addrds.buffer->data + 16, t1);
				putULong(addrds.buffer->data + 20, t2);

				log_debug("XMT:  | | X-- IAADDR %s",
					  piaddr(addr->address));
				log_debug("XMT:  | | | X-- Preferred "
					  "lifetime +%u", (unsigned)t1);
				log_debug("XMT:  | | | X-- Max lifetime +%u",
					  (unsigned)t2);

				break;

			      case DHCPV6_CONFIRM:
				/*
				 * Set preferred and max life to zero,
				 * per 17.1.3.
				 */
				memset(addrds.buffer->data + 16, 0, 8);
				log_debug("XMT:  | X-- Confirm Address %s",
					  piaddr(addr->address));
				break;

			      case DHCPV6_RELEASE:
				/* Preferred and max life are irrelevant */
				memset(addrds.buffer->data + 16, 0, 8);
				log_debug("XMT:  | X-- Release Address %s",
					  piaddr(addr->address));
				break;

			      default:
				log_fatal("Impossible condition at %s:%d.",
					  MDL);
			}

			append_option(&iads, &dhcpv6_universe, iaaddr_option,
				      &addrds);
			data_string_forget(&addrds, MDL);
		}

		/*
		 * It doesn't make sense to make a request without an
		 * address.
		 */
		if (ia->addrs == NULL) {
			log_debug("!!!:  V IA_TA has no IAADDRs - removed.");
			rval = ISC_R_FAILURE;
		} else if (rval == ISC_R_SUCCESS) {
			log_debug("XMT:  V IA_TA appended.");
			append_option(packet, &dhcpv6_universe, ia_ta_option,
				      &iads);
		}

		data_string_forget(&iads, MDL);
	}

	return rval;
}

/* For each IA_PD in the lease, for each prefix in the IA_PD,
 * append that information onto the packet-so-far.
 */
static isc_result_t
dhc6_add_ia_pd(struct client_state *client, struct data_string *packet,
	       struct dhc6_lease *lease, u_int8_t message)
{
	struct data_string iads;
	struct data_string prefds;
	struct dhc6_addr *pref;
	struct dhc6_ia *ia;
	isc_result_t rval = ISC_R_SUCCESS;
	TIME t1, t2;

	memset(&iads, 0, sizeof(iads));
	memset(&prefds, 0, sizeof(prefds));
	for (ia = lease->bindings;
	     ia != NULL && rval == ISC_R_SUCCESS;
	     ia = ia->next) {
		if (ia->ia_type != D6O_IA_PD)
			continue;

		if (!buffer_allocate(&iads.buffer, 12, MDL)) {
			log_error("Unable to allocate memory for IA_PD.");
			rval = ISC_R_NOMEMORY;
			break;
		}

		/* Copy the IAID into the packet buffer. */
		memcpy(iads.buffer->data, ia->iaid, 4);
		iads.data = iads.buffer->data;
		iads.len = 12;

		switch (message) {
		      case DHCPV6_REQUEST:
		      case DHCPV6_RENEW:
		      case DHCPV6_REBIND:

			t1 = client->config->requested_lease / 2;
			t2 = t1 + (t1 / 2);
#if MAX_TIME > 0xffffffff
			if (t1 > 0xffffffff)
				t1 = 0xffffffff;
			if (t2 > 0xffffffff)
				t2 = 0xffffffff;
#endif
			putULong(iads.buffer->data + 4, t1);
			putULong(iads.buffer->data + 8, t2);

			log_debug("XMT:  X-- IA_PD %s",
				  print_hex_1(4, iads.data, 59));
			log_debug("XMT:  | X-- Requested renew  +%u",
				  (unsigned) t1);
			log_debug("XMT:  | X-- Requested rebind +%u",
				  (unsigned) t2);
			break;

		      case DHCPV6_RELEASE:
			/* Set t1 and t2 to zero; server will ignore them */
			memset(iads.buffer->data + 4, 0, 8);
			log_debug("XMT:  X-- IA_PD %s",
				  print_hex_1(4, iads.buffer->data, 55));

			break;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}

		for (pref = ia->addrs ; pref != NULL ; pref = pref->next) {
			/*
			 * Do not confirm expired prefixes, do not request
			 * expired prefixes (but we keep them around for
			 * solicit).
			 */
			if (pref->flags & DHC6_ADDR_EXPIRED)
				continue;

			if (pref->address.len != 16) {
				log_error("Illegal IPv6 prefix "
					  "ignoring.  (%s:%d)",
					  MDL);
				continue;
			}

			if (pref->plen == 0) {
				log_info("Null IPv6 prefix, "
					 "ignoring. (%s:%d)",
					 MDL);
			}

			if (!buffer_allocate(&prefds.buffer, 25, MDL)) {
				log_error("Unable to allocate memory for "
					  "IAPREFIX.");
				rval = ISC_R_NOMEMORY;
				break;
			}

			prefds.data = prefds.buffer->data;
			prefds.len = 25;

			/* Copy the prefix into the packet buffer. */
			putUChar(prefds.buffer->data + 8, pref->plen);
			memcpy(prefds.buffer->data + 9,
			       pref->address.iabuf,
			       16);

			/* Copy in additional information as appropriate */
			switch (message) {
			      case DHCPV6_REQUEST:
			      case DHCPV6_RENEW:
			      case DHCPV6_REBIND:
				t1 = client->config->requested_lease;
				t2 = t1 + 300;
				putULong(prefds.buffer->data, t1);
				putULong(prefds.buffer->data + 4, t2);

				log_debug("XMT:  | | X-- IAPREFIX %s/%u",
					  piaddr(pref->address),
					  (unsigned) pref->plen);
				log_debug("XMT:  | | | X-- Preferred "
					  "lifetime +%u", (unsigned)t1);
				log_debug("XMT:  | | | X-- Max lifetime +%u",
					  (unsigned)t2);

				break;

			      case DHCPV6_RELEASE:
				/* Preferred and max life are irrelevant */
				memset(prefds.buffer->data, 0, 8);
				log_debug("XMT:  | X-- Release Prefix %s/%u",
					  piaddr(pref->address),
					  (unsigned) pref->plen);
				break;

			      default:
				log_fatal("Impossible condition at %s:%d.",
					  MDL);
			}

			append_option(&iads, &dhcpv6_universe,
				      iaprefix_option, &prefds);
			data_string_forget(&prefds, MDL);
		}

		/*
		 * It doesn't make sense to make a request without an
		 * address.
		 */
		if (ia->addrs == NULL) {
			log_debug("!!!:  V IA_PD has no IAPREFIXs - removed.");
			rval = ISC_R_FAILURE;
		} else if (rval == ISC_R_SUCCESS) {
			log_debug("XMT:  V IA_PD appended.");
			append_option(packet, &dhcpv6_universe,
				      ia_pd_option, &iads);
		}

		data_string_forget(&iads, MDL);
	}

	return rval;
}

/* stopping_finished() checks if there is a remaining work to do.
 */
static isc_boolean_t
stopping_finished(void)
{
	struct interface_info *ip;
	struct client_state *client;

	for (ip = interfaces; ip; ip = ip -> next) {
		for (client = ip -> client; client; client = client -> next) {
			if (client->state != S_STOPPED)
				return ISC_FALSE;
			if (client->active_lease != NULL)
				return ISC_FALSE;
		}
	}
	return ISC_TRUE;
}

/* reply_handler() accepts a Reply while we're attempting Select or Renew or
 * Rebind.  Basically any Reply packet.
 */
void
reply_handler(struct packet *packet, struct client_state *client)
{
	struct dhc6_lease *lease;
	isc_result_t check_status;

	if (packet->dhcpv6_msg_type != DHCPV6_REPLY)
		return;

	/* RFC3315 section 15.10 validation (same as 15.3 since we
	 * always include a client id).
	 */
	if (!valid_reply(packet, client)) {
		log_error("Invalid Reply - rejecting.");
		return;
	}

	lease = dhc6_leaseify(packet);

	/* This is an out of memory condition...hopefully a temporary
	 * problem.  Returning now makes us try to retransmit later.
	 */
	if (lease == NULL)
		return;

	check_status = dhc6_check_reply(client, lease);
	if (check_status != ISC_R_SUCCESS) {
		dhc6_lease_destroy(&lease, MDL);

		/* If no action was taken, but there is an error, then
		 * we wait for a retransmission.
		 */
		if (check_status != ISC_R_CANCELED)
			return;
	}

	/* We're done retransmitting at this point. */
	cancel_timeout(do_confirm6, client);
	cancel_timeout(do_select6, client);
	cancel_timeout(do_refresh6, client);
	cancel_timeout(do_release6, client);

	/* If this is in response to a Release/Decline, clean up and return. */
	if (client->state == S_STOPPED) {
		if (client->active_lease == NULL)
			return;

		dhc6_lease_destroy(&client->active_lease, MDL);
		client->active_lease = NULL;
		/* We should never wait for nothing!? */
		if (stopping_finished())
			exit(0);
		return;
	}

	/* Action was taken, so now that we've torn down our scheduled
	 * retransmissions, return.
	 */
	if (check_status == ISC_R_CANCELED)
		return;

	if (client->selected_lease != NULL) {
		dhc6_lease_destroy(&client->selected_lease, MDL);
		client->selected_lease = NULL;
	}

	/* If this is in response to a confirm, we use the lease we've
	 * already got, not the reply we were sent.
	 */
	if (client->state == S_REBOOTING) {
		if (client->active_lease == NULL)
			log_fatal("Impossible condition at %s:%d.", MDL);

		dhc6_lease_destroy(&lease, MDL);
		start_bound(client);
		return;
	}

	/* Merge any bindings in the active lease (if there is one) into
	 * the new active lease.
	 */
	dhc6_merge_lease(client->active_lease, lease);

	/* Cleanup if a previous attempt to go bound failed. */
	if (client->old_lease != NULL) {
		dhc6_lease_destroy(&client->old_lease, MDL);
		client->old_lease = NULL;
	}

	/* Make this lease active and BIND to it. */
	if (client->active_lease != NULL)
		client->old_lease = client->active_lease;
	client->active_lease = lease;

	/* We're done with the ADVERTISEd leases, if any. */
	while(client->advertised_leases != NULL) {
		lease = client->advertised_leases;
		client->advertised_leases = lease->next;

		dhc6_lease_destroy(&lease, MDL);
	}

	start_bound(client);
}

/* DHCPv6 packets are a little sillier than they needed to be - the root
 * packet contains options, then IA's which contain options, then within
 * that IAADDR's which contain options.
 *
 * To sort this out at dhclient-script time (which fetches config parameters
 * in environment variables), start_bound() iterates over each IAADDR, and
 * calls this function to marshall an environment variable set that includes
 * the most-specific option values related to that IAADDR in particular.
 *
 * To achieve this, we load environment variables for the root options space,
 * then the IA, then the IAADDR.  Any duplicate option names will be
 * over-written by the later versions.
 */
static void
dhc6_marshall_values(const char *prefix, struct client_state *client,
		     struct dhc6_lease *lease, struct dhc6_ia *ia,
		     struct dhc6_addr *addr)
{
	/* Option cache contents, in descending order of
	 * scope.
	 */
	if ((lease != NULL) && (lease->options != NULL))
		script_write_params6(client, prefix, lease->options);
	if ((ia != NULL) && (ia->options != NULL))
		script_write_params6(client, prefix, ia->options);
	if ((addr != NULL) && (addr->options != NULL))
		script_write_params6(client, prefix, addr->options);

	/* addr fields. */
	if (addr != NULL) {
		if ((ia != NULL) && (ia->ia_type == D6O_IA_PD)) {
			client_envadd(client, prefix,
				      "ip6_prefix", "%s/%u",
				      piaddr(addr->address),
				      (unsigned) addr->plen);
		} else {
			/* Current practice is that all subnets are /64's, but
			 * some suspect this may not be permanent.
			 */
			client_envadd(client, prefix, "ip6_prefixlen",
				      "%d", 64);
			client_envadd(client, prefix, "ip6_address",
				      "%s", piaddr(addr->address));
		}
		if ((ia != NULL) && (ia->ia_type == D6O_IA_TA)) {
			client_envadd(client, prefix,
				      "ip6_type", "temporary");
		}
		client_envadd(client, prefix, "life_starts", "%d",
			      (int)(addr->starts));
		client_envadd(client, prefix, "preferred_life", "%d",
			      (int)(addr->preferred_life));
		client_envadd(client, prefix, "max_life", "%d",
			      (int)(addr->max_life));
	}

	/* ia fields. */
	if (ia != NULL) {
		client_envadd(client, prefix, "iaid", "%s",
			      print_hex_1(4, ia->iaid, 12));
		client_envadd(client, prefix, "starts", "%d",
			      (int)(ia->starts));
		client_envadd(client, prefix, "renew", "%u", ia->renew);
		client_envadd(client, prefix, "rebind", "%u", ia->rebind);
	}
}

/* Look at where the client's active lease is sitting.  If it's looking to
 * time out on renew, rebind, depref, or expiration, do those things.
 */
static void
dhc6_check_times(struct client_state *client)
{
	struct dhc6_lease *lease;
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;
	TIME renew=MAX_TIME, rebind=MAX_TIME, depref=MAX_TIME,
	     lo_expire=MAX_TIME, hi_expire=0, tmp;
	int has_addrs = ISC_FALSE;
	struct timeval tv;

	lease = client->active_lease;

	/* Bit spammy.  We should probably keep record of scheduled
	 * events instead.
	 */
	cancel_timeout(start_renew6, client);
	cancel_timeout(start_rebind6, client);
	cancel_timeout(do_depref, client);
	cancel_timeout(do_expire, client);

	for(ia = lease->bindings ; ia != NULL ; ia = ia->next) {
		TIME this_ia_lo_expire, this_ia_hi_expire, use_expire;

		this_ia_lo_expire = MAX_TIME;
		this_ia_hi_expire = 0;

		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			if(!(addr->flags & DHC6_ADDR_DEPREFFED)) {
				if (addr->preferred_life == 0xffffffff)
					tmp = MAX_TIME;
				else
					tmp = addr->starts +
					      addr->preferred_life;

				if (tmp < depref)
					depref = tmp;
			}

			if (!(addr->flags & DHC6_ADDR_EXPIRED)) {
				/* Find EPOCH-relative expiration. */
				if (addr->max_life == 0xffffffff)
					tmp = MAX_TIME;
				else
					tmp = addr->starts + addr->max_life;

				/* Make the times ia->starts relative. */
				tmp -= ia->starts;

				if (tmp > this_ia_hi_expire)
					this_ia_hi_expire = tmp;
				if (tmp < this_ia_lo_expire)
					this_ia_lo_expire = tmp;

				has_addrs = ISC_TRUE;
			}
		}

		/* These times are ia->starts relative. */
		if (this_ia_lo_expire <= (this_ia_hi_expire / 2))
			use_expire = this_ia_hi_expire;
		else
			use_expire = this_ia_lo_expire;

		/*
		 * If the auto-selected expiration time is "infinite", or
		 * zero, assert a reasonable default.
		 */
		if ((use_expire == MAX_TIME) || (use_expire <= 1))
			use_expire = client->config->requested_lease / 2;
		else
			use_expire /= 2;

		/* Don't renew/rebind temporary addresses. */
		if (ia->ia_type != D6O_IA_TA) {

			if (ia->renew == 0) {
				tmp = ia->starts + use_expire;
			} else if (ia->renew == 0xffffffff)
				tmp = MAX_TIME;
			else
				tmp = ia->starts + ia->renew;

			if (tmp < renew)
				renew = tmp;

			if (ia->rebind == 0) {
				/* Set rebind to 3/4 expiration interval. */
				tmp = ia->starts;
				tmp += use_expire + (use_expire / 2);
			} else if (ia->rebind == 0xffffffff)
				tmp = MAX_TIME;
			else
				tmp = ia->starts + ia->rebind;

			if (tmp < rebind)
				rebind = tmp;
		}

		/*
		 * Return expiration ranges to EPOCH relative for event
		 * scheduling (add_timeout()).
		 */
		this_ia_hi_expire += ia->starts;
		this_ia_lo_expire += ia->starts;

		if (this_ia_hi_expire > hi_expire)
			hi_expire = this_ia_hi_expire;
		if (this_ia_lo_expire < lo_expire)
			lo_expire = this_ia_lo_expire;
	}

	/* If there are no addresses, give up, go to INIT.
	 * Note that if an address is unexpired with a date in the past,
	 * we're scheduling an expiration event to ocurr in the past.  We
	 * could probably optimize this to expire now (but then there's
	 * recursion).
	 *
	 * In the future, we may decide that we're done here, or to
	 * schedule a future request (using 4-pkt info-request model).
	 */
	if (has_addrs == ISC_FALSE) {
		dhc6_lease_destroy(&client->active_lease, MDL);
		client->active_lease = NULL;

		/* Go back to the beginning. */
		start_init6(client);
		return;
	}

	switch(client->state) {
	      case S_BOUND:
		/* We'd like to hit renewing, but if rebinding has already
		 * passed (time warp), head straight there.
		 */
		if ((rebind > cur_time) && (renew < rebind)) {
			log_debug("PRC: Renewal event scheduled in %d seconds, "
				  "to run for %u seconds.",
				  (int)(renew - cur_time),
				  (unsigned)(rebind - renew));
			client->next_MRD = rebind;
			tv.tv_sec = renew;
			tv.tv_usec = 0;
			add_timeout(&tv, start_renew6, client, NULL, NULL);

			break;
		}
		/* FALL THROUGH */
	      case S_RENEWING:
		/* While actively renewing, MRD is bounded by the time
		 * we stop renewing and start rebinding.  This helps us
		 * process the state change on time.
		 */
		client->MRD = rebind - cur_time;
		if (rebind != MAX_TIME) {
			log_debug("PRC: Rebind event scheduled in %d seconds, "
				  "to run for %d seconds.",
				  (int)(rebind - cur_time),
				  (int)(hi_expire - rebind));
			client->next_MRD = hi_expire;
			tv.tv_sec = rebind;
			tv.tv_usec = 0;
			add_timeout(&tv, start_rebind6, client, NULL, NULL);
		}
		break;

	      case S_REBINDING:
		/* For now, we rebind up until the last lease expires.  In
		 * the future, we might want to start SOLICITing when we've
		 * depreffed an address.
		 */
		client->MRD = hi_expire - cur_time;
		break;

	      default:
		log_fatal("Impossible condition at %s:%d.", MDL);
	}

	/* Separately, set a time at which we will depref and expire
	 * leases.  This might happen with multiple addresses while we
	 * keep trying to refresh.
	 */
	if (depref != MAX_TIME) {
		log_debug("PRC: Depreference scheduled in %d seconds.",
			  (int)(depref - cur_time));
		tv.tv_sec = depref;
		tv.tv_usec = 0;
		add_timeout(&tv, do_depref, client, NULL, NULL);
	}
	if (lo_expire != MAX_TIME) {
		log_debug("PRC: Expiration scheduled in %d seconds.",
			  (int)(lo_expire - cur_time));
		tv.tv_sec = lo_expire;
		tv.tv_usec = 0;
		add_timeout(&tv, do_expire, client, NULL, NULL);
	}
}

/* In a given IA chain, find the IA with the same type and 'iaid'. */
static struct dhc6_ia *
find_ia(struct dhc6_ia *head, u_int16_t type, const char *id)
{
	struct dhc6_ia *ia;

	for (ia = head ; ia != NULL ; ia = ia->next) {
		if (ia->ia_type != type)
			continue;
		if (memcmp(ia->iaid, id, 4) == 0)
			return ia;
	}

	return NULL;
}

/* In a given address chain, find a matching address. */
static struct dhc6_addr *
find_addr(struct dhc6_addr *head, struct iaddr *address)
{
	struct dhc6_addr *addr;

	for (addr = head ; addr != NULL ; addr = addr->next) {
		if ((addr->address.len == address->len) &&
		    (memcmp(addr->address.iabuf, address->iabuf,
			    address->len) == 0))
			return addr;
	}

	return NULL;
}

/* In a given prefix chain, find a matching prefix. */
static struct dhc6_addr *
find_pref(struct dhc6_addr *head, struct iaddr *prefix, u_int8_t plen)
{
	struct dhc6_addr *pref;

	for (pref = head ; pref != NULL ; pref = pref->next) {
		if ((pref->address.len == prefix->len) &&
		    (pref->plen == plen) &&
		    (memcmp(pref->address.iabuf, prefix->iabuf,
			    prefix->len) == 0))
			return pref;
	}

	return NULL;
}

/* Merge the bindings from the source lease into the destination lease
 * structure, where they are missing.  We have to copy the stateful
 * objects rather than move them over, because later code needs to be
 * able to compare new versus old if they contain any bindings.
 */
static void
dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst)
{
	struct dhc6_ia *sia, *dia, *tia;
	struct dhc6_addr *saddr, *daddr, *taddr;
	int changes = 0;

	if ((dst == NULL) || (src == NULL))
		return;

	for (sia = src->bindings ; sia != NULL ; sia = sia->next) {
		dia = find_ia(dst->bindings, sia->ia_type, (char *)sia->iaid);

		if (dia == NULL) {
			tia = dhc6_dup_ia(sia, MDL);

			if (tia == NULL)
				log_fatal("Out of memory merging lease - "
					  "Unable to continue without losing "
					  "state! (%s:%d)", MDL);

			/* XXX: consider sorting? */
			tia->next = dst->bindings;
			dst->bindings = tia;
			changes = 1;
		} else {
			for (saddr = sia->addrs ; saddr != NULL ;
			     saddr = saddr->next) {
				if (sia->ia_type != D6O_IA_PD)
					daddr = find_addr(dia->addrs,
							  &saddr->address);
				else
					daddr = find_pref(dia->addrs,
							  &saddr->address,
							  saddr->plen);

				if (daddr == NULL) {
					taddr = dhc6_dup_addr(saddr, MDL);

					if (taddr == NULL)
						log_fatal("Out of memory "
							  "merging lease - "
							  "Unable to continue "
							  "without losing "
							  "state! (%s:%d)",
							  MDL);

					/* XXX: consider sorting? */
					taddr->next = dia->addrs;
					dia->addrs = taddr;
					changes = 1;
				}
			}
		}
	}

	/* If we made changes, reset the score to 0 so it is recalculated. */
	if (changes)
		dst->score = 0;
}

/* We've either finished selecting or succeeded in Renew or Rebinding our
 * lease.  In all cases we got a Reply.  Give dhclient-script a tickle
 * to inform it about the new values, and then lay in wait for the next
 * event.
 */
static void
start_bound(struct client_state *client)
{
	struct dhc6_ia *ia, *oldia;
	struct dhc6_addr *addr, *oldaddr;
	struct dhc6_lease *lease, *old;
	const char *reason;
	TIME dns_update_offset = 1;

	lease = client->active_lease;
	if (lease == NULL) {
		log_error("Cannot enter bound state unless an active lease "
			  "is selected.");
		return;
	}
	lease->released = ISC_FALSE;
	old = client->old_lease;

	client->v6_handler = bound_handler;

	switch (client->state) {
	      case S_SELECTING:
	      case S_REBOOTING: /* Pretend we got bound. */
		reason = "BOUND6";
		break;

	      case S_RENEWING:
		reason = "RENEW6";
		break;

	      case S_REBINDING:
		reason = "REBIND6";
		break;

	      default:
		log_fatal("Impossible condition at %s:%d.", MDL);
		/* Silence compiler warnings. */
		return;
	}

	log_debug("PRC: Bound to lease %s.",
		  print_hex_1(client->active_lease->server_id.len,
			      client->active_lease->server_id.data, 55));
	client->state = S_BOUND;

	write_client6_lease(client, lease, 0, 1);

	oldia = NULL;
	for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
		if (old != NULL)
			oldia = find_ia(old->bindings,
					ia->ia_type,
					(char *)ia->iaid);
		else
			oldia = NULL;

		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			if (oldia != NULL) {
				if (ia->ia_type != D6O_IA_PD)
					oldaddr = find_addr(oldia->addrs,
							    &addr->address);
				else
					oldaddr = find_pref(oldia->addrs,
							    &addr->address,
							    addr->plen);
			} else
				oldaddr = NULL;

			if ((oldaddr == NULL) && (ia->ia_type == D6O_IA_NA))
				dhclient_schedule_updates(client,
							  &addr->address,
							  dns_update_offset++);

			/* Shell out to setup the new binding. */
			script_init(client, reason, NULL);

			if (old != NULL)
				dhc6_marshall_values("old_", client, old,
						     oldia, oldaddr);
			dhc6_marshall_values("new_", client, lease, ia, addr);

			script_go(client);
		}

		/* XXX: maybe we should loop on the old values instead? */
		if (ia->addrs == NULL) {
			script_init(client, reason, NULL);

			if (old != NULL)
				dhc6_marshall_values("old_", client, old,
						     oldia,
						     oldia != NULL ?
							 oldia->addrs : NULL);

			dhc6_marshall_values("new_", client, lease, ia,
					     NULL);

			script_go(client);
		}
	}

	/* XXX: maybe we should loop on the old values instead? */
	if (lease->bindings == NULL) {
		script_init(client, reason, NULL);

		if (old != NULL)
			dhc6_marshall_values("old_", client, old,
					     old->bindings,
					     (old->bindings != NULL) ?
						old->bindings->addrs : NULL);

		dhc6_marshall_values("new_", client, lease, NULL, NULL);

		script_go(client);
	}

	go_daemon();

	if (client->old_lease != NULL) {
		dhc6_lease_destroy(&client->old_lease, MDL);
		client->old_lease = NULL;
	}

	/* Schedule events. */
	dhc6_check_times(client);
}

/* While bound, ignore packets.  In the future we'll want to answer
 * Reconfigure-Request messages and the like.
 */
void
bound_handler(struct packet *packet, struct client_state *client)
{
	log_debug("RCV: Input packets are ignored once bound.");
}

/* start_renew6() gets us all ready to go to start transmitting Renew packets.
 * Note that client->next_MRD must be set before entering this function -
 * it must be set to the time at which the client should start Rebinding.
 */
void
start_renew6(void *input)
{
	struct client_state *client;

	client = (struct client_state *)input;

	log_info("PRC: Renewing lease on %s.",
		 client->name ? client->name : client->interface->name);
	client->state = S_RENEWING;

	client->v6_handler = reply_handler;

	/* Times per RFC3315 section 18.1.3. */
	client->IRT = REN_TIMEOUT * 100;
	client->MRT = REN_MAX_RT * 100;
	client->MRC = 0;
	/* MRD is special in renew - we need to set it by checking timer
	 * state.
	 */
	client->MRD = client->next_MRD - cur_time;

	dhc6_retrans_init(client);

	client->refresh_type = DHCPV6_RENEW;
	do_refresh6(client);
}

/* do_refresh6() transmits one DHCPv6 packet, be it a Renew or Rebind, and
 * gives the retransmission state a bump for the next time.  Note that
 * client->refresh_type must be set before entering this function.
 */
void
do_refresh6(void *input)
{
	struct option_cache *oc;
	struct sockaddr_in6 unicast, *dest_addr = &DHCPv6DestAddr;
	struct data_string ds;
	struct client_state *client;
	struct dhc6_lease *lease;
	struct timeval elapsed, tv;
	int send_ret;

	client = (struct client_state *)input;
	memset(&ds, 0, sizeof(ds));

	lease = client->active_lease;
	if (lease == NULL) {
		log_error("Cannot renew without an active binding.");
		return;
	}

	/* Ensure we're emitting a valid message type. */
	switch (client->refresh_type) {
	      case DHCPV6_RENEW:
	      case DHCPV6_REBIND:
		break;

	      default:
		log_fatal("Internal inconsistency (%d) at %s:%d.",
			  client->refresh_type, MDL);
	}

	/*
	 * Start_time starts at the first transmission.
	 */
	if (client->txcount == 0) {
		client->start_time.tv_sec = cur_tv.tv_sec;
		client->start_time.tv_usec = cur_tv.tv_usec;
	}

	/* elapsed = cur - start */
	elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec;
	elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec;
	if (elapsed.tv_usec < 0) {
		elapsed.tv_sec -= 1;
		elapsed.tv_usec += 1000000;
	}
	if (((client->MRC != 0) && (client->txcount > client->MRC)) ||
	    ((client->MRD != 0) && (elapsed.tv_sec >= client->MRD))) {
		/* We're done.  Move on to the next phase, if any. */
		dhc6_check_times(client);
		return;
	}

	/*
	 * Check whether the server has sent a unicast option; if so, we can
	 * use the address it specified for RENEWs.
	 */
	oc = lookup_option(&dhcpv6_universe, lease->options, D6O_UNICAST);
	if (oc && evaluate_option_cache(&ds, NULL, NULL, NULL,
					lease->options, NULL, &global_scope,
					oc, MDL)) {
		if (ds.len < 16) {
			log_error("Invalid unicast option length %d.", ds.len);
		} else {
			memset(&unicast, 0, sizeof(DHCPv6DestAddr));
			unicast.sin6_family = AF_INET6;
			unicast.sin6_port = remote_port;
			memcpy(&unicast.sin6_addr, ds.data, 16);
			if (client->refresh_type == DHCPV6_RENEW) {
				dest_addr = &unicast;
			}
		}

		data_string_forget(&ds, MDL);
	}

	/* Commence forming a renew packet. */
	memset(&ds, 0, sizeof(ds));
	if (!buffer_allocate(&ds.buffer, 4, MDL)) {
		log_error("Unable to allocate memory for packet.");
		return;
	}
	ds.data = ds.buffer->data;
	ds.len = 4;

	ds.buffer->data[0] = client->refresh_type;
	memcpy(ds.buffer->data + 1, client->dhcpv6_transaction_id, 3);

	/* Form an elapsed option. */
	/* Maximum value is 65535 1/100s coded as 0xffff. */
	if ((elapsed.tv_sec < 0) || (elapsed.tv_sec > 655) ||
	    ((elapsed.tv_sec == 655) && (elapsed.tv_usec > 350000))) {
		client->elapsed = 0xffff;
	} else {
		client->elapsed = elapsed.tv_sec * 100;
		client->elapsed += elapsed.tv_usec / 10000;
	}

	if (client->elapsed == 0)
		log_debug("XMT: Forming %s, 0 ms elapsed.",
			  dhcpv6_type_names[client->refresh_type]);
	else
		log_debug("XMT: Forming %s, %u0 ms elapsed.",
			  dhcpv6_type_names[client->refresh_type],
			  (unsigned)client->elapsed);

	client->elapsed = htons(client->elapsed);

	make_client6_options(client, &client->sent_options, lease,
			     client->refresh_type);

	/* Put in any options from the sent cache. */
	dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL,
				    client->sent_options, &global_scope,
				    &dhcpv6_universe);

	/* Append IA's */
	if (wanted_ia_na &&
	    dhc6_add_ia_na(client, &ds, lease,
			   client->refresh_type) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		return;
	}
	if (wanted_ia_pd &&
	    dhc6_add_ia_pd(client, &ds, lease,
			   client->refresh_type) != ISC_R_SUCCESS) {
		data_string_forget(&ds, MDL);
		return;
	}

	log_info("XMT: %s on %s, interval %ld0ms.",
		 dhcpv6_type_names[client->refresh_type],
		 client->name ? client->name : client->interface->name,
		 (long int)client->RT);

	send_ret = send_packet6(client->interface, ds.data, ds.len, dest_addr);

	if (send_ret != ds.len) {
		log_error("dhc6: send_packet6() sent %d of %d bytes",
			  send_ret, ds.len);
	}

	data_string_forget(&ds, MDL);

	/* Wait RT */
	tv.tv_sec = cur_tv.tv_sec + client->RT / 100;
	tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000;
	if (tv.tv_usec >= 1000000) {
		tv.tv_sec += 1;
		tv.tv_usec -= 1000000;
	}
	add_timeout(&tv, do_refresh6, client, NULL, NULL);

	dhc6_retrans_advance(client);
}

/* start_rebind6() gets us all set up to go and rebind a lease.  Note that
 * client->next_MRD must be set before entering this function.  In this case,
 * MRD must be set to the maximum time any address in the packet will
 * expire.
 */
void
start_rebind6(void *input)
{
	struct client_state *client;

	client = (struct client_state *)input;

	log_info("PRC: Rebinding lease on %s.",
		 client->name ? client->name : client->interface->name);
	client->state = S_REBINDING;

	client->v6_handler = reply_handler;

	/* Times per RFC3315 section 18.1.4. */
	client->IRT = REB_TIMEOUT * 100;
	client->MRT = REB_MAX_RT * 100;
	client->MRC = 0;
	/* MRD is special in rebind - it's determined by the timer
	 * state.
	 */
	client->MRD = client->next_MRD - cur_time;

	dhc6_retrans_init(client);

	client->refresh_type = DHCPV6_REBIND;
	do_refresh6(client);
}

/* do_depref() runs through a given lease's addresses, for each that has
 * not yet been depreffed, shells out to the dhclient-script to inform it
 * of the status change.  The dhclient-script should then do...something...
 * to encourage applications to move off the address and onto one of the
 * remaining 'preferred' addresses.
 */
void
do_depref(void *input)
{
	struct client_state *client;
	struct dhc6_lease *lease;
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;

	client = (struct client_state *)input;

	lease = client->active_lease;
	if (lease == NULL)
		return;

	for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			if (addr->flags & DHC6_ADDR_DEPREFFED)
				continue;

			if (addr->starts + addr->preferred_life <= cur_time) {
				script_init(client, "DEPREF6", NULL);
				dhc6_marshall_values("cur_", client, lease,
						     ia, addr);
				script_go(client);

				addr->flags |= DHC6_ADDR_DEPREFFED;

				if (ia->ia_type != D6O_IA_PD)
				    log_info("PRC: Address %s depreferred.",
					     piaddr(addr->address));
				else
				    log_info("PRC: Prefix %s/%u depreferred.",
					     piaddr(addr->address),
					     (unsigned) addr->plen);

				/* Remove DDNS bindings at depref time. */
				if ((ia->ia_type == D6O_IA_NA) &&
				    client->config->do_forward_update)
					client_dns_update(client, 0, 0,
							  &addr->address);
			}
		}
	}

	dhc6_check_times(client);
}

/* do_expire() searches through all the addresses on a given lease, and
 * expires/removes any addresses that are no longer valid.
 */
void
do_expire(void *input)
{
	struct client_state *client;
	struct dhc6_lease *lease;
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;
	int has_addrs = ISC_FALSE;

	client = (struct client_state *)input;

	lease = client->active_lease;
	if (lease == NULL)
		return;

	for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			if (addr->flags & DHC6_ADDR_EXPIRED)
				continue;

			if (addr->starts + addr->max_life <= cur_time) {
				script_init(client, "EXPIRE6", NULL);
				dhc6_marshall_values("old_", client, lease,
						     ia, addr);
				script_go(client);

				addr->flags |= DHC6_ADDR_EXPIRED;

				if (ia->ia_type != D6O_IA_PD)
				    log_info("PRC: Address %s expired.",
					     piaddr(addr->address));
				else
				    log_info("PRC: Prefix %s/%u expired.",
					     piaddr(addr->address),
					     (unsigned) addr->plen);

				/* We remove DNS records at depref time, but
				 * it is possible that we might get here
				 * without depreffing.
				 */
				if ((ia->ia_type == D6O_IA_NA) &&
				    client->config->do_forward_update &&
				    !(addr->flags & DHC6_ADDR_DEPREFFED))
					client_dns_update(client, 0, 0,
							  &addr->address);

				continue;
			}

			has_addrs = ISC_TRUE;
		}
	}

	/* Clean up empty leases. */
	if (has_addrs == ISC_FALSE) {
		log_info("PRC: Bound lease is devoid of active addresses."
			 "  Re-initializing.");

		dhc6_lease_destroy(&lease, MDL);
		client->active_lease = NULL;

		start_init6(client);
		return;
	}

	/* Schedule the next run through. */
	dhc6_check_times(client);
}

/*
 * Run client script to unconfigure interface.
 * Called with reason STOP6 when dhclient -x is run, or with reason
 * RELEASE6 when server has replied to a Release message.
 * Stateless is a special case.
 */
void
unconfigure6(struct client_state *client, const char *reason)
{
	struct dhc6_ia *ia;
	struct dhc6_addr *addr;

	if (stateless) {
		script_init(client, reason, NULL);
		if (client->active_lease != NULL)
			script_write_params6(client, "old_",
					     client->active_lease->options);
		script_go(client);
		return;
	}

	if (client->active_lease == NULL)
		return;

	for (ia = client->active_lease->bindings ; ia != NULL ; ia = ia->next) {
		if (ia->ia_type == D6O_IA_TA)
			continue;

		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
			script_init(client, reason, NULL);
			dhc6_marshall_values("old_", client,
					     client->active_lease, ia, addr);
			script_go(client);

			if ((ia->ia_type == D6O_IA_NA) &&
			    client->config->do_forward_update)
				client_dns_update(client, 0, 0, &addr->address);
		}
	}
}

void
refresh_info_request6(void *input)
{
	struct client_state *client;

	client = (struct client_state *)input;
	start_info_request6(client);
}

/* Timeout for Information-Request (using the IRT option).
 */
static void
dhc6_check_irt(struct client_state *client)
{
	struct option **req;
	struct option_cache *oc;
	TIME expire = MAX_TIME;
	struct timeval tv;
	int i;
	isc_boolean_t found = ISC_FALSE;

	cancel_timeout(refresh_info_request6, client);

	req = client->config->requested_options;
	for (i = 0; req[i] != NULL; i++) {
		if (req[i] == irt_option) {
			found = ISC_TRUE;
			break;
		}
	}
	/* Simply return gives a endless loop waiting for nothing. */
	if (!found)
		exit(0);

	oc = lookup_option(&dhcpv6_universe, client->active_lease->options,
			   D6O_INFORMATION_REFRESH_TIME);
	if (oc != NULL) {
		struct data_string irt;

		memset(&irt, 0, sizeof(irt));
		if (!evaluate_option_cache(&irt, NULL, NULL, client,
					   client->active_lease->options,
					   NULL, &global_scope, oc, MDL) ||
		    (irt.len < 4)) {
			log_error("Can't evaluate IRT.");
		} else {
			expire = getULong(irt.data);
			if (expire < IRT_MINIMUM)
				expire = IRT_MINIMUM;
			if (expire == 0xffffffff)
				expire = MAX_TIME;
		}
		data_string_forget(&irt, MDL);
	} else
		expire = IRT_DEFAULT;

	if (expire != MAX_TIME) {
		log_debug("PRC: Refresh event scheduled in %u seconds.",
			  (unsigned) expire);
		tv.tv_sec = cur_time + expire;
		tv.tv_usec = 0;
		add_timeout(&tv, refresh_info_request6, client, NULL, NULL);
	}
}

/* We got a Reply. Give dhclient-script a tickle to inform it about
 * the new values, and then lay in wait for the next event.
 */
static void
start_informed(struct client_state *client)
{
	client->v6_handler = informed_handler;

	log_debug("PRC: Done.");

	client->state = S_BOUND;

	script_init(client, "RENEW6", NULL);
	if (client->old_lease != NULL)
		script_write_params6(client, "old_",
				     client->old_lease->options);
	script_write_params6(client, "new_", client->active_lease->options);
	script_go(client);

	go_daemon();

	if (client->old_lease != NULL) {
		dhc6_lease_destroy(&client->old_lease, MDL);
		client->old_lease = NULL;
	}

	/* Schedule events. */
	dhc6_check_irt(client);
}

/* While informed, ignore packets.
 */
void
informed_handler(struct packet *packet, struct client_state *client)
{
	log_debug("RCV: Input packets are ignored once bound.");
}

/* make_client6_options() fetches option caches relevant to the client's
 * scope and places them into the sent_options cache.  This cache is later
 * used to populate DHCPv6 output packets with options.
 */
static void
make_client6_options(struct client_state *client, struct option_state **op,
		     struct dhc6_lease *lease, u_int8_t message)
{
	struct option_cache *oc;
	struct option **req;
	struct buffer *buffer;
	int buflen, i, oro_len;

	if ((op == NULL) || (client == NULL))
		return;

	if (*op)
		option_state_dereference(op, MDL);

	/* Create a cache to carry options to transmission. */
	option_state_allocate(op, MDL);

	/* Create and store an 'elapsed time' option in the cache. */
	oc = NULL;
	if (option_cache_allocate(&oc, MDL)) {
		const unsigned char *cdata;

		cdata = (unsigned char *)&client->elapsed;

		if (make_const_data(&oc->expression, cdata, 2, 0, 0, MDL)) {
			option_reference(&oc->option, elapsed_option, MDL);
			save_option(&dhcpv6_universe, *op, oc);
		}

		option_cache_dereference(&oc, MDL);
	}

	/* Bring in any configured options to send. */
	if (client->config->on_transmission)
		execute_statements_in_scope(NULL, NULL, NULL, client,
					    lease ? lease->options : NULL,
					    *op, &global_scope,
					    client->config->on_transmission,
					    NULL);

	/* Rapid-commit is only for SOLICITs. */
	if (message != DHCPV6_SOLICIT)
		delete_option(&dhcpv6_universe, *op, D6O_RAPID_COMMIT);

	/* See if the user configured a DUID in a relevant scope.  If not,
	 * introduce our default manufactured id.
	 */
	if ((oc = lookup_option(&dhcpv6_universe, *op,
				D6O_CLIENTID)) == NULL) {
		if (!option_cache(&oc, &default_duid, NULL, clientid_option,
				  MDL))
			log_fatal("Failure assembling a DUID.");

		save_option(&dhcpv6_universe, *op, oc);
		option_cache_dereference(&oc, MDL);
	}

	/* In cases where we're responding to a single server, put the
	 * server's id in the response.
	 *
	 * Note that lease is NULL for SOLICIT or INFO request messages,
	 * and otherwise MUST be present.
	 */
	if (lease == NULL) {
		if ((message != DHCPV6_SOLICIT) &&
		    (message != DHCPV6_INFORMATION_REQUEST))
			log_fatal("Impossible condition at %s:%d.", MDL);
	} else if ((message != DHCPV6_REBIND) &&
		   (message != DHCPV6_CONFIRM)) {
		oc = lookup_option(&dhcpv6_universe, lease->options,
				   D6O_SERVERID);
		if (oc != NULL)
			save_option(&dhcpv6_universe, *op, oc);
	}

	/* 'send dhcp6.oro foo;' syntax we used in 4.0.0a1/a2 has been
	 * deprecated by adjustments to the 'request' syntax also used for
	 * DHCPv4.
	 */
	if (lookup_option(&dhcpv6_universe, *op, D6O_ORO) != NULL)
		log_error("'send dhcp6.oro' syntax is deprecated, please "
			  "use the 'request' syntax (\"man dhclient.conf\").");

	/* Construct and store an ORO (Option Request Option).  It is a
	 * fatal error to fail to send an ORO (of at least zero length).
	 *
	 * Discussion:  RFC3315 appears to be inconsistent in its statements
	 * of whether or not the ORO is mandatory.  In section 18.1.1
	 * ("Creation and Transmission of Request Messages"):
	 *
	 *    The client MUST include an Option Request option (see section
	 *    22.7) to indicate the options the client is interested in
	 *    receiving.  The client MAY include options with data values as
	 *    hints to the server about parameter values the client would like
	 *    to have returned.
	 *
	 * This MUST is missing from the creation/transmission of other
	 * messages (such as Renew and Rebind), and the section 22.7 ("Option
	 * Request Option" format and definition):
	 *
	 *    A client MAY include an Option Request option in a Solicit,
	 *    Request, Renew, Rebind, Confirm or Information-request message to
	 *    inform the server about options the client wants the server to
	 *    send to the client.  A server MAY include an Option Request
	 *    option in a Reconfigure option to indicate which options the
	 *    client should request from the server.
	 *
	 * seems to relax the requirement from MUST to MAY (and still other
	 * language in RFC3315 supports this).
	 *
	 * In lieu of a clarification of RFC3315, we will conform with the
	 * MUST.  Instead of an absent ORO, we will if there are no options
	 * to request supply an empty ORO.  Theoretically, an absent ORO is
	 * difficult to interpret (does the client want all options or no
	 * options?).  A zero-length ORO is intuitively clear: requesting
	 * nothing.
	 */
	buffer = NULL;
	oro_len = 0;
	buflen = 32;
	if (!buffer_allocate(&buffer, buflen, MDL))
		log_fatal("Out of memory constructing DHCPv6 ORO.");
	req = client->config->requested_options;
	if (req != NULL) {
		for (i = 0 ; req[i] != NULL ; i++) {
			if (buflen == oro_len) {
				struct buffer *tmpbuf = NULL;

				buflen += 32;

				/* Shell game. */
				buffer_reference(&tmpbuf, buffer, MDL);
				buffer_dereference(&buffer, MDL);

				if (!buffer_allocate(&buffer, buflen, MDL))
					log_fatal("Out of memory resizing "
						  "DHCPv6 ORO buffer.");

				memcpy(buffer->data, tmpbuf->data, oro_len);

				buffer_dereference(&tmpbuf, MDL);
			}

			if (req[i]->universe == &dhcpv6_universe) {
				/* Append the code to the ORO. */
				putUShort(buffer->data + oro_len,
					  req[i]->code);
				oro_len += 2;
			}
		}
	}

	oc = NULL;
	if (make_const_option_cache(&oc, &buffer, NULL, oro_len,
				    oro_option, MDL)) {
		save_option(&dhcpv6_universe, *op, oc);
	} else {
		log_fatal("Unable to create ORO option cache.");
	}

	/*
	 * Note: make_const_option_cache() consumes the buffer, we do not
	 * need to dereference it (XXX).
	 */
	option_cache_dereference(&oc, MDL);
}

/* A clone of the DHCPv4 script_write_params() minus the DHCPv4-specific
 * filename, server-name, etc specifics.
 *
 * Simply, store all values present in all universes of the option state
 * (probably derived from a DHCPv6 packet) into environment variables
 * named after the option names (and universe names) but with the 'prefix'
 * prepended.
 *
 * Later, dhclient-script may compare for example "new_time_servers" and
 * "old_time_servers" for differences, and only upon detecting a change
 * bother to rewrite ntp.conf and restart it.  Or something along those
 * generic lines.
 */
static void
script_write_params6(struct client_state *client, const char *prefix,
		     struct option_state *options)
{
	struct envadd_state es;
	int i;

	if (options == NULL)
		return;

	es.client = client;
	es.prefix = prefix;

	for (i = 0 ; i < options->universe_count ; i++) {
		option_space_foreach(NULL, NULL, client, NULL, options,
				     &global_scope, universes[i], &es,
				     client_option_envadd);
	}
}

/*
 * Check if there is something not fully defined in the active lease.
 */
static isc_boolean_t
active_prefix(struct client_state *client)
{
	struct dhc6_lease *lease;
	struct dhc6_ia *ia;
	struct dhc6_addr *pref;
	char zeros[16];

	lease = client->active_lease;
	if (lease == NULL)
		return ISC_FALSE;
	memset(zeros, 0, 16);
	for (ia = lease->bindings; ia != NULL; ia = ia->next) {
		if (ia->ia_type != D6O_IA_PD)
			continue;
		for (pref = ia->addrs; pref != NULL; pref = pref->next) {
			if (pref->plen == 0)
				return ISC_FALSE;
			if (pref->address.len != 16)
				return ISC_FALSE;
			if (memcmp(pref->address.iabuf, zeros, 16) == 0)
				return ISC_FALSE;
		}
	}
	return ISC_TRUE;
}
#endif /* DHCPv6 */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>