// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #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 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) { reset(); } 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, " " \"use-conflict-resolution\" : true " "}"; return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str())); } void Dhcp6SrvD2Test::reset() { CfgMgr::instance().clear(); std::string config = "{ \"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"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& server_ip, const size_t port, const std::string& sender_ip, const size_t sender_port, const size_t max_queue_size) { std::ostringstream config; config << "{ \"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"hooks-libraries\": [ ]," "\"preferred-lifetime\": 3000," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"subnet6\": [ { " " \"id\": 1, " " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ]," " \"subnet\": \"2001:db8:1::/64\" } ]," " \"dhcp-ddns\" : {" " \"enable-updates\" : " << (enable_d2 ? "true" : "false") << ", " " \"server-ip\" : \"" << server_ip << "\", " " \"server-port\" : " << port << ", " " \"sender-ip\" : \"" << sender_ip << "\", " " \"sender-port\" : " << sender_port << ", " " \"max-queue-size\" : " << max_queue_size << ", " " \"ncr-protocol\" : \"UDP\", " " \"ncr-format\" : \"JSON\", " " \"override-no-update\" : true, " " \"override-client-update\" : true, " " \"replace-client-name\" : \"when-present\", " " \"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) << "parse comment: " << comment->stringValue(); } else { ASSERT_EQ(1, rcode) << "parse comment: " << comment->stringValue(); } if (rcode == 0) { CfgMgr::instance().commit(); } } // 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, "::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(mgr.clearQueue()); EXPECT_EQ(0, mgr.getQueueSize()); 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, which it does under all OSs // except Solaris 11. /// @todo Eventually we should find a way to test this under Solaris. #ifndef OS_SOLARIS TEST_F(Dhcp6SrvD2Test, forceUDPSendFailure) { #else TEST_F(Dhcp6SrvD2Test, DISABLED_forceUDPSendFailure) { #endif // 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. // Pass in a non-zero sender port to avoid validation error when // server-ip/port are same as sender-ip/port ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "::", 0, "::", 53001)); ASSERT_TRUE(mgr.ddnsEnabled()); ASSERT_NO_THROW(mgr.clearQueue()); EXPECT_EQ(0, mgr.getQueueSize()); try { srv_.startD2(); } catch (const std::exception& ex) { FAIL() << "startD2 failed with " << ex.what(); } catch (...) { FAIL() << "startD2 failed"; } 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(mgr.clearQueue()); EXPECT_EQ(0, mgr.getQueueSize()); 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