]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1428] Use ip-reservations-unique in alloc engine
authorMarcin Siodelski <marcin@isc.org>
Mon, 5 Oct 2020 17:28:27 +0000 (19:28 +0200)
committerFrancis Dupont <fdupont@isc.org>
Thu, 8 Oct 2020 13:44:39 +0000 (15:44 +0200)
src/bin/dhcp4/tests/host_unittest.cc
src/bin/dhcp6/tests/host_unittest.cc
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine_messages.cc
src/lib/dhcpsrv/alloc_engine_messages.h
src/lib/dhcpsrv/alloc_engine_messages.mes
src/lib/dhcpsrv/cfg_db_access.h

index 92fc30a6968c7dce7b7012ca6b63df04062ca7a6..dbfc45d5dce2a70c850f8351869b0440ee8bcdb6 100644 (file)
@@ -314,6 +314,7 @@ const char* CONFIGS[] = {
         "},\n"
         "\"valid-lifetime\": 600,\n"
         "\"ip-reservations-unique\": false,\n"
+        "\"host-reservation-identifiers\": [ \"hw-address\" ],\n"
         "\"subnet4\": [\n"
         "    {\n"
         "        \"subnet\": \"10.0.0.0/24\",\n"
@@ -461,6 +462,55 @@ public:
         ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
         EXPECT_EQ(second_address, resp->getYiaddr().toText());
     }
+
+    /// @brief Test that two clients having reservations for the same IP
+    /// address are offered the reserved lease.
+    ///
+    /// This test verifies the case when two clients have reservations for
+    /// the same IP address. The first client sends DHCPDICOVER and is
+    /// offered the reserved address. At the same time, the second client
+    /// having the reservation for the same IP address performs 4-way
+    /// exchange using the reserved address as a hint in DHCPDISCOVER.
+    /// The client gets the lease for this address. This test verifies
+    /// that the allocation engine correctly identifies that the second
+    /// client has a reservation for this address. In order to verify
+    /// that the allocation engine must fetch all reservations for the
+    /// reserved address and verifies that one of them belongs to the
+    /// second client.
+    ///
+    /// @param hw_address1 Hardware address of the first client having
+    /// the reservation.
+    /// @param hw_address2 Hardware address of the second client having
+    /// the reservation.
+    void testMultipleClientsRace(const std::string& hw_address1,
+                                 const std::string& hw_address2) {
+        // Create first client having the reservation.
+        Dhcp4Client client1(Dhcp4Client::SELECTING);
+        client1.setHWAddress(hw_address1);
+
+        // Configure the server.
+        ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[7], *client1.getServer()));
+
+        // Sends DHCPDISCOVER and make sure the client is offered the
+        // reserved IP address.
+        client1.doDiscover(boost::make_shared<IOAddress>("10.0.0.123"));
+        ASSERT_TRUE(client1.getContext().response_);
+        Pkt4Ptr resp = client1.getContext().response_;
+        ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+        EXPECT_EQ("10.0.0.123", resp->getYiaddr().toText());
+
+        // Create the second client matching the second reservation for
+        // the given IP address.
+        Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+        client2.setHWAddress(hw_address2);
+
+        // Make sure that the second client gets the reserved lease.
+        client2.doDORA(boost::make_shared<IOAddress>("10.0.0.123"));
+        ASSERT_TRUE(client2.getContext().response_);
+        resp = client2.getContext().response_;
+        ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+        EXPECT_EQ("10.0.0.123", resp->getYiaddr().toText());
+    }
 };
 
 // Verifies that a client, which fails to match to a global
