BIND 10 trac3033, updated. d508d4ac19b7260805fa0838ea627f2f05b960c5 [3033] Added DHCP-DDNS configuration paramater parsing to b10-dhcp4

BIND 10 source code commits bind10-changes at lists.isc.org
Mon Jan 6 16:03:49 UTC 2014


The branch, trac3033 has been updated
       via  d508d4ac19b7260805fa0838ea627f2f05b960c5 (commit)
      from  be25b97a4ca9f145303e1028bfb59cff19ff39fc (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit d508d4ac19b7260805fa0838ea627f2f05b960c5
Author: Thomas Markwalder <tmark at isc.org>
Date:   Mon Jan 6 10:56:59 2014 -0500

    [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.

-----------------------------------------------------------------------

Summary of changes:
 configure.ac                                       |    1 +
 src/bin/dhcp4/config_parser.cc                     |   10 +-
 src/bin/dhcp4/dhcp4.spec                           |  105 ++++++-
 src/bin/dhcp4/tests/config_parser_unittest.cc      |  129 ++++++++
 .../dhcp4}/tests/test_data_files_config.h.in       |    7 +-
 src/lib/dhcp_ddns/ncr_io.cc                        |   26 ++
 src/lib/dhcp_ddns/ncr_io.h                         |   25 ++
 src/lib/dhcp_ddns/ncr_msg.cc                       |   16 +
 src/lib/dhcp_ddns/ncr_msg.h                        |   19 ++
 src/lib/dhcp_ddns/tests/ncr_unittests.cc           |   20 +-
 src/lib/dhcpsrv/Makefile.am                        |    2 +
 src/lib/dhcpsrv/cfgmgr.cc                          |   19 +-
 src/lib/dhcpsrv/cfgmgr.h                           |   22 ++
 src/lib/dhcpsrv/d2_client.cc                       |  187 ++++++++++++
 src/lib/dhcpsrv/d2_client.h                        |  280 ++++++++++++++++++
 src/lib/dhcpsrv/dhcp_parsers.cc                    |  106 +++++++
 src/lib/dhcpsrv/dhcp_parsers.h                     |   74 ++++-
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |    3 +
 src/lib/dhcpsrv/tests/Makefile.am                  |    2 +
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |   42 +++
 src/lib/dhcpsrv/tests/d2_client_unittest.cc        |  308 ++++++++++++++++++++
 src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc     |  241 ++++++++++++++-
 22 files changed, 1625 insertions(+), 19 deletions(-)
 copy src/{hooks/dhcp/user_chk => bin/dhcp4}/tests/test_data_files_config.h.in (83%)
 create mode 100644 src/lib/dhcpsrv/d2_client.cc
 create mode 100644 src/lib/dhcpsrv/d2_client.h
 create mode 100644 src/lib/dhcpsrv/tests/d2_client_unittest.cc

-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index f508400..0b32b1c 100644
--- a/configure.ac
+++ b/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
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 777cce5..929942d 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/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) {
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 9372dfd..cb4c912 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/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": [
         {
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 1d9ccb7..a789b39 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/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());
+}
 
 }
diff --git a/src/bin/dhcp4/tests/test_data_files_config.h.in b/src/bin/dhcp4/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..0b778cf
--- /dev/null
+++ b/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"
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
index 4f3f1d7..eee948e 100644
--- a/src/lib/dhcp_ddns/ncr_io.cc
+++ b/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&
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
index 94d08f7..50d1c8c 100644
--- a/src/lib/dhcp_ddns/ncr_io.h
+++ b/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:
diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc
index 418eaa0..91c2608 100644
--- a/src/lib/dhcp_ddns/ncr_msg.cc
+++ b/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 ************************************/
 
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
index 47a7be6..5dab9e8 100644
--- a/src/lib/dhcp_ddns/ncr_msg.h
+++ b/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.
diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
index 9386a5f..e71ddda 100644
--- a/src/lib/dhcp_ddns/tests/ncr_unittests.cc
+++ b/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
 
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index b3c4206..69b14b2 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/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
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 7789c74..b5bcb66 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/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
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index cda59f2..07d4b9c 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/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
diff --git a/src/lib/dhcpsrv/d2_client.cc b/src/lib/dhcpsrv/d2_client.cc
new file mode 100644
index 0000000..47b6b40
--- /dev/null
+++ b/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
diff --git a/src/lib/dhcpsrv/d2_client.h b/src/lib/dhcpsrv/d2_client.h
new file mode 100644
index 0000000..792c3f9
--- /dev/null
+++ b/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
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index ffd49d3..7a9aee7 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/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
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
index 28c57b8..3e28252 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.h
+++ b/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;
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 6ee3e87..5c7854e 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/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
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 643fd63..ff57254 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/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
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 08ce768..2ccd3a5 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/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
diff --git a/src/lib/dhcpsrv/tests/d2_client_unittest.cc b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
new file mode 100644
index 0000000..6b32755
--- /dev/null
+++ b/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
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
index 928be23..c539678 100644
--- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
+++ b/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:



More information about the bind10-changes mailing list