]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1584] Suppress v4 NAKs for unknown addresses
authorThomas Markwalder <tmark@isc.org>
Mon, 11 Oct 2021 15:35:53 +0000 (11:35 -0400)
committerThomas Markwalder <tmark@isc.org>
Thu, 21 Oct 2021 17:32:16 +0000 (13:32 -0400)
kea-dhcp4 no longer NAKs when the client DHCPREQUESTs
an address that the server does not know.

Added ChangeLog entry

src/bin/dhcp4/dhcp4_messages.*
    DHCP4_UNKNOWN_ADDRESS_REQUESTED - new log message

src/bin/dhcp4/dhcp4_srv.cc
    Dhcpv4Srv::assignLease() - added logic to drop NAKs when
    the address is not managed the server.

src/bin/dhcp4/tests/dora_unittest.cc
src/bin/dhcp4/tests/hooks_unittest.cc
src/bin/dhcp4/tests/out_of_range_unittest.cc
    updated tests

src/lib/dhcpsrv/alloc_engine.*
    AllocEngine::ClientContext4 - added unknown_requested_addr_ flag
    AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) - sets
    unknown_requested_addr_ flag

ChangeLog
src/bin/dhcp4/dhcp4_messages.cc
src/bin/dhcp4/dhcp4_messages.h
src/bin/dhcp4/dhcp4_messages.mes
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/dora_unittest.cc
src/bin/dhcp4/tests/hooks_unittest.cc
src/bin/dhcp4/tests/out_of_range_unittest.cc
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h

