From: Thomas Markwalder Date: Mon, 6 Jan 2014 15:56:59 +0000 (-0500) Subject: [3033] Added DHCP-DDNS configuration paramater parsing to b10-dhcp4 X-Git-Tag: bind10-1.2.0beta1-release~137^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d508d4ac19b7260805fa0838ea627f2f05b960c5;p=thirdparty%2Fkea.git [3033] Added DHCP-DDNS configuration paramater parsing to b10-dhcp4 Added configuration paramters to dhcp4 and its spec file to support DHCP-DDNS. Created new classes D2ClientMgr, D2ClientConfig, and D2CientConfigParser in the libdhcpsrv. The new parameters are parsed, validated, and stored but do not yet affect behavior. That will be implemented as a seperate ticket. --- diff --git a/configure.ac b/configure.ac index f50840075e..0b32b1cc75 100644 --- a/configure.ac +++ b/configure.ac @@ -1365,6 +1365,7 @@ AC_CONFIG_FILES([compatcheck/Makefile src/bin/dhcp4/spec_config.h.pre src/bin/dhcp4/tests/Makefile src/bin/dhcp4/tests/marker_file.h + src/bin/dhcp4/tests/test_data_files_config.h src/bin/dhcp4/tests/test_libraries.h src/bin/dhcp6/Makefile src/bin/dhcp6/spec_config.h.pre diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc index 777cce5d2b..929942deca 100644 --- a/src/bin/dhcp4/config_parser.cc +++ b/src/bin/dhcp4/config_parser.cc @@ -396,6 +396,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) { parser = new HooksLibrariesParser(config_id); } else if (config_id.compare("echo-client-id") == 0) { parser = new BooleanParser(config_id, globalContext()->boolean_values_); + } else if (config_id.compare("dhcp-ddns") == 0) { + parser = new D2ClientConfigParser(config_id); } else { isc_throw(NotImplemented, "Parser error: Global configuration parameter not supported: " @@ -448,7 +450,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // Some of the parsers alter the state of the system in a way that can't // easily be undone. (Or alter it in a way such that undoing the change has // the same risk of failure as doing the change.) - ParserPtr hooks_parser_; + ParserPtr hooks_parser; // The subnet parsers implement data inheritance by directly // accessing global storage. For this reason the global data @@ -489,7 +491,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // Executing commit will alter currently-loaded hooks // libraries. Check if the supplied libraries are valid, // but defer the commit until everything else has committed. - hooks_parser_ = parser; + hooks_parser = parser; parser->build(config_pair.second); } else { // Those parsers should be started before other @@ -557,8 +559,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // This occurs last as if it succeeds, there is no easy way // revert it. As a result, the failure to commit a subsequent // change causes problems when trying to roll back. - if (hooks_parser_) { - hooks_parser_->commit(); + if (hooks_parser) { + hooks_parser->commit(); } } catch (const isc::Exception& ex) { diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec index 9372dfd8ad..cb4c912cb3 100644 --- a/src/bin/dhcp4/dhcp4.spec +++ b/src/bin/dhcp4/dhcp4.spec @@ -292,7 +292,110 @@ } } ] } - } + }, + + { "item_name": "dhcp-ddns", + "item_type": "map", + "item_optional": false, + "item_default": {"enable-updates": false}, + "map_item_spec": [ + { + "item_name": "enable-updates", + "item_type": "boolean", + "item_optional": false, + "item_default": False, + "item_description" : "Enables DDNS update processing" + }, + { + "item_name": "server_ip", + "item_type": "string", + "item_optional": true, + "item_default": "127.0.0.1", + "item_description" : "IP address of b10-dhcp-ddns" + }, + { + "item_name": "server_port", + "item_type": "integer", + "item_optional": true, + "item_default": 5301, + "item_description" : "port number of b10-dhcp-ddns" + }, + { + "item_name": "ncr_protocol", + "item_type": "string", + "item_optional": true, + "item_default": "UDP", + "item_description" : "Socket protocol to use with b10-dhcp-ddns" + }, + { + "item_name": "ncr_format", + "item_type": "string", + "item_optional": true, + "item_default": "JSON", + "item_description" : "Format of the update request packet" + }, + { + "item_name": "remove-on-renew", + "item_type": "boolean", + "item_optional": true, + "item_default": false, + "item_description": "Should server request a DNS Remove, before a DNS Update on renewals" + }, + { + + "item_name": "always-include-fqdn", + "item_type": "boolean", + "item_optional": true, + "item_default": False, + "item_description": "Should server always include the FQDN option in its response" + }, + { + + "item_name": "allow-client-update", + "item_type": "boolean", + "item_optional": true, + "item_default": False, + "item_description": "Enable AAAA RR update delegation to the client" + }, + { + "item_name": "override-no-update", + "item_type": "boolean", + "item_optional": true, + "item_default": false, + "item_description": "Do update, even if client requested no updates with N flag" + }, + { + "item_name": "override-client-update", + "item_type": "boolean", + "item_optional": true, + "item_default": true, + "item_description": "Server performs an update even if client requested delegation" + }, + { + "item_name": "replace-client-name", + "item_type": "boolean", + "item_optional": true, + "item_default": false, + "item_description": "Should server replace the domain-name supplied by the client" + }, + { + "item_name": "generated-prefix", + "item_type": "string", + "item_optional": true, + "item_default": "myhost", + "item_description": "Prefix to use when generating the client's name" + }, + + { + "item_name": "qualifying-suffix", + "item_type": "string", + "item_optional": true, + "item_default": "example.com", + "item_description": "Fully qualified domain-name suffix if partial name provided by client" + }, + ] + }, + ], "commands": [ { diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 1d9ccb7fa0..a789b39114 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -30,6 +30,7 @@ #include "marker_file.h" #include "test_libraries.h" +#include "test_data_files_config.h" #include #include @@ -50,6 +51,22 @@ using namespace std; namespace { +/// @brief Prepends the given name with the DHCP4 source directory +/// +/// @param name file name of the desired file +/// @return string containing the absolute path of the file in the DHCP source +/// directory. +std::string specfile(const std::string& name) { + return (std::string(DHCP4_SRC_DIR) + "/" + name); +} + +/// @brief Tests that the spec file is valid. +/// Verifies that the BIND10 DHCP-DDNS configuration specification file +// is valid. +TEST(Dhcp4SpecTest, basicSpec) { + ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec"))); +} + class Dhcp4ParserTest : public ::testing::Test { public: Dhcp4ParserTest() @@ -321,6 +338,7 @@ public: "\"renew-timer\": 1000, " "\"valid-lifetime\": 4000, " "\"subnet4\": [ ], " + "\"dhcp-ddns\": { \"enable-updates\" : false }, " "\"option-def\": [ ], " "\"option-data\": [ ] }"; static_cast(executeConfiguration(config, @@ -2299,6 +2317,117 @@ TEST_F(Dhcp4ParserTest, allInterfaces) { EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2")); } +// This test checks the ability of the server to parse a configuration +// containing a full, valid dhcp-ddns (D2ClientConfig) entry. +TEST_F(Dhcp4ParserTest, d2ClientConfig) { + ConstElementPtr status; + + // Verify that the D2 configuraiton can be fetched and is set to disabled. + D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig(); + EXPECT_FALSE(d2_client_config->getEnableUpdates()); + + // Verify that the convenience method agrees. + ASSERT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled()); + + string config_str = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" } ]," + " \"dhcp-ddns\" : {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"192.168.2.1\", " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" }," + "\"valid-lifetime\": 4000 }"; + + // Convert the JSON string to configuration elements. + ElementPtr config; + ASSERT_NO_THROW(config = Element::fromJSON(config_str)); + + // Pass the configuration in for parsing. + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config)); + + // check if returned status is OK + checkResult(status, 0); + + // Verify that DHCP-DDNS updating is enabled. + EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled()); + + // Verify that the D2 configuration can be retrieved. + d2_client_config = CfgMgr::instance().getD2ClientConfig(); + ASSERT_TRUE(d2_client_config); + + // Verify that the configuration values are correct. + EXPECT_TRUE(d2_client_config->getEnableUpdates()); + EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText()); + EXPECT_EQ(5301, d2_client_config->getServerPort()); + EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol()); + EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat()); + EXPECT_TRUE(d2_client_config->getRemoveOnRenew()); + EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn()); + EXPECT_TRUE(d2_client_config->getAllowClientUpdate()); + EXPECT_TRUE(d2_client_config->getOverrideNoUpdate()); + EXPECT_TRUE(d2_client_config->getOverrideClientUpdate()); + EXPECT_TRUE(d2_client_config->getReplaceClientName()); + EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix()); + EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix()); +} + +// This test checks the ability of the server to handle a configuration +// containing an invalid dhcp-ddns (D2ClientConfig) entry. +TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) { + ConstElementPtr status; + + // Configuration string with an invalid D2 client config, + // "server-ip" is missing. + string config_str = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" } ]," + " \"dhcp-ddns\" : {" + " \"enable-updates\" : true, " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" }," + "\"valid-lifetime\": 4000 }"; + + // Convert the JSON string to configuration elements. + ElementPtr config; + ASSERT_NO_THROW(config = Element::fromJSON(config_str)); + + // Configuration should not throw, but should fail. + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config)); + // check if returned status is failed. + checkResult(status, 1); + + // Verify that the D2 configuraiton can be fetched and is set to disabled. + D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig(); + EXPECT_FALSE(d2_client_config->getEnableUpdates()); + + // Verify that the convenience method agrees. + ASSERT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled()); +} } diff --git a/src/bin/dhcp4/tests/test_data_files_config.h.in b/src/bin/dhcp4/tests/test_data_files_config.h.in new file mode 100644 index 0000000000..0b778cf8a8 --- /dev/null +++ b/src/bin/dhcp4/tests/test_data_files_config.h.in @@ -0,0 +1,17 @@ +// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +/// @brief Path to dhcp4 source dir so tests against the dhcp4.spec file +/// can find it reliably. +#define DHCP4_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp4" diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc index 4f3f1d7289..eee948e936 100644 --- a/src/lib/dhcp_ddns/ncr_io.cc +++ b/src/lib/dhcp_ddns/ncr_io.cc @@ -18,6 +18,32 @@ namespace isc { namespace dhcp_ddns { +NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) { + if (protocol_str == "UDP") { + return NCR_UDP; + } + + if (protocol_str == "TCP") { + return NCR_TCP; + } + + isc_throw(BadValue, "Invalid NameChangeRequest protocol:" << protocol_str); +} + +std::string ncrProtocolToString(NameChangeProtocol protocol) { + switch (protocol) { + case NCR_UDP: + return ("UDP"); + case NCR_TCP: + return ("TCP"); + default: + break; + } + + return ("UNKNOWN"); +} + + //************************** NameChangeListener *************************** NameChangeListener::NameChangeListener(RequestReceiveHandler& diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h index 94d08f7e80..50d1c8c64e 100644 --- a/src/lib/dhcp_ddns/ncr_io.h +++ b/src/lib/dhcp_ddns/ncr_io.h @@ -66,6 +66,31 @@ namespace isc { namespace dhcp_ddns { +/// @brief Defines the list of socket protocols supported. +enum NameChangeProtocol { + NCR_UDP, + NCR_TCP +}; + +/// @brief Function which converts labels to NameChangeProtocol enum values. +/// +/// @param protocol_str text to convert to an enum. +/// Valid string values: "UDP", "TCP" +/// +/// @return NameChangeProtocol value which maps to the given string. +/// +/// @throw isc::BadValue if given a string value which does not map to an +/// enum value. +extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str); + +/// @brief Function which converts NameChangeProtocol enums to text labels. +/// +/// @param protocol enum value to convert to label +/// +/// @return std:string containing the text label if the value is valid, or +/// "UNKNOWN" if not. +extern std::string ncrProtocolToString(NameChangeProtocol protocol); + /// @brief Exception thrown if an NcrListenerError encounters a general error. class NcrListenerError : public isc::Exception { public: diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc index 418eaa0a1f..91c2608659 100644 --- a/src/lib/dhcp_ddns/ncr_msg.cc +++ b/src/lib/dhcp_ddns/ncr_msg.cc @@ -26,6 +26,22 @@ namespace isc { namespace dhcp_ddns { +NameChangeFormat stringToNcrFormat(const std::string& fmt_str) { + if (fmt_str == "JSON") { + return FMT_JSON; + } + + isc_throw(BadValue, "Invalid NameChangeRequest format:" << fmt_str); +} + + +std::string ncrFormatToString(NameChangeFormat format) { + if (format == FMT_JSON) { + return ("JSON"); + } + + return ("UNKNOWN"); +} /********************************* D2Dhcid ************************************/ diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h index 47a7be642e..5dab9e8dd8 100644 --- a/src/lib/dhcp_ddns/ncr_msg.h +++ b/src/lib/dhcp_ddns/ncr_msg.h @@ -70,6 +70,25 @@ enum NameChangeFormat { FMT_JSON }; +/// @brief Function which converts labels to NameChangeFormat enum values. +/// +/// @param format_str text to convert to an enum. +/// Valid string values: "JSON" +/// +/// @return NameChangeFormat value which maps to the given string. +/// +/// @throw isc::BadValue if given a string value which does not map to an +/// enum value. +extern NameChangeFormat stringToNcrFormat(const std::string& fmt_str); + +/// @brief Function which converts NameChangeFormat enums to text labels. +/// +/// @param format enum value to convert to label +/// +/// @return std:string containing the text label if the value is valid, or +/// "UNKNOWN" if not. +extern std::string ncrFormatToString(NameChangeFormat format); + /// @brief Container class for handling the DHCID value within a /// NameChangeRequest. It provides conversion to and from string for JSON /// formatting, but stores the data internally as unsigned bytes. diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc index 9386a5fcbf..e71ddda5fe 100644 --- a/src/lib/dhcp_ddns/tests/ncr_unittests.cc +++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc @@ -12,7 +12,7 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include +#include #include #include #include @@ -608,5 +608,23 @@ TEST(NameChangeRequestTest, ipAddresses) { ASSERT_THROW(ncr.setIpAddress("x001:1::f3"),NcrMessageError); } +/// @brief Tests conversion of NameChangeFormat between enum and strings. +TEST(NameChangeFormatTest, formatEnumConversion){ + ASSERT_EQ(stringToNcrFormat("JSON"), dhcp_ddns::FMT_JSON); + ASSERT_THROW(stringToNcrFormat("bogus"), isc::BadValue); + + ASSERT_EQ(ncrFormatToString(dhcp_ddns::FMT_JSON), "JSON"); +} + +/// @brief Tests conversion of NameChangeProtocol between enum and strings. +TEST(NameChangeProtocolTest, protocolEnumConversion){ + ASSERT_EQ(stringToNcrProtocol("UDP"), dhcp_ddns::NCR_UDP); + ASSERT_EQ(stringToNcrProtocol("TCP"), dhcp_ddns::NCR_TCP); + ASSERT_THROW(stringToNcrProtocol("bogus"), isc::BadValue); + + ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_UDP), "UDP"); + ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_TCP), "TCP"); +} + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index b3c420680f..69b14b2ee0 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -39,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES = libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h libb10_dhcpsrv_la_SOURCES += callout_handle_store.h +libb10_dhcpsrv_la_SOURCES += d2_client.cc d2_client.h libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h @@ -64,6 +65,7 @@ libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS) libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES) libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la +libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc index 7789c74454..b5bcb66fd7 100644 --- a/src/lib/dhcpsrv/cfgmgr.cc +++ b/src/lib/dhcpsrv/cfgmgr.cc @@ -348,9 +348,26 @@ CfgMgr::getUnicast(const std::string& iface) const { return (&(*addr).second); } +void +CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) { + d2_client_mgr_.setD2ClientConfig(new_config); +} + +bool +CfgMgr::isDhcpDdnsEnabled() { + return (d2_client_mgr_.isDhcpDdnsEnabled()); +} + +const D2ClientConfigPtr& +CfgMgr::getD2ClientConfig() const { + return (d2_client_mgr_.getD2ClientConfig()); +} + + CfgMgr::CfgMgr() : datadir_(DHCP_DATA_DIR), - all_ifaces_active_(false), echo_v4_client_id_(true) { + all_ifaces_active_(false), echo_v4_client_id_(true), + d2_client_mgr_() { // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am // Note: the definition of DHCP_DATA_DIR needs to include quotation marks // See AM_CPPFLAGS definition in Makefile.am diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h index cda59f2c58..07d4b9c017 100644 --- a/src/lib/dhcpsrv/cfgmgr.h +++ b/src/lib/dhcpsrv/cfgmgr.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -332,6 +333,24 @@ public: return (echo_v4_client_id_); } + /// @brief Updates the DHCP-DDNS client configuration to the given value. + /// + /// @param new_config pointer to the new client configuration. + /// + /// @throw Underlying method(s) will throw D2ClientError if given an empty + /// pointer. + void setD2ClientConfig(D2ClientConfigPtr& new_config); + + /// @param Convenience method for checking if DHCP-DDNS updates are enabled. + /// + /// @return True if the D2 configuration is enabled. + bool isDhcpDdnsEnabled(); + + /// @brief Fetches the DHCP-DDNS configuration pointer. + /// + /// @return a reference to the current configuration pointer. + const D2ClientConfigPtr& getD2ClientConfig() const; + protected: /// @brief Protected constructor. @@ -411,6 +430,9 @@ private: /// Indicates whether v4 server should send back client-id bool echo_v4_client_id_; + + /// @brief Manages the DHCP-DDNS client and its configuration. + D2ClientMgr d2_client_mgr_; }; } // namespace isc::dhcp diff --git a/src/lib/dhcpsrv/d2_client.cc b/src/lib/dhcpsrv/d2_client.cc new file mode 100644 index 0000000000..47b6b40165 --- /dev/null +++ b/src/lib/dhcpsrv/d2_client.cc @@ -0,0 +1,187 @@ +// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include + +using namespace std; + +namespace isc { +namespace dhcp { + +D2ClientConfig::D2ClientConfig(const bool enable_updates, + const isc::asiolink::IOAddress& server_ip, + const size_t server_port, + const dhcp_ddns:: + NameChangeProtocol& ncr_protocol, + const dhcp_ddns:: + NameChangeFormat& ncr_format, + const bool remove_on_renew, + const bool always_include_fqdn, + const bool allow_client_update, + const bool override_no_update, + const bool override_client_update, + const bool replace_client_name, + const std::string& generated_prefix, + const std::string& qualifying_suffix) + : enable_updates_(enable_updates), + server_ip_(server_ip.getAddress()), + server_port_(server_port), + ncr_protocol_(ncr_protocol), + ncr_format_(ncr_format), + remove_on_renew_(remove_on_renew), + always_include_fqdn_(always_include_fqdn), + allow_client_update_(allow_client_update), + override_no_update_(override_no_update), + override_client_update_(override_client_update), + replace_client_name_(replace_client_name), + generated_prefix_(generated_prefix), + qualifying_suffix_(qualifying_suffix) { + if (ncr_format_ != dhcp_ddns::FMT_JSON) { + isc_throw(D2ClientError, "D2ClientConfig: NCR Format:" + << dhcp_ddns::ncrFormatToString(ncr_format) + << " is not yet supported"); + } + + if (ncr_protocol_ != dhcp_ddns::NCR_UDP) { + isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol:" + << dhcp_ddns::ncrProtocolToString(ncr_protocol) + << " is not yet supported"); + } + + // @todo perhaps more validation we should do yet? + // Are there any invalid combinations of options we need to test against? + // For instance are allow_client_update and override_client_update mutually + // exclusive? + // Also do we care about validating contents if it's disabled? +} + +D2ClientConfig::D2ClientConfig() + : enable_updates_(false), + server_ip_(isc::asiolink::IOAddress("0.0.0.0")), + server_port_(0), + ncr_protocol_(dhcp_ddns::NCR_UDP), + ncr_format_(dhcp_ddns::FMT_JSON), + remove_on_renew_(false), + always_include_fqdn_(false), + allow_client_update_(false), + override_no_update_(false), + override_client_update_(false), + replace_client_name_(false), + generated_prefix_(""), + qualifying_suffix_("") { +} + +D2ClientConfig::~D2ClientConfig(){}; + +bool +D2ClientConfig::operator == (const D2ClientConfig& other) const { + return ((enable_updates_ == other.enable_updates_) && + (server_ip_ == other.server_ip_) && + (server_port_ == other.server_port_) && + (ncr_protocol_ == other.ncr_protocol_) && + (ncr_format_ == other.ncr_format_) && + (remove_on_renew_ == other.remove_on_renew_) && + (always_include_fqdn_ == other.always_include_fqdn_) && + (allow_client_update_ == other.allow_client_update_) && + (override_no_update_ == other.override_no_update_) && + (override_client_update_ == other.override_client_update_) && + (replace_client_name_ == other.replace_client_name_) && + (generated_prefix_ == other.generated_prefix_) && + (qualifying_suffix_ == other.qualifying_suffix_)); +} + +bool +D2ClientConfig::operator != (const D2ClientConfig& other) const { + return (!(*this == other)); +} + +std::string +D2ClientConfig::toText() const { + std::ostringstream stream; + + stream << "enable_updates: " << (enable_updates_ ? "yes" : "no"); + if (enable_updates_) { + stream << ", server_ip: " << server_ip_.toText() + << ", server_port: " << server_port_ + << ", ncr_protocol: " << ncr_protocol_ + << ", ncr_format: " << ncr_format_ + << ", remove_on_renew: " << (remove_on_renew_ ? "yes" : "no") + << ", always_include_fqdn: " << (always_include_fqdn_ ? + "yes" : "no") + << ", allow_client_update: " << (allow_client_update_ ? + "yes" : "no") + << ", override_no_update: " << (override_no_update_ ? + "yes" : "no") + << ", override_client_update: " << (override_client_update_ ? + "yes" : "no") + << ", replace_client_name: " << (replace_client_name_ ? + "yes" : "no") + << ", generated_prefix: [" << generated_prefix_ << "]" + << ", qualifying_suffix: [" << qualifying_suffix_ << "]"; + } + + return (stream.str()); +} + +std::ostream& +operator<<(std::ostream& os, const D2ClientConfig& config) { + os << config.toText(); + return (os); +} + +D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) { + // Default contstructor initializes with a disabled config. +} + +D2ClientMgr::~D2ClientMgr(){ +} + +void +D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) { + if (!new_config) { + isc_throw(D2ClientError, + "D2ClientMgr cannot set DHCP-DDNS configuration to NULL."); + } + + // @todo When NameChangeSender is integrated, we will need to handle these + // scenarios: + // 1. D2 was enabled but now it is disabled + // - destroy the sender, flush any queued + // 2. D2 is still enabled but server params have changed + // - preserve any queued, reconnect based on sender params + // 3. D2 was was disabled now it is enabled. + // - create sender + // + // For now we just update the configuration. + d2_client_config_ = new_config; + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS) + .arg(!isDhcpDdnsEnabled() ? "DHCP-DDNS updates disabled" : + "DHCP_DDNS updates enabled"); +} + +bool +D2ClientMgr::isDhcpDdnsEnabled() { + return (d2_client_config_->getEnableUpdates()); +} + +const D2ClientConfigPtr& +D2ClientMgr::getD2ClientConfig() const { + return (d2_client_config_); +} + +}; // namespace dhcp +}; // namespace isc diff --git a/src/lib/dhcpsrv/d2_client.h b/src/lib/dhcpsrv/d2_client.h new file mode 100644 index 0000000000..792c3f9701 --- /dev/null +++ b/src/lib/dhcpsrv/d2_client.h @@ -0,0 +1,280 @@ +// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef D2_CLIENT_H +#define D2_CLIENT_H + +/// @file d2_client.h Defines the D2ClientConfig and D2ClientMgr classes. +/// This file defines the classes Kea uses to act as a client of the b10- +/// dhcp-ddns module (aka D2). +/// +#include +#include +#include + +#include + +#include +#include +#include + +namespace isc { +namespace dhcp { + + +/// An exception that is thrown if an error occurs while configuring +/// the D2 DHCP DDNS client. +class D2ClientError : public isc::Exception { +public: + + /// @brief constructor + /// + /// @param file name of the file, where exception occurred + /// @param line line of the file, where exception occurred + /// @param what text description of the issue that caused exception + D2ClientError(const char* file, size_t line, const char* what) + : isc::Exception(file, line, what) {} +}; + +/// @brief Acts as a storage vault for D2 client configuration +/// +/// A simple container class for storing and retrieving the configuration +/// parameters associated with DHCP-DDNS and acting as a client of D2. +/// Instances of this class may be constructed through configuration parsing. +/// +class D2ClientConfig { +public: + /// @brief Constructor + /// + /// @param enable_updates Enables DHCP-DDNS updates + /// @param server_ip IP address of the b10-dhcp-ddns server + /// @param server_port IP port of the b10-dhcp-ddns server + /// @param ncr_protocol Socket protocol to use with b10-dhcp-ddns + /// Currently only UDP is supported. + /// @param ncr_format Format of the b10-dhcp-ddns requests. + /// Currently only JSON format is supported. + /// @param remove_on_renew Enables DNS Removes when renewing a lease + /// If true, Kea should request an explicit DNS remove prior to requesting + /// a DNS update when renewing a lease. + /// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove + /// is unnecessary). + /// @param always_include_fdqn Enables always including the FQDN option in + /// DHCP responses. + /// @param allow_client_update Enables delegation of updates to clients + /// @param override_no_update Enables updates, even if clients request no + /// updates. + /// @param override_client_update Perform updates, even if client requested + /// delegation. + /// @param replace_client_name enables replacement of the domain-name + /// supplied by the client with a generated name. + /// @param generated_prefix Prefix to use when generating domain-names. + /// @param qualifying_suffix Suffix to use to qualify partial domain-names. + /// + /// @throw D2ClientError if given an invalid protocol or format. + D2ClientConfig(const bool enable_updates_, + const isc::asiolink::IOAddress& server_ip_, + const size_t server_port_, + const dhcp_ddns::NameChangeProtocol& ncr_protocol_, + const dhcp_ddns::NameChangeFormat& ncr_format_, + const bool remove_on_renew_, + const bool always_include_fqdn_, + const bool allow_client_update_, + const bool override_no_update_, + const bool override_client_update_, + const bool replace_client_name_, + const std::string& generated_prefix_, + const std::string& qualifying_suffix_); + + /// @brief Default constructor + /// The default constructor creates an instance that has updates disabled. + D2ClientConfig(); + + /// @brief Destructor + virtual ~D2ClientConfig(); + + /// @brief Return whether or not DHCP-DDNS updating is enabled. + bool getEnableUpdates() const { + return(enable_updates_); + } + + /// @brief Return the IP address of b10-dhcp-ddns. + const isc::asiolink::IOAddress& getServerIp() const { + return(server_ip_); + } + + /// @brief Return the IP port of b10-dhcp-ddns. + size_t getServerPort() const { + return(server_port_); + } + + /// @brief Return the socket protocol to use with b10-dhcp-ddns. + const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const { + return(ncr_protocol_); + } + + /// @brief Return the b10-dhcp-ddns request format. + const dhcp_ddns::NameChangeFormat& getNcrFormat() const { + return(ncr_format_); + } + + /// @brief Return whether or not removes should be sent for lease renewals. + bool getRemoveOnRenew() const { + return(remove_on_renew_); + } + + /// @brief Return whether or not FQDN is always included in DHCP responses. + bool getAlwaysIncludeFqdn() const { + return(always_include_fqdn_); + } + + /// @brief Return whether or not updates can be delegated to clients. + bool getAllowClientUpdate() const { + return(allow_client_update_); + } + + /// @brief Return if updates are done even if clients request no updates. + bool getOverrideNoUpdate() const { + return(override_no_update_); + } + + /// @brief Return if updates are done even when clients request delegation. + bool getOverrideClientUpdate() const { + return(override_client_update_); + } + + /// @brief Return whether or not client's domain-name is always replaced. + bool getReplaceClientName() const { + return(replace_client_name_); + } + + /// @brief Return the prefix to use when generating domain-names. + const std::string& getGeneratedPrefix() const { + return(generated_prefix_); + } + + /// @brief Return the suffix to use to qualify partial domain-names. + const std::string& getQualifyingSuffix() const { + return(qualifying_suffix_); + } + + /// @brief Compares two D2ClientConfigs for equality + bool operator == (const D2ClientConfig& other) const; + + /// @brief Compares two D2ClientConfigs for inequality + bool operator != (const D2ClientConfig& other) const; + + /// @brief Generates a string representation of the class contents. + std::string toText() const; + +private: + /// @brief Indicates whether or not DHCP DDNS updating is enabled. + bool enable_updates_; + + /// @brief IP address of the b10-dhcp-ddns server. + isc::asiolink::IOAddress server_ip_; + + /// @brief IP port of the b10-dhcp-ddns server. + size_t server_port_; + + /// @brief The socket protocol to use with b10-dhcp-ddns. + /// Currently only UPD is supported. + dhcp_ddns::NameChangeProtocol ncr_protocol_; + + /// @brief Format of the b10-dhcp-ddns requests. + /// Currently only JSON format is supported. + dhcp_ddns::NameChangeFormat ncr_format_; + + /// @brief Should Kea request a DNS Remove when renewing a lease. + /// If true, Kea should request an explicit DNS remove prior to requesting + /// a DNS update when renewing a lease. + /// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove + /// is unnecessary). + bool remove_on_renew_; + + /// @brief Should Kea always include the FQDN option in its response. + bool always_include_fqdn_; + + /// @brief Should Kea permit the client to do updates. + bool allow_client_update_; + + /// @brief Should Kea perform updates, even if client requested no updates. + /// Overrides the client request for no updates via the N flag. + bool override_no_update_; + + /// @brief Should Kea perform updates, even if client requested delegation. + bool override_client_update_; + + /// @brief Should Kea replace the domain-name supplied by the client. + bool replace_client_name_; + + /// @brief Prefix Kea should use when generating domain-names. + std::string generated_prefix_; + + /// @brief Suffix Kea should use when to qualify partial domain-names. + std::string qualifying_suffix_; +}; + +std::ostream& +operator<<(std::ostream& os, const D2ClientConfig& config); + +/// @brief Defines a pointer for D2ClientConfig instances. +typedef boost::shared_ptr D2ClientConfigPtr; + +/// @brief D2ClientMgr isolates Kea from the details of being a D2 client. +/// +/// Provides services for managing the current D2ClientConfig and managing +/// communications with D2. (@todo The latter will be added once communication +/// with D2 is implemented through the integration of +/// dhcp_ddns::NameChangeSender interface(s). +/// +class D2ClientMgr { +public: + /// @brief Constructor + /// + /// Default constructor which constructs an instance which has DHCP-DDNS + /// updates disabled. + D2ClientMgr(); + + /// @brief Destructor. + ~D2ClientMgr(); + + /// @brief Updates the DHCP-DDNS client configuration to the given value. + /// + /// @param new_config pointer to the new client configuration. + /// @throw D2ClientError if passed an empty pointer. + void setD2ClientConfig(D2ClientConfigPtr& new_config); + + /// @param Convenience method for checking if DHCP-DDNS updates are enabled. + /// + /// @return True if the D2 configuration is enabled. + bool isDhcpDdnsEnabled(); + + /// @brief Fetches the DHCP-DDNS configuration pointer. + /// + /// @return a reference to the current configuration pointer. + const D2ClientConfigPtr& getD2ClientConfig() const; + +private: + /// @brief Container class for DHCP-DDNS configuration parameters. + D2ClientConfigPtr d2_client_config_; +}; + +/// @brief Defines a pointer for D2ClientMgr instances. +typedef boost::shared_ptr D2ClientMgrPtr; + + +} // namespace isc +} // namespace dhcp + +#endif diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc index ffd49d3083..7a9aee7de6 100644 --- a/src/lib/dhcpsrv/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/dhcp_parsers.cc @@ -1163,5 +1163,111 @@ SubnetConfigParser::getParam(const std::string& name) { return (Triplet(value)); } +//**************************** D2ClientConfigParser ********************** +D2ClientConfigParser::D2ClientConfigParser(const std::string& entry_name) + : entry_name_(entry_name), boolean_values_(new BooleanStorage()), + uint32_values_(new Uint32Storage()), string_values_(new StringStorage()), + local_client_config_() { +} + +D2ClientConfigParser::~D2ClientConfigParser() { +} + +void +D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) { + BOOST_FOREACH(ConfigPair param, client_config->mapValue()) { + ParserPtr parser(createConfigParser(param.first)); + parser->build(param.second); + parser->commit(); + } + + bool enable_updates = boolean_values_->getParam("enable-updates"); + if (!enable_updates) { + // If it's not enabled, don't bother validating the rest. This + // allows for an abbreviated config entry that only contains + // the flag. The default constructor creates a disabled instance. + local_client_config_.reset(new D2ClientConfig()); + return; + } + + // Get all parameters that are needed to create the D2ClientConfig. + asiolink::IOAddress server_ip(string_values_->getParam("server-ip")); + + uint32_t server_port = uint32_values_->getParam("server-port"); + + dhcp_ddns::NameChangeProtocol + ncr_protocol = dhcp_ddns:: stringToNcrProtocol(string_values_-> + getParam("ncr-protocol")); + + dhcp_ddns::NameChangeFormat + ncr_format = dhcp_ddns::stringToNcrFormat(string_values_-> + getParam("ncr-format")); + + std::string generated_prefix = string_values_->getParam("generated-prefix"); + std::string qualifying_suffix = string_values_-> + getParam("qualifying-suffix"); + + bool remove_on_renew = boolean_values_->getParam("remove-on-renew"); + bool always_include_fqdn = boolean_values_->getParam("always-include-fqdn"); + bool allow_client_update = boolean_values_->getParam("allow-client-update"); + bool override_no_update = boolean_values_->getParam("override-no-update"); + bool override_client_update = boolean_values_-> + getParam("override-client-update"); + bool replace_client_name = boolean_values_->getParam("replace-client-name"); + + // Attempt to create the new client config. + local_client_config_.reset(new D2ClientConfig(enable_updates, server_ip, + server_port, ncr_protocol, + ncr_format, remove_on_renew, + always_include_fqdn, + allow_client_update, + override_no_update, + override_client_update, + replace_client_name, + generated_prefix, + qualifying_suffix)); +} + +isc::dhcp::ParserPtr +D2ClientConfigParser::createConfigParser(const std::string& config_id) { + DhcpConfigParser* parser = NULL; + if (config_id.compare("server-port") == 0) { + parser = new Uint32Parser(config_id, uint32_values_); + } else if ((config_id.compare("server-ip") == 0) || + (config_id.compare("ncr-protocol") == 0) || + (config_id.compare("ncr-format") == 0) || + (config_id.compare("generated-prefix") == 0) || + (config_id.compare("qualifying-suffix") == 0)) { + parser = new StringParser(config_id, string_values_); + } else if ((config_id.compare("enable-updates") == 0) || + (config_id.compare("remove-on-renew") == 0) || + (config_id.compare("always-include-fqdn") == 0) || + (config_id.compare("allow-client-update") == 0) || + (config_id.compare("override-no-update") == 0) || + (config_id.compare("override-client-update") == 0) || + (config_id.compare("replace-client-name") == 0)) { + parser = new BooleanParser(config_id, boolean_values_); + } else { + isc_throw(NotImplemented, + "parser error: D2ClientConfig parameter not supported: " + << config_id); + } + + return (isc::dhcp::ParserPtr(parser)); +} + +void +D2ClientConfigParser::commit() { + // @todo if local_client_config_ is empty then shutdown the listener... + // @todo Should this also attempt to start a listener? + // In keeping with Interface, Subnet, and Hooks parsers, then this + // should initialize the listener. Failure to init it, should cause + // rollback. This gets sticky, because who owns the listener instance? + // Does CfgMgr maintain it or does the server class? If the latter + // how do we get that value here? + // I'm thinkikng D2ClientConfig could contain the listener instance + CfgMgr::instance().setD2ClientConfig(local_client_config_); +} + }; // namespace dhcp }; // namespace isc diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h index 28c57b8b93..3e28252c62 100644 --- a/src/lib/dhcpsrv/dhcp_parsers.h +++ b/src/lib/dhcpsrv/dhcp_parsers.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -243,7 +244,7 @@ public: // its value. If it doesn't we insert a new element. storage_->setParam(param_name_, value_); } - + private: /// Pointer to the storage where committed value is stored. boost::shared_ptr > storage_; @@ -876,6 +877,77 @@ protected: ParserContextPtr global_context_; }; +/// @brief Parser for D2ClientConfig +/// +/// This class parses the configuration element "dhcp-ddns" common to the +/// spec files for both dhcp4 and dhcp6. It creates an instance of a +/// D2ClientConfig. +class D2ClientConfigParser : public isc::dhcp::DhcpConfigParser { +public: + /// @brief Constructor + /// + /// @param entry_name is an arbitrary label assigned to this configuration + /// definition. + D2ClientConfigParser(const std::string& entry_name); + + /// @brief Destructor + virtual ~D2ClientConfigParser(); + + /// @brief Performs the parsing of the given dhcp-ddns element. + /// + /// The results of the parsing are retained internally for use during + /// commit. + /// + /// @param client_config is the "dhcp-ddns" configuration to parse + virtual void build(isc::data::ConstElementPtr client_config); + + /// @brief Creates a parser for the given "dhcp-ddns" member element id. + /// + /// The elements currently supported are: + /// -# enable-updates + /// -# server-ip + /// -# server-port + /// -# ncr-protocol + /// -# ncr-format + /// -# remove-on-renew + /// -# always-include-fqdn + /// -# allow-client-update + /// -# override-no-update + /// -# override-client-update + /// -# replace-client-name + /// -# generated-prefix + /// -# qualifying-suffix + /// (see d2::D2ClientConfig for details on each.) + /// + /// @param config_id is the "item_name" for a specific member element of + /// the "dns_server" specification. + /// + /// @return returns a pointer to newly created parser. + virtual isc::dhcp::ParserPtr createConfigParser(const std::string& + config_id); + + /// @brief Instantiates a D2ClientConfig from internal data values + /// passes to CfgMgr singleton. + virtual void commit(); + +private: + /// @brief Arbitrary label assigned to this parser instance. + /// Primarily used for diagnostics. + std::string entry_name_; + + /// Storage for subnet-specific boolean values. + BooleanStoragePtr boolean_values_; + + /// Storage for subnet-specific integer values. + Uint32StoragePtr uint32_values_; + + /// Storage for subnet-specific string values. + StringStoragePtr string_values_; + + /// @brief Pointer to temporary local instance created during build. + D2ClientConfigPtr local_client_config_ ; +}; + // Pointers to various parser objects. typedef boost::shared_ptr BooleanParserPtr; typedef boost::shared_ptr StringParserPtr; diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index 6ee3e87de7..5c7854e4d1 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes @@ -70,6 +70,9 @@ specified IPv6 subnet to its database. A debug message issued when server is being configured to listen on all interfaces. +% DHCPSRV_CFGMGR_CFG_DHCP_DDNS Setting DHCP-DDNS configuration to: %1 +A debug message issued when the server's DHCP-DDNS settings are changed. + % DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces A debug message issued when configuration manager clears the internal list of active interfaces. This doesn't prevent the server from listening to diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 643fd635a2..ff57254e8d 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -52,6 +52,7 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc +libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc libdhcpsrv_unittests_SOURCES += lease_unittest.cc libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc @@ -88,6 +89,7 @@ endif libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la +libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc index 08ce768f62..2ccd3a5cee 100644 --- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc @@ -670,6 +670,48 @@ TEST_F(CfgMgrTest, echoClientId) { EXPECT_TRUE(cfg_mgr.echoClientId()); } +// This test checks the D2ClientMgr wrapper methods. +TEST_F(CfgMgrTest, d2ClientConfig) { + CfgMgr& cfg_mgr = CfgMgr::instance(); + + // After CfgMgr construction, D2 configuration should be disabled. + // Fetch it and verify this is the case. + D2ClientConfigPtr original_config = CfgMgr::instance().getD2ClientConfig(); + ASSERT_TRUE(original_config); + EXPECT_FALSE(original_config->getEnableUpdates()); + + // Make sure convenience method agrees. + EXPECT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled()); + + // Verify that we cannot set the configuration to an empty pointer. + D2ClientConfigPtr new_cfg; + ASSERT_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg), D2ClientError); + + // Create a new, enabled configuration. + ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true, + isc::asiolink::IOAddress("127.0.0.1"), 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "suf-fix"))); + + // Verify that we can assign a new, non-empty configuration. + ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg)); + + // Verify that we can fetch the newly assigned configuration. + D2ClientConfigPtr updated_config = CfgMgr::instance().getD2ClientConfig(); + ASSERT_TRUE(updated_config); + EXPECT_TRUE(updated_config->getEnableUpdates()); + + // Make sure convenience method agrees with updated configuration. + EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled()); + + // Make sure the configuration we fetched is the one we assigned, + // and not the original configuration. + EXPECT_EQ(*new_cfg, *updated_config); + EXPECT_NE(*original_config, *updated_config); +} + + /// @todo Add unit-tests for testing: /// - addActiveIface() with invalid interface name /// - addActiveIface() with the same interface twice diff --git a/src/lib/dhcpsrv/tests/d2_client_unittest.cc b/src/lib/dhcpsrv/tests/d2_client_unittest.cc new file mode 100644 index 0000000000..6b32755b33 --- /dev/null +++ b/src/lib/dhcpsrv/tests/d2_client_unittest.cc @@ -0,0 +1,308 @@ +// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include + +#include + +using namespace std; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; +using namespace isc; + +namespace { + +// brief Checks constructors and accessors of D2ClientConfig. +TEST(D2ClientConfigTest, constructorsAndAccessors) { + D2ClientConfigPtr d2_client_config; + + // Verify default constructor creates a disabled instance. + ASSERT_NO_THROW(d2_client_config.reset(new D2ClientConfig())); + EXPECT_FALSE(d2_client_config->getEnableUpdates()); + + d2_client_config.reset(); + + bool enable_updates = true; + isc::asiolink::IOAddress server_ip("127.0.0.1"); + size_t server_port = 477; + dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP; + dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON; + bool remove_on_renew = true; + bool always_include_fqdn = true; + bool allow_client_update = true; + bool override_no_update = true; + bool override_client_update = true; + bool replace_client_name = true; + std::string generated_prefix = "the_prefix"; + std::string qualifying_suffix = "the.suffix."; + + // Verify that we can construct a valid, enabled instance. + ASSERT_NO_THROW(d2_client_config.reset(new + D2ClientConfig(enable_updates, + server_ip, + server_port, + ncr_protocol, + ncr_format, + remove_on_renew, + always_include_fqdn, + allow_client_update, + override_no_update, + override_client_update, + replace_client_name, + generated_prefix, + qualifying_suffix))); + + ASSERT_TRUE(d2_client_config); + + // Verify that the accessors return the expected values. + EXPECT_EQ(d2_client_config->getEnableUpdates(), enable_updates); + + EXPECT_EQ(d2_client_config->getServerIp(), server_ip); + EXPECT_EQ(d2_client_config->getServerPort(), server_port); + EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol); + EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format); + EXPECT_EQ(d2_client_config->getRemoveOnRenew(), remove_on_renew); + EXPECT_EQ(d2_client_config->getAlwaysIncludeFqdn(), always_include_fqdn); + EXPECT_EQ(d2_client_config->getAllowClientUpdate(), allow_client_update); + EXPECT_EQ(d2_client_config->getOverrideNoUpdate(), override_no_update); + EXPECT_EQ(d2_client_config->getOverrideClientUpdate(), + override_client_update); + EXPECT_EQ(d2_client_config->getReplaceClientName(), replace_client_name); + EXPECT_EQ(d2_client_config->getGeneratedPrefix(), generated_prefix); + EXPECT_EQ(d2_client_config->getQualifyingSuffix(), qualifying_suffix); + + // Verify that toText called by << operator doesn't bomb. + ASSERT_NO_THROW(std::cout << "toText test:" << std::endl << + *d2_client_config << std::endl); + + // Verify that constructor does not allow use of NCR_TCP. + // @todo obviously this becomes invalid once TCP is supported. + ASSERT_THROW(d2_client_config.reset(new + D2ClientConfig(enable_updates, + server_ip, + server_port, + dhcp_ddns::NCR_TCP, + ncr_format, + remove_on_renew, + always_include_fqdn, + allow_client_update, + override_no_update, + override_client_update, + replace_client_name, + generated_prefix, + qualifying_suffix)), + D2ClientError); + + // @todo if additional validation is added to ctor, this test needs to + // expand accordingly. +} + +// Tests the equality and inequality operators of D2ClientConfig. +TEST(D2ClientConfigTest, equalityOperator) { + D2ClientConfigPtr ref_config; + D2ClientConfigPtr test_config; + + isc::asiolink::IOAddress ref_address("127.0.0.1"); + isc::asiolink::IOAddress test_address("127.0.0.2"); + + // Create an instance to use as a reference. + ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(ref_config); + + // Check a configuration that is identical to reference configuration. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_TRUE(*ref_config == *test_config); + EXPECT_FALSE(*ref_config != *test_config); + + // Check a configuration that differs only by enable flag. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by server ip. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + test_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by server port. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 333, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by remove_on_renew. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + false, true, true, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by always_include_fqdn. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, false, true, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by allow_client_update. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, false, true, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by override_no_update. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, false, true, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by override_client_update. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, false, true, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by replace_client_name. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, false, + "pre-fix", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by generated_prefix. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "bogus", "suf-fix"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); + + // Check a configuration that differs only by qualifying_suffix. + ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true, + ref_address, 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "bogus"))); + ASSERT_TRUE(test_config); + EXPECT_FALSE(*ref_config == *test_config); + EXPECT_TRUE(*ref_config != *test_config); +} + +// This test checks the D2ClientMgr constructor. +TEST(D2ClientMgr, constructor) { + D2ClientMgrPtr d2_client_mgr; + + // Verify we can construct with the default constructor. + ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr())); + + // After construction, D2 configuration should be disabled. + // Fetch it and verify this is the case. + D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig(); + ASSERT_TRUE(original_config); + EXPECT_FALSE(original_config->getEnableUpdates()); + + // Make sure convenience method agrees. + EXPECT_FALSE(d2_client_mgr->isDhcpDdnsEnabled()); +} + +// This test checks passing the D2ClientMgr a valid D2 client configuration. +// @todo Once NameChangeSender is integrated, this test needs to expand, and +// additional scenario tests will need to be written. +TEST(D2ClientMgr, validConfig) { + D2ClientMgrPtr d2_client_mgr; + + // Construct the manager and fetch its initial configuration. + ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr())); + D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig(); + ASSERT_TRUE(original_config); + + // Verify that we cannot set the config to an empty pointer. + D2ClientConfigPtr new_cfg; + ASSERT_THROW(d2_client_mgr->setD2ClientConfig(new_cfg), D2ClientError); + + // Create a new, enabled config. + ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true, + isc::asiolink::IOAddress("127.0.0.1"), 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + true, true, true, true, true, true, + "pre-fix", "suf-fix"))); + + // Verify that we can assign a new, non-empty configuration. + ASSERT_NO_THROW(d2_client_mgr->setD2ClientConfig(new_cfg)); + + // Verify that we can fetch the newly assigned configuration. + D2ClientConfigPtr updated_config = d2_client_mgr->getD2ClientConfig(); + ASSERT_TRUE(updated_config); + EXPECT_TRUE(updated_config->getEnableUpdates()); + + // Make sure convenience method agrees with the updated configuration. + EXPECT_TRUE(d2_client_mgr->isDhcpDdnsEnabled()); + + // Make sure the configuration we fetched is the one we assigned, + // and not the original configuration. + EXPECT_EQ(*new_cfg, *updated_config); + EXPECT_NE(*original_config, *updated_config); +} + + +} // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index 928be23ea3..c5396785e9 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -367,8 +367,8 @@ public: /// /// Note that the method currently it only supports option-defs, option-data /// and hooks-libraries. - /// - /// @param config_id is the name of the configuration element. + /// + /// @param config_id is the name of the configuration element. /// /// @return returns a shared pointer to DhcpConfigParser. /// @@ -376,20 +376,21 @@ public: ParserPtr createConfigParser(const std::string& config_id) { ParserPtr parser; if (config_id.compare("option-data") == 0) { - parser.reset(new OptionDataListParser(config_id, - parser_context_->options_, + parser.reset(new OptionDataListParser(config_id, + parser_context_->options_, parser_context_, UtestOptionDataParser::factory)); } else if (config_id.compare("option-def") == 0) { - parser.reset(new OptionDefListParser(config_id, + parser.reset(new OptionDefListParser(config_id, parser_context_->option_defs_)); } else if (config_id.compare("hooks-libraries") == 0) { parser.reset(new HooksLibrariesParser(config_id)); hooks_libraries_parser_ = boost::dynamic_pointer_cast(parser); - + } else if (config_id.compare("dhcp-ddns") == 0) { + parser.reset(new D2ClientConfigParser(config_id)); } else { isc_throw(NotImplemented, "Parser error: configuration parameter not supported: " @@ -399,8 +400,8 @@ public: return (parser); } - /// @brief Convenience method for parsing a configuration - /// + /// @brief Convenience method for parsing a configuration + /// /// Given a configuration string, convert it into Elements /// and parse them. /// @param config is the configuration string to parse @@ -491,6 +492,10 @@ public: // Ensure no hooks libraries are loaded. HooksManager::unloadLibraries(); + + // Set it to minimal, disabled config + D2ClientConfigPtr tmp(new D2ClientConfig()); + CfgMgr::instance().setD2ClientConfig(tmp); } /// @brief Parsers used in the parsing of the configuration @@ -703,6 +708,226 @@ TEST_F(ParseConfigTest, invalidHooksLibrariesTest) { "Error text returned from parse failure is " << error_text_; } +/// @brief Checks that a valid, enabled D2 client configuration works correctly. +TEST_F(ParseConfigTest, validD2Config) { + + // Configuration string. This contains a set of valid libraries. + std::string config_str = + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"192.168.2.1\", " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" " + " }" + "}"; + + // Verify that the configuration string parses. + int rcode = parseConfiguration(config_str); + ASSERT_TRUE(rcode == 0) << error_text_; + + // Verify that DHCP-DDNS is enabled and we can fetch the configuration. + EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled()); + D2ClientConfigPtr d2_client_config; + ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig()); + ASSERT_TRUE(d2_client_config); + + // Verify that the configuration values are as expected. + EXPECT_TRUE(d2_client_config->getEnableUpdates()); + EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText()); + EXPECT_EQ(5301, d2_client_config->getServerPort()); + EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol()); + EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat()); + EXPECT_TRUE(d2_client_config->getRemoveOnRenew()); + EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn()); + EXPECT_TRUE(d2_client_config->getAllowClientUpdate()); + EXPECT_TRUE(d2_client_config->getOverrideNoUpdate()); + EXPECT_TRUE(d2_client_config->getOverrideClientUpdate()); + EXPECT_TRUE(d2_client_config->getReplaceClientName()); + EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix()); + EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix()); +} + +/// @brief Checks that D2 client can be configured with enable flag of +/// false only. +TEST_F(ParseConfigTest, validDisabledD2Config) { + + // Configuration string. This contains a set of valid libraries. + std::string config_str = + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : false" + " }" + "}"; + + // Verify that the configuration string parses. + int rcode = parseConfiguration(config_str); + ASSERT_TRUE(rcode == 0) << error_text_; + + // Verify that DHCP-DDNS is disabled. + EXPECT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled()); + + // Make sure fetched config agrees. + D2ClientConfigPtr d2_client_config; + ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig()); + EXPECT_TRUE(d2_client_config); + EXPECT_FALSE(d2_client_config->getEnableUpdates()); +} + +/// @brief Check various invalid D2 client configurations. +TEST_F(ParseConfigTest, invalidD2Config) { + std::string invalid_configs[] = { + // only the enable flag of true + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true" + " }" + "}", + // Missing server ip value + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true, " + //" \"server-ip\" : \"192.168.2.1\", " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" " + " }" + "}", + // Invalid server ip value + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"x192.168.2.1\", " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" " + " }" + "}", + // Unknown protocol + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"192.168.2.1\", " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"Bogus\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" " + " }" + "}", + // Unsupported protocol + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"192.168.2.1\", " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"TCP\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" " + " }" + "}", + // Unknown format + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"192.168.2.1\", " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"Bogus\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" " + " }" + "}", + // Missig Port + "{ \"dhcp-ddns\" :" + " {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"192.168.2.1\", " + // " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"remove-on-renew\" : true, " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" " + " }" + "}", + // stop + "" + }; + + // Fetch the original config. + D2ClientConfigPtr original_config; + ASSERT_NO_THROW(original_config = CfgMgr::instance().getD2ClientConfig()); + + // Iterate through the invalid configuration strings, attempting to + // parse each one. They should fail to parse, but fail gracefully. + D2ClientConfigPtr current_config; + int i = 0; + while (!invalid_configs[i].empty()) { + // Verify that the configuration string parses without throwing. + int rcode = parseConfiguration(invalid_configs[i]); + + // Verify that parse result indicates a parsing error. + ASSERT_TRUE(rcode != 0) << "Invalid config #: " << i + << " should not have passed!"; + + // Verify that the "official" config still matches the original config. + ASSERT_NO_THROW(current_config = + CfgMgr::instance().getD2ClientConfig()); + EXPECT_EQ(*original_config, *current_config); + ++i; + } +} + /// @brief DHCP Configuration Parser Context test fixture. class ParserContextTest : public ::testing::Test { public: