]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
- The client leasing subsystem was streamlined and corrected to account
authorDavid Hankins <dhankins@isc.org>
Wed, 24 Oct 2007 00:54:12 +0000 (00:54 +0000)
committerDavid Hankins <dhankins@isc.org>
Wed, 24 Oct 2007 00:54:12 +0000 (00:54 +0000)
  more closely for changes in client link attachment selection.
  [ISC-Bugs #17198]

RELNOTES
common/alloc.c
includes/dhcp6.h
includes/dhcpd.h
server/confpars.c
server/dhcpv6.c
server/mdb6.c

index 1b858125c616dc5c95656d57278c39c28f076255..9a81f810120f925d11c095131d0932fc670ec507 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -64,6 +64,9 @@ suggested fixes to <dhcp-users@isc.org>.
 - sendmsg()/recvmsg() control buffers are now declared in such a way to
   ensure they are correctly aligned on all (esp. 64-bit) architectures.
 
+- The client leasing subsystem was streamlined and corrected to account
+  more closely for changes in client link attachment selection.
+
                        Changes since 4.0.0a3
 
 - The DHCP server no longer requires a "ddns-update-style" statement, 
index d825de59e508f24df71db39f709690740785248a..a8a127f383f1b679b5244975580e5037790ef6db 100644 (file)
@@ -1262,11 +1262,9 @@ int binding_scope_reference (ptr, bp, file, line)
 /* Make a copy of the data in data_string, upping the buffer reference
    count if there's a buffer. */
 
-void data_string_copy (dest, src, file, line)
-       struct data_string *dest;
-       struct data_string *src;
-       const char *file;
-       int line;
+void
+data_string_copy(struct data_string *dest, const struct data_string *src,
+                const char *file, int line)
 {
        if (src -> buffer) {
                buffer_reference (&dest -> buffer, src -> buffer, file, line);
index 3de15ce3a60041656931b2757cc64a2d1b3a6427..c4ec55ae8d128d7ab96e9383f3402725172179ab 100644 (file)
@@ -107,6 +107,9 @@ extern const int dhcpv6_type_name_max;
 #define IA_NA_OFFSET 12 /* IAID, T1, T2, all 4 octets each */
 #define IA_TA_OFFSET  4 /* IAID only, 4 octets */
 
+/* Offsets into IAADDR's where Option spaces commence. */
+#define IAADDR_OFFSET 24
+
 /* 
  * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315 
  */
@@ -151,6 +154,9 @@ struct dhcpv6_packet {
        unsigned char options[0];
 };
 
+/* Offset into DHCPV6 Reply packets where Options spaces commence. */
+#define REPLY_OPTIONS_INDEX 4
+
 /* 
  * Relay packet format, defined in section 7 of RFC 3315 
  */
index 7658612d4663b131697214b5fc8b567791f67d36..d80b5fc9da84d50f284f9feefde04cbd9d7c9ed1 100644 (file)
@@ -1972,8 +1972,8 @@ int option_state_reference PROTO ((struct option_state **,
                                   struct option_state *, const char *, int));
 int option_state_dereference PROTO ((struct option_state **,
                                     const char *, int));
-void data_string_copy PROTO ((struct data_string *,
-                             struct data_string *, const char *, int));
+void data_string_copy(struct data_string *, const struct data_string *,
+                     const char *, int);
 void data_string_forget PROTO ((struct data_string *, const char *, int));
 void data_string_truncate PROTO ((struct data_string *, int));
 int executable_statement_allocate PROTO ((struct executable_statement **,
index 9aef2b8014a567426a3785be9a66dd7fb98c23c3..5b850ade6d70e72746140d3e8d0f4f6d1b006473 100644 (file)
@@ -4018,6 +4018,7 @@ parse_ia_na_declaration(struct parse *cfile) {
 
                /* add to our various structures */
                ia_na_add_iaaddr(ia_na, iaaddr, MDL);
+               ia_na_reference(&iaaddr->ia_na, ia_na, MDL);
                pool = NULL;
                if (find_ipv6_pool(&pool, &iaaddr->addr) != ISC_R_SUCCESS) {
                        inet_ntop(AF_INET6, &iaaddr->addr, 
index 327114a38228ad19cf32e45ae454d153b86ef5c5..ac6a9492e7a8b8be689e767d35ed898df1ae03ef 100644 (file)
  *       a single interface)
  */
 
+/*
+ * DHCPv6 Reply workflow assist.  A Reply packet is built by various
+ * different functions; this gives us one location where we keep state
+ * regarding a reply.
+ */
+struct reply_state {
+       /* root level persistent state */
+       struct shared_network *shared;
+       struct host_decl *host;
+       struct option_state *opt_state;
+       struct packet *packet;
+       struct data_string client_id;
+
+       /* IA level persistent state */
+       unsigned ia_count;
+       isc_boolean_t client_addressed, static_lease;
+       struct ia_na *ia_na;
+       struct ia_na *old_ia;
+       struct option_state *reply_ia;
+       struct data_string fixed;
+
+       /* IAADDR level persistent state */
+       struct iaaddr *lease;
+
+       /*
+        * "t1", "t2", preferred, and valid lifetimes records for calculating
+        * t1 and t2 (min/max).
+        */
+       u_int32_t renew, rebind, prefer, valid;
+
+       /* Client-rqeuested valid and preferred lifetimes. */
+       u_int32_t client_valid, client_prefer;
+
+       /* Chosen values to transmit for valid and preferred lifetimes. */
+       u_int32_t send_valid, send_prefer;
+
+       /* Index into the data field that has been sonsumed. */
+       unsigned cursor;
+
+       union reply_buffer {
+               unsigned char data[65536];
+               struct dhcpv6_packet reply;
+       } buf;
+};
+
 /* 
  * Prototypes local to this file.
  */
@@ -45,6 +90,25 @@ static int get_encapsulated_IA_state(struct option_state **enc_opt_state,
 static void build_dhcpv6_reply(struct data_string *, struct packet *);
 static isc_result_t shared_network_from_packet6(struct shared_network **shared,
                                                struct packet *packet);
+static void seek_shared_host(struct host_decl **hp,
+                            struct shared_network *shared);
+static isc_boolean_t fixed_matches_shared(struct host_decl *host,
+                                         struct shared_network *shared);
+static isc_result_t reply_process_ia(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_result_t reply_process_try_addr(struct reply_state *reply,
+                                          struct iaddr *addr);
+static isc_result_t find_client_address(struct reply_state *reply);
+static isc_result_t reply_process_is_addressed(struct reply_state *reply,
+                                              struct binding_scope **scope,
+                                              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);
 
 /*
  * DUID time starts 2000-01-01.
@@ -334,7 +398,7 @@ 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; " 
                                  "server identifier found "
@@ -696,14 +760,6 @@ start_reply(struct packet *packet,
        const unsigned char *server_id_data;
        int server_id_len;
 
-       reply->msg_type = DHCPV6_REPLY;
-
-       /* 
-        * Use the client's transaction identifier for the reply.
-        */
-       memcpy(reply->transaction_id, packet->dhcpv6_transaction_id, 
-              sizeof(reply->transaction_id));
-
        /*
         * Build our option state for reply.
         */
@@ -712,10 +768,54 @@ start_reply(struct packet *packet,
                log_error("start_reply: no memory for option_state.");
                return 0;
        }
-       execute_statements_in_scope(NULL, packet, NULL, NULL, 
-                                   packet->options, *opt_state, 
+       execute_statements_in_scope(NULL, packet, NULL, NULL,
+                                   packet->options, *opt_state,
                                    &global_scope, root_group, NULL);
 
+       /*
+        * A small bit of special handling for Solicit messages.
+        *
+        * We could move the logic into a flag, but for now just check
+        * explicitly.
+        */
+       if (packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
+               reply->msg_type = DHCPV6_ADVERTISE;
+
+               /*
+                * If:
+                * - this message type supports rapid commit (Solicit), and
+                * - the server is configured to supply a rapid commit, and
+                * - the client requests a rapid commit,
+                * Then we add a rapid commit option, and send Reply (instead
+                * of an Advertise).
+                */
+               oc = lookup_option(&dhcpv6_universe,
+                                  *opt_state, D6O_RAPID_COMMIT);
+               if (oc != NULL) {
+                       oc = lookup_option(&dhcpv6_universe,
+                                          packet->options, D6O_RAPID_COMMIT);
+                       if (oc != NULL) {
+                               if (!save_option_buffer(&dhcpv6_universe,
+                                                       *opt_state, NULL,
+                                                       (unsigned char *)"", 0,
+                                                       D6O_RAPID_COMMIT, 0)) {
+                                       log_error("start_reply: error saving "
+                                                 "RAPID_COMMIT option.");
+                                       return 0;
+                               }
+
+                               reply->msg_type = DHCPV6_REPLY;
+                       }
+               }
+       } else
+               reply->msg_type = DHCPV6_REPLY;
+
+       /* 
+        * Use the client's transaction identifier for the reply.
+        */
+       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.
@@ -796,7 +896,7 @@ start_reply(struct packet *packet,
 }
 
 /*
- * Try to get the IPv6 address the client asked for from the 
+ * Try to get the IPv6 address the client asked for from the
  * pool.
  *
  * addr is the result (should be a pointer to NULL on entry)
@@ -804,7 +904,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 iaaddr **addr,
                      struct ipv6_pool *pool,
                      const struct data_string *requested_addr)
 {
@@ -849,30 +949,15 @@ try_client_v6_address(struct iaaddr **addr,
  * client_id is the DUID for the client
  */
 static isc_result_t 
-pick_v6_address(struct iaaddr **addr,
-               struct ipv6_pool **pool,
-               struct packet *packet, 
-               const struct data_string *requested_iaaddr,
+pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network,
                const struct data_string *client_id)
 {
-       struct shared_network *shared_network;
        struct ipv6_pool *p;
-       isc_result_t status;
        int i;
        int start_pool;
        unsigned int attempts;
        char tmp_buf[INET6_ADDRSTRLEN];
 
-       /* First, find the shared network the client inhabits, so that an
-        * appropriate address can be selected.
-        */
-       shared_network = NULL;
-       status = shared_network_from_packet6(&shared_network, packet);
-       if (status != ISC_R_SUCCESS)
-               return status;
-       else if (shared_network == NULL)
-               return ISC_R_NOTFOUND; /* invarg?  invalid condition... */
-
        /*
         * No pools, we're done.
         */
@@ -883,31 +968,6 @@ pick_v6_address(struct iaaddr **addr,
                return ISC_R_NORESOURCES;
        }
 
-       /*
-        * If the client requested an address, try each subnet to see
-        * if the address is available in that subnet. We will try to
-        * respect the client's request if possible.
-        */
-       if ((requested_iaaddr != NULL) && (requested_iaaddr->len > 0)) {
-               for (i=0; shared_network->ipv6_pools[i] != NULL; i++) {
-                       p = shared_network->ipv6_pools[i];
-                       if (try_client_v6_address(addr, p, requested_iaaddr) 
-                                                       == ISC_R_SUCCESS) {
-                               ipv6_pool_reference(pool, p, MDL);
-                               shared_network_dereference(&shared_network, 
-                                                          MDL);
-                               log_debug("Picking requested address %s",
-                                         inet_ntop(AF_INET6, 
-                                                   requested_iaaddr->data,
-                                                   tmp_buf, sizeof(tmp_buf)));
-                               return ISC_R_SUCCESS;
-                       }
-               }
-               log_debug("NOT picking requested address %s",
-                         inet_ntop(AF_INET6, requested_iaaddr->data,
-                                   tmp_buf, sizeof(tmp_buf)));
-       }
-
        /*
         * Otherwise try to get a lease from the first subnet possible.
         *
@@ -922,8 +982,6 @@ pick_v6_address(struct iaaddr **addr,
                p = shared_network->ipv6_pools[i];
                if (activate_lease6(p, addr, &attempts, 
                                    client_id, 0) == ISC_R_SUCCESS) {
-                       ipv6_pool_reference(pool, p, MDL);
-
                        /*
                         * Record the pool used (or next one if there 
                         * was a collision).
@@ -953,82 +1011,65 @@ pick_v6_address(struct iaaddr **addr,
         * If we failed to pick an IPv6 address from any of the subnets.
         * Presumably that means we have no addresses for the client.
         */
-       shared_network_dereference(&shared_network, MDL);
        log_debug("Unable to pick client address: no addresses available");
        return ISC_R_NORESOURCES;
 }
 
-/* TODO: IA_TA */
+/*
+ * 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 uknown 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.
+ *
+ * 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.
+ */
 /* TODO: look at client hints for lease times */
-/* XXX: need to add IA_NA to our ORO? */
 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;
-       struct host_decl *packet_host;
-       int matched_packet_host;
-       struct option_cache *ia;
-       char reply_data[65536];
-       struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
-       int reply_ofs = (int)((char *)reply->options - (char *)reply);
-       struct option_state *opt_state;
-       struct host_decl *host;
-       struct option_state *host_opt_state;
-       /* 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;
-       u_int32_t preferred_lifetime;
-       u_int32_t valid_lifetime;
-       struct data_string iaaddr;
-       struct data_string fixed_addr;
-       struct data_string d;
-       u_int16_t len;
-       u_int32_t t1, t2;
-       struct host_decl *save_host;
-       char zeros[24];
-       struct ipv6_pool *pool;
-       struct iaaddr *lease;
-       struct group *group;
-       u_int32_t iaid;
-       struct ia_na *ia_na;
-       struct ia_na *existing_ia_na;
-       struct ia_na *old_ia_na;
-       int i;
-
-       /*
-        * Initialize to empty values, in case we have to exit early.
-        */
-       opt_state = NULL;
-       memset(&packet_oro, 0, sizeof(packet_oro));
-       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
-       cli_enc_opt_state = NULL;
-       host_opt_state = NULL;
-       memset(&fixed_addr, 0, sizeof(fixed_addr));
-       memset(&iaaddr, 0, sizeof(iaaddr));
-       ia_na = NULL;
-       lease = NULL;
 
-       /*
-        * Silence compiler warnings.
-        */
-       valid_lifetime = 0;
-       preferred_lifetime = 0;
+       /* Locate the client.  */
+       if (shared_network_from_packet6(&reply.shared,
+                                       packet) != ISC_R_SUCCESS)
+               goto exit;
 
        /* 
-        * Set up reply.
+        * Initialize the reply.
         */
-       if (!start_reply(packet, client_id, NULL, &opt_state, 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);
+       memset(&packet_oro, 0, sizeof(packet_oro));
        if (oc != NULL) {
                if (!evaluate_option_cache(&packet_oro, packet, 
                                           NULL, NULL, 
@@ -1039,773 +1080,1037 @@ lease_to_client(struct data_string *reply_ret,
                }
        }
 
-       /*
-        * A small bit of special handling for Solicit messages.
-        *
-        * We could move the logic into a flag, but for now just check
-        * explicitly.
+       /* 
+        * Find a host record that matches from the packet, if any, and is
+        * valid for the shared network the client is on.
         */
-       if (packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
+       if (find_hosts_by_option(&reply.host, packet, packet->options, MDL)) {
+               seek_shared_host(&reply.host, reply.shared);
+       }
 
-               reply->msg_type = DHCPV6_ADVERTISE;
+       if ((reply.host == NULL) &&
+           find_hosts_by_uid(&reply.host, client_id->data, client_id->len,
+                             MDL)) {
+               seek_shared_host(&reply.host, reply.shared);
+       }
 
-               /*
-                * If: 
-                * - this message type supports rapid commit (Solicit), and
-                * - the server is configured to supply a rapid commit, and
-                * - the client requests a rapid commit,
-                * Then we add a rapid commit option, and send Reply (instead
-                * of an Advertise).
-                */
-               oc = lookup_option(&dhcpv6_universe, 
-                                  opt_state, D6O_RAPID_COMMIT);
-               if (oc != NULL) {
-                       oc = lookup_option(&dhcpv6_universe, 
-                                          packet->options, D6O_RAPID_COMMIT);
-                       if (oc != NULL) {
-                               if (!save_option_buffer(&dhcpv6_universe, 
-                                                       opt_state, NULL, 
-                                                       (unsigned char *)"", 0,
-                                                       D6O_RAPID_COMMIT, 0)) {
-                                       log_error("start_reply: error saving "
-                                                 "RAPID_COMMIT option.");
-                                       goto exit;
-                               }
-                               
-                               reply->msg_type = DHCPV6_REPLY;
-                       }
+       /* 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);
+       for (; oc != NULL ; oc = oc->next) {
+               if (reply_process_ia(&reply, oc) != ISC_R_SUCCESS) {
+                       goto exit;
                }
+       }
+
+       /*
+        * Make no reply if we gave no resources.  This function is not used
+        * for Information-Request!
+        */
+       if (reply.ia_count == 0)
+               goto exit;
 
+       /*
+        * 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);
+
+       /* 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("No memory to store Reply.");
        }
+       memcpy(reply_ret->buffer->data, reply.buf.data, reply.cursor);
+       reply_ret->data = reply_ret->buffer->data;
+
+      exit:
+       /* Cleanup. */
+       if (reply.shared != NULL)
+               shared_network_dereference(&reply.shared, MDL);
+       if (reply.host != NULL)
+               host_dereference(&reply.host, 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);
+       reply.renew = reply.rebind = reply.prefer = reply.valid = 0;
+       reply.cursor = 0;
+}
 
+/* Process a client-supplied IA_NA.  This may append options ot the tail of
+ * the reply packet being built in the reply_state structure.
+ */
+static isc_result_t
+reply_process_ia(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));
 
-       /* 
-        * Find the host record that matches from the packet, if any.
-        */
-       packet_host = NULL;
-       if (!find_hosts_by_option(&packet_host, packet, packet->options, MDL)) {
-               packet_host = NULL;
-               /*
-                * If we don't have a match from the packet contents, 
-                * see if we can match by UID.
-                */
-               if (!find_hosts_by_uid(&packet_host, 
-                                      client_id->data, client_id->len, MDL)) {
-                       packet_host = NULL;
-               } 
+       /* Make sure there is at least room for the header. */
+       if ((reply->cursor + IA_NA_OFFSET + 4) > sizeof(reply->buf)) {
+               log_error("reply_process_ia: Reply too long for IA.");
+               return ISC_R_NOSPACE;
        }
-       matched_packet_host = 0;
 
-       /* 
-        * 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_solicit, &packet_oro);
 
-       /* 
-        * Now loop across each IA_NA that we have.
-        */
-       ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
-       while (ia != NULL) {
-               /* 
-                * T1 and T2, set to 0 means the client can choose.
-                * 
-                * We will adjust this based on preferred and valid 
-                * times if we have an address.
-                */
-               t1 = 0;
-               t2 = 0;
+       /* 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: error evaluating ia_na");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
 
-               if (!get_encapsulated_IA_state(&cli_enc_opt_state,
-                                              &cli_enc_opt_data,
-                                              packet, ia, IA_NA_OFFSET)) {
-                       goto exit;
+       /* Extract IA_NA header contents. */
+       iaid = getULong(ia_data.data);
+       reply->renew = getULong(ia_data.data + 4);
+       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.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+
+       /* Cache pre-existing IA, if any. */
+       ia_na_hash_lookup(&reply->old_ia, ia_active,
+                         (unsigned char *)reply->ia_na->iaid_duid.data,
+                         reply->ia_na->iaid_duid.len, MDL);
+
+       /*
+        * Create an option cache to carry the IA_NA 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 & cache the fixed host record. */
+       if ((reply->host != NULL) && (reply->host->fixed_addr != NULL)) {
+               if (!evaluate_option_cache(&reply->fixed, NULL, NULL, NULL,
+                                          NULL, NULL, &global_scope,
+                                          reply->host->fixed_addr, MDL)) {
+                       log_error("reply_process_ia: unable to evaluate "
+                                 "fixed address.");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
                }
 
-               /*
-                * Create an IA_NA structure.
-                */
-               iaid = getULong(cli_enc_opt_data.data);
-               ia_na = NULL;
-               if (ia_na_allocate(&ia_na, iaid, (char *)client_id->data, 
-                                  client_id->len, MDL) != ISC_R_SUCCESS) {
-                       log_fatal("lease_to_client: no memory for ia_na.");
+               if (reply->fixed.len < 16) {
+                       log_error("reply_process_ia: invalid fixed address.");
+                       status = ISC_R_INVALIDARG;
+                       goto cleanup;
                }
 
+               reply->static_lease = ISC_TRUE;
+       } else
+               reply->static_lease = ISC_FALSE;
+
+       /*
+        * Save the cursor position at the start of the IA, 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_NA header.  First the code. */
+       putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_NA);
+       reply->cursor += 2;
+
+       /* Then option length. */
+       putUShort(reply->buf.data + reply->cursor, 0x0Cu);
+       reply->cursor += 2;
+
+       /* Then IA_NA 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 address in this IA_NA, decide what to do about
+        * it.
+        */
+       oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
+       reply->valid = reply->prefer = 0xffffffff;
+       reply->client_valid = reply->client_prefer = 0;
+       reply->client_addressed = ISC_FALSE;
+       for (; oc != NULL ; oc = oc->next) {
+               status = reply_process_addr(reply, oc);
+
                /*
-                * Create state for this IA_NA.
+                * Canceled means we did not allocate addresses 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.
                 */
-               host_opt_state = NULL;
-               if (!option_state_allocate(&host_opt_state, MDL)) {
-                       log_error("lease_to_client: out of memory "
-                                 "allocating option_state.");
-                       goto exit;
-               }
+               if (status == ISC_R_CANCELED)
+                       break;
 
-               /* 
-                * See if this NA has an address. If so, we use it
-                * when trying to find a matching host record.
+               if (status != ISC_R_SUCCESS)
+                       goto cleanup;
+       }
+
+       reply->ia_count++;
+
+       /*
+        * If we fell through the above and never gave the client
+        * an address, give it one now.
+        */
+       if ((status != ISC_R_CANCELED) && !reply->client_addressed) {
+               status = find_client_address(reply);
+
+               /*
+                * RFC3315 section 17.2.2 (Solicit):
+                *
+                * If the server will not assign any addresses to any IAs in a
+                * subsequent Request from the client, the server MUST send an
+                * Advertise message to the client that includes only a Status
+                * Code option with code NoAddrsAvail and a status message for
+                * the user, a Server Identifier option with the server's DUID,
+                * and a Client Identifier option with the client's DUID.
+                *
+                * 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.
+                *
+                * Section 18.2.3 (Renew):
+                *
+                * The server may choose to change the list of addresses and
+                * the lifetimes of addresses in IAs that are returned to the
+                * client.
+                *
+                * Section 18.2.4 (Rebind):
+                *
+                * Absolutely nothing.  Interpretation:  Only address the
+                * client if its old binding can be found.
                 */
-               memset(&iaaddr, 0, sizeof(iaaddr));
-               oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, 
-                                  D6O_IAADDR);
-               if (oc != NULL) {
-                       /*
-                        * TODO: Check that the address is on one of
-                        *       the networks that we have, and issue
-                        *       NotOnLink if not (for Solicit/Request),
-                        *       or remember to set the address lifetime
-                        *       to 0 (for Renew/Rebind).
-                        */
-                       if (!evaluate_option_cache(&iaaddr, packet, 
-                                                  NULL, NULL, 
-                                                  packet->options, 
-                                                  NULL, &global_scope,
-                                                  oc, MDL)) {
-                               log_error("lease_to_client: "
-                                         "error evaluating IAADDR.");
-                               goto exit;
+               if ((status == ISC_R_NORESOURCES) &&
+                   (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) {
+                       /* If this is not a Renew, clear all IAADDRs. */
+                       if (reply->packet->dhcpv6_msg_type != DHCPV6_RENEW) {
+                               option_state_dereference(&reply->reply_ia, MDL);
+                               if (!option_state_allocate(&reply->reply_ia,
+                                                         MDL)) {
+                                       log_error("reply_process_ia: No memory "
+                                                 "for option state wipe.");
+                                       status = ISC_R_NOMEMORY;
+                                       goto cleanup;
+                               }
                        }
-                       /* 
-                        * Clients may choose to send :: as an address,
-                        * with the idea to give hints about 
-                        * preferred-lifetime or valid-lifetime.
-                        *
-                        * We ignore this.
-                        */
-                       memset(zeros, 0, sizeof(zeros));
-                       if ((iaaddr.len >= 24) && 
-                           !memcmp(iaaddr.data, zeros, 16)) {
-                               data_string_forget(&iaaddr, MDL);
+
+                       /* Set the status code. */
+                       if (!set_status_code(STATUS_NoAddrsAvail,
+                                            "No addresses available "
+                                            "for this interface.",
+                                            reply->reply_ia)) {
+                               log_error("reply_process_ia: Unable to set "
+                                         "NoAddrsAvail status code.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
                        }
+
+                       status = ISC_R_SUCCESS;
                }
 
-               /* 
-                * Now we need to figure out which host record matches
-                * this IA_NA.
-                * 
-                * We first try matching the encapsulated option state. 
-                * If nothing is there, then we will use the host entry 
-                * matched from the non-encapsulated option state, if 
-                * there was one.
-                * 
-                * We will only use this host entry for one of the
-                * IA_NA in the packet, to avoid having the same address
-                * on multiple interfaces.
-                */
-               host = NULL;
-               if (!find_hosts_by_option(&host, packet, 
-                                         cli_enc_opt_state, MDL)) { 
-                       if ((packet_host != NULL) && !matched_packet_host) {
-                               matched_packet_host = 1;
-                               host = packet_host;
-                       } else {
-                               host = NULL;
-                       }
+               if (status != ISC_R_SUCCESS)
+                       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));
+
+       /*
+        * 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.
+        */
+       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,
+                                          reply->packet->options,
+                                          reply->opt_state, &global_scope,
+                                          oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("Invalid renewal time.");
+               } else {
+                       reply->renew = getULong(data.data);
                }
 
-               if (iaaddr.len == 0) {
-                       /* 
-                        * Client did not specify the IAADDR to use.
-                        *
-                        * If we are renewing, this is a problem. Set
-                        * to no host, and this will cause the appropriate
-                        * response to be sent.
-                        * 
-                        * Otherwise, we simply find the first matching host.
-                        */
-                       if (packet->dhcpv6_msg_type == DHCPV6_RENEW) {
-                               host = NULL;
-                       } else {
-                               while ((host != NULL) && 
-                                      (host->fixed_addr == NULL)) {
-                                       host = host->n_ipaddr;
-                               }
-                       }
+               if (data.data != NULL)
+                       data_string_forget(&data, MDL);
+       }
+       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 {
-                       /* 
-                        * Client wanted a specific IAADDR, so we will
-                        * only accept a host entry that matches. 
-                        */
-                       save_host = host;
-                       for (; host != NULL; host = host->n_ipaddr) {
-                               if (host->fixed_addr == NULL) {
-                                       continue;
-                               } 
-                               if (!evaluate_option_cache(&fixed_addr, NULL, 
-                                                          NULL, NULL, NULL, 
-                                                          NULL, &global_scope,
-                                                          host->fixed_addr, 
-                                                          MDL)) {
-                                       log_error("lease_to_client: 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);
-                       }
-                       /* 
-                        * If we got a Solicit and don't have a matching 
-                        * host record, have gotten a bad hint.
-                        * Pick a host record.
-                        */
-                       if ((host == NULL) && 
-                           (packet->dhcpv6_msg_type == DHCPV6_SOLICIT)) {
-                               host = save_host;
-                               while ((host != NULL) && 
-                                      (host->fixed_addr == NULL)) {
-                                       host = host->n_ipaddr;
-                               }
-                       }
+                       reply->rebind = getULong(data.data);
                }
 
-               /*
-                * At this point, if we don't have a host entry,
-                * try to find the appropriate pool for this IA.
-                */
-               group = NULL;
-               lease = NULL;
-               if (host != NULL) {
-                       group = host->group;
-               } else if (num_pools > 0) {
-                       struct iaaddr *tmp;
-                       struct in6_addr *in6_addr;
+               if (data.data != NULL)
+                       data_string_forget(&data, MDL);
+       }
+       putULong(reply->buf.data + ia_cursor + 12, reply->rebind);
 
-                       /*
-                        * Find existing IA_NA.
-                        */
-                       existing_ia_na = NULL;
-                       if (ia_na_hash_lookup(&existing_ia_na, ia_active, 
-                                             (unsigned char *)
-                                             ia_na->iaid_duid.data,
-                                             ia_na->iaid_duid.len, MDL) == 0) {
-                               existing_ia_na = NULL;
-                       }
+       /*
+        * If this is not a 'soft' binding, consume the new changes into
+        * the database (if any have been attached to the ia_na).
+        *
+        * 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).
+        */
+       if ((status != ISC_R_CANCELED) && !reply->static_lease &&
+           (reply->packet->dhcpv6_msg_type != DHCPV6_SOLICIT) &&
+           (reply->ia_na->num_iaaddr != 0)) {
+               struct iaaddr *tmp;
+               struct data_string *ia_id;
+               int i;
 
-                       /*
-                        * If there are no addresses, we'll ignore this IA_NA.
-                        */
-                       if ((existing_ia_na != NULL) && 
-                           (existing_ia_na->num_iaaddr == 0)) {
-                               existing_ia_na = NULL;
-                       }
+               for (i = 0 ; i < reply->ia_na->num_iaaddr ; i++) {
+                       tmp = reply->ia_na->iaaddr[i];
 
-                       /* 
-                        * If we have this IA_NA, then use that.
-                        */
-                       if ((iaaddr.len == 0) && (existing_ia_na != NULL)) {
-                               /* 
-                                * If the client doesn't ask for a specific
-                                * address, we're cool.
-                                */
-                               tmp = existing_ia_na->iaaddr[0];
-                               pool = NULL;
-                               ipv6_pool_reference(&pool, tmp->ipv6_pool, MDL);
-                               iaaddr_reference(&lease, tmp, MDL);
-                       } else if (existing_ia_na != NULL) {
-                               /* 
-                                * Make sure this address is in the IA_NA.
-                                */
-                               pool = NULL;
-                               for (i=0; i<existing_ia_na->num_iaaddr; i++) {
-                                       tmp = existing_ia_na->iaaddr[i];
-                                       in6_addr = &tmp->addr;
-                                       if (memcmp(in6_addr, 
-                                                  iaaddr.data, 16) == 0) {
-                                               ipv6_pool_reference(&pool, 
-                                                       tmp->ipv6_pool, MDL);
-                                               iaaddr_reference(&lease,
-                                                                tmp, MDL);
-                                               break;
-                                       }
-                               }
+                       if (tmp->ia_na != NULL)
+                               ia_na_dereference(&tmp->ia_na, MDL);
+                       ia_na_reference(&tmp->ia_na, reply->ia_na, MDL);
 
-                               /*
-                                * If we didn't find a pool it means that the 
-                                * client sent an address we don't know about 
-                                * and we'll consider it a non-match.
-                                */
-                               if (pool == NULL) {
-                                       existing_ia_na = NULL;
-                               }
-                       }
+                       schedule_lease_timeout(tmp->ipv6_pool);
 
                        /*
-                        * If we don't have a matching IA_NA for this 
-                        * client, then we need to get a new address.
+                        * If this constitutes a 'hard' binding, perform ddns
+                        * updates.
                         */
-                       if (existing_ia_na == NULL) {
-                               pool = NULL;
-                               pick_v6_address(&lease, &pool, packet, 
-                                               &iaaddr, client_id);
+                       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,
+                                                         &reply->lease->scope,
+                                                         oc, MDL)) {
+                               ddns_updates(reply->packet, NULL, NULL,
+                                            reply->lease, NULL,
+                                            reply->opt_state);
                        }
+               }
 
-                       /*
-                        * If we got an address, get our group information
-                        * from the pool used.
-                        */
-                       if (pool != NULL) {
-                               group = pool->shared_network->group;
-                       }
+               /* Remove any old ia_na from the hash. */
+               if (reply->old_ia != NULL) {
+                       ia_id = &reply->old_ia->iaid_duid;
+                       ia_na_hash_delete(ia_active,
+                                         (unsigned char *)ia_id->data,
+                                         ia_id->len, MDL);
+                       ia_na_dereference(&reply->old_ia, MDL);
                }
 
-               /*
-                * Get the lease time from the group.
-                */
-               if (group != NULL) {
-                       /*
-                        * Execute statements for the host's group.
-                        */
-                       execute_statements_in_scope(NULL, packet, NULL, NULL, 
-                                                   packet->options, 
-                                                   host_opt_state, 
-                                                   &global_scope, 
-                                                   group, root_group);
-                       /* 
-                        * Get our lease time. Note that "preferred lifetime"
-                        * and "valid lifetime" are defined in RFC 2462.
-                        */
-                       oc = lookup_option(&server_universe, opt_state, 
-                                          SV_DEFAULT_LEASE_TIME);
-                       valid_lifetime = DEFAULT_DEFAULT_LEASE_TIME;
-                       if (oc != NULL) {
-                               memset(&d, 0, sizeof(d));
-                               if (!evaluate_option_cache(&d, packet, NULL, 
-                                                          NULL, 
-                                                          packet->options,
-                                                          opt_state,
-                                                          &global_scope,
-                                                          oc, MDL)) {
-                                       log_error("lease_to_client: error "
-                                                 "getting lease time, "
-                                                 "using default.");
-                               } else {
-                                       /* INSIST(d.len == 4); */
-                                       valid_lifetime = getULong(d.data);
-                                       data_string_forget(&d, MDL);
-                               }
-                       }
+               /* Put new ia_na into the hash. */
+               ia_id = &reply->ia_na->iaid_duid;
+               ia_na_hash_add(ia_active, (unsigned char *)ia_id->data,
+                              ia_id->len, reply->ia_na, MDL);
 
-                       /*
-                        * T1: RENEW time.
-                        * T2: REBIND time.
-                        * preferred: 'deprecate' address.
-                        * valid: address expires.
-                        *
-                        * Values are required for valid and preferred
-                        * lifetimes.  T1 and T2, if zero, will allow
-                        * the client to select their own behaviour.
-                        */
-                       t1 = t2 = 0;
-                       /* XXX: This is more than a little weird. */
-                       oc = lookup_option(&dhcp_universe, opt_state,
-                                          DHO_DHCP_RENEWAL_TIME);
-                       if (oc != NULL) {
-                               memset(&d, 0, sizeof(d));
-                               if (!evaluate_option_cache(&d, packet, NULL,
-                                                          NULL,
-                                                          packet->options,
-                                                          opt_state,
-                                                          &global_scope,
-                                                          oc, MDL)) {
-                                       /* XXX: I think there are already log
-                                        * lines by this point.
-                                        */
-                                       log_error("lease_to_client: error "
-                                                 "evaluating renew time, "
-                                                 "defaulting to 0");
-                               } else {
-                                       t1 = getULong(d.data);
-                                       data_string_forget(&d, MDL);
-                               }
-                       }
+               write_ia_na(reply->ia_na);
+       }
 
-                       oc = lookup_option(&dhcp_universe, opt_state,
-                                          DHO_DHCP_REBINDING_TIME);
-                       if (oc != NULL) {
-                               memset(&d, 0, sizeof(d));
-                               if (!evaluate_option_cache(&d, packet, NULL,
-                                                          NULL,
-                                                          packet->options,
-                                                          opt_state,
-                                                          &global_scope,
-                                                          oc, MDL)) {
-                                       /* XXX: I think there are already log
-                                        * lines by this point.
-                                        */
-                                       log_error("lease_to_client: error "
-                                                 "evaluating rebinding "
-                                                 "time, defaulting to 0");
-                               } else {
-                                       t2 = getULong(d.data);
-                                       data_string_forget(&d, MDL);
-                               }
-                       }
+      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_na != NULL)
+               ia_na_dereference(&reply->ia_na, MDL);
+       if (reply->old_ia != NULL)
+               ia_na_dereference(&reply->old_ia, MDL);
+       if (reply->fixed.data != NULL)
+               data_string_forget(&reply->fixed, MDL);
+
+       /*
+        * ISC_R_CANCELED is a status code used by the addr 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);
+}
+
+/*
+ * Process an IAADDR within a given IA_NA, 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.
+ */
+static isc_result_t
+reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
+       u_int32_t pref_life, valid_life;
+       struct binding_scope **scope;
+       struct group *group;
+       struct subnet *subnet;
+       struct iaddr tmp_addr;
+       struct data_string iaaddr, data;
+       isc_result_t status = ISC_R_SUCCESS;
+
+       /* Initializes values that will be cleaned up. */
+       memset(&iaaddr, 0, sizeof(iaaddr));
+       memset(&data, 0, sizeof(data));
+       /* Note that reply->lease may be set by address_is_owned() */
+
+       /*
+        * There is no point trying to process an incoming address if there
+        * is no room for an outgoing address.
+        */
+       if ((reply->cursor + 28) > sizeof(reply->buf)) {
+               log_error("reply_process_addr: Out of room for address.");
+               return ISC_R_NOSPACE;
+       }
+
+       /* Extract this IAADDR option. */
+       if (!evaluate_option_cache(&iaaddr, reply->packet, NULL, NULL, 
+                                  reply->packet->options, NULL, &global_scope,
+                                  addr, MDL) ||
+           (iaaddr.len < IAADDR_OFFSET)) {
+               log_error("reply_process_addr: error evaluating IAADDR.");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       tmp_addr.len = 16;
+       memcpy(tmp_addr.iabuf, iaaddr.data, 16);
+
+       pref_life = getULong(iaaddr.data + 16);
+       valid_life = getULong(iaaddr.data + 20);
+
+       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 :: as an address, with the idea to give
+        * hints about preferred-lifetime or valid-lifetime.
+        */
+       if (!memcmp(iaaddr.data, tmp_addr.iabuf, 16)) {
+               /* Status remains success; we just ignore this one. */
+               goto cleanup;
+       }
+
+       /*
+        * Verify that this address is on the client's network.
+        */
+       for (subnet = reply->shared->subnets ; subnet != NULL ;
+            subnet = subnet->next_sibling) {
+               if (addr_eq(subnet_number(tmp_addr, subnet->netmask),
+                           subnet->net))
+                       break;
+       }
 
-                       preferred_lifetime = t1 + t2;
+       /* Address not found on shared network. */
+       if (subnet == NULL) {
+               /* Ignore this address on 'soft' bindings. */
+               if (reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT)
+                       /* status remains success */
+                       goto cleanup;
 
-                       if (preferred_lifetime == 0 ||
-                           preferred_lifetime >= valid_lifetime)
-                               preferred_lifetime = (valid_lifetime / 2) +
-                                                    (valid_lifetime / 4);
+               /*
+                * RFC3315 section 18.2.1:
+                *
+                * If the server finds that the prefix on one or more IP
+                * addresses in any IA in the message from the client is not
+                * appropriate for the link to which the client is connected,
+                * the server MUST return the IA to the client with a Status
+                * Code option with the value NotOnLink.
+                */
+               if (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) {
+                       /* Rewind the IA_NA to empty. */
+                       option_state_dereference(&reply->reply_ia, MDL);
+                       if (!option_state_allocate(&reply->reply_ia, MDL)) {
+                               log_error("reply_process_addr: No memory for "
+                                         "option state wipe.");
+                               status = ISC_R_NOMEMORY;
+                               goto cleanup;
+                       }
 
-                       oc = lookup_option(&server_universe, opt_state,
-                                          SV_PREFER_LIFETIME);
-                       if (oc != NULL) {
-                               memset(&d, 0, sizeof(d));
-                               if (!evaluate_option_cache(&d, packet, NULL,
-                                                          NULL,
-                                                          packet->options,
-                                                          opt_state,
-                                                          &global_scope,
-                                                          oc, MDL)) {
-                                       /* XXX: I think there are already log
-                                        * lines by this point.
-                                        */
-                                       log_error("lease_to_client: error "
-                                                 "evaluating preferred "
-                                                 "lifetime, defaulting to "
-                                                 "%d", preferred_lifetime);
-                               } else {
-                                       preferred_lifetime = getULong(d.data);
-                                       data_string_forget(&d, MDL);
-                               }
+                       /* Append a NotOnLink status code. */
+                       if (!set_status_code(STATUS_NotOnLink,
+                                            "Address not for use on this "
+                                            "link.", reply->reply_ia)) {
+                               log_error("reply_process_addr: Failure "
+                                         "setting status code.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
                        }
+
+                       /* Fin (no more IAADDRs). */
+                       status = ISC_R_CANCELED;
+                       goto cleanup;
                }
 
                /*
-                * Shift the lease to the right place, based on our timeout.
+                * RFC3315 sections 18.2.3 and 18.2.4 have identical language:
+                *
+                * If the server finds that any of the addresses are not
+                * appropriate for the link to which the client is attached,
+                * the server returns the address to the client with lifetimes
+                * of 0.
                 */
-               if (lease != NULL) {
-                       lease->valid_lifetime_end_time = valid_lifetime + 
-                                                        cur_time;
-                       renew_lease6(pool, lease);
+               if ((reply->packet->dhcpv6_msg_type != DHCPV6_RENEW) &&
+                   (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) {
+                       log_error("It is impossible to lease a client that is "
+                                 "not sending a solict, request, renew, or "
+                                 "rebind.");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
                }
 
+               reply->send_prefer = reply->send_valid = 0;
+               goto send_addr;
+       }
+
+       /* Verify the address belongs to the client. */
+       if (!address_is_owned(reply, &tmp_addr)) {
                /*
-                * Get the address.
+                * For solicit and request, any addresses included are
+                * 'requested' addresses.  For rebind, we actually have
+                * no direction on what to do from 3315 section 18.2.4!
+                * So I think the best bet is to try and give it out, and if
+                * we can't, zero lifetimes.
                 */
-               memset(&fixed_addr, 0, sizeof(fixed_addr));
-               if (host != NULL) {
-                       if (!evaluate_option_cache(&fixed_addr, NULL, NULL, 
-                                                  NULL, NULL, NULL, 
-                                                  &global_scope,
-                                                  host->fixed_addr, MDL)) {
-                               log_error("lease_to_client: error "
-                                         "evaluating host address.");
-                               goto exit;
-                       }
-                       if (fixed_addr.len != 16) {
-                               log_error("lease_to_client: invalid address "
-                                         "length (%d)", fixed_addr.len);
-                               goto exit;
+               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_addr(reply, &tmp_addr);
+
+                       /* Either error out or skip this address. */
+                       if (status != ISC_R_SUCCESS)
+                               goto cleanup;
+
+                       if (reply->lease == NULL) {
+                               if (reply->packet->dhcpv6_msg_type ==
+                                                       DHCPV6_REBIND) {
+                                       reply->send_prefer = 0;
+                                       reply->send_valid = 0;
+                                       goto send_addr;
+                               }
+
+                               /* status remains success - ignore */
+                               goto cleanup;
                        }
-               } else if (lease != NULL) {
-                       fixed_addr.len = 16;
-                       if (!buffer_allocate(&fixed_addr.buffer, 16, MDL)) {
-                               log_fatal("lease_to_client: no memory for "
-                                         "address information.");
+               /*
+                * RFC3315 section 18.2.3:
+                *
+                * If the server cannot find a client entry for the IA the
+                * server returns the IA containing no addresses with a Status
+                * Code option set to NoBinding in the Reply message.
+                */
+               } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) {
+                       /* Rewind the IA_NA to empty. */
+                       option_state_dereference(&reply->reply_ia, MDL);
+                       if (!option_state_allocate(&reply->reply_ia, MDL)) {
+                               log_error("reply_process_addr: No memory for "
+                                         "option state wipe.");
+                               status = ISC_R_NOMEMORY;
+                               goto cleanup;
                        }
-                       fixed_addr.data = fixed_addr.buffer->data;
-                       memcpy(fixed_addr.buffer->data, &lease->addr, 16);
-               }
 
-               if (fixed_addr.len == 16) {
-                       /*
-                        * Store the address.
-                        *
-                        * XXX: This should allow multiple addresses, rather
-                        *      than only a single one!
-                        */
-                       memset(&d, 0, sizeof(d));
-                       d.len = 24;     /* From RFC 3315, section 22.6 */
-                       if (!buffer_allocate(&d.buffer, d.len, MDL)) {
-                               log_fatal("lease_to_client: no memory for "
-                                         "address information.");
+                       /* Append a NoBinding status code.  */
+                       if (!set_status_code(STATUS_NoBinding,
+                                            "Address not bound to this "
+                                            "interface.", reply->reply_ia)) {
+                               log_error("reply_process_addr: Unable to "
+                                         "attach status code.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
                        }
-                       d.data = d.buffer->data;
-                       memcpy(d.buffer->data, fixed_addr.data, 16);
-                       putULong(d.buffer->data+16, preferred_lifetime);
-                       putULong(d.buffer->data+20, valid_lifetime);
-                       data_string_forget(&fixed_addr, MDL);
-                       if (!save_option_buffer(&dhcpv6_universe, 
-                                               host_opt_state, 
-                                               d.buffer, 
-                                               d.buffer->data, 
-                                               d.len, D6O_IAADDR, 0)) {
-                               log_error("lease_to_client: error saving "
-                                         "IAADDR.");
-                               data_string_forget(&d, MDL);
-                               goto exit;
-                       }
-                       data_string_forget(&d, MDL);
+
+                       /* Fin (no more IAADDRs). */
+                       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 ((host != NULL) || (lease != NULL)) {
-                       
-                       /*
-                        * Remember the client identifier so we can look
-                        * it up later.
-                        */
-                       if (host != NULL) {
-                               change_host_uid(host, (char *)client_id->data, 
-                                               client_id->len);
-                       } 
-                       /*
-                        * Otherwise save the IA_NA, for the same reason.
-                        */
-                       else if (packet->dhcpv6_msg_type != DHCPV6_SOLICIT) {
-                               /*
-                                * Remove previous version of this IA_NA,
-                                * if one exists.
-                                */
-                               struct data_string *d = &ia_na->iaid_duid;
-                               old_ia_na = NULL;
-                               if (ia_na_hash_lookup(&old_ia_na, ia_active,
-                                                     (unsigned char *)d->data,
-                                                     d->len, MDL)) {
-                                       ia_na_hash_delete(ia_active, 
-                                                         (unsigned char *)
-                                                         d->data,
-                                                         d->len, MDL);
-                                       ia_na_dereference(&old_ia_na, MDL);
-                               }
+       if (reply->static_lease) {
+               if (reply->host == NULL)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
 
-                               /*
-                                * ia_na_add_iaaddr() will reference the
-                                * lease, so we need to dereference the
-                                * previous reference to the lease if one 
-                                * exists.
-                                */
-                               if (lease->ia_na != NULL) {
-                                       ia_na_dereference(&lease->ia_na, MDL);
-                               }
-                               if (ia_na_add_iaaddr(ia_na, lease, 
-                                                    MDL) != ISC_R_SUCCESS) {
-                                       log_fatal("lease_to_client: out of "
-                                                 "memory adding IAADDR");
-                               }
-                               ia_na_hash_add(ia_active, 
-                                              (unsigned char *)
-                                              ia_na->iaid_duid.data,
-                                              ia_na->iaid_duid.len, 
-                                              ia_na, MDL);
-
-                               /* If this constitutes a binding, and we
-                                * are performing ddns updates, then give
-                                * ddns_updates() a chance to do its mojo.
-                                */
-                               if (((packet->dhcpv6_msg_type
-                                                        == DHCPV6_REQUEST) ||
-                                    (packet->dhcpv6_msg_type
-                                                        == DHCPV6_RENEW) ||
-                                    (packet->dhcpv6_msg_type
-                                                        == DHCPV6_REBIND)) &&
-                                   (((oc = lookup_option(&server_universe,
-                                                         opt_state,
-                                                         SV_DDNS_UPDATES))
-                                                               == NULL) ||
-                                    evaluate_boolean_option_cache(NULL,
-                                               packet, NULL, NULL,
-                                               packet->options, opt_state,
-                                               &lease->scope, oc, MDL))) {
-                                       ddns_updates(packet, NULL, NULL,
-                                                    lease, /* XXX */ NULL,
-                                                    opt_state);
-                               }
+               scope = &global_scope;
+               group = reply->host->group;
+       } else {
+               if (reply->lease == NULL)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
 
-                               write_ia_na(ia_na);
-                                schedule_lease_timeout(lease->ipv6_pool);
-                       /* 
-                        * On SOLICIT, we want to forget this lease since we're
-                        * not actually doing anything with it.
-                        */
-                       } else {
-                               release_lease6(lease->ipv6_pool, lease);
-                       }
+               scope = &reply->lease->scope;
+               group = reply->shared->group;
+       }
 
-               } else {
+       status = reply_process_is_addressed(reply, scope, group);
+       if (status != ISC_R_SUCCESS)
+               goto cleanup;
 
-                       /*
-                        * We send slightly different errors, depending on
-                        * whether the client is asking for a new address, or
-                        * attempting to renew an address it thinks it has.
-                        */
-                       if (packet->dhcpv6_msg_type == DHCPV6_REBIND) {
-                               if (iaaddr.len >= 24) {
-                                       /* 
-                                        * If the we have a client address, 
-                                        * tell the client it is invalid.
-                                        */
-                                       memset(&d, 0, sizeof(d));
-                                       d.len = 24; 
-                                       if (!buffer_allocate(&d.buffer, 
-                                                            d.len, MDL)) {
-                                               log_fatal("lease_to_client: "
-                                                         "no memory for "
-                                                         "address "
-                                                         "information.");
-                                       }
-                                       d.data = d.buffer->data;
-                                       memcpy(d.buffer->data, iaaddr.data, 16);
-                                       putULong(d.buffer->data+16, 0);
-                                       putULong(d.buffer->data+20, 0);
-                                       if (!save_option_buffer(
-                                                             &dhcpv6_universe, 
-                                                             host_opt_state, 
-                                                             d.buffer, 
-                                                             d.buffer->data, 
-                                                             d.len, 
-                                                             D6O_IAADDR, 0)) {
-                                               log_error("lease_to_client: "
-                                                         "error saving "
-                                                         "IAADDR.");
-                                               data_string_forget(&d, MDL);
-                                               goto exit;
-                                       }
-                                       data_string_forget(&d, MDL);
-                               } else {
-                                       /* 
-                                        * Otherwise, this is an error.
-                                        * 
-                                        * XXX: The other possibility is to
-                                        *      not say anything for this
-                                        *      IA, which might be more 
-                                        *      correct. RFC 3315 does not 
-                                        *      address this case.
-                                        */
-                                       if (!set_status_code(STATUS_UnspecFail, 
-                                                    "Rebind requested without "
-                                                    "including an addresses.", 
-                                                    host_opt_state)) {
-                                               goto exit;
-                                       }
-                               }
-                       } else if (packet->dhcpv6_msg_type == DHCPV6_RENEW) {
-                               if (!set_status_code(STATUS_NoBinding, 
-                                                    "Address not bound "
-                                                    "to this interface.", 
-                                                    host_opt_state)) {
-                                       goto exit;
-                               }
-                       } else if ((iaaddr.len >= 24) &&
-                                  (packet->dhcpv6_msg_type == DHCPV6_REQUEST)){
-                               if (!set_status_code(STATUS_NotOnLink, 
-                                                    "Address not for "
-                                                    "use on this link.", 
-                                                    host_opt_state)) {
-                                       goto exit;
-                               }
-                       } else {
-                               if (!set_status_code(STATUS_NoAddrsAvail, 
-                                                    "No addresses available "
-                                                    "for this interface.", 
-                                                    host_opt_state)) {
-                                       goto exit;
-                               }
-                       }
+      send_addr:
+       status = reply_process_send_addr(reply, &tmp_addr);
 
-               }
+      cleanup:
+       if (iaaddr.data != NULL)
+               data_string_forget(&iaaddr, MDL);
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (reply->lease != NULL)
+               iaaddr_dereference(&reply->lease, MDL);
 
-               /*
-                * Insure we have enough space
-                */
-               if (sizeof(reply_data) < (reply_ofs + 16)) {
-                       log_error("lease_to_client: "
-                                 "out of space for reply packet.");
-                       goto exit;
-               }
+       return status;
+}
 
-               /*
-                * Store the encapsulated option data for this IA_NA into
-                * our reply packet.
-                */
-               len = store_options6(reply_data+reply_ofs+16, 
-                                    sizeof(reply_data)-reply_ofs-16, 
-                                    host_opt_state, packet,
-                                    required_opts_IA_NA, NULL);
+/*
+ * Verify the address belongs to the client.  If we've got a host
+ * record with a fixed address, it has to be the assigned address
+ * (fault out all else).  Otherwise it's a dynamic address, so lookup
+ * that address and make sure it belongs to this DUID:IAID pair.
+ */
+static isc_boolean_t
+address_is_owned(struct reply_state *reply, struct iaddr *addr) {
+       int i;
 
-               /*
-                * Store the non-encapsulated option data for this IA_NA 
-                * into our reply packet. Defined in RFC 3315, section 22.4.
-                */
-               /* option number */
-               putShort((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, cli_enc_opt_data.data, 4);
-               /* T1 and T2, set previously */
-               putULong((unsigned char *)reply_data+reply_ofs+8, t1);
-               putULong((unsigned char *)reply_data+reply_ofs+12, t2);
+       /*
+        * This faults out addresses that don't match fixed addresses.
+        */
+       if (reply->static_lease) {
+               if (reply->fixed.data == NULL)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
 
-               /*
-                * Get ready for next IA_NA.
-                */
-               reply_ofs += (len + 16);
+               if (memcmp(addr->iabuf, reply->fixed.data, 16) == 0)
+                       return ISC_TRUE;
 
-               /*
-                * Bit of cleanup.
-                */
-               if (lease != NULL) {
-                       iaaddr_dereference(&lease, MDL);
-               }
-               ia_na_dereference(&ia_na, MDL);
-               if (iaaddr.data != NULL) {
-                       data_string_forget(&iaaddr, MDL);
+               return ISC_FALSE;
+       }
+
+       if ((reply->old_ia == NULL) || (reply->old_ia->num_iaaddr == 0))
+               return ISC_FALSE;
+
+       for (i = 0 ; i < reply->old_ia->num_iaaddr ; i++) {
+               struct iaaddr *tmp;
+
+               tmp = reply->old_ia->iaaddr[i];
+
+               if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) {
+                       iaaddr_reference(&reply->lease, tmp, MDL);
+                       return ISC_TRUE;
                }
-               option_state_dereference(&host_opt_state, MDL);
-               option_state_dereference(&cli_enc_opt_state, MDL);
-               data_string_forget(&cli_enc_opt_data, MDL);
-               ia = ia->next;
        }
 
-       /* 
-        * 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.");
+       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) {
+       struct ipv6_pool *pool;
+       int i;
+       struct data_string data_addr;
+
+       if ((reply == NULL) || (reply->shared == NULL) ||
+           (reply->shared->ipv6_pools == NULL) || (addr == NULL) ||
+           (reply->lease != NULL))
+               return ISC_R_INVALIDARG;
+
+       memset(&data_addr, 0, sizeof(data_addr));
+       data_addr.len = addr->len;
+       data_addr.data = addr->iabuf;
+
+       for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
+               if (try_client_v6_address(&reply->lease, pool, &data_addr) ==
+                                                               ISC_R_SUCCESS)
+                       break;
        }
-       reply_ret->data = reply_ret->buffer->data;
-       memcpy(reply_ret->buffer->data, reply, reply_ofs);
 
-exit:
-       if (lease != NULL) {
-               iaaddr_dereference(&lease, MDL);
+       /* Note that this is just pedantry.  There is no allocation to free. */
+       data_string_forget(&data_addr, MDL);
+       return ISC_R_SUCCESS;
+}
+
+/* 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 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;
+
+               send_addr.len = 16;
+               memcpy(send_addr.iabuf, reply->fixed.data, 16);
+
+               status = ISC_R_SUCCESS;
+               scope = &global_scope;
+               goto send_addr;
        }
-       if (ia_na != NULL) {
-               ia_na_dereference(&ia_na, MDL);
+
+       if (reply->old_ia != NULL)  {
+               for (i = 0 ; i < reply->old_ia->num_iaaddr ; i++) {
+                       lease = reply->old_ia->iaaddr[i];
+
+                       best_lease = lease_compare(lease, best_lease);
+               }
        }
-       if (iaaddr.buffer != NULL) {
-               data_string_forget(&iaaddr, MDL);
+
+       /* 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;
        }
-       if (fixed_addr.buffer != NULL) {
-               data_string_forget(&fixed_addr, MDL);
+
+       /* 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); */
        }
-       if (host_opt_state != NULL) {
-               option_state_dereference(&host_opt_state, 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);
+
+       scope = &reply->lease->scope;
+       group = reply->shared->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;
+
+       /* Initialize values we will cleanup. */
+       memset(&data, 0, sizeof(data));
+
+       /* 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);
+
+       /* 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,
+                                          &reply->lease->scope,
+                                          oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_ia: uanble to "
+                                 "evaluate default lease time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_valid = getULong(data.data);
+               data_string_forget(&data, MDL);
        }
-       if (cli_enc_opt_state != NULL) {
-               option_state_dereference(&cli_enc_opt_state, MDL);
+
+       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_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,
+                                          &reply->lease->scope,
+                                          oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_ia: unable to "
+                                 "evaluate preferred lease time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_prefer = getULong(data.data);
+               data_string_forget(&data, MDL);
        }
-       if (cli_enc_opt_data.buffer != NULL) {
-               data_string_forget(&cli_enc_opt_data, MDL);
+
+       /* 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.  I think this was an error;
+        * XXX: it doesn't make sense to me now in retrospect to change
+        * XXX: what is essentially configuration state with network
+        * XXX: supplied values.
+        */
+       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) {
+               /* 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);
+
+               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));
+               }
+
+               /*
+                * 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);
+               }
        }
-       if (packet_oro.buffer != NULL) {
-               data_string_forget(&packet_oro, 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);
+
+      cleanup:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+
+       if (status == ISC_R_SUCCESS)
+               reply->client_addressed = ISC_TRUE;
+
+       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;
+
+       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_ia: out of memory alloating "
+                         "new IAADDR buffer.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
        }
-       if (opt_state != NULL) {
-               option_state_dereference(&opt_state, MDL);
+       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 (!save_option_buffer(&dhcpv6_universe, reply->reply_ia,
+                               data.buffer, data.buffer->data,
+                               data.len, D6O_IAADDR, 0)) {
+               log_error("reply_process_ia: unable to save IAADDR "
+                         "option");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+      cleanup:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+
+       return status;
+}
+
+/* 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;
+
+       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->valid_lifetime_end_time < 
+                           beta->valid_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->valid_lifetime_end_time <
+                           beta->valid_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->valid_lifetime_end_time <
+                           beta->valid_lifetime_end_time)
+                               return alpha;
+
+                     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;
 }
 
 /*
@@ -2035,7 +2340,7 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
                        if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
                                                   packet->options, NULL,
                                                   &global_scope, oc, MDL) ||
-                           (iaaddr.len < 24)) {
+                           (iaaddr.len < IAADDR_OFFSET)) {
                                log_error("dhcpv6_confirm: "
                                          "error evaluating IAADDR.");
                                goto exit;
@@ -2045,6 +2350,8 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
                        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;
 
@@ -2057,8 +2364,6 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
                                        break;
                        }
 
-                       data_string_forget(&iaaddr, MDL);
-
                        /* 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
@@ -2070,7 +2375,7 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
                                inappropriate = ISC_TRUE;
                                break;
                        }
-                }
+               }
 
                option_state_dereference(&cli_enc_opt_state, MDL);
                data_string_forget(&cli_enc_opt_data, MDL);
@@ -2192,7 +2497,7 @@ dhcpv6_rebind(struct data_string *reply, struct packet *packet) {
                return;
        }
 
-        lease_to_client(reply, packet, &client_id, NULL);
+       lease_to_client(reply, packet, &client_id, NULL);
 
        data_string_forget(&client_id, MDL);
 }
@@ -2483,7 +2788,7 @@ iterate_over_ia_na(struct data_string *reply_ret,
                        host = host->n_ipaddr;
                }
 
-               if ((host == NULL) && (iaaddr.len >= 24)) {
+               if ((host == NULL) && (iaaddr.len >= IAADDR_OFFSET)) {
                        /*
                         * Find existing IA_NA.
                         */
@@ -2513,7 +2818,7 @@ iterate_over_ia_na(struct data_string *reply_ret,
                                                iaaddr_reference(&lease,
                                                                 tmp, MDL);
                                                break;
-                                       }
+                                       }
                                }
                        }
 
@@ -2783,8 +3088,8 @@ dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
        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;
+       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")];
@@ -2847,44 +3152,44 @@ dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
        enc_packet->dhcpv6_container_packet = packet;
 
        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 ((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;
 
-                /* relay-specific data */
-                enc_packet->dhcpv6_hop_count = relay->hop_count;
+               /* 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, 
+               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 */
-                        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, 
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       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;
-                }
-        }
+                                        &dhcpv6_universe)) {
+                       /* no logging here, as parse_option_buffer() logs all
+                          cases where it fails */
+                       goto exit;
+               }
+       }
 
        /*
         * This is recursive. It is possible to exceed maximum packet size.
@@ -3144,5 +3449,70 @@ dhcpv6(struct packet *packet) {
        }
 }
 
+static void
+seek_shared_host(struct host_decl **hp, struct shared_network *shared) {
+       struct host_decl *nofixed = NULL;
+       struct host_decl *seek, *hold = NULL;
+
+       /*
+        * Seek forward through fixed addresses for the right broadcast
+        * domain.
+        */
+       host_reference(&hold, *hp, MDL);
+       host_dereference(hp, MDL);
+       seek = hold;
+       while (seek != NULL) {
+               if (seek->fixed_addr == NULL)
+                       nofixed = seek;
+               else if (fixed_matches_shared(seek, shared))
+                       break;
+
+               seek = seek->n_ipaddr;
+       }
+
+       if ((seek == NULL) && (nofixed != NULL))
+               seek = nofixed;
+
+       if (seek != NULL)
+               host_reference(hp, seek, MDL);
+}
+
+static isc_boolean_t
+fixed_matches_shared(struct host_decl *host, struct shared_network *shared) {
+       struct subnet *subnet;
+       struct data_string addr;
+       isc_boolean_t matched;
+       struct iaddr fixed;
+
+       if (host->fixed_addr == NULL)
+               return ISC_FALSE;
+
+       memset(&addr, 0, sizeof(addr));
+       if (!evaluate_option_cache(&addr, NULL, NULL, NULL, NULL, NULL,
+                                  &global_scope, host->fixed_addr, MDL))
+               return ISC_FALSE;
+
+       if (addr.len < 16) {
+               data_string_forget(&addr, MDL);
+               return ISC_FALSE;
+       }
+
+       fixed.len = 16;
+       memcpy(fixed.iabuf, addr.data, 16);
+
+       matched = ISC_FALSE;
+       for (subnet = shared->subnets ; subnet != NULL ;
+            subnet = subnet->next_sibling) {
+               if (addr_eq(subnet_number(fixed, subnet->netmask),
+                           subnet->net)) {
+                       matched = ISC_TRUE;
+                       break;
+               }
+       }
+
+       data_string_forget(&addr, MDL);
+       return matched;
+}
+
 #endif /* DHCPv6 */
 
index 14aad83c6e462052ff86f736bee71470bcf74328..398e6c6751595a15d9644c209879e289015d7697 100644 (file)
@@ -297,7 +297,6 @@ ia_na_add_iaaddr(struct ia_na *ia_na, struct iaaddr *iaaddr,
        iaaddr_reference(&(ia_na->iaaddr[ia_na->num_iaaddr]), iaaddr, 
                         file, line);
        ia_na->num_iaaddr++;
-       ia_na_reference(&iaaddr->ia_na, ia_na, file, line);
 
        return ISC_R_SUCCESS;
 }