#include <dhcpsrv/cfgmgr.h>
#include <hooks/hooks.h>
#include <process/daemon.h>
+#include <sflq_cmds.h>
using namespace isc::config;
using namespace isc::data;
return(lease_cmds.leaseWriteHandler(handle));
}
+/// @brief This is a command callout for 'sflq-pool4-create' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_create(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool4CreateHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-get-all' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_get_all(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool4GetAllHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-get-by-subnet' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_get_by_subnet(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool4GetBySubnetHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-get-by-range' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_get_by_range(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool4GetByRangeHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-del' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_del(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool4DelHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-create' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_create(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool6CreateHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-get-all' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_get_all(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool6GetAllHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-get-by-subnet' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_get_by_subnet(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool6GetBySubnetHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-get-by-range' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_get_by_range(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool6GetByRangeHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-del' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_del(CalloutHandle& handle) {
+ SflqCmds sflq_cmds;
+ return(sflq_cmds.sflqPool6DelHandler(handle));
+}
+
/// @brief This function is called when the library is loaded.
///
/// @param handle library handle
handle.registerCommandCallout("lease6-resend-ddns", lease6_resend_ddns);
handle.registerCommandCallout("lease4-write", lease4_write);
handle.registerCommandCallout("lease6-write", lease6_write);
+ handle.registerCommandCallout("sflq-pool4-create", sflq_pool4_create);
+ handle.registerCommandCallout("sflq-pool4-get-all", sflq_pool4_get_all);
+ handle.registerCommandCallout("sflq-pool4-get-by-subnet", sflq_pool4_get_by_subnet);
+ handle.registerCommandCallout("sflq-pool4-get-by-range", sflq_pool4_get_by_range);
+ handle.registerCommandCallout("sflq-pool4-del", sflq_pool4_del);
+ handle.registerCommandCallout("sflq-pool6-create", sflq_pool6_create);
+ handle.registerCommandCallout("sflq-pool6-get-all", sflq_pool6_get_all);
+ handle.registerCommandCallout("sflq-pool6-get-by-subnet", sflq_pool6_get_by_subnet);
+ handle.registerCommandCallout("sflq-pool6-get-by-range", sflq_pool6_get_by_range);
+ handle.registerCommandCallout("sflq-pool6-del", sflq_pool6_del);
// Instantiate the binding-variables manager singleton.
binding_var_mgr.reset(new BindingVariableMgr(family));
return (0);
}
-
} // end extern "C"
extern const isc::log::MessageID LEASE_CMDS_WIPE4_FAILED = "LEASE_CMDS_WIPE4_FAILED";
extern const isc::log::MessageID LEASE_CMDS_WIPE6 = "LEASE_CMDS_WIPE6";
extern const isc::log::MessageID LEASE_CMDS_WIPE6_FAILED = "LEASE_CMDS_WIPE6_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_CREATE = "SFLQ_POOL4_CREATE";
+extern const isc::log::MessageID SFLQ_POOL4_CREATE_FAILED = "SFLQ_POOL4_CREATE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_DEL = "SFLQ_POOL4_DEL";
+extern const isc::log::MessageID SFLQ_POOL4_DEL_FAILED = "SFLQ_POOL4_DEL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL = "SFLQ_POOL4_GET_ALL";
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL_FAILED = "SFLQ_POOL4_GET_ALL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE = "SFLQ_POOL4_GET_BY_RANGE";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE_FAILED = "SFLQ_POOL4_GET_BY_RANGE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET = "SFLQ_POOL4_GET_BY_SUBNET";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET_FAILED = "SFLQ_POOL4_GET_BY_SUBNET_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_CREATE = "SFLQ_POOL6_CREATE";
+extern const isc::log::MessageID SFLQ_POOL6_CREATE_FAILED = "SFLQ_POOL6_CREATE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_DEL = "SFLQ_POOL6_DEL";
+extern const isc::log::MessageID SFLQ_POOL6_DEL_FAILED = "SFLQ_POOL6_DEL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL = "SFLQ_POOL6_GET_ALL";
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL_FAILED = "SFLQ_POOL6_GET_ALL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE = "SFLQ_POOL6_GET_BY_RANGE";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE_FAILED = "SFLQ_POOL6_GET_BY_RANGE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET = "SFLQ_POOL6_GET_BY_SUBNET";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET_FAILED = "SFLQ_POOL6_GET_BY_SUBNET_FAILED";
namespace {
"LEASE_CMDS_WIPE4_FAILED", "lease4-wipe command failed (parameters: %1, reason: %2)",
"LEASE_CMDS_WIPE6", "lease6-wipe command successful (parameters: %1)",
"LEASE_CMDS_WIPE6_FAILED", "lease6-wipe command failed (parameters: %1, reason: %2)",
+ "SFLQ_POOL4_CREATE", "sflq-pool4-create command succeeded, (parameters: %1)",
+ "SFLQ_POOL4_CREATE_FAILED", "sflq-pool4-create command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL4_DEL", "sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2",
+ "SFLQ_POOL4_DEL_FAILED", "sflq-pool4-del command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL4_GET_ALL", "sflq-pool4-get-all command succeeded, (parameters: %1) pools found: %2",
+ "SFLQ_POOL4_GET_ALL_FAILED", "sflq-pool4-get-all command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL4_GET_BY_RANGE", "sflq-pool4-get-by-range command succeeded, (parameters: %1) pools found: %2",
+ "SFLQ_POOL4_GET_BY_RANGE_FAILED", "sflq-pool4-get-by-range command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL4_GET_BY_SUBNET", "sflq-pool4-get-by-subnet command succeeded, (parameters: %1) pools found: %2",
+ "SFLQ_POOL4_GET_BY_SUBNET_FAILED", "sflq-pool4-get-by-subnet command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL6_CREATE", "sflq-pool6-create command succeeded, (parameters: %1)",
+ "SFLQ_POOL6_CREATE_FAILED", "sflq-pool6-create command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL6_DEL", "sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2",
+ "SFLQ_POOL6_DEL_FAILED", "sflq-pool4-del command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL6_GET_ALL", "sflq-pool6-get-all command succeeded, (parameters: %1) pools found: %2",
+ "SFLQ_POOL6_GET_ALL_FAILED", "sflq-pool6-get-all command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL6_GET_BY_RANGE", "sflq-pool6-get-by-range command succeeded, (parameters: %1) pools found: %2",
+ "SFLQ_POOL6_GET_BY_RANGE_FAILED", "sflq-pool6-get-by-range command failed, (parameters: %1, reason: %2)",
+ "SFLQ_POOL6_GET_BY_SUBNET", "sflq-pool6-get-by-subnet command succeeded, (parameters: %1) pools found: %2",
+ "SFLQ_POOL6_GET_BY_SUBNET_FAILED", "sflq-pool6-get-by-subnet command failed, (parameters: %1, reason: %2)",
NULL
};
extern const isc::log::MessageID LEASE_CMDS_WIPE4_FAILED;
extern const isc::log::MessageID LEASE_CMDS_WIPE6;
extern const isc::log::MessageID LEASE_CMDS_WIPE6_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_CREATE;
+extern const isc::log::MessageID SFLQ_POOL4_CREATE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_DEL;
+extern const isc::log::MessageID SFLQ_POOL4_DEL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL;
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_CREATE;
+extern const isc::log::MessageID SFLQ_POOL6_CREATE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_DEL;
+extern const isc::log::MessageID SFLQ_POOL6_DEL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL;
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET_FAILED;
#endif // LEASE_CMDS_MESSAGES_H
% LEASE_CMDS_WIPE6_FAILED lease6-wipe command failed (parameters: %1, reason: %2)
The lease6-wipe command has failed. Both the reason as well as the
parameters passed are logged.
+
+% SFLQ_POOL4_CREATE sflq-pool4-create command succeeded, (parameters: %1)
+The sflq-pool4-create command was successful.
+
+% SFLQ_POOL4_CREATE_FAILED sflq-pool4-create command failed, (parameters: %1, reason: %2)
+The sflq-pool4-create command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_GET_ALL sflq-pool4-get-all command succeeded, (parameters: %1) pools found: %2
+The sflq-pool4-get-all command was successful.
+
+% SFLQ_POOL4_GET_ALL_FAILED sflq-pool4-get-all command failed, (parameters: %1, reason: %2)
+The sflq-pool4-get-all command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_GET_BY_SUBNET sflq-pool4-get-by-subnet command succeeded, (parameters: %1) pools found: %2
+The sflq-pool4-get-by-subnet command was successful.
+
+% SFLQ_POOL4_GET_BY_SUBNET_FAILED sflq-pool4-get-by-subnet command failed, (parameters: %1, reason: %2)
+The sflq-pool4-get-subnet command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_GET_BY_RANGE sflq-pool4-get-by-range command succeeded, (parameters: %1) pools found: %2
+The sflq-pool4-get-by-range command was successful.
+
+% SFLQ_POOL4_GET_BY_RANGE_FAILED sflq-pool4-get-by-range command failed, (parameters: %1, reason: %2)
+The sflq-pool4-get-range command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_DEL sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2
+The sflq-pool4-del command was successful.
+
+% SFLQ_POOL4_DEL_FAILED sflq-pool4-del command failed, (parameters: %1, reason: %2)
+The sflq-pool4-del command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_CREATE sflq-pool6-create command succeeded, (parameters: %1)
+The sflq-pool6-create command was successful.
+
+% SFLQ_POOL6_CREATE_FAILED sflq-pool6-create command failed, (parameters: %1, reason: %2)
+The sflq-pool6-create command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_GET_ALL sflq-pool6-get-all command succeeded, (parameters: %1) pools found: %2
+The sflq-pool6-create command was successful.
+
+% SFLQ_POOL6_GET_ALL_FAILED sflq-pool6-get-all command failed, (parameters: %1, reason: %2)
+The sflq-pool6-get-all command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_GET_BY_SUBNET sflq-pool6-get-by-subnet command succeeded, (parameters: %1) pools found: %2
+The sflq-pool6-get-by-subnet command was successful.
+
+% SFLQ_POOL6_GET_BY_SUBNET_FAILED sflq-pool6-get-by-subnet command failed, (parameters: %1, reason: %2)
+The sflq-pool6-get-subnet command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_GET_BY_RANGE sflq-pool6-get-by-range command succeeded, (parameters: %1) pools found: %2
+The sflq-pool6-get-by-range command was successful.
+
+% SFLQ_POOL6_GET_BY_RANGE_FAILED sflq-pool6-get-by-range command failed, (parameters: %1, reason: %2)
+The sflq-pool6-get-range command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_DEL sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2
+The sflq-pool6-del command was successful.
+
+% SFLQ_POOL6_DEL_FAILED sflq-pool4-del command failed, (parameters: %1, reason: %2)
+The sflq-pool6-del command has failed. Both the reason as well as the
+parameters passed are logged.
"lease4-del", "lease6-del",
"lease4-update", "lease6-update",
"lease4-wipe", "lease6-wipe",
- "lease4-resend-ddns", "lease6-resend-ddns"
+ "lease4-resend-ddns", "lease6-resend-ddns",
+ "sflq-pool4-create", "sflq-pool6-create",
+ "sflq-pool4-get-all", "sflq-pool6-get-all",
+ "sflq-pool4-get-by-subnet", "sflq-pool6-get-by-subnet",
+ "sflq-pool4-get-by-range", "sflq-pool6-get-by-range",
+ "sflq-pool4-del", "sflq-pool6-del",
};
setFamily(AF_INET);
testCommands(cmds);
endif
libs_testutils = [
+ kea_dhcpsrv_testutils_lib,
kea_testutils_lib
]
'lease_cmds_unittest.cc',
'load_unload_unittests.cc',
'run_unittests.cc',
+ 'sflq_cmds_unittests.cc',
cpp_args: [
f'-DLIBDHCP_LEASE_CMDS_SO="@TOP_BUILD_DIR@/src/hooks/dhcp/lease_cmds/libdhcp_lease_cmds.so"',
],
--- /dev/null
+// Copyright (C) 2026 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
+#include <config/command_mgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/resource_handler.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <lease_cmds.h>
+#include <lease_cmds_unittest.h>
+#include <stats/stats_mgr.h>
+#include <util/filesystem.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+#include <testutils/gtest_utils.h>
+#include <dhcpsrv/testutils/sflqtest_lease_mgr.h>
+#include <dhcpsrv/sflq_allocator.h>
+
+#include <gtest/gtest.h>
+
+#include <errno.h>
+#include <set>
+
+using namespace std;
+using namespace isc;
+using namespace isc::hooks;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::test;
+using namespace isc::lease_cmds;
+using namespace isc::dhcp::test;
+
+namespace {
+
+#define SCOPED_LINE(line) \
+ std::stringstream ss; \
+ ss << "Scenario at line: " << line; \
+ SCOPED_TRACE(ss.str());
+
+/// @brief Class dedicated to testing SFLQ commands in the lease_cmds library.
+///
+/// These tests use the dhcp::test::SflqTestLeaseMgr for tests as Memfile
+/// does not support SFLQ.
+///
+/// Provides convenience methods for loading, testing the SLFQ commands.
+class SflqCmdsTest : public LibLoadTest {
+public:
+ /// @brief Pointer to the lease manager
+ isc::dhcp::LeaseMgr* lmptr_;
+
+ /// @brief Constructor
+ ///
+ /// Sets the library filename and clears the lease manager pointer.
+ /// Also ensured there is no lease manager leftovers from previous
+ /// test.
+ SflqCmdsTest(uint16_t family)
+ : LibLoadTest(LIBDHCP_LEASE_CMDS_SO) {
+ setFamily(family);
+ isc::util::file::PathChecker::enableEnforcement(true);
+ }
+
+ /// @brief Pre-test setup.
+ ///
+ /// Installs the lease manager factory, creates a manager instance.
+ virtual void SetUp() {
+ LeaseMgrFactory::registerFactory("sflqtest", dhcp::test::SflqTestLeaseMgr::factory);
+ if (isc::dhcp::CfgMgr::instance().getFamily() == AF_INET) {
+ ASSERT_NO_THROW_LOG(LeaseMgrFactory::create("type=sflqtest universe=4"));
+ } else {
+ ASSERT_NO_THROW_LOG(LeaseMgrFactory::create("type=sflqtest universe=6"));
+ }
+
+ ASSERT_TRUE(LeaseMgrFactory::haveInstance());
+ ASSERT_EQ(LeaseMgrFactory::instance().getType(), "sflqtest");
+ // initSubnet4();
+ SharedFlqAllocator::setInUse(false);
+ }
+
+ /// @brief Destructor
+ ///
+ /// Removes library (if any), destroys lease manager (if any).
+ virtual ~SflqCmdsTest() {
+ // destroys lease manager first because the other order triggers
+ // a clang/boost bug
+ isc::dhcp::LeaseMgrFactory::destroy();
+ unloadLibs();
+ lmptr_ = 0;
+ isc::util::file::PathChecker::enableEnforcement(true);
+ }
+
+ /// @brief Creates JSON text command with arguments
+ ///
+ /// @param command command name
+ /// @param args Element map containing the command arguments. May be empty.
+ ///
+ /// @return Command with arguments as JSON text.
+ std::string buildCommand(const std::string& command,
+ ElementPtr args = Element::createMap()) const {
+ std::ostringstream oss;
+ oss << "{ \"command\": \"" << command << "\", \"arguments\": " << *args << "}";
+ return (oss.str());
+ }
+
+ /// Compare two SflqPoolInfo structures for equality.
+ ///
+ /// All members are compared for equality except the timestamps. Those
+ /// are considered correct if the lhs timestamps are greater than or
+ /// equal to their rhs counterparts. Asserts if they are not "equal".
+ ///
+ /// @param lhs left-side instance to compare
+ /// @param rhs reft-side instance to compare
+ /// @param lineno source line of invocation (pass in __LINE__)
+ void checkPoolInfos(const SflqPoolInfo& lhs, const SflqPoolInfo& rhs, int lineno) {
+ ASSERT_TRUE(lhs.lease_type_ == rhs.lease_type_ &&
+ lhs.start_address_ == rhs.start_address_ &&
+ lhs.end_address_ == rhs.end_address_ &&
+ lhs.delegated_len_ == rhs.delegated_len_ &&
+ lhs.subnet_id_ == rhs.subnet_id_ &&
+ lhs.free_leases_ == rhs.free_leases_ &&
+ lhs.created_ts_ >= rhs.created_ts_ &&
+ lhs.modified_ts_ >= rhs.modified_ts_
+ )
+ << "Pools don't match at line " << lineno << std::endl
+ << "lhs: " << *lhs.toElement() << std::endl
+ << "rhs: " << *rhs.toElement() << std::endl;
+ }
+
+ SflqPoolInfoCollectionPtr extractPools(ConstElementPtr cmd_rsp) {
+ if (!cmd_rsp) {
+ isc_throw(BadValue, "cmd_rsp is empty");
+ }
+
+ ConstElementPtr rsp_args = cmd_rsp->get("arguments");
+ if (!rsp_args || rsp_args->getType() != Element::map) {
+ isc_throw(BadValue, "rsp_args missing or not a map");
+ }
+
+ ConstElementPtr pools_elem = rsp_args->get("pools");
+ if (!pools_elem || pools_elem->getType() != Element::list) {
+ isc_throw(BadValue, "pools missing or not a list");
+ }
+
+ auto family = isc::dhcp::CfgMgr::instance().getFamily();
+ SflqPoolInfoCollectionPtr pool_infos(new SflqPoolInfoCollection());
+ for (int i = 0; i < pools_elem->size(); ++i) {
+ auto pool_elem = pools_elem->get(i);
+ SflqPoolInfoPtr pi(new SflqPoolInfo());
+ pi->start_address_ = SimpleParser::getAddress(pool_elem, "start-address");
+ pi->end_address_ = SimpleParser::getAddress(pool_elem, "end-address");
+ pi->subnet_id_ = SimpleParser::getInteger(pool_elem, "subnet-id");
+ pi->lease_type_ = extractLeaseType(pool_elem, family);
+ pi->delegated_len_ = SimpleParser::getInteger(pool_elem, "delegated-len");
+ pi->free_leases_ = SimpleParser::getInteger(pool_elem, "free-leases");
+ pi->created_ts_ = extractTimestamp(pool_elem, "created-ts");
+ pi->modified_ts_ = extractTimestamp(pool_elem, "modified-ts");
+ pool_infos->push_back(pi);
+ }
+
+ return (pool_infos);
+ }
+
+ Lease::Type extractLeaseType(ConstElementPtr& params, uint16_t family) {
+ auto tmp = SimpleParser::getString(params, "lease-type");
+ if (family == AF_INET) {
+ if (tmp == "V4" || tmp == "3") {
+ return (Lease::TYPE_V4);
+ }
+
+ isc_throw(BadValue, "invalid 'lease-type': " << tmp << " must be 'V4'");
+ }
+
+ if (tmp == "IA_NA" || tmp == "0") {
+ return (Lease::TYPE_NA);
+ } else if (tmp == "IA_PD" || tmp == "2") {
+ return (Lease::TYPE_PD);
+ }
+
+ isc_throw(BadValue, "invalid V6 'lease-type': "
+ << tmp << ", valid values are IA_NA and IA_PD");
+ }
+
+ /// @brief Converts a string into a boost::posix_time::ptime
+ ///
+ /// We're doing it this way because boost's function, time_from_string(),
+ /// is part of their date/time library which is not header only.
+ ///
+ /// @param params Element map containing the timestamp
+ /// @param timestamp name of the timestamp element
+ ///
+ /// @throw BadValue if the timestamp is not found or the string
+ /// cannot be converted into a ptime
+ boost::posix_time::ptime extractTimestamp(ConstElementPtr& params,
+ const std::string& name) {
+ auto tmp = SimpleParser::getString(params, name);
+ struct tm tm_result;
+ if (!strptime(tmp.c_str(), "%Y-%m-%d %H:%M:%S", &tm_result)) {
+ isc_throw(BadValue, "cannot convert timestamp string: " << tmp);
+ }
+
+ return (boost::posix_time::ptime_from_tm(tm_result));
+ }
+};
+
+/// @brief Class dedicated to testing v4 SFLQ commands in the lease_cmds library.
+///
+/// These tests use the dhcp::test::SflqTestLeaseMgr for tests as Memfile
+/// does not support SFLQ.
+///
+/// Provides convenience methods for loading, testing the SLFQ commands.
+class SflqCmds4Test : public SflqCmdsTest {
+public:
+
+ /// @brief Constructor
+ SflqCmds4Test()
+ : SflqCmdsTest(AF_INET) {}
+
+ /// @brief Destructor
+ virtual ~SflqCmds4Test() {}
+
+ /// @brief Exercises invalid parameter checks for sflq-pool4-create.
+ void sflqPool4CreateBadParams();
+
+ /// @todo invalid arguments for get-by-subnet, get-by-range, and delete
+
+ /// @brief Exercises all the V4 SFLQ commands.
+ /// The intent is to verify that each command gets the arguments
+ /// it expects, carries out the appropriate action, and returns
+ /// the correct responses.
+ void testSflqCommands();
+};
+
+void SflqCmds4Test::sflqPool4CreateBadParams() {
+ struct Scenario {
+ int line_;
+ std::string args_;
+ std::string exp_rsp_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ __LINE__,
+ R"( )",
+ "missing 'start-address' parameter"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "178.0.0.10"
+ )",
+ "missing 'end-address' parameter"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "178.0.0.10",
+ "end-address" : "178.0.0.20"
+ )",
+ "missing 'subnet-id' parameter"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::",
+ "end-address" : "178.0.0.20",
+ "subnet-id" : 1
+ )",
+ "invalid V4 range - start_address 3001::, end_address 178.0.0.20,"
+ " must be V4 addresses where start_address <= end_address"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "178.0.0.20",
+ "end-address" : "178.0.0.10",
+ "subnet-id" : 1
+ )",
+ "invalid V4 range - start_address 178.0.0.20, end_address 178.0.0.10,"
+ " must be V4 addresses where start_address <= end_address"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "178.0.0.10",
+ "end-address" : "178.0.0.20",
+ "subnet-id" : "bogus"
+ )",
+ "'subnet-id' parameter is not an integer"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "178.0.0.10",
+ "end-address" : "178.0.0.20",
+ "subnet-id" : 0
+ )",
+ "'subnet-id' must be greater than zero"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "178.0.0.10",
+ "end-address" : "178.0.0.20",
+ "subnet-id" : 0,
+ "recreate" : 1
+ )",
+ "'recreate' parameter is not a boolean"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "178.0.0.10",
+ "end-address" : "178.0.0.20",
+ "subnet-id" : 0,
+ "recreate" : true,
+ "bogus" : "fluff"
+ )",
+ "spurious 'bogus' parameter"
+ }};
+
+ for ( auto const& scenario : scenarios) {
+ std::ostringstream oss;
+ oss << "Scenerio at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+ std::ostringstream command;
+ command << R"({ "command": "sflq-pool4-create", "arguments": {)"
+ << scenario.args_ << "}}";
+
+ testCommand(command.str(), CONTROL_RESULT_ERROR, scenario.exp_rsp_);
+ }
+}
+
+void
+SflqCmds4Test::testSflqCommands() {
+ auto test_start = boost::posix_time::second_clock::local_time();
+
+ // Fetching all pools should find none.
+ auto command = buildCommand("sflq-pool4-get-all");
+ testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+
+ // Create three test pool infos.
+ SflqPoolInfoCollection test_pools;
+ SflqPoolInfoPtr pi;
+
+ pi.reset(new SflqPoolInfo());
+ pi->start_address_ = IOAddress("192.0.1.0");
+ pi->end_address_ = IOAddress("192.0.1.2");
+ pi->subnet_id_ = 1;
+ pi->created_ts_ = test_start;
+ pi->modified_ts_ = test_start;
+ pi->free_leases_ = 3;
+ test_pools.push_back(pi);
+
+ pi.reset(new SflqPoolInfo());
+ pi->start_address_ = IOAddress("192.0.2.0");
+ pi->end_address_ = IOAddress("192.0.2.2");
+ pi->subnet_id_ = 2;
+ pi->created_ts_ = test_start;
+ pi->modified_ts_ = test_start;
+ pi->free_leases_ = 3;
+ test_pools.push_back(pi);
+
+ pi.reset(new SflqPoolInfo());
+ pi->start_address_ = IOAddress("192.0.3.0");
+ pi->end_address_ = IOAddress("192.0.3.2");
+ pi->subnet_id_ = 1;
+ pi->created_ts_ = test_start;
+ pi->modified_ts_ = test_start;
+ pi->free_leases_ = 3;
+ test_pools.push_back(pi);
+
+ // Now create the pools.
+ for ( auto const& test_pool : test_pools) {
+ auto args = test_pool->toElement();
+ // Take out what we don't need.
+ args->remove("lease-type");
+ args->remove("delegated-len");
+ args->remove("created-ts");
+ args->remove("modified-ts");
+ args->remove("free-leases");
+ // Add recreate flag.
+ args->set("recreate", Element::create(false));
+
+ // Initial create should work.
+ command = buildCommand("sflq-pool4-create", args);
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+
+ // Trying again without recreate should say it already exists.
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool already exists.");
+
+ // Recreating it should work.
+ args->set("recreate", Element::create(true));
+ command = buildCommand("sflq-pool4-create", args);
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+ }
+
+ // Fetching all pools should find all three.
+ command = buildCommand("sflq-pool4-get-all");
+ auto cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+ auto pool_infos = extractPools(cmd_rsp);
+
+ // Should get them back in order they were created. Dummy back end doesn't sort.
+ for (int i = 0; i < test_pools.size(); ++i) {
+ checkPoolInfos(*(*pool_infos)[i], *test_pools[i], __LINE__);
+ }
+
+ // Fetch by subnet id for subnet_id = 1.
+ pool_infos.reset();
+ auto args = Element::fromJSON("{\"subnet-id\": 1}");
+ command = buildCommand("sflq-pool4-get-by-subnet", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "2 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(2, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+ checkPoolInfos(*(*pool_infos)[1], *test_pools[2], __LINE__);
+
+ // Fetch by subnet id for subnet_id = 2.
+ pool_infos.reset();
+ args = Element::fromJSON("{\"subnet-id\": 2}");
+ command = buildCommand("sflq-pool4-get-by-subnet", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(1, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pools[1], __LINE__);
+
+ // Fetch by subnet id for subnet_id = 99
+ pool_infos.reset();
+ pool_infos.reset();
+ args = Element::fromJSON("{\"subnet-id\": 99}");
+ command = buildCommand("sflq-pool4-get-by-subnet", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(0, pool_infos->size());
+
+ // Fetch by a range that excludes them all.
+ pool_infos.reset();
+ args = Element::fromJSON("{\"start-address\": \"1.2.3.4\","
+ " \"end-address\": \"1.2.3.4\"}");
+ command = buildCommand("sflq-pool4-get-by-range", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(0, pool_infos->size());
+
+ // Fetch by a range that includes them all.
+ pool_infos.reset();
+ args = Element::fromJSON("{\"start-address\": \"192.0.0.0\","
+ " \"end-address\": \"192.0.4.0\"}");
+ command = buildCommand("sflq-pool4-get-by-range", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(3, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+ checkPoolInfos(*(*pool_infos)[1], *test_pools[1], __LINE__);
+ checkPoolInfos(*(*pool_infos)[2], *test_pools[2], __LINE__);
+
+ // Fetch each by exact range match.
+ for ( auto const& test_pool : test_pools) {
+ pool_infos.reset();
+ args = Element::createMap();
+ args->set("start-address", Element::create(test_pool->start_address_.toText()));
+ args->set("end-address", Element::create(test_pool->end_address_.toText()));
+ command = buildCommand("sflq-pool4-get-by-range", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(1, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pool, __LINE__);
+ }
+
+ // Delete each pool.
+ for ( auto const& test_pool : test_pools) {
+ // Delete the pool.
+ args = Element::createMap();
+ args->set("start-address", Element::create(test_pool->start_address_.toText()));
+ args->set("end-address", Element::create(test_pool->end_address_.toText()));
+ command = buildCommand("sflq-pool4-del", args);
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool deleted");
+
+ // Try again, no such lease.
+ testCommand(command, CONTROL_RESULT_EMPTY, "SFLQ pool does not exist");
+ }
+}
+
+TEST_F(SflqCmds4Test, sflqPool4CreateBadParams) {
+ sflqPool4CreateBadParams();
+}
+
+TEST_F(SflqCmds4Test, sflqPool4CreateBadParamsMt) {
+ MultiThreadingTest mt(true);
+ sflqPool4CreateBadParams();
+}
+
+TEST_F(SflqCmds4Test, testSflqCommands) {
+ testSflqCommands();
+}
+
+TEST_F(SflqCmds4Test, testSflqCommandsMt) {
+ MultiThreadingTest mt(true);
+ testSflqCommands();
+}
+
+/// @brief Class dedicated to testing v6 SFLQ commands in the lease_cmds library.
+///
+/// These tests use the dhcp::test::SflqTestLeaseMgr for tests as Memfile
+/// does not support SFLQ.
+///
+/// Provides convenience methods for loading, testing the SLFQ commands.
+class SflqCmds6Test : public SflqCmdsTest {
+public:
+
+ /// @brief Constructor
+ SflqCmds6Test()
+ : SflqCmdsTest(AF_INET6) {}
+
+ /// @brief Destructor
+ virtual ~SflqCmds6Test() {}
+
+ /// @brief Exercises invalid parameter checks for sflq-pool6-create.
+ void sflqPool6CreateBadParams();
+
+ /// @todo invalid arguments for get-by-subnet, get-by-range, and delete
+
+ /// @brief Exercises all the V4 SFLQ commands.
+ ///
+ /// The intent is to verify that each command gets the arguments
+ /// it expects, carries out the appropriate action, and returns
+ /// the correct responses.
+ ///
+ /// @param lease_type lease type to test IA_NA or IA_PD
+ void testSflqCommands(Lease::Type lease_type);
+};
+
+void SflqCmds6Test::sflqPool6CreateBadParams() {
+ struct Scenario {
+ int line_;
+ std::string args_;
+ std::string exp_rsp_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ __LINE__,
+ R"( )",
+ "missing 'start-address' parameter"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10"
+ )",
+ "missing 'end-address' parameter"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20"
+ )",
+ "missing 'subnet-id' parameter"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 1
+ )",
+ "missing 'lease-type' parameter"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "178.0.0.20",
+ "subnet-id" : 1,
+ "lease-type": "IA_NA"
+ )",
+ "invalid V6 range - start_address 3001::10, end_address 178.0.0.20,"
+ " must be V6 addresses where start_address <= end_address"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::20",
+ "end-address" : "3001::10",
+ "subnet-id" : 1,
+ "lease-type": "IA_NA"
+ )",
+ "invalid V6 range - start_address 3001::20, end_address 3001::10,"
+ " must be V6 addresses where start_address <= end_address"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : "bogus",
+ "lease-type": "IA_NA"
+ )",
+ "'subnet-id' parameter is not an integer"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 0,
+ "lease-type": "IA_NA"
+ )",
+ "'subnet-id' must be greater than zero"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 1,
+ "lease-type": "bogus"
+ )",
+ "invalid V6 'lease-type': bogus, valid values are IA_NA and IA_PD"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 1,
+ "lease-type": "IA_PD",
+ "delegated-len": "bogus"
+ )",
+ "'delegated-len' parameter is not an integer"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 1,
+ "lease-type": "IA_PD",
+ "delegated-len": 0
+ )",
+ "'delegated-len' invalid: 0, it must be >= 1 and =< 128"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 1,
+ "lease-type": "IA_PD",
+ "delegated-len": 129
+ )",
+ "'delegated-len' invalid: 129, it must be >= 1 and =< 128"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 0,
+ "lease-type": "IA_NA",
+ "recreate" : 1
+ )",
+ "'recreate' parameter is not a boolean"
+ },
+ {
+ __LINE__,
+ R"(
+ "start-address" : "3001::10",
+ "end-address" : "3001::20",
+ "subnet-id" : 0,
+ "lease-type": "IA_NA",
+ "recreate" : true,
+ "bogus" : "fluff"
+ )",
+ "spurious 'bogus' parameter"
+ }};
+
+ for ( auto const& scenario : scenarios) {
+ std::ostringstream oss;
+ oss << "Scenerio at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+ std::ostringstream command;
+ command << R"({ "command": "sflq-pool6-create", "arguments": {)"
+ << scenario.args_ << "}}";
+
+ testCommand(command.str(), CONTROL_RESULT_ERROR, scenario.exp_rsp_);
+ }
+}
+
+void
+SflqCmds6Test::testSflqCommands(Lease::Type lease_type) {
+ auto test_start = boost::posix_time::second_clock::local_time();
+
+ // Fetching all pools should find none.
+ auto command = buildCommand("sflq-pool6-get-all");
+ auto cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+ auto pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(0, pool_infos->size());
+
+ // Create three test pool infos.
+ SflqPoolInfoCollection test_pools;
+ SflqPoolInfoPtr pi;
+ pi.reset(new SflqPoolInfo());
+ pi->start_address_ = IOAddress("3001::10");
+ pi->end_address_ = IOAddress("3001::12");
+ pi->subnet_id_ = 1;
+ pi->lease_type_ = lease_type;
+ pi->created_ts_ = test_start;
+ pi->modified_ts_ = test_start;
+ pi->free_leases_ = 3;
+ test_pools.push_back(pi);
+
+ pi.reset(new SflqPoolInfo());
+ pi->start_address_ = IOAddress("3001::20");
+ pi->end_address_ = IOAddress("3001::22");
+ pi->subnet_id_ = 2;
+ pi->lease_type_ = lease_type;
+ pi->created_ts_ = test_start;
+ pi->modified_ts_ = test_start;
+ pi->free_leases_ = 3;
+ test_pools.push_back(pi);
+
+
+ pi.reset(new SflqPoolInfo());
+ pi->start_address_ = IOAddress("3001::30");
+ pi->end_address_ = IOAddress("3001::32");
+ pi->subnet_id_ = 1;
+ pi->lease_type_ = lease_type;
+ pi->created_ts_ = test_start;
+ pi->modified_ts_ = test_start;
+ pi->free_leases_ = 3;
+ test_pools.push_back(pi);
+
+ // Now create the pools.
+ for ( auto const& test_pool : test_pools) {
+ auto args = test_pool->toElement();
+ // Take out what we don't need.
+ args->remove("created-ts");
+ args->remove("modified-ts");
+ args->remove("free-leases");
+ // Add recreate flag.
+ args->set("recreate", Element::create(false));
+
+ // Initial create should work.
+ command = buildCommand("sflq-pool6-create", args);
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+
+ // Trying again without recreate should say it already exists.
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool already exists.");
+
+ // Recreating it should work.
+ args->set("recreate", Element::create(true));
+ command = buildCommand("sflq-pool6-create", args);
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+
+ }
+
+ // Fetching all pools should find all three.
+ command = buildCommand("sflq-pool6-get-all");
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+
+ // Should get them back in order they were created. Dummy back end doesn't sort.
+ for (int i = 0; i < test_pools.size(); ++i) {
+ checkPoolInfos(*(*pool_infos)[i], *test_pools[i], __LINE__);
+ }
+
+ // Fetch by subnet id for subnet_id = 1.
+ pool_infos.reset();
+ auto args = Element::fromJSON("{\"subnet-id\": 1}");
+ command = buildCommand("sflq-pool6-get-by-subnet", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "2 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(2, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+ checkPoolInfos(*(*pool_infos)[1], *test_pools[2], __LINE__);
+
+ // Fetch by subnet id for subnet_id = 2.
+ pool_infos.reset();
+ args = Element::fromJSON("{\"subnet-id\": 2}");
+ command = buildCommand("sflq-pool6-get-by-subnet", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(1, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pools[1], __LINE__);
+
+ // Fetch by subnet id for subnet_id = 99
+ pool_infos.reset();
+ args = Element::fromJSON("{\"subnet-id\": 99}");
+ command = buildCommand("sflq-pool6-get-by-subnet", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(0, pool_infos->size());
+
+ // Fetch by a range that excludes them all.
+ pool_infos.reset();
+ args = Element::fromJSON("{\"start-address\": \"2001::1\","
+ " \"end-address\": \"2001::2\"}");
+ command = buildCommand("sflq-pool6-get-by-range", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(0, pool_infos->size());
+
+ // Fetch by a range that includes them all.
+ pool_infos.reset();
+ args = Element::fromJSON("{\"start-address\": \"3001::\","
+ " \"end-address\": \"3001::FF\"}");
+ command = buildCommand("sflq-pool6-get-by-range", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(3, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+ checkPoolInfos(*(*pool_infos)[1], *test_pools[1], __LINE__);
+ checkPoolInfos(*(*pool_infos)[2], *test_pools[2], __LINE__);
+
+ // Fetch each by exact range match.
+ for ( auto const& test_pool : test_pools) {
+ pool_infos.reset();
+ args = Element::createMap();
+ args->set("start-address", Element::create(test_pool->start_address_.toText()));
+ args->set("end-address", Element::create(test_pool->end_address_.toText()));
+ command = buildCommand("sflq-pool6-get-by-range", args);
+ cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+ pool_infos = extractPools(cmd_rsp);
+ ASSERT_TRUE(pool_infos);
+ ASSERT_EQ(1, pool_infos->size());
+ checkPoolInfos(*(*pool_infos)[0], *test_pool, __LINE__);
+ }
+
+ // Delete each pool.
+ for ( auto const& test_pool : test_pools) {
+ // Delete the pool.
+ args = Element::createMap();
+ args->set("start-address", Element::create(test_pool->start_address_.toText()));
+ args->set("end-address", Element::create(test_pool->end_address_.toText()));
+ command = buildCommand("sflq-pool6-del", args);
+ testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool deleted");
+
+ // Try again, no such lease.
+ testCommand(command, CONTROL_RESULT_EMPTY, "SFLQ pool does not exist");
+ }
+}
+
+TEST_F(SflqCmds6Test, sflqPool6CreateBadParams) {
+ sflqPool6CreateBadParams();
+}
+
+TEST_F(SflqCmds6Test, sflqPool6CreateBadParamsMt) {
+ MultiThreadingTest mt(true);
+ sflqPool6CreateBadParams();
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsNA) {
+ testSflqCommands(Lease::TYPE_NA);
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsNAMt) {
+ MultiThreadingTest mt(true);
+ testSflqCommands(Lease::TYPE_NA);
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsPD) {
+ testSflqCommands(Lease::TYPE_PD);
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsPDMt) {
+ MultiThreadingTest mt(true);
+ testSflqCommands(Lease::TYPE_PD);
+}
+
+
+} // end of anonymous namespace
'lease_cmds_log.cc',
'lease_cmds_messages.cc',
'lease_parser.cc',
+ 'sflq_cmds.cc',
'version.cc',
dependencies: [CRYPTO_DEP],
include_directories: [include_directories('.')] + INCLUDES,
--- /dev/null
+// Copyright (C) 2026 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <config/cmds_impl.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <database/db_exceptions.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet_id.h>
+#include <hooks/hooks.h>
+#include <exceptions/exceptions.h>
+#include <lease_cmds_log.h>
+#include <sflq_cmds.h>
+#include <util/multi_threading_mgr.h>
+
+#include <string>
+#include <sstream>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::asiolink;
+using namespace isc::hooks;
+using namespace isc::util;
+using namespace isc::log;
+using namespace std;
+
+namespace isc {
+namespace lease_cmds {
+
+int
+SflqCmdsImpl::sflqPool4CreateHandler(CalloutHandle& handle) {
+ static const data::SimpleKeywords keywords =
+ {
+ { "start-address", Element::string },
+ { "end-address", Element::string },
+ { "subnet-id", Element::integer },
+ { "recreate", Element::boolean }
+ };
+
+ static const data::SimpleRequiredKeywords required_keywords =
+ {
+ "start-address",
+ "end-address",
+ "subnet-id",
+ };
+
+ stringstream resp;
+ try {
+ extractCommand(handle);
+ if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+ isc_throw(isc::BadValue, "no parameters specified for the command");
+ }
+
+ SimpleParser::checkRequired(required_keywords, cmd_args_);
+ SimpleParser::checkKeywords(keywords, cmd_args_);
+
+ // Fetch the command parameters.
+ IOAddress start_address(IOAddress::IPV4_ZERO_ADDRESS());
+ IOAddress end_address(IOAddress::IPV4_ZERO_ADDRESS());
+ extractRange(cmd_args_, AF_INET, start_address, end_address);
+
+ /// @todo Should we verify the subnet-id and/or check allocator for subnet?
+ SubnetID subnet_id = SimpleParser::getInteger(cmd_args_, "subnet-id");
+ if (subnet_id <= 0) {
+ isc_throw(isc::BadValue, "'subnet-id' must be greater than zero");
+ }
+
+ bool recreate = extractBool(cmd_args_, "recreate", false);
+
+ // Invoke the pool create function inside a CriticalSection.
+ MultiThreadingCriticalSection cs;
+ bool created = LeaseMgrFactory::instance().
+ sflqCreateFlqPool4(start_address, end_address, subnet_id, recreate);
+
+ resp << (created ? "SFLQ pool created" : "SFLQ pool already exists") << ".";
+ } catch (const std::exception& ex) {
+ LOG_ERROR(lease_cmds_logger, SFLQ_POOL4_CREATE_FAILED)
+ .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+ .arg(ex.what());
+ setErrorResponse(handle, ex.what());
+ return (1);
+ }
+
+ LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA, SFLQ_POOL4_CREATE)
+ .arg(cmd_args_->str());
+
+ setSuccessResponse(handle, resp.str());
+ return (0);
+}
+
+int
+SflqCmdsImpl::sflqPool4GetAllHandler(CalloutHandle& handle) {
+ return (sflqPoolGetAll(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool4GetBySubnetHandler(CalloutHandle& handle) {
+ return (sflqPoolGetBySubnet(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool4GetByRangeHandler(CalloutHandle& handle) {
+ return (sflqPoolGetByRange(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool4DelHandler(CalloutHandle& handle) {
+ return (sflqPoolDel(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool6CreateHandler(CalloutHandle& handle) {
+ static const data::SimpleKeywords keywords =
+ {
+ { "start-address", Element::string },
+ { "end-address", Element::string },
+ { "subnet-id", Element::integer },
+ { "lease-type", Element::string },
+ { "delegated-len", Element::integer },
+ { "recreate", Element::boolean }
+ };
+
+ static const data::SimpleRequiredKeywords required_keywords =
+ {
+ "start-address",
+ "end-address",
+ "subnet-id",
+ "lease-type"
+ };
+
+ stringstream resp;
+ try {
+ extractCommand(handle);
+ if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+ isc_throw(isc::BadValue, "no parameters specified for the command");
+ }
+
+ SimpleParser::checkRequired(required_keywords, cmd_args_);
+ SimpleParser::checkKeywords(keywords, cmd_args_);
+
+ // Fetch the command parameters.
+ IOAddress start_address(IOAddress::IPV6_ZERO_ADDRESS());
+ IOAddress end_address(IOAddress::IPV6_ZERO_ADDRESS());
+ extractRange(cmd_args_, AF_INET6, start_address, end_address);
+
+ /// @todo Should we verify the subnet-id and/or check allocator for subnet?
+ SubnetID subnet_id = SimpleParser::getInteger(cmd_args_, "subnet-id");
+ if (subnet_id <= 0) {
+ isc_throw(isc::BadValue, "'subnet-id' must be greater than zero");
+ }
+
+ auto lease_type = extractLeaseType(cmd_args_, AF_INET6);
+ uint8_t delegated_len = extractDelegatedLen(cmd_args_);
+ bool recreate = extractBool(cmd_args_, "recreate", false);
+
+ // Invoke the pool create function inside a CriticalSection.
+ MultiThreadingCriticalSection cs;
+ bool created = LeaseMgrFactory::instance().
+ sflqCreateFlqPool6(start_address, end_address, lease_type,
+ delegated_len, subnet_id, recreate);
+
+ resp << (created ? "SFLQ pool created" : "SFLQ pool already exists") << ".";
+ } catch (const std::exception& ex) {
+ LOG_ERROR(lease_cmds_logger, SFLQ_POOL6_CREATE_FAILED)
+ .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+ .arg(ex.what());
+ setErrorResponse(handle, ex.what());
+ return (1);
+ }
+
+ LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA, SFLQ_POOL6_CREATE)
+ .arg(cmd_args_->str());
+
+ setSuccessResponse(handle, resp.str());
+ return (0);
+}
+
+int
+SflqCmdsImpl::sflqPool6GetAllHandler(CalloutHandle& handle) {
+ return (sflqPoolGetAll(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPool6GetBySubnetHandler(CalloutHandle& handle) {
+ return (sflqPoolGetBySubnet(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPool6GetByRangeHandler(CalloutHandle& handle) {
+ return (sflqPoolGetByRange(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPool6DelHandler(CalloutHandle& handle) {
+ return (sflqPoolDel(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPoolGetAll(CalloutHandle& handle, uint16_t family) {
+ static const data::SimpleKeywords keywords;
+ try {
+ extractCommand(handle);
+ if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+ isc_throw(isc::BadValue, "no parameters specified for the command");
+ }
+
+ // Command has no arguments, this will catch any supplied.
+ SimpleParser::checkKeywords(keywords, cmd_args_);
+
+ // Invoke the pool get function.
+ auto pools = (family == AF_INET ? LeaseMgrFactory::instance().sflqPool4GetAll()
+ : LeaseMgrFactory::instance().sflqPool6GetAll());
+
+ auto resp = buildGetResponse(pools);
+ setResponse(handle, resp);
+ LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+ (family == AF_INET ? SFLQ_POOL4_GET_ALL : SFLQ_POOL6_GET_ALL))
+ .arg(cmd_args_->str())
+ .arg(pools->size());
+ } catch (const std::exception& ex) {
+ LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_GET_ALL_FAILED
+ : SFLQ_POOL6_GET_ALL_FAILED))
+ .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+ .arg(ex.what());
+ setErrorResponse(handle, ex.what());
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+SflqCmdsImpl::sflqPoolGetBySubnet(CalloutHandle& handle, uint16_t family) {
+ static const data::SimpleKeywords keywords =
+ {
+ { "subnet-id", Element::integer },
+ };
+
+ static const data::SimpleRequiredKeywords required_keywords =
+ {
+ "subnet-id",
+ };
+
+ try {
+ extractCommand(handle);
+ if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+ isc_throw(isc::BadValue, "no parameters specified for the command");
+ }
+
+ SimpleParser::checkRequired(required_keywords, cmd_args_);
+ SimpleParser::checkKeywords(keywords, cmd_args_);
+ SubnetID subnet_id = SimpleParser::getInteger(cmd_args_, "subnet-id");
+
+ // Invoke the pool get by subnet function.
+ auto pools = (family == AF_INET ? LeaseMgrFactory::instance().sflqPool4Get(subnet_id)
+ : LeaseMgrFactory::instance().sflqPool6Get(subnet_id));
+
+ auto resp = buildGetResponse(pools);
+ setResponse(handle, resp);
+ LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+ (family == AF_INET ? SFLQ_POOL4_GET_BY_SUBNET : SFLQ_POOL6_GET_BY_SUBNET))
+ .arg(cmd_args_->str())
+ .arg(pools->size());
+ } catch (const std::exception& ex) {
+ LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_GET_BY_SUBNET_FAILED
+ : SFLQ_POOL6_GET_BY_SUBNET_FAILED))
+ .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+ .arg(ex.what());
+ setErrorResponse(handle, ex.what());
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+SflqCmdsImpl::sflqPoolGetByRange(CalloutHandle& handle, uint16_t family) {
+ static const data::SimpleKeywords keywords =
+ {
+ { "start-address", Element::string },
+ { "end-address", Element::string },
+ };
+
+ static const data::SimpleRequiredKeywords required_keywords =
+ {
+ "start-address",
+ "end-address",
+ };
+
+ try {
+ extractCommand(handle);
+ if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+ isc_throw(isc::BadValue, "no parameters specified for the command");
+ }
+
+ SimpleParser::checkRequired(required_keywords, cmd_args_);
+ SimpleParser::checkKeywords(keywords, cmd_args_);
+
+ // Fetch the command parameters.
+ IOAddress start_address(IOAddress::IPV4_ZERO_ADDRESS());
+ IOAddress end_address(IOAddress::IPV4_ZERO_ADDRESS());
+ extractRange(cmd_args_, family, start_address, end_address);
+
+ // Invoke the pool get by subnet function.
+ auto pools = (family == AF_INET
+ ? LeaseMgrFactory::instance().sflqPool4Get(start_address, end_address)
+ : LeaseMgrFactory::instance().sflqPool6Get(start_address, end_address));
+
+ auto resp = buildGetResponse(pools);
+ setResponse(handle, resp);
+ LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+ (family == AF_INET ? SFLQ_POOL4_GET_BY_RANGE : SFLQ_POOL6_GET_BY_RANGE))
+ .arg(cmd_args_->str())
+ .arg(pools->size());
+ } catch (const std::exception& ex) {
+ LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_GET_BY_RANGE_FAILED
+ : SFLQ_POOL6_GET_BY_RANGE_FAILED))
+ .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+ .arg(ex.what());
+ setErrorResponse(handle, ex.what());
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+SflqCmdsImpl::sflqPoolDel(CalloutHandle& handle, uint16_t family) {
+ static const data::SimpleKeywords keywords =
+ {
+ { "start-address", Element::string },
+ { "end-address", Element::string },
+ { "force", Element::boolean },
+ };
+
+ static const data::SimpleRequiredKeywords required_keywords =
+ {
+ "start-address",
+ "end-address",
+ };
+
+ try {
+ extractCommand(handle);
+ if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+ isc_throw(isc::BadValue, "no parameters specified for the command");
+ }
+
+ SimpleParser::checkRequired(required_keywords, cmd_args_);
+ SimpleParser::checkKeywords(keywords, cmd_args_);
+
+ // Fetch the command parameters.
+ IOAddress start_address(IOAddress::IPV4_ZERO_ADDRESS());
+ IOAddress end_address(IOAddress::IPV4_ZERO_ADDRESS());
+ extractRange(cmd_args_, family, start_address, end_address);
+ bool force = extractBool(cmd_args_, "force", false);
+
+ // Invoke the pool get by subnet function.
+ bool deleted = (family == AF_INET
+ ? LeaseMgrFactory::instance().sflqPool4Del(start_address,
+ end_address, force)
+ : LeaseMgrFactory::instance().sflqPool6Del(start_address,
+ end_address, force));
+
+ if (deleted) {
+ auto response = createAnswer(CONTROL_RESULT_SUCCESS,
+ "SFLQ pool deleted", cmd_args_);
+ setResponse(handle, response);
+ } else {
+ auto response = createAnswer(CONTROL_RESULT_EMPTY,
+ "SFLQ pool does not exist", cmd_args_);
+ setResponse(handle, response);
+ }
+
+ LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+ (family == AF_INET ? SFLQ_POOL4_DEL : SFLQ_POOL6_DEL))
+ .arg(cmd_args_->str())
+ .arg(deleted ? 1 : 0);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_DEL_FAILED
+ : SFLQ_POOL6_DEL_FAILED))
+ .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+ .arg(ex.what());
+ setErrorResponse(handle, ex.what());
+ return (1);
+ }
+
+ return (0);
+}
+
+ConstElementPtr
+SflqCmdsImpl::buildGetResponse(SflqPoolInfoCollectionPtr pools) {
+ ElementPtr pools_json = Element::createList();
+ for (auto const& pool : *pools) {
+ pools_json->add(pool->toElement());
+ }
+
+ auto pool_cnt = pools_json->size();
+ stringstream resp_msg;
+ resp_msg << pool_cnt << " pool(s) found.";
+
+ ElementPtr args = Element::createMap();
+ args->set("pools", pools_json);
+
+ auto response = createAnswer(pool_cnt > 0 ?
+ CONTROL_RESULT_SUCCESS : CONTROL_RESULT_EMPTY,
+ resp_msg.str(), args);
+ return(response);
+}
+
+
+void
+SflqCmdsImpl::extractRange(ConstElementPtr& params, uint8_t family,
+ IOAddress& start_address, IOAddress& end_address) {
+ start_address = SimpleParser::getAddress(params, "start-address");
+ end_address = SimpleParser::getAddress(params, "end-address");
+ if (family == AF_INET) {
+ validateV4Range(start_address, end_address);
+ } else {
+ validateV6Range(start_address, end_address);
+ }
+}
+
+bool
+SflqCmdsImpl::extractBool(ConstElementPtr& params, const std::string& name,
+ bool default_value) {
+ auto tmp = params->get(name);
+ if (!tmp) {
+ return (default_value);
+ }
+
+ if (tmp->getType() != Element::boolean) {
+ isc_throw(BadValue, "'" << name << "' parameter is not boolean.");
+ }
+
+ return (tmp->boolValue());
+}
+
+Lease::Type
+SflqCmdsImpl::extractLeaseType(ConstElementPtr& params, uint16_t family) {
+ auto tmp = SimpleParser::getString(params, "lease-type");
+ if (family == AF_INET) {
+ if (tmp == "V4" || tmp == "3") {
+ return (Lease::TYPE_V4);
+ }
+
+ isc_throw(BadValue, "invalid 'lease-type': " << tmp << " must be 'V4'");
+ }
+
+ if (tmp == "IA_NA" || tmp == "0") {
+ return (Lease::TYPE_NA);
+ } else if (tmp == "IA_PD" || tmp == "2") {
+ return (Lease::TYPE_PD);
+ }
+
+ isc_throw(BadValue, "invalid V6 'lease-type': "
+ << tmp << ", valid values are IA_NA and IA_PD");
+}
+
+uint8_t
+SflqCmdsImpl::extractDelegatedLen(ConstElementPtr& params) {
+ auto tmp = params->get("delegated-len");
+ if (!tmp) {
+ return (128);
+ }
+
+ if (tmp->getType() != Element::integer) {
+ isc_throw(BadValue, "'delegated-len' parameter is not integer.");
+ }
+
+ auto val = tmp->intValue();
+ if (val <= 0 || val > 128) {
+ isc_throw(BadValue, "'delegated-len' invalid: " << val
+ << ", it must be >= 1 and =< 128");
+ }
+
+ return (val);
+}
+
+SflqCmds::SflqCmds()
+ : sflq_impl_(new SflqCmdsImpl()) {
+}
+
+int
+SflqCmds::sflqPool4CreateHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool4CreateHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4GetAllHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool4GetAllHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4GetBySubnetHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool4GetBySubnetHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4GetByRangeHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool4GetByRangeHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4DelHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool4DelHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6CreateHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool6CreateHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6GetAllHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool6GetAllHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6GetBySubnetHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool6GetBySubnetHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6GetByRangeHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool6GetByRangeHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6DelHandler(CalloutHandle& handle) {
+ return (sflq_impl_->sflqPool6DelHandler(handle));
+}
+
+} // end of namespace lease_cmds
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2026 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SFLQ_CMDS_H
+#define SFLQ_CMDS_H
+
+#include <config.h>
+#include <config/cmds_impl.h>
+#include <cc/data.h>
+#include <asiolink/io_address.h>
+#include <database/db_exceptions.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/subnet_id.h>
+
+#include <string>
+#include <sstream>
+
+namespace isc {
+namespace lease_cmds {
+
+/// @brief Implements the logic for processing commands pertaining to
+/// SFLQ pools and data.
+class SflqCmdsImpl : private config::CmdsImpl {
+public:
+ /// @brief Consstructor.
+ SflqCmdsImpl() {};
+
+ /// @brief Destructor.
+ virtual ~SflqCmdsImpl() {};
+
+ /// @brief The 'sflq-pool4-create' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-create",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255",
+ /// "subnet-id": 77,
+ /// "recreate": true
+ /// }
+ /// }
+ ///
+ /// The 'recreate' parameter is optional, it defaults false.
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4CreateHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool4-get-all' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-get-all",
+ /// "arguments": {
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4GetAllHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool4-by-subnet' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-get-by-subnet",
+ /// "arguments": {
+ /// "subnet-id" : 100
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Handles a 'sflq-pool4-by-range' command
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-get-by-range",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255"
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4GetByRangeHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Delete the SFLQ V4 pool that matches a start and end address.
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-del",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255",
+ /// "force": false
+ /// }
+ /// }
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4DelHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool6-create' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-create",
+ /// "arguments": {
+ /// "start-address": "3001::",
+ /// "end-address": "3001::FF",
+ /// "subnet-id": 77,
+ /// "lease-type" : "IA_PD",
+ /// "delegated_len: 64
+ /// "recreate": true
+ /// }
+ /// }
+ ///
+ /// The 'recreate' parameter is optional, it defaults false.
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6CreateHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool6-get-all' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-get-all",
+ /// "arguments": {
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6GetAllHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool6-by-subnet' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-get-by-subnet",
+ /// "arguments": {
+ /// "subnet-id" : 100
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Handles a 'sflq-pool6-by-range' command
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-get-by-range",
+ /// "arguments": {
+ /// "start-address": "3001::",
+ /// "end-address": "3001::FFFF"
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6GetByRangeHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Delete the SFLQ V6 pool that matches a start and end address.
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-del",
+ /// "arguments": {
+ /// "start-address": "3001::",
+ /// "end-address": "3001::FFFF",
+ /// "force": false
+ /// }
+ /// }
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6DelHandler(hooks::CalloutHandle& handle);
+
+private:
+ /// @brief The 'sflq-poolX-get-all' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-poolX-get-all",
+ /// "arguments": {
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @param family Protocol family AF_NET or AF_INET6
+ /// @return result of the operation
+ int sflqPoolGetAll(hooks::CalloutHandle& handle, uint16_t family);
+
+ /// @brief Handles a 'sflq-poolX-by-subnet' command
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-poolX-get-by-subnet",
+ /// "arguments": {
+ /// "subnet-id" : 100
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @param family Protocol family AF_NET or AF_INET6
+ /// @return result of the operation
+ int sflqPoolGetBySubnet(hooks::CalloutHandle& handle, uint16_t family);
+
+ /// @brief Handles a 'sflq-poolX-by-range' command
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-poolX-get-by-range",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255"
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @param family Protocol family AF_NET or AF_INET6
+ /// @return result of the operation
+ int sflqPoolGetByRange(hooks::CalloutHandle& handle, uint16_t family);
+
+ /// @brief Handles a 'sflq-poolX-del' command
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-poolX-del",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255",
+ /// "force": false
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @param family Protocol family AF_NET or AF_INET6
+ /// @return result of the operation
+ int sflqPoolDel(hooks::CalloutHandle& handle, uint16_t family);
+
+public:
+ /// @brief Creates a success response from a list of SqlPoolInfos.
+ ///
+ /// @param pools list of pools to include in the response. May be empty.
+ ///
+ /// @return API response structure with a result code of CONTROL_RESULT_SUCCESS
+ /// if the list of pools is not empty, otherwise CONTROL_RESULT_EMPTY.
+ static data::ConstElementPtr buildGetResponse(dhcp::SflqPoolInfoCollectionPtr pools);
+
+ /// @brief Extracts an ip address range from given parameters map
+ ///
+ /// Expects the map to contain valid addressess of the the given
+ /// family (AF_INET or AF_INET6), specified as 'start-address' and
+ /// 'end-address' and where the former is less than or equal to the
+ /// latter.
+ ///
+ /// @param params Element map containging the command arguments.
+ /// @param family protocol family (AF_INET or AF_INET6).
+ /// @param[out] start_address IOAddress reference which receives the
+ /// extracted start-address value
+ /// @param[out] end_address IOAddress reference which receives the
+ /// extracted end-address value
+ ///
+ /// @throw BadValue is either parameter is missing, is not a valid
+ /// ip address or they do not constitute a valid range.
+ static void extractRange(data::ConstElementPtr& params, uint8_t family,
+ asiolink::IOAddress& start_address,
+ asiolink::IOAddress& end_address);
+
+ /// @brief Extracts a boolean from given parameters map
+ ///
+ /// Extracts a boolean element from the given map and returns its
+ /// value. If the parameter is not found in the map, the function will
+ /// the function will return the default value.
+ ///
+ /// @param params Element map containging the command arguments.
+ /// @param name name of the desired parameter.
+ /// @param default_value value to return if the parameter is optional and
+ /// not specified.
+ ///
+ /// @throw BadValue is either parameter is missing, is not a valid
+ /// ip address or they do not constitute a valid range.
+ static bool extractBool(data::ConstElementPtr& params, const std::string& name,
+ bool default_value = false);
+
+ /// @brief Extracts 'delegated-len' from given parameters map
+ ///
+ /// Extracts an integer element, 'delegate-len' from the given map
+ /// if it is present and is >= 1 and <= 128. If it is not present
+ /// it returns a value of 128.
+ ///
+ /// @param params Element map containging the command arguments.
+ ///
+ /// @throw BadValue if the parameter is specified as anything other
+ /// than an integer value >= 1 and <= 128.
+ static uint8_t extractDelegatedLen(data::ConstElementPtr& params);
+
+ /// @brief Extracts 'delegated-len' from given parameters map
+ ///
+ /// Extracts a string element, 'lease-type' from the given map
+ /// For v4 it can be either "V4" or "3", for V6 it can be "IA_NA"
+ /// "0", "IA_PD", or "2".
+ ///
+ /// @param params Element map containging the command arguments.
+ /// @param family protocol family (AF_INET or AF_INET6).
+ ///
+ /// @throw BadValue if the parameter is not valid for the protocol
+ /// family.
+ static dhcp::Lease::Type extractLeaseType(data::ConstElementPtr& params,
+ uint16_t family);
+};
+
+/// @brief Wrapper class around SFLQ pool commands.
+///
+/// This provides the interfacew through which callouts
+/// execute the commands.
+class SflqCmds {
+public:
+ /// @brief Constructor.
+ ///
+ /// It creates an instance of the @c SflqCmds
+ SflqCmds();
+
+ /// @brief sflq-pool4-create handler.
+ ///
+ /// Invokes LeaseMgr::sflqCreateFlqPool4() after parsing the arguments.
+ /// @brief lease6-resend-ddns command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-create",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255",
+ /// "subnet-id": 77,
+ /// "recreate": true
+ /// }
+ /// }
+ ///
+ /// The 'recreate' parameter is optional, it defaults false.
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4CreateHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool4-get-all' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-get-all",
+ /// "arguments": {
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4GetAllHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool4-get-by-subnet' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-get-by-subnet",
+ /// "arguments": {
+ /// "subnet-id" : 100
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Handles a 'sflq-pool4-by-range' command
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-get-by-range",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255"
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4GetByRangeHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Delete the SFLQ V4 pool that matches a start and end address.
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool4-del",
+ /// "arguments": {
+ /// "start-address": "192.0.2.0",
+ /// "end-address": "192.0.2.255",
+ /// "force": false
+ /// }
+ /// }
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool4DelHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool6-create' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-create",
+ /// "arguments": {
+ /// "start-address": "3001::",
+ /// "end-address": "3001::FF",
+ /// "subnet-id": 77,
+ /// "lease-type" : "IA_PD",
+ /// "delegated_len: 64
+ /// "recreate": true
+ /// }
+ /// }
+ ///
+ /// The 'delegated-len' is optional, it defaults to 128.
+ /// The 'recreate' parameter is optional, it defaults false.
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6CreateHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool6-get-all' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-get-all",
+ /// "arguments": {
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6GetAllHandler(hooks::CalloutHandle& handle);
+
+ /// @brief The 'sflq-pool6-get-by-subnet' command handler
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-get-by-subnet",
+ /// "arguments": {
+ /// "subnet-id" : 100
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Handles a 'sflq-pool6-by-range' command
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-get-by-range",
+ /// "arguments": {
+ /// "start-address": "3001::",
+ /// "end-address": "3001::FFFF"
+ /// }
+ /// }
+ ///
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6GetByRangeHandler(hooks::CalloutHandle& handle);
+
+ /// @brief Delete the SFLQ V6 pool that matches a start and end address.
+ ///
+ /// It extracts the command name and arguments from the given CalloutHandle,
+ /// attempts to process them, and then set's the handle's "response"
+ /// argument accordingly.
+ ///
+ /// An example command with all supported arguments is shown below:
+ /// {
+ /// "command": "sflq-pool6-del",
+ /// "arguments": {
+ /// "start-address": "3001::",
+ /// "end-address": "3001::FFFF",
+ /// "force": false
+ /// }
+ /// }
+ /// @param handle Callout context - which is expected to contain the
+ /// the command JSON text in the "command" argument
+ /// @return result of the operation
+ int sflqPool6DelHandler(hooks::CalloutHandle& handle);
+
+private:
+ /// Pointer to the SFLQ commands implementation
+ boost::shared_ptr<SflqCmdsImpl> sflq_impl_;
+};
+
+};
+};
+#endif
return (IOAddress::fromBytes(AF_INET6, &addr_bytes[0]));
}
-void validateV4Range(const IOAddress& start, const IOAddress& end) {
- if (!start.isV4() || !end.isV4() || end < start) {
+void validateV4Range(const IOAddress& start_address, const IOAddress& end_address) {
+ if (!start_address.isV4() || !end_address.isV4() || end_address < start_address) {
isc_throw (BadValue, "invalid V4 range - start_address "
- << start.toText() << "r, end_address " << end.toText()
- << ", must be V4 addresses where start <= end");
+ << start_address.toText() << ", end_address " << end_address.toText()
+ << ", must be V4 addresses where start_address <= end_address");
}
}
-void validateV6Range(const IOAddress& start, const IOAddress& end) {
- if (!start.isV6() || !end.isV6() || end < start) {
+void validateV6Range(const IOAddress& start_address, const IOAddress& end_address) {
+ if (!start_address.isV6() || !end_address.isV6() || end_address < start_address) {
isc_throw (BadValue, "invalid V6 range - start_address "
- << start.toText() << "r, end_address " << end.toText()
- << ", must be V6 addresses where start <= end");
+ << start_address.toText() << ", end_address " << end_address.toText()
+ << ", must be V6 addresses where start_address <= end_address");
}
}
/// @brief Ensures address pair are both v4 and start <= end
///
-/// @param start input start address
-/// @param end input end address
+/// @param start_address start of the range
+/// @param end_address end of the range
///
/// @throw BadValue if either address is not v4 or start > end
-void validateV4Range(const IOAddress& start, const IOAddress& end);
+void validateV4Range(const IOAddress& start_address, const IOAddress& end_address);
/// @brief Ensures address pair are both v6 and start <= end
///
-/// @param start input start address
-/// @param end input end address
+/// @param start_address start of the range
+/// @param end_address end of the range
///
/// @throw BadValue if either address is not v6 or start > end
-void validateV6Range(const IOAddress& start, const IOAddress& end);
+void validateV6Range(const IOAddress& start_address, const IOAddress& end_address);
} // namespace asiolink
} // namespace isc
}
-data::ConstElementPtr
+data::ElementPtr
SflqPoolInfo::toElement() const {
ElementPtr info = Element::createMap();
info->set("lease-type", Element::create(Lease::typeToText(lease_type_)));
/// @brief Defines a pointer to a LeaseStatsRow.
typedef boost::shared_ptr<LeaseStatsRow> LeaseStatsRowPtr;
+// Forward declaration.
+class SflqPoolInfo;
+/// @brief A pointer to a SFLQPoolInfo instance.
+typedef boost::shared_ptr<SflqPoolInfo> SflqPoolInfoPtr;
+
/// @brief Describes a SFLQ pool.
class SflqPoolInfo {
public:
boost::posix_time::ptime created_ts_;
boost::posix_time::ptime modified_ts_;
- data::ConstElementPtr toElement() const;
+ data::ElementPtr toElement() const;
};
-/// @brief A pointer to a SFLQPoolInfo instance.
-typedef boost::shared_ptr<SflqPoolInfo> SflqPoolInfoPtr;
-
/// @brief A collection of SFLQPoolInfo structures.
typedef std::vector<SflqPoolInfoPtr> SflqPoolInfoCollection;
typedef boost::shared_ptr<SflqPoolInfoCollection> SflqPoolInfoCollectionPtr;
return (free_address);
}
+SflqPoolInfoPtr
+SflqPool::toSflqPoolInfo() const {
+ SflqPoolInfoPtr pi(new SflqPoolInfo());
+ pi->lease_type_ = lease_type_;
+ pi->start_address_ = start_address_;
+ pi->end_address_ = end_address_;
+ pi->delegated_len_ = delegated_len_;
+ pi->subnet_id_ = subnet_id_;
+ pi->free_leases_ = free_addresses_.size();;
+ pi->created_ts_ = boost::posix_time::second_clock::local_time();
+ pi->modified_ts_ = pi->created_ts_;
+ return (pi);
+}
+
TrackingLeaseMgrPtr
SflqTestLeaseMgr::factory(const DatabaseConnection::ParameterMap& params) {
return (TrackingLeaseMgrPtr(new SflqTestLeaseMgr(params)));
SflqTestLeaseMgr::sflqCreateFlqPool4(IOAddress start_address, IOAddress end_address,
SubnetID subnet_id, bool recreate) {
auto sflq_pool = findPool(start_address, end_address);
- if (sflq_pool && recreate) {
- sflq_pool->repopulateFreeLeases();
+ if (sflq_pool) {
+ if (recreate) {
+ sflq_pool->repopulateFreeLeases();
+ return (true);
+ }
+
+ return (false);
}
// Create the pool and add it to the list of pools.
Lease::Type lease_type, uint8_t delegated_len,
SubnetID subnet_id, bool recreate) {
auto sflq_pool = findPool(start_address, end_address);
- if (sflq_pool && recreate) {
- sflq_pool->repopulateFreeLeases();
+ if (sflq_pool) {
+ if (recreate) {
+ sflq_pool->repopulateFreeLeases();
+ return (true);
+ }
+
+ return (false);
}
// Create the pool and add it to the list of pools.
return (std::string("sflqtest"));
}
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool4GetAll() {
+ SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+ for (auto pool : sflq_pools_) {
+ pools->push_back(pool->toSflqPoolInfo());
+ }
+
+ return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool4Get(SubnetID subnet_id) {
+ SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+ for (auto pool : sflq_pools_) {
+ if (pool->subnet_id_ == subnet_id) {
+ pools->push_back(pool->toSflqPoolInfo());
+ }
+ }
+
+ return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool4Get(IOAddress start_address, IOAddress end_address) {
+ SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+ for (auto pool : sflq_pools_) {
+ if ((pool->start_address_ <= start_address && start_address <= pool->end_address_) ||
+ (pool->start_address_ <= end_address && end_address <= pool->end_address_) ||
+ (start_address < pool->start_address_ && pool->end_address_ < end_address))
+ pools->push_back(pool->toSflqPoolInfo());
+ }
+
+ return (pools);
+}
+
+bool
+SflqTestLeaseMgr::sflqPool4Del(IOAddress start_address, IOAddress end_address,
+ bool force /* = false */) {
+ return(sflqPoolDel(start_address, end_address, force));
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool6GetAll() {
+ SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+ for (auto pool : sflq_pools_) {
+ pools->push_back(pool->toSflqPoolInfo());
+ }
+
+ return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool6Get(SubnetID subnet_id) {
+ SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+ for (auto pool : sflq_pools_) {
+ if (pool->subnet_id_ == subnet_id) {
+ pools->push_back(pool->toSflqPoolInfo());
+ }
+ }
+
+ return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool6Get(IOAddress start_address, IOAddress end_address) {
+ SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+ for (auto pool : sflq_pools_) {
+ if ((pool->start_address_ <= start_address && start_address <= pool->end_address_) ||
+ (pool->start_address_ <= end_address && end_address <= pool->end_address_) ||
+ (start_address < pool->start_address_ && pool->end_address_ < end_address))
+ pools->push_back(pool->toSflqPoolInfo());
+ }
+
+ return (pools);
+}
+
+bool
+SflqTestLeaseMgr::sflqPool6Del(IOAddress start_address, IOAddress end_address,
+ bool force /* = false */) {
+ return(sflqPoolDel(start_address, end_address, force));
+}
+
+bool
+SflqTestLeaseMgr::sflqPoolDel(IOAddress start_address, IOAddress end_address,
+ bool force) {
+ auto pools = sflqPool6Get(start_address, end_address);
+ if (pools->size() == 0) {
+ return (false);
+ }
+
+ if (pools->size() > 1 && force == false) {
+ // Overlapping pools, warn and bail.
+ isc_throw(InvalidOperation, "Delete would affect "
+ << pools->size() << " overlapping pools");
+ }
+
+ auto pool = sflq_pools_.begin();
+ while (pool != sflq_pools_.end()) {
+ if ((*pool)->start_address_ == start_address &&
+ (*pool)->end_address_ == end_address) {
+ sflq_pools_.erase(pool);
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
} // end of namespace isc::dhcp::test
} // end of namespace isc::dhcp
} // end of namespace isc
asiolink::IOAddress end_address_,
SubnetID subnet_id_,
Lease::Type lease_type = Lease::TYPE_V4,
- uint8_t delegated_len = 1);
+ uint8_t delegated_len = 128);
/// @brief Destructor.
~SflqPool(){};
/// IPV6_ZERO_ADDRESS() otherwise.
asiolink::IOAddress zeroAddress();
+ /// @brief Creates an SflqPoolInfo instance from this pool.
+ ///
+ /// SflqPoolInfo is the pool representaion returned by SFLQ API
+ /// "get" functions. The SflqPoolInfo timestamp members are set
+ /// to the current time.
+ ///
+ /// @return A pointer to a new SflqPoolInfo instance.
+ SflqPoolInfoPtr toSflqPoolInfo() const;
+
/// @brief First address in the pool.
asiolink::IOAddress start_address_;
virtual asiolink::IOAddress sflqPickFreeLease6(asiolink::IOAddress start_address,
asiolink::IOAddress end_address)
override;
+ /// @brief Fetch all SFLQ V4 pools.
+ ///
+ /// @return A collection of the SFLQ V4 pools.
+ virtual SflqPoolInfoCollectionPtr sflqPool4GetAll() override;
+
+ /// @brief Fetch all SFLQ V4 pools belonging to a subnet.
+ ///
+ /// @param subnet_id id of the desired subnet.
+ ///
+ /// @return A collection of the SFLQ V4 pools.
+ virtual SflqPoolInfoCollectionPtr sflqPool4Get(SubnetID subnet_id) override;
+
+ /// @brief Fetch all SFLQ V4 pools that overlap a range
+ ///
+ /// @param start_address start of ip address range.
+ /// @param end_address end of ip address range.
+ ///
+ /// @return A collection of the SFLQ V4 pools.
+ virtual SflqPoolInfoCollectionPtr sflqPool4Get(asiolink::IOAddress start_address,
+ asiolink::IOAddress end_address)
+ override;
+
+ /// @brief Delete the SFLQ V4 pool that matches a start and end address.
+ ///
+ /// @param start_address start address of the pool to delete.
+ /// @param end_address end address of the pool to delete.
+ /// @param force overrides check for overlapping pools when true. Defaults
+ /// to false.
+ ///
+ /// @return True a pool was deleted.
+ /// @throw InvalidOperation if force is false and overlapping pools are
+ /// detected.
+ virtual bool sflqPool4Del(asiolink::IOAddress start_address,
+ asiolink::IOAddress end_address,
+ bool force = false) override;
+
+ /// @brief Fetch all SFLQ V6 pools.
+ ///
+ /// @return A collection of the SFLQ V6 pools.
+ virtual SflqPoolInfoCollectionPtr sflqPool6GetAll() override;
+
+ /// @brief Fetch all SFLQ V6 pools belonging to a subnet.
+ ///
+ /// @param subnet_id id of the desired subnet.
+ ///
+ /// @return A collection of the SFLQ V6 pools.
+ virtual SflqPoolInfoCollectionPtr sflqPool6Get(SubnetID subnet_id) override;
+
+ /// @brief Fetch all SFLQ V6 pools that overlap a range
+ ///
+ /// @param start_address start of ip address range.
+ /// @param end_address end of ip address range.
+ ///
+ /// @return A collection of the SFLQ V6 pools.
+ virtual SflqPoolInfoCollectionPtr sflqPool6Get(asiolink::IOAddress start_address,
+ asiolink::IOAddress end_address)
+ override;
+
+ /// @brief Delete the SFLQ V6 pool that matches a start and end address.
+ ///
+ /// @param start_address start address of the pool to delete.
+ /// @param end_address end address of the pool to delete.
+ /// @param force overrides check for overlapping pools when true. Defaults
+ /// to false.
+ ///
+ /// @return True a pool was deleted.
+ /// @throw InvalidOperation if force is false and overlapping pools are
+ /// detected.
+ virtual bool sflqPool6Del(asiolink::IOAddress start_address,
+ asiolink::IOAddress end_address,
+ bool force = false) override;
+
+ /// @brief Delete the SFLQ pool that matches a start and end address.
+ ///
+ /// @param start_address start address of the pool to delete.
+ /// @param end_address end address of the pool to delete.
+ /// @param force overrides check for overlapping pools when true.
+ ///
+ /// @return True a pool was deleted.
+ /// @throw InvalidOperation if force is false and overlapping pools are
+ /// detected.
+ bool sflqPoolDel(asiolink::IOAddress start_address,
+ asiolink::IOAddress end_address,
+ bool force);
/// @brief Finds an SflqPool in the list of SflqPools
///
--- /dev/null
+{
+ "access": "write",
+ "avail": "3.2.0",
+ "brief": [
+ "This command requests the lease back end to a v4 SFLQ pool and its free lease data."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"sflq-pool4-create\",",
+ " \"arguments\": {",
+ " \"start-address\": \"1.2.3.4\",",
+ " \"end-address\": \"1.2.3.4\",",
+ " \"subnet-id\": 123",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp69\"/>",
+ "hook": "lease_cmds",
+ "name": "sflq-pool4-create",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"SFLQ pool created.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
--- /dev/null
+{
+ "access": "write",
+ "avail": "3.2.0",
+ "brief": [
+ "This command fetches a list of all V4 SFLQ pools from the lease back end."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"sflq-pool4-get-all\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp69\"/>",
+ "hook": "lease_cmds",
+ "name": "sflq-pool4-get-all",
+ "resp-syntax": [
+ "{",
+ "\"arguments\": {",
+ " \"pools\": [",
+ " {",
+ " \"created-ts\": \"2026-05-02 10:54:55.000000\",",
+ " \"delegated-len\": 128,",
+ " \"end-address\": \"192.0.2.255\",",
+ " \"free-leases\": 256,",
+ " \"lease-type\": \"V4\",",
+ " \"modified-ts\": \"2026-05-02 10:54:55.000000\",",
+ " \"start-address\": \"192.0.2.0\",",
+ " \"subnet-id\": 6",
+ " }",
+ "]]",
+ \"result\": 0,",
+ \"text\": \"1 pool(s) found.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}