From: Thomas Markwalder Date: Thu, 8 Jan 2015 14:44:51 +0000 (-0500) Subject: [master] Log v6 shared network lease counts, when none available for a DUID X-Git-Tag: v4_3_2.pre-beta~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fb98e02e120066e198f78e536014462ca21ee6ee;p=thirdparty%2Fdhcp.git [master] Log v6 shared network lease counts, when none available for a DUID Merges in rt26376 --- diff --git a/RELNOTES b/RELNOTES index b60b2d05b..2935c99ec 100644 --- a/RELNOTES +++ b/RELNOTES @@ -225,6 +225,16 @@ by Eric Young (eay@cryptsoft.com). [ISC-Bugs #36668] [ISC-Bugs #36652] +- Log content has been changed to more directly suggest that admins should + check for multiple IPv6 clients attempting to use the same DUID when only + abandoned addresses are available. Debug level logging will now emit counts + of the total number of, in-use, and abandoned addresses in a shared subnet + when the server finds no addresses available for a given DUID. Lastly, + threshold logging is now automatically disabled for shared subnets whose + total number of possible addresses exceeds (2^64)-1. + [ISC-Bugs #26376] + [ISC-Bugs #38131] + Changes since 4.3.1b1 - Modify the linux and openwrt dhclient scripts to process information diff --git a/includes/dhcpd.h b/includes/dhcpd.h index b44682444..f9fba731e 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -3,7 +3,7 @@ Definitions for dhcpd... */ /* - * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2015 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1996-2003 by Internet Software Consortium * * Permission to use, copy, modify, and distribute this software for any @@ -1583,7 +1583,8 @@ struct ipv6_pool { int bits; /* number of bits, CIDR style */ int units; /* allocation unit in bits */ iasubopt_hash_t *leases; /* non-free leases */ - int num_active; /* count of active leases */ + isc_uint64_t num_active; /* count of active leases */ + isc_uint64_t num_abandoned; /* count of abandoned leases */ isc_heap_t *active_timeouts; /* timeouts for active leases */ int num_inactive; /* count of inactive leases */ isc_heap_t *inactive_timeouts; /* timeouts for expired or @@ -1619,12 +1620,20 @@ struct ipv6_pond { struct ipv6_pool **ipv6_pools; /* NULL-terminated array */ int last_ipv6_pool; /* offset of last IPv6 pool used to issue a lease */ - int num_total; /* Total number of elements in the pond */ - int num_active; /* Number of elements in the pond in use */ + isc_uint64_t num_total; /* Total number of elements in the pond */ + isc_uint64_t num_active; /* Number of elements in the pond in use */ + isc_uint64_t num_abandoned; /* count of abandoned leases */ int logged; /* already logged a message */ - int low_threshold; /* low threshold to restart logging */ + isc_uint64_t low_threshold; /* low threshold to restart logging */ + int jumbo_range; }; +/* + * Max addresses in a pond that can be supported by log threshold + * Currently based on max value supported by isc_uint64_t. +*/ +#define POND_TRACK_MAX ULLONG_MAX + /* Flags and state for dhcp_ddns_cb_t */ #define DDNS_UPDATE_ADDR 0x01 #define DDNS_UPDATE_PTR 0x02 @@ -3646,6 +3655,7 @@ void schedule_all_ipv6_lease_timeouts(); void mark_hosts_unavailable(void); void mark_phosts_unavailable(void); void mark_interfaces_unavailable(void); +void report_jumbo_ranges(); #define MAX_ADDRESS_STRING_LEN \ (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")) @@ -3657,4 +3667,7 @@ void mark_interfaces_unavailable(void); ((count) > (INT_MAX / 100) ? \ ((count) / 100) * (percent) : ((count) * (percent)) / 100) - +#define FIND_POND6_PERCENT(count, percent) \ + ((count) > (POND_TRACK_MAX / 100) ? \ + ((count) / 100) * (percent) : ((count) * (percent)) / 100) + diff --git a/server/confpars.c b/server/confpars.c index 006528af1..c9c149f5b 100644 --- a/server/confpars.c +++ b/server/confpars.c @@ -3,7 +3,7 @@ Parser for dhcpd config file... */ /* - * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2015 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 @@ -3842,12 +3842,40 @@ add_ipv6_pool_to_subnet(struct subnet *subnet, u_int16_t type, */ ipv6_pool_reference(&pond->ipv6_pools[num_pools], pool, MDL); pond->ipv6_pools[num_pools+1] = NULL; + /* Update the number of elements in the pond. Conveniently * we have the total size of the block in bits and the amount * we would allocate per element in units. For an address units * will always be 128, for a prefix it will be something else. - */ - pond->num_total += 1 << (units - bits); + * + * We need to make sure the number of elements isn't too large + * to track. If so, we flag it to avoid wasting time with log + * threshold logic. We also emit a log stating that log-threshold + * will be disabled for the shared-network but that's done + * elsewhere via report_log_threshold(). + * + */ + + /* Only bother if we aren't already flagged as jumbo */ + if (pond->jumbo_range == 0) { + if ((units - bits) > (sizeof(isc_uint64_t) * 8)) { + pond->jumbo_range = 1; + pond->num_total = POND_TRACK_MAX; + } + else { + isc_uint64_t space_left + = POND_TRACK_MAX - pond->num_total; + isc_uint64_t addon + = (isc_uint64_t)(1) << (units - bits); + + if (addon > space_left) { + pond->jumbo_range = 1; + pond->num_total = POND_TRACK_MAX; + } else { + pond->num_total += addon; + } + } + } } /*! diff --git a/server/dhcpd.c b/server/dhcpd.c index 1d00507fd..04c4bbef4 100644 --- a/server/dhcpd.c +++ b/server/dhcpd.c @@ -3,7 +3,7 @@ DHCP Server Daemon. */ /* - * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2015 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1996-2003 by Internet Software Consortium * * Permission to use, copy, modify, and distribute this software for any @@ -617,6 +617,11 @@ main(int argc, char **argv) { if (set_chroot) setup_chroot (set_chroot); #endif /* PARANOIA && !EARLY_CHROOT */ +#ifdef DHCPv6 + /* log info about ipv6_ponds with large address ranges */ + report_jumbo_ranges(); +#endif + /* test option should cause an early exit */ if (cftest && !lftest) exit(0); @@ -677,7 +682,6 @@ main(int argc, char **argv) { } #endif /* DHCPv6 */ - /* Make up a seed for the random number generator from current time plus the sum of the last four bytes of each interface's hardware address interpreted as an integer. diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5 index e437ce3cf..d7d62dc4b 100644 --- a/server/dhcpd.conf.5 +++ b/server/dhcpd.conf.5 @@ -1716,12 +1716,43 @@ lease the server has offered is not valid. When the server receives a DHCPDECLINE for a particular address, it normally abandons that address, assuming that some unauthorized system is using it. Unfortunately, a malicious or buggy client can, using DHCPDECLINE -messages, completely exhaust the DHCP server's allocation pool. The -server will reclaim these leases, but while the client is running -through the pool, it may cause serious thrashing in the DNS, and it -will also cause the DHCP server to forget old DHCP client address +messages, to completely exhaust the DHCP server's allocation pool. The +server will eventually reclaim these leases, but not while the client +is running through the pool. This may cause serious thrashing in the DNS, +and it will also cause the DHCP server to forget old DHCP client address allocations. .PP +Currently, abandoned IPv6 addresses are reclaimed in one of two ways: + a) Client renews a specific address: + If a client using a given DUID submits a DHCP REQUEST containing the + last address abandoned by that DUID, the address will be reassigned to + that client. + + b) Upon the second restart following an address abandonment. When an + address is abandoned it is both recorded as such in the lease file and + retained as abandoned in server memory until the server is restarted. Upon + restart, the server will process the lease file and all addresses whose + last known state is abandoned will be retained as such in memory but not + rewritten to the lease file. This means that a subsequent restart of the + server will not see the abandoned addresses in the lease file and + therefore have no record of them as abandoned in memory and as such + perceive them as free for assignment. +.PP +The total number addresses in a pool, available for a given DUID value, +is internally limited by the server's address generation mechanism. If +through mistaken configuration, multiple clients are using the same +DUID they will competing for the same addresses causing the server to reach +this internal limit rather quickly. The internal limit isolates this type +of activity such that address range is not exhausted for other DUID values. +The appearance of the following error log, can be an indication of this +condition: + + "Best match for DUID is an abandoned address, This may be a result of + multiple clients attempting to use this DUID" + + where is an actual DUID value depicted as colon separated string of + bytes in hexadecimal values. +.PP The \fBdeclines\fR flag tells the DHCP server whether or not to honor DHCPDECLINE messages. If it is set to \fBdeny\fR or \fBignore\fR in a particular scope, the DHCP server will not respond to DHCPDECLINE @@ -2543,6 +2574,16 @@ threshold is not given, it default to a value of zero. A special case occurs when the low threshold is set to be higer than the high threshold. In this case, a message will be generated each time a lease is acknowledged when the pool usage is above the high threshold. +.PP +Note that threshold logging will be automatically disabled for shared +subnets whose total number of addresses is larger than (2^64)-1. The server +will emit a log statement at startup when threshold logging is disabled as +shown below: + + "Threshold logging disabled for shared subnet of ranges: " + +This is likely to have no practical runtime effect as CPUs are unlikely +to support a server actually reaching such a large number of leases. .RE .PP The diff --git a/server/dhcpv6.c b/server/dhcpv6.c index 138101de0..62d35c47f 100644 --- a/server/dhcpv6.c +++ b/server/dhcpv6.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2006-2015 by Internet Systems Consortium, Inc. ("ISC") * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -823,7 +823,8 @@ void check_pool6_threshold(struct reply_state *reply, struct iasubopt *lease) { struct ipv6_pond *pond; - int used, count, high_threshold, poolhigh = 0, poollow = 0; + isc_uint64_t used, count, high_threshold; + int poolhigh = 0, poollow = 0; char *shared_name = "no name"; char tmp_addr[INET6_ADDRSTRLEN]; @@ -831,9 +832,20 @@ void check_pool6_threshold(struct reply_state *reply, return; pond = lease->ipv6_pool->ipv6_pond; + /* If the address range is too large to track, just skip all this. */ + if (pond->jumbo_range == 1) { + return; + } + count = pond->num_total; used = pond->num_active; + /* get network name for logging */ + if ((pond->shared_network != NULL) && + (pond->shared_network->name != NULL)) { + shared_name = pond->shared_network->name; + } + /* The logged flag indicates if we have already crossed the high * threshold and emitted a log message. If it is set we check to * see if we have re-crossed the low threshold and need to reset @@ -849,7 +861,7 @@ void check_pool6_threshold(struct reply_state *reply, pond->low_threshold = 0; pond->logged = 0; log_error("Pool threshold reset - shared subnet: %s; " - "address: %s; low threshold %d/%d.", + "address: %s; low threshold %llu/%llu.", shared_name, inet_ntop(AF_INET6, &lease->addr, tmp_addr, sizeof(tmp_addr)), @@ -874,19 +886,15 @@ void check_pool6_threshold(struct reply_state *reply, } /* we have a valid value, have we exceeded it */ - high_threshold = FIND_PERCENT(count, poolhigh); + high_threshold = FIND_POND6_PERCENT(count, poolhigh); if (used < high_threshold) { /* nope, no more to do */ return; } /* we've exceeded it, output a message */ - if ((pond->shared_network != NULL) && - (pond->shared_network->name != NULL)) { - shared_name = pond->shared_network->name; - } log_error("Pool threshold exceeded - shared subnet: %s; " - "address: %s; high threshold %d%% %d/%d.", + "address: %s; high threshold %d%% %llu/%llu.", shared_name, inet_ntop(AF_INET6, &lease->addr, tmp_addr, sizeof(tmp_addr)), poolhigh, used, count); @@ -908,7 +916,7 @@ void check_pool6_threshold(struct reply_state *reply, */ if (poollow < poolhigh) { pond->logged = 1; - pond->low_threshold = FIND_PERCENT(count, poollow); + pond->low_threshold = FIND_POND6_PERCENT(count, poollow); } } @@ -1134,6 +1142,12 @@ pick_v6_address(struct reply_state *reply) unsigned int attempts; char tmp_buf[INET6_ADDRSTRLEN]; struct iasubopt **addr = &reply->lease; + isc_uint64_t total = 0; + isc_uint64_t active = 0; + isc_uint64_t abandoned = 0; + int jumbo_range = 0; + char *shared_name = (reply->shared->name ? + reply->shared->name : "(no name)"); /* * Do a quick walk through of the ponds and pools @@ -1170,6 +1184,8 @@ pick_v6_address(struct reply_state *reply) */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { + isc_result_t result; + if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && @@ -1180,26 +1196,31 @@ pick_v6_address(struct reply_state *reply) i = start_pool; do { p = pond->ipv6_pools[i]; - if ((p->pool_type == D6O_IA_NA) && - (create_lease6(p, addr, &attempts, - &reply->ia->iaid_duid, - cur_time + 120) == ISC_R_SUCCESS)) { - /* - * Record the pool used (or next one if there - * was a collision). - */ - if (attempts > 1) { - i++; - if (pond->ipv6_pools[i] == NULL) { - i = 0; + if (p->pool_type == D6O_IA_NA) { + result = create_lease6(p, addr, &attempts, + &reply->ia->iaid_duid, + cur_time + 120); + if (result == ISC_R_SUCCESS) { + /* + * Record the pool used (or next one if + * there was a collision). + */ + if (attempts > 1) { + i++; + if (pond->ipv6_pools[i] + == NULL) { + i = 0; + } } - } - pond->last_ipv6_pool = i; - log_debug("Picking pool address %s", - inet_ntop(AF_INET6, &((*addr)->addr), - tmp_buf, sizeof(tmp_buf))); - return (ISC_R_SUCCESS); + pond->last_ipv6_pool = i; + + log_debug("Picking pool address %s", + inet_ntop(AF_INET6, + &((*addr)->addr), + tmp_buf, sizeof(tmp_buf))); + return (ISC_R_SUCCESS); + } } i++; @@ -1207,13 +1228,31 @@ pick_v6_address(struct reply_state *reply) i = 0; } } while (i != start_pool); + + if (result == ISC_R_NORESOURCES) { + jumbo_range += pond->jumbo_range; + total += pond->num_total; + active += pond->num_active; + abandoned += pond->num_abandoned; + } } /* * If we failed to pick an IPv6 address from any of the subnets. * Presumably that means we have no addresses for the client. */ - log_debug("Unable to pick client address: no addresses available"); + if (jumbo_range != 0) { + log_debug("Unable to pick client address: " + "no addresses available - shared network %s: " + " 2^64-1 < total, %llu active, %llu abandoned", + shared_name, active - abandoned, abandoned); + } else { + log_debug("Unable to pick client address: " + "no addresses available - shared network %s: " + "%llu total, %llu active, %llu abandoned", + shared_name, total, active - abandoned, abandoned); + } + return ISC_R_NORESOURCES; } @@ -3140,9 +3179,11 @@ find_client_address(struct reply_state *reply) { /* Pick the abandoned lease as a last resort. */ if ((status == ISC_R_NORESOURCES) && (best_lease != NULL)) { /* I don't see how this is supposed to be done right now. */ - log_error("Reclaiming abandoned addresses is not yet " - "supported. Treating this as an out of space " - "condition."); + log_error("Best match for DUID %s is an abandoned address," + " This may be a result of multiple clients attempting" + " to use this DUID", + print_hex_1(reply->client_id.len, + reply->client_id.data, 60)); /* iasubopt_reference(&reply->lease, best_lease, MDL); */ } diff --git a/server/mdb6.c b/server/mdb6.c index 6eac7fca3..3633cd58a 100644 --- a/server/mdb6.c +++ b/server/mdb6.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2013 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2007-2015 by Internet Systems Consortium, Inc. ("ISC") * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -1191,6 +1191,12 @@ cleanup_lease6(ia_hash_t *ia_table, if (pool->ipv6_pond) pool->ipv6_pond->num_active--; + if (lease->state == FTS_ABANDONED) { + pool->num_abandoned--; + if (pool->ipv6_pond) + pool->ipv6_pond->num_abandoned--; + } + iasubopt_hash_delete(pool->leases, &test_iasubopt->addr, sizeof(test_iasubopt->addr), MDL); ia_remove_iasubopt(old_ia, test_iasubopt, MDL); @@ -1257,6 +1263,12 @@ add_lease6(struct ipv6_pool *pool, struct iasubopt *lease, pool->num_active--; if (pool->ipv6_pond) pool->ipv6_pond->num_active--; + + if (test_iasubopt->state == FTS_ABANDONED) { + pool->num_abandoned--; + if (pool->ipv6_pond) + pool->ipv6_pond->num_abandoned--; + } } else { isc_heap_delete(pool->inactive_timeouts, test_iasubopt->heap_index); @@ -1295,6 +1307,12 @@ add_lease6(struct ipv6_pool *pool, struct iasubopt *lease, pool->num_active++; if (pool->ipv6_pond) pool->ipv6_pond->num_active++; + + if (tmp_iasubopt->state == FTS_ABANDONED) { + pool->num_abandoned++; + if (pool->ipv6_pond) + pool->ipv6_pond->num_abandoned++; + } } } else { @@ -1387,6 +1405,7 @@ move_lease_to_active(struct ipv6_pool *pool, struct iasubopt *lease) { lease->state = FTS_ACTIVE; if (pool->ipv6_pond) pool->ipv6_pond->num_active++; + } return insert_result; } @@ -1443,6 +1462,11 @@ renew_lease6(struct ipv6_pool *pool, struct iasubopt *lease) { log_info("Reclaiming previously abandoned address %s", inet_ntop(AF_INET6, &(lease->addr), tmp_addr, sizeof(tmp_addr))); + + pool->num_abandoned--; + if (pool->ipv6_pond) + pool->ipv6_pond->num_abandoned--; + return ISC_R_SUCCESS; } else { return move_lease_to_active(pool, lease); @@ -1515,6 +1539,12 @@ move_lease_to_inactive(struct ipv6_pool *pool, struct iasubopt *lease, pool->num_inactive++; if (pool->ipv6_pond) pool->ipv6_pond->num_active--; + + if (lease->state == FTS_ABANDONED) { + pool->num_abandoned--; + if (pool->ipv6_pond) + pool->ipv6_pond->num_abandoned--; + } } return insert_result; } @@ -1575,6 +1605,11 @@ decline_lease6(struct ipv6_pool *pool, struct iasubopt *lease) { } } lease->state = FTS_ABANDONED; + + pool->num_abandoned++; + if (pool->ipv6_pond) + pool->ipv6_pond->num_abandoned++; + lease->hard_lifetime_end_time = MAX_TIME; isc_heap_decreased(pool->active_timeouts, lease->heap_index); return ISC_R_SUCCESS; @@ -2459,4 +2494,70 @@ ipv6_pond_dereference(struct ipv6_pond **pond, const char *file, int line) { return ISC_R_SUCCESS; } +/* + * Emits a log for each pond that has been flagged as being a "jumbo range" + * A pond is considered a "jumbo range" when the total number of elements + * exceeds the maximum value of POND_TRACK_MAX (currently maximum value + * that can be stored by ipv6_pond.num_total). Since we disable threshold + * logging for jumbo ranges, we need to report this to the user. This + * function allows us to report jumbo ponds after config parsing, so the + * logs can be seen both on the console (-T) and the log facility (i.e syslog). + * + * Note, threshold logging is done at the pond level, so we need emit a list + * of the addresses ranges of the pools in the pond affected. + */ +void +report_jumbo_ranges() { + struct shared_network* s; + char log_buf[1084]; + + /* Loop thru all the networks looking for jumbo range ponds */ + for (s = shared_networks; s; s = s -> next) { + struct ipv6_pond* pond = s->ipv6_pond; + while (pond) { + /* if its a jumbo and has pools(sanity check) */ + if (pond->jumbo_range == 1 && (pond->ipv6_pools)) { + struct ipv6_pool* pool; + char *bufptr = log_buf; + size_t space_left = sizeof(log_buf) - 1; + int i = 0; + int used = 0; + + /* Build list containing the start-address/CIDR + * of each pool */ + *bufptr = '\0'; + while ((pool = pond->ipv6_pools[i++]) && + (space_left > (INET6_ADDRSTRLEN + 6))) { + /* more than one so add a comma */ + if (i > 1) { + *bufptr++ = ','; + *bufptr++ = ' '; + *bufptr = '\0'; + space_left -= 2; + } + + /* add the address */ + inet_ntop(AF_INET6, &pool->start_addr, + bufptr, INET6_ADDRSTRLEN); + + used = strlen(bufptr); + bufptr += used; + space_left -= used; + + /* add the CIDR */ + sprintf (bufptr, "/%d",pool->bits); + used = strlen(bufptr); + bufptr += used; + space_left -= used; + *bufptr = '\0'; + } + + log_info("Threshold logging disabled for shared" + " subnet of ranges: %s", log_buf); + } + pond = pond->next; + } + } +} + /* unittest moved to server/tests/mdb6_unittest.c */