summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorThomas Markwalder <tmark@isc.org>2017-12-07 10:48:06 -0500
committerThomas Markwalder <tmark@isc.org>2017-12-07 10:48:06 -0500
commitbbdac71d26cd42e50d399de7738dc91bf74f6e42 (patch)
tree7873c067c2dac44ef5f713f831393cfa0da0ae90 /server
parentb53ba1572c0d9ef0cb57c85904b11f59be032b61 (diff)
downloadisc-dhcp-bbdac71d26cd42e50d399de7738dc91bf74f6e42.tar.gz
[master] server (-6) now supports dhcp-cache-threshold
Merges in rt45292.
Diffstat (limited to 'server')
-rw-r--r--server/dhcpd.conf.530
-rw-r--r--server/dhcpv6.c346
2 files changed, 307 insertions, 69 deletions
diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5
index a0d88a83..076a35fb 100644
--- a/server/dhcpd.conf.5
+++ b/server/dhcpd.conf.5
@@ -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.
diff --git a/server/dhcpv6.c b/server/dhcpv6.c
index a45e4025..dec3c48a 100644
--- a/server/dhcpv6.c
+++ b/server/dhcpv6.c
@@ -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);
}
@@ -2379,6 +2396,28 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
}
/*
+ * Writes the populated IA_xx in wire format to the reply buffer
+ */
+void
+write_to_packet(struct reply_state *reply, unsigned ia_cursor) {
+ reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+ sizeof(reply->buf) - reply->cursor,
+ reply->reply_ia, reply->packet,
+ (reply->ia->ia_type != D6O_IA_PD ?
+ required_opts_IA : required_opts_IA_PD),
+ NULL);
+
+ /* Reset the length of this IA to match what was just written. */
+ putUShort(reply->buf.data + ia_cursor + 2,
+ reply->cursor - (ia_cursor + 4));
+
+ if (reply->ia->ia_type != D6O_IA_TA) {
+ /* Calculate T1/T2 and stuff them in the reply */
+ set_reply_tee_times(reply, ia_cursor);
+ }
+}
+
+/*
* Process an IAADDR within a given IA_xA, storing any IAADDR reply contents
* into the reply's current ia-scoped option cache. Returns ISC_R_CANCELED
* in the event we are replying with a status code and do not wish to process
@@ -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);
}