Parcourir la source

[master] Merge branch 'trac3033'

Adds support for DHCP-DDNS configuration parameters to b10-dhcp4.
Thomas Markwalder il y a 11 ans
Parent
commit
0ba8598345

+ 1 - 0
configure.ac

@@ -1389,6 +1389,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/bin/dhcp4/spec_config.h.pre
                  src/bin/dhcp4/spec_config.h.pre
                  src/bin/dhcp4/tests/Makefile
                  src/bin/dhcp4/tests/Makefile
                  src/bin/dhcp4/tests/marker_file.h
                  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/dhcp4/tests/test_libraries.h
                  src/bin/dhcp6/Makefile
                  src/bin/dhcp6/Makefile
                  src/bin/dhcp6/spec_config.h.pre
                  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);
         parser = new HooksLibrariesParser(config_id);
     } else if (config_id.compare("echo-client-id") == 0) {
     } else if (config_id.compare("echo-client-id") == 0) {
         parser = new BooleanParser(config_id, globalContext()->boolean_values_);
         parser = new BooleanParser(config_id, globalContext()->boolean_values_);
+    } else if (config_id.compare("dhcp-ddns") == 0) {
+        parser = new D2ClientConfigParser(config_id);
     } else {
     } else {
         isc_throw(NotImplemented,
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
                 "Parser error: Global configuration parameter not supported: "
@@ -452,7 +454,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     // Some of the parsers alter the state of the system in a way that can't
     // 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
     // easily be undone. (Or alter it in a way such that undoing the change has
     // the same risk of failure as doing the change.)
     // the same risk of failure as doing the change.)
-    ParserPtr hooks_parser_;
+    ParserPtr hooks_parser;
 
 
     // The subnet parsers implement data inheritance by directly
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
     // accessing global storage. For this reason the global data
@@ -493,7 +495,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 // Executing commit will alter currently-loaded hooks
                 // Executing commit will alter currently-loaded hooks
                 // libraries.  Check if the supplied libraries are valid,
                 // libraries.  Check if the supplied libraries are valid,
                 // but defer the commit until everything else has committed.
                 // but defer the commit until everything else has committed.
-                hooks_parser_ = parser;
+                hooks_parser = parser;
                 parser->build(config_pair.second);
                 parser->build(config_pair.second);
             } else {
             } else {
                 // Those parsers should be started before other
                 // Those parsers should be started before other
@@ -561,8 +563,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
             // This occurs last as if it succeeds, there is no easy way
             // This occurs last as if it succeeds, there is no easy way
             // revert it.  As a result, the failure to commit a subsequent
             // revert it.  As a result, the failure to commit a subsequent
             // change causes problems when trying to roll back.
             // 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) {
         catch (const isc::Exception& ex) {

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

@@ -292,7 +292,103 @@
                   }
                   }
                 } ]
                 } ]
          }
          }
