diff options
Diffstat (limited to 'src/lib/dhcpsrv/cfg_subnets4.cc')
-rw-r--r-- | src/lib/dhcpsrv/cfg_subnets4.cc | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc new file mode 100644 index 0000000..2f160b6 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_subnets4.cc @@ -0,0 +1,559 @@ +// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/option_custom.h> +#include <dhcpsrv/cfg_subnets4.h> +#include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/lease_mgr_factory.h> +#include <dhcpsrv/shared_network.h> +#include <dhcpsrv/subnet_id.h> +#include <asiolink/io_address.h> +#include <asiolink/addr_utilities.h> +#include <stats/stats_mgr.h> +#include <sstream> + +using namespace isc::asiolink; +using namespace isc::data; + +namespace isc { +namespace dhcp { + +void +CfgSubnets4::add(const Subnet4Ptr& subnet) { + if (getBySubnetId(subnet->getID())) { + isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv4 subnet '" + << subnet->getID() << "' is already in use"); + + } else if (getByPrefix(subnet->toText())) { + /// @todo: Check that this new subnet does not cross boundaries of any + /// other already defined subnet. + isc_throw(isc::dhcp::DuplicateSubnetID, "subnet with the prefix of '" + << subnet->toText() << "' already exists"); + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4) + .arg(subnet->toText()); + static_cast<void>(subnets_.insert(subnet)); +} + +Subnet4Ptr +CfgSubnets4::replace(const Subnet4Ptr& subnet) { + // Get the subnet with the same ID. + const SubnetID& subnet_id = subnet->getID(); + auto& index = subnets_.template get<SubnetSubnetIdIndexTag>(); + auto subnet_it = index.find(subnet_id); + if (subnet_it == index.end()) { + isc_throw(BadValue, "There is no IPv4 subnet with ID " <<subnet_id); + } + Subnet4Ptr old = *subnet_it; + bool ret = index.replace(subnet_it, subnet); + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_UPDATE_SUBNET4) + .arg(subnet_id).arg(ret); + if (ret) { + return (old); + } else { + return (Subnet4Ptr()); + } +} + +void +CfgSubnets4::del(const ConstSubnet4Ptr& subnet) { + del(subnet->getID()); +} + +void +CfgSubnets4::del(const SubnetID& subnet_id) { + auto& index = subnets_.get<SubnetSubnetIdIndexTag>(); + auto subnet_it = index.find(subnet_id); + if (subnet_it == index.end()) { + isc_throw(BadValue, "no subnet with ID of '" << subnet_id + << "' found"); + } + + Subnet4Ptr subnet = *subnet_it; + + index.erase(subnet_it); + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DEL_SUBNET4) + .arg(subnet->toText()); +} + +void +CfgSubnets4::merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks4Ptr networks, + CfgSubnets4& other) { + auto& index_id = subnets_.get<SubnetSubnetIdIndexTag>(); + auto& index_prefix = subnets_.get<SubnetPrefixIndexTag>(); + + // Iterate over the subnets to be merged. They will replace the existing + // subnets with the same id. All new subnets will be inserted into the + // configuration into which we're merging. + auto const& other_subnets = other.getAll(); + for (auto const& other_subnet : (*other_subnets)) { + + // Check if there is a subnet with the same ID. + auto subnet_id_it = index_id.find(other_subnet->getID()); + if (subnet_id_it != index_id.end()) { + + // Subnet found. + auto existing_subnet = *subnet_id_it; + + // If the existing subnet and other subnet + // are the same instance skip it. + if (existing_subnet == other_subnet) { + continue; + } + + // Updating the prefix can lead to problems... e.g. pools + // and reservations going outside range. + // @todo: check prefix change. + + // We're going to replace the existing subnet with the other + // version. If it belongs to a shared network, we need + // remove it from that network. + SharedNetwork4Ptr network; + existing_subnet->getSharedNetwork(network); + if (network) { + network->del(existing_subnet->getID()); + } + + // Now we remove the existing subnet. + index_id.erase(subnet_id_it); + } + + // Check if there is a subnet with the same prefix. + auto subnet_prefix_it = index_prefix.find(other_subnet->toText()); + if (subnet_prefix_it != index_prefix.end()) { + + // Subnet found. + auto existing_subnet = *subnet_prefix_it; + + // Updating the id can lead to problems... e.g. reservation + // for the previous subnet ID. + // @todo: check reservations + + // We're going to replace the existing subnet with the other + // version. If it belongs to a shared network, we need + // remove it from that network. + SharedNetwork4Ptr network; + existing_subnet->getSharedNetwork(network); + if (network) { + network->del(existing_subnet->getID()); + } + + // Now we remove the existing subnet. + index_prefix.erase(subnet_prefix_it); + } + + // Create the subnet's options based on the given definitions. + other_subnet->getCfgOption()->createOptions(cfg_def); + + // Create the options for pool based on the given definitions. + for (auto const& pool : other_subnet->getPoolsWritable(Lease::TYPE_V4)) { + pool->getCfgOption()->createOptions(cfg_def); + } + + // Add the "other" subnet to the our collection of subnets. + static_cast<void>(subnets_.insert(other_subnet)); + + // If it belongs to a shared network, find the network and + // add the subnet to it + std::string network_name = other_subnet->getSharedNetworkName(); + if (!network_name.empty()) { + SharedNetwork4Ptr network = networks->getByName(network_name); + if (network) { + network->add(other_subnet); + } else { + // This implies the shared-network collection we were given + // is out of sync with the subnets we were given. + isc_throw(InvalidOperation, "Cannot assign subnet ID of " + << other_subnet->getID() + << " to shared network: " << network_name + << ", network does not exist"); + } + } + } +} + +ConstSubnet4Ptr +CfgSubnets4::getBySubnetId(const SubnetID& subnet_id) const { + const auto& index = subnets_.get<SubnetSubnetIdIndexTag>(); + auto subnet_it = index.find(subnet_id); + return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr()); +} + +ConstSubnet4Ptr +CfgSubnets4::getByPrefix(const std::string& subnet_text) const { + const auto& index = subnets_.get<SubnetPrefixIndexTag>(); + auto subnet_it = index.find(subnet_text); + return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr()); +} + +bool +CfgSubnets4::hasSubnetWithServerId(const asiolink::IOAddress& server_id) const { + const auto& index = subnets_.get<SubnetServerIdIndexTag>(); + auto subnet_it = index.find(server_id); + return (subnet_it != index.cend()); +} + +SubnetSelector +CfgSubnets4::initSelector(const Pkt4Ptr& query) { + SubnetSelector selector; + selector.ciaddr_ = query->getCiaddr(); + selector.giaddr_ = query->getGiaddr(); + selector.local_address_ = query->getLocalAddr(); + selector.remote_address_ = query->getRemoteAddr(); + selector.client_classes_ = query->classes_; + selector.iface_name_ = query->getIface(); + + // If the link-selection sub-option is present, extract its value. + // "The link-selection sub-option is used by any DHCP relay agent + // that desires to specify a subnet/link for a DHCP client request + // that it is relaying but needs the subnet/link specification to + // be different from the IP address the DHCP server should use + // when communicating with the relay agent." (RFC 3527) + // + // Try first Relay Agent Link Selection sub-option + OptionPtr rai = query->getOption(DHO_DHCP_AGENT_OPTIONS); + if (rai) { + OptionCustomPtr rai_custom = + boost::dynamic_pointer_cast<OptionCustom>(rai); + if (rai_custom) { + OptionPtr link_select = + rai_custom->getOption(RAI_OPTION_LINK_SELECTION); + if (link_select) { + OptionBuffer link_select_buf = link_select->getData(); + if (link_select_buf.size() == sizeof(uint32_t)) { + selector.option_select_ = + IOAddress::fromBytes(AF_INET, &link_select_buf[0]); + return (selector); + } + } + } + } + // The query does not include a RAI option or that option does + // not contain the link-selection sub-option. Try subnet-selection + // option. + OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION); + if (sbnsel) { + OptionCustomPtr oc = + boost::dynamic_pointer_cast<OptionCustom>(sbnsel); + if (oc) { + selector.option_select_ = oc->readAddress(); + } + } + return (selector); +} + +Subnet4Ptr +CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const { + for (auto const& subnet : subnets_) { + Cfg4o6& cfg4o6 = subnet->get4o6(); + + // Is this an 4o6 subnet at all? + if (!cfg4o6.enabled()) { + continue; // No? Let's try the next one. + } + + // First match criteria: check if we have a prefix/len defined. + std::pair<asiolink::IOAddress, uint8_t> pref = cfg4o6.getSubnet4o6(); + if (!pref.first.isV6Zero()) { + + // Let's check if the IPv6 address is in range + IOAddress first = firstAddrInPrefix(pref.first, pref.second); + IOAddress last = lastAddrInPrefix(pref.first, pref.second); + if ((first <= selector.remote_address_) && + (selector.remote_address_ <= last)) { + return (subnet); + } + } + + // Second match criteria: check if the interface-id matches + if (cfg4o6.getInterfaceId() && selector.interface_id_ && + cfg4o6.getInterfaceId()->equals(selector.interface_id_)) { + return (subnet); + } + + // Third match criteria: check if the interface name matches + if (!cfg4o6.getIface4o6().empty() && !selector.iface_name_.empty() + && cfg4o6.getIface4o6() == selector.iface_name_) { + return (subnet); + } + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_SUBNET4O6_SELECT_FAILED); + + // Ok, wasn't able to find any matching subnet. + return (Subnet4Ptr()); +} + +Subnet4Ptr +CfgSubnets4::selectSubnet(const SubnetSelector& selector) const { + // First use RAI link select sub-option or subnet select option + if (!selector.option_select_.isV4Zero()) { + return (selectSubnet(selector.option_select_, + selector.client_classes_)); + } else { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_SUBNET4_SELECT_NO_RAI_OPTIONS); + } + + // If relayed message has been received, try to match the giaddr with the + // relay address specified for a subnet and/or shared network. It is also + // possible that the relay address will not match with any of the relay + // addresses across all subnets, but we need to verify that for all subnets + // before we can try to use the giaddr to match with the subnet prefix. + if (!selector.giaddr_.isV4Zero()) { + for (auto const& subnet : subnets_) { + + // If relay information is specified for this subnet, it must match. + // Otherwise, we ignore this subnet. + if (subnet->hasRelays()) { + if (!subnet->hasRelayAddress(selector.giaddr_)) { + continue; + } + } else { + // Relay information is not specified on the subnet level, + // so let's try matching on the shared network level. + SharedNetwork4Ptr network; + subnet->getSharedNetwork(network); + if (!network || !(network->hasRelayAddress(selector.giaddr_))) { + continue; + } + } + + // If a subnet meets the client class criteria return it. + if (subnet->clientSupported(selector.client_classes_)) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_CFGMGR_SUBNET4_RELAY) + .arg(subnet->toText()) + .arg(selector.giaddr_.toText()); + return (subnet); + } + } + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_NO_MATCH) + .arg(selector.giaddr_.toText()); + } else { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_SUBNET4_SELECT_NO_RELAY_ADDRESS); + } + + // If we got to this point it means that we were not able to match the + // giaddr with any of the addresses specified for subnets. Let's determine + // what address from the client's packet to use to match with the + // subnets' prefixes. + + IOAddress address = IOAddress::IPV4_ZERO_ADDRESS(); + // If there is a giaddr, use it for subnet selection. + if (!selector.giaddr_.isV4Zero()) { + address = selector.giaddr_; + + // If it is a Renew or Rebind, use the ciaddr. + } else if (!selector.ciaddr_.isV4Zero() && + !selector.local_address_.isV4Bcast()) { + address = selector.ciaddr_; + + // If ciaddr is not specified, use the source address. + } else if (!selector.remote_address_.isV4Zero() && + !selector.local_address_.isV4Bcast()) { + address = selector.remote_address_; + + // If local interface name is known, use the local address on this + // interface. + } else if (!selector.iface_name_.empty()) { + IfacePtr iface = IfaceMgr::instance().getIface(selector.iface_name_); + // This should never happen in the real life. Hence we throw an + // exception. + if (iface == NULL) { + isc_throw(isc::BadValue, "interface " << selector.iface_name_ + << " doesn't exist and therefore it is impossible" + " to find a suitable subnet for its IPv4 address"); + } + + // Attempt to select subnet based on the interface name. + Subnet4Ptr subnet = selectSubnet(selector.iface_name_, + selector.client_classes_); + + // If it matches - great. If not, we'll try to use a different + // selection criteria below. + if (subnet) { + return (subnet); + } else { + // Let's try to get an address from the local interface and + // try to match it to defined subnet. + iface->getAddress4(address); + } + } + + // Unable to find a suitable address to use for subnet selection. + if (address.isV4Zero()) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_SUBNET4_SELECT_NO_USABLE_ADDRESS); + + return (Subnet4Ptr()); + } + + // We have identified an address in the client's packet that can be + // used for subnet selection. Match this packet with the subnets. + return (selectSubnet(address, selector.client_classes_)); +} + +Subnet4Ptr +CfgSubnets4::selectSubnet(const std::string& iface, + const ClientClasses& client_classes) const { + for (auto const& subnet : subnets_) { + Subnet4Ptr subnet_selected; + + // First, try subnet specific interface name. + if (!subnet->getIface(Network4::Inheritance::NONE).empty()) { + if (subnet->getIface(Network4::Inheritance::NONE) == iface) { + subnet_selected = subnet; + } + + } else { + // Interface not specified for a subnet, so let's try if + // we can match with shared network specific setting of + // the interface. + SharedNetwork4Ptr network; + subnet->getSharedNetwork(network); + if (network && + (network->getIface(Network4::Inheritance::NONE) == iface)) { + subnet_selected = subnet; + } + } + + if (subnet_selected) { + // If a subnet meets the client class criteria return it. + if (subnet_selected->clientSupported(client_classes)) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_CFGMGR_SUBNET4_IFACE) + .arg(subnet->toText()) + .arg(iface); + return (subnet_selected); + } + } + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_NO_MATCH) + .arg(iface); + + // Failed to find a subnet. + return (Subnet4Ptr()); +} + +Subnet4Ptr +CfgSubnets4::getSubnet(const SubnetID id) const { + /// @todo: Once this code is migrated to multi-index container, use + /// an index rather than full scan. + for (auto const& subnet : subnets_) { + if (subnet->getID() == id) { + return (subnet); + } + } + return (Subnet4Ptr()); +} + +Subnet4Ptr +CfgSubnets4::selectSubnet(const IOAddress& address, + const ClientClasses& client_classes) const { + for (auto const& subnet : subnets_) { + + // Address is in range for the subnet prefix, so return it. + if (!subnet->inRange(address)) { + continue; + } + + // If a subnet meets the client class criteria return it. + if (subnet->clientSupported(client_classes)) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET4_ADDR) + .arg(subnet->toText()) + .arg(address.toText()); + return (subnet); + } + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_NO_MATCH) + .arg(address.toText()); + + // Failed to find a subnet. + return (Subnet4Ptr()); +} + +void +CfgSubnets4::removeStatistics() { + using namespace isc::stats; + + // For each v4 subnet currently configured, remove the statistic. + StatsMgr& stats_mgr = StatsMgr::instance(); + for (auto const& subnet4 : subnets_) { + SubnetID subnet_id = subnet4->getID(); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "total-addresses")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "assigned-addresses")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-addresses")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "declined-addresses")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases")); + } +} + +void +CfgSubnets4::updateStatistics() { + using namespace isc::stats; + + StatsMgr& stats_mgr = StatsMgr::instance(); + for (auto const& subnet4 : subnets_) { + SubnetID subnet_id = subnet4->getID(); + + stats_mgr.setValue(StatsMgr:: + generateName("subnet", subnet_id, "total-addresses"), + static_cast<int64_t> + (subnet4->getPoolCapacity(Lease::TYPE_V4))); + std::string name = + StatsMgr::generateName("subnet", subnet_id, "cumulative-assigned-addresses"); + if (!stats_mgr.getObservation(name)) { + stats_mgr.setValue(name, static_cast<int64_t>(0)); + } + + name = StatsMgr::generateName("subnet", subnet_id, "v4-reservation-conflicts"); + if (!stats_mgr.getObservation(name)) { + stats_mgr.setValue(name, static_cast<int64_t>(0)); + } + } + + // Only recount the stats if we have subnets. + if (subnets_.begin() != subnets_.end()) { + LeaseMgrFactory::instance().recountLeaseStats4(); + } +} + +ElementPtr +CfgSubnets4::toElement() const { + ElementPtr result = Element::createList(); + // Iterate subnets + for (auto const& subnet : subnets_) { + result->add(subnet->toElement()); + } + return (result); +} + +} // end of namespace isc::dhcp +} // end of namespace isc |