]> git.ipfire.org Git - thirdparty/dhcp.git/blobdiff - server/dhcpv6.c
Update README - added section on Release status which was in the TOC
[thirdparty/dhcp.git] / server / dhcpv6.c
index 4480e6cf492b471cab22dd0226a99150faf20c23..0ea053256ea1ed8be5353f1bc954e8b91fbefa65 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * Copyright (C) 2006-2008 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 2006-2017 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.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
+/*! \file server/dhcpv6.c */
+
 #include "dhcpd.h"
 
 #ifdef DHCPv6
 
+#ifdef DHCP4o6
+static void forw_dhcpv4_query(struct packet *packet);
+static void send_dhcpv4_response(struct data_string *raw);
+
+static void recv_dhcpv4_query(struct data_string *raw);
+static void dhcp4o6_dhcpv4_query(struct data_string *reply_ret,
+                                struct packet *packet);
+
+struct udp_data4o6 {
+       u_int16_t src_port;
+       u_int8_t  rsp_opt_exist;
+       u_int8_t  reserved;
+};
+
+static int offset_data4o6 = 36; /* 16+16+4 */
+#endif
+
 /*
- * We use print_hex_1() to output DUID values. We could actually output 
- * the DUID with more information... MAC address if using type 1 or 3, 
- * and so on. However, RFC 3315 contains Grave Warnings against actually 
+ * We use print_hex_1() to output DUID values. We could actually output
+ * the DUID with more information... MAC address if using type 1 or 3,
+ * and so on. However, RFC 3315 contains Grave Warnings against actually
  * attempting to understand a DUID.
  */
 
-/* 
+/*
  * TODO: gettext() or other method of localization for the messages
  *       for status codes (and probably for log formats eventually)
  * TODO: refactoring (simplify, simplify, simplify)
- * TODO: support multiple shared_networks on each interface (this 
- *       will allow the server to issue multiple IPv6 addresses to 
+ * TODO: support multiple shared_networks on each interface (this
+ *       will allow the server to issue multiple IPv6 addresses to
  *       a single interface)
  */
 
@@ -43,28 +62,32 @@ struct reply_state {
        /* root level persistent state */
        struct shared_network *shared;
        struct host_decl *host;
+       struct subnet *subnet; /* Used to match fixed-addrs to subnet scopes. */
        struct option_state *opt_state;
        struct packet *packet;
        struct data_string client_id;
 
        /* IA level persistent state */
        unsigned ia_count;
+       unsigned pd_count;
        unsigned client_resources;
-       isc_boolean_t ia_addrs_included;
+       isc_boolean_t resources_included;
        isc_boolean_t static_lease;
-       struct ia_na *ia_na;
-       struct ia_na *old_ia_na;
+       unsigned static_prefixes;
+       struct ia_xx *ia;
+       struct ia_xx *old_ia;
        struct option_state *reply_ia;
        struct data_string fixed;
+       struct iaddrcidrnet fixed_pref; /* static prefix for logging */
 
-       /* IAADDR level persistent state */
-       struct iaaddr *lease;
+       /* IAADDR/PREFIX level persistent state */
+       struct iasubopt *lease;
 
        /*
         * "t1", "t2", preferred, and valid lifetimes records for calculating
         * t1 and t2 (min/max).
         */
-       u_int32_t renew, rebind, prefer, valid;
+       u_int32_t renew, rebind, min_prefer, min_valid;
 
        /* Client-requested valid and preferred lifetimes. */
        u_int32_t client_valid, client_prefer;
@@ -72,16 +95,22 @@ struct reply_state {
        /* Chosen values to transmit for valid and preferred lifetimes. */
        u_int32_t send_valid, send_prefer;
 
+       /* Preferred prefix length (-1 is any). */
+       int preflen;
+
        /* Index into the data field that has been consumed. */
        unsigned cursor;
 
+       /* Space for the on commit statements for a fixed host */
+       struct on_star on_star;
+
        union reply_buffer {
                unsigned char data[65536];
                struct dhcpv6_packet reply;
        } buf;
 };
 
-/* 
+/*
  * Prototypes local to this file.
  */
 static int get_encapsulated_IA_state(struct option_state **enc_opt_state,
@@ -98,10 +127,15 @@ static isc_boolean_t fixed_matches_shared(struct host_decl *host,
                                          struct shared_network *shared);
 static isc_result_t reply_process_ia_na(struct reply_state *reply,
                                        struct option_cache *ia);
+static isc_result_t reply_process_ia_ta(struct reply_state *reply,
+                                       struct option_cache *ia);
 static isc_result_t reply_process_addr(struct reply_state *reply,
                                       struct option_cache *addr);
 static isc_boolean_t address_is_owned(struct reply_state *reply,
                                      struct iaddr *addr);
+static isc_boolean_t temporary_is_available(struct reply_state *reply,
+                                           struct iaddr *addr);
+static isc_result_t find_client_temporaries(struct reply_state *reply);
 static isc_result_t reply_process_try_addr(struct reply_state *reply,
                                           struct iaddr *addr);
 static isc_result_t find_client_address(struct reply_state *reply);
@@ -110,7 +144,184 @@ static isc_result_t reply_process_is_addressed(struct reply_state *reply,
                                               struct group *group);
 static isc_result_t reply_process_send_addr(struct reply_state *reply,
                                            struct iaddr *addr);
-static struct iaaddr *lease_compare(struct iaaddr *alpha, struct iaaddr *beta);
+static struct iasubopt *lease_compare(struct iasubopt *alpha,
+                                     struct iasubopt *beta);
+static isc_result_t reply_process_ia_pd(struct reply_state *reply,
+                                       struct option_cache *ia_pd);
+static struct group *find_group_by_prefix(struct reply_state *reply);
+static isc_result_t reply_process_prefix(struct reply_state *reply,
+                                        struct option_cache *pref);
+static isc_boolean_t prefix_is_owned(struct reply_state *reply,
+                                    struct iaddrcidrnet *pref);
+static isc_result_t find_client_prefix(struct reply_state *reply);
+static isc_result_t reply_process_try_prefix(struct reply_state *reply,
+                                            struct iaddrcidrnet *pref);
+static isc_result_t reply_process_is_prefixed(struct reply_state *reply,
+                                             struct binding_scope **scope,
+                                             struct group *group);
+static isc_result_t reply_process_send_prefix(struct reply_state *reply,
+                                             struct iaddrcidrnet *pref);
+static struct iasubopt *prefix_compare(struct reply_state *reply,
+                                      struct iasubopt *alpha,
+                                      struct iasubopt *beta);
+static void schedule_lease_timeout_reply(struct reply_state *reply);
+
+static int eval_prefix_mode(int thislen, int preflen, int prefix_mode);
+static isc_result_t pick_v6_prefix_helper(struct reply_state *reply,
+                                         int prefix_mode);
+
+static void unicast_reject(struct data_string *reply_ret, struct packet *packet,
+                 const struct data_string *client_id,
+                 const struct data_string *server_id);
+
+static isc_boolean_t is_unicast_option_defined(struct packet *packet);
+static isc_result_t shared_network_from_requested_addr (struct shared_network
+                                                       **shared,
+                                                       struct packet* packet);
+static isc_result_t get_first_ia_addr_val (struct packet* packet, int addr_type,
+                                          struct iaddr* iaddr);
+
+static void
+set_reply_tee_times(struct reply_state* reply, unsigned ia_cursor);
+
+static const char *iasubopt_plen_str(struct iasubopt *lease);
+static int release_on_roam(struct reply_state *reply);
+
+static int reuse_lease6(struct reply_state *reply, struct iasubopt *lease);
+static void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease,
+                             time_t age, int threshold);
+static void write_to_packet(struct reply_state *reply, unsigned ia_cursor);
+static const char *iasubopt_plen_str(struct iasubopt *lease);
+
+#ifdef NSUPDATE
+static void ddns_update_static6(struct reply_state* reply);
+#endif
+
+#ifdef DHCP4o6
+/*
+ * \brief Omapi I/O handler
+ *
+ * The inter-process communication receive handler.
+ * Get the message, put it into the raw data_string
+ * and call \ref send_dhcpv4_response() (DHCPv6 side) or
+ * \ref recv_dhcpv4_query() (DHCPv4 side)
+ *
+ * \param h the OMAPI object
+ * \return a result for I/O success or error (used by the I/O subsystem)
+ */
+isc_result_t dhcpv4o6_handler(omapi_object_t *h) {
+       char buf[65536];
+       struct data_string raw;
+       int cc;
+
+       if (h->type != dhcp4o6_type)
+               return DHCP_R_INVALIDARG;
+
+       cc = recv(dhcp4o6_fd, buf, sizeof(buf), 0);
+
+       if (cc < DHCP_FIXED_NON_UDP + offset_data4o6)
+               return ISC_R_UNEXPECTED;
+       memset(&raw, 0, sizeof(raw));
+       if (!buffer_allocate(&raw.buffer, cc, MDL)) {
+               log_error("dhcpv4o6_handler: no memory buffer.");
+               return ISC_R_NOMEMORY;
+       }
+       raw.data = raw.buffer->data;
+       raw.len = cc;
+       memcpy(raw.buffer->data, buf, cc);
+
+       if (local_family == AF_INET6) {
+               send_dhcpv4_response(&raw);
+       } else {
+               recv_dhcpv4_query(&raw);
+       }
+
+       data_string_forget(&raw, MDL);
+
+       return ISC_R_SUCCESS;
+}
+
+/*
+ * \brief Send the DHCPv4-response back to the DHCPv6 side
+ *  (DHCPv6 server function)
+ *
+ * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-response message
+ *
+ * \param raw the IPC message content
+ */
+static void send_dhcpv4_response(struct data_string *raw) {
+       struct interface_info *ip;
+       char name[16 + 1];
+       struct sockaddr_in6 to_addr;
+       char pbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+       struct udp_data4o6 udp_data;
+       int send_ret;
+
+       memset(name, 0, sizeof(name));
+       memcpy(name, raw->data, 16);
+       for (ip = interfaces; ip != NULL; ip = ip->next) {
+               if (!strcmp(name, ip->name))
+                       break;
+       }
+       if (ip == NULL) {
+               log_error("send_dhcpv4_response: can't find interface %s.",
+                         name);
+               return;
+       }
+
+       memset(&to_addr, 0, sizeof(to_addr));
+       to_addr.sin6_family = AF_INET6;
+       memcpy(&to_addr.sin6_addr, raw->data + 16, 16);
+       memset(&udp_data, 0, sizeof(udp_data));
+       memcpy(&udp_data, raw->data + 32, 4);
+       if ((raw->data[36] == DHCPV6_RELAY_FORW) ||
+           (raw->data[36] == DHCPV6_RELAY_REPL)) {
+               if (udp_data.rsp_opt_exist) {
+                       to_addr.sin6_port = udp_data.src_port;
+               } else {
+                       to_addr.sin6_port = local_port;
+               }
+       } else {
+               to_addr.sin6_port = remote_port;
+       }
+
+       log_info("send_dhcpv4_response(): sending %s on %s to %s port %d",
+                dhcpv6_type_names[raw->data[36]],
+                name,
+                inet_ntop(AF_INET6, raw->data + 16, pbuf, sizeof(pbuf)),
+                ntohs(to_addr.sin6_port));
+
+       send_ret = send_packet6(ip, raw->data + 36, raw->len - 36, &to_addr);
+       if (send_ret < 0) {
+               log_error("send_dhcpv4_response: send_packet6(): %m");
+       } else if (send_ret != raw->len - 36) {
+               log_error("send_dhcpv4_response: send_packet6() "
+                         "sent %d of %d bytes",
+                         send_ret, raw->len - 36);
+       }
+}
+#endif /* DHCP4o6 */
+
+/*
+ * Schedule lease timeouts for all of the iasubopts in the reply.
+ * This is currently used to schedule timeouts for soft leases.
+ */
+
+static void
+schedule_lease_timeout_reply(struct reply_state *reply) {
+       struct iasubopt *tmp;
+       int i;
+
+       /* sanity check the reply */
+       if ((reply == NULL) || (reply->ia == NULL) || (reply->ia->iasubopt == NULL))
+               return;
+
+       /* walk through the list, scheduling as we go */
+       for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+               tmp = reply->ia->iasubopt[i];
+               schedule_lease_timeout(tmp->ipv6_pool);
+       }
+}
 
 /*
  * This function returns the time since DUID time start for the
@@ -131,7 +342,7 @@ duid_time(time_t when) {
 }
 
 
-/* 
+/*
  * Server DUID.
  *
  * This must remain the same for the lifetime of this server, because
@@ -139,9 +350,9 @@ duid_time(time_t when) {
  *
  * We pick the server DUID like this:
  *
- * 1. Check dhcpd.conf - any value the administrator has configured 
+ * 1. Check dhcpd.conf - any value the administrator has configured
  *    overrides any possible values.
- * 2. Check the leases.txt - we want to use the previous value if 
+ * 2. Check the leases.txt - we want to use the previous value if
  *    possible.
  * 3. Check if dhcpd.conf specifies a type of server DUID to use,
  *    and generate that type.
@@ -152,7 +363,7 @@ static struct data_string server_duid;
 /*
  * Check if the server_duid has been set.
  */
-isc_boolean_t 
+isc_boolean_t
 server_duid_isset(void) {
        return (server_duid.data != NULL);
 }
@@ -184,7 +395,7 @@ set_server_duid(struct data_string *new_duid) {
 
 /*
  * Set the server DUID based on the D6O_SERVERID option. This handles
- * the case where the administrator explicitly put it in the dhcpd.conf 
+ * the case where the administrator explicitly put it in the dhcpd.conf
  * file.
  */
 isc_result_t
@@ -200,15 +411,16 @@ set_server_duid_from_option(void) {
        }
 
        execute_statements_in_scope(NULL, NULL, NULL, NULL, NULL,
-                                   opt_state, &global_scope, root_group, NULL);
+                                   opt_state, &global_scope, root_group,
+                                   NULL, NULL);
 
        oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
        if (oc == NULL) {
                ret_val = ISC_R_NOTFOUND;
        } else {
                memset(&option_duid, 0, sizeof(option_duid));
-               if (!evaluate_option_cache(&option_duid, NULL, NULL, NULL, 
-                                          opt_state, NULL, &global_scope, 
+               if (!evaluate_option_cache(&option_duid, NULL, NULL, NULL,
+                                          opt_state, NULL, &global_scope,
                                           oc, MDL)) {
                        ret_val = ISC_R_UNEXPECTED;
                } else {
@@ -225,17 +437,17 @@ set_server_duid_from_option(void) {
 
 /*
  * DUID layout, as defined in RFC 3315, section 9.
- * 
+ *
  * We support type 1 (hardware address plus time) and type 3 (hardware
  * address).
  *
- * We can support type 2 for specific vendors in the future, if they 
+ * We can support type 2 for specific vendors in the future, if they
  * publish the specification. And of course there may be additional
  * types later.
  */
 static int server_duid_type = DUID_LLT;
 
-/* 
+/*
  * Set the DUID type.
  */
 void
@@ -244,7 +456,7 @@ set_server_duid_type(int type) {
 }
 
 /*
- * Generate a new server DUID. This is done if there was no DUID in 
+ * Generate a new server DUID. This is done if there was no DUID in
  * the leases.txt or in the dhcpd.conf file.
  */
 isc_result_t
@@ -259,7 +471,7 @@ generate_new_server_duid(void) {
        if ((server_duid_type != DUID_LL) && (server_duid_type != DUID_LLT)) {
                log_error("Invalid DUID type %d specified, "
                          "only LL and LLT types supported", server_duid_type);
-               return ISC_R_INVALIDARG;
+               return DHCP_R_INVALIDARG;
        }
 
        /*
@@ -282,32 +494,32 @@ generate_new_server_duid(void) {
        if (server_duid_type == DUID_LLT) {
                time_val = duid_time(time(NULL));
                generated_duid.len = 8 + p->hw_address.hlen - 1;
-               if (!buffer_allocate(&generated_duid.buffer, 
+               if (!buffer_allocate(&generated_duid.buffer,
                                     generated_duid.len, MDL)) {
                        log_fatal("No memory for server DUID.");
                }
                generated_duid.data = generated_duid.buffer->data;
                putUShort(generated_duid.buffer->data, DUID_LLT);
-               putUShort(generated_duid.buffer->data + 2, 
+               putUShort(generated_duid.buffer->data + 2,
                          p->hw_address.hbuf[0]);
                putULong(generated_duid.buffer->data + 4, time_val);
-               memcpy(generated_duid.buffer->data + 8, 
+               memcpy(generated_duid.buffer->data + 8,
                       p->hw_address.hbuf+1, p->hw_address.hlen-1);
        } else if (server_duid_type == DUID_LL) {
                generated_duid.len = 4 + p->hw_address.hlen - 1;
-               if (!buffer_allocate(&generated_duid.buffer, 
+               if (!buffer_allocate(&generated_duid.buffer,
                                     generated_duid.len, MDL)) {
                        log_fatal("No memory for server DUID.");
                }
                generated_duid.data = generated_duid.buffer->data;
                putUShort(generated_duid.buffer->data, DUID_LL);
-               putUShort(generated_duid.buffer->data + 2, 
+               putUShort(generated_duid.buffer->data + 2,
                          p->hw_address.hbuf[0]);
-               memcpy(generated_duid.buffer->data +4, 
+               memcpy(generated_duid.buffer->data + 4,
                       p->hw_address.hbuf+1, p->hw_address.hlen-1);
        } else {
                log_fatal("Unsupported server DUID type %d.", server_duid_type);
-       } 
+       }
 
        set_server_duid(&generated_duid);
        data_string_forget(&generated_duid, MDL);
@@ -326,7 +538,7 @@ get_client_id(struct packet *packet, struct data_string *client_id) {
         * Verify our client_id structure is empty.
         */
        if ((client_id->data != NULL) || (client_id->len != 0)) {
-               return ISC_R_INVALIDARG;
+               return DHCP_R_INVALIDARG;
        }
 
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_CLIENTID);
@@ -334,7 +546,7 @@ get_client_id(struct packet *packet, struct data_string *client_id) {
                return ISC_R_NOTFOUND;
        }
 
-       if (!evaluate_option_cache(client_id, packet, NULL, NULL, 
+       if (!evaluate_option_cache(client_id, packet, NULL, NULL,
                                   packet->options, NULL,
                                   &global_scope, oc, MDL)) {
                return ISC_R_FAILURE;
@@ -365,7 +577,7 @@ valid_client_msg(struct packet *packet, struct data_string *client_id) {
                        break;
                case ISC_R_NOTFOUND:
                        log_debug("Discarding %s from %s; "
-                                 "client identifier missing", 
+                                 "client identifier missing",
                                  dhcpv6_type_names[packet->dhcpv6_msg_type],
                                  piaddr(packet->client_addr));
                        goto exit;
@@ -382,7 +594,7 @@ valid_client_msg(struct packet *packet, struct data_string *client_id) {
         */
        if (packet->unicast) {
                log_debug("Discarding %s from %s; packet sent unicast "
-                         "(CLIENTID %s)", 
+                         "(CLIENTID %s)",
                          dhcpv6_type_names[packet->dhcpv6_msg_type],
                          piaddr(packet->client_addr),
                          print_hex_1(client_id->len, client_id->data, 60));
@@ -393,23 +605,23 @@ valid_client_msg(struct packet *packet, struct data_string *client_id) {
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
        if (oc != NULL) {
                if (evaluate_option_cache(&data, packet, NULL, NULL,
-                                         packet->options, NULL, 
+                                         packet->options, NULL,
                                          &global_scope, oc, MDL)) {
-                       log_debug("Discarding %s from %s; " 
+                       log_debug("Discarding %s from %s; "
                                  "server identifier found "
-                                 "(CLIENTID %s, SERVERID %s)", 
+                                 "(CLIENTID %s, SERVERID %s)",
                                  dhcpv6_type_names[packet->dhcpv6_msg_type],
                                  piaddr(packet->client_addr),
-                                 print_hex_1(client_id->len, 
+                                 print_hex_1(client_id->len,
                                              client_id->data, 60),
                                  print_hex_2(data.len,
                                              data.data, 60));
                } else {
-                       log_debug("Discarding %s from %s; " 
+                       log_debug("Discarding %s from %s; "
                                  "server identifier found "
-                                 "(CLIENTID %s)", 
+                                 "(CLIENTID %s)",
                                  dhcpv6_type_names[packet->dhcpv6_msg_type],
-                                 print_hex_1(client_id->len, 
+                                 print_hex_1(client_id->len,
                                              client_id->data, 60),
                                  piaddr(packet->client_addr));
                }
@@ -420,19 +632,15 @@ valid_client_msg(struct packet *packet, struct data_string *client_id) {
        ret_val = 1;
 
 exit:
-       if (data.len > 0) {
-               data_string_forget(&data, MDL);
-       }
+       data_string_forget(&data, MDL);
        if (!ret_val) {
-               if (client_id->len > 0) {
-                       data_string_forget(client_id, MDL);
-               }
+               data_string_forget(client_id, MDL);
        }
        return ret_val;
 }
 
 /*
- * Response validation, defined in RFC 3315, sections 15.4, 15.6, 15.8, 
+ * Response validation, defined in RFC 3315, sections 15.4, 15.6, 15.8,
  * 15.9 (slightly different wording, but same meaning):
  *
  *   Servers MUST discard any received Request message that meet any of
@@ -462,7 +670,7 @@ valid_client_resp(struct packet *packet,
                        break;
                case ISC_R_NOTFOUND:
                        log_debug("Discarding %s from %s; "
-                                 "client identifier missing", 
+                                 "client identifier missing",
                                  dhcpv6_type_names[packet->dhcpv6_msg_type],
                                  piaddr(packet->client_addr));
                        goto exit;
@@ -477,13 +685,13 @@ valid_client_resp(struct packet *packet,
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
        if (oc == NULL) {
                log_debug("Discarding %s from %s: "
-                         "server identifier missing (CLIENTID %s)", 
+                         "server identifier missing (CLIENTID %s)",
                          dhcpv6_type_names[packet->dhcpv6_msg_type],
                          piaddr(packet->client_addr),
                          print_hex_1(client_id->len, client_id->data, 60));
                goto exit;
        }
-       if (!evaluate_option_cache(server_id, packet, NULL, NULL, 
+       if (!evaluate_option_cache(server_id, packet, NULL, NULL,
                                   packet->options, NULL,
                                   &global_scope, oc, MDL)) {
                log_error("Error processing %s from %s; "
@@ -493,11 +701,11 @@ valid_client_resp(struct packet *packet,
                          print_hex_1(client_id->len, client_id->data, 60));
                goto exit;
        }
-       if ((server_duid.len != server_id->len) || 
+       if ((server_duid.len != server_id->len) ||
            (memcmp(server_duid.data, server_id->data, server_duid.len) != 0)) {
-               log_debug("Discarding %s from %s; " 
+               log_debug("Discarding %s from %s; "
                          "not our server identifier "
-                         "(CLIENTID %s, SERVERID %s, server DUID %s)", 
+                         "(CLIENTID %s, SERVERID %s, server DUID %s)",
                          dhcpv6_type_names[packet->dhcpv6_msg_type],
                          piaddr(packet->client_addr),
                          print_hex_1(client_id->len, client_id->data, 60),
@@ -511,12 +719,8 @@ valid_client_resp(struct packet *packet,
 
 exit:
        if (!ret_val) {
-               if (server_id->len > 0) {
-                       data_string_forget(server_id, MDL);
-               }
-               if (client_id->len > 0) {
-                       data_string_forget(client_id, MDL);
-               }
+               data_string_forget(server_id, MDL);
+               data_string_forget(client_id, MDL);
        }
        return ret_val;
 }
@@ -542,13 +746,14 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) {
 
        ret_val = 0;
        memset(server_id, 0, sizeof(*server_id));
+       memset(&client_id, 0, sizeof(client_id));
 
        /*
-        * Make a string that we can print out to give more 
+        * Make a string that we can print out to give more
         * information about the client if we need to.
         *
-        * By RFC 3315, Section 18.1.5 clients SHOULD have a 
-        * client-id on an Information-request packet, but it 
+        * By RFC 3315, Section 18.1.5 clients SHOULD have a
+        * client-id on an Information-request packet, but it
         * is not strictly necessary.
         */
        if (get_client_id(packet, &client_id) == ISC_R_SUCCESS) {
@@ -572,7 +777,7 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) {
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
        if (oc != NULL) {
                log_debug("Discarding %s from %s; "
-                         "IA_NA option present%s", 
+                         "IA_NA option present%s",
                          dhcpv6_type_names[packet->dhcpv6_msg_type],
                          piaddr(packet->client_addr), client_id_str);
                goto exit;
@@ -580,7 +785,7 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) {
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
        if (oc != NULL) {
                log_debug("Discarding %s from %s; "
-                         "IA_TA option present%s", 
+                         "IA_TA option present%s",
                          dhcpv6_type_names[packet->dhcpv6_msg_type],
                          piaddr(packet->client_addr), client_id_str);
                goto exit;
@@ -588,7 +793,7 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) {
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
        if (oc != NULL) {
                log_debug("Discarding %s from %s; "
-                         "IA_PD option present%s", 
+                         "IA_PD option present%s",
                          dhcpv6_type_names[packet->dhcpv6_msg_type],
                          piaddr(packet->client_addr), client_id_str);
                goto exit;
@@ -596,7 +801,7 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) {
 
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
        if (oc != NULL) {
-               if (!evaluate_option_cache(server_id, packet, NULL, NULL, 
+               if (!evaluate_option_cache(server_id, packet, NULL, NULL,
                                           packet->options, NULL,
                                           &global_scope, oc, MDL)) {
                        log_error("Error processing %s from %s; "
@@ -605,17 +810,17 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) {
                                  piaddr(packet->client_addr), client_id_str);
                        goto exit;
                }
-               if ((server_duid.len != server_id->len) || 
-                   (memcmp(server_duid.data, server_id->data, 
+               if ((server_duid.len != server_id->len) ||
+                   (memcmp(server_duid.data, server_id->data,
                            server_duid.len) != 0)) {
-                       log_debug("Discarding %s from %s; " 
+                       log_debug("Discarding %s from %s; "
                                  "not our server identifier "
-                                 "(SERVERID %s, server DUID %s)%s", 
+                                 "(SERVERID %s, server DUID %s)%s",
                                  dhcpv6_type_names[packet->dhcpv6_msg_type],
                                  piaddr(packet->client_addr),
-                                 print_hex_1(server_id->len, 
+                                 print_hex_1(server_id->len,
                                              server_id->data, 60),
-                                 print_hex_2(server_duid.len, 
+                                 print_hex_2(server_duid.len,
                                              server_duid.data, 60),
                                  client_id_str);
                        goto exit;
@@ -627,14 +832,12 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) {
 
 exit:
        if (!ret_val) {
-               if (server_id->len > 0) {
-                       data_string_forget(server_id, MDL);
-               }
+               data_string_forget(server_id, MDL);
        }
        return ret_val;
 }
 
-/* 
+/*
  * Options that we want to send, in addition to what was requested
  * via the ORO.
  */
@@ -645,12 +848,6 @@ static const int required_opts[] = {
        D6O_PREFERENCE,
        0
 };
-static const int required_opts_NAA[] = {
-       D6O_CLIENTID,
-       D6O_SERVERID,
-       D6O_STATUS_CODE,
-       0
-};
 static const int required_opts_solicit[] = {
        D6O_CLIENTID,
        D6O_SERVERID,
@@ -659,35 +856,46 @@ static const int required_opts_solicit[] = {
        D6O_IA_PD,
        D6O_RAPID_COMMIT,
        D6O_STATUS_CODE,
-       D6O_VENDOR_OPTS,
        D6O_RECONF_ACCEPT,
        D6O_PREFERENCE,
        0
 };
-static const int required_opts_IA_NA[] = {
-       D6O_IAADDR,
-       D6O_STATUS_CODE,
-       D6O_VENDOR_OPTS,
+static const int required_opts_agent[] = {
+       D6O_INTERFACE_ID,
+#if defined(RELAY_PORT)
+       D6O_RELAY_SOURCE_PORT,
+#endif
+       D6O_RELAY_MSG,
        0
 };
-/*
-static const int required_opts_IA_TA[] = {
+static const int required_opts_IA[] = {
        D6O_IAADDR,
        D6O_STATUS_CODE,
-       D6O_VENDOR_OPTS,
        0
 };
 static const int required_opts_IA_PD[] = {
        D6O_IAPREFIX,
        D6O_STATUS_CODE,
-       D6O_VENDOR_OPTS,
        0
 };
-*/
 static const int required_opts_STATUS_CODE[] = {
        D6O_STATUS_CODE,
        0
 };
+#ifdef DHCP4o6
+static const int required_opts_4o6[] = {
+       D6O_DHCPV4_MSG,
+       0
+};
+#endif
+
+static const int unicast_reject_opts[] = {
+       D6O_CLIENTID,
+       D6O_SERVERID,
+       D6O_STATUS_CODE,
+       0
+};
+
 
 /*
  * Extracts from packet contents an IA_* option, storing the IA structure
@@ -696,13 +904,13 @@ static const int required_opts_STATUS_CODE[] = {
  * where in the IA_* the DHCPv6 options commence.
  */
 static int
-get_encapsulated_IA_state(struct option_state **enc_opt_state, 
+get_encapsulated_IA_state(struct option_state **enc_opt_state,
                          struct data_string *enc_opt_data,
                          struct packet *packet,
                          struct option_cache *oc,
                          int offset)
 {
-       /* 
+       /*
         * Get the raw data for the encapsulated options.
         */
        memset(enc_opt_data, 0, sizeof(*enc_opt_data));
@@ -720,7 +928,7 @@ get_encapsulated_IA_state(struct option_state **enc_opt_state,
        }
 
        /*
-        * Now create the option state structure, and pass it to the 
+        * Now create the option state structure, and pass it to the
         * function that parses options.
         */
        *enc_opt_state = NULL;
@@ -729,8 +937,8 @@ get_encapsulated_IA_state(struct option_state **enc_opt_state,
                data_string_forget(enc_opt_data, MDL);
                return 0;
        }
-       if (!parse_option_buffer(*enc_opt_state, 
-                                enc_opt_data->data + offset, 
+       if (!parse_option_buffer(*enc_opt_state,
+                                enc_opt_data->data + offset,
                                 enc_opt_data->len - offset,
                                 &dhcpv6_universe)) {
                log_error("get_encapsulated_IA_state: error parsing options.");
@@ -756,10 +964,10 @@ set_status_code(u_int16_t status_code, const char *status_message,
        }
        d.data = d.buffer->data;
        putUShort(d.buffer->data, status_code);
-       memcpy(d.buffer->data + sizeof(status_code), 
+       memcpy(d.buffer->data + sizeof(status_code),
               status_message, d.len - sizeof(status_code));
-       if (!save_option_buffer(&dhcpv6_universe, opt_state, 
-                               d.buffer, (unsigned char *)d.data, d.len, 
+       if (!save_option_buffer(&dhcpv6_universe, opt_state,
+                               d.buffer, (unsigned char *)d.data, d.len,
                                D6O_STATUS_CODE, 0)) {
                log_error("set_status_code: error saving status code.");
                ret_val = 0;
@@ -770,13 +978,116 @@ set_status_code(u_int16_t status_code, const char *status_message,
        return ret_val;
 }
 
+void check_pool6_threshold(struct reply_state *reply,
+                          struct iasubopt *lease)
+{
+       struct ipv6_pond *pond;
+       isc_uint64_t used, count, high_threshold;
+       int poolhigh = 0, poollow = 0;
+       char *shared_name = "no name";
+       char tmp_addr[INET6_ADDRSTRLEN];
+
+       if ((lease->ipv6_pool == NULL) || (lease->ipv6_pool->ipv6_pond == NULL))
+               return;
+       pond = lease->ipv6_pool->ipv6_pond;
+
+       /* If the address range is too large to track, just skip all this. */
+       if (pond->jumbo_range == 1) {
+               return;
+       }
+
+       count = pond->num_total;
+       used = pond->num_active;
+
+       /* get network name for logging */
+       if ((pond->shared_network != NULL) &&
+           (pond->shared_network->name != NULL)) {
+               shared_name = pond->shared_network->name;
+       }
+
+       /* The logged flag indicates if we have already crossed the high
+        * threshold and emitted a log message.  If it is set we check to
+        * see if we have re-crossed the low threshold and need to reset
+        * things.  When we cross the high threshold we determine what
+        * the low threshold is and save it into the low_threshold value.
+        * When we cross that threshold we reset the logged flag and
+        * the low_threshold to 0 which allows the high threshold message
+        * to be emitted once again.
+        * if we haven't recrossed the boundry we don't need to do anything.
+        */
+       if (pond->logged !=0) {
+               if (used <= pond->low_threshold) {
+                       pond->low_threshold = 0;
+                       pond->logged = 0;
+                       log_error("Pool threshold reset - shared subnet: %s; "
+                                 "address: %s; low threshold %llu/%llu.",
+                                 shared_name,
+                                 inet_ntop(AF_INET6, &lease->addr,
+                                           tmp_addr, sizeof(tmp_addr)),
+                                 (long long unsigned)(used),
+                                 (long long unsigned)(count));
+               }
+               return;
+       }
+
+       /* find the high threshold */
+       if (get_option_int(&poolhigh, &server_universe, reply->packet, NULL,
+                          NULL, reply->packet->options, reply->opt_state,
+                          reply->opt_state, &lease->scope,
+                          SV_LOG_THRESHOLD_HIGH, MDL) == 0) {
+               /* no threshold bail out */
+               return;
+       }
+
+       /* We do have a threshold for this pool, see if its valid */
+       if ((poolhigh <= 0) || (poolhigh > 100)) {
+               /* not valid */
+               return;
+       }
+
+       /* we have a valid value, have we exceeded it */
+       high_threshold = FIND_POND6_PERCENT(count, poolhigh);
+       if (used < high_threshold) {
+               /* nope, no more to do */
+               return;
+       }
+
+       /* we've exceeded it, output a message */
+       log_error("Pool threshold exceeded - shared subnet: %s; "
+                 "address: %s; high threshold %d%% %llu/%llu.",
+                 shared_name,
+                 inet_ntop(AF_INET6, &lease->addr, tmp_addr, sizeof(tmp_addr)),
+                 poolhigh, (long long unsigned)(used),
+                 (long long unsigned)(count));
+
+       /* handle the low threshold now, if we don't
+        * have one we default to 0. */
+       if ((get_option_int(&poollow, &server_universe, reply->packet, NULL,
+                           NULL, reply->packet->options, reply->opt_state,
+                           reply->opt_state, &lease->scope,
+                           SV_LOG_THRESHOLD_LOW, MDL) == 0) ||
+           (poollow > 100)) {
+               poollow = 0;
+       }
+
+       /*
+        * If the low theshold is higher than the high threshold we continue to log
+        * If it isn't then we set the flag saying we already logged and determine
+        * what the reset threshold is.
+        */
+       if (poollow < poolhigh) {
+               pond->logged = 1;
+               pond->low_threshold = FIND_POND6_PERCENT(count, poollow);
+       }
+}
+
 /*
  * We have a set of operations we do to set up the reply packet, which
  * is the same for many message types.
  */
 static int
 start_reply(struct packet *packet,
-           const struct data_string *client_id, 
+           const struct data_string *client_id,
            const struct data_string *server_id,
            struct option_state **opt_state,
            struct dhcpv6_packet *reply)
@@ -795,7 +1106,7 @@ start_reply(struct packet *packet,
        }
        execute_statements_in_scope(NULL, packet, NULL, NULL,
                                    packet->options, *opt_state,
-                                   &global_scope, root_group, NULL);
+                                   &global_scope, root_group, NULL, NULL);
 
        /*
         * A small bit of special handling for Solicit messages.
@@ -839,21 +1150,21 @@ start_reply(struct packet *packet,
                }
        }
 
-       /* 
+       /*
         * Use the client's transaction identifier for the reply.
         */
-       memcpy(reply->transaction_id, packet->dhcpv6_transaction_id, 
+       memcpy(reply->transaction_id, packet->dhcpv6_transaction_id,
               sizeof(reply->transaction_id));
 
-       /* 
+       /*
         * RFC 3315, section 18.2 says we need server identifier and
         * client identifier.
         *
         * If the server ID is defined via the configuration file, then
-        * it will already be present in the option state at this point, 
+        * it will already be present in the option state at this point,
         * so we don't need to set it.
         *
-        * If we have a server ID passed in from the caller, 
+        * If we have a server ID passed in from the caller,
         * use that, otherwise use the global DUID.
         */
        oc = lookup_option(&dhcpv6_universe, *opt_state, D6O_SERVERID);
@@ -865,7 +1176,7 @@ start_reply(struct packet *packet,
                        server_id_data = server_id->data;
                        server_id_len = server_id->len;
                }
-               if (!save_option_buffer(&dhcpv6_universe, *opt_state, 
+               if (!save_option_buffer(&dhcpv6_universe, *opt_state,
                                        NULL, (unsigned char *)server_id_data,
                                        server_id_len, D6O_SERVERID, 0)) {
                                log_error("start_reply: "
@@ -875,10 +1186,10 @@ start_reply(struct packet *packet,
        }
 
        if (client_id->buffer != NULL) {
-               if (!save_option_buffer(&dhcpv6_universe, *opt_state, 
-                                       client_id->buffer, 
-                                       (unsigned char *)client_id->data, 
-                                       client_id->len, 
+               if (!save_option_buffer(&dhcpv6_universe, *opt_state,
+                                       client_id->buffer,
+                                       (unsigned char *)client_id->data,
+                                       client_id->len,
                                        D6O_CLIENTID, 0)) {
                        log_error("start_reply: error saving "
                                  "client identifier.");
@@ -897,7 +1208,7 @@ start_reply(struct packet *packet,
                           D6O_RECONF_ACCEPT);
        if (oc != NULL) {
                if (!save_option_buffer(&dhcpv6_universe, *opt_state,
-                                       NULL, (unsigned char *)"", 0, 
+                                       NULL, (unsigned char *)"", 0,
                                        D6O_RECONF_ACCEPT, 0)) {
                        log_error("start_reply: "
                                  "error saving RECONF_ACCEPT option.");
@@ -918,7 +1229,7 @@ start_reply(struct packet *packet,
  * requested_addr is the address the client wants
  */
 static isc_result_t
-try_client_v6_address(struct iaaddr **addr,
+try_client_v6_address(struct iasubopt **addr,
                      struct ipv6_pool *pool,
                      const struct data_string *requested_addr)
 {
@@ -926,199 +1237,590 @@ try_client_v6_address(struct iaaddr **addr,
        isc_result_t result;
 
        if (requested_addr->len < sizeof(tmp_addr)) {
-               return ISC_R_INVALIDARG;
+               return DHCP_R_INVALIDARG;
        }
        memcpy(&tmp_addr, requested_addr->data, sizeof(tmp_addr));
        if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr)) {
                return ISC_R_FAILURE;
        }
 
-       if (!ipv6_addr_in_pool(&tmp_addr, pool)) {
-               return ISC_R_FAILURE;
+       /*
+        * The address is not covered by this (or possibly any) dynamic
+        * range.
+        */
+       if (!ipv6_in_pool(&tmp_addr, pool)) {
+               return ISC_R_ADDRNOTAVAIL;
        }
 
        if (lease6_exists(pool, &tmp_addr)) {
                return ISC_R_ADDRINUSE;
        }
 
-       result = iaaddr_allocate(addr, MDL);
+       result = iasubopt_allocate(addr, MDL);
        if (result != ISC_R_SUCCESS) {
                return result;
        }
        (*addr)->addr = tmp_addr;
+       (*addr)->plen = 0;
 
-       result = add_lease6(pool, *addr, 0);
+       /* Default is soft binding for 2 minutes. */
+       result = add_lease6(pool, *addr, cur_time + 120);
        if (result != ISC_R_SUCCESS) {
-               iaaddr_dereference(addr, MDL);
+               iasubopt_dereference(addr, MDL);
        }
        return result;
 }
 
-/*
- * Get an IPv6 address for the client.
+/*!
  *
- * addr is the result (should be a pointer to NULL on entry)
- * packet is the information about the packet from the client
- * requested_iaaddr is a hint from the client
- * client_id is the DUID for the client
+ * \brief  Get an IPv6 address for the client.
+ *
+ * Attempt to find a usable address for the client.  We walk through
+ * the ponds checking for permit and deny then through the pools
+ * seeing if they have an available address.
+ *
+ * \param reply = the state structure for the current work on this request
+ *                if we create a lease we return it using reply->lease
+ *
+ * \return
+ * ISC_R_SUCCESS = we were able to find an address and are returning a
+ *                 pointer to the lease
+ * ISC_R_NORESOURCES = there don't appear to be any free addresses.  This
+ *                     is probabalistic.  We don't exhaustively try the
+ *                     address range, instead we hash the duid and if
+ *                     the address derived from the hash is in use we
+ *                     hash the address.  After a number of failures we
+ *                     conclude the pool is basically full.
  */
-static isc_result_t 
-pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network,
-               const struct data_string *client_id)
+static isc_result_t
+pick_v6_address(struct reply_state *reply)
 {
-       struct ipv6_pool *p;
+       struct ipv6_pool *p = NULL;
+       struct ipv6_pond *pond;
        int i;
        int start_pool;
        unsigned int attempts;
        char tmp_buf[INET6_ADDRSTRLEN];
+       struct iasubopt **addr = &reply->lease;
+        isc_uint64_t total = 0;
+        isc_uint64_t active = 0;
+        isc_uint64_t abandoned = 0;
+       int jumbo_range = 0;
+       char *shared_name = (reply->shared->name ?
+                            reply->shared->name : "(no name)");
 
        /*
-        * No pools, we're done.
+        * Do a quick walk through of the ponds and pools
+        * to see if we have any NA address pools
         */
-       if (shared_network->ipv6_pools == NULL) {
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (pond->ipv6_pools == NULL)
+                       continue;
+
+               for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) {
+                       if (p->pool_type == D6O_IA_NA)
+                               break;
+               }
+               if (p != NULL)
+                       break;
+       }
+
+       /* If we get here and p is NULL we have no useful pools */
+       if (p == NULL) {
                log_debug("Unable to pick client address: "
                          "no IPv6 pools on this shared network");
                return ISC_R_NORESOURCES;
        }
 
        /*
-        * Otherwise try to get a lease from the first subnet possible.
+        * We have at least one pool that could provide an address
+        * Now we walk through the ponds and pools again and check
+        * to see if the client is permitted and if an address is
+        * available
         *
-        * We start looking at the last pool we allocated from, unless
-        * it had a collision trying to allocate an address. This will
-        * tend to move us into less-filled pools.
+        * Within a given pond we start looking at the last pool we
+        * allocated from, unless it had a collision trying to allocate
+        * an address. This will tend to move us into less-filled pools.
         */
-       start_pool = shared_network->last_ipv6_pool;
-       i = start_pool;
-       do {
 
-               p = shared_network->ipv6_pools[i];
-               if (activate_lease6(p, addr, &attempts, 
-                                   client_id, 0) == ISC_R_SUCCESS) {
-                       /*
-                        * Record the pool used (or next one if there 
-                        * was a collision).
-                        */
-                       if (attempts > 1) {
-                               i++;
-                               if (shared_network->ipv6_pools[i] == NULL) {
-                                       i = 0;
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               isc_result_t result = ISC_R_FAILURE;
+
+               if (((pond->prohibit_list != NULL) &&
+                    (permitted(reply->packet, pond->prohibit_list))) ||
+                   ((pond->permit_list != NULL) &&
+                    (!permitted(reply->packet, pond->permit_list))))
+                       continue;
+
+#ifdef EUI_64
+               /* If pond is EUI-64 but client duid isn't a valid EUI-64
+                * id, then skip this pond */
+               if (pond->use_eui_64 &&
+                   !valid_eui_64_duid(&reply->ia->iaid_duid, IAID_LEN)) {
+                       continue;
+               }
+#endif
+
+               start_pool = pond->last_ipv6_pool;
+               i = start_pool;
+               do {
+                       p = pond->ipv6_pools[i];
+                       if (p->pool_type == D6O_IA_NA) {
+#ifdef EUI_64
+                               if (pond->use_eui_64) {
+                                       result =
+                                       create_lease6_eui_64(p, addr,
+                                                     &reply->ia->iaid_duid,
+                                                     cur_time + 120);
+                               }
+                               else
+#endif
+                               {
+                                       result =
+                                       create_lease6(p, addr, &attempts,
+                                                     &reply->ia->iaid_duid,
+                                                     cur_time + 120);
+
+                               }
+
+                               if (result == ISC_R_SUCCESS) {
+                                       /*
+                                        * Record the pool used (or next one if
+                                        * there was a collision).
+                                        */
+                                       if (attempts > 1) {
+                                               i++;
+                                               if (pond->ipv6_pools[i]
+                                                   == NULL) {
+                                                       i = 0;
+                                               }
+                                       }
+
+                                       pond->last_ipv6_pool = i;
+
+                                       log_debug("Picking pool address %s",
+                                                 inet_ntop(AF_INET6,
+                                                 &((*addr)->addr),
+                                                 tmp_buf, sizeof(tmp_buf)));
+                                       return (ISC_R_SUCCESS);
                                }
                        }
-                       shared_network->last_ipv6_pool = i;
 
-                       log_debug("Picking pool address %s",
-                                 inet_ntop(AF_INET6, &((*addr)->addr),
-                                           tmp_buf, sizeof(tmp_buf)));
-                       return ISC_R_SUCCESS;
-               }
+                       i++;
+                       if (pond->ipv6_pools[i] == NULL) {
+                               i = 0;
+                       }
+               } while (i != start_pool);
 
-               i++;
-               if (shared_network->ipv6_pools[i] == NULL) {
-                       i = 0;
+               if (result == ISC_R_NORESOURCES) {
+                       jumbo_range += pond->jumbo_range;
+                       total += pond->num_total;
+                       active += pond->num_active;
+                       abandoned += pond->num_abandoned;
                }
-       } while (i != start_pool);
+       }
 
        /*
         * If we failed to pick an IPv6 address from any of the subnets.
         * Presumably that means we have no addresses for the client.
         */
-       log_debug("Unable to pick client address: no addresses available");
+       if (jumbo_range != 0) {
+               log_debug("Unable to pick client address: "
+                         "no addresses available  - shared network %s: "
+                         " 2^64-1 < total, %llu active,  %llu abandoned",
+                         shared_name, (long long unsigned)(active - abandoned),
+                         (long long unsigned)(abandoned));
+       } else {
+               log_debug("Unable to pick client address: "
+                         "no addresses available  - shared network %s: "
+                         "%llu total, %llu active,  %llu abandoned",
+                         shared_name, (long long unsigned)(total),
+                         (long long unsigned)(active - abandoned),
+                         (long long unsigned)(abandoned));
+       }
+
        return ISC_R_NORESOURCES;
 }
 
 /*
- * lease_to_client() is called from several messages to construct a
- * reply that contains all that we know about the client's correct lease
- * (or projected lease).
- *
- * Solicit - "Soft" binding, ignore unknown addresses or bindings, just
- *          send what we "may" give them on a request.
- *
- * Request - "Hard" binding, but ignore supplied addresses (just provide what
- *          the client should really use).
- *
- * Renew   - "Hard" binding, but client-supplied addresses are 'real'.  Error
- * Rebind    out any "wrong" addresses the client sends.  This means we send
- *          an empty IA_NA with a status code of NoBinding or NotOnLink or
- *          possibly send the address with zeroed lifetimes.
- *
- * Information-Request - No binding.
+ * Try to get the IPv6 prefix the client asked for from the
+ * prefix pool.
  *
- * The basic structure is to traverse the client-supplied data first, and
- * validate and echo back any contents that can be.  If the client-supplied
- * data does not error out (on renew/rebind as above), but we did not send
- * any addresses, attempt to allocate one.
+ * pref is the result (should be a pointer to NULL on entry)
+ * pool is the prefix pool to search in
+ * requested_pref is the address the client wants
  */
-/* TODO: look at client hints for lease times */
-static void
-lease_to_client(struct data_string *reply_ret,
-               struct packet *packet, 
-               const struct data_string *client_id,
-               const struct data_string *server_id)
+static isc_result_t
+try_client_v6_prefix(struct iasubopt **pref,
+                    struct ipv6_pool *pool,
+                    const struct data_string *requested_pref)
 {
-       static struct reply_state reply;
-       struct option_cache *oc;
-       struct data_string packet_oro;
-       isc_boolean_t no_addrs_avail;
+       u_int8_t tmp_plen;
+       struct in6_addr tmp_pref;
+       struct iaddr ia;
+       isc_result_t result;
 
-       /* Locate the client.  */
-       if (shared_network_from_packet6(&reply.shared,
-                                       packet) != ISC_R_SUCCESS)
-               goto exit;
+       if (requested_pref->len < sizeof(tmp_plen) + sizeof(tmp_pref)) {
+               return DHCP_R_INVALIDARG;
+       }
 
-       /* 
-        * Initialize the reply.
-        */
-       packet_reference(&reply.packet, packet, MDL);
-       data_string_copy(&reply.client_id, client_id, MDL);
+       tmp_plen = (int) requested_pref->data[0];
+       if ((tmp_plen < 3) || (tmp_plen > 128)) {
+               return ISC_R_FAILURE;
+       }
 
-       if (!start_reply(packet, client_id, server_id, &reply.opt_state,
-                        &reply.buf.reply))
-               goto exit;
+       memcpy(&tmp_pref, requested_pref->data + 1, sizeof(tmp_pref));
+       if (IN6_IS_ADDR_UNSPECIFIED(&tmp_pref)) {
+               return ISC_R_FAILURE;
+       }
 
-       /* Set the write cursor to just past the reply header. */
-       reply.cursor = REPLY_OPTIONS_INDEX;
+       ia.len = 16;
+       memcpy(&ia.iabuf, &tmp_pref, 16);
+       if (!is_cidr_mask_valid(&ia, (int) tmp_plen)) {
+               return ISC_R_FAILURE;
+       }
 
-       /*
-        * Get the ORO from the packet, if any.
-        */
-       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ORO);
-       memset(&packet_oro, 0, sizeof(packet_oro));
-       if (oc != NULL) {
-               if (!evaluate_option_cache(&packet_oro, packet, 
-                                          NULL, NULL, 
-                                          packet->options, NULL,
-                                          &global_scope, oc, MDL)) {
-                       log_error("lease_to_client: error evaluating ORO.");
-                       goto exit;
-               }
+       if (!ipv6_in_pool(&tmp_pref, pool) ||
+           ((int)tmp_plen != pool->units)) {
+               return ISC_R_ADDRNOTAVAIL;
        }
 
-       /* 
-        * Find a host record that matches from the packet, if any, and is
-        * valid for the shared network the client is on.
-        */
-       if (find_hosts_by_option(&reply.host, packet, packet->options, MDL)) {
-               seek_shared_host(&reply.host, reply.shared);
+       if (prefix6_exists(pool, &tmp_pref, tmp_plen)) {
+               return ISC_R_ADDRINUSE;
        }
 
-       if ((reply.host == NULL) &&
-           find_hosts_by_uid(&reply.host, client_id->data, client_id->len,
-                             MDL)) {
-               seek_shared_host(&reply.host, reply.shared);
+       result = iasubopt_allocate(pref, MDL);
+       if (result != ISC_R_SUCCESS) {
+               return result;
        }
 
-       /* Process the client supplied IA_NA's onto the reply buffer. */
-       reply.ia_count = 0;
-       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
-       no_addrs_avail = ISC_FALSE;
-       for (; oc != NULL ; oc = oc->next) {
-               isc_result_t status;
+       (*pref)->addr = tmp_pref;
+       (*pref)->plen = tmp_plen;
 
-               /* Start counting resources (addresses) offered. */
-               reply.client_resources = 0;
-               reply.ia_addrs_included = ISC_FALSE;
+       /* Default is soft binding for 2 minutes. */
+       result = add_lease6(pool, *pref, cur_time + 120);
+       if (result != ISC_R_SUCCESS) {
+               iasubopt_dereference(pref, MDL);
+       }
+
+       return result;
+}
+
+/*!
+ *
+ * \brief  Get an IPv6 prefix for the client.
+ *
+ * Attempt to find a usable prefix for the client.  Based upon the prefix
+ * length mode and the plen supplied by the client (if one), we make one
+ * or more calls to pick_v6_prefix_helper() to find a prefix as follows:
+ *
+ * PLM_IGNORE or client specifies a plen of zero, use the first available
+ * prefix regardless of it's length.
+ *
+ * PLM_PREFER â€“ look for an exact match to client's plen first, if none
+ * found, use the first available prefix of any length
+ *
+ * PLM_EXACT â€“ look for an exact match first, if none found then fail. This
+ * is the default behavior.
+ *
+ * PLM_MAXIMUM  - look for an exact match first, then the first available whose
+ * prefix length is less than client's plen, otherwise fail.
+ *
+ * PLM_MINIMUM  - look for an exact match first, then the first available whose
+ * prefix length is greater than client's plen, otherwise fail.
+ *
+ * Note that the selection mode is configurable at the global scope only via
+ * prefix-len-mode.
+ *
+ * \param reply = the state structure for the current work on this request
+ *                if we create a lease we return it using reply->lease
+ *
+ * \return
+ * ISC_R_SUCCESS = we were able to find an prefix and are returning a
+ *                 pointer to the lease
+ * ISC_R_NORESOURCES = there don't appear to be any free addresses.  This
+ *                     is probabalistic.  We don't exhaustively try the
+ *                     address range, instead we hash the duid and if
+ *                     the address derived from the hash is in use we
+ *                     hash the address.  After a number of failures we
+ *                     conclude the pool is basically full.
+ */
+static isc_result_t
+pick_v6_prefix(struct reply_state *reply) {
+        struct ipv6_pool *p = NULL;
+        struct ipv6_pond *pond;
+        int i;
+       isc_result_t result;
+
+       /*
+        * Do a quick walk through of the ponds and pools
+        * to see if we have any prefix pools
+       */
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (pond->ipv6_pools == NULL)
+                       continue;
+
+               for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) {
+                       if (p->pool_type == D6O_IA_PD)
+                               break;
+               }
+               if (p != NULL)
+                       break;
+       }
+
+       /* If we get here and p is NULL we have no useful pools */
+       if (p == NULL) {
+               log_debug("Unable to pick client prefix: "
+                         "no IPv6 pools on this shared network");
+               return ISC_R_NORESOURCES;
+       }
+
+       if (reply->preflen <= 0) {
+               /* If we didn't get a plen (-1) or client plen is 0, then just
+                * select first available (same as PLM_INGORE) */
+               result = pick_v6_prefix_helper(reply, PLM_IGNORE);
+       } else {
+               switch (prefix_length_mode) {
+               case PLM_PREFER:
+                       /* First we look for an exact match, if not found
+                        * then first available */
+                       result = pick_v6_prefix_helper(reply, PLM_EXACT);
+                       if (result != ISC_R_SUCCESS) {
+                               result = pick_v6_prefix_helper(reply,
+                                                             PLM_IGNORE);
+                       }
+                       break;
+
+               case PLM_EXACT:
+                       /* Match exactly or fail */
+                       result = pick_v6_prefix_helper(reply, PLM_EXACT);
+                       break;
+
+               case PLM_MINIMUM:
+               case PLM_MAXIMUM:
+                       /* First we look for an exact match, if not found
+                        * then first available by mode */
+                       result = pick_v6_prefix_helper(reply, PLM_EXACT);
+                       if (result != ISC_R_SUCCESS) {
+                               result = pick_v6_prefix_helper(reply,
+                                                           prefix_length_mode);
+                       }
+                       break;
+
+               default:
+                       /* First available */
+                       result = pick_v6_prefix_helper(reply, PLM_IGNORE);
+                       break;
+               }
+       }
+
+       if (result == ISC_R_SUCCESS) {
+               char tmp_buf[INET6_ADDRSTRLEN];
+
+               log_debug("Picking pool prefix %s/%u",
+                         inet_ntop(AF_INET6, &(reply->lease->addr),
+                                   tmp_buf, sizeof(tmp_buf)),
+                                   (unsigned)(reply->lease->plen));
+               return (ISC_R_SUCCESS);
+       }
+
+       /*
+        * If we failed to pick an IPv6 prefix
+        * Presumably that means we have no prefixes for the client.
+       */
+       log_debug("Unable to pick client prefix: no prefixes available");
+       return ISC_R_NORESOURCES;
+}
+
+/*!
+ *
+ * \brief  Get an IPv6 prefix for the client based upon selection mode.
+ *
+ * We walk through the ponds checking for permit and deny. If a pond is
+ * permissable to use, loop through its PD pools checking prefix lengths
+ * against the client plen based on the prefix length mode, looking for
+ * available prefixes.
+ *
+ * \param reply = the state structure for the current work on this request
+ *                if we create a lease we return it using reply->lease
+ * \prefix_mode = selection mode to use
+ *
+ * \return
+ * ISC_R_SUCCESS = we were able to find a prefix and are returning a
+ *                 pointer to the lease
+ * ISC_R_NORESOURCES = there don't appear to be any free addresses.  This
+ *                     is probabalistic.  We don't exhaustively try the
+ *                     address range, instead we hash the duid and if
+ *                     the address derived from the hash is in use we
+ *                     hash the address.  After a number of failures we
+ *                     conclude the pool is basically full.
+ */
+isc_result_t
+pick_v6_prefix_helper(struct reply_state *reply, int prefix_mode) {
+       struct ipv6_pool *p = NULL;
+       struct ipv6_pond *pond;
+       int i;
+       unsigned int attempts;
+       struct iasubopt **pref = &reply->lease;
+
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (((pond->prohibit_list != NULL) &&
+                    (permitted(reply->packet, pond->prohibit_list))) ||
+                   ((pond->permit_list != NULL) &&
+                    (!permitted(reply->packet, pond->permit_list))))
+                       continue;
+
+               for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) {
+                       if ((p->pool_type == D6O_IA_PD) &&
+                           (eval_prefix_mode(p->units, reply->preflen,
+                                             prefix_mode) == 1) &&
+                           (create_prefix6(p, pref, &attempts,
+                                           &reply->ia->iaid_duid,
+                                           cur_time + 120) == ISC_R_SUCCESS)) {
+                               return (ISC_R_SUCCESS);
+                       }
+               }
+       }
+
+       return ISC_R_NORESOURCES;
+}
+
+/*!
+ *
+ * \brief Test a prefix length against another based on prefix length mode
+ *
+ * \param len - prefix length to test
+ * \param preflen - preferred prefix length against which to test
+ * \param prefix_mode - prefix selection mode with which to test
+ *
+ * Note that the case of preferred length of 0 is not short-cut here as it
+ * is assumed to be done at a higher level.
+ *
+ * \return 1 if the given length is usable based upon mode and a preferred
+ * length, 0 if not.
+ */
+int
+eval_prefix_mode(int len, int preflen, int prefix_mode) {
+       int use_it = 1;
+       switch (prefix_mode) {
+       case PLM_EXACT:
+               use_it = (len == preflen);
+               break;
+       case PLM_MINIMUM:
+               /* they asked for a prefix length no "shorter" than preflen */
+               use_it = (len >= preflen);
+               break;
+       case PLM_MAXIMUM:
+               /* they asked for a prefix length no "longer" than preflen */
+               use_it = (len <= preflen);
+               break;
+       default:
+               /* otherwise use it */
+               break;
+       }
+
+       return (use_it);
+}
+
+/*
+ *! \file server/dhcpv6.c
+ *
+ * \brief construct a reply containing information about a client's lease
+ *
+ * lease_to_client() is called from several messages to construct a
+ * reply that contains all that we know about the client's correct lease
+ * (or projected lease).
+ *
+ * Solicit - "Soft" binding, ignore unknown addresses or bindings, just
+ *          send what we "may" give them on a request.
+ *
+ * Request - "Hard" binding, but ignore supplied addresses (just provide what
+ *          the client should really use).
+ *
+ * Renew   - "Hard" binding, but client-supplied addresses are 'real'.  Error
+ * Rebind    out any "wrong" addresses the client sends.  This means we send
+ *          an empty IA_NA with a status code of NoBinding or NotOnLink or
+ *          possibly send the address with zeroed lifetimes.
+ *
+ * Information-Request - No binding.
+ *
+ * The basic structure is to traverse the client-supplied data first, and
+ * validate and echo back any contents that can be.  If the client-supplied
+ * data does not error out (on renew/rebind as above), but we did not send
+ * any addresses, attempt to allocate one.
+ *
+ * At the end of the this function we call commit_leases_timed() to
+ * fsync and rotate the file as necessary.  commit_leases_timed() will
+ * check that we have written at least one lease to the file and that
+ * some time has passed before doing any fsync or file rewrite so we
+ * don't bother tracking if we did a write_ia during this function.
+ */
+/* TODO: look at client hints for lease times */
+
+static void
+lease_to_client(struct data_string *reply_ret,
+               struct packet *packet,
+               const struct data_string *client_id,
+               const struct data_string *server_id)
+{
+       static struct reply_state reply;
+       struct option_cache *oc;
+       struct data_string packet_oro;
+       int i;
+
+       memset(&packet_oro, 0, sizeof(packet_oro));
+
+       /* Locate the client.  */
+       if (shared_network_from_packet6(&reply.shared,
+                                       packet) != ISC_R_SUCCESS)
+               goto exit;
+
+       /*
+        * Initialize the reply.
+        */
+       packet_reference(&reply.packet, packet, MDL);
+       data_string_copy(&reply.client_id, client_id, MDL);
+
+       if (!start_reply(packet, client_id, server_id, &reply.opt_state,
+                        &reply.buf.reply))
+               goto exit;
+
+       /* Set the write cursor to just past the reply header. */
+       reply.cursor = REPLY_OPTIONS_INDEX;
+
+       /*
+        * Get the ORO from the packet, if any.
+        */
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ORO);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&packet_oro, packet,
+                                          NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("lease_to_client: error evaluating ORO.");
+                       goto exit;
+               }
+       }
+
+       /*
+        * Find a host record that matches the packet, if any, and is
+        * valid for the shared network the client is on.
+        */
+       if (find_hosts6(&reply.host, packet, client_id, MDL)) {
+               packet->known = 1;
+               seek_shared_host(&reply.host, reply.shared);
+       }
+
+       /* Process the client supplied IA's onto the reply buffer. */
+       reply.ia_count = 0;
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+
+       for (; oc != NULL ; oc = oc->next) {
+               isc_result_t status;
+
+               /* Start counting resources (addresses) offered. */
+               reply.client_resources = 0;
+               reply.resources_included = ISC_FALSE;
 
                status = reply_process_ia_na(&reply, oc);
 
@@ -1129,24 +1831,88 @@ lease_to_client(struct data_string *reply_ret,
                if ((status != ISC_R_SUCCESS) &&
                    (status != ISC_R_NORESOURCES))
                        goto exit;
+       }
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
+       for (; oc != NULL ; oc = oc->next) {
+               isc_result_t status;
+
+               /* Start counting resources (addresses) offered. */
+               reply.client_resources = 0;
+               reply.resources_included = ISC_FALSE;
+
+               status = reply_process_ia_ta(&reply, oc);
 
                /*
-                * If any address can be given to any IA, then do not set the
-                * NoAddrsAvail status code.
+                * We continue to try other IA's whether we can address
+                * this one or not.  Any other result is an immediate fail.
                 */
-               if (reply.client_resources == 0)
-                       no_addrs_avail = ISC_TRUE;
+               if ((status != ISC_R_SUCCESS) &&
+                   (status != ISC_R_NORESOURCES))
+                       goto exit;
        }
 
-       /* Do IA_TA and IA_PD */
+       /* Same for IA_PD's. */
+       reply.pd_count = 0;
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+       for (; oc != NULL ; oc = oc->next) {
+               isc_result_t status;
+
+               /* Start counting resources (prefixes) offered. */
+               reply.client_resources = 0;
+               reply.resources_included = ISC_FALSE;
+
+               status = reply_process_ia_pd(&reply, oc);
+
+               /*
+                * We continue to try other IA_PD's whether we can address
+                * this one or not.  Any other result is an immediate fail.
+                */
+               if ((status != ISC_R_SUCCESS) &&
+                   (status != ISC_R_NORESOURCES))
+                       goto exit;
+       }
 
        /*
         * Make no reply if we gave no resources and is not
         * for Information-Request.
         */
-       if ((reply.ia_count == 0) &&
-           (packet->dhcpv6_msg_type != DHCPV6_INFORMATION_REQUEST))
-               goto exit;
+       if ((reply.ia_count == 0) && (reply.pd_count == 0)) {
+               if (reply.packet->dhcpv6_msg_type !=
+                                           DHCPV6_INFORMATION_REQUEST)
+                       goto exit;
+
+               /*
+                * Because we only execute statements on a per-IA basis,
+                * we need to execute statements in any non-IA reply to
+                * source configuration.
+                */
+               execute_statements_in_scope(NULL, reply.packet, NULL, NULL,
+                                           reply.packet->options,
+                                           reply.opt_state, &global_scope,
+                                           reply.shared->group, root_group,
+                                           NULL);
+
+               /* Execute statements from class scopes. */
+               for (i = reply.packet->class_count; i > 0; i--) {
+                       execute_statements_in_scope(NULL, reply.packet,
+                                                   NULL, NULL,
+                                                   reply.packet->options,
+                                                   reply.opt_state,
+                                                   &global_scope,
+                                                   reply.packet->classes[i - 1]->group,
+                                                   reply.shared->group, NULL);
+               }
+
+               /* Bring in any configuration from a host record. */
+               if (reply.host != NULL)
+                       execute_statements_in_scope(NULL, reply.packet,
+                                                   NULL, NULL,
+                                                   reply.packet->options,
+                                                   reply.opt_state,
+                                                   &global_scope,
+                                                   reply.host->group,
+                                                   reply.shared->group, NULL);
+       }
 
        /*
         * RFC3315 section 17.2.2 (Solicit):
@@ -1158,6 +1924,9 @@ lease_to_client(struct data_string *reply_ret,
         * the user, a Server Identifier option with the server's DUID,
         * and a Client Identifier option with the client's DUID.
         *
+        * This has been updated by an errata such that the server
+        * can always send an IA.
+        *
         * Section 18.2.1 (Request):
         *
         * If the server cannot assign any addresses to an IA in the
@@ -1172,49 +1941,16 @@ lease_to_client(struct data_string *reply_ret,
         * the server.
         * Sends a Renew/Rebind if the IA is not in the Reply message.
         */
-       if (no_addrs_avail &&
-           (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT))
-       {
-               /* Set the NoAddrsAvail status code. */
-               if (!set_status_code(STATUS_NoAddrsAvail,
-                                    "No addresses available for this "
-                                    "interface.", reply.opt_state)) {
-                       log_error("lease_to_client: Unable to set "
-                                 "NoAddrsAvail status code.");
-                       goto exit;
-               }
-
-               /* Rewind the cursor to the start. */
-               reply.cursor = REPLY_OPTIONS_INDEX;
 
-               /*
-                * Produce an advertise that includes only:
-                *
-                * Status code.
-                * Server DUID.
-                * Client DUID.
-                */
-               reply.buf.reply.msg_type = DHCPV6_ADVERTISE;
-               reply.cursor += store_options6((char *)reply.buf.data +
-                                                       reply.cursor,
-                                              sizeof(reply.buf) -
-                                                       reply.cursor,
-                                              reply.opt_state, reply.packet,
-                                              required_opts_NAA,
-                                              NULL);
-       } else {
-               /*
-                * Having stored the client's IA_NA's, store any options that
-                * will fit in the remaining space.
-                */
-               reply.cursor += store_options6((char *)reply.buf.data +
-                                                       reply.cursor,
-                                              sizeof(reply.buf) -
-                                                       reply.cursor,
-                                              reply.opt_state, reply.packet,
-                                              required_opts_solicit,
-                                              &packet_oro);
-       }
+       /*
+        * Having stored the client's IA's, store any options that
+        * will fit in the remaining space.
+        */
+       reply.cursor += store_options6((char *)reply.buf.data + reply.cursor,
+                                      sizeof(reply.buf) - reply.cursor,
+                                      reply.opt_state, reply.packet,
+                                      required_opts_solicit,
+                                      &packet_oro);
 
        /* Return our reply to the caller. */
        reply_ret->len = reply.cursor;
@@ -1225,6 +1961,9 @@ lease_to_client(struct data_string *reply_ret,
        memcpy(reply_ret->buffer->data, reply.buf.data, reply.cursor);
        reply_ret->data = reply_ret->buffer->data;
 
+       /* If appropriate commit and rotate the lease file */
+       (void) commit_leases_timed();
+
       exit:
        /* Cleanup. */
        if (reply.shared != NULL)
@@ -1237,7 +1976,9 @@ lease_to_client(struct data_string *reply_ret,
                packet_dereference(&reply.packet, MDL);
        if (reply.client_id.data != NULL)
                data_string_forget(&reply.client_id, MDL);
-       reply.renew = reply.rebind = reply.prefer = reply.valid = 0;
+       if (packet_oro.buffer != NULL)
+               data_string_forget(&packet_oro, MDL);
+       reply.renew = reply.rebind = reply.min_prefer = reply.min_valid = 0;
        reply.cursor = 0;
 }
 
@@ -1252,15 +1993,13 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
        struct option_state *packet_ia;
        struct option_cache *oc;
        struct data_string ia_data, data;
-       isc_boolean_t lease_in_database;
 
        /* Initialize values that will get cleaned up on return. */
        packet_ia = NULL;
        memset(&ia_data, 0, sizeof(ia_data));
        memset(&data, 0, sizeof(data));
-       lease_in_database = ISC_FALSE;
-       /* 
-        * Note that find_client_address() may set reply->lease. 
+       /*
+        * Note that find_client_address() may set reply->lease.
         */
 
        /* Make sure there is at least room for the header. */
@@ -1273,7 +2012,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
        /* Fetch the IA_NA contents. */
        if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
                                       ia, IA_NA_OFFSET)) {
-               log_error("reply_process_ia_na: error evaluating ia_na");
+               log_error("reply_process_ia_na: error evaluating ia");
                status = ISC_R_FAILURE;
                goto cleanup;
        }
@@ -1284,18 +2023,18 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
        reply->rebind = getULong(ia_data.data + 8);
 
        /* Create an IA_NA structure. */
-       if (ia_na_allocate(&reply->ia_na, iaid, (char *)reply->client_id.data, 
-                          reply->client_id.len, MDL) != ISC_R_SUCCESS) {
-               log_error("lease_to_client: no memory for ia_na.");
+       if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data,
+                       reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+               log_error("reply_process_ia_na: no memory for ia.");
                status = ISC_R_NOMEMORY;
                goto cleanup;
        }
-       reply->ia_na->ia_type = D6O_IA_NA;
+       reply->ia->ia_type = D6O_IA_NA;
 
        /* Cache pre-existing IA, if any. */
-       ia_na_hash_lookup(&reply->old_ia_na, ia_na_active,
-                         (unsigned char *)reply->ia_na->iaid_duid.data,
-                         reply->ia_na->iaid_duid.len, MDL);
+       ia_hash_lookup(&reply->old_ia, ia_na_active,
+                      (unsigned char *)reply->ia->iaid_duid.data,
+                      reply->ia->iaid_duid.len, MDL);
 
        /*
         * Create an option cache to carry the IA_NA option contents, and
@@ -1308,6 +2047,8 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
 
        /* Check & cache the fixed host record. */
        if ((reply->host != NULL) && (reply->host->fixed_addr != NULL)) {
+               struct iaddr tmp_addr;
+
                if (!evaluate_option_cache(&reply->fixed, NULL, NULL, NULL,
                                           NULL, NULL, &global_scope,
                                           reply->host->fixed_addr, MDL)) {
@@ -1319,10 +2060,18 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
 
                if (reply->fixed.len < 16) {
                        log_error("reply_process_ia_na: invalid fixed address.");
-                       status = ISC_R_INVALIDARG;
+                       status = DHCP_R_INVALIDARG;
                        goto cleanup;
                }
 
+               /* Find the static lease's subnet. */
+               tmp_addr.len = 16;
+               memcpy(tmp_addr.iabuf, reply->fixed.data, 16);
+
+               if (find_grouped_subnet(&reply->subnet, reply->shared,
+                                       tmp_addr, MDL) == 0)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+
                reply->static_lease = ISC_TRUE;
        } else
                reply->static_lease = ISC_FALSE;
@@ -1355,17 +2104,17 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
        putULong(reply->buf.data + reply->cursor, reply->rebind);
        reply->cursor += 4;
 
-       /* 
+       /*
         * For each address in this IA_NA, decide what to do about it.
         *
         * Guidelines:
         *
-        * The client leaves unchanged any infomation about addresses
+        * The client leaves unchanged any information about addresses
         * it has recorded but are not included ("cancel/break" below).
         * A not included IA ("cleanup" below) could give a Renew/Rebind.
         */
        oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
-       reply->valid = reply->prefer = 0xffffffff;
+       reply->min_valid = reply->min_prefer = INFINITE_TIME;
        reply->client_valid = reply->client_prefer = 0;
        for (; oc != NULL ; oc = oc->next) {
                status = reply_process_addr(reply, oc);
@@ -1379,7 +2128,9 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                if (status == ISC_R_CANCELED)
                        break;
 
-               if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE))
+               if ((status != ISC_R_SUCCESS) &&
+                   (status != ISC_R_ADDRINUSE) &&
+                   (status != ISC_R_ADDRNOTAVAIL))
                        goto cleanup;
        }
 
@@ -1396,9 +2147,10 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                        switch (reply->packet->dhcpv6_msg_type) {
                              case DHCPV6_SOLICIT:
                                /*
-                                * No address for all the IA's is handled
+                                * No address for any IA is handled
                                 * by the caller.
                                 */
+                               /* FALL THROUGH */
 
                              case DHCPV6_REQUEST:
                                /* Section 18.2.1 (Request):
@@ -1452,7 +2204,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                                 * provide zero addresses including zeroed
                                 * lifetimes.
                                 */
-                               if (reply->ia_addrs_included)
+                               if (reply->resources_included)
                                        status = ISC_R_SUCCESS;
                                else
                                        goto cleanup;
@@ -1464,59 +2216,71 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                        goto cleanup;
        }
 
-       reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
-                                       sizeof(reply->buf) - reply->cursor,
-                                       reply->reply_ia, reply->packet,
-                                       required_opts_IA_NA, NULL);
-
-       /* Reset the length of this IA to match what was just written. */
-       putUShort(reply->buf.data + ia_cursor + 2,
-                 reply->cursor - (ia_cursor + 4));
+       /*
+        * yes, goto's aren't the best but we also want to avoid extra
+        * indents
+        */
+       if (status == ISC_R_CANCELED) {
+               /* We're replying with a status code so we still need to
+                * write it out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+               goto cleanup;
+       }
 
        /*
-        * T1/T2 time selection is kind of weird.  We actually use DHCP
-        * (v4) scoped options as handy existing places where these might
-        * be configured by an administrator.  A value of zero tells the
-        * client it may choose its own renewal time.
+        * Handle static leases, we always log stuff and if it's
+        * a hard binding we run any commit statements that we have
         */
-       reply->renew = 0;
-       oc = lookup_option(&dhcp_universe, reply->opt_state,
-                          DHO_DHCP_RENEWAL_TIME);
-       if (oc != NULL) {
-               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+       if (reply->static_lease) {
+               char tmp_addr[INET6_ADDRSTRLEN];
+               log_info("%s NA: address %s to client with duid %s iaid = %d "
+                        "static",
+                        dhcpv6_type_names[reply->buf.reply.msg_type],
+                        inet_ntop(AF_INET6, reply->fixed.data, tmp_addr,
+                                  sizeof(tmp_addr)),
+                        print_hex_1(reply->client_id.len,
+                                    reply->client_id.data, 60),
+                        iaid);
+
+               /* Write the lease out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+#ifdef NSUPDATE
+               /* Performs DDNS updates if we're configured to do them */
+               ddns_update_static6(reply);
+#endif
+               if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
+                   (reply->on_star.on_commit != NULL)) {
+                       execute_statements(NULL, reply->packet, NULL, NULL,
                                           reply->packet->options,
-                                          reply->opt_state, &global_scope,
-                                          oc, MDL) ||
-                   (data.len != 4)) {
-                       log_error("Invalid renewal time.");
-               } else {
-                       reply->renew = getULong(data.data);
+                                          reply->opt_state, NULL,
+                                          reply->on_star.on_commit, NULL);
+                       executable_statement_dereference
+                               (&reply->on_star.on_commit, MDL);
                }
+               goto cleanup;
+       }
 
-               if (data.data != NULL)
-                       data_string_forget(&data, MDL);
+       /*
+        * If we have any addresses log what we are doing.
+        */
+       if (reply->ia->num_iasubopt != 0) {
+               struct iasubopt *tmp;
+               int i;
+               char tmp_addr[INET6_ADDRSTRLEN];
+
+               for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+                       tmp = reply->ia->iasubopt[i];
+
+                       log_info("%s NA: address %s to client with duid %s "
+                                "iaid = %d valid for %u seconds",
+                                dhcpv6_type_names[reply->buf.reply.msg_type],
+                                inet_ntop(AF_INET6, &tmp->addr,
+                                          tmp_addr, sizeof(tmp_addr)),
+                                print_hex_1(reply->client_id.len,
+                                            reply->client_id.data, 60),
+                                iaid, tmp->valid);
+               }
        }
-       putULong(reply->buf.data + ia_cursor + 8, reply->renew);
-
-       /* Now T2. */
-       reply->rebind = 0;
-       oc = lookup_option(&dhcp_universe, reply->opt_state,
-                          DHO_DHCP_REBINDING_TIME);
-       if (oc != NULL) {
-               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
-                                          reply->packet->options,
-                                          reply->opt_state, &global_scope,
-                                          oc, MDL) ||
-                   (data.len != 4)) {
-                       log_error("Invalid rebinding time.");
-               } else {
-                       reply->rebind = getULong(data.data);
-               }
-
-               if (data.data != NULL)
-                       data_string_forget(&data, MDL);
-       }
-       putULong(reply->buf.data + ia_cursor + 12, reply->rebind);
 
        /*
         * If this is not a 'soft' binding, consume the new changes into
@@ -1525,27 +2289,45 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
         * Loop through the assigned dynamic addresses, referencing the
         * leases onto this IA_NA rather than any old ones, and updating
         * pool timers for each (if any).
+        *
+        * Note that we must do ddns_updates() before we test for lease
+        * reuse (so we'll know if DNS entries are different).  To ensure
+        * we don't break any configs, we run on_commit statements before
+        * we do ddns_updates() just in case the former affects the later.
+        * This is symetrical with v4 logic.  We always run on_commit and
+        * ddns_udpates() whether a lease is reused or renewed.
         */
-       if ((status != ISC_R_CANCELED) && !reply->static_lease &&
-           (reply->buf.reply.msg_type == DHCPV6_REPLY) &&
-           (reply->ia_na->num_iaaddr != 0)) {
-               struct iaaddr *tmp;
+       if ((reply->ia->num_iasubopt != 0) &&
+           (reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+               int must_commit = 0;
+               struct iasubopt *tmp;
                struct data_string *ia_id;
                int i;
 
-               for (i = 0 ; i < reply->ia_na->num_iaaddr ; i++) {
-                       tmp = reply->ia_na->iaaddr[i];
+               for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+                       tmp = reply->ia->iasubopt[i];
+                       if (tmp->ia != NULL) {
+                               ia_dereference(&tmp->ia, MDL);
+                       }
+
+                       ia_reference(&tmp->ia, reply->ia, MDL);
 
-                       if (tmp->ia_na != NULL)
-                               ia_na_dereference(&tmp->ia_na, MDL);
-                       ia_na_reference(&tmp->ia_na, reply->ia_na, MDL);
+                       /* If we have anything to do on commit do it now */
+                       if (tmp->on_star.on_commit != NULL) {
+                               execute_statements(NULL, reply->packet,
+                                                  NULL, NULL,
+                                                  reply->packet->options,
+                                                  reply->opt_state,
+                                                  &tmp->scope,
+                                                  tmp->on_star.on_commit,
+                                                  &tmp->on_star);
+                               executable_statement_dereference
+                                       (&tmp->on_star.on_commit, MDL);
+                       }
 
-                       schedule_lease_timeout(tmp->ipv6_pool);
+#if defined (NSUPDATE)
 
-                       /*
-                        * If this constitutes a 'hard' binding, perform ddns
-                        * updates.
-                        */
+                       /* Perform ddns updates */
                        oc = lookup_option(&server_universe, reply->opt_state,
                                           SV_DDNS_UPDATES);
                        if ((oc == NULL) ||
@@ -1558,40 +2340,48 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                                ddns_updates(reply->packet, NULL, NULL,
                                             tmp, NULL, reply->opt_state);
                        }
+#endif
+                       if (!reuse_lease6(reply, tmp)) {
+                               /* Commit 'hard' bindings. */
+                               must_commit = 1;
+                               renew_lease6(tmp->ipv6_pool, tmp);
+                               schedule_lease_timeout(tmp->ipv6_pool);
+
+                               /* Do our threshold check. */
+                               check_pool6_threshold(reply, tmp);
+                       }
                }
 
-               /* Remove any old ia_na from the hash. */
-               if (reply->old_ia_na != NULL) {
-                       ia_id = &reply->old_ia_na->iaid_duid;
-                       ia_na_hash_delete(ia_na_active,
-                                         (unsigned char *)ia_id->data,
-                                         ia_id->len, MDL);
-                       ia_na_dereference(&reply->old_ia_na, MDL);
-               }
+               /* write the IA_NA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
 
-               /* Put new ia_na into the hash. */
-               ia_id = &reply->ia_na->iaid_duid;
-               ia_na_hash_add(ia_na_active, (unsigned char *)ia_id->data,
-                              ia_id->len, reply->ia_na, MDL);
+               /* Remove any old ia from the hash. */
+               if (reply->old_ia != NULL) {
+                       if (!release_on_roam(reply)) {
+                               ia_id = &reply->old_ia->iaid_duid;
+                               ia_hash_delete(ia_na_active,
+                                              (unsigned char *)ia_id->data,
+                                              ia_id->len, MDL);
+                       }
 
-               write_ia(reply->ia_na);
+                       ia_dereference(&reply->old_ia, MDL);
+               }
 
-               /* 
-                * Note that we wrote the lease into the database,
-                * so that we know not to release it when we're done
-                * with this function.
-                */
-               lease_in_database = ISC_TRUE;
+               /* Put new ia into the hash. */
+               reply->ia->cltt = cur_time;
+               ia_id = &reply->ia->iaid_duid;
+               ia_hash_add(ia_na_active, (unsigned char *)ia_id->data,
+                           ia_id->len, reply->ia, MDL);
 
-       /*
-        * If this is a soft binding, we will check to see if we are 
-        * suggesting the existing database entry to the client.
-        */
-       } else if ((status != ISC_R_CANCELED) && !reply->static_lease &&
-           (reply->old_ia_na != NULL)) {
-               if (ia_na_equal(reply->old_ia_na, reply->ia_na)) {
-                       lease_in_database = ISC_TRUE;
+               /* If we couldn't reuse all of the iasubopts, we
+               * must update udpate the lease db */
+               if (must_commit) {
+                       write_ia(reply->ia);
                }
+       } else {
+               /* write the IA_NA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+               schedule_lease_timeout_reply(reply);
        }
 
       cleanup:
@@ -1603,18 +2393,22 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                data_string_forget(&ia_data, MDL);
        if (data.data != NULL)
                data_string_forget(&data, MDL);
-       if (reply->ia_na != NULL)
-               ia_na_dereference(&reply->ia_na, MDL);
-       if (reply->old_ia_na != NULL)
-               ia_na_dereference(&reply->old_ia_na, MDL);
-       if (reply->lease != NULL) {
-               if (!lease_in_database) {
-                       release_lease6(reply->lease->ipv6_pool, reply->lease);
-               }
-               iaaddr_dereference(&reply->lease, MDL);
-       }
+       if (reply->ia != NULL)
+               ia_dereference(&reply->ia, MDL);
+       if (reply->old_ia != NULL)
+               ia_dereference(&reply->old_ia, MDL);
+       if (reply->lease != NULL)
+               iasubopt_dereference(&reply->lease, MDL);
        if (reply->fixed.data != NULL)
                data_string_forget(&reply->fixed, MDL);
+       if (reply->subnet != NULL)
+               subnet_dereference(&reply->subnet, MDL);
+       if (reply->on_star.on_expiry != NULL)
+               executable_statement_dereference
+                       (&reply->on_star.on_expiry, MDL);
+       if (reply->on_star.on_release != NULL)
+               executable_statement_dereference
+                       (&reply->on_star.on_release, MDL);
 
        /*
         * ISC_R_CANCELED is a status code used by the addr processing to
@@ -1625,7 +2419,29 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
 }
 
 /*
- * Process an IAADDR within a given IA_NA, storing any IAADDR reply contents
+ * Writes the populated IA_xx in wire format to the reply buffer
+ */
+void
+write_to_packet(struct reply_state *reply, unsigned ia_cursor) {
+       reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+                                       sizeof(reply->buf) - reply->cursor,
+                                       reply->reply_ia, reply->packet,
+                                       (reply->ia->ia_type != D6O_IA_PD ?
+                                       required_opts_IA : required_opts_IA_PD),
+                                       NULL);
+
+       /* Reset the length of this IA to match what was just written. */
+       putUShort(reply->buf.data + ia_cursor + 2,
+                 reply->cursor - (ia_cursor + 4));
+
+       if (reply->ia->ia_type != D6O_IA_TA) {
+               /* Calculate T1/T2 and stuff them in the reply */
+               set_reply_tee_times(reply, ia_cursor);
+       }
+}
+
+/*
+ * Process an IAADDR within a given IA_xA, storing any IAADDR reply contents
  * into the reply's current ia-scoped option cache.  Returns ISC_R_CANCELED
  * in the event we are replying with a status code and do not wish to process
  * more IAADDRs within this IA.
@@ -1640,6 +2456,9 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
        struct option_cache *oc;
        struct data_string iaaddr, data;
        isc_result_t status = ISC_R_SUCCESS;
+#ifdef EUI_64
+       int invalid_for_eui_64 = 0;
+#endif
 
        /* Initializes values that will be cleaned up. */
        memset(&iaaddr, 0, sizeof(iaaddr));
@@ -1656,7 +2475,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
        }
 
        /* Extract this IAADDR option. */
-       if (!evaluate_option_cache(&iaaddr, reply->packet, NULL, NULL, 
+       if (!evaluate_option_cache(&iaaddr, reply->packet, NULL, NULL,
                                   reply->packet->options, NULL, &global_scope,
                                   addr, MDL) ||
            (iaaddr.len < IAADDR_OFFSET)) {
@@ -1677,7 +2496,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
            (reply->client_prefer > pref_life))
                reply->client_prefer = pref_life;
 
-       /* 
+       /*
         * Clients may choose to send :: as an address, with the idea to give
         * hints about preferred-lifetime or valid-lifetime.
         */
@@ -1701,12 +2520,41 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
                        break;
        }
 
+#ifdef EUI_64
+       if (subnet) {
+               /* If the requested address falls into an EUI-64 pool, then
+                * we need to verify if it has EUI-64 duid AND the requested
+                * address is correct for that duid.  If not we treat it just
+                * like an not-on-link request. */
+               struct ipv6_pool* pool = NULL;
+               struct in6_addr* addr = (struct in6_addr*)(iaaddr.data);
+               if ((find_ipv6_pool(&pool, D6O_IA_NA, addr) == ISC_R_SUCCESS)
+                   && (pool->ipv6_pond->use_eui_64) &&
+                  (!valid_for_eui_64_pool(pool, &reply->client_id, 0, addr))) {
+                       log_debug ("Requested address: %s,"
+                                  " not valid for EUI-64 pool",
+                                  pin6_addr(addr));
+                       invalid_for_eui_64 = 1;
+               }
+       }
+#endif
+
        /* Address not found on shared network. */
+#ifdef EUI_64
+       if ((subnet == NULL) || invalid_for_eui_64) {
+#else
        if (subnet == NULL) {
+#endif
                /* Ignore this address on 'soft' bindings. */
-               if (reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT)
+               if (reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
+                       /* disable rapid commit */
+                       reply->buf.reply.msg_type = DHCPV6_ADVERTISE;
+                       delete_option(&dhcpv6_universe,
+                                     reply->opt_state,
+                                     D6O_RAPID_COMMIT);
                        /* status remains success */
                        goto cleanup;
+               }
 
                /*
                 * RFC3315 section 18.2.1:
@@ -1763,6 +2611,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
                goto send_addr;
        }
 
+
        /* Verify the address belongs to the client. */
        if (!address_is_owned(reply, &tmp_addr)) {
                /*
@@ -1777,11 +2626,21 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
                    (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) {
                        status = reply_process_try_addr(reply, &tmp_addr);
 
-                       /* Either error out or skip this address. */
-                       if ((status != ISC_R_SUCCESS) && 
-                           (status != ISC_R_ADDRINUSE)) 
+                       /*
+                        * If the address is in use, or isn't in any dynamic
+                        * range, continue as normal.  If any other error was
+                        * found, error out.
+                        */
+                       if ((status != ISC_R_SUCCESS) &&
+                           (status != ISC_R_ADDRINUSE) &&
+                           (status != ISC_R_ADDRNOTAVAIL))
                                goto cleanup;
 
+                       /*
+                        * If we didn't honor this lease, for solicit and
+                        * request we simply omit it from our answer.  For
+                        * rebind, we send it with zeroed lifetimes.
+                        */
                        if (reply->lease == NULL) {
                                if (reply->packet->dhcpv6_msg_type ==
                                                        DHCPV6_REBIND) {
@@ -1840,13 +2699,13 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
                        log_fatal("Impossible condition at %s:%d.", MDL);
 
                scope = &global_scope;
-               group = reply->host->group;
+               group = reply->subnet->group;
        } else {
                if (reply->lease == NULL)
                        log_fatal("Impossible condition at %s:%d.", MDL);
 
                scope = &reply->lease->scope;
-               group = reply->shared->group;
+               group = reply->lease->ipv6_pool->ipv6_pond->group;
        }
 
        /*
@@ -1903,7 +2762,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
        if (data.data != NULL)
                data_string_forget(&data, MDL);
        if (reply->lease != NULL)
-               iaaddr_dereference(&reply->lease, MDL);
+               iasubopt_dereference(&reply->lease, MDL);
 
        return status;
 }
@@ -1917,6 +2776,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
 static isc_boolean_t
 address_is_owned(struct reply_state *reply, struct iaddr *addr) {
        int i;
+       struct ipv6_pond *pond;
 
        /*
         * This faults out addresses that don't match fixed addresses.
@@ -1926,1543 +2786,4714 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) {
                        log_fatal("Impossible condition at %s:%d.", MDL);
 
                if (memcmp(addr->iabuf, reply->fixed.data, 16) == 0)
-                       return ISC_TRUE;
+                       return (ISC_TRUE);
 
-               return ISC_FALSE;
+               return (ISC_FALSE);
        }
 
-       if ((reply->old_ia_na == NULL) || (reply->old_ia_na->num_iaaddr == 0))
-               return ISC_FALSE;
+       if ((reply->old_ia == NULL) || (reply->old_ia->num_iasubopt == 0))
+               return (ISC_FALSE);
 
-       for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) {
-               struct iaaddr *tmp;
+       for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+               struct iasubopt *tmp;
 
-               tmp = reply->old_ia_na->iaaddr[i];
+               tmp = reply->old_ia->iasubopt[i];
 
                if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) {
-                       iaaddr_reference(&reply->lease, tmp, MDL);
-                       return ISC_TRUE;
-               }
-       }
-
-       return ISC_FALSE;
-}
-
-/*
- * This function only returns failure on 'hard' failures.  If it succeeds,
- * it will leave a lease structure behind.
- */
-static isc_result_t
-reply_process_try_addr(struct reply_state *reply, struct iaddr *addr) {
-       isc_result_t status = ISC_R_FAILURE;
-       struct ipv6_pool *pool;
-       int i;
-       struct data_string data_addr;
+                       if (lease6_usable(tmp) == ISC_FALSE) {
+                               return (ISC_FALSE);
+                       }
 
-       if ((reply == NULL) || (reply->shared == NULL) ||
-           (reply->shared->ipv6_pools == NULL) || (addr == NULL) ||
-           (reply->lease != NULL))
-               return ISC_R_INVALIDARG;
+                       pond = tmp->ipv6_pool->ipv6_pond;
+                       if (((pond->prohibit_list != NULL) &&
+                            (permitted(reply->packet, pond->prohibit_list))) ||
+                           ((pond->permit_list != NULL) &&
+                            (!permitted(reply->packet, pond->permit_list))))
+                               return (ISC_FALSE);
 
-       memset(&data_addr, 0, sizeof(data_addr));
-       data_addr.len = addr->len;
-       data_addr.data = addr->iabuf;
+                       iasubopt_reference(&reply->lease, tmp, MDL);
 
-       for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
-               status = try_client_v6_address(&reply->lease, pool,
-                                              &data_addr);
-               if (status == ISC_R_SUCCESS)
-                       break;
+                       return (ISC_TRUE);
+               }
        }
 
-       /* Note that this is just pedantry.  There is no allocation to free. */
-       data_string_forget(&data_addr, MDL);
-       /* Return just the most recent status... */
-       return status;
+       return (ISC_FALSE);
 }
 
-/* Look around for an address to give the client.  First, look through the
- * old IA for addresses we can extend.  Second, try to allocate a new address.
- * Finally, actually add that address into the current reply IA.
+/* Process a client-supplied IA_TA.  This may append options to the tail of
+ * the reply packet being built in the reply_state structure.
  */
 static isc_result_t
-find_client_address(struct reply_state *reply) {
-       struct iaddr send_addr;
-       isc_result_t status = ISC_R_NORESOURCES;
-       struct iaaddr *lease, *best_lease = NULL;
-       struct binding_scope **scope;
-       struct group *group;
-       int i;
-
-       if (reply->host != NULL)
-               group = reply->host->group;
-       else
-               group = reply->shared->group;
-
-       if (reply->static_lease) {
-               if (reply->host == NULL)
-                       return ISC_R_INVALIDARG;
+reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
+       isc_result_t status = ISC_R_SUCCESS;
+       u_int32_t iaid;
+       unsigned ia_cursor;
+       struct option_state *packet_ia;
+       struct option_cache *oc;
+       struct data_string ia_data, data;
+       struct data_string iaaddr;
+       u_int32_t pref_life, valid_life;
+       struct iaddr tmp_addr;
 
-               send_addr.len = 16;
-               memcpy(send_addr.iabuf, reply->fixed.data, 16);
+       /* Initialize values that will get cleaned up on return. */
+       packet_ia = NULL;
+       memset(&ia_data, 0, sizeof(ia_data));
+       memset(&data, 0, sizeof(data));
+       memset(&iaaddr, 0, sizeof(iaaddr));
 
-               status = ISC_R_SUCCESS;
-               scope = &global_scope;
-               goto send_addr;
+       /* Make sure there is at least room for the header. */
+       if ((reply->cursor + IA_TA_OFFSET + 4) > sizeof(reply->buf)) {
+               log_error("reply_process_ia_ta: Reply too long for IA.");
+               return ISC_R_NOSPACE;
        }
 
-       if (reply->old_ia_na != NULL)  {
-               for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) {
-                       lease = reply->old_ia_na->iaaddr[i];
 
-                       best_lease = lease_compare(lease, best_lease);
-               }
+       /* Fetch the IA_TA contents. */
+       if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
+                                      ia, IA_TA_OFFSET)) {
+               log_error("reply_process_ia_ta: error evaluating ia");
+               status = ISC_R_FAILURE;
+               goto cleanup;
        }
 
-       /* Try to pick a new address if we didn't find one, or if we found an
-        * abandoned lease.
-        */
-       if ((best_lease == NULL) || (best_lease->state == FTS_ABANDONED)) {
-               status = pick_v6_address(&reply->lease, reply->shared,
-                                        &reply->client_id);
-       } else if (best_lease != NULL) {
-               iaaddr_reference(&reply->lease, best_lease, MDL);
-               status = ISC_R_SUCCESS;
-       }
+       /* Extract IA_TA header contents. */
+       iaid = getULong(ia_data.data);
 
-       /* Pick the abandoned lease as a last resort. */
-       if ((status == ISC_R_NORESOURCES) && (best_lease != NULL)) {
-               /* I don't see how this is supposed to be done right now. */
-               log_error("Reclaiming abandoned addresses is not yet "
-                         "supported.  Treating this as an out of space "
-                         "condition.");
-               /* lease_reference(&reply->lease, best_lease, MDL); */
+       /* Create an IA_TA structure. */
+       if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data,
+                       reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+               log_error("reply_process_ia_ta: no memory for ia.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
        }
+       reply->ia->ia_type = D6O_IA_TA;
 
-       /* Give up now if we didn't find a lease. */
-       if (status != ISC_R_SUCCESS)
-               return status;
-
-       if (reply->lease == NULL)
-               log_fatal("Impossible condition at %s:%d.", MDL);
-
-       scope = &reply->lease->scope;
-       group = reply->shared->group;
-
-       send_addr.len = 16;
-       memcpy(send_addr.iabuf, &reply->lease->addr, 16);
+       /* Cache pre-existing IA, if any. */
+       ia_hash_lookup(&reply->old_ia, ia_ta_active,
+                      (unsigned char *)reply->ia->iaid_duid.data,
+                      reply->ia->iaid_duid.len, MDL);
 
-      send_addr:
-       status = reply_process_is_addressed(reply, scope, group);
-       if (status != ISC_R_SUCCESS)
-               return status;
+       /*
+        * Create an option cache to carry the IA_TA option contents, and
+        * execute any user-supplied values into it.
+        */
+       if (!option_state_allocate(&reply->reply_ia, MDL)) {
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
 
-       status = reply_process_send_addr(reply, &send_addr);
-       return status;
-}
+       /*
+        * Temporary leases are dynamic by definition.
+        */
+       reply->static_lease = ISC_FALSE;
 
-/* Once an address is found for a client, perform several common functions;
- * Calculate and store valid and preferred lease times, draw client options
- * into the option state.
- */
-static isc_result_t
-reply_process_is_addressed(struct reply_state *reply,
-                          struct binding_scope **scope, struct group *group)
-{
-       isc_result_t status = ISC_R_SUCCESS;
-       struct data_string data;
-       struct option_cache *oc;
+       /*
+        * Save the cursor position at the start of the IA, so we can
+        * set length later.  We write a temporary
+        * header out now just in case we decide to adjust the packet
+        * within sub-process functions.
+        */
+       ia_cursor = reply->cursor;
 
-       /* Initialize values we will cleanup. */
-       memset(&data, 0, sizeof(data));
+       /* Initialize the IA_TA header.  First the code. */
+       putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_TA);
+       reply->cursor += 2;
 
-       /* Execute relevant options into root scope. */
-       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
-                                   reply->packet->options, reply->opt_state,
-                                   scope, group, root_group);
+       /* Then option length. */
+       putUShort(reply->buf.data + reply->cursor, 0x04u);
+       reply->cursor += 2;
 
-       /* Determine valid lifetime. */
-       if (reply->client_valid == 0)
-               reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME;
-       else
-               reply->send_valid = reply->client_valid;
+       /* Then IA_TA header contents; IAID. */
+       putULong(reply->buf.data + reply->cursor, iaid);
+       reply->cursor += 4;
 
-       oc = lookup_option(&server_universe, reply->opt_state,
-                          SV_DEFAULT_LEASE_TIME);
-       if (oc != NULL) {
-               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
-                                          reply->packet->options,
-                                          reply->opt_state,
-                                          scope, oc, MDL) ||
-                   (data.len != 4)) {
-                       log_error("reply_process_is_addressed: unable to "
-                                 "evaluate default lease time");
+       /*
+        * Deal with an IAADDR for lifetimes.
+        * For all or none, process IAADDRs as hints.
+        */
+       reply->min_valid = reply->min_prefer = INFINITE_TIME;
+       reply->client_valid = reply->client_prefer = 0;
+       oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
+       for (; oc != NULL; oc = oc->next) {
+               memset(&iaaddr, 0, sizeof(iaaddr));
+               if (!evaluate_option_cache(&iaaddr, reply->packet,
+                                          NULL, NULL,
+                                          reply->packet->options, NULL,
+                                          &global_scope, oc, MDL) ||
+                   (iaaddr.len < IAADDR_OFFSET)) {
+                       log_error("reply_process_ia_ta: error "
+                                 "evaluating IAADDR.");
                        status = ISC_R_FAILURE;
                        goto cleanup;
                }
+               /* The first 16 bytes are the IPv6 address. */
+               pref_life = getULong(iaaddr.data + 16);
+               valid_life = getULong(iaaddr.data + 20);
 
-               reply->send_valid = getULong(data.data);
-               data_string_forget(&data, MDL);
-       }
+               if ((reply->client_valid == 0) ||
+                   (reply->client_valid > valid_life))
+                       reply->client_valid = valid_life;
 
-       if (reply->client_prefer == 0)
-               reply->send_prefer = reply->send_valid;
-       else
-               reply->send_prefer = reply->client_prefer;
+               if ((reply->client_prefer == 0) ||
+                   (reply->client_prefer > pref_life))
+                       reply->client_prefer = pref_life;
 
-       if (reply->send_prefer >= reply->send_valid)
-               reply->send_prefer = (reply->send_valid / 2) +
-                                    (reply->send_valid / 8);
+               /* Nothing more if something has failed. */
+               if (status == ISC_R_CANCELED)
+                       continue;
 
-       oc = lookup_option(&server_universe, reply->opt_state,
-                          SV_PREFER_LIFETIME);
-       if (oc != NULL) {
-               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
-                                          reply->packet->options,
-                                          reply->opt_state,
-                                          scope, oc, MDL) ||
-                   (data.len != 4)) {
-                       log_error("reply_process_is_addressed: unable to "
-                                 "evaluate preferred lease time");
-                       status = ISC_R_FAILURE;
+               tmp_addr.len = 16;
+               memcpy(tmp_addr.iabuf, iaaddr.data, 16);
+               if (!temporary_is_available(reply, &tmp_addr))
+                       goto bad_temp;
+               status = reply_process_is_addressed(reply,
+                                                   &reply->lease->scope,
+                                                   reply->lease->ipv6_pool->ipv6_pond->group);
+               if (status != ISC_R_SUCCESS)
+                       goto bad_temp;
+               status = reply_process_send_addr(reply, &tmp_addr);
+               if (status != ISC_R_SUCCESS)
+                       goto bad_temp;
+               if (reply->lease != NULL)
+                       iasubopt_dereference(&reply->lease, MDL);
+               continue;
+
+       bad_temp:
+               /* Rewind the IA_TA to empty. */
+               option_state_dereference(&reply->reply_ia, MDL);
+               if (!option_state_allocate(&reply->reply_ia, MDL)) {
+                       status = ISC_R_NOMEMORY;
                        goto cleanup;
                }
-
-               reply->send_prefer = getULong(data.data);
-               data_string_forget(&data, MDL);
+               status = ISC_R_CANCELED;
+               reply->client_resources = 0;
+               reply->resources_included = ISC_FALSE;
+               if (reply->lease != NULL)
+                       iasubopt_dereference(&reply->lease, MDL);
        }
+       reply->ia_count++;
 
-       /* Note lowest values for later calculation of renew/rebind times. */
-       if (reply->prefer > reply->send_prefer)
-               reply->prefer = reply->send_prefer;
-
-       if (reply->valid > reply->send_valid)
-               reply->valid = reply->send_valid;
-
-#if 0
        /*
-        * XXX: Old 4.0.0 alpha code would change the host {} record
-        * XXX: uid upon lease assignment.  This was intended to cover the
-        * XXX: case where a client first identifies itself using vendor
-        * XXX: options in a solicit, or request, but later neglects to include
-        * XXX: these options in a Renew or Rebind.  It is not clear that this
-        * XXX: is required, and has some startling ramifications (such as
-        * XXX: how to recover this dynamic host {} state across restarts).
+        * Give the client temporary addresses.
         */
-       if (reply->host != NULL)
-               change_host_uid(host, reply->client_id->data,
-                               reply->client_id->len);
-#endif /* 0 */
+       if (reply->client_resources != 0)
+               goto store;
+       status = find_client_temporaries(reply);
+       if (status == ISC_R_NORESOURCES) {
+               switch (reply->packet->dhcpv6_msg_type) {
+                     case DHCPV6_SOLICIT:
+                       /*
+                        * No address for any IA is handled
+                        * by the caller.
+                        */
+                       /* FALL THROUGH */
+
+                     case DHCPV6_REQUEST:
+                       /* Section 18.2.1 (Request):
+                        *
+                        * If the server cannot assign any addresses to
+                        * an IA in the message from the client, the
+                        * server MUST include the IA in the Reply
+                        * message with no addresses in the IA and a
+                        * Status Code option in the IA containing
+                        * status code NoAddrsAvail.
+                        */
+                       option_state_dereference(&reply->reply_ia, MDL);
+                       if (!option_state_allocate(&reply->reply_ia,  MDL)) {
+                               log_error("reply_process_ia_ta: No "
+                                         "memory for option state wipe.");
+                               status = ISC_R_NOMEMORY;
+                               goto cleanup;
+                       }
 
-       /* Perform dynamic lease related update work. */
-       if (reply->lease != NULL) {
-               /* Advance (or rewind) the valid lifetime. */
-               reply->lease->valid_lifetime_end_time = cur_time +
-                                                       reply->send_valid;
-               renew_lease6(reply->lease->ipv6_pool, reply->lease);
+                       if (!set_status_code(STATUS_NoAddrsAvail,
+                                            "No addresses available "
+                                            "for this interface.",
+                                             reply->reply_ia)) {
+                               log_error("reply_process_ia_ta: Unable "
+                                         "to set NoAddrsAvail status code.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
+                       }
 
-               status = ia_na_add_iaaddr(reply->ia_na, reply->lease, MDL);
-               if (status != ISC_R_SUCCESS) {
-                       log_fatal("reply_process_addr: Unable to attach lease "
-                                 "to new IA: %s", isc_result_totext(status));
-               }
+                       status = ISC_R_SUCCESS;
+                       break;
 
-               /*
-                * If this is a new lease, make sure it is attached somewhere.
-                */
-               if (reply->lease->ia_na == NULL) {
-                       ia_na_reference(&reply->lease->ia_na, reply->ia_na,
-                                       MDL);
+                     default:
+                       /*
+                        * We don't want to include the IA if we
+                        * provide zero addresses including zeroed
+                        * lifetimes.
+                        */
+                       if (reply->resources_included)
+                               status = ISC_R_SUCCESS;
+                       else
+                               goto cleanup;
+                       break;
                }
-       }
-
-       /* Bring a copy of the relevant options into the IA scope. */
-       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
-                                   reply->packet->options, reply->reply_ia,
-                                   scope, group, root_group);
-
-      cleanup:
-       if (data.data != NULL)
-               data_string_forget(&data, MDL);
-
-       if (status == ISC_R_SUCCESS)
-               reply->client_resources++;
-
-       return status;
-}
-
-/* Simply send an IAADDR within the IA_NA scope as described. */
-static isc_result_t
-reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) {
-       isc_result_t status = ISC_R_SUCCESS;
-       struct data_string data;
+       } else if (status != ISC_R_SUCCESS)
+               goto cleanup;
 
-       memset(&data, 0, sizeof(data));
+      store:
 
-       /* Now append the lease. */
-       data.len = IAADDR_OFFSET;
-       if (!buffer_allocate(&data.buffer, data.len, MDL)) {
-               log_error("reply_process_send_addr: out of memory allocating "
-                         "new IAADDR buffer.");
-               status = ISC_R_NOMEMORY;
+       /*
+        * yes, goto's aren't the best but we also want to avoid extra
+        * indents
+        */
+       if (status == ISC_R_CANCELED) {
+               /* We're replying with a status code so we still need to
+               * write it out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
                goto cleanup;
        }
-       data.data = data.buffer->data;
-
-       memcpy(data.buffer->data, addr->iabuf, 16);
-       putULong(data.buffer->data + 16, reply->send_prefer);
-       putULong(data.buffer->data + 20, reply->send_valid);
 
-       if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
-                                 data.buffer, data.buffer->data,
-                                 data.len, D6O_IAADDR, 0)) {
-               log_error("reply_process_send_addr: unable to save IAADDR "
-                         "option");
-               status = ISC_R_FAILURE;
-               goto cleanup;
+       /*
+        * If we have any addresses log what we are doing.
+        */
+       if (reply->ia->num_iasubopt != 0) {
+               struct iasubopt *tmp;
+               int i;
+               char tmp_addr[INET6_ADDRSTRLEN];
+
+               for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+                       tmp = reply->ia->iasubopt[i];
+
+                       log_info("%s TA: address %s to client with duid %s "
+                                "iaid = %d valid for %u seconds",
+                                dhcpv6_type_names[reply->buf.reply.msg_type],
+                                inet_ntop(AF_INET6, &tmp->addr,
+                                          tmp_addr, sizeof(tmp_addr)),
+                                print_hex_1(reply->client_id.len,
+                                            reply->client_id.data, 60),
+                                iaid,
+                                tmp->valid);
+               }
        }
 
-       reply->ia_addrs_included = ISC_TRUE;
+       /*
+        * For hard bindings we consume the new changes into
+        * the database (if any have been attached to the ia_ta).
+        *
+        * Loop through the assigned dynamic addresses, referencing the
+        * leases onto this IA_TA rather than any old ones, and updating
+        * pool timers for each (if any).
+        */
+       if ((reply->ia->num_iasubopt != 0) &&
+           (reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+               int must_commit = 0;
+               struct iasubopt *tmp;
+               struct data_string *ia_id;
+               int i;
 
-      cleanup:
-       if (data.data != NULL)
-               data_string_forget(&data, MDL);
+               for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+                       tmp = reply->ia->iasubopt[i];
 
-       return status;
-}
+                       if (tmp->ia != NULL)
+                               ia_dereference(&tmp->ia, MDL);
+                       ia_reference(&tmp->ia, reply->ia, MDL);
 
-/* Choose the better of two leases. */
-static struct iaaddr *
-lease_compare(struct iaaddr *alpha, struct iaaddr *beta) {
-       if (alpha == NULL)
-               return beta;
-       if (beta == NULL)
-               return alpha;
+                       /* If we have anything to do on commit do it now */
+                       if (tmp->on_star.on_commit != NULL) {
+                               execute_statements(NULL, reply->packet,
+                                                  NULL, NULL,
+                                                  reply->packet->options,
+                                                  reply->opt_state,
+                                                  &tmp->scope,
+                                                  tmp->on_star.on_commit,
+                                                  &tmp->on_star);
+                               executable_statement_dereference
+                                       (&tmp->on_star.on_commit, MDL);
+                       }
 
-       switch(alpha->state) {
-             case FTS_ACTIVE:
-               switch(beta->state) {
-                     case FTS_ACTIVE:
-                       /* Choose the lease with the longest lifetime (most
-                        * likely the most recently allocated).
+#if defined (NSUPDATE)
+                       /*
+                        * Perform ddns updates.
                         */
-                       if (alpha->valid_lifetime_end_time < 
-                           beta->valid_lifetime_end_time)
-                               return beta;
-                       else
-                               return alpha;
+                       oc = lookup_option(&server_universe, reply->opt_state,
+                                          SV_DDNS_UPDATES);
+                       if ((oc == NULL) ||
+                           evaluate_boolean_option_cache(NULL, reply->packet,
+                                                         NULL, NULL,
+                                                       reply->packet->options,
+                                                         reply->opt_state,
+                                                         &tmp->scope,
+                                                         oc, MDL)) {
+                               ddns_updates(reply->packet, NULL, NULL,
+                                            tmp, NULL, reply->opt_state);
+                       }
+#endif
 
-                     case FTS_EXPIRED:
-                     case FTS_ABANDONED:
-                       return alpha;
+                       if (!reuse_lease6(reply, tmp)) {
+                               /* Commit 'hard' bindings. */
+                               must_commit = 1;
+                               renew_lease6(tmp->ipv6_pool, tmp);
+                               schedule_lease_timeout(tmp->ipv6_pool);
 
-                     default:
-                       log_fatal("Impossible condition at %s:%d.", MDL);
+                               /* Do our threshold check. */
+                               check_pool6_threshold(reply, tmp);
+                       }
                }
-               break;
 
-             case FTS_EXPIRED:
-               switch (beta->state) {
-                     case FTS_ACTIVE:
-                       return beta;
+               /* write the IA_TA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
 
-                     case FTS_EXPIRED:
-                       /* Choose the most recently expired lease. */
-                       if (alpha->valid_lifetime_end_time <
-                           beta->valid_lifetime_end_time)
-                               return beta;
-                       else
-                               return alpha;
+               /* Remove any old ia from the hash. */
+               if (reply->old_ia != NULL) {
+                       if (!release_on_roam(reply)) {
+                               ia_id = &reply->old_ia->iaid_duid;
+                               ia_hash_delete(ia_ta_active,
+                                              (unsigned char *)ia_id->data,
+                                              ia_id->len, MDL);
+                       }
 
-                     case FTS_ABANDONED:
-                       return alpha;
+                       ia_dereference(&reply->old_ia, MDL);
+               }
 
-                     default:
-                       log_fatal("Impossible condition at %s:%d.", MDL);
+               /* Put new ia into the hash. */
+               reply->ia->cltt = cur_time;
+               ia_id = &reply->ia->iaid_duid;
+               ia_hash_add(ia_ta_active, (unsigned char *)ia_id->data,
+                           ia_id->len, reply->ia, MDL);
+
+               /* If we couldn't reuse all of the iasubopts, we
+               * must update udpate the lease db */
+               if (must_commit) {
+                       write_ia(reply->ia);
                }
-               break;
+       } else {
+               /* write the IA_TA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+               schedule_lease_timeout_reply(reply);
+       }
 
-             case FTS_ABANDONED:
-               switch (beta->state) {
-                     case FTS_ACTIVE:
-                     case FTS_EXPIRED:
-                       return alpha;
+      cleanup:
+       if (packet_ia != NULL)
+               option_state_dereference(&packet_ia, MDL);
+       if (iaaddr.data != NULL)
+               data_string_forget(&iaaddr, MDL);
+       if (reply->reply_ia != NULL)
+               option_state_dereference(&reply->reply_ia, MDL);
+       if (ia_data.data != NULL)
+               data_string_forget(&ia_data, MDL);
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (reply->ia != NULL)
+               ia_dereference(&reply->ia, MDL);
+       if (reply->old_ia != NULL)
+               ia_dereference(&reply->old_ia, MDL);
+       if (reply->lease != NULL)
+               iasubopt_dereference(&reply->lease, MDL);
 
-                     case FTS_ABANDONED:
-                       /* Choose the lease that was abandoned longest ago. */
-                       if (alpha->valid_lifetime_end_time <
-                           beta->valid_lifetime_end_time)
-                               return alpha;
+       /*
+        * ISC_R_CANCELED is a status code used by the addr processing to
+        * indicate we're replying with other addresses.  This is still a
+        * success at higher layers.
+        */
+       return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
+}
+/*
+ * Determines if a lease (iasubopt) can be reused without extending it.
+ * If dhcp-cache-threshold is greater than zero (i.e enabled) then
+ * a lease may be reused without going through a full renewal if
+ * it meets all the requirements.  In short it must be active, younger
+ * than the threshold, and not have DNS changes.
+ *
+ * If it is determined that it can be reused, that a call to
+ * shorten_lifetimes() is made to reduce the valid and preferred lifetimes
+ * sent to the client by the age of the lease.
+ *
+ * Returns 1 if lease can be reused, 0 otherwise
+ */
+int
+reuse_lease6(struct reply_state *reply, struct iasubopt *lease) {
+       int threshold = DEFAULT_CACHE_THRESHOLD;
+       struct option_cache* oc = NULL;
+       struct data_string d1;
+       time_t age;
+       time_t limit;
+       int reuse_it = 0;
+
+       /* In order to even qualify for reuse consideration:
+        * 1. Lease must be active
+        * 2. It must have been accepted at least once
+        * 3. DNS info must not have changed */
+       if ((lease->state != FTS_ACTIVE) ||
+           (lease->hard_lifetime_end_time == 0) ||
+           (lease->ddns_cb != NULL)) {
+               return (0);
+       }
+
+       /* Look up threshold value */
+       memset(&d1, 0, sizeof(struct data_string));
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_CACHE_THRESHOLD);
+       if (oc &&
+           evaluate_option_cache(&d1, reply->packet, NULL, NULL,
+                                 reply->packet->options, reply->opt_state,
+                                 &lease->scope, oc, MDL)) {
+                       if (d1.len == 1 && (d1.data[0] < 100)) {
+                                threshold = d1.data[0];
+                       }
 
-                     default:
-                       log_fatal("Impossible condition at %s:%d.", MDL);
-               }
-               break;
+               data_string_forget(&d1, MDL);
+       }
 
-             default:
-               log_fatal("Impossible condition at %s:%d.", MDL);
+       if (threshold <= 0) {
+               return (0);
        }
 
-       log_fatal("Triple impossible condition at %s:%d.", MDL);
-       return NULL;
+       if (lease->valid >= MAX_TIME) {
+               /* Infinite leases are always reused.  We have to make
+               * a choice because we cannot determine when they actually
+               * began, so we either always reuse them or we never do. */
+               log_debug ("reusing infinite lease for: %s%s",
+                           pin6_addr(&lease->addr), iasubopt_plen_str(lease));
+               return (1);
+       }
+
+       age = cur_tv.tv_sec - (lease->hard_lifetime_end_time - lease->valid);
+       if (lease->valid <= (INT_MAX / threshold))
+               limit = lease->valid * threshold / 100;
+       else
+               limit = lease->valid / 100 * threshold;
+
+       if (age < limit) {
+               /* Reduce valid/preferred going to the client by age */
+               shorten_lifetimes(reply, lease, age, threshold);
+               reuse_it = 1;
+       }
+
+       return (reuse_it);
 }
 
 /*
- * Solicit is how a client starts requesting addresses.
+ * Reduces the valid and preferred lifetimes for a given lease (iasubopt)
  *
- * If the client asks for rapid commit, and we support it, we will 
- * allocate the addresses and reply.
+ * We cannot determine until after a iasubopt has been added to
+ * the reply if the lease can be reused. Therefore, when we do reuse a
+ * lease we need a way to alter the lifetimes that will be sent to the client.
+ * That's where this function comes in handy:
  *
- * Otherwise we will send an advertise message.
+ * Locate the iasubopt by it's address within the reply the reduce both
+ * the preferred and valid lifetimes by the given number of seconds.
+ *
+ * Note that this function, by necessity, works directly with the
+ * option_cache data. Sort of a no-no but I don't have any better ideas.
  */
+void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease,
+                      time_t age, int threshold) {
+       struct option_cache* oc = NULL;
+       int subopt_type;
+       int addr_offset;
+       int pref_offset;
+       int val_offset;
+       int exp_length;
+
+       if (reply->ia->ia_type != D6O_IA_PD) {
+               subopt_type = D6O_IAADDR;
+               addr_offset = IASUBOPT_NA_ADDR_OFFSET;
+               pref_offset = IASUBOPT_NA_PREF_OFFSET;
+               val_offset = IASUBOPT_NA_VALID_OFFSET;
+               exp_length = IASUBOPT_NA_LEN;
+       }
+       else {
+               subopt_type = D6O_IAPREFIX;
+               addr_offset = IASUBOPT_PD_PREFIX_OFFSET;
+               pref_offset = IASUBOPT_PD_PREF_OFFSET;
+               val_offset = IASUBOPT_PD_VALID_OFFSET;
+               exp_length = IASUBOPT_PD_LEN;
+       }
+
+       // loop through the iasubopts for the one that matches this lease
+       oc = lookup_option(&dhcpv6_universe, reply->reply_ia, subopt_type);
+        for (; oc != NULL ; oc = oc->next) {
+               if (oc->data.data == NULL || oc->data.len != exp_length) {
+                       /* shouldn't happen */
+                       continue;
+               }
 
-static void
-dhcpv6_solicit(struct data_string *reply_ret, struct packet *packet) {
-       struct data_string client_id;
+               /* If address matches (and for PDs the prefix len matches)
+               * we assume this is our subopt, so update the lifetimes */
+               if (!memcmp(oc->data.data + addr_offset, &lease->addr, 16) &&
+                   (subopt_type != D6O_IAPREFIX ||
+                    (oc->data.data[IASUBOPT_PD_PREFLEN_OFFSET] ==
+                     lease->plen))) {
+                       u_int32_t pref_life = getULong(oc->data.data +
+                                                      pref_offset);
+                       u_int32_t valid_life = getULong(oc->data.data +
+                                                       val_offset);
+
+                       if (pref_life < MAX_TIME && pref_life > age) {
+                               pref_life -= age;
+                               putULong((unsigned char*)(oc->data.data) +
+                                         pref_offset, pref_life);
+
+                               if (reply->min_prefer > pref_life) {
+                                       reply->min_prefer = pref_life;
+                               }
+                       }
 
-       /* 
-        * Validate our input.
-        */
-       if (!valid_client_msg(packet, &client_id)) {
-               return;
-       }
+                       if (valid_life < MAX_TIME && valid_life > age) {
+                               valid_life -= age;
+                               putULong((unsigned char*)(oc->data.data) +
+                                        val_offset, valid_life);
 
-       lease_to_client(reply_ret, packet, &client_id, NULL);
+                               if (reply->min_valid > reply->send_valid) {
+                                       reply->min_valid = valid_life;
+                               }
+                       }
 
-       /*
-        * Clean up.
-        */
-       data_string_forget(&client_id, MDL);
+                       log_debug ("Reusing lease for: %s%s, "
+                                  "age %ld secs < %d%%,"
+                                  " sending shortened lifetimes -"
+                                  " preferred: %u, valid %u",
+                                  pin6_addr(&lease->addr),
+                                  iasubopt_plen_str(lease),
+                                  (long)age, threshold,
+                                  pref_life, valid_life);
+                       break;
+               }
+       }
 }
 
 /*
- * Request is how a client actually requests addresses.
- *
- * Very similar to Solicit handling, except the server DUID is required.
+ * Verify the temporary address is available.
  */
+static isc_boolean_t
+temporary_is_available(struct reply_state *reply, struct iaddr *addr) {
+       struct in6_addr tmp_addr;
+       struct subnet *subnet;
+       struct ipv6_pool *pool = NULL;
+       struct ipv6_pond *pond = NULL;
+       int i;
 
-/* TODO: reject unicast messages, unless we set unicast option */
-static void
-dhcpv6_request(struct data_string *reply_ret, struct packet *packet) {
-       struct data_string client_id;
-       struct data_string server_id;
+       memcpy(&tmp_addr, addr->iabuf, sizeof(tmp_addr));
+       /*
+        * Clients may choose to send :: as an address, with the idea to give
+        * hints about preferred-lifetime or valid-lifetime.
+        * So this is not a request for this address.
+        */
+       if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr))
+               return ISC_FALSE;
 
        /*
-        * Validate our input.
+        * Verify that this address is on the client's network.
         */
-       if (!valid_client_resp(packet, &client_id, &server_id)) {
-               return;
+       for (subnet = reply->shared->subnets ; subnet != NULL ;
+            subnet = subnet->next_sibling) {
+               if (addr_eq(subnet_number(*addr, subnet->netmask),
+                           subnet->net))
+                       break;
        }
 
+       /* Address not found on shared network. */
+       if (subnet == NULL)
+               return ISC_FALSE;
+
        /*
-        * Issue our lease.
+        * Check if this address is owned (must be before next step).
         */
-       lease_to_client(reply_ret, packet, &client_id, &server_id);
+       if (address_is_owned(reply, addr))
+               return ISC_TRUE;
 
        /*
-        * Cleanup.
+        * Verify that this address is in a temporary pool and try to get it.
         */
-       data_string_forget(&client_id, MDL);
-       data_string_forget(&server_id, MDL);
-}
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (((pond->prohibit_list != NULL) &&
+                    (permitted(reply->packet, pond->prohibit_list))) ||
+                   ((pond->permit_list != NULL) &&
+                    (!permitted(reply->packet, pond->permit_list))))
+                       continue;
 
-/* Find a DHCPv6 packet's shared network from hints in the packet.
+               for (i = 0 ; (pool = pond->ipv6_pools[i]) != NULL ; i++) {
+                       if (pool->pool_type != D6O_IA_TA)
+                               continue;
+
+                       if (ipv6_in_pool(&tmp_addr, pool))
+                               break;
+               }
+
+               if (pool != NULL)
+                       break;
+       }
+
+       if (pool == NULL)
+               return ISC_FALSE;
+       if (lease6_exists(pool, &tmp_addr))
+               return ISC_FALSE;
+       if (iasubopt_allocate(&reply->lease, MDL) != ISC_R_SUCCESS)
+               return ISC_FALSE;
+       reply->lease->addr = tmp_addr;
+       reply->lease->plen = 0;
+       /* Default is soft binding for 2 minutes. */
+       if (add_lease6(pool, reply->lease, cur_time + 120) != ISC_R_SUCCESS)
+               return ISC_FALSE;
+
+       return ISC_TRUE;
+}
+
+/*
+ * Get a temporary address per prefix.
  */
 static isc_result_t
-shared_network_from_packet6(struct shared_network **shared,
-                           struct packet *packet)
-{
-       const struct packet *chk_packet;
-       const struct in6_addr *link_addr, *first_link_addr;
-       struct iaddr tmp_addr;
-       struct subnet *subnet;
-       isc_result_t status;
-
-       if ((shared == NULL) || (*shared != NULL) || (packet == NULL))
-               return ISC_R_INVALIDARG;
+find_client_temporaries(struct reply_state *reply) {
+       int i;
+       struct ipv6_pool *p = NULL;
+       struct ipv6_pond *pond;
+       isc_result_t status = ISC_R_NORESOURCES;;
+       unsigned int attempts;
+       struct iaddr send_addr;
 
        /*
-        * First, find the link address where the packet from the client
-        * first appeared (if this packet was relayed).
+        * Do a quick walk through of the ponds and pools
+        * to see if we have any prefix pools
         */
-       first_link_addr = NULL;
-       chk_packet = packet->dhcpv6_container_packet;
-       while (chk_packet != NULL) {
-               link_addr = &chk_packet->dhcpv6_link_address;
-               if (!IN6_IS_ADDR_UNSPECIFIED(link_addr) &&
-                   !IN6_IS_ADDR_LINKLOCAL(link_addr)) {
-                       first_link_addr = link_addr;
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (pond->ipv6_pools == NULL)
+                       continue;
+
+               for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) {
+                       if (p->pool_type == D6O_IA_TA)
+                               break;
                }
-               chk_packet = chk_packet->dhcpv6_container_packet;
+               if (p != NULL)
+                       break;
        }
 
-       /*
-        * If there is a relayed link address, find the subnet associated
-        * with that, and use that to get the appropriate
-        * shared_network.
-        */
-       if (first_link_addr != NULL) {
-               tmp_addr.len = sizeof(*first_link_addr);
-               memcpy(tmp_addr.iabuf,
-                      first_link_addr, sizeof(*first_link_addr));
-               subnet = NULL;
-               if (!find_subnet(&subnet, tmp_addr, MDL)) {
-                       log_debug("No subnet found for link-address %s.",
-                                 piaddr(tmp_addr));
-                       return ISC_R_NOTFOUND;
-               }
-               status = shared_network_reference(shared,
-                                                 subnet->shared_network, MDL);
-               subnet_dereference(&subnet, MDL);
+       /* If we get here and p is NULL we have no useful pools */
+       if (p == NULL) {
+               log_debug("Unable to get client addresses: "
+                         "no IPv6 pools on this shared network");
+               return ISC_R_NORESOURCES;
+       }
 
        /*
-        * If there is no link address, we will use the interface
-        * that this packet came in on to pick the shared_network.
+        * We have at least one pool that could provide an address
+        * Now we walk through the ponds and pools again and check
+        * to see if the client is permitted and if an address is
+        * available
         */
-       } else {
-               status = shared_network_reference(shared,
-                                        packet->interface->shared_network,
-                                        MDL);
+
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (((pond->prohibit_list != NULL) &&
+                    (permitted(reply->packet, pond->prohibit_list))) ||
+                   ((pond->permit_list != NULL) &&
+                    (!permitted(reply->packet, pond->permit_list))))
+                       continue;
+
+               for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) {
+                       if (p->pool_type != D6O_IA_TA) {
+                               continue;
+                       }
+
+                       /*
+                        * Get an address in this temporary pool.
+                        */
+                       status = create_lease6(p, &reply->lease, &attempts,
+                                              &reply->client_id,
+                                               cur_time + 120);
+
+                       if (status != ISC_R_SUCCESS) {
+                               log_debug("Unable to get a temporary address.");
+                               goto cleanup;
+                       }
+
+                       status = reply_process_is_addressed(reply,
+                                                           &reply->lease->scope,
+                                                           pond->group);
+                       if (status != ISC_R_SUCCESS) {
+                               goto cleanup;
+                       }
+                       send_addr.len = 16;
+                       memcpy(send_addr.iabuf, &reply->lease->addr, 16);
+                       status = reply_process_send_addr(reply, &send_addr);
+                       if (status != ISC_R_SUCCESS) {
+                               goto cleanup;
+                       }
+                       /*
+                        * reply->lease can't be null as we use it above
+                        * add check if that changes
+                        */
+                       iasubopt_dereference(&reply->lease, MDL);
+               }
        }
 
+      cleanup:
+       if (reply->lease != NULL) {
+               iasubopt_dereference(&reply->lease, MDL);
+       }
        return status;
 }
 
 /*
- * When a client thinks it might be on a new link, it sends a 
- * Confirm message.
- *
- * From RFC3315 section 18.2.2:
- *
- *   When the server receives a Confirm message, the server determines
- *   whether the addresses in the Confirm message are appropriate for the
- *   link to which the client is attached.  If all of the addresses in the
- *   Confirm message pass this test, the server returns a status of
- *   Success.  If any of the addresses do not pass this test, the server
- *   returns a status of NotOnLink.  If the server is unable to perform
- *   this test (for example, the server does not have information about
- *   prefixes on the link to which the client is connected), or there were
- *   no addresses in any of the IAs sent by the client, the server MUST
- *   NOT send a reply to the client.
+ * This function only returns failure on 'hard' failures.  If it succeeds,
+ * it will leave a lease structure behind.
  */
+static isc_result_t
+reply_process_try_addr(struct reply_state *reply, struct iaddr *addr) {
+       isc_result_t status = ISC_R_ADDRNOTAVAIL;
+       struct ipv6_pool *pool = NULL;
+       struct ipv6_pond *pond = NULL;
+       int i;
+       struct data_string data_addr;
 
-static void
-dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
-       struct shared_network *shared;
-       struct subnet *subnet;
-       struct option_cache *ia, *ta, *oc;
-       struct data_string cli_enc_opt_data, iaaddr, client_id, packet_oro;
-       struct option_state *cli_enc_opt_state, *opt_state;
-       struct iaddr cli_addr;
-       int pass;
-       isc_boolean_t inappropriate, has_addrs;
-       char reply_data[65536];
-       struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
-       int reply_ofs = (int)((char *)reply->options - (char *)reply);
+       if ((reply == NULL) || (reply->shared == NULL) ||
+           (addr == NULL) || (reply->lease != NULL))
+               return (DHCP_R_INVALIDARG);
 
-       /* 
-        * Basic client message validation.
+       /*
+        * Do a quick walk through of the ponds and pools
+        * to see if we have any NA address pools
         */
-       memset(&client_id, 0, sizeof(client_id));
-       if (!valid_client_msg(packet, &client_id)) {
-               return;
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (pond->ipv6_pools == NULL)
+                       continue;
+
+               for (i = 0; ; i++) {
+                       pool = pond->ipv6_pools[i];
+                       if ((pool == NULL) ||
+                           (pool->pool_type == D6O_IA_NA))
+                               break;
+               }
+               if (pool != NULL)
+                       break;
        }
 
-       /* Do not process Confirms that do not have IA's we do not recognize.
-        */
-       ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
-       ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
-       if ((ia == NULL) && (ta == NULL))
-               return;
+       /* If we get here and p is NULL we have no useful pools */
+       if (pool == NULL) {
+               return (ISC_R_ADDRNOTAVAIL);
+       }
 
-       /* 
-        * Bit of variable initialization.
-        */
-       opt_state = cli_enc_opt_state = NULL;
-       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
-       memset(&iaaddr, 0, sizeof(iaaddr));
-       memset(&packet_oro, 0, sizeof(packet_oro));
+       memset(&data_addr, 0, sizeof(data_addr));
+       data_addr.len = addr->len;
+       data_addr.data = addr->iabuf;
 
-       /* Determine what shared network the client is connected to.  We
-        * must not respond if we don't have any information about the
-        * network the client is on.
+       /*
+        * We have at least one pool that could provide an address
+        * Now we walk through the ponds and pools again and check
+        * to see if the client is permitted and if an address is
+        * available
+        *
+        * Within a given pond we start looking at the last pool we
+        * allocated from, unless it had a collision trying to allocate
+        * an address. This will tend to move us into less-filled pools.
         */
-       shared = NULL;
-       if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) ||
-           (shared == NULL))
-               goto exit;
 
-       /* If there are no recorded subnets, then we have no
-        * information about this subnet - ignore Confirms.
-        */
-       subnet = shared->subnets;
-       if (subnet == NULL)
-               goto exit;
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (((pond->prohibit_list != NULL) &&
+                    (permitted(reply->packet, pond->prohibit_list))) ||
+                   ((pond->permit_list != NULL) &&
+                    (!permitted(reply->packet, pond->permit_list))))
+                       continue;
 
-       /* Are the addresses in all the IA's appropriate for that link? */
-       has_addrs = inappropriate = ISC_FALSE;
-       pass = D6O_IA_NA;
-       while(!inappropriate) {
-               /* If we've reached the end of the IA_NA pass, move to the
-                * IA_TA pass.
-                */
-               if ((pass == D6O_IA_NA) && (ia == NULL)) {
-                       pass = D6O_IA_TA;
-                       ia = ta;
+               for (i = 0 ; (pool = pond->ipv6_pools[i]) != NULL ; i++) {
+                       if (pool->pool_type != D6O_IA_NA)
+                               continue;
+
+                       status = try_client_v6_address(&reply->lease, pool,
+                                                      &data_addr);
+                       if (status == ISC_R_SUCCESS)
+                               break;
                }
 
-               /* If we've reached the end of all passes, we're done. */
-               if (ia == NULL)
+               if (status == ISC_R_SUCCESS)
                        break;
+       }
 
-               if (((pass == D6O_IA_NA) &&
-                    !get_encapsulated_IA_state(&cli_enc_opt_state,
-                                               &cli_enc_opt_data,
-                                               packet, ia, IA_NA_OFFSET)) ||
-                   ((pass == D6O_IA_TA) &&
-                    !get_encapsulated_IA_state(&cli_enc_opt_state,
-                                               &cli_enc_opt_data,
-                                               packet, ia, IA_TA_OFFSET))) {
-                       goto exit;
-               }
+       /* Note that this is just pedantry.  There is no allocation to free. */
+       data_string_forget(&data_addr, MDL);
+       /* Return just the most recent status... */
+       return (status);
+}
 
-               oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
-                                  D6O_IAADDR);
+/* Look around for an address to give the client.  First, look through the
+ * old IA for addresses we can extend.  Second, try to allocate a new address.
+ * Finally, actually add that address into the current reply IA.
+ */
+static isc_result_t
+find_client_address(struct reply_state *reply) {
+       struct iaddr send_addr;
+       isc_result_t status = ISC_R_NORESOURCES;
+       struct iasubopt *lease, *best_lease = NULL;
+       struct binding_scope **scope;
+       struct group *group;
+       int i;
 
-               for ( ; oc != NULL ; oc = oc->next) {
-                       if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
-                                                  packet->options, NULL,
-                                                  &global_scope, oc, MDL) ||
-                           (iaaddr.len < IAADDR_OFFSET)) {
-                               log_error("dhcpv6_confirm: "
-                                         "error evaluating IAADDR.");
-                               goto exit;
-                       }
+       if (reply->static_lease) {
+               if (reply->host == NULL)
+                       return DHCP_R_INVALIDARG;
 
-                       /* Copy out the IPv6 address for processing. */
-                       cli_addr.len = 16;
-                       memcpy(cli_addr.iabuf, iaaddr.data, 16);
+               send_addr.len = 16;
+               memcpy(send_addr.iabuf, reply->fixed.data, 16);
 
-                       data_string_forget(&iaaddr, MDL);
+               scope = &global_scope;
+               group = reply->subnet->group;
+               goto send_addr;
+       }
 
-                       /* Record that we've processed at least one address. */
-                       has_addrs = ISC_TRUE;
+       if (reply->old_ia != NULL)  {
+               for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+                       struct shared_network *candidate_shared;
+                       struct ipv6_pond *pond;
 
-                       /* Find out if any subnets cover this address. */
-                       for (subnet = shared->subnets ; subnet != NULL ;
-                            subnet = subnet->next_sibling) {
-                               if (addr_eq(subnet_number(cli_addr,
-                                                         subnet->netmask),
-                                           subnet->net))
-                                       break;
-                       }
+                       lease = reply->old_ia->iasubopt[i];
+                       candidate_shared = lease->ipv6_pool->shared_network;
+                       pond = lease->ipv6_pool->ipv6_pond;
 
-                       /* If we reach the end of the subnet list, and no
-                        * subnet matches the client address, then it must
-                        * be inappropriate to the link (so far as our
-                        * configuration says).  Once we've found one
-                        * inappropriate address, there is no reason to
-                        * continue searching.
+                       /*
+                        * Look for the best lease on the client's shared
+                        * network, that is still permitted
                         */
-                       if (subnet == NULL) {
-                               inappropriate = ISC_TRUE;
-                               break;
+
+                       if ((candidate_shared != reply->shared) ||
+                           (lease6_usable(lease) != ISC_TRUE))
+                               continue;
+
+                       if (((pond->prohibit_list != NULL) &&
+                            (permitted(reply->packet, pond->prohibit_list))) ||
+                           ((pond->permit_list != NULL) &&
+                            (!permitted(reply->packet, pond->permit_list))))
+                               continue;
+
+                       best_lease = lease_compare(lease, best_lease);
+               }
+       }
+
+       /* Try to pick a new address if we didn't find one, or if we found an
+        * abandoned lease.
+        */
+       if ((best_lease == NULL) || (best_lease->state == FTS_ABANDONED)) {
+               status = pick_v6_address(reply);
+       } else if (best_lease != NULL) {
+               iasubopt_reference(&reply->lease, best_lease, MDL);
+               status = ISC_R_SUCCESS;
+       }
+
+       /* Pick the abandoned lease as a last resort. */
+       if ((status == ISC_R_NORESOURCES) && (best_lease != NULL)) {
+               /* I don't see how this is supposed to be done right now. */
+               log_error("Best match for DUID %s is an abandoned address,"
+                         " This may be a result of multiple clients attempting"
+                         " to use this DUID",
+                        print_hex_1(reply->client_id.len,
+                                    reply->client_id.data, 60));
+               /* iasubopt_reference(&reply->lease, best_lease, MDL); */
+       }
+
+       /* Give up now if we didn't find a lease. */
+       if (status != ISC_R_SUCCESS)
+               return status;
+
+       if (reply->lease == NULL)
+               log_fatal("Impossible condition at %s:%d.", MDL);
+
+       /* Draw binding scopes from the lease's binding scope, and config
+        * from the lease's containing subnet and higher.  Note that it may
+        * be desirable to place the group attachment directly in the pool.
+        */
+       scope = &reply->lease->scope;
+       group = reply->lease->ipv6_pool->ipv6_pond->group;
+
+       send_addr.len = 16;
+       memcpy(send_addr.iabuf, &reply->lease->addr, 16);
+
+      send_addr:
+       status = reply_process_is_addressed(reply, scope, group);
+       if (status != ISC_R_SUCCESS)
+               return status;
+
+       status = reply_process_send_addr(reply, &send_addr);
+       return status;
+}
+
+/* Once an address is found for a client, perform several common functions;
+ * Calculate and store valid and preferred lease times, draw client options
+ * into the option state.
+ */
+static isc_result_t
+reply_process_is_addressed(struct reply_state *reply,
+                          struct binding_scope **scope, struct group *group)
+{
+       isc_result_t status = ISC_R_SUCCESS;
+       struct data_string data;
+       struct option_cache *oc;
+       struct option_state *tmp_options = NULL;
+       struct on_star *on_star;
+       int i;
+
+       /* Initialize values we will cleanup. */
+       memset(&data, 0, sizeof(data));
+
+       /*
+        * Find the proper on_star block to use.  We use the
+        * one in the lease if we have a lease or the one in
+        * the reply if we don't have a lease because this is
+        * a static instance
+        */
+       if (reply->lease) {
+               on_star = &reply->lease->on_star;
+       } else {
+               on_star = &reply->on_star;
+       }
+
+       /*
+        * Bring in the root configuration.  We only do this to bring
+        * in the on * statements, as we didn't have the lease available
+        * we did it the first time.
+        */
+       option_state_allocate(&tmp_options, MDL);
+       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                   reply->packet->options, tmp_options,
+                                   &global_scope, root_group, NULL,
+                                   on_star);
+       if (tmp_options != NULL) {
+               option_state_dereference(&tmp_options, MDL);
+       }
+
+       /*
+        * Bring configured options into the root packet level cache - start
+        * with the lease's closest enclosing group (passed in by the caller
+        * as 'group').
+        */
+       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                   reply->packet->options, reply->opt_state,
+                                   scope, group, root_group, on_star);
+
+       /* Execute statements from class scopes. */
+       for (i = reply->packet->class_count; i > 0; i--) {
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->opt_state, scope,
+                                           reply->packet->classes[i - 1]->group,
+                                           group, on_star);
+       }
+
+       /*
+        * If there is a host record, over-ride with values configured there,
+        * without re-evaluating configuration from the previously executed
+        * group or its common enclosers.
+        */
+       if (reply->host != NULL)
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->opt_state, scope,
+                                           reply->host->group, group,
+                                           on_star);
+
+       /* Determine valid lifetime. */
+       if (reply->client_valid == 0)
+               reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME;
+       else
+               reply->send_valid = reply->client_valid;
+
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_DEFAULT_LEASE_TIME);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+                                          reply->packet->options,
+                                          reply->opt_state,
+                                          scope, oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_is_addressed: unable to "
+                                 "evaluate default lease time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_valid = getULong(data.data);
+               data_string_forget(&data, MDL);
+       }
+
+       /* Check to see if the lease time would cause us to wrap
+        * in which case we make it infinite.
+        * The following doesn't work on at least some systems:
+        * (cur_time + reply->send_valid < cur_time)
+        */
+       if (reply->send_valid != INFINITE_TIME) {
+               time_t test_time = cur_time + reply->send_valid;
+               if (test_time < cur_time)
+                       reply->send_valid = INFINITE_TIME;
+        }
+
+       if (reply->client_prefer == 0)
+               reply->send_prefer = reply->send_valid;
+       else
+               reply->send_prefer = reply->client_prefer;
+
+       if ((reply->send_prefer >= reply->send_valid) &&
+           (reply->send_valid != INFINITE_TIME))
+               reply->send_prefer = (reply->send_valid / 2) +
+                                    (reply->send_valid / 8);
+
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_PREFER_LIFETIME);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+                                          reply->packet->options,
+                                          reply->opt_state,
+                                          scope, oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_is_addressed: unable to "
+                                 "evaluate preferred lease time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_prefer = getULong(data.data);
+               data_string_forget(&data, MDL);
+       }
+
+       /* Note lowest values for later calculation of renew/rebind times. */
+       if (reply->min_prefer > reply->send_prefer)
+               reply->min_prefer = reply->send_prefer;
+
+       if (reply->min_valid > reply->send_valid)
+               reply->min_valid = reply->send_valid;
+
+#if 0
+       /*
+        * XXX: Old 4.0.0 alpha code would change the host {} record
+        * XXX: uid upon lease assignment.  This was intended to cover the
+        * XXX: case where a client first identifies itself using vendor
+        * XXX: options in a solicit, or request, but later neglects to include
+        * XXX: these options in a Renew or Rebind.  It is not clear that this
+        * XXX: is required, and has some startling ramifications (such as
+        * XXX: how to recover this dynamic host {} state across restarts).
+        */
+       if (reply->host != NULL)
+               change_host_uid(host, reply->client_id->data,
+                               reply->client_id->len);
+#endif /* 0 */
+
+       /* Perform dynamic lease related update work. */
+       if (reply->lease != NULL) {
+               /* Cached lifetimes */
+               reply->lease->prefer = reply->send_prefer;
+               reply->lease->valid = reply->send_valid;
+
+               /* Advance (or rewind) the valid lifetime.
+                * In the protocol 0xFFFFFFFF is infinite
+                * when connecting to the lease file MAX_TIME is
+                */
+               if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
+                       if (reply->send_valid == INFINITE_TIME) {
+                               reply->lease->soft_lifetime_end_time = MAX_TIME;
+                       } else {
+                               reply->lease->soft_lifetime_end_time =
+                                 cur_time + reply->send_valid;
+                       }
+                       /* Wait before renew! */
+               }
+
+               status = ia_add_iasubopt(reply->ia, reply->lease, MDL);
+               if (status != ISC_R_SUCCESS) {
+                       log_fatal("reply_process_is_addressed: Unable to "
+                                 "attach lease to new IA: %s",
+                                 isc_result_totext(status));
+               }
+
+               /*
+                * If this is a new lease, make sure it is attached somewhere.
+                */
+               if (reply->lease->ia == NULL) {
+                       ia_reference(&reply->lease->ia, reply->ia, MDL);
+               }
+       }
+
+       /* Bring a copy of the relevant options into the IA scope. */
+       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                   reply->packet->options, reply->reply_ia,
+                                   scope, group, root_group, NULL);
+
+       /* Execute statements from class scopes. */
+       for (i = reply->packet->class_count; i > 0; i--) {
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->reply_ia, scope,
+                                           reply->packet->classes[i - 1]->group,
+                                           group, NULL);
+       }
+
+       /*
+        * And bring in host record configuration, if any, but not to overlap
+        * the previous group or its common enclosers.
+        */
+       if (reply->host != NULL)
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->reply_ia, scope,
+                                           reply->host->group, group, NULL);
+
+      cleanup:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+
+       if (status == ISC_R_SUCCESS)
+               reply->client_resources++;
+
+       return status;
+}
+
+/* Simply send an IAADDR within the IA scope as described. */
+static isc_result_t
+reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) {
+       isc_result_t status = ISC_R_SUCCESS;
+       struct data_string data;
+
+       memset(&data, 0, sizeof(data));
+
+       /* Now append the lease. */
+       data.len = IAADDR_OFFSET;
+       if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+               log_error("reply_process_send_addr: out of memory"
+                         "allocating new IAADDR buffer.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+       data.data = data.buffer->data;
+
+       memcpy(data.buffer->data, addr->iabuf, 16);
+       putULong(data.buffer->data + 16, reply->send_prefer);
+       putULong(data.buffer->data + 20, reply->send_valid);
+
+       if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
+                                 data.buffer, data.buffer->data,
+                                 data.len, D6O_IAADDR, 0)) {
+               log_error("reply_process_send_addr: unable "
+                         "to save IAADDR option");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       reply->resources_included = ISC_TRUE;
+
+      cleanup:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+
+       return status;
+}
+
+/* Choose the better of two leases. */
+static struct iasubopt *
+lease_compare(struct iasubopt *alpha, struct iasubopt *beta) {
+       if (alpha == NULL)
+               return beta;
+       if (beta == NULL)
+               return alpha;
+
+       switch(alpha->state) {
+             case FTS_ACTIVE:
+               switch(beta->state) {
+                     case FTS_ACTIVE:
+                       /* Choose the lease with the longest lifetime (most
+                        * likely the most recently allocated).
+                        */
+                       if (alpha->hard_lifetime_end_time <
+                           beta->hard_lifetime_end_time)
+                               return beta;
+                       else
+                               return alpha;
+
+                     case FTS_EXPIRED:
+                     case FTS_ABANDONED:
+                       return alpha;
+
+                     default:
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+               }
+               break;
+
+             case FTS_EXPIRED:
+               switch (beta->state) {
+                     case FTS_ACTIVE:
+                       return beta;
+
+                     case FTS_EXPIRED:
+                       /* Choose the most recently expired lease. */
+                       if (alpha->hard_lifetime_end_time <
+                           beta->hard_lifetime_end_time)
+                               return beta;
+                       else if ((alpha->hard_lifetime_end_time ==
+                                 beta->hard_lifetime_end_time) &&
+                                (alpha->soft_lifetime_end_time <
+                                 beta->soft_lifetime_end_time))
+                               return beta;
+                       else
+                               return alpha;
+
+                     case FTS_ABANDONED:
+                       return alpha;
+
+                     default:
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+               }
+               break;
+
+             case FTS_ABANDONED:
+               switch (beta->state) {
+                     case FTS_ACTIVE:
+                     case FTS_EXPIRED:
+                       return alpha;
+
+                     case FTS_ABANDONED:
+                       /* Choose the lease that was abandoned longest ago. */
+                       if (alpha->hard_lifetime_end_time <
+                           beta->hard_lifetime_end_time)
+                               return alpha;
+                       else
+                               return beta;
+
+                     default:
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+               }
+               break;
+
+             default:
+               log_fatal("Impossible condition at %s:%d.", MDL);
+       }
+
+       log_fatal("Triple impossible condition at %s:%d.", MDL);
+       return NULL;
+}
+
+/* Process a client-supplied IA_PD.  This may append options to the tail of
+ * the reply packet being built in the reply_state structure.
+ */
+static isc_result_t
+reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
+       isc_result_t status = ISC_R_SUCCESS;
+       u_int32_t iaid;
+       unsigned ia_cursor;
+       struct option_state *packet_ia;
+       struct option_cache *oc;
+       struct data_string ia_data, data;
+
+       /* Initialize values that will get cleaned up on return. */
+       packet_ia = NULL;
+       memset(&ia_data, 0, sizeof(ia_data));
+       memset(&data, 0, sizeof(data));
+       /*
+        * Note that find_client_prefix() may set reply->lease.
+        */
+
+       /* Make sure there is at least room for the header. */
+       if ((reply->cursor + IA_PD_OFFSET + 4) > sizeof(reply->buf)) {
+               log_error("reply_process_ia_pd: Reply too long for IA.");
+               return ISC_R_NOSPACE;
+       }
+
+
+       /* Fetch the IA_PD contents. */
+       if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
+                                      ia, IA_PD_OFFSET)) {
+               log_error("reply_process_ia_pd: error evaluating ia");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       /* Extract IA_PD header contents. */
+       iaid = getULong(ia_data.data);
+       reply->renew = getULong(ia_data.data + 4);
+       reply->rebind = getULong(ia_data.data + 8);
+
+       /* Create an IA_PD structure. */
+       if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data,
+                       reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+               log_error("reply_process_ia_pd: no memory for ia.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+       reply->ia->ia_type = D6O_IA_PD;
+
+       /* Cache pre-existing IA_PD, if any. */
+       ia_hash_lookup(&reply->old_ia, ia_pd_active,
+                      (unsigned char *)reply->ia->iaid_duid.data,
+                      reply->ia->iaid_duid.len, MDL);
+
+       /*
+        * Create an option cache to carry the IA_PD option contents, and
+        * execute any user-supplied values into it.
+        */
+       if (!option_state_allocate(&reply->reply_ia, MDL)) {
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+
+       /* Check & count the fixed prefix host records. */
+       reply->static_prefixes = 0;
+       if ((reply->host != NULL) && (reply->host->fixed_prefix != NULL)) {
+               struct iaddrcidrnetlist *fp;
+
+               for (fp = reply->host->fixed_prefix; fp != NULL;
+                    fp = fp->next) {
+                       reply->static_prefixes += 1;
+               }
+       }
+
+       /*
+        * Save the cursor position at the start of the IA_PD, so we can
+        * set length and adjust t1/t2 values later.  We write a temporary
+        * header out now just in case we decide to adjust the packet
+        * within sub-process functions.
+        */
+       ia_cursor = reply->cursor;
+
+       /* Initialize the IA_PD header.  First the code. */
+       putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_PD);
+       reply->cursor += 2;
+
+       /* Then option length. */
+       putUShort(reply->buf.data + reply->cursor, 0x0Cu);
+       reply->cursor += 2;
+
+       /* Then IA_PD header contents; IAID. */
+       putULong(reply->buf.data + reply->cursor, iaid);
+       reply->cursor += 4;
+
+       /* We store the client's t1 for now, and may over-ride it later. */
+       putULong(reply->buf.data + reply->cursor, reply->renew);
+       reply->cursor += 4;
+
+       /* We store the client's t2 for now, and may over-ride it later. */
+       putULong(reply->buf.data + reply->cursor, reply->rebind);
+       reply->cursor += 4;
+
+       /*
+        * For each prefix in this IA_PD, decide what to do about it.
+        */
+       oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAPREFIX);
+       reply->min_valid = reply->min_prefer = INFINITE_TIME;
+       reply->client_valid = reply->client_prefer = 0;
+       reply->preflen = -1;
+       for (; oc != NULL ; oc = oc->next) {
+               status = reply_process_prefix(reply, oc);
+
+               /*
+                * Canceled means we did not allocate prefixes to the
+                * client, but we're "done" with this IA - we set a status
+                * code.  So transmit this reply, e.g., move on to the next
+                * IA.
+                */
+               if (status == ISC_R_CANCELED)
+                       break;
+
+               if ((status != ISC_R_SUCCESS) &&
+                   (status != ISC_R_ADDRINUSE) &&
+                   (status != ISC_R_ADDRNOTAVAIL))
+                       goto cleanup;
+       }
+
+       reply->pd_count++;
+
+       /*
+        * If we fell through the above and never gave the client
+        * a prefix, give it one now.
+        */
+       if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) {
+               status = find_client_prefix(reply);
+
+               if (status == ISC_R_NORESOURCES) {
+                       switch (reply->packet->dhcpv6_msg_type) {
+                             case DHCPV6_SOLICIT:
+                               /*
+                                * No prefix for any IA is handled
+                                * by the caller.
+                                */
+                               /* FALL THROUGH */
+
+                             case DHCPV6_REQUEST:
+                               /* Same than for addresses. */
+                               option_state_dereference(&reply->reply_ia, MDL);
+                               if (!option_state_allocate(&reply->reply_ia,
+                                                          MDL))
+                               {
+                                       log_error("reply_process_ia_pd: No "
+                                                 "memory for option state "
+                                                 "wipe.");
+                                       status = ISC_R_NOMEMORY;
+                                       goto cleanup;
+                               }
+
+                               if (!set_status_code(STATUS_NoPrefixAvail,
+                                                    "No prefixes available "
+                                                    "for this interface.",
+                                                     reply->reply_ia)) {
+                                       log_error("reply_process_ia_pd: "
+                                                 "Unable to set "
+                                                 "NoPrefixAvail status "
+                                                 "code.");
+                                       status = ISC_R_FAILURE;
+                                       goto cleanup;
+                               }
+
+                               status = ISC_R_SUCCESS;
+                               break;
+
+                             default:
+                               if (reply->resources_included)
+                                       status = ISC_R_SUCCESS;
+                               else
+                                       goto cleanup;
+                               break;
+                       }
+               }
+
+               if (status != ISC_R_SUCCESS)
+                       goto cleanup;
+       }
+
+       /*
+        * yes, goto's aren't the best but we also want to avoid extra
+        * indents
+        */
+       if (status == ISC_R_CANCELED) {
+               /* We're replying with a status code so we still need to
+                * write it out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+               goto cleanup;
+       }
+
+       /*
+        * Handle static prefixes, we always log stuff and if it's
+        * a hard binding we run any commit statements that we have
+        */
+       if (reply->static_prefixes != 0) {
+               char tmp_addr[INET6_ADDRSTRLEN];
+               log_info("%s PD: address %s/%d to client with duid %s "
+                        "iaid = %d static",
+                        dhcpv6_type_names[reply->buf.reply.msg_type],
+                        inet_ntop(AF_INET6, reply->fixed_pref.lo_addr.iabuf,
+                                  tmp_addr, sizeof(tmp_addr)),
+                        reply->fixed_pref.bits,
+                        print_hex_1(reply->client_id.len,
+                                    reply->client_id.data, 60),
+                        iaid);
+
+               /* Write the lease out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+
+               if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
+                   (reply->on_star.on_commit != NULL)) {
+                       execute_statements(NULL, reply->packet, NULL, NULL,
+                                          reply->packet->options,
+                                          reply->opt_state,
+                                          NULL, reply->on_star.on_commit,
+                                          NULL);
+                       executable_statement_dereference
+                               (&reply->on_star.on_commit, MDL);
+               }
+               goto cleanup;
+       }
+
+       /*
+        * If we have any addresses log what we are doing.
+        */
+       if (reply->ia->num_iasubopt != 0) {
+               struct iasubopt *tmp;
+               int i;
+               char tmp_addr[INET6_ADDRSTRLEN];
+
+               for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+                       tmp = reply->ia->iasubopt[i];
+
+                       log_info("%s PD: address %s/%d to client with duid %s"
+                                " iaid = %d valid for %u seconds",
+                                dhcpv6_type_names[reply->buf.reply.msg_type],
+                                inet_ntop(AF_INET6, &tmp->addr,
+                                          tmp_addr, sizeof(tmp_addr)),
+                                (int)tmp->plen,
+                                print_hex_1(reply->client_id.len,
+                                            reply->client_id.data, 60),
+                                iaid, tmp->valid);
+               }
+       }
+
+       /*
+        * If this is not a 'soft' binding, consume the new changes into
+        * the database (if any have been attached to the ia_pd).
+        *
+        * Loop through the assigned dynamic prefixes, referencing the
+        * prefixes onto this IA_PD rather than any old ones, and updating
+        * prefix pool timers for each (if any).
+        *
+        * If a lease can be reused we skip renewing it or checking the
+        * pool threshold. If it can't we flag that the IA must be commited
+        * to the db and do the renewal and pool check.
+        */
+       if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
+           (reply->ia->num_iasubopt != 0)) {
+               int must_commit = 0;
+               struct iasubopt *tmp;
+               struct data_string *ia_id;
+               int i;
+
+               for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
+                       tmp = reply->ia->iasubopt[i];
+
+                       if (tmp->ia != NULL)
+                               ia_dereference(&tmp->ia, MDL);
+                       ia_reference(&tmp->ia, reply->ia, MDL);
+
+                       /* If we have anything to do on commit do it now */
+                       if (tmp->on_star.on_commit != NULL) {
+                               execute_statements(NULL, reply->packet,
+                                                  NULL, NULL,
+                                                  reply->packet->options,
+                                                  reply->opt_state,
+                                                  &tmp->scope,
+                                                  tmp->on_star.on_commit,
+                                                  &tmp->on_star);
+                               executable_statement_dereference
+                                       (&tmp->on_star.on_commit, MDL);
+                       }
+
+                       if (!reuse_lease6(reply, tmp)) {
+                               /* Commit 'hard' bindings. */
+                               must_commit = 1;
+                               renew_lease6(tmp->ipv6_pool, tmp);
+                               schedule_lease_timeout(tmp->ipv6_pool);
+
+                               /* Do our threshold check. */
+                               check_pool6_threshold(reply, tmp);
+                       }
+               }
+
+               /* write the IA_PD in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+
+               /* Remove any old ia from the hash. */
+               if (reply->old_ia != NULL) {
+                       if (!release_on_roam(reply)) {
+                               ia_id = &reply->old_ia->iaid_duid;
+                               ia_hash_delete(ia_pd_active,
+                                              (unsigned char *)ia_id->data,
+                                              ia_id->len, MDL);
+                       }
+
+                       ia_dereference(&reply->old_ia, MDL);
+               }
+
+               /* Put new ia into the hash. */
+               reply->ia->cltt = cur_time;
+               ia_id = &reply->ia->iaid_duid;
+               ia_hash_add(ia_pd_active, (unsigned char *)ia_id->data,
+                           ia_id->len, reply->ia, MDL);
+
+               /* If we couldn't reuse all of the iasubopts, we
+               * must udpate the lease db */
+               if (must_commit) {
+                       write_ia(reply->ia);
+               }
+       } else {
+               /* write the IA_PD in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+               schedule_lease_timeout_reply(reply);
+       }
+
+      cleanup:
+       if (packet_ia != NULL)
+               option_state_dereference(&packet_ia, MDL);
+       if (reply->reply_ia != NULL)
+               option_state_dereference(&reply->reply_ia, MDL);
+       if (ia_data.data != NULL)
+               data_string_forget(&ia_data, MDL);
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (reply->ia != NULL)
+               ia_dereference(&reply->ia, MDL);
+       if (reply->old_ia != NULL)
+               ia_dereference(&reply->old_ia, MDL);
+       if (reply->lease != NULL)
+               iasubopt_dereference(&reply->lease, MDL);
+       if (reply->on_star.on_expiry != NULL)
+               executable_statement_dereference
+                       (&reply->on_star.on_expiry, MDL);
+       if (reply->on_star.on_release != NULL)
+               executable_statement_dereference
+                       (&reply->on_star.on_release, MDL);
+
+       /*
+        * ISC_R_CANCELED is a status code used by the prefix processing to
+        * indicate we're replying with a status code.  This is still a
+        * success at higher layers.
+        */
+       return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
+}
+
+/*!
+ *
+ * \brief Find the proper scoping group for use with a v6 static prefix.
+ *
+ * We start by trying to find a subnet based on the given prefix and
+ * the shared network.  If we don't find one then the prefix has been
+ * declared outside of any subnets.  If there is a static address
+ * associated with the host we use it to try and find a subnet (this
+ * should succeed).  If there isn't a static address we fall back
+ * to the shared subnet itself.
+ * Once we have a subnet we extract the group from it and return it.
+ *
+ * \param reply - the reply structure we use to collect information
+ *                we will use the fields shared, fixed_pref and host
+ *                from the structure
+ *
+ * \return a pointer to the group structure to use for scoping
+ */
+
+static struct group *
+find_group_by_prefix(struct reply_state *reply) {
+       /* default group if we don't find anything better */
+       struct group *group = reply->shared->group;
+       struct subnet *subnet = NULL;
+       struct iaddr tmp_addr;
+       struct data_string fixed_addr;
+
+       /* Try with the prefix first */
+       if (find_grouped_subnet(&subnet, reply->shared,
+                               reply->fixed_pref.lo_addr, MDL) != 0) {
+               group = subnet->group;
+               subnet_dereference(&subnet, MDL);
+               return (group);
+       }
+
+       /* Didn't find a subnet via prefix, what about fixed address */
+       /* The caller has already tested reply->host != NULL */
+
+       memset(&fixed_addr, 0, sizeof(fixed_addr));
+
+       if ((reply->host->fixed_addr != NULL) &&
+           (evaluate_option_cache(&fixed_addr, NULL, NULL, NULL,
+                                  NULL, NULL, &global_scope,
+                                  reply->host->fixed_addr, MDL))) {
+               if (fixed_addr.len >= 16) {
+                       tmp_addr.len = 16;
+                       memcpy(tmp_addr.iabuf, fixed_addr.data, 16);
+                       if (find_grouped_subnet(&subnet, reply->shared,
+                                               tmp_addr, MDL) != 0) {
+                               group = subnet->group;
+                               subnet_dereference(&subnet, MDL);
+                       }
+               }
+               data_string_forget(&fixed_addr, MDL);
+       }
+
+       /* return whatever we got */
+       return (group);
+}
+
+/*
+ * Process an IAPREFIX within a given IA_PD, storing any IAPREFIX reply
+ * contents into the reply's current ia_pd-scoped option cache.  Returns
+ * ISC_R_CANCELED in the event we are replying with a status code and do
+ * not wish to process more IAPREFIXes within this IA_PD.
+ */
+static isc_result_t
+reply_process_prefix(struct reply_state *reply, struct option_cache *pref) {
+       u_int32_t pref_life, valid_life;
+       struct binding_scope **scope;
+       struct iaddrcidrnet tmp_pref;
+       struct option_cache *oc;
+       struct data_string iapref, data;
+       isc_result_t status = ISC_R_SUCCESS;
+       struct group *group;
+
+       /* Initializes values that will be cleaned up. */
+       memset(&iapref, 0, sizeof(iapref));
+       memset(&data, 0, sizeof(data));
+       /* Note that reply->lease may be set by prefix_is_owned() */
+
+       /*
+        * There is no point trying to process an incoming prefix if there
+        * is no room for an outgoing prefix.
+        */
+       if ((reply->cursor + 29) > sizeof(reply->buf)) {
+               log_error("reply_process_prefix: Out of room for prefix.");
+               return ISC_R_NOSPACE;
+       }
+
+       /* Extract this IAPREFIX option. */
+       if (!evaluate_option_cache(&iapref, reply->packet, NULL, NULL,
+                                  reply->packet->options, NULL, &global_scope,
+                                  pref, MDL) ||
+           (iapref.len < IAPREFIX_OFFSET)) {
+               log_error("reply_process_prefix: error evaluating IAPREFIX.");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       /*
+        * Layout: preferred and valid lifetimes followed by the prefix
+        * length and the IPv6 address.
+        */
+       pref_life = getULong(iapref.data);
+       valid_life = getULong(iapref.data + 4);
+
+       if ((reply->client_valid == 0) ||
+           (reply->client_valid > valid_life))
+               reply->client_valid = valid_life;
+
+       if ((reply->client_prefer == 0) ||
+           (reply->client_prefer > pref_life))
+               reply->client_prefer = pref_life;
+
+       /*
+        * Clients may choose to send ::/0 as a prefix, with the idea to give
+        * hints about preferred-lifetime or valid-lifetime.
+        */
+       tmp_pref.lo_addr.len = 16;
+       memset(tmp_pref.lo_addr.iabuf, 0, 16);
+       if ((iapref.data[8] == 0) &&
+           (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0)) {
+               /* Status remains success; we just ignore this one. */
+               goto cleanup;
+       }
+
+       /*
+        * Clients may choose to send ::/X as a prefix to specify a
+        * preferred/requested prefix length. Note X is never zero here.
+        */
+       tmp_pref.bits = (int) iapref.data[8];
+       if (reply->preflen < 0) {
+               /* Cache the first preferred prefix length. */
+               reply->preflen = tmp_pref.bits;
+       }
+       if (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0) {
+               goto cleanup;
+       }
+
+       memcpy(tmp_pref.lo_addr.iabuf, iapref.data + 9, 16);
+
+       /* Verify the prefix belongs to the client. */
+       if (!prefix_is_owned(reply, &tmp_pref)) {
+               /* Same than for addresses. */
+               if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) ||
+                   (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) ||
+                   (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) {
+                       status = reply_process_try_prefix(reply, &tmp_pref);
+
+                       /* Either error out or skip this prefix. */
+                       if ((status != ISC_R_SUCCESS) &&
+                           (status != ISC_R_ADDRINUSE) &&
+                           (status != ISC_R_ADDRNOTAVAIL))
+                               goto cleanup;
+
+                       if (reply->lease == NULL) {
+                               if (reply->packet->dhcpv6_msg_type ==
+                                                       DHCPV6_REBIND) {
+                                       reply->send_prefer = 0;
+                                       reply->send_valid = 0;
+                                       goto send_pref;
+                               }
+
+                               /* status remains success - ignore */
+                               goto cleanup;
+                       }
+               /*
+                * RFC3633 section 18.2.3:
+                *
+                * If the delegating router cannot find a binding
+                * for the requesting router's IA_PD the delegating
+                * router returns the IA_PD containing no prefixes
+                * with a Status Code option set to NoBinding in the
+                * Reply message.
+                *
+                * On mismatch we (ab)use this pretending we have not the IA
+                * as soon as we have not a prefix.
+                */
+               } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) {
+                       /* Rewind the IA_PD to empty. */
+                       option_state_dereference(&reply->reply_ia, MDL);
+                       if (!option_state_allocate(&reply->reply_ia, MDL)) {
+                               log_error("reply_process_prefix: No memory "
+                                         "for option state wipe.");
+                               status = ISC_R_NOMEMORY;
+                               goto cleanup;
+                       }
+
+                       /* Append a NoBinding status code.  */
+                       if (!set_status_code(STATUS_NoBinding,
+                                            "Prefix not bound to this "
+                                            "interface.", reply->reply_ia)) {
+                               log_error("reply_process_prefix: Unable to "
+                                         "attach status code.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
+                       }
+
+                       /* Fin (no more IAPREFIXes). */
+                       status = ISC_R_CANCELED;
+                       goto cleanup;
+               } else {
+                       log_error("It is impossible to lease a client that is "
+                                 "not sending a solicit, request, renew, or "
+                                 "rebind message.");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+       }
+
+       if (reply->static_prefixes > 0) {
+               if (reply->host == NULL)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+
+               scope = &global_scope;
+
+               /* Copy the static prefix for logging and finding the group */
+               memcpy(&reply->fixed_pref, &tmp_pref, sizeof(tmp_pref));
+
+               /* Try to find a group for the static prefix */
+               group = find_group_by_prefix(reply);
+       } else {
+               if (reply->lease == NULL)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+
+               scope = &reply->lease->scope;
+               group = reply->lease->ipv6_pool->ipv6_pond->group;
+       }
+
+       /*
+        * If client_resources is nonzero, then the reply_process_is_prefixed
+        * function has executed configuration state into the reply option
+        * cache.  We will use that valid cache to derive configuration for
+        * whether or not to engage in additional prefixes, and similar.
+        */
+       if (reply->client_resources != 0) {
+               unsigned limit = 1;
+
+               /*
+                * Does this client have "enough" prefixes already?  Default
+                * to one.  Everybody gets one, and one should be enough for
+                * anybody.
+                */
+               oc = lookup_option(&server_universe, reply->opt_state,
+                                  SV_LIMIT_PREFS_PER_IA);
+               if (oc != NULL) {
+                       if (!evaluate_option_cache(&data, reply->packet,
+                                                  NULL, NULL,
+                                                  reply->packet->options,
+                                                  reply->opt_state,
+                                                  scope, oc, MDL) ||
+                           (data.len != 4)) {
+                               log_error("reply_process_prefix: unable to "
+                                         "evaluate prefs-per-ia value.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
+                       }
+
+                       limit = getULong(data.data);
+                       data_string_forget(&data, MDL);
+               }
+
+               /*
+                * If we wish to limit the client to a certain number of
+                * prefixes, then omit the prefix from the reply.
+                */
+               if (reply->client_resources >= limit)
+                       goto cleanup;
+       }
+
+       status = reply_process_is_prefixed(reply, scope, group);
+       if (status != ISC_R_SUCCESS)
+               goto cleanup;
+
+      send_pref:
+       status = reply_process_send_prefix(reply, &tmp_pref);
+
+      cleanup:
+       if (iapref.data != NULL)
+               data_string_forget(&iapref, MDL);
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (reply->lease != NULL)
+               iasubopt_dereference(&reply->lease, MDL);
+
+       return status;
+}
+
+/*
+ * Verify the prefix belongs to the client.  If we've got a host
+ * record with fixed prefixes, it has to be an assigned prefix
+ * (fault out all else).  Otherwise it's a dynamic prefix, so lookup
+ * that prefix and make sure it belongs to this DUID:IAID pair.
+ */
+static isc_boolean_t
+prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) {
+       struct iaddrcidrnetlist *l;
+       int i;
+       struct ipv6_pond *pond;
+
+       /*
+        * This faults out prefixes that don't match fixed prefixes.
+        */
+       if (reply->static_prefixes > 0) {
+               for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
+                       if ((pref->bits == l->cidrnet.bits) &&
+                           (memcmp(pref->lo_addr.iabuf,
+                                   l->cidrnet.lo_addr.iabuf, 16) == 0))
+                               return (ISC_TRUE);
+               }
+               return (ISC_FALSE);
+       }
+
+       if ((reply->old_ia == NULL) ||
+           (reply->old_ia->num_iasubopt == 0))
+               return (ISC_FALSE);
+
+       for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+               struct iasubopt *tmp;
+
+               tmp = reply->old_ia->iasubopt[i];
+
+               if ((pref->bits == (int) tmp->plen) &&
+                   (memcmp(pref->lo_addr.iabuf, &tmp->addr, 16) == 0)) {
+                       if (lease6_usable(tmp) == ISC_FALSE) {
+                               return (ISC_FALSE);
+                       }
+
+                       pond = tmp->ipv6_pool->ipv6_pond;
+                       if (((pond->prohibit_list != NULL) &&
+                            (permitted(reply->packet, pond->prohibit_list))) ||
+                           ((pond->permit_list != NULL) &&
+                            (!permitted(reply->packet, pond->permit_list))))
+                               return (ISC_FALSE);
+
+                       iasubopt_reference(&reply->lease, tmp, MDL);
+                       return (ISC_TRUE);
+               }
+       }
+
+       return (ISC_FALSE);
+}
+
+/*
+ * This function only returns failure on 'hard' failures.  If it succeeds,
+ * it will leave a prefix structure behind.
+ */
+static isc_result_t
+reply_process_try_prefix(struct reply_state *reply,
+                        struct iaddrcidrnet *pref) {
+       isc_result_t status = ISC_R_ADDRNOTAVAIL;
+       struct ipv6_pool *pool = NULL;
+       struct ipv6_pond *pond = NULL;
+       int i;
+       struct data_string data_pref;
+
+       if ((reply == NULL) || (reply->shared == NULL) ||
+           (pref == NULL) || (reply->lease != NULL))
+               return (DHCP_R_INVALIDARG);
+
+       /*
+        * Do a quick walk through of the ponds and pools
+        * to see if we have any prefix pools
+        */
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (pond->ipv6_pools == NULL)
+                       continue;
+
+               for (i = 0; (pool = pond->ipv6_pools[i]) != NULL; i++) {
+                       if (pool->pool_type == D6O_IA_PD)
+                               break;
+               }
+               if (pool != NULL)
+                       break;
+       }
+
+       /* If we get here and p is NULL we have no useful pools */
+       if (pool == NULL) {
+               return (ISC_R_ADDRNOTAVAIL);
+       }
+
+       memset(&data_pref, 0, sizeof(data_pref));
+       data_pref.len = 17;
+       if (!buffer_allocate(&data_pref.buffer, data_pref.len, MDL)) {
+               log_error("reply_process_try_prefix: out of memory.");
+               return (ISC_R_NOMEMORY);
+       }
+       data_pref.data = data_pref.buffer->data;
+       data_pref.buffer->data[0] = (u_int8_t) pref->bits;
+       memcpy(data_pref.buffer->data + 1, pref->lo_addr.iabuf, 16);
+
+       /*
+        * We have at least one pool that could provide a prefix
+        * Now we walk through the ponds and pools again and check
+        * to see if the client is permitted and if an prefix is
+        * available
+        *
+        */
+
+       for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
+               if (((pond->prohibit_list != NULL) &&
+                    (permitted(reply->packet, pond->prohibit_list))) ||
+                   ((pond->permit_list != NULL) &&
+                    (!permitted(reply->packet, pond->permit_list))))
+                       continue;
+
+               for (i = 0; (pool = pond->ipv6_pools[i]) != NULL; i++) {
+                       if (pool->pool_type != D6O_IA_PD) {
+                               continue;
+                       }
+
+                       status = try_client_v6_prefix(&reply->lease, pool,
+                                                     &data_pref);
+                       /* If we found it in this pool (either in use or available),
+                          there is no need to look further. */
+                       if ( (status == ISC_R_SUCCESS) || (status == ISC_R_ADDRINUSE) )
+                               break;
+                       }
+               if ( (status == ISC_R_SUCCESS) || (status == ISC_R_ADDRINUSE) )
+                       break;
+       }
+
+       data_string_forget(&data_pref, MDL);
+       /* Return just the most recent status... */
+       return (status);
+}
+
+/* Look around for a prefix to give the client.  First, look through the old
+ * IA_PD for prefixes we can extend.  Second, try to allocate a new prefix.
+ * Finally, actually add that prefix into the current reply IA_PD.
+ */
+static isc_result_t
+find_client_prefix(struct reply_state *reply) {
+       struct iaddrcidrnet send_pref;
+       isc_result_t status = ISC_R_NORESOURCES;
+       struct iasubopt *prefix, *best_prefix = NULL;
+       struct binding_scope **scope;
+       int i;
+       struct group *group;
+
+       if (reply->static_prefixes > 0) {
+               struct iaddrcidrnetlist *l;
+
+               if (reply->host == NULL)
+                       return DHCP_R_INVALIDARG;
+
+               for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
+                       if (l->cidrnet.bits == reply->preflen)
+                               break;
+               }
+               if (l == NULL) {
+                       /*
+                        * If no fixed prefix has the preferred length,
+                        * get the first one.
+                        */
+                       l = reply->host->fixed_prefix;
+               }
+               memcpy(&send_pref, &l->cidrnet, sizeof(send_pref));
+
+               scope = &global_scope;
+
+               /* Copy the prefix for logging purposes */
+               memcpy(&reply->fixed_pref, &l->cidrnet, sizeof(send_pref));
+
+               /* Try to find a group for the static prefix */
+               group = find_group_by_prefix(reply);
+
+               goto send_pref;
+       }
+
+       if (reply->old_ia != NULL)  {
+               for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
+                       struct shared_network *candidate_shared;
+                       struct ipv6_pond *pond;
+
+                       prefix = reply->old_ia->iasubopt[i];
+                       candidate_shared = prefix->ipv6_pool->shared_network;
+                       pond = prefix->ipv6_pool->ipv6_pond;
+
+                       /*
+                        * Consider this prefix if it is in a global pool or
+                        * if it is scoped in a pool under the client's shared
+                        * network.
+                        */
+                       if (((candidate_shared != NULL) &&
+                            (candidate_shared != reply->shared)) ||
+                           (lease6_usable(prefix) != ISC_TRUE))
+                               continue;
+
+                       /*
+                        * And check if the prefix is still permitted
+                        */
+
+                       if (((pond->prohibit_list != NULL) &&
+                            (permitted(reply->packet, pond->prohibit_list))) ||
+                           ((pond->permit_list != NULL) &&
+                            (!permitted(reply->packet, pond->permit_list))))
+                               continue;
+
+                       best_prefix = prefix_compare(reply, prefix,
+                                                    best_prefix);
+               }
+
+               /*
+                * If we have prefix length hint and we're not igoring them,
+                * then toss the best match if it doesn't match the hint,
+                * unless this is in response to a rebind.  In the latter
+                * case we're supposed to return it with zero lifetimes.
+                * (See rt45780) */
+               if (best_prefix && (reply->preflen > 0)
+                   && (prefix_length_mode != PLM_IGNORE)
+                   && (reply->preflen != best_prefix->plen)
+                   && (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) {
+                       best_prefix = NULL;
+               }
+       }
+
+       /* Try to pick a new prefix if we didn't find one, or if we found an
+        * abandoned prefix.
+        */
+       if ((best_prefix == NULL) || (best_prefix->state == FTS_ABANDONED)) {
+               status = pick_v6_prefix(reply);
+       } else if (best_prefix != NULL) {
+               iasubopt_reference(&reply->lease, best_prefix, MDL);
+               status = ISC_R_SUCCESS;
+       }
+
+       /* Pick the abandoned prefix as a last resort. */
+       if ((status == ISC_R_NORESOURCES) && (best_prefix != NULL)) {
+               /* I don't see how this is supposed to be done right now. */
+               log_error("Reclaiming abandoned prefixes is not yet "
+                         "supported.  Treating this as an out of space "
+                         "condition.");
+               /* iasubopt_reference(&reply->lease, best_prefix, MDL); */
+       }
+
+       /* Give up now if we didn't find a prefix. */
+       if (status != ISC_R_SUCCESS)
+               return status;
+
+       if (reply->lease == NULL)
+               log_fatal("Impossible condition at %s:%d.", MDL);
+
+       scope = &reply->lease->scope;
+       group = reply->lease->ipv6_pool->ipv6_pond->group;
+
+       send_pref.lo_addr.len = 16;
+       memcpy(send_pref.lo_addr.iabuf, &reply->lease->addr, 16);
+       send_pref.bits = (int) reply->lease->plen;
+
+      send_pref:
+       status = reply_process_is_prefixed(reply, scope, group);
+       if (status != ISC_R_SUCCESS)
+               return status;
+
+       status = reply_process_send_prefix(reply, &send_pref);
+       return status;
+}
+
+/* Once a prefix is found for a client, perform several common functions;
+ * Calculate and store valid and preferred prefix times, draw client options
+ * into the option state.
+ */
+static isc_result_t
+reply_process_is_prefixed(struct reply_state *reply,
+                         struct binding_scope **scope, struct group *group)
+{
+       isc_result_t status = ISC_R_SUCCESS;
+       struct data_string data;
+       struct option_cache *oc;
+       struct option_state *tmp_options = NULL;
+       struct on_star *on_star;
+       int i;
+
+       /* Initialize values we will cleanup. */
+       memset(&data, 0, sizeof(data));
+
+       /*
+        * Find the proper on_star block to use.  We use the
+        * one in the lease if we have a lease or the one in
+        * the reply if we don't have a lease because this is
+        * a static instance
+        */
+       if (reply->lease) {
+               on_star = &reply->lease->on_star;
+       } else {
+               on_star = &reply->on_star;
+       }
+
+       /*
+        * Bring in the root configuration.  We only do this to bring
+        * in the on * statements, as we didn't have the lease available
+        * we we did it the first time.
+        */
+       option_state_allocate(&tmp_options, MDL);
+       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                   reply->packet->options, tmp_options,
+                                   &global_scope, root_group, NULL,
+                                   on_star);
+       if (tmp_options != NULL) {
+               option_state_dereference(&tmp_options, MDL);
+       }
+
+       /*
+        * Bring configured options into the root packet level cache - start
+        * with the lease's closest enclosing group (passed in by the caller
+        * as 'group').
+        */
+       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                   reply->packet->options, reply->opt_state,
+                                   scope, group, root_group, on_star);
+
+       /* Execute statements from class scopes. */
+       for (i = reply->packet->class_count; i > 0; i--) {
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->opt_state, scope,
+                                           reply->packet->classes[i - 1]->group,
+                                           group, on_star);
+       }
+
+       /*
+        * If there is a host record, over-ride with values configured there,
+        * without re-evaluating configuration from the previously executed
+        * group or its common enclosers.
+        */
+       if (reply->host != NULL)
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->opt_state, scope,
+                                           reply->host->group, group,
+                                           on_star);
+
+       /* Determine valid lifetime. */
+       if (reply->client_valid == 0)
+               reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME;
+       else
+               reply->send_valid = reply->client_valid;
+
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_DEFAULT_LEASE_TIME);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+                                          reply->packet->options,
+                                          reply->opt_state,
+                                          scope, oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_is_prefixed: unable to "
+                                 "evaluate default prefix time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_valid = getULong(data.data);
+               data_string_forget(&data, MDL);
+       }
+
+       /* Check to see if the lease time would cause us to wrap
+        * in which case we make it infinite.
+        * The following doesn't work on at least some systems:
+        * (cur_time + reply->send_valid < cur_time)
+        */
+       if (reply->send_valid != INFINITE_TIME) {
+               time_t test_time = cur_time + reply->send_valid;
+               if (test_time < cur_time)
+                       reply->send_valid = INFINITE_TIME;
+        }
+
+       if (reply->client_prefer == 0)
+               reply->send_prefer = reply->send_valid;
+       else
+               reply->send_prefer = reply->client_prefer;
+
+       if ((reply->send_prefer >= reply->send_valid) &&
+           (reply->send_valid != INFINITE_TIME))
+               reply->send_prefer = (reply->send_valid / 2) +
+                                    (reply->send_valid / 8);
+
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_PREFER_LIFETIME);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+                                          reply->packet->options,
+                                          reply->opt_state,
+                                          scope, oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_is_prefixed: unable to "
+                                 "evaluate preferred prefix time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_prefer = getULong(data.data);
+               data_string_forget(&data, MDL);
+       }
+
+       /* Note lowest values for later calculation of renew/rebind times. */
+       if (reply->min_prefer > reply->send_prefer)
+               reply->min_prefer = reply->send_prefer;
+
+       if (reply->min_valid > reply->send_valid)
+               reply->min_valid = reply->send_valid;
+
+       /* Perform dynamic prefix related update work. */
+       if (reply->lease != NULL) {
+               /* Cached lifetimes */
+               reply->lease->prefer = reply->send_prefer;
+               reply->lease->valid = reply->send_valid;
+
+               /* Advance (or rewind) the valid lifetime.
+                * In the protocol 0xFFFFFFFF is infinite
+                * when connecting to the lease file MAX_TIME is
+                */
+               if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
+                       if (reply->send_valid == INFINITE_TIME) {
+                               reply->lease->soft_lifetime_end_time = MAX_TIME;
+                       } else {
+                               reply->lease->soft_lifetime_end_time =
+                                 cur_time + reply->send_valid;
+                       }
+                       /* Wait before renew! */
+               }
+
+               status = ia_add_iasubopt(reply->ia, reply->lease, MDL);
+               if (status != ISC_R_SUCCESS) {
+                       log_fatal("reply_process_is_prefixed: Unable to "
+                                 "attach prefix to new IA_PD: %s",
+                                 isc_result_totext(status));
+               }
+
+               /*
+                * If this is a new prefix, make sure it is attached somewhere.
+                */
+               if (reply->lease->ia == NULL) {
+                       ia_reference(&reply->lease->ia, reply->ia, MDL);
+               }
+       }
+
+       /* Bring a copy of the relevant options into the IA_PD scope. */
+       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                   reply->packet->options, reply->reply_ia,
+                                   scope, group, root_group, NULL);
+
+       /* Execute statements from class scopes. */
+       for (i = reply->packet->class_count; i > 0; i--) {
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->reply_ia, scope,
+                                           reply->packet->classes[i - 1]->group,
+                                           group, NULL);
+       }
+
+       /*
+        * And bring in host record configuration, if any, but not to overlap
+        * the previous group or its common enclosers.
+        */
+       if (reply->host != NULL)
+               execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                           reply->packet->options,
+                                           reply->reply_ia, scope,
+                                           reply->host->group, group, NULL);
+
+      cleanup:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+
+       if (status == ISC_R_SUCCESS)
+               reply->client_resources++;
+
+       return status;
+}
+
+/* Simply send an IAPREFIX within the IA_PD scope as described. */
+static isc_result_t
+reply_process_send_prefix(struct reply_state *reply,
+                         struct iaddrcidrnet *pref) {
+       isc_result_t status = ISC_R_SUCCESS;
+       struct data_string data;
+
+       memset(&data, 0, sizeof(data));
+
+       /* Now append the prefix. */
+       data.len = IAPREFIX_OFFSET;
+       if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+               log_error("reply_process_send_prefix: out of memory"
+                         "allocating new IAPREFIX buffer.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+       data.data = data.buffer->data;
+
+       putULong(data.buffer->data, reply->send_prefer);
+       putULong(data.buffer->data + 4, reply->send_valid);
+       data.buffer->data[8] = pref->bits;
+       memcpy(data.buffer->data + 9, pref->lo_addr.iabuf, 16);
+
+       if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
+                                 data.buffer, data.buffer->data,
+                                 data.len, D6O_IAPREFIX, 0)) {
+               log_error("reply_process_send_prefix: unable "
+                         "to save IAPREFIX option");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       reply->resources_included = ISC_TRUE;
+
+      cleanup:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+
+       return status;
+}
+
+/* Choose the better of two prefixes. */
+static struct iasubopt *
+prefix_compare(struct reply_state *reply,
+              struct iasubopt *alpha, struct iasubopt *beta) {
+       if (alpha == NULL)
+               return beta;
+       if (beta == NULL)
+               return alpha;
+
+       if (reply->preflen >= 0) {
+               if ((alpha->plen == reply->preflen) &&
+                   (beta->plen != reply->preflen))
+                       return alpha;
+               if ((beta->plen == reply->preflen) &&
+                   (alpha->plen != reply->preflen))
+                       return beta;
+       }
+
+       switch(alpha->state) {
+             case FTS_ACTIVE:
+               switch(beta->state) {
+                     case FTS_ACTIVE:
+                       /* Choose the prefix with the longest lifetime (most
+                        * likely the most recently allocated).
+                        */
+                       if (alpha->hard_lifetime_end_time <
+                           beta->hard_lifetime_end_time)
+                               return beta;
+                       else
+                               return alpha;
+
+                     case FTS_EXPIRED:
+                     case FTS_ABANDONED:
+                       return alpha;
+
+                     default:
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+               }
+               break;
+
+             case FTS_EXPIRED:
+               switch (beta->state) {
+                     case FTS_ACTIVE:
+                       return beta;
+
+                     case FTS_EXPIRED:
+                       /* Choose the most recently expired prefix. */
+                       if (alpha->hard_lifetime_end_time <
+                           beta->hard_lifetime_end_time)
+                               return beta;
+                       else if ((alpha->hard_lifetime_end_time ==
+                                 beta->hard_lifetime_end_time) &&
+                                (alpha->soft_lifetime_end_time <
+                                 beta->soft_lifetime_end_time))
+                               return beta;
+                       else
+                               return alpha;
+
+                     case FTS_ABANDONED:
+                       return alpha;
+
+                     default:
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+               }
+               break;
+
+             case FTS_ABANDONED:
+               switch (beta->state) {
+                     case FTS_ACTIVE:
+                     case FTS_EXPIRED:
+                       return alpha;
+
+                     case FTS_ABANDONED:
+                       /* Choose the prefix that was abandoned longest ago. */
+                       if (alpha->hard_lifetime_end_time <
+                           beta->hard_lifetime_end_time)
+                               return alpha;
+                       else
+                               return beta;
+
+                     default:
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+               }
+               break;
+
+             default:
+               log_fatal("Impossible condition at %s:%d.", MDL);
+       }
+
+       log_fatal("Triple impossible condition at %s:%d.", MDL);
+       return NULL;
+}
+
+/*
+ * Solicit is how a client starts requesting addresses.
+ *
+ * If the client asks for rapid commit, and we support it, we will
+ * allocate the addresses and reply.
+ *
+ * Otherwise we will send an advertise message.
+ */
+
+static void
+dhcpv6_solicit(struct data_string *reply_ret, struct packet *packet) {
+       struct data_string client_id;
+
+       /*
+        * Validate our input.
+        */
+       if (!valid_client_msg(packet, &client_id)) {
+               return;
+       }
+
+       lease_to_client(reply_ret, packet, &client_id, NULL);
+
+       /*
+        * Clean up.
+        */
+       data_string_forget(&client_id, MDL);
+}
+
+/*
+ * Request is how a client actually requests addresses.
+ *
+ * Very similar to Solicit handling, except the server DUID is required.
+ */
+
+static void
+dhcpv6_request(struct data_string *reply_ret, struct packet *packet) {
+       struct data_string client_id;
+       struct data_string server_id;
+
+       /*
+        * Validate our input.
+        */
+       if (!valid_client_resp(packet, &client_id, &server_id)) {
+               return;
+       }
+
+       /* If the REQUEST arrived via unicast and unicast option isn't set,
+        * reject it per RFC 3315, Sec 18.2.1 */
+       if (packet->unicast == ISC_TRUE &&
+           is_unicast_option_defined(packet) == ISC_FALSE) {
+               unicast_reject(reply_ret, packet, &client_id, &server_id);
+       } else {
+               /*
+                * Issue our lease.
+                */
+               lease_to_client(reply_ret, packet, &client_id, &server_id);
+       }
+
+       /*
+        * Cleanup.
+        */
+       data_string_forget(&client_id, MDL);
+       data_string_forget(&server_id, MDL);
+}
+
+/* Find a DHCPv6 packet's shared network from hints in the packet.
+ */
+static isc_result_t
+shared_network_from_packet6(struct shared_network **shared,
+                           struct packet *packet)
+{
+       const struct packet *chk_packet;
+       const struct in6_addr *link_addr, *first_link_addr;
+       struct iaddr tmp_addr;
+       struct subnet *subnet;
+       isc_result_t status;
+
+       if ((shared == NULL) || (*shared != NULL) || (packet == NULL))
+               return DHCP_R_INVALIDARG;
+
+       /*
+        * First, find the link address where the packet from the client
+        * first appeared (if this packet was relayed).
+        */
+       first_link_addr = NULL;
+       chk_packet = packet->dhcpv6_container_packet;
+       while (chk_packet != NULL) {
+               link_addr = &chk_packet->dhcpv6_link_address;
+               if (!IN6_IS_ADDR_UNSPECIFIED(link_addr) &&
+                   !IN6_IS_ADDR_LINKLOCAL(link_addr)) {
+                       first_link_addr = link_addr;
+                       break;
+               }
+               chk_packet = chk_packet->dhcpv6_container_packet;
+       }
+
+       /*
+        * If there is a relayed link address, find the subnet associated
+        * with that, and use that to get the appropriate
+        * shared_network.
+        */
+       if (first_link_addr != NULL) {
+               tmp_addr.len = sizeof(*first_link_addr);
+               memcpy(tmp_addr.iabuf,
+                      first_link_addr, sizeof(*first_link_addr));
+               subnet = NULL;
+               if (!find_subnet(&subnet, tmp_addr, MDL)) {
+                       log_debug("No subnet found for link-address %s.",
+                                 piaddr(tmp_addr));
+                       return ISC_R_NOTFOUND;
+               }
+               status = shared_network_reference(shared,
+                                                 subnet->shared_network, MDL);
+               subnet_dereference(&subnet, MDL);
+
+       /*
+        * If there is no link address, we will use the interface
+        * that this packet came in on to pick the shared_network.
+        */
+       } else if (packet->interface != NULL) {
+               status = shared_network_reference(shared,
+                                        packet->interface->shared_network,
+                                        MDL);
+                if (packet->dhcpv6_container_packet != NULL) {
+                       log_info("[L2 Relay] No link address in relay packet "
+                                "assuming L2 relay and using receiving "
+                                "interface");
+                }
+
+       } else {
+               /*
+                * We shouldn't be able to get here but if there is no link
+                * address and no interface we don't know where to get the
+                * pool from log an error and return an error.
+                */
+               log_error("No interface and no link address "
+                         "can't determine pool");
+               status = DHCP_R_INVALIDARG;
+       }
+
+       return status;
+}
+
+/*
+ * When a client thinks it might be on a new link, it sends a
+ * Confirm message.
+ *
+ * From RFC3315 section 18.2.2:
+ *
+ *   When the server receives a Confirm message, the server determines
+ *   whether the addresses in the Confirm message are appropriate for the
+ *   link to which the client is attached.  If all of the addresses in the
+ *   Confirm message pass this test, the server returns a status of
+ *   Success.  If any of the addresses do not pass this test, the server
+ *   returns a status of NotOnLink.  If the server is unable to perform
+ *   this test (for example, the server does not have information about
+ *   prefixes on the link to which the client is connected), or there were
+ *   no addresses in any of the IAs sent by the client, the server MUST
+ *   NOT send a reply to the client.
+ */
+
+static void
+dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
+       struct shared_network *shared;
+       struct subnet *subnet;
+       struct option_cache *ia, *ta, *oc;
+       struct data_string cli_enc_opt_data, iaaddr, client_id, packet_oro;
+       struct option_state *cli_enc_opt_state, *opt_state;
+       struct iaddr cli_addr;
+       int pass;
+       isc_boolean_t inappropriate, has_addrs;
+       char reply_data[65536];
+       struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
+       int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options));
+
+       /*
+        * Basic client message validation.
+        */
+       memset(&client_id, 0, sizeof(client_id));
+       if (!valid_client_msg(packet, &client_id)) {
+               return;
+       }
+
+       /*
+        * Do not process Confirms that do not have IA's we do not recognize.
+        */
+       ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+       ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
+       if ((ia == NULL) && (ta == NULL))
+               return;
+
+       /*
+        * IA_PD's are simply ignored.
+        */
+       delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+
+       /*
+        * Bit of variable initialization.
+        */
+       opt_state = cli_enc_opt_state = NULL;
+       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
+       memset(&iaaddr, 0, sizeof(iaaddr));
+       memset(&packet_oro, 0, sizeof(packet_oro));
+
+       /* Determine what shared network the client is connected to.  We
+        * must not respond if we don't have any information about the
+        * network the client is on.
+        */
+       shared = NULL;
+       if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) ||
+           (shared == NULL))
+               goto exit;
+
+       /* If there are no recorded subnets, then we have no
+        * information about this subnet - ignore Confirms.
+        */
+       subnet = shared->subnets;
+       if (subnet == NULL)
+               goto exit;
+
+       /* Are the addresses in all the IA's appropriate for that link? */
+       has_addrs = inappropriate = ISC_FALSE;
+       pass = D6O_IA_NA;
+       while(!inappropriate) {
+               /* If we've reached the end of the IA_NA pass, move to the
+                * IA_TA pass.
+                */
+               if ((pass == D6O_IA_NA) && (ia == NULL)) {
+                       pass = D6O_IA_TA;
+                       ia = ta;
+               }
+
+               /* If we've reached the end of all passes, we're done. */
+               if (ia == NULL)
+                       break;
+
+               if (((pass == D6O_IA_NA) &&
+                    !get_encapsulated_IA_state(&cli_enc_opt_state,
+                                               &cli_enc_opt_data,
+                                               packet, ia, IA_NA_OFFSET)) ||
+                   ((pass == D6O_IA_TA) &&
+                    !get_encapsulated_IA_state(&cli_enc_opt_state,
+                                               &cli_enc_opt_data,
+                                               packet, ia, IA_TA_OFFSET))) {
+                       goto exit;
+               }
+
+               oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+                                  D6O_IAADDR);
+
+               for ( ; oc != NULL ; oc = oc->next) {
+                       if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
+                                                  packet->options, NULL,
+                                                  &global_scope, oc, MDL) ||
+                           (iaaddr.len < IAADDR_OFFSET)) {
+                               log_error("dhcpv6_confirm: "
+                                         "error evaluating IAADDR.");
+                               goto exit;
+                       }
+
+                       /* Copy out the IPv6 address for processing. */
+                       cli_addr.len = 16;
+                       memcpy(cli_addr.iabuf, iaaddr.data, 16);
+
+                       data_string_forget(&iaaddr, MDL);
+
+                       /* Record that we've processed at least one address. */
+                       has_addrs = ISC_TRUE;
+
+                       /* Find out if any subnets cover this address. */
+                       for (subnet = shared->subnets ; subnet != NULL ;
+                            subnet = subnet->next_sibling) {
+                               if (addr_eq(subnet_number(cli_addr,
+                                                         subnet->netmask),
+                                           subnet->net))
+                                       break;
+                       }
+
+                       /* If we reach the end of the subnet list, and no
+                        * subnet matches the client address, then it must
+                        * be inappropriate to the link (so far as our
+                        * configuration says).  Once we've found one
+                        * inappropriate address, there is no reason to
+                        * continue searching.
+                        */
+                       if (subnet == NULL) {
+                               inappropriate = ISC_TRUE;
+                               break;
+                       }
+               }
+
+               option_state_dereference(&cli_enc_opt_state, MDL);
+               data_string_forget(&cli_enc_opt_data, MDL);
+
+               /* Advance to the next IA_*. */
+               ia = ia->next;
+       }
+
+       /* If the client supplied no addresses, do not reply. */
+       if (!has_addrs)
+               goto exit;
+
+       /*
+        * Set up reply.
+        */
+       if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) {
+               goto exit;
+       }
+
+       /*
+        * Set our status.
+        */
+       if (inappropriate) {
+               if (!set_status_code(STATUS_NotOnLink,
+                                    "Some of the addresses are not on link.",
+                                    opt_state)) {
+                       goto exit;
+               }
+       } else {
+               if (!set_status_code(STATUS_Success,
+                                    "All addresses still on link.",
+                                    opt_state)) {
+                       goto exit;
+               }
+       }
+
+       /*
+        * Only one option: add it.
+        */
+       reply_ofs += store_options6(reply_data+reply_ofs,
+                                   sizeof(reply_data)-reply_ofs,
+                                   opt_state, packet,
+                                   required_opts, &packet_oro);
+
+       /*
+        * Return our reply to the caller.
+        */
+       reply_ret->len = reply_ofs;
+       reply_ret->buffer = NULL;
+       if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
+               log_fatal("No memory to store reply.");
+       }
+       reply_ret->data = reply_ret->buffer->data;
+       memcpy(reply_ret->buffer->data, reply, reply_ofs);
+
+exit:
+       /* Cleanup any stale data strings. */
+       if (cli_enc_opt_data.buffer != NULL)
+               data_string_forget(&cli_enc_opt_data, MDL);
+       if (iaaddr.buffer != NULL)
+               data_string_forget(&iaaddr, MDL);
+       if (client_id.buffer != NULL)
+               data_string_forget(&client_id, MDL);
+       if (packet_oro.buffer != NULL)
+               data_string_forget(&packet_oro, MDL);
+
+       /* Release any stale option states. */
+       if (cli_enc_opt_state != NULL)
+               option_state_dereference(&cli_enc_opt_state, MDL);
+       if (opt_state != NULL)
+               option_state_dereference(&opt_state, MDL);
+}
+
+/*
+ * Renew is when a client wants to extend its lease/prefix, at time T1.
+ *
+ * We handle this the same as if the client wants a new lease/prefix,
+ * except for the error code of when addresses don't match.
+ */
+
+static void
+dhcpv6_renew(struct data_string *reply, struct packet *packet) {
+       struct data_string client_id;
+       struct data_string server_id;
+
+       /*
+        * Validate the request.
+        */
+       if (!valid_client_resp(packet, &client_id, &server_id)) {
+               return;
+       }
+
+       /* If the RENEW arrived via unicast and unicast option isn't set,
+        * reject it per RFC 3315, Sec 18.2.3 */
+       if (packet->unicast == ISC_TRUE &&
+           is_unicast_option_defined(packet) == ISC_FALSE) {
+               unicast_reject(reply, packet, &client_id, &server_id);
+       } else {
+               /*
+                * Renew our lease.
+                */
+               lease_to_client(reply, packet, &client_id, &server_id);
+       }
+
+       /*
+        * Cleanup.
+        */
+       data_string_forget(&server_id, MDL);
+       data_string_forget(&client_id, MDL);
+}
+
+/*
+ * Rebind is when a client wants to extend its lease, at time T2.
+ *
+ * We handle this the same as if the client wants a new lease, except
+ * for the error code of when addresses don't match.
+ */
+
+static void
+dhcpv6_rebind(struct data_string *reply, struct packet *packet) {
+       struct data_string client_id;
+
+       if (!valid_client_msg(packet, &client_id)) {
+               return;
+       }
+
+       lease_to_client(reply, packet, &client_id, NULL);
+
+       data_string_forget(&client_id, MDL);
+}
+
+static void
+ia_na_match_decline(const struct data_string *client_id,
+                   const struct data_string *iaaddr,
+                   struct iasubopt *lease)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+
+       log_error("Client %s reports address %s is "
+                 "already in use by another host!",
+                 print_hex_1(client_id->len, client_id->data, 60),
+                 inet_ntop(AF_INET6, iaaddr->data,
+                           tmp_addr, sizeof(tmp_addr)));
+       if (lease != NULL) {
+               decline_lease6(lease->ipv6_pool, lease);
+               lease->ia->cltt = cur_time;
+               write_ia(lease->ia);
+       }
+}
+
+static void
+ia_na_nomatch_decline(const struct data_string *client_id,
+                     const struct data_string *iaaddr,
+                     u_int32_t *ia_na_id,
+                     struct packet *packet,
+                     char *reply_data,
+                     int *reply_ofs,
+                     int reply_len)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+       struct option_state *host_opt_state;
+       int len;
+
+       log_info("Client %s declines address %s, which is not offered to it.",
+                print_hex_1(client_id->len, client_id->data, 60),
+                inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+
+       /*
+        * Create state for this IA_NA.
+        */
+       host_opt_state = NULL;
+       if (!option_state_allocate(&host_opt_state, MDL)) {
+               log_error("ia_na_nomatch_decline: out of memory "
+                         "allocating option_state.");
+               goto exit;
+       }
+
+       if (!set_status_code(STATUS_NoBinding, "Decline for unknown address.",
+                            host_opt_state)) {
+               goto exit;
+       }
+
+       /*
+        * Insure we have enough space
+        */
+       if (reply_len < (*reply_ofs + 16)) {
+               log_error("ia_na_nomatch_decline: "
+                         "out of space for reply packet.");
+               goto exit;
+       }
+
+       /*
+        * Put our status code into the reply packet.
+        */
+       len = store_options6(reply_data+(*reply_ofs)+16,
+                            reply_len-(*reply_ofs)-16,
+                            host_opt_state, packet,
+                            required_opts_STATUS_CODE, NULL);
+
+       /*
+        * Store the non-encapsulated option data for this
+        * IA_NA into our reply packet. Defined in RFC 3315,
+        * section 22.4.
+        */
+       /* option number */
+       putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
+       /* option length */
+       putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
+       /* IA_NA, copied from the client */
+       memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
+       /* t1 and t2, odd that we need them, but here it is */
+       putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
+       putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+
+       /*
+        * Get ready for next IA_NA.
+        */
+       *reply_ofs += (len + 16);
+
+exit:
+       option_state_dereference(&host_opt_state, MDL);
+}
+
+static void
+iterate_over_ia_na(struct data_string *reply_ret,
+                  struct packet *packet,
+                  const struct data_string *client_id,
+                  const struct data_string *server_id,
+                  const char *packet_type,
+                  void (*ia_na_match)(),
+                  void (*ia_na_nomatch)())
+{
+       struct option_state *opt_state;
+       struct host_decl *packet_host;
+       struct option_cache *ia;
+       struct option_cache *oc;
+       /* cli_enc_... variables come from the IA_NA/IA_TA options */
+       struct data_string cli_enc_opt_data;
+       struct option_state *cli_enc_opt_state;
+       struct host_decl *host;
+       struct data_string iaaddr;
+       struct data_string fixed_addr;
+       char reply_data[65536];
+       struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
+       int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options));
+       char status_msg[32];
+       struct iasubopt *lease;
+       struct ia_xx *existing_ia_na;
+       int i;
+       struct data_string key;
+       u_int32_t iaid;
+
+       /*
+        * Initialize to empty values, in case we have to exit early.
+        */
+       opt_state = NULL;
+       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
+       cli_enc_opt_state = NULL;
+       memset(&iaaddr, 0, sizeof(iaaddr));
+       memset(&fixed_addr, 0, sizeof(fixed_addr));
+       lease = NULL;
+
+       /*
+        * Find the host record that matches from the packet, if any.
+        */
+       packet_host = NULL;
+       find_hosts6(&packet_host, packet, client_id, MDL);
+
+       /*
+        * Set our reply information.
+        */
+       reply->msg_type = DHCPV6_REPLY;
+       memcpy(reply->transaction_id, packet->dhcpv6_transaction_id,
+              sizeof(reply->transaction_id));
+
+       /*
+        * Build our option state for reply.
+        */
+       opt_state = NULL;
+       if (!option_state_allocate(&opt_state, MDL)) {
+               log_error("iterate_over_ia_na: no memory for option_state.");
+               goto exit;
+       }
+       execute_statements_in_scope(NULL, packet, NULL, NULL,
+                                   packet->options, opt_state,
+                                   &global_scope, root_group, NULL, NULL);
+
+       /*
+        * RFC 3315, section 18.2.7 tells us which options to include.
+        */
+       oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
+       if (oc == NULL) {
+               if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                                       (unsigned char *)server_duid.data,
+                                       server_duid.len, D6O_SERVERID, 0)) {
+                       log_error("iterate_over_ia_na: "
+                                 "error saving server identifier.");
+                       goto exit;
+               }
+       }
+
+       if (!save_option_buffer(&dhcpv6_universe, opt_state,
+                               client_id->buffer,
+                               (unsigned char *)client_id->data,
+                               client_id->len,
+                               D6O_CLIENTID, 0)) {
+               log_error("iterate_over_ia_na: "
+                         "error saving client identifier.");
+               goto exit;
+       }
+
+       snprintf(status_msg, sizeof(status_msg), "%s received.", packet_type);
+       if (!set_status_code(STATUS_Success, status_msg, opt_state)) {
+               goto exit;
+       }
+
+       /*
+        * Add our options that are not associated with any IA_NA or IA_TA.
+        */
+       reply_ofs += store_options6(reply_data+reply_ofs,
+                                   sizeof(reply_data)-reply_ofs,
+                                   opt_state, packet,
+                                   required_opts, NULL);
+
+       /*
+        * Loop through the IA_NA reported by the client, and deal with
+        * addresses reported as already in use.
+        */
+       for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+            ia != NULL; ia = ia->next) {
+
+               if (!get_encapsulated_IA_state(&cli_enc_opt_state,
+                                              &cli_enc_opt_data,
+                                              packet, ia, IA_NA_OFFSET)) {
+                       goto exit;
+               }
+
+               iaid = getULong(cli_enc_opt_data.data);
+
+               /*
+                * XXX: It is possible that we can get multiple addresses
+                *      sent by the client. We don't send multiple
+                *      addresses, so this indicates a client error.
+                *      We should check for multiple IAADDR options, log
+                *      if found, and set as an error.
+                */
+               oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+                                  D6O_IAADDR);
+               if (oc == NULL) {
+                       /* no address given for this IA, ignore */
+                       option_state_dereference(&cli_enc_opt_state, MDL);
+                       data_string_forget(&cli_enc_opt_data, MDL);
+                       continue;
+               }
+
+               memset(&iaaddr, 0, sizeof(iaaddr));
+               if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("iterate_over_ia_na: "
+                                 "error evaluating IAADDR.");
+                       goto exit;
+               }
+
+               /*
+                * Now we need to figure out which host record matches
+                * this IA_NA and IAADDR (encapsulated option contents
+                * matching a host record by option).
+                *
+                * XXX: We don't currently track IA_NA separately, but
+                *      we will need to do this!
+                */
+               host = NULL;
+               if (!find_hosts_by_option(&host, packet,
+                                         cli_enc_opt_state, MDL)) {
+                       if (packet_host != NULL) {
+                               host = packet_host;
+                       } else {
+                               host = NULL;
+                       }
+               }
+               while (host != NULL) {
+                       if (host->fixed_addr != NULL) {
+                               if (!evaluate_option_cache(&fixed_addr, NULL,
+                                                          NULL, NULL, NULL,
+                                                          NULL, &global_scope,
+                                                          host->fixed_addr,
+                                                          MDL)) {
+                                       log_error("iterate_over_ia_na: error "
+                                                 "evaluating host address.");
+                                       goto exit;
+                               }
+                               if ((iaaddr.len >= 16) &&
+                                   !memcmp(fixed_addr.data, iaaddr.data, 16)) {
+                                       data_string_forget(&fixed_addr, MDL);
+                                       break;
+                               }
+                               data_string_forget(&fixed_addr, MDL);
+                       }
+                       host = host->n_ipaddr;
+               }
+
+               if ((host == NULL) && (iaaddr.len >= IAADDR_OFFSET)) {
+                       /*
+                        * Find existing IA_NA.
+                        */
+                       if (ia_make_key(&key, iaid,
+                                       (char *)client_id->data,
+                                       client_id->len,
+                                       MDL) != ISC_R_SUCCESS) {
+                               log_fatal("iterate_over_ia_na: no memory for "
+                                         "key.");
+                       }
+
+                       existing_ia_na = NULL;
+                       if (ia_hash_lookup(&existing_ia_na, ia_na_active,
+                                          (unsigned char *)key.data,
+                                          key.len, MDL)) {
+                               /*
+                                * Make sure this address is in the IA_NA.
+                                */
+                               for (i=0; i<existing_ia_na->num_iasubopt; i++) {
+                                       struct iasubopt *tmp;
+                                       struct in6_addr *in6_addr;
+
+                                       tmp = existing_ia_na->iasubopt[i];
+                                       in6_addr = &tmp->addr;
+                                       if (memcmp(in6_addr,
+                                                  iaaddr.data, 16) == 0) {
+                                               iasubopt_reference(&lease,
+                                                                  tmp, MDL);
+                                               break;
+                                       }
+                               }
+                       }
+
+                       data_string_forget(&key, MDL);
+               }
+
+               if ((host != NULL) || (lease != NULL)) {
+                       ia_na_match(client_id, &iaaddr, lease);
+               } else {
+                       ia_na_nomatch(client_id, &iaaddr,
+                                     (u_int32_t *)cli_enc_opt_data.data,
+                                     packet, reply_data, &reply_ofs,
+                                     sizeof(reply_data));
+               }
+
+               if (lease != NULL) {
+                       iasubopt_dereference(&lease, MDL);
+               }
+
+               data_string_forget(&iaaddr, MDL);
+               option_state_dereference(&cli_enc_opt_state, MDL);
+               data_string_forget(&cli_enc_opt_data, MDL);
+       }
+
+       /*
+        * Return our reply to the caller.
+        */
+       reply_ret->len = reply_ofs;
+       reply_ret->buffer = NULL;
+       if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
+               log_fatal("No memory to store reply.");
+       }
+       reply_ret->data = reply_ret->buffer->data;
+       memcpy(reply_ret->buffer->data, reply, reply_ofs);
+
+exit:
+       if (lease != NULL) {
+               iasubopt_dereference(&lease, MDL);
+       }
+       if (fixed_addr.buffer != NULL) {
+               data_string_forget(&fixed_addr, MDL);
+       }
+       if (iaaddr.buffer != NULL) {
+               data_string_forget(&iaaddr, MDL);
+       }
+       if (cli_enc_opt_state != NULL) {
+               option_state_dereference(&cli_enc_opt_state, MDL);
+       }
+       if (cli_enc_opt_data.buffer != NULL) {
+               data_string_forget(&cli_enc_opt_data, MDL);
+       }
+       if (opt_state != NULL) {
+               option_state_dereference(&opt_state, MDL);
+       }
+}
+
+/*
+ * Decline means a client has detected that something else is using an
+ * address we gave it.
+ *
+ * Since we're only dealing with fixed leases for now, there's not
+ * much we can do, other that log the occurrence.
+ *
+ * When we start issuing addresses from pools, then we will have to
+ * record our declined addresses and issue another. In general with
+ * IPv6 there is no worry about DoS by clients exhausting space, but
+ * we still need to be aware of this possibility.
+ */
+
+/* TODO: IA_TA */
+static void
+dhcpv6_decline(struct data_string *reply, struct packet *packet) {
+       struct data_string client_id;
+       struct data_string server_id;
+
+       /*
+        * Validate our input.
+        */
+       if (!valid_client_resp(packet, &client_id, &server_id)) {
+               return;
+       }
+
+       /* If the DECLINE arrived via unicast and unicast option isn't set,
+        * reject it per RFC 3315, Sec 18.2.7 */
+       if (packet->unicast == ISC_TRUE &&
+           is_unicast_option_defined(packet) == ISC_FALSE) {
+               unicast_reject(reply, packet, &client_id, &server_id);
+       } else {
+               /*
+                * Undefined for IA_PD.
+                */
+               delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+
+               /*
+                * And operate on each IA_NA in this packet.
+                */
+               iterate_over_ia_na(reply, packet, &client_id, &server_id,
+                                  "Decline", ia_na_match_decline,
+                                  ia_na_nomatch_decline);
+
+       }
+
+       data_string_forget(&server_id, MDL);
+       data_string_forget(&client_id, MDL);
+}
+
+static void
+ia_na_match_release(const struct data_string *client_id,
+                   const struct data_string *iaaddr,
+                   struct iasubopt *lease)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+
+       log_info("Client %s releases address %s",
+                print_hex_1(client_id->len, client_id->data, 60),
+                inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+       if (lease != NULL) {
+               release_lease6(lease->ipv6_pool, lease);
+               lease->ia->cltt = cur_time;
+               write_ia(lease->ia);
+       }
+}
+
+static void
+ia_na_nomatch_release(const struct data_string *client_id,
+                     const struct data_string *iaaddr,
+                     u_int32_t *ia_na_id,
+                     struct packet *packet,
+                     char *reply_data,
+                     int *reply_ofs,
+                     int reply_len)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+       struct option_state *host_opt_state;
+       int len;
+
+       log_info("Client %s releases address %s, which is not leased to it.",
+                print_hex_1(client_id->len, client_id->data, 60),
+                inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+
+       /*
+        * Create state for this IA_NA.
+        */
+       host_opt_state = NULL;
+       if (!option_state_allocate(&host_opt_state, MDL)) {
+               log_error("ia_na_nomatch_release: out of memory "
+                         "allocating option_state.");
+               goto exit;
+       }
+
+       if (!set_status_code(STATUS_NoBinding,
+                            "Release for non-leased address.",
+                            host_opt_state)) {
+               goto exit;
+       }
+
+       /*
+        * Insure we have enough space
+        */
+       if (reply_len < (*reply_ofs + 16)) {
+               log_error("ia_na_nomatch_release: "
+                         "out of space for reply packet.");
+               goto exit;
+       }
+
+       /*
+        * Put our status code into the reply packet.
+        */
+       len = store_options6(reply_data+(*reply_ofs)+16,
+                            reply_len-(*reply_ofs)-16,
+                            host_opt_state, packet,
+                            required_opts_STATUS_CODE, NULL);
+
+       /*
+        * Store the non-encapsulated option data for this
+        * IA_NA into our reply packet. Defined in RFC 3315,
+        * section 22.4.
+        */
+       /* option number */
+       putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
+       /* option length */
+       putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
+       /* IA_NA, copied from the client */
+       memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
+       /* t1 and t2, odd that we need them, but here it is */
+       putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
+       putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+
+       /*
+        * Get ready for next IA_NA.
+        */
+       *reply_ofs += (len + 16);
+
+exit:
+       option_state_dereference(&host_opt_state, MDL);
+}
+
+static void
+ia_pd_match_release(const struct data_string *client_id,
+                   const struct data_string *iapref,
+                   struct iasubopt *prefix)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+
+       log_info("Client %s releases prefix %s/%u",
+                print_hex_1(client_id->len, client_id->data, 60),
+                inet_ntop(AF_INET6, iapref->data + 9,
+                          tmp_addr, sizeof(tmp_addr)),
+                (unsigned) getUChar(iapref->data + 8));
+       if (prefix != NULL) {
+               release_lease6(prefix->ipv6_pool, prefix);
+               prefix->ia->cltt = cur_time;
+               write_ia(prefix->ia);
+       }
+}
+
+static void
+ia_pd_nomatch_release(const struct data_string *client_id,
+                     const struct data_string *iapref,
+                     u_int32_t *ia_pd_id,
+                     struct packet *packet,
+                     char *reply_data,
+                     int *reply_ofs,
+                     int reply_len)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+       struct option_state *host_opt_state;
+       int len;
+
+       log_info("Client %s releases prefix %s/%u, which is not leased to it.",
+                print_hex_1(client_id->len, client_id->data, 60),
+                inet_ntop(AF_INET6, iapref->data + 9,
+                          tmp_addr, sizeof(tmp_addr)),
+                (unsigned) getUChar(iapref->data + 8));
+
+       /*
+        * Create state for this IA_PD.
+        */
+       host_opt_state = NULL;
+       if (!option_state_allocate(&host_opt_state, MDL)) {
+               log_error("ia_pd_nomatch_release: out of memory "
+                         "allocating option_state.");
+               goto exit;
+       }
+
+       if (!set_status_code(STATUS_NoBinding,
+                            "Release for non-leased prefix.",
+                            host_opt_state)) {
+               goto exit;
+       }
+
+       /*
+        * Insure we have enough space
+        */
+       if (reply_len < (*reply_ofs + 16)) {
+               log_error("ia_pd_nomatch_release: "
+                         "out of space for reply packet.");
+               goto exit;
+       }
+
+       /*
+        * Put our status code into the reply packet.
+        */
+       len = store_options6(reply_data+(*reply_ofs)+16,
+                            reply_len-(*reply_ofs)-16,
+                            host_opt_state, packet,
+                            required_opts_STATUS_CODE, NULL);
+
+       /*
+        * Store the non-encapsulated option data for this
+        * IA_PD into our reply packet. Defined in RFC 3315,
+        * section 22.4.
+        */
+       /* option number */
+       putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_PD);
+       /* option length */
+       putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
+       /* IA_PD, copied from the client */
+       memcpy(reply_data+(*reply_ofs)+4, ia_pd_id, 4);
+       /* t1 and t2, odd that we need them, but here it is */
+       putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
+       putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+
+       /*
+        * Get ready for next IA_PD.
+        */
+       *reply_ofs += (len + 16);
+
+exit:
+       option_state_dereference(&host_opt_state, MDL);
+}
+
+static void
+iterate_over_ia_pd(struct data_string *reply_ret,
+                  struct packet *packet,
+                  const struct data_string *client_id,
+                  const struct data_string *server_id,
+                  const char *packet_type,
+                  void (*ia_pd_match)(),
+                  void (*ia_pd_nomatch)())
+{
+       struct data_string reply_new;
+       int reply_len;
+       struct option_state *opt_state;
+       struct host_decl *packet_host;
+       struct option_cache *ia;
+       struct option_cache *oc;
+       /* cli_enc_... variables come from the IA_PD options */
+       struct data_string cli_enc_opt_data;
+       struct option_state *cli_enc_opt_state;
+       struct host_decl *host;
+       struct data_string iaprefix;
+       char reply_data[65536];
+       int reply_ofs;
+       struct iasubopt *prefix;
+       struct ia_xx *existing_ia_pd;
+       int i;
+       struct data_string key;
+       u_int32_t iaid;
+
+       /*
+        * Initialize to empty values, in case we have to exit early.
+        */
+       memset(&reply_new, 0, sizeof(reply_new));
+       opt_state = NULL;
+       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
+       cli_enc_opt_state = NULL;
+       memset(&iaprefix, 0, sizeof(iaprefix));
+       prefix = NULL;
+
+       /*
+        * Compute the available length for the reply.
+        */
+       reply_len = sizeof(reply_data) - reply_ret->len;
+       reply_ofs = 0;
+
+       /*
+        * Find the host record that matches from the packet, if any.
+        */
+       packet_host = NULL;
+       find_hosts6(&packet_host, packet, client_id, MDL);
+
+       /*
+        * Build our option state for reply.
+        */
+       opt_state = NULL;
+       if (!option_state_allocate(&opt_state, MDL)) {
+               log_error("iterate_over_ia_pd: no memory for option_state.");
+               goto exit;
+       }
+       execute_statements_in_scope(NULL, packet, NULL, NULL,
+                                   packet->options, opt_state,
+                                   &global_scope, root_group, NULL, NULL);
+
+       /*
+        * Loop through the IA_PD reported by the client, and deal with
+        * prefixes reported as already in use.
+        */
+       for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+            ia != NULL; ia = ia->next) {
+
+           if (!get_encapsulated_IA_state(&cli_enc_opt_state,
+                                          &cli_enc_opt_data,
+                                          packet, ia, IA_PD_OFFSET)) {
+               goto exit;
+           }
+
+           iaid = getULong(cli_enc_opt_data.data);
+
+           oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+                              D6O_IAPREFIX);
+           if (oc == NULL) {
+               /* no prefix given for this IA_PD, ignore */
+               option_state_dereference(&cli_enc_opt_state, MDL);
+               data_string_forget(&cli_enc_opt_data, MDL);
+               continue;
+           }
+
+           for (; oc != NULL; oc = oc->next) {
+               memset(&iaprefix, 0, sizeof(iaprefix));
+               if (!evaluate_option_cache(&iaprefix, packet, NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("iterate_over_ia_pd: "
+                                 "error evaluating IAPREFIX.");
+                       goto exit;
+               }
+
+               /*
+                * Now we need to figure out which host record matches
+                * this IA_PD and IAPREFIX (encapsulated option contents
+                * matching a host record by option).
+                *
+                * XXX: We don't currently track IA_PD separately, but
+                *      we will need to do this!
+                */
+               host = NULL;
+               if (!find_hosts_by_option(&host, packet,
+                                         cli_enc_opt_state, MDL)) {
+                       if (packet_host != NULL) {
+                               host = packet_host;
+                       } else {
+                               host = NULL;
+                       }
+               }
+               while (host != NULL) {
+                       if (host->fixed_prefix != NULL) {
+                               struct iaddrcidrnetlist *l;
+                               int plen = (int) getUChar(iaprefix.data + 8);
+
+                               for (l = host->fixed_prefix; l != NULL;
+                                    l = l->next) {
+                                       if (plen != l->cidrnet.bits)
+                                               continue;
+                                       if (memcmp(iaprefix.data + 9,
+                                                  l->cidrnet.lo_addr.iabuf,
+                                                  16) == 0)
+                                               break;
+                               }
+                               if ((l != NULL) && (iaprefix.len >= 17))
+                                       break;
                        }
+                       host = host->n_ipaddr;
                }
 
-               option_state_dereference(&cli_enc_opt_state, MDL);
-               data_string_forget(&cli_enc_opt_data, MDL);
-
-               /* Advance to the next IA_*. */
-               ia = ia->next;
-       }
+               if ((host == NULL) && (iaprefix.len >= IAPREFIX_OFFSET)) {
+                       /*
+                        * Find existing IA_PD.
+                        */
+                       if (ia_make_key(&key, iaid,
+                                       (char *)client_id->data,
+                                       client_id->len,
+                                       MDL) != ISC_R_SUCCESS) {
+                               log_fatal("iterate_over_ia_pd: no memory for "
+                                         "key.");
+                       }
 
-       /* If the client supplied no addresses, do not reply. */
-       if (!has_addrs)
-               goto exit;
+                       existing_ia_pd = NULL;
+                       if (ia_hash_lookup(&existing_ia_pd, ia_pd_active,
+                                          (unsigned char *)key.data,
+                                          key.len, MDL)) {
+                               /*
+                                * Make sure this prefix is in the IA_PD.
+                                */
+                               for (i = 0;
+                                    i < existing_ia_pd->num_iasubopt;
+                                    i++) {
+                                       struct iasubopt *tmp;
+                                       u_int8_t plen;
+
+                                       plen = getUChar(iaprefix.data + 8);
+                                       tmp = existing_ia_pd->iasubopt[i];
+                                       if ((tmp->plen == plen) &&
+                                           (memcmp(&tmp->addr,
+                                                   iaprefix.data + 9,
+                                                   16) == 0)) {
+                                               iasubopt_reference(&prefix,
+                                                                  tmp, MDL);
+                                               break;
+                                       }
+                               }
+                       }
 
-       /* 
-        * Set up reply.
-        */
-       if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) {
-               goto exit;
-       }
+                       data_string_forget(&key, MDL);
+               }
 
-       /* 
-        * Set our status.
-        */
-       if (inappropriate) {
-               if (!set_status_code(STATUS_NotOnLink, 
-                                    "Some of the addresses are not on link.",
-                                    opt_state)) {
-                       goto exit;
+               if ((host != NULL) || (prefix != NULL)) {
+                       ia_pd_match(client_id, &iaprefix, prefix);
+               } else {
+                       ia_pd_nomatch(client_id, &iaprefix,
+                                     (u_int32_t *)cli_enc_opt_data.data,
+                                     packet, reply_data, &reply_ofs,
+                                     reply_len - reply_ofs);
                }
-       } else {
-               if (!set_status_code(STATUS_Success, 
-                                    "All addresses still on link.",
-                                    opt_state)) {
-                       goto exit;
+
+               if (prefix != NULL) {
+                       iasubopt_dereference(&prefix, MDL);
                }
-       }
 
-       /* 
-        * Only one option: add it.
-        */
-       reply_ofs += store_options6(reply_data+reply_ofs,
-                                   sizeof(reply_data)-reply_ofs, 
-                                   opt_state, packet,
-                                   required_opts, &packet_oro);
+               data_string_forget(&iaprefix, MDL);
+           }
 
-       /* 
+           option_state_dereference(&cli_enc_opt_state, MDL);
+           data_string_forget(&cli_enc_opt_data, MDL);
+       }
+
+       /*
         * Return our reply to the caller.
+        * The IA_NA routine has already filled at least the header.
         */
-       reply_ret->len = reply_ofs;
-       reply_ret->buffer = NULL;
-       if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
+       reply_new.len = reply_ret->len + reply_ofs;
+       if (!buffer_allocate(&reply_new.buffer, reply_new.len, MDL)) {
                log_fatal("No memory to store reply.");
        }
-       reply_ret->data = reply_ret->buffer->data;
-       memcpy(reply_ret->buffer->data, reply, reply_ofs);
+       reply_new.data = reply_new.buffer->data;
+       memcpy(reply_new.buffer->data,
+              reply_ret->buffer->data, reply_ret->len);
+       memcpy(reply_new.buffer->data + reply_ret->len,
+              reply_data, reply_ofs);
+       data_string_forget(reply_ret, MDL);
+       data_string_copy(reply_ret, &reply_new, MDL);
+       data_string_forget(&reply_new, MDL);
 
 exit:
-       /* Cleanup any stale data strings. */
-       if (cli_enc_opt_data.buffer != NULL)
-               data_string_forget(&cli_enc_opt_data, MDL);
-       if (iaaddr.buffer != NULL)
-               data_string_forget(&iaaddr, MDL);
-       if (client_id.buffer != NULL)
-               data_string_forget(&client_id, MDL);
-       if (packet_oro.buffer != NULL)
-               data_string_forget(&packet_oro, MDL);
-
-       /* Release any stale option states. */
-       if (cli_enc_opt_state != NULL)
+       if (prefix != NULL) {
+               iasubopt_dereference(&prefix, MDL);
+       }
+       if (iaprefix.buffer != NULL) {
+               data_string_forget(&iaprefix, MDL);
+       }
+       if (cli_enc_opt_state != NULL) {
                option_state_dereference(&cli_enc_opt_state, MDL);
-       if (opt_state != NULL)
+       }
+       if (cli_enc_opt_data.buffer != NULL) {
+               data_string_forget(&cli_enc_opt_data, MDL);
+       }
+       if (opt_state != NULL) {
                option_state_dereference(&opt_state, MDL);
+       }
 }
 
 /*
- * Renew is when a client wants to extend its lease, at time T1.
- *
- * We handle this the same as if the client wants a new lease, except
- * for the error code of when addresses don't match.
+ * Release means a client is done with the leases.
  */
 
-/* TODO: reject unicast messages, unless we set unicast option */
 static void
-dhcpv6_renew(struct data_string *reply, struct packet *packet) {
+dhcpv6_release(struct data_string *reply, struct packet *packet) {
        struct data_string client_id;
        struct data_string server_id;
 
-       /* 
-        * Validate the request.
+       /*
+        * Validate our input.
         */
        if (!valid_client_resp(packet, &client_id, &server_id)) {
                return;
        }
 
-       /*
-        * Renew our lease.
-        */
-       lease_to_client(reply, packet, &client_id, &server_id);
+       /* If the RELEASE arrived via unicast and unicast option isn't set,
+        * reject it per RFC 3315, Sec 18.2.6 */
+       if (packet->unicast == ISC_TRUE &&
+           is_unicast_option_defined(packet) == ISC_FALSE) {
+               unicast_reject(reply, packet, &client_id, &server_id);
+       } else {
+               /*
+                * And operate on each IA_NA in this packet.
+                */
+               iterate_over_ia_na(reply, packet, &client_id, &server_id,
+                                  "Release", ia_na_match_release,
+                                  ia_na_nomatch_release);
+
+               /*
+                * And operate on each IA_PD in this packet.
+                */
+               iterate_over_ia_pd(reply, packet, &client_id, &server_id,
+                                  "Release", ia_pd_match_release,
+                                  ia_pd_nomatch_release);
+       }
 
-       /*
-        * Cleanup.
-        */
        data_string_forget(&server_id, MDL);
        data_string_forget(&client_id, MDL);
 }
 
 /*
- * Rebind is when a client wants to extend its lease, at time T2.
- *
- * We handle this the same as if the client wants a new lease, except
- * for the error code of when addresses don't match.
+ * Information-Request is used by clients who have obtained an address
+ * from other means, but want configuration information from the server.
  */
 
 static void
-dhcpv6_rebind(struct data_string *reply, struct packet *packet) {
+dhcpv6_information_request(struct data_string *reply, struct packet *packet) {
        struct data_string client_id;
+       struct data_string server_id;
 
-       if (!valid_client_msg(packet, &client_id)) {
+       /*
+        * Validate our input.
+        */
+       if (!valid_client_info_req(packet, &server_id)) {
                return;
        }
 
-       lease_to_client(reply, packet, &client_id, NULL);
-
-       data_string_forget(&client_id, MDL);
-}
+       /*
+        * Get our client ID, if there is one.
+        */
+       memset(&client_id, 0, sizeof(client_id));
+       if (get_client_id(packet, &client_id) != ISC_R_SUCCESS) {
+               data_string_forget(&client_id, MDL);
+       }
 
-static void
-ia_na_match_decline(const struct data_string *client_id,
-                   const struct data_string *iaaddr,
-                   struct iaaddr *lease)
-{
-       char tmp_addr[INET6_ADDRSTRLEN];
+       /*
+        * Use the lease_to_client() function. This will work fine,
+        * because the valid_client_info_req() insures that we
+        * don't have any IA that would cause us to allocate
+        * resources to the client.
+        */
+       lease_to_client(reply, packet, &client_id,
+                       server_id.data != NULL ? &server_id : NULL);
 
-       log_error("Client %s reports address %s is "
-                 "already in use by another host!",
-                 print_hex_1(client_id->len, client_id->data, 60),
-                 inet_ntop(AF_INET6, iaaddr->data, 
-                           tmp_addr, sizeof(tmp_addr)));
-       if (lease != NULL) {
-               decline_lease6(lease->ipv6_pool, lease);
-               write_ia(lease->ia_na);
+       /*
+        * Cleanup.
+        */
+       if (client_id.data != NULL) {
+               data_string_forget(&client_id, MDL);
        }
+       data_string_forget(&server_id, MDL);
 }
 
+/*
+ * The Relay-forw message is sent by relays. It typically contains a
+ * single option, which encapsulates an entire packet.
+ *
+ * We need to build an encapsulated reply.
+ */
+
+/* XXX: this is very, very similar to do_packet6(), and should probably
+       be combined in a clever way */
+/* DHCPv6 server side */
 static void
-ia_na_nomatch_decline(const struct data_string *client_id,
-                     const struct data_string *iaaddr,
-                     u_int32_t *ia_na_id,
-                     struct packet *packet,
-                     char *reply_data,
-                     int *reply_ofs,
-                     int reply_len)
-{
-       char tmp_addr[INET6_ADDRSTRLEN];
-       struct option_state *host_opt_state;
-       int len;
+dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
+       struct option_cache *oc;
+       struct data_string enc_opt_data;
+       struct packet *enc_packet;
+       unsigned char msg_type;
+       const struct dhcpv6_packet *msg;
+       const struct dhcpv6_relay_packet *relay;
+       struct data_string enc_reply;
+       char link_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+       char peer_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+       struct data_string a_opt, packet_ero;
+       struct option_state *opt_state;
+       static char reply_data[65536];
+       struct dhcpv6_relay_packet *reply;
+       int reply_ofs;
 
-       log_info("Client %s declines address %s, which is not offered to it.",
-                print_hex_1(client_id->len, client_id->data, 60),
-                inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+       /*
+        * Initialize variables for early exit.
+        */
+       opt_state = NULL;
+       memset(&a_opt, 0, sizeof(a_opt));
+       memset(&packet_ero, 0, sizeof(packet_ero));
+       memset(&enc_reply, 0, sizeof(enc_reply));
+       memset(&enc_opt_data, 0, sizeof(enc_opt_data));
+       enc_packet = NULL;
 
        /*
-        * Create state for this IA_NA.
+        * Get our encapsulated relay message.
         */
-       host_opt_state = NULL;
-       if (!option_state_allocate(&host_opt_state, MDL)) {
-               log_error("ia_na_nomatch_decline: out of memory "
-                         "allocating option_state.");
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG);
+       if (oc == NULL) {
+               inet_ntop(AF_INET6, &packet->dhcpv6_link_address,
+                         link_addr, sizeof(link_addr));
+               inet_ntop(AF_INET6, &packet->dhcpv6_peer_address,
+                         peer_addr, sizeof(peer_addr));
+               log_info("Relay-forward from %s with link address=%s and "
+                        "peer address=%s missing Relay Message option.",
+                         piaddr(packet->client_addr), link_addr, peer_addr);
                goto exit;
        }
 
-       if (!set_status_code(STATUS_NoBinding, "Decline for unknown address.",
-                            host_opt_state)) {
+       if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL,
+                                  NULL, NULL, &global_scope, oc, MDL)) {
+               /* should be dhcpv6_relay_forw */
+               log_error("dhcpv6_forw_relay: error evaluating "
+                         "relayed message.");
+               goto exit;
+       }
+
+       if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) {
+               /* should be dhcpv6_relay_forw */
+               log_error("dhcpv6_forw_relay: encapsulated packet too short.");
                goto exit;
        }
 
        /*
-        * Insure we have enough space
+        * Build a packet structure from this encapsulated packet.
         */
-       if (reply_len < (*reply_ofs + 16)) {
-               log_error("ia_na_nomatch_decline: "
-                         "out of space for reply packet.");
+       enc_packet = NULL;
+       if (!packet_allocate(&enc_packet, MDL)) {
+               /* should be dhcpv6_relay_forw */
+               log_error("dhcpv6_forw_relay: "
+                         "no memory for encapsulated packet.");
+               goto exit;
+       }
+
+       if (!option_state_allocate(&enc_packet->options, MDL)) {
+               /* should be dhcpv6_relay_forw */
+               log_error("dhcpv6_forw_relay: "
+                         "no memory for encapsulated packet's options.");
+               goto exit;
+       }
+
+       enc_packet->client_port = packet->client_port;
+       enc_packet->client_addr = packet->client_addr;
+       interface_reference(&enc_packet->interface, packet->interface, MDL);
+       enc_packet->dhcpv6_container_packet = packet;
+
+       msg_type = enc_opt_data.data[0];
+       if ((msg_type == DHCPV6_RELAY_FORW) ||
+           (msg_type == DHCPV6_RELAY_REPL)) {
+               int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options));
+               relay = (struct dhcpv6_relay_packet *)enc_opt_data.data;
+               enc_packet->dhcpv6_msg_type = relay->msg_type;
+
+               /* relay-specific data */
+               enc_packet->dhcpv6_hop_count = relay->hop_count;
+               memcpy(&enc_packet->dhcpv6_link_address,
+                      relay->link_address, sizeof(relay->link_address));
+               memcpy(&enc_packet->dhcpv6_peer_address,
+                      relay->peer_address, sizeof(relay->peer_address));
+
+               if (!parse_option_buffer(enc_packet->options,
+                                        relay->options,
+                                        enc_opt_data.len - relaylen,
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       goto exit;
+               }
+       } else if ((msg_type == DHCPV6_DHCPV4_QUERY) ||
+                  (msg_type == DHCPV6_DHCPV4_RESPONSE)) {
+#ifdef DHCP4o6
+               if (!dhcpv4_over_dhcpv6 ||
+                   (msg_type == DHCPV6_DHCPV4_RESPONSE)) {
+                       log_error("dhcpv6_relay_forw: "
+                                 "unsupported %s message type.",
+                                 dhcpv6_type_names[msg_type]);
+                       goto exit;
+               }
+               forw_dhcpv4_query(packet);
                goto exit;
-       }
+#else /* DHCP4o6 */
+               log_error("dhcpv6_relay_forw: unsupported %s message type.",
+                         dhcpv6_type_names[msg_type]);
+               goto exit;
+#endif /* DHCP4o6 */
+       } else {
+               int msglen = (int)(offsetof(struct dhcpv6_packet, options));
+               msg = (struct dhcpv6_packet *)enc_opt_data.data;
+               enc_packet->dhcpv6_msg_type = msg->msg_type;
 
-       /*
-        * Put our status code into the reply packet.
-        */
-       len = store_options6(reply_data+(*reply_ofs)+16,
-                            reply_len-(*reply_ofs)-16,
-                            host_opt_state, packet,
-                            required_opts_STATUS_CODE, NULL);
+               /* message-specific data */
+               memcpy(enc_packet->dhcpv6_transaction_id,
+                      msg->transaction_id,
+                      sizeof(enc_packet->dhcpv6_transaction_id));
 
-       /*
-        * Store the non-encapsulated option data for this 
-        * IA_NA into our reply packet. Defined in RFC 3315, 
-        * section 22.4.  
-        */
-       /* option number */
-       putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
-       /* option length */
-       putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
-       /* IA_NA, copied from the client */
-       memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
-       /* t1 and t2, odd that we need them, but here it is */
-       putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
-       putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+               if (!parse_option_buffer(enc_packet->options,
+                                        msg->options,
+                                        enc_opt_data.len - msglen,
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       goto exit;
+               }
+       }
 
        /*
-        * Get ready for next IA_NA.
+        * This is recursive. It is possible to exceed maximum packet size.
+        * XXX: This will cause the packet send to fail.
         */
-       *reply_ofs += (len + 16);
-
-exit:
-       option_state_dereference(&host_opt_state, MDL);
-}
-
-static void
-iterate_over_ia_na(struct data_string *reply_ret, 
-                  struct packet *packet,
-                  const struct data_string *client_id,
-                  const struct data_string *server_id,
-                  const char *packet_type,
-                  void (*ia_na_match)(),
-                  void (*ia_na_nomatch)())
-{
-       struct option_state *opt_state;
-       struct host_decl *packet_host;
-       struct option_cache *ia;
-       struct option_cache *oc;
-       /* cli_enc_... variables come from the IA_NA/IA_TA options */
-       struct data_string cli_enc_opt_data;
-       struct option_state *cli_enc_opt_state;
-       struct host_decl *host;
-       struct option_state *host_opt_state;
-       struct data_string iaaddr;
-       struct data_string fixed_addr;
-       int iaaddr_is_found;
-       char reply_data[65536];
-       struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
-       int reply_ofs = (int)((char *)reply->options - (char *)reply);
-       char status_msg[32];
-       struct iaaddr *lease;
-       struct ia_na *existing_ia_na;
-       int i;
-       struct data_string key;
-       u_int32_t iaid;
+       build_dhcpv6_reply(&enc_reply, enc_packet);
 
        /*
-        * Initialize to empty values, in case we have to exit early.
-        */
-       opt_state = NULL;
-       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
-       cli_enc_opt_state = NULL;
-       memset(&iaaddr, 0, sizeof(iaaddr));
-       memset(&fixed_addr, 0, sizeof(fixed_addr));
-       host_opt_state = NULL;
-       lease = NULL;
-
-       /* 
-        * Find the host record that matches from the packet, if any.
+        * If we got no encapsulated data, then it is discarded, and
+        * our reply-forw is also discarded.
         */
-       packet_host = NULL;
-       if (!find_hosts_by_uid(&packet_host, 
-                              client_id->data, client_id->len, MDL)) {
-               packet_host = NULL;
-               /* 
-                * Note: In general, we don't expect a client to provide
-                *       enough information to match by option for these
-                *       types of messages, but if we don't have a UID
-                *       match we can check anyway.
-                */
-               if (!find_hosts_by_option(&packet_host, 
-                                         packet, packet->options, MDL)) {
-                       packet_host = NULL;
-               }
+       if (enc_reply.data == NULL) {
+               goto exit;
        }
 
-       /* 
-        * Set our reply information.
+       /*
+        * Now we can use the reply_data buffer.
+        * Packet header stuff all comes from the forward message.
         */
-       reply->msg_type = DHCPV6_REPLY;
-       memcpy(reply->transaction_id, packet->dhcpv6_transaction_id, 
-              sizeof(reply->transaction_id));
+       reply = (struct dhcpv6_relay_packet *)reply_data;
+       reply->msg_type = DHCPV6_RELAY_REPL;
+       reply->hop_count = packet->dhcpv6_hop_count;
+       memcpy(reply->link_address, &packet->dhcpv6_link_address,
+              sizeof(reply->link_address));
+       memcpy(reply->peer_address, &packet->dhcpv6_peer_address,
+              sizeof(reply->peer_address));
+       reply_ofs = (int)(offsetof(struct dhcpv6_relay_packet, options));
 
        /*
-        * Build our option state for reply.
+        * Get the reply option state.
         */
        opt_state = NULL;
        if (!option_state_allocate(&opt_state, MDL)) {
-               log_error("iterate_over_ia_na: no memory for option_state.");
+               log_error("dhcpv6_relay_forw: no memory for option state.");
                goto exit;
        }
-       execute_statements_in_scope(NULL, packet, NULL, NULL, 
-                                   packet->options, opt_state, 
-                                   &global_scope, root_group, NULL);
 
-       /* 
-        * RFC 3315, section 18.2.7 tells us which options to include.
+       /*
+        * Append the interface-id if present.
         */
-       oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
-       if (oc == NULL) {
-               if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, 
-                                       (unsigned char *)server_duid.data, 
-                                       server_duid.len, D6O_SERVERID, 0)) {
-                       log_error("iterate_over_ia_na: "
-                                 "error saving server identifier.");
+       oc = lookup_option(&dhcpv6_universe, packet->options,
+                          D6O_INTERFACE_ID);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&a_opt, packet,
+                                          NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("dhcpv6_relay_forw: error evaluating "
+                                 "Interface ID.");
+                       goto exit;
+               }
+               if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                                       (unsigned char *)a_opt.data,
+                                       a_opt.len,
+                                       D6O_INTERFACE_ID, 0)) {
+                       log_error("dhcpv6_relay_forw: error saving "
+                                 "Interface ID.");
                        goto exit;
                }
+               data_string_forget(&a_opt, MDL);
        }
 
-       if (!save_option_buffer(&dhcpv6_universe, opt_state, 
-                               client_id->buffer, 
-                               (unsigned char *)client_id->data,
-                               client_id->len, 
-                               D6O_CLIENTID, 0)) {
-               log_error("iterate_over_ia_na: "
-                         "error saving client identifier.");
-               goto exit;
-       }
+#if defined(RELAY_PORT)
+       /*
+        * Append the relay_source_port option if present.
+        */
+       oc = lookup_option(&dhcpv6_universe, packet->options,
+                          D6O_RELAY_SOURCE_PORT);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&a_opt, packet,
+                                          NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("dhcpv6_relay_forw: error evaluating "
+                                 "Relay Source Port.");
+                       goto exit;
+               }
+               if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                                       (unsigned char *)a_opt.data,
+                                       a_opt.len,
+                                       D6O_RELAY_SOURCE_PORT, 0)) {
+                       log_error("dhcpv6_relay_forw: error saving "
+                                 "Relay Source Port.");
+                       goto exit;
+               }
+               data_string_forget(&a_opt, MDL);
 
-       snprintf(status_msg, sizeof(status_msg), "%s received.", packet_type);
-       if (!set_status_code(STATUS_Success, status_msg, opt_state)) {
-               goto exit;
+               packet->relay_source_port = ISC_TRUE;
        }
+#endif
 
-       /* 
-        * Add our options that are not associated with any IA_NA or IA_TA. 
+       /*
+        * Append our encapsulated stuff for caller.
         */
-       reply_ofs += store_options6(reply_data+reply_ofs,
-                                   sizeof(reply_data)-reply_ofs, 
-                                   opt_state, packet,
-                                   required_opts, NULL);
+       if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                               (unsigned char *)enc_reply.data,
+                               enc_reply.len,
+                               D6O_RELAY_MSG, 0)) {
+               log_error("dhcpv6_relay_forw: error saving Relay MSG.");
+               goto exit;
+       }
 
        /*
-        * Loop through the IA_NA reported by the client, and deal with
-        * addresses reported as already in use.
+        * Get the ERO if any.
         */
-       for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
-            ia != NULL; ia = ia->next) {
-               iaaddr_is_found = 0;
-
-               if (!get_encapsulated_IA_state(&cli_enc_opt_state,
-                                              &cli_enc_opt_data,
-                                              packet, ia, IA_NA_OFFSET)) {
-                       goto exit;
-               }
-
-               iaid = getULong(cli_enc_opt_data.data);
-
-               /* 
-                * XXX: It is possible that we can get multiple addresses
-                *      sent by the client. We don't send multiple 
-                *      addresses, so this indicates a client error. 
-                *      We should check for multiple IAADDR options, log
-                *      if found, and set as an error.
-                */
-               oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, 
-                                  D6O_IAADDR);
-               if (oc == NULL) {
-                       /* no address given for this IA, ignore */
-                       option_state_dereference(&cli_enc_opt_state, MDL);
-                       data_string_forget(&cli_enc_opt_data, MDL);
-                       continue;
-               }
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ERO);
+       if (oc != NULL) {
+               unsigned req;
+               int i;
 
-               memset(&iaaddr, 0, sizeof(iaaddr));
-               if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL, 
+               if (!evaluate_option_cache(&packet_ero, packet,
+                                          NULL, NULL,
                                           packet->options, NULL,
-                                          &global_scope, oc, MDL)) {
-                       log_error("iterate_over_ia_na: "
-                                 "error evaluating IAADDR.");
+                                          &global_scope, oc, MDL) ||
+                       (packet_ero.len & 1)) {
+                       log_error("dhcpv6_relay_forw: error evaluating ERO.");
                        goto exit;
                }
 
-               /* 
-                * Now we need to figure out which host record matches
-                * this IA_NA and IAADDR.
-                *
-                * XXX: We don't currently track IA_NA separately, but
-                *      we will need to do this!
-                */
-               host = NULL;
-               if (!find_hosts_by_option(&host, packet, 
-                                         cli_enc_opt_state, MDL)) { 
-                       if (packet_host != NULL) {
-                               host = packet_host;
-                       } else {
-                               host = NULL;
-                       }
-               }
-               while (host != NULL) {
-                       if (host->fixed_addr != NULL) {
-                               if (!evaluate_option_cache(&fixed_addr, NULL, 
-                                                          NULL, NULL, NULL, 
-                                                          NULL, &global_scope,
-                                                          host->fixed_addr, 
-                                                          MDL)) {
-                                       log_error("iterate_over_ia_na: error "
-                                                 "evaluating host address.");
-                                       goto exit;
-                               }
-                               if ((iaaddr.len >= 16) &&
-                                   !memcmp(fixed_addr.data, iaaddr.data, 16)) {
-                                       data_string_forget(&fixed_addr, MDL);
-                                       break;
-                               }
-                               data_string_forget(&fixed_addr, MDL);
-                       }
-                       host = host->n_ipaddr;
-               }
-
-               if ((host == NULL) && (iaaddr.len >= IAADDR_OFFSET)) {
-                       /*
-                        * Find existing IA_NA.
-                        */
-                       if (ia_make_key(&key, iaid, 
-                                       (char *)client_id->data,
-                                       client_id->len, 
-                                       MDL) != ISC_R_SUCCESS) {
-                               log_fatal("iterate_over_ia_na: no memory for "
-                                         "key.");
+               /* Decode and apply the ERO. */
+               for (i = 0; i < packet_ero.len; i += 2) {
+                       req = getUShort(packet_ero.data + i);
+                       /* Already in the reply? */
+                       oc = lookup_option(&dhcpv6_universe, opt_state, req);
+                       if (oc != NULL)
+                               continue;
+                       /* Get it from the packet if present. */
+                       oc = lookup_option(&dhcpv6_universe,
+                                          packet->options,
+                                          req);
+                       if (oc == NULL)
+                               continue;
+                       if (!evaluate_option_cache(&a_opt, packet,
+                                                  NULL, NULL,
+                                                  packet->options, NULL,
+                                                  &global_scope, oc, MDL)) {
+                               log_error("dhcpv6_relay_forw: error "
+                                         "evaluating option %u.", req);
+                               goto exit;
                        }
-
-                       existing_ia_na = NULL;
-                       if (ia_na_hash_lookup(&existing_ia_na, ia_na_active, 
-                                             (unsigned char *)key.data, 
-                                             key.len, MDL)) {
-                               /* 
-                                * Make sure this address is in the IA_NA.
-                                */
-                               for (i=0; i<existing_ia_na->num_iaaddr; i++) {
-                                       struct iaaddr *tmp;
-                                       struct in6_addr *in6_addr;
-
-                                       tmp = existing_ia_na->iaaddr[i];
-                                       in6_addr = &tmp->addr;
-                                       if (memcmp(in6_addr, 
-                                                  iaaddr.data, 16) == 0) {
-                                               iaaddr_reference(&lease,
-                                                                tmp, MDL);
-                                               break;
-                                       }
-                               }
+                       if (!save_option_buffer(&dhcpv6_universe,
+                                               opt_state,
+                                               NULL,
+                                               (unsigned char *)a_opt.data,
+                                               a_opt.len,
+                                               req,
+                                               0)) {
+                               log_error("dhcpv6_relay_forw: error saving "
+                                         "option %u.", req);
+                               goto exit;
                        }
-
-                       data_string_forget(&key, MDL);
-               }
-
-               if ((host != NULL) || (lease != NULL)) {
-                       ia_na_match(client_id, &iaaddr, lease);
-               } else {
-                       ia_na_nomatch(client_id, &iaaddr, 
-                                     (u_int32_t *)cli_enc_opt_data.data, 
-                                     packet, reply_data, &reply_ofs, 
-                                     sizeof(reply_data));
+                       data_string_forget(&a_opt, MDL);
                }
-
-               if (lease != NULL) {
-                       iaaddr_dereference(&lease, MDL);
-               }
-
-               data_string_forget(&iaaddr, MDL);
-               option_state_dereference(&cli_enc_opt_state, MDL);
-               data_string_forget(&cli_enc_opt_data, MDL);
        }
 
-       /* 
+       reply_ofs += store_options6(reply_data + reply_ofs,
+                                   sizeof(reply_data) - reply_ofs,
+                                   opt_state, packet,
+                                   required_opts_agent, &packet_ero);
+
+       /*
         * Return our reply to the caller.
         */
        reply_ret->len = reply_ofs;
        reply_ret->buffer = NULL;
-       if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
+       if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) {
                log_fatal("No memory to store reply.");
        }
        reply_ret->data = reply_ret->buffer->data;
-       memcpy(reply_ret->buffer->data, reply, reply_ofs);
+       memcpy(reply_ret->buffer->data, reply_data, reply_ofs);
 
 exit:
-       if (lease != NULL) {
-               iaaddr_dereference(&lease, MDL);
-       }
-       if (host_opt_state != NULL) {
-               option_state_dereference(&host_opt_state, MDL);
-       }
-       if (fixed_addr.buffer != NULL) {
-               data_string_forget(&fixed_addr, MDL);
+       if (opt_state != NULL)
+               option_state_dereference(&opt_state, MDL);
+       if (a_opt.data != NULL) {
+               data_string_forget(&a_opt, MDL);
        }
-       if (iaaddr.buffer != NULL) {
-               data_string_forget(&iaaddr, MDL);
+       if (packet_ero.data != NULL) {
+               data_string_forget(&packet_ero, MDL);
        }
-       if (cli_enc_opt_state != NULL) {
-               option_state_dereference(&cli_enc_opt_state, MDL);
+       if (enc_reply.data != NULL) {
+               data_string_forget(&enc_reply, MDL);
        }
-       if (cli_enc_opt_data.buffer != NULL) {
-               data_string_forget(&cli_enc_opt_data, MDL);
+       if (enc_opt_data.data != NULL) {
+               data_string_forget(&enc_opt_data, MDL);
        }
-       if (opt_state != NULL) {
-               option_state_dereference(&opt_state, MDL);
+       if (enc_packet != NULL) {
+               packet_dereference(&enc_packet, MDL);
        }
 }
 
-/*
- * Decline means a client has detected that something else is using an
- * address we gave it.
+#ifdef DHCP4o6
+/* \brief Internal processing of a relayed DHCPv4-query
+ *  (DHCPv4 server side)
  *
- * Since we're only dealing with fixed leases for now, there's not
- * much we can do, other that log the occurrence.
- * 
- * When we start issuing addresses from pools, then we will have to
- * record our declined addresses and issue another. In general with
- * IPv6 there is no worry about DoS by clients exhausting space, but
- * we still need to be aware of this possibility.
+ * Code copied from \ref dhcpv6_relay_forw() which itself is
+ * from \ref do_packet6().
+ *
+ * \param reply_ret pointer to the response
+ * \param packet the query
  */
-
-/* TODO: reject unicast messages, unless we set unicast option */
-/* TODO: IA_TA */
 static void
-dhcpv6_decline(struct data_string *reply, struct packet *packet) {
-       struct data_string client_id;
-       struct data_string server_id;
+dhcp4o6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
+       struct option_cache *oc;
+       struct data_string enc_opt_data;
+       struct packet *enc_packet;
+       unsigned char msg_type;
+       const struct dhcpv6_relay_packet *relay;
+       const struct dhcpv4_over_dhcpv6_packet *msg;
+       struct data_string enc_reply;
+       char link_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+       char peer_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+       struct data_string a_opt, packet_ero;
+       struct option_state *opt_state;
+       static char reply_data[65536];
+       struct dhcpv6_relay_packet *reply;
+       int reply_ofs;
 
-       /* 
-        * Validate our input.
+       /*
+        * Initialize variables for early exit.
         */
-       if (!valid_client_resp(packet, &client_id, &server_id)) {
-               return;
-       }
+       opt_state = NULL;
+       memset(&a_opt, 0, sizeof(a_opt));
+       memset(&packet_ero, 0, sizeof(packet_ero));
+       memset(&enc_reply, 0, sizeof(enc_reply));
+       memset(&enc_opt_data, 0, sizeof(enc_opt_data));
+       enc_packet = NULL;
 
        /*
-        * And operate on each IA_NA in this packet.
+        * Get our encapsulated relay message.
         */
-       iterate_over_ia_na(reply, packet, &client_id, &server_id, "Decline", 
-                          ia_na_match_decline, ia_na_nomatch_decline);
-}
-
-static void
-ia_na_match_release(const struct data_string *client_id,
-                   const struct data_string *iaaddr,
-                   struct iaaddr *lease)
-{
-       char tmp_addr[INET6_ADDRSTRLEN];
-
-       log_info("Client %s releases address %s",
-                print_hex_1(client_id->len, client_id->data, 60),
-                inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
-       if (lease != NULL) {
-               release_lease6(lease->ipv6_pool, lease);
-               write_ia(lease->ia_na);
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG);
+       if (oc == NULL) {
+               inet_ntop(AF_INET6, &packet->dhcpv6_link_address,
+                         link_addr, sizeof(link_addr));
+               inet_ntop(AF_INET6, &packet->dhcpv6_peer_address,
+                         peer_addr, sizeof(peer_addr));
+               log_info("Relay-forward from %s with link address=%s and "
+                        "peer address=%s missing Relay Message option.",
+                         piaddr(packet->client_addr), link_addr, peer_addr);
+               goto exit;
        }
-}
 
-static void
-ia_na_nomatch_release(const struct data_string *client_id,
-                     const struct data_string *iaaddr,
-                     u_int32_t *ia_na_id,
-                     struct packet *packet,
-                     char *reply_data,
-                     int *reply_ofs,
-                     int reply_len)
-{
-       char tmp_addr[INET6_ADDRSTRLEN];
-       struct option_state *host_opt_state;
-       int len;
+       if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL,
+                                  NULL, NULL, &global_scope, oc, MDL)) {
+               log_error("dhcp4o6_relay_forw: error evaluating "
+                         "relayed message.");
+               goto exit;
+       }
 
-       log_info("Client %s releases address %s, which is not leased to it.",
-                print_hex_1(client_id->len, client_id->data, 60),
-                inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
+       if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) {
+               log_error("dhcp4o6_relay_forw: "
+                         "encapsulated packet too short.");
+               goto exit;
+       }
 
        /*
-        * Create state for this IA_NA.
+        * Build a packet structure from this encapsulated packet.
         */
-       host_opt_state = NULL;
-       if (!option_state_allocate(&host_opt_state, MDL)) {
-               log_error("ia_na_nomatch_release: out of memory "
-                         "allocating option_state.");
+       if (!packet_allocate(&enc_packet, MDL)) {
+               log_error("dhcp4o6_relay_forw: "
+                         "no memory for encapsulated packet.");
                goto exit;
        }
 
-       if (!set_status_code(STATUS_NoBinding, 
-                            "Release for non-leased address.",
-                            host_opt_state)) {
+       if (!option_state_allocate(&enc_packet->options, MDL)) {
+               log_error("dhcp4o6_relay_forw: "
+                         "no memory for encapsulated packet's options.");
+               goto exit;
+       }
+
+       enc_packet->client_port = packet->client_port;
+       enc_packet->client_addr = packet->client_addr;
+       interface_reference(&enc_packet->interface, packet->interface, MDL);
+       enc_packet->dhcpv6_container_packet = packet;
+
+       msg_type = enc_opt_data.data[0];
+       if ((msg_type == DHCPV6_RELAY_FORW) ||
+           (msg_type == DHCPV6_RELAY_REPL)) {
+               int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options));
+               relay = (struct dhcpv6_relay_packet *)enc_opt_data.data;
+               enc_packet->dhcpv6_msg_type = relay->msg_type;
+
+               /* relay-specific data */
+               enc_packet->dhcpv6_hop_count = relay->hop_count;
+               memcpy(&enc_packet->dhcpv6_link_address,
+                      relay->link_address, sizeof(relay->link_address));
+               memcpy(&enc_packet->dhcpv6_peer_address,
+                      relay->peer_address, sizeof(relay->peer_address));
+
+               if (!parse_option_buffer(enc_packet->options,
+                                        relay->options,
+                                        enc_opt_data.len - relaylen,
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       goto exit;
+               }
+       } else if ((msg_type == DHCPV6_DHCPV4_QUERY) ||
+                  (msg_type == DHCPV6_DHCPV4_RESPONSE)) {
+               int msglen =
+                   (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options));
+               msg = (struct dhcpv4_over_dhcpv6_packet *)enc_opt_data.data;
+               enc_packet->dhcpv6_msg_type = msg->msg_type;
+
+               /* message-specific data */
+               memcpy(enc_packet->dhcp4o6_flags,
+                      msg->flags,
+                      sizeof(enc_packet->dhcp4o6_flags));
+
+               if (!parse_option_buffer(enc_packet->options,
+                                        msg->options,
+                                        enc_opt_data.len - msglen,
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       goto exit;
+               }
+       } else {
+               log_error("dhcp4o6_relay_forw: unexpected message of type %d.",
+                         (int)msg_type);
                goto exit;
        }
 
        /*
-        * Insure we have enough space
+        * This is recursive. It is possible to exceed maximum packet size.
+        * XXX: This will cause the packet send to fail.
         */
-       if (reply_len < (*reply_ofs + 16)) {
-               log_error("ia_na_nomatch_release: "
-                         "out of space for reply packet.");
+       build_dhcpv6_reply(&enc_reply, enc_packet);
+
+       /*
+        * If we got no encapsulated data, then it is discarded, and
+        * our reply-forw is also discarded.
+        */
+       if (enc_reply.data == NULL) {
                goto exit;
        }
 
        /*
-        * Put our status code into the reply packet.
+        * Now we can use the reply_data buffer.
+        * Packet header stuff all comes from the forward message.
         */
-       len = store_options6(reply_data+(*reply_ofs)+16,
-                            reply_len-(*reply_ofs)-16,
-                            host_opt_state, packet,
-                            required_opts_STATUS_CODE, NULL);
+       reply = (struct dhcpv6_relay_packet *)reply_data;
+       reply->msg_type = DHCPV6_RELAY_REPL;
+       reply->hop_count = packet->dhcpv6_hop_count;
+       memcpy(reply->link_address, &packet->dhcpv6_link_address,
+              sizeof(reply->link_address));
+       memcpy(reply->peer_address, &packet->dhcpv6_peer_address,
+              sizeof(reply->peer_address));
+       reply_ofs = (int)(offsetof(struct dhcpv6_relay_packet, options));
 
        /*
-        * Store the non-encapsulated option data for this 
-        * IA_NA into our reply packet. Defined in RFC 3315, 
-        * section 22.4.  
+        * Get the reply option state.
         */
-       /* option number */
-       putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
-       /* option length */
-       putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
-       /* IA_NA, copied from the client */
-       memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
-       /* t1 and t2, odd that we need them, but here it is */
-       putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
-       putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+       if (!option_state_allocate(&opt_state, MDL)) {
+               log_error("dhcp4o6_relay_forw: no memory for option state.");
+               goto exit;
+       }
 
        /*
-        * Get ready for next IA_NA.
+        * Append the interface-id if present.
         */
-       *reply_ofs += (len + 16);
-
-exit:
-       option_state_dereference(&host_opt_state, MDL);
-}
+       oc = lookup_option(&dhcpv6_universe, packet->options,
+                          D6O_INTERFACE_ID);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&a_opt, packet,
+                                          NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("dhcp4o6_relay_forw: error evaluating "
+                                 "Interface ID.");
+                       goto exit;
+               }
+               if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                                       (unsigned char *)a_opt.data,
+                                       a_opt.len,
+                                       D6O_INTERFACE_ID, 0)) {
+                       log_error("dhcp4o6_relay_forw: error saving "
+                                 "Interface ID.");
+                       goto exit;
+               }
+               data_string_forget(&a_opt, MDL);
+       }
 
-/*
- * Release means a client is done with the addresses.
- */
+#if defined(RELAY_PORT)
+       /*
+        * Append the relay_source_port option if present.
+        */
+       oc = lookup_option(&dhcpv6_universe, packet->options,
+                          D6O_RELAY_SOURCE_PORT);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&a_opt, packet,
+                                          NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("dhcpv4o6_relay_forw: error evaluating "
+                                 "Relay Source Port.");
+                       goto exit;
+               }
+               if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                                       (unsigned char *)a_opt.data,
+                                       a_opt.len,
+                                       D6O_RELAY_SOURCE_PORT, 0)) {
+                       log_error("dhcpv4o6_relay_forw: error saving "
+                                 "Relay Source Port.");
+                       goto exit;
+               }
+               data_string_forget(&a_opt, MDL);
 
-/* TODO: reject unicast messages, unless we set unicast option */
-static void
-dhcpv6_release(struct data_string *reply, struct packet *packet) {
-       struct data_string client_id;
-       struct data_string server_id;
+               packet->relay_source_port = ISC_TRUE;
+       }
+#endif
 
-       /* 
-        * Validate our input.
+       /*
+        * Append our encapsulated stuff for caller.
         */
-       if (!valid_client_resp(packet, &client_id, &server_id)) {
-               return;
+       if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                               (unsigned char *)enc_reply.data,
+                               enc_reply.len,
+                               D6O_RELAY_MSG, 0)) {
+               log_error("dhcp4o6_relay_forw: error saving Relay MSG.");
+               goto exit;
        }
 
        /*
-        * And operate on each IA_NA in this packet.
+        * Get the ERO if any.
         */
-       iterate_over_ia_na(reply, packet, &client_id, &server_id, "Release", 
-                          ia_na_match_release, ia_na_nomatch_release);
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ERO);
+       if (oc != NULL) {
+               unsigned req;
+               int i;
 
-       data_string_forget(&server_id, MDL);
-       data_string_forget(&client_id, MDL);
-}
+               if (!evaluate_option_cache(&packet_ero, packet,
+                                          NULL, NULL,
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL) ||
+                       (packet_ero.len & 1)) {
+                       log_error("dhcp4o6_relay_forw: error evaluating ERO.");
+                       goto exit;
+               }
 
-/*
- * Information-Request is used by clients who have obtained an address
- * from other means, but want configuration information from the server.
- */
+               /* Decode and apply the ERO. */
+               for (i = 0; i < packet_ero.len; i += 2) {
+                       req = getUShort(packet_ero.data + i);
+                       /* Already in the reply? */
+                       oc = lookup_option(&dhcpv6_universe, opt_state, req);
+                       if (oc != NULL)
+                               continue;
+                       /* Get it from the packet if present. */
+                       oc = lookup_option(&dhcpv6_universe,
+                                          packet->options,
+                                          req);
+                       if (oc == NULL)
+                               continue;
+                       if (!evaluate_option_cache(&a_opt, packet,
+                                                  NULL, NULL,
+                                                  packet->options, NULL,
+                                                  &global_scope, oc, MDL)) {
+                               log_error("dhcp4o6_relay_forw: error "
+                                         "evaluating option %u.", req);
+                               goto exit;
+                       }
+                       if (!save_option_buffer(&dhcpv6_universe,
+                                               opt_state,
+                                               NULL,
+                                               (unsigned char *)a_opt.data,
+                                               a_opt.len,
+                                               req,
+                                               0)) {
+                               log_error("dhcp4o6_relay_forw: error saving "
+                                         "option %u.", req);
+                               goto exit;
+                       }
+                       data_string_forget(&a_opt, MDL);
+               }
+       }
 
-static void
-dhcpv6_information_request(struct data_string *reply, struct packet *packet) {
-       struct data_string client_id;
-       struct data_string server_id;
+       reply_ofs += store_options6(reply_data + reply_ofs,
+                                   sizeof(reply_data) - reply_ofs,
+                                   opt_state, packet,
+                                   required_opts_agent, &packet_ero);
 
        /*
-        * Validate our input.
+        * Return our reply to the caller.
         */
-       if (!valid_client_info_req(packet, &server_id)) {
-               return;
+       reply_ret->len = reply_ofs;
+       reply_ret->buffer = NULL;
+       if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) {
+               log_fatal("No memory to store reply.");
        }
+       reply_ret->data = reply_ret->buffer->data;
+       memcpy(reply_ret->buffer->data, reply_data, reply_ofs);
 
-       /*
-        * Get our client ID, if there is one.
-        */
-       memset(&client_id, 0, sizeof(client_id));
-       if (get_client_id(packet, &client_id) != ISC_R_SUCCESS) {
-               data_string_forget(&client_id, MDL);
+exit:
+       if (opt_state != NULL)
+               option_state_dereference(&opt_state, MDL);
+       if (a_opt.data != NULL) {
+               data_string_forget(&a_opt, MDL);
+       }
+       if (packet_ero.data != NULL) {
+               data_string_forget(&packet_ero, MDL);
+       }
+       if (enc_reply.data != NULL) {
+               data_string_forget(&enc_reply, MDL);
+       }
+       if (enc_opt_data.data != NULL) {
+               data_string_forget(&enc_opt_data, MDL);
        }
-
-       /*
-        * Use the lease_to_client() function. This will work fine, 
-        * because the valid_client_info_req() insures that we 
-        * don't have any IA_NA or IA_TA that would cause us to
-        * allocate addresses to the client.
-        */
-       lease_to_client(reply, packet, &client_id,
-                       server_id.data != NULL ? &server_id : NULL);
-
-       /*
-        * Cleanup.
-        */
-       if (client_id.data != NULL) {
-               data_string_forget(&client_id, MDL);
+       if (enc_packet != NULL) {
+               packet_dereference(&enc_packet, MDL);
        }
-       data_string_forget(&server_id, MDL);
 }
 
-/* 
- * The Relay-forw message is sent by relays. It typically contains a
- * single option, which encapsulates an entire packet.
+/*
+ * \brief Internal processing of a DHCPv4-query
+ *  (DHCPv4 server function)
  *
- * We need to build an encapsulated reply.
+ * Code copied from \ref do_packet().
+ *
+ * \param reply_ret pointer to the response
+ * \param packet the query
  */
-
-/* XXX: this is very, very similar to do_packet6(), and should probably
-       be combined in a clever way */
 static void
-dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
-       struct dhcpv6_relay_packet reply;
+dhcp4o6_dhcpv4_query(struct data_string *reply_ret, struct packet *packet) {
        struct option_cache *oc;
        struct data_string enc_opt_data;
        struct packet *enc_packet;
-       unsigned char msg_type;
-       const struct dhcpv6_packet *msg;
-       const struct dhcpv6_relay_packet *relay;
-       struct data_string enc_reply;
-       char link_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
-       char peer_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
-       struct data_string interface_id;
+       struct data_string enc_response;
+       struct option_state *opt_state;
+       static char response_data[65536];
+       struct dhcpv4_over_dhcpv6_packet *response;
+       int response_ofs;
 
-       /* 
+       /*
         * Initialize variables for early exit.
         */
+       opt_state = NULL;
+       memset(&enc_response, 0, sizeof(enc_response));
        memset(&enc_opt_data, 0, sizeof(enc_opt_data));
        enc_packet = NULL;
-       memset(&enc_reply, 0, sizeof(enc_reply));
-       memset(&interface_id, 0, sizeof(interface_id));
 
        /*
         * Get our encapsulated relay message.
         */
-       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG);
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_DHCPV4_MSG);
        if (oc == NULL) {
-               inet_ntop(AF_INET6, &packet->dhcpv6_link_address,
-                         link_addr, sizeof(link_addr));
-               inet_ntop(AF_INET6, &packet->dhcpv6_peer_address,
-                         peer_addr, sizeof(peer_addr));
-               log_info("Relay-forward from %s with link address=%s and "
-                        "peer address=%s missing Relay Message option.",
-                         piaddr(packet->client_addr), link_addr, peer_addr);
+               log_info("DHCPv4-query from %s missing DHCPv4 Message option.",
+                        piaddr(packet->client_addr));
                goto exit;
        }
 
-       memset(&enc_opt_data, 0, sizeof(enc_opt_data));
-       if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, 
+       if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL,
                                   NULL, NULL, &global_scope, oc, MDL)) {
-               log_error("dhcpv6_forw_relay: error evaluating "
-                         "relayed message.");
+               log_error("dhcp4o6_dhcpv4_query: error evaluating "
+                         "DHCPv4 message.");
                goto exit;
        }
 
-       if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) {
-               log_error("dhcpv6_forw_relay: encapsulated packet too short.");
+       if (enc_opt_data.len < DHCP_FIXED_NON_UDP) {
+               log_error("dhcp4o6_dhcpv4_query: DHCPv4 packet too short.");
                goto exit;
        }
 
        /*
         * Build a packet structure from this encapsulated packet.
-        */
-       enc_packet = NULL;
+         */
        if (!packet_allocate(&enc_packet, MDL)) {
-               log_error("dhcpv6_forw_relay: "
+               log_error("dhcp4o6_dhcpv4_query: "
                          "no memory for encapsulated packet.");
                goto exit;
        }
 
-       if (!option_state_allocate(&enc_packet->options, MDL)) {
-               log_error("dhcpv6_forw_relay: "
-                         "no memory for encapsulated packet's options.");
-               goto exit;
-       }
-
+       enc_packet->raw = (struct dhcp_packet *)enc_opt_data.data;
+       enc_packet->packet_length = enc_opt_data.len;
+       enc_packet->dhcp4o6_response = &enc_response;
        enc_packet->client_port = packet->client_port;
        enc_packet->client_addr = packet->client_addr;
+       interface_reference(&enc_packet->interface, packet->interface, MDL);
        enc_packet->dhcpv6_container_packet = packet;
+       if (packet->dhcp4o6_flags[0] & DHCP4O6_QUERY_UNICAST)
+               enc_packet->unicast = 1;
 
-       msg_type = enc_opt_data.data[0];
-       if ((msg_type == DHCPV6_RELAY_FORW) ||
-           (msg_type == DHCPV6_RELAY_REPL)) {
-               relay = (struct dhcpv6_relay_packet *)enc_opt_data.data;
-               enc_packet->dhcpv6_msg_type = relay->msg_type;
+       if (enc_packet->raw->hlen > sizeof(enc_packet->raw->chaddr)) {
+               log_info("dhcp4o6_dhcpv4_query: "
+                        "discarding packet with bogus hlen.");
+               goto exit;
+       }
 
-               /* relay-specific data */
-               enc_packet->dhcpv6_hop_count = relay->hop_count;
-               memcpy(&enc_packet->dhcpv6_link_address,
-                      relay->link_address, sizeof(relay->link_address));
-               memcpy(&enc_packet->dhcpv6_peer_address,
-                      relay->peer_address, sizeof(relay->peer_address));
+       /* Allocate packet->options now so it is non-null for all packets */
+       if (!option_state_allocate (&enc_packet->options, MDL)) {
+               log_error("dhcp4o6_dhcpv4_query: no memory for options.");
+               goto exit;
+       }
 
-               if (!parse_option_buffer(enc_packet->options,
-                                        relay->options, 
-                                        enc_opt_data.len-sizeof(*relay),
-                                        &dhcpv6_universe)) {
-                       /* no logging here, as parse_option_buffer() logs all
-                          cases where it fails */
+       /* If there's an option buffer, try to parse it. */
+       if (enc_packet->packet_length >= DHCP_FIXED_NON_UDP + 4) {
+               struct option_cache *op;
+               if (!parse_options(enc_packet)) {
+                       if (enc_packet->options)
+                               option_state_dereference
+                                       (&enc_packet->options, MDL);
+                       packet_dereference (&enc_packet, MDL);
                        goto exit;
                }
-       } else {
-               msg = (struct dhcpv6_packet *)enc_opt_data.data;
-               enc_packet->dhcpv6_msg_type = msg->msg_type;
-
-               /* message-specific data */
-               memcpy(enc_packet->dhcpv6_transaction_id,
-                      msg->transaction_id,
-                      sizeof(enc_packet->dhcpv6_transaction_id));
 
-               if (!parse_option_buffer(enc_packet->options,
-                                        msg->options, 
-                                        enc_opt_data.len-sizeof(*msg),
-                                        &dhcpv6_universe)) {
-                       /* no logging here, as parse_option_buffer() logs all
-                          cases where it fails */
-                       goto exit;
+               if (enc_packet->options_valid &&
+                   (op = lookup_option(&dhcp_universe,
+                                       enc_packet->options,
+                                       DHO_DHCP_MESSAGE_TYPE))) {
+                       struct data_string dp;
+                       memset(&dp, 0, sizeof dp);
+                       evaluate_option_cache(&dp, enc_packet, NULL, NULL,
+                                             enc_packet->options, NULL,
+                                             NULL, op, MDL);
+                       if (dp.len > 0)
+                               enc_packet->packet_type = dp.data[0];
+                       else
+                               enc_packet->packet_type = 0;
+                       data_string_forget(&dp, MDL);
                }
        }
 
-       /*
-        * This is recursive. It is possible to exceed maximum packet size.
-        * XXX: This will cause the packet send to fail.
-        */
-       build_dhcpv6_reply(&enc_reply, enc_packet);
+       if (validate_packet(enc_packet) != 0) {
+               if (enc_packet->packet_type)
+                       dhcp(enc_packet);
+               else
+                       bootp(enc_packet);
+       }
+
+       /* If the caller kept the packet, they'll have upped the refcnt. */
+       packet_dereference(&enc_packet, MDL);
 
        /*
-        * If we got no encapsulated data, then it is discarded, and
-        * our reply-forw is also discarded.
+        * If we got no response data, then it is discarded, and
+        * our DHCPv4-response is also discarded.
         */
-       if (enc_reply.data == NULL) {
+       if (enc_response.data == NULL) {
                goto exit;
        }
 
        /*
-        * Append the interface-id if present
+        * Now we can use the response_data buffer.
         */
-       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_INTERFACE_ID);
-       if (oc != NULL) {
-               memset(&interface_id, 0, sizeof(interface_id));
-               if (!evaluate_option_cache(&interface_id, NULL, NULL, NULL, 
-                                          NULL, NULL, &global_scope, 
-                                          oc, MDL)) {
-                       log_error("dhcpv6_forw_relay: error evaluating "
-                                 "Interface ID.");
-                       goto exit;
-               }
-       }
+       response = (struct dhcpv4_over_dhcpv6_packet *)response_data;
+       response->msg_type = DHCPV6_DHCPV4_RESPONSE;
+       response->flags[0] = response->flags[1] = response->flags[2] = 0;
+       response_ofs =
+               (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options));
 
        /*
-        * Packet header stuff all comes from the forward message.
-        */
-       reply.msg_type = DHCPV6_RELAY_REPL;
-       reply.hop_count = packet->dhcpv6_hop_count;
-       memcpy(reply.link_address, &packet->dhcpv6_link_address,
-              sizeof(reply.link_address));
-       memcpy(reply.peer_address, &packet->dhcpv6_peer_address,
-              sizeof(reply.peer_address));
-
-       /* 
-        * Copy our encapsulated stuff for caller.
+        * Get the response option state.
         */
-       reply_ret->len = sizeof(reply) + 4 + enc_reply.len;
-       if (interface_id.data != NULL) {
-               reply_ret->len += 4 + interface_id.len;
+       if (!option_state_allocate(&opt_state, MDL)) {
+               log_error("dhcp4o6_dhcpv4_query: no memory for option state.");
+               goto exit;
        }
-       /* 
-        * XXX: We should not allow this to happen, perhaps by letting
-        *      build_dhcp_reply() know our space remaining.
+
+       /*
+        * Append our encapsulated stuff for caller.
         */
-       if (reply_ret->len >= 65536) {
-               log_error("dhcpv6_forw_relay: RELAY-REPL too big (%d bytes)",
-                         reply_ret->len);
+       if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+                               (unsigned char *)enc_response.data,
+                               enc_response.len,
+                               D6O_DHCPV4_MSG, 0)) {
+               log_error("dhcp4o6_dhcpv4_query: error saving DHCPv4 MSG.");
                goto exit;
        }
+
+       response_ofs += store_options6(response_data + response_ofs,
+                                      sizeof(response_data) - response_ofs,
+                                      opt_state, packet,
+                                      required_opts_4o6, NULL);
+
+       /*
+         * Return our response to the caller.
+        */
+       reply_ret->len = response_ofs;
        reply_ret->buffer = NULL;
        if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) {
-               log_fatal("No memory to store reply.");
+               log_fatal("dhcp4o6_dhcpv4_query: no memory to store reply.");
        }
        reply_ret->data = reply_ret->buffer->data;
-       memcpy(reply_ret->buffer->data, &reply, sizeof(reply));
-       putShort(reply_ret->buffer->data+sizeof(reply), D6O_RELAY_MSG);
-       putShort(reply_ret->buffer->data+sizeof(reply)+2, enc_reply.len);
-       memcpy(reply_ret->buffer->data+sizeof(reply)+4, 
-              enc_reply.data, enc_reply.len);
-       if (interface_id.data != NULL) {
-               putShort(reply_ret->buffer->data+sizeof(reply)+4+enc_reply.len,
-                        D6O_INTERFACE_ID);
-               putShort(reply_ret->buffer->data+sizeof(reply)+6+enc_reply.len,
-                        interface_id.len);
-               memcpy(reply_ret->buffer->data+sizeof(reply)+8+enc_reply.len,
-                      interface_id.data, interface_id.len);
-       }
+       memcpy(reply_ret->buffer->data, response_data, response_ofs);
 
 exit:
-       if (interface_id.data != NULL) {
-               data_string_forget(&interface_id, MDL);
-       }
-       if (enc_reply.data != NULL) {
-               data_string_forget(&enc_reply, MDL);
+       if (opt_state != NULL)
+               option_state_dereference(&opt_state, MDL);
+       if (enc_response.data != NULL) {
+               data_string_forget(&enc_response, MDL);
        }
        if (enc_opt_data.data != NULL) {
                data_string_forget(&enc_opt_data, MDL);
@@ -3472,64 +7503,164 @@ exit:
        }
 }
 
+/*
+ * \brief Forward a DHCPv4-query message to the DHCPv4 side
+ *  (DHCPv6 server function)
+ *
+ * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-query message
+ *
+ * \brief packet the DHCPv6 DHCPv4-query message
+ */
+static void forw_dhcpv4_query(struct packet *packet) {
+       struct data_string ds;
+       struct udp_data4o6 udp_data;
+       unsigned len;
+       int cc;
+
+       /* Get the initial message. */
+       while (packet->dhcpv6_container_packet != NULL)
+               packet = packet->dhcpv6_container_packet;
+
+       /* Check the initial message. */
+       if ((packet->raw == NULL) ||
+           (packet->client_addr.len != 16) ||
+           (packet->interface == NULL)) {
+               log_error("forw_dhcpv4_query: can't find initial message.");
+               return;
+       }
+
+       /* Get a buffer. */
+       len = packet->packet_length + 36;
+       memset(&ds, 0, sizeof(ds));
+       if (!buffer_allocate(&ds.buffer, len, MDL)) {
+               log_error("forw_dhcpv4_query: "
+                         "no memory for encapsulating packet.");
+               return;
+       }
+       ds.data = ds.buffer->data;
+       ds.len = len;
+
+       /* Fill the buffer. */
+       strncpy((char *)ds.buffer->data, packet->interface->name, 16);
+       memcpy(ds.buffer->data + 16,
+              packet->client_addr.iabuf, 16);
+       memset(&udp_data, 0, sizeof(udp_data));
+       udp_data.src_port = packet->client_port;
+       memcpy(ds.buffer->data + 32, &udp_data, 4);
+       memcpy(ds.buffer->data + 36,
+              (unsigned char *)packet->raw,
+              packet->packet_length);
+
+       /* Forward to the DHCPv4 server. */
+       cc = send(dhcp4o6_fd, ds.data, ds.len, 0);
+       if (cc < 0)
+               log_error("forw_dhcpv4_query: send(): %m");
+       data_string_forget(&ds, MDL);
+}
+#endif
+
 static void
 dhcpv6_discard(struct packet *packet) {
        /* INSIST(packet->msg_type > 0); */
        /* INSIST(packet->msg_type < dhcpv6_type_name_max); */
 
-       log_debug("Discarding %s from %s; message type not handled by server", 
+       log_debug("Discarding %s from %s; message type not handled by server",
                  dhcpv6_type_names[packet->dhcpv6_msg_type],
                  piaddr(packet->client_addr));
 }
 
-static void 
+static void
 build_dhcpv6_reply(struct data_string *reply, struct packet *packet) {
        memset(reply, 0, sizeof(*reply));
+
+       /* I would like to classify the client once here, but
+        * as I don't want to classify all of the incoming packets
+        * I need to do it before handling specific types.
+        * We don't need to classify if we are tossing the packet
+        * or if it is a relay - the classification step will get
+        * done when we process the inner client packet.
+        */
+
        switch (packet->dhcpv6_msg_type) {
                case DHCPV6_SOLICIT:
+                       classify_client(packet);
                        dhcpv6_solicit(reply, packet);
                        break;
                case DHCPV6_ADVERTISE:
                        dhcpv6_discard(packet);
                        break;
                case DHCPV6_REQUEST:
+                       classify_client(packet);
                        dhcpv6_request(reply, packet);
                        break;
                case DHCPV6_CONFIRM:
+                       classify_client(packet);
                        dhcpv6_confirm(reply, packet);
                        break;
                case DHCPV6_RENEW:
+                       classify_client(packet);
                        dhcpv6_renew(reply, packet);
                        break;
                case DHCPV6_REBIND:
+                       classify_client(packet);
                        dhcpv6_rebind(reply, packet);
                        break;
                case DHCPV6_REPLY:
                        dhcpv6_discard(packet);
                        break;
                case DHCPV6_RELEASE:
+                       classify_client(packet);
                        dhcpv6_release(reply, packet);
                        break;
                case DHCPV6_DECLINE:
+                       classify_client(packet);
                        dhcpv6_decline(reply, packet);
                        break;
                case DHCPV6_RECONFIGURE:
                        dhcpv6_discard(packet);
                        break;
                case DHCPV6_INFORMATION_REQUEST:
+                       classify_client(packet);
                        dhcpv6_information_request(reply, packet);
                        break;
                case DHCPV6_RELAY_FORW:
+#ifdef DHCP4o6
+                       if (dhcpv4_over_dhcpv6 && (local_family == AF_INET))
+                               dhcp4o6_relay_forw(reply, packet);
+                       else
+#endif /* DHCP4o6 */
                        dhcpv6_relay_forw(reply, packet);
                        break;
                case DHCPV6_RELAY_REPL:
                        dhcpv6_discard(packet);
                        break;
+               case DHCPV6_LEASEQUERY:
+                       classify_client(packet);
+                       dhcpv6_leasequery(reply, packet);
+                       break;
+               case DHCPV6_LEASEQUERY_REPLY:
+                       dhcpv6_discard(packet);
+                       break;
+               case DHCPV6_DHCPV4_QUERY:
+#ifdef DHCP4o6
+                       if (dhcpv4_over_dhcpv6) {
+                               if (local_family == AF_INET6) {
+                                       forw_dhcpv4_query(packet);
+                               } else {
+                                       dhcp4o6_dhcpv4_query(reply, packet);
+                               }
+                       } else
+#endif /* DHCP4o6 */
+                       dhcpv6_discard(packet);
+                       break;
+               case DHCPV6_DHCPV4_RESPONSE:
+                       dhcpv6_discard(packet);
+                       break;
                default:
-                       /* XXX: would be nice if we had "notice" level, 
+                       /* XXX: would be nice if we had "notice" level,
                                as syslog, for this */
                        log_info("Discarding unknown DHCPv6 message type %d "
-                                "from %s", packet->dhcpv6_msg_type, 
+                                "from %s", packet->dhcpv6_msg_type,
                                 piaddr(packet->client_addr));
        }
 }
@@ -3549,33 +7680,34 @@ log_packet_in(const struct packet *packet) {
                                     piaddr(packet->client_addr),
                                     ntohs(packet->client_port));
        } else {
-               data_string_sprintfa(&s, 
+               data_string_sprintfa(&s,
                                     "Unknown message type %d from %s port %d",
                                     packet->dhcpv6_msg_type,
                                     piaddr(packet->client_addr),
                                     ntohs(packet->client_port));
        }
-       if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) || 
+       if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) ||
            (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
                addr = &packet->dhcpv6_link_address;
-               data_string_sprintfa(&s, ", link address %s", 
-                                    inet_ntop(AF_INET6, addr, 
+               data_string_sprintfa(&s, ", link address %s",
+                                    inet_ntop(AF_INET6, addr,
                                               tmp_addr, sizeof(tmp_addr)));
                addr = &packet->dhcpv6_peer_address;
-               data_string_sprintfa(&s, ", peer address %s", 
-                                    inet_ntop(AF_INET6, addr, 
+               data_string_sprintfa(&s, ", peer address %s",
+                                    inet_ntop(AF_INET6, addr,
                                               tmp_addr, sizeof(tmp_addr)));
-       } else {
+       } else if ((packet->dhcpv6_msg_type != DHCPV6_DHCPV4_QUERY) &&
+                  (packet->dhcpv6_msg_type != DHCPV6_DHCPV4_RESPONSE)) {
                tid = 0;
                memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3);
                data_string_sprintfa(&s, ", transaction ID 0x%06X", tid);
 
 /*
-               oc = lookup_option(&dhcpv6_universe, packet->options, 
+               oc = lookup_option(&dhcpv6_universe, packet->options,
                                   D6O_CLIENTID);
                if (oc != NULL) {
                        memset(&tmp_ds, 0, sizeof(tmp_ds_));
-                       if (!evaluate_option_cache(&tmp_ds, packet, NULL, NULL, 
+                       if (!evaluate_option_cache(&tmp_ds, packet, NULL, NULL,
                                                   packet->options, NULL,
                                                   &global_scope, oc, MDL)) {
                                log_error("Error evaluating Client Identifier");
@@ -3585,61 +7717,290 @@ log_packet_in(const struct packet *packet) {
                                data_string_forget(&tmp_ds, MDL);
                        }
                }
-*/
-
+*/
+
+       }
+       log_info("%s", s.data);
+
+       data_string_forget(&s, MDL);
+}
+
+void
+dhcpv6(struct packet *packet) {
+       struct data_string reply;
+       struct sockaddr_in6 to_addr;
+       int send_ret;
+
+       /*
+        * Log a message that we received this packet.
+        */
+       log_packet_in(packet);
+
+       /*
+        * Build our reply packet.
+        */
+       build_dhcpv6_reply(&reply, packet);
+
+       if (reply.data != NULL) {
+               /*
+                * Send our reply, if we have one.
+                */
+               memset(&to_addr, 0, sizeof(to_addr));
+               to_addr.sin6_family = AF_INET6;
+               if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) ||
+                   (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
+                       to_addr.sin6_port = local_port;
+               } else {
+                       to_addr.sin6_port = remote_port;
+               }
+
+#if defined (REPLY_TO_SOURCE_PORT)
+               /*
+                * This appears to have been included for testing so we would
+                * not need a root client, but was accidently left in the
+                * final code.  We continue to include it in case
+                * some users have come to rely upon it, but leave
+                * it off by default as it's a bad idea.
+                */
+               to_addr.sin6_port = packet->client_port;
+#endif
+
+#if defined(RELAY_PORT)
+               /*
+                * Check relay source port.
+                */
+               if (packet->relay_source_port) {
+                       to_addr.sin6_port = packet->client_port;
+               }
+#endif
+
+               memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf,
+                      sizeof(to_addr.sin6_addr));
+
+               log_info("Sending %s to %s port %d",
+                        dhcpv6_type_names[reply.data[0]],
+                        piaddr(packet->client_addr),
+                        ntohs(to_addr.sin6_port));
+
+               send_ret = send_packet6(packet->interface,
+                                       reply.data, reply.len, &to_addr);
+               if (send_ret != reply.len) {
+                       log_error("dhcpv6: send_packet6() sent %d of %d bytes",
+                                 send_ret, reply.len);
+               }
+               data_string_forget(&reply, MDL);
+       }
+}
+
+#ifdef DHCP4o6
+/*
+ * \brief Receive a DHCPv4-query message from the DHCPv6 side
+ *  (DHCPv4 server function)
+ *
+ * Receive a message with a DHCPv4-query inside from the DHCPv6 server.
+ * (code copied from \ref do_packet6() \ref and dhcpv6())
+ *
+ * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-query message
+ *
+ * \param raw the DHCPv6 DHCPv4-query message raw content
+ */
+static void recv_dhcpv4_query(struct data_string *raw) {
+       struct interface_info *ip;
+       char name[16 + 1];
+       struct iaddr iaddr;
+       struct packet *packet;
+       unsigned char msg_type;
+       const struct dhcpv6_relay_packet *relay;
+       const struct dhcpv4_over_dhcpv6_packet *msg;
+       struct data_string reply;
+       struct data_string ds;
+       struct udp_data4o6 udp_data;
+       unsigned len;
+       int cc;
+
+       memset(name, 0, sizeof(name));
+       memcpy(name, raw->data, 16);
+       for (ip = interfaces; ip != NULL; ip = ip->next) {
+               if (!strcmp(name, ip->name))
+                       break;
+       }
+       if (ip == NULL) {
+               log_error("recv_dhcpv4_query: can't find interface %s.",
+                         name);
+               return;
+       }
+
+       iaddr.len = 16;
+       memcpy(iaddr.iabuf, raw->data + 16, 16);
+
+       memset(&udp_data, 0, sizeof(udp_data));
+       memcpy(&udp_data, raw->data + 32, 4);
+
+       /*
+        * From do_packet6().
+        */
+
+       if (!packet6_len_okay((char *)raw->data + 36, raw->len - 36)) {
+               log_error("recv_dhcpv4_query: "
+                        "short packet from %s, len %d, dropped",
+                        piaddr(iaddr), raw->len - 36);
+               return;
+       }
+
+       /*
+        * Build a packet structure.
+        */
+       packet = NULL;
+       if (!packet_allocate(&packet, MDL)) {
+               log_error("recv_dhcpv4_query: no memory for packet.");
+               return;
+       }
+
+       if (!option_state_allocate(&packet->options, MDL)) {
+               log_error("recv_dhcpv4_query: no memory for options.");
+               packet_dereference(&packet, MDL);
+               return;
+       }
+
+       packet->raw = (struct dhcp_packet *)(raw->data + 36);
+       packet->packet_length = raw->len - 36;
+       packet->client_port = udp_data.src_port;
+       packet->client_addr = iaddr;
+       interface_reference(&packet->interface, ip, MDL);
+
+       msg_type = raw->data[36];
+       if ((msg_type == DHCPV6_RELAY_FORW) ||
+           (msg_type == DHCPV6_RELAY_REPL)) {
+               int relaylen =
+                   (int)(offsetof(struct dhcpv6_relay_packet, options));
+               relay = (const struct dhcpv6_relay_packet *)(raw->data + 36);
+               packet->dhcpv6_msg_type = relay->msg_type;
+
+               /* relay-specific data */
+               packet->dhcpv6_hop_count = relay->hop_count;
+               memcpy(&packet->dhcpv6_link_address,
+                      relay->link_address, sizeof(relay->link_address));
+               memcpy(&packet->dhcpv6_peer_address,
+                      relay->peer_address, sizeof(relay->peer_address));
+
+               if (!parse_option_buffer(packet->options,
+                                        relay->options,
+                                        raw->len - 36 - relaylen,
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       packet_dereference(&packet, MDL);
+                       return;
+               }
+       } else if ((msg_type == DHCPV6_DHCPV4_QUERY) ||
+                  (msg_type == DHCPV6_DHCPV4_RESPONSE)) {
+               int msglen =
+                   (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options));
+               msg = (struct dhcpv4_over_dhcpv6_packet *)(raw->data + 36);
+               packet->dhcpv6_msg_type = msg->msg_type;
+
+               /* message-specific data */
+               memcpy(packet->dhcp4o6_flags, msg->flags,
+                      sizeof(packet->dhcp4o6_flags));
+
+               if (!parse_option_buffer(packet->options,
+                                        msg->options,
+                                        raw->len - 36 - msglen,
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       packet_dereference(&packet, MDL);
+                       return;
+               }
+       } else {
+               log_error("recv_dhcpv4_query: unexpected message of type %d.",
+                         (int)msg_type);
+               packet_dereference(&packet, MDL);
+               return;
        }
-       log_info("%s", s.data);
-
-       data_string_forget(&s, MDL);
-}
 
-void 
-dhcpv6(struct packet *packet) {
-       struct data_string reply;
-       struct sockaddr_in6 to_addr;
-       int send_ret;
+       /*
+        * From dhcpv6().
+        */
 
-       /* 
+       /*
         * Log a message that we received this packet.
         */
-       log_packet_in(packet); 
+       /* log_packet_in(packet); */
+       memset(&ds, 0, sizeof(ds));
+       if (packet->dhcpv6_msg_type < dhcpv6_type_name_max) {
+               data_string_sprintfa(&ds, "%s message from %s",
+                                    dhcpv6_type_names[packet->dhcpv6_msg_type],
+                                    piaddr(packet->client_addr));
+       } else {
+               data_string_sprintfa(&ds,
+                                    "Unknown message type %d from %s",
+                                    packet->dhcpv6_msg_type,
+                                    piaddr(packet->client_addr));
+       }
+       if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) ||
+           (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
+               char tmp_addr[INET6_ADDRSTRLEN];
+               const void *addr;
+
+               addr = &packet->dhcpv6_link_address;
+               data_string_sprintfa(&ds, ", link address %s",
+                                    inet_ntop(AF_INET6, addr,
+                                              tmp_addr, sizeof(tmp_addr)));
+               addr = &packet->dhcpv6_peer_address;
+               data_string_sprintfa(&ds, ", peer address %s",
+                                    inet_ntop(AF_INET6, addr,
+                                              tmp_addr, sizeof(tmp_addr)));
+       } else if ((packet->dhcpv6_msg_type != DHCPV6_DHCPV4_QUERY) &&
+                  (packet->dhcpv6_msg_type != DHCPV6_DHCPV4_RESPONSE)) {
+               u_int32_t tid = 0;
+
+               memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3);
+               data_string_sprintfa(&ds, ", transaction ID 0x%06X", tid);
+       }
+       log_info("%s", ds.data);
+       data_string_forget(&ds, MDL);
 
        /*
         * Build our reply packet.
-        */
+         */
        build_dhcpv6_reply(&reply, packet);
 
-       if (reply.data != NULL) {
-               /* 
-                * Send our reply, if we have one.
-                */
-               memset(&to_addr, 0, sizeof(to_addr));
-               to_addr.sin6_family = AF_INET6;
-               if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) || 
-                   (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
-                       to_addr.sin6_port = local_port;
-               } else {
-                       to_addr.sin6_port = remote_port;
-               }
-/* For testing, we reply to the sending port, so we don't need a root client */
-               to_addr.sin6_port = packet->client_port;
-               memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf, 
-                      sizeof(to_addr.sin6_addr));
-
-               log_info("Sending %s to %s port %d", 
-                        dhcpv6_type_names[reply.data[0]],
-                        piaddr(packet->client_addr),
-                        ntohs(to_addr.sin6_port));
+       if (reply.data == NULL) {
+               packet_dereference(&packet, MDL);
+               return;
+       }
 
-               send_ret = send_packet6(packet->interface, 
-                                       reply.data, reply.len, &to_addr);
-               if (send_ret != reply.len) {
-                       log_error("dhcpv6: send_packet6() sent %d of %d bytes",
-                                 send_ret, reply.len);
-               }
-               data_string_forget(&reply, MDL);
+       /*
+        * Forward the response.
+        */
+       len = reply.len + 36;
+       memset(&ds, 0, sizeof(ds));
+       if (!buffer_allocate(&ds.buffer, len, MDL)) {
+               log_error("recv_dhcpv4_query: no memory.");
+               packet_dereference(&packet, MDL);
+               return;
        }
+       ds.data = ds.buffer->data;
+       ds.len = len;
+
+       memcpy(ds.buffer->data, name, 16);
+       memcpy(ds.buffer->data + 16, iaddr.iabuf, 16);
+       udp_data.rsp_opt_exist = packet->relay_source_port ? 1 : 0;
+       memcpy(ds.buffer->data + 32, &udp_data, 4);
+       memcpy(ds.buffer->data + 36, reply.data, reply.len);
+
+       /*
+        * Now we can release the packet.
+        */
+       packet_dereference(&packet, MDL);
+
+       cc = send(dhcp4o6_fd, ds.data, ds.len, 0);
+       if (cc < 0)
+               log_error("recv_dhcpv4_query: send(): %m");
+       data_string_forget(&ds, MDL);
 }
+#endif /* DHCP4o6 */
 
 static void
 seek_shared_host(struct host_decl **hp, struct shared_network *shared) {
@@ -3647,8 +8008,9 @@ seek_shared_host(struct host_decl **hp, struct shared_network *shared) {
        struct host_decl *seek, *hold = NULL;
 
        /*
-        * Seek forward through fixed addresses for the right broadcast
-        * domain.
+        * Seek forward through fixed addresses for the right link.
+        *
+        * Note: how to do this for fixed prefixes???
         */
        host_reference(&hold, *hp, MDL);
        host_dereference(hp, MDL);
@@ -3706,5 +8068,550 @@ fixed_matches_shared(struct host_decl *host, struct shared_network *shared) {
        return matched;
 }
 
-#endif /* DHCPv6 */
+/*!
+ *
+ * \brief Constructs a REPLY with status of UseMulticast to a given packet
+ *
+ * Per RFC 3315 Secs 18.2.1,3,6 & 7, when a server rejects a client's
+ * unicast-sent packet, the response must only contain the client id,
+ * server id, and a status code option of 5 (UseMulticast).  This function
+ * constructs such a packet and returns it as a data_string.
+ *
+ * \param reply_ret = data_string which will receive the newly constructed
+ * reply
+ * \param packet = client request which is being rejected
+ * \param client_id = data_string which contains the client id
+ * \param server_id = data_string which which contains the server id
+ *
+ */
+void
+unicast_reject(struct data_string *reply_ret,
+            struct packet *packet,
+            const struct data_string *client_id,
+            const struct data_string *server_id)
+{
+       struct reply_state reply;
+       memset(&reply, 0x0, sizeof(struct reply_state));
+
+       /* Locate the client. */
+       if (shared_network_from_packet6(&reply.shared, packet)
+               != ISC_R_SUCCESS) {
+               log_error("unicast_reject: could not locate client.");
+               return;
+       }
+
+       /* Initialize the reply. */
+       packet_reference(&reply.packet, packet, MDL);
+       data_string_copy(&reply.client_id, client_id, MDL);
+
+       if (start_reply(packet, client_id, server_id, &reply.opt_state,
+                        &reply.buf.reply)) {
+               /* Set the UseMulticast status code. */
+               if (!set_status_code(STATUS_UseMulticast,
+                                    "Unicast not allowed by server.",
+                                    reply.opt_state)) {
+                       log_error("unicast_reject: Unable to set status code.");
+               } else {
+                       /* Set write cursor to just past the reply header. */
+                       reply.cursor = REPLY_OPTIONS_INDEX;
+                       reply.cursor += store_options6(((char *)reply.buf.data
+                                                       + reply.cursor),
+                                                      (sizeof(reply.buf)
+                                                       - reply.cursor),
+                                                      reply.opt_state,
+                                                      reply.packet,
+                                                      unicast_reject_opts,
+                                                      NULL);
+
+                       /* Return our reply to the caller. */
+                       reply_ret->len = reply.cursor;
+                       reply_ret->buffer = NULL;
+                       if (!buffer_allocate(&reply_ret->buffer,
+                                            reply.cursor, MDL)) {
+                               log_fatal("unicast_reject:"
+                                         "No memory to store Reply.");
+                       }
+
+                       memcpy(reply_ret->buffer->data, reply.buf.data,
+                              reply.cursor);
+                       reply_ret->data = reply_ret->buffer->data;
+               }
+
+       }
+
+       /* Cleanup. */
+       if (reply.shared != NULL)
+               shared_network_dereference(&reply.shared, MDL);
+       if (reply.opt_state != NULL)
+               option_state_dereference(&reply.opt_state, MDL);
+       if (reply.packet != NULL)
+               packet_dereference(&reply.packet, MDL);
+       if (reply.client_id.data != NULL)
+               data_string_forget(&reply.client_id, MDL);
+}
+
+/*!
+ *
+ * \brief Checks if the dhcp6.unicast option has been defined
+ *
+ * Scans the option space for the presence of the dhcp6.unicast option. The
+ * function attempts to map the inbound packet to a shared network first
+ * by an ip address specified via an D6O_IA_XX option and if that fails then
+ * by the packet's source information (e.g. relay link, link, or interace).
+ * Once the packet is mapped to a shared network, the function executes all
+ * statements from the network's group outward into a local option cache.
+ * The option cache is then scanned for the presence of unicast option.  If
+ * the packet cannot be mapped to a shared network, the function returns
+ * ISC_FALSE.
+ * \param packet inbound packet from the client
+ *
+ * \return ISC_TRUE if the dhcp6.unicast option is defined, false otherwise.
+ *
+ */
+isc_boolean_t
+is_unicast_option_defined(struct packet *packet) {
+        isc_boolean_t is_defined = ISC_FALSE;
+       struct option_state *opt_state = NULL;
+       struct option_cache *oc = NULL;
+       struct shared_network *shared = NULL;
+
+       if (!option_state_allocate(&opt_state, MDL)) {
+               log_fatal("is_unicast_option_defined:"
+                         "No memory for option state.");
+       }
+
+       /* We try to map the packet to a network first by an IA_XX value.
+        * If that fails, we try by packet source. */
+       if (((shared_network_from_requested_addr(&shared, packet)
+            != ISC_R_SUCCESS) &&
+           (shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS))
+           || (shared == NULL)) {
+               /* @todo what would this really mean? I think wrong network
+                * logic will catch it */
+               log_error("is_unicast_option_defined:"
+                         "cannot attribute packet to a network.");
+               return (ISC_FALSE);
+       }
+
+       /* Now that we've mapped it to a network, execute statments to that
+        * scope, looking for the unicast option. We don't care about the
+        * value of the option, only whether or not it is defined. */
+       execute_statements_in_scope(NULL, NULL, NULL, NULL, NULL, opt_state,
+                                   &global_scope, shared->group, NULL, NULL);
+
+       oc = lookup_option(&dhcpv6_universe, opt_state, D6O_UNICAST);
+       is_defined = (oc != NULL ? ISC_TRUE : ISC_FALSE);
+       log_debug("is_unicast_option_defined: option found : %d", is_defined);
+
+       if (shared != NULL) {
+               shared_network_dereference(&shared, MDL);
+       }
+
+       if (opt_state != NULL) {
+               option_state_dereference(&opt_state, MDL);
+       }
+
+       return (is_defined);
+}
+
+/*!
+ *
+ * \brief Maps a packet to a shared network based on the requested IP address
+ *
+ * The function attempts to find a subnet that matches the first requested IP
+ * address contained within the given packet.  Note that it looks first for
+ * D6O_IA_NAs, then D6O_IA_PDs and lastly D6O_IA_TAs.  If a matching network is
+ * found, a reference to it is returned in the parameter, shared.
+ *
+ * \param shared shared_network pointer which will receive the matching network
+ * \param packet inbound packet from the client
+ *
+ * \return ISC_R_SUCCESS if the packet can be mapped to a shared_network.
+ *
+ */
+static isc_result_t
+shared_network_from_requested_addr (struct shared_network **shared,
+                                   struct packet* packet) {
+       struct iaddr iaddr;
+       struct subnet* subnet = NULL;
+       isc_result_t status = ISC_R_FAILURE;
+
+       /* Try to match first IA_ address or prefix we find to a subnet. In
+        * theory all  IA_ values in a given request are supposed to be in the
+        * same subnet so we only need to try one right? */
+       if ((get_first_ia_addr_val(packet, D6O_IA_NA, &iaddr) != ISC_R_SUCCESS)
+            && (get_first_ia_addr_val(packet, D6O_IA_PD, &iaddr)
+                != ISC_R_SUCCESS)
+            && (get_first_ia_addr_val(packet, D6O_IA_TA, &iaddr)
+                != ISC_R_SUCCESS))  {
+               /* we found nothing to match against */
+               log_debug("share_network_from_request_addr: nothing to match");
+               return (ISC_R_FAILURE);
+       }
+
+       if (!find_subnet(&subnet, iaddr, MDL)) {
+               log_debug("shared_network_from_requested_addr:"
+                         "No subnet found for addr %s.", piaddr(iaddr));
+       } else {
+               status = shared_network_reference(shared,
+                                                  subnet->shared_network, MDL);
+               subnet_dereference(&subnet, MDL);
+               log_debug("shared_network_from_requested_addr:"
+                         " found shared network %s for address %s.",
+                         ((*shared)->name ? (*shared)->name : "unnamed"),
+                         piaddr(iaddr));
+               return (status);
+       }
+
+       return (ISC_R_FAILURE);
+}
+
+/*!
+ *
+ * \brief Retrieves the first IP address from a given packet of a given type
+ *
+ * Search a packet for options of a given type (D6O_IA_AN, D6O_IA_PD, or
+ * D6O_IA_TA) for the first non-blank IA_XX value and return its IP address
+ * component.
+ *
+ * \param packet packet received from the client
+ * \param addr_type the address option type (D6O_IA_NA , D6O_IA_PD, or
+ * D6O_IP_TA) to look for within the packet.
+ * \param iaddr pointer to the iaddr structure which will receive the extracted
+ * address.
+ *
+ * \return ISC_R_SUCCESS if an address was succesfully extracted, ISC_R_FALURE
+ * otherwise.
+ *
+ */
+static isc_result_t
+get_first_ia_addr_val (struct packet* packet, int addr_type,
+                      struct iaddr* iaddr)  {
+        struct option_cache *ia;
+        struct option_cache *oc = NULL;
+        struct data_string cli_enc_opt_data;
+        struct option_state *cli_enc_opt_state;
+       int addr_opt_offset;
+       int addr_opt;
+       int addr_opt_data_len;
+       int ip_addr_offset;
+
+       isc_result_t status = ISC_R_FAILURE;
+       memset(iaddr, 0, sizeof(struct iaddr));
+
+       /* Set up address type specifics */
+       switch (addr_type) {
+       case D6O_IA_NA:
+               addr_opt_offset = IA_NA_OFFSET;
+               addr_opt = D6O_IAADDR;
+               addr_opt_data_len = 24;
+               ip_addr_offset = 0;
+               break;
+       case D6O_IA_TA:
+               addr_opt_offset = IA_TA_OFFSET;
+               addr_opt = D6O_IAADDR;
+               addr_opt_data_len = 24;
+               ip_addr_offset = 0;
+               break;
+       case D6O_IA_PD:
+               addr_opt_offset = IA_PD_OFFSET;
+               addr_opt = D6O_IAPREFIX;
+               addr_opt_data_len = 25;
+               ip_addr_offset = 9;
+               break;
+       default:
+               /* shouldn't be here */
+               log_error ("get_first_ia_addr_val: invalid opt type %d",
+                          addr_type);
+               return (ISC_R_FAILURE);
+       }
+
+       /* Find the first, non-blank IA_XX value within an D6O_IA_XX option. */
+       for (ia = lookup_option(&dhcpv6_universe, packet->options, addr_type);
+             ia != NULL && oc == NULL; ia = ia->next) {
+                if (!get_encapsulated_IA_state(&cli_enc_opt_state,
+                                               &cli_enc_opt_data,
+                                               packet, ia, addr_opt_offset)) {
+                       log_debug ("get_first_ia_addr_val:"
+                                  " couldn't unroll enclosing option");
+                        return (ISC_R_FAILURE);
+                }
+
+                oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+                                   addr_opt);
+                if (oc == NULL) {
+                       /* no address given for this IA, ignore */
+                       option_state_dereference(&cli_enc_opt_state, MDL);
+                       data_string_forget(&cli_enc_opt_data, MDL);
+               }
+       }
+
+       /* If we found a non-blank IA_XX then extract its ip address. */
+       if (oc != NULL) {
+               struct data_string iaddr_str;
+
+               memset(&iaddr_str, 0, sizeof(iaddr_str));
+               if (!evaluate_option_cache(&iaddr_str, packet, NULL, NULL,
+                                         packet->options, NULL, &global_scope,
+                                         oc, MDL)) {
+                       log_error("get_first_ia_addr_val: "
+                                 "error evaluating IA_XX option.");
+               } else {
+                       if (iaddr_str.len != addr_opt_data_len) {
+                               log_error("shared_network_from_requested_addr:"
+                                         " invalid length %d, expected %d",
+                                         iaddr_str.len, addr_opt_data_len);
+                       } else {
+                               iaddr->len = 16;
+                               memcpy (iaddr->iabuf,
+                                       iaddr_str.data + ip_addr_offset, 16);
+                               status = ISC_R_SUCCESS;
+                       }
+                       data_string_forget(&iaddr_str, MDL);
+               }
+
+               option_state_dereference(&cli_enc_opt_state, MDL);
+               data_string_forget(&cli_enc_opt_data, MDL);
+       }
+
+       return (status);
+}
+
+/*
+* \brief Calculates the reply T1/T2 times and stuffs them in outbound buffer
+*
+* T1/T2 time selection is kind of weird.  We actually use DHCP * (v4) scoped
+* options, dhcp-renewal-time and dhcp-rebinding-time, as handy existing places
+* where these can be configured by an administrator.  A value of zero tells the
+* client it may choose its own value.
+*
+* When those options are not defined, the values will be set to zero unless
+* the global option, dhcpv6-set-tee-times is enabled. When this option is
+* enabled the values are calculated as recommended by RFC 3315, Section 22.4:
+*
+*      T1 will be set to 0.5 times the shortest preferred lifetime
+*      in the IA_XX option.  If the "shortest" preferred lifetime is
+*      0xFFFFFFFF,  T1 will set to 0xFFFFFFFF.
+*
+*      T2 will be set to 0.8 times the shortest preferred lifetime
+*      in the IA_XX option.  If the "shortest" preferred lifetime is
+*      0xFFFFFFFF,  T2 will set to 0xFFFFFFFF.
+*
+* Note that dhcpv6-set-tee-times is intended to be transitional and will
+* likely be removed in 4.4.0, leaving the behavior as getting the values
+* either from the configured parameters (if you want zeros, define them as
+* zeros) or by calculating them per the RFC.
+*
+* \param reply - pointer to the reply_state structure
+* \param ia_cursor - offset of the beginning of the IA_XX option within the
+* reply's outbound data buffer
+*/
+static void
+set_reply_tee_times(struct reply_state* reply, unsigned ia_cursor)
+{
+       struct option_cache *oc;
+       int set_tee_times;
+
+       /* Found out if calculated values are enabled. */
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_DHCPV6_SET_TEE_TIMES);
+       set_tee_times = (oc &&
+                        evaluate_boolean_option_cache(NULL, reply->packet,
+                                                      NULL, NULL,
+                                                      reply->packet->options,
+                                                      reply->opt_state,
+                                                      &global_scope, oc, MDL));
+
+       oc = lookup_option(&dhcp_universe, reply->opt_state,
+                          DHO_DHCP_RENEWAL_TIME);
+       if (oc != NULL) {
+               /* dhcp-renewal-time is defined, use it */
+               struct data_string data;
+               memset(&data, 0x00, sizeof(data));
+
+               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+                                          reply->packet->options,
+                                          reply->opt_state, &global_scope,
+                                          oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("Invalid renewal time.");
+                       reply->renew = 0;
+               } else {
+                       reply->renew = getULong(data.data);
+               }
+
+               if (data.data != NULL)
+                       data_string_forget(&data, MDL);
+       } else if (set_tee_times) {
+               /* Setting them is enabled so T1 is either infinite or
+                * 0.5 * the shortest preferred lifetime in the IA_XX  */
+               if (reply->min_prefer == INFINITE_TIME)
+                       reply->renew = INFINITE_TIME;
+               else
+                       reply->renew = reply->min_prefer / 2;
+       } else {
+               /* Default is to let the client choose */
+               reply->renew = 0;
+       }
+
+       putULong(reply->buf.data + ia_cursor + 8, reply->renew);
+
+       /* Now T2. */
+       oc = lookup_option(&dhcp_universe, reply->opt_state,
+                          DHO_DHCP_REBINDING_TIME);
+       if (oc != NULL) {
+               /* dhcp-rebinding-time is defined, use it */
+               struct data_string data;
+               memset(&data, 0x00, sizeof(data));
+
+               if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
+                                          reply->packet->options,
+                                          reply->opt_state, &global_scope,
+                                          oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("Invalid rebinding time.");
+                       reply->rebind = 0;
+               } else {
+                       reply->rebind = getULong(data.data);
+               }
+
+               if (data.data != NULL)
+                       data_string_forget(&data, MDL);
+       } else if (set_tee_times) {
+               /* Setting them is enabled so T2 is either infinite or
+                * 0.8 * the shortest preferred lifetime in the reply */
+               if (reply->min_prefer == INFINITE_TIME)
+                       reply->rebind = INFINITE_TIME;
+               else
+                       reply->rebind = (reply->min_prefer / 5) * 4;
+       } else {
+               /* Default is to let the client choose */
+               reply->rebind = 0;
+       }
+
+       putULong(reply->buf.data + ia_cursor + 12, reply->rebind);
+}
+
+/*
+ * Releases the iasubopts in the pre-existing IA, if they are not in
+ * the same shared-network as the new IA.
+ *
+ * returns 1 if the release was done, 0 otherwise
+ */
+int
+release_on_roam(struct reply_state* reply) {
+       struct ia_xx* old_ia = reply->old_ia;
+       struct iasubopt *lease = NULL;
+       int i;
+
+       if ((!do_release_on_roam) || old_ia == NULL
+           || old_ia->num_iasubopt <= 0) {
+               return(0);
+       }
+
+       /* If the old shared-network and new are the same, client hasn't
+       * roamed, nothing to do. We only check the first one because you
+       * cannot have iasubopts on different shared-networks within a
+       * single ia. */
+       lease = old_ia->iasubopt[0];
+       if (lease->ipv6_pool->shared_network == reply->shared) {
+               return (0);
+       }
+
+       /* Old and new are on different shared networks so the client must
+       * roamed. Release the old leases. */
+       for (i = 0;  i < old_ia->num_iasubopt; i++) {
+               lease = old_ia->iasubopt[i];
+
+               log_info("Client: %s roamed to new network,"
+                        " releasing lease: %s%s",
+                        print_hex_1(reply->client_id.len,
+                                    reply->client_id.data, 60),
+                        pin6_addr(&lease->addr), iasubopt_plen_str(lease));
 
+                release_lease6(lease->ipv6_pool, lease);
+                lease->ia->cltt = cur_time;
+                write_ia(lease->ia);
+        }
+
+       return (1);
+}
+
+/*
+ * Convenience function which returns a string (static buffer)
+ * containing either a "/" followed by the prefix length or an
+ * empty string depending on the lease type
+ */
+const char *iasubopt_plen_str(struct iasubopt *lease) {
+       static char prefix_buf[16];
+       *prefix_buf = 0;
+       if ((lease->ia) && (lease->ia->ia_type == D6O_IA_PD)) {
+               sprintf(prefix_buf, "/%-d", lease->plen);
+       }
+
+       return (prefix_buf);
+}
+
+#ifdef NSUPDATE
+/*
+ * Initiates DDNS updates for static v6 leases if configured to do so.
+ *
+ * The function, which must be called after the IA has been written to the
+ * packet, adds an iasubopt to the IA for static lease.  This is done so we
+ * have an iasubopt to pass into ddns_updates().  A reference to the IA is
+ * added to the DDNS control block to ensure it and it's iasubopt remain in
+ * scope until the update is complete.
+ *
+ */
+void ddns_update_static6(struct reply_state* reply) {
+       struct iasubopt *iasub = NULL;
+       struct binding_scope *scope = NULL;
+       struct option_cache *oc = NULL;
+
+       oc = lookup_option(&server_universe, reply->opt_state, SV_DDNS_UPDATES);
+       if ((oc != NULL) &&
+               (evaluate_boolean_option_cache(NULL, reply->packet, NULL, NULL,
+                                              reply->packet->options,
+                                               reply->opt_state, NULL,
+                                               oc, MDL) == 0)) {
+               return;
+       }
+
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_UPDATE_STATIC_LEASES);
+       if ((oc == NULL) ||
+               (evaluate_boolean_option_cache(NULL, reply->packet,
+                                                    NULL, NULL,
+                                                    reply->packet->options,
+                                                    reply->opt_state, NULL,
+                                                    oc, MDL) == 0)) {
+               return;
+       }
+
+       if (iasubopt_allocate(&iasub, MDL) != ISC_R_SUCCESS) {
+               log_fatal("No memory for iasubopt.");
+       }
+
+       if (ia_add_iasubopt(reply->ia, iasub, MDL) != ISC_R_SUCCESS) {
+               log_fatal("Could not add iasubopt.");
+       }
+
+       ia_reference(&iasub->ia, reply->ia, MDL);
+
+       memcpy(iasub->addr.s6_addr, reply->fixed.data, 16);
+       iasub->plen = 0;
+       iasub->prefer =  MAX_TIME;
+       iasub->valid =  MAX_TIME;
+       iasub->static_lease = 1;
+
+       if (!binding_scope_allocate(&scope, MDL)) {
+               log_fatal("Out of memory for binding scope.");
+       }
+
+       binding_scope_reference(&iasub->scope, scope, MDL);
+
+       ddns_updates(reply->packet, NULL, NULL, iasub, NULL, reply->opt_state);
+}
+#endif /* NSUPDATE */
+
+#endif /* DHCPv6 */