From d9b43370a44f0764f26ebf6fa73e3bffaf50afbb Mon Sep 17 00:00:00 2001 From: Shane Kerr Date: Fri, 18 May 2007 09:26:58 +0000 Subject: [PATCH] Expire old IPv6 leases. Also a number of fixes. See RT ticket #16849 for details. --- RELNOTES | 4 +- includes/dhcpd.h | 12 +- server/confpars.c | 32 ++++- server/dhcpd.c | 7 +- server/dhcpv6.c | 20 ++- server/mdb6.c | 356 +++++++++++++++++++++++++++++++++++----------- 6 files changed, 334 insertions(+), 97 deletions(-) diff --git a/RELNOTES b/RELNOTES index 711c0ba1..21399a40 100644 --- a/RELNOTES +++ b/RELNOTES @@ -39,8 +39,6 @@ release, which will be addressed shortly: - Dynamically allocated leases do not respond to Confirm messages. -- Old (expired) leases are never cleaned. - - The client and server can only operate DHCPv4 or DHCPv6 at a time, not both, so two instances of the daemons are required with the "-6" command line option. @@ -52,6 +50,8 @@ the README file. Changes since 4.0.0-20070413 +- Old (expired) leases are now cleaned. + - IPv6 subnets now have support for arbitrary allocation ranges via a new 'range6' configuration directive. diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 022cd9c4..a8f9ba8d 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -1249,8 +1249,10 @@ typedef unsigned char option_mask [16]; #define MAX_TIME 0x7fffffff #define MIN_TIME 0 + /* these are referenced */ typedef struct hash_table ia_na_hash_t; typedef struct hash_table iaaddr_hash_t; + int num_inactive; /* count of inactive IAADDR */ struct iaaddr { int refcnt; /* reference count */ @@ -1260,6 +1262,12 @@ struct iaaddr { time_t valid_lifetime_end_time; /* time address expires */ struct ia_na *ia_na; /* IA for this address */ struct ipv6_pool *ipv6_pool; /* pool for this address */ +/* + * For now, just pick an arbitrary time to keep old leases + * around (value in seconds). + */ +#define EXPIRED_IPV6_CLEANUP_TIME (60*60) + int heap_index; /* index into heap, or -1 (internal use only) */ }; @@ -3110,6 +3118,7 @@ isc_result_t dhcp_failover_process_update_request_all (dhcp_failover_state_t *, failover_message_t *); isc_result_t dhcp_failover_process_update_done (dhcp_failover_state_t *, failover_message_t *); +void ia_na_remove_all_iaaddr(struct ia_na *ia_na, const char *file, int line); void dhcp_failover_recover_done (void *); void failover_print PROTO ((char *, unsigned *, unsigned, const char *)); void update_partner PROTO ((struct lease *)); @@ -3194,10 +3203,11 @@ isc_result_t find_ipv6_pool(struct ipv6_pool **pool, isc_boolean_t ipv6_addr_in_pool(const struct in6_addr *addr, const struct ipv6_pool *pool); -void expire_leases(time_t now); isc_result_t renew_leases(struct ia_na *ia_na); isc_result_t release_leases(struct ia_na *ia_na); isc_result_t decline_leases(struct ia_na *ia_na); +void schedule_lease_timeout(struct ipv6_pool *pool); +void schedule_all_ipv6_lease_timeouts(); void mark_hosts_unavailable(void); void mark_interfaces_unavailable(void); diff --git a/server/confpars.c b/server/confpars.c index 61b696b6..218a0789 100644 --- a/server/confpars.c +++ b/server/confpars.c @@ -34,7 +34,7 @@ #ifndef lint static char copyright[] = -"$Id: confpars.c,v 1.163 2007/05/08 23:05:21 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; +"$Id: confpars.c,v 1.164 2007/05/18 09:26:58 shane Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" @@ -3805,6 +3805,7 @@ parse_ia_na_declaration(struct parse *cfile) { enum dhcp_token token; struct ia_na *ia_na; const char *val; + struct ia_na *old_ia_na; int len; u_int32_t iaid; struct iaddr iaddr; @@ -3959,23 +3960,42 @@ parse_ia_na_declaration(struct parse *cfile) { add_lease6(pool, iaaddr, end_time); switch (state) { case FTS_ABANDONED: - decline_lease6(pool, iaaddr); + release_lease6(pool, iaaddr); break; case FTS_EXPIRED: decline_lease6(pool, iaaddr); iaaddr->state = FTS_EXPIRED; break; case FTS_RELEASED: - decline_lease6(pool, iaaddr); - iaaddr->state = FTS_RELEASED; + release_lease6(pool, iaaddr); break; } ipv6_pool_dereference(&pool, MDL); iaaddr_dereference(&iaaddr, MDL); } - ia_na_hash_add(ia_active, (char *)ia_na->iaid_duid.data, - ia_na->iaid_duid.len, ia_na, MDL); + /* + * If we have an existing record for this IA_NA, remove it. + */ + old_ia_na = NULL; + if (ia_na_hash_lookup(&old_ia_na, ia_active, + (char *)ia_na->iaid_duid.data, + ia_na->iaid_duid.len, MDL)) { + ia_na_hash_delete(ia_active, + (char *)ia_na->iaid_duid.data, + ia_na->iaid_duid.len, MDL); + ia_na_remove_all_iaaddr(old_ia_na, MDL); + ia_na_dereference(&old_ia_na, MDL); + } + + /* + * If we have addresses, add this, otherwise don't bother. + */ + if (ia_na->num_iaaddr > 0) { + ia_na_hash_add(ia_active, (char *)ia_na->iaid_duid.data, + ia_na->iaid_duid.len, ia_na, MDL); + } + ia_na_dereference(&ia_na, MDL); } /* diff --git a/server/dhcpd.c b/server/dhcpd.c index 6de4d9f2..2c0a8d16 100644 --- a/server/dhcpd.c +++ b/server/dhcpd.c @@ -34,7 +34,7 @@ #ifndef lint static char ocopyright[] = -"$Id: dhcpd.c,v 1.122 2007/05/08 23:05:22 dhankins Exp $ Copyright 2004-2006 Internet Systems Consortium."; +"$Id: dhcpd.c,v 1.123 2007/05/18 09:26:58 shane Exp $ Copyright 2004-2006 Internet Systems Consortium."; #endif static char copyright[] = @@ -952,6 +952,11 @@ void postdb_startup (void) /* Initialize the failover listener state. */ dhcp_failover_startup (); #endif + + /* + * Begin our lease timeout background task. + */ + schedule_all_ipv6_lease_timeouts(); } /* Print usage message. */ diff --git a/server/dhcpv6.c b/server/dhcpv6.c index fca44c56..96a463cd 100644 --- a/server/dhcpv6.c +++ b/server/dhcpv6.c @@ -989,6 +989,7 @@ lease_to_client(struct data_string *reply_ret, u_int32_t iaid; struct ia_na *ia_na; struct ia_na *existing_ia_na; + struct ia_na *old_ia_na; int i; /* @@ -1548,9 +1549,21 @@ lease_to_client(struct data_string *reply_ret, * Otherwise save the IA_NA, for the same reason. */ else if (packet->dhcpv6_msg_type != DHCPV6_SOLICIT) { - ia_na_hash_delete(ia_active, - (char *)ia_na->iaid_duid.data, - ia_na->iaid_duid.len, MDL); + /* + * Remove previous version of this IA_NA, + * if one exists. + */ + struct data_string *d = &ia_na->iaid_duid; + old_ia_na = NULL; + if (ia_na_hash_lookup(&old_ia_na, ia_active, + (char *)d->data, + d->len, MDL)) { + ia_na_hash_delete(ia_active, + (char *)d->data, + d->len, MDL); + ia_na_dereference(&old_ia_na, MDL); + } + /* * ia_na_add_iaaddr() will reference the * lease, so we need to dereference the @@ -1570,6 +1583,7 @@ lease_to_client(struct data_string *reply_ret, ia_na->iaid_duid.len, ia_na, MDL); write_ia_na(ia_na); + schedule_lease_timeout(lease->ipv6_pool); /* If this constitutes a binding, and we * are performing ddns updates, then give diff --git a/server/mdb6.c b/server/mdb6.c index 2f94bd9a..fcfc2c13 100644 --- a/server/mdb6.c +++ b/server/mdb6.c @@ -321,6 +321,8 @@ ia_na_remove_iaaddr(struct ia_na *ia_na, struct iaaddr *iaaddr, ia_na->iaaddr[j-1] = ia_na->iaaddr[j]; } /* decrease our total count */ + /* remove the back-reference in the IAADDR itself */ + ia_na_dereference(&iaaddr->ia_na, file, line); ia_na->num_iaaddr--; return; } @@ -328,6 +330,19 @@ ia_na_remove_iaaddr(struct ia_na *ia_na, struct iaaddr *iaaddr, log_error("%s(%d): IAADDR not in IA_NA", file, line); } + * Remove all addresses from an IA_NA. + */ +void +ia_na_remove_all_iaaddr(struct ia_na *ia_na, const char *file, int line) { + int i, j; + + for (i=0; inum_iaaddr; i++) { + iaaddr_dereference(&(ia_na->iaaddr[i]), file, line); + } + ia_na->num_iaaddr = 0; +} + +/* /* * Helper function for lease heaps. * Makes the top of the heap the oldest lease. @@ -572,8 +587,7 @@ activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr, struct iaaddr *test_iaaddr; struct data_string new_ds; struct iaaddr *iaaddr; - isc_result_t iaaddr_allocate_result; - isc_result_t insert_result; + isc_result_t result; /* * Use the UID as our initial seed for the hash @@ -616,8 +630,8 @@ activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr, return ISC_R_NOMEMORY; } new_ds.data = new_ds.buffer->data; - memcpy((char *)new_ds.data, ds.data, ds.len); - memcpy((char *)new_ds.data + ds.len, &tmp, sizeof(tmp)); + memcpy(new_ds.buffer->data, ds.data, ds.len); + memcpy(new_ds.buffer->data + ds.len, &tmp, sizeof(tmp)); data_string_forget(&ds, MDL); data_string_copy(&ds, &new_ds, MDL); data_string_forget(&new_ds, MDL); @@ -630,14 +644,21 @@ activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr, * to hold it. */ iaaddr = NULL; - iaaddr_allocate_result = iaaddr_allocate(&iaaddr, MDL); - if (iaaddr_allocate_result != ISC_R_SUCCESS) { - return iaaddr_allocate_result; + result = iaaddr_allocate(&iaaddr, MDL); + if (result != ISC_R_SUCCESS) { + return result; } memcpy(&iaaddr->addr, &tmp, sizeof(iaaddr->addr)); - iaaddr_reference(addr, iaaddr, MDL); - return add_lease6(pool, iaaddr, valid_lifetime_end_time); + /* + * Add the lease to the pool. + */ + result = add_lease6(pool, iaaddr, valid_lifetime_end_time); + if (result == ISC_R_SUCCESS) { + iaaddr_reference(addr, iaaddr, MDL); + } + iaaddr_dereference(&iaaddr, MDL); + return result; } /* @@ -648,24 +669,58 @@ isc_result_t add_lease6(struct ipv6_pool *pool, struct iaaddr *iaaddr, time_t valid_lifetime_end_time) { isc_result_t insert_result; + struct iaaddr *test_iaaddr; + struct iaaddr *tmp_iaaddr; iaaddr->state = FTS_ACTIVE; iaaddr->valid_lifetime_end_time = valid_lifetime_end_time; ipv6_pool_reference(&iaaddr->ipv6_pool, pool, MDL); + /* + * If this IAADDR is already in our structures, remove the + * old one. + */ + test_iaaddr = NULL; + if (iaaddr_hash_lookup(&test_iaaddr, pool->addrs, + &iaaddr->addr, sizeof(iaaddr->addr), MDL)) { + isc_heap_delete(pool->active_timeouts, test_iaaddr->heap_index); + iaaddr_hash_delete(pool->addrs, &test_iaaddr->addr, + sizeof(test_iaaddr->addr), MDL); + pool->num_active--; + + /* + * We're going to do a bit of evil trickery here. + * + * We need to dereference the entry once to remove our + * current reference (in test_iaaddr), and then one + * more time to remove the reference left when the + * address was added to the pool before. + */ + tmp_iaaddr = test_iaaddr; + iaaddr_dereference(&test_iaaddr, MDL); + iaaddr_dereference(&tmp_iaaddr, MDL); + } + /* * Add IAADDR to our structures. */ - iaaddr_hash_add(pool->addrs, &iaaddr->addr, - sizeof(iaaddr->addr), iaaddr, MDL); - insert_result = isc_heap_insert(pool->active_timeouts, iaaddr); + tmp_iaaddr = NULL; + iaaddr_reference(&tmp_iaaddr, iaaddr, MDL); + iaaddr_hash_add(pool->addrs, &tmp_iaaddr->addr, + sizeof(tmp_iaaddr->addr), iaaddr, MDL); + insert_result = isc_heap_insert(pool->active_timeouts, tmp_iaaddr); if (insert_result != ISC_R_SUCCESS) { iaaddr_hash_delete(pool->addrs, &iaaddr->addr, sizeof(iaaddr->addr), MDL); - iaaddr_dereference(&iaaddr, MDL); + iaaddr_dereference(&tmp_iaaddr, MDL); return insert_result; } + /* + * Note: we intentionally leave tmp_iaaddr referenced; there + * is a reference in the heap/hash, after all. + */ + /* * And we're done. */ @@ -673,6 +728,9 @@ add_lease6(struct ipv6_pool *pool, struct iaaddr *iaaddr, return ISC_R_SUCCESS; } +/* + * Determine if an address is present in a pool or not. + */ isc_boolean_t lease6_exists(const struct ipv6_pool *pool, const struct in6_addr *addr) { struct iaaddr *test_iaaddr; @@ -687,6 +745,27 @@ lease6_exists(const struct ipv6_pool *pool, const struct in6_addr *addr) { } } +/* + * Put the lease on our active pool. + */ +static isc_result_t +move_lease_to_active(struct ipv6_pool *pool, struct iaaddr *addr) { + isc_result_t insert_result; + int old_heap_index; + + old_heap_index = addr->heap_index; + insert_result = isc_heap_insert(pool->active_timeouts, addr); + if (insert_result == ISC_R_SUCCESS) { + iaaddr_hash_add(pool->addrs, &addr->addr, + sizeof(addr->addr), addr, MDL); + isc_heap_delete(pool->inactive_timeouts, old_heap_index); + pool->num_active++; + pool->num_inactive--; + addr->state = FTS_ACTIVE; + } + return insert_result; +} + /* * Renew an lease in the pool. * @@ -694,58 +773,22 @@ lease6_exists(const struct ipv6_pool *pool, const struct in6_addr *addr) { * and then invoke renew_lease() on the address. * * WARNING: lease times must only be extended, never reduced!!! - * - * We return a isc_result_t so this function can be called the same - * as release or decline. */ isc_result_t renew_lease6(struct ipv6_pool *pool, struct iaaddr *addr) { - isc_heap_decreased(pool->active_timeouts, addr->heap_index); - return ISC_R_SUCCESS; -} - -/* - * Expire the oldest lease if it's lifetime_end_time is - * older than the given time. - * - * - iaaddr must be a pointer to a (struct iaaddr *) pointer previously - * initialized to NULL - * - * On return iaaddr has a reference to the removed entry. It is left - * pointing to NULL if the oldest lease has not expired. - */ -isc_result_t -expire_lease6(struct iaaddr **addr, struct ipv6_pool *pool, time_t now) { - struct iaaddr *tmp; - isc_result_t insert_result; - - if (addr == NULL) { - log_error("%s(%d): NULL pointer reference", MDL); - return ISC_R_INVALIDARG; + /* + * If we're already active, then we can just move our expiration + * time down the heap. + * + * Otherwise, we have to move from the inactive heap to the + * active heap. + */ + if (addr->state == FTS_ACTIVE) { + isc_heap_decreased(pool->active_timeouts, addr->heap_index); + return ISC_R_SUCCESS; + } else { + return move_lease_to_active(pool, addr); } - if (*addr != NULL) { - log_error("%s(%d): non-NULL pointer", MDL); - return ISC_R_INVALIDARG; - } - - if (pool->num_active > 0) { - tmp = (struct iaaddr *)isc_heap_element(pool->active_timeouts, - 1); - if (now > tmp->valid_lifetime_end_time) { - insert_result = isc_heap_insert(pool->inactive_timeouts, - tmp); - if (insert_result != ISC_R_SUCCESS) { - return insert_result; - } - iaaddr_hash_delete(pool->addrs, - &tmp->addr, sizeof(tmp->addr), MDL); - isc_heap_delete(pool->active_timeouts, 1); - tmp->state = FTS_EXPIRED; - iaaddr_reference(addr, tmp, MDL); - pool->num_active--; - } - } - return ISC_R_SUCCESS; } /* @@ -765,16 +808,64 @@ move_lease_to_inactive(struct ipv6_pool *pool, struct iaaddr *addr, isc_heap_delete(pool->active_timeouts, old_heap_index); addr->state = state; pool->num_active--; + pool->num_inactive++; } return insert_result; } +/* + * Expire the oldest lease if it's lifetime_end_time is + * older than the given time. + * + * - iaaddr must be a pointer to a (struct iaaddr *) pointer previously + * initialized to NULL + * + * On return iaaddr has a reference to the removed entry. It is left + * pointing to NULL if the oldest lease has not expired. + */ +isc_result_t +expire_lease6(struct iaaddr **addr, struct ipv6_pool *pool, time_t now) { + struct iaaddr *tmp; + isc_result_t result; + + if (addr == NULL) { + log_error("%s(%d): NULL pointer reference", MDL); + return ISC_R_INVALIDARG; + } + if (*addr != NULL) { + log_error("%s(%d): non-NULL pointer", MDL); + return ISC_R_INVALIDARG; + } + + if (pool->num_active > 0) { + tmp = (struct iaaddr *)isc_heap_element(pool->active_timeouts, + 1); + if (now > tmp->valid_lifetime_end_time) { + result = move_lease_to_inactive(pool, tmp, FTS_EXPIRED); + if (result == ISC_R_SUCCESS) { + iaaddr_reference(addr, tmp, MDL); + } + return result; + } + } + return ISC_R_SUCCESS; +} + + /* * For a declined lease, leave it on the "active" pool, but mark * it as declined. Give it an infinite (well, really long) life. */ isc_result_t decline_lease6(struct ipv6_pool *pool, struct iaaddr *addr) { + isc_result_t result; + + if (addr->state != FTS_ACTIVE) { + result = move_lease_to_active(pool, addr); + if (result != ISC_R_SUCCESS) { + return result; + } + } addr->state = FTS_ABANDONED; addr->valid_lifetime_end_time = MAX_TIME; isc_heap_decreased(pool->active_timeouts, addr->heap_index); @@ -786,7 +877,11 @@ decline_lease6(struct ipv6_pool *pool, struct iaaddr *addr) { */ isc_result_t release_lease6(struct ipv6_pool *pool, struct iaaddr *addr) { - return move_lease_to_inactive(pool, addr, FTS_RELEASED); + if (addr->state == FTS_ACTIVE) { + return move_lease_to_inactive(pool, addr, FTS_RELEASED); + } else { + return ISC_R_SUCCESS; + } } /* @@ -835,34 +930,127 @@ add_ipv6_pool(struct ipv6_pool *pool) { } +static void +cleanup_old_expired(struct ipv6_pool *pool) { + struct iaaddr *tmp; + struct ia_na *ia_na; + + while (pool->num_inactive > 0) { + tmp = (struct iaaddr *)isc_heap_element(pool->inactive_timeouts, + 1); + if (cur_time < + tmp->valid_lifetime_end_time + EXPIRED_IPV6_CLEANUP_TIME) { + break; + } + + isc_heap_delete(pool->inactive_timeouts, tmp->heap_index); + pool->num_inactive--; + + ia_na = NULL; + ia_na_reference(&ia_na, tmp->ia_na, MDL); + ia_na_remove_iaaddr(ia_na, tmp, MDL); + iaaddr_dereference(&tmp, MDL); + if (ia_na->num_iaaddr <= 0) { + ia_na_hash_delete(ia_active, + (char *)ia_na->iaid_duid.data, + ia_na->iaid_duid.len, MDL); + } + ia_na_dereference(&ia_na, MDL); + } +} + +static void +lease_timeout_support(void *vpool) { + struct ipv6_pool *pool; + struct iaaddr *addr; + + pool = (struct ipv6_pool *)vpool; + for (;;) { + /* + * Get the next lease scheduled to expire. + * + * Note that if there are no leases in the pool, + * expire_lease6() will return ISC_R_SUCCESS with + * a NULL lease. + */ + addr = NULL; + if (expire_lease6(&addr, pool, cur_time) != ISC_R_SUCCESS) { + break; + } + if (addr == NULL) { + break; + } + + /* Look to see if there were ddns updates, and if + * so, drop them. + * + * DH: Do we want to do this on a special 'depref' + * timer rather than expiration timer? + */ + ddns_removals(NULL, addr); + + write_ia_na(addr->ia_na); + + iaaddr_dereference(&addr, MDL); + } + + /* + * Do some cleanup of our expired leases. + */ + cleanup_old_expired(pool); + + /* + * Schedule next round of expirations. + */ + schedule_lease_timeout(pool); +} + /* - * Remove all leases that have expired from the active pool. + * For a given pool, add a timer that will remove the next + * lease to expire. */ void -expire_leases(time_t now) { - struct ipv6_pool *pool; +schedule_lease_timeout(struct ipv6_pool *pool) { + struct iaaddr *tmp; + time_t timeout; + time_t next_timeout; + + next_timeout = MAX_TIME; + + if (pool->num_active > 0) { + tmp = (struct iaaddr *)isc_heap_element(pool->active_timeouts, + 1); + if (tmp->valid_lifetime_end_time < next_timeout) { + next_timeout = tmp->valid_lifetime_end_time; + } + } + + if (pool->num_inactive > 0) { + tmp = (struct iaaddr *)isc_heap_element(pool->inactive_timeouts, + 1); + timeout = tmp->valid_lifetime_end_time + + EXPIRED_IPV6_CLEANUP_TIME; + if (timeout < next_timeout) { + next_timeout = timeout; + } + } + + if (next_timeout < MAX_TIME) { + add_timeout(next_timeout, lease_timeout_support, pool, + (tvref_t)ipv6_pool_reference, + (tvunref_t)ipv6_pool_dereference); + } +} + +/* + * Schedule timeouts across all pools. + */ +void +schedule_all_ipv6_lease_timeouts(void) { int i; - struct iaaddr *addr; for (i=0; i