From: Marcin Siodelski Date: Thu, 31 Aug 2017 08:16:11 +0000 (+0200) Subject: [5305] Introduced shared networks holding subnets. X-Git-Tag: trac5073a_base~11^2~17 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=090f6c00f29fc30c2c2ae77bfe71ab1bb31d9bc6;p=thirdparty%2Fkea.git [5305] Introduced shared networks holding subnets. --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 82af75510e..7452255123 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -88,6 +88,7 @@ libkea_dhcpsrv_la_SOURCES = libkea_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h libkea_dhcpsrv_la_SOURCES += alloc_engine_log.cc alloc_engine_log.h +libkea_dhcpsrv_la_SOURCES += assignable_network.h libkea_dhcpsrv_la_SOURCES += base_host_data_source.h libkea_dhcpsrv_la_SOURCES += callout_handle_store.h libkea_dhcpsrv_la_SOURCES += cfg_4o6.cc cfg_4o6.h @@ -101,6 +102,8 @@ libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h +libkea_dhcpsrv_la_SOURCES += cfg_shared_networks4.cc cfg_shared_networks4.h +libkea_dhcpsrv_la_SOURCES += cfg_shared_networks6.h libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h @@ -138,6 +141,7 @@ libkea_dhcpsrv_la_SOURCES += mysql_host_data_source.cc mysql_host_data_source.h endif libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h +libkea_dhcpsrv_la_SOURCES += network.h if HAVE_PGSQL libkea_dhcpsrv_la_SOURCES += pgsql_connection.cc pgsql_connection.h @@ -150,6 +154,7 @@ libkea_dhcpsrv_la_SOURCES += cql_lease_mgr.cc cql_lease_mgr.h libkea_dhcpsrv_la_SOURCES += cql_connection.cc cql_connection.h endif libkea_dhcpsrv_la_SOURCES += pool.cc pool.h +libkea_dhcpsrv_la_SOURCES += shared_network.cc shared_network.h libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h libkea_dhcpsrv_la_SOURCES += subnet_id.h @@ -178,6 +183,9 @@ libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.h libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.h +libkea_dhcpsrv_la_SOURCES += parsers/shared_network_parser.cc +libkea_dhcpsrv_la_SOURCES += parsers/shared_network_parser.h +libkea_dhcpsrv_la_SOURCES += parsers/shared_networks_list_parser.h libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.cc libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.h libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc diff --git a/src/lib/dhcpsrv/assignable_network.h b/src/lib/dhcpsrv/assignable_network.h new file mode 100644 index 0000000000..2b64d44ce2 --- /dev/null +++ b/src/lib/dhcpsrv/assignable_network.h @@ -0,0 +1,59 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef ASSIGNABLE_NETWORK_H +#define ASSIGNABLE_NETWORK_H + +#include + +namespace isc { +namespace dhcp { + +/// @brief Represents a network that can be associated with a subnet. +/// +/// This class represents a network that can be associated with a subnet +/// using @c Subnet::setSharedNetwork method. This class is a friend +/// of a @ref Subnet class, so it can call its @c Subnet::setSharedNetwork +/// private method. Association of a network with a subnet must be always +/// conducted using this class. This prevents unwanted replacements of +/// shared networks within subnets. +class AssignableNetwork : public Network { +protected: + + /// @brief Returns shared pointer to this object. + /// + /// This abstract method must be implemented by derived classes to + /// return shared pointers the derivation. + /// + /// @return Pointer to this network. + virtual NetworkPtr sharedFromThis() = 0; + + /// @brief Associates a subnet with this network. + /// + /// @param subnet Pointer to a subnet to be associated with the network. + /// + /// @tparam SubnetPtr Type of the subnet pointer. + template + void setSharedNetwork(const SubnetPtr& subnet) { + subnet->setSharedNetwork(sharedFromThis()); + } + + /// @brief Removes association of a subnet with a network. + /// + /// @param subnet Pointer to a subnet for which association should be + /// removed. + /// + /// @tparam SubnetPtr Type of the subnet pointer. + template + void clearSharedNetwork(const SubnetPtr& subnet) { + subnet->setSharedNetwork(NetworkPtr()); + } +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // ASSIGNABLE_NETWORK_H diff --git a/src/lib/dhcpsrv/cfg_shared_networks4.cc b/src/lib/dhcpsrv/cfg_shared_networks4.cc new file mode 100644 index 0000000000..792fe2047a --- /dev/null +++ b/src/lib/dhcpsrv/cfg_shared_networks4.cc @@ -0,0 +1,17 @@ +// Copyright (C) 2017 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 + +namespace isc { +namespace dhcp { + +void +CfgSharedNetworks4::add(const SharedNetwork4Ptr& network) { +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/cfg_shared_networks4.h b/src/lib/dhcpsrv/cfg_shared_networks4.h new file mode 100644 index 0000000000..76805da5e9 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_shared_networks4.h @@ -0,0 +1,29 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CFG_SHARED_NETWORKS4_H +#define CFG_SHARED_NETWORKS4_H + +#include +#include +#include + +namespace isc { +namespace dhcp { + +class CfgSharedNetworks4 : public data::CfgToElement { +public: + + void add(const SharedNetwork4Ptr& shared_network); + +}; + +typedef boost::shared_ptr CfgSharedNetworks4Ptr; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // CFG_SHARED_NETWORKS4_H diff --git a/src/lib/dhcpsrv/cfg_shared_networks6.h b/src/lib/dhcpsrv/cfg_shared_networks6.h new file mode 100644 index 0000000000..daf4952bf2 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_shared_networks6.h @@ -0,0 +1,26 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CFG_SHARED_NETWORKS6_H +#define CFG_SHARED_NETWORKS6_H + +#include +#include + +namespace isc { +namespace dhcp { + +class CfgSharedNetworks6 : public data::CfgToElement { +public: + +}; + +typedef boost::shared_ptr CfgSharedNetworks6Ptr; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // CFG_SHARED_NETWORKS6_H diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h new file mode 100644 index 0000000000..a349b44382 --- /dev/null +++ b/src/lib/dhcpsrv/network.h @@ -0,0 +1,74 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef NETWORK_H +#define NETWORK_H + +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Common interface representing a network to which the DHCP clients +/// are connected. +/// +/// The most common type of network, in Kea's terminology, is a subnet. The +/// @ref Subnet implements this interface. Another types of objects implementing +/// this interface are @ref SharedNetwork objects. They group multiple subnets +/// together to provide means for extending available address pools (a single +/// client may obtain IP address from any of the pools belonging to subnets in +/// the shared network), or for selecting a subnet on a given link, depending +/// on the class of the client (e.g. cable network case: different subnet is +/// selected for cable modems, different one for routers). +/// +/// The subnets and shared networks share many data structures, e.g. DHCP +/// options, local interface name, address manipulation methods, thus this +/// class provides an abstract interface that must be implemented by derived +/// classes and, where appropriate, implements common methods used by the +/// derived classes. +class Network { +public: + + /// @brief Virtual destructor. + /// + /// Does nothing at the moment. + virtual ~Network() { }; + + /// @brief Sets local name of the interface for which this network is + /// selected. + /// + /// If the interface is specified, the server will use the network + /// associated with this local interface to allocate IP addresses and + /// other resources to a client. + /// + /// @param iface_name Interface name. + void setIface(const std::string& iface_name) { + iface_name_ = iface_name; + } + + /// @brief Returns name of the local interface for which this network is + /// selected. + /// + /// @return Interface name as text. + std::string getIface() const { + return (iface_name_); + }; + +protected: + + /// @brief Holds interface name for which this network is selected. + std::string iface_name_; + +}; + +/// @brief Pointer to the @ref Network object. +typedef boost::shared_ptr NetworkPtr; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // NETWORK_H diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc new file mode 100644 index 0000000000..d73dd64b33 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc @@ -0,0 +1,30 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SHARED_NETWORK_PARSER_H +#define SHARED_NETWORK_PARSER_H + +#include +#include +#include + +using namespace isc::data; + +namespace isc { +namespace dhcp { + +SharedNetworkParser::~SharedNetworkParser() { +} + +SharedNetworkPtr +SharedNetworkParser::parse(const ConstElementPtr& shared_network_data) { + return (SharedNetworkPtr()); +} + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // SHARED_NETWORK_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.h b/src/lib/dhcpsrv/parsers/shared_network_parser.h new file mode 100644 index 0000000000..aab0bcaadf --- /dev/null +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.h @@ -0,0 +1,30 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SHARED_SUBNET_PARSER_H +#define SHARED_SUBNET_PARSER_H + +#include +#include +#include + +namespace isc { +namespace dhcp { + +class SharedNetworkParser : public isc::data::SimpleParser { +public: + + virtual ~SharedNetworkParser(); + + virtual SharedNetworkPtr + parse(const data::ConstElementPtr& shared_network_data); + +}; + +} // enf of namespace isc::dhcp +} // end of namespace isc + +#endif // SHARED_SUBNET_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h b/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h new file mode 100644 index 0000000000..ea9eb89813 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h @@ -0,0 +1,37 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SHARED_NETWORKS_LIST_PARSER_H +#define SHARED_NETWORKS_LIST_PARSER_H + +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +template +class SharedNetworksListParser : public data::SimpleParser { +protected: + + template + void parse(const data::ConstElementPtr& shared_networks_list_data, + CfgSharedNetworksTypePtr& cfg) { + if (shared_networks_list_data->getType() != Element::list) { + isc_throw(data::DhcpConfigError, "shared-networks value must be a list"); + } + } + +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // SHARED_NETWORKS_LIST_PARSER_H diff --git a/src/lib/dhcpsrv/shared_network.cc b/src/lib/dhcpsrv/shared_network.cc new file mode 100644 index 0000000000..117441abbf --- /dev/null +++ b/src/lib/dhcpsrv/shared_network.cc @@ -0,0 +1,69 @@ +// Copyright (C) 2017 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 + +using namespace isc::dhcp; + +namespace isc { +namespace dhcp { + +NetworkPtr +SharedNetwork4::sharedFromThis() { + return (shared_from_this()); +} + +void +SharedNetwork4::add(const Subnet4Ptr& subnet) { + SharedNetwork::add(subnets_, subnet); +} + +void +SharedNetwork4::del(const SubnetID& subnet_id) { + SharedNetwork::del(subnets_, subnet_id); +} + +Subnet4Ptr +SharedNetwork4::getSubnet(const SubnetID& subnet_id) const { + return (SharedNetwork::getSubnet(subnets_, subnet_id)); +} + +Subnet4Ptr +SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet, + const Subnet4Ptr& current_subnet) const { + return (SharedNetwork::getNextSubnet(subnets_, first_subnet, + current_subnet)); +} + +NetworkPtr +SharedNetwork6::sharedFromThis() { + return (shared_from_this()); +} + +void +SharedNetwork6::add(const Subnet6Ptr& subnet) { + SharedNetwork::add(subnets_, subnet); +} + +void +SharedNetwork6::del(const SubnetID& subnet_id) { + SharedNetwork::del(subnets_, subnet_id); +} + +Subnet6Ptr +SharedNetwork6::getSubnet(const SubnetID& subnet_id) const { + return (SharedNetwork::getSubnet(subnets_, subnet_id)); +} + +Subnet6Ptr +SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet, + const Subnet6Ptr& current_subnet) const { + return (SharedNetwork::getNextSubnet(subnets_, first_subnet, + current_subnet)); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/shared_network.h b/src/lib/dhcpsrv/shared_network.h new file mode 100644 index 0000000000..7fcc579648 --- /dev/null +++ b/src/lib/dhcpsrv/shared_network.h @@ -0,0 +1,374 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SHARED_NETWORK_H +#define SHARED_NETWORK_H + +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +class SharedNetwork; + +/// @brief Pointer to the @ref SharedNetwork object. +typedef boost::shared_ptr SharedNetworkPtr; + +/// @brief Represents shared network, which groups multiple subnets. +/// +/// Shared networks provide means for grouping multiple subnets together. +/// There are two major use cases for such grouping: +/// +/// - Extending available address pools to be allocated for clients on +/// a particular link without a need to renumber. +/// - Fulfill requirement in cable networks whereby two classes of devices +/// are connected on the same link: cable modems and routers. Such devices +/// must be assign addresses/prefixes from different pools based on client +/// classification. +/// +/// Shared networks provide similar interface like subnets, thus they both +/// derive from the @ref Network class. +class SharedNetwork : public AssignableNetwork { +protected: + + /// @brief Adds a subnet to a shared network. + /// + /// This is a generic method for adding a new subnet to a shared network. + /// + /// @param [out] subnets Container holding subnets for this shared network. + /// @param subnet Pointer to a subnet being added to this shared network. + /// + /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr + /// or @ref Subnet6Ptr. + /// @tparam SubnetCollectionType Type of a container holding subnets, i.e. + /// @ref Subnet4Collection or @ref Subnet6Collection. + /// + /// @throw isc::BadValue if subnet is null. + /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id + /// already exists in this shared network. + /// @throw InvalidOperation if a subnet is already associated with some + /// shared network. + template + void add(SubnetCollectionType& subnets, const SubnetPtrType& subnet) { + // Subnet must be non-null. + if (!subnet) { + isc_throw(BadValue, "null pointer specified when adding a subnet" + " to a shared network"); + } + + // Check if a subnet with this id already exists. + if (getSubnet(subnets, subnet->getID())) { + isc_throw(DuplicateSubnetID, "attempted to add subnet with a" + " duplicated subnet identifier " << subnet->getID()); + } + + // Check if the subnet is already associated with some network. + SharedNetworkPtr network; + subnet->getSharedNetwork(network); + if (network) { + isc_throw(InvalidOperation, "subnet " << subnet->getID() + << " being added to a shared network" + " already belongs to a shared network"); + } + + // Add a subnet to the collection of subnets for this shared network. + subnets.push_back(subnet); + // Associate the subnet with this network. + setSharedNetwork(subnet); + } + + /// @brief Removes a subnet from the shared network. + /// + /// @param [out] subnets Container holding subnets for this shared network. + /// @param subnet_id Identifier of a subnet to be removed. + /// + /// @tparam SubnetCollectionType Type of a container holding subnets, i.e. + /// @ref Subnet4Collection or @ref Subnet6Collection. + /// + /// @throw BadValue if a subnet with specified identifier doesn't exist. + template + void del(SubnetCollectionType& subnets, const SubnetID& subnet_id) { + auto& index = subnets.template get(); + auto subnet_it = index.find(subnet_id); + if (subnet_it == index.end()) { + isc_throw(BadValue, "unable to delete subnet " << subnet_id + << " from shared network. Subnet doesn't belong" + " to this shared network"); + } + auto subnet = *subnet_it; + index.erase(subnet_it); + clearSharedNetwork(subnet); + } + + /// @brief Returns a subnet belonging to this network for a given subnet id. + /// + /// @param subnets Container holding subnets for this shared network. + /// @param subnet_id Identifier of a subnet being retrieved. + /// + /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr + /// or @ref Subnet6Ptr. + /// @tparam SubnetCollectionType Type of a container holding subnets, i.e. + /// @ref Subnet4Collection or @ref Subnet6Collection. + /// + /// @return Pointer to the subnet or null if the subnet doesn't exist. + template + SubnetPtrType getSubnet(const SubnetCollectionType& subnets, + const SubnetID& subnet_id) const { + const auto& index = subnets.template get(); + auto subnet_it = index.find(subnet_id); + if (subnet_it != index.cend()) { + return (*subnet_it); + } + + // Subnet not found. + return (SubnetPtrType()); + } + + /// @brief Retrieves next available subnet within shared network. + /// + /// This method returns next available subnet within a shared network. + /// The subnets are ordered and retrieved using random access index + /// (first in/first out). The next subnet means next in turn after + /// the current subnet, which is specified as an argument. A caller + /// can iterate over all subnets starting from any of the subnets + /// belonging to a shared network. This subnet is called here as + /// a first subnet and is also specified as a method argument. When the + /// method detects that the next available subnet is a first subnet, it + /// returns a null pointer to indicate that there are no more subnets + /// available. + /// + /// The typical use case for this method is to allow DHCP server's + /// allocation engine to walk over the available subnets within a shared + /// network, starting from a subnet that has been selected during the + /// "subnet selection" processing step. In some cases the allocation + /// engine is unable to allocate resources from a selected subnet due + /// to client classification restrictions or address shortage within + /// its pools. It then uses this mechanism to move to another subnet + /// belonging to the same shared network. + /// + /// @param subnets Container holding subnets belonging to this shared + /// network. + /// @param first_subnet Pointer to a subnet from which the caller is + /// iterating over subnets within shared network. This is typically a + /// subnet selected during "subnet selection" step. + /// @param current_subnet Pointer to a subnet for which next subnet is + /// to be found. + /// + /// @tparam SubnetPtrType Type of the pointer to a subnet, i.e. + /// @ref Subnet4Ptr or @ref Subnet6Ptr. + /// @tparam SubnetCollectionType Type of the container holding subnets, i.e. + /// @ref Subnet4Collection or @ref Subnet6Collection. + /// + /// @return Pointer to next subnet or null pointer if no more subnets found. + /// + /// @throw isc::BadValue if invalid arguments specified, e.g. unable to + /// find first or current subnet within the container. + template + SubnetPtrType getNextSubnet(const SubnetCollectionType& subnets, + const SubnetPtrType& first_subnet, + const SubnetPtrType& current_subnet) const { + // Current subnet must not be null. The caller must explicitly set it + // to one of the pointers that belong to this shared network, typically + // to a selected subnet. + if (!current_subnet) { + isc_throw(BadValue, "null subnet specified for a shared" + " network while searching for next subnet is this" + " network"); + } + + // It is ok to have a shared network without any subnets, but in this + // case there is nothing else we can return but null pointer. + if (subnets.empty()) { + return (SubnetPtrType()); + } + + // Need to retrieve an iterator to the current subnet first. The + // subnet must exist in this container, thus we throw if the iterator + // is not found. + const auto& index = subnets.template get(); + auto subnet_id_it = index.find(current_subnet->getID()); + if (subnet_id_it == index.cend()) { + isc_throw(BadValue, "no such subnet " << current_subnet->getID() + << " within shared network"); + } + + // We need to transform this iterator (by subnet id) to a random access + // index iterator. Multi index container has a nice way of doing it. + auto subnet_it = subnets.template project(subnet_id_it); + + // Step to a next subnet within random access index. + if (++subnet_it == subnets.cend()) { + // If we reached the end of the container, start over from the + // beginning. + subnet_it = subnets.cbegin(); + } + + // Check if we have made a full circle. If we did, return a null pointer + // to indicate that there are no more subnets. + if ((*subnet_it)->getID() == first_subnet->getID()) { + return (SubnetPtrType()); + } + + // Got the next subnet, so return it. + return (*subnet_it); + } + +}; + +/// @brief Shared network holding IPv4 subnets. +/// +/// Specialization of the @ref SharedNetwork class for IPv4 subnets. +class SharedNetwork4 : public SharedNetwork, + public boost::enable_shared_from_this { +public: + + /// @brief Returns shared pointer to this network. + /// + /// This method is required by the parent @ref AssignableNetwork class. + /// + /// @return Shared pointer to this object. + virtual NetworkPtr sharedFromThis(); + + /// @brief Adds IPv4 subnet to a shared network. + /// + /// @param subnet Pointer to a subnet being added to this shared network. + /// + /// @throw isc::BadValue if subnet is null. + /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id + /// already exists in this shared network. + /// @throw InvalidOperation if a subnet is already associated with some + /// shared network. + void add(const Subnet4Ptr& subnet); + + /// @brief Removes subnet from a shared network. + /// + /// @param subnet_id Identifier of a subnet to be removed. + /// + /// @throw BadValue When specified subnet doesn't exist. + void del(const SubnetID& subnet_id); + + /// @brief Returns a pointer to the collection of subnets within this + /// shared network. + const Subnet4Collection* getAllSubnets() const { + return (&subnets_); + } + + /// @brief Returns a subnet for a specified subnet id. + /// + /// @param subnet_id Subnet identifier. + /// + /// @return Shared pointer to a subnet using this id or null pointer + /// if such subnet doesn't exist within shared network. + Subnet4Ptr getSubnet(const SubnetID& subnet_id) const; + + /// @brief Retrieves next available IPv4 subnet within shared network. + /// + /// See documentation for @ref SharedNetwork::getNextSubnet. + /// + /// @param first_subnet Pointer to a subnet from which the caller is + /// iterating over subnets within shared network. This is typically a + /// subnet selected during "subnet selection" step. + /// @param current_subnet Pointer to a subnet for which next subnet is + /// to be found. + /// + /// @return Pointer to next subnet or null pointer if no more subnets found. + /// + /// @throw isc::BadValue if invalid arguments specified, e.g. unable to + /// find first or current subnet within shared network. + Subnet4Ptr getNextSubnet(const Subnet4Ptr& first_subnet, + const Subnet4Ptr& current_subnet) const; + +private: + + /// @brief Collection of IPv4 subnets within shared network. + Subnet4Collection subnets_; + +}; + +/// @brief Pointer to @ref SharedNetwork4 object. +typedef boost::shared_ptr SharedNetwork4Ptr; + +/// @brief Shared network holding IPv6 subnets. +/// +/// Specialization of the @ref SharedNetwork class for IPv6 subnets. +class SharedNetwork6 : public SharedNetwork, + public boost::enable_shared_from_this { +public: + + /// @brief Returns shared pointer to this network. + /// + /// This method is required by the parent @ref AssignableNetwork class. + /// + /// @return Shared pointer to this object. + virtual NetworkPtr sharedFromThis(); + + /// @brief Adds IPv6 subnet to a shared network. + /// + /// @param subnet Pointer to a subnet being added to this shared network. + /// + /// @throw isc::BadValue if subnet is null. + /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id + /// already exists in this shared network. + /// @throw InvalidOperation if a subnet is already associated with some + /// shared network. + void add(const Subnet6Ptr& subnet); + + /// @brief Removes subnet from a shared network. + /// + /// @param subnet_id Identifier of a subnet to be removed. + /// + /// @throw BadValue When specified subnet doesn't exist. + void del(const SubnetID& subnet_id); + + /// @brief Returns a pointer to the collection of subnets within this + /// shared network. + const Subnet6Collection* getAllSubnets() const { + return (&subnets_); + } + + /// @brief Returns a subnet for a specified subnet id. + /// + /// @param subnet_id Subnet identifier. + /// + /// @return Shared pointer to a subnet using this id or null pointer + /// if such subnet doesn't exist within shared network. + Subnet6Ptr getSubnet(const SubnetID& subnet_id) const; + + /// @brief Retrieves next available IPv6 subnet within shared network. + /// + /// See documentation for @ref SharedNetwork::getNextSubnet. + /// + /// @param first_subnet Pointer to a subnet from which the caller is + /// iterating over subnets within shared network. This is typically a + /// subnet selected during "subnet selection" step. + /// @param current_subnet Pointer to a subnet for which next subnet is + /// to be found. + /// + /// @return Pointer to next subnet or null pointer if no more subnets found. + /// + /// @throw isc::BadValue if invalid arguments specified, e.g. unable to + /// find first or current subnet within shared network. + Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet, + const Subnet6Ptr& current_subnet) const; + +private: + + /// @brief Collection of IPv6 subnets within shared network. + Subnet6Collection subnets_; +}; + +/// @brief Pointer to @ref SharedNetwork6 object. +typedef boost::shared_ptr SharedNetwork6Ptr; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // SHARED_NETWORK_H diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index a28c0a9cbf..14ef5e83d7 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -354,16 +354,6 @@ Subnet::delPools(Lease::Type type) { getPoolsWritable(type).clear(); } -void -Subnet::setIface(const std::string& iface_name) { - iface_ = iface_name; -} - -std::string -Subnet::getIface() const { - return (iface_); -} - bool Subnet::inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const { diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index 3d91ae8bb9..7efbdb985d 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -24,12 +25,18 @@ #include #include #include +#include #include namespace isc { namespace dhcp { -class Subnet { +class Subnet : public Network { + + // Assignable network is our friend to allow it to call + // @ref Subnet::setSharedNetwork private function. + friend class AssignableNetwork; + public: /// @brief Holds optional information about relay. @@ -233,16 +240,6 @@ public: /// @param type type of the lease uint64_t getPoolCapacity(Lease::Type type) const; - /// @brief Sets name of the network interface for directly attached networks - /// - /// @param iface_name name of the interface - void setIface(const std::string& iface_name); - - /// @brief Network interface name used to reach subnet (or "" for remote - /// subnets) - /// @return network interface name for directly attached subnets or "" - std::string getIface() const; - /// @brief Returns textual representation of the subnet (e.g. /// "2001:db8::/64") /// @@ -349,6 +346,41 @@ public: host_reservation_mode_ = mode; } + /// @brief Retrieves pointer to a shared network associated with a subnet. + /// + /// By implementing it as a template function we overcome a need to + /// include shared_network.h header file to specify return type explicitly. + /// The header can't be included because it would cause circular dependency + /// between subnet.h and shared_network.h. + /// + /// This method uses an argument to hold a return value to allow the compiler + /// to infer the return type without a need to call this function with an + /// explicit return type as template argument. + /// + /// @param [out] shared_network Pointer to the shared network where returned + /// value should be assigned. + /// + /// @tparam Type of the shared network, i.e. @ref SharedNetwork4 or a + /// @ref SharedNetwork6. + template + void getSharedNetwork(SharedNetworkPtrType& shared_network) const { + shared_network = boost::dynamic_pointer_cast< + typename SharedNetworkPtrType::element_type>(shared_network_); + } + +private: + + /// @brief Assigns shared network to a subnet. + /// + /// This method replaces any shared network associated with a subnet with + /// a new shared network. + /// + /// @param shared_network Pointer to a new shared network to be associated + /// with the subnet. + void setSharedNetwork(const NetworkPtr& shared_network) { + shared_network_ = shared_network; + } + protected: /// @brief Returns all pools (non-const variant) /// @@ -518,10 +550,11 @@ protected: /// /// See @ref HRMode type for details. HRMode host_reservation_mode_; -private: /// @brief Pointer to the option data configuration for this subnet. CfgOptionPtr cfg_option_; + + NetworkPtr shared_network_; }; /// @brief A generic pointer to either Subnet4 or Subnet6 object diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 97368608d0..0b68e8d2e7 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -121,6 +121,7 @@ if HAVE_CQL libdhcpsrv_unittests_SOURCES += cql_lease_mgr_unittest.cc endif libdhcpsrv_unittests_SOURCES += pool_unittest.cc +libdhcpsrv_unittests_SOURCES += shared_network_unittest.cc libdhcpsrv_unittests_SOURCES += srv_config_unittest.cc libdhcpsrv_unittests_SOURCES += subnet_unittest.cc libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h diff --git a/src/lib/dhcpsrv/tests/shared_network_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_unittest.cc new file mode 100644 index 0000000000..c1ce2f9d54 --- /dev/null +++ b/src/lib/dhcpsrv/tests/shared_network_unittest.cc @@ -0,0 +1,172 @@ +// Copyright (C) 2017 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 + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +// This test verifies that an IPv4 subnet can be added to a shared network. +// It also verifies that two subnets with the same ID can't be added to +// a shared network and that a single subnet can't be added to two different +// shared subnets. +TEST(SharedNetwork4Test, addSubnet4) { + // First, create a network. + SharedNetwork4Ptr network(new SharedNetwork4()); + + // Try to add null pointer. It should throw. + Subnet4Ptr subnet; + ASSERT_THROW(network->add(subnet), BadValue); + + // Create a valid subnet. It should now be added successfully. + subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(15))); + ASSERT_NO_THROW(network->add(subnet)); + ASSERT_EQ(1, network->getAllSubnets()->size()); + + // Retrieve the subnet from the network and make sure it is returned + // as expected. + Subnet4Ptr returned_subnet = network->getAllSubnets()->front(); + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(subnet->getID(), returned_subnet->getID()); + SharedNetwork4Ptr network1; + subnet->getSharedNetwork(network1); + ASSERT_TRUE(network1); + EXPECT_TRUE(network1 == network); + + // Create another subnet with the same ID. Adding a network with the + // same ID should cause an error. + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(15))); + ASSERT_THROW(network->add(subnet), DuplicateSubnetID); + + // Create another network and try to add a subnet to it. It should fail + // because the subnet is already associated with the first network. + SharedNetwork4Ptr network2(new SharedNetwork4()); + ASSERT_THROW(network2->add(subnet), InvalidOperation); +} + +// This test verifies that it is possible to remove a specified subnet. +TEST(SharedNetwork4Test, delSubnet4) { + // Create two subnets and add them to the shared network. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + + SharedNetwork4Ptr network(new SharedNetwork4()); + ASSERT_NO_THROW(network->add(subnet1)); + ASSERT_NO_THROW(network->add(subnet2)); + + // Make sure they have been added successfully. + ASSERT_EQ(2, network->getAllSubnets()->size()); + + // Try to remove a subnet that doesn't exist in this shared network. + // It should cause an error. + ASSERT_THROW(network->del(SubnetID(5)), BadValue); + + // Now delete the subnet that exists. + ASSERT_NO_THROW(network->del(subnet1->getID())); + // We should be left with only one subnet. + ASSERT_EQ(1, network->getAllSubnets()->size()); + Subnet4Ptr subnet_returned = network->getAllSubnets()->front(); + ASSERT_TRUE(subnet_returned); + EXPECT_EQ(subnet2->getID(), subnet_returned->getID()); + + // Check that shared network has been cleared for the removed subnet. + SharedNetwork4Ptr network1; + subnet1->getSharedNetwork(network1); + EXPECT_FALSE(network1); + + // Remove another subnet and make sure there are no subnets left. + ASSERT_NO_THROW(network->del(subnet2->getID())); + EXPECT_EQ(0, network->getAllSubnets()->size()); + + // The network pointer should be cleared for this second subnet too. + SharedNetwork4Ptr network2; + subnet1->getSharedNetwork(network2); + EXPECT_FALSE(network2); +} + +// This test verifies that it is possible to iterate over the subnets +// associated with a particular shared network. +TEST(SharedNetwork4Test, getNextSubnet) { + SharedNetwork4Ptr network(new SharedNetwork4()); + + // Create three subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + Subnet4Ptr subnet3(new Subnet4(IOAddress("172.16.25.0"), 24, 10, 20, 30, + SubnetID(3))); + std::vector subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + + // Subnets have unique IDs so they should successfully be added to the + // network. + for (auto i = 0; i < subnets.size(); ++i) { + ASSERT_NO_THROW(network->add(subnets[i])) + << "failed to add subnet with id " << subnets[i]->getID() + << " to shared network"; + } + + // Collect networks associated with our subnets in the vector. + std::vector networks; + for (auto i = 0; i < subnets.size(); ++i) { + SharedNetwork4Ptr network; + subnets[i]->getSharedNetwork(network); + ASSERT_TRUE(network) << "failed to retrieve shared network for a" + << " subnet id " << subnets[i]->getID(); + networks.push_back(network); + } + + // All subnets should be associated with the same network. + for (auto i = 1; i < networks.size(); ++i) { + EXPECT_TRUE(networks[0] == networks[i]); + } + + // Perform the test 3 times where each subnet belonging to the shared + // network is treated as a "first" subnet in the call to getNextSubnet. + for (auto i = 0; i < subnets.size(); ++i) { + Subnet4Ptr s = subnets[i]; + + // Iterate over the subnets starting from the subnet with index i. + for (auto j = 0; j < subnets.size(); ++j) { + // Get next subnet (following the one currently in s). + s = networks[0]->getNextSubnet(subnets[i], s); + // The last iteration should return empty pointer to indicate end of + // the subnets within shared network. If we're not at last iteration + // check that the subnet identifier of the returned subnet is valid. + if (j < subnets.size() - 1) { + ASSERT_TRUE(s) << "retrieving next subnet failed for pair of" + " indexes (i, j) = (" << i << ", " << j << ")"; + const auto expected_subnet_id = (i + j + 1) % subnets.size() + 1; + EXPECT_EQ(expected_subnet_id, s->getID()); + } else { + // Null subnet returned for a last iteration. + ASSERT_FALSE(s) << "expected null pointer to be returned as" + " next subnet for pair of indexes (i, j) = (" + << i << ", " << j << ")"; + } + } + } +} + +} // end of anonymous namespace