From: Marcin Siodelski Date: Wed, 30 Sep 2020 13:16:30 +0000 (+0200) Subject: [#1428] Calls to fetch many hosts by id/address X-Git-Tag: Kea-1.9.1~143 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c7e9763582b72da958d91282c3cd8107ebb34487;p=thirdparty%2Fkea.git [#1428] Calls to fetch many hosts by id/address The new calls have been added to the host backends to retrieve multiple hosts by subnet_id and address. --- diff --git a/src/lib/dhcpsrv/base_host_data_source.h b/src/lib/dhcpsrv/base_host_data_source.h index 576b89b02a..7b37fde2c7 100644 --- a/src/lib/dhcpsrv/base_host_data_source.h +++ b/src/lib/dhcpsrv/base_host_data_source.h @@ -310,6 +310,33 @@ public: get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const = 0; + /// @brief Returns all hosts connected to the IPv4 subnet and having + /// a reservation for a specified address. + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv4 address within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv4 reservation. In that case, the number of hosts returned + /// by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv4 address in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv4 address is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv4 address is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const = 0; + /// @brief Returns a host connected to the IPv6 subnet. /// /// @param subnet_id Subnet identifier. @@ -346,6 +373,33 @@ public: get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const = 0; + /// @brief Returns all hosts connected to the IPv6 subnet and having + /// a reservation for a specified address or delegated prefix (lease). + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv6 lease within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv6 address or prefix. In that case, the number of hosts + /// returned by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv6 lease in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv6 lease is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv6 lease is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv6 address/prefix. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const = 0; + /// @brief Adds a new host to the collection. /// /// The implementations of this method should guard against duplicate diff --git a/src/lib/dhcpsrv/cfg_hosts.cc b/src/lib/dhcpsrv/cfg_hosts.cc index fe25c071f8..bba9b39362 100644 --- a/src/lib/dhcpsrv/cfg_hosts.cc +++ b/src/lib/dhcpsrv/cfg_hosts.cc @@ -701,6 +701,30 @@ CfgHosts::get4(const SubnetID& subnet_id, const IOAddress& address) const { return (ConstHostPtr()); } +ConstHostCollection +CfgHosts::getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4) + .arg(subnet_id).arg(address.toText()); + + ConstHostCollection hosts; + for (auto host : getAll4(address)) { + if (host->getIPv4SubnetID() == subnet_id) { + LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA, + HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST) + .arg(subnet_id) + .arg(address.toText()) + .arg(host->toText()); + hosts.push_back(host); + } + } + LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT) + .arg(subnet_id) + .arg(address.toText()) + .arg(hosts.size()); + + return (hosts); +} ConstHostPtr CfgHosts::get6(const SubnetID& subnet_id, @@ -744,6 +768,14 @@ CfgHosts::get6(const SubnetID& subnet_id, return (getHostInternal6(subnet_id, address)); } +ConstHostCollection +CfgHosts::getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + ConstHostCollection hosts; + getAllInternal6(subnet_id, address, hosts); + return (hosts); +} + template ReturnType CfgHosts::getHostInternal6(const SubnetID& subnet_id, diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h index 050df7c11f..d3f136d339 100644 --- a/src/lib/dhcpsrv/cfg_hosts.h +++ b/src/lib/dhcpsrv/cfg_hosts.h @@ -407,6 +407,33 @@ public: virtual ConstHostPtr get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv4 subnet and having + /// a reservation for a specified address. + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv4 address within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv4 reservation. In that case, the number of hosts returned + /// by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv4 address in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv4 address is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv4 address is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Returns a host connected to the IPv6 subnet. /// /// @param subnet_id Subnet identifier. @@ -474,6 +501,33 @@ public: virtual HostPtr get6(const SubnetID& subnet_id, const asiolink::IOAddress& address); + /// @brief Returns all hosts connected to the IPv6 subnet and having + /// a reservation for a specified address or delegated prefix (lease). + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv6 lease within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv6 address or prefix. In that case, the number of hosts + /// returned by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv6 lease in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv6 lease is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv6 lease is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv6 address/prefix. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Adds a new host to the collection. /// /// @param host Pointer to the new @c Host object being added. diff --git a/src/lib/dhcpsrv/cql_host_data_source.cc b/src/lib/dhcpsrv/cql_host_data_source.cc index dc83a23bcc..6b10e2fb27 100644 --- a/src/lib/dhcpsrv/cql_host_data_source.cc +++ b/src/lib/dhcpsrv/cql_host_data_source.cc @@ -3597,6 +3597,17 @@ CqlHostDataSource::get4(const SubnetID& subnet_id, return (impl_->get4(subnet_id, address)); } +ConstHostCollection +CqlHostDataSource::getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + ConstHostCollection hosts; + auto host = get4(subnet_id, address); + if (host) { + hosts.push_back(host); + } + return (hosts); +} + ConstHostPtr CqlHostDataSource::get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type, @@ -3623,6 +3634,17 @@ CqlHostDataSource::get6(const SubnetID& subnet_id, return (impl_->get6(subnet_id, address)); } +ConstHostCollection +CqlHostDataSource::getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + ConstHostCollection hosts; + auto host = get6(subnet_id, address); + if (host) { + hosts.push_back(host); + } + return (hosts); +} + ConstHostCollection CqlHostDataSource::getAllHosts() const { return (impl_->getAllHosts()); diff --git a/src/lib/dhcpsrv/cql_host_data_source.h b/src/lib/dhcpsrv/cql_host_data_source.h index 15fb02de3a..0b58300898 100644 --- a/src/lib/dhcpsrv/cql_host_data_source.h +++ b/src/lib/dhcpsrv/cql_host_data_source.h @@ -328,6 +328,21 @@ public: get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const override; + /// @brief Returns all hosts connected to the IPv4 subnet and having + /// a reservation for a specified address. + /// + /// This backend does not support a configuration in which multiple + /// reservations can be created for a single IPv4 address, so it + /// always returns 1 or 0 hosts. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const override; + /// @brief Returns a @ref Host connected to an IPv6 subnet. /// /// @param subnet_id subnet identifier to filter by @@ -366,6 +381,21 @@ public: get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const override; + /// @brief Returns all hosts connected to the IPv6 subnet and having + /// a reservation for a specified address or delegated prefix (lease). + /// + /// This backend does not support a configuration in which multiple + /// reservations can be created for a single IPv6 address, so it + /// always returns 1 or 0 hosts. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv6 address. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Returns a collection of all the hosts. /// /// This method may return multiple @ref Host objects. It is only used in diff --git a/src/lib/dhcpsrv/host_container.h b/src/lib/dhcpsrv/host_container.h index 113f4b8b13..95c06ad134 100644 --- a/src/lib/dhcpsrv/host_container.h +++ b/src/lib/dhcpsrv/host_container.h @@ -209,7 +209,7 @@ typedef boost::multi_index_container< // Second index is used to search by (subnet_id, address) pair. // This is - boost::multi_index::ordered_unique< + boost::multi_index::ordered_non_unique< /// This is a composite key. It uses two keys: subnet-id and /// IPv6 address reservation. diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc index ba043d0316..a2e1d875c0 100644 --- a/src/lib/dhcpsrv/host_mgr.cc +++ b/src/lib/dhcpsrv/host_mgr.cc @@ -395,6 +395,40 @@ HostMgr::get4(const SubnetID& subnet_id, return (ConstHostPtr()); } +ConstHostCollection +HostMgr::getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + auto hosts = getCfgHosts()->getAll4(subnet_id, address); + + LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, + HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4) + .arg(subnet_id) + .arg(address.toText()); + + if (cache_ptr_) { + auto cached = cache_ptr_->getAll4(subnet_id, address); + if (!cached.empty()) { + for (auto host : cached) { + if (!host->getNegative()) { + hosts.push_back(host); + } + } + return (hosts); + } + } + + for (auto source : alternate_sources_) { + if (source == cache_ptr_) { + continue; + } + auto alternate_hosts = source->getAll4(subnet_id, address); + for (auto host : alternate_hosts) { + cache(host); + hosts.push_back(host); + } + } + return (hosts); +} ConstHostPtr HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const { @@ -509,6 +543,41 @@ HostMgr::get6(const SubnetID& subnet_id, return (ConstHostPtr()); } +ConstHostCollection +HostMgr::getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + auto hosts = getCfgHosts()->getAll6(subnet_id, address); + + LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, + HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6) + .arg(subnet_id) + .arg(address.toText()); + + if (cache_ptr_) { + auto cached = cache_ptr_->getAll6(subnet_id, address); + if (!cached.empty()) { + for (auto host : cached) { + if (!host->getNegative()) { + hosts.push_back(host); + } + } + return (hosts); + } + } + + for (auto source : alternate_sources_) { + if (source == cache_ptr_) { + continue; + } + auto alternate_hosts = source->getAll6(subnet_id, address); + for (auto host : alternate_hosts) { + cache(host); + hosts.push_back(host); + } + } + return (hosts); +} + void HostMgr::add(const HostPtr& host) { if (alternate_sources_.empty()) { diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index 6db3581759..3c61cb228d 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -377,6 +377,33 @@ public: virtual ConstHostPtr get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv4 subnet and having + /// a reservation for a specified address. + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv4 address within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv4 reservation. In that case, the number of hosts returned + /// by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv4 address in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv4 address is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv4 address is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Returns any host connected to the IPv6 subnet. /// /// This method returns a host connected to the IPv6 subnet as described @@ -436,6 +463,33 @@ public: virtual ConstHostPtr get6(const SubnetID& subnet_id, const asiolink::IOAddress& addr) const; + /// @brief Returns all hosts connected to the IPv6 subnet and having + /// a reservation for a specified address or delegated prefix (lease). + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv6 lease within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv6 address or prefix. In that case, the number of hosts + /// returned by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv6 lease in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv6 lease is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv6 lease is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv6 address/prefix. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Adds a new host to the alternate data source. /// /// This method will throw an exception if no alternate data source is diff --git a/src/lib/dhcpsrv/hosts_messages.cc b/src/lib/dhcpsrv/hosts_messages.cc index 9be0525239..b0a103ff23 100644 --- a/src/lib/dhcpsrv/hosts_messages.cc +++ b/src/lib/dhcpsrv/hosts_messages.cc @@ -42,6 +42,9 @@ extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST = "HOSTS_CFG_ extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6 = "HOSTS_CFG_GET_ALL_SUBNET_ID6"; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT = "HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT"; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST = "HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST"; +extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4 = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4"; +extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT"; +extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST"; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6 = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6"; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT"; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST"; @@ -66,6 +69,8 @@ extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6 = " extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER"; extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST"; extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL"; +extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4 = "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4"; +extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6 = "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6"; extern const isc::log::MessageID HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED = "HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED"; } // namespace dhcp @@ -109,6 +114,9 @@ const char* values[] = { "HOSTS_CFG_GET_ALL_SUBNET_ID6", "get all hosts with reservations for IPv6 subnet %1", "HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT", "using IPv6 subnet %1, found %2 host(s)", "HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST", "using IPv6 subnet %1, found host: %2", + "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4", "get all hosts with reservations for subnet id %1 and IPv4 address %2", + "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT", "using IPv4 subnet %1 and IPv4 address %2, found %3 host(s)", + "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST", "using IPv4 subnet %1 and IPv4 address %2, found host: %3", "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6", "get all hosts with reservations for subnet id %1 and IPv6 address %2", "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT", "using subnet id %1 and address %2, found %3 host(s)", "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST", "using subnet id %1 and address %2, found host: %3", @@ -133,6 +141,8 @@ const char* values[] = { "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER", "get one host with IPv6 reservation for subnet id %1, identified by %2", "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST", "using subnet id %1 and identifier %2, found in %3 host: %4", "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL", "host not found using subnet id %1 and identifier %2", + "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4", "trying alternate sources for hosts using subnet id %1 and address %2", + "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6", "trying alternate sources for hosts using subnet id %1 and address %2", "HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED", "host data source %1 does not support the mode in which IP reservations are non-unique", NULL }; diff --git a/src/lib/dhcpsrv/hosts_messages.h b/src/lib/dhcpsrv/hosts_messages.h index a1d2b0712d..f056857350 100644 --- a/src/lib/dhcpsrv/hosts_messages.h +++ b/src/lib/dhcpsrv/hosts_messages.h @@ -43,6 +43,9 @@ extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST; +extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4; +extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT; +extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT; extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST; @@ -67,6 +70,8 @@ extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6; extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER; extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST; extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL; +extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4; +extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6; extern const isc::log::MessageID HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED; } // namespace dhcp diff --git a/src/lib/dhcpsrv/hosts_messages.mes b/src/lib/dhcpsrv/hosts_messages.mes index 8c1e8c0f8e..06a7844edd 100644 --- a/src/lib/dhcpsrv/hosts_messages.mes +++ b/src/lib/dhcpsrv/hosts_messages.mes @@ -146,6 +146,25 @@ description of the host found. This debug message is issued when starting to retrieve all hosts connected to the specific DHCPv4 subnet. The argument specifies subnet id. +% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4 get all hosts with reservations for subnet id %1 and IPv4 address %2 +This debug message is issued when starting to retrieve all hosts including +the reservation for the given IPv4 address within the given subnet. The +first argument specifies subnet identifier. The second argument specifies +the IPv4 address for which the reservation is to be returned. + +% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT using IPv4 subnet %1 and IPv4 address %2, found %3 host(s) +This debug message logs the number of hosts found having the reservation +for the specified IPv4 address within the specified subnet. The first +argument specifies the subnet identifier. The second argument specifies +the reserved IPv4 address. The third argument specifies the number of +hosts found. + +% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST using IPv4 subnet %1 and IPv4 address %2, found host: %3 +This debug message is issued when found host having the reservation for +the specified IPv4 address in the specified subnet. The first argument +specifies the subnet identifier. The second argument specifies the reserved +IPv4 address. The third argument specifies host details. + % HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT using IPv4 subnet %1, found %2 host(s) This debug message include the details of the host found using the DHCPv4 subnet id. The arguments specify subnet id and the number of hosts found @@ -284,6 +303,14 @@ identifier. This debug message is issued when no host was found using the specified subnet id and host identifier. +% HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4 trying alternate sources for hosts using subnet id %1 and address %2 +This debug message is issued when the Host Manager is starting to search +for hosts in alternate host data sources by subnet ID and IPv4 address. + +% HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6 trying alternate sources for hosts using subnet id %1 and address %2 +This debug message is issued when the Host Manager is starting to search +for hosts in alternate host data sources by subnet ID and IPv6 address. + % HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED host data source %1 does not support the mode in which IP reservations are non-unique This warning message is issued when an administrator attempted to configure the server to allow multiple host reservations for the same IP address or prefix. diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index e4c97998d7..0eb73c8ea4 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -3580,6 +3580,37 @@ MySqlHostDataSource::get4(const SubnetID& subnet_id, 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, @@ -3680,6 +3711,42 @@ MySqlHostDataSource::get6(const SubnetID& subnet_id, 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 diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index f6266d4c67..6823e5a939 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -307,6 +307,33 @@ public: virtual ConstHostPtr get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv4 subnet and having + /// a reservation for a specified address. + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv4 address within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv4 reservation. In that case, the number of hosts returned + /// by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv4 address in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv4 address is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv4 address is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Returns a host connected to the IPv6 subnet. /// /// @param subnet_id Subnet identifier. @@ -341,6 +368,33 @@ public: virtual ConstHostPtr get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv6 subnet and having + /// a reservation for a specified address or delegated prefix (lease). + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv6 lease within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv6 address or prefix. In that case, the number of hosts + /// returned by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv6 lease in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv6 lease is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv6 lease is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv6 address/prefix. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index 84a859b169..135d882cfd 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -2867,6 +2867,33 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id, return (result); } +ConstHostCollection +PgSqlHostDataSource::getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + // Get a context + PgSqlHostContextAlloc get_context(*impl_); + PgSqlHostContextPtr ctx = get_context.ctx_; + + if (!address.isV4()) { + isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - " + " wrong address type, address supplied is an IPv6 address"); + } + + // Set up the WHERE clause value + PsqlBindArrayPtr bind_array(new PsqlBindArray()); + + // Add the subnet id + bind_array->add(subnet_id); + + // Add the address + bind_array->add(address); + + ConstHostCollection collection; + impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, + bind_array, ctx->host_ipv4_exchange_, collection, false); + return (collection); +} + ConstHostPtr PgSqlHostDataSource::get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type, @@ -2949,6 +2976,34 @@ PgSqlHostDataSource::get6(const SubnetID& subnet_id, return (result); } +ConstHostCollection +PgSqlHostDataSource::getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + if (!address.isV6()) { + isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): " + "wrong address type, address supplied is an IPv4 address"); + } + + // Get a context + PgSqlHostContextAlloc get_context(*impl_); + PgSqlHostContextPtr ctx = get_context.ctx_; + + // Set up the WHERE clause value + PsqlBindArrayPtr bind_array(new PsqlBindArray()); + + // Add the subnet id + bind_array->add(subnet_id); + + // Add the prefix + bind_array->add(address); + + ConstHostCollection collection; + impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, + bind_array, ctx->host_ipv6_exchange_, collection, false); + return (collection); +} + + // Miscellaneous database methods. std::string diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h index 152d8b0f72..d9d8414c4f 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.h +++ b/src/lib/dhcpsrv/pgsql_host_data_source.h @@ -354,6 +354,33 @@ public: virtual ConstHostPtr get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv4 subnet and having + /// a reservation for a specified address. + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv4 address within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv4 reservation. In that case, the number of hosts returned + /// by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv4 address in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv4 address is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv4 address is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Returns a host connected to the IPv6 subnet. /// /// @param subnet_id Subnet identifier. @@ -388,6 +415,33 @@ public: virtual ConstHostPtr get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv6 subnet and having + /// a reservation for a specified address or delegated prefix (lease). + /// + /// In most cases it is desired that there is at most one reservation + /// for a given IPv6 lease within a subnet. In a default configuration, + /// the backend does not allow for inserting more than one host with + /// the same IPv6 address or prefix. In that case, the number of hosts + /// returned by this function is 0 or 1. + /// + /// If the backend is configured to allow multiple hosts with reservations + /// for the same IPv6 lease in the given subnet, this method can return + /// more than one host. + /// + /// The typical use case when a single IPv6 lease is reserved for multiple + /// hosts is when these hosts represent different interfaces of the same + /// machine and each interface comes with a different MAC address. In that + /// case, the same IPv6 lease is assigned regarless of which interface is + /// used by the DHCP client to communicate with the server. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv6 address/prefix. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Return backend type /// /// Returns the type of database as the string "postgresql". This is diff --git a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc index 0432587c21..5baf7dad26 100644 --- a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc @@ -951,7 +951,11 @@ TEST_F(CfgHostsTest, allow4AlreadyReserved) { SubnetID(1), SUBNET_ID_UNUSED, IOAddress("192.0.2.1"))); // Adding this should work because the HW address is different. - EXPECT_NO_THROW(cfg.add(host2)); + ASSERT_NO_THROW(cfg.add(host2)); + + ConstHostCollection returned; + ASSERT_NO_THROW(returned = cfg.getAll4(host1->getIPv4SubnetID(), IOAddress("192.0.2.1"))); + EXPECT_EQ(2, returned.size()); } // Checks that it's not possible for two hosts to have the same address @@ -1004,7 +1008,11 @@ TEST_F(CfgHostsTest, allow6AlreadyReserved) { IOAddress("2001:db8::1"))); // Adding this should work because the DUID is different. - EXPECT_NO_THROW(cfg.add(host2)); + ASSERT_NO_THROW(cfg.add(host2)); + + ConstHostCollection returned; + ASSERT_NO_THROW(returned = cfg.getAll6(host1->getIPv6SubnetID(), IOAddress("2001:db8::1"))); + EXPECT_EQ(2, returned.size()); } // Check that no error is reported when adding a host with subnet diff --git a/src/lib/dhcpsrv/tests/host_cache_unittest.cc b/src/lib/dhcpsrv/tests/host_cache_unittest.cc index 1a9e1e6cdf..34d0554b1d 100644 --- a/src/lib/dhcpsrv/tests/host_cache_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_cache_unittest.cc @@ -658,6 +658,11 @@ public: return (getOne()); } + ConstHostCollection + getAll4(const SubnetID&, const asiolink::IOAddress&) const { + return (getCollection()); + } + ConstHostPtr get6(const SubnetID&, const Host::IdentifierType&, const uint8_t*, const size_t) const { return (getOne()); @@ -671,6 +676,11 @@ public: return (getOne()); } + ConstHostCollection + getAll6(const SubnetID&, const IOAddress&) const { + return (getCollection()); + } + void add(const HostPtr&) { } diff --git a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc index adbbd07ec1..5facee2ace 100644 --- a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc @@ -247,6 +247,33 @@ protected: void testGet6ByPrefix(BaseHostDataSource& data_source1, BaseHostDataSource& data_source2); + /// @brief This test verifies that HostMgr returns all reservations for the + /// specified DHCPv4 subnet and IPv4 address. + /// + /// If reservations are added to different host data sources, it is expected + /// that the @c HostMgr will retrieve reservations from both of them. + /// + /// @param data_source1 Host data source to which first reservation is + /// inserted. + /// @param data_source2 Host data source to which second reservation is + /// inserted. + void testGetAll4BySubnetIP(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2); + + /// @brief This test verifies that HostMgr returns all reservations for the + /// specified DHCPv6 subnet and IPv6 address. + /// + /// If reservations are added to different host data sources, it is expected + /// that the @c HostMgr will retrieve reservations from both of them. + /// + /// @param data_source1 Host data source to which first reservation is + /// inserted. + /// @param data_source2 Host data source to which second reservation is + /// inserted. + void testGetAll6BySubnetIP(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2); + + /// @brief HW addresses to be used by the tests. std::vector hwaddrs_; /// @brief DUIDs to be used by the tests. @@ -1131,6 +1158,80 @@ HostMgrTest::testGet6ByPrefix(BaseHostDataSource& data_source1, EXPECT_FALSE(host); } +void +HostMgrTest::testGetAll4BySubnetIP(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2) { + // Set the mode of operation with multiple reservations for the same + // IP address. + ASSERT_TRUE(HostMgr::instance().setIPReservationUnique(false)); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationUnique(false); + + // Initially, no reservations should be present. + ConstHostCollection hosts = HostMgr::instance().getAll4(SubnetID(1), + IOAddress("192.0.2.5")); + ASSERT_TRUE(hosts.empty()); + + // Add two reservations for the same subnet and IP address. + addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5")); + addHost4(data_source2, hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.5")); + + CfgMgr::instance().commit(); + + // If there non-matching subnet is specified, nothing should be returned. + hosts = HostMgr::instance().getAll4(SubnetID(100), IOAddress("192.0.2.5")); + ASSERT_TRUE(hosts.empty()); + + // For the correct subnet, there should be two reservations. + hosts = HostMgr::instance().getAll4(SubnetID(1), IOAddress("192.0.2.5")); + ASSERT_EQ(2, hosts.size()); + + // Make sure that subnet is correct. + EXPECT_EQ(1, hosts[0]->getIPv4SubnetID()); + EXPECT_EQ(1, hosts[1]->getIPv4SubnetID()); + + // Make sure that two hosts were returned. + EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText()); + EXPECT_EQ("192.0.2.5", hosts[1]->getIPv4Reservation().toText()); +} + +void +HostMgrTest::testGetAll6BySubnetIP(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2) { + // Set the mode of operation with multiple reservations for the same + // IP address. + ASSERT_TRUE(HostMgr::instance().setIPReservationUnique(false)); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationUnique(false); + + // Initially, no reservations should be present. + ConstHostCollection hosts = HostMgr::instance().getAll6(SubnetID(1), + IOAddress("2001:db8:1::5")); + ASSERT_TRUE(hosts.empty()); + + // Add two reservations for the same subnet. + addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5")); + addHost6(data_source2, duids_[1], SubnetID(1), IOAddress("2001:db8:1::5")); + + CfgMgr::instance().commit(); + + // If there non-matching subnet is specified, nothing should be returned. + hosts = HostMgr::instance().getAll6(SubnetID(100), IOAddress("2001:db8:1::5")); + ASSERT_TRUE(hosts.empty()); + + // For the correct subnet, there should be two reservations. + hosts = HostMgr::instance().getAll6(SubnetID(1), IOAddress("2001:db8:1::5")); + ASSERT_EQ(2, hosts.size()); + + // Make sure that subnet is correct. + EXPECT_EQ(1, hosts[0]->getIPv6SubnetID()); + EXPECT_EQ(1, hosts[1]->getIPv6SubnetID()); + + // Make sure that two different hosts were returned. + EXPECT_TRUE(hosts[0]->hasReservation( + IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5")))); + EXPECT_TRUE(hosts[1]->hasReservation( + IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5")))); +} + // This test verifies that HostMgr returns all reservations for the // specified HW address. The reservations are defined in the server's // configuration. @@ -1145,6 +1246,20 @@ TEST_F(HostMgrTest, getAll4BySubnet) { testGetAll4BySubnet(*getCfgHosts(), *getCfgHosts()); } +// This test verifies that HostMgr returns all reservations for the specified +// IPv4 subnet and reserved address. The reservations are specified in the +// server's configuration. +TEST_F(HostMgrTest, getAll4BySubnetIP) { + testGetAll4BySubnetIP(*getCfgHosts(), *getCfgHosts()); +} + +// This test verifies that HostMgr returns all reservations for the specified +// IPv6 subnet and reserved address. The reservations are specified in the +// server's configuration. +TEST_F(HostMgrTest, getAll6BySubnetIP) { + testGetAll6BySubnetIP(*getCfgHosts(), *getCfgHosts()); +} + // This test verifies that HostMgr returns all reservations for the // specified DHCPv6 subnet. The reservations are defined in the server's // configuration. @@ -1435,6 +1550,18 @@ TEST_F(MySQLHostMgrTest, getAll6BySubnet) { testGetAll6BySubnet(*getCfgHosts(), HostMgr::instance()); } +// This test verifies that HostMgr returns all reservations for the specified +// IPv4 subnet and reserved address. +TEST_F(MySQLHostMgrTest, getAll4BySubnetIP) { + testGetAll4BySubnetIP(*getCfgHosts(), *getCfgHosts()); +} + +// This test verifies that HostMgr returns all reservations for the specified +// IPv6 subnet and reserved address. +TEST_F(MySQLHostMgrTest, getAll6BySubnetIP) { + testGetAll6BySubnetIP(*getCfgHosts(), *getCfgHosts()); +} + // This test verifies that reservations for a particular hostname can be // retrieved from the configuration file and a database simultaneously. TEST_F(MySQLHostMgrTest, getAllbyHostname) { @@ -1600,6 +1727,18 @@ TEST_F(PostgreSQLHostMgrTest, getAll6BySubnet) { testGetAll6BySubnet(*getCfgHosts(), HostMgr::instance()); } +// This test verifies that HostMgr returns all reservations for the specified +// IPv4 subnet and reserved address. +TEST_F(PostgreSQLHostMgrTest, getAll4BySubnetIP) { + testGetAll4BySubnetIP(*getCfgHosts(), *getCfgHosts()); +} + +// This test verifies that HostMgr returns all reservations for the specified +// IPv6 subnet and reserved address. +TEST_F(PostgreSQLHostMgrTest, getAll6BySubnetIP) { + testGetAll6BySubnetIP(*getCfgHosts(), *getCfgHosts()); +} + // This test verifies that reservations for a particular hostname can be // retrieved from the configuration file and a database simultaneously. TEST_F(PostgreSQLHostMgrTest, getAllbyHostname) { diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc index 8e34bc1506..9df193692a 100644 --- a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc @@ -1832,21 +1832,33 @@ GenericHostDataSourceTest::testAllowDuplicateIPv6() { ASSERT_TRUE(hdsptr_->setIPReservationUnique(false)); // Create a host reservations. - HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true); + HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true, true); + auto host_id = host->getHostId(); + auto subnet_id = host->getIPv6SubnetID(); // Add this reservation once. ASSERT_NO_THROW(hdsptr_->add(host)); // Then try to add it again, it should throw an exception because the // HWADDR is the same. + host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true, false); + host->setHostId(++host_id); + host->setIPv6SubnetID(subnet_id); ASSERT_THROW(hdsptr_->add(host), DuplicateEntry); // This time use a different host identifier and try again. // This update should succeed because we permitted to create // multiple IP reservations for the same IP address but different // identifier. - ASSERT_NO_THROW(host->setIdentifier("01:02:03:04:05:06", "hw-address")); + host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true, true); + host->setHostId(++host_id); + host->setIPv6SubnetID(subnet_id); ASSERT_NO_THROW(hdsptr_->add(host)); + + ConstHostCollection returned; + ASSERT_NO_THROW(returned = hdsptr_->getAll6(host->getIPv6SubnetID(), IOAddress("2001:db8::1"))); + EXPECT_EQ(2, returned.size()); + EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText()); } void @@ -1894,21 +1906,33 @@ GenericHostDataSourceTest::testAllowDuplicateIPv4() { ASSERT_TRUE(hdsptr_->setIPReservationUnique(false)); // Create a host reservations. - HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID); + HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, true); + auto host_id = host->getHostId(); + auto subnet_id = host->getIPv4SubnetID(); // Add this reservation once. ASSERT_NO_THROW(hdsptr_->add(host)); // Then try to add it again, it should throw an exception because the // DUID is the same. + host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, false); + host->setHostId(++host_id); + host->setIPv4SubnetID(subnet_id); ASSERT_THROW(hdsptr_->add(host), DuplicateEntry); // This time use a different host identifier and try again. // This update should succeed because we permitted to create // multiple IP reservations for the same IP address but different // identifier. - ASSERT_NO_THROW(host->setIdentifier("01:02:03:04:05:06", "hw-address")); + host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, true); + host->setHostId(++host_id); + host->setIPv4SubnetID(subnet_id); ASSERT_NO_THROW(hdsptr_->add(host)); + + ConstHostCollection returned; + ASSERT_NO_THROW(returned = hdsptr_->getAll4(host->getIPv4SubnetID(), IOAddress("192.0.2.1"))); + EXPECT_EQ(2, returned.size()); + EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText()); } void diff --git a/src/lib/dhcpsrv/testutils/host_data_source_utils.cc b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc index 1c64472861..00cd1df0bb 100644 --- a/src/lib/dhcpsrv/testutils/host_data_source_utils.cc +++ b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc @@ -58,12 +58,13 @@ HostDataSourceUtils::generateIdentifier(const bool new_identifier) { HostPtr HostDataSourceUtils::initializeHost4(const std::string& address, - const Host::IdentifierType& id) { + const Host::IdentifierType& id, + const bool new_identifier) { std::vector ident; if (id == Host::IDENT_HWADDR) { - ident = generateHWAddr(); + ident = generateHWAddr(new_identifier); } else { - ident = generateIdentifier(); + ident = generateIdentifier(new_identifier); } // Let's create ever increasing subnet-ids. Let's keep those different, diff --git a/src/lib/dhcpsrv/testutils/host_data_source_utils.h b/src/lib/dhcpsrv/testutils/host_data_source_utils.h index c10f2de57e..ff3fabd39c 100644 --- a/src/lib/dhcpsrv/testutils/host_data_source_utils.h +++ b/src/lib/dhcpsrv/testutils/host_data_source_utils.h @@ -20,14 +20,17 @@ namespace test { /// Intended to be used in tests and benchmarks. class HostDataSourceUtils { public: - /// @brief Creates a host reservation for specified IPv4 address. + /// @brief Creates a host reservation for specified IPv4 address. /// /// @param address IPv4 address to be set /// @param id Identifier type. + /// @param new_identifier Boolean value indicating if new host + /// identifier should be generated or the same as previously. /// /// @return generated Host object static isc::dhcp::HostPtr initializeHost4(const std::string& address, - const Host::IdentifierType& id); + const Host::IdentifierType& id, + const bool new_identifier = true); /// @brief Creates a host reservation for specified IPv6 address. /// diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc index 736047a118..fdfc6f73ff 100644 --- a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc +++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc @@ -239,6 +239,17 @@ MemHostDataSource::get4(const SubnetID& subnet_id, return (ConstHostPtr()); } +ConstHostCollection +MemHostDataSource::getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + ConstHostCollection hosts; + auto host = get4(subnet_id, address); + if (host) { + hosts.push_back(host); + } + return (hosts); +} + ConstHostPtr MemHostDataSource::get6(const asiolink::IOAddress& /*prefix*/, const uint8_t /*prefix_len*/) const { @@ -271,6 +282,17 @@ MemHostDataSource::get6(const SubnetID& subnet_id, return (ConstHostPtr()); } +ConstHostCollection +MemHostDataSource::getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const { + ConstHostCollection hosts; + auto host = get6(subnet_id, address); + if (host) { + hosts.push_back(host); + } + return (hosts); +} + void MemHostDataSource::add(const HostPtr& host) { host->setHostId(++next_host_id_); diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.h b/src/lib/dhcpsrv/testutils/memory_host_data_source.h index 7ea08e0b08..a97fd4faac 100644 --- a/src/lib/dhcpsrv/testutils/memory_host_data_source.h +++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.h @@ -158,6 +158,17 @@ public: get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv4 subnet and having + /// a reservation for a specified address. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Returns a host connected to the IPv6 subnet. /// /// @param subnet_id Subnet identifier. @@ -192,6 +203,17 @@ public: virtual ConstHostPtr get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Returns all hosts connected to the IPv6 subnet and having + /// a reservation for a specified address or delegated prefix (lease). + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv6 address/prefix. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll6(const SubnetID& subnet_id, + const asiolink::IOAddress& address) const; + /// @brief Adds a new host to the collection. /// /// @param host Pointer to the new @c Host object being added.