From: Marcin Siodelski Date: Fri, 10 Jun 2016 09:37:13 +0000 (+0200) Subject: [4321] Added basic test for multiple v6 reservations. X-Git-Tag: trac4551_base~29^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2e3cedb832cedcfaad3f5881a74ac03db877833b;p=thirdparty%2Fkea.git [4321] Added basic test for multiple v6 reservations. --- diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc index 1aaed1b7ee..b3cc4e6543 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include +using namespace isc::asiolink; using namespace isc::dhcp; using namespace isc::dhcp::test; @@ -63,6 +65,25 @@ struct getLeasesByPropertyFun { } }; +/// @brief Returns leases which belong to specified pool. +/// +/// @param config DHCP client configuration structure holding leases. +/// @param pool Pool to which returned leases belong. +/// @param [out] leases A vector in which the function will store leases +/// found. +void getLeasesByPool(const Dhcp6Client::Configuration& config, + const Pool6& pool, std::vector& leases) { + for (std::vector::const_iterator lease = + config.leases_.begin(); lease != config.leases_.end(); + ++lease) { + // Check if prefix in range. + if (pool.inRange(lease->addr_)) { + // Found the matching lease. + leases.push_back(*lease); + } + } +} + }; // end of anonymous namespace namespace isc { @@ -392,7 +413,7 @@ Dhcp6Client::doSARR() { } void -Dhcp6Client::doSolicit() { +Dhcp6Client::doSolicit(const bool always_apply_config) { context_.query_ = createMsg(DHCPV6_SOLICIT); if (forced_server_id_) { context_.query_->addOption(forced_server_id_); @@ -412,9 +433,10 @@ Dhcp6Client::doSolicit() { context_.response_ = receiveOneMsg(); // If using Rapid Commit and the server has responded with Reply, - // let's apply received configuration. - if (use_rapid_commit_ && context_.response_ && - context_.response_->getType() == DHCPV6_REPLY) { + // let's apply received configuration. We also apply the configuration + // for the Advertise if instructed to do so. + if (context_.response_ && (always_apply_config || (use_rapid_commit_ && + context_.response_->getType() == DHCPV6_REPLY))) { config_.clear(); applyRcvdConfiguration(context_.response_); } @@ -648,6 +670,66 @@ Dhcp6Client::getLeasesWithZeroLifetime() const { return (leases); } +std::vector +Dhcp6Client::getLeasesByAddress(const IOAddress& address) const { + std::vector leases; + getLeasesByProperty(address, true, leases); + return (leases); +} + +std::vector +Dhcp6Client::getLeasesByAddressRange(const IOAddress& first, + const IOAddress& second) const { + std::vector leases; + getLeasesByPool(config_, Pool6(Lease::TYPE_NA, first, second), leases); + return (leases); +} + +std::vector +Dhcp6Client::getLeasesByPrefixPool(const asiolink::IOAddress& prefix, + const uint8_t prefix_len, + const uint8_t delegated_len) const { + std::vector leases; + getLeasesByPool(config_, Pool6(Lease::TYPE_PD, prefix, prefix_len, + delegated_len), leases); + return (leases); +} + +bool +Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address) const { + std::vector leases = getLeasesByAddress(address); + return (!leases.empty()); +} + +bool +Dhcp6Client::hasLeaseForAddressRange(const asiolink::IOAddress& first, + const asiolink::IOAddress& last) const { + std::vector leases = getLeasesByAddressRange(first, last); + return (!leases.empty()); +} + +bool +Dhcp6Client::hasLeaseForPrefix(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) { + return (true); + } + } + return (false); +} + +bool +Dhcp6Client::hasLeaseForPrefixPool(const asiolink::IOAddress& prefix, + const uint8_t prefix_len, + const uint8_t delegated_len) const { + std::vector leases = getLeasesByPrefixPool(prefix, prefix_len, + delegated_len); + return (!leases.empty()); +} + + uint16_t Dhcp6Client::getStatusCode(const uint32_t iaid) const { std::map::const_iterator status_code = diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h index a53b761c3a..d2076d84a7 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.h +++ b/src/bin/dhcp6/tests/dhcp6_client.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -226,12 +227,15 @@ public: /// i.e. sends a Solicit to the server and receives Advertise. It doesn't /// set the lease configuration in the @c config_. /// + /// @param always_apply_config Apply received configuration even if the + /// Advertise message is received. Default value is false. + /// /// @throw This function doesn't throw exceptions on its own, but it calls /// functions that are not exception safe, so it may throw exceptions if /// error occurs. /// /// @todo Perform sanity checks on returned messages. - void doSolicit(); + void doSolicit(const bool always_apply_config = false); /// @brief Sends a Renew to the server and receives the Reply. /// @@ -358,6 +362,72 @@ public: /// @brief Returns leases with zero lifetimes. std::vector getLeasesWithZeroLifetime() const; + /// @brief Returns leases by lease address/prefix. + /// + /// @param address Leased address. + /// + /// @return Vector containing leases for the specified address. + std::vector getLeasesByAddress(const asiolink::IOAddress& address) const; + + /// @brief Returns leases belonging to specified address range. + /// + /// @param first Lower bound of the address range. + /// @param second Upper bound of the address range. + /// + /// @return Vector containing leases belonging to specified address range. + std::vector getLeasesByAddressRange(const asiolink::IOAddress& first, + const asiolink::IOAddress& second) const; + + /// @brief Returns leases belonging to prefix pool. + /// + /// @param prefix Prefix of the pool. + /// @param prefix_len Prefix length. + /// @param delegated_len Delegated prefix length. + /// + /// @return Vector containing leases belonging to specified prefix pool. + std::vector getLeasesByPrefixPool(const asiolink::IOAddress& prefix, + const uint8_t prefix_len, + const uint8_t delegated_len) const; + + /// @brief Checks if client has lease for the specified address. + /// + /// @param address Address for which lease should be found. + /// + /// @return true if client has lease for the address, false otherwise. + bool hasLeaseForAddress(const asiolink::IOAddress& address) const; + + /// @brief Checks if client has a lease for an address within range. + /// + /// @param first Lower bound of the address range. + /// @param last Upper bound of the address range. + /// + /// @return true if client has lease for the address within the range, + /// false otherwise. + bool hasLeaseForAddressRange(const asiolink::IOAddress& first, + const asiolink::IOAddress& last) const; + + /// @brief Checks if client has a lease for a prefix. + /// + /// @param prefix Prefix. + /// @param prefix_len Prefix length. + /// + /// @return true if client has a lease for the specified prefix, false + /// otherwise. + bool hasLeaseForPrefix(const asiolink::IOAddress& prefix, + const uint8_t prefix_len) const; + + /// @brief Checks if client has a lease belonging to a prefix pool. + /// + /// @param prefix Pool prefix. + /// @param prefix_len Prefix length. + /// @param delegated_len Delegated prefix length. + /// + /// @return true if client has a lease belonging to specified pool, + /// false otherwise. + bool hasLeaseForPrefixPool(const asiolink::IOAddress& prefix, + const uint8_t prefix_len, + const uint8_t delegated_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 4dc9892f68..f5efbcf32b 100644 --- a/src/bin/dhcp6/tests/host_unittest.cc +++ b/src/bin/dhcp6/tests/host_unittest.cc @@ -9,6 +9,10 @@ #include #include #include +#include +#include +#include +#include using namespace isc; using namespace isc::asiolink; @@ -107,12 +111,90 @@ 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 { +public: + Reservation(const std::string& resource); + + bool isEmpty() const; + + bool isPrefix() const; + + static const Reservation& UNSPEC(); + + operator std::string() const; + +private: + IOAddress prefix_; + uint8_t prefix_len_; +}; + +Reservation::Reservation(const std::string& resource) + : prefix_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_len_(0) { + 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)); + } + prefix_ = IOAddress(resource.substr(0, slash_pos)); +} + +bool +Reservation::isEmpty() const { + return (prefix_.isV6Zero()); +} + +bool +Reservation::isPrefix() const { + return (!isEmpty() && (prefix_len_ > 0)); +} + +const Reservation& Reservation::UNSPEC() { + static Reservation unspec("::/0"); + return (unspec); +} + +Reservation::operator std::string() const { + std::ostringstream s; + s << "\"" << prefix_; + if (prefix_len_ > 0) { + s << "/" << static_cast(prefix_len_); + } + s << "\""; + return (s.str()); +} + /// @brief Test fixture class for testing host reservations class HostTest : public Dhcpv6SrvTest { public: + + /// @brief Constructor. /// /// Sets up fake interfaces. @@ -153,10 +235,98 @@ public: EXPECT_EQ(exp_ip_address, lease_client.addr_.toText()); } + static void storeReservation(const Reservation& r, + std::list& address_list, + std::list& prefix_list); + + std::string configString(const DUID& duid, + 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; + /// @brief Interface Manager's fake configuration control. IfaceMgrTestConfig iface_mgr_test_config_; }; +void +HostTest::storeReservation(const Reservation& r, + std::list& address_list, + std::list& prefix_list) { + if (!r.isEmpty()) { + if (r.isPrefix()) { + prefix_list.push_back(r); + } else { + address_list.push_back(r); + } + } +} + +std::string +HostTest::configString(const DUID& duid, + const Reservation& r1, const Reservation& r2, + const Reservation& r3, const Reservation& r4, + const Reservation& r5, const Reservation& r6) const { + std::list address_list; + std::list prefix_list; + storeReservation(r1, address_list, prefix_list); + storeReservation(r2, address_list, prefix_list); + storeReservation(r3, address_list, prefix_list); + storeReservation(r4, address_list, prefix_list); + storeReservation(r5, address_list, prefix_list); + storeReservation(r6, address_list, prefix_list); + + std::ostringstream s; + s << "{ " + "\"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\" } ]," + " \"pd-pools\": [ { \"prefix\": \"3001::\", \"prefix-len\": 32," + " \"delegated-len\": 64 } ]," + " \"interface\" : \"eth0\""; + + if (!address_list.empty() || !prefix_list.empty()) { + s << "," + " \"reservations\": [" + " {" + " \"duid\": "; + s << "\"" << duid.toText() << "\","; + + if (!address_list.empty()) { + s << " \"ip-addresses\": [ " + << boost::algorithm::join(address_list, ", ") + << "]"; + } + + if (!prefix_list.empty()) { + if (!address_list.empty()) { + s << ", "; + } + s << " \"prefixes\": [ " + << boost::algorithm::join(prefix_list, ", ") + << "]"; + } + + s << " } ]"; + } + + s << " } ]" + "}"; + + return (s.str()); +} + + // Test basic SARR scenarios against a server configured with one subnet // containing two reservations. One reservation with a hostname, one // without a hostname. Scenarios: @@ -376,4 +546,107 @@ 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)); +} + +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)); +} + +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")); + + 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::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)); + + EXPECT_TRUE(client.hasLeaseForAddressRange(IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::10"))); + EXPECT_TRUE(client.hasLeaseForPrefixPool(IOAddress("3001::"), 32, 64)); +} + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 31d62c3b50..a6b7fade69 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -762,10 +762,16 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, // Get the IPv6 reservations of specified type. const IPv6ResrvRange& reservs = ctx.host_->getIPv6Reservations(type); for (IPv6ResrvIterator resv = reservs.first; resv != reservs.second; ++resv) { - // We do have a reservation for addr. + // We do have a reservation for address or prefix. IOAddress addr = resv->second.getPrefix(); uint8_t prefix_len = resv->second.getPrefixLen(); + // We have allocated this address/prefix while processing one of the + // previous IAs, so let's try another reservation. + if (ctx.isAllocated(addr, prefix_len)) { + continue; + } + // Check if already have this lease on the existing_leases list. for (Lease6Collection::iterator l = existing_leases.begin(); l != existing_leases.end(); ++l) {