2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-09 02:15:21 +00:00
Files
kea/src/lib/dhcpsrv/mysql_host_data_source.cc

4142 lines
169 KiB
C++
Raw Normal View History

2023-03-24 23:51:18 +01:00
// Copyright (C) 2015-2023 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 <asiolink/io_service.h>
#include <database/db_exceptions.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_db_access.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/mysql_host_data_source.h>
#include <dhcpsrv/timer_mgr.h>
#include <util/buffer.h>
#include <util/multi_threading_mgr.h>
#include <util/optional.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/array.hpp>
#include <boost/pointer_cast.hpp>
#include <boost/static_assert.hpp>
#include <mysql.h>
#include <mysqld_error.h>
#include <stdint.h>
#include <mutex>
#include <string>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::db;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::data;
using namespace std;
namespace {
/// @brief Numeric value representing last supported identifier.
2016-04-05 13:30:37 +02:00
///
/// This value is used to validate whether the identifier type stored in
/// a database is within bounds. of supported identifiers.
const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
/// @brief This class provides mechanisms for sending and retrieving
/// information from the 'hosts' table.
///
/// This class is used to insert and retrieve entries from the 'hosts' table.
/// The queries used with this class do not retrieve IPv6 reservations or
/// options associated with a host to minimize impact on performance. Other
/// classes derived from @ref MySqlHostExchange should be used to retrieve
/// information about IPv6 reservations and options.
///
/// Database schema contains several unique indexes to guard against adding
/// multiple hosts for the same client identifier in a single subnet and for
/// adding multiple hosts with a reservation for the same IPv4 address in a
2017-11-27 10:12:23 -05:00
/// single subnet. The exceptions that have to be taken into account are
/// listed below:
/// - zero or null IPv4 address indicates that there is no reservation for the
/// IPv4 address for the host,
/// - zero or null subnet identifier (either IPv4 or IPv6) indicates that
/// this subnet identifier must be ignored. Specifically, this is the case
2017-11-27 10:12:23 -05:00
/// when host reservation is for the DHCPv4 server, the IPv6 subnet id should
/// be ignored. Conversely, when host reservation is created for the DHCPv6 server,
/// the IPv4 subnet id should be ignored.
///
/// To exclude those special case values from the unique indexes, the MySQL
/// backend relies on the property of the unique indexes in MySQL, i.e. null
/// values are excluded from unique indexes. That means that there might be
/// multiple null values in a given column on which unique index is applied.
/// Therefore, the MySQL backend converts subnet identifiers and IPv4 addresses
/// of 0 to null before inserting a host to the database.
class MySqlHostExchange {
private:
2023-05-26 18:19:25 +03:00
/// @brief Column numbers for each column in the hosts table.
/// These are used for both retrieving data and for looking up
/// column labels for logging. Note that their numeric order
/// MUST match that of the column order in the hosts table.
//@{
static const size_t HOST_ID_COL = 0;
static const size_t DHCP_IDENTIFIER_COL = 1;
static const size_t DHCP_IDENTIFIER_TYPE_COL = 2;
static const size_t DHCP4_SUBNET_ID_COL = 3;
static const size_t DHCP6_SUBNET_ID_COL = 4;
static const size_t IPV4_ADDRESS_COL = 5;
static const size_t HOSTNAME_COL = 6;
static const size_t DHCP4_CLIENT_CLASSES_COL = 7;
static const size_t DHCP6_CLIENT_CLASSES_COL = 8;
static const size_t USER_CONTEXT_COL = 9;
static const size_t DHCP4_NEXT_SERVER_COL = 10;
static const size_t DHCP4_SERVER_HOSTNAME_COL = 11;
static const size_t DHCP4_BOOT_FILE_NAME_COL = 12;
static const size_t AUTH_KEY_COL = 13;
//@}
2021-06-08 17:49:23 +03:00
/// @brief Number of columns returned for SELECT queries sent by this class.
static const size_t HOST_COLUMNS = 14;
public:
/// @brief Constructor
///
/// @param additional_columns_num This value is set by the derived classes
/// to indicate how many additional columns will be returned by SELECT
/// queries performed by the derived class. This constructor will allocate
/// resources for these columns, e.g. binding table, error indicators.
MySqlHostExchange(const size_t additional_columns_num = 0)
: columns_num_(HOST_COLUMNS + additional_columns_num),
bind_(columns_num_), columns_(columns_num_),
error_(columns_num_, MLM_FALSE), host_id_(0),
dhcp_identifier_length_(0), dhcp_identifier_type_(0),
dhcp4_subnet_id_(SUBNET_ID_UNUSED),
[5704] host backends and kea-dhcp4/6 support global HR storage - Added constants for special SubnetIDs: SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED - Modified code throughout to use these constants, rather than hard-coded values. Note, MySQL and PostgreSQL host backends convert from NULL to UNUSED and back. - kea-dhcp4/6 servers will now parse a "reservations" element at the global level. src/lib/dhcpsrv/subnet_id.h Added constants SubnetID SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED src/bin/dhcp4/dhcp4_lexer.ll src/bin/dhcp4/dhcp4_parser.yy src/bin/dhcp4/json_config_parser.cc kea-dhcp4 parsing now handles reservations as a global element src/bin/dhcp4/tests/config_parser_unittest.cc TEST_F(Dhcp4ParserTest, globalReservations) - new test to verify global HR parsing src/bin/dhcp4/tests/dora_unittest.cc src/lib/dhcpsrv/cfg_hosts.cc src/lib/dhcpsrv/host.cc src/lib/dhcpsrv/host_mgr.cc src/lib/dhcpsrv/mysql_host_data_source.cc src/lib/dhcpsrv/parsers/host_reservation_parser.cc src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc src/lib/dhcpsrv/tests/alloc_engine_utils.cc src/lib/dhcpsrv/tests/host_mgr_unittest.cc src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED src/lib/dhcpsrv/srv_config.cc SrvConfig::toElement() - added global reservations output src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc TEST_F(CfgHostsTest, globalSubnetIDs) TEST_F(CfgHostsTest, unusedSubnetIDs) - new tests src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED TEST_F(HostTest, toText) - updated to verify global ID output src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc TEST_F(MySqlHostDataSourceTest, globalSubnetId4) TEST_F(MySqlHostDataSourceTest, globalSubnetId6) - new tests src/lib/dhcpsrv/tests/srv_config_unittest.cc TEST_F(SrvConfigTest, unparseHR) - added global HRs src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.* GenericHostDataSourceTest::testGlobalSubnetId4() GenericHostDataSourceTest::testGlobalSubnetId6() src/bin/dhcp6/dhcp6_lexer.ll src/bin/dhcp6/dhcp6_parser.yy src/bin/dhcp6/json_config_parser.cc kea-dhcp6 now parses reservations as a global element src/bin/dhcp6/tests/config_parser_unittest.cc TEST_F(Dhcp6ParserTest, globalReservations) - new test
2018-08-07 06:46:30 -04:00
dhcp6_subnet_id_(SUBNET_ID_UNUSED), ipv4_address_(0),
hostname_length_(0), dhcp4_client_classes_length_(0),
dhcp6_client_classes_length_(0),
2017-12-03 19:23:00 +01:00
user_context_length_(0),
dhcp4_next_server_(0),
dhcp4_server_hostname_length_(0),
dhcp4_boot_file_name_length_(0),
auth_key_length_(0),
dhcp4_subnet_id_null_(MLM_FALSE),
dhcp6_subnet_id_null_(MLM_FALSE),
ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
dhcp4_client_classes_null_(MLM_FALSE),
dhcp6_client_classes_null_(MLM_FALSE),
2017-12-03 19:23:00 +01:00
user_context_null_(MLM_FALSE),
dhcp4_next_server_null_(MLM_FALSE),
dhcp4_server_hostname_null_(MLM_FALSE),
dhcp4_boot_file_name_null_(MLM_FALSE),
auth_key_null_(MLM_FALSE) {
// Fill arrays with 0 so as they don't include any garbage.
memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
memset(hostname_, 0, sizeof(hostname_));
memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
2017-12-03 19:23:00 +01:00
memset(user_context_, 0, sizeof(user_context_));
memset(dhcp4_server_hostname_, 0, sizeof(dhcp4_server_hostname_));
memset(dhcp4_boot_file_name_, 0, sizeof(dhcp4_boot_file_name_));
memset(auth_key_, 0, sizeof(auth_key_));
// Set the column names for use by this class. This only comprises
// names used by the MySqlHostExchange class. Derived classes will
// need to set names for the columns they use.
2023-05-26 18:19:25 +03:00
columns_[HOST_ID_COL] = "host_id";
columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
columns_[IPV4_ADDRESS_COL] = "ipv4_address";
columns_[HOSTNAME_COL] = "hostname";
columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
columns_[USER_CONTEXT_COL] = "user_context";
columns_[DHCP4_NEXT_SERVER_COL] = "dhcp4_next_server";
columns_[DHCP4_SERVER_HOSTNAME_COL] = "dhcp4_server_hostname";
columns_[DHCP4_BOOT_FILE_NAME_COL] = "dhcp4_boot_file_name";
columns_[AUTH_KEY_COL] = "auth_key";
BOOST_STATIC_ASSERT(13 < HOST_COLUMNS);
};
/// @brief Virtual destructor.
virtual ~MySqlHostExchange() {
}
/// @brief Returns index of the first uninitialized column name.
///
/// This method is called by the derived classes to determine which
/// column indexes are available for the derived classes within a
/// binding array, error array and column names. This method
/// determines the first available index by searching the first
/// empty value within the columns_ vector. Previously we relied on
/// the fixed values set for each class, but this was hard to maintain
/// when new columns were added to the SELECT queries. It required
/// modifying indexes in all derived classes.
///
/// Derived classes must call this method in their constructors and
/// use returned value as an index for the first column used by the
/// derived class and increment this value for each subsequent column.
size_t findAvailColumn() const {
std::vector<std::string>::const_iterator empty_column =
std::find(columns_.begin(), columns_.end(), std::string());
return (std::distance(columns_.begin(), empty_column));
}
/// @brief Returns value of host id.
///
/// This method is used by derived classes.
uint64_t getHostId() const {
return (host_id_);
};
/// @brief Set error indicators
///
/// Sets the error indicator for each of the MYSQL_BIND elements. It points
/// the "error" field within an element of the input array to the
/// corresponding element of the passed error array.
///
/// @param bind Array of BIND elements
/// @param error Array of error elements. If there is an error in getting
/// data associated with one of the "bind" elements, the
/// corresponding element in the error array is set to MLM_TRUE.
static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
std::vector<my_bools>& error) {
for (size_t i = 0; i < error.size(); ++i) {
error[i] = MLM_FALSE;
2020-01-21 16:44:13 +02:00
bind[i].error = reinterpret_cast<my_bool*>(&error[i]);
}
};
/// @brief Return columns in error
///
/// If an error is returned from a fetch (in particular, a truncated
/// status), this method can be called to get the names of the fields in
/// error. It returns a string comprising the names of the fields
/// separated by commas. In the case of there being no error indicators
/// set, it returns the string "(None)".
///
/// @param error Array of error elements. An element is set to MLM_TRUE
/// if the corresponding column in the database is the source of
/// the error.
/// @param names Array of column names, the same size as the error array.
/// @param count Size of each of the arrays.
static std::string getColumnsInError(std::vector<my_bools>& error,
const std::vector<std::string>& names) {
std::string result = "";
// Accumulate list of column names
for (size_t i = 0; i < names.size(); ++i) {
if (error[i] == MLM_TRUE) {
if (!result.empty()) {
result += ", ";
}
result += names[i];
}
}
if (result.empty()) {
result = "(None)";
}
return (result);
};
/// @brief Create MYSQL_BIND objects for Host Pointer
///
/// Fills in the MYSQL_BIND array for sending data stored in the Host object
/// to the database.
///
/// @param host Host object to be added to the database.
/// None of the fields in the host reservation are modified -
/// the host data is only read.
/// @param unique_ip boolean value indicating if multiple reservations for the
/// same IP address are allowed (false) or not (true).
///
/// @return Vector of MySQL BIND objects representing the data to be added.
std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host, const bool unique_ip) {
// Store host object to ensure it remains valid.
host_ = host;
// Initialize prior to constructing the array of MYSQL_BIND structures.
// It sets all fields, including is_null, to zero, so we need to set
// is_null only if it should be true. This gives up minor performance
// benefit while being safe approach.
memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
// Set up the structures for the various components of the host structure.
try {
// host_id : INT UNSIGNED NOT NULL
// The host_id is auto_incremented by MySQL database,
// so we need to pass the NULL value
host_id_ = 0;
bind_[0].buffer_type = MYSQL_TYPE_LONG;
bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
bind_[0].is_unsigned = MLM_TRUE;
// dhcp_identifier : VARBINARY(255) NOT NULL
2016-04-05 13:30:37 +02:00
dhcp_identifier_length_ = host->getIdentifier().size();
memcpy(static_cast<void*>(dhcp_identifier_buffer_),
&(host->getIdentifier())[0],
host->getIdentifier().size());
bind_[1].buffer_type = MYSQL_TYPE_BLOB;
bind_[1].buffer = dhcp_identifier_buffer_;
bind_[1].buffer_length = dhcp_identifier_length_;
bind_[1].length = &dhcp_identifier_length_;
// dhcp_identifier_type : TINYINT NOT NULL
2016-04-05 13:30:37 +02:00
dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
bind_[2].buffer_type = MYSQL_TYPE_TINY;
bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
bind_[2].is_unsigned = MLM_TRUE;
// dhcp4_subnet_id : INT UNSIGNED NULL
// Can't take an address of intermediate object, so let's store it
// in dhcp4_subnet_id_
dhcp4_subnet_id_ = host->getIPv4SubnetID();
[5704] host backends and kea-dhcp4/6 support global HR storage - Added constants for special SubnetIDs: SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED - Modified code throughout to use these constants, rather than hard-coded values. Note, MySQL and PostgreSQL host backends convert from NULL to UNUSED and back. - kea-dhcp4/6 servers will now parse a "reservations" element at the global level. src/lib/dhcpsrv/subnet_id.h Added constants SubnetID SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED src/bin/dhcp4/dhcp4_lexer.ll src/bin/dhcp4/dhcp4_parser.yy src/bin/dhcp4/json_config_parser.cc kea-dhcp4 parsing now handles reservations as a global element src/bin/dhcp4/tests/config_parser_unittest.cc TEST_F(Dhcp4ParserTest, globalReservations) - new test to verify global HR parsing src/bin/dhcp4/tests/dora_unittest.cc src/lib/dhcpsrv/cfg_hosts.cc src/lib/dhcpsrv/host.cc src/lib/dhcpsrv/host_mgr.cc src/lib/dhcpsrv/mysql_host_data_source.cc src/lib/dhcpsrv/parsers/host_reservation_parser.cc src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc src/lib/dhcpsrv/tests/alloc_engine_utils.cc src/lib/dhcpsrv/tests/host_mgr_unittest.cc src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED src/lib/dhcpsrv/srv_config.cc SrvConfig::toElement() - added global reservations output src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc TEST_F(CfgHostsTest, globalSubnetIDs) TEST_F(CfgHostsTest, unusedSubnetIDs) - new tests src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED TEST_F(HostTest, toText) - updated to verify global ID output src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc TEST_F(MySqlHostDataSourceTest, globalSubnetId4) TEST_F(MySqlHostDataSourceTest, globalSubnetId6) - new tests src/lib/dhcpsrv/tests/srv_config_unittest.cc TEST_F(SrvConfigTest, unparseHR) - added global HRs src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.* GenericHostDataSourceTest::testGlobalSubnetId4() GenericHostDataSourceTest::testGlobalSubnetId6() src/bin/dhcp6/dhcp6_lexer.ll src/bin/dhcp6/dhcp6_parser.yy src/bin/dhcp6/json_config_parser.cc kea-dhcp6 now parses reservations as a global element src/bin/dhcp6/tests/config_parser_unittest.cc TEST_F(Dhcp6ParserTest, globalReservations) - new test
2018-08-07 06:46:30 -04:00
dhcp4_subnet_id_null_ = host->getIPv4SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
bind_[3].buffer_type = MYSQL_TYPE_LONG;
bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
bind_[3].is_unsigned = MLM_TRUE;
bind_[3].is_null = &dhcp4_subnet_id_null_;
// dhcp6_subnet_id : INT UNSIGNED NULL
// Can't take an address of intermediate object, so let's store it
// in dhcp6_subnet_id_
dhcp6_subnet_id_ = host->getIPv6SubnetID();
[5704] host backends and kea-dhcp4/6 support global HR storage - Added constants for special SubnetIDs: SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED - Modified code throughout to use these constants, rather than hard-coded values. Note, MySQL and PostgreSQL host backends convert from NULL to UNUSED and back. - kea-dhcp4/6 servers will now parse a "reservations" element at the global level. src/lib/dhcpsrv/subnet_id.h Added constants SubnetID SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED src/bin/dhcp4/dhcp4_lexer.ll src/bin/dhcp4/dhcp4_parser.yy src/bin/dhcp4/json_config_parser.cc kea-dhcp4 parsing now handles reservations as a global element src/bin/dhcp4/tests/config_parser_unittest.cc TEST_F(Dhcp4ParserTest, globalReservations) - new test to verify global HR parsing src/bin/dhcp4/tests/dora_unittest.cc src/lib/dhcpsrv/cfg_hosts.cc src/lib/dhcpsrv/host.cc src/lib/dhcpsrv/host_mgr.cc src/lib/dhcpsrv/mysql_host_data_source.cc src/lib/dhcpsrv/parsers/host_reservation_parser.cc src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc src/lib/dhcpsrv/tests/alloc_engine_utils.cc src/lib/dhcpsrv/tests/host_mgr_unittest.cc src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED src/lib/dhcpsrv/srv_config.cc SrvConfig::toElement() - added global reservations output src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc TEST_F(CfgHostsTest, globalSubnetIDs) TEST_F(CfgHostsTest, unusedSubnetIDs) - new tests src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED TEST_F(HostTest, toText) - updated to verify global ID output src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc TEST_F(MySqlHostDataSourceTest, globalSubnetId4) TEST_F(MySqlHostDataSourceTest, globalSubnetId6) - new tests src/lib/dhcpsrv/tests/srv_config_unittest.cc TEST_F(SrvConfigTest, unparseHR) - added global HRs src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.* GenericHostDataSourceTest::testGlobalSubnetId4() GenericHostDataSourceTest::testGlobalSubnetId6() src/bin/dhcp6/dhcp6_lexer.ll src/bin/dhcp6/dhcp6_parser.yy src/bin/dhcp6/json_config_parser.cc kea-dhcp6 now parses reservations as a global element src/bin/dhcp6/tests/config_parser_unittest.cc TEST_F(Dhcp6ParserTest, globalReservations) - new test
2018-08-07 06:46:30 -04:00
dhcp6_subnet_id_null_ = host->getIPv6SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
bind_[4].is_unsigned = MLM_TRUE;
bind_[4].is_null = &dhcp6_subnet_id_null_;
// ipv4_address : INT UNSIGNED NULL
// The address in the Host structure is an IOAddress object. Convert
// this to an integer for storage.
ipv4_address_ = host->getIPv4Reservation().toUint32();
ipv4_address_null_ = ipv4_address_ == 0 ? MLM_TRUE : MLM_FALSE;
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
bind_[5].is_unsigned = MLM_TRUE;
bind_[5].is_null = &ipv4_address_null_;
// hostname : VARCHAR(255) NULL
strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
hostname_length_ = host->getHostname().length();
bind_[6].buffer_type = MYSQL_TYPE_STRING;
bind_[6].buffer = reinterpret_cast<char*>(hostname_);
bind_[6].buffer_length = hostname_length_;
// dhcp4_client_classes : VARCHAR(255) NULL
bind_[7].buffer_type = MYSQL_TYPE_STRING;
// Override default separator to not include space after comma.
string classes4_txt = host->getClientClasses4().toText(",");
strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
bind_[7].buffer = dhcp4_client_classes_;
bind_[7].buffer_length = classes4_txt.length();
// dhcp6_client_classes : VARCHAR(255) NULL
bind_[8].buffer_type = MYSQL_TYPE_STRING;
// Override default separator to not include space after comma.
string classes6_txt = host->getClientClasses6().toText(",");
strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
bind_[8].buffer = dhcp6_client_classes_;
bind_[8].buffer_length = classes6_txt.length();
2017-12-03 19:23:00 +01:00
// user_context : TEXT NULL
ConstElementPtr ctx = host->getContext();
if (ctx) {
bind_[9].buffer_type = MYSQL_TYPE_STRING;
string ctx_txt = ctx->str();
strncpy(user_context_, ctx_txt.c_str(), USER_CONTEXT_MAX_LEN - 1);
bind_[9].buffer = user_context_;
bind_[9].buffer_length = ctx_txt.length();
} else {
bind_[9].buffer_type = MYSQL_TYPE_NULL;
}
// ipv4_address : INT UNSIGNED NULL
// The address in the Host structure is an IOAddress object. Convert
// this to an integer for storage.
dhcp4_next_server_ = host->getNextServer().toUint32();
2017-12-03 19:23:00 +01:00
bind_[10].buffer_type = MYSQL_TYPE_LONG;
bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
bind_[10].is_unsigned = MLM_TRUE;
// bind_[10].is_null = &MLM_FALSE; // commented out for performance
// reasons, see memset() above
// dhcp4_server_hostname
2017-12-03 19:23:00 +01:00
bind_[11].buffer_type = MYSQL_TYPE_STRING;
std::string server_hostname = host->getServerHostname();
strncpy(dhcp4_server_hostname_, server_hostname.c_str(),
SERVER_HOSTNAME_MAX_LEN - 1);
2017-12-03 19:23:00 +01:00
bind_[11].buffer = dhcp4_server_hostname_;
bind_[11].buffer_length = server_hostname.length();
// dhcp4_boot_file_name
2017-12-03 19:23:00 +01:00
bind_[12].buffer_type = MYSQL_TYPE_STRING;
std::string boot_file_name = host->getBootFileName();
strncpy(dhcp4_boot_file_name_, boot_file_name.c_str(),
BOOT_FILE_NAME_MAX_LEN - 1);
2017-12-03 19:23:00 +01:00
bind_[12].buffer = dhcp4_boot_file_name_;
bind_[12].buffer_length = boot_file_name.length();
// auth key
bind_[13].buffer_type = MYSQL_TYPE_STRING;
std::string auth_key = host->getKey().toText();
std::strncpy(auth_key_, auth_key.c_str(), TEXT_AUTH_KEY_LEN - 1);
2022-05-18 22:09:45 +03:00
auth_key_null_ = auth_key.empty() ? MLM_TRUE : MLM_FALSE;
bind_[13].buffer = auth_key_;
bind_[13].buffer_length = auth_key.length();
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not create bind array from Host: "
<< host->getHostname() << ", reason: " << ex.what());
}
// Add the data to the vector.
std::vector<MYSQL_BIND> vec(bind_.begin(), bind_.begin() + HOST_COLUMNS);
// When checking whether the IP is unique we need to bind the IPv4 address
// at the end of the query as it has additional binding for the IPv4
// address.
if (unique_ip) {
vec.push_back(bind_[5]); // ipv4_address
vec.push_back(bind_[3]); // subnet_id
}
return (vec);
};
/// @brief Create BIND array to receive Host data.
///
/// Creates a MYSQL_BIND array to receive Host data from the database.
/// After data is successfully received, @ref retrieveHost can be called
/// to retrieve the Host object.
///
/// @return Vector of MYSQL_BIND objects representing data to be retrieved.
virtual std::vector<MYSQL_BIND> createBindForReceive() {
// Initialize MYSQL_BIND array.
// It sets all fields, including is_null, to zero, so we need to set
// is_null only if it should be true. This gives up minor performance
// benefit while being safe approach. For improved readability, the
// code that explicitly sets is_null is there, but is commented out.
2017-07-23 16:25:04 -04:00
// This also takes care of setting bind_[X].is_null to MLM_FALSE.
memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
// host_id : INT UNSIGNED NOT NULL
bind_[0].buffer_type = MYSQL_TYPE_LONG;
bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
bind_[0].is_unsigned = MLM_TRUE;
// dhcp_identifier : VARBINARY(255) NOT NULL
dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
bind_[1].buffer_type = MYSQL_TYPE_BLOB;
bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
bind_[1].buffer_length = dhcp_identifier_length_;
bind_[1].length = &dhcp_identifier_length_;
// dhcp_identifier_type : TINYINT NOT NULL
bind_[2].buffer_type = MYSQL_TYPE_TINY;
bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
bind_[2].is_unsigned = MLM_TRUE;
// dhcp4_subnet_id : INT UNSIGNED NULL
dhcp4_subnet_id_null_ = MLM_FALSE;
bind_[3].buffer_type = MYSQL_TYPE_LONG;
bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
bind_[3].is_unsigned = MLM_TRUE;
bind_[3].is_null = &dhcp4_subnet_id_null_;
// dhcp6_subnet_id : INT UNSIGNED NULL
dhcp6_subnet_id_null_ = MLM_FALSE;
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
bind_[4].is_unsigned = MLM_TRUE;
bind_[4].is_null = &dhcp6_subnet_id_null_;
// ipv4_address : INT UNSIGNED NULL
ipv4_address_null_ = MLM_FALSE;
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
bind_[5].is_unsigned = MLM_TRUE;
bind_[5].is_null = &ipv4_address_null_;
// hostname : VARCHAR(255) NULL
hostname_null_ = MLM_FALSE;
hostname_length_ = sizeof(hostname_);
bind_[6].buffer_type = MYSQL_TYPE_STRING;
bind_[6].buffer = reinterpret_cast<char*>(hostname_);
bind_[6].buffer_length = hostname_length_;
bind_[6].length = &hostname_length_;
bind_[6].is_null = &hostname_null_;
// dhcp4_client_classes : VARCHAR(255) NULL
dhcp4_client_classes_null_ = MLM_FALSE;
dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
bind_[7].buffer_type = MYSQL_TYPE_STRING;
bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
bind_[7].buffer_length = dhcp4_client_classes_length_;
bind_[7].length = &dhcp4_client_classes_length_;
bind_[7].is_null = &dhcp4_client_classes_null_;
// dhcp6_client_classes : VARCHAR(255) NULL
dhcp6_client_classes_null_ = MLM_FALSE;
dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
bind_[8].buffer_type = MYSQL_TYPE_STRING;
bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
bind_[8].buffer_length = dhcp6_client_classes_length_;
bind_[8].length = &dhcp6_client_classes_length_;
bind_[8].is_null = &dhcp6_client_classes_null_;
2017-12-03 19:23:00 +01:00
// user_context : TEXT NULL
user_context_null_ = MLM_FALSE;
user_context_length_ = sizeof(user_context_);
bind_[9].buffer_type = MYSQL_TYPE_STRING;
bind_[9].buffer = reinterpret_cast<char*>(user_context_);
bind_[9].buffer_length = user_context_length_;
bind_[9].length = &user_context_length_;
bind_[9].is_null = &user_context_null_;
// dhcp4_next_server
dhcp4_next_server_null_ = MLM_FALSE;
2017-12-03 19:23:00 +01:00
bind_[10].buffer_type = MYSQL_TYPE_LONG;
bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
bind_[10].is_unsigned = MLM_TRUE;
bind_[10].is_null = &dhcp4_next_server_null_;
// dhcp4_server_hostname
dhcp4_server_hostname_null_ = MLM_FALSE;
dhcp4_server_hostname_length_ = sizeof(dhcp4_server_hostname_);
2017-12-03 19:23:00 +01:00
bind_[11].buffer_type = MYSQL_TYPE_STRING;
bind_[11].buffer = reinterpret_cast<char*>(dhcp4_server_hostname_);
bind_[11].buffer_length = dhcp4_server_hostname_length_;
bind_[11].length = &dhcp4_server_hostname_length_;
bind_[11].is_null = &dhcp4_server_hostname_null_;
// dhcp4_boot_file_name
dhcp4_boot_file_name_null_ = MLM_FALSE;
dhcp4_boot_file_name_length_ = sizeof(dhcp4_boot_file_name_);
2017-12-03 19:23:00 +01:00
bind_[12].buffer_type = MYSQL_TYPE_STRING;
bind_[12].buffer = reinterpret_cast<char*>(dhcp4_boot_file_name_);
bind_[12].buffer_length = dhcp4_boot_file_name_length_;
bind_[12].length = &dhcp4_boot_file_name_length_;
bind_[12].is_null = &dhcp4_boot_file_name_null_;
// auth_key_
auth_key_null_ = MLM_FALSE;
auth_key_length_ = sizeof(auth_key_);
bind_[13].buffer_type = MYSQL_TYPE_STRING;
bind_[13].buffer = reinterpret_cast<char*>(auth_key_);
bind_[13].buffer_length = auth_key_length_;
bind_[13].length = &auth_key_length_;
bind_[13].is_null = &auth_key_null_;
// Add the error flags
setErrorIndicators(bind_, error_);
// Add the data to the vector. Note the end element is one after the
// end of the array.
return (bind_);
};
/// @brief Copy received data into Host object
///
/// This function copies information about the host into a newly created
/// @ref Host object. This method is called after @ref createBindForReceive.
/// has been used.
///
/// @return Host Pointer to a @ref HostPtr object holding a pointer to the
/// @ref Host object returned.
HostPtr retrieveHost() {
2016-04-05 13:30:37 +02:00
// Check if the identifier stored in the database is correct.
if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
isc_throw(BadValue, "invalid dhcp identifier type returned: "
2016-04-05 13:30:37 +02:00
<< static_cast<int>(dhcp_identifier_type_));
}
// Set the dhcp identifier type in a variable of the appropriate
// data type.
2016-04-05 13:30:37 +02:00
Host::IdentifierType type =
static_cast<Host::IdentifierType>(dhcp_identifier_type_);
// Set DHCPv4 subnet ID to the value returned. If NULL returned,
// set to 0.
[5704] host backends and kea-dhcp4/6 support global HR storage - Added constants for special SubnetIDs: SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED - Modified code throughout to use these constants, rather than hard-coded values. Note, MySQL and PostgreSQL host backends convert from NULL to UNUSED and back. - kea-dhcp4/6 servers will now parse a "reservations" element at the global level. src/lib/dhcpsrv/subnet_id.h Added constants SubnetID SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED src/bin/dhcp4/dhcp4_lexer.ll src/bin/dhcp4/dhcp4_parser.yy src/bin/dhcp4/json_config_parser.cc kea-dhcp4 parsing now handles reservations as a global element src/bin/dhcp4/tests/config_parser_unittest.cc TEST_F(Dhcp4ParserTest, globalReservations) - new test to verify global HR parsing src/bin/dhcp4/tests/dora_unittest.cc src/lib/dhcpsrv/cfg_hosts.cc src/lib/dhcpsrv/host.cc src/lib/dhcpsrv/host_mgr.cc src/lib/dhcpsrv/mysql_host_data_source.cc src/lib/dhcpsrv/parsers/host_reservation_parser.cc src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc src/lib/dhcpsrv/tests/alloc_engine_utils.cc src/lib/dhcpsrv/tests/host_mgr_unittest.cc src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED src/lib/dhcpsrv/srv_config.cc SrvConfig::toElement() - added global reservations output src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc TEST_F(CfgHostsTest, globalSubnetIDs) TEST_F(CfgHostsTest, unusedSubnetIDs) - new tests src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED TEST_F(HostTest, toText) - updated to verify global ID output src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc TEST_F(MySqlHostDataSourceTest, globalSubnetId4) TEST_F(MySqlHostDataSourceTest, globalSubnetId6) - new tests src/lib/dhcpsrv/tests/srv_config_unittest.cc TEST_F(SrvConfigTest, unparseHR) - added global HRs src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.* GenericHostDataSourceTest::testGlobalSubnetId4() GenericHostDataSourceTest::testGlobalSubnetId6() src/bin/dhcp6/dhcp6_lexer.ll src/bin/dhcp6/dhcp6_parser.yy src/bin/dhcp6/json_config_parser.cc kea-dhcp6 now parses reservations as a global element src/bin/dhcp6/tests/config_parser_unittest.cc TEST_F(Dhcp6ParserTest, globalReservations) - new test
2018-08-07 06:46:30 -04:00
SubnetID ipv4_subnet_id(SUBNET_ID_UNUSED);
if (dhcp4_subnet_id_null_ == MLM_FALSE) {
ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
}
// Set DHCPv6 subnet ID to the value returned. If NULL returned,
// set to 0.
[5704] host backends and kea-dhcp4/6 support global HR storage - Added constants for special SubnetIDs: SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED - Modified code throughout to use these constants, rather than hard-coded values. Note, MySQL and PostgreSQL host backends convert from NULL to UNUSED and back. - kea-dhcp4/6 servers will now parse a "reservations" element at the global level. src/lib/dhcpsrv/subnet_id.h Added constants SubnetID SUBNET_ID_GLOBAL, SUBNET_ID_MAX, SUBNET_ID_UNUSED src/bin/dhcp4/dhcp4_lexer.ll src/bin/dhcp4/dhcp4_parser.yy src/bin/dhcp4/json_config_parser.cc kea-dhcp4 parsing now handles reservations as a global element src/bin/dhcp4/tests/config_parser_unittest.cc TEST_F(Dhcp4ParserTest, globalReservations) - new test to verify global HR parsing src/bin/dhcp4/tests/dora_unittest.cc src/lib/dhcpsrv/cfg_hosts.cc src/lib/dhcpsrv/host.cc src/lib/dhcpsrv/host_mgr.cc src/lib/dhcpsrv/mysql_host_data_source.cc src/lib/dhcpsrv/parsers/host_reservation_parser.cc src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc src/lib/dhcpsrv/tests/alloc_engine_utils.cc src/lib/dhcpsrv/tests/host_mgr_unittest.cc src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED src/lib/dhcpsrv/srv_config.cc SrvConfig::toElement() - added global reservations output src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc TEST_F(CfgHostsTest, globalSubnetIDs) TEST_F(CfgHostsTest, unusedSubnetIDs) - new tests src/lib/dhcpsrv/tests/host_unittest.cc Replaced SubnetID 0 with SUBNET_ID_UNUSED TEST_F(HostTest, toText) - updated to verify global ID output src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc TEST_F(MySqlHostDataSourceTest, globalSubnetId4) TEST_F(MySqlHostDataSourceTest, globalSubnetId6) - new tests src/lib/dhcpsrv/tests/srv_config_unittest.cc TEST_F(SrvConfigTest, unparseHR) - added global HRs src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.* GenericHostDataSourceTest::testGlobalSubnetId4() GenericHostDataSourceTest::testGlobalSubnetId6() src/bin/dhcp6/dhcp6_lexer.ll src/bin/dhcp6/dhcp6_parser.yy src/bin/dhcp6/json_config_parser.cc kea-dhcp6 now parses reservations as a global element src/bin/dhcp6/tests/config_parser_unittest.cc TEST_F(Dhcp6ParserTest, globalReservations) - new test
2018-08-07 06:46:30 -04:00
SubnetID ipv6_subnet_id(SUBNET_ID_UNUSED);
if (dhcp6_subnet_id_null_ == MLM_FALSE) {
ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
}
// Set IPv4 address reservation if it was given, if not, set IPv4 zero
// address
asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
if (ipv4_address_null_ == MLM_FALSE) {
ipv4_reservation = asiolink::IOAddress(ipv4_address_);
}
// Set hostname if non NULL value returned. Otherwise, leave an
// empty string.
std::string hostname;
if (hostname_null_ == MLM_FALSE) {
hostname = std::string(hostname_, hostname_length_);
}
// Set DHCPv4 client classes if non NULL value returned.
std::string dhcp4_client_classes;
if (dhcp4_client_classes_null_ == MLM_FALSE) {
dhcp4_client_classes = std::string(dhcp4_client_classes_,
dhcp4_client_classes_length_);
}
// Set DHCPv6 client classes if non NULL value returned.
std::string dhcp6_client_classes;
if (dhcp6_client_classes_null_ == MLM_FALSE) {
dhcp6_client_classes = std::string(dhcp6_client_classes_,
dhcp6_client_classes_length_);
}
2017-12-03 19:23:00 +01:00
// Convert user_context to string as well.
std::string user_context;
if (user_context_null_ == MLM_FALSE) {
user_context_[user_context_length_] = '\0';
user_context.assign(user_context_);
}
// Set next server value (siaddr) if non NULL value returned.
asiolink::IOAddress next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
if (dhcp4_next_server_null_ == MLM_FALSE) {
next_server = asiolink::IOAddress(dhcp4_next_server_);
}
// Set server hostname (sname) if non NULL value returned.
std::string dhcp4_server_hostname;
if (dhcp4_server_hostname_null_ == MLM_FALSE) {
dhcp4_server_hostname = std::string(dhcp4_server_hostname_,
dhcp4_server_hostname_length_);
}
// Set boot file name (file) if non NULL value returned.
std::string dhcp4_boot_file_name;
if (dhcp4_boot_file_name_null_ == MLM_FALSE) {
dhcp4_boot_file_name = std::string(dhcp4_boot_file_name_,
dhcp4_boot_file_name_length_);
}
// Set the auth key if a non empty array is retrieved
std::string auth_key;
if (auth_key_null_ == MLM_FALSE) {
auth_key = std::string(auth_key_, auth_key_length_);
}
// Create and return Host object from the data gathered.
HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
hostname, dhcp4_client_classes, dhcp6_client_classes,
next_server, dhcp4_server_hostname,
dhcp4_boot_file_name, AuthKey(auth_key)));
h->setHostId(host_id_);
2017-12-03 19:23:00 +01:00
// Set the user context if there is one.
if (!user_context.empty()) {
try {
ConstElementPtr ctx = Element::fromJSON(user_context);
if (!ctx || (ctx->getType() != Element::map)) {
isc_throw(BadValue, "user context '" << user_context
2017-12-16 15:27:31 +01:00
<< "' is not a JSON map");
2017-12-03 19:23:00 +01:00
}
h->setContext(ctx);
} catch (const isc::data::JSONError& ex) {
isc_throw(BadValue, "user context '" << user_context
<< "' is invalid JSON: " << ex.what());
}
}
return (h);
};
/// @brief Processes one row of data fetched from a database.
///
/// The processed data must contain host id, which uniquely identifies a
/// host. This method creates a host and inserts it to the hosts collection
/// only if the last inserted host has a different host id. This prevents
/// adding duplicated hosts to the collection, assuming that processed
/// rows are primarily ordered by host id column.
///
2017-07-23 15:19:44 -04:00
/// This method must be overridden in the derived classes to also
/// retrieve IPv6 reservations and DHCP options associated with a host.
///
/// @param [out] hosts Collection of hosts to which a new host created
/// from the processed data should be inserted.
virtual void processFetchedData(ConstHostCollection& hosts) {
HostPtr host;
// Add new host only if there are no hosts yet or the host id of the
// most recently added host is different than the host id of the
// currently processed host.
if (hosts.empty() || (hosts.back()->getHostId() != getHostId())) {
// Create Host object from the fetched data and append it to the
// collection.
host = retrieveHost();
hosts.push_back(host);
}
}
/// @brief Return columns in error
///
/// If an error is returned from a fetch (in particular, a truncated
/// status), this method can be called to get the names of the fields in
/// error. It returns a string comprising the names of the fields
/// separated by commas. In the case of there being no error indicators
/// set, it returns the string "(None)".
///
/// @return Comma-separated list of columns in error, or the string
/// "(None)".
std::string getErrorColumns() {
return (getColumnsInError(error_, columns_));
};
protected:
/// Number of columns returned in queries.
size_t columns_num_;
/// Vector of MySQL bindings.
std::vector<MYSQL_BIND> bind_;
/// Column names.
std::vector<std::string> columns_;
/// Error array.
std::vector<my_bools> error_;
/// Pointer to Host object holding information to be inserted into
/// Hosts table.
HostPtr host_;
private:
/// Host identifier (primary key in Hosts table).
uint64_t host_id_;
/// Buffer holding client's identifier (e.g. DUID, HW address)
/// in the binary format.
uint8_t dhcp_identifier_buffer_[ClientId::MAX_CLIENT_ID_LEN];
/// Length of a data in the dhcp_identifier_buffer_.
unsigned long dhcp_identifier_length_;
/// Type of the identifier in the dhcp_identifier_buffer_. This
/// value corresponds to the @ref Host::IdentifierType value.
uint8_t dhcp_identifier_type_;
/// DHCPv4 subnet identifier.
uint32_t dhcp4_subnet_id_;
/// DHCPv6 subnet identifier.
uint32_t dhcp6_subnet_id_;
/// Reserved IPv4 address.
uint32_t ipv4_address_;
/// Name reserved for the host.
char hostname_[HOSTNAME_MAX_LEN];
/// Hostname length.
unsigned long hostname_length_;
/// A string holding comma separated list of DHCPv4 client classes.
char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];
/// A length of the string holding comma separated list of DHCPv4
/// client classes.
unsigned long dhcp4_client_classes_length_;
/// A string holding comma separated list of DHCPv6 client classes.
char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];
/// A length of the string holding comma separated list of DHCPv6
/// client classes.
unsigned long dhcp6_client_classes_length_;
2017-12-03 19:23:00 +01:00
/// @brief Buffer holding textual user context.
char user_context_[USER_CONTEXT_MAX_LEN];
/// @brief User context length.
unsigned long user_context_length_;
/// Next server address (siaddr).
uint32_t dhcp4_next_server_;
/// Server hostname (sname).
char dhcp4_server_hostname_[SERVER_HOSTNAME_MAX_LEN];
/// A length of the string holding server hostname.
unsigned long dhcp4_server_hostname_length_;
/// Boot file name (file).
char dhcp4_boot_file_name_[BOOT_FILE_NAME_MAX_LEN];
/// A length of the string holding boot file name.
unsigned long dhcp4_boot_file_name_length_;
/// Authentication keys
char auth_key_[TEXT_AUTH_KEY_LEN];
/// The length of the string for holding keys
unsigned long auth_key_length_;
/// @name Boolean values indicating if values of specific columns in
/// the database are NULL.
//@{
/// Boolean flag indicating if the value of the DHCPv4 subnet is NULL.
my_bool dhcp4_subnet_id_null_;
/// Boolean flag indicating if the value of the DHCPv6 subnet is NULL.
my_bool dhcp6_subnet_id_null_;
/// Boolean flag indicating if the value of IPv4 reservation is NULL.
my_bool ipv4_address_null_;
/// Boolean flag indicating if the value if hostname is NULL.
my_bool hostname_null_;
/// Boolean flag indicating if the value of DHCPv4 client classes is
/// NULL.
my_bool dhcp4_client_classes_null_;
/// Boolean flag indicating if the value of DHCPv6 client classes is
/// NULL.
my_bool dhcp6_client_classes_null_;
2017-12-03 19:23:00 +01:00
/// @brief Boolean flag indicating if the value of user context is NULL.
my_bool user_context_null_;
/// Boolean flag indicating if the value of next server is NULL.
my_bool dhcp4_next_server_null_;
/// Boolean flag indicating if the value of server hostname is NULL.
my_bool dhcp4_server_hostname_null_;
/// Boolean flag indicating if the value of boot file name is NULL.
my_bool dhcp4_boot_file_name_null_;
/// Boolean flag indicating if the value of string is NULL.
my_bool auth_key_null_;
//@}
};
/// @brief Extends base exchange class with ability to retrieve DHCP options
/// from the 'dhcp4_options' and 'dhcp6_options' tables.
///
/// This class provides means to retrieve both DHCPv4 and DHCPv6 options
/// along with the host information. It is not used to retrieve IPv6
/// reservations. The following types of queries are supported:
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
class MySqlHostWithOptionsExchange : public MySqlHostExchange {
private:
2022-05-18 22:09:45 +03:00
/// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
2023-03-06 17:34:30 +01:00
static const size_t OPTION_COLUMNS = 8;
/// @brief Receives DHCPv4 or DHCPv6 options information from the
/// dhcp4_options or dhcp6_options tables respectively.
///
/// The MySqlHostWithOptionsExchange class holds two respective instances
/// of this class, one for receiving DHCPv4 options, one for receiving
/// DHCPv6 options.
///
/// The following are the basic functions of this class:
/// - bind class members to specific columns in MySQL binding tables,
/// - set DHCP options specific column names,
/// - create instances of options retrieved from the database.
///
/// The reason for isolating those functions in a separate C++ class is
/// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
class OptionProcessor {
public:
/// @brief Constructor.
///
/// @param universe V4 or V6. The type of the options' instances
/// created by this class depends on this parameter.
/// @param start_column Index of the first column to be used by this
/// class.
OptionProcessor(const Option::Universe& universe,
const size_t start_column)
: universe_(universe), start_column_(start_column), option_id_(0),
code_(0), value_length_(0), formatted_value_length_(0),
2023-03-06 17:34:30 +01:00
space_length_(0), persistent_(false), cancelled_(false),
user_context_length_(0),
option_id_null_(MLM_FALSE), code_null_(MLM_FALSE),
value_null_(MLM_FALSE), formatted_value_null_(MLM_FALSE),
space_null_(MLM_FALSE), user_context_null_(MLM_FALSE),
option_id_index_(start_column), code_index_(start_column_ + 1),
value_index_(start_column_ + 2),
formatted_value_index_(start_column_ + 3),
space_index_(start_column_ + 4),
persistent_index_(start_column_ + 5),
2023-03-06 17:34:30 +01:00
cancelled_index_(start_column_ + 6),
user_context_index_(start_column_ + 7),
most_recent_option_id_(0) {
memset(value_, 0, sizeof(value_));
memset(formatted_value_, 0, sizeof(formatted_value_));
memset(space_, 0, sizeof(space_));
memset(user_context_, 0, sizeof(user_context_));
}
/// @brief Returns identifier of the currently processed option.
uint64_t getOptionId() const {
if (option_id_null_ == MLM_FALSE) {
return (option_id_);
}
return (0);
}
/// @brief Creates instance of the currently processed option.
///
/// This method detects if the currently processed option is a new
/// instance. It makes it determination by comparing the identifier
/// of the currently processed option, with the most recently processed
/// option. If the current value is greater than the id of the recently
/// processed option it is assumed that the processed row holds new
/// option information. In such case the option instance is created and
/// inserted into the configuration passed as argument.
///
/// @param cfg Pointer to the configuration object into which new
/// option instances should be inserted.
void retrieveOption(const CfgOptionPtr& cfg) {
// option_id may be NULL if dhcp4_options or dhcp6_options table
// doesn't contain any options for the particular host. Also, the
// current option id must be greater than id if the most recent
// option because options are ordered by option id. Otherwise
// we assume that this is already processed option.
if ((option_id_null_ == MLM_TRUE) ||
(most_recent_option_id_ >= option_id_)) {
return;
}
// Remember current option id as the most recent processed one. We
// will be comparing it with option ids in subsequent rows.
most_recent_option_id_ = option_id_;
// Convert it to string object for easier comparison.
std::string space;
if (space_null_ == MLM_FALSE) {
// Typically, the string values returned by the database are not
// NULL terminated.
space_[space_length_] = '\0';
space.assign(space_);
}
2016-09-20 15:37:11 +02:00
// If empty or null space provided, use a default top level space.
if (space.empty()) {
2020-08-03 17:37:32 +02:00
space = (universe_ == Option::V4 ?
DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
2016-09-20 15:37:11 +02:00
}
// Convert formatted_value to string as well.
std::string formatted_value;
if (formatted_value_null_ == MLM_FALSE) {
formatted_value_[formatted_value_length_] = '\0';
formatted_value.assign(formatted_value_);
}
// Convert user_context to string as well.
std::string user_context;
if (user_context_null_ == MLM_FALSE) {
user_context_[user_context_length_] = '\0';
user_context.assign(user_context_);
}
// Options are held in a binary or textual format in the database.
// This is similar to having an option specified in a server
// configuration file. Such option is converted to appropriate C++
// class, using option definition. Thus, we need to find the
// option definition for this option code and option space.
// Check if this is a standard option.
OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code_);
// Otherwise, we may check if this an option encapsulated within the
// vendor space.
if (!def && (space != DHCP4_OPTION_SPACE) &&
(space != DHCP6_OPTION_SPACE)) {
uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
if (vendor_id > 0) {
def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_);
}
}
// In all other cases, we use runtime option definitions, which
// should be also registered within the libdhcp++.
if (!def) {
def = LibDHCP::getRuntimeOptionDef(space, code_);
}
// Finish with a last resort option definition.
if (!def) {
def = LibDHCP::getLastResortOptionDef(space, code_);
}
OptionPtr option;
// If no definition found, we use generic option type.
if (!def) {
// We have to pay attention if the value is NULL. If it is,
// we must create an empty option instance. We can't rely on
// the value_length_ because it may contain garbage for the
// null values. Thus we check explicitly whether or not the
// NULL flag is set.
if (value_null_ == MLM_FALSE) {
OptionBuffer buf(value_, value_ + value_length_);
option.reset(new Option(universe_, code_, buf.begin(),
buf.end()));
} else {
option.reset(new Option(universe_, code_));
}
} else {
// The option value may be specified in textual or binary format
// in the database. If formatted_value is empty, the binary
// format is used. Depending on the format we use a different
// variant of the optionFactory function.
if (formatted_value.empty()) {
OptionBuffer buf(value_, value_ + value_length_);
// Again, check if the value is null before submitting the
// buffer to the factory function.
option = def->optionFactory(universe_, code_, buf.begin(),
value_null_ == MLM_FALSE ? buf.end() :
buf.begin());
2023-07-04 18:27:51 +03:00
} else {
// Spit the value specified in comma separated values
// format.
std::vector<std::string> split_vec;
boost::split(split_vec, formatted_value, boost::is_any_of(","));
option = def->optionFactory(universe_, code_, split_vec);
}
}
2023-03-06 17:34:30 +01:00
OptionDescriptor desc(option, persistent_, cancelled_,
formatted_value);
// Set the user context if there is one into the option descriptor.
if (!user_context.empty()) {
try {
ConstElementPtr ctx = Element::fromJSON(user_context);
if (!ctx || (ctx->getType() != Element::map)) {
isc_throw(BadValue, "user context '" << user_context
<< "' is no a JSON map");
}
desc.setContext(ctx);
} catch (const isc::data::JSONError& ex) {
isc_throw(BadValue, "user context '" << user_context
<< "' is invalid JSON: " << ex.what());
}
}
cfg->add(desc, space);
}
/// @brief Specify column names.
///
/// @param [out] columns Reference to a vector holding names of option
/// specific columns.
void setColumnNames(std::vector<std::string>& columns) {
columns[option_id_index_] = "option_id";
columns[code_index_] = "code";
columns[value_index_] = "value";
columns[formatted_value_index_] = "formatted_value";
columns[space_index_] = "space";
columns[persistent_index_] = "persistent";
2023-03-06 17:34:30 +01:00
columns[cancelled_index_] = "cancelled";
columns[user_context_index_] = "user_context";
}
/// @brief Initialize binding table fields for options.
///
2020-01-27 16:06:38 +02:00
/// Resets most_recent_option_id_ to 0 and other exchange members to
/// default values.
///
/// @param [out] bind Binding table.
void setBindFields(std::vector<MYSQL_BIND>& bind) {
// This method is called just before making a new query, so we
2020-01-27 16:55:07 +02:00
// reset the most_recent_option_id_ and other exchange members to
2020-01-27 16:06:38 +02:00
// start over with options processing.
most_recent_option_id_ = 0;
option_id_ = 0;
code_ = 0;
persistent_ = false;
2023-03-06 17:34:30 +01:00
cancelled_ = false;
option_id_null_ = MLM_FALSE;
code_null_ = MLM_FALSE;
value_null_ = MLM_FALSE;
formatted_value_null_ = MLM_FALSE;
space_null_ = MLM_FALSE;
user_context_null_ = MLM_FALSE;
memset(value_, 0, sizeof(value_));
memset(formatted_value_, 0, sizeof(formatted_value_));
memset(space_, 0, sizeof(space_));
memset(user_context_, 0, sizeof(user_context_));
// option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
bind[option_id_index_].is_unsigned = MLM_TRUE;
bind[option_id_index_].is_null = &option_id_null_;
// code : TINYINT OR SHORT UNSIGNED NOT NULL
bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
bind[code_index_].is_unsigned = MLM_TRUE;
bind[code_index_].is_null = &code_null_;
// value : BLOB NULL
value_length_ = sizeof(value_);
bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
bind[value_index_].buffer = reinterpret_cast<char*>(value_);
bind[value_index_].buffer_length = value_length_;
bind[value_index_].length = &value_length_;
bind[value_index_].is_null = &value_null_;
// formatted_value : TEXT NULL
formatted_value_length_ = sizeof(formatted_value_);
bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
bind[formatted_value_index_].buffer_length = formatted_value_length_;
bind[formatted_value_index_].length = &formatted_value_length_;
bind[formatted_value_index_].is_null = &formatted_value_null_;
// space : VARCHAR(128) NULL
space_length_ = sizeof(space_);
bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
bind[space_index_].buffer = reinterpret_cast<char*>(space_);
bind[space_index_].buffer_length = space_length_;
bind[space_index_].length = &space_length_;
bind[space_index_].is_null = &space_null_;
// persistent : TINYINT(1) NOT NULL DEFAULT 0
bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
bind[persistent_index_].is_unsigned = MLM_TRUE;
2023-03-06 17:34:30 +01:00
// cancelled : TINYINT(1) NOT NULL DEFAULT 0
bind[cancelled_index_].buffer_type = MYSQL_TYPE_TINY;
bind[cancelled_index_].buffer = reinterpret_cast<char*>(&cancelled_);
bind[cancelled_index_].is_unsigned = MLM_TRUE;
// user_context : TEXT NULL
user_context_length_ = sizeof(user_context_);
bind[user_context_index_].buffer_type = MYSQL_TYPE_STRING;
bind[user_context_index_].buffer = reinterpret_cast<char*>(user_context_);
bind[user_context_index_].buffer_length = user_context_length_;
bind[user_context_index_].length = &user_context_length_;
bind[user_context_index_].is_null = &user_context_null_;
}
private:
/// @brief Universe: V4 or V6.
Option::Universe universe_;
/// @brief Index of first column used by this class.
size_t start_column_;
/// @brief Option id.
uint32_t option_id_;
/// @brief Option code.
uint16_t code_;
/// @brief Buffer holding binary value of an option.
uint8_t value_[OPTION_VALUE_MAX_LEN];
/// @brief Option value length.
unsigned long value_length_;
/// @brief Buffer holding textual value of an option.
char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];
/// @brief Formatted option value length.
unsigned long formatted_value_length_;
/// @brief Buffer holding option space name.
char space_[OPTION_SPACE_MAX_LEN];
/// @brief Option space length.
unsigned long space_length_;
/// @brief Flag indicating if option is always sent or only if
/// requested.
bool persistent_;
2023-03-06 17:34:30 +01:00
/// @brief Flag indicating if option must be never sent.
bool cancelled_;
/// @brief Buffer holding textual user context of an option.
char user_context_[USER_CONTEXT_MAX_LEN];
/// @brief User context length.
unsigned long user_context_length_;
/// @name Boolean values indicating if values of specific columns in
/// the database are NULL.
//@{
/// @brief Boolean flag indicating if the DHCPv4 option id is NULL.
my_bool option_id_null_;
/// @brief Boolean flag indicating if the DHCPv4 option code is NULL.
my_bool code_null_;
/// @brief Boolean flag indicating if the DHCPv4 option value is NULL.
my_bool value_null_;
/// @brief Boolean flag indicating if the DHCPv4 formatted option value
/// is NULL.
my_bool formatted_value_null_;
/// @brief Boolean flag indicating if the DHCPv4 option space is NULL.
my_bool space_null_;
/// @brief Boolean flag indicating if the DHCPv4 option user context is NULL.
my_bool user_context_null_;
//@}
/// @name Indexes of the specific columns
//@{
/// @brief Option id
size_t option_id_index_;
/// @brief Code
size_t code_index_;
/// @brief Value
size_t value_index_;
/// @brief Formatted value
size_t formatted_value_index_;
/// @brief Space
size_t space_index_;
/// @brief Persistent
size_t persistent_index_;
2023-03-06 17:34:30 +01:00
/// @brief Cancelled
size_t cancelled_index_;
//@}
2020-01-21 11:14:26 +02:00
/// @brief User context
size_t user_context_index_;
/// @brief Option id for last processed row.
uint32_t most_recent_option_id_;
};
/// @brief Pointer to the @ref OptionProcessor class.
typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
public:
/// @brief DHCP option types to be fetched from the database.
///
/// Supported types are:
/// - Only DHCPv4 options,
/// - Only DHCPv6 options,
/// - Both DHCPv4 and DHCPv6 options.
enum FetchedOptions {
DHCP4_ONLY,
DHCP6_ONLY,
DHCP4_AND_DHCP6
};
/// @brief Constructor.
///
/// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
/// be fetched from the database for a host.
/// @param additional_columns_num Number of additional columns for which
/// resources should be allocated, e.g. binding table, column names etc.
/// This parameter should be set to a non zero value by derived classes to
/// allocate resources for the columns supported by derived classes.
MySqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
const size_t additional_columns_num = 0)
: MySqlHostExchange(getRequiredColumnsNum(fetched_options)
+ additional_columns_num),
opt_proc4_(), opt_proc6_() {
// Create option processor for DHCPv4 options, if required.
if ((fetched_options == DHCP4_ONLY) ||
(fetched_options == DHCP4_AND_DHCP6)) {
opt_proc4_.reset(new OptionProcessor(Option::V4,
findAvailColumn()));
opt_proc4_->setColumnNames(columns_);
}
// Create option processor for DHCPv6 options, if required.
if ((fetched_options == DHCP6_ONLY) ||
(fetched_options == DHCP4_AND_DHCP6)) {
opt_proc6_.reset(new OptionProcessor(Option::V6,
findAvailColumn()));
opt_proc6_->setColumnNames(columns_);
}
}
/// @brief Processes the current row.
///
/// The processed row includes both host information and DHCP option
/// information. Because used SELECT query use LEFT JOIN clause, the
/// some rows contain duplicated host or options entries. This method
/// detects duplicated information and discards such entries.
///
/// @param [out] hosts Container holding parsed hosts and options.
virtual void processFetchedData(ConstHostCollection& hosts) {
// Holds pointer to the previously parsed host.
HostPtr most_recent_host;
if (!hosts.empty()) {
// Const cast is not very elegant way to deal with it, but
// there is a good reason to use it here. This method is called
// to build a collection of const hosts to be returned to the
// caller. If we wanted to use non-const collection we'd need
// to copy the whole collection before returning it, which has
// performance implications. Alternatively, we could store the
// most recently added host in a class member but this would
// make the code less readable.
most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
}
// If no host has been parsed yet or we're at the row holding next
// host, we create a new host object and put it at the end of the
// list.
if (!most_recent_host || (most_recent_host->getHostId() < getHostId())) {
HostPtr host = retrieveHost();
hosts.push_back(host);
most_recent_host = host;
}
// Parse DHCPv4 options if required to do so.
if (opt_proc4_) {
CfgOptionPtr cfg = most_recent_host->getCfgOption4();
opt_proc4_->retrieveOption(cfg);
}
// Parse DHCPv6 options if required to do so.
if (opt_proc6_) {
CfgOptionPtr cfg = most_recent_host->getCfgOption6();
opt_proc6_->retrieveOption(cfg);
}
}
/// @brief Bind variables for receiving option data.
///
/// @return Vector of MYSQL_BIND object representing data to be retrieved.
virtual std::vector<MYSQL_BIND> createBindForReceive() {
// The following call sets bind_ values between 0 and 8.
static_cast<void>(MySqlHostExchange::createBindForReceive());
// Bind variables for DHCPv4 options.
if (opt_proc4_) {
opt_proc4_->setBindFields(bind_);
}
// Bind variables for DHCPv6 options.
if (opt_proc6_) {
opt_proc6_->setBindFields(bind_);
}
// Add the error flags
setErrorIndicators(bind_, error_);
return (bind_);
};
private:
/// @brief Returns a number of columns required to retrieve option data.
///
/// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
/// DHCPv6 a different number of columns is required in the binding array.
/// This method returns the number of required columns, according to the
/// value of @c fetched_columns passed in the constructor.
///
/// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
/// both types of options should be retrieved.
///
/// @return Number of required columns.
static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
OPTION_COLUMNS);
}
/// @brief Pointer to DHCPv4 options processor.
///
/// If this object is NULL, the DHCPv4 options are not fetched.
OptionProcessorPtr opt_proc4_;
/// @brief Pointer to DHCPv6 options processor.
///
/// If this object is NULL, the DHCPv6 options are not fetched.
OptionProcessorPtr opt_proc6_;
};
/// @brief This class provides mechanisms for sending and retrieving
/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
///
/// This class extends the @ref MySqlHostWithOptionsExchange class with the
2016-12-14 16:57:44 +02:00
/// mechanisms to retrieve IPv6 reservations. This class is used in situations
/// when it is desired to retrieve DHCPv6 specific information about the host
/// (DHCPv6 options and reservations), or entire information about the host
/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
/// queries used with this class:
/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
/// LEFT JOIN ipv6_reservations ...
/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
class MySqlHostIPv6Exchange : public MySqlHostWithOptionsExchange {
private:
/// @brief Number of columns holding IPv6 reservation information.
static const size_t RESERVATION_COLUMNS = 5;
public:
/// @brief Constructor.
///
/// Apart from initializing the base class data structures it also
/// initializes values representing IPv6 reservation information.
MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
: MySqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
reservation_id_(0),
reserv_type_(0), reserv_type_null_(MLM_FALSE),
ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
reservation_id_index_(findAvailColumn()),
address_index_(reservation_id_index_ + 1),
prefix_len_index_(reservation_id_index_ + 2),
type_index_(reservation_id_index_ + 3),
iaid_index_(reservation_id_index_ + 4),
most_recent_reservation_id_(0) {
memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
// Provide names of additional columns returned by the queries.
columns_[reservation_id_index_] = "reservation_id";
columns_[address_index_] = "address";
columns_[prefix_len_index_] = "prefix_len";
columns_[type_index_] = "type";
columns_[iaid_index_] = "dhcp6_iaid";
}
/// @brief Returns last fetched reservation id.
///
/// @return Reservation id or 0 if no reservation data is fetched.
uint32_t getReservationId() const {
if (reserv_type_null_ == MLM_FALSE) {
return (reservation_id_);
}
return (0);
};
/// @brief Creates IPv6 reservation from the data contained in the
/// currently processed row.
///
/// Called after the MYSQL_BIND array created by createBindForReceive().
///
/// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
IPv6Resrv retrieveReservation() {
// Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;
switch (reserv_type_) {
case 0:
type = IPv6Resrv::TYPE_NA;
break;
case 2:
type = IPv6Resrv::TYPE_PD;
break;
default:
isc_throw(BadValue,
"invalid IPv6 reservation type returned: "
<< static_cast<int>(reserv_type_)
<< ". Only 0 or 2 are allowed.");
}
IOAddress addr6 = IOAddress::fromBytes(AF_INET6, ipv6_address_buffer_);
IPv6Resrv r(type, addr6, prefix_len_);
return (r);
};
/// @brief Processes one row of data fetched from a database.
///
/// The processed data must contain host id, which uniquely identifies a
/// host. This method creates a host and inserts it to the hosts collection
/// only if the last inserted host has a different host id. This prevents
/// adding duplicated hosts to the collection, assuming that processed
/// rows are primarily ordered by host id column.
///
/// Depending on the value of the @c fetched_options specified in the
/// constructor, this method also parses options returned as a result
/// of SELECT queries.
///
/// For any returned row which contains IPv6 reservation information it
/// checks if the reservation is not a duplicate of previously parsed
/// reservation and appends the IPv6Resrv object into the host object
/// if the parsed row contains new reservation information.
///
/// @param [out] hosts Collection of hosts to which a new host created
/// from the processed data should be inserted.
virtual void processFetchedData(ConstHostCollection& hosts) {
// Call parent class to fetch host information and options.
MySqlHostWithOptionsExchange::processFetchedData(hosts);
if (getReservationId() == 0) {
return;
}
if (hosts.empty()) {
isc_throw(Unexpected, "no host information while retrieving"
" IPv6 reservation");
}
HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
// If we're dealing with a new reservation, let's add it to the
// host.
if (getReservationId() > most_recent_reservation_id_) {
most_recent_reservation_id_ = getReservationId();
if (most_recent_reservation_id_ > 0) {
host->addReservation(retrieveReservation());
}
}
}
/// @brief Create BIND array to receive Host data with IPv6 reservations.
///
/// Creates a MYSQL_BIND array to receive Host data from the database.
/// After data is successfully received, @ref processedFetchedData is
/// called for each returned row to build collection of @ref Host
/// objects with associated IPv6 reservations.
///
/// @return Vector of MYSQL_BIND objects representing data to be retrieved.
virtual std::vector<MYSQL_BIND> createBindForReceive() {
// Reset most recent reservation id value because we're now making
// a new SELECT query.
most_recent_reservation_id_ = 0;
// Bind values supported by parent classes.
static_cast<void>(MySqlHostWithOptionsExchange::createBindForReceive());
// reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
// IPv6 address/prefix BINARY(16)
ipv6_address_buffer_len_ = isc::asiolink::V6ADDRESS_LEN;
bind_[address_index_].buffer_type = MYSQL_TYPE_BLOB;
bind_[address_index_].buffer = reinterpret_cast<char*>(ipv6_address_buffer_);
bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
bind_[address_index_].length = &ipv6_address_buffer_len_;
// prefix_len : TINYINT
bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
// (reservation) type : TINYINT
reserv_type_null_ = MLM_FALSE;
bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
bind_[type_index_].is_unsigned = MLM_TRUE;
bind_[type_index_].is_null = &reserv_type_null_;
// dhcp6_iaid INT UNSIGNED
bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
bind_[iaid_index_].is_unsigned = MLM_TRUE;
// Add the error flags
setErrorIndicators(bind_, error_);
return (bind_);
};
private:
/// @brief IPv6 reservation id.
uint32_t reservation_id_;
/// @brief IPv6 reservation type.
uint8_t reserv_type_;
/// @brief Boolean flag indicating if reservation type field is null.
///
/// This flag is used by the class to determine if the returned row
/// contains IPv6 reservation information.
my_bool reserv_type_null_;
/// @brief Buffer holding IPv6 address/prefix in textual format.
uint8_t ipv6_address_buffer_[isc::asiolink::V6ADDRESS_LEN];
/// @brief Length of the textual address representation.
unsigned long ipv6_address_buffer_len_;
/// @brief Length of the prefix (128 for addresses)
uint8_t prefix_len_;
/// @brief IAID.
uint32_t iaid_;
/// @name Indexes of columns holding information about IPv6 reservations.
//@{
/// @brief Index of reservation_id column.
size_t reservation_id_index_;
/// @brief Index of address column.
size_t address_index_;
/// @brief Index of prefix_len column.
size_t prefix_len_index_;
/// @brief Index of type column.
size_t type_index_;
/// @brief Index of IAID column.
size_t iaid_index_;
//@}
/// @brief Reservation id for last processed row.
uint32_t most_recent_reservation_id_;
};
/// @brief This class is used for storing IPv6 reservations in a MySQL database.
///
/// This class is only used to insert IPv6 reservations into the
/// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
/// retrieve IPv6 reservation the @ref MySqlHostIPv6Exchange class should be
/// used instead.
///
2016-03-23 12:07:50 +01:00
/// When a new IPv6 reservation is inserted into the database, an appropriate
/// host must be defined in the hosts table. An attempt to insert IPv6
/// reservation for non-existing host will result in failure.
class MySqlIPv6ReservationExchange {
private:
/// @brief Set number of columns for ipv6_reservation table.
static const size_t RESRV_COLUMNS = 6;
public:
/// @brief Constructor
///
/// Initialize class members representing a single IPv6 reservation.
MySqlIPv6ReservationExchange()
: host_id_(0), prefix_len_(0), type_(0),
iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
// Reset error table.
std::fill(&error_[0], &error_[RESRV_COLUMNS], MLM_FALSE);
// Set the column names (for error messages)
columns_[0] = "host_id";
columns_[1] = "address";
columns_[2] = "prefix_len";
columns_[3] = "type";
columns_[4] = "dhcp6_iaid";
2023-05-26 18:19:25 +03:00
BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
}
/// @brief Create MYSQL_BIND objects for IPv6 Reservation.
///
/// Fills in the MYSQL_BIND array for sending data in the IPv6 Reservation
/// object to the database.
///
/// @param resv An object representing IPv6 reservation which will be
/// sent to the database.
/// None of the fields in the reservation are modified -
/// the reservation data is only read.
/// @param id ID of a host owning this reservation
/// @param unique_ip boolean value indicating if multiple reservations for the
/// same IP address are allowed (false) or not (true).
///
/// @return Vector of MySQL BIND objects representing the data to be added.
std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
const HostID& id,
const bool unique_ip) {
// Store the values to ensure they remain valid.
resv_ = resv;
host_id_ = id;
// Initialize prior to constructing the array of MYSQL_BIND structures.
// It sets all fields, including is_null, to zero, so we need to set
// is_null only if it should be true. This gives up minor performance
// benefit while being safe approach. For improved readability, the
// code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// Set up the structures for the various components of the host structure.
try {
addr6_ = resv.getPrefix().toBytes();
if (addr6_.size() != isc::asiolink::V6ADDRESS_LEN) {
isc_throw(DbOperationError, "createBindForSend() - prefix is not "
<< isc::asiolink::V6ADDRESS_LEN << " bytes long");
}
addr6_length_ = isc::asiolink::V6ADDRESS_LEN;
bind_[0].buffer_type = MYSQL_TYPE_BLOB;
bind_[0].buffer = reinterpret_cast<char*>(&addr6_[0]);
bind_[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
bind_[0].length = &addr6_length_;
// prefix_len tinyint
prefix_len_ = resv.getPrefixLen();
bind_[1].buffer_type = MYSQL_TYPE_TINY;
bind_[1].buffer = reinterpret_cast<char*>(&prefix_len_);
bind_[1].is_unsigned = MLM_TRUE;
// type tinyint
// See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
type_ = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
bind_[2].buffer_type = MYSQL_TYPE_TINY;
bind_[2].buffer = reinterpret_cast<char*>(&type_);
bind_[2].is_unsigned = MLM_TRUE;
// dhcp6_iaid INT UNSIGNED
/// @todo: We don't support iaid in the IPv6Resrv yet.
iaid_ = 0;
bind_[3].buffer_type = MYSQL_TYPE_LONG;
bind_[3].buffer = reinterpret_cast<char*>(&iaid_);
bind_[3].is_unsigned = MLM_TRUE;
// host_id INT UNSIGNED NOT NULL
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&host_id_);
bind_[4].is_unsigned = MLM_TRUE;
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not create bind array from IPv6 Reservation: "
<< resv_.toText() << ", reason: " << ex.what());
}
// Add the data to the vector. Note the end element is one after the
// end of the array.
// RESRV_COLUMNS -1 as we do not set reservation_id.
std::vector<MYSQL_BIND> vec(&bind_[0], &bind_[RESRV_COLUMNS-1]);
// When checking whether the IP is unique we need to bind the IPv6 address
// and prefix length at the end of the query as it has additional binding
// for the IPv6 address and prefix length.
if (unique_ip) {
vec.push_back(bind_[0]); // address
vec.push_back(bind_[1]); // prefix_len
}
return (vec);
}
private:
/// @brief Host unique identifier.
uint64_t host_id_;
2020-01-10 20:19:18 +02:00
/// @brief Length of the prefix (128 for addresses).
uint8_t prefix_len_;
/// @brief Reservation type.
uint8_t type_;
/// @brief IAID.
uint8_t iaid_;
/// @brief Object holding reservation being sent to the database.
IPv6Resrv resv_;
/// @brief Array of MySQL bindings.
MYSQL_BIND bind_[RESRV_COLUMNS];
/// @brief Array of strings holding columns names.
std::string columns_[RESRV_COLUMNS];
/// @brief Array of boolean values indicating if error occurred
/// for respective columns.
my_bool error_[RESRV_COLUMNS];
/// @brief Binary address data.
std::vector<uint8_t> addr6_;
/// @brief Binary address length.
unsigned long addr6_length_;
};
/// @brief This class is used for inserting options into a database.
///
/// This class supports inserting both DHCPv4 and DHCPv6 options.
class MySqlOptionExchange {
private:
2023-05-26 18:19:25 +03:00
static const size_t OPTION_ID_COL = 0;
static const size_t CODE_COL = 1;
static const size_t VALUE_COL = 2;
static const size_t FORMATTED_VALUE_COL = 3;
static const size_t SPACE_COL = 4;
static const size_t PERSISTENT_COL = 5;
static const size_t CANCELLED_COL = 6;
static const size_t USER_CONTEXT_COL = 7;
static const size_t DHCP_SUBNET_ID_COL = 8;
static const size_t HOST_ID_COL = 9;
/// @brief Number of columns in the option tables holding bindable values.
static const size_t OPTION_COLUMNS = 10;
public:
/// @brief Constructor.
MySqlOptionExchange()
: type_(0), value_len_(0), formatted_value_len_(0), space_(),
2023-03-06 17:34:30 +01:00
space_len_(0), persistent_(false), cancelled_(false),
user_context_(), user_context_len_(0), subnet_id_(SUBNET_ID_UNUSED),
host_id_(0), option_() {
BOOST_STATIC_ASSERT(10 <= OPTION_COLUMNS);
}
/// @brief Creates binding array to insert option data into database.
///
/// @return Vector of MYSQL_BIND object representing an option.
std::vector<MYSQL_BIND> createBindForSend(const OptionDescriptor& opt_desc,
const std::string& opt_space,
const Optional<SubnetID>& subnet_id,
const HostID& host_id) {
// Hold pointer to the option to make sure it remains valid until
// we complete a query.
option_ = opt_desc.option_;
memset(bind_, 0, sizeof(bind_));
try {
// option_id: INT UNSIGNED NOT NULL
// The option_id is auto_incremented, so we need to pass the NULL
// value.
bind_[0].buffer_type = MYSQL_TYPE_NULL;
// code: SMALLINT UNSIGNED NOT NULL
2022-05-18 22:09:45 +03:00
type_ = option_->getType();
bind_[1].buffer_type = MYSQL_TYPE_SHORT;
bind_[1].buffer = reinterpret_cast<char*>(&type_);
bind_[1].is_unsigned = MLM_TRUE;
// value: BLOB NULL
if (opt_desc.formatted_value_.empty() &&
(opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
// The formatted_value is empty and the option value is
// non-empty so we need to prepare on-wire format for the
// option and store it in the database as a blob.
OutputBuffer buf(opt_desc.option_->len());
opt_desc.option_->pack(buf);
const char* buf_ptr = static_cast<const char*>(buf.getData());
value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
buf_ptr + buf.getLength());
value_len_ = value_.size();
bind_[2].buffer_type = MYSQL_TYPE_BLOB;
bind_[2].buffer = &value_[0];
bind_[2].buffer_length = value_len_;
bind_[2].length = &value_len_;
} else {
// No value or formatted_value specified. In this case, the
// value blob is NULL.
value_.clear();
bind_[2].buffer_type = MYSQL_TYPE_NULL;
}
// formatted_value: TEXT NULL,
if (!opt_desc.formatted_value_.empty()) {
formatted_value_len_ = opt_desc.formatted_value_.size();
bind_[3].buffer_type = MYSQL_TYPE_STRING;
bind_[3].buffer = const_cast<char*>(opt_desc.formatted_value_.c_str());
bind_[3].buffer_length = formatted_value_len_;
bind_[3].length = &formatted_value_len_;
} else {
bind_[3].buffer_type = MYSQL_TYPE_NULL;
}
// space: VARCHAR(128) NULL
space_ = opt_space;
space_len_ = space_.size();
bind_[4].buffer_type = MYSQL_TYPE_STRING;
bind_[4].buffer = const_cast<char*>(space_.c_str());
bind_[4].buffer_length = space_len_;
bind_[4].length = &space_len_;
// persistent: TINYINT(1) NOT NULL DEFAULT 0
persistent_ = opt_desc.persistent_;
bind_[5].buffer_type = MYSQL_TYPE_TINY;
bind_[5].buffer = reinterpret_cast<char*>(&persistent_);
bind_[5].is_unsigned = MLM_TRUE;
2023-03-06 17:34:30 +01:00
// cancelled: TINYINT(1) NOT NULL DEFAULT 0
cancelled_ = opt_desc.cancelled_;
bind_[6].buffer_type = MYSQL_TYPE_TINY;
bind_[6].buffer = reinterpret_cast<char*>(&cancelled_);
bind_[6].is_unsigned = MLM_TRUE;
// user_context: TEST NULL,
ConstElementPtr ctx = opt_desc.getContext();
if (ctx) {
user_context_ = ctx->str();
user_context_len_ = user_context_.size();
2023-03-06 17:34:30 +01:00
bind_[7].buffer_type = MYSQL_TYPE_STRING;
bind_[7].buffer = const_cast<char*>(user_context_.c_str());
bind_[7].buffer_length = user_context_len_;
bind_[7].length = &user_context_len_;
} else {
2023-03-06 17:34:30 +01:00
bind_[7].buffer_type = MYSQL_TYPE_NULL;
}
// dhcp4_subnet_id: INT UNSIGNED NULL
if (!subnet_id.unspecified()) {
subnet_id_ = subnet_id;
2023-03-06 17:34:30 +01:00
bind_[8].buffer_type = MYSQL_TYPE_LONG;
bind_[8].buffer = reinterpret_cast<char*>(subnet_id_);
bind_[8].is_unsigned = MLM_TRUE;
} else {
2023-03-06 17:34:30 +01:00
bind_[8].buffer_type = MYSQL_TYPE_NULL;
}
// host_id: INT UNSIGNED NOT NULL
host_id_ = host_id;
2023-03-06 17:34:30 +01:00
bind_[9].buffer_type = MYSQL_TYPE_LONG;
bind_[9].buffer = reinterpret_cast<char*>(&host_id_);
bind_[9].is_unsigned = MLM_TRUE;
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not create bind array for inserting DHCP "
"option: " << option_->toText() << ", reason: "
<< ex.what());
}
return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[OPTION_COLUMNS]));
}
private:
/// @brief Option type.
uint16_t type_;
/// @brief Option value as binary.
std::vector<uint8_t> value_;
/// @brief Option value length.
unsigned long value_len_;
/// @brief Formatted option value length.
unsigned long formatted_value_len_;
/// @brief Option space name.
std::string space_;
/// @brief Option space name length.
unsigned long space_len_;
/// @brief Boolean flag indicating if the option is always returned to
/// a client or only when requested.
bool persistent_;
2023-03-06 17:34:30 +01:00
/// @brief Boolean flag indicating if the option must be never returned
/// to a client.
bool cancelled_;
/// @brief User context.
std::string user_context_;
/// @brief User context length.
unsigned long user_context_len_;
/// @brief Subnet identifier.
uint32_t subnet_id_;
/// @brief Host identifier.
uint32_t host_id_;
/// @brief Pointer to currently parsed option.
OptionPtr option_;
/// @brief Array of MYSQL_BIND elements representing inserted data.
MYSQL_BIND bind_[OPTION_COLUMNS];
};
2020-01-21 11:14:26 +02:00
} // namespace
namespace isc {
namespace dhcp {
/// @brief MySQL Host Context
///
/// This class stores the thread context for the manager pool.
2020-01-27 15:55:03 +02:00
/// The class is needed by all get/update/delete functions which must use one
/// or more exchanges to perform database operations.
/// Each context provides a set of such exchanges for each thread.
/// The context instances are lazy initialized by the requesting thread by using
/// the manager's createContext function and are destroyed when the manager's
/// pool instance is destroyed.
class MySqlHostContext {
public:
/// @brief Constructor
///
/// @param parameters See MySqlHostMgr constructor.
2021-03-18 23:32:07 +02:00
/// @param io_service_accessor The IOService accessor function.
2020-12-09 14:32:17 +02:00
/// @param db_reconnect_callback The connection recovery callback.
MySqlHostContext(const DatabaseConnection::ParameterMap& parameters,
2021-03-18 23:32:07 +02:00
IOServiceAccessorPtr io_service_accessor,
2020-12-09 14:32:17 +02:00
db::DbCallback db_reconnect_callback);
/// The exchange objects are used for transfer of data to/from the database.
/// They are pointed-to objects as the contents may change in "const" calls,
/// while the rest of this object does not. (At alternative would be to
/// declare them as "mutable".)
2020-01-21 14:50:20 +02:00
/// @brief Pointer to an object representing an exchange which can
/// be used to retrieve hosts and DHCPv4 options.
boost::shared_ptr<MySqlHostWithOptionsExchange> host_ipv4_exchange_;
/// @brief Pointer to an object representing an exchange which can
/// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv6_exchange_;
/// @brief Pointer to an object representing an exchange which can
/// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
/// IPv6 reservations using a single query.
boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv46_exchange_;
/// @brief Pointer to an object representing an exchange which can
/// be used to insert new IPv6 reservation.
boost::shared_ptr<MySqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
/// @brief Pointer to an object representing an exchange which can
/// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
/// or dhcp6_options table.
boost::shared_ptr<MySqlOptionExchange> host_option_exchange_;
/// @brief MySQL connection
2020-01-10 19:44:32 +02:00
MySqlConnection conn_;
/// @brief Indicates if the database is opened in read only mode.
bool is_readonly_;
};
/// @brief MySQL Host Context Pool
///
/// This class provides a pool of contexts.
2021-01-22 01:36:41 +02:00
/// The manager will use this class to handle available contexts.
2020-01-27 15:55:03 +02:00
/// There is only one ContextPool per manager per back-end, which is created
/// and destroyed by the respective manager factory class.
class MySqlHostContextPool {
public:
/// @brief The vector of available contexts.
std::vector<MySqlHostContextPtr> pool_;
/// @brief The mutex to protect pool access.
std::mutex mutex_;
};
/// @brief Type of pointers to context pools.
typedef boost::shared_ptr<MySqlHostContextPool> MySqlHostContextPoolPtr;
/// @brief Implementation of the @ref MySqlHostDataSource.
class MySqlHostDataSourceImpl {
public:
/// @brief Statement Tags
///
/// The contents of the enum are indexes into the list of SQL statements.
2016-12-14 16:57:44 +02:00
/// It is assumed that the order is such that the indices of statements
/// reading the database are less than those of statements modifying the
/// database.
/// @note: please add new statements doing read only operations before
/// the WRITE_STMTS_BEGIN position.
enum StatementIndex {
GET_HOST_DHCPID, // Gets hosts by host identifier
GET_HOST_ADDR, // Gets hosts by IPv4 address
GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
GET_HOST_PREFIX, // Gets host by IPv6 prefix
GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
2023-06-26 17:07:11 +02:00
GET_HOST_ADDR6, // Gets hosts by IPv6 address/prefix
GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
GET_HOST_HOSTNAME, // Gets hosts by hostname
GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
DEL_HOST_ADDR6, // Delete v6 host (subnet-id, addr6)
DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
NUM_STATEMENTS // Number of statements
};
/// @brief Index of first statement performing write to the database.
///
/// This value is used to mark border line between queries and other
/// statements and statements performing write operation on the database,
/// such as INSERT, DELETE, UPDATE.
static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_NON_UNIQUE_IP;
/// @brief Constructor.
///
/// This constructor opens database connection and initializes prepared
/// statements used in the queries.
MySqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters);
/// @brief Destructor.
2021-08-05 11:17:58 +03:00
~MySqlHostDataSourceImpl();
/// @brief Attempts to reconnect the server to the host DB backend manager.
///
/// This is a self-rescheduling function that attempts to reconnect to the
/// server's host DB backends after connectivity to one or more have been
/// lost. Upon entry it will attempt to reconnect via
/// @ref HostDataSourceFactory::add.
/// If this is successful, DHCP servicing is re-enabled and server returns
/// to normal operation.
///
/// If reconnection fails and the maximum number of retries has not been
/// exhausted, it will schedule a call to itself to occur at the
/// configured retry interval. DHCP service remains disabled.
///
/// If the maximum number of retries has been exhausted an error is logged
/// and the server shuts down.
///
2020-12-09 14:32:17 +02:00
/// This function is passed to the connection recovery mechanism. It will be
/// invoked when a connection loss is detected.
///
/// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
/// configured reconnect parameters.
/// @return true if connection has been recovered, false otherwise.
static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);
/// @brief Create a new context.
///
/// The database is opened with all the SQL commands pre-compiled.
///
/// @return A new (never null) context.
2020-01-10 20:19:18 +02:00
///
/// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
/// @throw isc::db::DbOperationError An operation on the open database has
/// failed.
MySqlHostContextPtr createContext() const;
/// @brief Returns backend version.
///
/// The method is called by the constructor before opening the database
/// to verify that the schema version is correct.
///
/// @return Version number stored in the database, as a pair of unsigned
/// integers. "first" is the major version number, "second" the
/// minor number.
///
/// @throw isc::dhcp::DbOperationError An operation on the open database
/// has failed.
std::pair<uint32_t, uint32_t> getVersion() const;
2016-05-25 15:28:41 +02:00
/// @brief Executes statements which inserts a row into one of the tables.
///
/// @param ctx Context
/// @param stindex Index of a statement being executed.
/// @param bind Vector of MYSQL_BIND objects to be used when making the
/// query.
///
/// @throw isc::db::DuplicateEntry Database throws duplicate entry error
void addStatement(MySqlHostContextPtr& ctx,
MySqlHostDataSourceImpl::StatementIndex stindex,
2016-05-25 15:28:41 +02:00
std::vector<MYSQL_BIND>& bind);
/// @brief Executes statements that delete records.
///
/// @param ctx Context
/// @param stindex Index of a statement being executed.
/// @param bind Vector of MYSQL_BIND objects to be used when making the
/// query.
2020-01-10 20:19:18 +02:00
///
/// @return true if any records were deleted, false otherwise
bool delStatement(MySqlHostContextPtr& ctx,
StatementIndex stindex,
MYSQL_BIND* bind);
/// @brief Inserts IPv6 Reservation into ipv6_reservation table.
///
/// @param ctx Context
/// @param resv IPv6 Reservation to be added
/// @param id ID of a host owning this reservation
void addResv(MySqlHostContextPtr& ctx,
const IPv6Resrv& resv,
const HostID& id);
/// @brief Inserts a single DHCP option into the database.
///
/// @param ctx Context
/// @param stindex Index of a statement being executed.
/// @param opt_desc Option descriptor holding information about an option
/// to be inserted into the database.
/// @param opt_space Option space name.
/// @param subnet_id Subnet identifier.
/// @param host_id Host identifier.
void addOption(MySqlHostContextPtr& ctx,
const MySqlHostDataSourceImpl::StatementIndex& stindex,
const OptionDescriptor& opt_desc,
const std::string& opt_space,
const Optional<SubnetID>& subnet_id,
const HostID& host_id);
/// @brief Inserts multiple options into the database.
///
/// @param ctx Context
/// @param stindex Index of a statement being executed.
/// @param options_cfg An object holding a collection of options to be
/// inserted into the database.
/// @param host_id Host identifier retrieved using @c mysql_insert_id.
void addOptions(MySqlHostContextPtr& ctx,
const StatementIndex& stindex,
const ConstCfgOptionPtr& options_cfg,
const uint64_t host_id);
/// @brief Check Error and Throw Exception
///
/// This method invokes @ref db::MySqlConnection::checkError.
///
/// @param ctx Context
/// @param status Status code: non-zero implies an error
/// @param index Index of statement that caused the error
/// @param what High-level description of the error
///
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
void checkError(MySqlHostContextPtr& ctx,
const int status,
const StatementIndex index,
const char* what) const;
/// @brief Creates collection of @ref Host objects with associated
2016-06-01 17:35:44 +02:00
/// information such as IPv6 reservations and/or DHCP options.
///
/// This method performs a query which returns host information from
/// the 'hosts' table. The query may also use LEFT JOIN clause to
/// retrieve information from other tables, e.g. ipv6_reservations,
/// dhcp4_options and dhcp6_options.
/// Whether IPv6 reservations and/or options are assigned to the
/// @ref Host objects depends on the type of the exchange object.
///
/// @param ctx Context
/// @param stindex Statement index.
/// @param bind Pointer to an array of MySQL bindings.
/// @param exchange Pointer to the exchange object used for the
/// particular query.
/// @param [out] result Reference to the collection of hosts returned.
/// @param single A boolean value indicating if a single host is
/// expected to be returned, or multiple hosts.
void getHostCollection(MySqlHostContextPtr& ctx,
StatementIndex stindex,
MYSQL_BIND* bind,
boost::shared_ptr<MySqlHostExchange> exchange,
ConstHostCollection& result,
bool single) const;
/// @brief Retrieves a host by subnet and client's unique identifier.
///
/// This method is used by both MySqlHostDataSource::get4 and
/// MySqlHOstDataSource::get6 methods.
///
/// @param ctx Context
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
2016-12-14 16:57:44 +02:00
/// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
/// @param stindex Statement index.
/// @param exchange Pointer to the exchange object used for the
/// particular query.
///
/// @return Pointer to const instance of Host or null pointer if
/// no host found.
ConstHostPtr getHost(MySqlHostContextPtr& ctx,
const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len,
StatementIndex stindex,
boost::shared_ptr<MySqlHostExchange> exchange) const;
/// @brief Throws exception if database is read only.
///
/// This method should be called by the methods which write to the
/// database. If the backend is operating in read-only mode this
/// method will throw exception.
///
/// @param ctx Context
///
/// @throw DbReadOnly if backend is operating in read only mode.
void checkReadOnly(MySqlHostContextPtr& ctx) const;
/// @brief The parameters
2020-01-10 19:44:32 +02:00
DatabaseConnection::ParameterMap parameters_;
/// @brief Holds the setting whether the IP reservations must be unique or
/// may be non-unique.
bool ip_reservations_unique_;
/// @brief The pool of contexts
MySqlHostContextPoolPtr pool_;
/// @brief Indicates if there is at least one connection that can no longer
/// be used for normal operations.
bool unusable_;
/// @brief Timer name used to register database reconnect timer.
std::string timer_name_;
};
namespace {
/// @brief Array of tagged statements.
typedef boost::array<TaggedStatement, MySqlHostDataSourceImpl::NUM_STATEMENTS>
TaggedStatementArray;
/// @brief Prepared MySQL statements used by the backend to insert and
/// retrieve hosts from the database.
TaggedStatementArray tagged_statements = { {
// Retrieves host information, IPv6 reservations and both DHCPv4 and
// DHCPv6 options associated with the host. The LEFT JOIN clause is used
// to retrieve information from 4 different tables using a single query.
// Hence, this query returns multiple rows for a single host.
{MySqlHostDataSourceImpl::GET_HOST_DHCPID,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
"h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
2017-12-03 19:23:00 +01:00
"h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
2023-03-06 17:34:30 +01:00
"o4.persistent, o4.cancelled, o4.user_context, "
"o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
2023-03-06 17:34:30 +01:00
"o6.persistent, o6.cancelled, o6.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o4 "
"ON h.host_id = o4.host_id "
"LEFT JOIN dhcp6_options AS o6 "
"ON h.host_id = o6.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? "
"ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
// Retrieves host information along with the DHCPv4 options associated with
// it. Left joining the dhcp4_options table results in multiple rows being
// returned for the same host. The host is retrieved by IPv4 address.
{MySqlHostDataSourceImpl::GET_HOST_ADDR,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2017-12-03 19:23:00 +01:00
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o "
"ON h.host_id = o.host_id "
"WHERE ipv4_address = ? "
"ORDER BY h.host_id, o.option_id"},
// Retrieves host information and DHCPv4 options using subnet identifier
// and client's identifier. Left joining the dhcp4_options table results in
// multiple rows being returned for the same host.
{MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2017-12-03 19:23:00 +01:00
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o "
"ON h.host_id = o.host_id "
"WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
2020-01-10 17:16:49 +02:00
"AND h.dhcp_identifier = ? "
"ORDER BY h.host_id, o.option_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
// associated with a host. The number of rows returned is a multiplication
// of number of IPv6 reservations and DHCPv6 options.
{MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2017-12-03 19:23:00 +01:00
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
"AND h.dhcp_identifier = ? "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
// Retrieves host information and DHCPv4 options for the host using subnet
// identifier and IPv4 reservation. Left joining the dhcp4_options table
// results in multiple rows being returned for the host. The number of
// rows depends on the number of options defined for the host.
{MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2017-12-03 19:23:00 +01:00
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o "
"ON h.host_id = o.host_id "
"WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
"ORDER BY h.host_id, o.option_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
// associated with a host using prefix and prefix length. This query
// returns host information for a single host. However, multiple rows
// are returned due to left joining IPv6 reservations and DHCPv6 options.
// The number of rows returned is multiplication of number of existing
// IPv6 reservations and DHCPv6 options.
{MySqlHostDataSourceImpl::GET_HOST_PREFIX,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2017-12-03 19:23:00 +01:00
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context,"
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE h.host_id = "
2020-01-10 17:09:20 +02:00
"( SELECT host_id FROM ipv6_reservations "
"WHERE address = ? AND prefix_len = ? ) "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
2020-01-21 11:40:38 +02:00
// associated with a host using IPv6 subnet id and prefix. This query
// returns host information for a single host. However, multiple rows
// are returned due to left joining IPv6 reservations and DHCPv6 options.
// The number of rows returned is multiplication of number of existing
// IPv6 reservations and DHCPv6 options.
{MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2017-12-03 19:23:00 +01:00
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE h.dhcp6_subnet_id = ? AND h.host_id IN "
"(SELECT host_id FROM ipv6_reservations "
"WHERE address = ?) "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
2023-06-22 13:17:11 +02:00
// associated with a host using IPv6 address/prefix. This query
2023-06-20 11:06:14 +02:00
// may return host information for one or more host reservations. Even
// if only one host is found, multiple rows
// are returned due to left joining IPv6 reservations and DHCPv6 options.
// The number of rows returned is multiplication of number of existing
// IPv6 reservations and DHCPv6 options.
{MySqlHostDataSourceImpl::GET_HOST_ADDR6,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
"o.persistent, o.cancelled, o.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
2023-06-22 00:28:38 +02:00
"WHERE h.host_id IN "
"(SELECT host_id FROM ipv6_reservations "
"WHERE address = ?) "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
// Retrieves host information along with the DHCPv4 options associated with
// it. Left joining the dhcp4_options table results in multiple rows being
// returned for the same host. Hosts are retrieved by IPv4 subnet id.
{MySqlHostDataSourceImpl::GET_HOST_SUBID4,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o "
"ON h.host_id = o.host_id "
"WHERE h.dhcp4_subnet_id = ? "
"ORDER BY h.host_id, o.option_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
// associated with a host. The number of rows returned is a multiplication
// of number of IPv6 reservations and DHCPv6 options. Hosts are retrieved
// by IPv6 subnet id.
{MySqlHostDataSourceImpl::GET_HOST_SUBID6,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE h.dhcp6_subnet_id = ? "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
// Retrieves host information, IPv6 reservations and both DHCPv4 and
// DHCPv6 options associated with the host. The LEFT JOIN clause is used
// to retrieve information from 4 different tables using a single query.
// Hence, this query returns multiple rows for a single host.
{MySqlHostDataSourceImpl::GET_HOST_HOSTNAME,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
"h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
"h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
2023-03-06 17:34:30 +01:00
"o4.persistent, o4.cancelled, o4.user_context, "
"o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
2023-03-06 17:34:30 +01:00
"o6.persistent, o6.cancelled, o6.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o4 "
"ON h.host_id = o4.host_id "
"LEFT JOIN dhcp6_options AS o6 "
"ON h.host_id = o6.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE h.hostname = ? "
"ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
// Retrieves host information and DHCPv4 options using hostname and
// subnet identifier. Left joining the dhcp4_options table results in
// multiple rows being returned for the same host.
{MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o "
"ON h.host_id = o.host_id "
"WHERE h.hostname = ? AND h.dhcp4_subnet_id = ? "
"ORDER BY h.host_id, o.option_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
// using hostname and subnet identifier. The number of rows returned
// is a multiplication of number of IPv6 reservations and DHCPv6 options.
{MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM hosts AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE h.hostname = ? AND h.dhcp6_subnet_id = ? "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
// Retrieves host information along with the DHCPv4 options associated with
// it. Left joining the dhcp4_options table results in multiple rows being
// returned for the same host. Hosts are retrieved by IPv4 subnet id
// and with a host id greater than the start one.
// The number of hosts returned is lower or equal to the limit.
{MySqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context "
"FROM ( SELECT * FROM hosts AS h "
2020-01-10 17:09:20 +02:00
"WHERE h.dhcp4_subnet_id = ? AND h.host_id > ? "
"ORDER BY h.host_id "
"LIMIT ? ) AS h "
"LEFT JOIN dhcp4_options AS o "
"ON h.host_id = o.host_id "
"ORDER BY h.host_id, o.option_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
// associated with a host. The number of rows returned is a multiplication
// of number of IPv6 reservations and DHCPv6 options. Hosts are retrieved
// by IPv6 subnet id and with a host id greater than the start one.
// The number of hosts returned is lower or equal to the limit.
{MySqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM ( SELECT * FROM hosts AS h "
2020-01-10 17:09:20 +02:00
"WHERE h.dhcp6_subnet_id = ? AND h.host_id > ? "
"ORDER BY h.host_id "
"LIMIT ? ) AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
2020-09-01 13:41:04 +02:00
// Retrieves host information along with the DHCPv4 options associated with
// it. Left joining the dhcp4_options table results in multiple rows being
// returned for the same host. Hosts are retrieved with a host id greater
// than the start one.
// The number of hosts returned is lower or equal to the limit.
{MySqlHostDataSourceImpl::GET_HOST_PAGE4,
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context "
2020-09-01 13:41:04 +02:00
"FROM ( SELECT * FROM hosts AS h "
"WHERE h.host_id > ? "
"ORDER BY h.host_id "
"LIMIT ? ) AS h "
"LEFT JOIN dhcp4_options AS o "
"ON h.host_id = o.host_id "
"ORDER BY h.host_id, o.option_id"},
// Retrieves host information, IPv6 reservations and DHCPv6 options
// associated with a host. The number of rows returned is a multiplication
// of number of IPv6 reservations and DHCPv6 options. Hosts are retrieved
// with a host id greater than the start one.
// The number of hosts returned is lower or equal to the limit.
{MySqlHostDataSourceImpl::GET_HOST_PAGE6,
"SELECT h.host_id, h.dhcp_identifier, "
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, "
"h.dhcp4_boot_file_name, h.auth_key, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
2023-03-06 17:34:30 +01:00
"o.persistent, o.cancelled, o.user_context, "
2020-09-01 13:41:04 +02:00
"r.reservation_id, r.address, r.prefix_len, r.type, "
"r.dhcp6_iaid "
"FROM ( SELECT * FROM hosts AS h "
"WHERE h.host_id > ? "
"ORDER BY h.host_id "
"LIMIT ? ) AS h "
"LEFT JOIN dhcp6_options AS o "
"ON h.host_id = o.host_id "
"LEFT JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
// Inserts a host into the 'hosts' table without checking that there is
// a reservation for the IP address.
{MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
"INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
"dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
"dhcp4_client_classes, dhcp6_client_classes, "
"user_context, dhcp4_next_server, "
"dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
// Inserts a host into the 'hosts' table with checking that reserved IP
// address is unique. The innermost query checks if there is at least
// one host for the given IP/subnet combination. For checking whether
// hosts exists or not it doesn't matter if we select actual columns,
// thus SELECT 1 was used as an optimization to avoid selecting data
// that will be ignored anyway. If the host does not exist the new
// host is inserted. DUAL is a special MySQL table from which we can
// select the values to be inserted. If the host with the given IP
// address already exists the new host won't be inserted. The caller
// can check the number of affected rows to detect that there was
// a duplicate host in the database.
{MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP,
"INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
"dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
"dhcp4_client_classes, dhcp6_client_classes, "
"user_context, dhcp4_next_server, "
"dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM DUAL "
"WHERE NOT EXISTS ("
"SELECT 1 FROM hosts "
"WHERE ipv4_address = ? AND dhcp4_subnet_id = ? "
"LIMIT 1"
")"},
// Inserts a single IPv6 reservation into 'reservations' table without
// checking that the inserted reservation is unique.
{MySqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE,
2020-01-10 17:09:20 +02:00
"INSERT INTO ipv6_reservations(address, prefix_len, type, "
"dhcp6_iaid, host_id) "
2020-01-10 17:16:49 +02:00
"VALUES (?, ?, ?, ?, ?)"},
// Inserts a single IPv6 reservation into 'reservations' table with
// checking that the inserted reservation is unique.
{MySqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE,
"INSERT INTO ipv6_reservations(address, prefix_len, type, "
"dhcp6_iaid, host_id) "
"SELECT ?, ?, ?, ?, ? FROM DUAL "
"WHERE NOT EXISTS ("
"SELECT 1 FROM ipv6_reservations "
"WHERE address = ? AND prefix_len = ? "
"LIMIT 1"
")"},
// Inserts a single DHCPv4 option into 'dhcp4_options' table.
// Using fixed scope_id = 3, which associates an option with host.
2020-01-21 11:40:38 +02:00
{MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
"INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
2023-03-06 17:34:30 +01:00
"persistent, cancelled, user_context, dhcp4_subnet_id, host_id, scope_id) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
// Inserts a single DHCPv6 option into 'dhcp6_options' table.
// Using fixed scope_id = 3, which associates an option with host.
2020-01-21 11:40:38 +02:00
{MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
"INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
2023-03-06 17:34:30 +01:00
"persistent, cancelled, user_context, dhcp6_subnet_id, host_id, scope_id) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
// Delete IPv4 reservations by subnet id and reserved address.
{MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
"DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
// Delete IPv6 reservations by subnet id and reserved address/prefix.
{MySqlHostDataSourceImpl::DEL_HOST_ADDR6,
"DELETE h FROM hosts AS h "
"INNER JOIN ipv6_reservations AS r "
"ON h.host_id = r.host_id "
"WHERE h.dhcp6_subnet_id = ? AND r.address = ?"},
// Delete a single IPv4 reservation by subnet id and identifier.
{MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
"DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
"AND dhcp_identifier = ?"},
// Delete a single IPv6 reservation by subnet id and identifier.
{MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
"DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? "
"AND dhcp_identifier = ?"}
}
};
2020-01-21 11:14:26 +02:00
} // namespace
// MySqlHostContext Constructor
MySqlHostContext::MySqlHostContext(const DatabaseConnection::ParameterMap& parameters,
2021-03-18 23:32:07 +02:00
IOServiceAccessorPtr io_service_accessor,
2020-12-09 14:32:17 +02:00
db::DbCallback db_reconnect_callback)
2021-03-18 23:32:07 +02:00
: conn_(parameters, io_service_accessor, db_reconnect_callback),
is_readonly_(true) {
}
// MySqlHostContextAlloc Constructor and Destructor
MySqlHostDataSource::MySqlHostContextAlloc::MySqlHostContextAlloc(
MySqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {
if (MultiThreadingMgr::instance().getMode()) {
2020-01-27 11:43:22 +01:00
// multi-threaded
{
2020-01-27 11:43:22 +01:00
// we need to protect the whole pool_ operation, hence extra scope {}
lock_guard<mutex> lock(mgr_.pool_->mutex_);
if (!mgr_.pool_->pool_.empty()) {
ctx_ = mgr_.pool_->pool_.back();
mgr_.pool_->pool_.pop_back();
}
}
if (!ctx_) {
ctx_ = mgr_.createContext();
}
} else {
2020-01-27 15:55:03 +02:00
// single-threaded
if (mgr_.pool_->pool_.empty()) {
2020-05-11 01:37:32 +02:00
isc_throw(Unexpected, "No available MySQL host context?!");
}
ctx_ = mgr_.pool_->pool_.back();
}
}
MySqlHostDataSource::MySqlHostContextAlloc::~MySqlHostContextAlloc() {
if (MultiThreadingMgr::instance().getMode()) {
2020-01-27 11:43:22 +01:00
// multi-threaded
lock_guard<mutex> lock(mgr_.pool_->mutex_);
mgr_.pool_->pool_.push_back(ctx_);
if (ctx_->conn_.isUnusable()) {
mgr_.unusable_ = true;
}
} else if (ctx_->conn_.isUnusable()) {
mgr_.unusable_ = true;
}
}
MySqlHostDataSourceImpl::MySqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters)
: parameters_(parameters), ip_reservations_unique_(true), unusable_(false),
timer_name_("") {
2021-03-17 17:32:54 +02:00
// Create unique timer name per instance.
2020-11-13 11:54:26 +02:00
timer_name_ = "MySqlHostMgr[";
timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
timer_name_ += "]DbReconnectTimer";
2020-01-21 11:40:38 +02:00
// Validate the schema version first.
std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
MYSQL_SCHEMA_VERSION_MINOR);
std::pair<uint32_t, uint32_t> db_version = getVersion();
if (code_version != db_version) {
2020-01-21 11:40:38 +02:00
isc_throw(DbOpenError,
"MySQL schema version mismatch: need version: "
<< code_version.first << "." << code_version.second
<< " found version: " << db_version.first << "."
<< db_version.second);
}
// Create an initial context.
pool_.reset(new MySqlHostContextPool());
pool_->pool_.push_back(createContext());
}
// Create context.
MySqlHostContextPtr
MySqlHostDataSourceImpl::createContext() const {
MySqlHostContextPtr ctx(new MySqlHostContext(parameters_,
2021-03-18 23:32:07 +02:00
IOServiceAccessorPtr(new IOServiceAccessor(&HostMgr::getIOService)),
&MySqlHostDataSourceImpl::dbReconnect));
// Open the database.
ctx->conn_.openDatabase();
// Check if we have TLS when we required it.
if (ctx->conn_.getTls()) {
std::string cipher = ctx->conn_.getTlsCipher();
if (cipher.empty()) {
LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_NO_TLS);
} else {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_MYSQL_TLS_CIPHER)
.arg(cipher);
}
}
// Prepare query statements. Those are will be only used to retrieve
// information from the database, so they can be used even if the
// database is read only for the current user.
ctx->conn_.prepareStatements(tagged_statements.begin(),
tagged_statements.begin() + WRITE_STMTS_BEGIN);
// Check if the backend is explicitly configured to operate with
// read only access to the database.
ctx->is_readonly_ = ctx->conn_.configuredReadOnly();
// If we are using read-write mode for the database we also prepare
// statements for INSERTS etc.
if (!ctx->is_readonly_) {
// Prepare statements for writing to the database, e.g. INSERT.
ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
tagged_statements.end());
} else {
LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_READONLY);
}
// Create the exchange objects for use in exchanging data between the
// program and the database.
2020-01-21 14:50:20 +02:00
ctx->host_ipv4_exchange_.reset(new MySqlHostWithOptionsExchange(MySqlHostWithOptionsExchange::DHCP4_ONLY));
ctx->host_ipv6_exchange_.reset(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP6_ONLY));
ctx->host_ipv46_exchange_.reset(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
ctx->host_ipv6_reservation_exchange_.reset(new MySqlIPv6ReservationExchange());
ctx->host_option_exchange_.reset(new MySqlOptionExchange());
2021-03-17 17:32:54 +02:00
// Create ReconnectCtl for this connection.
ctx->conn_.makeReconnectCtl(timer_name_);
return (ctx);
}
2021-08-05 11:17:58 +03:00
MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() {
}
bool
MySqlHostDataSourceImpl::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
MultiThreadingCriticalSection cs;
2020-12-09 14:32:17 +02:00
// Invoke application layer connection lost callback.
if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
return (false);
}
bool reopened = false;
const std::string timer_name = db_reconnect_ctl->timerName();
// At least one connection was lost.
try {
CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
for (std::string& hds : host_db_access_list) {
auto parameters = DatabaseConnection::parse(hds);
if (HostMgr::delBackend("mysql", hds, true)) {
HostMgr::addBackend(hds);
}
}
reopened = true;
} catch (const std::exception& ex) {
2020-11-13 13:38:20 +02:00
LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED)
.arg(ex.what());
}
if (reopened) {
// Cancel the timer.
if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
TimerMgr::instance()->unregisterTimer(timer_name);
}
2020-12-09 14:32:17 +02:00
// Invoke application layer connection recovered callback.
if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
return (false);
}
} else {
if (!db_reconnect_ctl->checkRetries()) {
// We're out of retries, log it and initiate shutdown.
2020-11-13 13:38:20 +02:00
LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_RECONNECT_FAILED)
.arg(db_reconnect_ctl->maxRetries());
// Cancel the timer.
if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
TimerMgr::instance()->unregisterTimer(timer_name);
}
2020-12-09 14:32:17 +02:00
// Invoke application layer connection failed callback.
DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
return (false);
}
2020-11-13 13:38:20 +02:00
LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE)
.arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
.arg(db_reconnect_ctl->maxRetries())
.arg(db_reconnect_ctl->retryInterval());
// Start the timer.
if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
TimerMgr::instance()->registerTimer(timer_name,
std::bind(&MySqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
db_reconnect_ctl->retryInterval(),
asiolink::IntervalTimer::ONE_SHOT);
}
TimerMgr::instance()->setup(timer_name);
}
return (true);
}
std::pair<uint32_t, uint32_t>
MySqlHostDataSourceImpl::getVersion() const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MYSQL_HOST_DB_GET_VERSION);
return (MySqlConnection::getVersion(parameters_));
}
void
MySqlHostDataSourceImpl::addStatement(MySqlHostContextPtr& ctx,
StatementIndex stindex,
2016-05-25 15:28:41 +02:00
std::vector<MYSQL_BIND>& bind) {
// Bind the parameters to the statement
int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), &bind[0]);
checkError(ctx, status, stindex, "unable to bind parameters");
// Execute the statement
status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));
if (status != 0) {
// Failure: check for the special case of duplicate entry.
if (mysql_errno(ctx->conn_.mysql_) == ER_DUP_ENTRY) {
isc_throw(DuplicateEntry, "Database duplicate entry error");
}
checkError(ctx, status, stindex, "unable to execute");
}
// If the number of rows inserted is 0 it means that the query detected
// an attempt to insert duplicated data for which there is no unique
// index in the database. Unique indexes are not created in the database
// when it may be sometimes allowed to insert duplicated records per
// server's configuration.
my_ulonglong numrows = mysql_stmt_affected_rows(ctx->conn_.getStatement(stindex));
if (numrows == 0) {
isc_throw(DuplicateEntry, "Database duplicate entry error");
}
}
bool
MySqlHostDataSourceImpl::delStatement(MySqlHostContextPtr& ctx,
StatementIndex stindex,
MYSQL_BIND* bind) {
// Bind the parameters to the statement
int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), &bind[0]);
checkError(ctx, status, stindex, "unable to bind parameters");
// Execute the statement
status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));
if (status != 0) {
checkError(ctx, status, stindex, "unable to execute");
}
// Let's check how many hosts were deleted.
my_ulonglong numrows = mysql_stmt_affected_rows(ctx->conn_.getStatement(stindex));
return (numrows != 0);
}
void
MySqlHostDataSourceImpl::addResv(MySqlHostContextPtr& ctx,
const IPv6Resrv& resv,
const HostID& id) {
std::vector<MYSQL_BIND> bind = ctx->host_ipv6_reservation_exchange_->
createBindForSend(resv, id, ip_reservations_unique_);
addStatement(ctx, ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE, bind);
}
void
MySqlHostDataSourceImpl::addOption(MySqlHostContextPtr& ctx,
const StatementIndex& stindex,
const OptionDescriptor& opt_desc,
const std::string& opt_space,
const Optional<SubnetID>& subnet_id,
const HostID& id) {
std::vector<MYSQL_BIND> bind = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, subnet_id, id);
addStatement(ctx, stindex, bind);
}
void
MySqlHostDataSourceImpl::addOptions(MySqlHostContextPtr& ctx,
const StatementIndex& stindex,
const ConstCfgOptionPtr& options_cfg,
const uint64_t host_id) {
// Get option space names and vendor space names and combine them within a
// single list.
std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
vendor_spaces.end());
// For each option space retrieve all options and insert them into the
// database.
for (auto space = option_spaces.begin(); space != option_spaces.end(); ++space) {
OptionContainerPtr options = options_cfg->getAllCombined(*space);
if (options && !options->empty()) {
for (auto opt = options->begin(); opt != options->end(); ++opt) {
addOption(ctx, stindex, *opt, *space, Optional<SubnetID>(), host_id);
}
}
}
}
void
MySqlHostDataSourceImpl::checkError(MySqlHostContextPtr& ctx,
const int status,
const StatementIndex index,
const char* what) const {
ctx->conn_.checkError(status, index, what);
}
void
MySqlHostDataSourceImpl::getHostCollection(MySqlHostContextPtr& ctx,
StatementIndex stindex,
MYSQL_BIND* bind,
boost::shared_ptr<MySqlHostExchange> exchange,
ConstHostCollection& result,
bool single) const {
// Bind the selection parameters to the statement
int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), bind);
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<MYSQL_BIND> outbind = exchange->createBindForReceive();
status = mysql_stmt_bind_result(ctx->conn_.getStatement(stindex), &outbind[0]);
checkError(ctx, status, stindex, "unable to bind SELECT clause parameters");
// Execute the statement
status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));
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(ctx->conn_.getStatement(stindex));
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. mysql_stmt_fetch return value equal to 0 represents
// successful data fetch.
MySqlFreeResult fetch_release(ctx->conn_.getStatement(stindex));
while ((status = mysql_stmt_fetch(ctx->conn_.getStatement(stindex))) ==
MLM_MYSQL_FETCH_SUCCESS) {
try {
exchange->processFetchedData(result);
} catch (const isc::BadValue& ex) {
// Rethrow the exception with a bit more data.
isc_throw(BadValue, ex.what() << ". Statement is <" <<
2020-01-21 10:28:56 +02:00
ctx->conn_.text_statements_[stindex] << ">");
}
if (single && (result.size() > 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 mysql_stmt_fetch return value is equal to 1 an error occurred.
if (status == MLM_MYSQL_FETCH_FAILURE) {
// 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
isc_throw(DataTruncated, ctx->conn_.text_statements_[stindex]
<< " returned truncated data: columns affected are "
<< exchange->getErrorColumns());
}
}
ConstHostPtr
MySqlHostDataSourceImpl::getHost(MySqlHostContextPtr& ctx,
const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len,
StatementIndex stindex,
boost::shared_ptr<MySqlHostExchange> exchange) const {
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
memset(inbind, 0, sizeof(inbind));
uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
inbind[0].is_unsigned = MLM_TRUE;
// Identifier value.
std::vector<char> identifier_vec(identifier_begin,
identifier_begin + identifier_len);
unsigned long length = identifier_vec.size();
inbind[2].buffer_type = MYSQL_TYPE_BLOB;
inbind[2].buffer = &identifier_vec[0];
inbind[2].buffer_length = length;
inbind[2].length = &length;
// Identifier type.
char identifier_type_copy = static_cast<char>(identifier_type);
inbind[1].buffer_type = MYSQL_TYPE_TINY;
inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection collection;
getHostCollection(ctx, stindex, inbind, exchange, collection, true);
// Return single record if present, else clear the host.
ConstHostPtr result;
if (!collection.empty()) {
result = *collection.begin();
}
return (result);
}
void
MySqlHostDataSourceImpl::checkReadOnly(MySqlHostContextPtr& ctx) const {
if (ctx->is_readonly_) {
isc_throw(ReadOnlyDb, "MySQL host database backend is configured to"
" operate in read only mode");
}
}
MySqlHostDataSource::MySqlHostDataSource(const DatabaseConnection::ParameterMap& parameters)
2020-11-06 11:59:54 +02:00
: impl_(new MySqlHostDataSourceImpl(parameters)) {
}
2021-08-05 11:17:58 +03:00
MySqlHostDataSource::~MySqlHostDataSource() {
}
DatabaseConnection::ParameterMap
MySqlHostDataSource::getParameters() const {
2021-08-02 11:00:12 +03:00
return (impl_->parameters_);
}
void
MySqlHostDataSource::add(const HostPtr& host) {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// If operating in read-only mode, throw exception.
impl_->checkReadOnly(ctx);
// Initiate MySQL transaction as we will have to make multiple queries
// to insert host information into multiple tables. If that fails on
// any stage, the transaction will be rolled back by the destructor of
// the MySqlTransaction class.
MySqlTransaction transaction(ctx->conn_);
// If we're configured to check that an IP reservation within a given subnet
// is unique, the IP reservation exists and the subnet is actually set
// we will be using a special query that checks for uniqueness. Otherwise,
// we will use a regular insert statement.
bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
&& host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
// Create the MYSQL_BIND array for the host
std::vector<MYSQL_BIND> bind = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
2016-05-25 15:28:41 +02:00
// ... and insert the host.
impl_->addStatement(ctx, unique_ip ? MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP, bind);
// Gets the last inserted hosts id
uint64_t host_id = mysql_insert_id(ctx->conn_.mysql_);
// Insert DHCPv4 options.
ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
if (cfg_option4) {
2020-01-21 11:40:38 +02:00
impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
cfg_option4, host_id);
}
// Insert DHCPv6 options.
ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
if (cfg_option6) {
2020-01-21 11:40:38 +02:00
impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
cfg_option6, host_id);
}
2016-05-25 15:28:41 +02:00
// Insert IPv6 reservations.
IPv6ResrvRange v6resv = host->getIPv6Reservations();
if (std::distance(v6resv.first, v6resv.second) > 0) {
for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
++resv) {
impl_->addResv(ctx, resv->second, host_id);
2016-05-25 15:28:41 +02:00
}
}
// Everything went fine, so explicitly commit the transaction.
transaction.commit();
}
bool
MySqlHostDataSource::del(const SubnetID& subnet_id,
const asiolink::IOAddress& addr) {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// If operating in read-only mode, throw exception.
impl_->checkReadOnly(ctx);
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
uint32_t subnet = subnet_id;
memset(inbind, 0, sizeof(inbind));
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
// v4
if (addr.isV4()) {
uint32_t addr4 = addr.toUint32();
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&addr4);
inbind[1].is_unsigned = MLM_TRUE;
return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
}
// v6
std::vector<uint8_t>addr6 = addr.toBytes();
if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
isc_throw(DbOperationError, "del() - address is not "
<< isc::asiolink::V6ADDRESS_LEN << " bytes long");
}
unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
inbind[1].buffer_type = MYSQL_TYPE_BLOB;
inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
inbind[1].length = &addr6_length;
return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR6, inbind));
}
bool
MySqlHostDataSource::del4(const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len) {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// If operating in read-only mode, throw exception.
impl_->checkReadOnly(ctx);
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
// subnet-id
memset(inbind, 0, sizeof(inbind));
uint32_t subnet = static_cast<uint32_t>(subnet_id);
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
// identifier type
char identifier_type_copy = static_cast<char>(identifier_type);
inbind[1].buffer_type = MYSQL_TYPE_TINY;
inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
inbind[1].is_unsigned = MLM_TRUE;
// identifier value
std::vector<char> identifier_vec(identifier_begin,
identifier_begin + identifier_len);
unsigned long length = identifier_vec.size();
inbind[2].buffer_type = MYSQL_TYPE_BLOB;
inbind[2].buffer = &identifier_vec[0];
inbind[2].buffer_length = length;
inbind[2].length = &length;
ConstHostCollection collection;
return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, inbind));
}
bool
MySqlHostDataSource::del6(const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len) {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// If operating in read-only mode, throw exception.
impl_->checkReadOnly(ctx);
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
// subnet-id
memset(inbind, 0, sizeof(inbind));
uint32_t subnet = static_cast<uint32_t>(subnet_id);
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
// identifier type
char identifier_type_copy = static_cast<char>(identifier_type);
inbind[1].buffer_type = MYSQL_TYPE_TINY;
inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
inbind[1].is_unsigned = MLM_TRUE;
// identifier value
std::vector<char> identifier_vec(identifier_begin,
identifier_begin + identifier_len);
unsigned long length = identifier_vec.size();
inbind[2].buffer_type = MYSQL_TYPE_BLOB;
inbind[2].buffer = &identifier_vec[0];
inbind[2].buffer_length = length;
inbind[2].length = &length;
ConstHostCollection collection;
return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, inbind));
}
ConstHostCollection
MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
// Identifier type.
char identifier_type_copy = static_cast<char>(identifier_type);
inbind[1].buffer = &identifier_type_copy;
inbind[1].buffer_type = MYSQL_TYPE_TINY;
inbind[1].is_unsigned = MLM_TRUE;
// Identifier value.
std::vector<char> identifier_vec(identifier_begin,
identifier_begin + identifier_len);
unsigned long int length = identifier_vec.size();
inbind[0].buffer_type = MYSQL_TYPE_BLOB;
inbind[0].buffer = &identifier_vec[0];
inbind[0].buffer_length = length;
inbind[0].length = &length;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_DHCPID, inbind,
ctx->host_ipv46_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAll4(const SubnetID& subnet_id) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
uint32_t subnet = subnet_id;
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4, inbind,
2020-01-21 14:50:20 +02:00
ctx->host_ipv4_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAll6(const SubnetID& subnet_id) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
uint32_t subnet = subnet_id;
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6, inbind,
ctx->host_ipv6_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
// Hostname
char hostname_[HOSTNAME_MAX_LEN];
strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
unsigned long length = hostname.length();
inbind[0].buffer_type = MYSQL_TYPE_STRING;
inbind[0].buffer = reinterpret_cast<char*>(hostname_);
inbind[0].buffer_length = length;
inbind[0].length = &length;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME, inbind,
ctx->host_ipv46_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAllbyHostname4(const std::string& hostname,
const SubnetID& subnet_id) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
// Hostname
char hostname_[HOSTNAME_MAX_LEN];
strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
unsigned long length = hostname.length();
inbind[0].buffer_type = MYSQL_TYPE_STRING;
inbind[0].buffer = reinterpret_cast<char*>(hostname_);
inbind[0].buffer_length = length;
inbind[0].length = &length;
// Subnet ID
uint32_t subnet = subnet_id;
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&subnet);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4, inbind,
2020-01-21 14:50:20 +02:00
ctx->host_ipv4_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAllbyHostname6(const std::string& hostname,
const SubnetID& subnet_id) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
// Hostname
char hostname_[HOSTNAME_MAX_LEN];
strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
unsigned long length = hostname.length();
inbind[0].buffer_type = MYSQL_TYPE_STRING;
inbind[0].buffer = reinterpret_cast<char*>(hostname_);
inbind[0].buffer_length = length;
inbind[0].length = &length;
// Subnet ID
uint32_t subnet = subnet_id;
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&subnet);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6, inbind,
ctx->host_ipv6_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getPage4(const SubnetID& subnet_id,
size_t& /*source_index*/,
uint64_t lower_host_id,
const HostPageSize& page_size) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
memset(inbind, 0, sizeof(inbind));
// Bind subnet id
uint32_t subnet = subnet_id;
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
// Bind lower host id
uint32_t host_id = lower_host_id;
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&host_id);
inbind[1].is_unsigned = MLM_TRUE;
// Bind page size value
uint32_t page_size_data = page_size.page_size_;
inbind[2].buffer_type = MYSQL_TYPE_LONG;
inbind[2].buffer = reinterpret_cast<char*>(&page_size_data);
inbind[2].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE, inbind,
2020-01-21 14:50:20 +02:00
ctx->host_ipv4_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getPage6(const SubnetID& subnet_id,
size_t& /*source_index*/,
uint64_t lower_host_id,
const HostPageSize& page_size) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
memset(inbind, 0, sizeof(inbind));
// Bind subnet id
uint32_t subnet = subnet_id;
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
// Bind lower host id
uint32_t host_id = lower_host_id;
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&host_id);
inbind[1].is_unsigned = MLM_TRUE;
// Bind page size value
uint32_t page_size_data = page_size.page_size_;
inbind[2].buffer_type = MYSQL_TYPE_LONG;
inbind[2].buffer = reinterpret_cast<char*>(&page_size_data);
inbind[2].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE, inbind,
ctx->host_ipv6_exchange_, result, false);
return (result);
}
2020-09-01 13:41:04 +02:00
ConstHostCollection
MySqlHostDataSource::getPage4(size_t& /*source_index*/,
uint64_t lower_host_id,
const HostPageSize& page_size) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
// Bind lower host id
uint32_t host_id = lower_host_id;
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&host_id);
inbind[0].is_unsigned = MLM_TRUE;
// Bind page size value
uint32_t page_size_data = page_size.page_size_;
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&page_size_data);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PAGE4, inbind,
ctx->host_ipv4_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getPage6(size_t& /*source_index*/,
uint64_t lower_host_id,
const HostPageSize& page_size) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
// Bind lower host id
uint32_t host_id = lower_host_id;
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&host_id);
inbind[0].is_unsigned = MLM_TRUE;
// Bind page size value
uint32_t page_size_data = page_size.page_size_;
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&page_size_data);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PAGE6, inbind,
ctx->host_ipv6_exchange_, result, false);
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
uint32_t addr4 = address.toUint32();
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&addr4);
inbind[0].is_unsigned = MLM_TRUE;
ConstHostCollection result;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_ADDR, inbind,
2020-01-21 14:50:20 +02:00
ctx->host_ipv4_exchange_, result, false);
return (result);
}
ConstHostPtr
MySqlHostDataSource::get4(const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
2020-01-21 14:50:20 +02:00
ctx->host_ipv4_exchange_));
}
ConstHostPtr
MySqlHostDataSource::get4(const SubnetID& subnet_id,
const asiolink::IOAddress& address) const {
if (!address.isV4()) {
isc_throw(BadValue, "MySqlHostDataSource::get4(id, address): "
"wrong address type, address supplied is an IPv6 address");
}
2020-01-10 20:19:18 +02:00
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
uint32_t subnet = subnet_id;
memset(inbind, 0, sizeof(inbind));
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
uint32_t addr4 = address.toUint32();
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&addr4);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection collection;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, inbind,
2020-01-21 14:50:20 +02:00
ctx->host_ipv4_exchange_, collection, true);
// Return single record if present, else clear the host.
ConstHostPtr result;
if (!collection.empty()) {
result = *collection.begin();
}
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAll4(const SubnetID& subnet_id,
const asiolink::IOAddress& address) const {
if (!address.isV4()) {
isc_throw(BadValue, "MySqlHostDataSource::getAll4(id, address): "
"wrong address type, address supplied is an IPv6 address");
}
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
uint32_t subnet = subnet_id;
memset(inbind, 0, sizeof(inbind));
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet);
inbind[0].is_unsigned = MLM_TRUE;
uint32_t addr4 = address.toUint32();
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&addr4);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection collection;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, inbind,
ctx->host_ipv4_exchange_, collection, false);
return (collection);
}
ConstHostPtr
MySqlHostDataSource::get6(const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len) const {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
ctx->host_ipv6_exchange_));
}
ConstHostPtr
MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
const uint8_t prefix_len) const {
if (!prefix.isV6()) {
isc_throw(BadValue, "MySqlHostDataSource::get6(prefix, prefix_len): "
"wrong address type, address supplied is an IPv4 address");
}
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
std::vector<uint8_t>addr6 = prefix.toBytes();
if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
isc_throw(DbOperationError, "get6() - prefix is not "
<< isc::asiolink::V6ADDRESS_LEN << " bytes long");
}
unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
inbind[0].buffer_type = MYSQL_TYPE_BLOB;
inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
inbind[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
inbind[0].length = &addr6_length;
uint8_t tmp = prefix_len;
inbind[1].buffer_type = MYSQL_TYPE_TINY;
inbind[1].buffer = reinterpret_cast<char*>(&tmp);
inbind[1].is_unsigned = MLM_TRUE;
ConstHostCollection collection;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PREFIX, inbind,
ctx->host_ipv6_exchange_, collection, true);
// Return single record if present, else clear the host.
ConstHostPtr result;
if (!collection.empty()) {
result = *collection.begin();
}
return (result);
}
ConstHostPtr
MySqlHostDataSource::get6(const SubnetID& subnet_id,
const asiolink::IOAddress& address) const {
if (!address.isV6()) {
isc_throw(BadValue, "MySqlHostDataSource::get6(id, address): "
"wrong address type, address supplied is an IPv4 address");
}
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
inbind[0].is_unsigned = MLM_TRUE;
std::vector<uint8_t>addr6 = address.toBytes();
if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
isc_throw(DbOperationError, "get6() - address is not "
<< isc::asiolink::V6ADDRESS_LEN << " bytes long");
}
unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
inbind[1].buffer_type = MYSQL_TYPE_BLOB;
inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
inbind[1].length = &addr6_length;
ConstHostCollection collection;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, inbind,
ctx->host_ipv6_exchange_, collection, true);
// Return single record if present, else clear the host.
ConstHostPtr result;
if (!collection.empty()) {
result = *collection.begin();
}
return (result);
}
ConstHostCollection
MySqlHostDataSource::getAll6(const SubnetID& subnet_id,
const asiolink::IOAddress& address) const {
if (!address.isV6()) {
isc_throw(BadValue, "MySqlHostDataSource::getAll6(id, address): "
"wrong address type, address supplied is an IPv4 address");
}
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
inbind[0].buffer_type = MYSQL_TYPE_LONG;
inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
inbind[0].is_unsigned = MLM_TRUE;
std::vector<uint8_t>addr6 = address.toBytes();
if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
isc_throw(DbOperationError, "getAll6() - address is not "
<< isc::asiolink::V6ADDRESS_LEN << " bytes long");
}
unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
inbind[1].buffer_type = MYSQL_TYPE_BLOB;
inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
inbind[1].length = &addr6_length;
ConstHostCollection collection;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, inbind,
ctx->host_ipv6_exchange_, collection, false);
return (collection);
}
ConstHostCollection
MySqlHostDataSource::getAll6(const IOAddress& address) const {
if (!address.isV6()) {
isc_throw(BadValue, "MySqlHostDataSource::getAll6(address): "
"wrong address type, address supplied is an IPv4 address");
}
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
2023-06-22 18:20:55 +02:00
std::vector<uint8_t>addr6 = address.toBytes();
if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
isc_throw(DbOperationError, "getAll6() - address is not "
<< isc::asiolink::V6ADDRESS_LEN << " bytes long");
}
2023-06-22 18:20:55 +02:00
unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
inbind[0].buffer_type = MYSQL_TYPE_BLOB;
2023-06-22 18:20:55 +02:00
inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
inbind[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
inbind[0].length = &addr6_length;
ConstHostCollection collection;
impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_ADDR6, inbind,
ctx->host_ipv6_exchange_, collection, false);
return (collection);
}
2023-04-05 14:48:26 +03:00
void
MySqlHostDataSource::update(HostPtr const& host) {
// Get a context.
MySqlHostContextAlloc const context(*impl_);
MySqlHostContextPtr ctx(context.ctx_);
// If operating in read-only mode, throw exception.
impl_->checkReadOnly(ctx);
// Initiate MySQL transaction as we will have to make multiple queries
// to update host information into multiple tables. If that fails on
// any stage, the transaction will be rolled back by the destructor of
// the MySqlTransaction class.
MySqlTransaction transaction(ctx->conn_);
// As much as having dedicated prepared statements for updating tables would be consistent with
// the implementation of other commands, it's difficult if not impossible to cover all cases for
// updating the host to exactly as is described in the command, which may involve inserts and
// deletes alongside updates. So let's delete and add. The delete cascades into all tables. The
// add explicitly adds into all tables.
BaseHostDataSource::update(host);
2023-04-05 14:48:26 +03:00
// Everything went fine, so explicitly commit the transaction.
transaction.commit();
}
// Miscellaneous database methods.
std::string
MySqlHostDataSource::getName() const {
std::string name = "";
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
try {
name = ctx->conn_.getParameter("name");
} catch (...) {
// Return an empty name
}
return (name);
}
std::string
MySqlHostDataSource::getDescription() const {
return (std::string("Host data source that stores host information"
"in MySQL database"));
}
2020-01-21 11:14:26 +02:00
std::pair<uint32_t, uint32_t>
MySqlHostDataSource::getVersion() const {
return(impl_->getVersion());
}
void
MySqlHostDataSource::commit() {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// If operating in read-only mode, throw exception.
impl_->checkReadOnly(ctx);
if (ctx->conn_.isTransactionStarted()) {
ctx->conn_.commit();
}
}
void
MySqlHostDataSource::rollback() {
// Get a context
MySqlHostContextAlloc get_context(*impl_);
MySqlHostContextPtr ctx = get_context.ctx_;
// If operating in read-only mode, throw exception.
impl_->checkReadOnly(ctx);
if (ctx->conn_.isTransactionStarted()) {
ctx->conn_.rollback();
}
}
bool
MySqlHostDataSource::setIPReservationsUnique(const bool unique) {
impl_->ip_reservations_unique_ = unique;
return (true);
}
bool
MySqlHostDataSource::isUnusable() {
return (impl_->unusable_);
}
2020-01-10 17:00:11 +02:00
} // namespace dhcp
} // namespace isc