Browse Source

[master] Merge branch 'trac5022'

Marcin Siodelski 8 years ago
parent
commit
e3b2785c79

+ 25 - 4
doc/examples/kea6/multiple-options.json

@@ -31,11 +31,16 @@
 # Defining a subnet. There are 2 DHCP options returned to the
 # Defining a subnet. There are 2 DHCP options returned to the
 # clients connected to this subnet. The first option is identified
 # clients connected to this subnet. The first option is identified
 # by the name. The second option is identified by the code.
 # by the name. The second option is identified by the code.
+# There are two address pools defined within this subnet. Pool
+# specific value for option 12 is defined for the pool:
+# 2001:db8:1::1 - 2001:db8:1::100. Clients obtaining an address
+# from this pool will be assigned option 12 with a value of
+# 3001:cafe::21. Clients belonging to this subnet but obtaining
+# addresses from the other pool, or the clients obtaining
+# stateless configuration will be assigned subnet specific value
+# of option 12, i.e. 2001:db8:1:0:ff00::1.
   "subnet6": [
   "subnet6": [
     {
     {
-      "pools": [ { "pool": "2001:db8:1::/80" } ],
-      "subnet": "2001:db8:1::/64",
-      "interface": "ethX",
       "option-data": [
       "option-data": [
         {
         {
             "name": "dns-servers",
             "name": "dns-servers",
@@ -45,7 +50,23 @@
             "code": 12,
             "code": 12,
             "data": "2001:db8:1:0:ff00::1"
             "data": "2001:db8:1:0:ff00::1"
         },
         },
-      ]
+      ],
+      "pools": [
+        {
+            "pool": "2001:db8:1::1 - 2001:db8:1::100",
+            "option-data": [
+              {
+                  "code": 12,
+                  "data": "3001:cafe::21"
+              }
+            ]
+        },
+        {
+            "pool": "2001:db8:1::500 - 2001:db8:2::1000"
+        }
+      ],
+      "subnet": "2001:db8:1::/64",
+      "interface": "ethX",
     }
     }
   ]
   ]
 },
 },

+ 43 - 0
doc/guide/dhcp6-srv.xml

@@ -932,6 +932,7 @@ temporarily override a list of interface names and listen on all interfaces.
       (Dhcp6/option-data), rather you should set only subnet-specific values
       (Dhcp6/option-data), rather you should set only subnet-specific values
       (Dhcp6/subnet[X]/option-data[Y]).
       (Dhcp6/subnet[X]/option-data[Y]).
      </para>
      </para>
+
      <para>
      <para>
       The following commands override the global
       The following commands override the global
       DNS servers option for a particular subnet, setting a single DNS
       DNS servers option for a particular subnet, setting a single DNS
@@ -959,6 +960,48 @@ temporarily override a list of interface names and listen on all interfaces.
 </screen>
 </screen>
     </para>
     </para>
 
 
+     <para>
+       In some cases it is useful to associate some options with an
+       address or prefix pool from which a client is assigned a lease. Pool
+       specific option values override subnet specific and global option
+       values. If the client is assigned multiple leases from different
+       pools, the server will assign options from all pools from which the
+       leases have been obtained. However, if the particular option is specified
+       in multiple pools from which the client obtains the leases, only one
+       instance of this option will be handed out to the client. 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>
+"Dhcp6": {
+    "subnet6": [
+        {
+            "pools": [
+                {
+                    "pool": "2001:db8:1::100-2001:db8:1::300",
+                    <userinput>"option-data": [
+                        {
+                            "name": "dns-servers",
+                            "data": "2001:db8:1::10"
+                        }
+                    ]</userinput>
+                }
+            ]
+        },
+        ...
+    ],
+    ...
+}
+</screen>
+     </para>
+
     <para>
     <para>
       The currently supported standard DHCPv6 options are
       The currently supported standard DHCPv6 options are
       listed in <xref linkend="dhcp6-std-options-list"/>.
       listed in <xref linkend="dhcp6-std-options-list"/>.

+ 2 - 1
src/bin/dhcp4/json_config_parser.cc

@@ -32,6 +32,7 @@
 
 
 #include <limits>
 #include <limits>
 #include <iostream>
 #include <iostream>
+#include <netinet/in.h>
 #include <vector>
 #include <vector>
 #include <map>
 #include <map>
 
 
@@ -61,7 +62,7 @@ public:
     /// @param pools storage container in which to store the parsed pool
     /// @param pools storage container in which to store the parsed pool
     /// upon "commit"
     /// upon "commit"
     Pool4Parser(const std::string& param_name,  PoolStoragePtr pools)
     Pool4Parser(const std::string& param_name,  PoolStoragePtr pools)
-        :PoolParser(param_name, pools) {
+        :PoolParser(param_name, pools, AF_INET) {
     }
     }
 
 
 protected:
 protected:

+ 113 - 11
src/bin/dhcp6/dhcp6.spec

@@ -441,17 +441,70 @@
                   "item_optional": false,
                   "item_optional": false,
                   "item_default": 7200
                   "item_default": 7200
                 },
                 },
