diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/dhcp6/tests/d2_unittest.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-upstream.tar.xz isc-kea-upstream.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/dhcp6/tests/d2_unittest.cc')
-rw-r--r-- | src/bin/dhcp6/tests/d2_unittest.cc | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/src/bin/dhcp6/tests/d2_unittest.cc b/src/bin/dhcp6/tests/d2_unittest.cc new file mode 100644 index 0000000..da9d6ee --- /dev/null +++ b/src/bin/dhcp6/tests/d2_unittest.cc @@ -0,0 +1,415 @@ +// 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 <config.h> + +#include <dhcp/iface_mgr.h> +#include <dhcp6/json_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) { + 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 + |