Parcourir la source

[3033] Added DHCP-DDNS configuration paramater parsing to b10-dhcp4

Added configuration paramters to dhcp4 and its spec file to support DHCP-DDNS.
Created new classes D2ClientMgr, D2ClientConfig, and D2CientConfigParser in the
libdhcpsrv.   The new parameters are parsed, validated, and stored  but do
not yet affect behavior.  That will be implemented as a seperate ticket.
Thomas Markwalder il y a 11 ans
Parent
commit
d508d4ac19

+ 1 - 0
configure.ac

@@ -1365,6 +1365,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/bin/dhcp4/spec_config.h.pre
                  src/bin/dhcp4/tests/Makefile
                  src/bin/dhcp4/tests/marker_file.h
+                 src/bin/dhcp4/tests/test_data_files_config.h
                  src/bin/dhcp4/tests/test_libraries.h
                  src/bin/dhcp6/Makefile
                  src/bin/dhcp6/spec_config.h.pre

+ 6 - 4
src/bin/dhcp4/config_parser.cc

@@ -396,6 +396,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
         parser = new HooksLibrariesParser(config_id);
     } else if (config_id.compare("echo-client-id") == 0) {
         parser = new BooleanParser(config_id, globalContext()->boolean_values_);
+    } else if (config_id.compare("dhcp-ddns") == 0) {
+        parser = new D2ClientConfigParser(config_id);
     } else {
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
@@ -448,7 +450,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     // Some of the parsers alter the state of the system in a way that can't
     // easily be undone. (Or alter it in a way such that undoing the change has
     // the same risk of failure as doing the change.)
-    ParserPtr hooks_parser_;
+    ParserPtr hooks_parser;
 
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
@@ -489,7 +491,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 // Executing commit will alter currently-loaded hooks
                 // libraries.  Check if the supplied libraries are valid,
                 // but defer the commit until everything else has committed.
-                hooks_parser_ = parser;
+                hooks_parser = parser;
                 parser->build(config_pair.second);
             } else {
                 // Those parsers should be started before other
@@ -557,8 +559,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
             // This occurs last as if it succeeds, there is no easy way
             // revert it.  As a result, the failure to commit a subsequent
             // change causes problems when trying to roll back.
-            if (hooks_parser_) {
-                hooks_parser_->commit();
+            if (hooks_parser) {
+                hooks_parser->commit();
             }
         }
         catch (const isc::Exception& ex) {

+ 104 - 1
src/bin/dhcp4/dhcp4.spec

@@ -292,7 +292,110 @@
                   }
                 } ]
          }
