Browse Source

[master] Merge branch 'trac5023' (contexts in v6 pools)

# Conflicts:
#	src/lib/dhcpsrv/pool.h
Tomek Mrugalski 8 years ago
parent
commit
4f81789e19

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

@@ -3713,6 +3713,51 @@ src/lib/dhcpsrv/cfg_host_operations.cc -->
       </itemizedlist>
       </itemizedlist>
     </section>
     </section>
 
 
+      <section>
+        <title>User context in IPv4 pools</title>
+        <para>
+          Kea allows loading hook libraries that sometimes could benefit from
+          additional parameters. If such a parameter is specific to the whole
+          library, it is typically defined as a parameter for the hook library.
+          However, sometimes there is a need to specify parameters that are
+          different for each pool.
+        </para>
+        <para>
+          Let's consider an imaginary case of devices that have color LED
+          lights. Depending on their location, they should glow red, blue or
+          green. It would be easy to write a hook library that would send
+          specific values as maybe vendor option. However, the server has to
+          have some way to specify that value for each pool. This need is
+          addressed by user contexts. In essence, any user data can specified
+          in the user context as long as it is a valid JSON map. For example,
+          the forementioned case of LED devices could be configured in the
+          following way:
+
+          <screen>
+"Dhcp4": {
+    "subnet4": [
+        {
+            "subnet": "192.0.2.0/24",
+            "pools": [ { "pool": "192.0.2.10 - 192.0.2.20" } ],
+            <userinput>"user-context": { "colour": "red" }</userinput>
+        },
+        ...
+    ],
+    ...
+}</screen>
+        </para>
+
+        <para>
+          It should be noted that Kea will not use that information, but will
+          simply store and make it available to hook libraries. It is up to the
+        hook library to extract that information and make use of it.</para>
+        <para>
+          Currently only pools allow definition of user contexts, but this
+          concept is expected to be enhanced to other structures in the future.
+          For more background information, see <xref linkend="user-context"/>
+        </para>
+    </section>
+
     <section id="dhcp4-limit">
     <section id="dhcp4-limit">
       <title>DHCPv4 Server Limitations</title>
       <title>DHCPv4 Server Limitations</title>
       <para>These are the current limitations of the DHCPv4 server
       <para>These are the current limitations of the DHCPv4 server

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

@@ -4077,6 +4077,66 @@ If not specified, the default value is:
       <xref linkend="command-shutdown" />, respectively.</para>
       <xref linkend="command-shutdown" />, respectively.</para>
     </section>
     </section>
 
 
+
+      <section>
+        <title>User context in IPv6 pools</title>
+        <para>
+          Kea allows loading hook libraries that sometimes could benefit from
+          additional parameters. If such a parameter is specific to the whole
+          library, it is typically defined as a parameter for the hook library.
+          However, sometimes there is a need to specify parameters that are
+          different for each pool.
+        </para>
+        <para>
+          Let's consider a lightweight 4over6 deployment as an example. It is an
+          IPv6 transition technology that allows mapping IPv6 prefix into full
+          or parts of IPv4 addresses. In DHCP context, these are certain
+          parameters that are supposed to be delivered to clients in form of
+          additional options. Values of those options are correlated to
+          delegated prefixes, so it is reasonable to keep those parameters
+          together with the PD pool. On the other hand, lightweight 4over6 is
+          not a commonly used feature, so it is not a part of the base Kea
+          code. The solution to this problem is to use user context. For each PD
+          pool that is expected to be used for lightweight 4over6, user context
+          with extra parameters is defined. Those extra parameters will be used
+          by hook library that would be loaded only when dynamic calculation of
+          the lightweight 4over6 option is actually needed. An example
+          configuration looks as follows:
+          <screen>
+"Dhcp4": {
+    "subnet6": [ {
+        "pd-pools": [
+        {
+            "prefix":  "2001:db8::",
+            "prefix-len": 56,
+            "delegated-len": 64,
+            <userinput>"user-context": {
+                "lw4over6-sharing-ratio": 64,
+                "lw4over6-v4-pool": "192.0.2.0/24",
+                "lw4over6-sysports-exclude": true,
+                "lw4over6-bind-prefix-len": 56
+            }</userinput>
+        } ],
+        "subnet": "2001:db8::/32"
+    } ],
+    ...
+}</screen>
+        </para>
+
+        <para>
+          It should be noted that Kea will not use any information in the user
+          context, but will simply store and make it available to the hook
+          libraries. It is up to the hook library to extract that information
+          and make use of it.
+        </para>
+        <para>
+          Currently only address and prefix pools allow definition of user
+          contexts, but this concept is expected to be enhanced to other
+          structures in the future. For more background information, see <xref
+          linkend="user-context"/>
+        </para>
+    </section>
+
     <section id="dhcp6-std">
     <section id="dhcp6-std">
       <title>Supported DHCPv6 Standards</title>
       <title>Supported DHCPv6 Standards</title>
       <para>The following standards are currently
       <para>The following standards are currently

