2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[#3693] Skip reclamation when service is disabled

/src/bin/dhcp4/ctrl_dhcp4_srv.cc
    ControlledDhcpv4Srv::reclaimExpiredLeases()
    ControlledDhcpv4Srv::deleteExpiredReclaimedLeases()
    - do nothing but reschedule the timer when DHCP service
    is disabled

/src/bin/dhcp4/dhcp4_messages.mes
    DHCP4_RECLAIM_EXPIRED_LEASES_SKIPPED - new log message

/src/bin/dhcp4/tests/kea_controller_unittest.cc
    TEST_F(JSONFileBackendTest, reclaimOnlyWhenServiceEnabled) - new test

/src/bin/dhcp6/ctrl_dhcp6_srv.cc
    ControlledDhcpv6Srv::reclaimExpiredLeases()
    ControlledDhcpv6Srv::deleteExpiredReclaimedLeases()
    - do nothing but reschedule the timer when DHCP service
    is disabled

/src/bin/dhcp6/dhcp6_messages.mes
    DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED  - new log message

/src/bin/dhcp6/tests/kea_controller_unittest.cc
    TEST_F(JSONFileBackendTest, reclaimOnlyWhenServiceEnabled) - new test
This commit is contained in:
Thomas Markwalder
2025-03-11 07:51:48 -04:00
parent 0e84e5e105
commit 04f61c8758
11 changed files with 276 additions and 6 deletions

View File

@@ -0,0 +1,5 @@
[func] tmark
Lease reclamation is now skipped and rescheduled
while DHCP service is disabled. This applies to
both kea-dhcp4 and kea-dhcp6.
(Gitlab #3693)

View File

@@ -1610,9 +1610,15 @@ ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
const bool remove_lease,
const uint16_t max_unwarned_cycles) {
try {
server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
remove_lease,
max_unwarned_cycles);
if (network_state_->isServiceEnabled()) {
server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
remove_lease,
max_unwarned_cycles);
} else {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_RECLAIM_EXPIRED_LEASES_SKIPPED)
.arg(CfgMgr::instance().getCurrentCfg()->
getCfgExpiration()->getReclaimTimerWaitTime());
}
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_RECLAIM_EXPIRED_LEASES_FAIL)
.arg(ex.what());
@@ -1623,7 +1629,10 @@ ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
void
ControlledDhcpv4Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
server_->alloc_engine_->deleteExpiredReclaimedLeases4(secs);
if (network_state_->isServiceEnabled()) {
server_->alloc_engine_->deleteExpiredReclaimedLeases4(secs);
}
// We're using the ONE_SHOT timer so there is a need to re-schedule it.
TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
}

View File