-      }
+      },
+
+      { "item_name": "dhcp-ddns",
+        "item_type": "map",
+        "item_optional": false,
+        "item_default": {"enable-updates": false},
+        "map_item_spec": [
+            {
+                "item_name": "enable-updates",
+                "item_type": "boolean",
+                "item_optional": false,
+                "item_default": False,
+                "item_description" : "Enables DDNS update processing"
+            },
+            {
+                "item_name": "server_ip",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "127.0.0.1",
+                "item_description" : "IP address of b10-dhcp-ddns"
+            },
+            {
+                "item_name": "server_port",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 5301,
+                "item_description" : "port number of b10-dhcp-ddns"
+            },
+            {
+                "item_name": "ncr_protocol",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "UDP",
+                "item_description" : "Socket protocol to use with b10-dhcp-ddns"
+            },
+            {
+                "item_name": "ncr_format",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "JSON",
+                "item_description" : "Format of the update request packet"
+            },
+            {
+                "item_name": "remove-on-renew",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Should server request a DNS Remove, before a DNS Update on renewals"
+            },
+            {
+
+                "item_name": "always-include-fqdn",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": False,
+                "item_description": "Should server always include the FQDN option in its response"
+            },
+            {
+
+                "item_name": "allow-client-update",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": False,
+                "item_description": "Enable AAAA RR update delegation to the client"
+            },
+            {
+                "item_name": "override-no-update",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Do update, even if client requested no updates with N flag"
+            },
+            {
+                "item_name": "override-client-update",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": true,
+                "item_description": "Server performs an update even if client requested delegation"
+            },
+            {
+                "item_name": "replace-client-name",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Should server replace the domain-name supplied by the client"
+            },
+            {
+                "item_name": "generated-prefix",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "myhost",
+                "item_description": "Prefix to use when generating the client's name"
+            },
+
+            {
+                "item_name": "qualifying-suffix",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "example.com",
+                "item_description": "Fully qualified domain-name suffix if partial name provided by client"
+            },
+        ]
+      },
+
     ],
     "commands": [
         {

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

@@ -30,6 +30,7 @@
 
 #include "marker_file.h"
 #include "test_libraries.h"
+#include "test_data_files_config.h"
 
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -50,6 +51,22 @@ using namespace std;
 
 namespace {
 
+/// @brief Prepends the given name with the DHCP4 source directory
+///
+/// @param name file name of the desired file
+/// @return string containing the absolute path of the file in the DHCP source
+/// directory.
+std::string specfile(const std::string& name) {
+    return (std::string(DHCP4_SRC_DIR) + "/" + name);
+}
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the BIND10 DHCP-DDNS configuration specification file
+//  is valid.
+TEST(Dhcp4SpecTest, basicSpec) {
+    ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
+}
+
 class Dhcp4ParserTest : public ::testing::Test {
 public:
     Dhcp4ParserTest()
@@ -321,6 +338,7 @@ public:
             "\"renew-timer\": 1000, "
             "\"valid-lifetime\": 4000, "
             "\"subnet4\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
             "\"option-def\": [ ], "
             "\"option-data\": [ ] }";
         static_cast<void>(executeConfiguration(config,
@@ -2299,6 +2317,117 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
     EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
 }
 
+// This test checks the ability of the server to parse a configuration
+// containing a full, valid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp4ParserTest, d2ClientConfig) {
+    ConstElementPtr status;
+
+    // Verify that the D2 configuraiton can be fetched and is set to disabled.
+    D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+    // Verify that the convenience method agrees.
+    ASSERT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
+
+    string config_str = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    // Convert the JSON string to configuration elements.
+    ElementPtr config;
+    ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+    // Pass the configuration in for parsing.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Verify that DHCP-DDNS updating is enabled.
+    EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled());
+
+    // Verify that the D2 configuration can be retrieved.
+    d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    ASSERT_TRUE(d2_client_config);
+
+    // Verify that the configuration values are correct.
+    EXPECT_TRUE(d2_client_config->getEnableUpdates());
+    EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
+    EXPECT_EQ(5301, d2_client_config->getServerPort());
+    EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+    EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+    EXPECT_TRUE(d2_client_config->getRemoveOnRenew());
+    EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
+    EXPECT_TRUE(d2_client_config->getAllowClientUpdate());
+    EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
+    EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
+    EXPECT_TRUE(d2_client_config->getReplaceClientName());
+    EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
+    EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
+}
+
+// This test checks the ability of the server to handle a configuration
+// containing an invalid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
+    ConstElementPtr status;
+
+    // Configuration string with an invalid D2 client config,
+    // "server-ip" is missing.
+    string config_str = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : true, "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    // Convert the JSON string to configuration elements.
+    ElementPtr config;
+    ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+    // Configuration should not throw, but should fail.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
 
+    // check if returned status is failed.
+    checkResult(status, 1);
+
+    // Verify that the D2 configuraiton can be fetched and is set to disabled.
+    D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+    // Verify that the convenience method agrees.
+    ASSERT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
+}
 
 }

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

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

+ 26 - 0
src/lib/dhcp_ddns/ncr_io.cc

@@ -18,6 +18,32 @@
 namespace isc {
 namespace dhcp_ddns {
 
+NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
+    if (protocol_str == "UDP") {
+        return NCR_UDP;
+    }
+
+    if (protocol_str == "TCP") {
+        return NCR_TCP;
+    }
+
+    isc_throw(BadValue, "Invalid NameChangeRequest protocol:" << protocol_str);
+}
+
+std::string ncrProtocolToString(NameChangeProtocol protocol) {
+    switch (protocol) {
+    case NCR_UDP:
+        return ("UDP");
+    case NCR_TCP:
+        return ("TCP");
+    default:
+        break;
+    }
+
+    return ("UNKNOWN");
+}
+
+
 //************************** NameChangeListener ***************************
 
 NameChangeListener::NameChangeListener(RequestReceiveHandler&

+ 25 - 0
src/lib/dhcp_ddns/ncr_io.h

@@ -66,6 +66,31 @@
 namespace isc {
 namespace dhcp_ddns {
 
+/// @brief Defines the list of socket protocols supported.
+enum NameChangeProtocol {
+  NCR_UDP,
+  NCR_TCP
+};
+
+/// @brief Function which converts labels to  NameChangeProtocol enum values.
+///
+/// @param protocol_str text to convert to an enum.
+/// Valid string values: "UDP", "TCP"
+///
+/// @return NameChangeProtocol value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str);
+
+/// @brief Function which converts NameChangeProtocol enums to text labels.
+///
+/// @param protocol enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrProtocolToString(NameChangeProtocol protocol);
+
 /// @brief Exception thrown if an NcrListenerError encounters a general error.
 class NcrListenerError : public isc::Exception {
 public:

+ 16 - 0
src/lib/dhcp_ddns/ncr_msg.cc

@@ -26,6 +26,22 @@
 namespace isc {
 namespace dhcp_ddns {
 
+NameChangeFormat stringToNcrFormat(const std::string& fmt_str) {
+    if (fmt_str == "JSON") {
+        return FMT_JSON;
+    }
+
+    isc_throw(BadValue, "Invalid NameChangeRequest format:" << fmt_str);
+}
+
+
+std::string ncrFormatToString(NameChangeFormat format) {
+    if (format == FMT_JSON) {
+        return ("JSON");
+    }
+
+    return ("UNKNOWN");
+}
 
 /********************************* D2Dhcid ************************************/
 

+ 19 - 0
src/lib/dhcp_ddns/ncr_msg.h

@@ -70,6 +70,25 @@ enum NameChangeFormat {
   FMT_JSON
 };
 
+/// @brief Function which converts labels to  NameChangeFormat enum values.
+///
+/// @param format_str text to convert to an enum.
+/// Valid string values: "JSON"
+///
+/// @return NameChangeFormat value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeFormat stringToNcrFormat(const std::string& fmt_str);
+
+/// @brief Function which converts NameChangeFormat enums to text labels.
+///
+/// @param format enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrFormatToString(NameChangeFormat format);
+
 /// @brief Container class for handling the DHCID value within a
 /// NameChangeRequest. It provides conversion to and from string for JSON
 /// formatting, but stores the data internally as unsigned bytes.

+ 19 - 1
src/lib/dhcp_ddns/tests/ncr_unittests.cc

@@ -12,7 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp_ddns/ncr_io.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <util/time_utilities.h>
@@ -608,5 +608,23 @@ TEST(NameChangeRequestTest, ipAddresses) {
     ASSERT_THROW(ncr.setIpAddress("x001:1::f3"),NcrMessageError);
 }
 
+/// @brief Tests conversion of NameChangeFormat between enum and strings.
+TEST(NameChangeFormatTest, formatEnumConversion){
+    ASSERT_EQ(stringToNcrFormat("JSON"), dhcp_ddns::FMT_JSON);
+    ASSERT_THROW(stringToNcrFormat("bogus"), isc::BadValue);
+
+    ASSERT_EQ(ncrFormatToString(dhcp_ddns::FMT_JSON), "JSON");
+}
+
+/// @brief Tests conversion of NameChangeProtocol between enum and strings.
+TEST(NameChangeProtocolTest, protocolEnumConversion){
+    ASSERT_EQ(stringToNcrProtocol("UDP"), dhcp_ddns::NCR_UDP);
+    ASSERT_EQ(stringToNcrProtocol("TCP"), dhcp_ddns::NCR_TCP);
+    ASSERT_THROW(stringToNcrProtocol("bogus"), isc::BadValue);
+
+    ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_UDP), "UDP");
+    ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_TCP), "TCP");
+}
+
 } // end of anonymous namespace
 

+ 2 - 0
src/lib/dhcpsrv/Makefile.am

@@ -39,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES  =
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
+libb10_dhcpsrv_la_SOURCES += d2_client.cc d2_client.h
 libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
@@ -64,6 +65,7 @@ libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcpsrv_la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/log/libb10-log.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la

+ 18 - 1
src/lib/dhcpsrv/cfgmgr.cc

@@ -348,9 +348,26 @@ CfgMgr::getUnicast(const std::string& iface) const {
     return (&(*addr).second);
 }
 
+void
+CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+    d2_client_mgr_.setD2ClientConfig(new_config);
+}
+
+bool
+CfgMgr::isDhcpDdnsEnabled() {
+    return (d2_client_mgr_.isDhcpDdnsEnabled());
+}
+
+const D2ClientConfigPtr&
+CfgMgr::getD2ClientConfig() const {
+    return (d2_client_mgr_.getD2ClientConfig());
+}
+
+
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR),
-      all_ifaces_active_(false), echo_v4_client_id_(true) {
+      all_ifaces_active_(false), echo_v4_client_id_(true),
+      d2_client_mgr_() {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // See AM_CPPFLAGS definition in Makefile.am

+ 22 - 0
src/lib/dhcpsrv/cfgmgr.h

@@ -19,6 +19,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_space.h>
+#include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
@@ -332,6 +333,24 @@ public:
         return (echo_v4_client_id_);
     }
 
+    /// @brief Updates the DHCP-DDNS client configuration to the given value.
+    ///
+    /// @param new_config pointer to the new client configuration.
+    ///
+    /// @throw Underlying method(s) will throw D2ClientError if given an empty
+    /// pointer.
+    void setD2ClientConfig(D2ClientConfigPtr& new_config);
+
+    /// @param Convenience method for checking if DHCP-DDNS updates are enabled.
+    ///
+    /// @return True if the D2 configuration is enabled.
+    bool isDhcpDdnsEnabled();
+
+    /// @brief Fetches the DHCP-DDNS configuration pointer.
+    ///
+    /// @return a reference to the current configuration pointer.
+    const D2ClientConfigPtr& getD2ClientConfig() const;
+
 protected:
 
     /// @brief Protected constructor.
@@ -411,6 +430,9 @@ private:
 
     /// Indicates whether v4 server should send back client-id
     bool echo_v4_client_id_;
+
+    /// @brief Manages the DHCP-DDNS client and its configuration.
+    D2ClientMgr d2_client_mgr_;
 };
 
 } // namespace isc::dhcp

