Parcourir la source

[3151] Added prefix delegation configuration support to b10-dhcp6

Added entries to dhcp6.spec file to allow a list of prefix
delegation pools to be defined for subnet6 entries, along
with the necessary parsing logic in dhcp6.
Thomas Markwalder il y a 11 ans
Parent
commit
f67708b12c

+ 1 - 0
configure.ac

@@ -1463,6 +1463,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/auth/gen-statisticsitems.py.pre
            src/bin/auth/gen-statisticsitems.py.pre
            src/bin/dhcp4/spec_config.h.pre
            src/bin/dhcp4/spec_config.h.pre
            src/bin/dhcp6/spec_config.h.pre
            src/bin/dhcp6/spec_config.h.pre
+           src/bin/dhcp6/tests/test_data_files_config.h
            src/bin/d2/spec_config.h.pre
            src/bin/d2/spec_config.h.pre
            src/bin/d2/tests/test_data_files_config.h
            src/bin/d2/tests/test_data_files_config.h
            src/bin/tests/process_rename_test.py
            src/bin/tests/process_rename_test.py

+ 167 - 0
src/bin/dhcp6/config_parser.cc

@@ -166,6 +166,171 @@ protected:
     }
     }
 };
 };
 
 
+class PdPoolParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param param_name name of the parameter. Note, it is passed through
+    /// but unused, parameter is currently always "Dhcp6/subnet6[X]/pool"
+    /// @param pools storage container in which to store the parsed pool
+    /// upon "commit"
+    PdPoolParser(const std::string&,  PoolStoragePtr pools)
+        : uint32_values_(new Uint32Storage()),
+          string_values_(new StringStorage()), pools_(pools) {
+        if (!pools_) {
+            isc_throw(isc::dhcp::DhcpConfigError,
+                      "PdPoolParser context storage may not be NULL");
+        }
+    }
+
+    /// @brief Builds a prefix delegation pool from the given configuration
+    ///
+    /// This function parses configuration entries and creates an instance
+    /// of a dhcp::Pool6 configured for prefix delegation.
+    ///
+    /// @param pd_pool_ pointer to an element that holds configuration entries
+    /// that define a prefix delegation pool.
+    ///
+    /// @throw DhcpConfigError if configuration parsing fails.
+    virtual void build(ConstElementPtr pd_pool_) {
+        // Parse the elements that make up the option definition.
+        BOOST_FOREACH(ConfigPair param, pd_pool_->mapValue()) {
+            std::string entry(param.first);
+            ParserPtr parser;
+            if (entry == "prefix") {
+                StringParserPtr str_parser(new StringParser(entry,
+                                                            string_values_));
+                parser = str_parser;
+            } else if (entry == "prefix-len" || entry == "delegated-len") {
+                Uint32ParserPtr code_parser(new Uint32Parser(entry,
+                                                             uint32_values_));
+                parser = code_parser;
+            } else {
+                isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+            }
+
+            parser->build(param.second);
+            parser->commit();
+        }
+
+        try {
+            // We should now have all of the pool elements we need to create
+            // the pool.  Fetch them and pass them into the Pool6 constructor.
+            // The constructor is expected to enforce any value validation.
+            const std::string addr_str = string_values_->getParam("prefix");
+            IOAddress addr(addr_str);
+
+            uint32_t prefix_len = uint32_values_->getParam("prefix-len");
+
+            uint32_t delegated_len = uint32_values_->getParam("delegated-len");
+
+            // Attempt to construct the local pool.
+            pool_.reset(new Pool6(Lease::TYPE_PD, addr, prefix_len,
+                                 delegated_len));
+        } catch (const std::exception& ex) {
+            isc_throw(isc::dhcp::DhcpConfigError,
+                      "PdPoolParser failed to build pool: " << ex.what());
+        }
+    }
+
+    // @brief Commits the constructed local pool to the pool storage.
+    virtual void commit() {
+        // Add the local pool to the external storage ptr.
+        pools_->push_back(pool_);
+    }
+
+protected:
+    /// Storage for subnet-specific integer values.
+    Uint32StoragePtr uint32_values_;
+
+    /// Storage for subnet-specific string values.
+    StringStoragePtr string_values_;
+
+    /// Parsers are stored here.
+    ParserCollection parsers_;
+
+    /// Pointer to the created pool object.
+    isc::dhcp::Pool6Ptr pool_;
+
+    /// Pointer to storage to which the local pool is written upon commit.
+    isc::dhcp::PoolStoragePtr pools_;
+};
+
+/// @brief Parser for a list of prefix delegation pools.
+///
+/// This parser iterates over a list of prefix delegation pool entries and
+/// creates pool instances for each one. If the parsing is successful, the
+/// collection of pools is committed to the provided storage.
+class PdPoolListParser : public DhcpConfigParser {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param dummy first argument is ignored, all Parser constructors
+    /// accept string as first argument.
+    /// @param storage is the pool storage in which to store the parsed
+    /// pools in this list
+    /// @throw isc::dhcp::DhcpConfigError if storage is null.
+    PdPoolListParser(const std::string&, PoolStoragePtr pools)
+        : local_pools_(new PoolStorage()), pools_(pools) {
+        if (!pools_) {
+            isc_throw(isc::dhcp::DhcpConfigError,
+                      "PdPoolListParser pools storage may not be NULL");
+        }
+    }
+
+    /// @brief Parse configuration entries.
+    ///
+    /// This function parses configuration entries and creates instances
+    /// of prefix delegation pools .
+    ///
+    /// @param pd_pool_list pointer to an element that holds entries
+    /// that define a prefix delegation pool.
+    ///
+    /// @throw DhcpConfigError if configuration parsing fails.
+    void build(isc::data::ConstElementPtr pd_pool_list) {
+        // Make sure the local list is empty.
+        local_pools_.reset(new PoolStorage());
+
+        // Make sure we have a configuration elements to parse.
+        if (!pd_pool_list) {
+            isc_throw(DhcpConfigError,
+                      "PdPoolListParser: list of pool definitions is empty");
+        }
+
+        // Loop through the list of pd pools.
+        BOOST_FOREACH(ConstElementPtr pd_pool, pd_pool_list->listValue()) {
+            boost::shared_ptr<PdPoolParser>
+                // Create the PdPool parser.
+                parser(new PdPoolParser("pd-pool", local_pools_));
+                // Build the pool instance
+                parser->build(pd_pool);
+                // Commit the pool to the local list of pools.
+                parser->commit();
+        }
+    }
+
+    /// @brief  Commits the pools created to the external storage area.
+    ///
+    /// Note that this method adds the local list of pools to the storage area
+    /// rather than replacing its contents.  This permits other parsers to
+    /// contribute to the set of pools.
+    void commit() {
+        // local_pools_ holds the values produced by the build function.
+        // At this point parsing should have completed successfully so
+        // we can append new data to the supplied storage.
+        pools_->insert(pools_->end(), local_pools_->begin(),
+                       local_pools_->end());
+    }
+
+private:
+    /// @brief storage for local pools
+    PoolStoragePtr local_pools_;
+
+    /// @brief External storage where pools are stored upon list commit.
+    PoolStoragePtr pools_;
+};
+
 /// @brief This class parses a single IPv6 subnet.
 /// @brief This class parses a single IPv6 subnet.
 ///
 ///
 /// This is the IPv6 derivation of the SubnetConfigParser class and it parses
 /// This is the IPv6 derivation of the SubnetConfigParser class and it parses