-                { "item_name": "pool",
-                  "item_type": "list",
-                  "item_optional": false,
-                  "item_default": [],
-                    "list_item_spec":
-                    {
-                        "item_name": "type",
-                        "item_type": "string",
+                { "item_name": "pools",
+                  "item_type": "map",
+                  "item_optional": true,
+                  "item_default" : {},
+                  "map_item_spec": [
+                      { "item_name": "pool",
+                        "item_type": "list",
                         "item_optional": false,
                         "item_optional": false,
-                        "item_default": ""
-                    }
+                        "item_default": [],
+                        "list_item_spec":
+                        {
+                            "item_name": "type",
+                            "item_type": "string",
+                            "item_optional": false,
+                            "item_default": ""
+                        }
+                      },
+                      { "item_name": "option-data",
+                        "item_type": "list",
+                        "item_optional": false,
+                        "item_default": [],
+                        "item_description": "Holds a list of pool specific DHCP options.",
+                        "list_item_spec":
+                        {
+                            "item_name": "single-option-data",
+                            "item_type": "map",
+                            "item_optional": false,
+                            "item_default": {},
+                            "item_description": "One of the pool specific DHCP options.",
+                            "map_item_spec": [
+                                {
+                                    "item_name": "name",
+                                    "item_type": "string",
+                                    "item_optional": false,
+                                    "item_default": "",
+                                    "item_description": "Option name."
+                                },
+
+                                { "item_name": "code",
+                                  "item_type": "integer",
+                                  "item_optional": false,
+                                  "item_default": 0,
+                                  "item_description": "Option code."
+                                },
+                                { "item_name": "data",
+                                  "item_type": "string",
+                                  "item_optional": false,
+                                  "item_default": "",
+                                  "item_description": "Option value."
+                                },
+                                { "item_name": "csv-format",
+                                  "item_type": "boolean",
+                                  "item_optional": false,
+                                  "item_default": false,
+                                  "item_description": "Indicates if option value is specified as comma separated values."
+                                },
+                                { "item_name": "space",
+                                  "item_type": "string",
+                                  "item_optional": false,
+                                  "item_default": "dhcp6",
+                                  "item_description": "Option space."
+                                } ]
+                        }
+                      } ]
                 },
                 },
 
 
                 { "item_name": "client-class",
                 { "item_name": "client-class",
@@ -506,7 +559,56 @@
                            "item_type": "integer",
                            "item_type": "integer",
                            "item_optional": false,
                            "item_optional": false,
                            "item_default": 128
                            "item_default": 128
-                       }]
+                       },
+                       {
+                           "item_name": "option-data",
+                           "item_type": "list",
+                           "item_optional": false,
+                           "item_default": [],
+                           "item_description": "Holds a list of the pd-pool specific DHCP options.",
+                           "list_item_spec":
+                           {
+                               "item_name": "single-option-data",
+                               "item_type": "map",
+                               "item_optional": false,
+                               "item_default": {},
+                               "item_description": "One of the pd-pool specific DHCP options.",
+                               "map_item_spec": [
+                                {
+                                    "item_name": "name",
+                                    "item_type": "string",
+                                    "item_optional": false,
+                                    "item_default": "",
+                                    "item_description": "Option name."
+                                },
+
+                                { "item_name": "code",
+                                  "item_type": "integer",
+                                  "item_optional": false,
+                                  "item_default": 0,
+                                  "item_description": "Option code."
+
+                                },
+                                { "item_name": "data",
+                                  "item_type": "string",
+                                  "item_optional": false,
+                                  "item_default": "",
+                                  "item_description": "Option value."
+                                },
+                                { "item_name": "csv-format",
+                                  "item_type": "boolean",
+                                  "item_optional": false,
+                                  "item_default": false,
+                                  "item_description": "Indicates if option value is specified as comma separated values."
+                                },
+                                { "item_name": "space",
+                                  "item_type": "string",
+                                  "item_optional": false,
+                                  "item_default": "dhcp6",
+                                  "item_description": "Option space."
+                                } ]
+                           }
+                       } ]
                     }
                     }
                 },
                 },
                 { "item_name": "option-data",
                 { "item_name": "option-data",

+ 26 - 9
src/bin/dhcp6/dhcp6_srv.cc

@@ -820,6 +820,20 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
         co_list.push_back(ctx.host_->getCfgOption6());
         co_list.push_back(ctx.host_->getCfgOption6());
     }
     }
 
 
+    // Secondly, pool specific options. Pools are defined within a subnet, so
+    // if there is no subnet, there is nothing to do.
+    if (ctx.subnet_) {
+        BOOST_FOREACH(const AllocEngine::ResourceType& resource,
+                      ctx.allocated_resources_) {
+            PoolPtr pool = ctx.subnet_->getPool(resource.second == 128 ?
+                                                Lease::TYPE_NA : Lease::TYPE_PD,
+                                                resource.first, false);
+            if (pool && !pool->getCfgOption()->empty()) {
+                co_list.push_back(pool->getCfgOption());
+            }
+        }
+    };
+
     // Next, subnet configured options.
     // Next, subnet configured options.
     if (ctx.subnet_ && !ctx.subnet_->getCfgOption()->empty()) {
     if (ctx.subnet_ && !ctx.subnet_->getCfgOption()->empty()) {
         co_list.push_back(ctx.subnet_->getCfgOption());
         co_list.push_back(ctx.subnet_->getCfgOption());
@@ -2316,6 +2330,9 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
         }
         }
     }
     }
 
 
+    processClientFqdn(solicit, response, ctx);
+    assignLeases(solicit, response, ctx);
+
     copyClientOptions(solicit, response);
     copyClientOptions(solicit, response);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(solicit, ctx, co_list);
     buildCfgOptionList(solicit, ctx, co_list);
@@ -2323,9 +2340,6 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     appendRequestedOptions(solicit, response, co_list);
     appendRequestedOptions(solicit, response, co_list);
     appendRequestedVendorOptions(solicit, response, ctx, co_list);
     appendRequestedVendorOptions(solicit, response, ctx, co_list);
 
 
