]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
[master] Added DHCPv6 prefix-length-mode configuration parameter
authorThomas Markwalder <tmark@isc.org>
Thu, 8 Jan 2015 15:30:12 +0000 (10:30 -0500)
committerThomas Markwalder <tmark@isc.org>
Thu, 8 Jan 2015 15:30:12 +0000 (10:30 -0500)
    Merges in rt36780.

RELNOTES
includes/dhcpd.h
server/dhcpd.c
server/dhcpd.conf.5
server/dhcpv6.c
server/stables.c

index 2935c99ec570db87f7679c797c386b1b14252bae..69400ba85e33e5c28087819b94f6dd489fdc0555 100644 (file)
--- 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
index f9fba731ec8a9be363820545dab0ff37c7eab1da..417e365c60046a6bde28e42aae534a7b171deae4 100644 (file)
@@ -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);
index 04c4bbef420eb8b83f2e1a980b8d60626233c375..9a45aa1136eda7f28b2f26eedebaf41d10f84780 100644 (file)
@@ -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);
 }
index d7d62dc4b055c9d5aa25ec875b2c7ccf90197ae9..11f6e13fe7493ec6da7ed084dd6567bf1f42ab9d 100644 (file)
@@ -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
index 62d35c47f0dcd5264c238c6165762ed5d5d344ca..6209d0f76adeed68b15423ad840b257bd9390eba 100644 (file)
@@ -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
  *
index 436883510e023ce35b3f93740fc4f4df6b93195b..4d53a8344faf6bd9a703dbe69707d7187072d2e0 100644 (file)
@@ -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 },