@@ -141,6 +141,7 @@ extern const isc::log::MessageID DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL = "DHCP4
extern const isc::log::MessageID DHCP4_QUERY_DATA = "DHCP4_QUERY_DATA";
extern const isc::log::MessageID DHCP4_QUERY_LABEL = "DHCP4_QUERY_LABEL";
extern const isc::log::MessageID DHCP4_RECLAIM_EXPIRED_LEASES_FAIL = "DHCP4_RECLAIM_EXPIRED_LEASES_FAIL";
extern const isc::log::MessageID DHCP4_RECLAIM_EXPIRED_LEASES_SKIPPED = "DHCP4_RECLAIM_EXPIRED_LEASES_SKIPPED";
extern const isc::log::MessageID DHCP4_RECOVERED_STASHED_RELAY_AGENT_INFO = "DHCP4_RECOVERED_STASHED_RELAY_AGENT_INFO";
extern const isc::log::MessageID DHCP4_RELEASE = "DHCP4_RELEASE";
extern const isc::log::MessageID DHCP4_RELEASE_DELETED = "DHCP4_RELEASE_DELETED";
@@ -319,6 +320,7 @@ const char* values[] = {
"DHCP4_QUERY_DATA", "%1, packet details: %2",
"DHCP4_QUERY_LABEL", "received query: %1",
"DHCP4_RECLAIM_EXPIRED_LEASES_FAIL", "failed to reclaim expired leases: %1",
"DHCP4_RECLAIM_EXPIRED_LEASES_SKIPPED", "dhcp6 service is currently disabled. Try again in %1 seconds.",
"DHCP4_RECOVERED_STASHED_RELAY_AGENT_INFO", "recovered for query %1 relay agent option from lease %2: %3",
"DHCP4_RELEASE", "%1: address %2 was released properly.",
"DHCP4_RELEASE_DELETED", "%1: address %2 was deleted on release.",

View File

@@ -142,6 +142,7 @@ extern const isc::log::MessageID DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL;
extern const isc::log::MessageID DHCP4_QUERY_DATA;
extern const isc::log::MessageID DHCP4_QUERY_LABEL;
extern const isc::log::MessageID DHCP4_RECLAIM_EXPIRED_LEASES_FAIL;
extern const isc::log::MessageID DHCP4_RECLAIM_EXPIRED_LEASES_SKIPPED;
extern const isc::log::MessageID DHCP4_RECOVERED_STASHED_RELAY_AGENT_INFO;
extern const isc::log::MessageID DHCP4_RELEASE;
extern const isc::log::MessageID DHCP4_RELEASE_DELETED;

View File

@@ -916,6 +916,11 @@ the client and the transaction identification information.
This error message indicates that the reclaim expired leases operation failed
and provides the cause of failure.
% DHCP4_RECLAIM_EXPIRED_LEASES_SKIPPED dhcp6 service is currently disabled. Try again in %1 seconds.
This debug message is emitted when lease reclamation was scheduled to begin
but skipped because DHCPv6 service was disabled. Reclamation will continue
to be scheduled according to the configured value of reclaim-timer-wait-time.
% DHCP4_RECOVERED_STASHED_RELAY_AGENT_INFO recovered for query %1 relay agent option from lease %2: %3
Logged at debug log level 55.
This debug message indicates that agent options were stashed in the lease for

View File

@@ -188,6 +188,18 @@ public:
// stub implementation used in tests.
cb_control_.reset(new TestCBControlDHCPv4());
}
/// @brief Convenience method that enables or disables DHCP service.
///
/// @param enable true to enable service, false to disable it.
void enableService(bool enable) {
if (enable) {
network_state_->enableService(NetworkState::USER_COMMAND);
} else {
network_state_->disableService(NetworkState::USER_COMMAND);
}
}
};
/// @brief test class for Kea configuration backend.
@@ -1103,4 +1115,107 @@ TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMemfileToMySQL) {
#endif
// This test verifies that the DHCP server only reclaims or flushes leases
// when DHCP6 service is enabled.
TEST_F(JSONFileBackendTest, reclaimOnlyWhenServiceEnabled) {
// This is a basic configuration which enables timers for reclaiming
// expired leases and flushing them after 500 seconds since they expire.
// Both timers run at 1 second intervals.
string config =
"{ \"Dhcp4\": {"
"\"interfaces-config\": {"
" \"interfaces\": [ ]"
"},"
"\"lease-database\": {"
" \"type\": \"memfile\","
" \"persist\": false"
"},"
"\"expired-leases-processing\": {"
" \"reclaim-timer-wait-time\": 1,"
" \"hold-reclaimed-time\": 500,"
" \"flush-reclaimed-timer-wait-time\": 1"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, \n"
"\"subnet4\": [ ],"
"\"valid-lifetime\": 4000 }"
"}";
writeFile(TEST_FILE, config);
// Create an instance of the server and initialize it.
boost::scoped_ptr<NakedControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv4Srv()));
ASSERT_NO_THROW(srv->init(TEST_FILE));
// Create an expired lease. The lease is expired by 40 seconds ago
// (valid lifetime = 60, cltt = now - 100). The lease will be reclaimed
// but shouldn't be flushed in the database because the reclaimed are
// held in the database 500 seconds after reclamation, according to the
// current configuration.
HWAddrPtr hwaddr_expired(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
Lease4Ptr lease_expired(new Lease4(IOAddress("10.0.0.1"), hwaddr_expired,
ClientIdPtr(), 60,
time(NULL) - 100, SubnetID(1)));
// Create expired-reclaimed lease. The lease has expired 1000 - 60 seconds
// ago. It should be removed from the lease database when the "flush" timer
// goes off.
HWAddrPtr hwaddr_reclaimed(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
Lease4Ptr lease_reclaimed(new Lease4(IOAddress("10.0.0.2"), hwaddr_reclaimed,
ClientIdPtr(), 60,
time(NULL) - 1000, SubnetID(1)));
lease_reclaimed->state_ = Lease4::STATE_EXPIRED_RECLAIMED;
// Add leases to the database.
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
// Make sure they have been added.
ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.1")));
ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.2")));
// Disable service.
srv->enableService(false);
// Poll the timers for a while to make sure that each of them is executed
// at least once.
ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 5000));
// Verify that the leases in the database have not been processed.
ASSERT_NO_THROW(
lease_expired = lease_mgr.getLease4(IOAddress("10.0.0.1"))
);
ASSERT_TRUE(lease_expired);
ASSERT_EQ(Lease::STATE_DEFAULT, lease_expired->state_);
// Second lease should not have been removed.
ASSERT_NO_THROW(
lease_reclaimed = lease_mgr.getLease4(IOAddress("10.0.0.2"))
);
ASSERT_TRUE(lease_reclaimed);
ASSERT_EQ(Lease::STATE_EXPIRED_RECLAIMED, lease_reclaimed->state_);
// Enable service.
srv->enableService(true);
// Poll the timers for a while to make sure that each of them is executed
// at least once.
ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 5000));
// Verify that the leases in the database have been processed as expected.
// First lease should be reclaimed, but not removed.
ASSERT_NO_THROW(lease_expired = lease_mgr.getLease4(IOAddress("10.0.0.1")));
ASSERT_TRUE(lease_expired);
EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
// Second lease should have been removed.
ASSERT_NO_THROW(
lease_reclaimed = lease_mgr.getLease4(IOAddress("10.0.0.2"));
);
EXPECT_FALSE(lease_reclaimed);
}
} // End of anonymous namespace