+ 187 - 0
src/lib/dhcpsrv/d2_client.cc

@@ -0,0 +1,187 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/d2_client.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <string>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+D2ClientConfig::D2ClientConfig(const  bool enable_updates,
+                               const isc::asiolink::IOAddress& server_ip,
+                               const size_t server_port,
+                               const dhcp_ddns::
+                                     NameChangeProtocol& ncr_protocol,
+                               const dhcp_ddns::
+                                     NameChangeFormat& ncr_format,
+                               const bool remove_on_renew,
+                               const bool always_include_fqdn,
+                               const bool allow_client_update,
+                               const bool override_no_update,
+                               const bool override_client_update,
+                               const bool replace_client_name,
+                               const std::string& generated_prefix,
+                               const std::string& qualifying_suffix)
+    : enable_updates_(enable_updates),
+    server_ip_(server_ip.getAddress()),
+    server_port_(server_port),
+    ncr_protocol_(ncr_protocol),
+    ncr_format_(ncr_format),
+    remove_on_renew_(remove_on_renew),
+    always_include_fqdn_(always_include_fqdn),
+    allow_client_update_(allow_client_update),
+    override_no_update_(override_no_update),
+    override_client_update_(override_client_update),
+    replace_client_name_(replace_client_name),
+    generated_prefix_(generated_prefix),
+    qualifying_suffix_(qualifying_suffix) {
+    if (ncr_format_ != dhcp_ddns::FMT_JSON) {
+        isc_throw(D2ClientError, "D2ClientConfig: NCR Format:"
+                    << dhcp_ddns::ncrFormatToString(ncr_format)
+                    << " is not yet supported");
+    }
+
+    if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
+        isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol:"
+                    << dhcp_ddns::ncrProtocolToString(ncr_protocol)
+                    << " is not yet supported");
+    }
+
+    // @todo perhaps more validation we should do yet?
+    // Are there any invalid combinations of options we need to test against?
+    // For instance are allow_client_update and override_client_update mutually
+    // exclusive?
+    // Also do we care about validating contents if it's disabled?
+}
+
+D2ClientConfig::D2ClientConfig()
+    : enable_updates_(false),
+      server_ip_(isc::asiolink::IOAddress("0.0.0.0")),
+      server_port_(0),
+      ncr_protocol_(dhcp_ddns::NCR_UDP),
+      ncr_format_(dhcp_ddns::FMT_JSON),
+      remove_on_renew_(false),
+      always_include_fqdn_(false),
+      allow_client_update_(false),
+      override_no_update_(false),
+      override_client_update_(false),
+      replace_client_name_(false),
+      generated_prefix_(""),
+      qualifying_suffix_("") {
+}
+
+D2ClientConfig::~D2ClientConfig(){};
+
+bool
+D2ClientConfig::operator == (const D2ClientConfig& other) const {
+    return ((enable_updates_ == other.enable_updates_) &&
+            (server_ip_ == other.server_ip_) &&
+            (server_port_ == other.server_port_) &&
+            (ncr_protocol_ == other.ncr_protocol_) &&
+            (ncr_format_ == other.ncr_format_) &&
+            (remove_on_renew_ == other.remove_on_renew_) &&
+            (always_include_fqdn_ == other.always_include_fqdn_) &&
+            (allow_client_update_ == other.allow_client_update_) &&
+            (override_no_update_ == other.override_no_update_) &&
+            (override_client_update_ == other.override_client_update_) &&
+            (replace_client_name_ == other.replace_client_name_) &&
+            (generated_prefix_ == other.generated_prefix_) &&
+            (qualifying_suffix_ == other.qualifying_suffix_));
+}
+
+bool
+D2ClientConfig::operator != (const D2ClientConfig& other) const {
+    return (!(*this == other));
+}
+
+std::string
+D2ClientConfig::toText() const {
+    std::ostringstream stream;
+
+    stream << "enable_updates: " << (enable_updates_ ? "yes" : "no");
+    if (enable_updates_) {
+        stream << ", server_ip: " << server_ip_.toText()
+               << ", server_port: " << server_port_
+               << ", ncr_protocol: " << ncr_protocol_
+               << ", ncr_format: " << ncr_format_
+               << ", remove_on_renew: " << (remove_on_renew_ ? "yes" : "no")
+               << ", always_include_fqdn: " << (always_include_fqdn_ ?
+                                                "yes" : "no")
+               << ", allow_client_update: " << (allow_client_update_ ?
+                                                "yes" : "no")
+               << ", override_no_update: " << (override_no_update_ ?
+                                               "yes" : "no")
+               << ", override_client_update: " << (override_client_update_ ?
+                                                   "yes" : "no")
+               << ", replace_client_name: " << (replace_client_name_ ?
+                                                "yes" : "no")
+               << ", generated_prefix: [" << generated_prefix_ << "]"
+               << ", qualifying_suffix: [" << qualifying_suffix_ << "]";
+    }
+
+    return (stream.str());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config) {
+    os << config.toText();
+    return (os);
+}
+
+D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) {
+    // Default contstructor initializes with a disabled config.
+}
+
+D2ClientMgr::~D2ClientMgr(){
+}
+
+void
+D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+    if (!new_config) {
+        isc_throw(D2ClientError,
+                  "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
+    }
+
+    // @todo When NameChangeSender is integrated, we will need to handle these
+    // scenarios:
+    // 1. D2 was enabled but now it is disabled
+    //     - destroy the sender, flush any queued
+    // 2. D2 is still enabled but server params have changed
+    //     - preserve any queued,  reconnect based on sender params
+    // 3. D2 was was disabled now it is enabled.
+    //     - create sender
+    //
+    // For now we just update the configuration.
+    d2_client_config_ = new_config;
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
+              .arg(!isDhcpDdnsEnabled() ? "DHCP-DDNS updates disabled" :
+                   "DHCP_DDNS updates enabled");
+}
+
+bool
+D2ClientMgr::isDhcpDdnsEnabled() {
+    return (d2_client_config_->getEnableUpdates());
+}
+
+const D2ClientConfigPtr&
+D2ClientMgr::getD2ClientConfig() const {
+    return (d2_client_config_);
+}
+
+};  // namespace dhcp
+};  // namespace isc