-      }
+      },
+
+      { "item_name": "dhcp-ddns",
+        "item_type": "map",
+        "item_optional": false,
+        "item_default": {"enable-updates": false},
+        "item_description" : "Contains parameters pertaining DHCP-driven DDNS updates",
+        "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 (IPv4 or IPv6)"
+            },
+            {
+                "item_name": "server-port",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 53001,
+                "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": "Enable requesting 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": "Enable always including the FQDN option in its response"
+            },
+            {
+                "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": false,
+                "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": [
     "commands": [
         {
         {

+ 130 - 1
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -30,6 +30,7 @@
 
 
 #include "marker_file.h"
 #include "marker_file.h"
 #include "test_libraries.h"
 #include "test_libraries.h"
+#include "test_data_files_config.h"
 
 
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -50,6 +51,23 @@ using namespace std;
 
 
 namespace {
 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) {
+    (isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
+    ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
+}
+
 class Dhcp4ParserTest : public ::testing::Test {
 class Dhcp4ParserTest : public ::testing::Test {
 public:
 public:
     Dhcp4ParserTest()
     Dhcp4ParserTest()
@@ -321,6 +339,7 @@ public:
             "\"renew-timer\": 1000, "
             "\"renew-timer\": 1000, "
             "\"valid-lifetime\": 4000, "
             "\"valid-lifetime\": 4000, "
             "\"subnet4\": [ ], "
             "\"subnet4\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
             "\"option-def\": [ ], "
             "\"option-def\": [ ], "
             "\"option-data\": [ ] }";
             "\"option-data\": [ ] }";
         static_cast<void>(executeConfiguration(config,
         static_cast<void>(executeConfiguration(config,
@@ -2486,6 +2505,116 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
     EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
     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().ddnsEnabled());
+
+    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\" : 777, "
+        "     \"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().ddnsEnabled());
+
+    // 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(777, 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->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().ddnsEnabled());
+}
 
 
 }
 }

+ 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"

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

@@ -15,9 +15,39 @@
 #include <dhcp_ddns/dhcp_ddns_log.h>
 #include <dhcp_ddns/dhcp_ddns_log.h>
 #include <dhcp_ddns/ncr_io.h>
 #include <dhcp_ddns/ncr_io.h>
 
 
+#include <boost/algorithm/string/predicate.hpp>
+
 namespace isc {
 namespace isc {
 namespace dhcp_ddns {
 namespace dhcp_ddns {
 
 
+NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
+    if (boost::iequals(protocol_str, "UDP")) {
+        return (NCR_UDP);
+    } 
+
+    if (boost::iequals(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;
+    }
+
+    std::ostringstream stream;
+    stream  << "UNKNOWN(" << protocol << ")";
+    return (stream.str());
+}
+
+
 //************************** NameChangeListener ***************************
 //************************** NameChangeListener ***************************
 
 
 NameChangeListener::NameChangeListener(RequestReceiveHandler&
 NameChangeListener::NameChangeListener(RequestReceiveHandler&

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

@@ -66,6 +66,35 @@
 namespace isc {
 namespace isc {
 namespace dhcp_ddns {
 namespace dhcp_ddns {
 
 
+/// @brief Defines the list of socket protocols supported.
+/// Currently only UDP is implemented.
+/// @todo TCP is intended to be implemented prior 1.0 release.
+/// @todo Give some thought to an ANY protocol which might try
+/// first as UDP then as TCP, etc.
+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.
 /// @brief Exception thrown if an NcrListenerError encounters a general error.
 class NcrListenerError : public isc::Exception {
 class NcrListenerError : public isc::Exception {
 public:
 public:

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

@@ -18,15 +18,36 @@
 #include <asiolink/io_error.h>
 #include <asiolink/io_error.h>
 #include <cryptolink/cryptolink.h>
 #include <cryptolink/cryptolink.h>
 
 
+#include <boost/algorithm/string/predicate.hpp>
 #include <botan/sha2_32.h>
 #include <botan/sha2_32.h>
 
 
 #include <sstream>
 #include <sstream>
 #include <limits>
 #include <limits>
 
 
+
 namespace isc {
 namespace isc {
 namespace dhcp_ddns {
 namespace dhcp_ddns {
 
 
 
 
+NameChangeFormat stringToNcrFormat(const std::string& fmt_str) {
+    if (boost::iequals(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");
+    }
+
+    std::ostringstream stream;
+    stream  << "UNKNOWN(" << format << ")";
+    return (stream.str());
+}
+
 /********************************* D2Dhcid ************************************/
 /********************************* D2Dhcid ************************************/
 
 
 namespace {
 namespace {

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

@@ -70,6 +70,25 @@ enum NameChangeFormat {
   FMT_JSON
   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
 /// @brief Container class for handling the DHCID value within a
 /// NameChangeRequest. It provides conversion to and from string for JSON
 /// NameChangeRequest. It provides conversion to and from string for JSON
 /// formatting, but stores the data internally as unsigned bytes.
 /// formatting, but stores the data internally as unsigned bytes.

+ 22 - 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
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp_ddns/ncr_io.h>
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
 #include <util/time_utilities.h>
 #include <util/time_utilities.h>
@@ -608,5 +608,26 @@ TEST(NameChangeRequestTest, ipAddresses) {
     ASSERT_THROW(ncr.setIpAddress("x001:1::f3"),NcrMessageError);
     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_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("udP"), dhcp_ddns::NCR_UDP);
+    ASSERT_EQ(stringToNcrProtocol("TCP"), dhcp_ddns::NCR_TCP);
+    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
 } // 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 += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_SOURCES += callout_handle_store.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 += dbaccess_parser.cc dbaccess_parser.h
 libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.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_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/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/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/hooks/libb10-hooks.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/log/libb10-log.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
 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);
     return (&(*addr).second);
 }
 }
 
 
+void
+CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+    d2_client_mgr_.setD2ClientConfig(new_config);
+}
+
+bool
+CfgMgr::ddnsEnabled() {
+    return (d2_client_mgr_.ddnsEnabled());
+}
+
+const D2ClientConfigPtr&
+CfgMgr::getD2ClientConfig() const {
+    return (d2_client_mgr_.getD2ClientConfig());
+}
+
+
 CfgMgr::CfgMgr()
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR),
     : 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
     // 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
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // See AM_CPPFLAGS definition in Makefile.am
     // 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.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_space.h>
 #include <dhcp/option_space.h>
+#include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
@@ -332,6 +333,24 @@ public:
         return (echo_v4_client_id_);
         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 ddnsEnabled();
+
+    /// @brief Fetches the DHCP-DDNS configuration pointer.
+    ///
+    /// @return a reference to the current configuration pointer.
+    const D2ClientConfigPtr& getD2ClientConfig() const;
+
 protected:
 protected:
 
 
     /// @brief Protected constructor.
     /// @brief Protected constructor.
@@ -411,6 +430,9 @@ private:
 
 
     /// Indicates whether v4 server should send back client-id
     /// Indicates whether v4 server should send back client-id
     bool echo_v4_client_id_;
     bool echo_v4_client_id_;
+
+    /// @brief Manages the DHCP-DDNS client and its configuration.
+    D2ClientMgr d2_client_mgr_;
 };
 };
 
 
 } // namespace isc::dhcp
 } // namespace isc::dhcp

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

@@ -0,0 +1,185 @@
+// Copyright (C) 2013-2014 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 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),
+    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) {
+    validateContents();
+}
+
+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),
+      override_no_update_(false),
+      override_client_update_(false),
+      replace_client_name_(false),
+      generated_prefix_(""),
+      qualifying_suffix_("") {
+    validateContents();
+}
+
+D2ClientConfig::~D2ClientConfig(){};
+
+void
+D2ClientConfig::validateContents() {
+    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?
+    // Also do we care about validating contents if it's disabled?
+}
+
+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_) &&
+            (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")
+               << ", 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(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
+                   "DHCP_DDNS updates enabled");
+}
+
+bool
+D2ClientMgr::ddnsEnabled() {
+    return (d2_client_config_->getEnableUpdates());
+}
+
+const D2ClientConfigPtr&
+D2ClientMgr::getD2ClientConfig() const {
+    return (d2_client_config_);
+}
+
+};  // namespace dhcp
+};  // namespace isc

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

@@ -0,0 +1,278 @@
+// Copyright (C) 2013-2014 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 (IPv4 or IPv6)
+    /// @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_fqdn Enables always including the FQDN option in
+    /// DHCP responses.
+    /// @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 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 (IPv4 or IPv6).
+    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 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;
+
+protected:
+    /// @brief Validates member values.
+    ///
+    /// Method is used by the constructor to validate member contents.
+    ///
+    /// @throw D2ClientError if given an invalid protocol or format.
+    virtual void validateContents();
+
+private:
+    /// @brief Indicates whether or not DHCP DDNS updating is enabled.
+    bool enable_updates_;
+
+    /// @brief IP address of the b10-dhcp-ddns server (IPv4 or IPv6).
+    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 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);
+
+    /// @brief Convenience method for checking if DHCP-DDNS is enabled.
+    ///
+    /// @return True if the D2 configuration is enabled.
+    bool ddnsEnabled();
+
+    /// @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

+ 105 - 1
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -1163,5 +1163,109 @@ SubnetConfigParser::getParam(const std::string& name) {
     return (Triplet<uint32_t>(value));
     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 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,
+                                                  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 dhcp
 };  // namespace isc
 };  // namespace isc

+ 74 - 2
src/lib/dhcpsrv/dhcp_parsers.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_definition.h>
+#include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
@@ -243,7 +244,7 @@ public:
         // its value. If it doesn't we insert a new element.
         // its value. If it doesn't we insert a new element.
         storage_->setParam(param_name_, value_);
         storage_->setParam(param_name_, value_);
     }
     }
- 
+
 private:
 private:
     /// Pointer to the storage where committed value is stored.
     /// Pointer to the storage where committed value is stored.
     boost::shared_ptr<ValueStorage<ValueType> > storage_;
     boost::shared_ptr<ValueStorage<ValueType> > storage_;
@@ -876,6 +877,77 @@ protected:
     ParserContextPtr global_context_;
     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 (see isc::dhcp::D2ClientConfig
+    /// for details on each):
+    /// -# 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
+    ///
+    /// @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.
 // Pointers to various parser objects.
 typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
 typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
 typedef boost::shared_ptr<StringParser> StringParserPtr;
 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
 A debug message issued when server is being configured to listen on all
 interfaces.
 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
 % DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
 A debug message issued when configuration manager clears the internal list
 A debug message issued when configuration manager clears the internal list
 of active interfaces. This doesn't prevent the server from listening to
 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 += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_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 += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_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/dhcpsrv/libb10-dhcpsrv.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.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/config/libb10-cfgclient.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la

+ 3 - 3
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -154,7 +154,7 @@ public:
         EXPECT_TRUE(false == lease->fqdn_fwd_);
         EXPECT_TRUE(false == lease->fqdn_fwd_);
         EXPECT_TRUE(false == lease->fqdn_rev_);
         EXPECT_TRUE(false == lease->fqdn_rev_);
         EXPECT_TRUE(*lease->duid_ == *duid_);
         EXPECT_TRUE(*lease->duid_ == *duid_);
-        // @todo: check cltt
+        /// @todo: check cltt
     }
     }
 
 
     /// @brief Checks if specified address is increased properly
     /// @brief Checks if specified address is increased properly
@@ -408,7 +408,7 @@ public:
             EXPECT_TRUE(*lease->client_id_ == *clientid_);
             EXPECT_TRUE(*lease->client_id_ == *clientid_);
         }
         }
         EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
         EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
-        // @todo: check cltt
+        /// @todo: check cltt
      }
      }
 
 
     virtual ~AllocEngine4Test() {
     virtual ~AllocEngine4Test() {

+ 43 - 3
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -538,7 +538,7 @@ TEST_F(CfgMgrTest, optionSpace4) {
         cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
         cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
     );
     );
 
 
-    // @todo decode if a duplicate vendor space is allowed.
+    /// @todo decode if a duplicate vendor space is allowed.
 }
 }
 
 
 // This test verifies that new DHCPv6 option spaces can be added to
 // This test verifies that new DHCPv6 option spaces can be added to
@@ -571,7 +571,7 @@ TEST_F(CfgMgrTest, optionSpace6) {
         cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
         cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
     );
     );
 
 
-    // @todo decide if a duplicate vendor space is allowed.
+    /// @todo decide if a duplicate vendor space is allowed.
 }
 }
 
 
 // This test verifies that it is possible to specify interfaces that server
 // This test verifies that it is possible to specify interfaces that server