+ 19 - 0
doc/guide/hooks.xml

@@ -545,4 +545,23 @@ link address: 3001::1, hop count: 1, identified by remote-id:
         </section>
         </section>
       </section>
       </section>
     </section>
     </section>
+    <section id="user-context">
+      <title>User contexts</title>
+      <para>Hook libraries can have their own configuration parameters. That is
+      convenient if the parameter applies to the whole library. However,
+      sometimes it is very useful if certain configuration entities are extended
+      with additional configuration data. This is where the concept of user
+      contexts comes in. A sysadmin can define an arbitrary set of data and
+      attach it to Kea structures, as long as the data is specified as JSON map.
+      In particular, it is possible to define fields that are integers, strings,
+      boolean, lists and maps. It is possible to define nested structures of
+      arbitrary complexity. Kea does not use that data on its own, simply stores
+      and makes it available for the hook libraries.
+      </para>
+      <para>
+        As of Kea 1.2, the only structures that allow user contexts are address
+        and prefix pools, but it is expected to extend other structures with the
+        user context capability.
+      </para>
+    </section>
    </chapter> <!-- hooks-libraries -->
    </chapter> <!-- hooks-libraries -->

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

@@ -50,6 +50,64 @@ using namespace isc::hooks;
 using namespace std;
 using namespace std;
 
 
 namespace {
 namespace {
+const char* PARSER_CONFIGS[] = {
+    // CONFIGURATION 0: one subnet with one pool, no user contexts
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet4\": [ {"
+    "        \"pools\": [ "
+    "            { \"pool\":  \"192.0.2.0/28\" }"
+    "        ],"
+    "        \"subnet\": \"192.0.2.0/24\""
+    "     } ]"
+    "}",
+
+    // Configuration 1: one pool with empty user context
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet4\": [ {"
+    "        \"pools\": [ "
+    "            { \"pool\":  \"192.0.2.0/28\","
+    "                \"user-context\": {"
+    "                }"
+    "            }"
+    "        ],"
+    "        \"subnet\": \"192.0.2.0/24\""
+    "     } ]"
+    "}",
+
+    // Configuration 2: one pool with user context containing lw4over6 parameters
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet4\": [ {"
+    "        \"pools\": [ "
+    "            { \"pool\":  \"192.0.2.0/28\","
+    "                \"user-context\": {"
+    "                    \"integer-param\": 42,"
+    "                    \"string-param\": \"Sagittarius\","
+    "                    \"bool-param\": true"
+    "                }"
+    "            }"
+    "        ],"
+    "        \"subnet\": \"192.0.2.0/24\""
+    "     } ]"
+    "}"
+};
 
 
 /// @brief Prepends the given name with the DHCP4 source directory
 /// @brief Prepends the given name with the DHCP4 source directory
 ///
 ///
@@ -501,6 +559,37 @@ public:
         return (ReturnType());
         return (ReturnType());
     }
     }
 
 
+
+    /// @brief This utility method attempts to configure using specified
+    ///        config and then returns requested pool from requested subnet
+    ///
+    /// @param config configuration to be applied
+    /// @param subnet_index index of the subnet to be returned (0 - the first subnet)
+    /// @param pool_index index of the pool within a subnet (0 - the first pool)
+    /// @param pool [out] Pool pointer will be stored here (if found)
+    void getPool(const std::string& config, size_t subnet_index,
+                 size_t pool_index, PoolPtr& pool) {
+        ConstElementPtr status;
+        ElementPtr json = Element::fromJSON(config);
+
+        EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+        ASSERT_TRUE(status);
+        checkResult(status, 0);
+
+        ConstCfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+        ASSERT_TRUE(subnets4);
+
+        const Subnet4Collection* subnets = subnets4->getAll();
+        ASSERT_TRUE(subnets);
+        ASSERT_GE(subnets->size(), subnet_index + 1);
+
+        const PoolCollection pools = subnets->at(subnet_index)->getPools(Lease::TYPE_V4);
+        ASSERT_GE(pools.size(), pool_index + 1);
+
+        pool = pools.at(pool_index);
+        EXPECT_TRUE(pool);
+    }
+
     boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
     boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
     int rcode_;                         ///< Return code from element parsing
     int rcode_;                         ///< Return code from element parsing
     ConstElementPtr comment_;           ///< Reason for parse fail
     ConstElementPtr comment_;           ///< Reason for parse fail