@@ -219,6 +384,8 @@ protected:
             parser = new StringParser(config_id, string_values_);
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool6Parser(config_id, pools_);
             parser = new Pool6Parser(config_id, pools_);
+        } else if (config_id.compare("pd-pools") == 0) {
+            parser = new PdPoolListParser(config_id, pools_);
         } else if (config_id.compare("option-data") == 0) {
         } else if (config_id.compare("option-data") == 0) {
            parser = new OptionDataListParser(config_id, options_,
            parser = new OptionDataListParser(config_id, options_,
                                              global_context_,
                                              global_context_,

+ 34 - 2
src/bin/dhcp6/dhcp6.spec

@@ -16,7 +16,7 @@
           "item_default": ""
           "item_default": ""
         }
         }
       },
       },
- 
+
       { "item_name": "interfaces",
       { "item_name": "interfaces",
         "item_type": "list",
         "item_type": "list",
         "item_optional": false,
         "item_optional": false,
@@ -254,6 +254,38 @@
                         "item_default": ""
                         "item_default": ""
                     }
                     }
                 },
                 },
+                {
+                  "item_name": "pd-pools",
+                  "item_type": "list",
+                  "item_optional": true,
+                  "item_default": [],
+                  "list_item_spec":
+                  {
+                      "item_name": "pd-pool",
+                      "item_type": "map",
+                      "item_optional": false,
+                      "item_default": {},
+                      "map_item_spec": [
+                      {
+                          "item_name": "prefix",
+                          "item_type": "string",
+                          "item_optional": false,
+                          "item_default": ""
+                       },
+                       {
+                           "item_name": "prefix-len",
+                           "item_type": "integer",
+                           "item_optional": false,
+                           "item_default": 128
+                       },
+                       {
+                           "item_name": "delegated-len",
+                           "item_type": "integer",
+                           "item_optional": false,
+                           "item_default": 128
+                       }]
+                    }
+                },
                 { "item_name": "option-data",
                 { "item_name": "option-data",
                   "item_type": "list",
                   "item_type": "list",
                   "item_optional": false,
                   "item_optional": false,
@@ -313,7 +345,7 @@
 
 
         {
         {
             "command_name": "libreload",
             "command_name": "libreload",
-            "command_description": "Reloads the current hooks libraries.", 
+            "command_description": "Reloads the current hooks libraries.",
             "command_args": []
             "command_args": []
         }
         }
     ]
     ]