@@ -670,6 +670,46 @@ TEST_F(CfgMgrTest, echoClientId) {
     EXPECT_TRUE(cfg_mgr.echoClientId());
     EXPECT_TRUE(cfg_mgr.echoClientId());
 }
 }
 
 
+// This test checks the D2ClientMgr wrapper methods.
+TEST_F(CfgMgrTest, d2ClientConfig) {
+    // 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().ddnsEnabled());
+
+    // 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,
+                                  "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().ddnsEnabled());
+
+    // 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:
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name
 /// - addActiveIface() with invalid interface name
 /// - addActiveIface() with the same interface twice
 /// - addActiveIface() with the same interface twice

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

@@ -0,0 +1,294 @@
+// Copyright (C) 2012-2014 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 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,
+                                                          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->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,
+                                                       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.
+}
+
+/// @brief 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,
+                    "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,
+                    "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,
+                    "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,
+                    "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,
+                    "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,
+                    "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,
+                    "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, 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, 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, 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,
+                    "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,
+                    "pre-fix", "bogus")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+}
+
+/// @brief 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->ddnsEnabled());
+}
+
+/// @brief 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,
+                                  "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->ddnsEnabled());
+
+    // 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

+ 273 - 12
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -367,8 +367,8 @@ public:
     ///
     ///
     /// Note that the method currently it only supports option-defs, option-data
     /// Note that the method currently it only supports option-defs, option-data
     /// and hooks-libraries.
     /// 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.
     /// @return returns a shared pointer to DhcpConfigParser.
     ///
     ///
