From: Andrei Pavel Date: Tue, 21 Jun 2022 14:27:22 +0000 (+0300) Subject: [#2444] add checkLimitsX() methods to LeaseMgr X-Git-Tag: Kea-2.1.7~65 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0b3af58241b94dcb4e196da804683a8a9efc3e0d;p=thirdparty%2Fkea.git [#2444] add checkLimitsX() methods to LeaseMgr --- diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index 261780d375..6f19c63485 100644 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -697,6 +698,32 @@ public: /// @return number of leases removed. virtual size_t wipeLeases6(const SubnetID& subnet_id) = 0; + /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded. + /// Abstract method. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ], + /// "subnet": { "id": 1, "address-limit": 2 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + virtual std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const = 0; + + /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded. + /// Abstract method. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ], + /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + virtual std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const = 0; + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc index 8ea5feab92..f1337b5bcb 100644 --- a/src/lib/dhcpsrv/memfile_lease_mgr.cc +++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc @@ -40,6 +40,8 @@ using namespace isc::asiolink; using namespace isc::db; using namespace isc::util; +using isc::data::ConstElementPtr; + namespace isc { namespace dhcp { @@ -2057,6 +2059,16 @@ Memfile_LeaseMgr::wipeLeases6(const SubnetID& subnet_id) { return (num); } +std::string +Memfile_LeaseMgr::checkLimits4(ConstElementPtr const& user_context) const { + isc_throw(NotImplemented, "Memfile_LeaseMgr::checkLimits4() not implemented"); +} + +std::string +Memfile_LeaseMgr::checkLimits6(ConstElementPtr const& user_context) const { + isc_throw(NotImplemented, "Memfile_LeaseMgr::checkLimits4() not implemented"); +} + } // namespace dhcp } // namespace isc diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h index ec277324e4..15be0cb564 100644 --- a/src/lib/dhcpsrv/memfile_lease_mgr.h +++ b/src/lib/dhcpsrv/memfile_lease_mgr.h @@ -504,6 +504,32 @@ public: /// @return number of leases removed. virtual size_t wipeLeases6(const SubnetID& subnet_id); + /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded. + /// Memfile implementation. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ], + /// "subnet": { "id": 1, "address-limit": 2 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override; + + /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded. + /// Memfile implementation. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ], + /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override; + private: /// @name Internal methods called while holding the mutex in multi threading diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc index 5717b760a3..e71c252c65 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.cc +++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc @@ -325,9 +325,11 @@ tagged_statements = { { "SELECT subnet_id, lease_type, state, leases as state_count " "FROM lease6_stat " "WHERE subnet_id >= ? and subnet_id <= ? " - "ORDER BY subnet_id, lease_type, state"} - } -}; + "ORDER BY subnet_id, lease_type, state"}, + // TODO: remove single quotes from the following two SELECTs when the functions are implemented + {MySqlLeaseMgr::CHECK_LEASE4_LIMITS, "SELECT 'checkLease4Limits(?)'"}, + {MySqlLeaseMgr::CHECK_LEASE6_LIMITS, "SELECT 'checkLease6Limits(?)'"}, +} }; // tagged_statements } // namespace @@ -3091,6 +3093,95 @@ MySqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs, return (deleted_leases); } +string +MySqlLeaseMgr::checkLimits(ConstElementPtr const& user_context, StatementIndex const stindex) const { + constexpr int column_count(1); + + // Set up the WHERE clause value. + MYSQL_BIND inbind[column_count]; + memset(inbind, 0, sizeof(inbind)); + std::string const& user_context_string(user_context->str()); + inbind[0].buffer = const_cast(user_context_string.c_str()); + inbind[0].buffer_length = user_context_string.length(); + inbind[0].buffer_type = MYSQL_TYPE_STRING; + + my_bool error[column_count]; + MySqlLeaseExchange::setErrorIndicators(inbind, error, column_count); + + // Get a context and a prepared statement. + MySqlLeaseContextAlloc get_context(*this); + MySqlLeaseContextPtr ctx = get_context.ctx_; + MYSQL_STMT* prepared_statement(ctx->conn_.statements_[stindex]); + + // Bind the selection parameters to the statement. + int status(mysql_stmt_bind_param(prepared_statement, inbind)); + checkError(ctx, status, stindex, "unable to bind WHERE clause parameter"); + + // Set up the MYSQL_BIND array for the data being returned and bind it to + // the statement. + std::vector outbind; + outbind.push_back(MYSQL_BIND()); + my_bool result_null(MLM_FALSE); + char result[USER_CONTEXT_MAX_LEN]; + unsigned long result_length(sizeof(result)); + outbind[0].buffer_type = MYSQL_TYPE_STRING; + outbind[0].buffer = reinterpret_cast(result); + outbind[0].buffer_length = result_length; + outbind[0].length = &result_length; + outbind[0].is_null = &result_null; + status = mysql_stmt_bind_result(prepared_statement, &outbind[0]); + checkError(ctx, status, stindex, "unable to bind SELECT clause parameters"); + + // Execute the statement. + status = MysqlExecuteStatement(prepared_statement); + checkError(ctx, status, stindex, "unable to execute"); + + // Ensure that all the lease information is retrieved in one go to avoid + // overhead of going back and forth between client and server. + status = mysql_stmt_store_result(prepared_statement); + checkError(ctx, status, stindex, "unable to set up for storing all results"); + + // Set up the fetch "release" object to release resources associated + // with the call to mysql_stmt_fetch when this method exits, then + // retrieve the data. + MySqlFreeResult fetch_release(prepared_statement); + int count = 0; + while ((status = mysql_stmt_fetch(prepared_statement)) == 0) { + // "result" should be populated now. We'll use that directly outside the loop. + + if (++count > 1) { + isc_throw(MultipleRecords, "multiple records were found in the " + "database where only one was expected for query " + << ctx->conn_.text_statements_[stindex]); + } + } + + // How did the fetch end? + if (status == 1) { + // Error - unable to fetch results + checkError(ctx, status, stindex, "unable to fetch results"); + } else if (status == MYSQL_DATA_TRUNCATED) { + // Data truncated - throw an exception indicating what was at fault. + std::string columns[column_count]; + columns[0] = "limits"; + isc_throw(DataTruncated, ctx->conn_.text_statements_[stindex] + << " returned truncated data: columns affected are " + << MySqlLeaseExchange::getColumnsInError(error, columns, column_count)); + } + + return result; +} + +string +MySqlLeaseMgr::checkLimits4(ConstElementPtr const& user_context) const { + return checkLimits(user_context, CHECK_LEASE4_LIMITS); +} + +string +MySqlLeaseMgr::checkLimits6(ConstElementPtr const& user_context) const { + return checkLimits(user_context, CHECK_LEASE6_LIMITS); +} + LeaseStatsQueryPtr MySqlLeaseMgr::startLeaseStatsQuery4() { // Get a context diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h index 5a53ef644b..5623102841 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.h +++ b/src/lib/dhcpsrv/mysql_lease_mgr.h @@ -724,6 +724,8 @@ public: ALL_LEASE6_STATS, // Fetches IPv6 lease statistics SUBNET_LEASE6_STATS, // Fetched IPv6 lease stats for a single subnet. SUBNET_RANGE_LEASE6_STATS, // Fetched IPv6 lease stats for a subnet range. + CHECK_LEASE4_LIMITS, // Check if allocated IPv4 leases are inside the set limits. + CHECK_LEASE6_LIMITS, // Check if allocated IPv6 leases are inside the set limits. NUM_STATEMENTS // Number of statements }; @@ -925,6 +927,46 @@ private: uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs, StatementIndex statement_index); + /// @brief Checks if the lease limits set in the given user context are exceeded. + /// Contains common logic used by @ref checkLimits4 and @ref checkLimits6. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ], + /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string + checkLimits(isc::data::ConstElementPtr const& user_context, StatementIndex const stindex) const; + + /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded. + /// MySQL implementation. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ], + /// "subnet": { "id": 1, "address-limit": 2 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override; + + /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded. + /// MySQL implementation. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ], + /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override; + /// @brief Check Error and Throw Exception /// /// This method invokes @ref MySqlConnection::checkError. diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index 2470c26a04..e702324f0a 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -358,6 +358,19 @@ PgSqlTaggedStatement tagged_statements[] = { " FROM lease6_stat " " WHERE subnet_id >= $1 and subnet_id <= $2 " " ORDER BY subnet_id, lease_type, state" }, + + // TODO: remove single quotes from the following two SELECTs when the functions are implemented + + // CHECK_LEASE4_LIMITS + { 1, { OID_TEXT }, + "check_lease4_limits", + "SELECT 'checkLease4Limits($1)'" }, + + // CHECK_LEASE6_LIMITS + { 1, { OID_TEXT }, + "check_lease6_limits", + "SELECT 'checkLease6Limits($1)'" }, + // End of list sentinel { 0, { 0 }, NULL, NULL} }; @@ -2275,6 +2288,47 @@ PgSqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs, return (deleteLeaseCommon(statement_index, bind_array)); } +string +PgSqlLeaseMgr::checkLimits(ConstElementPtr const& user_context, StatementIndex const stindex) const { + // Bindings + PsqlBindArray bind_array; + std::string const user_context_str(user_context->str()); + bind_array.add(user_context_str); + + // Get a context. + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx(get_context.ctx_); + + PgSqlResult r(PQexecPrepared(ctx->conn_, + tagged_statements[stindex].name, + tagged_statements[stindex].nbparams, + &bind_array.values_[0], + &bind_array.lengths_[0], + &bind_array.formats_[0], 0)); + + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + + int rows = PQntuples(r); + if (rows > 1) { + isc_throw(MultipleRecords, "multiple records were found in the " + "database where only one was expected for query " + << tagged_statements[stindex].name); + } + + std::string const limits(PgSqlExchange::getRawColumnValue(r, 0, 0)); + return limits; +} + +string +PgSqlLeaseMgr::checkLimits4(ConstElementPtr const& user_context) const { + return checkLimits(user_context, CHECK_LEASE4_LIMITS); +} + +string +PgSqlLeaseMgr::checkLimits6(ConstElementPtr const& user_context) const { + return checkLimits(user_context, CHECK_LEASE6_LIMITS); +} + LeaseStatsQueryPtr PgSqlLeaseMgr::startLeaseStatsQuery4() { // Get a context diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h index 4268f6ca18..3abd14c9d1 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.h +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h @@ -699,6 +699,8 @@ public: ALL_LEASE6_STATS, // Fetches IPv6 lease statistics SUBNET_LEASE6_STATS, // Fetched IPv6 lease stats for a single subnet. SUBNET_RANGE_LEASE6_STATS, // Fetched IPv6 lease stats for a subnet range. + CHECK_LEASE4_LIMITS, // Check if allocated IPv4 leases are inside the set limits. + CHECK_LEASE6_LIMITS, // Check if allocated IPv6 leases are inside the set limits. NUM_STATEMENTS // Number of statements }; @@ -899,6 +901,46 @@ private: uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs, StatementIndex statement_index); + /// @brief Checks if the lease limits set in the given user context are exceeded. + /// Contains common logic used by @ref checkLimits4 and @ref checkLimits6. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ], + /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string + checkLimits(isc::data::ConstElementPtr const& user_context, StatementIndex const stindex) const; + + /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded. + /// PostgreSQL implementation. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ], + /// "subnet": { "id": 1, "address-limit": 2 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override; + + /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded. + /// MySQL implementation. + /// + /// @param user_context all or part of the lease's user context which, for the intents and + /// purposes of lease limiting should have the following format + /// (not all nodes are mandatory and values are given only as examples): + /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ], + /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } } + /// + /// @return a string describing a limit that is being exceeded, or an empty + /// string if no limits are exceeded + std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override; + /// @brief Context RAII Allocator. class PgSqlLeaseContextAlloc { public: diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc index fc8dc31f9d..0fce71ec02 100644 --- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc @@ -361,6 +361,14 @@ public: isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases6 not implemented"); } + std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const override { + isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits4() not implemented"); + } + + std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const override{ + isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits6() not implemented"); + } + /// @brief Returns backend type. /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)