summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Markwalder <tmark@isc.org>2015-01-08 10:30:12 -0500
committerThomas Markwalder <tmark@isc.org>2015-01-08 10:30:12 -0500
commit1a006ff6ed213bb58b69f1d3fcdc05607ceea438 (patch)
tree39cf30a10b28c50150fc950465c5b9fdff1e6ed8
parentfb98e02e120066e198f78e536014462ca21ee6ee (diff)
downloadisc-dhcp-1a006ff6ed213bb58b69f1d3fcdc05607ceea438.tar.gz
[master] Added DHCPv6 prefix-length-mode configuration parameter
Merges in rt36780.
-rw-r--r--RELNOTES10
-rw-r--r--includes/dhcpd.h11
-rw-r--r--server/dhcpd.c15
-rw-r--r--server/dhcpd.conf.576
-rw-r--r--server/dhcpv6.c209
-rw-r--r--server/stables.c18
6 files changed, 295 insertions, 44 deletions
diff --git a/RELNOTES b/RELNOTES
index 2935c99e..69400ba8 100644
--- a/RELNOTES
+++ b/RELNOTES
@@ -235,6 +235,16 @@ by Eric Young (eay@cryptsoft.com).
[ISC-Bugs #26376]
[ISC-Bugs #38131]
+- Added a global parameter, prefix-length-mode, which may be used to determine
+ how the server uses a non-zero value for prefix-length supplied by clients
+ when soliciting DHCPv6 prefixes. The server supports selection modes of:
+ ignore, prefer, exact, minimum and maximum which are described in detail in
+ the server man pages. The prior behavior of the server was to only offer a
+ prefix whose length exactly matched the prefix-length value requested. If
+ no such prefixes were available, the server returned a status of none
+ available. Note the default mode, "exact", provides this same behavior.
+ [ISC-Bugs #36780]
+
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 f9fba731..417e365c 100644
--- a/includes/dhcpd.h
+++ b/includes/dhcpd.h
@@ -741,6 +741,7 @@ struct lease_state {
#define SV_LOG_THRESHOLD_HIGH 84
#define SV_ECHO_CLIENT_ID 85
#define SV_SERVER_ID_CHECK 86
+#define SV_PREFIX_LEN_MODE 87
#if !defined (DEFAULT_PING_TIMEOUT)
# define DEFAULT_PING_TIMEOUT 1
@@ -789,6 +790,12 @@ struct lease_state {
# define MIN_LEASE_WRITE 15
#endif
+#define PLM_IGNORE 0
+#define PLM_PREFER 1
+#define PLM_EXACT 2
+#define PLM_MINIMUM 3
+#define PLM_MAXIMUM 4
+
/* Client option names */
#define CL_TIMEOUT 1
@@ -1962,6 +1969,8 @@ extern int ddns_update_style;
extern int dont_use_fsync;
extern int server_id_check;
+extern int prefix_length_mode;
+
extern const char *path_dhcpd_conf;
extern const char *path_dhcpd_db;
extern const char *path_dhcpd_pid;
@@ -2751,6 +2760,8 @@ extern struct enumeration ddns_styles;
extern struct enumeration syslog_enum;
void initialize_server_option_spaces (void);
+extern struct enumeration prefix_length_modes;
+
/* inet.c */
struct iaddr subnet_number (struct iaddr, struct iaddr);
struct iaddr ip_addr (struct iaddr, struct iaddr, u_int32_t);
diff --git a/server/dhcpd.c b/server/dhcpd.c
index 04c4bbef..9a45aa11 100644
--- a/server/dhcpd.c
+++ b/server/dhcpd.c
@@ -73,6 +73,7 @@ option server.ddns-rev-domainname = \"in-addr.arpa.\";";
int ddns_update_style;
int dont_use_fsync = 0; /* 0 = default, use fsync, 1 = don't use fsync */
int server_id_check = 0; /* 0 = default, don't check server id, 1 = do check */
+int prefix_length_mode = PLM_EXACT;
const char *path_dhcpd_conf = _PATH_DHCPD_CONF;
const char *path_dhcpd_db = _PATH_DHCPD_DB;
@@ -548,6 +549,7 @@ main(int argc, char **argv) {
dhcp_interface_setup_hook = dhcpd_interface_setup_hook;
bootp_packet_handler = do_packet;
#ifdef DHCPv6
+ add_enumeration (&prefix_length_modes);
dhcpv6_packet_handler = do_packet6;
#endif /* DHCPv6 */
@@ -1100,6 +1102,19 @@ void postconf_initialization (int quiet)
server_id_check = 1;
}
+ oc = lookup_option(&server_universe, options, SV_PREFIX_LEN_MODE);
+ if ((oc != NULL) &&
+ evaluate_option_cache(&db, NULL, NULL, NULL, options, NULL,
+ &global_scope, oc, MDL)) {
+ if (db.len == 1) {
+ prefix_length_mode = db.data[0];
+ } else {
+ log_fatal("invalid prefix-len-mode");
+ }
+
+ data_string_forget(&db, MDL);
+ }
+
/* Don't need the options anymore. */
option_state_dereference(&options, MDL);
}
diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5
index d7d62dc4..11f6e13f 100644
--- a/server/dhcpd.conf.5
+++ b/server/dhcpd.conf.5
@@ -1,6 +1,6 @@
.\" dhcpd.conf.5
.\"
-.\" 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
@@ -2786,6 +2786,80 @@ default lease time if none were specified.
.RE
.PP
The
+.I prefix-length-mode
+statement
+.RS 0.25i
+.PP
+.B prefix-length-mode
+.I mode\fR\fB;\fR
+.PP
+According to RFC 3633, DHCPv6 clients may specify preferences when soliciting
+prefixes by including an IA_PD Prefix option within the IA_PD option. Among
+the preferences that may be conveyed is the "prefix-length". When non-zero it
+indicates a client's desired length for offered prefixes. The RFC states that
+servers "MAY choose to use the information...to select prefix(es)" but does
+not specify any particular rules for doing so. The prefix-length-mode statement
+can be used to set the prefix selection rules employed by the server,
+when clients send a non-zero prefix-length value. The mode parameter must
+be one of \fBignore\fR, \fBprefer\fR, \fBexact\fR, \fBminimum\fR, or
+\fBmaximum\fR where:
+.PP
+1. ignore - The requested length is ignored. The server will offer the first
+available prefix.
+.PP
+2. prefer - The server will offer the first available prefix with the same
+length as the requested length. If none are found then it will offer the
+first available prefix of any length.
+.PP
+3. exact - The server will offer the first available prefix with the same
+length as the requested length. If none are found, it will return a status
+indicating no prefixes available. This is the default behavior.
+.PP
+4. minimum - The server will offer the first available prefix with the same
+length as the requested length. If none are found, it will return the first
+available prefix whose length is greater than (e.g. longer than), the
+requested value. If none of those are found, it will return a status
+indicating no prefixes available. For example, if client requests a length
+of /60, and the server has available prefixes of lengths /56 and /64, it will
+offer prefix of length /64.
+.PP
+5. maximum - The server will offer the first available prefix with the same
+length as the requested length. If none are found, it will return the first
+available prefix whose length is less than (e.g. shorter than), the
+requested value. If none of those are found, it will return a status
+indicating no prefixes available. For example, if client requests a length
+of /60, and the server has available prefixes of lengths /56 and /64, it will
+offer a prefix of length /56.
+.PP
+In general "first available" is determined by the order in which pools are
+defined in the server's configuration. For example, if a subnet is defined
+with three prefix pools A,B, and C:
+.PP
+.nf
+subnet 3000::/64 {
+ # pool A
+ pool6 {
+ :
+ }
+ # pool B
+ pool6 {
+ :
+ }
+ # pool C
+ pool6 {
+ :
+ }
+}
+.fi
+.PP
+then the pools will be checked in the order A, B, C. For modes \fBprefer\fR,
+\fBminimum\fR, and \fBmaximum\fR this may mean checking the pools in that order
+twice. A first pass through is made looking for an available prefix of exactly
+the preferred length. If none are found, then a second pass is performed
+starting with pool A but with appropriately adjusted length criteria.
+.RE
+.PP
+The
.I remote-port
statement
.RS 0.25i
diff --git a/server/dhcpv6.c b/server/dhcpv6.c
index 62d35c47..6209d0f7 100644
--- a/server/dhcpv6.c
+++ b/server/dhcpv6.c
@@ -150,6 +150,10 @@ static int find_hosts_by_duid_chaddr(struct host_decl **host,
const struct data_string *client_id);
static void schedule_lease_timeout_reply(struct reply_state *reply);
+static int eval_prefix_mode(int thislen, int preflen, int prefix_mode);
+static isc_result_t pick_v6_prefix_helper(struct reply_state *reply,
+ int prefix_mode);
+
/*
* Schedule lease timeouts for all of the iasubopts in the reply.
* This is currently used to schedule timeouts for soft leases.
@@ -1319,9 +1323,27 @@ try_client_v6_prefix(struct iasubopt **pref,
*
* \brief Get an IPv6 prefix for the client.
*
- * Attempt to find a usable prefix for the client. We walk through
- * the ponds checking for permit and deny then through the pools
- * seeing if they have an available prefix.
+ * Attempt to find a usable prefix for the client. Based upon the prefix
+ * length mode and the plen supplied by the client (if one), we make one
+ * or more calls to pick_v6_prefix_helper() to find a prefix as follows:
+ *
+ * PLM_IGNORE or client specifies a plen of zero, use the first available
+ * prefix regardless of it's length.
+ *
+ * PLM_PREFER – look for an exact match to client's plen first, if none
+ * found, use the first available prefix of any length
+ *
+ * PLM_EXACT – look for an exact match first, if none found then fail. This
+ * is the default behavior.
+ *
+ * PLM_MAXIMUM - look for an exact match first, then the first available whose
+ * prefix length is less than client's plen, otherwise fail.
+ *
+ * PLM_MINIMUM - look for an exact match first, then the first available whose
+ * prefix length is greater than client's plen, otherwise fail.
+ *
+ * Note that the selection mode is configurable at the global scope only via
+ * prefix-len-mode.
*
* \param reply = the state structure for the current work on this request
* if we create a lease we return it using reply->lease
@@ -1336,21 +1358,17 @@ try_client_v6_prefix(struct iasubopt **pref,
* hash the address. After a number of failures we
* conclude the pool is basically full.
*/
-
static isc_result_t
-pick_v6_prefix(struct reply_state *reply)
-{
- struct ipv6_pool *p = NULL;
- struct ipv6_pond *pond;
- int i;
- unsigned int attempts;
- char tmp_buf[INET6_ADDRSTRLEN];
- struct iasubopt **pref = &reply->lease;
+pick_v6_prefix(struct reply_state *reply) {
+ struct ipv6_pool *p = NULL;
+ struct ipv6_pond *pond;
+ int i;
+ isc_result_t result;
/*
* Do a quick walk through of the ponds and pools
* to see if we have any prefix pools
- */
+ */
for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
if (pond->ipv6_pools == NULL)
continue;
@@ -1370,13 +1388,93 @@ pick_v6_prefix(struct reply_state *reply)
return ISC_R_NORESOURCES;
}
+ if (reply->preflen <= 0) {
+ /* If we didn't get a plen (-1) or client plen is 0, then just
+ * select first available (same as PLM_INGORE) */
+ result = pick_v6_prefix_helper(reply, PLM_IGNORE);
+ } else {
+ switch (prefix_length_mode) {
+ case PLM_PREFER:
+ /* First we look for an exact match, if not found
+ * then first available */
+ result = pick_v6_prefix_helper(reply, PLM_EXACT);
+ if (result != ISC_R_SUCCESS) {
+ result = pick_v6_prefix_helper(reply,
+ PLM_IGNORE);
+ }
+ break;
+
+ case PLM_EXACT:
+ /* Match exactly or fail */
+ result = pick_v6_prefix_helper(reply, PLM_EXACT);
+ break;
+
+ case PLM_MINIMUM:
+ case PLM_MAXIMUM:
+ /* First we look for an exact match, if not found
+ * then first available by mode */
+ result = pick_v6_prefix_helper(reply, PLM_EXACT);
+ if (result != ISC_R_SUCCESS) {
+ result = pick_v6_prefix_helper(reply,
+ prefix_length_mode);
+ }
+ break;
+
+ default:
+ /* First available */
+ result = pick_v6_prefix_helper(reply, PLM_IGNORE);
+ break;
+ }
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ char tmp_buf[INET6_ADDRSTRLEN];
+
+ log_debug("Picking pool prefix %s/%u",
+ inet_ntop(AF_INET6, &(reply->lease->addr),
+ tmp_buf, sizeof(tmp_buf)),
+ (unsigned)(reply->lease->plen));
+ return (ISC_R_SUCCESS);
+ }
+
/*
- * We have at least one pool that could provide a prefix
- * Now we walk through the ponds and pools again and check
- * to see if the client is permitted and if an prefix is
- * available
- *
- */
+ * If we failed to pick an IPv6 prefix
+ * Presumably that means we have no prefixes for the client.
+ */
+ log_debug("Unable to pick client prefix: no prefixes available");
+ return ISC_R_NORESOURCES;
+}
+
+/*!
+ *
+ * \brief Get an IPv6 prefix for the client based upon selection mode.
+ *
+ * We walk through the ponds checking for permit and deny. If a pond is
+ * permissable to use, loop through its PD pools checking prefix lengths
+ * against the client plen based on the prefix length mode, looking for
+ * available prefixes.
+ *
+ * \param reply = the state structure for the current work on this request
+ * if we create a lease we return it using reply->lease
+ * \prefix_mode = selection mode to use
+ *
+ * \return
+ * ISC_R_SUCCESS = we were able to find a prefix and are returning a
+ * pointer to the lease
+ * ISC_R_NORESOURCES = there don't appear to be any free addresses. This
+ * is probabalistic. We don't exhaustively try the
+ * address range, instead we hash the duid and if
+ * the address derived from the hash is in use we
+ * hash the address. After a number of failures we
+ * conclude the pool is basically full.
+ */
+isc_result_t
+pick_v6_prefix_helper(struct reply_state *reply, int prefix_mode) {
+ struct ipv6_pool *p = NULL;
+ struct ipv6_pond *pond;
+ int i;
+ unsigned int attempts;
+ struct iasubopt **pref = &reply->lease;
for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) {
if (((pond->prohibit_list != NULL) &&
@@ -1386,37 +1484,64 @@ pick_v6_prefix(struct reply_state *reply)
continue;
for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) {
- if (p->pool_type != D6O_IA_PD) {
- continue;
- }
-
- /*
- * Try only pools with the requested prefix length if any.
- */
- if ((reply->preflen >= 0) && (p->units != reply->preflen)) {
- continue;
- }
-
- if (create_prefix6(p, pref, &attempts, &reply->ia->iaid_duid,
- cur_time + 120) == ISC_R_SUCCESS) {
- log_debug("Picking pool prefix %s/%u",
- inet_ntop(AF_INET6, &((*pref)->addr),
- tmp_buf, sizeof(tmp_buf)),
- (unsigned) (*pref)->plen);
-
+ if ((p->pool_type == D6O_IA_PD) &&
+ (eval_prefix_mode(p->units, reply->preflen,
+ prefix_mode) == 1) &&
+ (create_prefix6(p, pref, &attempts,
+ &reply->ia->iaid_duid,
+ cur_time + 120) == ISC_R_SUCCESS)) {
return (ISC_R_SUCCESS);
}
}
}
- /*
- * If we failed to pick an IPv6 prefix
- * Presumably that means we have no prefixes for the client.
- */
- log_debug("Unable to pick client prefix: no prefixes available");
return ISC_R_NORESOURCES;
}
+/*!
+ *
+ * \brief Test a prefix length against another based on prefix length mode
+ *
+ * \param len - prefix length to test
+ * \param preflen - preferred prefix length against which to test
+ * \param prefix_mode - prefix selection mode with which to test
+ *
+ * Note that the case of preferred length of 0 is not short-cut here as it
+ * is assumed to be done at a higher level.
+ *
+ * \return 1 if the given length is usable based upon mode and a preferred
+ * length, 0 if not.
+ */
+int
+eval_prefix_mode(int len, int preflen, int prefix_mode) {
+ int use_it = 1;
+ switch (prefix_mode) {
+ case PLM_EXACT:
+ use_it = (len == preflen);
+ break;
+ case PLM_MINIMUM:
+ /* they asked for a prefix length no "shorter" than preflen */
+ use_it = (len >= preflen);
+ break;
+ case PLM_MAXIMUM:
+ /* they asked for a prefix length no "longer" than preflen */
+ use_it = (len <= preflen);
+ break;
+ default:
+ /* otherwise use it */
+ break;
+ }
+
+#if defined (DEBUG)
+ log_debug("eval_prefix_mode: "
+ "len %d, preflen %d, mode %s, use_it %d",
+ len, preflen,
+ prefix_length_modes.values[prefix_mode].name, use_it);
+#endif
+
+ return (use_it);
+}
+
/*
*! \file server/dhcpv6.c
*
diff --git a/server/stables.c b/server/stables.c
index 43688351..4d53a834 100644
--- a/server/stables.c
+++ b/server/stables.c
@@ -3,7 +3,7 @@
Tables of information only used by server... */
/*
- * Copyright (c) 2004-2011,2013-2014 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2011,2013-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
@@ -269,6 +269,7 @@ static struct option server_options[] = {
{ "log-threshold-high", "B", &server_universe, 84, 1 },
{ "echo-client-id", "f", &server_universe, SV_ECHO_CLIENT_ID, 1 },
{ "server-id-check", "f", &server_universe, SV_SERVER_ID_CHECK, 1 },
+ { "prefix-length-mode", "Nprefix_length_modes.", &server_universe, SV_PREFIX_LEN_MODE, 1 },
{ NULL, NULL, NULL, 0, 0 }
};
@@ -342,6 +343,21 @@ struct enumeration ddns_styles = {
ddns_styles_values
};
+struct enumeration_value prefix_length_modes_values[] = {
+ { "ignore", PLM_IGNORE },
+ { "prefer", PLM_PREFER },
+ { "exact", PLM_EXACT },
+ { "minimum", PLM_MINIMUM },
+ { "maximum", PLM_MAXIMUM },
+ { (char *)0, 0 }
+};
+
+struct enumeration prefix_length_modes = {
+ (struct enumeration *)0,
+ "prefix_length_modes", 1,
+ prefix_length_modes_values
+};
+
struct enumeration_value syslog_values [] = {
#if defined (LOG_KERN)
{ "kern", LOG_KERN },