Parcourir la source

[master] Merged trac5288 (option-data in DHCPv4 pools)

Francis Dupont il y a 7 ans
Parent
commit
3310b338b6

+ 26 - 4
doc/examples/kea4/multiple-options.json

@@ -32,9 +32,11 @@
 // clients connected to this subnet. The first two options are
 // identified by the name. The third option is identified by the
 // option code.
+// There is an address pool defined within this subnet. Pool
+// specific value for option domain-name-servers is defined
+// for the pool.
   "subnet4": [
     {
-       "pools": [ { "pool":  "192.0.2.10 - 192.0.2.200" } ],
        "subnet": "192.0.2.0/24",
        "interface": "ethX",
        "option-data": [
@@ -124,9 +126,29 @@
             "name": "default-ip-ttl",
             "data": "0xf0"
          }
-       ]
-    } 
-  ]
+       ],
+
+        // Now we define pools. There are two pools here.
+        "pools": [ {
+            // This is the first pool. Nothing spectacular here, just a range
+            // of addresses.
+           "pool": "192.0.2.10 - 192.0.2.100"
+
+          }, {
+            // This second pool is more interesting. Anyone who gets an
+            // address from this pool will also get this specific option
+            // value if asks for DNS servers configuration. This value,
+            // being more specific, overrides any values that were specified
+            // on either global or subnet scope.
+            "pool":  "192.0.2.101 - 192.0.2.200",
+            "option-data": [
+                {
+                    "name": "domain-name-servers",
+                    "data": "192.0.2.3, 192.0.2.4"
+                }
+          ]
+        } ]
+    } ]
 },
 
 // The following configures logging. It assumes that messages with at

+ 42 - 0
doc/guide/dhcp4-srv.xml

@@ -1058,6 +1058,48 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
 
       <para>
+        In some cases it is useful to associate some options with an
+        address pool from which a client is assigned a lease. Pool
+        specific option values override subnet specific and global
+        option values. The server's administrator must not try to
+        prioritize assignment of pool specific options by trying to
+        order pools declarations in the server configuration. Future
+        Kea releases may change the order in which options are
+        assigned from the pools without any notice.
+      </para>
+
+     <para>
+       The following configuration snippet demonstrates how to specify the
+       DNS servers option, which will be assigned to a client only if the
+       client obtains an address from the given pool:
+<screen>
+"Dhcp4": {
+    "subnet4": [
+        {
+            "pools": [
+                {
+                    "pool": "192.0.2.1 - 192.0.2.200",
+                    <userinput>"option-data": [
+                        {
+                            "name": "domain-name-servers",
+                            "data": "192.0.2.3"
+                         },
+                         ...
+                    ]</userinput>,
+                    ...
+                },
+                ...
+            ],
+            ...
+        },
+        ...
+    ],
+    ...
+}
+</screen>
+      </para>
+
+      <para>
         The currently supported standard DHCPv4 options are
         listed in <xref linkend="dhcp4-std-options-list"/>.
         The "Name" and "Code"

+ 153 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -2764,6 +2764,159 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
 }
 