+ 280 - 0
src/lib/dhcpsrv/d2_client.h

@@ -0,0 +1,280 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CLIENT_H
+#define D2_CLIENT_H
+
+/// @file d2_client.h Defines the D2ClientConfig and D2ClientMgr classes.
+/// This file defines the classes Kea uses to act as a client of the b10-
+/// dhcp-ddns module (aka D2).
+///
+#include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+
+/// An exception that is thrown if an error occurs while configuring
+/// the D2 DHCP DDNS client.
+class D2ClientError : public isc::Exception {
+public:
+
+    /// @brief constructor
+    ///
+    /// @param file name of the file, where exception occurred
+    /// @param line line of the file, where exception occurred
+    /// @param what text description of the issue that caused exception
+    D2ClientError(const char* file, size_t line, const char* what)
+        : isc::Exception(file, line, what) {}
+};
+
+/// @brief Acts as a storage vault for D2 client configuration
+///
+/// A simple container class for storing and retrieving the configuration
+/// parameters associated with DHCP-DDNS and acting as a client of D2.
+/// Instances of this class may be constructed through configuration parsing.
+///
+class D2ClientConfig {
+public:
+    /// @brief Constructor
+    ///
+    /// @param enable_updates Enables DHCP-DDNS updates
+    /// @param server_ip IP address of the b10-dhcp-ddns server
+    /// @param server_port IP port of the b10-dhcp-ddns server
+    /// @param ncr_protocol Socket protocol to use with b10-dhcp-ddns
+    /// Currently only UDP is supported.
+    /// @param ncr_format Format of the b10-dhcp-ddns requests.
+    /// Currently only JSON format is supported.
+    /// @param remove_on_renew Enables DNS Removes when renewing a lease
+    /// If true, Kea should request an explicit DNS remove prior to requesting
+    /// a DNS update when renewing a lease.
+    /// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove
+    /// is unnecessary).
+    /// @param always_include_fdqn Enables always including the FQDN option in
+    /// DHCP responses.
+    /// @param allow_client_update Enables delegation of updates to clients
+    /// @param override_no_update Enables updates, even if clients request no
+    /// updates.
+    /// @param override_client_update Perform updates, even if client requested
+    /// delegation.
+    /// @param replace_client_name enables replacement of the domain-name
+    /// supplied by the client with a generated name.
+    /// @param generated_prefix Prefix to use when generating domain-names.
+    /// @param  qualifying_suffix Suffix to use to qualify partial domain-names.
+    ///
+    /// @throw D2ClientError if given an invalid protocol or format.
+    D2ClientConfig(const  bool enable_updates_,
+                   const isc::asiolink::IOAddress& server_ip_,
+                   const size_t server_port_,
+                   const dhcp_ddns::NameChangeProtocol& ncr_protocol_,
+                   const dhcp_ddns::NameChangeFormat& ncr_format_,
+                   const bool remove_on_renew_,
+                   const bool always_include_fqdn_,
+                   const bool allow_client_update_,
+                   const bool override_no_update_,
+                   const bool override_client_update_,
+                   const bool replace_client_name_,
+                   const std::string& generated_prefix_,
+                   const std::string& qualifying_suffix_);
+
+    /// @brief Default constructor
+    /// The default constructor creates an instance that has updates disabled.
+    D2ClientConfig();
+
+    /// @brief Destructor
+    virtual ~D2ClientConfig();
+
+    /// @brief Return whether or not DHCP-DDNS updating is enabled.
+    bool getEnableUpdates() const {
+        return(enable_updates_);
+    }
+
+    /// @brief Return the IP address of b10-dhcp-ddns.
+    const isc::asiolink::IOAddress& getServerIp() const {
+        return(server_ip_);
+    }
+
+    /// @brief Return the IP port of b10-dhcp-ddns.
+    size_t getServerPort() const {
+        return(server_port_);
+    }
+
+    /// @brief Return the socket protocol to use with b10-dhcp-ddns.
+    const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
+         return(ncr_protocol_);
+    }
+
+    /// @brief Return the b10-dhcp-ddns request format.
+    const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
+        return(ncr_format_);
+    }
+
+    /// @brief Return whether or not removes should be sent for lease renewals.
+    bool getRemoveOnRenew() const {
+        return(remove_on_renew_);
+    }
+
+    /// @brief Return whether or not FQDN is always included in DHCP responses.
+    bool getAlwaysIncludeFqdn() const {
+        return(always_include_fqdn_);
+    }
+
+    /// @brief Return whether or not updates can be delegated to clients.
+    bool getAllowClientUpdate() const {
+        return(allow_client_update_);
+    }
+
+    /// @brief Return if updates are done even if clients request no updates.
+    bool getOverrideNoUpdate() const {
+        return(override_no_update_);
+    }
+
+    /// @brief Return if updates are done even when clients request delegation.
+    bool getOverrideClientUpdate() const {
+        return(override_client_update_);
+    }
+
+    /// @brief Return whether or not client's domain-name is always replaced.
+    bool getReplaceClientName() const {
+        return(replace_client_name_);
+    }
+
+    /// @brief Return the prefix to use when generating domain-names.
+    const std::string& getGeneratedPrefix() const {
+        return(generated_prefix_);
+    }
+
+    /// @brief Return the suffix to use to qualify partial domain-names.
+    const std::string& getQualifyingSuffix() const {
+        return(qualifying_suffix_);
+    }
+
+    /// @brief Compares two D2ClientConfigs for equality
+    bool operator == (const D2ClientConfig& other) const;
+
+    /// @brief Compares two D2ClientConfigs for inequality
+    bool operator != (const D2ClientConfig& other) const;
+
+    /// @brief Generates a string representation of the class contents.
+    std::string toText() const;
+
+private:
+    /// @brief Indicates whether or not DHCP DDNS updating is enabled.
+    bool enable_updates_;
+
+    /// @brief IP address of the b10-dhcp-ddns server.
+    isc::asiolink::IOAddress server_ip_;
+
+    /// @brief IP port of the b10-dhcp-ddns server.
+    size_t server_port_;
+
+    /// @brief The socket protocol to use with b10-dhcp-ddns.
+    /// Currently only UPD is supported.
+    dhcp_ddns::NameChangeProtocol ncr_protocol_;
+
+    /// @brief Format of the b10-dhcp-ddns requests.
+    /// Currently only JSON format is supported.
+    dhcp_ddns::NameChangeFormat ncr_format_;
+
+    /// @brief Should Kea request a DNS Remove when renewing a lease.
+    /// If true, Kea should request an explicit DNS remove prior to requesting
+    /// a DNS update when renewing a lease.
+    /// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove
+    /// is unnecessary).
+    bool remove_on_renew_;
+
+    /// @brief Should Kea always include the FQDN option in its response.
+    bool always_include_fqdn_;
+
+    /// @brief Should Kea permit the client to do updates.
+    bool allow_client_update_;
+
+    /// @brief Should Kea perform updates, even if client requested no updates.
+    /// Overrides the client request for no updates via the N flag.
+    bool override_no_update_;
+
+    /// @brief Should Kea perform updates, even if client requested delegation.
+    bool override_client_update_;
+
+    /// @brief Should Kea replace the domain-name supplied by the client.
+    bool replace_client_name_;
+
+    /// @brief Prefix Kea should use when generating domain-names.
+    std::string generated_prefix_;
+
+    /// @brief Suffix Kea should use when to qualify partial domain-names.
+    std::string qualifying_suffix_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config);
+
+/// @brief Defines a pointer for D2ClientConfig instances.
+typedef boost::shared_ptr<D2ClientConfig> D2ClientConfigPtr;
+
+/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
+///
+/// Provides services for managing the current D2ClientConfig and managing
+/// communications with D2. (@todo The latter will be added once communication
+/// with D2 is implemented through the integration of
+/// dhcp_ddns::NameChangeSender interface(s).
+///
+class D2ClientMgr {
+public:
+    /// @brief Constructor
+    ///
+    /// Default constructor which constructs an instance which has DHCP-DDNS
+    /// updates disabled.
+    D2ClientMgr();
+
+    /// @brief Destructor.
+    ~D2ClientMgr();
+
+    /// @brief Updates the DHCP-DDNS client configuration to the given value.
+    ///
+    /// @param new_config pointer to the new client configuration.
+    /// @throw D2ClientError if passed an empty pointer.
+    void setD2ClientConfig(D2ClientConfigPtr& new_config);
+
+    /// @param Convenience method for checking if DHCP-DDNS updates are enabled.
+    ///
+    /// @return True if the D2 configuration is enabled.
+    bool isDhcpDdnsEnabled();
+
+    /// @brief Fetches the DHCP-DDNS configuration pointer.
+    ///
+    /// @return a reference to the current configuration pointer.
+    const D2ClientConfigPtr& getD2ClientConfig() const;
+
+private:
+    /// @brief Container class for DHCP-DDNS configuration parameters.
+    D2ClientConfigPtr d2_client_config_;
+};
+
+/// @brief Defines a pointer for D2ClientMgr instances.
+typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
+
+
+} // namespace isc
+} // namespace dhcp
+
+#endif