@@ -4277,5 +4366,61 @@ TEST_F(Dhcp4ParserTest, invalidClientClassDictionary) {
     checkResult(status, 1);
     checkResult(status, 1);
 }
 }
 
 
+// Test verifies that regular configuration does not provide any user context
+// in the address pool.
+TEST_F(Dhcp4ParserTest, poolUserContextMissing) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[0]), 0, 0, pool);
+    ASSERT_TRUE(pool);
+    EXPECT_FALSE(pool->getContext());
+}
+
+// Test verifies that it's possible to specify empty user context in the
+// address pool.
+TEST_F(Dhcp4ParserTest, poolUserContextEmpty) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[1]), 0, 0, pool);
+    ASSERT_TRUE(pool);
+    ConstElementPtr ctx = pool->getContext();
+    ASSERT_TRUE(ctx);
+
+    // The context should be of type map and not contain any parameters.
+    EXPECT_EQ(Element::map, ctx->getType());
+    EXPECT_EQ(0, ctx->size());
+}
+
+// Test verifies that it's possible to specify parameters in the user context
+// in the address pool.
+TEST_F(Dhcp4ParserTest, poolUserContextData) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[2]), 0, 0, pool);
+    ASSERT_TRUE(pool);
+    ConstElementPtr ctx = pool->getContext();
+    ASSERT_TRUE(ctx);
+
+    // The context should be of type map and contain 4 parameters.
+    EXPECT_EQ(Element::map, ctx->getType());
+    EXPECT_EQ(3, ctx->size());
+    ConstElementPtr int_param  = ctx->get("integer-param");
+    ConstElementPtr str_param  = ctx->get("string-param");
+    ConstElementPtr bool_param = ctx->get("bool-param");
+
+    ASSERT_TRUE(int_param);
+    ASSERT_EQ(Element::integer, int_param->getType());
+    int64_t int_value;
+    EXPECT_NO_THROW(int_param->getValue(int_value));
+    EXPECT_EQ(42L, int_value);
+
+    ASSERT_TRUE(str_param);
+    ASSERT_EQ(Element::string, str_param->getType());
+    EXPECT_EQ("Sagittarius", str_param->stringValue());
+
+    ASSERT_TRUE(bool_param);
+    bool bool_value;
+    ASSERT_EQ(Element::boolean, bool_param->getType());
+    EXPECT_NO_THROW(bool_param->getValue(bool_value));
+    EXPECT_EQ(true, bool_value);
+}
+
 
 
 }
 }

+ 9 - 1
src/bin/dhcp6/json_config_parser.cc

@@ -188,7 +188,9 @@ public:
                                                                                options_,
                                                                                options_,
                                                                                AF_INET6));
                                                                                AF_INET6));
                 parser = option_parser;
                 parser = option_parser;
-
+            } else if (entry == "user-context") {
+                user_context_ = param.second;
+                continue; // no parser to remember, simply store the value
             } else {
             } else {
                 isc_throw(DhcpConfigError, "unsupported parameter: " << entry
                 isc_throw(DhcpConfigError, "unsupported parameter: " << entry
                           << " (" << param.second->getPosition() << ")");
                           << " (" << param.second->getPosition() << ")");
@@ -222,6 +224,10 @@ public:
             isc_throw(isc::dhcp::DhcpConfigError, ex.what()
             isc_throw(isc::dhcp::DhcpConfigError, ex.what()
                       << " (" << pd_pool_->getPosition() << ")");
                       << " (" << pd_pool_->getPosition() << ")");
         }
         }
+
+        if (user_context_) {
+            pool_->setUserContext(user_context_);
+        }
     }
     }
 
 
     // @brief Commits the constructed local pool to the pool storage.
     // @brief Commits the constructed local pool to the pool storage.
