Commit bbdac71d authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[master] server (-6) now supports dhcp-cache-threshold

    Merges in rt45292.
parent b53ba157
......@@ -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.
......
......@@ -116,15 +116,15 @@
#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
......@@ -132,8 +132,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
......@@ -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.
......
......@@ -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. */