+ 106 - 0
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -1163,5 +1163,111 @@ SubnetConfigParser::getParam(const std::string& name) {
     return (Triplet<uint32_t>(value));
 }
 
+//**************************** D2ClientConfigParser **********************
+D2ClientConfigParser::D2ClientConfigParser(const std::string& entry_name)
+    : entry_name_(entry_name), boolean_values_(new BooleanStorage()),
+      uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
+      local_client_config_() {
+}
+
+D2ClientConfigParser::~D2ClientConfigParser() {
+}
+
+void
+D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
+    BOOST_FOREACH(ConfigPair param, client_config->mapValue()) {
+        ParserPtr parser(createConfigParser(param.first));
+        parser->build(param.second);
+        parser->commit();
+    }
+
+    bool enable_updates = boolean_values_->getParam("enable-updates");
+    if (!enable_updates) {
+        // If it's not enabled, don't bother validating the rest.  This
+        // allows for an abbreviated config entry that only contains
+        // the flag.  The default constructor creates a disabled instance.
+        local_client_config_.reset(new D2ClientConfig());
+        return;
+    }
+
+    // Get all parameters that are needed to create the D2ClientConfig.
+    asiolink::IOAddress server_ip(string_values_->getParam("server-ip"));
+
+    uint32_t server_port = uint32_values_->getParam("server-port");
+
+    dhcp_ddns::NameChangeProtocol
+    ncr_protocol = dhcp_ddns:: stringToNcrProtocol(string_values_->
+                                                   getParam("ncr-protocol"));
+
+    dhcp_ddns::NameChangeFormat
+    ncr_format = dhcp_ddns::stringToNcrFormat(string_values_->
+                                              getParam("ncr-format"));
+
+    std::string generated_prefix = string_values_->getParam("generated-prefix");
+    std::string qualifying_suffix = string_values_->
+                                    getParam("qualifying-suffix");
+
+    bool remove_on_renew = boolean_values_->getParam("remove-on-renew");
+    bool always_include_fqdn = boolean_values_->getParam("always-include-fqdn");
+    bool allow_client_update = boolean_values_->getParam("allow-client-update");
+    bool override_no_update = boolean_values_->getParam("override-no-update");
+    bool override_client_update = boolean_values_->
+                                  getParam("override-client-update");
+    bool replace_client_name = boolean_values_->getParam("replace-client-name");
+
+    // Attempt to create the new client config.
+    local_client_config_.reset(new D2ClientConfig(enable_updates, server_ip,
+                                                  server_port, ncr_protocol,
+                                                  ncr_format, remove_on_renew,
+                                                  always_include_fqdn,
+                                                  allow_client_update,
+                                                  override_no_update,
+                                                  override_client_update,
+                                                  replace_client_name,
+                                                  generated_prefix,
+                                                  qualifying_suffix));
+}
+
+isc::dhcp::ParserPtr
+D2ClientConfigParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    if (config_id.compare("server-port") == 0) {
+        parser = new Uint32Parser(config_id, uint32_values_);
+    } else if ((config_id.compare("server-ip") == 0) ||
+        (config_id.compare("ncr-protocol") == 0) ||
+        (config_id.compare("ncr-format") == 0) ||
+        (config_id.compare("generated-prefix") == 0) ||
+        (config_id.compare("qualifying-suffix") == 0)) {
+        parser = new StringParser(config_id, string_values_);
+    } else if ((config_id.compare("enable-updates") == 0) ||
+        (config_id.compare("remove-on-renew") == 0) ||
+        (config_id.compare("always-include-fqdn") == 0) ||
+        (config_id.compare("allow-client-update") == 0) ||
+        (config_id.compare("override-no-update") == 0) ||
+        (config_id.compare("override-client-update") == 0) ||
+        (config_id.compare("replace-client-name") == 0)) {
+        parser = new BooleanParser(config_id, boolean_values_);
+    } else {
+        isc_throw(NotImplemented,
+            "parser error: D2ClientConfig parameter not supported: "
+            << config_id);
+    }
+
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+D2ClientConfigParser::commit() {
+    // @todo if local_client_config_ is empty then shutdown the listener...
+    // @todo Should this also attempt to start a listener?
+    // In keeping with Interface, Subnet, and Hooks parsers, then this
+    // should initialize the listener.  Failure to init it, should cause
+    // rollback.  This gets sticky, because who owns the listener instance?
+    // Does CfgMgr maintain it or does the server class?  If the latter
+    // how do we get that value here?
+    // I'm thinkikng D2ClientConfig could contain the listener instance
+    CfgMgr::instance().setD2ClientConfig(local_client_config_);
+}
+
 };  // namespace dhcp
 };  // namespace isc