index aa8b811ddd2f5c137bec83a154e1c56bff331dad..0d2bd80d3abc0a7b32703fee8698381fa35a7918 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,10 +1,15 @@
+1955.  [bug]           tmark
+       kea-dhcp4 no longer sends DHCPNAKs in response to
+       DHCPREQUESTs for addresses for which it has no knowledge.
+       (Gitlab #1584)
+
 1954.  [doc]           fdupont
        Updated the Developer's Guide to explain what to do when
        GSS-TSIG hook unit tests fail from a system Kerberos
        incompatible configuration.
        (Gitlab #2056)
 
-1953.  [build]         fdupont
+1953.  [build]         fdupont
        Changed the name of the GSS-TSIG hook library object to
        libddns_gss_tsig.so.
        (Gitlab #2115)
index c8966d74b2b9d331ee73bac229118c49015391e2..24f6704d3d8095d3e5681f96146058a362dccb87 100644 (file)
@@ -155,6 +155,7 @@ extern const isc::log::MessageID DHCP4_SUBNET_DYNAMICALLY_CHANGED = "DHCP4_SUBNE
 extern const isc::log::MessageID DHCP4_SUBNET_SELECTED = "DHCP4_SUBNET_SELECTED";
 extern const isc::log::MessageID DHCP4_SUBNET_SELECTION_FAILED = "DHCP4_SUBNET_SELECTION_FAILED";
 extern const isc::log::MessageID DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED = "DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED";
+extern const isc::log::MessageID DHCP4_UNKNOWN_ADDRESS_REQUESTED = "DHCP4_UNKNOWN_ADDRESS_REQUESTED";
 extern const isc::log::MessageID DHCP6_DHCP4O6_PACKET_RECEIVED = "DHCP6_DHCP4O6_PACKET_RECEIVED";
 
 } // namespace dhcp
@@ -311,6 +312,7 @@ const char* values[] = {
     "DHCP4_SUBNET_SELECTED", "%1: the subnet with ID %2 was selected for client assignments",
     "DHCP4_SUBNET_SELECTION_FAILED", "%1: failed to select subnet for the client",
     "DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED", "All packets will be send to source address of an incoming packet - use only for testing",
+    "DHCP4_UNKNOWN_ADDRESS_REQUESTED", "%1: client requested an unknown address, client sent ciaddr %2, requested-ip-address %3",
     "DHCP6_DHCP4O6_PACKET_RECEIVED", "received DHCPv4o6 packet from DHCPv6 server (type %1) for %2 port %3 on interface %4",
     NULL
 };
index a52e733fde8bbe178fe6baee051f404837b83bcc..59b7531bf0ece7b3a0849237ec13fde1f2e73d1b 100644 (file)
@@ -156,6 +156,7 @@ extern const isc::log::MessageID DHCP4_SUBNET_DYNAMICALLY_CHANGED;
 extern const isc::log::MessageID DHCP4_SUBNET_SELECTED;
 extern const isc::log::MessageID DHCP4_SUBNET_SELECTION_FAILED;
 extern const isc::log::MessageID DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED;
+extern const isc::log::MessageID DHCP4_UNKNOWN_ADDRESS_REQUESTED;
 extern const isc::log::MessageID DHCP6_DHCP4O6_PACKET_RECEIVED;
 
 } // namespace dhcp
index c98c43bc38e50a5fa3076799a4a550e8112fa32f..4de3e5bdc8085c0cb00e995302ec09526d6a4dc0 100644 (file)
@@ -923,3 +923,11 @@ to simulate multiple subnet traffic from single source.
 % DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv6 server (type %1) for %2 port %3 on interface %4
 This debug message is printed when the server is receiving a DHCPv4o6
 from the DHCPv6 server over inter-process communication.
+
+% DHCP4_UNKNOWN_ADDRESS_REQUESTED %1: client requested an unknown address, client sent ciaddr %2, requested-ip-address %3
+This message indicates that the client requested an address that does
+not belong to any dynamic pools managed by this server.  The first argument
+contains the client and the transaction identification information.
+The second argument contains the IPv4 address in the ciaddr field. The
+third argument contains the IPv4 address in the requested-ip-address
+option (if present).
index 4d732a2c077062aa587de32f00a65733c542ba44..4e97155c58fa337800312861a37b1042010b2f3e 100644 (file)
@@ -2314,6 +2314,7 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
     // it should include this hint. That will help us during the actual lease
     // allocation.
     bool fake_allocation = (query->getType() == DHCPDISCOVER);
+    Subnet4Ptr original_subnet = subnet;
 
     // Get client-id. It is not mandatory in DHCPv4.
     ClientIdPtr client_id = ex.getContext()->clientid_;
@@ -2329,7 +2330,7 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
             .arg(hint.toText());
 
         Lease4Ptr lease;
-        Subnet4Ptr original_subnet = subnet;
+//        Subnet4Ptr original_subnet = subnet;
 
         // We used to issue a separate query (two actually: one for client-id
         // and another one for hw-addr for) each subnet in the shared network.
@@ -2573,6 +2574,34 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
     } else {
         // Allocation engine did not allocate a lease. The engine logged
         // cause of that failure.
+        if ((ctx->unknown_requested_addr_) /*&& !original_subnet->getAuthoritative()*/) {
+            Subnet4Ptr s = original_subnet;
+            // Address might have been rejected via class guard (i.e. not allowed for
+            // this client). We need to determine if we truly do not know about the
+            // address or whether this client just isn't allowed to have that address.
+            // We should only NAK For the latter.
+            while (s) {
+                if (s->inPool(Lease::TYPE_V4, hint)) {
+                    break;
+                }
+
+                s = s->getNextSubnet(original_subnet);
+            }
+
+            // If we didn't find a subnet, it's not an address we know about
+            // so we we drop the NAK.
+            if (!s) {
+                LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
+                          DHCP4_UNKNOWN_ADDRESS_REQUESTED)
+                          .arg(query->getLabel())
+                          .arg(query->getCiaddr().toText())
+                          .arg(opt_requested_address ?
+                          opt_requested_address->readAddress().toText() : "(no address)");
+                ex.deleteResponse();
+                return;
+            }
+        }
+
         LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL, fake_allocation ?
                   DHCP4_PACKET_NAK_0003 : DHCP4_PACKET_NAK_0004)
             .arg(query->getLabel())
