]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
Add IA_TA and IA_PD support in server
authorFrancis Dupont <fdupont@isc.org>
Wed, 20 Feb 2008 12:45:53 +0000 (12:45 +0000)
committerFrancis Dupont <fdupont@isc.org>
Wed, 20 Feb 2008 12:45:53 +0000 (12:45 +0000)
client/dhc6.c
common/conflex.c
includes/dhcpd.h
includes/dhctoken.h
server/confpars.c
server/dhcpd.c
server/dhcpd.conf.5
server/dhcpv6.c
server/mdb6.c
server/stables.c

index 37cdab61f008093749d7817b41b9933d82948978..908262234f47c05b4b0733095f879d054f9ab19e 100644 (file)
@@ -1012,12 +1012,12 @@ dhc6_parse_prefs(struct dhc6_addr **ppref, struct packet *packet,
                                                 oc, MDL) &&
                           (ds.len >= 25)) {
 
-                       pref->plen = getUChar(ds.data);
+                       pref->preferred_life = getULong(ds.data);
+                       pref->max_life = getULong(ds.data + 4);
+                       pref->plen = getUChar(ds.data + 8);
                        pref->address.len = 16;
-                       memcpy(pref->address.iabuf, ds.data + 1, 16);
+                       memcpy(pref->address.iabuf, ds.data + 9, 16);
                        pref->starts = cur_time;
-                       pref->preferred_life = getULong(ds.data + 17);
-                       pref->max_life = getULong(ds.data + 21);
 
                        log_debug("RCV:  | | X-- IAPREFIX %s/%d",
                                  piaddr(pref->address), (int)pref->plen);
index 3cdc9146d978766c34c52242a7591502ce4538a7..d676c643a9efd4151367190564cf626cd4c238b9 100644 (file)
@@ -918,6 +918,8 @@ intern(char *atom, enum dhcp_token dfv) {
                        return FIXED_ADDR;
                if (!strcasecmp (atom + 1, "ixed-address6"))
                        return FIXED_ADDR6;
+               if (!strcasecmp (atom + 1, "ixed-prefix6"))
+                       return FIXED_PREFIX6;
                if (!strcasecmp (atom + 1, "ddi"))
                        return TOKEN_FDDI;
                if (!strcasecmp (atom + 1, "ormerr"))
@@ -1145,6 +1147,8 @@ intern(char *atom, enum dhcp_token dfv) {
                        return PACKET;
                if (!strcasecmp (atom + 1, "ool"))
                        return POOL;
+               if (!strcasecmp (atom + 1, "refix6"))
+                       return PREFIX6;
                if (!strcasecmp (atom + 1, "seudo"))
                        return PSEUDO;
                if (!strcasecmp (atom + 1, "eer"))
@@ -1365,6 +1369,8 @@ intern(char *atom, enum dhcp_token dfv) {
                        return TSFP;
                if (!strcasecmp (atom + 1, "ransmission"))
                        return TRANSMISSION;
+               if (!strcasecmp(atom + 1, "emporary"))
+                       return TEMPORARY;
                break;
              case 'u':
                if (!strcasecmp (atom + 1, "case"))
index 240bcf5a8c7ec0dffe6b6b27b3ff66adcf7b3dff..7f04e0cb50490427ba62f3fe88549776f9cd919f 100644 (file)
@@ -622,7 +622,8 @@ struct lease_state {
 #define SV_DHCPV6_LEASE_FILE_NAME       54
 #define SV_DHCPV6_PID_FILE_NAME         55
 #define SV_LIMIT_ADDRS_PER_IA          56
-#define SV_DELAYED_ACK                 57
+#define SV_LIMIT_PREFS_PER_IA          57
+#define SV_DELAYED_ACK                 58
 
 #if !defined (DEFAULT_PING_TIMEOUT)
 # define DEFAULT_PING_TIMEOUT 1
@@ -741,6 +742,7 @@ struct host_decl {
                  not an option_cache, but it's referenced in a lot of
                  places, so we'll leave it for now. */
        struct option_cache *fixed_addr;
+       struct iaddrcidrnetlist *fixed_prefix;
        struct group *group;
        struct group_object *named_group;
        struct data_string auth_key_id;
@@ -1358,6 +1360,7 @@ extern ia_na_hash_t *ia_ta_active;
 struct ipv6_pool {
        int refcnt;                             /* reference count */
        struct in6_addr start_addr;             /* first IPv6 address */
+#define POOL_IS_FOR_TEMP       0x8000
        int bits;                               /* number of bits, CIDR style */
        iaaddr_hash_t *addrs;                   /* non-free IAADDR */
        int num_active;                         /* count of active IAADDR */
@@ -1738,6 +1741,8 @@ int parse_ip6_prefix(struct parse *, struct iaddr *, u_int8_t *);
 void parse_address_range PROTO ((struct parse *, struct group *, int,
                                 struct pool *, struct lease **));
 void parse_address_range6(struct parse *cfile, struct group *group);
+void parse_prefix6(struct parse *cfile, struct group *group);
+void parse_fixed_prefix6(struct parse *cfile, struct host_decl *host_decl);
 void parse_ia_na_declaration(struct parse *);
 void parse_ia_ta_declaration(struct parse *);
 void parse_ia_pd_declaration(struct parse *);
@@ -3290,6 +3295,7 @@ isc_result_t ia_pd_add_iaprefix(struct ia_pd *ia_pd, struct iaprefix *iaprefix,
                                const char *file, int line);
 void ia_pd_remove_iaprefix(struct ia_pd *ia_pd, struct iaprefix *iaprefix,
                           const char *file, int line);
+isc_boolean_t ia_pd_equal(const struct ia_pd *a, const struct ia_pd *b);
 
 isc_result_t ipv6_pool_allocate(struct ipv6_pool **pool,
                                const struct in6_addr *start_addr, int bits, 
@@ -3342,13 +3348,15 @@ isc_boolean_t prefix6_exists(const struct ipv6_ppool *ppool,
                             const struct in6_addr *pref, u_int8_t plen);
 
 isc_result_t add_ipv6_pool(struct ipv6_pool *pool);
-isc_result_t find_ipv6_pool(struct ipv6_pool **pool, 
+isc_result_t find_ipv6_pool(struct ipv6_pool **pool, int temp,
                            const struct in6_addr *addr);
 isc_boolean_t ipv6_addr_in_pool(const struct in6_addr *addr, 
                                const struct ipv6_pool *pool);
 isc_result_t add_ipv6_ppool(struct ipv6_ppool *ppool);
 isc_result_t find_ipv6_ppool(struct ipv6_ppool **pool,
                             const struct in6_addr *pref);
+isc_boolean_t ipv6_prefix_in_ppool(const struct in6_addr *pref,
+                                  const struct ipv6_ppool *ppool);
 
 isc_result_t renew_leases(struct ia_na *ia_na);
 isc_result_t release_leases(struct ia_na *ia_na);
@@ -3362,5 +3370,6 @@ void schedule_prefix_timeout(struct ipv6_ppool *ppool);
 void schedule_all_ipv6_prefix_timeouts();
 
 void mark_hosts_unavailable(void);
+void mark_phosts_unavailable(void);
 void mark_interfaces_unavailable(void);
 
index a23567c12d5edb4dd54caaffb8d6d2a6167dc37e..0f013885747a283f8277c892a6bbe960e8aef539 100644 (file)
@@ -349,7 +349,10 @@ enum dhcp_token {
        WHITESPACE = 652,
        TOKEN_ALSO = 653,
        AFTER = 654,
-        ZEROLEN = 655
+       ZEROLEN = 655,
+       TEMPORARY = 656,
+       PREFIX6 = 657,
+       FIXED_PREFIX6 = 658
 };
 
 #define is_identifier(x)       ((x) >= FIRST_TOKEN &&  \
index eca7b80b193ec071911a06fa75a1e9a966e27dc3..a367d61c6605ec6bf97baf856ad71488cd755f1c 100644 (file)
@@ -617,6 +617,30 @@ int parse_statement (cfile, group, type, host_decl, declaration)
                }
                parse_address_range6(cfile, group);
                return declaration;
+
+             case PREFIX6:
+               next_token(NULL, NULL, cfile);
+               if (type != ROOT_GROUP) {
+                       parse_warn (cfile,
+                                   "prefix6 definitions may not be scoped.");
+                       skip_to_semi(cfile);
+                       return declaration;
+               }
+               parse_prefix6(cfile, group);
+               return declaration;
+
+             case FIXED_PREFIX6:
+               next_token(&val, NULL, cfile);
+               if (!host_decl) {
+                       parse_warn (cfile,
+                                   "fixed-prefix6 declaration not "
+                                   "allowed here.");
+                       skip_to_semi(cfile);
+                       break;
+               }
+               parse_fixed_prefix6(cfile, host_decl);
+               break;
+
 #endif /* DHCPv6 */
 
              case TOKEN_NOT:
@@ -3672,7 +3696,8 @@ add_ipv6_pool_to_shared_network(struct shared_network *share,
 }
 
 /* address-range6-declaration :== ip-address6 ip-address6 SEMI
-                              | ip-address6 SLASH number SEMI */
+                              | ip-address6 SLASH number SEMI
+                              | ip-address6 TEMPORARY SEMI */
 
 void 
 parse_address_range6(struct parse *cfile, struct group *group) {
@@ -3707,7 +3732,7 @@ parse_address_range6(struct parse *cfile, struct group *group) {
        }
 
        /* 
-        * See if we we're using range or CIDR notation.
+        * See if we we're using range or CIDR notation or TEMPORARY
         */
        token = peek_token(&val, NULL, cfile);
        if (token == SLASH) {
@@ -3735,6 +3760,20 @@ parse_address_range6(struct parse *cfile, struct group *group) {
 
                add_ipv6_pool_to_shared_network(share, &lo, bits);
 
+       } else if (token == TEMPORARY) {
+               /*
+                * temporary (RFC 4941)
+                */
+               next_token(NULL, NULL, cfile);
+               bits = 64;
+               if (!is_cidr_mask_valid(&lo, bits)) {
+                       parse_warn(cfile, "network mask too short");
+                       skip_to_semi(cfile);
+                       return;
+               }
+               bits |= POOL_IS_FOR_TEMP;
+
+               add_ipv6_pool_to_shared_network(share, &lo, bits);
        } else {
                /*
                 * No '/', so we are looking for the end address of 
@@ -3769,6 +3808,196 @@ parse_address_range6(struct parse *cfile, struct group *group) {
                return;
        }
 }
+
+static void
+add_ipv6_ppool_to_global(struct iaddr *start_addr,
+                        int pool_bits,
+                        int alloc_bits) {
+       struct ipv6_ppool *ppool;
+       struct in6_addr tmp_in6_addr;
+
+       /*
+        * Create our prefix pool.
+        */
+       if (start_addr->len != sizeof(tmp_in6_addr)) {
+               log_fatal("Internal error: Attempt to add non-IPv6 prefix.");
+       }
+       memcpy(&tmp_in6_addr, start_addr->iabuf, sizeof(tmp_in6_addr));
+       ppool = NULL;
+       if (ipv6_ppool_allocate(&ppool, &tmp_in6_addr,
+                               (u_int8_t) pool_bits, (u_int8_t) alloc_bits,
+                               MDL) != ISC_R_SUCCESS) {
+               log_fatal("Out of memory");
+       }
+
+       /*
+        * Add to our IPv6 prefix pool set.
+        */
+       if (add_ipv6_ppool(ppool) != ISC_R_SUCCESS) {
+               log_fatal ("Out of memory");
+       }
+}
+
+/* prefix6-declaration :== ip-address6 ip-address6 SLASH number SEMI */
+
+void 
+parse_prefix6(struct parse *cfile, struct group *group) {
+       struct iaddr lo, hi;
+       int bits;
+       enum dhcp_token token;
+       const char *val;
+       struct iaddrcidrnetlist *nets;
+       struct iaddrcidrnetlist *p;
+
+       /*
+        * Read starting and ending address.
+        */
+       if (!parse_ip6_addr(cfile, &lo)) {
+               return;
+       }
+       if (!parse_ip6_addr(cfile, &hi)) {
+               return;
+       }
+
+       /*
+        * Next is '/' number ';'.
+        */
+       token = next_token(NULL, NULL, cfile);
+       if (token != SLASH) {
+               parse_warn(cfile, "expecting '/'");
+               if (token != SEMI)
+                       skip_to_semi(cfile);
+               return;
+       }
+       token = next_token(&val, NULL, cfile);
+       if (token != NUMBER) {
+               parse_warn(cfile, "expecting number");
+               if (token != SEMI)
+                       skip_to_semi(cfile);
+               return;
+       }
+       bits = atoi(val);
+       if ((bits <= 0) || (bits >= 128)) {
+               parse_warn(cfile, "networks have 0 to 128 bits (exclusive)");
+               return;
+       }
+       if (!is_cidr_mask_valid(&lo, bits) ||
+           !is_cidr_mask_valid(&hi, bits)) {
+               parse_warn(cfile, "network mask too short");
+               return;
+       }
+       token = next_token(NULL, NULL, cfile);
+       if (token != SEMI) {
+               parse_warn(cfile, "semicolon expected.");
+               skip_to_semi(cfile);
+               return;
+       }
+
+
+       /*
+        * Convert our range to a set of CIDR networks.
+        */
+       nets = NULL;
+       if (range2cidr(&nets, &lo, &hi) != ISC_R_SUCCESS) {
+               log_fatal("Error converting prefix to CIDR");
+       }
+
+       for (p = nets; p != NULL; p = p->next) {
+               /* Normalize and check. */
+               if (p->cidrnet.bits == 128) {
+                       p->cidrnet.bits = bits;
+               }
+               if (p->cidrnet.bits > bits) {
+                       parse_warn(cfile, "impossible mask length");
+                       continue;
+               }
+               add_ipv6_ppool_to_global(&p->cidrnet.lo_addr,
+                                        p->cidrnet.bits, bits);
+       }
+
+       free_iaddrcidrnetlist(&nets);
+}
+
+/* fixed-prefix6 :== ip6-address SLASH number SEMI */
+
+void
+parse_fixed_prefix6(struct parse *cfile, struct host_decl *host_decl) {
+       struct iaddrcidrnetlist *ia, **h;
+       enum dhcp_token token;
+       const char *val;
+
+       /*
+        * Get the head of the fixed-prefix list.
+        */
+       h = &host_decl->fixed_prefix;
+
+       /*
+        * Walk to the end.
+        */
+       while (*h != NULL) {
+               h = &((*h)->next);
+       }
+
+       /*
+        * Allocate a new iaddrcidrnetlist structure.
+        */
+       ia = dmalloc(sizeof(*ia), MDL);
+       if (!ia) {
+               log_fatal("Out of memory");
+       }
+
+       /*
+        * Parse it.
+        */
+       if (!parse_ip6_addr(cfile, &ia->cidrnet.lo_addr)) {
+               dfree(ia, MDL);
+               return;
+       }
+       token = next_token(NULL, NULL, cfile);
+       if (token != SLASH) {
+               dfree(ia, MDL);
+               parse_warn(cfile, "expecting '/'");
+               if (token != SEMI)
+                       skip_to_semi(cfile);
+               return;
+       }
+       token = next_token(&val, NULL, cfile);
+       if (token != NUMBER) {
+               dfree(ia, MDL);
+               parse_warn(cfile, "expecting number");
+               if (token != SEMI)
+                       skip_to_semi(cfile);
+               return;
+       }
+       token = next_token(NULL, NULL, cfile);
+       if (token != SEMI) {
+               dfree(ia, MDL);
+               parse_warn(cfile, "semicolon expected.");
+               skip_to_semi(cfile);
+               return;
+       }
+
+       /*
+        * Fill it.
+        */
+       ia->cidrnet.bits = atoi(val);
+       if ((ia->cidrnet.bits < 0) || (ia->cidrnet.bits > 128)) {
+               dfree(ia, MDL);
+               parse_warn(cfile, "networks have 0 to 128 bits");
+               return;
+       }
+       if (!is_cidr_mask_valid(&ia->cidrnet.lo_addr, ia->cidrnet.bits)) {
+               dfree(ia, MDL);
+               parse_warn(cfile, "network mask too short");
+               return;
+       }
+
+       /*
+        * Store it.
+        */
+       *h = ia;
+       return;
+}
 #endif /* DHCPv6 */
 
 /* allow-deny-keyword :== BOOTP
@@ -4099,7 +4328,7 @@ parse_ia_na_declaration(struct parse *cfile) {
                ia_na_add_iaaddr(ia, iaaddr, MDL);
                ia_na_reference(&iaaddr->ia_na, ia, MDL);
                pool = NULL;
-               if (find_ipv6_pool(&pool, &iaaddr->addr) != ISC_R_SUCCESS) {
+               if (find_ipv6_pool(&pool, 0, &iaaddr->addr) != ISC_R_SUCCESS) {
                        inet_ntop(AF_INET6, &iaaddr->addr, 
                                  addr_buf, sizeof(addr_buf));
                        parse_warn(cfile, "no pool found for address %s", 
@@ -4393,7 +4622,7 @@ parse_ia_ta_declaration(struct parse *cfile) {
                ia_na_add_iaaddr(ia, iaaddr, MDL);
                ia_na_reference(&iaaddr->ia_na, ia, MDL);
                pool = NULL;
-               if (find_ipv6_pool(&pool, &iaaddr->addr) != ISC_R_SUCCESS) {
+               if (find_ipv6_pool(&pool, 1, &iaaddr->addr) != ISC_R_SUCCESS) {
                        inet_ntop(AF_INET6, &iaaddr->addr, 
                                  addr_buf, sizeof(addr_buf));
                        parse_warn(cfile, "no pool found for address %s", 
index 2ac796e64aa0726dbf01ba7860c5f82c882b9a31..0b488d38df49388475696e2a0a01da6b97acfc04 100644 (file)
@@ -611,6 +611,7 @@ main(int argc, char **argv) {
         */
        if (local_family == AF_INET6) {
                mark_hosts_unavailable();
+               mark_phosts_unavailable();
                mark_interfaces_unavailable();
        }
 #endif /* DHCPv6 */
index 6fbcf40df17a9ea3abb1c5766b338b52327fe8be..221ce295020fc4afe364af0f22b755b87033073e 100644 (file)
@@ -28,7 +28,7 @@
 .\" see ``http://www.vix.com''.   To learn more about Nominum, Inc., see
 .\" ``http://www.nominum.com''.
 .\"
-.\" $Id: dhcpd.conf.5,v 1.93 2008/01/24 02:43:05 each Exp $
+.\" $Id: dhcpd.conf.5,v 1.94 2008/02/20 12:45:53 fdupont Exp $
 .\"
 .TH dhcpd.conf 5
 .SH NAME
@@ -1515,6 +1515,7 @@ single address, \fIhigh-address\fR can be omitted.
 .nf
 .B range6\fR \fIlow-address\fR \fIhigh-address\fR\fB;\fR
 .B range6\fR \fIsubnet6-number\fR\fB;\fR
+.B range6\fR \fIaddress\fR \fBtemporary\fR\fB;\fR
 .fi
 .PP
 For any IPv6 subnet6 on which addresses will be assigned dynamically, there
@@ -1524,9 +1525,33 @@ use CIDR notation, specified as ip6-address/bits. All IP addresses
 in the \fIrange6\fR should be in the subnet6 in which the
 \fIrange6\fR statement is declared.
 .PP
+The \fItemporay\fR variant makes the 64 bit prefix \fIaddress\fR available
+for temporary (RFC 4941) addresses. A new address per prefix in the shared
+network is computed at each request with an IA_TA option. Release and Confirm
+ignores temporary addresses, Renew and Rebind are not defined and raise an
+error.
+.PP
 Any IPv6 addresses given to hosts with \fIfixed-address6\fR are excluded 
 from the \fIrange6\fR, as are IPv6 addresses on the server itself.
 .PP
+.PP
+.B The
+.I prefix6
+.B statement
+.PP
+.nf
+.B prefix6\fR \fIlow-address\fR \fIhigh-address\fR \fB/\fR \fIbits\fR\fB;\fR
+.fi
+.PP
+The \fIprefix6\fR is the \fIrange6\fR equivalent for Prefix Delegation
+(RFC 3633). Prefixes of \fIbits\fR length are assigned between
+\fIlow-address\fR and \fIhigh-address\fR.
+.PP
+Any IPv6 prefixes given to static entries (hosts) with \fIfixed-prefix6\fR
+are excluded from the \fIprefix6\fR.
+.PP
+This statement is currently global but it should have a shared-network scope.
+.PP
 .B The
 .I host
 .B statement
index 4480e6cf492b471cab22dd0226a99150faf20c23..6965d8d9444e913021604f8776331bd86c316f9f 100644 (file)
@@ -49,16 +49,26 @@ struct reply_state {
 
        /* IA level persistent state */
        unsigned ia_count;
+       unsigned ia_pd_count;
        unsigned client_resources;
-       isc_boolean_t ia_addrs_included;
+       isc_boolean_t ia_resources_included;
        isc_boolean_t static_lease;
+       unsigned static_prefixes;
        struct ia_na *ia_na;
-       struct ia_na *old_ia_na;
+       struct ia_na *ia_ta;
+       union {
+               struct ia_na *old__ia;
+               struct ia_pd *old__pd;
+       } old;
+#define old_ia         old.old__ia
+#define old_ia_pd      old.old__pd
+       struct ia_pd *ia_pd;
        struct option_state *reply_ia;
        struct data_string fixed;
 
-       /* IAADDR level persistent state */
+       /* IAADDR/IAPREFIX level persistent state */
        struct iaaddr *lease;
+       struct iaprefix *prefix;
 
        /*
         * "t1", "t2", preferred, and valid lifetimes records for calculating
@@ -72,6 +82,9 @@ struct reply_state {
        /* Chosen values to transmit for valid and preferred lifetimes. */
        u_int32_t send_valid, send_prefer;
 
+       /* Preferred prefix length (-1 is any). */
+       int preflen;
+
        /* Index into the data field that has been consumed. */
        unsigned cursor;
 
@@ -90,27 +103,49 @@ static int get_encapsulated_IA_state(struct option_state **enc_opt_state,
                                     struct option_cache *oc,
                                     int offset);
 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 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_na(struct reply_state *reply,
                                        struct option_cache *ia);
+static isc_result_t reply_process_ia_ta(struct reply_state *reply,
+                                       struct option_cache *ia);
 static isc_result_t reply_process_addr(struct reply_state *reply,
                                       struct option_cache *addr);
 static isc_boolean_t address_is_owned(struct reply_state *reply,
                                      struct iaddr *addr);
+static isc_result_t find_client_temporaries(struct reply_state *reply);
 static isc_result_t reply_process_try_addr(struct reply_state *reply,
                                           struct iaddr *addr);
 static isc_result_t find_client_address(struct reply_state *reply);
 static isc_result_t reply_process_is_addressed(struct reply_state *reply,
+                                              struct ia_na *ia,
                                               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);
+static isc_result_t reply_process_ia_pd(struct reply_state *reply,
+                                       struct option_cache *ia_pd);
+static isc_result_t reply_process_prefix(struct reply_state *reply,
+                                        struct option_cache *pref);
+static isc_boolean_t prefix_is_owned(struct reply_state *reply,
+                                    struct iaddrcidrnet *pref);
+static isc_result_t find_client_prefix(struct reply_state *reply);
+static isc_result_t reply_process_try_prefix(struct reply_state *reply,
+                                            struct iaddrcidrnet *pref);
+static isc_result_t reply_process_is_prefixed(struct reply_state *reply,
+                                             struct ia_pd *ia_pd,
+                                             struct binding_scope **scope,
+                                             struct group *group);
+static isc_result_t reply_process_send_prefix(struct reply_state *reply,
+                                             struct iaddrcidrnet *pref);
+static struct iaprefix *prefix_compare(struct reply_state *reply,
+                                      struct iaprefix *alpha,
+                                      struct iaprefix *beta);
 
 /*
  * This function returns the time since DUID time start for the
@@ -664,14 +699,7 @@ static const int required_opts_solicit[] = {
        D6O_PREFERENCE,
        0
 };
-static const int required_opts_IA_NA[] = {
-       D6O_IAADDR,
-       D6O_STATUS_CODE,
-       D6O_VENDOR_OPTS,
-       0
-};
-/*
-static const int required_opts_IA_TA[] = {
+static const int required_opts_IA[] = {
        D6O_IAADDR,
        D6O_STATUS_CODE,
        D6O_VENDOR_OPTS,
@@ -683,7 +711,6 @@ static const int required_opts_IA_PD[] = {
        D6O_VENDOR_OPTS,
        0
 };
-*/
 static const int required_opts_STATUS_CODE[] = {
        D6O_STATUS_CODE,
        0
@@ -973,13 +1000,25 @@ pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network,
        char tmp_buf[INET6_ADDRSTRLEN];
 
        /*
-        * No pools, we're done.
+        * No pools or all temporary, we're done.
         */
        if (shared_network->ipv6_pools == NULL) {
                log_debug("Unable to pick client address: "
                          "no IPv6 pools on this shared network");
                return ISC_R_NORESOURCES;
        }
+       for (i = 0;; i++) {
+               p = shared_network->ipv6_pools[i];
+               if (p == NULL) {
+                       log_debug("Unable to pick client address: "
+                                 "only temporary IPv6 pools "
+                                 "on this shared network");
+                       return ISC_R_NORESOURCES;
+               }
+               if ((p->bits & POOL_IS_FOR_TEMP) == 0) {
+                       break;
+               }
+       }
 
        /*
         * Otherwise try to get a lease from the first subnet possible.
@@ -993,8 +1032,9 @@ pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network,
        do {
 
                p = shared_network->ipv6_pools[i];
-               if (activate_lease6(p, addr, &attempts, 
-                                   client_id, 0) == ISC_R_SUCCESS) {
+               if (((p->bits & POOL_IS_FOR_TEMP) == 0) &&
+                   (activate_lease6(p, addr, &attempts, 
+                                    client_id, 0) == ISC_R_SUCCESS)) {
                        /*
                         * Record the pool used (or next one if there 
                         * was a collision).
@@ -1027,6 +1067,125 @@ pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network,
        return ISC_R_NORESOURCES;
 }
 
+/*
+ * Try to get the IPv6 prefix the client asked for from the
+ * prefix pool.
+ *
+ * pref is the result (should be a pointer to NULL on entry)
+ * ppool is the prefix pool to search in
+ * requested_pref is the address the client wants
+ */
+static isc_result_t
+try_client_v6_prefix(struct iaprefix **pref,
+                    struct ipv6_ppool *ppool,
+                    const struct data_string *requested_pref)
+{
+       u_int8_t tmp_plen;
+       struct in6_addr tmp_pref;
+       struct iaddr ia;
+       isc_result_t result;
+
+       if (requested_pref->len < sizeof(tmp_plen) + sizeof(tmp_pref)) {
+               return ISC_R_INVALIDARG;
+       }
+       tmp_plen = (int) requested_pref->data[0];
+       if ((tmp_plen < 3) || (tmp_plen > 128)) {
+               return ISC_R_FAILURE;
+       }
+       memcpy(&tmp_pref, requested_pref->data + 1, sizeof(tmp_pref));
+       if (IN6_IS_ADDR_UNSPECIFIED(&tmp_pref)) {
+               return ISC_R_FAILURE;
+       }
+       ia.len = 16;
+       memcpy(&ia.iabuf, &tmp_pref, 16);
+       if (!is_cidr_mask_valid(&ia, (int) tmp_plen)) {
+               return ISC_R_FAILURE;
+       }
+
+       if ((tmp_plen != ppool->alloc_plen) ||
+           !ipv6_prefix_in_ppool(&tmp_pref, ppool)) {
+               return ISC_R_FAILURE;
+       }
+
+       if (prefix6_exists(ppool, &tmp_pref, tmp_plen)) {
+               return ISC_R_ADDRINUSE;
+       }
+
+       result = iaprefix_allocate(pref, MDL);
+       if (result != ISC_R_SUCCESS) {
+               return result;
+       }
+       (*pref)->pref = tmp_pref;
+       (*pref)->plen = tmp_plen;
+
+       result = add_prefix6(ppool, *pref, 0);
+       if (result != ISC_R_SUCCESS) {
+               iaprefix_dereference(pref, MDL);
+       }
+       return result;
+}
+
+/*
+ * Get an IPv6 prefix for the client.
+ *
+ * pref is the result (should be a pointer to NULL on entry)
+ * packet is the information about the packet from the client
+ * requested_iaprefix is a hint from the client
+ * plen is -1 or the requested prefix length
+ * client_id is the DUID for the client
+ */
+static isc_result_t 
+pick_v6_prefix(struct iaprefix **pref, int plen,
+              const struct data_string *client_id)
+{
+       struct ipv6_ppool *p;
+       int i;
+       unsigned int attempts;
+       char tmp_buf[INET6_ADDRSTRLEN];
+
+       /*
+        * No prefix pools, we're done.
+        */
+       if (!num_ppools) {
+               log_debug("Unable to pick client prefix: "
+                         "no IPv6 prefix pools");
+               return ISC_R_NORESOURCES;
+       }
+
+       /*
+        * Otherwise try to get a prefix.
+        */
+       for (i = 0; i < num_ppools; i++) {
+               p = ppools[i];
+               if (p == NULL) {
+                       continue;
+               }
+
+               /*
+                * Try only pools with the requested prefix length if any.
+                */
+               if ((plen >= 0) && ((int) p->alloc_plen != plen)) {
+                       continue;
+               }
+
+               if (activate_prefix6(p, pref, &attempts, 
+                                    client_id, 0) == ISC_R_SUCCESS) {
+                       log_debug("Picking pool prefix %s/%u",
+                                 inet_ntop(AF_INET6, &((*pref)->pref),
+                                           tmp_buf, sizeof(tmp_buf)),
+                                 (unsigned) (*pref)->plen);
+                       return ISC_R_SUCCESS;
+               }
+       }
+
+       /*
+        * If we failed to pick an IPv6 prefix
+        * Presumably that means we have no prefixes for the client.
+        */
+       log_debug("Unable to pick client prefix: no prefixes available");
+       return ISC_R_NORESOURCES;
+}
+
 /*
  * lease_to_client() is called from several messages to construct a
  * reply that contains all that we know about the client's correct lease
@@ -1060,12 +1219,10 @@ lease_to_client(struct data_string *reply_ret,
        static struct reply_state reply;
        struct option_cache *oc;
        struct data_string packet_oro;
-       isc_boolean_t no_addrs_avail;
+       isc_boolean_t no_resources_avail;
 
        /* Locate the client.  */
-       if (shared_network_from_packet6(&reply.shared,
-                                       packet) != ISC_R_SUCCESS)
-               goto exit;
+       shared_network_from_packet6(&reply.shared, packet);
 
        /* 
         * Initialize the reply.
@@ -1100,25 +1257,33 @@ lease_to_client(struct data_string *reply_ret,
         * valid for the shared network the client is on.
         */
        if (find_hosts_by_option(&reply.host, packet, packet->options, MDL)) {
-               seek_shared_host(&reply.host, reply.shared);
+               if (reply.shared != NULL) {
+                       seek_shared_host(&reply.host, reply.shared);
+               }
        }
 
        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 (reply.shared != NULL) {
+                       seek_shared_host(&reply.host, reply.shared);
+               }
        }
 
-       /* Process the client supplied IA_NA's onto the reply buffer. */
+       /* Process the client supplied IA's onto the reply buffer. */
        reply.ia_count = 0;
        oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
-       no_addrs_avail = ISC_FALSE;
+       no_resources_avail = ISC_FALSE;
        for (; oc != NULL ; oc = oc->next) {
                isc_result_t status;
 
+               /* A shared network is required. */
+               if (reply.shared == NULL)
+                       goto exit;
+
                /* Start counting resources (addresses) offered. */
                reply.client_resources = 0;
-               reply.ia_addrs_included = ISC_FALSE;
+               reply.ia_resources_included = ISC_FALSE;
 
                status = reply_process_ia_na(&reply, oc);
 
@@ -1131,21 +1296,76 @@ lease_to_client(struct data_string *reply_ret,
                        goto exit;
 
                /*
-                * If any address can be given to any IA, then do not set the
+                * If any address cannot be given to any IA, then set the
+                * NoAddrsAvail status code.
+                */
+               if (reply.client_resources == 0)
+                       no_resources_avail = ISC_TRUE;
+       }
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
+       for (; oc != NULL ; oc = oc->next) {
+               isc_result_t status;
+
+               /* A shared network is required. */
+               if (reply.shared == NULL)
+                       goto exit;
+
+               /* Start counting resources (addresses) offered. */
+               reply.client_resources = 0;
+               reply.ia_resources_included = ISC_FALSE;
+
+               status = reply_process_ia_ta(&reply, oc);
+
+               /*
+                * We continue to try other IA's whether we can address
+                * this one or not.  Any other result is an immediate fail.
+                */
+               if ((status != ISC_R_SUCCESS) &&
+                   (status != ISC_R_NORESOURCES))
+                       goto exit;
+
+               /*
+                * If any address cannot be given to any IA, then set the
                 * NoAddrsAvail status code.
                 */
                if (reply.client_resources == 0)
-                       no_addrs_avail = ISC_TRUE;
+                       no_resources_avail = ISC_TRUE;
        }
 
-       /* Do IA_TA and IA_PD */
+       /* Same for IA_PD's. */
+       reply.ia_pd_count = 0;
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+       for (; oc != NULL ; oc = oc->next) {
+               isc_result_t status;
+
+               /* Start counting resources (prefixes) offered. */
+               reply.client_resources = 0;
+               reply.ia_resources_included = ISC_FALSE;
+
+               status = reply_process_ia_pd(&reply, oc);
+
+               /*
+                * We continue to try other IA_PD's whether we can address
+                * this one or not.  Any other result is an immediate fail.
+                */
+               if ((status != ISC_R_SUCCESS) &&
+                   (status != ISC_R_NORESOURCES))
+                       goto exit;
+
+               /*
+                * If any prefix cannot be given to any IA_PD, then
+                * set the NoPrefixAvail status code.
+                */
+               if (reply.client_resources == 0)
+                       no_resources_avail = ISC_TRUE;
+       }
 
        /*
         * Make no reply if we gave no resources and is not
         * for Information-Request.
         */
-       if ((reply.ia_count == 0) &&
-           (packet->dhcpv6_msg_type != DHCPV6_INFORMATION_REQUEST))
+       if ((reply.ia_count == 0) && (reply.ia_pd_count == 0) &&
+           (reply.packet->dhcpv6_msg_type != DHCPV6_INFORMATION_REQUEST))
                goto exit;
 
        /*
@@ -1172,7 +1392,7 @@ lease_to_client(struct data_string *reply_ret,
         * the server.
         * Sends a Renew/Rebind if the IA is not in the Reply message.
         */
-       if (no_addrs_avail &&
+       if (no_resources_avail && (reply.ia_count != 0) &&
            (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT))
        {
                /* Set the NoAddrsAvail status code. */
@@ -1187,6 +1407,36 @@ lease_to_client(struct data_string *reply_ret,
                /* Rewind the cursor to the start. */
                reply.cursor = REPLY_OPTIONS_INDEX;
 
+               /*
+                * Produce an advertise that includes only:
+                *
+                * Status code.
+                * Server DUID.
+                * Client DUID.
+                */
+               reply.buf.reply.msg_type = DHCPV6_ADVERTISE;
+               reply.cursor += store_options6((char *)reply.buf.data +
+                                                       reply.cursor,
+                                              sizeof(reply.buf) -
+                                                       reply.cursor,
+                                              reply.opt_state, reply.packet,
+                                              required_opts_NAA,
+                                              NULL);
+       } else if (no_resources_avail && (reply.ia_count == 0) &&
+                  (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT))
+       {
+               /* Set the NoPrefixAvail status code. */
+               if (!set_status_code(STATUS_NoPrefixAvail,
+                                    "No prefixes available for this "
+                                    "interface.", reply.opt_state)) {
+                       log_error("lease_to_client: Unable to set "
+                                 "NoPrefixAvail status code.");
+                       goto exit;
+               }
+
+               /* Rewind the cursor to the start. */
+               reply.cursor = REPLY_OPTIONS_INDEX;
+
                /*
                 * Produce an advertise that includes only:
                 *
@@ -1204,7 +1454,7 @@ lease_to_client(struct data_string *reply_ret,
                                               NULL);
        } else {
                /*
-                * Having stored the client's IA_NA's, store any options that
+                * Having stored the client's IA's, store any options that
                 * will fit in the remaining space.
                 */
                reply.cursor += store_options6((char *)reply.buf.data +
@@ -1293,7 +1543,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
        reply->ia_na->ia_type = D6O_IA_NA;
 
        /* Cache pre-existing IA, if any. */
-       ia_na_hash_lookup(&reply->old_ia_na, ia_na_active,
+       ia_na_hash_lookup(&reply->old_ia, ia_na_active,
                          (unsigned char *)reply->ia_na->iaid_duid.data,
                          reply->ia_na->iaid_duid.len, MDL);
 
@@ -1396,9 +1646,10 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                        switch (reply->packet->dhcpv6_msg_type) {
                              case DHCPV6_SOLICIT:
                                /*
-                                * No address for all the IA's is handled
+                                * No address for any IA is handled
                                 * by the caller.
                                 */
+                               /* FALL THROUGH */
 
                              case DHCPV6_REQUEST:
                                /* Section 18.2.1 (Request):
@@ -1452,7 +1703,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                                 * provide zero addresses including zeroed
                                 * lifetimes.
                                 */
-                               if (reply->ia_addrs_included)
+                               if (reply->ia_resources_included)
                                        status = ISC_R_SUCCESS;
                                else
                                        goto cleanup;
@@ -1467,7 +1718,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
        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);
+                                       required_opts_IA, NULL);
 
        /* Reset the length of this IA to match what was just written. */
        putUShort(reply->buf.data + ia_cursor + 2,
@@ -1561,12 +1812,12 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                }
 
                /* Remove any old ia_na from the hash. */
-               if (reply->old_ia_na != NULL) {
-                       ia_id = &reply->old_ia_na->iaid_duid;
+               if (reply->old_ia != NULL) {
+                       ia_id = &reply->old_ia->iaid_duid;
                        ia_na_hash_delete(ia_na_active,
                                          (unsigned char *)ia_id->data,
                                          ia_id->len, MDL);
-                       ia_na_dereference(&reply->old_ia_na, MDL);
+                       ia_na_dereference(&reply->old_ia, MDL);
                }
 
                /* Put new ia_na into the hash. */
@@ -1588,8 +1839,8 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
         * suggesting the existing database entry to the client.
         */
        } else if ((status != ISC_R_CANCELED) && !reply->static_lease &&
-           (reply->old_ia_na != NULL)) {
-               if (ia_na_equal(reply->old_ia_na, reply->ia_na)) {
+           (reply->old_ia != NULL)) {
+               if (ia_na_equal(reply->old_ia, reply->ia_na)) {
                        lease_in_database = ISC_TRUE;
                }
        }
@@ -1605,8 +1856,8 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                data_string_forget(&data, MDL);
        if (reply->ia_na != NULL)
                ia_na_dereference(&reply->ia_na, MDL);
-       if (reply->old_ia_na != NULL)
-               ia_na_dereference(&reply->old_ia_na, MDL);
+       if (reply->old_ia != NULL)
+               ia_na_dereference(&reply->old_ia, MDL);
        if (reply->lease != NULL) {
                if (!lease_in_database) {
                        release_lease6(reply->lease->ipv6_pool, reply->lease);
@@ -1890,7 +2141,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
                        goto cleanup;
        }
 
-       status = reply_process_is_addressed(reply, scope, group);
+       status = reply_process_is_addressed(reply, reply->ia_na, scope, group);
        if (status != ISC_R_SUCCESS)
                goto cleanup;
 
@@ -1931,13 +2182,13 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) {
                return ISC_FALSE;
        }
 
-       if ((reply->old_ia_na == NULL) || (reply->old_ia_na->num_iaaddr == 0))
+       if ((reply->old_ia == NULL) || (reply->old_ia->num_iaaddr == 0))
                return ISC_FALSE;
 
-       for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) {
+       for (i = 0 ; i < reply->old_ia->num_iaaddr ; i++) {
                struct iaaddr *tmp;
 
-               tmp = reply->old_ia_na->iaaddr[i];
+               tmp = reply->old_ia->iaaddr[i];
 
                if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) {
                        iaaddr_reference(&reply->lease, tmp, MDL);
@@ -1948,126 +2199,1467 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) {
        return ISC_FALSE;
 }
 
-/*
- * This function only returns failure on 'hard' failures.  If it succeeds,
- * it will leave a lease structure behind.
+/* Process a client-supplied IA_TA.  This may append options to the tail of
+ * the reply packet being built in the reply_state structure.
  */
 static isc_result_t
-reply_process_try_addr(struct reply_state *reply, struct iaddr *addr) {
-       isc_result_t status = ISC_R_FAILURE;
-       struct ipv6_pool *pool;
-       int i;
-       struct data_string data_addr;
-
-       if ((reply == NULL) || (reply->shared == NULL) ||
-           (reply->shared->ipv6_pools == NULL) || (addr == NULL) ||
-           (reply->lease != NULL))
-               return ISC_R_INVALIDARG;
+reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
+       isc_result_t status = ISC_R_SUCCESS;
+       u_int32_t iaid;
+       unsigned ia_cursor;
+       struct option_state *packet_ia;
+       struct option_cache *oc;
+       struct data_string ia_data, data;
+       struct data_string iaaddr;
+       u_int32_t pref_life, valid_life;
 
-       memset(&data_addr, 0, sizeof(data_addr));
-       data_addr.len = addr->len;
-       data_addr.data = addr->iabuf;
+       /* Initialize values that will get cleaned up on return. */
+       packet_ia = NULL;
+       memset(&ia_data, 0, sizeof(ia_data));
+       memset(&data, 0, sizeof(data));
+       memset(&iaaddr, 0, sizeof(iaaddr));
 
-       for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
-               status = try_client_v6_address(&reply->lease, pool,
-                                              &data_addr);
-               if (status == ISC_R_SUCCESS)
-                       break;
+       /* Make sure there is at least room for the header. */
+       if ((reply->cursor + IA_TA_OFFSET + 4) > sizeof(reply->buf)) {
+               log_error("reply_process_ia_ta: Reply too long for IA.");
+               return ISC_R_NOSPACE;
        }
 
-       /* Note that this is just pedantry.  There is no allocation to free. */
-       data_string_forget(&data_addr, MDL);
-       /* Return just the most recent status... */
-       return status;
-}
-
-/* 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;
+       /* Fetch the IA_TA contents. */
+       if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
+                                      ia, IA_TA_OFFSET)) {
+               log_error("reply_process_ia_ta: error evaluating ia_ta");
+               status = ISC_R_FAILURE;
+               goto cleanup;
        }
 
-       if (reply->old_ia_na != NULL)  {
-               for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) {
-                       lease = reply->old_ia_na->iaaddr[i];
+       /* Extract IA_TA header contents. */
+       iaid = getULong(ia_data.data);
 
-                       best_lease = lease_compare(lease, best_lease);
-               }
+       /* Create an IA_TA structure. */
+       if (ia_na_allocate(&reply->ia_ta, iaid, (char *)reply->client_id.data, 
+                          reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+               log_error("lease_to_client: no memory for ia_ta.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
        }
+       reply->ia_ta->ia_type = D6O_IA_TA;
 
-       /* Try to pick a new address if we didn't find one, or if we found an
-        * abandoned lease.
+       /* Cache pre-existing IA, if any. */
+       ia_na_hash_lookup(&reply->old_ia, ia_ta_active,
+                         (unsigned char *)reply->ia_ta->iaid_duid.data,
+                         reply->ia_ta->iaid_duid.len, MDL);
+
+       /*
+        * Create an option cache to carry the IA_TA option contents, and
+        * execute any user-supplied values into it.
         */
-       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 (!option_state_allocate(&reply->reply_ia, MDL)) {
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
        }
 
-       /* 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); */
-       }
+       /*
+        * Temporary leases are dynamic by definition.
+        */
+       reply->static_lease = ISC_FALSE;
 
-       /* Give up now if we didn't find a lease. */
+       /*
+        * Save the cursor position at the start of the IA, so we can
+        * set length later.  We write a temporary
+        * header out now just in case we decide to adjust the packet
+        * within sub-process functions.
+        */
+       ia_cursor = reply->cursor;
+
+       /* Initialize the IA_TA header.  First the code. */
+       putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_TA);
+       reply->cursor += 2;
+
+       /* Then option length. */
+       putUShort(reply->buf.data + reply->cursor, 0x04u);
+       reply->cursor += 2;
+
+       /* Then IA_TA header contents; IAID. */
+       putULong(reply->buf.data + reply->cursor, iaid);
+       reply->cursor += 4;
+
+       /* 
+        * Deal with an IAADDR for lifetimes.
+        */
+       reply->valid = reply->prefer = 0xffffffff;
+       reply->client_valid = reply->client_prefer = 0;
+       oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
+       if (oc != NULL) {
+               if (!evaluate_option_cache(&iaaddr, reply->packet,
+                                          NULL, NULL,
+                                          reply->packet->options, NULL,
+                                          &global_scope, oc, MDL) ||
+                   (iaaddr.len < IAADDR_OFFSET)) {
+                       log_error("reply_process_ia_ta: error "
+                                 "evaluating IAADDR.");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+               /* The first 16 bytes are the IPv6 address. */
+               pref_life = getULong(iaaddr.data + 16);
+               valid_life = getULong(iaaddr.data + 20);
+
+               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;
+       }
+       reply->ia_count++;
+
+       /*
+        * Cancel if not Solicit or Request.
+        */
+       if ((reply->packet->dhcpv6_msg_type != DHCPV6_SOLICIT) &&
+           (reply->packet->dhcpv6_msg_type != DHCPV6_REQUEST)) {
+               if (!set_status_code(STATUS_UnspecFail,
+                                    "Unsupported message for temporary.",
+                                    reply->reply_ia)) {
+                       log_error("reply_process_ia_ta: Unable to set "
+                                 "UnspecFail status code.");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+               status = ISC_R_CANCELED;
+               goto store;
+       }
+
+       /*
+        * Give the client temporary addresses.
+        */
+       status = find_client_temporaries(reply);
+       if (status == ISC_R_NORESOURCES) {
+               switch (reply->packet->dhcpv6_msg_type) {
+                     case DHCPV6_SOLICIT:
+                       /*
+                        * No address for any IA is handled
+                        * by the caller.
+                        */
+                       /* FALL THROUGH */
+
+                     case DHCPV6_REQUEST:
+                       /* Section 18.2.1 (Request):
+                        *
+                        * If the server cannot assign any addresses to
+                        * an IA in the message from the client, the
+                        * server MUST include the IA in the Reply
+                        * message with no addresses in the IA and a
+                        * Status Code option in the IA containing
+                        * status code NoAddrsAvail.
+                        */
+                       option_state_dereference(&reply->reply_ia, MDL);
+                       if (!option_state_allocate(&reply->reply_ia,  MDL)) {
+                               log_error("reply_process_ia_ta: No "
+                                         "memory for option state wipe.");
+                               status = ISC_R_NOMEMORY;
+                               goto cleanup;
+                       }
+
+                       if (!set_status_code(STATUS_NoAddrsAvail,
+                                            "No addresses available "
+                                            "for this interface.",
+                                             reply->reply_ia)) {
+                               log_error("reply_process_ia_ta: Unable "
+                                         "to set NoAddrsAvail status code.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
+                       }
+
+                       status = ISC_R_SUCCESS;
+                       break;
+
+                     default:
+                       /* Should not happen! */
+                       goto cleanup;
+               }
+       } else if (status != ISC_R_SUCCESS)
+               goto cleanup;
+
+      store:
+       reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+                                       sizeof(reply->buf) - reply->cursor,
+                                       reply->reply_ia, reply->packet,
+                                       required_opts_IA, 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));
+
+       /*
+        * Consume the new changes into the database (if any have been
+        * attached to the ia_ta).
+        *
+        * Loop through the assigned dynamic addresses, referencing the
+        * leases onto this IA_TA rather than any old ones, and updating
+        * pool timers for each (if any).
+        */
+       if ((status != ISC_R_CANCELED) &&
+           (reply->buf.reply.msg_type == DHCPV6_REPLY) &&
+           (reply->ia_ta->num_iaaddr != 0)) {
+               struct iaaddr *tmp;
+               struct data_string *ia_id;
+               int i;
+
+               for (i = 0 ; i < reply->ia_ta->num_iaaddr ; i++) {
+                       tmp = reply->ia_ta->iaaddr[i];
+
+                       if (tmp->ia_na != NULL)
+                               ia_na_dereference(&tmp->ia_na, MDL);
+                       ia_na_reference(&tmp->ia_na, reply->ia_ta, MDL);
+
+                       schedule_lease_timeout(tmp->ipv6_pool);
+
+                       /*
+                        * Perform ddns updates.
+                        */
+                       oc = lookup_option(&server_universe, reply->opt_state,
+                                          SV_DDNS_UPDATES);
+                       if ((oc == NULL) ||
+                           evaluate_boolean_option_cache(NULL, reply->packet,
+                                                         NULL, NULL,
+                                                       reply->packet->options,
+                                                         reply->opt_state,
+                                                         &tmp->scope,
+                                                         oc, MDL)) {
+                               ddns_updates(reply->packet, NULL, NULL,
+                                            tmp, NULL, reply->opt_state);
+                       }
+               }
+
+               /* Remove any old ia_ta from the hash. */
+               if (reply->old_ia != NULL) {
+                       ia_id = &reply->old_ia->iaid_duid;
+                       ia_na_hash_delete(ia_ta_active,
+                                         (unsigned char *)ia_id->data,
+                                         ia_id->len, MDL);
+                       ia_na_dereference(&reply->old_ia, MDL);
+               }
+
+               /* Put new ia_ta into the hash. */
+               ia_id = &reply->ia_ta->iaid_duid;
+               ia_na_hash_add(ia_ta_active, (unsigned char *)ia_id->data,
+                              ia_id->len, reply->ia_ta, MDL);
+
+               write_ia(reply->ia_ta);
+       }
+
+      cleanup:
+       if (packet_ia != NULL)
+               option_state_dereference(&packet_ia, MDL);
+       if (iaaddr.data != NULL)
+               data_string_forget(&iaaddr, MDL);
+       if (reply->reply_ia != NULL)
+               option_state_dereference(&reply->reply_ia, MDL);
+       if (ia_data.data != NULL)
+               data_string_forget(&ia_data, MDL);
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (reply->ia_ta != NULL)
+               ia_na_dereference(&reply->ia_ta, MDL);
+       if (reply->old_ia != NULL)
+               ia_na_dereference(&reply->old_ia, 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);
+}
+
+/*
+ * Get a temporary address per prefix.
+ */
+static isc_result_t
+find_client_temporaries(struct reply_state *reply) {
+       struct shared_network *shared;
+       int i;
+       struct ipv6_pool *p;
+       isc_result_t status;
+       unsigned int attempts;
+       struct iaddr send_addr;
+
+       /*
+        * No pools, we're done.
+        */
+       shared = reply->shared;
+       if (shared->ipv6_pools == NULL) {
+               log_debug("Unable to get client addresses: "
+                         "no IPv6 pools on this shared network");
+               return ISC_R_NORESOURCES;
+       }
+
+       status = ISC_R_NORESOURCES;
+       for (i = 0;; i++) {
+               p = shared->ipv6_pools[i];
+               if (p == NULL) {
+                       break;
+               }
+               if ((p->bits & POOL_IS_FOR_TEMP) == 0) {
+                       continue;
+               }
+
+               /*
+                * Get an address in this temporary pool.
+                */
+               status = activate_lease6(p, &reply->lease, &attempts,
+                                        &reply->client_id, 0);
+               if (status != ISC_R_SUCCESS) {
+                       log_debug("Unable to get a temporary address.");
+                       goto cleanup;
+               }
+
+               status = reply_process_is_addressed(reply,
+                                                   reply->ia_ta,
+                                                   &reply->lease->scope,
+                                                   reply->shared->group);
+               if (status != ISC_R_SUCCESS) {
+                       goto cleanup;
+               }
+               send_addr.len = 16;
+               memcpy(send_addr.iabuf, &reply->lease->addr, 16);
+               status = reply_process_send_addr(reply, &send_addr);
+               if (status != ISC_R_SUCCESS) {
+                       goto cleanup;
+               }
+               if (reply->lease != NULL) {
+                       iaaddr_dereference(&reply->lease, MDL);
+               }
+       }
+
+      cleanup:
+       if (reply->lease != NULL) {
+               iaaddr_dereference(&reply->lease, MDL);
+       }
+       return status;
+}
+
+/*
+ * This function only returns failure on 'hard' failures.  If it succeeds,
+ * it will leave a lease structure behind.
+ */
+static isc_result_t
+reply_process_try_addr(struct reply_state *reply, struct iaddr *addr) {
+       isc_result_t status = ISC_R_FAILURE;
+       struct ipv6_pool *pool;
+       int i;
+       struct data_string data_addr;
+
+       if ((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++) {
+               status = try_client_v6_address(&reply->lease, pool,
+                                              &data_addr);
+               if (status == ISC_R_SUCCESS)
+                       break;
+       }
+
+       /* Note that this is just pedantry.  There is no allocation to free. */
+       data_string_forget(&data_addr, MDL);
+       /* Return just the most recent status... */
+       return status;
+}
+
+/* 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 (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);
+               }
+       }
+
+       /* 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;
+       }
+
+       /* 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); */
+       }
+
+       /* 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, reply->ia_na, 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 ia_na *ia,
+                          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,
+                                          scope, oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_is_addressed: unable to "
+                                 "evaluate default lease time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_valid = getULong(data.data);
+               data_string_forget(&data, MDL);
+       }
+
+       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,
+                                          scope, oc, MDL) ||
+                   (data.len != 4)) {
+                       log_error("reply_process_is_addressed: unable to "
+                                 "evaluate preferred lease time");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+
+               reply->send_prefer = getULong(data.data);
+               data_string_forget(&data, MDL);
+       }
+
+       /* Note lowest values for later calculation of renew/rebind times. */
+       if (reply->prefer > reply->send_prefer)
+               reply->prefer = reply->send_prefer;
+
+       if (reply->valid > reply->send_valid)
+               reply->valid = reply->send_valid;
+
+#if 0
+       /*
+        * XXX: Old 4.0.0 alpha code would change the host {} record
+        * XXX: uid upon lease assignment.  This was intended to cover the
+        * XXX: case where a client first identifies itself using vendor
+        * XXX: options in a solicit, or request, but later neglects to include
+        * XXX: these options in a Renew or Rebind.  It is not clear that this
+        * XXX: is required, and has some startling ramifications (such as
+        * XXX: how to recover this dynamic host {} state across restarts).
+        */
+       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(ia, reply->lease, MDL);
+               if (status != ISC_R_SUCCESS) {
+                       log_fatal("reply_process_is_addressed: Unable to "
+                                 "attach lease to new IA: %s",
+                                 isc_result_totext(status));
+               }
+
+               /*
+                * If this is a new lease, make sure it is attached somewhere.
+                */
+               if (reply->lease->ia_na == NULL) {
+                       ia_na_reference(&reply->lease->ia_na, ia, MDL);
+               }
+       }
+
+       /* Bring a copy of the relevant options into the IA scope. */
+       execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
+                                   reply->packet->options, reply->reply_ia,
+                                   scope, group, root_group);
+
+      cleanup:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+
+       if (status == ISC_R_SUCCESS)
+               reply->client_resources++;
+
+       return status;
+}
+
+/* Simply send an IAADDR within the IA scope as described. */
+static isc_result_t
+reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) {
+       isc_result_t status = ISC_R_SUCCESS;
+       struct data_string data;
+
+       memset(&data, 0, sizeof(data));
+
+       /* Now append the lease. */
+       data.len = IAADDR_OFFSET;
+       if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+               log_error("reply_process_send_addr: out of memory"
+                         "allocating new IAADDR buffer.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+       data.data = data.buffer->data;
+
+       memcpy(data.buffer->data, addr->iabuf, 16);
+       putULong(data.buffer->data + 16, reply->send_prefer);
+       putULong(data.buffer->data + 20, reply->send_valid);
+
+       if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
+                                 data.buffer, data.buffer->data,
+                                 data.len, D6O_IAADDR, 0)) {
+               log_error("reply_process_send_addr: unable "
+                         "to save IAADDR option");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       reply->ia_resources_included = ISC_TRUE;
+
+      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;
+}
+
+/* Process a client-supplied IA_PD.  This may append options to the tail of
+ * the reply packet being built in the reply_state structure.
+ */
+static isc_result_t
+reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia_pd) {
+       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_pd_data, data;
+       isc_boolean_t prefix_in_database;
+
+       /* Initialize values that will get cleaned up on return. */
+       packet_ia = NULL;
+       memset(&ia_pd_data, 0, sizeof(ia_pd_data));
+       memset(&data, 0, sizeof(data));
+       prefix_in_database = ISC_FALSE;
+       /* 
+        * Note that find_client_prefix() may set reply->prefix. 
+        */
+
+       /* Make sure there is at least room for the header. */
+       if ((reply->cursor + IA_PD_OFFSET + 4) > sizeof(reply->buf)) {
+               log_error("reply_process_ia_pd: Reply too long for IA.");
+               return ISC_R_NOSPACE;
+       }
+
+
+       /* Fetch the IA_PD contents. */
+       if (!get_encapsulated_IA_state(&packet_ia, &ia_pd_data, reply->packet,
+                                      ia_pd, IA_PD_OFFSET)) {
+               log_error("reply_process_ia_pd: error evaluating ia_pd");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       /* Extract IA_PD header contents. */
+       iaid = getULong(ia_pd_data.data);
+       reply->renew = getULong(ia_pd_data.data + 4);
+       reply->rebind = getULong(ia_pd_data.data + 8);
+
+       /* Create an IA_PD structure. */
+       if (ia_pd_allocate(&reply->ia_pd, iaid, (char *)reply->client_id.data, 
+                          reply->client_id.len, MDL) != ISC_R_SUCCESS) {
+               log_error("reply_process_ia_pd: no memory for ia_pd.");
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+
+       /* Cache pre-existing IA_PD, if any. */
+       ia_pd_hash_lookup(&reply->old_ia_pd, ia_pd_active,
+                         (unsigned char *)reply->ia_pd->iaid_duid.data,
+                         reply->ia_pd->iaid_duid.len, MDL);
+
+       /*
+        * Create an option cache to carry the IA_PD option contents, and
+        * execute any user-supplied values into it.
+        */
+       if (!option_state_allocate(&reply->reply_ia, MDL)) {
+               status = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+
+       /* Check & count the fixed prefix host records. */
+       reply->static_prefixes = 0;
+       if ((reply->host != NULL) && (reply->host->fixed_prefix != NULL)) {
+               struct iaddrcidrnetlist *fp;
+
+               for (fp = reply->host->fixed_prefix; fp != NULL;
+                    fp = fp->next) {
+                       reply->static_prefixes += 1;
+               }
+       }
+
+       /*
+        * Save the cursor position at the start of the IA_PD, so we can
+        * set length and adjust t1/t2 values later.  We write a temporary
+        * header out now just in case we decide to adjust the packet
+        * within sub-process functions.
+        */
+       ia_cursor = reply->cursor;
+
+       /* Initialize the IA_PD header.  First the code. */
+       putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_PD);
+       reply->cursor += 2;
+
+       /* Then option length. */
+       putUShort(reply->buf.data + reply->cursor, 0x0Cu);
+       reply->cursor += 2;
+
+       /* Then IA_PD header contents; IAID. */
+       putULong(reply->buf.data + reply->cursor, iaid);
+       reply->cursor += 4;
+
+       /* We store the client's t1 for now, and may over-ride it later. */
+       putULong(reply->buf.data + reply->cursor, reply->renew);
+       reply->cursor += 4;
+
+       /* We store the client's t2 for now, and may over-ride it later. */
+       putULong(reply->buf.data + reply->cursor, reply->rebind);
+       reply->cursor += 4;
+
+       /* 
+        * For each prefix in this IA_PD, decide what to do about it.
+        */
+       oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAPREFIX);
+       reply->valid = reply->prefer = 0xffffffff;
+       reply->client_valid = reply->client_prefer = 0;
+       reply->preflen = -1;
+       for (; oc != NULL ; oc = oc->next) {
+               status = reply_process_prefix(reply, oc);
+
+               /*
+                * Canceled means we did not allocate prefixes to the
+                * client, but we're "done" with this IA - we set a status
+                * code.  So transmit this reply, e.g., move on to the next
+                * IA.
+                */
+               if (status == ISC_R_CANCELED)
+                       break;
+
+               if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE))
+                       goto cleanup;
+       }
+
+       reply->ia_pd_count++;
+
+       /*
+        * If we fell through the above and never gave the client
+        * a prefix, give it one now.
+        */
+       if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) {
+               status = find_client_prefix(reply);
+
+               if (status == ISC_R_NORESOURCES) {
+                       switch (reply->packet->dhcpv6_msg_type) {
+                             case DHCPV6_SOLICIT:
+                               /*
+                                * No prefix for any IA is handled
+                                * by the caller.
+                                */
+                               /* FALL THROUGH */
+
+                             case DHCPV6_REQUEST:
+                               /* Same than for addresses. */
+                               option_state_dereference(&reply->reply_ia, MDL);
+                               if (!option_state_allocate(&reply->reply_ia,
+                                                          MDL))
+                               {
+                                       log_error("reply_process_ia_pd: No "
+                                                 "memory for option state "
+                                                 "wipe.");
+                                       status = ISC_R_NOMEMORY;
+                                       goto cleanup;
+                               }
+
+                               if (!set_status_code(STATUS_NoPrefixAvail,
+                                                    "No prefixes available "
+                                                    "for this interface.",
+                                                     reply->reply_ia)) {
+                                       log_error("reply_process_ia_pd: "
+                                                 "Unable to set "
+                                                 "NoPrefixAvail status "
+                                                 "code.");
+                                       status = ISC_R_FAILURE;
+                                       goto cleanup;
+                               }
+
+                               status = ISC_R_SUCCESS;
+                               break;
+
+                             default:
+                               if (reply->ia_resources_included)
+                                       status = ISC_R_SUCCESS;
+                               else
+                                       goto cleanup;
+                               break;
+                       }
+               }
+
+               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_PD, NULL);
+
+       /* Reset the length of this IA_PD 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 (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 {
+                       reply->rebind = getULong(data.data);
+               }
+
+               if (data.data != NULL)
+                       data_string_forget(&data, MDL);
+       }
+       putULong(reply->buf.data + ia_cursor + 12, reply->rebind);
+
+       /*
+        * If this is not a 'soft' binding, consume the new changes into
+        * the database (if any have been attached to the ia_pd).
+        *
+        * Loop through the assigned dynamic prefixes, referencing the
+        * prefixes onto this IA_PD rather than any old ones, and updating
+        * prefix pool timers for each (if any).
+        */
+       if ((status != ISC_R_CANCELED) && (reply->static_prefixes == 0) &&
+           (reply->buf.reply.msg_type == DHCPV6_REPLY) &&
+           (reply->ia_pd->num_iaprefix != 0)) {
+               struct iaprefix *tmp;
+               struct data_string *ia_id;
+               int i;
+
+               for (i = 0 ; i < reply->ia_pd->num_iaprefix ; i++) {
+                       tmp = reply->ia_pd->iaprefix[i];
+
+                       if (tmp->ia_pd != NULL)
+                               ia_pd_dereference(&tmp->ia_pd, MDL);
+                       ia_pd_reference(&tmp->ia_pd, reply->ia_pd, MDL);
+
+                       schedule_prefix_timeout(tmp->ipv6_ppool);
+               }
+
+               /* Remove any old ia_pd from the hash. */
+               if (reply->old_ia_pd != NULL) {
+                       ia_id = &reply->old_ia_pd->iaid_duid;
+                       ia_pd_hash_delete(ia_pd_active,
+                                         (unsigned char *)ia_id->data,
+                                         ia_id->len, MDL);
+                       ia_pd_dereference(&reply->old_ia_pd, MDL);
+               }
+
+               /* Put new ia_pd into the hash. */
+               ia_id = &reply->ia_pd->iaid_duid;
+               ia_pd_hash_add(ia_pd_active, (unsigned char *)ia_id->data,
+                              ia_id->len, reply->ia_pd, MDL);
+
+               write_ia_pd(reply->ia_pd);
+
+               /* 
+                * Note that we wrote the prefix into the database,
+                * so that we know not to release it when we're done
+                * with this function.
+                */
+               prefix_in_database = ISC_TRUE;
+
+       /*
+        * If this is a soft binding, we will check to see if we are 
+        * suggesting the existing database entry to the client.
+        */
+       } else if ((status != ISC_R_CANCELED) &&
+                  (reply->static_prefixes == 0) &&
+                  (reply->old_ia_pd != NULL)) {
+               if (ia_pd_equal(reply->old_ia_pd, reply->ia_pd)) {
+                       prefix_in_database = ISC_TRUE;
+               }
+       }
+
+      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_pd_data.data != NULL)
+               data_string_forget(&ia_pd_data, MDL);
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (reply->old_ia_pd != NULL)
+               ia_pd_dereference(&reply->old_ia_pd, MDL);
+       if (reply->prefix != NULL)
+               iaprefix_dereference(&reply->prefix, MDL);
+       if (!prefix_in_database) {
+               /*
+                * Cleanup soft bindings, assume:
+                *  reply->static_prefixes == 0
+                *  reply->ia_pd != NULL
+                *  reply->ia_pd->num_iaprefix != 0
+                */
+               struct iaprefix *tmp;
+               int i;
+
+               for (i = 0 ; i < reply->ia_pd->num_iaprefix ; i++) {
+                       tmp = reply->ia_pd->iaprefix[i];
+                       release_prefix6(tmp->ipv6_ppool, tmp);
+               }
+       }
+       if (reply->ia_pd != NULL)
+               ia_pd_dereference(&reply->ia_pd, MDL);
+
+       /*
+        * ISC_R_CANCELED is a status code used by the prefix processing to
+        * indicate we're replying with a status code.  This is still a
+        * success at higher layers.
+        */
+       return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
+}
+
+/*
+ * Process an IAPREFIX within a given IA_PD, storing any IAPREFIX reply
+ * contents into the reply's current ia_pd-scoped option cache.  Returns
+ * ISC_R_CANCELED in the event we are replying with a status code and do
+ * not wish to process more IAPREFIXes within this IA_PD.
+ */
+static isc_result_t
+reply_process_prefix(struct reply_state *reply, struct option_cache *pref) {
+       u_int32_t pref_life, valid_life;
+       struct binding_scope **scope;
+       struct group *group;
+       struct iaddrcidrnet tmp_pref;
+       struct option_cache *oc;
+       struct data_string iapref, data;
+       isc_boolean_t held_prefix;
+       isc_result_t status = ISC_R_SUCCESS;
+
+       /* Initializes values that will be cleaned up. */
+       held_prefix = ISC_FALSE;
+       memset(&iapref, 0, sizeof(iapref));
+       memset(&data, 0, sizeof(data));
+       /* Note that reply->prefix may be set by prefix_is_owned() */
+
+       /*
+        * There is no point trying to process an incoming prefix if there
+        * is no room for an outgoing prefix.
+        */
+       if ((reply->cursor + 29) > sizeof(reply->buf)) {
+               log_error("reply_process_prefix: Out of room for prefix.");
+               return ISC_R_NOSPACE;
+       }
+
+       /* Extract this IAPREFIX option. */
+       if (!evaluate_option_cache(&iapref, reply->packet, NULL, NULL, 
+                                  reply->packet->options, NULL, &global_scope,
+                                  pref, MDL) ||
+           (iapref.len < IAPREFIX_OFFSET)) {
+               log_error("reply_process_prefix: error evaluating IAPREFIX.");
+               status = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       /*
+        * Layout: preferred and valid lifetimes followed by the prefix
+        * length and the IPv6 address.
+        */
+       pref_life = getULong(iapref.data);
+       valid_life = getULong(iapref.data + 4);
+
+       if ((reply->client_valid == 0) ||
+           (reply->client_valid > valid_life))
+               reply->client_valid = valid_life;
+
+       if ((reply->client_prefer == 0) ||
+           (reply->client_prefer > pref_life))
+               reply->client_prefer = pref_life;
+
+       /* 
+        * Clients may choose to send ::/0 as a prefix, with the idea to give
+        * hints about preferred-lifetime or valid-lifetime.
+        */
+       tmp_pref.lo_addr.len = 16;
+       memset(tmp_pref.lo_addr.iabuf, 0, 16);
+       if ((iapref.data[8] == 0) &&
+           (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0)) {
+               /* Status remains success; we just ignore this one. */
+               goto cleanup;
+       }
+
+       /*
+        * Clients may choose to send ::/X as a prefix to specify a
+        * preferred/requested prefix length. Note X is never zero here.
+        */
+       tmp_pref.bits = (int) iapref.data[8];
+       if (reply->preflen < 0) {
+               /* Cache the first preferred prefix length. */
+               reply->preflen = tmp_pref.bits;
+       }
+       if (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0) {
+               goto cleanup;
+       }
+
+       memcpy(tmp_pref.lo_addr.iabuf, iapref.data + 9, 16);
+
+       /* Verify the prefix belongs to the client. */
+       if (!prefix_is_owned(reply, &tmp_pref)) {
+               /* Same than for addresses. */
+               if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) ||
+                   (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) ||
+                   (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) {
+                       status = reply_process_try_prefix(reply, &tmp_pref);
+
+                       /* Either error out or skip this prefix. */
+                       if ((status != ISC_R_SUCCESS) && 
+                           (status != ISC_R_ADDRINUSE)) 
+                               goto cleanup;
+
+                       if (reply->prefix == NULL) {
+                               if (reply->packet->dhcpv6_msg_type ==
+                                                       DHCPV6_REBIND) {
+                                       reply->send_prefer = 0;
+                                       reply->send_valid = 0;
+                                       goto send_pref;
+                               }
+
+                               /* status remains success - ignore */
+                               goto cleanup;
+                       }
+
+                       held_prefix = ISC_TRUE;
+
+               /*
+                * RFC3633 section 18.2.3:
+                *
+                * If the delegating router cannot find a binding
+                * for the requesting router's IA_PD the delegating
+                * router returns the IA_PD containing no prefixes
+                * with a Status Code option set to NoBinding in the
+                * Reply message.
+                *
+                * On mismatch we (ab)use this pretending we have not the IA
+                * as soon as we have not a prefix.
+                */
+               } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) {
+                       /* Rewind the IA_PD to empty. */
+                       option_state_dereference(&reply->reply_ia, MDL);
+                       if (!option_state_allocate(&reply->reply_ia, MDL)) {
+                               log_error("reply_process_prefix: No memory "
+                                         "for option state wipe.");
+                               status = ISC_R_NOMEMORY;
+                               goto cleanup;
+                       }
+
+                       /* Append a NoBinding status code.  */
+                       if (!set_status_code(STATUS_NoBinding,
+                                            "Prefix not bound to this "
+                                            "interface.", reply->reply_ia)) {
+                               log_error("reply_process_prefix: Unable to "
+                                         "attach status code.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
+                       }
+
+                       /* Fin (no more IAPREFIXes). */
+                       status = ISC_R_CANCELED;
+                       goto cleanup;
+               } else {
+                       log_error("It is impossible to lease a client that is "
+                                 "not sending a solicit, request, renew, or "
+                                 "rebind message.");
+                       status = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+       }
+
+       if (reply->static_prefixes > 0) {
+               if (reply->host == NULL)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+
+               scope = &global_scope;
+               group = reply->host->group;
+       } else {
+               if (reply->prefix == NULL)
+                       log_fatal("Impossible condition at %s:%d.", MDL);
+
+               scope = &reply->prefix->scope;
+               group = root_group;
+       }
+
+       /*
+        * If client_resources is nonzero, then the reply_process_is_prefixed
+        * function has executed configuration state into the reply option
+        * cache.  We will use that valid cache to derive configuration for
+        * whether or not to engage in additional prefixes, and similar.
+        */
+       if (reply->client_resources != 0) {
+               unsigned limit = 1;
+
+               /*
+                * Does this client have "enough" prefixes already?  Default
+                * to one.  Everybody gets one, and one should be enough for
+                * anybody.
+                */
+               oc = lookup_option(&server_universe, reply->opt_state,
+                                  SV_LIMIT_PREFS_PER_IA);
+               if (oc != NULL) {
+                       if (!evaluate_option_cache(&data, reply->packet,
+                                                  NULL, NULL,
+                                                  reply->packet->options,
+                                                  reply->opt_state,
+                                                  scope, oc, MDL) ||
+                           (data.len != 4)) {
+                               log_error("reply_process_prefix: unable to "
+                                         "evaluate prefs-per-ia value.");
+                               status = ISC_R_FAILURE;
+                               goto cleanup;
+                       }
+
+                       limit = getULong(data.data);
+                       data_string_forget(&data, MDL);
+               }
+
+               /*
+                * If we wish to limit the client to a certain number of
+                * prefixes, then omit the prefix from the reply.
+                */
+               if (reply->client_resources >= limit)
+                       goto cleanup;
+       }
+
+       status = reply_process_is_prefixed(reply, reply->ia_pd, scope, group);
+       if (status != ISC_R_SUCCESS)
+               goto cleanup;
+
+      send_pref:
+       status = reply_process_send_prefix(reply, &tmp_pref);
+
+      cleanup:
+       if (iapref.data != NULL)
+               data_string_forget(&iapref, MDL);
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (held_prefix && (status != ISC_R_SUCCESS))
+               release_prefix6(reply->prefix->ipv6_ppool, reply->prefix);
+       if (reply->prefix != NULL)
+               iaprefix_dereference(&reply->prefix, MDL);
+
+       return status;
+}
+
+/*
+ * Verify the prefix belongs to the client.  If we've got a host
+ * record with fixed prefixes, it has to be an assigned prefix
+ * (fault out all else).  Otherwise it's a dynamic prefix, so lookup
+ * that prefix and make sure it belongs to this DUID:IAID pair.
+ */
+static isc_boolean_t
+prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) {
+       struct iaddrcidrnetlist *l;
+       int i;
+
+       /*
+        * This faults out prefixes that don't match fixed prefixes.
+        */
+       if (reply->static_prefixes > 0) {
+               for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
+                       if ((pref->bits == l->cidrnet.bits) &&
+                           (memcmp(pref->lo_addr.iabuf,
+                                   l->cidrnet.lo_addr.iabuf, 16) == 0))
+                               return ISC_TRUE;
+               }
+               return ISC_FALSE;
+       }
+
+       if ((reply->old_ia_pd == NULL) ||
+           (reply->old_ia_pd->num_iaprefix == 0))
+               return ISC_FALSE;
+
+       for (i = 0 ; i < reply->old_ia_pd->num_iaprefix ; i++) {
+               struct iaprefix *tmp;
+
+               tmp = reply->old_ia_pd->iaprefix[i];
+
+               if ((pref->bits == (int) tmp->plen) &&
+                   memcmp(pref->lo_addr.iabuf, &tmp->pref, 16) == 0) {
+                       iaprefix_reference(&reply->prefix, tmp, MDL);
+                       return ISC_TRUE;
+               }
+       }
+
+       return ISC_FALSE;
+}
+
+/*
+ * This function only returns failure on 'hard' failures.  If it succeeds,
+ * it will leave a prefix structure behind.
+ */
+static isc_result_t
+reply_process_try_prefix(struct reply_state *reply,
+                        struct iaddrcidrnet *pref) {
+       isc_result_t status = ISC_R_FAILURE;
+       struct ipv6_ppool *ppool;
+       int i;
+       struct data_string data_pref;
+
+       if ((reply == NULL) || (pref == NULL) || (reply->prefix != NULL))
+               return ISC_R_INVALIDARG;
+
+       memset(&data_pref, 0, sizeof(data_pref));
+       data_pref.len = 17;
+       if (!buffer_allocate(&data_pref.buffer, data_pref.len, MDL)) {
+               log_error("reply_process_try_prefix: out of memory.");
+               return ISC_R_NOMEMORY;
+       }
+       data_pref.data = data_pref.buffer->data;
+       data_pref.buffer->data[0] = (u_int8_t) pref->bits;
+       memcpy(data_pref.buffer->data + 1, pref->lo_addr.iabuf, 16);
+
+       for (i = 0 ; i < num_ppools ; i++) {
+               ppool = ppools[i];
+               if (ppool == NULL)
+                       continue;
+               status = try_client_v6_prefix(&reply->prefix, ppool,
+                                             &data_pref);
+               if (status == ISC_R_SUCCESS)
+                       break;
+       }
+
+       data_string_forget(&data_pref, MDL);
+       /* Return just the most recent status... */
+       return status;
+}
+
+/* Look around for a prefix to give the client.  First, look through the old
+ * IA_PD for prefixes we can extend.  Second, try to allocate a new prefix.
+ * Finally, actually add that prefix into the current reply IA_PD.
+ */
+static isc_result_t
+find_client_prefix(struct reply_state *reply) {
+       struct iaddrcidrnet send_pref;
+       isc_result_t status = ISC_R_NORESOURCES;
+       struct iaprefix *prefix, *best_prefix = NULL;
+       struct binding_scope **scope;
+       struct group *group;
+       int i;
+
+       if (reply->host != NULL)
+               group = reply->host->group;
+       else if (reply->shared != NULL)
+               group = reply->shared->group;
+       else
+               group = root_group;
+
+       if (reply->static_prefixes > 0) {
+               struct iaddrcidrnetlist *l;
+
+               if (reply->host == NULL)
+                       return ISC_R_INVALIDARG;
+
+               for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
+                       if (l->cidrnet.bits == reply->preflen)
+                               break;
+               }
+               if (l == NULL) {
+                       /*
+                        * If no fixed prefix has the preferred length,
+                        * get the first one.
+                        */
+                       l = reply->host->fixed_prefix;
+               }
+               memcpy(&send_pref, &l->cidrnet, sizeof(send_pref));
+
+               status = ISC_R_SUCCESS;
+               scope = &global_scope;
+               goto send_pref;
+       }
+
+       if (reply->old_ia_pd != NULL)  {
+               for (i = 0 ; i < reply->old_ia_pd->num_iaprefix ; i++) {
+                       prefix = reply->old_ia_pd->iaprefix[i];
+
+                       best_prefix = prefix_compare(reply, prefix,
+                                                    best_prefix);
+               }
+       }
+
+       /* Try to pick a new prefix if we didn't find one, or if we found an
+        * abandoned prefix.
+        */
+       if ((best_prefix == NULL) || (best_prefix->state == FTS_ABANDONED)) {
+               status = pick_v6_prefix(&reply->prefix, reply->preflen,
+                                       &reply->client_id);
+       } else if (best_prefix != NULL) {
+               iaprefix_reference(&reply->prefix, best_prefix, MDL);
+               status = ISC_R_SUCCESS;
+       }
+
+       /* Pick the abandoned prefix as a last resort. */
+       if ((status == ISC_R_NORESOURCES) && (best_prefix != NULL)) {
+               /* I don't see how this is supposed to be done right now. */
+               log_error("Reclaiming abandoned prefixes is not yet "
+                         "supported.  Treating this as an out of space "
+                         "condition.");
+               /* prefix_reference(&reply->prefix, best_prefix, MDL); */
+       }
+
+       /* Give up now if we didn't find a prefix. */
        if (status != ISC_R_SUCCESS)
                return status;
 
-       if (reply->lease == NULL)
+       if (reply->prefix == NULL)
                log_fatal("Impossible condition at %s:%d.", MDL);
 
-       scope = &reply->lease->scope;
-       group = reply->shared->group;
+       scope = &reply->prefix->scope;
+       if (reply->shared != NULL) {
+               group = reply->shared->group;
+       } else {
+               group = root_group;
+       }
 
-       send_addr.len = 16;
-       memcpy(send_addr.iabuf, &reply->lease->addr, 16);
+       send_pref.lo_addr.len = 16;
+       memcpy(send_pref.lo_addr.iabuf, &reply->prefix->pref, 16);
+       send_pref.bits = (int) reply->prefix->plen;
 
-      send_addr:
-       status = reply_process_is_addressed(reply, scope, group);
+      send_pref:
+       status = reply_process_is_prefixed(reply, reply->ia_pd, scope, group);
        if (status != ISC_R_SUCCESS)
                return status;
 
-       status = reply_process_send_addr(reply, &send_addr);
+       status = reply_process_send_prefix(reply, &send_pref);
        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
+/* Once a prefix is found for a client, perform several common functions;
+ * Calculate and store valid and preferred prefix times, draw client options
  * into the option state.
  */
 static isc_result_t
-reply_process_is_addressed(struct reply_state *reply,
-                          struct binding_scope **scope, struct group *group)
+reply_process_is_prefixed(struct reply_state *reply, struct ia_pd *ia_pd,
+                         struct binding_scope **scope, struct group *group)
 {
        isc_result_t status = ISC_R_SUCCESS;
        struct data_string data;
@@ -2095,8 +3687,8 @@ reply_process_is_addressed(struct reply_state *reply,
                                           reply->opt_state,
                                           scope, oc, MDL) ||
                    (data.len != 4)) {
-                       log_error("reply_process_is_addressed: unable to "
-                                 "evaluate default lease time");
+                       log_error("reply_process_is_prefixed: unable to "
+                                 "evaluate default prefix time");
                        status = ISC_R_FAILURE;
                        goto cleanup;
                }
@@ -2122,8 +3714,8 @@ reply_process_is_addressed(struct reply_state *reply,
                                           reply->opt_state,
                                           scope, oc, MDL) ||
                    (data.len != 4)) {
-                       log_error("reply_process_is_addressed: unable to "
-                                 "evaluate preferred lease time");
+                       log_error("reply_process_is_prefixed: unable to "
+                                 "evaluate preferred prefix time");
                        status = ISC_R_FAILURE;
                        goto cleanup;
                }
@@ -2139,44 +3731,29 @@ reply_process_is_addressed(struct reply_state *reply,
        if (reply->valid > reply->send_valid)
                reply->valid = reply->send_valid;
 
-#if 0
-       /*
-        * XXX: Old 4.0.0 alpha code would change the host {} record
-        * XXX: uid upon lease assignment.  This was intended to cover the
-        * XXX: case where a client first identifies itself using vendor
-        * XXX: options in a solicit, or request, but later neglects to include
-        * XXX: these options in a Renew or Rebind.  It is not clear that this
-        * XXX: is required, and has some startling ramifications (such as
-        * XXX: how to recover this dynamic host {} state across restarts).
-        */
-       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) {
+       /* Perform dynamic prefix related update work. */
+       if (reply->prefix != NULL) {
                /* Advance (or rewind) the valid lifetime. */
-               reply->lease->valid_lifetime_end_time = cur_time +
+               reply->prefix->valid_lifetime_end_time = cur_time +
                                                        reply->send_valid;
-               renew_lease6(reply->lease->ipv6_pool, reply->lease);
+               renew_prefix6(reply->prefix->ipv6_ppool, reply->prefix);
 
-               status = ia_na_add_iaaddr(reply->ia_na, reply->lease, MDL);
+               status = ia_pd_add_iaprefix(ia_pd, reply->prefix, MDL);
                if (status != ISC_R_SUCCESS) {
-                       log_fatal("reply_process_addr: Unable to attach lease "
-                                 "to new IA: %s", isc_result_totext(status));
+                       log_fatal("reply_process_is_prefixed: Unable to "
+                                 "attach prefix to new IA_PD: %s",
+                                 isc_result_totext(status));
                }
 
                /*
-                * If this is a new lease, make sure it is attached somewhere.
+                * If this is a new prefix, make sure it is attached somewhere.
                 */
-               if (reply->lease->ia_na == NULL) {
-                       ia_na_reference(&reply->lease->ia_na, reply->ia_na,
-                                       MDL);
+               if (reply->prefix->ia_pd == NULL) {
+                       ia_pd_reference(&reply->prefix->ia_pd, ia_pd, MDL);
                }
        }
 
-       /* Bring a copy of the relevant options into the IA scope. */
+       /* Bring a copy of the relevant options into the IA_PD scope. */
        execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
                                    reply->packet->options, reply->reply_ia,
                                    scope, group, root_group);
@@ -2191,38 +3768,40 @@ reply_process_is_addressed(struct reply_state *reply,
        return status;
 }
 
-/* Simply send an IAADDR within the IA_NA scope as described. */
+/* Simply send an IAPREFIX within the IA_PD scope as described. */
 static isc_result_t
-reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) {
+reply_process_send_prefix(struct reply_state *reply,
+                         struct iaddrcidrnet *pref) {
        isc_result_t status = ISC_R_SUCCESS;
        struct data_string data;
 
        memset(&data, 0, sizeof(data));
 
-       /* Now append the lease. */
-       data.len = IAADDR_OFFSET;
+       /* Now append the prefix. */
+       data.len = IAPREFIX_OFFSET;
        if (!buffer_allocate(&data.buffer, data.len, MDL)) {
-               log_error("reply_process_send_addr: out of memory allocating "
-                         "new IAADDR buffer.");
+               log_error("reply_process_send_prefix: out of memory"
+                         "allocating new IAPREFIX buffer.");
                status = ISC_R_NOMEMORY;
                goto cleanup;
        }
        data.data = data.buffer->data;
 
-       memcpy(data.buffer->data, addr->iabuf, 16);
-       putULong(data.buffer->data + 16, reply->send_prefer);
-       putULong(data.buffer->data + 20, reply->send_valid);
+       putULong(data.buffer->data, reply->send_prefer);
+       putULong(data.buffer->data + 4, reply->send_valid);
+       data.buffer->data[8] = pref->bits;
+       memcpy(data.buffer->data + 9, pref->lo_addr.iabuf, 16);
 
        if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
                                  data.buffer, data.buffer->data,
-                                 data.len, D6O_IAADDR, 0)) {
-               log_error("reply_process_send_addr: unable to save IAADDR "
-                         "option");
+                                 data.len, D6O_IAPREFIX, 0)) {
+               log_error("reply_process_send_prefix: unable "
+                         "to save IAPREFIX option");
                status = ISC_R_FAILURE;
                goto cleanup;
        }
 
-       reply->ia_addrs_included = ISC_TRUE;
+       reply->ia_resources_included = ISC_TRUE;
 
       cleanup:
        if (data.data != NULL)
@@ -2231,19 +3810,29 @@ reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) {
        return status;
 }
 
-/* Choose the better of two leases. */
-static struct iaaddr *
-lease_compare(struct iaaddr *alpha, struct iaaddr *beta) {
+/* Choose the better of two prefixes. */
+static struct iaprefix *
+prefix_compare(struct reply_state *reply,
+              struct iaprefix *alpha, struct iaprefix *beta) {
        if (alpha == NULL)
                return beta;
        if (beta == NULL)
                return alpha;
 
+       if (reply->preflen >= 0) {
+               if ((alpha->plen == reply->preflen) &&
+                   (beta->plen != reply->preflen))
+                       return alpha;
+               if ((beta->plen == reply->preflen) &&
+                   (alpha->plen != reply->preflen))
+                       return beta;
+       }
+
        switch(alpha->state) {
              case FTS_ACTIVE:
                switch(beta->state) {
                      case FTS_ACTIVE:
-                       /* Choose the lease with the longest lifetime (most
+                       /* Choose the prefix with the longest lifetime (most
                         * likely the most recently allocated).
                         */
                        if (alpha->valid_lifetime_end_time < 
@@ -2267,7 +3856,7 @@ lease_compare(struct iaaddr *alpha, struct iaaddr *beta) {
                        return beta;
 
                      case FTS_EXPIRED:
-                       /* Choose the most recently expired lease. */
+                       /* Choose the most recently expired prefix. */
                        if (alpha->valid_lifetime_end_time <
                            beta->valid_lifetime_end_time)
                                return beta;
@@ -2289,7 +3878,7 @@ lease_compare(struct iaaddr *alpha, struct iaaddr *beta) {
                        return alpha;
 
                      case FTS_ABANDONED:
-                       /* Choose the lease that was abandoned longest ago. */
+                       /* Choose the prefix that was abandoned longest ago. */
                        if (alpha->valid_lifetime_end_time <
                            beta->valid_lifetime_end_time)
                                return alpha;
@@ -2368,7 +3957,7 @@ dhcpv6_request(struct data_string *reply_ret, struct packet *packet) {
 
 /* Find a DHCPv6 packet's shared network from hints in the packet.
  */
-static isc_result_t
+static void
 shared_network_from_packet6(struct shared_network **shared,
                            struct packet *packet)
 {
@@ -2376,10 +3965,11 @@ shared_network_from_packet6(struct shared_network **shared,
        const struct in6_addr *link_addr, *first_link_addr;
        struct iaddr tmp_addr;
        struct subnet *subnet;
-       isc_result_t status;
 
-       if ((shared == NULL) || (*shared != NULL) || (packet == NULL))
-               return ISC_R_INVALIDARG;
+       if ((shared == NULL) || (*shared != NULL) || (packet == NULL)) {
+               log_error("shared_network_from_packet6: invalid arg.");
+               return;
+       }
 
        /*
         * First, find the link address where the packet from the client
@@ -2409,10 +3999,9 @@ shared_network_from_packet6(struct shared_network **shared,
                if (!find_subnet(&subnet, tmp_addr, MDL)) {
                        log_debug("No subnet found for link-address %s.",
                                  piaddr(tmp_addr));
-                       return ISC_R_NOTFOUND;
+                       return;
                }
-               status = shared_network_reference(shared,
-                                                 subnet->shared_network, MDL);
+               shared_network_reference(shared, subnet->shared_network, MDL);
                subnet_dereference(&subnet, MDL);
 
        /*
@@ -2420,12 +4009,10 @@ shared_network_from_packet6(struct shared_network **shared,
         * that this packet came in on to pick the shared_network.
         */
        } else {
-               status = shared_network_reference(shared,
+               shared_network_reference(shared,
                                         packet->interface->shared_network,
                                         MDL);
        }
-
-       return status;
 }
 
 /*
@@ -2468,13 +4055,19 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
                return;
        }
 
-       /* Do not process Confirms that do not have IA's we do not recognize.
+       /*
+        * Do not process Confirms that do not have IA's we do not recognize.
         */
        ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
        ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
        if ((ia == NULL) && (ta == NULL))
                return;
 
+       /*
+        * IA_PD's are simply ignored.
+        */
+       delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+
        /* 
         * Bit of variable initialization.
         */
@@ -2488,8 +4081,8 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
         * network the client is on.
         */
        shared = NULL;
-       if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) ||
-           (shared == NULL))
+       shared_network_from_packet6(&shared, packet);
+       if (shared == NULL)
                goto exit;
 
        /* If there are no recorded subnets, then we have no
@@ -2643,10 +4236,10 @@ exit:
 }
 
 /*
- * Renew is when a client wants to extend its lease, at time T1.
+ * Renew is when a client wants to extend its lease/prefix, at time T1.
  *
- * We handle this the same as if the client wants a new lease, except
- * for the error code of when addresses don't match.
+ * We handle this the same as if the client wants a new lease/prefix,
+ * except for the error code of when addresses don't match.
  */
 
 /* TODO: reject unicast messages, unless we set unicast option */
@@ -3097,11 +4690,19 @@ dhcpv6_decline(struct data_string *reply, struct packet *packet) {
                return;
        }
 
+       /*
+        * Undefined for IA_PD.
+        */
+       delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+
        /*
         * And operate on each IA_NA in this packet.
         */
        iterate_over_ia_na(reply, packet, &client_id, &server_id, "Decline", 
                           ia_na_match_decline, ia_na_nomatch_decline);
+
+       data_string_forget(&server_id, MDL);
+       data_string_forget(&client_id, MDL);
 }
 
 static void
@@ -3194,6 +4795,348 @@ exit:
        option_state_dereference(&host_opt_state, MDL);
 }
 
+static void
+ia_pd_match_release(const struct data_string *client_id,
+                   const struct data_string *iapref,
+                   struct iaprefix *prefix)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+
+       log_info("Client %s releases prefix %s/%u",
+                print_hex_1(client_id->len, client_id->data, 60),
+                inet_ntop(AF_INET6, iapref->data + 9,
+                          tmp_addr, sizeof(tmp_addr)),
+                (unsigned) getUChar(iapref->data + 8));
+       if (prefix != NULL) {
+               release_prefix6(prefix->ipv6_ppool, prefix);
+               write_ia_pd(prefix->ia_pd);
+       }
+}
+
+static void
+ia_pd_nomatch_release(const struct data_string *client_id,
+                     const struct data_string *iapref,
+                     u_int32_t *ia_pd_id,
+                     struct packet *packet,
+                     char *reply_data,
+                     int *reply_ofs,
+                     int reply_len)
+{
+       char tmp_addr[INET6_ADDRSTRLEN];
+       struct option_state *host_opt_state;
+       int len;
+
+       log_info("Client %s releases prefix %s/%u, which is not leased to it.",
+                print_hex_1(client_id->len, client_id->data, 60),
+                inet_ntop(AF_INET6, iapref->data + 9,
+                          tmp_addr, sizeof(tmp_addr)),
+                (unsigned) getUChar(iapref->data + 8));
+
+       /*
+        * Create state for this IA_PD.
+        */
+       host_opt_state = NULL;
+       if (!option_state_allocate(&host_opt_state, MDL)) {
+               log_error("ia_pd_nomatch_release: out of memory "
+                         "allocating option_state.");
+               goto exit;
+       }
+
+       if (!set_status_code(STATUS_NoBinding, 
+                            "Release for non-leased prefix.",
+                            host_opt_state)) {
+               goto exit;
+       }
+
+       /*
+        * Insure we have enough space
+        */
+       if (reply_len < (*reply_ofs + 16)) {
+               log_error("ia_pd_nomatch_release: "
+                         "out of space for reply packet.");
+               goto exit;
+       }
+
+       /*
+        * Put our status code into the reply packet.
+        */
+       len = store_options6(reply_data+(*reply_ofs)+16,
+                            reply_len-(*reply_ofs)-16,
+                            host_opt_state, packet,
+                            required_opts_STATUS_CODE, NULL);
+
+       /*
+        * Store the non-encapsulated option data for this 
+        * IA_PD into our reply packet. Defined in RFC 3315, 
+        * section 22.4.  
+        */
+       /* option number */
+       putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_PD);
+       /* option length */
+       putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
+       /* IA_PD, copied from the client */
+       memcpy(reply_data+(*reply_ofs)+4, ia_pd_id, 4);
+       /* t1 and t2, odd that we need them, but here it is */
+       putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
+       putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);
+
+       /*
+        * Get ready for next IA_PD.
+        */
+       *reply_ofs += (len + 16);
+
+exit:
+       option_state_dereference(&host_opt_state, MDL);
+}
+
+static void
+iterate_over_ia_pd(struct data_string *reply_ret, 
+                  struct packet *packet,
+                  const struct data_string *client_id,
+                  const struct data_string *server_id,
+                  const char *packet_type,
+                  void (*ia_pd_match)(),
+                  void (*ia_pd_nomatch)())
+{
+       struct data_string reply_new;
+       int reply_len;
+       struct option_state *opt_state;
+       struct host_decl *packet_host;
+       struct option_cache *ia;
+       struct option_cache *oc;
+       /* cli_enc_... variables come from the IA_PD options */
+       struct data_string cli_enc_opt_data;
+       struct option_state *cli_enc_opt_state;
+       struct host_decl *host;
+       struct option_state *host_opt_state;
+       struct data_string iaprefix;
+       int iaprefix_is_found;
+       char reply_data[65536];
+       int reply_ofs;
+       struct iaprefix *prefix;
+       struct ia_pd *existing_ia_pd;
+       int i;
+       struct data_string key;
+       u_int32_t iaid;
+
+       /*
+        * Initialize to empty values, in case we have to exit early.
+        */
+       memset(&reply_new, 0, sizeof(reply_new));
+       opt_state = NULL;
+       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
+       cli_enc_opt_state = NULL;
+       memset(&iaprefix, 0, sizeof(iaprefix));
+       host_opt_state = NULL;
+       prefix = NULL;
+
+       /*
+        * Compute the available length for the reply.
+        */
+       reply_len = sizeof(reply_data) - reply_ret->len;
+       reply_ofs = 0;
+
+       /* 
+        * Find the host record that matches from the packet, if any.
+        */
+       packet_host = NULL;
+       if (!find_hosts_by_uid(&packet_host, 
+                              client_id->data, client_id->len, MDL)) {
+               packet_host = NULL;
+               /* 
+                * Note: In general, we don't expect a client to provide
+                *       enough information to match by option for these
+                *       types of messages, but if we don't have a UID
+                *       match we can check anyway.
+                */
+               if (!find_hosts_by_option(&packet_host, 
+                                         packet, packet->options, MDL)) {
+                       packet_host = NULL;
+               }
+       }
+
+       /*
+        * Build our option state for reply.
+        */
+       opt_state = NULL;
+       if (!option_state_allocate(&opt_state, MDL)) {
+               log_error("iterate_over_ia_pd: no memory for option_state.");
+               goto exit;
+       }
+       execute_statements_in_scope(NULL, packet, NULL, NULL, 
+                                   packet->options, opt_state, 
+                                   &global_scope, root_group, NULL);
+
+       /*
+        * Loop through the IA_PD reported by the client, and deal with
+        * prefixes reported as already in use.
+        */
+       for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
+            ia != NULL; ia = ia->next) {
+           iaprefix_is_found = 0;
+
+           if (!get_encapsulated_IA_state(&cli_enc_opt_state,
+                                          &cli_enc_opt_data,
+                                          packet, ia, IA_PD_OFFSET)) {
+               goto exit;
+           }
+
+           iaid = getULong(cli_enc_opt_data.data);
+
+           oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, 
+                              D6O_IAPREFIX);
+           if (oc == NULL) {
+               /* no prefix given for this IA_PD, ignore */
+               option_state_dereference(&cli_enc_opt_state, MDL);
+               data_string_forget(&cli_enc_opt_data, MDL);
+               continue;
+           }
+
+           for (; oc != NULL; oc = oc->next) {
+               memset(&iaprefix, 0, sizeof(iaprefix));
+               if (!evaluate_option_cache(&iaprefix, packet, NULL, NULL, 
+                                          packet->options, NULL,
+                                          &global_scope, oc, MDL)) {
+                       log_error("iterate_over_ia_pd: "
+                                 "error evaluating IAPREFIX.");
+                       goto exit;
+               }
+
+               /* 
+                * Now we need to figure out which host record matches
+                * this IA_PD and IAPREFIX.
+                *
+                * XXX: We don't currently track IA_PD separately, but
+                *      we will need to do this!
+                */
+               host = NULL;
+               if (!find_hosts_by_option(&host, packet, 
+                                         cli_enc_opt_state, MDL)) { 
+                       if (packet_host != NULL) {
+                               host = packet_host;
+                       } else {
+                               host = NULL;
+                       }
+               }
+               while (host != NULL) {
+                       if (host->fixed_prefix != NULL) {
+                               struct iaddrcidrnetlist *l;
+                               int plen = (int) getUChar(iaprefix.data + 8);
+
+                               for (l = host->fixed_prefix; l != NULL;
+                                    l = l->next) {
+                                       if (plen != l->cidrnet.bits)
+                                               continue;
+                                       if (memcmp(iaprefix.data + 9,
+                                                  l->cidrnet.lo_addr.iabuf,
+                                                  16) == 0)
+                                               break;
+                               }
+                               if ((l != NULL) && (iaprefix.len >= 17))
+                                       break;
+                       }
+                       host = host->n_ipaddr;
+               }
+
+               if ((host == NULL) && (iaprefix.len >= IAPREFIX_OFFSET)) {
+                       /*
+                        * Find existing IA_PD.
+                        */
+                       if (ia_make_key(&key, iaid, 
+                                       (char *)client_id->data,
+                                       client_id->len, 
+                                       MDL) != ISC_R_SUCCESS) {
+                               log_fatal("iterate_over_ia_pd: no memory for "
+                                         "key.");
+                       }
+
+                       existing_ia_pd = NULL;
+                       if (ia_pd_hash_lookup(&existing_ia_pd, ia_pd_active, 
+                                             (unsigned char *)key.data, 
+                                             key.len, MDL)) {
+                               /* 
+                                * Make sure this prefix is in the IA_PD.
+                                */
+                               for (i = 0;
+                                    i < existing_ia_pd->num_iaprefix;
+                                    i++) {
+                                       struct iaprefix *tmp;
+                                       u_int8_t plen;
+
+                                       plen = getUChar(iaprefix.data + 8);
+                                       tmp = existing_ia_pd->iaprefix[i];
+                                       if ((tmp->plen == plen) &&
+                                           (memcmp(&tmp->pref,
+                                                   iaprefix.data + 9,
+                                                   16) == 0)) {
+                                               iaprefix_reference(&prefix,
+                                                                  tmp, MDL);
+                                               break;
+                                       }
+                               }
+                       }
+
+                       data_string_forget(&key, MDL);
+               }
+
+               if ((host != NULL) || (prefix != NULL)) {
+                       ia_pd_match(client_id, &iaprefix, prefix);
+               } else {
+                       ia_pd_nomatch(client_id, &iaprefix, 
+                                     (u_int32_t *)cli_enc_opt_data.data, 
+                                     packet, reply_data, &reply_ofs, 
+                                     reply_len - reply_ofs);
+               }
+
+               if (prefix != NULL) {
+                       iaprefix_dereference(&prefix, MDL);
+               }
+
+               data_string_forget(&iaprefix, MDL);
+           }
+
+           option_state_dereference(&cli_enc_opt_state, MDL);
+           data_string_forget(&cli_enc_opt_data, MDL);
+       }
+
+       /* 
+        * Return our reply to the caller.
+        * The IA_NA routine has already filled at least the header.
+        */
+       reply_new.len = reply_ret->len + reply_ofs;
+       if (!buffer_allocate(&reply_new.buffer, reply_new.len, MDL)) {
+               log_fatal("No memory to store reply.");
+       }
+       reply_new.data = reply_new.buffer->data;
+       memcpy(reply_new.buffer->data,
+              reply_ret->buffer->data, reply_ret->len);
+       memcpy(reply_new.buffer->data + reply_ret->len,
+              reply_data, reply_ofs);
+       data_string_forget(reply_ret, MDL);
+       data_string_copy(reply_ret, &reply_new, MDL);
+       data_string_forget(&reply_new, MDL);
+
+exit:
+       if (prefix != NULL) {
+               iaprefix_dereference(&prefix, MDL);
+       }
+       if (host_opt_state != NULL) {
+               option_state_dereference(&host_opt_state, MDL);
+       }
+       if (iaprefix.buffer != NULL) {
+               data_string_forget(&iaprefix, MDL);
+       }
+       if (cli_enc_opt_state != NULL) {
+               option_state_dereference(&cli_enc_opt_state, MDL);
+       }
+       if (cli_enc_opt_data.buffer != NULL) {
+               data_string_forget(&cli_enc_opt_data, MDL);
+       }
+       if (opt_state != NULL) {
+               option_state_dereference(&opt_state, MDL);
+       }
+}
+
 /*
  * Release means a client is done with the addresses.
  */
@@ -3217,6 +5160,12 @@ dhcpv6_release(struct data_string *reply, struct packet *packet) {
        iterate_over_ia_na(reply, packet, &client_id, &server_id, "Release", 
                           ia_na_match_release, ia_na_nomatch_release);
 
+       /*
+        * And operate on each IA_PD in this packet.
+        */
+       iterate_over_ia_pd(reply, packet, &client_id, &server_id, "Release",
+                          ia_pd_match_release, ia_pd_nomatch_release);
+
        data_string_forget(&server_id, MDL);
        data_string_forget(&client_id, MDL);
 }
@@ -3249,8 +5198,8 @@ dhcpv6_information_request(struct data_string *reply, struct packet *packet) {
        /*
         * Use the lease_to_client() function. This will work fine, 
         * because the valid_client_info_req() insures that we 
-        * don't have any IA_NA or IA_TA that would cause us to
-        * allocate addresses to the client.
+        * don't have any IA that would cause us to allocate
+        * resources to the client.
         */
        lease_to_client(reply, packet, &client_id,
                        server_id.data != NULL ? &server_id : NULL);
index 383facef4adb1ba07f528d00a23a4f6376e46ba9..3cd3b2b7cc3229dcc50ee94225613490566ace62 100644 (file)
@@ -1149,6 +1149,50 @@ create_address(struct in6_addr *addr,
                str[8] &= ~0x02;
 }
 
+/* 
+ * Create a temporary address by a variant of RFC 4941 algo.
+ */
+static void
+create_temporary(struct in6_addr *addr, 
+                const struct in6_addr *net_start_addr, 
+                const struct data_string *input) {
+       static u_int8_t history[8];
+       static u_int32_t counter = 0;
+       MD5_CTX ctx;
+       unsigned char md[16];
+       extern int dst_s_random(u_int8_t *, unsigned);
+
+       /*
+        * First time/time to reseed.
+        * Please use a good pseudo-random generator here!
+        */
+       if (counter == 0) {
+               if (dst_s_random(history, 8) != 8)
+                       log_fatal("Random failed.");
+       }
+
+       /* 
+        * Use MD5 as recommended by RFC 4941.
+        */
+       MD5_Init(&ctx);
+       MD5_Update(&ctx, history, 8UL);
+       MD5_Update(&ctx, input->data, input->len);
+       MD5_Final(md, &ctx);
+
+       /*
+        * Build the address.
+        */
+       memcpy(&addr->s6_addr[0], &net_start_addr->s6_addr[0], 8);
+       memcpy(&addr->s6_addr[8], md, 8);
+       addr->s6_addr[8] &= ~0x02;
+
+       /*
+        * Save history for the next call.
+        */
+       memcpy(history, md + 8, 8);
+       counter++;
+}
+
 /* Reserved Subnet Router Anycast ::0:0:0:0. */
 static struct in6_addr rtany;
 /* Reserved Subnet Anycasts ::fdff:ffff:ffff:ff80-::fdff:ffff:ffff:ffff. */
@@ -1216,9 +1260,14 @@ activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr,
                }
 
                /* 
-                * Create an address
+                * Create an address or a temporary address.
                 */
-               create_address(&tmp, &pool->start_addr, pool->bits, &ds);
+               if ((pool->bits & POOL_IS_FOR_TEMP) == 0) {
+                       create_address(&tmp, &pool->start_addr,
+                                      pool->bits, &ds);
+               } else {
+                       create_temporary(&tmp, &pool->start_addr, &ds);
+               }
 
                /*
                 * Avoid reserved interface IDs.
@@ -1277,7 +1326,7 @@ activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr,
        memcpy(&iaaddr->addr, &tmp, sizeof(iaaddr->addr));
 
        /*
-        * Add the lease to the pool.
+        * Add the lease to the pool (note state is free, not active?!).
         */
        result = add_lease6(pool, iaaddr, valid_lifetime_end_time);
        if (result == ISC_R_SUCCESS) {
@@ -1578,6 +1627,7 @@ create_prefix(struct in6_addr *pref,
        for (i=0; i<net_bytes; i++) {
                str[i] = net_str[i];
        }
+       i = net_bytes;
        switch (pool_bits % 8) {
                case 1: str[i] = (str[i] & 0x7F) | (net_str[i] & 0x80); break;
                case 2: str[i] = (str[i] & 0x3F) | (net_str[i] & 0xC0); break;
@@ -1594,15 +1644,16 @@ create_prefix(struct in6_addr *pref,
        for (i=net_bytes+1; i<16; i++) {
                str[i] = 0;
        }
+       i = net_bytes;
        switch (pref_bits % 8) {
-               case 0: str[i] &= 0;
-               case 1: str[i] &= 0x80;
-               case 2: str[i] &= 0xC0;
-               case 3: str[i] &= 0xE0;
-               case 4: str[i] &= 0xF0;
-               case 5: str[i] &= 0xF8;
-               case 6: str[i] &= 0xFC;
-               case 7: str[i] &= 0xFE;
+               case 0: str[i] &= 0; break;
+               case 1: str[i] &= 0x80; break;
+               case 2: str[i] &= 0xC0; break;
+               case 3: str[i] &= 0xE0; break;
+               case 4: str[i] &= 0xF0; break;
+               case 5: str[i] &= 0xF8; break;
+               case 6: str[i] &= 0xFC; break;
+               case 7: str[i] &= 0xFE; break;
        }
 }
 
@@ -1628,10 +1679,10 @@ create_prefix(struct in6_addr *pref,
  * the long term.
  */
 isc_result_t
-activate_prefix(struct ipv6_ppool *ppool, struct iaprefix **pref, 
-               unsigned int *attempts,
-               const struct data_string *uid,
-               time_t valid_lifetime_end_time) {
+activate_prefix6(struct ipv6_ppool *ppool, struct iaprefix **pref, 
+                unsigned int *attempts,
+                const struct data_string *uid,
+                time_t valid_lifetime_end_time) {
        struct data_string ds;
        struct in6_addr tmp;
        struct iaprefix *test_iapref;
@@ -1700,10 +1751,11 @@ activate_prefix(struct ipv6_ppool *ppool, struct iaprefix **pref,
        if (result != ISC_R_SUCCESS) {
                return result;
        }
+       iapref->plen = ppool->alloc_plen;
        memcpy(&iapref->pref, &tmp, sizeof(iapref->pref));
 
        /*
-        * Add the prefix to the pool.
+        * Add the prefix to the pool (note state is free, not active?!).
         */
        result = add_prefix6(ppool, iapref, valid_lifetime_end_time);
        if (result == ISC_R_SUCCESS) {
@@ -1976,6 +2028,27 @@ mark_address_unavailable(struct ipv6_pool *pool, const struct in6_addr *addr) {
        return result;
 }
 
+/*
+ * Mark an IPv6 prefix as unavailable from a prefix pool.
+ *
+ * This is used for host entries.
+ */
+isc_result_t
+mark_prefix_unavailable(struct ipv6_ppool *ppool,
+                       const struct in6_addr *pref) {
+       struct iaprefix *dummy_iapref;
+       isc_result_t result;
+
+       dummy_iapref = NULL;
+       result = iaprefix_allocate(&dummy_iapref, MDL);
+       if (result == ISC_R_SUCCESS) {
+               dummy_iapref->pref = *pref;
+               iaprefix_hash_add(ppool->prefs, &dummy_iapref->pref,
+                                 sizeof(*pref), dummy_iapref, MDL);
+       }
+       return result;
+}
+
 /* 
  * Add a pool.
  */
@@ -2374,7 +2447,7 @@ isc_boolean_t
 ipv6_addr_in_pool(const struct in6_addr *addr, const struct ipv6_pool *pool) {
        struct in6_addr tmp;
        
-       ipv6_network_portion(&tmp, addr, pool->bits);
+       ipv6_network_portion(&tmp, addr, pool->bits & ~POOL_IS_FOR_TEMP);
        if (memcmp(&tmp, &pool->start_addr, sizeof(tmp)) == 0) {
                return ISC_TRUE;
        } else {
@@ -2389,7 +2462,8 @@ ipv6_addr_in_pool(const struct in6_addr *addr, const struct ipv6_pool *pool) {
  *   initialized to NULL
  */
 isc_result_t
-find_ipv6_pool(struct ipv6_pool **pool, const struct in6_addr *addr) {
+find_ipv6_pool(struct ipv6_pool **pool, int temp,
+              const struct in6_addr *addr) {
        int i;
 
        if (pool == NULL) {
@@ -2402,6 +2476,12 @@ find_ipv6_pool(struct ipv6_pool **pool, const struct in6_addr *addr) {
        }
 
        for (i=0; i<num_pools; i++) {
+               if (temp && ((pools[i]->bits & POOL_IS_FOR_TEMP) == 0)) {
+                       continue;
+               }
+               if (!temp && ((pools[i]->bits & POOL_IS_FOR_TEMP) != 0)) {
+                       continue;
+               }
                if (ipv6_addr_in_pool(addr, pools[i])) { 
                        ipv6_pool_reference(pool, pools[i], MDL);
                        return ISC_R_SUCCESS;
@@ -2421,13 +2501,21 @@ change_leases(struct ia_na *ia,
        isc_result_t renew_retval;
        struct ipv6_pool *pool;
        struct in6_addr *addr;
-       int i;
+       int temp, i;
 
        retval = ISC_R_SUCCESS;
+       if (ia->ia_type == D6O_IA_NA) {
+               temp = 0;
+       } else if (ia->ia_type == D6O_IA_TA) {
+               temp = 1;
+       } else {
+               log_error("IA without type.");
+               return ISC_R_INVALIDARG;
+       }
        for (i=0; i<ia->num_iaaddr; i++) {
                pool = NULL;
                addr = &ia->iaaddr[i]->addr;
-               if (find_ipv6_pool(&pool, addr) == ISC_R_SUCCESS) {
+               if (find_ipv6_pool(&pool, temp, addr) == ISC_R_SUCCESS) {
                        renew_retval =  change_func(pool, ia->iaaddr[i]);
                        if (renew_retval != ISC_R_SUCCESS) {
                                retval = renew_retval;
@@ -2655,7 +2743,11 @@ mark_hosts_unavailable_support(const void *name, unsigned len, void *value) {
         * sit in any pool.)
         */
        p = NULL;
-       if (find_ipv6_pool(&p, &addr) == ISC_R_SUCCESS) {
+       if (find_ipv6_pool(&p, 0, &addr) == ISC_R_SUCCESS) {
+               mark_address_unavailable(p, &addr);
+               ipv6_pool_dereference(&p, MDL);
+       } 
+       if (find_ipv6_pool(&p, 1, &addr) == ISC_R_SUCCESS) {
                mark_address_unavailable(p, &addr);
                ipv6_pool_dereference(&p, MDL);
        } 
@@ -2668,6 +2760,56 @@ mark_hosts_unavailable(void) {
        hash_foreach(host_name_hash, mark_hosts_unavailable_support);
 }
 
+static isc_result_t
+mark_phosts_unavailable_support(const void *name, unsigned len, void *value) {
+       struct host_decl *h;
+       struct iaddrcidrnetlist *l;
+       struct in6_addr pref;
+       struct ipv6_ppool *p;
+
+       h = (struct host_decl *)value;
+
+       /*
+        * If the host has no prefix, we don't need to mark anything.
+        */
+       if (h->fixed_prefix == NULL) {
+               return ISC_R_SUCCESS;
+       }
+
+       /* 
+        * Get the fixed prefixes.
+        */
+       for (l = h->fixed_prefix; l != NULL; l = l->next) {
+               if (l->cidrnet.lo_addr.len != 16) {
+                       continue;
+               }
+               memcpy(&pref, l->cidrnet.lo_addr.iabuf, 16);
+
+               /*
+                * Find the pool holding this host, and mark the prefix.
+                * (I suppose it is arguably valid to have a host that does not
+                * sit in any pool.)
+                */
+               p = NULL;
+               if (find_ipv6_ppool(&p, &pref) != ISC_R_SUCCESS) {
+                       continue;
+               }
+               if (l->cidrnet.bits != (int) p->alloc_plen) {
+                       ipv6_ppool_dereference(&p, MDL);
+                       continue;
+               }
+               mark_prefix_unavailable(p, &pref);
+               ipv6_ppool_dereference(&p, MDL);
+       } 
+
+       return ISC_R_SUCCESS;
+}
+
+void
+mark_phosts_unavailable(void) {
+       hash_foreach(host_name_hash, mark_phosts_unavailable_support);
+}
+
 void 
 mark_interfaces_unavailable(void) {
        struct interface_info *ip;
@@ -2678,7 +2820,13 @@ mark_interfaces_unavailable(void) {
        while (ip != NULL) {
                for (i=0; i<ip->v6address_count; i++) {
                        p = NULL;
-                       if (find_ipv6_pool(&p, &ip->v6addresses[i]) 
+                       if (find_ipv6_pool(&p, 0, &ip->v6addresses[i]) 
+                                                       == ISC_R_SUCCESS) {
+                               mark_address_unavailable(p, 
+                                                        &ip->v6addresses[i]);
+                               ipv6_pool_dereference(&p, MDL);
+                       } 
+                       if (find_ipv6_pool(&p, 1, &ip->v6addresses[i]) 
                                                        == ISC_R_SUCCESS) {
                                mark_address_unavailable(p, 
                                                         &ip->v6addresses[i]);
@@ -3006,8 +3154,8 @@ main(int argc, char *argv[]) {
                printf("ERROR: activate_lease6() %s:%d\n", MDL);
                return 1;
        }
-       if (pool->num_active != 1) {
-               printf("ERROR: bad num_active %s:%d\n", MDL);
+       if (pool->num_inactive != 1) {
+               printf("ERROR: bad num_inactive %s:%d\n", MDL);
                return 1;
        }
        if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
@@ -3058,6 +3206,10 @@ main(int argc, char *argv[]) {
                printf("ERROR: activate_lease6() %s:%d\n", MDL);
                return 1;
        }
+       if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+               printf("ERROR: renew_lease6() %s:%d\n", MDL);
+               return 1;
+       }
        if (pool->num_active != 1) {
                printf("ERROR: bad num_active %s:%d\n", MDL);
                return 1;
@@ -3079,6 +3231,10 @@ main(int argc, char *argv[]) {
                printf("ERROR: activate_lease6() %s:%d\n", MDL);
                return 1;
        }
+       if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+               printf("ERROR: renew_lease6() %s:%d\n", MDL);
+               return 1;
+       }
        if (pool->num_active != 1) {
                printf("ERROR: bad num_active %s:%d\n", MDL);
                return 1;
@@ -3155,6 +3311,10 @@ main(int argc, char *argv[]) {
                        printf("ERROR: activate_lease6() %s:%d\n", MDL);
                        return 1;
                }
+               if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+                       printf("ERROR: renew_lease6() %s:%d\n", MDL);
+                       return 1;
+               }
                if (iaaddr_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
                        printf("ERROR: iaaddr_dereference() %s:%d\n", MDL);
                        return 1;
@@ -3211,6 +3371,7 @@ main(int argc, char *argv[]) {
         * Test 8: small pool
         */
        pool = NULL;
+       addr.s6_addr[14] = 0x81;
        if (ipv6_pool_allocate(&pool, &addr, 127, MDL) != ISC_R_SUCCESS) {
                printf("ERROR: ipv6_pool_allocate() %s:%d\n", MDL);
                return 1;
@@ -3220,6 +3381,10 @@ main(int argc, char *argv[]) {
                printf("ERROR: activate_lease6() %s:%d\n", MDL);
                return 1;
        }
+       if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+               printf("ERROR: renew_lease6() %s:%d\n", MDL);
+               return 1;
+       }
        if (iaaddr_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
                printf("ERROR: iaaddr_dereference() %s:%d\n", MDL);
                return 1;
@@ -3229,6 +3394,10 @@ main(int argc, char *argv[]) {
                printf("ERROR: activate_lease6() %s:%d\n", MDL);
                return 1;
        }
+       if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) {
+               printf("ERROR: renew_lease6() %s:%d\n", MDL);
+               return 1;
+       }
        if (iaaddr_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) {
                printf("ERROR: iaaddr_dereference() %s:%d\n", MDL);
                return 1;
@@ -3242,6 +3411,7 @@ main(int argc, char *argv[]) {
                printf("ERROR: ipv6_pool_dereference() %s:%d\n", MDL);
                return 1;
        }
+       addr.s6_addr[14] = 0;
 
        /* 
         * Test 9: functions across all pools
@@ -3260,7 +3430,7 @@ main(int argc, char *argv[]) {
                return 1;
        }
        pool = NULL;
-       if (find_ipv6_pool(&pool, &addr) != ISC_R_SUCCESS) {
+       if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_SUCCESS) {
                printf("ERROR: find_ipv6_pool() %s:%d\n", MDL);
                return 1;
        }
@@ -3270,7 +3440,7 @@ main(int argc, char *argv[]) {
        }
        inet_pton(AF_INET6, "1:2:3:4:ffff:ffff:ffff:ffff", &addr);
        pool = NULL;
-       if (find_ipv6_pool(&pool, &addr) != ISC_R_SUCCESS) {
+       if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_SUCCESS) {
                printf("ERROR: find_ipv6_pool() %s:%d\n", MDL);
                return 1;
        }
@@ -3280,13 +3450,13 @@ main(int argc, char *argv[]) {
        }
        inet_pton(AF_INET6, "1:2:3:5::", &addr);
        pool = NULL;
-       if (find_ipv6_pool(&pool, &addr) != ISC_R_NOTFOUND) {
+       if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_NOTFOUND) {
                printf("ERROR: find_ipv6_pool() %s:%d\n", MDL);
                return 1;
        }
        inet_pton(AF_INET6, "1:2:3:3:ffff:ffff:ffff:ffff", &addr);
        pool = NULL;
-       if (find_ipv6_pool(&pool, &addr) != ISC_R_NOTFOUND) {
+       if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_NOTFOUND) {
                printf("ERROR: find_ipv6_pool() %s:%d\n", MDL);
                return 1;
        }
@@ -3301,11 +3471,14 @@ main(int argc, char *argv[]) {
        {
                struct in6_addr r;
                struct data_string ds;
+               u_char data[16];
                char buf[64];
                int i, j;
 
+               memset(&ds, 0, sizeof(ds));
+               memset(data, 0xaa, sizeof(data));
                ds.len = 16;
-               ds.data = &addr;
+               ds.data = data;
 
                inet_pton(AF_INET6, "3ffe:501:ffff:100::", &addr);
                for (i = 32; i < 42; i++)
index eaa338ae4f0d82234413751a4f2334c9555c542c..abb9647992c9f264350a54124ee15af3e8b33a2a 100644 (file)
@@ -238,7 +238,8 @@ static struct option server_options[] = {
        { "dhcpv6-lease-file-name", "t",        &server_universe,  54, 1 },
        { "dhcpv6-pid-file-name", "t",          &server_universe,  55, 1 },
        { "limit-addrs-per-ia", "L",            &server_universe,  56, 1 },
-       { "delayed-ack", "S",                   &server_universe,  57, 1 },
+       { "limit-prefs-per-ia", "L",            &server_universe,  57, 1 },
+       { "delayed-ack", "S",                   &server_universe,  58, 1 },
        { NULL, NULL, NULL, 0, 0 }
 };