From 1a006ff6ed213bb58b69f1d3fcdc05607ceea438 Mon Sep 17 00:00:00 2001 From: Thomas Markwalder Date: Thu, 8 Jan 2015 10:30:12 -0500 Subject: [PATCH] [master] Added DHCPv6 prefix-length-mode configuration parameter Merges in rt36780. --- RELNOTES | 10 +++ includes/dhcpd.h | 11 +++ server/dhcpd.c | 15 ++++ server/dhcpd.conf.5 | 76 +++++++++++++++- server/dhcpv6.c | 209 +++++++++++++++++++++++++++++++++++--------- server/stables.c | 18 +++- 6 files changed, 295 insertions(+), 44 deletions(-) diff --git a/RELNOTES b/RELNOTES index 2935c99ec..69400ba85 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 f9fba731e..417e365c6 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 04c4bbef4..9a45aa113 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 d7d62dc4b..11f6e13fe 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 62d35c47f..6209d0f76 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 436883510..4d53a8344 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 }, -- 2.39.2