index 0faf40fe0a3e21e2027db48ba5d11f33059c24e8..78df3176e91a20ab64a5bc4408fb0bf67f04c8cb 100644 (file)
@@ -1099,7 +1099,18 @@ DORATest::authoritative() {
     // Configure DHCP server.
     configure(DORA_CONFIGS[15], *client.getServer());
     client.includeClientId("11:22");
+
+    // Try to renew an address that is outside the pool.
+    client.setState(Dhcp4Client::RENEWING);
+    client.ciaddr_ = IOAddress("10.0.0.9");
+    ASSERT_NO_THROW_LOG(client.doRequest());
+    // Even though we're authoritative server should not respond
+    // since it does not know this address.
+    ASSERT_FALSE(client.getContext().response_);
+
     // Obtain a lease from the server using the 4-way exchange.
+    client.ciaddr_ = IOAddress("0.0.0.0");
+    client.setState(Dhcp4Client::SELECTING);
     ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
                                   IOAddress>(new IOAddress("10.0.0.50"))));
     // Make sure that the server responded.
@@ -1202,7 +1213,18 @@ DORATest::notAuthoritative() {
     // Configure DHCP server.
     configure(DORA_CONFIGS[16], *client.getServer());
     client.includeClientId("11:22");
+
+    // Try to renew an address that is outside the pool.
+    client.setState(Dhcp4Client::RENEWING);
+    client.ciaddr_ = IOAddress("10.0.0.9");
+    ASSERT_NO_THROW_LOG(client.doRequest());
+    // We are not authoritative sure that the server does
+    // not respond at all.
+    ASSERT_FALSE(client.getContext().response_);
+
     // Obtain a lease from the server using the 4-way exchange.
+    client.ciaddr_ = IOAddress("0.0.0.0");
+    client.setState(Dhcp4Client::SELECTING);
     ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
                                   IOAddress>(new IOAddress("10.0.0.50"))));
     // Make sure that the server responded.
@@ -1977,11 +1999,12 @@ DORATest::reservationsWithConflicts() {
 
     // Try to renew the existing lease again.
     ASSERT_NO_THROW(client.doRequest());
-    // The reservation has been removed, so the server should respond with
-    // a DHCPNAK because the address that the client is using doesn't belong
-    // to a dynamic pool.
+
+    // The reservation has been removed. Since address that the client is
+    // using doesn't belong a dynamic pool and the server is not
+    // authoritative it should not send a DHCPNAK.
     resp = client.getContext().response_;
-    ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+    ASSERT_FALSE(client.getContext().response_);
 
     // A conforming client would go back to the server discovery.
     client.setState(Dhcp4Client::SELECTING);
index ccfa2d775764bf86d107862a04ebe9ad8a365ef1..c30b709323413a8a86f646d1dd011450bc1f02f0 100644 (file)
@@ -2046,17 +2046,14 @@ TEST_F(HooksDhcpv4SrvTest, leases4CommittedRequest) {
 
     ASSERT_NO_THROW(client.doRequest());
 
-    // Make sure that we received a response
-    ASSERT_TRUE(client.getContext().response_);
-
-    // Check that the callback called is indeed the one we installed
-    EXPECT_EQ("leases4_committed", callback_name_);
+    // Make sure that we did not receive a response. If we're
+    // not authoritative, there should not be a NAK.
+    ASSERT_FALSE(client.getContext().response_);
 
+    // Check that no callback was not called.
+    EXPECT_EQ("", callback_name_);
     EXPECT_FALSE(callback_lease4_);
     EXPECT_FALSE(callback_deleted_lease4_);
-
-    // Check if the callout handle state was reset after the callout.
-    checkCalloutHandleReset(client.getContext().query_);
 }
 
 // This test verifies that the callout installed on the leases4_committed hook
index 850002da2e0ab0c8bdb99239572f598a282e448b..a11cfe67355b27818470630d73ce1c9967057c01 100644 (file)
@@ -178,7 +178,8 @@ enum CfgIndex {
 /// @brief Enum for specifying expected response to client renewal attempt
 enum RenewOutcome {
     DOES_RENEW,
-    DOES_NOT_RENEW
+    DOES_NOT_RENEW,
+    DOES_NOT_NAK
 };
 
 /// @brief Enum for specifying expected response to client release attempt
@@ -343,9 +344,19 @@ OutOfRangeTest::oorRenewReleaseTest(enum CfgIndex cfg_idx,
     ASSERT_NO_THROW(client.doRequest());
     resp = client.getContext().response_;
 
-    // Verify we got ACK'd or NAK'd as expected
-    ASSERT_EQ((renew_outcome == DOES_RENEW ? DHCPACK : DHCPNAK),
-              static_cast<int>(resp->getType()));
+    switch(renew_outcome) {
+    case DOES_RENEW:
+        ASSERT_TRUE(resp);
+        ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+        break;
+    case DOES_NOT_RENEW:
+        ASSERT_TRUE(resp);
+        ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+        break;
+    case DOES_NOT_NAK:
+        ASSERT_FALSE(resp);
+        break;
+    }
 
     // Verify that the lease still exists in the database as it has not
     // been explicitly released.
@@ -386,7 +397,7 @@ TEST_F(OutOfRangeTest, dynamicOutOfPool) {
     std::string expected_address = "";
 
     oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address,
-                        DOES_NOT_RENEW);
+                        DOES_NOT_NAK);
 
 }
 
@@ -415,7 +426,7 @@ TEST_F(OutOfRangeTest, dynamicHostOutOfPool) {
     std::string hwaddress = "dd:dd:dd:dd:dd:01";
     std::string expected_address = "";
 
-    oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address, DOES_NOT_RENEW);
+    oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address, DOES_NOT_NAK);
 }
 
 // Test verifies that once-valid dynamic address host reservation,