-    processClientFqdn(solicit, response, ctx);
-    assignLeases(solicit, response, ctx);
-
     // Only generate name change requests if sending a Reply as a result
     // Only generate name change requests if sending a Reply as a result
     // of receiving Rapid Commit option.
     // of receiving Rapid Commit option.
     if (response->getType() == DHCPV6_REPLY) {
     if (response->getType() == DHCPV6_REPLY) {
@@ -2347,6 +2361,9 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
 
 
+    processClientFqdn(request, reply, ctx);
+    assignLeases(request, reply, ctx);
+
     copyClientOptions(request, reply);
     copyClientOptions(request, reply);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(request, ctx, co_list);
     buildCfgOptionList(request, ctx, co_list);
@@ -2354,8 +2371,6 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     appendRequestedOptions(request, reply, co_list);
     appendRequestedOptions(request, reply, co_list);
     appendRequestedVendorOptions(request, reply, ctx, co_list);
     appendRequestedVendorOptions(request, reply, ctx, co_list);
 
 
-    processClientFqdn(request, reply, ctx);
-    assignLeases(request, reply, ctx);
     generateFqdn(reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
     createNameChangeRequests(reply, ctx);
 
 
@@ -2374,6 +2389,9 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
 
 
+    processClientFqdn(renew, reply, ctx);
+    extendLeases(renew, reply, ctx);
+
     copyClientOptions(renew, reply);
     copyClientOptions(renew, reply);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(renew, ctx, co_list);
     buildCfgOptionList(renew, ctx, co_list);
@@ -2381,8 +2399,6 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     appendRequestedOptions(renew, reply, co_list);
     appendRequestedOptions(renew, reply, co_list);
     appendRequestedVendorOptions(renew, reply, ctx, co_list);
     appendRequestedVendorOptions(renew, reply, ctx, co_list);
 
 
-    processClientFqdn(renew, reply, ctx);
-    extendLeases(renew, reply, ctx);
     generateFqdn(reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
     createNameChangeRequests(reply, ctx);
 
 
@@ -2401,6 +2417,9 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
 
 
+    processClientFqdn(rebind, reply, ctx);
+    extendLeases(rebind, reply, ctx);
+
     copyClientOptions(rebind, reply);
     copyClientOptions(rebind, reply);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(rebind, ctx, co_list);
     buildCfgOptionList(rebind, ctx, co_list);
@@ -2408,8 +2427,6 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
     appendRequestedOptions(rebind, reply, co_list);
     appendRequestedOptions(rebind, reply, co_list);
     appendRequestedVendorOptions(rebind, reply, ctx, co_list);
     appendRequestedVendorOptions(rebind, reply, ctx, co_list);
 
 
-    processClientFqdn(rebind, reply, ctx);
-    extendLeases(rebind, reply, ctx);
     generateFqdn(reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
     createNameChangeRequests(reply, ctx);
 
 

+ 15 - 2
src/bin/dhcp6/json_config_parser.cc

@@ -43,6 +43,7 @@
 #include <iostream>
 #include <iostream>
 #include <limits>
 #include <limits>
 #include <map>
 #include <map>
+#include <netinet/in.h>
 #include <vector>
 #include <vector>
 
 
 #include <stdint.h>
 #include <stdint.h>
@@ -78,7 +79,7 @@ public:
     /// @param pools storage container in which to store the parsed pool
     /// @param pools storage container in which to store the parsed pool
     /// upon "commit"
     /// upon "commit"
     Pool6Parser(const std::string& param_name,  PoolStoragePtr pools)
     Pool6Parser(const std::string& param_name,  PoolStoragePtr pools)
-        :PoolParser(param_name, pools) {
+        :PoolParser(param_name, pools, AF_INET6) {
     }
     }
 
 
 protected:
 protected:
@@ -151,7 +152,8 @@ public:
     /// upon "commit"
     /// upon "commit"
     PdPoolParser(const std::string&,  PoolStoragePtr pools)
     PdPoolParser(const std::string&,  PoolStoragePtr pools)
         : uint32_values_(new Uint32Storage()),
         : uint32_values_(new Uint32Storage()),
-          string_values_(new StringStorage()), pools_(pools) {
+          string_values_(new StringStorage()), pools_(pools),
+          options_(new CfgOption()) {
         if (!pools_) {
         if (!pools_) {
             isc_throw(isc::dhcp::DhcpConfigError,
             isc_throw(isc::dhcp::DhcpConfigError,
                       "PdPoolParser context storage may not be NULL");
                       "PdPoolParser context storage may not be NULL");
@@ -180,6 +182,12 @@ public:
                 Uint32ParserPtr code_parser(new Uint32Parser(entry,
                 Uint32ParserPtr code_parser(new Uint32Parser(entry,
                                                              uint32_values_));
                                                              uint32_values_));
                 parser = code_parser;
                 parser = code_parser;
+            } else if (entry == "option-data") {
+                OptionDataListParserPtr option_parser(new OptionDataListParser(entry,
+                                                                               options_,
+                                                                               AF_INET6));
+                parser = option_parser;
+
             } else {
             } else {
                 isc_throw(DhcpConfigError, "unsupported parameter: " << entry
                 isc_throw(DhcpConfigError, "unsupported parameter: " << entry
                           << " (" << param.second->getPosition() << ")");
                           << " (" << param.second->getPosition() << ")");
@@ -199,6 +207,8 @@ public:
             // Attempt to construct the local pool.
             // Attempt to construct the local pool.
             pool_.reset(new Pool6(Lease::TYPE_PD, IOAddress(addr_str),
             pool_.reset(new Pool6(Lease::TYPE_PD, IOAddress(addr_str),
                                   prefix_len, delegated_len));
                                   prefix_len, delegated_len));
+            // Merge options specified for a pool into pool configuration.
+            options_->copyTo(*pool_->getCfgOption());
         } catch (const std::exception& ex) {
         } catch (const std::exception& ex) {
             // Some parameters don't exist or are invalid. Since we are not
             // Some parameters don't exist or are invalid. Since we are not
             // aware whether they don't exist or are invalid, let's append
             // aware whether they don't exist or are invalid, let's append
@@ -229,6 +239,9 @@ protected:
 
 
     /// Pointer to storage to which the local pool is written upon commit.
     /// Pointer to storage to which the local pool is written upon commit.
     isc::dhcp::PoolStoragePtr pools_;
     isc::dhcp::PoolStoragePtr pools_;
+
+    /// A storage for pool specific option values.
+    CfgOptionPtr options_;
 };
 };
 
 
 /// @brief Parser for a list of prefix delegation pools.
 /// @brief Parser for a list of prefix delegation pools.

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

@@ -2556,6 +2556,154 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
                sizeof(user_class_expected));
                sizeof(user_class_expected));
 }
 }
 
 
+// This test verifies that it is possible to specify options on
+// pool levels.
+TEST_F(Dhcp6ParserTest, optionDataMultiplePools) {
+    ConstElementPtr x;
+    string config = "{ " + genIfaceConfig() + ","
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { "
+        "        \"pool\": \"2001:db8:1::10 - 2001:db8:1::100\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"subscriber-id\","
+        "            \"data\": \"0102030405060708090A\","
+        "            \"csv-format\": False"
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"pool\": \"2001:db8:1::300 - 2001:db8:1::400\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"user-class\","
+        "            \"data\": \"FFFEFDFCFB\","
+        "            \"csv-format\": False"
+        "        } ]"
+        "    } ],"
+        "    \"pd-pools\": [ { "
+        "        \"prefix\": \"3000::\","
+        "        \"prefix-len\": 48,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"subscriber-id\","
+        "            \"data\": \"112233445566\","
+        "            \"csv-format\": False"
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"prefix\": \"3001::\","
+        "        \"prefix-len\": 48,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"user-class\","
+        "            \"data\": \"aabbccddee\","
+        "            \"csv-format\": False"
+        "        } ]"
+        "    } ],"
+        "    \"subnet\": \"2001:db8:1::/64\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 0);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+    ASSERT_TRUE(subnet);
+
+    PoolPtr pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3000::"), false);
+    ASSERT_TRUE(pool);
+    Pool6Ptr pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+    ASSERT_TRUE(pool6);
+
+    OptionContainerPtr options1 = pool6->getCfgOption()->getAll("dhcp6");
+    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(D6O_SUBSCRIBER_ID);
+    // Expect a single Subscriber ID option instance.
+    ASSERT_EQ(1, std::distance(range1.first, range1.second));
+    const uint8_t subscriber_id_expected[] = {
+        0x11, 0x22, 0x33, 0x44, 0x55, 0x66
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range1.first, D6O_SUBSCRIBER_ID, subscriber_id_expected,
+               sizeof(subscriber_id_expected));
+
+    // Test another pool in the same way.
+    pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3001::"), false);
+    ASSERT_TRUE(pool);
+    pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+    ASSERT_TRUE(pool6);
+
+    OptionContainerPtr options2 = pool6->getCfgOption()->getAll("dhcp6");
+    ASSERT_EQ(1, options2->size());
+
+    const OptionContainerTypeIndex& idx2 = options2->get<1>();
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range2 =
+        idx2.equal_range(D6O_USER_CLASS);
+    ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+    const uint8_t user_class_expected[] = {
+        0xAA, 0xBB, 0xCC, 0xDD, 0xEE
+    };
+    testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+               sizeof(user_class_expected));
+
+    // Test options in NA pools.
+    pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::10"));
+    ASSERT_TRUE(pool);
+    pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+    ASSERT_TRUE(pool6);
+
+    OptionContainerPtr options3 = pool6->getCfgOption()->getAll("dhcp6");
+    ASSERT_EQ(1, options3->size());
+
+    const OptionContainerTypeIndex& idx3 = options3->get<1>();
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range3 =
+        idx3.equal_range(D6O_SUBSCRIBER_ID);
+    ASSERT_EQ(1, std::distance(range3.first, range3.second));
+
+    const uint8_t subscriber_id_expected2[] = {
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A
+    };
+    testOption(*range3.first, D6O_SUBSCRIBER_ID, subscriber_id_expected2,
+               sizeof(subscriber_id_expected2));
+
+
+    pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::300"));
+    ASSERT_TRUE(pool);
+    pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+    ASSERT_TRUE(pool6);
+
+    OptionContainerPtr options4 = pool6->getCfgOption()->getAll("dhcp6");
+    ASSERT_EQ(1, options4->size());
+
+    const OptionContainerTypeIndex& idx4 = options4->get<1>();
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range4 =
+        idx4.equal_range(D6O_USER_CLASS);
+    ASSERT_EQ(1, std::distance(range4.first, range4.second));
+
+    const uint8_t user_class_expected2[] = {
+        0xFF, 0xFE, 0xFD, 0xFC, 0xFB
+    };
+    testOption(*range4.first, D6O_USER_CLASS, user_class_expected2,
+               sizeof(user_class_expected2));
+}
+
 // The goal of this test is to check that the option carrying a boolean
 // The goal of this test is to check that the option carrying a boolean
 // value can be configured using one of the values: "true", "false", "0"
 // value can be configured using one of the values: "true", "false", "0"
 // or "1".
 // or "1".

+ 18 - 2
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -8,11 +8,12 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_status_code.h>
 #include <dhcp/option6_status_code.h>
-#include <dhcp/option_int_array.h>
-#include <dhcp/option_vendor.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/pool.h>
@@ -20,6 +21,7 @@
 #include <util/buffer.h>
 #include <util/buffer.h>
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 #include <boost/pointer_cast.hpp>
 #include <boost/pointer_cast.hpp>
+#include <algorithm>
 #include <cstdlib>
 #include <cstdlib>
 #include <time.h>
 #include <time.h>
 
 
@@ -803,6 +805,20 @@ Dhcp6Client::hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix
     return (false);
     return (false);
 }
 }
 
 
+bool
+Dhcp6Client::hasOptionWithAddress(const uint16_t option_type,
+                                  const std::string& expected_address) const {
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(config_.findOption(option_type));
+    if (opt) {
+        Option6AddrLst::AddressContainer addrs = opt->getAddresses();
+        if (!addrs.empty()) {
+            return (std::find(addrs.begin(), addrs.end(),
+                              IOAddress(expected_address)) != addrs.end());
+        }
+    }
+    return (false);
+}
 
 
 uint16_t
 uint16_t
 Dhcp6Client::getStatusCode(const uint32_t iaid) const {
 Dhcp6Client::getStatusCode(const uint32_t iaid) const {

+ 13 - 0
src/bin/dhcp6/tests/dhcp6_client.h

@@ -468,6 +468,19 @@ public:
     bool hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix,
     bool hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix,
                                            const uint8_t prefix_len) const;
                                            const uint8_t prefix_len) const;
 
 
+    /// @brief Checks that specified option exists and contains a desired
+    /// address.
+    ///
+    /// The option must cast to the @ref Option6AddrLst type. The function
+    /// expects that this option contains at least one address and checks
+    /// first address for equality with @ref expected_address.
+    ///
+    /// @param option_type Option type.
+    /// @param expected_address Desired address.
+    /// @param config Configuration obtained from the server.
+    bool hasOptionWithAddress(const uint16_t option_type,
+                              const std::string& expected_address) const;
+
     /// @brief Returns the value of the global status code for the last
     /// @brief Returns the value of the global status code for the last
     /// transaction.
     /// transaction.
     uint16_t getStatusCode() const {
     uint16_t getStatusCode() const {

+ 1 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -624,6 +624,7 @@ public:
     /// @param stat_name this statistic is expected to be set to 1
     /// @param stat_name this statistic is expected to be set to 1
     void testReceiveStats(uint8_t pkt_type, const std::string& stat_name);
     void testReceiveStats(uint8_t pkt_type, const std::string& stat_name);
 
 
+
     /// A subnet used in most tests
     /// A subnet used in most tests
     isc::dhcp::Subnet6Ptr subnet_;
     isc::dhcp::Subnet6Ptr subnet_;
 
 

+ 19 - 12
src/bin/dhcp6/tests/host_unittest.cc

@@ -40,8 +40,8 @@ namespace {
 ///   in non-default order.
 ///   in non-default order.
 ///
 ///
 /// - Configuration 3:
 /// - Configuration 3:
-///   - Used to test that host specific options override subnet specific
-///     options and global options.
+///   - Used to test that host specific options override pool specific,
+///     subnet specific and global options.
 ///
 ///
 /// - Configuration 4:
 /// - Configuration 4:
 ///   - Used to test that client receives options solely specified in a
 ///   - Used to test that client receives options solely specified in a
@@ -150,7 +150,13 @@ const char* CONFIGS[] = {
         "\"subnet6\": [ "
         "\"subnet6\": [ "
         " { "
         " { "
         "    \"subnet\": \"2001:db8:1::/48\", "
         "    \"subnet\": \"2001:db8:1::/48\", "
-        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"pools\": [ {"
+        "        \"pool\": \"2001:db8:1::/64\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"dns-servers\","
+        "            \"data\": \"3000:2::111\""
+        "        } ]"
+        "    } ],"
         "    \"interface\" : \"eth0\","
         "    \"interface\" : \"eth0\","
         "    \"option-data\": [ {"
         "    \"option-data\": [ {"
         "        \"name\": \"dns-servers\","
         "        \"name\": \"dns-servers\","
@@ -498,7 +504,8 @@ public:
     void testOverrideRequestedOptions(const uint16_t msg_type);
     void testOverrideRequestedOptions(const uint16_t msg_type);
 
 
     /// @brief Verifies that client receives options when they are solely
     /// @brief Verifies that client receives options when they are solely
-    /// defined in the host scope (and not in the global or subnet scope).
+    /// defined in the host scope (and not in the global, subnet or pool
+    /// scope).
     ///
     ///
     /// @param msg_type DHCPv6 message type to be sent to the server. If the
     /// @param msg_type DHCPv6 message type to be sent to the server. If the
     /// message type is Renew or Rebind, the 4-way exchange is made prior to
     /// message type is Renew or Rebind, the 4-way exchange is made prior to
@@ -1212,29 +1219,29 @@ TEST_F(HostTest, hostIdentifiersOrder) {
 }
 }
 
 
 // This test checks that host specific options override subnet specific
 // This test checks that host specific options override subnet specific
-// options. Overridden options are requested with Option Request
-// option (Information-request case).
+// and pool specific options. Overridden options are requested with Option
+// Request option (Information-request case).
 TEST_F(HostTest, overrideRequestedOptionsInformationRequest) {
 TEST_F(HostTest, overrideRequestedOptionsInformationRequest) {
     testOverrideRequestedOptions(DHCPV6_INFORMATION_REQUEST);
     testOverrideRequestedOptions(DHCPV6_INFORMATION_REQUEST);
 }
 }
 
 
 // This test checks that host specific options override subnet specific
 // This test checks that host specific options override subnet specific
-// options. Overridden options are requested with Option Request
-// option (Request case).
+// and pool specific options. Overridden options are requested with Option
+// Request option (Request case).
 TEST_F(HostTest, overrideRequestedOptionsRequest) {
 TEST_F(HostTest, overrideRequestedOptionsRequest) {
     testOverrideRequestedOptions(DHCPV6_REQUEST);
     testOverrideRequestedOptions(DHCPV6_REQUEST);
 }
 }
 
 
 // This test checks that host specific options override subnet specific
 // This test checks that host specific options override subnet specific
-// options. Overridden options are requested with Option Request
-// option (Renew case).
+// and pool specific options. Overridden options are requested with Option
+// Request option (Renew case).
 TEST_F(HostTest, overrideRequestedOptionsRenew) {
 TEST_F(HostTest, overrideRequestedOptionsRenew) {
     testOverrideRequestedOptions(DHCPV6_RENEW);
     testOverrideRequestedOptions(DHCPV6_RENEW);
 }
 }
 
 
 // This test checks that host specific options override subnet specific
 // This test checks that host specific options override subnet specific
-// options. Overridden options are requested with Option Request
-// option (Rebind case).
+// and pool specific options. Overridden options are requested with Option
+// Request option (Rebind case).
 TEST_F(HostTest, overrideRequestedOptionsRebind) {
 TEST_F(HostTest, overrideRequestedOptionsRebind) {
     testOverrideRequestedOptions(DHCPV6_REBIND);
     testOverrideRequestedOptions(DHCPV6_REBIND);
 }
 }

+ 142 - 1
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -69,6 +69,15 @@ namespace {
 ///   - 1 subnet for eth0 and 1 subnet for eth1
 ///   - 1 subnet for eth0 and 1 subnet for eth1
 ///   - DOCSIS vendor config file sub-option
 ///   - DOCSIS vendor config file sub-option
 ///
 ///
+/// - Configuration 8:
+///   - single subnet 3000::/32,
+///   - two options specified in the subnet scope,
+///   - one option specified at the global scope,
+///   - two address pools: 3000::10-3000::20, 3000::40-3000::50,
+///   - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64,
+///   - an option with unique value specified for each pool, so as it is
+///     possible to test that pool specific options can be assigned.
+///
 const char* REBIND_CONFIGS[] = {
 const char* REBIND_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -256,7 +265,69 @@ const char* REBIND_CONFIGS[] = {
         "    \"interface-id\": \"\","
         "    \"interface-id\": \"\","
         "    \"interface\": \"eth1\""
         "    \"interface\": \"eth1\""
         " } ],"
         " } ],"
-        "\"valid-lifetime\": 4000 }"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 8
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ {"
+        "    \"name\": \"dns-servers\","
+        "    \"data\": \"3000:1::234\""
+        "},"
+        "{"
+        "    \"name\": \"sntp-servers\","
+        "    \"data\": \"3000:2::1\""
+        "} ],"
+        "\"subnet6\": [ { "
+        "    \"option-data\": [ {"
+        "        \"name\": \"dns-servers\","
+        "        \"data\": \"3000:1::567\""
+        "    },"
+        "    {"
+        "        \"name\": \"sntp-servers\","
+        "        \"data\": \"3000:2::1\""
+        "    } ],"
+        "    \"pools\": [ { "
+        "        \"pool\": \"3000::10 - 3000::20\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"sntp-servers\","
+        "            \"data\": \"3000:2::2\""
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"pool\": \"3000::40 - 3000::50\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"nisp-servers\","
+        "            \"data\": \"3000:2::3\""
+        "        } ]"
+        "    } ],"
+        "    \"pd-pools\": [ { "
+        "        \"prefix\": \"2001:db8:3::\","
+        "        \"prefix-len\": 64,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"dns-servers\","
+        "            \"data\": \"3000:1::678\""
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"prefix\": \"2001:db8:4::\","
+        "        \"prefix-len\": 64,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"nis-servers\","
+        "            \"data\": \"3000:1::789\""
+        "        } ]"
+        "    } ],"
+        "    \"subnet\": \"3000::/32\", "
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000"
+    "}"
 };
 };
 
 
 /// @brief Test fixture class for testing Rebind.
 /// @brief Test fixture class for testing Rebind.
@@ -1004,4 +1075,74 @@ TEST_F(RebindTest, docsisORO) {
     EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
     EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
 }
 }
 
 
+// This test verifies that the same options can be specified on the global
+// level, subnet level and pool level. The options associated with pools
+// are used when the lease is handed out from these pools.
+TEST_F(RebindTest, optionsInheritance) {
+    Dhcp6Client client;
+    // Request a single address and single prefix.
+    ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::")));
+    ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45")));
+    // Request two options configured for the pools from which the client may get
+    // a lease.
+    client.requestOption(D6O_NAME_SERVERS);
+    client.requestOption(D6O_NIS_SERVERS);
+    client.requestOption(D6O_NISP_SERVERS);
+    client.requestOption(D6O_SNTP_SERVERS);
+    ASSERT_NO_FATAL_FAILURE(configure(REBIND_CONFIGS[8], *client.getServer()));
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(1, subnets->size());
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Simulate aging of leases.
+    client.fastFwdTime(1000);
+
+    // Send Rebind message to the server.
+    ASSERT_NO_THROW(client.doRebind());
+
+    // We have provided hints so we should get leases appropriate
+    // for the hints we provided.
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+    // We shouldn't have leases for the prefix and address which we didn't
+    // request.
+    ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+    ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+    // We should have received options associated with a prefix pool and
+    // address pool from which we have requested the leases. We should not
+    // have received options associated with the remaining pools. Instead,
+    // we should have received options associated with a subnet.
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1"));
+
+    // Let's now also request a prefix and an address from the remaining pools.
+    ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::")));
+    ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11")));
+
+    client.fastFwdTime(1000);
+
+    // Send another Rebind.
+    ASSERT_NO_THROW(client.doRebind());
+
+    // We should now have two prefixes from two distinct pools.
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+    //  We should also have two addresses from two distinct pools.
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+    // This time, options from all pools should have been assigned.
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
+}
+
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 140 - 1
src/bin/dhcp6/tests/renew_unittest.cc

@@ -44,6 +44,15 @@ namespace {
 ///   - 1 subnet with 2001:db8:1::/64 pool
 ///   - 1 subnet with 2001:db8:1::/64 pool
 ///   - DOCSIS vendor config file sub-option
 ///   - DOCSIS vendor config file sub-option
 ///
 ///
+/// - Configuration 4:
+///   - single subnet 3000::/32,
+///   - two options specified in the subnet scope,
+///   - one option specified at the global scope,
+///   - two address pools: 3000::10-3000::20, 3000::40-3000::50,
+///   - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64,
+///   - an option with unique value specified for each pool, so as it is
+///     possible to test that pool specific options can be assigned.
+///
 const char* RENEW_CONFIGS[] = {
 const char* RENEW_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -117,8 +126,69 @@ const char* RENEW_CONFIGS[] = {
         "    \"interface-id\": \"\","
         "    \"interface-id\": \"\","
         "    \"interface\": \"eth0\""
         "    \"interface\": \"eth0\""
         " } ],"
         " } ],"
-        "\"valid-lifetime\": 4000 }"
+        "\"valid-lifetime\": 4000 }",
 
 
+// Configuration 4
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ {"
+        "    \"name\": \"dns-servers\","
+        "    \"data\": \"3000:1::234\""
+        "},"
+        "{"
+        "    \"name\": \"sntp-servers\","
+        "    \"data\": \"3000:2::1\""
+        "} ],"
+        "\"subnet6\": [ { "
+        "    \"option-data\": [ {"
+        "        \"name\": \"dns-servers\","
+        "        \"data\": \"3000:1::567\""
+        "    },"
+        "    {"
+        "        \"name\": \"sntp-servers\","
+        "        \"data\": \"3000:2::1\""
+        "    } ],"
+        "    \"pools\": [ { "
+        "        \"pool\": \"3000::10 - 3000::20\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"sntp-servers\","
+        "            \"data\": \"3000:2::2\""
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"pool\": \"3000::40 - 3000::50\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"nisp-servers\","
+        "            \"data\": \"3000:2::3\""
+        "        } ]"
+        "    } ],"
+        "    \"pd-pools\": [ { "
+        "        \"prefix\": \"2001:db8:3::\","
+        "        \"prefix-len\": 64,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"dns-servers\","
+        "            \"data\": \"3000:1::678\""
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"prefix\": \"2001:db8:4::\","
+        "        \"prefix-len\": 64,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"nis-servers\","
+        "            \"data\": \"3000:1::789\""
+        "        } ]"
+        "    } ],"
+        "    \"subnet\": \"3000::/32\", "
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000"
+    "}"
 };
 };
 
 
 /// @brief Test fixture class for testing Renew.
 /// @brief Test fixture class for testing Renew.
@@ -472,4 +542,73 @@ TEST_F(RenewTest, docsisORO) {
     EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
     EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
 }
 }
 
 
+// This test verifies that the same options can be specified on the global
+// level, subnet level and pool level. The options associated with pools
+// are used when the lease is handed out from these pools.
+TEST_F(RenewTest, optionsInheritance) {
+    Dhcp6Client client;
+    // Request a single address and single prefix.
+    ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::")));
+    ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45")));
+    // Request two options configured for the pools from which the client may get
+    // a lease.
+    client.requestOption(D6O_NAME_SERVERS);
+    client.requestOption(D6O_NIS_SERVERS);
+    client.requestOption(D6O_NISP_SERVERS);
+    client.requestOption(D6O_SNTP_SERVERS);
+    ASSERT_NO_FATAL_FAILURE(configure(RENEW_CONFIGS[4], *client.getServer()));
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(1, subnets->size());
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Simulate aging of leases.
+    client.fastFwdTime(1000);
+
+    // Send Renew message to the server.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // We have provided hints so we should get leases appropriate
+    // for the hints we provided.
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+    // We shouldn't have leases for the prefix and address which we didn't
+    // request.
+    ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+    ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+    // We should have received options associated with a prefix pool and
+    // address pool from which we have requested the leases. We should not
+    // have received options associated with the remaining pools. Instead,
+    // we should have received options associated with a subnet.
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1"));
+
+    // Let's now also request a prefix and an address from the remaining pools.
+    ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::")));
+    ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11")));
+
+    client.fastFwdTime(1000);
+
+    // Send another Renew.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // We should now have two prefixes from two distinct pools.
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+    //  We should also have two addresses from two distinct pools.
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+    // This time, options from all pools should have been assigned.
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
+}
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 132 - 0
src/bin/dhcp6/tests/sarr_unittest.cc

@@ -39,6 +39,15 @@ namespace {
 ///     one
 ///     one
 ///   - DNS updates enabled
 ///   - DNS updates enabled
 ///
 ///
+/// - Configuration 2:
+///   - single subnet 3000::/32,
+///   - two options specified in the subnet scope,
+///   - one option specified at the global scope,
+///   - two address pools: 3000::10-3000::20, 3000::40-3000::50,
+///   - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64,
+///   - an option with unique value specified for each pool, so as it is
+///     possible to test that pool specific options can be assigned.
+///
 const char* CONFIGS[] = {
 const char* CONFIGS[] = {
     // Configuration 0
     // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -82,6 +91,68 @@ const char* CONFIGS[] = {
         " \"dhcp-ddns\" : {"
         " \"dhcp-ddns\" : {"
         "     \"enable-updates\" : True, "
         "     \"enable-updates\" : True, "
         "     \"qualifying-suffix\" : \"example.com\" }"
         "     \"qualifying-suffix\" : \"example.com\" }"
+    "}",
+
+// Configuration 2
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ {"
+        "    \"name\": \"dns-servers\","
+        "    \"data\": \"3000:1::234\""
+        "},"
+        "{"
+        "    \"name\": \"sntp-servers\","
+        "    \"data\": \"3000:2::1\""
+        "} ],"
+        "\"subnet6\": [ { "
+        "    \"option-data\": [ {"
+        "        \"name\": \"dns-servers\","
+        "        \"data\": \"3000:1::567\""
+        "    },"
+        "    {"
+        "        \"name\": \"sntp-servers\","
+        "        \"data\": \"3000:2::1\""
+        "    } ],"
+        "    \"pools\": [ { "
+        "        \"pool\": \"3000::10 - 3000::20\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"sntp-servers\","
+        "            \"data\": \"3000:2::2\""
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"pool\": \"3000::40 - 3000::50\","
+        "        \"option-data\": [ {"
+        "            \"name\": \"nisp-servers\","
+        "            \"data\": \"3000:2::3\""
+        "        } ]"
+        "    } ],"
+        "    \"pd-pools\": [ { "
+        "        \"prefix\": \"2001:db8:3::\","
+        "        \"prefix-len\": 64,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"dns-servers\","
+        "            \"data\": \"3000:1::678\""
+        "        } ]"
+        "    },"
+        "    {"
+        "        \"prefix\": \"2001:db8:4::\","
+        "        \"prefix-len\": 64,"
+        "        \"delegated-len\": 64,"
+        "        \"option-data\": [ {"
+        "            \"name\": \"nis-servers\","
+        "            \"data\": \"3000:1::789\""
+        "        } ]"
+        "    } ],"
+        "    \"subnet\": \"3000::/32\", "
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000"
     "}"
     "}"
 };
 };
 
 
@@ -173,6 +244,67 @@ TEST_F(SARRTest, directClientPrefixHint) {
     ASSERT_TRUE(lease_server);
     ASSERT_TRUE(lease_server);
 }
 }
 
 
+// This test verifies that the same options can be specified on the global
+// level, subnet level and pool level. The options associated with pools
+// are used when the lease is handed out from these pools.
+TEST_F(SARRTest, optionsInheritance) {
+    Dhcp6Client client;
+    // Request a single address and single prefix.
+    ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::")));
+    ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45")));
+    // Request two options configured for the pools from which the client may get
+    // a lease.
+    client.requestOption(D6O_NAME_SERVERS);
+    client.requestOption(D6O_NIS_SERVERS);
+    client.requestOption(D6O_NISP_SERVERS);
+    client.requestOption(D6O_SNTP_SERVERS);
+    ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[2], *client.getServer()));
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(1, subnets->size());
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // We have provided hints so we should get leases appropriate
+    // for the hints we provided.
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+    // We shouldn't have leases for the prefix and address which we didn't
+    // request.
+    ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+    ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+    // We should have received options associated with a prefix pool and
+    // address pool from which we have requested the leases. We should not
+    // have received options associated with the remaining pools. Instead,
+    // we should have received options associated with a subnet.
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1"));
+
+    // Let's now also request a prefix and an address from the remaining pools.
+    ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::")));
+    ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11")));
+
+    // Perform 4-way exchange again.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // We should now have two prefixes from two distinct pools.
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+    ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+    //  We should also have two addresses from two distinct pools.
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+    ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+    // This time, options from all pools should have been assigned.
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+    ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
+}
+
 // Check that when the client includes the Rapid Commit option in its
 // Check that when the client includes the Rapid Commit option in its
 // Solicit, the server responds with Reply and commits the lease.
 // Solicit, the server responds with Reply and commits the lease.
 TEST_F(SARRTest, rapidCommitEnable) {
 TEST_F(SARRTest, rapidCommitEnable) {

+ 48 - 19
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -1039,8 +1039,10 @@ void PoolsListParser::commit() {
 }
 }
 
 
 //****************************** PoolParser ********************************
 //****************************** PoolParser ********************************
-PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
-        :pools_(pools) {
+PoolParser::PoolParser(const std::string&, PoolStoragePtr pools,
+                       const uint16_t address_family)
+        :pools_(pools), options_(new CfgOption()),
+         address_family_(address_family) {
 
 
     if (!pools_) {
     if (!pools_) {
         isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
         isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
@@ -1067,6 +1069,8 @@ PoolParser::build(ConstElementPtr pool_structure) {
     boost::erase_all(txt, " "); // space
     boost::erase_all(txt, " "); // space
     boost::erase_all(txt, "\t"); // tabulation
     boost::erase_all(txt, "\t"); // tabulation
 
 
+    PoolPtr pool;
+
     // Is this prefix/len notation?
     // Is this prefix/len notation?
     size_t pos = txt.find("/");
     size_t pos = txt.find("/");
     if (pos != string::npos) {
     if (pos != string::npos) {
@@ -1093,28 +1097,53 @@ PoolParser::build(ConstElementPtr pool_structure) {
                       << " (" << text_pool->getPosition() << ")");
                       << " (" << text_pool->getPosition() << ")");
         }
         }
 
 
-        PoolPtr pool(poolMaker(addr, len));
+        pool = poolMaker(addr, len);
         local_pools_.push_back(pool);
         local_pools_.push_back(pool);
-        return;
-    }
 
 
-    // Is this min-max notation?
-    pos = txt.find("-");
-    if (pos != string::npos) {
-        // using min-max notation
-        isc::asiolink::IOAddress min(txt.substr(0,pos));
-        isc::asiolink::IOAddress max(txt.substr(pos + 1));
+    } else {
 
 
-        PoolPtr pool(poolMaker(min, max));
-        local_pools_.push_back(pool);
-        return;
+        // Is this min-max notation?
+        pos = txt.find("-");
+        if (pos != string::npos) {
+            // using min-max notation
+            isc::asiolink::IOAddress min(txt.substr(0,pos));
+            isc::asiolink::IOAddress max(txt.substr(pos + 1));
+
+            pool = poolMaker(min, max);
+            local_pools_.push_back(pool);
+        }
     }
     }
 
 
-    isc_throw(DhcpConfigError, "invalid pool definition: "
-              << text_pool->stringValue() <<
-              ". There are two acceptable formats <min address-max address>"
-              " or <prefix/len> ("
-              << text_pool->getPosition() << ")");
+    if (!pool) {
+        isc_throw(DhcpConfigError, "invalid pool definition: "
+                  << text_pool->stringValue() <<
+                  ". There are two acceptable formats <min address-max address>"
+                  " or <prefix/len> ("
+                  << text_pool->getPosition() << ")");
+    }
+
+    // Parser pool specific options.
+    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");
+            }
+
+            OptionDataListParserPtr option_parser(new OptionDataListParser("option-data",
+                                                                           options_,
+                                                                           address_family_));
+            option_parser->build(option_data);
+            option_parser->commit();
+            options_->copyTo(*pool->getCfgOption());;
+
+        } catch (const std::exception& ex) {
+            isc_throw(isc::dhcp::DhcpConfigError, ex.what()
+                      << " (" << option_data->getPosition() << ")");
+        }
+    }
 }
 }
 
 
 void
 void

+ 9 - 1
src/lib/dhcpsrv/parsers/dhcp_parsers.h

@@ -835,8 +835,10 @@ public:
     /// accept string as first argument.
     /// accept string as first argument.
     /// @param pools is the storage in which to store the parsed pool
     /// @param pools is the storage in which to store the parsed pool
     /// upon "commit".
     /// upon "commit".
+    /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6).
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
-    PoolParser(const std::string& dummy, PoolStoragePtr pools);
+    PoolParser(const std::string& dummy, PoolStoragePtr pools,
+               const uint16_t address_family);
 
 
     /// @brief parses the actual structure
     /// @brief parses the actual structure
     ///
     ///
@@ -879,6 +881,12 @@ protected:
     /// A temporary storage for pools configuration. It is a
     /// A temporary storage for pools configuration. It is a
     /// storage where pools are stored by build function.
     /// storage where pools are stored by build function.
     PoolStorage local_pools_;
     PoolStorage local_pools_;
+
+    /// A storage for pool specific option values.
+    CfgOptionPtr options_;
+
+    /// @brief Address family: AF_INET (for DHCPv4) or AF_INET6 for DHCPv6.
+    uint16_t address_family_;
 };
 };
 
 
 /// @brief Parser for a list of pools
 /// @brief Parser for a list of pools