@@ -376,20 +376,21 @@ public:
     ParserPtr createConfigParser(const std::string& config_id) {
     ParserPtr createConfigParser(const std::string& config_id) {
         ParserPtr parser;
         ParserPtr parser;
         if (config_id.compare("option-data") == 0) {
         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_,
                                               parser_context_,
                                               UtestOptionDataParser::factory));
                                               UtestOptionDataParser::factory));
 
 
         } else if (config_id.compare("option-def") == 0) {
         } else if (config_id.compare("option-def") == 0) {
-            parser.reset(new OptionDefListParser(config_id, 
+            parser.reset(new OptionDefListParser(config_id,
                                               parser_context_->option_defs_));
                                               parser_context_->option_defs_));
 
 
         } else if (config_id.compare("hooks-libraries") == 0) {
         } else if (config_id.compare("hooks-libraries") == 0) {
             parser.reset(new HooksLibrariesParser(config_id));
             parser.reset(new HooksLibrariesParser(config_id));
             hooks_libraries_parser_ =
             hooks_libraries_parser_ =
                 boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
                 boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
-
+        } else if (config_id.compare("dhcp-ddns") == 0) {
+            parser.reset(new D2ClientConfigParser(config_id));
         } else {
         } else {
             isc_throw(NotImplemented,
             isc_throw(NotImplemented,
                 "Parser error: configuration parameter not supported: "
                 "Parser error: configuration parameter not supported: "
@@ -399,8 +400,8 @@ public:
         return (parser);
         return (parser);
     }
     }
 
 
-    /// @brief Convenience method for parsing a configuration 
-    /// 
+    /// @brief Convenience method for parsing a configuration
+    ///
     /// Given a configuration string, convert it into Elements
     /// Given a configuration string, convert it into Elements
     /// and parse them.
     /// and parse them.
     /// @param config is the configuration string to parse
     /// @param config is the configuration string to parse
@@ -491,6 +492,10 @@ public:
 
 
         // Ensure no hooks libraries are loaded.
         // Ensure no hooks libraries are loaded.
         HooksManager::unloadLibraries();
         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
     /// @brief Parsers used in the parsing of the configuration
@@ -566,7 +571,7 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
         "    \"name\": \"foo\","
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
         "    \"space\": \"isc\","
         "    \"code\": 100,"
         "    \"code\": 100,"
-        "    \"data\": \"192.168.2.1\","
+        "    \"data\": \"192.0.2.0\","
         "    \"csv-format\": True"
         "    \"csv-format\": True"
         " } ]"
         " } ]"
         "}";
         "}";
@@ -581,7 +586,7 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
 
 
     // Verify that the option definition is correct.
     // Verify that the option definition is correct.
     std::string val = "type=100, len=4, data fields:\n "
     std::string val = "type=100, len=4, data fields:\n "
-                      " #0 192.168.2.1 ( ipv4-address ) \n";
+                      " #0 192.0.2.0 ( ipv4-address ) \n";
 
 
     EXPECT_EQ(val, opt_ptr->toText());
     EXPECT_EQ(val, opt_ptr->toText());
 }
 }