@@ -503,7 +514,7 @@ TEST_F(OutOfRangeTest, fixedHostReservationRemoved) {
     std::string hwaddress = "ff:ff:ff:ff:ff:01";
     std::string expected_address = "10.0.0.7";
 
-    oorRenewReleaseTest(NO_HR, hwaddress, expected_address, DOES_NOT_RENEW);
+    oorRenewReleaseTest(NO_HR, hwaddress, expected_address, DOES_NOT_NAK);
 }
 
 // Test verifies that once-valid fixed address host reservation,
index 085da6c697e54b40be9030eb3abeb6621eba705f..0829c96ca55ff7760f57d4ea0b2ce3bd6b9f029b 100644 (file)
@@ -3378,7 +3378,7 @@ AllocEngine::ClientContext4::ClientContext4()
       fwd_dns_update_(false), rev_dns_update_(false),
       hostname_(""), callout_handle_(), fake_allocation_(false),
       old_lease_(), new_lease_(), hosts_(), conflicting_lease_(),
-      query_(), host_identifiers_(),
+      query_(), host_identifiers_(), unknown_requested_addr_(false),
       ddns_params_() {
 }
 
@@ -3395,7 +3395,7 @@ AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
       fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
       hostname_(hostname), callout_handle_(),
       fake_allocation_(fake_allocation), old_lease_(), new_lease_(),
-      hosts_(), host_identifiers_(),
+      hosts_(), host_identifiers_(), unknown_requested_addr_(false),
       ddns_params_(new DdnsParams()) {
 
     // Initialize host identifiers.
@@ -3806,6 +3806,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
                 .arg(ctx.query_->getLabel())
                 .arg(ctx.requested_address_);
 
+            ctx.unknown_requested_addr_ = true;
             return (Lease4Ptr());
         }
     }
index 7d3f90507a0559034574c605b630281e926e6b2a..8298aaaa660e2d1e40ef166a43e087c34cd558cc 100644 (file)
@@ -1440,6 +1440,10 @@ public:
         /// received by the server.
         IdentifierList host_identifiers_;
 
+        /// @brief True when the address DHPCREQUEST'ed by client is not within
+        /// a dynamic pool the server knows about.
+        bool unknown_requested_addr_;
+
         /// @brief Returns the set of DDNS behavioral parameters based on
         /// the selected subnet.
         ///
@@ -1449,6 +1453,7 @@ public:
         /// @return pointer to a DdnsParams instance
         DdnsParamsPtr getDdnsParams();
 
+
         /// @brief Convenience function adding host identifier into
         /// @ref host_identifiers_ list.
         ///