+ 2 - 2
src/lib/dhcpsrv/pool.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -17,7 +17,7 @@ namespace dhcp {
 Pool::Pool(Lease::Type type, const isc::asiolink::IOAddress& first,
 Pool::Pool(Lease::Type type, const isc::asiolink::IOAddress& first,
            const isc::asiolink::IOAddress& last)
            const isc::asiolink::IOAddress& last)
     :id_(getNextID()), first_(first), last_(last), type_(type),
     :id_(getNextID()), first_(first), last_(last), type_(type),
-     capacity_(0) {
+     capacity_(0), cfg_option_(new CfgOption()) {
 }
 }
 
 
 bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
 bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {

+ 17 - 1
src/lib/dhcpsrv/pool.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 
 
 #include <vector>
 #include <vector>
@@ -79,6 +80,18 @@ public:
     uint64_t getCapacity() const {
     uint64_t getCapacity() const {
         return (capacity_);
         return (capacity_);
     }
     }
+
+    /// @brief Returns pointer to the option data configuration for this pool.
+    CfgOptionPtr getCfgOption() {
+        return (cfg_option_);
+    }
+
+    /// @brief Returns const pointer to the option data configuration for
+    /// this pool.
+    ConstCfgOptionPtr getCfgOption() const {
+        return (cfg_option_);
+    }
+
 protected:
 protected:
 
 
     /// @brief protected constructor
     /// @brief protected constructor
@@ -128,6 +141,9 @@ protected:
     /// the result. Note that for very large pools, the number is capped at
     /// the result. Note that for very large pools, the number is capped at
     /// max value of uint64_t.
     /// max value of uint64_t.
     uint64_t capacity_;
     uint64_t capacity_;
+
+    /// @brief Pointer to the option data configuration for this pool.
+    CfgOptionPtr cfg_option_;
 };
 };
 
 
 /// @brief Pool information for IPv4 addresses
 /// @brief Pool information for IPv4 addresses

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -282,5 +282,55 @@ TEST(Pool6Test, leasesCount) {
     EXPECT_EQ(65536, pool2.getCapacity());
     EXPECT_EQ(65536, pool2.getCapacity());
 }
 }
 
 
+// This test checks that it is possible to specify pool specific options.
+TEST(Pool6Test, addOptions) {
+    // Create a pool to add options to it.
+    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("3000::"), 64, 128));
+
+    // Differentiate options by their codes (100-109)
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "dhcp6"));
+    }
+
+    // Add 7 options to another option space. The option codes partially overlap
+    // with option codes that we have added to dhcp6 option space.
+    for (uint16_t code = 105; code < 112; ++code) {
+        OptionPtr option(new Option(Option::V6, 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("dhcp6");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(10, options->size());
+
+    // Validate codes of options added to dhcp6 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());
+}
 
 
 }; // end of anonymous namespace
 }; // end of anonymous namespace