// Copyright (C) 2015-2022 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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. /// /// 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(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 /// 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 /// 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: /// @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), dhcp6_subnet_id_(SUBNET_ID_UNUSED), ipv4_address_(0), hostname_length_(0), dhcp4_client_classes_length_(0), dhcp6_client_classes_length_(0), 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), 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_)); 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. columns_[0] = "host_id"; columns_[1] = "dhcp_identifier"; columns_[2] = "dhcp_identifier_type"; columns_[3] = "dhcp4_subnet_id"; columns_[4] = "dhcp6_subnet_id"; columns_[5] = "ipv4_address"; columns_[6] = "hostname"; columns_[7] = "dhcp4_client_classes"; columns_[8] = "dhcp6_client_classes"; columns_[9] = "user_context"; columns_[10] = "dhcp4_next_server"; columns_[11] = "dhcp4_server_hostname"; columns_[12] = "dhcp4_boot_file_name"; columns_[13] = "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::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& bind, std::vector& error) { for (size_t i = 0; i < error.size(); ++i) { error[i] = MLM_FALSE; bind[i].error = reinterpret_cast(&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& error, const std::vector& 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 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(&host_id_); bind_[0].is_unsigned = MLM_TRUE; // dhcp_identifier : VARBINARY(128) NOT NULL dhcp_identifier_length_ = host->getIdentifier().size(); memcpy(static_cast(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 dhcp_identifier_type_ = static_cast(host->getIdentifierType()); bind_[2].buffer_type = MYSQL_TYPE_TINY; bind_[2].buffer = reinterpret_cast(&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(); 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(&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(); 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(&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(&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(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(); // 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(); bind_[10].buffer_type = MYSQL_TYPE_LONG; bind_[10].buffer = reinterpret_cast(&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 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); bind_[11].buffer = dhcp4_server_hostname_; bind_[11].buffer_length = server_hostname.length(); // dhcp4_boot_file_name 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); 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); 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 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 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. // 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(&host_id_); bind_[0].is_unsigned = MLM_TRUE; // dhcp_identifier : VARBINARY(128) NOT NULL dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_); bind_[1].buffer_type = MYSQL_TYPE_BLOB; bind_[1].buffer = reinterpret_cast(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(&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(&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(&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(&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(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(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(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_; // 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(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; bind_[10].buffer_type = MYSQL_TYPE_LONG; bind_[10].buffer = reinterpret_cast(&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_); bind_[11].buffer_type = MYSQL_TYPE_STRING; bind_[11].buffer = reinterpret_cast(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_); bind_[12].buffer_type = MYSQL_TYPE_STRING; bind_[12].buffer = reinterpret_cast(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(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() { // 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: " << static_cast(dhcp_identifier_type_)); } // Set the dhcp identifier type in a variable of the appropriate // data type. Host::IdentifierType type = static_cast(dhcp_identifier_type_); // Set DHCPv4 subnet ID to the value returned. If NULL returned, // set to 0. SubnetID ipv4_subnet_id(SUBNET_ID_UNUSED); if (dhcp4_subnet_id_null_ == MLM_FALSE) { ipv4_subnet_id = static_cast(dhcp4_subnet_id_); } // Set DHCPv6 subnet ID to the value returned. If NULL returned, // set to 0. SubnetID ipv6_subnet_id(SUBNET_ID_UNUSED); if (dhcp6_subnet_id_null_ == MLM_FALSE) { ipv6_subnet_id = static_cast(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_); } // 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_); // 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 << "' is not a JSON map"); } 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. /// /// 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 bind_; /// Column names. std::vector columns_; /// Error array. std::vector 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_[DUID::MAX_DUID_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_; /// @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_; /// @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: /// @brief Number of columns holding DHCPv4 or DHCPv6 option information. static const size_t OPTION_COLUMNS = 7; /// @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), space_length_(0), persistent_(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), user_context_index_(start_column_ + 6), 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_); } // If empty or null space provided, use a default top level space. if (space.empty()) { space = (universe_ == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE); } // 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_); } OptionPtr option; if (!def) { // If no definition found, we use generic option type. OptionBuffer buf(value_, value_ + value_length_); option.reset(new Option(universe_, code_, buf.begin(), buf.end())); } 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_); option = def->optionFactory(universe_, code_, buf.begin(), buf.end()); } else { // Spit the value specified in comma separated values // format. std::vector split_vec; boost::split(split_vec, formatted_value, boost::is_any_of(",")); option = def->optionFactory(universe_, code_, split_vec); } } OptionDescriptor desc(option, persistent_, 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& 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"; columns[user_context_index_] = "user_context"; } /// @brief Initialize binding table fields for options. /// /// Resets most_recent_option_id_ to 0 and other exchange members to /// default values. /// /// @param [out] bind Binding table. void setBindFields(std::vector& bind) { // This method is called just before making a new query, so we // reset the most_recent_option_id_ and other exchange members to // start over with options processing. most_recent_option_id_ = 0; option_id_ = 0; code_ = 0; persistent_ = 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(&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(&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(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(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(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(&persistent_); bind[persistent_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(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_; /// @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_; //@} /// @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 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(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 createBindForReceive() { // The following call sets bind_ values between 0 and 8. static_cast(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 /// 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(reserv_type_) << ". Only 0 or 2 are allowed."); } ipv6_address_buffer_[ipv6_address_buffer_len_] = '\0'; std::string address = ipv6_address_buffer_; IPv6Resrv r(type, IOAddress(address), 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(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 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(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(&reservation_id_); bind_[reservation_id_index_].is_unsigned = MLM_TRUE; // IPv6 address/prefix VARCHAR(39) ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1; bind_[address_index_].buffer_type = MYSQL_TYPE_STRING; bind_[address_index_].buffer = 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(&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(&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(&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. char ipv6_address_buffer_[ADDRESS6_TEXT_MAX_LEN + 1]; /// @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. /// /// 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), address_("::"), address_len_(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"; 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 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 { // address VARCHAR(39) address_ = resv.getPrefix().toText(); address_len_ = address_.length(); bind_[0].buffer_type = MYSQL_TYPE_BLOB; bind_[0].buffer = reinterpret_cast (const_cast(address_.c_str())); bind_[0].buffer_length = address_len_; bind_[0].length = &address_len_; // prefix_len tinyint prefix_len_ = resv.getPrefixLen(); bind_[1].buffer_type = MYSQL_TYPE_TINY; bind_[1].buffer = reinterpret_cast(&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(&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(&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(&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 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_; /// @brief Address (or prefix). std::string address_; /// @brief Length of the textual address representation. unsigned long address_len_; /// @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 This class is used for inserting options into a database. /// /// This class supports inserting both DHCPv4 and DHCPv6 options. class MySqlOptionExchange { private: /// @brief Number of columns in the tables holding options. static const size_t OPTION_COLUMNS = 10; public: /// @brief Constructor. MySqlOptionExchange() : type_(0), value_len_(0), formatted_value_len_(0), space_(), space_len_(0), persistent_(false), user_context_(), user_context_len_(0), subnet_id_(SUBNET_ID_UNUSED), host_id_(0), option_() { BOOST_STATIC_ASSERT(9 < OPTION_COLUMNS); } /// @brief Creates binding array to insert option data into database. /// /// @return Vector of MYSQL_BIND object representing an option. std::vector createBindForSend(const OptionDescriptor& opt_desc, const std::string& opt_space, const Optional& 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 type_ = option_->getType(); bind_[1].buffer_type = MYSQL_TYPE_SHORT; bind_[1].buffer = reinterpret_cast(&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(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(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(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(&persistent_); bind_[5].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(); bind_[6].buffer_type = MYSQL_TYPE_STRING; bind_[6].buffer = const_cast(user_context_.c_str()); bind_[6].buffer_length = user_context_len_; bind_[6].length = &user_context_len_; } else { bind_[6].buffer_type = MYSQL_TYPE_NULL; } // dhcp4_subnet_id: INT UNSIGNED NULL if (!subnet_id.unspecified()) { subnet_id_ = subnet_id; bind_[7].buffer_type = MYSQL_TYPE_LONG; bind_[7].buffer = reinterpret_cast(subnet_id_); bind_[7].is_unsigned = MLM_TRUE; } else { bind_[7].buffer_type = MYSQL_TYPE_NULL; } // host_id: INT UNSIGNED NOT NULL host_id_ = host_id; bind_[8].buffer_type = MYSQL_TYPE_LONG; bind_[8].buffer = reinterpret_cast(&host_id_); bind_[8].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(&bind_[0], &bind_[OPTION_COLUMNS])); } private: /// @brief Option type. uint16_t type_; /// @brief Option value as binary. std::vector 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_; /// @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]; }; } // namespace namespace isc { namespace dhcp { /// @brief MySQL Host Context /// /// This class stores the thread context for the manager pool. /// 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. /// @param io_service_accessor The IOService accessor function. /// @param db_reconnect_callback The connection recovery callback. MySqlHostContext(const DatabaseConnection::ParameterMap& parameters, IOServiceAccessorPtr io_service_accessor, 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".) /// @brief Pointer to an object representing an exchange which can /// be used to retrieve hosts and DHCPv4 options. boost::shared_ptr 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 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 host_ipv46_exchange_; /// @brief Pointer to an object representing an exchange which can /// be used to insert new IPv6 reservation. boost::shared_ptr 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 host_option_exchange_; /// @brief MySQL connection 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. /// The manager will use this class to handle available contexts. /// 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 pool_; /// @brief The mutex to protect pool access. std::mutex mutex_; }; /// @brief Type of pointers to context pools. typedef boost::shared_ptr 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. /// 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 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. ~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. /// /// 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. /// /// @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 getVersion() const; /// @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, std::vector& 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. /// /// @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& 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 /// 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 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. /// @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 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 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 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, " "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, " "o4.persistent, o4.user_context, " "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, " "o6.persistent, 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, " "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.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, " "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.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 = ? " "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, " "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.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, " "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.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, " "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.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 = " "( 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 // 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, " "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.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 r.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, " "o.persistent, 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, " "o.persistent, 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, " "o4.persistent, o4.user_context, " "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, " "o6.persistent, 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, " "o.persistent, 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, " "o.persistent, 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, " "o.persistent, o.user_context " "FROM ( SELECT * FROM hosts AS h " "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, " "o.persistent, o.user_context, " "r.reservation_id, r.address, r.prefix_len, r.type, " "r.dhcp6_iaid " "FROM ( SELECT * FROM hosts AS h " "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"}, // 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, " "o.persistent, o.user_context " "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, " "o.persistent, o.user_context, " "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, "INSERT INTO ipv6_reservations(address, prefix_len, type, " "dhcp6_iaid, host_id) " "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. {MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION, "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, " "persistent, 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. {MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION, "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, " "persistent, 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 = ?"} } }; } // namespace // MySqlHostContext Constructor MySqlHostContext::MySqlHostContext(const DatabaseConnection::ParameterMap& parameters, IOServiceAccessorPtr io_service_accessor, db::DbCallback db_reconnect_callback) : 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()) { // multi-threaded { // we need to protect the whole pool_ operation, hence extra scope {} lock_guard 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 { // single-threaded if (mgr_.pool_->pool_.empty()) { isc_throw(Unexpected, "No available MySQL host context?!"); } ctx_ = mgr_.pool_->pool_.back(); } } MySqlHostDataSource::MySqlHostContextAlloc::~MySqlHostContextAlloc() { if (MultiThreadingMgr::instance().getMode()) { // multi-threaded lock_guard 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_("") { // Create unique timer name per instance. timer_name_ = "MySqlHostMgr["; timer_name_ += boost::lexical_cast(reinterpret_cast(this)); timer_name_ += "]DbReconnectTimer"; // Validate the schema version first. std::pair code_version(MYSQL_SCHEMA_VERSION_MAJOR, MYSQL_SCHEMA_VERSION_MINOR); std::pair db_version = getVersion(); if (code_version != db_version) { 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_, 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. 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()); // Create ReconnectCtl for this connection. ctx->conn_.makeReconnectCtl(timer_name_); return (ctx); } MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() { } bool MySqlHostDataSourceImpl::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) { MultiThreadingCriticalSection cs; // 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 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) { 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); } // 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. 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); } // Invoke application layer connection failed callback. DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl); return (false); } 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 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, std::vector& bind) { // Bind the parameters to the statement int status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], &bind[0]); checkError(ctx, status, stindex, "unable to bind parameters"); // Execute the statement status = MysqlExecuteStatement(ctx->conn_.statements_[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_.statements_[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_.statements_[stindex], &bind[0]); checkError(ctx, status, stindex, "unable to bind parameters"); // Execute the statement status = MysqlExecuteStatement(ctx->conn_.statements_[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_.statements_[stindex]); return (numrows != 0); } void MySqlHostDataSourceImpl::addResv(MySqlHostContextPtr& ctx, const IPv6Resrv& resv, const HostID& id) { std::vector 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& subnet_id, const HostID& id) { std::vector 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 option_spaces = options_cfg->getOptionSpaceNames(); std::list 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->getAll(*space); if (options && !options->empty()) { for (auto opt = options->begin(); opt != options->end(); ++opt) { addOption(ctx, stindex, *opt, *space, Optional(), 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 exchange, ConstHostCollection& result, bool single) const { // Bind the selection parameters to the statement int status = mysql_stmt_bind_param(ctx->conn_.statements_[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 outbind = exchange->createBindForReceive(); status = mysql_stmt_bind_result(ctx->conn_.statements_[stindex], &outbind[0]); checkError(ctx, status, stindex, "unable to bind SELECT clause parameters"); // Execute the statement status = MysqlExecuteStatement(ctx->conn_.statements_[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_.statements_[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_.statements_[stindex]); while ((status = mysql_stmt_fetch(ctx->conn_.statements_[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 <" << 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 exchange) const { // Set up the WHERE clause value MYSQL_BIND inbind[3]; memset(inbind, 0, sizeof(inbind)); uint32_t subnet_buffer = static_cast(subnet_id); inbind[0].buffer_type = MYSQL_TYPE_LONG; inbind[0].buffer = reinterpret_cast(&subnet_buffer); inbind[0].is_unsigned = MLM_TRUE; // Identifier value. std::vector 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(identifier_type); inbind[1].buffer_type = MYSQL_TYPE_TINY; inbind[1].buffer = reinterpret_cast(&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) : impl_(new MySqlHostDataSourceImpl(parameters)) { } MySqlHostDataSource::~MySqlHostDataSource() { } DatabaseConnection::ParameterMap MySqlHostDataSource::getParameters() const { 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 bind = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip); // ... 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) { impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION, cfg_option4, host_id); } // Insert DHCPv6 options. ConstCfgOptionPtr cfg_option6 = host->getCfgOption6(); if (cfg_option6) { impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION, cfg_option6, host_id); } // 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); } } // 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(&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(&addr4); inbind[1].is_unsigned = MLM_TRUE; return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind)); } // v6 std::string addr_str = addr.toText(); unsigned long addr_len = addr_str.size(); inbind[1].buffer_type = MYSQL_TYPE_BLOB; inbind[1].buffer = reinterpret_cast (const_cast(addr_str.c_str())); inbind[1].length = &addr_len; inbind[1].buffer_length = addr_len; 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(subnet_id); inbind[0].buffer_type = MYSQL_TYPE_LONG; inbind[0].buffer = reinterpret_cast(&subnet); inbind[0].is_unsigned = MLM_TRUE; // identifier type char identifier_type_copy = static_cast(identifier_type); inbind[1].buffer_type = MYSQL_TYPE_TINY; inbind[1].buffer = reinterpret_cast(&identifier_type_copy); inbind[1].is_unsigned = MLM_TRUE; // identifier value std::vector 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(subnet_id); inbind[0].buffer_type = MYSQL_TYPE_LONG; inbind[0].buffer = reinterpret_cast(&subnet); inbind[0].is_unsigned = MLM_TRUE; // identifier type char identifier_type_copy = static_cast(identifier_type); inbind[1].buffer_type = MYSQL_TYPE_TINY; inbind[1].buffer = reinterpret_cast(&identifier_type_copy); inbind[1].is_unsigned = MLM_TRUE; // identifier value std::vector 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(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 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(&subnet); inbind[0].is_unsigned = MLM_TRUE; ConstHostCollection result; impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4, inbind, 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(&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(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(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(&subnet); inbind[1].is_unsigned = MLM_TRUE; ConstHostCollection result; impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4, inbind, 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(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(&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(&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(&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(&page_size_data); inbind[2].is_unsigned = MLM_TRUE; ConstHostCollection result; impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE, inbind, 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(&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(&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(&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); } 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(&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(&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(&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(&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(&addr4); inbind[0].is_unsigned = MLM_TRUE; ConstHostCollection result; impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_ADDR, inbind, 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, 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"); } // 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(&subnet); inbind[0].is_unsigned = MLM_TRUE; uint32_t addr4 = address.toUint32(); inbind[1].buffer_type = MYSQL_TYPE_LONG; inbind[1].buffer = reinterpret_cast(&addr4); inbind[1].is_unsigned = MLM_TRUE; ConstHostCollection collection; impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, inbind, 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(&subnet); inbind[0].is_unsigned = MLM_TRUE; uint32_t addr4 = address.toUint32(); inbind[1].buffer_type = MYSQL_TYPE_LONG; inbind[1].buffer = reinterpret_cast(&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::string addr6 = prefix.toText(); unsigned long addr6_length = addr6.size(); inbind[0].buffer_type = MYSQL_TYPE_BLOB; inbind[0].buffer = reinterpret_cast (const_cast(addr6.c_str())); inbind[0].length = &addr6_length; inbind[0].buffer_length = addr6_length; uint8_t tmp = prefix_len; inbind[1].buffer_type = MYSQL_TYPE_TINY; inbind[1].buffer = reinterpret_cast(&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(subnet_id); inbind[0].buffer_type = MYSQL_TYPE_LONG; inbind[0].buffer = reinterpret_cast(&subnet_buffer); inbind[0].is_unsigned = MLM_TRUE; std::string addr6 = address.toText(); unsigned long addr6_length = addr6.size(); inbind[1].buffer_type = MYSQL_TYPE_BLOB; inbind[1].buffer = reinterpret_cast (const_cast(addr6.c_str())); inbind[1].length = &addr6_length; inbind[1].buffer_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(subnet_id); inbind[0].buffer_type = MYSQL_TYPE_LONG; inbind[0].buffer = reinterpret_cast(&subnet_buffer); inbind[0].is_unsigned = MLM_TRUE; std::string addr6 = address.toText(); unsigned long addr6_length = addr6.size(); inbind[1].buffer_type = MYSQL_TYPE_BLOB; inbind[1].buffer = reinterpret_cast (const_cast(addr6.c_str())); inbind[1].length = &addr6_length; inbind[1].buffer_length = addr6_length; ConstHostCollection collection; impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, inbind, ctx->host_ipv6_exchange_, collection, false); return (collection); } // 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")); } std::pair 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_); } } // namespace dhcp } // namespace isc