]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
[master] server (-6) now supports dhcp-cache-threshold
authorThomas Markwalder <tmark@isc.org>
Thu, 7 Dec 2017 15:48:06 +0000 (10:48 -0500)
committerThomas Markwalder <tmark@isc.org>
Thu, 7 Dec 2017 15:48:06 +0000 (10:48 -0500)
    Merges in rt45292.

RELNOTES
includes/dhcp6.h
server/dhcpd.conf.5
server/dhcpv6.c

index edfd705d95e206c06714c653ee4bfd2c8e7979f8..eee0d8dad16de007200e85a13b6ac0a3b52b3b3e 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -264,6 +264,17 @@ dhcp-users@lists.isc.org.
   the out-of-the-box user experience.
   [ISC-Bugs #45615]
 
+- Added support for 'dhcp-cache-threshold' to IPv6 operation: If a client
+  renews before 'dhcp-cache-threshold' percent of its lease has elapsed
+  (default 25%), the server will reuse the allocated lease (provide a
+  lease within the currently allocated lease-time) rather than extend or
+  renew the lease.  This allows the server to reply without needlessly
+  writing leases to disk.  The preferred and valid lease lifetimes
+  sent to the client will be reduced by the age of the lease. The option
+  may be specified down to the pool level and is supported for all three
+  pool types: NA, TA, and PD.
+  [ISC-Bugs #45292]
+
                        Changes since 4.3.0 (bug fixes)
 
 - Tidy up several small tickets.
index 4e9c46c074845f4121e01dc333640caa09961a26..b51bac26611c92b1606713e443b25d8a49c99173 100644 (file)
 #define D6O_DHCPV4_MSG                         87 /* RFC7341 */
 #define D6O_DHCP4_O_DHCP6_SERVER               88 /* RFC7341 */
 
-/* 
+/*
  * Status Codes, from RFC 3315 section 24.4, and RFC 3633, 5007, 5460.
  */
 #define STATUS_Success          0
 #define STATUS_UnspecFail       1
 #define STATUS_NoAddrsAvail     2
 #define STATUS_NoBinding        3
-#define STATUS_NotOnLink        4 
-#define STATUS_UseMulticast     5 
+#define STATUS_NotOnLink        4
+#define STATUS_UseMulticast     5
 #define STATUS_NoPrefixAvail    6
 #define STATUS_UnknownQueryType         7
 #define STATUS_MalformedQuery   8
 #define STATUS_NotAllowed      10
 #define STATUS_QueryTerminated 11
 
-/* 
- * DHCPv6 message types, defined in section 5.3 of RFC 3315 
+/*
+ * DHCPv6 message types, defined in section 5.3 of RFC 3315
  */
 #define DHCPV6_SOLICIT             1
 #define DHCPV6_ADVERTISE           2
@@ -181,8 +181,8 @@ extern const int dhcpv6_type_name_max;
 /* Offset into LQ_QUERY's where Option spaces commence. */
 #define LQ_QUERY_OFFSET 17
 
-/* 
- * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315 
+/*
+ * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315
  */
 #define All_DHCP_Relay_Agents_and_Servers "FF02::1:2"
 #define All_DHCP_Servers "FF05::1:3"
@@ -219,8 +219,8 @@ extern const int dhcpv6_type_name_max;
 #define LQ6_MAX_RT       10
 #define LQ6_MAX_RC        5
 
-/* 
- * Normal packet format, defined in section 6 of RFC 3315 
+/*
+ * Normal packet format, defined in section 6 of RFC 3315
  */
 struct dhcpv6_packet {
        unsigned char msg_type;
@@ -231,8 +231,8 @@ struct dhcpv6_packet {
 /* Offset into DHCPV6 Reply packets where Options spaces commence. */
 #define REPLY_OPTIONS_INDEX 4
 
-/* 
- * Relay packet format, defined in section 7 of RFC 3315 
+/*
+ * Relay packet format, defined in section 7 of RFC 3315
  */
 struct dhcpv6_relay_packet {
        unsigned char msg_type;
@@ -281,14 +281,14 @@ struct dhcpv4_over_dhcpv6_packet {
 #define IAID_LEN 4
 
 /* Offsets with iasubopt wire data of data values for IA_NA and TA */
-#define IASUBOPT_NA_ADDR_OFFSET         0
-#define IASUBOPT_NA_PREF_OFFSET         16
-#define IASUBOPT_NA_VALID_OFFSET        20
-#define IASUBOPT_NA_LEN                 24
+#define IASUBOPT_NA_ADDR_OFFSET                0
+#define IASUBOPT_NA_PREF_OFFSET                16
+#define IASUBOPT_NA_VALID_OFFSET       20
+#define IASUBOPT_NA_LEN                        24
 
 /* Offsets with iasubopt wire data of data values for PD */
-#define IASUBOPT_PD_PREF_OFFSET         0
-#define IASUBOPT_PD_VALID_OFFSET        4
-#define IASUBOPT_PD_PREFLEN_OFFSET      8
-#define IASUBOPT_PD_PREFIX_OFFSET       9
-#define IASUBOPT_PD_LEN                 25
+#define IASUBOPT_PD_PREF_OFFSET                0
+#define IASUBOPT_PD_VALID_OFFSET       4
+#define IASUBOPT_PD_PREFLEN_OFFSET     8
+#define IASUBOPT_PD_PREFIX_OFFSET      9
+#define IASUBOPT_PD_LEN                        25
index a0d88a83a4817ca6a395beb19a320ee3b8fc2e7d..076a35fb29cf11d532fecf5d32dfe5924dd011fa 100644 (file)
@@ -2191,16 +2191,28 @@ with allowed values between 0 and 100. The default value is 25 (25% of
 the lease time). This parameter expresses the percentage of the total
 lease time, measured from the beginning, during which a
 client's attempt to renew its lease will result in getting
-the already assigned lease, rather than an extended lease.
+the already assigned lease, rather than an extended lease.  This feature
+is supported for both IPv4 and IPv6 and down to the pool level and for
+IPv6 all three pool types: NA, TA and PD.
 .PP
 Clients that attempt renewal frequently can cause the server to
 update and write the database frequently resulting in a performance
 impact on the server.  The \fIdhcp-cache-threshold\fR
 statement instructs the DHCP server to avoid updating leases too
-frequently thus avoiding this behavior.  Instead the server assigns the
+frequently thus avoiding this behavior.  Instead the server replies with the
 same lease (i.e. reuses it) with no modifications except for CLTT (Client Last
-Transmission Time) which does not require disk operations. This
-feature applies to IPv4 only.
+Transmission Time) and for IPv4:
+
+    the lease time sent to the client is shortened by the age of
+    the lease
+
+while for IPv6:
+
+    the preferred and valid lifetimes sent to the client are
+    shortened by the age of the lease.
+
+None of these changes require writing the lease to disk.
+
 .PP
 When an existing lease is matched to a renewing client, it will be reused
 if all of the following conditions are true:
@@ -2212,12 +2224,16 @@ if all of the following conditions are true:
     4. The client information provided in the renewal does not alter
     any of the following:
        a. DNS information and DNS updates are enabled
-       b. Billing class to which the lease is associated
-       c. The host declaration associated with the lease
+       b. Billing class to which the lease is associated (IPv4 only)
+       c. The host declaration associated with the lease (IPv4 only)
        d. The client id - this may happen if a client boots without
-       a client id and then starts using one in subsequent requests.
+          a client id and then starts using one in subsequent
+          requests. (IPv4 only)
 .fi
 .PP
+While lease data is not written to disk when a lease is reused, the server
+will still execute any on-commit statements.
+.PP
 Note that the lease can be reused if the options the client or relay agent
 sends are changed.  These changes will not be recorded in the in-memory or
 on-disk databases until the client renews after the threshold time is reached.
index a45e4025a11ecb4ac3b3a738bb6f154e2888b76b..dec3c48aeb2df2b99504f9befcc0b6ec52b7067e 100644 (file)
@@ -179,6 +179,12 @@ set_reply_tee_times(struct reply_state* reply, unsigned ia_cursor);
 static const char *iasubopt_plen_str(struct iasubopt *lease);
 static int release_on_roam(struct reply_state *reply);
 
+static int reuse_lease6(struct reply_state *reply, struct iasubopt *lease);
+static void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease,
+                             time_t age, int threshold);
+static void write_to_packet(struct reply_state *reply, unsigned ia_cursor);
+static const char *iasubopt_plen_str(struct iasubopt *lease);
+
 #ifdef DHCP4o6
 /*
  * \brief Omapi I/O handler
@@ -2193,24 +2199,16 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                        goto cleanup;
        }
 
-       reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
-                                       sizeof(reply->buf) - reply->cursor,
-                                       reply->reply_ia, reply->packet,
-                                       required_opts_IA, 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));
-
-       /* Calculate T1/T2 and stuff them in the reply */
-       set_reply_tee_times(reply, ia_cursor);
-
        /*
         * yes, goto's aren't the best but we also want to avoid extra
         * indents
         */
-       if (status == ISC_R_CANCELED)
+       if (status == ISC_R_CANCELED) {
+               /* We're replying with a status code so we still need to
+                * write it out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
                goto cleanup;
+       }
 
        /*
         * Handle static leases, we always log stuff and if it's
@@ -2268,24 +2266,28 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
         * Loop through the assigned dynamic addresses, referencing the
         * leases onto this IA_NA rather than any old ones, and updating
         * pool timers for each (if any).
+        *
+        * Note that we must do ddns_updates() before we test for lease
+        * reuse (so we'll know if DNS entries are different).  To ensure
+        * we don't break any configs, we run on_commit statements before
+        * we do ddns_updates() just in case the former affects the later.
+        * This is symetrical with v4 logic.  We always run on_commit and
+        * ddns_udpates() whether a lease is reused or renewed.
         */
-
        if ((reply->ia->num_iasubopt != 0) &&
            (reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+               int must_commit = 0;
                struct iasubopt *tmp;
                struct data_string *ia_id;
                int i;
 
                for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
                        tmp = reply->ia->iasubopt[i];
-
-                       if (tmp->ia != NULL)
+                       if (tmp->ia != NULL) {
                                ia_dereference(&tmp->ia, MDL);
-                       ia_reference(&tmp->ia, reply->ia, MDL);
+                       }
 
-                       /* Commit 'hard' bindings. */
-                       renew_lease6(tmp->ipv6_pool, tmp);
-                       schedule_lease_timeout(tmp->ipv6_pool);
+                       ia_reference(&tmp->ia, reply->ia, MDL);
 
                        /* If we have anything to do on commit do it now */
                        if (tmp->on_star.on_commit != NULL) {
@@ -2301,9 +2303,8 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                        }
 
 #if defined (NSUPDATE)
-                       /*
-                        * Perform ddns updates.
-                        */
+
+                       /* Perform ddns updates */
                        oc = lookup_option(&server_universe, reply->opt_state,
                                           SV_DDNS_UPDATES);
                        if ((oc == NULL) ||
@@ -2317,10 +2318,20 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                                             tmp, NULL, reply->opt_state);
                        }
 #endif
-                       /* Do our threshold check. */
-                       check_pool6_threshold(reply, tmp);
+                       if (!reuse_lease6(reply, tmp)) {
+                               /* Commit 'hard' bindings. */
+                               must_commit = 1;
+                               renew_lease6(tmp->ipv6_pool, tmp);
+                               schedule_lease_timeout(tmp->ipv6_pool);
+
+                               /* Do our threshold check. */
+                               check_pool6_threshold(reply, tmp);
+                       }
                }
 
+               /* write the IA_NA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+
                /* Remove any old ia from the hash. */
                if (reply->old_ia != NULL) {
                        if (!release_on_roam(reply)) {
@@ -2339,8 +2350,14 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
                ia_hash_add(ia_na_active, (unsigned char *)ia_id->data,
                            ia_id->len, reply->ia, MDL);
 
-               write_ia(reply->ia);
+               /* If we couldn't reuse all of the iasubopts, we
+               * must update udpate the lease db */
+               if (must_commit) {
+                       write_ia(reply->ia);
+               }
        } else {
+               /* write the IA_NA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
                schedule_lease_timeout_reply(reply);
        }
 
@@ -2378,6 +2395,28 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
        return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
 }
 
+/*
+ * Writes the populated IA_xx in wire format to the reply buffer
+ */
+void
+write_to_packet(struct reply_state *reply, unsigned ia_cursor) {
+       reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+                                       sizeof(reply->buf) - reply->cursor,
+                                       reply->reply_ia, reply->packet,
+                                       (reply->ia->ia_type != D6O_IA_PD ?
+                                       required_opts_IA : required_opts_IA_PD),
+                                       NULL);
+
+       /* Reset the length of this IA to match what was just written. */
+       putUShort(reply->buf.data + ia_cursor + 2,
+                 reply->cursor - (ia_cursor + 4));
+
+       if (reply->ia->ia_type != D6O_IA_TA) {
+               /* Calculate T1/T2 and stuff them in the reply */
+               set_reply_tee_times(reply, ia_cursor);
+       }
+}
+
 /*
  * Process an IAADDR within a given IA_xA, storing any IAADDR reply contents
  * into the reply's current ia-scoped option cache.  Returns ISC_R_CANCELED
@@ -2973,21 +3012,17 @@ reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
                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));
 
        /*
         * yes, goto's aren't the best but we also want to avoid extra
         * indents
         */
-       if (status == ISC_R_CANCELED)
+       if (status == ISC_R_CANCELED) {
+               /* We're replying with a status code so we still need to
+               * write it out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
                goto cleanup;
+       }
 
        /*
         * If we have any addresses log what we are doing.
@@ -3022,6 +3057,7 @@ reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
         */
        if ((reply->ia->num_iasubopt != 0) &&
            (reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+               int must_commit = 0;
                struct iasubopt *tmp;
                struct data_string *ia_id;
                int i;
@@ -3033,10 +3069,6 @@ reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
                                ia_dereference(&tmp->ia, MDL);
                        ia_reference(&tmp->ia, reply->ia, MDL);
 
-                       /* Commit 'hard' bindings. */
-                       renew_lease6(tmp->ipv6_pool, tmp);
-                       schedule_lease_timeout(tmp->ipv6_pool);
-
                        /* If we have anything to do on commit do it now */
                        if (tmp->on_star.on_commit != NULL) {
                                execute_statements(NULL, reply->packet,
@@ -3067,10 +3099,21 @@ reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
                                             tmp, NULL, reply->opt_state);
                        }
 #endif
-                       /* Do our threshold check. */
-                       check_pool6_threshold(reply, tmp);
+
+                       if (!reuse_lease6(reply, tmp)) {
+                               /* Commit 'hard' bindings. */
+                               must_commit = 1;
+                               renew_lease6(tmp->ipv6_pool, tmp);
+                               schedule_lease_timeout(tmp->ipv6_pool);
+
+                               /* Do our threshold check. */
+                               check_pool6_threshold(reply, tmp);
+                       }
                }
 
+               /* write the IA_TA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+
                /* Remove any old ia from the hash. */
                if (reply->old_ia != NULL) {
                        if (!release_on_roam(reply)) {
@@ -3089,8 +3132,14 @@ reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
                ia_hash_add(ia_ta_active, (unsigned char *)ia_id->data,
                            ia_id->len, reply->ia, MDL);
 
-               write_ia(reply->ia);
+               /* If we couldn't reuse all of the iasubopts, we
+               * must update udpate the lease db */
+               if (must_commit) {
+                       write_ia(reply->ia);
+               }
        } else {
+               /* write the IA_TA in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
                schedule_lease_timeout_reply(reply);
        }
 
@@ -3119,6 +3168,170 @@ reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
         */
        return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
 }
+/*
+ * Determines if a lease (iasubopt) can be reused without extending it.
+ * If dhcp-cache-threshold is greater than zero (i.e enabled) then
+ * a lease may be reused without going through a full renewal if
+ * it meets all the requirements.  In short it must be active, younger
+ * than the threshold, and not have DNS changes.
+ *
+ * If it is determined that it can be reused, that a call to
+ * shorten_lifetimes() is made to reduce the valid and preferred lifetimes
+ * sent to the client by the age of the lease.
+ *
+ * Returns 1 if lease can be reused, 0 otherwise
+ */
+int
+reuse_lease6(struct reply_state *reply, struct iasubopt *lease) {
+       int threshold = DEFAULT_CACHE_THRESHOLD;
+       struct option_cache* oc = NULL;
+       struct data_string d1;
+       time_t age;
+       time_t limit;
+       int reuse_it = 0;
+
+       /* In order to even qualify for reuse consideration:
+        * 1. Lease must be active
+        * 2. It must have been accepted at least once
+        * 3. DNS info must not have changed */
+       if ((lease->state != FTS_ACTIVE) ||
+           (lease->hard_lifetime_end_time == 0) ||
+           (lease->ddns_cb != NULL)) {
+               return (0);
+       }
+
+       /* Look up threshold value */
+       memset(&d1, 0, sizeof(struct data_string));
+       oc = lookup_option(&server_universe, reply->opt_state,
+                          SV_CACHE_THRESHOLD);
+       if (oc &&
+           evaluate_option_cache(&d1, reply->packet, NULL, NULL,
+                                 reply->packet->options, reply->opt_state,
+                                 &lease->scope, oc, MDL)) {
+                       if (d1.len == 1 && (d1.data[0] < 100)) {
+                                threshold = d1.data[0];
+                       }
+
+               data_string_forget(&d1, MDL);
+       }
+
+       if (threshold <= 0) {
+               return (0);
+       }
+
+       if (lease->valid >= MAX_TIME) {
+               /* Infinite leases are always reused.  We have to make
+               * a choice because we cannot determine when they actually
+               * began, so we either always reuse them or we never do. */
+               log_debug ("reusing infinite lease for: %s%s",
+                           pin6_addr(&lease->addr), iasubopt_plen_str(lease));
+               return (1);
+       }
+
+       age = cur_tv.tv_sec - (lease->hard_lifetime_end_time - lease->valid);
+       if (lease->valid <= (INT_MAX / threshold))
+               limit = lease->valid * threshold / 100;
+       else
+               limit = lease->valid / 100 * threshold;
+
+       if (age < limit) {
+               /* Reduce valid/preferred going to the client by age */
+               shorten_lifetimes(reply, lease, age, threshold);
+               reuse_it = 1;
+       }
+
+       return (reuse_it);
+}
+
+/*
+ * Reduces the valid and preferred lifetimes for a given lease (iasubopt)
+ *
+ * We cannot determine until after a iasubopt has been added to
+ * the reply if the lease can be reused. Therefore, when we do reuse a
+ * lease we need a way to alter the lifetimes that will be sent to the client.
+ * That's where this function comes in handy:
+ *
+ * Locate the iasubopt by it's address within the reply the reduce both
+ * the preferred and valid lifetimes by the given number of seconds.
+ *
+ * Note that this function, by necessity, works directly with the
+ * option_cache data. Sort of a no-no but I don't have any better ideas.
+ */
+void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease,
+                      time_t age, int threshold) {
+       struct option_cache* oc = NULL;
+       int subopt_type;
+       int addr_offset;
+       int pref_offset;
+       int val_offset;
+       int exp_length;
+
+       if (reply->ia->ia_type != D6O_IA_PD) {
+               subopt_type = D6O_IAADDR;
+               addr_offset = IASUBOPT_NA_ADDR_OFFSET;
+               pref_offset = IASUBOPT_NA_PREF_OFFSET;
+               val_offset = IASUBOPT_NA_VALID_OFFSET;
+               exp_length = IASUBOPT_NA_LEN;
+       }
+       else {
+               subopt_type = D6O_IAPREFIX;
+               addr_offset = IASUBOPT_PD_PREFIX_OFFSET;
+               pref_offset = IASUBOPT_PD_PREF_OFFSET;
+               val_offset = IASUBOPT_PD_VALID_OFFSET;
+               exp_length = IASUBOPT_PD_LEN;
+       }
+
+       // loop through the iasubopts for the one that matches this lease
+       oc = lookup_option(&dhcpv6_universe, reply->reply_ia, subopt_type);
+        for (; oc != NULL ; oc = oc->next) {
+               if (oc->data.data == NULL || oc->data.len != exp_length) {
+                       /* shouldn't happen */
+                       continue;
+               }
+
+               /* If address matches (and for PDs the prefix len matches)
+               * we assume this is our subopt, so update the lifetimes */
+               if (!memcmp(oc->data.data + addr_offset, &lease->addr, 16) &&
+                   (subopt_type != D6O_IA_PD ||
+                    (oc->data.data[IASUBOPT_PD_PREFLEN_OFFSET] ==
+                     lease->plen))) {
+                       u_int32_t pref_life = getULong(oc->data.data +
+                                                      pref_offset);
+                       u_int32_t valid_life = getULong(oc->data.data +
+                                                       val_offset);
+
+                       if (pref_life < MAX_TIME && pref_life > age) {
+                               pref_life -= age;
+                               putULong((unsigned char*)(oc->data.data) +
+                                         pref_offset, pref_life);
+
+                               if (reply->min_prefer > pref_life) {
+                                       reply->min_prefer = pref_life;
+                               }
+                       }
+
+                       if (valid_life < MAX_TIME && valid_life > age) {
+                               valid_life -= age;
+                               putULong((unsigned char*)(oc->data.data) +
+                                        val_offset, valid_life);
+
+                               if (reply->min_valid > reply->send_valid) {
+                                       reply->min_valid = valid_life;
+                               }
+                       }
+
+                       log_debug ("Reusing lease for: %s%s, "
+                                  "age %ld secs < %d%%,"
+                                  " sending shortened lifetimes -"
+                                  " preferred: %u, valid %u",
+                                  pin6_addr(&lease->addr),
+                                  iasubopt_plen_str(lease),
+                                  age, threshold,
+                                  pref_life, valid_life);
+                       break;
+               }
+       }
+}
 
 /*
  * Verify the temporary address is available.
@@ -4008,24 +4221,16 @@ reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
                        goto cleanup;
        }
 
-       reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
-                                       sizeof(reply->buf) - reply->cursor,
-                                       reply->reply_ia, reply->packet,
-                                       required_opts_IA_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));
-
-       /* Calculate T1/T2 and stuff them in the reply */
-       set_reply_tee_times(reply, ia_cursor);
-
        /*
         * yes, goto's aren't the best but we also want to avoid extra
         * indents
         */
-       if (status == ISC_R_CANCELED)
+       if (status == ISC_R_CANCELED) {
+               /* We're replying with a status code so we still need to
+                * write it out in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
                goto cleanup;
+       }
 
        /*
         * Handle static prefixes, we always log stuff and if it's
@@ -4085,9 +4290,14 @@ reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
         * Loop through the assigned dynamic prefixes, referencing the
         * prefixes onto this IA_PD rather than any old ones, and updating
         * prefix pool timers for each (if any).
+        *
+        * If a lease can be reused we skip renewing it or checking the
+        * pool threshold. If it can't we flag that the IA must be commited
+        * to the db and do the renewal and pool check.
         */
        if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
            (reply->ia->num_iasubopt != 0)) {
+               int must_commit = 0;
                struct iasubopt *tmp;
                struct data_string *ia_id;
                int i;
@@ -4099,10 +4309,6 @@ reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
                                ia_dereference(&tmp->ia, MDL);
                        ia_reference(&tmp->ia, reply->ia, MDL);
 
-                       /* Commit 'hard' bindings. */
-                       renew_lease6(tmp->ipv6_pool, tmp);
-                       schedule_lease_timeout(tmp->ipv6_pool);
-
                        /* If we have anything to do on commit do it now */
                        if (tmp->on_star.on_commit != NULL) {
                                execute_statements(NULL, reply->packet,
@@ -4116,10 +4322,20 @@ reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
                                        (&tmp->on_star.on_commit, MDL);
                        }
 
-                       /* Do our threshold check. */
-                       check_pool6_threshold(reply, tmp);
+                       if (!reuse_lease6(reply, tmp)) {
+                               /* Commit 'hard' bindings. */
+                               must_commit = 1;
+                               renew_lease6(tmp->ipv6_pool, tmp);
+                               schedule_lease_timeout(tmp->ipv6_pool);
+
+                               /* Do our threshold check. */
+                               check_pool6_threshold(reply, tmp);
+                       }
                }
 
+               /* write the IA_PD in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
+
                /* Remove any old ia from the hash. */
                if (reply->old_ia != NULL) {
                        if (!release_on_roam(reply)) {
@@ -4138,8 +4354,14 @@ reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
                ia_hash_add(ia_pd_active, (unsigned char *)ia_id->data,
                            ia_id->len, reply->ia, MDL);
 
-               write_ia(reply->ia);
+               /* If we couldn't reuse all of the iasubopts, we
+               * must udpate the lease db */
+               if (must_commit) {
+                       write_ia(reply->ia);
+               }
        } else {
+               /* write the IA_PD in wire-format to the outbound buffer */
+               write_to_packet(reply, ia_cursor);
                schedule_lease_timeout_reply(reply);
        }