View File

@@ -1396,9 +1396,15 @@ ControlledDhcpv6Srv::reclaimExpiredLeases(const size_t max_leases,
const bool remove_lease,
const uint16_t max_unwarned_cycles) {
try {
if (network_state_->isServiceEnabled()) {
server_->alloc_engine_->reclaimExpiredLeases6(max_leases, timeout,
remove_lease,
max_unwarned_cycles);
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED)
.arg(CfgMgr::instance().getCurrentCfg()->
getCfgExpiration()->getReclaimTimerWaitTime());
}
} catch (const std::exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_RECLAIM_EXPIRED_LEASES_FAIL)
.arg(ex.what());
@@ -1409,7 +1415,10 @@ ControlledDhcpv6Srv::reclaimExpiredLeases(const size_t max_leases,
void
ControlledDhcpv6Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
server_->alloc_engine_->deleteExpiredReclaimedLeases6(secs);
if (network_state_->isServiceEnabled()) {
server_->alloc_engine_->deleteExpiredReclaimedLeases6(secs);
}
// We're using the ONE_SHOT timer so there is a need to re-schedule it.
TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
}

View File

@@ -141,6 +141,7 @@ extern const isc::log::MessageID DHCP6_QUERY_DATA = "DHCP6_QUERY_DATA";
extern const isc::log::MessageID DHCP6_QUERY_LABEL = "DHCP6_QUERY_LABEL";
extern const isc::log::MessageID DHCP6_RAPID_COMMIT = "DHCP6_RAPID_COMMIT";
extern const isc::log::MessageID DHCP6_RECLAIM_EXPIRED_LEASES_FAIL = "DHCP6_RECLAIM_EXPIRED_LEASES_FAIL";
extern const isc::log::MessageID DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED = "DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED";
extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_ADD_FAIL = "DHCP6_REGISTERED_LEASE_ADD_FAIL";
extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_UPDATE_FAIL = "DHCP6_REGISTERED_LEASE_UPDATE_FAIL";
extern const isc::log::MessageID DHCP6_RELEASE_NA = "DHCP6_RELEASE_NA";
@@ -314,6 +315,7 @@ const char* values[] = {
"DHCP6_QUERY_LABEL", "received query: %1",
"DHCP6_RAPID_COMMIT", "%1: Rapid Commit option received, following 2-way exchange",
"DHCP6_RECLAIM_EXPIRED_LEASES_FAIL", "failed to reclaim expired leases: %1",
"DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED", "dhcp6 service is currently disabled. Try again in %1 seconds.",
"DHCP6_REGISTERED_LEASE_ADD_FAIL", "error in registered lease add for %1",
"DHCP6_REGISTERED_LEASE_UPDATE_FAIL", "error in registered lease update for %1: %2",
"DHCP6_RELEASE_NA", "%1: binding for address %2 and iaid=%3 was released properly",

View File

@@ -142,6 +142,7 @@ extern const isc::log::MessageID DHCP6_QUERY_DATA;
extern const isc::log::MessageID DHCP6_QUERY_LABEL;
extern const isc::log::MessageID DHCP6_RAPID_COMMIT;
extern const isc::log::MessageID DHCP6_RECLAIM_EXPIRED_LEASES_FAIL;
extern const isc::log::MessageID DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED;
extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_ADD_FAIL;
extern const isc::log::MessageID DHCP6_REGISTERED_LEASE_UPDATE_FAIL;
extern const isc::log::MessageID DHCP6_RELEASE_NA;

View File

@@ -928,6 +928,11 @@ specifies the client and transaction identification information.
This error message indicates that the reclaim expired leases operation failed
and provides the cause of failure.
% DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED dhcp6 service is currently disabled. Try again in %1 seconds.
This debug message is emitted when lease reclamation was scheduled to begin
but skipped because DHCPv6 service was disabled. Reclamation will continue
to be scheduled according to the configured value of reclaim-timer-wait-time.
% DHCP6_REGISTERED_LEASE_ADD_FAIL error in registered lease add for %1
This error message indicates that the registered lease add failed and
provides the address being registered.

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2025 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
@@ -185,6 +185,17 @@ public:
// stub implementation used in tests.
cb_control_.reset(new TestCBControlDHCPv6());
}
/// @brief Convenience method that enables or disables DHCP service.
///
/// @param enable true to enable service, false to disable it.
void enableService(bool enable) {
if (enable) {
network_state_->enableService(NetworkState::USER_COMMAND);
} else {
network_state_->disableService(NetworkState::USER_COMMAND);
}
}
};
/// @brief test class for Kea configuration backend.
@@ -1090,4 +1101,109 @@ TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMemfileToMySQL) {
#endif
// This test verifies that the DHCP server only reclaims or flushes leases
// when DHCP6 service is enabled.
TEST_F(JSONFileBackendTest, reclaimOnlyWhenServiceEnabled) {
// This is a basic configuration which enables timers for reclaiming
// expired leases and flushing them after 500 seconds since they expire.
// Both timers run at 1 second intervals.
string config =
"{ \"Dhcp6\": {"
"\"interfaces-config\": {"
" \"interfaces\": [ ]"
"},"
"\"lease-database\": {"
" \"type\": \"memfile\","
" \"persist\": false"
"},"
"\"expired-leases-processing\": {"
" \"reclaim-timer-wait-time\": 1,"
" \"hold-reclaimed-time\": 500,"
" \"flush-reclaimed-timer-wait-time\": 1"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ ],"
"\"preferred-lifetime\": 3000, "
"\"valid-lifetime\": 4000 }"
"}";
writeFile(TEST_FILE, config);
// Create an instance of the server and initialize it.
boost::scoped_ptr<NakedControlledDhcpv6Srv> srv;
ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv6Srv()));
ASSERT_NO_THROW(srv->init(TEST_FILE));
// Create an expired lease. The lease is expired by 40 seconds ago
// (valid lifetime = 60, cltt = now - 100). The lease will be reclaimed
// but shouldn't be flushed in the database because the reclaimed are
// held in the database 500 seconds after reclamation, according to the
// current configuration.
DuidPtr duid_expired(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
Lease6Ptr lease_expired(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
duid_expired, 1, 50, 60, SubnetID(1)));
lease_expired->cltt_ = time(NULL) - 100;
// Create expired-reclaimed lease. The lease has expired 1000 - 60 seconds
// ago. It should be removed from the lease database when the "flush" timer
// goes off.
DuidPtr duid_reclaimed(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
Lease6Ptr lease_reclaimed(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
duid_reclaimed, 1, 50, 60, SubnetID(1)));
lease_reclaimed->cltt_ = time(NULL) - 1000;
lease_reclaimed->state_ = Lease6::STATE_EXPIRED_RECLAIMED;
// Add leases to the database.
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
// Make sure they have been added.
ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1")));
ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2")));
// Disable service.
srv->enableService(false);
// Poll the timers for a while to make sure that each of them is executed
// at least once.
ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 5000));
// Verify that the leases in the database have not been processed.
ASSERT_NO_THROW(
lease_expired = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
);
ASSERT_TRUE(lease_expired);
ASSERT_EQ(Lease::STATE_DEFAULT, lease_expired->state_);
// Second lease should not have been removed.
ASSERT_NO_THROW(
lease_reclaimed = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
);
ASSERT_TRUE(lease_reclaimed);
ASSERT_EQ(Lease::STATE_EXPIRED_RECLAIMED, lease_reclaimed->state_);
// Enable service.
srv->enableService(true);
// Poll the timers for a while to make sure that each of them is executed
// at least once.
ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 5000));
// Verify that the leases in the database have been processed as expected.
// First lease should be reclaimed, but not removed.
ASSERT_NO_THROW(
lease_expired = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
);
ASSERT_TRUE(lease_expired);
EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
// Second lease should have been removed.
ASSERT_NO_THROW(
lease_reclaimed = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
);
EXPECT_FALSE(lease_reclaimed);
}
} // End of anonymous namespace