[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
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
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
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
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"))
((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)
+
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
*/
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;
+ }
+ }
+ }
}
/*!
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
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);
}
#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.
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 <XX> is an abandoned address, This may be a result of
+ multiple clients attempting to use this DUID"
+
+ where <XX> 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
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: <addresses>"
+
+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
/*
- * 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
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];
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
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)),
}
/* 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);
*/
if (poollow < poolhigh) {
pond->logged = 1;
- pond->low_threshold = FIND_PERCENT(count, poollow);
+ pond->low_threshold = FIND_POND6_PERCENT(count, poollow);
}
}
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
*/
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) &&
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++;
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;
}
/* 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); */
}
/*
- * 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
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);
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);
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 {
lease->state = FTS_ACTIVE;
if (pool->ipv6_pond)
pool->ipv6_pond->num_active++;
+
}
return insert_result;
}
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);
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;
}
}
}
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;
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 */