+// This test verifies that it is possible to specify options on
+// pool levels.
+TEST_F(Dhcp4ParserTest, optionDataSinglePool) {
+    ConstElementPtr x;
+    string config = "{ " + genIfaceConfig() + ","
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { "
+        "        \"pool\": \"192.0.2.1 - 192.0.2.100\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"dhcp-message\","
+        "            \"data\": \"ABCDEF0105\","
+        "            \"csv-format\": false"
+        "        },"
+        "        {"
+        "          \"name\": \"default-ip-ttl\","
+        "          \"data\": \"01\","
+        "          \"csv-format\": false"
+        "        } ]"
+        "    } ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 0);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->
+        selectSubnet(IOAddress("192.0.2.24"), classify_);
+    ASSERT_TRUE(subnet);
+
+    PoolPtr pool = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.24"), false);
+    ASSERT_TRUE(pool);
+    Pool4Ptr pool4 = boost::dynamic_pointer_cast<Pool4>(pool);
+    ASSERT_TRUE(pool4);
+
+    OptionContainerPtr options = pool4->getCfgOption()->getAll("dhcp4");
+    ASSERT_EQ(2, options->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const OptionContainerTypeIndex& idx = options->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(56);
+    // Expect a single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t foo_expected[] = {
+	0xAB, 0xCD, 0xEF, 0x01, 0x05
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+    range = idx.equal_range(23);
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // Do another round of testing with second option.
+    const uint8_t foo2_expected[] = {
+        0x01
+    };
+    testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// This test verifies that it's possible to define different options in
+// different pools and those options are not confused.
+TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
+    ConstElementPtr x;
+    string config = "{ " + genIfaceConfig() + ","
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { "
+        "        \"pool\": \"192.0.2.1 - 192.0.2.100\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"dhcp-message\","
+        "            \"data\": \"ABCDEF0105\","
+        "            \"csv-format\": false"
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"pool\": \"192.0.2.200 - 192.0.2.250\","
+        "        \"option-data\": [ {"
+        "          \"name\": \"default-ip-ttl\","
+        "          \"data\": \"01\","
+        "          \"csv-format\": false"
+        "        } ]"
+        "    } ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 0);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->
+        selectSubnet(IOAddress("192.0.2.24"), classify_);
+    ASSERT_TRUE(subnet);
+
+    PoolPtr pool1 = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.24"), false);
+    ASSERT_TRUE(pool1);
+    Pool4Ptr pool41 = boost::dynamic_pointer_cast<Pool4>(pool1);
+    ASSERT_TRUE(pool41);
+
+    OptionContainerPtr options1 = pool41->getCfgOption()->getAll("dhcp4");
+    ASSERT_EQ(1, options1->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const OptionContainerTypeIndex& idx1 = options1->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range1 =
+        idx1.equal_range(56);
+    // Expect a single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range1.first, range1.second));
+    const uint8_t foo_expected[] = {
+	0xAB, 0xCD, 0xEF, 0x01, 0x05
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
+
+    // Test another pool in the same way.
+    PoolPtr pool2 = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.240"), false);
+    ASSERT_TRUE(pool2);
+    Pool4Ptr pool42 = boost::dynamic_pointer_cast<Pool4>(pool2);
+    ASSERT_TRUE(pool42);
+
+    OptionContainerPtr options2 = pool42->getCfgOption()->getAll("dhcp4");
+    ASSERT_EQ(1, options2->size());
+
+    const OptionContainerTypeIndex& idx2 = options2->get<1>();
+    std::pair<OptionContainerTypeIndex::const_iterator,
+	      OptionContainerTypeIndex::const_iterator> range2 =
+	idx2.equal_range(23);
+    ASSERT_EQ(1, std::distance(range2.first, range2.second));
+    const uint8_t foo2_expected[] = {
+        0x01
+    };
+    testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
+}
 
 
 // Verify that empty option name is rejected in the configuration.

Fichier diff supprimé car celui-ci est trop grand
+ 341 - 48
src/bin/dhcp4/tests/get_config_unittest.cc


+ 1 - 1
src/bin/dhcp4/tests/get_config_unittest.cc.skel

@@ -340,6 +340,6 @@ TEST_P(Dhcp4GetConfigTest, run) {
 
 /// Define the parametrized test loop
 INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
-                        ::testing::Range(0UL, max_config_counter));
+                        ::testing::Range(static_cast<size_t>(0), max_config_counter));
 
 };

+ 3 - 1
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -335,7 +335,9 @@ CfgSubnets4::toElement() const {
             if (!isNull(context)) {
                 pool_map->set("user-context", context);
             }
-            // Set pool options (not yet supported)
+            // Set pool options
+            ConstCfgOptionPtr opts = (*pool)->getCfgOption();
+            pool_map->set("option-data", opts->toElement());
             // Push on the pool list
             pool_list->add(pool_map);
         }

+ 0 - 6
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -749,12 +749,6 @@ PoolParser::parse(PoolStoragePtr pools,
     ConstElementPtr option_data = pool_structure->get("option-data");
     if (option_data) {
         try {
-            // Currently we don't support specifying options for the DHCPv4 server.
-            if (address_family == AF_INET) {
-                isc_throw(DhcpConfigError, "option-data is not supported for DHCPv4"
-                          " address pools");
-            }
-
             CfgOptionPtr cfg = pool->getCfgOption();
             OptionDataListParser option_parser(address_family);
             option_parser.parse(cfg, option_data);

+ 2 - 0
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -544,8 +544,10 @@ TEST(CfgSubnets4Test, unparsePool) {
         "    \"option-data\": [],\n"
         "    \"pools\": [\n"
         "        {\n"
+        "            \"option-data\": [ ],\n"
         "            \"pool\": \"192.0.2.1-192.0.2.10\"\n"
         "        },{\n"
+        "            \"option-data\": [ ],\n"
         "            \"pool\": \"192.0.2.64/26\"\n"
         "        }\n"
         "    ]\n"

+ 53 - 1
src/lib/dhcpsrv/tests/pool_unittest.cc

@@ -120,6 +120,58 @@ TEST(Pool4Test, toText) {
     EXPECT_EQ("type=V4, 192.0.2.128-192.0.2.143", pool2.toText());
 }
 
+// This test checks that it is possible to specify pool specific options.
+TEST(Pool4Test, addOptions) {
+    // Create a pool to add options to it.
+    Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+                            IOAddress("192.0.2.255")));
+
+    // Differentiate options by their codes (100-109)
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "dhcp4"));
+    }
+
+    // Add 7 options to another option space. The option codes partially overlap
+    // with option codes that we have added to dhcp4 option space.
+    for (uint16_t code = 105; code < 112; ++code) {
+        OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "isc"));
+    }
+
+    // Get options from the pool and check if all 10 are there.
+    OptionContainerPtr options = pool->getCfgOption()->getAll("dhcp4");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(10, options->size());
+
+    // Validate codes of options added to dhcp4 option space.
+    uint16_t expected_code = 100;
+    for (OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
+        ++expected_code;
+    }
+
+    options = pool->getCfgOption()->getAll("isc");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(7, options->size());
+
+    // Validate codes of options added to isc option space.
+    expected_code = 105;
+    for (OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option_);
+        EXPECT_EQ(expected_code, option_desc->option_->getType());
+        ++expected_code;
+    }
+
+    // Try to get options from a non-existing option space.
+    options = pool->getCfgOption()->getAll("abcd");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+}
+
 TEST(Pool6Test, constructor_first_last) {
 
     // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
@@ -329,7 +381,7 @@ TEST(Pool6Test, unique_id) {
 }
 
 // Simple check if toText returns reasonable values
-TEST(Pool6Test,toText) {
+TEST(Pool6Test, toText) {
     Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
                 IOAddress("2001:db8::2"));
     EXPECT_EQ("type=IA_NA, 2001:db8::1-2001:db8::2, delegated_len=128",