From: Thomas Markwalder Date: Thu, 13 Feb 2014 21:08:43 +0000 (-0500) Subject: [3329] Added D2ClientMgr control into b10-dhcp4 X-Git-Tag: bind10-1.2.0beta1-release~45^2~5 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=35c022c9fed4ea2b739c7492bdd2a2b7d95e5563;p=thirdparty%2Fkea.git [3329] Added D2ClientMgr control into b10-dhcp4 Server now starts D2ClientMgr send mode after configuration if updates are enabled, and provides an error handler to intervene if D2 communications fail. Added new unitests in d2_unittest.h and .cc. --- diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index 72e0b219d9..9808528151 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -114,6 +114,16 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) { return (answer); } + // Server will start DDNS communications if its enabled. + try { + server_->startD2(); + } catch (const std::exception& ex) { + std::ostringstream err; + err << "error starting DHCP_DDNS client " + " after server reconfiguration: " << ex.what(); + return (isc::config::createAnswer(1, err.str())); + } + // Configuration may change active interfaces. Therefore, we have to reopen // sockets according to new configuration. This operation is not exception // safe and we really don't want to emit exceptions to the callback caller. diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index e8b5812839..04314fe1ed 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -370,3 +370,8 @@ steps in the processing of incoming client message. This warning message is output when a packet was received from a subnet for which the DHCPv4 server has not been configured. The most probable cause is a misconfiguration of the server. + +% DHCP4_DDNS_REQUEST_SEND_FAILED failed sending a request to b10-dhcp-ddns, error : %1, ncr: %2 +This error message indicates that IPv4 DHCP server attempted to send a DDNS +update reqeust to the DHCP-DDNS server. This is most likely a configuration or +networking error. diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index bcafaefd2d..ace3cd8edb 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -1869,5 +1869,29 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp return (true); } +void +Dhcpv4Srv::startD2() { + D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr(); + if (d2_mgr.ddnsEnabled()) { + // Updates are enabled, so lets start the sender, passing in + // our error handler. + // This may throw so wherever this is called needs to ready. + d2_mgr.startSender(boost::bind(&Dhcpv4Srv::d2ClientErrorHandler, + this, _1, _2)); + } +} + +void +Dhcpv4Srv::d2ClientErrorHandler(const + dhcp_ddns::NameChangeSender::Result result, + dhcp_ddns::NameChangeRequestPtr& ncr) { + LOG_ERROR(dhcp4_logger, DHCP4_DDNS_REQUEST_SEND_FAILED). + arg(result).arg((ncr ? ncr->toText() : " NULL ")); + // We cannot communicate with b10-dhcp-ddns, suspend futher updates. + /// @todo We may wish to revisit this, but for now we will simpy turn + /// them off. + CfgMgr::instance().getD2ClientMgr().suspendUpdates(); +} + } // namespace dhcp } // namespace isc diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 087120cb96..ebb2f51415 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -165,6 +166,30 @@ public: /// @param use_bcast should broadcast flags be set on the sockets. static void openActiveSockets(const uint16_t port, const bool use_bcast); + /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled. + /// + /// If updates are enabled, it Instructs the D2ClientMgr singleton to + /// enter send mode. If D2ClientMgr encounters errors it may throw + /// D2ClientErrors. This method does not catch exceptions. + void startD2(); + + /// @brief Implements the error handler for DHCP_DDNS IO errors + /// + /// Invoked when a NameChangeRequest send to b10-dhcp-ddns completes with + /// a failed status. These are communications errors, not data related + /// failures. + /// + /// This method logs the failure and then suspends all further updates. + /// Updating can only be restored by reconfiguration or restarting the + /// server. There is currently no retry logic so the first IO error that + /// occurs will suspend updates. + /// @todo We may wish to make this more robust or sophisticated. + /// + /// @param result Result code of the send operation. + /// @param ncr NameChangeRequest which failed to send. + virtual void d2ClientErrorHandler(const dhcp_ddns:: + NameChangeSender::Result result, + dhcp_ddns::NameChangeRequestPtr& ncr); protected: /// @name Functions filtering and sanity-checking received messages. diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index f7fb5cc77c..4992501678 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -76,6 +76,7 @@ TESTS += dhcp4_unittests dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h +dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc dhcp4_unittests_SOURCES += dhcp4_test_utils.h dhcp4_unittests_SOURCES += dhcp4_unittests.cc dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc diff --git a/src/bin/dhcp4/tests/d2_unittest.cc b/src/bin/dhcp4/tests/d2_unittest.cc new file mode 100644 index 0000000000..d8b4ee8c82 --- /dev/null +++ b/src/bin/dhcp4/tests/d2_unittest.cc @@ -0,0 +1,326 @@ +// Copyright (C) 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 +#include +#include +#include + +#include + +#include + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::data; + +namespace isc { +namespace dhcp { +namespace test { + +/// @todo +void +D2Dhcpv4Srv::d2ClientErrorHandler(const + dhcp_ddns::NameChangeSender::Result result, + dhcp_ddns::NameChangeRequestPtr& ncr) { + ++error_count_; + // call base class error handler + Dhcpv4Srv::d2ClientErrorHandler(result, ncr); +} + +const bool Dhcp4SrvD2Test::SHOULD_PASS; +const bool Dhcp4SrvD2Test::SHOULD_FAIL; + +Dhcp4SrvD2Test::Dhcp4SrvD2Test() { +} + +Dhcp4SrvD2Test::~Dhcp4SrvD2Test() { + reset(); +} + +dhcp_ddns::NameChangeRequestPtr +Dhcp4SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) { + // Build an NCR from json string. + std::ostringstream stream; + + stream << + "{" + " \"change_type\" : 0 , " + " \"forward_change\" : true , " + " \"reverse_change\" : false , " + " \"fqdn\" : \"myhost.example.com.\" , " + " \"ip_address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"" + + << std::hex << std::setfill('0') << std::setw(16) + << dhcid_id_num << "\" , " + + " \"lease_expires_on\" : \"20140121132405\" , " + " \"lease_length\" : 1300 " + "}"; + + return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str())); +} + +void +Dhcp4SrvD2Test::reset() { + std::string config = "{ \"interfaces\": [ \"*\" ]," + "\"hooks-libraries\": [ ], " + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"subnet4\": [ ], " + "\"dhcp-ddns\": { \"enable-updates\" : false }, " + "\"option-def\": [ ], " + "\"option-data\": [ ] }"; + configure(config, SHOULD_PASS); +} + +void +Dhcp4SrvD2Test::configureD2(bool enable_d2, const bool exp_result, + const std::string& ip_address, + const uint32_t port) { + std::ostringstream config; + config << + "{ \"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\" : " << (enable_d2 ? "true" : "false") << ", " + " \"server-ip\" : \"" << ip_address << "\", " + " \"server-port\" : " << port << ", " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"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 }"; + + configure(config.str(), exp_result); +} + +void +Dhcp4SrvD2Test::configure(const std::string& config, bool exp_result) { + ElementPtr json = Element::fromJSON(config); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json)); + ASSERT_TRUE(status); + + int rcode; + ConstElementPtr comment = config::parseAnswer(rcode, status); + if (exp_result == SHOULD_PASS) { + ASSERT_EQ(0, rcode); + } else { + ASSERT_EQ(1, rcode); + } +} + +TEST_F(Dhcp4SrvD2Test, enableDisable) { + // Grab the manager and verify that be default ddns is off + // and a sender was not started. + dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr(); + ASSERT_FALSE(mgr.ddnsEnabled()); + ASSERT_FALSE(mgr.amSending()); + + // Verify a valid config with ddns enabled configures ddns properly, + // but does not start the sender. + ASSERT_NO_FATAL_FAILURE(configureD2(true)); + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_FALSE(mgr.amSending()); + + // Verify that calling start does not throw and starts the sender. + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + + // Verify a valid config with ddns disabled configures ddns properly. + // Sender should not have been started. + ASSERT_NO_FATAL_FAILURE(configureD2(false)); + ASSERT_FALSE(mgr.ddnsEnabled()); + ASSERT_FALSE(mgr.amSending()); + + // Verify that the sender does NOT get started when ddns is disabled. + srv_.startD2(); + ASSERT_FALSE(mgr.amSending()); +} + +TEST_F(Dhcp4SrvD2Test, badConfig) { + // Grab the manager and verify that be default ddns is off + // and a sender was not started. + dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr(); + ASSERT_FALSE(mgr.ddnsEnabled()); + + // Configure it enabled and start it. + ASSERT_NO_FATAL_FAILURE(configureD2(true)); + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + + // Now attempt to give it an invalid configuration. + // Result should indicate failure. + ASSERT_NO_FATAL_FAILURE(configureD2(false, SHOULD_FAIL, "bogus_ip")); + + // Configure was not altered, so ddns should be enabled and still sending. + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_TRUE(mgr.amSending()); + + // Verify that calling start does not throw or stop the sender. + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + +} + +TEST_F(Dhcp4SrvD2Test, sameConfig) { + // Grab the manager and verify that be default ddns is off + // and a sender was not started. + dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr(); + ASSERT_FALSE(mgr.ddnsEnabled()); + + // Configure it enabled and start it. + ASSERT_NO_FATAL_FAILURE(configureD2(true)); + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + + // Now submit an identical configuration. + ASSERT_NO_FATAL_FAILURE(configureD2(true)); + + // Configuration was not altered, so ddns should still enabled and sending. + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_TRUE(mgr.amSending()); + + // Verify that calling start does not throw or stop the sender. + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); +} + +TEST_F(Dhcp4SrvD2Test, differentConfig) { + // Grab the manager and verify that be default ddns is off + // and a sender was not started. + dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr(); + ASSERT_FALSE(mgr.ddnsEnabled()); + + // Configure it enabled and start it. + ASSERT_NO_FATAL_FAILURE(configureD2(true)); + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + + // Now enable it on a different port. + ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "127.0.0.1", 54001)); + + // Configuration was altered, so ddns should still enabled but not sending. + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_FALSE(mgr.amSending()); + + // Verify that calling start starts the sender. + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); +} + +TEST_F(Dhcp4SrvD2Test, simpleUDPSend) { + // Grab the manager and verify that be default ddns is off + // and a sender was not started. + dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr(); + ASSERT_FALSE(mgr.ddnsEnabled()); + + // Configure it enabled and start it. + ASSERT_NO_FATAL_FAILURE(configureD2(true)); + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + + // Verify that we can queue up a message. + dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(); + ASSERT_NO_THROW(mgr.sendRequest(ncr)); + EXPECT_EQ(1, mgr.getQueueSize()); + + // Calling receive should detect the ready IO on the sender's select-fd, + // and invoke callback, which should complete the send. + ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0)); + + // Verify the queue is now empty. + EXPECT_EQ(0, mgr.getQueueSize()); +} + +TEST_F(Dhcp4SrvD2Test, forceUDPSendFailure) { + // Grab the manager and verify that be default ddns is off + // and a sender was not started. + dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr(); + ASSERT_FALSE(mgr.ddnsEnabled()); + + // Configure it enabled and start it. + // Using server address of 0.0.0.0/0 should induce failure on send. + ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0)); + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + + // Queue up 3 messages. + for (int i = 0; i < 3; i++) { + dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1); + ASSERT_NO_THROW(mgr.sendRequest(ncr)); + } + EXPECT_EQ(3, mgr.getQueueSize()); + + // Calling receive should detect the ready IO on the sender's select-fd, + // and invoke callback, which should complete the send, which should + // fail. + ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0)); + + // Verify the error handler was invoked. + EXPECT_EQ(1, srv_.error_count_); + + // Verify that updates are disabled and we are no longer sending. + ASSERT_FALSE(mgr.ddnsEnabled()); + ASSERT_FALSE(mgr.amSending()); + + // Verify message is still in the queue. + EXPECT_EQ(3, mgr.getQueueSize()); + + // Verify that we can't just restart it. + /// @todo This may change if we add ability to resume. + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_FALSE(mgr.amSending()); + + // Configure it enabled and start it. + ASSERT_NO_FATAL_FAILURE(configureD2(true)); + ASSERT_TRUE(mgr.ddnsEnabled()); + ASSERT_NO_THROW(srv_.startD2()); + ASSERT_TRUE(mgr.amSending()); + + // Verify message is still in the queue. + EXPECT_EQ(3, mgr.getQueueSize()); + + // This will finish sending the 1st message in queue + // and initiate send of 2nd message. + ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0)); + EXPECT_EQ(1, srv_.error_count_); + + // First message is off the queue. + EXPECT_EQ(2, mgr.getQueueSize()); +} + +} // namespace test +} // namespace dhcp +} // namespace isc + diff --git a/src/bin/dhcp4/tests/d2_unittest.h b/src/bin/dhcp4/tests/d2_unittest.h new file mode 100644 index 0000000000..e33a0af401 --- /dev/null +++ b/src/bin/dhcp4/tests/d2_unittest.h @@ -0,0 +1,117 @@ +// Copyright (C) 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. + +/// @file d2_unittest.h Defines classes for testing Dhcpv4srv with D2ClientMgr + +#ifndef D2_UNITTEST_H +#define D2_UNITTEST_H + +#include +#include + +#include + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Test derivation of Dhcpv4Srv class used in D2 testing. +/// Use of this class allows the intervention at strategic points in testing +/// by permitting overridden methods and access to scope protected members. +class D2Dhcpv4Srv : public Dhcpv4Srv { +public: + /// @brief Counts the number of times the client error handler is called. + int error_count_; + + /// @brief Constructor + D2Dhcpv4Srv() + : Dhcpv4Srv(0, "type=memfile", false, false), error_count_(0) { + } + + /// @brief virtual Destructor. + virtual ~D2Dhcpv4Srv() { + } + + /// @brief Override the error handler. + virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender:: + Result result, + dhcp_ddns::NameChangeRequestPtr& ncr); +}; + +/// @brief Test fixture which permits testing the interaction between the +/// D2ClientMgr and Dhcpv4Srv. +class Dhcp4SrvD2Test : public ::testing::Test { +public: + /// @brief Mnemonic constants for calls to configuration methods. + static const bool SHOULD_PASS = true; + static const bool SHOULD_FAIL = false; + + /// @brief Constructor + Dhcp4SrvD2Test(); + + /// @brief virtual Destructor + virtual ~Dhcp4SrvD2Test(); + + /// @brief Resets the CfgMgr singleton to defaults. + /// Primarily used in the test destructor as gtest doesn't exit between + /// tests. + /// @todo CfgMgr should provide a method to reset everything or maybe + /// reconstruct the singleton. + void reset(); + + /// @brief Configures the server with D2 enabled or disabled + /// + /// Constructs a configuration string including dhcp-ddns with the + /// parameters given and passes it into the server's configuration handler. + /// + /// @param enable_updates value to assign to the enable-updates parameter + /// @param exp_result indicates if configuration should pass or fail + /// @param ip_address IP address for the D2 server + /// @param port port for the D2 server + void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS, + const std::string& ip_address = "127.0.0.1", + const uint32_t port = 53001); + + /// @brief Configures the server with the given configuration + /// + /// Passes the given configuration string into the server's configuration + /// handler. It accepts a flag indicating whether or not the configuration + /// is expected to succeed or fail. This permits testing the server's + /// response to both valid and invalid configurations. + /// + /// @param config JSON string containing the configuration + /// @param exp_result indicates if configuration should pass or fail + void configure(const std::string& config, bool exp_result = SHOULD_PASS); + + /// @brief Contructs a NameChangeRequest message from a fixed JSON string. + /// + /// @param dhcid_id_num Integer value to use as the DHCID. + dhcp_ddns::NameChangeRequestPtr buildTestNcr(uint32_t + dhcid_id_num = 0xdeadbeef); + + /// @brief Stores the return code of the last configuration attempt. + int rcode_; + + /// @brief Stores the message component of the last configuration tattempt. + isc::data::ConstElementPtr comment_; + + /// @brief Server object under test. + D2Dhcpv4Srv srv_; +}; + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // D2_UNITTEST_H