+ 227 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -26,6 +26,7 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
 
 
+#include "test_data_files_config.h"
 #include "test_libraries.h"
 #include "test_libraries.h"
 #include "marker_file.h"
 #include "marker_file.h"
 
 
@@ -53,6 +54,17 @@ using namespace std;
 
 
 namespace {
 namespace {
 
 
+std::string specfile(const std::string& name) {
+    return (std::string(DHCP6_SRC_DIR) + "/" + name);
+}
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the DHCP6 configuration specification file is valid.
+TEST(Dhcp6SpecTest, basicSpec) {
+    ASSERT_NO_THROW(isc::config::
+                    moduleSpecFromFile(specfile("dhcp6.spec")));
+}
+
 class Dhcp6ParserTest : public ::testing::Test {
 class Dhcp6ParserTest : public ::testing::Test {
 public:
 public:
     Dhcp6ParserTest() :rcode_(-1), srv_(0) {
     Dhcp6ParserTest() :rcode_(-1), srv_(0) {
@@ -712,6 +724,221 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
     EXPECT_EQ(4000, subnet->getValid());
     EXPECT_EQ(4000, subnet->getValid());
 }
 }
 
 
+// Goal of this test is to verify the basic parsing of a prefix delegation
+// pool. It uses a single, valid pd pool.
+TEST_F(Dhcp6ParserTest, pdPoolBasics) {
+
+    ConstElementPtr x;
+
+    // Define a single valid pd pool.
+    string config =
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/64\","
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:1::\", "
+        "          \"prefix-len\": 64, "
+        "          \"delegated-len\": 128"
+        "        } ],"
+        "\"valid-lifetime\": 4000 }"
+        "] }";
+
+    // Convert the JSON string into Elements.
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(config));
+
+    // Verify that DHCP6 configuration processing succeeds.
+    // Returned value must be non-empty ConstElementPtr to config result.
+    // rcode should be 0 which indicates successful configuration processing.
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+
+    // Test that we can retrieve the subnet.
+    Subnet6Ptr subnet = CfgMgr::
+                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+
+    ASSERT_TRUE(subnet);
+
+    // Fetch the collection of PD pools.  It should have 1 entry.
+    PoolCollection pc;
+    ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+    EXPECT_EQ(1, pc.size());
+
+    // Get a pointer to the pd pool instance, and verify its contents.
+    Pool6Ptr p6;
+    ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[0]));
+    ASSERT_TRUE(p6);
+    EXPECT_EQ("2001:db8:1::", p6->getFirstAddress().toText());
+    EXPECT_EQ(128, p6->getLength());
+}
+
+// Goal of this test is verify that a list of PD pools can be configured.
+TEST_F(Dhcp6ParserTest, pdPoolList) {
+
+    ConstElementPtr x;
+
+    const char* prefixes[] = {
+        "2001:db8:1:1::",
+        "2001:db8:1:2::",
+        "2001:db8:1:3::"
+    };
+
+    string config =
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/40\","
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:1:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        },"
+        "        { \"prefix\": \"2001:db8:1:02::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 88"
+        "        },"
+        "        { \"prefix\": \"2001:db8:1:03::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 96"
+        "        }"
+        "],"
+        "\"valid-lifetime\": 4000 }"
+        "] }";
+
+    // Convert the JSON string into Elements.
+    ElementPtr json = Element::fromJSON(config);
+    ASSERT_NO_THROW(json = Element::fromJSON(config));
+
+    // Verify that DHCP6 configuration processing succeeds.
+    // Returned value must be non-empty ConstElementPtr to config result.
+    // rcode should be 0 which indicates successful configuration processing.
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+
+    // Test that we can retrieve the subnet.
+    Subnet6Ptr subnet = CfgMgr::
+                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+
+    // Fetch the collection of PD pools.  It should have 3 entries.
+    PoolCollection pc;
+    ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+    EXPECT_EQ(3, pc.size());
+
+    // Loop through the pools and verify their contents.
+    for (int i = 0; i < 3; i++) {
+        Pool6Ptr p6;
+        ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[i]));
+        ASSERT_TRUE(p6);
+        EXPECT_EQ(prefixes[i], p6->getFirstAddress().toText());
+        EXPECT_EQ((80 + (i * 8)), p6->getLength());
+    }
+}
+
+// Goal of this test is check for proper handling of invalid prefix delegation
+// pool configuration.  It uses an array of invalid configurations to check
+// a variety of configuration errors.
+TEST_F(Dhcp6ParserTest, invalidPdPools) {
+
+    ConstElementPtr x;
+
+    const char *config[] =  {
+        // No prefix.
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/64\","
+        "    \"pd-pools\": ["
+        "        { "
+        "          \"prefix-len\": 64, "
+        "          \"delegated-len\": 128"
+        "        } ],"
+        "\"valid-lifetime\": 4000 }"
+        "] }",
+        // No prefix-len.
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/64\","
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:1::\", "
+        "          \"delegated-len\": 128"
+        "        } ],"
+        "\"valid-lifetime\": 4000 }"
+        "] }",
+        // No delegated-len.
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/64\","
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:1::\", "
+        "          \"prefix-len\": 64 "
+        "        } ],"
+        "\"valid-lifetime\": 4000 }"
+        "] }",
+        // Delegated length is too short.
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/64\","
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:1::\", "
+        "          \"prefix-len\": 128, "
+        "          \"delegated-len\": 64"
+        "        } ],"
+        "\"valid-lifetime\": 4000 }"
+        "] }",
+        // Pool is not within the subnet.
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/64\","
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:77::\", "
+        "          \"prefix-len\": 64, "
+        "          \"delegated-len\": 128"
+        "        } ],"
+        "\"valid-lifetime\": 4000 }"
+        "] }"
+        };
+
+    ElementPtr json;
+    int num_msgs = sizeof(config)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        // Convert JSON string to Elements.
+        ASSERT_NO_THROW(json = Element::fromJSON(config[i]));
+
+        // Configuration processing should fail without a throw.
+        ASSERT_NO_THROW(x = configureDhcp6Server(srv_, json));
+
+        // Returned value must be non-empty ConstElementPtr to config result.
+        // rcode should be 1 which indicates configuration error.
+        ASSERT_TRUE(x);
+        comment_ = parseAnswer(rcode_, x);
+        EXPECT_EQ(1, rcode_);
+    }
+}
+
 // The goal of this test is to check whether an option definition
 // The goal of this test is to check whether an option definition
 // that defines an option carrying an IPv6 address can be created.
 // that defines an option carrying an IPv6 address can be created.
 TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
 TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {

+ 17 - 0
src/bin/dhcp6/tests/test_data_files_config.h.in

@@ -0,0 +1,17 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")   
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to DHCP6 source dir so tests against the dhcp6.spec file
+/// can find it reliably.
+#define DHCP6_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp6"

+ 3 - 3
src/lib/dhcpsrv/tests/test_libraries.h

@@ -37,13 +37,13 @@ namespace {
 
 
 // Library with load/unload functions creating marker files to check their
 // Library with load/unload functions creating marker files to check their
 // operation.
 // operation.
-static const char* CALLOUT_LIBRARY_1 = "/home/thomson/devel/bind10/src/lib/dhcpsrv/tests/.libs/libco1"
+static const char* CALLOUT_LIBRARY_1 = "/Users/tmark/build/trac3151/bind10/src/lib/dhcpsrv/tests/.libs/libco1"
                                            DLL_SUFFIX;
                                            DLL_SUFFIX;
-static const char* CALLOUT_LIBRARY_2 = "/home/thomson/devel/bind10/src/lib/dhcpsrv/tests/.libs/libco2"
+static const char* CALLOUT_LIBRARY_2 = "/Users/tmark/build/trac3151/bind10/src/lib/dhcpsrv/tests/.libs/libco2"
                                            DLL_SUFFIX;
                                            DLL_SUFFIX;
 
 
 // Name of a library which is not present.
 // Name of a library which is not present.
-static const char* NOT_PRESENT_LIBRARY = "/home/thomson/devel/bind10/src/lib/dhcpsrv/tests/.libs/libnothere"
+static const char* NOT_PRESENT_LIBRARY = "/Users/tmark/build/trac3151/bind10/src/lib/dhcpsrv/tests/.libs/libnothere"
                                          DLL_SUFFIX;
                                          DLL_SUFFIX;
 } // anonymous namespace
 } // anonymous namespace