+ 73 - 1
src/lib/dhcpsrv/dhcp_parsers.h

@@ -18,6 +18,7 @@
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <dhcp/option_definition.h>
+#include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
@@ -243,7 +244,7 @@ public:
         // its value. If it doesn't we insert a new element.
         storage_->setParam(param_name_, value_);
     }
- 
+
 private:
     /// Pointer to the storage where committed value is stored.
     boost::shared_ptr<ValueStorage<ValueType> > storage_;
@@ -876,6 +877,77 @@ protected:
     ParserContextPtr global_context_;
 };
 
+/// @brief Parser for  D2ClientConfig
+///
+/// This class parses the configuration element "dhcp-ddns" common to the
+/// spec files for both dhcp4 and dhcp6. It creates an instance of a
+/// D2ClientConfig.
+class D2ClientConfigParser : public  isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition.
+    D2ClientConfigParser(const std::string& entry_name);
+
+    /// @brief Destructor
+    virtual ~D2ClientConfigParser();
+
+    /// @brief Performs the parsing of the given dhcp-ddns element.
+    ///
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param client_config is the "dhcp-ddns" configuration to parse
+    virtual void build(isc::data::ConstElementPtr client_config);
+
+    /// @brief Creates a parser for the given "dhcp-ddns" member element id.
+    ///
+    /// The elements currently supported are:
+    /// -# enable-updates
+    /// -# server-ip
+    /// -# server-port
+    /// -# ncr-protocol
+    /// -# ncr-format
+    /// -# remove-on-renew
+    /// -# always-include-fqdn
+    /// -# allow-client-update
+    /// -# override-no-update
+    /// -# override-client-update
+    /// -# replace-client-name
+    /// -# generated-prefix
+    /// -# qualifying-suffix
+    /// (see d2::D2ClientConfig for details on each.)
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "dns_server" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Instantiates a D2ClientConfig from internal data values
+    /// passes to CfgMgr singleton.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    /// Primarily used for diagnostics.
+    std::string entry_name_;
+
+    /// Storage for subnet-specific boolean values.
+    BooleanStoragePtr boolean_values_;
+
+    /// Storage for subnet-specific integer values.
+    Uint32StoragePtr uint32_values_;
+
+    /// Storage for subnet-specific string values.
+    StringStoragePtr string_values_;
+
+    /// @brief Pointer to temporary local instance created during build.
+    D2ClientConfigPtr local_client_config_ ;
+};
+
 // Pointers to various parser objects.
 typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
 typedef boost::shared_ptr<StringParser> StringParserPtr;

+ 3 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -70,6 +70,9 @@ specified IPv6 subnet to its database.
 A debug message issued when server is being configured to listen on all
 interfaces.
 
+% DHCPSRV_CFGMGR_CFG_DHCP_DDNS Setting DHCP-DDNS configuration to: %1
+A debug message issued when the server's DHCP-DDNS settings are changed.
+
 % DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
 A debug message issued when configuration manager clears the internal list
 of active interfaces. This doesn't prevent the server from listening to

+ 2 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -52,6 +52,7 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
@@ -88,6 +89,7 @@ endif
 
 libdhcpsrv_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la

+ 42 - 0
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -670,6 +670,48 @@ TEST_F(CfgMgrTest, echoClientId) {
     EXPECT_TRUE(cfg_mgr.echoClientId());
 }
 
+// This test checks the D2ClientMgr wrapper methods.
+TEST_F(CfgMgrTest, d2ClientConfig) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // After CfgMgr construction, D2 configuration should be disabled.
+    // Fetch it and verify this is the case.
+    D2ClientConfigPtr original_config = CfgMgr::instance().getD2ClientConfig();
+    ASSERT_TRUE(original_config);
+    EXPECT_FALSE(original_config->getEnableUpdates());
+
+    // Make sure convenience method agrees.
+    EXPECT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
+
+    // Verify that we cannot set the configuration to an empty pointer.
+    D2ClientConfigPtr new_cfg;
+    ASSERT_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg), D2ClientError);
+
+    // Create a new, enabled configuration.
+    ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  true, true, true, true, true, true,
+                                  "pre-fix", "suf-fix")));
+
+    // Verify that we can assign a new, non-empty configuration.
+    ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg));
+
+    // Verify that we can fetch the newly assigned configuration.
+    D2ClientConfigPtr updated_config = CfgMgr::instance().getD2ClientConfig();
+    ASSERT_TRUE(updated_config);
+    EXPECT_TRUE(updated_config->getEnableUpdates());
+
+    // Make sure convenience method agrees with updated configuration.
+    EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled());
+
+    // Make sure the configuration we fetched is the one we assigned,
+    // and not the original configuration.
+    EXPECT_EQ(*new_cfg, *updated_config);
+    EXPECT_NE(*original_config, *updated_config);
+}
+
+
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name
 /// - addActiveIface() with the same interface twice