@@ -621,14 +671,76 @@ TEST_F(HostTest, clientClassPoolSelection) {
 
 // Verifies that if the server is configured to allow for specifying
 // multiple reservations for the same IP address the first client
-// matching the reservation will be given this address.
-TEST_F(HostTest, oneOfMultiple) {
-    Dhcp4Client client(Dhcp4Client::SELECTING);
+// matching the reservation will be given this address. The second
+// client will be given a different lease.
+TEST_F(HostTest, firstClientGetsReservedAddress) {
+    // Create a client which has MAC address matching the reservation.
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setHWAddress("aa:bb:cc:dd:ee:fe");
+    // Do 4-way exchange for this client to get the reserved address.
+    runDoraTest(CONFIGS[7], client1, "", "10.0.0.123");
+
+    // Create another client that has a reservation for the same
+    // IP address.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setHWAddress("aa:bb:cc:dd:ee:ff");
+    // Do 4-way exchange with client2.
+    ASSERT_NO_THROW(client2.doDORA());
+
+    // Make sure that the server responded with DHCPACK.
+    ASSERT_TRUE(client2.getContext().response_);
+    Pkt4Ptr resp = client2.getContext().response_;
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    // Even though the client has reservation for this address the
+    // server should not assign this address because another client
+    // has taken it already.
+    EXPECT_NE("10.0.0.123", resp->getYiaddr().toText());
+
+    // If the client1 releases the reserved lease, the client2 should acquire it.
+    ASSERT_NO_THROW(client1.doRelease());
+
+    // Client2 attempts to renew the currently used lease, but should get the
+    // DHCPNAK.
+    client2.setState(Dhcp4Client::RENEWING);
+    ASSERT_NO_THROW(client2.doRequest());
+    resp = client2.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+    // The client falls back to 4-way exchange and gets the reserved address.
+    client2.setState(Dhcp4Client::SELECTING);
+    ASSERT_NO_THROW(client2.doDORA());
+    resp = client2.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    EXPECT_EQ("10.0.0.123", resp->getYiaddr().toText());
+}
 
-    // Hardware address matches all reservations
-    client.setHWAddress("aa:bb:cc:dd:ee:fe");
+// This test verifies the case when two clients have reservations for
+// the same IP address. The first client sends DHCPDICOVER and is
+// offered the reserved address. At the same time, the second client
+// having the reservation for the same IP address performs 4-way
+// exchange using the reserved address as a hint in DHCPDISCOVER.
+// The client gets the lease for this address. This test verifies
+// that the allocation engine correctly identifies that the second
+// client has a reservation for this address. In order to verify
+// that the allocation engine must fetch all reservations for the
+// reserved address and verifies that one of them belongs to the
+// second client.
+TEST_F(HostTest, multipleClientsRace1) {
+    ASSERT_NO_FATAL_FAILURE(testMultipleClientsRace("aa:bb:cc:dd:ee:fe",
+                                                    "aa:bb:cc:dd:ee:ff"));
+}
 
-    runDoraTest(CONFIGS[7], client, "", "10.0.0.123");
+// This is a second variant of the multipleClientsRace1. The test is almost
+// the same but the client matching the second reservation sends DHCPDISCOVER
+// first and then the client having the first reservation performs 4-way
+// exchange. This is to ensure that the order in which reservations are
+// defined does not matter.
+TEST_F(HostTest, multipleClientsRace2) {
+    ASSERT_NO_FATAL_FAILURE(testMultipleClientsRace("aa:bb:cc:dd:ee:ff",
+                                                    "aa:bb:cc:dd:ee:fe"));
 }
 
 } // end of anonymous namespace
index 85cb81467ed918646088f7daf6a1ea8d0b223ae3..2629938cd5997bffebd21cade361b6bfb7811652 100644 (file)
@@ -589,6 +589,38 @@ const char* CONFIGS[] = {
         "        \"interface\": \"eth0\"\n"
         "    }\n"
         "]\n"
+    "}",
+
+    // Configuration 14 multiple reservations for the same delegated prefix.
+    "{ \"interfaces-config\": {\n"
+        "      \"interfaces\": [ \"*\" ]\n"
+        "},\n"
+        "\"valid-lifetime\": 4000,\n"
+        "\"ip-reservations-unique\": false,\n"
+        "\"subnet6\": [\n"
+        "    {\n"
+        "        \"subnet\": \"2001:db8:1::/64\",\n"
+        "        \"id\": 10,"
+        "        \"reservations\": [\n"
+        "            {\n"
+        "                \"duid\": \"01:02:03:04\",\n"
+        "                \"prefixes\": [ \"3000::5a:0/112\" ]\n"
+        "            },\n"
+        "            {\n"
+        "                \"duid\": \"01:02:03:05\",\n"
+        "                \"prefixes\": [ \"3000::5a:0/112\" ]\n"
+        "            }\n"
+        "        ],\n"
+        "        \"pd-pools\": ["
+        "            {\n"
+        "                \"prefix\": \"3000::\",\n"
+        "                \"prefix-len\": 64,\n"
+        "                \"delegated-len\": 112\n"
+        "            }\n"
+        "        ],\n"
+        "        \"interface\": \"eth0\"\n"
+        "    }\n"
+        "]\n"
     "}"
 };
 
@@ -998,6 +1030,25 @@ public:
                                             const std::string& first_address = "2001:db8:1::10",
                                             const std::string& second_address = "2001:db8:2::10");
 