@@ -248,6 +254,8 @@ protected:
 
 
     /// A storage for pool specific option values.
     /// A storage for pool specific option values.
     CfgOptionPtr options_;
     CfgOptionPtr options_;
+
+    isc::data::ConstElementPtr user_context_;
 };
 };
 
 
 /// @brief Parser for a list of prefix delegation pools.
 /// @brief Parser for a list of prefix delegation pools.

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

@@ -55,6 +55,135 @@ using namespace std;
 
 
 namespace {
 namespace {
 
 
+const char* PARSER_CONFIGS[] = {
+    // CONFIGURATION 0: one subnet with one pool, no user contexts
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"preferred-lifetime\": 3000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet6\": [ {"
+    "        \"pools\": [ "
+    "            { \"pool\":  \"2001:db8::/64\" }"
+    "        ],"
+    "        \"subnet\": \"2001:db8::/32\""
+    "     } ]"
+    "}",
+
+    // Configuration 1: one pool with empty user context
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"preferred-lifetime\": 3000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet6\": [ {"
+    "        \"pools\": [ "
+    "            { \"pool\":  \"2001:db8::/64\","
+    "                \"user-context\": {"
+    "                }"
+    "            }"
+    "        ],"
+    "        \"subnet\": \"2001:db8::/32\""
+    "     } ]"
+    "}",
+
+    // Configuration 2: one pool with user context containing lw4over6 parameters
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"preferred-lifetime\": 3000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet6\": [ {"
+    "        \"pools\": [ "
+    "            { \"pool\":  \"2001:db8::/64\","
+    "                \"user-context\": {"
+    "                    \"lw4over6-sharing-ratio\": 64,"
+    "                    \"lw4over6-v4-pool\": \"192.0.2.0/24\","
+    "                    \"lw4over6-sysports-exclude\": true,"
+    "                    \"lw4over6-bind-prefix-len\": 56"
+    "                }"
+    "            }"
+    "        ],"
+    "        \"subnet\": \"2001:db8::/32\""
+    "     } ]"
+    "}",
+
+    // Configuration 3: pd-pool without any user-context
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"preferred-lifetime\": 3000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet6\": [ {"
+    "        \"pd-pools\": [ "
+    "            { \"prefix\": \"2001:db8::\","
+    "              \"prefix-len\": 56,"
+    "              \"delegated-len\": 64 }"
+    "        ],"
+    "        \"subnet\": \"2001:db8::/32\""
+    "     } ]"
+    "}",
+
+    // Configuration 4: pd-pool with empty user-context
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"preferred-lifetime\": 3000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet6\": [ {"
+    "        \"pd-pools\": [ "
+    "            { \"prefix\":  \"2001:db8::\","
+    "              \"prefix-len\": 56,"
+    "              \"delegated-len\": 64,"
+    "              \"user-context\": { }"
+    "            }"
+    "        ],"
+    "        \"subnet\": \"2001:db8::/32\""
+    "     } ]"
+    "}",
+
+    // Configuration 5: pd-pool with user-context with lw4over6 parameters
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [\"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 4000,"
+    "    \"preferred-lifetime\": 3000,"
+    "    \"rebind-timer\": 2000,"
+    "    \"renew-timer\": 1000,"
+    "    \"subnet6\": [ {"
+    "        \"pd-pools\": [ "
+    "            { \"prefix\":  \"2001:db8::\","
+    "              \"prefix-len\": 56,"
+    "              \"delegated-len\": 64,"
+    "              \"user-context\": {"
+    "                  \"lw4over6-sharing-ratio\": 64,"
+    "                  \"lw4over6-v4-pool\": \"192.0.2.0/24\","
+    "                  \"lw4over6-sysports-exclude\": true,"
+    "                  \"lw4over6-bind-prefix-len\": 56"
+    "              }"
+    "            }"
+    "        ],"
+    "        \"subnet\": \"2001:db8::/32\""
+    "     } ]"
+    "}"
+};
+
 std::string specfile(const std::string& name) {
 std::string specfile(const std::string& name) {
     return (std::string(DHCP6_SRC_DIR) + "/" + name);
     return (std::string(DHCP6_SRC_DIR) + "/" + name);
 }
 }
@@ -562,6 +691,37 @@ public:
         CfgMgr::instance().clear();
         CfgMgr::instance().clear();
     }
     }
 
 
+    /// @brief This utility method attempts to configure using specified
+    ///        config and then returns requested pool from requested subnet
+    ///
+    /// @param config configuration to be applied
+    /// @param subnet_index index of the subnet to be returned (0 - the first subnet)
+    /// @param pool_index index of the pool within a subnet (0 - the first pool)
+    /// @param type Pool type (TYPE_NA or TYPE_PD)
+    /// @param pool [out] Pool pointer will be stored here (if found)
+    void getPool(const std::string& config, size_t subnet_index,
+                 size_t pool_index, Lease::Type type, PoolPtr& pool) {
+        ConstElementPtr status;
+        ElementPtr json = Element::fromJSON(config);
+
+        EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+        ASSERT_TRUE(status);
+        checkResult(status, 0);
+
+        ConstCfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+        ASSERT_TRUE(subnets6);
+
+        const Subnet6Collection* subnets = subnets6->getAll();
+        ASSERT_TRUE(subnets);
+        ASSERT_GE(subnets->size(), subnet_index + 1);
+
+        const PoolCollection pools = subnets->at(subnet_index)->getPools(type);
+        ASSERT_GE(pools.size(), pool_index + 1);
+
+        pool = pools.at(pool_index);
+        EXPECT_TRUE(pool);
+    }
+
     int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
     int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
     Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
     Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
@@ -4581,4 +4741,129 @@ TEST_F(Dhcp6ParserTest, invalidClientClassDictionary) {
     checkResult(status, 1);
     checkResult(status, 1);
 }
 }
 
 