@@ -677,7 +682,7 @@ TEST_F(ParseConfigTest, validHooksLibrariesTest) {
 // Check with a set of libraries, some of which are invalid.
 // Check with a set of libraries, some of which are invalid.
 TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
 TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
 
 
-    // @todo Initialize global library context to null
+    /// @todo Initialize global library context to null
 
 
     // Configuration string.  This contains an invalid library which should
     // Configuration string.  This contains an invalid library which should
     // trigger an error in the "build" stage.
     // trigger an error in the "build" stage.
@@ -703,6 +708,262 @@ TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
         "Error text returned from parse failure is " << error_text_;
         "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 containing valid values.
+    std::string config_str =
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.0.2.0\", "
+        "     \"server-port\" : 3432, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : 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().ddnsEnabled());
+    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.0.2.0", d2_client_config->getServerIp().toText());
+    EXPECT_EQ(3432, 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->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());
+
+    // Another valid Configuration string.
+    // This one has IPV6 server ip, control flags false,
+    // empty prefix/suffix
+    std::string config_str2 =
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"2001:db8::\", "
+        "     \"server-port\" : 43567, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : false, "
+        "     \"always-include-fqdn\" : false, "
+        "     \"override-no-update\" : false, "
+        "     \"override-client-update\" : false, "
+        "     \"replace-client-name\" : false, "
+        "     \"generated-prefix\" : \"\", "
+        "     \"qualifying-suffix\" : \"\" "
+        "    }"
+        "}";
+
+    // Verify that the configuration string parses.
+    rcode = parseConfiguration(config_str2);
+    ASSERT_TRUE(rcode == 0) << error_text_;
+
+    // Verify that DHCP-DDNS is enabled and we can fetch the configuration.
+    EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+    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("2001:db8::", d2_client_config->getServerIp().toText());
+    EXPECT_EQ(43567, 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_FALSE(d2_client_config->getRemoveOnRenew());
+    EXPECT_FALSE(d2_client_config->getAlwaysIncludeFqdn());
+    EXPECT_FALSE(d2_client_config->getOverrideNoUpdate());
+    EXPECT_FALSE(d2_client_config->getOverrideClientUpdate());
+    EXPECT_FALSE(d2_client_config->getReplaceClientName());
+    EXPECT_EQ("", d2_client_config->getGeneratedPrefix());
+    EXPECT_EQ("", 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().ddnsEnabled());
+
+    // 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.0.2.0\", "
+        "     \"server-port\" : 53001, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : 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.0.2.0\", "
+        "     \"server-port\" : 53001, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : 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.0.2.0\", "
+        "     \"server-port\" : 53001, "
+        "     \"ncr-protocol\" : \"Bogus\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : 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.0.2.0\", "
+        "     \"server-port\" : 53001, "
+        "     \"ncr-protocol\" : \"TCP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : 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.0.2.0\", "
+        "     \"server-port\" : 53001, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"Bogus\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : 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.0.2.0\", "
+        // "     \"server-port\" : 53001, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"remove-on-renew\" : true, "
+        "     \"always-include-fqdn\" : 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.
 /// @brief DHCP Configuration Parser Context test fixture.
 class ParserContextTest : public ::testing::Test {
 class ParserContextTest : public ::testing::Test {
 public:
 public:

+ 2 - 2
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -144,7 +144,7 @@ TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
     EXPECT_EQ(Lease6Ptr(), x);
     EXPECT_EQ(Lease6Ptr(), x);
 }
 }
 
 
-// @todo Write more memfile tests
+/// @todo Write more memfile tests
 
 
 // Simple test about lease4 retrieval through client id method
 // Simple test about lease4 retrieval through client id method
 TEST_F(MemfileLeaseMgrTest, getLease4ClientId) {
 TEST_F(MemfileLeaseMgrTest, getLease4ClientId) {

+ 12 - 12
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -549,7 +549,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
     }
     }
 
 
     // Get the leases matching the hardware address of lease 1
     // Get the leases matching the hardware address of lease 1
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
     HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
     Lease4Collection returned = lmptr_->getLease4(tmp);
     Lease4Collection returned = lmptr_->getLease4(tmp);
 
 
@@ -568,14 +568,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
     EXPECT_EQ(straddress4_[5], addresses[2]);
     EXPECT_EQ(straddress4_[5], addresses[2]);
 
 
     // Repeat test with just one expected match
     // Repeat test with just one expected match
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
     returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
     ASSERT_EQ(1, returned.size());
     ASSERT_EQ(1, returned.size());
     detailCompareLease(leases[2], *returned.begin());
     detailCompareLease(leases[2], *returned.begin());
 
 
     // Check that an empty vector is valid
     // Check that an empty vector is valid
     EXPECT_TRUE(leases[7]->hwaddr_.empty());
     EXPECT_TRUE(leases[7]->hwaddr_.empty());
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
     returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
     ASSERT_EQ(1, returned.size());
     ASSERT_EQ(1, returned.size());
     detailCompareLease(leases[7], *returned.begin());
     detailCompareLease(leases[7], *returned.begin());
@@ -599,7 +599,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
     for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
     for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
         leases[1]->hwaddr_.resize(i, i);
         leases[1]->hwaddr_.resize(i, i);
         EXPECT_TRUE(lmptr_->addLease(leases[1]));
         EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        // @todo: Simply use HWAddr directly once 2589 is implemented
+        /// @todo: Simply use HWAddr directly once 2589 is implemented
         Lease4Collection returned =
         Lease4Collection returned =
             lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
             lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
 
 
@@ -610,7 +610,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
 
 
     // Database should not let us add one that is too big
     // Database should not let us add one that is too big
     // (The 42 is a random value put in each byte of the address.)
     // (The 42 is a random value put in each byte of the address.)
-    // @todo: 2589 will make this test impossible
+    /// @todo: 2589 will make this test impossible
     leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
     leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
     EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
     EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
 }
 }
