diff options
Diffstat (limited to 'src/bin/dhcp4/tests/out_of_range_unittest.cc')
-rw-r--r-- | src/bin/dhcp4/tests/out_of_range_unittest.cc | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/src/bin/dhcp4/tests/out_of_range_unittest.cc b/src/bin/dhcp4/tests/out_of_range_unittest.cc new file mode 100644 index 0000000..a11cfe6 --- /dev/null +++ b/src/bin/dhcp4/tests/out_of_range_unittest.cc @@ -0,0 +1,534 @@ +// Copyright (C) 2015-2021 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 <asiolink/io_address.h> +#include <cc/data.h> +#include <dhcp/dhcp4.h> +#include <dhcp_ddns/ncr_msg.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/subnet_id.h> +#include <dhcp4/tests/dhcp4_test_utils.h> +#include <dhcp4/tests/dhcp4_client.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief Set of JSON configurations used throughout the out of range +/// tests. +/// +/// - Configuration 0 - Reference configuration, all tests start with the +/// server configured with this configuration: +/// - 1 subnet: 10.0.0.0/24 +/// - with one pool 10.0.0.10 - 10.0.0.100 +/// - 1 address reservation (fixed host) for HW address: +/// ff:ff:ff:ff:ff:01 +/// - 1 hostname reservation (dynamic host) for HW address: +/// dd:dd:dd:dd:dd:01, +/// - DDNS enabled +/// - Configuration 1 - same subnet, different pool +/// - Configuration 2 - same subnet, different pool, no reservations +/// - Configuration 3 - different subnet with reservations +/// - Configuration 4 - different subnet with no reservations +/// - Configuration 5 - same as reference, no reservations +/// +const char* OOR_CONFIGS[] = { +// Configuration 0 - reference configuration + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"valid-lifetime\": 600," + "\"subnet4\": [ { " + " \"subnet\": \"10.0.0.0/24\", " + " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]," + " \"reservations\": [ " + " {" + " \"hw-address\": \"ff:ff:ff:ff:ff:01\"," + " \"ip-address\": \"10.0.0.7\"" + " }," + " {" + " \"hw-address\": \"dd:dd:dd:dd:dd:01\"," + " \"hostname\": \"test.example.com\"" + " }" + " ]" + "} ]," + "\"dhcp-ddns\": {" + " \"enable-updates\": true," + " \"qualifying-suffix\": \"\"" + "}" + "}", + +// Configuration 1 - same subnet as reference, different pool + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"valid-lifetime\": 600," + "\"subnet4\": [ { " + " \"subnet\": \"10.0.0.0/24\", " + " \"pools\": [ { \"pool\": \"10.0.0.101-10.0.0.200\" } ]," + " \"reservations\": [ " + " {" + " \"hw-address\": \"ff:ff:ff:ff:ff:01\"," + " \"ip-address\": \"10.0.0.7\"" + " }," + " {" + " \"hw-address\": \"dd:dd:dd:dd:dd:01\"," + " \"hostname\": \"reserved.example.com\"" + " }" + " ]" + "} ]," + "\"dhcp-ddns\": {" + " \"enable-updates\": true," + " \"qualifying-suffix\": \"\"" + "}" + "}", + +// Configuration 2 - same subnet as reference, different pool, no reservations + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"valid-lifetime\": 600," + "\"subnet4\": [ { " + " \"subnet\": \"10.0.0.0/24\", " + " \"pools\": [ { \"pool\": \"10.0.0.101-10.0.0.200\" } ]," + "} ]," + "\"dhcp-ddns\": {" + " \"enable-updates\": true," + " \"qualifying-suffix\": \"\"" + "}" + "}", + + +// Configuration 3 - different subnet with reservations + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"valid-lifetime\": 600," + "\"subnet4\": [ { " + " \"subnet\": \"192.0.2.0/24\", " + " \"pools\": [ { \"pool\": \"192.0.2.101-192.0.2.200\" } ]," + " \"reservations\": [ " + " {" + " \"hw-address\": \"ff:ff:ff:ff:ff:01\"," + " \"ip-address\": \"192.0.2.7\"" + " }," + " {" + " \"hw-address\": \"dd:dd:dd:dd:dd:01\"," + " \"hostname\": \"reserved.example.com\"" + " }" + " ]" + "} ]," + "\"dhcp-ddns\": {" + " \"enable-updates\": true," + " \"qualifying-suffix\": \"\"" + "}" + "}", + +// Configuration 4 - different subnet with no reservations + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"valid-lifetime\": 600," + "\"subnet4\": [ { " + " \"subnet\": \"192.0.2.0/24\", " + " \"pools\": [ { \"pool\": \"192.0.2.101-192.0.2.200\" } ]" + "} ]," + "\"dhcp-ddns\": {" + " \"enable-updates\": true," + " \"qualifying-suffix\": \"\"" + "}" + "}", + +// Configuration 5 - same as reference, no reservations + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"valid-lifetime\": 600," + "\"subnet4\": [ { " + " \"subnet\": \"10.0.0.0/24\", " + " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]" + "} ]," + "\"dhcp-ddns\": {" + " \"enable-updates\": true," + " \"qualifying-suffix\": \"\"" + "}" + "}", + +}; + +/// @brief Enum for indexing into the array of configurations. +/// These were created to make the test cases easier to follow. +enum CfgIndex { + REF_CFG = 0, + DIFF_POOL, + DIFF_POOL_NO_HR, + DIFF_SUBNET, + DIFF_SUBNET_NO_HR, + NO_HR +}; + +/// @brief Enum for specifying expected response to client renewal attempt +enum RenewOutcome { + DOES_RENEW, + DOES_NOT_RENEW, + DOES_NOT_NAK +}; + +/// @brief Enum for specifying expected response to client release attempt +enum ReleaseOutcome { + DOES_RELEASE, + DOES_NOT_RELEASE +}; + +/// @brief Test fixture class for testing various exchanges when the client's +/// leased address is out of range due to configuration changes. +class OutOfRangeTest : public Dhcpv4SrvTest { +public: + D2ClientMgr& d2_mgr_; + + /// @brief Constructor. + /// + /// Sets up fake interfaces. + OutOfRangeTest() + : Dhcpv4SrvTest(), + d2_mgr_(CfgMgr::instance().getD2ClientMgr()), + iface_mgr_test_config_(true) { + } + + /// @brief Destructor. + /// + /// Cleans up statistics after the test. + ~OutOfRangeTest() { + } + + void configure(const std::string& config, Dhcp4Client& client) { + NakedDhcpv4Srv& server = *client.getServer(); + ASSERT_NO_FATAL_FAILURE(Dhcpv4SrvTest::configure(config, server)); + if (d2_mgr_.ddnsEnabled()) { + ASSERT_NO_THROW(server.startD2()); + } + } + + /// @briefVerify that a NameChangeRequest has been queued + /// + /// Checks that a NCR of a given type (ADD or REMOVE) for a given + /// ip address has been queued. If the NCR exits, it is processed + /// off the queue. Note the function expects there to be 1 and only + /// 1 NCR queued. + /// + /// @param type - NCR type expected, either CHG_ADD or CHG_REMOVE + /// @param addr - string containing the ip address expected in the NCR + void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type, + const std::string& addr) { + ASSERT_TRUE(d2_mgr_.getQueueSize() > 0); + + isc::dhcp_ddns::NameChangeRequestPtr ncr; + ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0)); + ASSERT_TRUE(ncr); + + EXPECT_EQ(type, ncr->getChangeType()); + EXPECT_EQ(addr, ncr->getIpAddress()); + + // Process the message off the queue + ASSERT_NO_THROW(d2_mgr_.runReadyIO()); + } + + /// @brief Conducts a single out-of-range test scenario + /// + /// Each test cycles consists of the following two stages, the first is + /// a set-up stage during which the server is configured with an initial, + /// reference, configuration and a client then verifies that it can acquire + /// and renew a lease. The second stage verifies that the server, having + /// been reconfigured such that the original lease is now "out-of-range", + /// responds correctly to the same client first attempting to renew the + /// lease and then attempting to release the lease. + /// + /// The test also expects the configuration to enable DDNS, and verifies + /// that a DNS add is done during stage one, and a DNS remove is done + /// in stage two as part of the release request processing. + /// + /// @param cfg_idx - index of the "replacement" configuration used to + /// reconfigure the server during stage two + /// @param hwaddress - text value, if not empty, to use as the hardware + /// address. This is used for host reservation tests. + /// in client queries + /// @param expected_address - text value of the expected address. If not + /// empty the test will fail if the server responds with a different lease + /// address + /// @param renew_outcome - expected server reaction in response to the + /// client's stage two renewal attempt. + /// @param release_outcome - expected server reaction in response to the + /// client's stage two release attempt. Currently defaults to DOES_RELEASE + /// as no cases have been identified which do otherwise. + void oorRenewReleaseTest(enum CfgIndex cfg_idx, + const std::string& hwaddress, + const std::string& expected_address, + enum RenewOutcome renew_outcome, + enum ReleaseOutcome release_outcome = DOES_RELEASE); + + /// @brief Interface Manager's fake configuration control. + IfaceMgrTestConfig iface_mgr_test_config_; + +}; + +void +OutOfRangeTest::oorRenewReleaseTest(enum CfgIndex cfg_idx, + const std::string& hwaddress, + const std::string& expected_address, + enum RenewOutcome renew_outcome, + enum ReleaseOutcome release_outcome) { + // STAGE ONE: + + // Step 1 is to acquire the lease + Dhcp4Client client(Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(OOR_CONFIGS[REF_CFG], client); + + // Set the host name so DNS updates will be performed + client.includeHostname("test.example.com"); + + // Set the hw address if given one + if (!hwaddress.empty()) { + client.setHWAddress(hwaddress); + } + + // Acquire the lease via DORA + ASSERT_NO_THROW(client.doDORA()); + + // Make sure that the server responded. + ASSERT_TRUE(client.getContext().response_); + Pkt4Ptr resp = client.getContext().response_; + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType())); + + // Verify a lease was created + IOAddress leased_address = client.config_.lease_.addr_; + Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address); + ASSERT_TRUE(lease); + + // Check the expected address if given + if (!expected_address.empty()) { + ASSERT_EQ(leased_address, expected_address); + } + + // Verify that a DNS add was requested + verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, leased_address.toText()); + + // The address is still valid, let's verify the lease can be renewed + // Set the unicast destination address to indicate that it is a renewal. + client.setState(Dhcp4Client::RENEWING); + client.setDestAddress(IOAddress("10.0.0.1")); + ASSERT_NO_THROW(client.doRequest()); + + // Verify that we received an ACK to our renewal + resp = client.getContext().response_; + ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType())); + EXPECT_EQ(0, d2_mgr_.getQueueSize()); + + // STAGE TWO: + + // Now reconfigure which should render our leased address out-of-range + configure(OOR_CONFIGS[cfg_idx], client); + + // Try to renew after the configuration change.. + ASSERT_NO_THROW(client.doRequest()); + resp = client.getContext().response_; + + switch(renew_outcome) { + case DOES_RENEW: + ASSERT_TRUE(resp); + ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType())); + break; + case DOES_NOT_RENEW: + ASSERT_TRUE(resp); + ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType())); + break; + case DOES_NOT_NAK: + ASSERT_FALSE(resp); + break; + } + + // Verify that the lease still exists in the database as it has not + // been explicitly released. + lease = LeaseMgrFactory::instance().getLease4(leased_address); + ASSERT_TRUE(lease); + + // Recreate the client lease info, a preceding NAK will have wiped it out + client.createLease(leased_address, lease->valid_lft_); + + // Send the release. The server should remove it from the DB and + // request DNS remove. + ASSERT_NO_THROW(client.doRelease()); + + lease = LeaseMgrFactory::instance().getLease4(leased_address); + + if (release_outcome == DOES_RELEASE) { + EXPECT_FALSE(lease); + // Verify the DNS remove was queued. + verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, + leased_address.toText()); + } else { + // Lease should still exist, and no NCR should be queued. + EXPECT_TRUE(lease); + EXPECT_EQ(0, d2_mgr_.getQueueSize()); + } +} + + +// Verifies that once-valid lease, whose address is no longer +// within the subnet's pool: +// +// a: Is NAKed upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, dynamicOutOfPool) { + + std::string hwaddress = ""; + std::string expected_address = ""; + + oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address, + DOES_NOT_NAK); + +} + +// Verifies that once-valid lease whose address is no longer +// within any configured subnet: +// +// a: Is NAKed upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, dynamicOutOfSubnet) { + + std::string hwaddress = ""; + std::string expected_address = ""; + + oorRenewReleaseTest(DIFF_SUBNET, hwaddress, expected_address, + DOES_NOT_RENEW); +} + +// Test verifies that once-valid dynamic address host reservation, +// whose address is no longer within the subnet's pool: +// +// a: Is NAKed upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, dynamicHostOutOfPool) { + std::string hwaddress = "dd:dd:dd:dd:dd:01"; + std::string expected_address = ""; + + oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address, DOES_NOT_NAK); +} + +// Test verifies that once-valid dynamic address host reservation, +// whose address is no longer within any configured subnet: +// +// a: Is NAKed upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, dynamicHostOutOfSubnet) { + std::string hwaddress = "dd:dd:dd:dd:dd:01"; + std::string expected_address = ""; + + oorRenewReleaseTest(DIFF_SUBNET, hwaddress, expected_address, + DOES_NOT_RENEW); +} + +// Test verifies that once-valid dynamic address host reservation, +// whose address is within the configured subnet and pool, but whose +// reservation has been removed: +// +// a: Is allowed to renew +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, dynamicHostReservationRemoved) { + Dhcp4Client client(Dhcp4Client::SELECTING); + + std::string hwaddress = "dd:dd:dd:dd:dd:01"; + std::string expected_address = ""; + + oorRenewReleaseTest(NO_HR, hwaddress, expected_address, DOES_RENEW); +} + +// Test verifies that once-valid dynamic address host reservation, +// whose address is no longer within any configured subnet, and which +// no longer has reservation defined: +// +// a: Is NAKed upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, dynamicHostOutOfSubnetReservationRemoved) { + Dhcp4Client client(Dhcp4Client::SELECTING); + + std::string hwaddress = "dd:dd:dd:dd:dd:01"; + std::string expected_address = ""; + + oorRenewReleaseTest(DIFF_SUBNET_NO_HR, hwaddress, expected_address, + DOES_NOT_RENEW); +} + +// Test verifies that once-valid in-subnet fixed-address host reservation, +// after the subnet pool changes: +// +// a: Is NAK'd upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, fixedHostOutOfSubnet) { + std::string hwaddress = "ff:ff:ff:ff:ff:01"; + std::string expected_address = "10.0.0.7"; + + oorRenewReleaseTest(DIFF_SUBNET, hwaddress, expected_address, + DOES_NOT_RENEW); +} + + +// Test verifies that once-valid in-subnet fixed-address host reservation, +// after the subnet pool is changed: +// +// a: Is ACK'd upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, fixedHostDifferentPool) { + std::string hwaddress = "ff:ff:ff:ff:ff:01"; + std::string expected_address = "10.0.0.7"; + + oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address, DOES_RENEW); +} + +// Test verifies that once-valid in-subnet fixed-address host reservation, +// whose reservation has been removed from the configuration +// +// a: Is NAK'd upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, fixedHostReservationRemoved) { + std::string hwaddress = "ff:ff:ff:ff:ff:01"; + std::string expected_address = "10.0.0.7"; + + oorRenewReleaseTest(NO_HR, hwaddress, expected_address, DOES_NOT_NAK); +} + +// Test verifies that once-valid fixed address host reservation, +// whose address is no longer within any configured subnet +// +// a: Is NAKed upon a renewal attempt +// b: Is released properly upon release, including DNS removal +// +TEST_F(OutOfRangeTest, fixedHostOutOfSubnetReservationRemoved) { + std::string hwaddress = "ff:ff:ff:ff:ff:01"; + std::string expected_address = "10.0.0.7"; + + oorRenewReleaseTest(DIFF_SUBNET_NO_HR, hwaddress, expected_address, + DOES_NOT_RENEW); +} + +} // end of anonymous namespace |