+// Test verifies that regular configuration does not provide any user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, poolUserContextMissing) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[0]), 0, 0, Lease::TYPE_NA, pool);
+    ASSERT_TRUE(pool);
+    EXPECT_FALSE(pool->getContext());
+}
+
+// Test verifies that it's possible to specify empty user context in the
+// address pool.
+TEST_F(Dhcp6ParserTest, poolUserContextEmpty) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[1]), 0, 0, Lease::TYPE_NA, pool);
+    ASSERT_TRUE(pool);
+    ConstElementPtr ctx = pool->getContext();
+    ASSERT_TRUE(ctx);
+
+    // The context should be of type map and not contain any parameters.
+    EXPECT_EQ(Element::map, ctx->getType());
+    EXPECT_EQ(0, ctx->size());
+}
+
+// Test verifies that it's possible to specify parameters in the user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, poolUserContextlw4over6) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[2]), 0, 0, Lease::TYPE_NA, pool);
+    ASSERT_TRUE(pool);
+    ConstElementPtr ctx = pool->getContext();
+    ASSERT_TRUE(ctx);
+
+    // The context should be of type map and contain 4 parameters.
+    EXPECT_EQ(Element::map, ctx->getType());
+    EXPECT_EQ(4, ctx->size());
+    ConstElementPtr ratio   = ctx->get("lw4over6-sharing-ratio");
+    ConstElementPtr v4pool  = ctx->get("lw4over6-v4-pool");
+    ConstElementPtr exclude = ctx->get("lw4over6-sysports-exclude");
+    ConstElementPtr v6len   = ctx->get("lw4over6-bind-prefix-len");
+
+    ASSERT_TRUE(ratio);
+    ASSERT_EQ(Element::integer, ratio->getType());
+    int64_t int_value;
+    EXPECT_NO_THROW(ratio->getValue(int_value));
+    EXPECT_EQ(64L, int_value);
+
+    ASSERT_TRUE(v4pool);
+    ASSERT_EQ(Element::string, v4pool->getType());
+    EXPECT_EQ("192.0.2.0/24", v4pool->stringValue());
+
+    ASSERT_TRUE(exclude);
+    bool bool_value;
+    ASSERT_EQ(Element::boolean, exclude->getType());
+    EXPECT_NO_THROW(exclude->getValue(bool_value));
+    EXPECT_EQ(true, bool_value);
+
+    ASSERT_TRUE(v6len);
+    ASSERT_EQ(Element::integer, v6len->getType());
+    EXPECT_NO_THROW(v6len->getValue(int_value));
+    EXPECT_EQ(56L, int_value);
+}
+
+// Test verifies that regular configuration does not provide any user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, pdPoolUserContextMissing) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[3]), 0, 0, Lease::TYPE_PD, pool);
+    ASSERT_TRUE(pool);
+    EXPECT_FALSE(pool->getContext());
+}
+
+// Test verifies that it's possible to specify empty user context in the
+// address pool.
+TEST_F(Dhcp6ParserTest, pdPoolUserContextEmpty) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[4]), 0, 0, Lease::TYPE_PD, pool);
+    ASSERT_TRUE(pool);
+    ConstElementPtr ctx = pool->getContext();
+    ASSERT_TRUE(ctx);
+
+    // The context should be of type map and not contain any parameters.
+    EXPECT_EQ(Element::map, ctx->getType());
+    EXPECT_EQ(0, ctx->size());
+}
+
+// Test verifies that it's possible to specify parameters in the user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, pdPoolUserContextlw4over6) {
+    PoolPtr pool;
+    getPool(string(PARSER_CONFIGS[5]), 0, 0, Lease::TYPE_PD, pool);
+    ASSERT_TRUE(pool);
+    ConstElementPtr ctx = pool->getContext();
+    ASSERT_TRUE(ctx);
+
+    // The context should be of type map and contain 4 parameters.
+    EXPECT_EQ(Element::map, ctx->getType());
+    EXPECT_EQ(4, ctx->size());
+    ConstElementPtr ratio   = ctx->get("lw4over6-sharing-ratio");
+    ConstElementPtr v4pool  = ctx->get("lw4over6-v4-pool");
+    ConstElementPtr exclude = ctx->get("lw4over6-sysports-exclude");
+    ConstElementPtr v6len   = ctx->get("lw4over6-bind-prefix-len");
+
+    ASSERT_TRUE(ratio);
+    ASSERT_EQ(Element::integer, ratio->getType());
+    int64_t int_value;
+    EXPECT_NO_THROW(ratio->getValue(int_value));
+    EXPECT_EQ(64L, int_value);
+
+    ASSERT_TRUE(v4pool);
+    ASSERT_EQ(Element::string, v4pool->getType());
+    EXPECT_EQ("192.0.2.0/24", v4pool->stringValue());
+
+    ASSERT_TRUE(exclude);
+    bool bool_value;
+    ASSERT_EQ(Element::boolean, exclude->getType());
+    EXPECT_NO_THROW(exclude->getValue(bool_value));
+    EXPECT_EQ(true, bool_value);
+
+    ASSERT_TRUE(v6len);
+    ASSERT_EQ(Element::integer, v6len->getType());
+    EXPECT_NO_THROW(v6len->getValue(int_value));
+    EXPECT_EQ(56L, int_value);
+}
+
+
 };
 };

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