+ 308 - 0
src/lib/dhcpsrv/tests/d2_client_unittest.cc

@@ -0,0 +1,308 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcpsrv/d2_client.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc;
+
+namespace {
+
+// brief Checks constructors and accessors of D2ClientConfig.
+TEST(D2ClientConfigTest, constructorsAndAccessors) {
+    D2ClientConfigPtr d2_client_config;
+
+    // Verify default constructor creates a disabled instance.
+    ASSERT_NO_THROW(d2_client_config.reset(new D2ClientConfig()));
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+    d2_client_config.reset();
+
+    bool enable_updates = true;
+    isc::asiolink::IOAddress server_ip("127.0.0.1");
+    size_t server_port = 477;
+    dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
+    dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
+    bool remove_on_renew = true;
+    bool always_include_fqdn = true;
+    bool allow_client_update = true;
+    bool override_no_update = true;
+    bool override_client_update = true;
+    bool replace_client_name = true;
+    std::string generated_prefix = "the_prefix";
+    std::string qualifying_suffix = "the.suffix.";
+
+    // Verify that we can construct a valid, enabled instance.
+    ASSERT_NO_THROW(d2_client_config.reset(new
+                                           D2ClientConfig(enable_updates,
+                                                          server_ip,
+                                                          server_port,
+                                                          ncr_protocol,
+                                                          ncr_format,
+                                                          remove_on_renew,
+                                                          always_include_fqdn,
+                                                          allow_client_update,
+                                                          override_no_update,
+                                                         override_client_update,
+                                                          replace_client_name,
+                                                          generated_prefix,
+                                                          qualifying_suffix)));
+
+    ASSERT_TRUE(d2_client_config);
+
+    // Verify that the accessors return the expected values.
+    EXPECT_EQ(d2_client_config->getEnableUpdates(), enable_updates);
+
+    EXPECT_EQ(d2_client_config->getServerIp(), server_ip);
+    EXPECT_EQ(d2_client_config->getServerPort(), server_port);
+    EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol);
+    EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format);
+    EXPECT_EQ(d2_client_config->getRemoveOnRenew(), remove_on_renew);
+    EXPECT_EQ(d2_client_config->getAlwaysIncludeFqdn(), always_include_fqdn);
+    EXPECT_EQ(d2_client_config->getAllowClientUpdate(), allow_client_update);
+    EXPECT_EQ(d2_client_config->getOverrideNoUpdate(), override_no_update);
+    EXPECT_EQ(d2_client_config->getOverrideClientUpdate(),
+              override_client_update);
+    EXPECT_EQ(d2_client_config->getReplaceClientName(), replace_client_name);
+    EXPECT_EQ(d2_client_config->getGeneratedPrefix(), generated_prefix);
+    EXPECT_EQ(d2_client_config->getQualifyingSuffix(), qualifying_suffix);
+
+    // Verify that toText called by << operator doesn't bomb.
+    ASSERT_NO_THROW(std::cout << "toText test:" << std::endl <<
+                    *d2_client_config << std::endl);
+
+    // Verify that constructor does not allow use of NCR_TCP.
+    // @todo obviously this becomes invalid once TCP is supported.
+    ASSERT_THROW(d2_client_config.reset(new
+                                        D2ClientConfig(enable_updates,
+                                                       server_ip,
+                                                       server_port,
+                                                       dhcp_ddns::NCR_TCP,
+                                                       ncr_format,
+                                                       remove_on_renew,
+                                                       always_include_fqdn,
+                                                       allow_client_update,
+                                                       override_no_update,
+                                                       override_client_update,
+                                                       replace_client_name,
+                                                       generated_prefix,
+                                                       qualifying_suffix)),
+                 D2ClientError);
+
+    // @todo if additional validation is added to ctor, this test needs to
+    // expand accordingly.
+}
+
+// Tests the equality and inequality operators of D2ClientConfig.
+TEST(D2ClientConfigTest, equalityOperator) {
+    D2ClientConfigPtr ref_config;
+    D2ClientConfigPtr test_config;
+
+    isc::asiolink::IOAddress ref_address("127.0.0.1");
+    isc::asiolink::IOAddress test_address("127.0.0.2");
+
+    // Create an instance to use as a reference.
+    ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(ref_config);
+
+    // Check a configuration that is identical to reference configuration.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_TRUE(*ref_config == *test_config);
+    EXPECT_FALSE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by enable flag.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by server ip.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    test_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by server port.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 333,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by remove_on_renew.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    false, true, true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by always_include_fqdn.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, false, true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by allow_client_update.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, false, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by override_no_update.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, false, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by override_client_update.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, false, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by replace_client_name.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, false,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by generated_prefix.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, true,
+                    "bogus", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by qualifying_suffix.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true, true, true,
+                    "pre-fix", "bogus")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+}
+
+// This test checks the D2ClientMgr constructor.
+TEST(D2ClientMgr, constructor) {
+    D2ClientMgrPtr d2_client_mgr;
+
+    // Verify we can construct with the default constructor.
+    ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
+
+    // After construction, D2 configuration should be disabled.
+    // Fetch it and verify this is the case.
+    D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
+    ASSERT_TRUE(original_config);
+    EXPECT_FALSE(original_config->getEnableUpdates());
+
+    // Make sure convenience method agrees.
+    EXPECT_FALSE(d2_client_mgr->isDhcpDdnsEnabled());
+}
+
+// This test checks passing the D2ClientMgr a valid D2 client configuration.
+// @todo Once NameChangeSender is integrated, this test needs to expand, and
+// additional scenario tests will need to be written.
+TEST(D2ClientMgr, validConfig) {
+    D2ClientMgrPtr d2_client_mgr;
+
+    // Construct the manager and fetch its initial configuration.
+    ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
+    D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
+    ASSERT_TRUE(original_config);
+
+    // Verify that we cannot set the config to an empty pointer.
+    D2ClientConfigPtr new_cfg;
+    ASSERT_THROW(d2_client_mgr->setD2ClientConfig(new_cfg), D2ClientError);
+
+    // Create a new, enabled config.
+    ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  true, true, true, true, true, true,
+                                  "pre-fix", "suf-fix")));
+
+    // Verify that we can assign a new, non-empty configuration.
+    ASSERT_NO_THROW(d2_client_mgr->setD2ClientConfig(new_cfg));
+
+    // Verify that we can fetch the newly assigned configuration.
+    D2ClientConfigPtr updated_config = d2_client_mgr->getD2ClientConfig();
+    ASSERT_TRUE(updated_config);
+    EXPECT_TRUE(updated_config->getEnableUpdates());
+
+    // Make sure convenience method agrees with the updated configuration.
+    EXPECT_TRUE(d2_client_mgr->isDhcpDdnsEnabled());
+
+    // Make sure the configuration we fetched is the one  we assigned,
+    // and not the original configuration.
+    EXPECT_EQ(*new_cfg, *updated_config);
+    EXPECT_NE(*original_config, *updated_config);
+}
+
+
+} // end of anonymous namespace