@@ -628,7 +628,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
 
 
     // Get the leases matching the hardware address of lease 1 and
     // Get the leases matching the hardware address of lease 1 and
     // subnet ID of lease 1.  Result should be a single lease - lease 1.
     // subnet ID of lease 1.  Result should be a single lease - lease 1.
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
     Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
         HTYPE_ETHER), leases[1]->subnet_id_);
         HTYPE_ETHER), leases[1]->subnet_id_);
 
 
@@ -637,7 +637,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
 
 
     // Try for a match to the hardware address of lease 1 and the wrong
     // Try for a match to the hardware address of lease 1 and the wrong
     // subnet ID.
     // subnet ID.
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
     returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
                                  leases[1]->subnet_id_ + 1);
                                  leases[1]->subnet_id_ + 1);
     EXPECT_FALSE(returned);
     EXPECT_FALSE(returned);
@@ -645,14 +645,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
     // Try for a match to the subnet ID of lease 1 (and lease 4) but
     // Try for a match to the subnet ID of lease 1 (and lease 4) but
     // the wrong hardware address.
     // the wrong hardware address.
     vector<uint8_t> invalid_hwaddr(15, 0x77);
     vector<uint8_t> invalid_hwaddr(15, 0x77);
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
     returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
                                  leases[1]->subnet_id_);
                                  leases[1]->subnet_id_);
     EXPECT_FALSE(returned);
     EXPECT_FALSE(returned);
 
 
     // Try for a match to an unknown hardware address and an unknown
     // Try for a match to an unknown hardware address and an unknown
     // subnet ID.
     // subnet ID.
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
     returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
                                  leases[1]->subnet_id_ + 1);
                                  leases[1]->subnet_id_ + 1);
     EXPECT_FALSE(returned);
     EXPECT_FALSE(returned);
@@ -665,7 +665,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
     EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
     EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
     leases[1]->addr_ = leases[2]->addr_;
     leases[1]->addr_ = leases[2]->addr_;
     EXPECT_TRUE(lmptr_->addLease(leases[1]));
     EXPECT_TRUE(lmptr_->addLease(leases[1]));
-    // @todo: Simply use HWAddr directly once 2589 is implemented
+    /// @todo: Simply use HWAddr directly once 2589 is implemented
     EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
     EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
                                                     HTYPE_ETHER),
                                                     HTYPE_ETHER),
                                              leases[1]->subnet_id_),
                                              leases[1]->subnet_id_),
@@ -687,7 +687,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
     for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
     for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
         leases[1]->hwaddr_.resize(i, i);
         leases[1]->hwaddr_.resize(i, i);
         EXPECT_TRUE(lmptr_->addLease(leases[1]));
         EXPECT_TRUE(lmptr_->addLease(leases[1]));
-        // @todo: Simply use HWAddr directly once 2589 is implemented
+        /// @todo: Simply use HWAddr directly once 2589 is implemented
         Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
         Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
                                                       HTYPE_ETHER),
                                                       HTYPE_ETHER),
                                                leases[1]->subnet_id_);
                                                leases[1]->subnet_id_);