@@ -1096,6 +1096,16 @@ PoolParser::build(ConstElementPtr pool_structure) {
         pool = poolMaker(addr, len);
         pool = poolMaker(addr, len);
         local_pools_.push_back(pool);
         local_pools_.push_back(pool);
 
 
+        // If there's user-context specified, store it.
+        ConstElementPtr user_context = pool_structure->get("user-context");
+        if (user_context) {
+            if (user_context->getType() != Element::map) {
+                isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+                          << user_context->getPosition() << ")");
+            }
+            pool->setUserContext(user_context);
+        }
+
     } else {
     } else {
 
 
         // Is this min-max notation?
         // Is this min-max notation?

+ 16 - 0
src/lib/dhcpsrv/pool.h

@@ -9,6 +9,8 @@
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option6_pdexclude.h>
 #include <dhcp/option6_pdexclude.h>
+#include <boost/shared_ptr.hpp>
+#include <cc/data.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -93,6 +95,17 @@ public:
         return (cfg_option_);
         return (cfg_option_);
     }
     }
 
 
+    /// @brief Returns const pointer to the user context.
+    data::ConstElementPtr getContext() const {
+        return (user_context_);
+    }
+
+    /// @brief Sets user context.
+    /// @param ctx user context to be stored.
+    void setUserContext(const data::ConstElementPtr& ctx) {
+        user_context_ = ctx;
+    }
+
 protected:
 protected:
 
 
     /// @brief protected constructor
     /// @brief protected constructor
@@ -145,6 +158,9 @@ protected:
 
 
     /// @brief Pointer to the option data configuration for this pool.
     /// @brief Pointer to the option data configuration for this pool.
     CfgOptionPtr cfg_option_;
     CfgOptionPtr cfg_option_;
+
+    /// @brief Pointer to the user context (may be NULL)
+    data::ConstElementPtr user_context_;
 };
 };
 
 
 /// @brief Pool information for IPv4 addresses
 /// @brief Pool information for IPv4 addresses