// 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 #include #include #include #include #include #include #include #include #include #include #include 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(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(); auto subnet_it = index.find(subnet_id); if (subnet_it == index.end()) { isc_throw(BadValue, "There is no IPv4 subnet with ID " <getID()); } void CfgSubnets4::del(const SubnetID& subnet_id) { auto& index = subnets_.get(); 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(); auto& index_prefix = subnets_.get(); // 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(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(); 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(); 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(); 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(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(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 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 (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(0)); } name = StatsMgr::generateName("subnet", subnet_id, "v4-reservation-conflicts"); if (!stats_mgr.getObservation(name)) { stats_mgr.setValue(name, static_cast(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