+    /// @brief Test that two clients having reservations for the same IP
+    /// address are offered the reserved lease.
+    ///
+    /// This test verifies the case when two clients have reservations for
+    /// the same IP address. The first client sends Solicit and is offered
+    /// the reserved address. At the same time, the second client having
+    /// the reservation for the same IP address performs 4-way exchange
+    /// using the reserved address as a hint in Solicit.
+    /// The client gets the lease for this address. This test verifies
+    /// that the allocation engine correctly identifies that the second
+    /// client has a reservation for this address.
+    ///
+    /// @param duid1 Hardware address of the first client having the
+    /// reservation.
+    /// @param duid2 Hardware address of the second client having the
+    /// reservation.
+    void testMultipleClientsRace(const std::string& duid1,
+                                 const std::string& duid2);
+
     /// @brief Configures client to include 6 IAs without hints.
     ///
     /// This method configures the client to include 3 IA_NAs and
@@ -1379,6 +1430,37 @@ HostTest::testGlobalClassSubnetPoolSelection(const int config_idx,
     EXPECT_EQ(second_address, lease_client.addr_.toText());
 }
 
+void
+HostTest::testMultipleClientsRace(const std::string& duid1,
+                                  const std::string& duid2) {
+    Dhcp6Client client1;
+    client1.setDUID(duid1);
+    ASSERT_NO_THROW(configure(CONFIGS[13], *client1.getServer()));
+    // client1 performs 4-way exchange to get the reserved lease.
+    requestIA(client1, Hint(IAID(1), "2001:db8:1::15"));
+    ASSERT_NO_THROW(client1.doSARR());
+
+    // Make sure the client has obtained reserved lease.
+    ASSERT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+    // Create another client that has a reservation for the same
+    // IP address.
+    Dhcp6Client client2(client1.getServer());
+    client2.setDUID(duid2);
+    requestIA(client2, Hint(IAID(1), "2001:db8:1::15"));
+
+    // client2 performs 4-way exchange.
+    ASSERT_NO_THROW(client2.doSARR());
+
+    // Make sure the client didn't get the reserved lease. This lease has been
+    // already taken by the client1.
+    EXPECT_FALSE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+    // Make sure the client2 got a lease from the configured pool.
+    EXPECT_TRUE(client2.hasLeaseForAddressRange(IOAddress("2001:db8:1::10"),
+                                                IOAddress("2001:db8:1::200")));
+}
+
 void
 HostTest::requestEmptyIAs(Dhcp6Client& client) {
     // Create IAs with IAIDs between 1 and 6.
@@ -2389,21 +2471,120 @@ TEST_F(HostTest, clientClassPoolSelection) {
 
 // Verifies that if the server is configured to allow for specifying
 // multiple reservations for the same IP address the first client
-// matching the reservation will be given this address.
-TEST_F(HostTest, oneOfMultiple) {
+// matching the reservation will be given this address. The second
+// client will be given a different lease.
+TEST_F(HostTest, firstClientGetsReservedAddress) {
+    // Create a client which has DUID matching the reservation.
     Dhcp6Client client1;
     client1.setDUID("01:02:03:04");
-
     ASSERT_NO_THROW(configure(CONFIGS[13], *client1.getServer()));
-
-    // First client performs 4-way exchange and obtains an address and
-    // prefix indicated in hints.
+    // client1 performs 4-way exchange to get the reserved lease.
     requestIA(client1, Hint(IAID(1), "2001:db8:1::10"));
-
     ASSERT_NO_THROW(client1.doSARR());
 
-    // Make sure the client has obtained requested leases.
+    // Make sure the client has obtained reserved lease.
     ASSERT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+    // Create another client that has a reservation for the same
+    // IP address.
+    Dhcp6Client client2(client1.getServer());
+    client2.setDUID("01:02:03:05");
+    requestIA(client2, Hint(IAID(1), "2001:db8:1::10"));
+
+    // client2 performs 4-way exchange.
+    ASSERT_NO_THROW(client2.doSARR());
+
+    // Make sure the client didn't get the reserved lease. This lease has been
+    // already taken by the client1.
+    EXPECT_FALSE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+    // Make sure the client2 got a lease from the configured pool.
+    auto leases = client2.getLeasesByAddressRange(IOAddress("2001:db8:1::10"),
+                                                  IOAddress("2001:db8:1::200"));
+    EXPECT_EQ(1, leases.size());
+
+    // Verify that the client1 can renew the lease.
+    ASSERT_NO_THROW(client1.doRenew());
+    EXPECT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+    // The client2 should also renew the lease.
+    ASSERT_NO_THROW(client2.doRenew());
+    EXPECT_FALSE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+    leases = client2.getLeasesByAddressRange(IOAddress("2001:db8:1::10"),
+                                             IOAddress("2001:db8:1::200"));
+    EXPECT_EQ(1, leases.size());
+
+    // If the client1 releases the reserved lease, the client2 should acquire it.
+    ASSERT_NO_THROW(client1.doRelease());
+    ASSERT_NO_THROW(client2.doRenew());
+    EXPECT_TRUE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+}
+
+// Verifies that if the server is configured to allow for specifying
+// multiple reservations for the same delegated prefix the first client
+// matching the reservation will be given this prefix. The second
+// client will be given a different lease.
+TEST_F(HostTest, firstClientGetsReservedPrefix) {
+    // Create a client which has DUID matching the reservation.
+    Dhcp6Client client1;
+    client1.setDUID("01:02:03:04");
+    ASSERT_NO_THROW(configure(CONFIGS[14], *client1.getServer()));
+    // client1 performs 4-way exchange to get the reserved lease.
+    client1.requestPrefix(1);
+    ASSERT_NO_THROW(client1.doSARR());
+
+    // Make sure the client has obtained reserved lease.
+    ASSERT_TRUE(client1.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+
+    // Create another client that has a reservation for the same
+    // IP address.
+    Dhcp6Client client2(client1.getServer());
+    client2.setDUID("01:02:03:05");
+    client2.requestPrefix(1);
+
+    // client2 performs 4-way exchange.
+    ASSERT_NO_THROW(client2.doSARR());
+
+    // Make sure the client didn't get the reserved lease. This lease has been
+    // already taken by the client1.
+    EXPECT_FALSE(client2.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+
+    // Make sure the client2 got a lease from the configured pool.
+    EXPECT_TRUE(client2.hasLeaseForPrefixPool(IOAddress("3000::"), 64, 112));
+
+    // Verify that the client1 can renew the lease.
+    ASSERT_NO_THROW(client1.doRenew());
+    EXPECT_TRUE(client1.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+
+    // The client2 should also renew the lease.
+    ASSERT_NO_THROW(client2.doRenew());
+    EXPECT_TRUE(client2.hasLeaseForPrefixPool(IOAddress("3000::"), 64, 112));
+
+    // If the client1 releases the reserved lease, the client2 should acquire it.
+    ASSERT_NO_THROW(client1.doRelease());
+    ASSERT_NO_THROW(client2.doRenew());
+    EXPECT_TRUE(client2.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+}
+
+/// This test verifies the case when two clients have reservations for
+/// the same IP address. The first client sends Solicit and is offered
+/// the reserved address. At the same time, the second client having
+/// the reservation for the same IP address performs 4-way exchange
+/// using the reserved address as a hint in Solicit.
+/// The client gets the lease for this address. This test verifies
+/// that the allocation engine correctly identifies that the second
+/// client has a reservation for this address.
+TEST_F(HostTest, multipleClientsRace1) {
+    ASSERT_NO_FATAL_FAILURE(testMultipleClientsRace("01:02:03:04", "01:02:03:05"));
+}
+
+// This is a second variant of the multipleClientsRace1. The test is almost
+// the same but the client matching the second reservation sends Solicit
+// first and then the client having the first reservation performs 4-way
+// exchange. This is to ensure that the order in which reservations are
+// defined does not matter.
+TEST_F(HostTest, multipleClientsRace2) {
+    ASSERT_NO_FATAL_FAILURE(testMultipleClientsRace("01:02:03:05", "01:02:03:04"));
 }
 
 } // end of anonymous namespace
index e66c1471c724b7abc18b2d0b597be835c7ca8f3a..2b600712b2b87190f677604e797206d88951ef1a 100644 (file)
@@ -377,6 +377,37 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
 
 namespace {
 
+/// @brief Find reservations for the given subnet and address or delegated prefix.
+///
+/// @param subnet_id Subnet identifier for which the reservations should
+/// be found.
+/// @param address Address or delegated prefix for which the reservations
+/// should be found.
+///
+/// @return Collection of host reservations.
+ConstHostCollection
+getIPv6Resrv(const SubnetID& subnet_id, const IOAddress& address) {
+    ConstHostCollection reserved;
+    // The global parameter ip-reservations-unique controls whether it is allowed
+    // to specify multiple reservations for the same IP address or delegated prefix
+    // or IP reservations must be unique. Some host backends do not support the
+    // former, thus we can't always use getAll6 calls to get the reservations
+    // for the given IP. When we're in the default mode, when IP reservations
+    // are unique, we should call get6 (supported by all backends). If we're in
+    // the mode in which non-unique reservations are allowed the backends which
+    // don't support it are not used and we can safely call getAll6.
+    if (CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique()) {
+        auto host = HostMgr::instance().get6(subnet_id, address);
+        if (host) {
+            reserved.push_back(host);
+        }
+    } else {
+        auto hosts = HostMgr::instance().getAll6(subnet_id, address);
+        reserved.insert(reserved.end(), hosts.begin(), hosts.end());
+    }
+    return (reserved);
+}
+
 /// @brief Checks if the specified address belongs to one of the subnets
 /// within a shared network.
 ///
@@ -897,12 +928,12 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
             // else. There is no need to check for whom it is reserved, because if
             // it has been reserved for us we would have already allocated a lease.
 
-            ConstHostPtr host;
+            ConstHostCollection hosts;
             if (hr_mode != Network::HR_DISABLED) {
-                host = HostMgr::instance().get6(subnet->getID(), hint);
+                hosts = getIPv6Resrv(subnet->getID(), hint);
             }
 
-            if (!host) {
+            if (hosts.empty()) {
                 // If the in-pool reservations are disabled, or there is no
                 // reservation for a given hint, we're good to go.
 
@@ -931,13 +962,13 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
         } else if (lease->expired()) {
 
             // If the lease is expired, we may likely reuse it, but...
-            ConstHostPtr host;
+            ConstHostCollection hosts;
             if (hr_mode != Network::HR_DISABLED) {
-                host = HostMgr::instance().get6(subnet->getID(), hint);
+                hosts = getIPv6Resrv(subnet->getID(), hint);
             }
 
             // Let's check if there is a reservation for this address.
-            if (!host) {
+            if (hosts.empty()) {
 
                 // Copy an existing, expired lease so as it can be returned
                 // to the caller.
@@ -1041,10 +1072,12 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
             }
 
             // First check for reservation when it is the choice.
-            if (check_reservation_first && (hr_mode == Network::HR_ALL) &&
-                HostMgr::instance().get6(subnet->getID(), candidate)) {
-                // Don't allocate.
-                continue;
+            if (check_reservation_first && (hr_mode == Network::HR_ALL)) {
+                auto hosts = getIPv6Resrv(subnet->getID(), candidate);
+                if (!hosts.empty()) {
+                    // Don't allocate.
+                    continue;
+                }
             }
 
             // Check if the resource is busy i.e. can be being allocated
@@ -1064,11 +1097,12 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
                 /// In-pool reservations: Check if this address is reserved for someone
                 /// else. There is no need to check for whom it is reserved, because if
                 /// it has been reserved for us we would have already allocated a lease.
-                if (!check_reservation_first && (hr_mode == Network::HR_ALL) &&
-                    HostMgr::instance().get6(subnet->getID(), candidate)) {
-
-                    // Don't allocate.
-                    continue;
+                if (!check_reservation_first && (hr_mode == Network::HR_ALL)) {
+                    auto hosts = getIPv6Resrv(subnet->getID(), candidate);
+                    if (!hosts.empty()) {
+                        // Don't allocate.
+                        continue;
+                    }
                 }
 
                 // there's no existing lease for selected candidate, so it is
@@ -1095,10 +1129,12 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
                 // allocation attempts.
             } else if (existing->expired()) {
                 // Make sure it's not reserved.
-                if (!check_reservation_first && (hr_mode == Network::HR_ALL) &&
-                    HostMgr::instance().get6(subnet->getID(), candidate)) {
-                    // Don't allocate.
-                    continue;
+                if (!check_reservation_first && (hr_mode == Network::HR_ALL)) {
+                    auto hosts = getIPv6Resrv(subnet->getID(), candidate);
+                    if (!hosts.empty()) {
+                        // Don't allocate.
+                        continue;
+                    }
                 }
 
                 // Copy an existing, expired lease so as it can be returned
@@ -1453,30 +1489,43 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
         // 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_);
+        auto hosts = getIPv6Resrv(ctx.subnet_->getID(), 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 && inAllowedPool(ctx, candidate->type_,
-                                   candidate->addr_, false)) {
+        if (hosts.empty() && inAllowedPool(ctx, candidate->type_,
+                                           candidate->addr_, false)) {
             continue;
         }
 
-        if (host) {
+        if (!hosts.empty()) {
             // 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());
+            if (hosts.size() == 1) {
+                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(hosts.front()->getIdentifierAsText());
+                } else {
+                    LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE)
+                        .arg(candidate->addr_.toText())
+                        .arg(static_cast<int>(candidate->prefixlen_))
+                        .arg(ctx.duid_->toText())
+                        .arg(hosts.front()->getIdentifierAsText());
+                }
             } else {
-                LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE)
-                    .arg(candidate->addr_.toText())
-                    .arg(static_cast<int>(candidate->prefixlen_))
-                    .arg(ctx.duid_->toText())
-                    .arg(host->getIdentifierAsText());
+                if (ctx.currentIA().type_ == Lease::TYPE_NA) {
+                    LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE)
+                        .arg(candidate->addr_.toText()).arg(ctx.duid_->toText())
+                        .arg(hosts.size());
+                } else {
+                    LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE)
+                        .arg(candidate->addr_.toText())
+                        .arg(static_cast<int>(candidate->prefixlen_))
+                        .arg(ctx.duid_->toText())
+                        .arg(hosts.size());
+                }
             }
         }
 
@@ -2902,16 +2951,41 @@ addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx
         ((ctx.subnet_->getHostReservationMode() == Network::HR_ALL) ||
          ((ctx.subnet_->getHostReservationMode() == Network::HR_OUT_OF_POOL) &&
           (!ctx.subnet_->inPool(Lease::TYPE_V4, address))))) {
-        ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(), address);
-        if (host) {
+        // The global parameter ip-reservations-unique controls whether it is allowed
+        // to specify multiple reservations for the same IP address or delegated prefix
+        // or IP reservations must be unique. Some host backends do not support the
+        // former, thus we can't always use getAll4 calls to get the reservations
+        // for the given IP. When we're in the default mode, when IP reservations
+        // are unique, we should call get4 (supported by all backends). If we're in
+        // the mode in which non-unique reservations are allowed the backends which
+        // don't support it are not used and we can safely call getAll4.
+        ConstHostCollection hosts;
+        if (CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique()) {
+            // Reservations are unique. It is safe to call get4 to get the unique host.
+            ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(), address);
+            if (host) {
+                hosts.push_back(host);
+            }
+        } else {
+            // Reservations can be non-unique. Need to get all reservations for that address.
+            hosts = HostMgr::instance().getAll4(ctx.subnet_->getID(), address);
+        }
+
+        for (auto host : hosts) {
             for (auto id = ctx.host_identifiers_.cbegin(); id != ctx.host_identifiers_.cend();
                  ++id) {
-                if (id->first == host->getIdentifierType()) {
-                    return (id->second != host->getIdentifier());
+                // If we find the matching host we know that this address is reserved
+                // for us and we can return immediatelly.
+                if (id->first == host->getIdentifierType() &&
+                    id->second == host->getIdentifier()) {
+                    return (false);
                 }
             }
-            return (true);
         }
+        // We didn't find a matching host. If there are any reservations it means that
+        // address is reserved for another client or multiple clients. If there are
+        // no reservations address is not reserved for another client.
+        return (!hosts.empty());
     }
     return (false);
 }
index b9005e09ed94d450090680f2827550bedbea1cce..e5e16bca9a80edb3747e48fe73536f0da2527e29 100644 (file)
@@ -1,4 +1,4 @@
-// File created from ../../../src/lib/dhcpsrv/alloc_engine_messages.mes on Mon Sep 28 2020 14:52
+// File created from ../../../src/lib/dhcpsrv/alloc_engine_messages.mes on Tue Oct 06 2020 05:22
 
 #include <cstddef>
 #include <log/message_types.h>
@@ -72,6 +72,8 @@ extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED = "ALLO
 extern const isc::log::MessageID ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA = "ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA";
 extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE = "ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE";
 extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE = "ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE = "ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE = "ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE";
 
 } // namespace dhcp
 } // namespace isc
@@ -143,7 +145,9 @@ const char* values[] = {
     "ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED", "dynamically allocating leases for the renewing client %1",
     "ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA", "%1: reusing expired lease, updated lease information: %2",
     "ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE", "address %1 was revoked from client %2 as it is reserved for client %3",
-    "ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE", "Prefix %1/%2 was revoked from client %3 as it is reserved for client %4",
+    "ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE", "prefix %1/%2 was revoked from client %3 as it is reserved for client %4",
+    "ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE", "address %1 was revoked from client %2 as it is reserved for %3 other clients",
+    "ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE", "prefix %1/%2 was revoked from client %3 as it is reserved for %4 other clients",
     NULL
 };
 
index 22cd0d627155c825ab7d4fc706eae102d7b84c46..60de188297a4c7c27c9bb3fa5763fc42b5c7b220 100644 (file)
@@ -1,4 +1,4 @@
-// File created from ../../../src/lib/dhcpsrv/alloc_engine_messages.mes on Mon Sep 28 2020 14:52
+// File created from ../../../src/lib/dhcpsrv/alloc_engine_messages.mes on Tue Oct 06 2020 05:22
 
 #ifndef ALLOC_ENGINE_MESSAGES_H
 #define ALLOC_ENGINE_MESSAGES_H
@@ -73,6 +73,8 @@ extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED;
 extern const isc::log::MessageID ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA;
 extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE;
 extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE;
 
 } // namespace dhcp
 } // namespace isc
index 250da47b09f1e466baad9e2254491e3d4523fa2e..0199af0f6f73a6defcf2220e69a945190c588c5d 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2015-2020 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
@@ -457,7 +457,7 @@ in cases such as the system administrator adding a reservation for an
 address that is currently in use by another client.  The server will fully
 recover from this situation, but clients will change their addresses.
 
-% ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE Prefix %1/%2 was revoked from client %3 as it is reserved for client %4
+% ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE prefix %1/%2 was revoked from client %3 as it is reserved for client %4
 This informational message is an indication that the specified IPv6
 prefix was used by client A but it is now reserved for client B. Client
 A has been told to stop using it so that it can be leased to client B.
@@ -465,3 +465,23 @@ This is a normal occurrence during conflict resolution, which can occur
 in cases such as the system administrator adding a reservation for an
 address that is currently in use by another client.  The server will fully
 recover from this situation, but clients will change their prefixes.
+
+% ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE address %1 was revoked from client %2 as it is reserved for %3 other clients
+This informational message is an indication that the specified IPv6
+address was used by client A but it is now reserved for multiple other
+clients. Client A has been told to stop using it so that it can be
+leased to one of the clients having the reservation for it. This is a
+normal occurrence during conflict resolution, which can occur in cases
+such as the system administrator adding reservations for an address
+that is currently in use by another client.  The server will fully
+recover from this situation, but clients will change their addresses.
+
+% ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE prefix %1/%2 was revoked from client %3 as it is reserved for %4 other clients
+This informational message is an indication that the specified IPv6
+prefix was used by client A but it is now reserved for multiple other
+clients. Client A has been told to stop using it so that it can be
+leased to one of the clients having the reservation for it. This is a
+normal occurrence during conflict resolution, which can occur in cases
+such as the system administrator adding reservations for an address
+that is currently in use by another client.  The server will fully
+recover from this situation, but clients will change their prefixes.
index 6c73efc1283eaa7449b3318ecc65fcf69bec4006..29b27c1bb66a78770da8d467a53a3238e83d6039 100644 (file)
@@ -75,7 +75,7 @@ public:
     std::list<std::string> getHostDbAccessStringList() const;
 
     /// @brief Modifies the setting imposing whether the IP reservations
-    /// are unique or can be non-unique.
+    /// are unique or can be non unique.
     ///
     /// This flag can be set to @c false when the server is explicitly
     /// configured to allow multiple hosts to have the same IP reservation.
@@ -91,6 +91,15 @@ public:
         ip_reservations_unique_ = unique;
     }
 
+    /// @brief Returns the setting indicating if the IP reservations are
+    /// unique or can be non unique.
+    ///
+    /// @return true if the IP reservations must be unique or false if
+    /// the reservations can be non unique.
+    bool getIPReservationsUnique() const {
+        return (ip_reservations_unique_);
+    }
+
     /// @brief Creates instance of lease manager and host data sources
     /// according to the configuration specified.
     void createManagers();