+ 233 - 8
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -367,8 +367,8 @@ public:
     ///
     /// Note that the method currently it only supports option-defs, option-data
     /// and hooks-libraries.
-    /// 
-    /// @param config_id is the name of the configuration element. 
+    ///
+    /// @param config_id is the name of the configuration element.
     ///
     /// @return returns a shared pointer to DhcpConfigParser.
     ///
@@ -376,20 +376,21 @@ public:
     ParserPtr createConfigParser(const std::string& config_id) {
         ParserPtr parser;
         if (config_id.compare("option-data") == 0) {
-            parser.reset(new OptionDataListParser(config_id, 
-                                              parser_context_->options_, 
+            parser.reset(new OptionDataListParser(config_id,
+                                              parser_context_->options_,
                                               parser_context_,
                                               UtestOptionDataParser::factory));
 
         } else if (config_id.compare("option-def") == 0) {
-            parser.reset(new OptionDefListParser(config_id, 
+            parser.reset(new OptionDefListParser(config_id,
                                               parser_context_->option_defs_));
 
         } else if (config_id.compare("hooks-libraries") == 0) {
             parser.reset(new HooksLibrariesParser(config_id));
             hooks_libraries_parser_ =
                 boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
-
+        } else if (config_id.compare("dhcp-ddns") == 0) {
+            parser.reset(new D2ClientConfigParser(config_id));
         } else {
             isc_throw(NotImplemented,
                 "Parser error: configuration parameter not supported: "
@@ -399,8 +400,8 @@ public:
         return (parser);
     }
 
-    /// @brief Convenience method for parsing a configuration 
-    /// 
+    /// @brief Convenience method for parsing a configuration
+    ///
     /// Given a configuration string, convert it into Elements
     /// and parse them.
     /// @param config is the configuration string to parse
@@ -491,6 +492,10 @@ public:
 
         // Ensure no hooks libraries are loaded.
         HooksManager::unloadLibraries();
+
+        // Set it to minimal, disabled config
+        D2ClientConfigPtr tmp(new D2ClientConfig());
+        CfgMgr::instance().setD2ClientConfig(tmp);
     }
 
     /// @brief Parsers used in the parsing of the configuration
@@ -703,6 +708,226 @@ TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
         "Error text returned from parse failure is " << error_text_;
 }
 
+/// @brief Checks that a valid, enabled D2 client configuration works correctly.
+TEST_F(ParseConfigTest, validD2Config) {
+
+    // Configuration string.  This contains a set of valid libraries.
+    std::string config_str =
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config_str);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+
+    // Verify that DHCP-DDNS is enabled and we can fetch the configuration.
+    EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled());
+    D2ClientConfigPtr d2_client_config;
+    ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+    ASSERT_TRUE(d2_client_config);
+
+    // Verify that the configuration values are as expected.
+    EXPECT_TRUE(d2_client_config->getEnableUpdates());
+    EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
+    EXPECT_EQ(5301, d2_client_config->getServerPort());
+    EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+    EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+    EXPECT_TRUE(d2_client_config->getRemoveOnRenew());
+    EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
+    EXPECT_TRUE(d2_client_config->getAllowClientUpdate());
+    EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
+    EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
+    EXPECT_TRUE(d2_client_config->getReplaceClientName());
+    EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
+    EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
+}
+
+/// @brief Checks that D2 client can be configured with enable flag of
+/// false only.
+TEST_F(ParseConfigTest, validDisabledD2Config) {
+
+    // Configuration string.  This contains a set of valid libraries.
+    std::string config_str =
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : false"
+        "    }"
+        "}";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config_str);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+
+    // Verify that DHCP-DDNS is disabled.
+    EXPECT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
+
+    // Make sure fetched config agrees.
+    D2ClientConfigPtr d2_client_config;
+    ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+    EXPECT_TRUE(d2_client_config);
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+}
+
+/// @brief Check various invalid D2 client configurations.
+TEST_F(ParseConfigTest, invalidD2Config) {
+    std::string invalid_configs[] = {
+        // only the enable flag of true
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true"
+        "    }"
+        "}",
+        // Missing server ip value
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        //"     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
+        // Invalid server ip value
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"x192.168.2.1\", "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
+        // Unknown protocol
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"Bogus\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
+        // Unsupported protocol
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"TCP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
+        // Unknown format
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"Bogus\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
+        // Missig Port
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        // "     \"server-port\" : 5301, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
+        // stop
+        ""
+    };
+
+    // Fetch the original config.
+    D2ClientConfigPtr original_config;
+    ASSERT_NO_THROW(original_config = CfgMgr::instance().getD2ClientConfig());
+
+    // Iterate through the invalid configuration strings, attempting to
+    // parse each one.  They should fail to parse, but fail gracefully.
+    D2ClientConfigPtr current_config;
+    int i = 0;
+    while (!invalid_configs[i].empty()) {
+        // Verify that the configuration string parses without throwing.
+        int rcode = parseConfiguration(invalid_configs[i]);
+
+        // Verify that parse result indicates a parsing error.
+        ASSERT_TRUE(rcode != 0) << "Invalid config #: " << i
+                                << " should not have passed!";
+
+        // Verify that the "official" config still matches the original config.
+        ASSERT_NO_THROW(current_config =
+                        CfgMgr::instance().getD2ClientConfig());
+        EXPECT_EQ(*original_config, *current_config);
+        ++i;
+    }
+}
+
 /// @brief DHCP Configuration Parser Context test fixture.
 class ParserContextTest : public ::testing::Test {
 public: