--- /dev/null
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp6/tests/d2_unittest.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @todo
+void
+D2Dhcpv6Srv::d2ClientErrorHandler(const
+ dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ ++error_count_;
+ // call base class error handler
+ Dhcpv6Srv::d2ClientErrorHandler(result, ncr);
+}
+
+const bool Dhcp6SrvD2Test::SHOULD_PASS;
+const bool Dhcp6SrvD2Test::SHOULD_FAIL;
+
+Dhcp6SrvD2Test::Dhcp6SrvD2Test() : rcode_(-1) {
+}
+
+Dhcp6SrvD2Test::~Dhcp6SrvD2Test() {
+ reset();
+}
+
+dhcp_ddns::NameChangeRequestPtr
+Dhcp6SrvD2Test::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
+Dhcp6SrvD2Test::reset() {
+ std::string config = "{ \"interfaces\": [ \"all\" ],"
+ "\"hooks-libraries\": [ ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet6\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ configure(config, SHOULD_PASS);
+}
+
+void
+Dhcp6SrvD2Test::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, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ " \"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
+Dhcp6SrvD2Test::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 = configureDhcp6Server(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);
+ }
+}
+
+// Tests ability to turn on and off ddns updates by submitting
+// by submitting the appropriate configuration to Dhcp6 server
+// and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, 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());
+}
+
+// Tests Dhcp6 server's ability to correctly handle a flawed dhcp-ddns configuration.
+// It does so by first enabling updates by submitting a valid configuration and then
+// ensuring they remain on after submitting a flawed configuration.
+// and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, 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());
+
+}
+
+// Checks that submitting an identical dhcp-ddns configuration
+// is handled properly. Not effect should be no change in
+// status for ddns updating. Updates should still enabled and
+// in send mode. This indicates that the sender was not stopped.
+TEST_F(Dhcp6SrvD2Test, 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());
+}
+
+// Checks that submitting an different, but valid dhcp-ddns configuration
+// is handled properly. Updates should be enabled, however they should
+// not yet be running. This indicates that the sender was stopped and
+// replaced, but not yet started.
+TEST_F(Dhcp6SrvD2Test, 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());
+}
+
+// Checks that given a valid, enabled configuration and placing
+// sender in send mode, permits NCR requests to be sent via UPD
+// socket. Note this test does not employ any sort of receiving
+// client to verify actual transmission. These types of tests
+// are including under dhcp_ddns and d2 unit testing.
+TEST_F(Dhcp6SrvD2Test, 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());
+}
+
+// Checks that an IO error in sending a request to D2, results in ddns updates
+// being suspended. This indicates that Dhcp6Srv's error handler has been
+// invoked as expected. Note that this unit test relies on an attempt to send
+// to a server address of 0.0.0.0 port 0 fails under all OSs.
+TEST_F(Dhcp6SrvD2Test, 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());
+}
+
+// Tests error handling of D2ClientMgr::sendRequest() failure
+// by attempting to queue maximum number of messages.
+TEST_F(Dhcp6SrvD2Test, queueMaxError) {
+ // Configure it enabled and start it.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Attempt to queue more then the maximum allowed.
+ int max_msgs = mgr.getQueueMaxSize();
+ for (int i = 0; i < max_msgs + 1; i++) {
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+ ASSERT_NO_THROW(mgr.sendRequest(ncr));
+ }
+
+ // Stopping sender will complete the first message so there
+ // should be max less one.
+ EXPECT_EQ(max_msgs - 1, mgr.getQueueSize());
+
+ // 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());
+}
+
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
--- /dev/null
+// 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 Dhcpv6srv with D2ClientMgr
+
+#ifndef D2_UNITTEST_H
+#define D2_UNITTEST_H
+
+#include <dhcp6/dhcp6_srv.h>
+#include <config/ccsession.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test derivation of Dhcpv6Srv 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 D2Dhcpv6Srv : public Dhcpv6Srv {
+public:
+ /// @brief Counts the number of times the client error handler is called.
+ int error_count_;
+
+ /// @brief Constructor
+ D2Dhcpv6Srv()
+ : Dhcpv6Srv(0), error_count_(0) {
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~D2Dhcpv6Srv() {
+ }
+
+ /// @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 Dhcpv6Srv.
+class Dhcp6SrvD2Test : 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
+ Dhcp6SrvD2Test();
+
+ /// @brief virtual Destructor
+ virtual ~Dhcp6SrvD2Test();
+
+ /// @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.
+ D2Dhcpv6Srv srv_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // D2_UNITTEST_H