diff --git a/RELNOTES b/RELNOTES index 0745fc29..3b6c9b31 100644 --- a/RELNOTES +++ b/RELNOTES @@ -113,6 +113,17 @@ work on other platforms. Please report any problems and suggested fixes to the requirement of failover peers for denying dynamic bootp clients. [ISC-bugs #28574] +- Multiple items to clean up IPv6 address processing. + When processing an IA that we've seen check to see if the + addresses are usable (not in use by somebody else) before + handing it out. + When reading in leases from the file discard expired addresses. + When picking an address for a client include the IA ID in + addition to the client ID to generally pick different addresses + for different IAs. + [ISC-Bugs #23138] [ISC-Bugs #27945] [ISC-Bugs #25586] + [ISC-Bugs #27684] + Changes since 4.2.2 - Fix the code that checks for an existing DDNS transaction to cancel diff --git a/includes/dhcpd.h b/includes/dhcpd.h index b7e8bc79..6e7817c6 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -3495,9 +3495,13 @@ isc_result_t release_lease6(struct ipv6_pool *pool, struct iasubopt *lease); isc_result_t decline_lease6(struct ipv6_pool *pool, struct iasubopt *lease); isc_boolean_t lease6_exists(const struct ipv6_pool *pool, const struct in6_addr *addr); +isc_boolean_t lease6_usable(struct iasubopt *lease); +isc_result_t cleanup_lease6(ia_hash_t *ia_table, + struct ipv6_pool *pool, + struct iasubopt *lease, + struct ia_xx *ia); isc_result_t mark_lease_unavailble(struct ipv6_pool *pool, const struct in6_addr *addr); - isc_result_t create_prefix6(struct ipv6_pool *pool, struct iasubopt **pref, unsigned int *attempts, diff --git a/server/confpars.c b/server/confpars.c index c0742d49..1d822536 100644 --- a/server/confpars.c +++ b/server/confpars.c @@ -3,7 +3,7 @@ Parser for dhcpd config file... */ /* - * Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2012 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium * * Permission to use, copy, modify, and distribute this software for any @@ -4441,21 +4441,39 @@ parse_ia_na_declaration(struct parse *cfile) { binding_scope_dereference(&scope, MDL); } - /* add to our various structures */ - ia_add_iasubopt(ia, iaaddr, MDL); - ia_reference(&iaaddr->ia, ia, MDL); + /* find the pool this address is in */ pool = NULL; if (find_ipv6_pool(&pool, D6O_IA_NA, &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", + parse_warn(cfile, "no pool found for address %s", addr_buf); return; } - add_lease6(pool, iaaddr, end_time); - ipv6_pool_dereference(&pool, MDL); + + /* remove old information */ + if (cleanup_lease6(ia_na_active, pool, + iaaddr, ia) != ISC_R_SUCCESS) { + inet_ntop(AF_INET6, &iaaddr->addr, + addr_buf, sizeof(addr_buf)); + parse_warn(cfile, "duplicate na lease for address %s", + addr_buf); + } + + /* + * if we like the lease we add it to our various structues + * otherwise we leave it and it will get cleaned when we + * do the iasubopt_dereference. + */ + if ((state == FTS_ACTIVE) || (state == FTS_ABANDONED)) { + ia_add_iasubopt(ia, iaaddr, MDL); + ia_reference(&iaaddr->ia, ia, MDL); + add_lease6(pool, iaaddr, end_time); + } + iasubopt_dereference(&iaaddr, MDL); + ipv6_pool_dereference(&pool, MDL); } /* @@ -4804,19 +4822,37 @@ parse_ia_ta_declaration(struct parse *cfile) { binding_scope_dereference(&scope, MDL); } - /* add to our various structures */ - ia_add_iasubopt(ia, iaaddr, MDL); - ia_reference(&iaaddr->ia, ia, MDL); + /* find the pool this address is in */ pool = NULL; if (find_ipv6_pool(&pool, D6O_IA_TA, &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", + parse_warn(cfile, "no pool found for address %s", addr_buf); return; } - add_lease6(pool, iaaddr, end_time); + + /* remove old information */ + if (cleanup_lease6(ia_ta_active, pool, + iaaddr, ia) != ISC_R_SUCCESS) { + inet_ntop(AF_INET6, &iaaddr->addr, + addr_buf, sizeof(addr_buf)); + parse_warn(cfile, "duplicate ta lease for address %s", + addr_buf); + } + + /* + * if we like the lease we add it to our various structues + * otherwise we leave it and it will get cleaned when we + * do the iasubopt_dereference. + */ + if ((state == FTS_ACTIVE) || (state == FTS_ABANDONED)) { + ia_add_iasubopt(ia, iaaddr, MDL); + ia_reference(&iaaddr->ia, ia, MDL); + add_lease6(pool, iaaddr, end_time); + } + ipv6_pool_dereference(&pool, MDL); iasubopt_dereference(&iaaddr, MDL); } @@ -5168,19 +5204,37 @@ parse_ia_pd_declaration(struct parse *cfile) { binding_scope_dereference(&scope, MDL); } - /* add to our various structures */ - ia_add_iasubopt(ia, iapref, MDL); - ia_reference(&iapref->ia, ia, MDL); + /* find the pool this address is in */ pool = NULL; if (find_ipv6_pool(&pool, D6O_IA_PD, &iapref->addr) != ISC_R_SUCCESS) { inet_ntop(AF_INET6, &iapref->addr, addr_buf, sizeof(addr_buf)); - parse_warn(cfile, "no pool found for address %s", + parse_warn(cfile, "no pool found for address %s", addr_buf); return; } - add_lease6(pool, iapref, end_time); + + /* remove old information */ + if (cleanup_lease6(ia_pd_active, pool, + iapref, ia) != ISC_R_SUCCESS) { + inet_ntop(AF_INET6, &iapref->addr, + addr_buf, sizeof(addr_buf)); + parse_warn(cfile, "duplicate pd lease for address %s", + addr_buf); + } + + /* + * if we like the lease we add it to our various structues + * otherwise we leave it and it will get cleaned when we + * do the iasubopt_dereference. + */ + if ((state == FTS_ACTIVE) || (state == FTS_ABANDONED)) { + ia_add_iasubopt(ia, iapref, MDL); + ia_reference(&iapref->ia, ia, MDL); + add_lease6(pool, iapref, end_time); + } + ipv6_pool_dereference(&pool, MDL); iasubopt_dereference(&iapref, MDL); } diff --git a/server/dhcpv6.c b/server/dhcpv6.c index 336b3a07..96e2b2f9 100644 --- a/server/dhcpv6.c +++ b/server/dhcpv6.c @@ -2228,13 +2228,13 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) { log_fatal("Impossible condition at %s:%d.", MDL); if (memcmp(addr->iabuf, reply->fixed.data, 16) == 0) - return ISC_TRUE; + return (ISC_TRUE); - return ISC_FALSE; + return (ISC_FALSE); } if ((reply->old_ia == NULL) || (reply->old_ia->num_iasubopt == 0)) - return ISC_FALSE; + return (ISC_FALSE); for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) { struct iasubopt *tmp; @@ -2242,12 +2242,15 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) { tmp = reply->old_ia->iasubopt[i]; if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) { + if (lease6_usable(tmp) == ISC_FALSE) { + return (ISC_FALSE); + } iasubopt_reference(&reply->lease, tmp, MDL); - return ISC_TRUE; + return (ISC_TRUE); } } - return ISC_FALSE; + return (ISC_FALSE); } /* Process a client-supplied IA_TA. This may append options to the tail of @@ -2769,7 +2772,8 @@ find_client_address(struct reply_state *reply) { * Look for the best lease on the client's shared * network. */ - if (candidate_shared == reply->shared) { + if ((candidate_shared == reply->shared) && + (lease6_usable(lease) == ISC_TRUE)) { best_lease = lease_compare(lease, best_lease); } } @@ -2780,7 +2784,7 @@ find_client_address(struct reply_state *reply) { */ if ((best_lease == NULL) || (best_lease->state == FTS_ABANDONED)) { status = pick_v6_address(&reply->lease, reply->shared, - &reply->client_id); + &reply->ia->iaid_duid); } else if (best_lease != NULL) { iasubopt_reference(&reply->lease, best_lease, MDL); status = ISC_R_SUCCESS; @@ -3652,14 +3656,14 @@ prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) { if ((pref->bits == l->cidrnet.bits) && (memcmp(pref->lo_addr.iabuf, l->cidrnet.lo_addr.iabuf, 16) == 0)) - return ISC_TRUE; + return (ISC_TRUE); } - return ISC_FALSE; + return (ISC_FALSE); } if ((reply->old_ia == NULL) || (reply->old_ia->num_iasubopt == 0)) - return ISC_FALSE; + return (ISC_FALSE); for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) { struct iasubopt *tmp; @@ -3667,13 +3671,16 @@ prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) { tmp = reply->old_ia->iasubopt[i]; if ((pref->bits == (int) tmp->plen) && - memcmp(pref->lo_addr.iabuf, &tmp->addr, 16) == 0) { + (memcmp(pref->lo_addr.iabuf, &tmp->addr, 16) == 0)) { + if (lease6_usable(tmp) == ISC_FALSE) { + return (ISC_FALSE); + } iasubopt_reference(&reply->lease, tmp, MDL); - return ISC_TRUE; + return (ISC_TRUE); } } - return ISC_FALSE; + return (ISC_FALSE); } /* @@ -3767,8 +3774,9 @@ find_client_prefix(struct reply_state *reply) { * if it is scoped in a pool under the client's shared * network. */ - if (candidate_shared == NULL || - candidate_shared == reply->shared) { + if (((candidate_shared == NULL) || + (candidate_shared == reply->shared)) && + (lease6_usable(prefix) == ISC_TRUE)) { best_prefix = prefix_compare(reply, prefix, best_prefix); } diff --git a/server/mdb6.c b/server/mdb6.c index a9936594..e6d0a1ad 100644 --- a/server/mdb6.c +++ b/server/mdb6.c @@ -14,9 +14,68 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* TODO: assert() */ -/* TODO: simplify functions, as pool is now in iaaddr */ +/*! + * \todo assert() + * \todo simplify functions, as pool is now in iaaddr + */ +/*! \file server/mdb6.c + * + * \page ipv6structures IPv6 Structures Overview + * + * A brief description of the IPv6 structures as reverse engineered. + * + * There are three major data strucutes involved in the database: + * ipv6_pool - this contains information about a pool of addresses or prefixes + * that the server is using. This includes a hash table that + * tracks the active items and a pair of heap tables one for + * active items and one for non-active items. The heap tables + * are used to determine the next items to be modified due to + * timing events (expire mostly). + * ia_xx - this contains information about a single IA from a request + * normally it will contain one pointer to a lease for the client + * but it may contain more in some circumstances. There are 3 + * hash tables to aid in accessing these one each for NA, TA and PD + * iasubopt - the v6 lease structure. These are creaeted dynamically when + * a client asks for something and will eventually be destroyed + * if the client doesn't re-ask for that item. A lease has space + * for backpointers to the IA and to the pool to which it belongs. + * The pool backpointer is always filled, the IA pointer may not be + * + * In normal use we then have something like this: + * + * ia hash tables + * ia_na_active +----------------+ + * ia_ta_active +------------+ | pool | + * ia_pd_active | iasubopt |<--| active hash | + * +-----------------+ | aka lease |<--| active heap | + * | ia_xx | | pool ptr |-->| | + * | iasubopt array |<---| iaptr |<--| inactive heap | + * | lease ptr |--->| | | | + * +-----------------+ +------------+ +----------------+ + * + * For the pool either the inactive heap will have a pointer + * or both the active heap and the active hash will have pointers. + * + * I think there are several major items to notice. The first is + * that as a lease moves around it will be added to and removed + * from the address hash table in the pool and between the active + * and inactive hash tables. The hash table and the active heap + * are used when the lease is either active or abandoned. The + * inactive heap is used for all other states. In particular a + * lease that has expired or been released will be cleaned + * (DDNS removal etc) and then moved to the inactive heap. After + * some time period (currently 1 hour) it will be freed. + * + * The second is that when a client requests specific addresses, + * either because it previously owned them or if the server supplied + * them as part of a solicit, the server will try to lookup the ia_xx + * associated with the client and find the addresses there. If it + * does find appropriate leases it moves them from the old IA to + * a new IA and eventually replaces the old IA with the new IA + * in the IA hash tables. + * + */ #include "config.h" #include @@ -874,6 +933,145 @@ create_lease6(struct ipv6_pool *pool, struct iasubopt **addr, return result; } + +/*! \file server/mdb6.c + * + * \brief Cleans up leases when reading from a lease file + * + * This function is only expected to be run when reading leases in from a file. + * It checks to see if a lease already exists for the new leases's address. + * We don't add expired leases to the structures when reading a lease file + * which limits what can happen. We have two variables the owners of the leases + * being the same or different and the new lease being active or non-active: + * Owners active + * same no remove old lease and its connections + * same yes nothing to do, other code will update the structures. + * diff no nothing to do + * diff yes this combination shouldn't happen, we should only have a + * single active lease per address at a time and that lease + * should move to non-active before any other lease can + * become active for that address. + * Currently we delete the previous lease and pass an error + * to the caller who should log an error. + * + * When we remove a lease we remove it from the hash table and active heap + * (remember only active leases are in the structures at this time) for the + * pool, and from the IA's array. If, after we've removed the pointer from + * IA's array to the lease, the IA has no more pointers we remove it from + * the appropriate hash table as well. + * + * \param[in] ia_table = the hash table for the IA + * \param[in] pool = the pool to update + * \param[in] lease = the new lease we want to add + * \param[in] ia = the new ia we are building + * + * \return + * ISC_R_SUCCESS = the incoming lease and any previous lease were in + * an expected state - one of the first 3 options above. + * If necessary the old lease was removed. + * ISC_R_FAILURE = there is already an active lease for the address in + * the incoming lease. This shouldn't happen if it does + * flag an error for the caller to log. + */ + +isc_result_t +cleanup_lease6(ia_hash_t *ia_table, + struct ipv6_pool *pool, + struct iasubopt *lease, + struct ia_xx *ia) { + + struct iasubopt *test_iasubopt, *tmp_iasubopt; + struct ia_xx *old_ia; + isc_result_t status = ISC_R_SUCCESS; + + test_iasubopt = NULL; + old_ia = NULL; + + /* + * Look up the address - if we don't find a lease + * we don't need to do anything. + */ + if (iasubopt_hash_lookup(&test_iasubopt, pool->leases, + &lease->addr, sizeof(lease->addr), + MDL) == 0) { + return (ISC_R_SUCCESS); + } + + if (test_iasubopt->ia == NULL) { + /* no old ia, no work to do */ + iasubopt_dereference(&test_iasubopt, MDL); + return (status); + } + + ia_reference(&old_ia, test_iasubopt->ia, MDL); + + if ((old_ia->iaid_duid.len == ia->iaid_duid.len) && + (memcmp((unsigned char *)ia->iaid_duid.data, + (unsigned char *)old_ia->iaid_duid.data, + ia->iaid_duid.len) == 0)) { + /* same IA */ + if ((lease->state == FTS_ACTIVE) || + (lease->state == FTS_ABANDONED)) { + /* still active, no need to delete */ + goto cleanup; + } + } else { + /* different IA */ + if ((lease->state != FTS_ACTIVE) && + (lease->state != FTS_ABANDONED)) { + /* new lease isn't active, no work */ + goto cleanup; + } + + /* + * We appear to have two active leases, this shouldn't happen. + * Before a second lease can be set to active the first lease + * should be set to inactive (released, expired etc). For now + * delete the previous lease and indicate a failure to the + * caller so it can generate a warning. + * In the future we may try and determine which is the better + * lease to keep. + */ + + status = ISC_R_FAILURE; + } + + /* + * Remove the old lease from the active heap and from the hash table + * then remove the lease from the IA and clean up the IA if necessary. + */ + isc_heap_delete(pool->active_timeouts, test_iasubopt->heap_index); + pool->num_active--; + + iasubopt_hash_delete(pool->leases, &test_iasubopt->addr, + sizeof(test_iasubopt->addr), MDL); + ia_remove_iasubopt(old_ia, test_iasubopt, MDL); + if (old_ia->num_iasubopt <= 0) { + ia_hash_delete(ia_table, + (unsigned char *)old_ia->iaid_duid.data, + old_ia->iaid_duid.len, MDL); + } + + /* + * We derefenrece the subopt here as we've just removed it from + * the hash table in the pool. We need to make a copy as we + * need to derefernece it again later. + */ + tmp_iasubopt = test_iasubopt; + iasubopt_dereference(&tmp_iasubopt, MDL); + + cleanup: + ia_dereference(&old_ia, MDL); + + /* + * Clean up the reference, this is in addition to the deference + * above after removing the entry from the hash table + */ + iasubopt_dereference(&test_iasubopt, MDL); + + return (status); +} + /* * Put a lease in the pool directly. This is intended to be used when * loading leases from the file. @@ -984,6 +1182,38 @@ lease6_exists(const struct ipv6_pool *pool, const struct in6_addr *addr) { } } +/*! + * + * \brief Check if address is available to a lease + * + * Determine if the address in the lease is available to that + * lease. Either the address isn't in use or it is in use + * but by that lease. + * + * \param[in] lease = lease to check + * + * \return + * ISC_TRUE = The lease is allowed to use that address + * ISC_FALSE = The lease isn't allowed to use that address + */ +isc_boolean_t +lease6_usable(struct iasubopt *lease) { + struct iasubopt *test_iaaddr; + isc_boolean_t status = ISC_TRUE; + + test_iaaddr = NULL; + if (iasubopt_hash_lookup(&test_iaaddr, lease->ipv6_pool->leases, + (void *)&lease->addr, + sizeof(lease->addr), MDL)) { + if (test_iaaddr != lease) { + status = ISC_FALSE; + } + iasubopt_dereference(&test_iaaddr, MDL); + } + + return (status); +} + /* * Put the lease on our active pool. */