From: Marcin Siodelski Date: Wed, 15 Jun 2016 12:08:32 +0000 (+0200) Subject: [4321] Extended host tests for multiple IAs. X-Git-Tag: trac4551_base~29^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=de5f1831edda1f09050610058d798a909a82aa55;p=thirdparty%2Fkea.git [4321] Extended host tests for multiple IAs. Tests now use common function to verify the server's response. Also, improved commentary in the tests. --- diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc index b3cc4e6543..9fa894a19e 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -495,8 +495,10 @@ Dhcp6Client::doRenew() { appendFQDN(); context_.query_ = query; + sendMsg(context_.query_); context_.response_ = receiveOneMsg(); + // Apply configuration only if the server has responded. if (context_.response_) { config_.clear(); @@ -566,11 +568,6 @@ void Dhcp6Client::generateIAFromLeases(const Pkt6Ptr& query) { /// @todo: add support for IAPREFIX here. -/* if (!use_na_) { - // If we're told to not use IA_NA at all, there's nothing to be done here - return; - } */ - for (std::vector::const_iterator lease = config_.leases_.begin(); lease != config_.leases_.end(); ++lease) { if (lease->type_ != Lease::TYPE_NA) { @@ -701,6 +698,18 @@ Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address) const { return (!leases.empty()); } +bool +Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address, + const uint32_t iaid) const { + std::vector leases = getLeasesByAddress(address); + BOOST_FOREACH(const Lease6& lease, leases) { + if (lease.iaid_ == iaid) { + return (true); + } + } + return (false); +} + bool Dhcp6Client::hasLeaseForAddressRange(const asiolink::IOAddress& first, const asiolink::IOAddress& last) const { @@ -708,6 +717,19 @@ Dhcp6Client::hasLeaseForAddressRange(const asiolink::IOAddress& first, return (!leases.empty()); } +bool +Dhcp6Client:: +hasLeaseWithZeroLifetimeForAddress(const asiolink::IOAddress& address) const { + std::vector leases = getLeasesByAddress(address); + BOOST_FOREACH(const Lease6& lease, leases) { + if ((lease.preferred_lft_ == 0) && (lease.valid_lft_ == 0)) { + return (true); + } + } + return (false); +} + + bool Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const { @@ -720,6 +742,20 @@ Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix, return (false); } +bool +Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix, + const uint8_t prefix_len, + const uint32_t iaid) const { + std::vector leases = getLeasesByAddress(prefix); + BOOST_FOREACH(const Lease6& lease, leases) { + if ((lease.prefixlen_ == prefix_len) && + (lease.iaid_ == iaid)) { + return (true); + } + } + return (false); +} + bool Dhcp6Client::hasLeaseForPrefixPool(const asiolink::IOAddress& prefix, const uint8_t prefix_len, @@ -729,6 +765,19 @@ Dhcp6Client::hasLeaseForPrefixPool(const asiolink::IOAddress& prefix, return (!leases.empty()); } +bool +Dhcp6Client::hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix, + const uint8_t prefix_len) const { + std::vector leases = getLeasesByAddress(prefix); + BOOST_FOREACH(const Lease6& lease, leases) { + if ((lease.prefixlen_ == prefix_len) && (lease.preferred_lft_ == 0) && + (lease.valid_lft_ == 0)) { + return (true); + } + } + return (false); +} + uint16_t Dhcp6Client::getStatusCode(const uint32_t iaid) const { @@ -797,12 +846,14 @@ Dhcp6Client::sendMsg(const Pkt6Ptr& msg) { } // Repack the message to simulate wire-data parsing. msg->pack(); + Pkt6Ptr msg_copy(new Pkt6(static_cast (msg->getBuffer().getData()), msg->getBuffer().getLength())); msg_copy->setRemoteAddr(link_local_); msg_copy->setLocalAddr(dest_addr_); msg_copy->setIface(iface_name_); + srv_->fakeReceive(msg_copy); srv_->run(); } diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h index d2076d84a7..614be2294f 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.h +++ b/src/bin/dhcp6/tests/dhcp6_client.h @@ -396,6 +396,14 @@ public: /// @return true if client has lease for the address, false otherwise. bool hasLeaseForAddress(const asiolink::IOAddress& address) const; + /// @brief Checks if client has lease for the specified address in the + /// IA_NA identified by IAID. + /// + /// @param address Address for which lease should be found. + /// @param iaid IAID of the IA_NA in which the lease is expected. + bool hasLeaseForAddress(const asiolink::IOAddress& address, + const uint32_t iaid) const; + /// @brief Checks if client has a lease for an address within range. /// /// @param first Lower bound of the address range. @@ -406,6 +414,14 @@ public: bool hasLeaseForAddressRange(const asiolink::IOAddress& first, const asiolink::IOAddress& last) const; + /// @brief Checks if client has a lease with zero lifetimes for the + /// specified address. + /// + /// @param address Address for which lease should be found. + /// + /// @return true if client has a lease, false otherwise. + bool hasLeaseWithZeroLifetimeForAddress(const asiolink::IOAddress& address) const; + /// @brief Checks if client has a lease for a prefix. /// /// @param prefix Prefix. @@ -416,6 +432,16 @@ public: bool hasLeaseForPrefix(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const; + /// @brief Checks if client as a lease for prefix in the IA_PD identified + /// by specified IAID. + /// + /// @param prefix Prefix. + /// @param prefix_len Prefix length. + /// @param iaid IAID of the IA_PD in which the lease is expected. + bool hasLeaseForPrefix(const asiolink::IOAddress& prefix, + const uint8_t prefix_len, + const uint32_t iaid) const; + /// @brief Checks if client has a lease belonging to a prefix pool. /// /// @param prefix Pool prefix. @@ -428,6 +454,9 @@ public: const uint8_t prefix_len, const uint8_t delegated_len) const; + bool hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix, + const uint8_t prefix_len) const; + /// @brief Returns the value of the global status code for the last /// transaction. uint16_t getStatusCode() const { diff --git a/src/bin/dhcp6/tests/host_unittest.cc b/src/bin/dhcp6/tests/host_unittest.cc index f5efbcf32b..44e06adfbe 100644 --- a/src/bin/dhcp6/tests/host_unittest.cc +++ b/src/bin/dhcp6/tests/host_unittest.cc @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -111,53 +113,129 @@ const char* CONFIGS[] = { " \"ip-addresses\": [ \"2001:db8:1::2\" ]" " } ]" " } ]" - "}", - - // Configuration 3: - "{ " - "\"interfaces-config\": {" - " \"interfaces\": [ \"*\" ]" - "}," - "\"valid-lifetime\": 4000, " - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"subnet6\": [ " - " { " - " \"subnet\": \"2001:db8:1::/48\", " - " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ]," - " \"interface\" : \"eth0\"," - " \"reservations\": [" - " {" - " \"duid\": \"01:02:03:04\"," - " \"ip-addresses\": [ \"2001:db8:1:1::1\", \"2001:db8:1:1::2\"," - "\"2001:db8:1:1::3\" ]," - " \"prefixes\": [ \"3000:1:1::/32\", \"3000:1:2::/32\"," - "\"3000:1:3::/32\" ]" - " } ]" - " } ]" "}" }; -class Reservation { +/// @brief Generic wrapper to provide strongly typed values. +/// +/// In many cases, the test fixture class methods require providing many +/// paramaters, of which some ore optional. Some of the parameters may also +/// be implicitly converted to other types. Non-careful test implementer +/// may often "shift by one" or swap two values on the arguments list, which +/// will be accepted by the compiler but will result in troubles running the +/// function. Sometimes it takes non trivial amount of debugging to find out +/// why the particular function fails until we find that the arguments were +/// swapped or shifted. In addition, the use of classes wrapping simple types +/// results in better readbility of the test code. +/// +/// @tparam ValueType Type of the wrapped value. +template +struct SpecializedTypeWrapper { + + /// @brief Constructor + /// + /// @param value Wrapped value + explicit SpecializedTypeWrapper(const ValueType& value) + : value_(value) { } + + /// @brief Operator returning a wrapped value. + operator ValueType () const { + return (value_); + } + + /// @brief Wrapped value. + ValueType value_; +}; + + +/// @brief Class representing strongly typed IAID. +struct IAID : public SpecializedTypeWrapper { + /// @brief Constructor + /// + /// @param iaid IAID. + explicit IAID(const uint32_t iaid) + : SpecializedTypeWrapper(iaid) { } +}; + +/// @brief Class representing stronly typed value for strict IAID checks. +/// +/// Strict IAID checks are used to verify that the particular address has been +/// assign to a specific IA. In many cases we don't check that because it may +/// not be possible to predict to which IA the specific lease will be assigned. +struct StrictIAIDChecking : public SpecializedTypeWrapper { + /// @brief Constructor. + /// + /// @param strict_check Boolean value indicating if strict checking should + /// be performed. + explicit StrictIAIDChecking(const bool strict_check) + : SpecializedTypeWrapper(strict_check) { } + + /// @brief Convenience function returning an object indicating that strict + /// checks should be performed. + static const StrictIAIDChecking YES() { + static StrictIAIDChecking strict_check(true); + return (strict_check); + } + + /// @brief Convenience function returning an object indicating that strict + /// checks should not be performed. + static StrictIAIDChecking NO() { + static StrictIAIDChecking strict_check(false); + return (strict_check); + } + +}; + +/// @brief Base class representing leases and hints coveyed within IAs. +/// +/// This is a base class for @ref Reservation and @ref Hint classes. +class IAResource { public: - Reservation(const std::string& resource); + /// @brief Constructor. + /// + /// Creates a resource instance from a string. The string is provided in + /// one of the following formats: + /// - "2001:db8:1::1" for addresses. + /// - "2001:db8::/64" for prefixes. + /// - "::/0" to mark lease or hint as unspecified (empty). + IAResource(const std::string& resource); + + /// @brief Checks if resource is unspecified. + /// + /// @return true if resource is unspecified. bool isEmpty() const; + /// @brief Checks if resource is a prefix. + /// + /// @return true if resource is a prefix. bool isPrefix() const; - static const Reservation& UNSPEC(); + /// @brief Returns prefix or address (depending on resource type). + const IOAddress& getPrefix() const; + /// @brief Returns prefix length. + uint8_t getPrefixLen() const; + + /// @brief Returns textual representation of the resource. + std::string toText() const; + + /// @brief Operator converting resource to string. operator std::string() const; private: + + /// @brief Holds prefix or address (depending on resource type). IOAddress prefix_; + + /// @brief Holds prefix length (for prefixes). uint8_t prefix_len_; + }; -Reservation::Reservation(const std::string& resource) +IAResource::IAResource(const std::string& resource) : prefix_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_len_(0) { + // Check if resource is a prefix, i.e. search for slash. size_t slash_pos = resource.find("/"); if ((slash_pos != std::string::npos) && (slash_pos < resource.size() - 1)) { prefix_len_ = boost::lexical_cast(resource.substr(slash_pos + 1)); @@ -166,21 +244,27 @@ Reservation::Reservation(const std::string& resource) } bool -Reservation::isEmpty() const { - return (prefix_.isV6Zero()); +IAResource::isEmpty() const { + return (prefix_.isV6Zero() && (prefix_len_ == 0)); } bool -Reservation::isPrefix() const { +IAResource::isPrefix() const { return (!isEmpty() && (prefix_len_ > 0)); } -const Reservation& Reservation::UNSPEC() { - static Reservation unspec("::/0"); - return (unspec); +const IOAddress& +IAResource::getPrefix() const { + return (prefix_); } -Reservation::operator std::string() const { +uint8_t +IAResource::getPrefixLen() const { + return (prefix_len_); +} + +std::string +IAResource::toText() const { std::ostringstream s; s << "\"" << prefix_; if (prefix_len_ > 0) { @@ -190,6 +274,67 @@ Reservation::operator std::string() const { return (s.str()); } +IAResource::operator std::string() const { + return (toText()); +} + +/// @brief Address or prefix reservation. +class Reservation : public IAResource { +public: + + /// @brief Constructor + /// + /// @param resource Resource string as for @ref IAResource constructor. + Reservation(const std::string& resource) + : IAResource(resource) { + } + + /// @brief Convenience function returning unspecified resource. + static const Reservation& UNSPEC(); + +}; + +const Reservation& Reservation::UNSPEC() { + static Reservation unspec("::/0"); + return (unspec); +} + +/// @brief Address or prefix hint. +class Hint : public IAResource { +public: + + /// @brief Constructor. + /// + /// Includes IAID of an IA in which hint should be placed. + /// + /// @param iaid IAID of IA in which hint should be placed. + /// @param resource Resource string as for @ref IAResource constructor. + Hint(const IAID& iaid, const std::string& resource) + : IAResource(resource), iaid_(iaid.value_) { + } + + /// @brief Returns IAID as 32-bit unsigned value. + uint32_t getIAID() const; + + /// @brief Convenience function returning unspecified hint. + static const Hint& UNSPEC(); + +private: + + /// @brief Holds IAID as 32-bit unsigned integer. + uint32_t iaid_; +}; + +uint32_t +Hint::getIAID() const { + return (iaid_); +} + +const Hint& Hint::UNSPEC() { + static Hint unspec(IAID(0), "::/0"); + return (unspec); +} + /// @brief Test fixture class for testing host reservations class HostTest : public Dhcpv6SrvTest { public: @@ -200,7 +345,10 @@ public: /// Sets up fake interfaces. HostTest() : Dhcpv6SrvTest(), - iface_mgr_test_config_(true) { + iface_mgr_test_config_(true), + client_(), + do_solicit_(boost::bind(&Dhcp6Client::doSolicit, &client_, true)), + do_solicit_request_(boost::bind(&Dhcp6Client::doSARR, &client_)) { } /// @brief Verifies that the reservation is retrieved by the server @@ -235,10 +383,113 @@ public: EXPECT_EQ(exp_ip_address, lease_client.addr_.toText()); } + /// @brief Checks if the client obtained lease for specified reservation. + /// + /// @param r Reservation. + /// @param [out] address_count This value is incremented if the client + /// obtained the address lease. + /// @param [out] prefix_count This value is incremented if the client + /// obtained the prefix lease. + void testLeaseForIA(const Reservation& r, size_t& address_count, + size_t& prefix_count); + + /// @brief Checks if the client obtined lease for specified hint. + /// + /// The hint belongs to a specific IA (identified by IAID) and is expected + /// to be returned in this IA by the server. + /// + /// @param h Hint. + void testLeaseForIA(const Hint& h); + + /// @brief A generic test for assigning multiple reservations to a single + /// client sending multiple IAs. + /// + /// This test creates a server configuration which includes one subnet, + /// address pool of "2001:db8:1::1 - 2001:db8:1::10" and the prefix pool + /// of 3001::/32, with delegated prefix length of 64. The configuration + /// may include between 0 and 6 reservations for a client with DUID of + /// "01:02:03:04". + /// + /// The test performs an exchange with a server, typically 4-way exchange + /// or Solicit-Advertise. The client's message includes 3 IA_NAs (with + /// IAIDs in range of 1..3) and 3 IA_PDs (with IAIDs in range of 4..6). + /// + /// It is possible to specify hints for selected IAs. The IA is in such + /// case identified by the IAID. + /// + /// The test expects that the server returns 6 leases. It checks if those + /// leases contain all reserved addresses and prefixes specified as + /// arguments of the test. If the number of IAs is greater than the + /// number of reservations it checks that for the remaining IAs the + /// leases from dynamic pools are assigned. + /// + /// The strict_iaid_check flag controlls whether the test should verify + /// that the address or prefix specified as a hint is assigned by the + /// server to the IA in which the hint was placed by the client. + /// + /// @param client_operation Dhcp6Client function to be executed to + /// perform an exchange with the server. + /// @param r1 Reservation 1. Default value is "unspecified", in which + /// case the reservation will not be created. + /// @param r2 Reservation 2. + /// @param r3 Reservation 3. + /// @param r4 Reservation 4. + /// @param r5 Reservation 5. + /// @param r6 Reservation 6. + /// @param strict_iaid_check Indicates if the test should check if the + /// hints sent by the client have been allocated by the server to the + /// particular IAs. Default value is NO (no checks). + /// @param h1 Hint 1. Default value is "unspecified", in which case the + /// hint will not be included. + /// @param h2 Hint 2. + /// @param h3 Hint 3. + /// @param h4 Hint 4. + /// @param h5 Hint 5. + /// @param h6 Hint 6. + void testMultipleIAs(const boost::function& client_operation, + const Reservation& r1 = Reservation::UNSPEC(), + const Reservation& r2 = Reservation::UNSPEC(), + const Reservation& r3 = Reservation::UNSPEC(), + const Reservation& r4 = Reservation::UNSPEC(), + const Reservation& r5 = Reservation::UNSPEC(), + const Reservation& r6 = Reservation::UNSPEC(), + const StrictIAIDChecking& strict_iaid_check = + StrictIAIDChecking::NO(), + const Hint& h1 = Hint::UNSPEC(), + const Hint& h2 = Hint::UNSPEC(), + const Hint& h3 = Hint::UNSPEC(), + const Hint& h4 = Hint::UNSPEC(), + const Hint& h5 = Hint::UNSPEC(), + const Hint& h6 = Hint::UNSPEC()); + + /// @brief Checks if specified reservation is for address or prefix and + /// stores reservation in the textual format on one of the lists. + /// + /// @param [out] address_list Reference to a list containing address + /// reservations. + /// @param [out] prefix_list Reference to a list containing prefix + /// reservations. static void storeReservation(const Reservation& r, std::list& address_list, std::list& prefix_list); + /// @brief Creates configuration for testing processing multiple IAs. + /// + /// This method creates a server configuration which includes one subnet, + /// address pool of "2001:db8:1::1 - 2001:db8:1::10" and the prefix pool + /// of 3001::/32, with delegated prefix length of 64. The configuration + /// may include between 0 and 6 reservations for a client with DUID of + /// "01:02:03:04". + /// + /// @param r1 Reservation 1. Default value is "unspecified" in which case + /// the reservation will not be included in the configruation. + /// @param r2 Reservation 2. + /// @param r3 Reservation 3. + /// @param r4 Reservation 4. + /// @param r5 Reservation 5. + /// @param r6 Reservation 6. + /// + /// @return Text containing server configuration in JSON format. std::string configString(const DUID& duid, const Reservation& r1 = Reservation::UNSPEC(), const Reservation& r2 = Reservation::UNSPEC(), @@ -247,10 +498,132 @@ public: const Reservation& r5 = Reservation::UNSPEC(), const Reservation& r6 = Reservation::UNSPEC()) const; + /// @brief Configures client to include hint. + /// + /// @param client Reference to a client. + /// @param hint Const reference to an object holding the hint. + static void requestIA(Dhcp6Client& client, const Hint& hint); + + /// @brief Configures client to include 6 IAs without hints. + /// + /// This method configures the client to include 3 IA_NAs and + /// 3 IA_PDs. + /// + /// @param client Reference to a client. + static void requestEmptyIAs(Dhcp6Client& client); + /// @brief Interface Manager's fake configuration control. IfaceMgrTestConfig iface_mgr_test_config_; + + /// @brief Instance of the common DHCPv6 client. + Dhcp6Client client_; + + /// @brief Pointer to the Dhcp6Client::doSolicit method. + boost::function do_solicit_; + + /// @brief Pointer to the Dhcp6Client::doSARR method. + boost::function do_solicit_request_; }; +void +HostTest::testLeaseForIA(const Reservation& r, size_t& address_count, + size_t& prefix_count) { + if (r.isPrefix()) { + ++prefix_count; + EXPECT_TRUE(client_.hasLeaseForPrefix(r.getPrefix(), r.getPrefixLen())); + + } else if (!r.isEmpty()) { + ++address_count; + EXPECT_TRUE(client_.hasLeaseForAddress(r.getPrefix())); + } +} + +void +HostTest::testLeaseForIA(const Hint& h) { + if (h.isPrefix()) { + EXPECT_TRUE(client_.hasLeaseForPrefix(h.getPrefix(), h.getPrefixLen(), + h.getIAID())) + << "there is no lease for prefix " << h.toText() + << " and IAID = " << h.getIAID(); + + } else if (!h.isEmpty()) { + EXPECT_TRUE(client_.hasLeaseForAddress(h.getPrefix(), h.getIAID())) + << "there is no lease for address " << h.toText() + << " and IAID = " << h.getIAID(); + } +} + +void +HostTest::testMultipleIAs(const boost::function& client_operation, + const Reservation& r1, const Reservation& r2, + const Reservation& r3, const Reservation& r4, + const Reservation& r5, const Reservation& r6, + const StrictIAIDChecking& strict_iaid_check, + const Hint& h1, const Hint& h2 , + const Hint& h3, const Hint& h4, + const Hint& h5, const Hint& h6) { + client_.setDUID("01:02:03:04"); + + /// Create configuration with 0 to 6 reservations. + const std::string c = configString(*client_.getDuid(), r1, r2, r3, + r4, r5, r6); + + ASSERT_NO_THROW(configure(c, *client_.getServer())); + + // First includes all IAs. They are initially empty. + requestEmptyIAs(client_); + + // For each specified hint, include it in the respective IA. Hints + // which are "unspecified" will not be included. + requestIA(client_, h1); + requestIA(client_, h2); + requestIA(client_, h3); + requestIA(client_, h4); + requestIA(client_, h5); + requestIA(client_, h6); + + + // Send Solicit and require that the client saves received configuration + // so as we can test that advertised configuration is correct. + ASSERT_NO_THROW(client_operation()); + + ASSERT_EQ(6, client_.getLeaseNum()); + + // Count reserved addresses and prefixes assigned from reservations. + size_t address_count = 0; + size_t prefix_count = 0; + + testLeaseForIA(r1, address_count, prefix_count); + testLeaseForIA(r2, address_count, prefix_count); + testLeaseForIA(r3, address_count, prefix_count); + testLeaseForIA(r4, address_count, prefix_count); + testLeaseForIA(r5, address_count, prefix_count); + testLeaseForIA(r6, address_count, prefix_count); + + // Get all addresses assigned from the dynamic pool (not reserved). + std::vector leases = + client_.getLeasesByAddressRange(IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::10")); + // There are 3 IA_NAs and for a few we have assigned reserved addresses. + // The remaining ones should be assigned from the dynamic pool. + ASSERT_EQ(3 - address_count, leases.size()); + + // Get all prefixes assigned from the dynamic pool (not reserved). + leases = client_.getLeasesByPrefixPool(IOAddress("3001::"), 32, 64); + ASSERT_EQ(3 - prefix_count, leases.size()); + + // Check that the hints have been allocated to respective IAs. + if (strict_iaid_check) { + testLeaseForIA(h1); + testLeaseForIA(h2); + testLeaseForIA(h3); + testLeaseForIA(h4); + testLeaseForIA(h5); + testLeaseForIA(h6); + } +} + + void HostTest::storeReservation(const Reservation& r, std::list& address_list, @@ -295,6 +668,7 @@ HostTest::configString(const DUID& duid, " \"delegated-len\": 64 } ]," " \"interface\" : \"eth0\""; + // Create reservations. if (!address_list.empty() || !prefix_list.empty()) { s << "," " \"reservations\": [" @@ -326,6 +700,29 @@ HostTest::configString(const DUID& duid, return (s.str()); } +void +HostTest::requestIA(Dhcp6Client& client, const Hint& hint) { + if ((hint.getIAID() != 0) && !hint.isEmpty()) { + if (hint.isPrefix()) { + client.requestPrefix(hint.getIAID(), hint.getPrefixLen(), + hint.getPrefix()); + } else { + client.requestAddress(hint.getIAID(), hint.getPrefix()); + } + } +} + +void +HostTest::requestEmptyIAs(Dhcp6Client& client) { + // Create IAs with IAIDs between 1 and 6. + client.requestAddress(1); + client.requestAddress(2); + client.requestAddress(3); + client.requestPrefix(4); + client.requestPrefix(5); + client.requestPrefix(6); +} + // Test basic SARR scenarios against a server configured with one subnet // containing two reservations. One reservation with a hostname, one @@ -546,107 +943,258 @@ TEST_F(HostTest, hostIdentifiersOrder) { testReservationByIdentifier(client, 2, "2001:db8:1::2"); } -TEST_F(HostTest, reservationMultipleIASolicit) { - Dhcp6Client client; - client.setDUID("01:02:03:04"); - - const std::string c = configString(*client.getDuid(), - Reservation("2001:db8:1:1::1"), - Reservation("2001:db8:1:1::2"), - Reservation("2001:db8:1:1::3"), - Reservation("3000:1:1::/32"), - Reservation("3000:1:2::/32"), - Reservation("3000:1:3::/32")); - - ASSERT_NO_THROW(configure(c, *client.getServer())); - - client.requestAddress(1234); - client.requestAddress(2345); - client.requestAddress(3456); - client.requestPrefix(5678); - client.requestPrefix(6789); - client.requestPrefix(7890); - - // Send Solicit and require that the client saves received configuration - // so as we can test that advertised configuration is correct. - ASSERT_NO_THROW(client.doSolicit(true)); - - ASSERT_EQ(6, client.getLeaseNum()); - - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::1"))); - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2"))); - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"))); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32)); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:2::"), 32)); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32)); +// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs +// without hints and the server should return those IAs with 3 reserved +// addresses and 3 reserved prefixes. +TEST_F(HostTest, multipleIAsSolicit) { + testMultipleIAs(do_solicit_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation("3000:1:3::/64")); } -TEST_F(HostTest, reservationMultipleIARequest) { - Dhcp6Client client; - client.setDUID("01:02:03:04"); - - const std::string c = configString(*client.getDuid(), - Reservation("2001:db8:1:1::1"), - Reservation("2001:db8:1:1::2"), - Reservation("2001:db8:1:1::3"), - Reservation("3000:1:1::/32"), - Reservation("3000:1:2::/32"), - Reservation("3000:1:3::/32")); - - ASSERT_NO_THROW(configure(c, *client.getServer())); - - client.requestAddress(1234); - client.requestAddress(2345); - client.requestAddress(3456); - client.requestPrefix(5678); - client.requestPrefix(6789); - client.requestPrefix(7890); - - ASSERT_NO_THROW(client.doSARR()); - - ASSERT_EQ(6, client.getLeaseNum()); - - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::1"))); - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2"))); - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"))); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32)); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:2::"), 32)); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32)); +// In this test the client performs 4-way exchange, sending 3 IA_NAs +// and 3 IA_PDs without hints. The server should return those IAs +// with 3 reserved addresses and 3 reserved prefixes. +TEST_F(HostTest, multipleIAsRequest) { + testMultipleIAs(do_solicit_request_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation("3000:1:3::/64")); } -TEST_F(HostTest, reservationAndDynamicIAs) { - Dhcp6Client client; - client.setDUID("01:02:03:04"); - - const std::string c = configString(*client.getDuid(), - Reservation("2001:db8:1:1::2"), - Reservation("2001:db8:1:1::3"), - Reservation("3000:1:1::/32"), - Reservation("3000:1:3::/32")); +// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs +// without hints. The server has 2 reservations for addresses and +// 2 reservations for prefixes for this client. The server should +// assign reserved addresses and prefixes to the client, and return +// them in 2 IA_NAs and 2 IA_PDs. For the remaining IA_NA and IA_PD +// the server should allocate address and prefix from a dynamic pools. +TEST_F(HostTest, staticAndDynamicIAs) { + testMultipleIAs(do_solicit_, + Reservation("2001:db8:1:1::2"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:3::/64")); +} - ASSERT_NO_THROW(configure(c, *client.getServer())); +// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs. +// The client includes an address hint for IAID = 1, a prefix length +// hint for the IAID = 5, and the prefix hint for IAID = 6. The hints +// match the reserved resources and should be allocated for the client. +TEST_F(HostTest, multipleIAsHintsForReservations) { + testMultipleIAs(do_solicit_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation("3000:1:3::/64"), + StrictIAIDChecking::NO(), + Hint(IAID(1), "2001:db8:1:1::2"), + Hint(IAID(5), "::/64"), + Hint(IAID(6), "3000:1:1::/64")); +} - client.requestAddress(1234); - client.requestAddress(2345); - client.requestAddress(3456); - client.requestPrefix(5678); - client.requestPrefix(6789); - client.requestPrefix(7890); +// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs. +// The client includes one address hint for IAID = 1 and one +// prefix hint for IAID = 6. The hints point to an address and prefix +// from the dynamic pools, but because the server has reservations +// for other addresses and prefixes outside the pool, the address +// and prefix specified as hint should not be allocated. Instead +// the server should allocate reserved leases. +TEST_F(HostTest, multipleIAsHintsInPool) { + testMultipleIAs(do_solicit_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation("3000:1:3::/64"), + StrictIAIDChecking::NO(), + Hint(IAID(1), "2001:db8:1::2"), + Hint(IAID(6), "3001::/64")); +} - // Send Solicit and require that the client saves received configuration - // so as we can test that advertised configuration is correct. - ASSERT_NO_THROW(client.doSolicit(true)); +// In this test, the client sends Solicit with 3 IA_NAs and 3 IA_PDs. +// The client includes one address hint for which the client has +// reservation, one prefix hint for which the client has reservation, +// one hint for an address from the dynamic pool and one hint for a +// prefix from a dynamic pool. The server has reservations for 2 +// addresses and 2 prefixes. The server should allocate reserved +// leases and address and prefix from a dynamic pool, which client +// included as hints. +TEST_F(HostTest, staticAndDynamicIAsHints) { + testMultipleIAs(do_solicit_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation::UNSPEC(), + Reservation::UNSPEC(), + StrictIAIDChecking::NO(), + Hint(IAID(1), "2001:db8:1::2"), + Hint(IAID(3), "2001:db8:1:1::1"), + Hint(IAID(5), "3001::/64"), + Hint(IAID(6), "3000::/64")); +} - ASSERT_EQ(6, client.getLeaseNum()); +// In this test, the client sends Solicit with 3 IA_NAs and 3 IA_PDs. +// The server has reservation for two addresses and two prefixes for +// this client. The client includes address hint in the third IA_NA +// and in the third IA_PD. The server should offer 2 addresses in the +// first two IA_NAs and 2 prefixes in the two IA_PDs. The server should +// respect hints provided within the 3rd IA_NA and 3rd IA_PD. The server +// wouldn't respect hints if they were provided within 1st or 2nd IA of +// a given type, because the server always tries to allocate the +// reserved leases in the first place. +TEST_F(HostTest, staticAndDynamicIAsHintsStrictIAIDCheck) { + testMultipleIAs(do_solicit_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation::UNSPEC(), + Reservation::UNSPEC(), + StrictIAIDChecking::YES(), + Hint(IAID(3), "2001:db8:1::5"), + Hint(IAID(6), "3001:0:0:10::/64")); +} - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2"))); - EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"))); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32)); - EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32)); +// In this test, the client performs 4-way exchange and includes 3 IA_NAs +// and 3 IA_PDs. The client provides no hints. The server has 3 address +// reservations and 3 prefix reservations for this client and allocates them +// as a result of 4-way exchange. The client then sends a Renew and the server +// should renew all leases allocated for the client during the 4-way exchange. +TEST_F(HostTest, multipleIAsRenew) { + // 4-way exchange + testMultipleIAs(do_solicit_request_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation("3000:1:3::/64")); + + // Renew + ASSERT_NO_THROW(client_.doRenew()); + + // Make sure that the client still has the same leases. + ASSERT_EQ(6, client_.getLeaseNum()); + + EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::1"))); + EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::2"))); + EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"))); + EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:1::"), 64)); + EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:2::"), 64)); + EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64)); +} - EXPECT_TRUE(client.hasLeaseForAddressRange(IOAddress("2001:db8:1::1"), - IOAddress("2001:db8:1::10"))); - EXPECT_TRUE(client.hasLeaseForPrefixPool(IOAddress("3001::"), 32, 64)); +// In this test, the client performs 4-way exchange and includes 3 IA_NAs and +// 3 IA_PDs and includes no hints. The server has reservations for 2 addresses +// and 2 prefixes for this client. The server allocates reserved leases and +// an additional address and prefix from the dynamic pools. The server is +// reconfigured to add 3rd address and 3rd prefix reservation for the client. +// The client sends a Renew and the server should renew existing leases and +// allocate newly reserved address and prefix, replacing the previously +// allocated dynamic leases. For both dynamically allocated leases, the +// server should return IAs with zero lifetimes. +TEST_F(HostTest, additionalReservationDuringRenew) { + // 4-way exchange to acquire 4 reserved leases and 2 dynamic leases. + testMultipleIAs(do_solicit_request_, + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64")); + + // The server must have not lease for the address and prefix for which + // we will later make reservations, because these are outside of the + // dynamic pool. + ASSERT_FALSE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"))); + ASSERT_FALSE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64)); + + // Retrieve leases from the dynamic pools and store them so as we can + // later check that they were returned with zero lifetimes when the + // reservations are added. + std::vector leases = + client_.getLeasesByAddressRange(IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::10")); + ASSERT_EQ(1, leases.size()); + IOAddress dynamic_address_lease = leases[0].addr_; + + leases = client_.getLeasesByPrefixPool(IOAddress("3001::"), 32, 64); + ASSERT_EQ(1, leases.size()); + IOAddress dynamic_prefix_lease = leases[0].addr_; + + // Add two additional reservations. + std::string c = configString(*client_.getDuid(), + Reservation("2001:db8:1:1::1"), + Reservation("2001:db8:1:1::2"), + Reservation("2001:db8:1:1::3"), + Reservation("3000:1:1::/64"), + Reservation("3000:1:2::/64"), + Reservation("3000:1:3::/64")); + + ASSERT_NO_THROW(configure(c, *client_.getServer())); + + // Client renews and includes all leases it currently has in the IAs. + ASSERT_NO_THROW(client_.doRenew()); + + // The expectation is that the server allocated two new reserved leases to + // the client and removed leases allocated from the dynamic pools. The + // number if leases in the server configuration should include those that + // are returned with zero lifetimes. Hence, the total number of leases + // should be equal to 6 + 2 = 8. + ASSERT_EQ(8, client_.getLeaseNum()); + + EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::1"))); + EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::2"))); + EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"))); + EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:1::"), 64)); + EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:2::"), 64)); + EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64)); + + // Make sure that the replaced leases have been returned with zero liftimes. + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(dynamic_address_lease)); + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(dynamic_prefix_lease, 64)); + + // Now let's test the scenario when all reservations are removed for this + // client. + c = configString(*client_.getDuid()); + + ASSERT_NO_THROW(configure(c, *client_.getServer())); + + // An attempt to renew should result in removing all allocated leases, + // because these leases are no longer reserved and they don't belong to the + // dynamic pools. + ASSERT_NO_THROW(client_.doRenew()); + + // The total number of leases should include removed leases and newly + // allocated once, i.e. 6 + 6 = 12. + ASSERT_EQ(12, client_.getLeaseNum()); + + // All removed leases should be returned with zero liftimes. + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1:1::1"))); + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1:1::2"))); + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1:1::3"))); + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000:1:1::"), 64)); + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000:1:2::"), 64)); + EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000:1:3::"), 64)); + + // Make sure that all address leases are within the dynamic pool range. + leases = client_.getLeasesByAddressRange(IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::10")); + EXPECT_EQ(3, leases.size()); + + // Make sure that all prefix leases are also within the dynamic pool range. + leases = client_.getLeasesByPrefixPool(IOAddress("3001::"), 32, 64); + EXPECT_EQ(3, leases.size()); } + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index a6b7fade69..c868e05e99 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -845,37 +845,60 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, // so the operation shouldn't be that expensive. Lease6Collection copy = existing_leases; - for (Lease6Collection::const_iterator candidate = copy.begin(); - candidate != copy.end(); ++candidate) { + BOOST_FOREACH(const Lease6Ptr& candidate, copy) { + // If we have reservation we should check if the reservation is for + // the candidate lease. If so, we simply accept the lease. + if (ctx.host_) { + if (candidate->type_ == Lease6::TYPE_NA) { + if (ctx.host_->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, + candidate->addr_))) { + continue; + } + } else { + if (ctx.host_->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, + candidate->addr_, + candidate->prefixlen_))) { + continue; + } + } + } + // The candidate address doesn't appear to be reserved for us. + // We have to make a bit more expensive operation here to retrieve + // the reservation for the candidate lease and see if it is + // reserved for someone else. ConstHostPtr host = HostMgr::instance().get6(ctx.subnet_->getID(), - (*candidate)->addr_); - - if (!host || (host == ctx.host_)) { - // Not reserved or reserved for us. That's ok, let's check - // the next lease. + candidate->addr_); + // If lease is not reserved to someone else, it means that it can + // be allocated to us from a dynamic pool, but we must check if + // this lease belongs to any pool. If it does, we can proceed to + // checking the next lease. + if (!host && ctx.subnet_->inPool(candidate->type_, candidate->addr_)) { continue; } - // Ok, we have a problem. This host has a lease that is reserved - // for someone else. We need to recover from this. - if (ctx.currentIA().type_ == Lease::TYPE_NA) { - LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE) - .arg((*candidate)->addr_.toText()).arg(ctx.duid_->toText()) - .arg(host->getIdentifierAsText()); - } else { - LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE) - .arg((*candidate)->addr_.toText()) - .arg(static_cast((*candidate)->prefixlen_)) - .arg(ctx.duid_->toText()) - .arg(host->getIdentifierAsText()); + if (host) { + // Ok, we have a problem. This host has a lease that is reserved + // for someone else. We need to recover from this. + if (ctx.currentIA().type_ == Lease::TYPE_NA) { + LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE) + .arg(candidate->addr_.toText()).arg(ctx.duid_->toText()) + .arg(host->getIdentifierAsText()); + } else { + LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE) + .arg(candidate->addr_.toText()) + .arg(static_cast(candidate->prefixlen_)) + .arg(ctx.duid_->toText()) + .arg(host->getIdentifierAsText()); + } } - // Remove this lease from LeaseMgr - LeaseMgrFactory::instance().deleteLease((*candidate)->addr_); + // Remove this lease from LeaseMgr as it is reserved to someone + // else or doesn't belong to a pool. + LeaseMgrFactory::instance().deleteLease(candidate->addr_); // Update DNS if needed. - queueNCR(CHG_REMOVE, *candidate); + queueNCR(CHG_REMOVE, candidate); // Need to decrease statistic for assigned addresses. StatsMgr::instance().addValue( @@ -890,10 +913,10 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, // should not interfere with it. // Add this to the list of removed leases. - ctx.currentIA().old_leases_.push_back(*candidate); + ctx.currentIA().old_leases_.push_back(candidate); // Let's remove this candidate from existing leases - removeLeases(existing_leases, (*candidate)->addr_); + removeLeases(existing_leases, candidate->addr_); } } diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h index 9e022946f8..d318eac931 100644 --- a/src/lib/dhcpsrv/lease.h +++ b/src/lib/dhcpsrv/lease.h @@ -484,7 +484,7 @@ struct Lease6 : public Lease { uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1, uint32_t t2, SubnetID subnet_id, const bool fqdn_fwd, const bool fqdn_rev, const std::string& hostname, - const HWAddrPtr& hwaddr = HWAddrPtr(), uint8_t prefixlen = 0); + const HWAddrPtr& hwaddr = HWAddrPtr(), uint8_t prefixlen = 128); /// @brief Constructor ///