cfg_subnets4.cc 16.9 KB
Newer Older
1
// Copyright (C) 2014-2019 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// 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/.
6

7
#include <config.h>
8
#include <dhcp/iface_mgr.h>
9
#include <dhcp/option_custom.h>
10 11
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/dhcpsrv_log.h>
12
#include <dhcpsrv/lease_mgr_factory.h>
13
#include <dhcpsrv/shared_network.h>
14
#include <dhcpsrv/subnet_id.h>
15
#include <asiolink/io_address.h>
16
#include <asiolink/addr_utilities.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
17
#include <stats/stats_mgr.h>
18
#include <sstream>
19 20

using namespace isc::asiolink;
21
using namespace isc::data;
22 23 24 25 26 27

namespace isc {
namespace dhcp {

void
CfgSubnets4::add(const Subnet4Ptr& subnet) {
28
    if (getBySubnetId(subnet->getID())) {
29
        isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv4 subnet '"
30
                  << subnet->getID() << "' is already in use");
31 32 33 34 35 36

    } 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");
37
    }
38

39 40 41 42 43
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
              .arg(subnet->toText());
    subnets_.push_back(subnet);
}

44 45
void
CfgSubnets4::del(const ConstSubnet4Ptr& subnet) {
46
    auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
47 48 49 50 51 52 53 54 55 56 57
    auto subnet_it = index.find(subnet->getID());
    if (subnet_it == index.end()) {
        isc_throw(BadValue, "no subnet with ID of '" << subnet->getID()
                  << "' found");
    }
    index.erase(subnet_it);

    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DEL_SUBNET4)
        .arg(subnet->toText());
}

58
void
59 60
CfgSubnets4::merge(CfgSharedNetworks4Ptr networks,
                   const CfgSubnets4& other) {
61 62 63 64 65 66
    auto& index = subnets_.get<SubnetSubnetIdIndexTag>();

    // 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 other_subnets = other.getAll();
67
    for (auto other_subnet = other_subnets->begin(); other_subnet != other_subnets->end();
68 69 70 71 72 73 74
         ++other_subnet) {

        // Check if there is a subnet with the same ID.
        auto subnet_it = index.find((*other_subnet)->getID());
        if (subnet_it != index.end()) {

            // Subnet found.
75
            auto existing_subnet = *subnet_it;
76

77 78 79
            // Continue if the existing subnet and other subnet
            // are the same instance skip it.
            if (existing_subnet == *other_subnet) {
80 81 82
                continue;
            }

83 84 85
            // 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.
86
            SharedNetwork4Ptr network;
87
            existing_subnet->getSharedNetwork(network);
88
            if (network) {
89
                network->del(existing_subnet->getID());
90 91
            }

92
            // Now we remove the existing subnet.
93 94 95
            index.erase(subnet_it);
        }

96 97
        // Add the "other" subnet to the our collection of subnets.
        subnets_.push_back(*other_subnet);
98

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
        // 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");
            }
114 115 116 117
        }
    }
}

118 119
ConstSubnet4Ptr
CfgSubnets4::getBySubnetId(const SubnetID& subnet_id) const {
120
    const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
121 122 123 124 125 126 127 128 129 130 131
    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());
}

132 133 134 135 136 137 138
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());
}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
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]);
                }
            }
        }
    } else {
        // Or 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);
}

187 188 189 190 191 192 193 194 195 196 197 198 199 200
Subnet4Ptr
CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {

    for (Subnet4Collection::const_iterator subnet = subnets_.begin();
         subnet != subnets_.end(); ++subnet) {
        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();
201
        if (!pref.first.isV6Zero()) {
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

            // 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);
        }
    }

    // Ok, wasn't able to find any matching subnet.
    return (Subnet4Ptr());
}

229
Subnet4Ptr
230
CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
231

232 233 234 235 236 237
    // First use RAI link select sub-option or subnet select option
    if (!selector.option_select_.isV4Zero()) {
        return (selectSubnet(selector.option_select_,
                             selector.client_classes_));
    }

238
    // If relayed message has been received, try to match the giaddr with the
239 240 241 242
    // 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.
Francis Dupont's avatar
Francis Dupont committed
243
    if (!selector.giaddr_.isV4Zero()) {
244 245
        for (Subnet4Collection::const_iterator subnet = subnets_.begin();
             subnet != subnets_.end(); ++subnet) {
246

Tomek Mrugalski's avatar
Tomek Mrugalski committed
247
            // If relay information is specified for this subnet, it must match.
248
            // Otherwise, we ignore this subnet.
249 250
            if ((*subnet)->hasRelays()) {
                if (!(*subnet)->hasRelayAddress(selector.giaddr_)) {
251 252 253 254 255 256 257
                    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);
258
                if (!network || !(network->hasRelayAddress(selector.giaddr_))) {
259 260
                    continue;
                }
261 262
            }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
263
            // If a subnet meets the client class criteria return it.
264
            if ((*subnet)->clientSupported(selector.client_classes_)) {
265 266 267 268 269 270 271 272 273 274
                return (*subnet);
            }
        }
    }

    // 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.

Francis Dupont's avatar
Francis Dupont committed
275
    IOAddress address = IOAddress::IPV4_ZERO_ADDRESS();
276
    // If there is a giaddr, use it for subnet selection.
Francis Dupont's avatar
Francis Dupont committed
277
    if (!selector.giaddr_.isV4Zero()) {
278 279 280
        address = selector.giaddr_;

    // If it is a Renew or Rebind, use the ciaddr.
Francis Dupont's avatar
Francis Dupont committed
281 282
    } else if (!selector.ciaddr_.isV4Zero() &&
               !selector.local_address_.isV4Bcast()) {
283 284 285
        address = selector.ciaddr_;

    // If ciaddr is not specified, use the source address.
Francis Dupont's avatar
Francis Dupont committed
286 287
    } else if (!selector.remote_address_.isV4Zero() &&
               !selector.local_address_.isV4Bcast()) {
288 289 290 291
        address = selector.remote_address_;

    // If local interface name is known, use the local address on this
    // interface.
292
    } else if (!selector.iface_name_.empty()) {
293
        IfacePtr iface = IfaceMgr::instance().getIface(selector.iface_name_);
294 295 296
        // This should never happen in the real life. Hence we throw an
        // exception.
        if (iface == NULL) {
297
            isc_throw(isc::BadValue, "interface " << selector.iface_name_
298 299 300
                      << " doesn't exist and therefore it is impossible"
                      " to find a suitable subnet for its IPv4 address");
        }
301 302 303 304 305 306 307 308 309

        // 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);
310 311 312 313
        } else {
            // Let's try to get an address from the local interface and
            // try to match it to defined subnet.
            iface->getAddress4(address);
314
        }
315 316 317
    }

    // Unable to find a suitable address to use for subnet selection.
Francis Dupont's avatar
Francis Dupont committed
318
    if (address.isV4Zero()) {
319 320 321 322
        return (Subnet4Ptr());
    }

    // We have identified an address in the client's packet that can be
Tomek Mrugalski's avatar
Tomek Mrugalski committed
323
    // used for subnet selection. Match this packet with the subnets.
324
    return (selectSubnet(address, selector.client_classes_));
325 326
}

327 328
Subnet4Ptr
CfgSubnets4::selectSubnet(const std::string& iface,
329
                          const ClientClasses& client_classes) const {
330 331 332
    for (Subnet4Collection::const_iterator subnet = subnets_.begin();
         subnet != subnets_.end(); ++subnet) {

333 334 335 336 337 338 339
        Subnet4Ptr subnet_selected;

        // First, try subnet specific interface name.
        if (!(*subnet)->getIface().empty()) {
            if ((*subnet)->getIface() == iface) {
                subnet_selected = (*subnet);
            }
340

341 342 343 344 345 346 347 348 349 350
        } 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().empty() &&
                (network->getIface() == iface)) {
                subnet_selected = (*subnet);
            }
351 352
        }

353 354 355 356 357 358 359 360 361 362
        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);
            }
363 364 365 366 367 368 369
        }
    }

    // Failed to find a subnet.
    return (Subnet4Ptr());
}

370 371 372 373 374 375 376 377 378 379 380 381 382
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 subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
        if ((*subnet)->getID() == id) {
            return (*subnet);
        }
    }
    return (Subnet4Ptr());
}

383
Subnet4Ptr
384
CfgSubnets4::selectSubnet(const IOAddress& address,
385
                 const ClientClasses& client_classes) const {
386 387
    for (Subnet4Collection::const_iterator subnet = subnets_.begin();
         subnet != subnets_.end(); ++subnet) {
388

389 390
        // Address is in range for the subnet prefix, so return it.
        if (!(*subnet)->inRange(address)) {
391 392 393
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
394
        // If a subnet meets the client class criteria return it.
395
        if ((*subnet)->clientSupported(client_classes)) {
396
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET4_ADDR)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
397 398
                .arg((*subnet)->toText())
                .arg(address.toText());
399 400 401 402 403 404 405 406
            return (*subnet);
        }
    }

    // Failed to find a subnet.
    return (Subnet4Ptr());
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
407 408 409 410 411
void
CfgSubnets4::removeStatistics() {
    using namespace isc::stats;

    // For each v4 subnet currently configured, remove the statistic.
412
    StatsMgr& stats_mgr = StatsMgr::instance();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
413 414
    for (Subnet4Collection::const_iterator subnet4 = subnets_.begin();
         subnet4 != subnets_.end(); ++subnet4) {
415 416 417
        SubnetID subnet_id = (*subnet4)->getID();
        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "total-addresses"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
418

419 420
        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "assigned-addresses"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
421

422 423 424 425 426
        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "declined-addresses"));

        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "declined-reclaimed-addresses"));
427 428 429

        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "reclaimed-leases"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
430 431
    }
}
432

Tomek Mrugalski's avatar
Tomek Mrugalski committed
433 434 435
void
CfgSubnets4::updateStatistics() {
    using namespace isc::stats;
436

437 438 439 440 441 442 443 444 445 446 447
    StatsMgr& stats_mgr = StatsMgr::instance();
    for (Subnet4Collection::const_iterator subnet4 = subnets_.begin();
         subnet4 != subnets_.end(); ++subnet4) {
        SubnetID subnet_id = (*subnet4)->getID();

        stats_mgr.setValue(StatsMgr::
                           generateName("subnet", subnet_id, "total-addresses"),
                                        static_cast<int64_t>
                                        ((*subnet4)->getPoolCapacity(Lease::
                                                                     TYPE_V4)));
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
448

449 450
    // Only recount the stats if we have subnets.
    if (subnets_.begin() != subnets_.end()) {
451
            LeaseMgrFactory::instance().recountLeaseStats4();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
452 453
    }
}
454

455 456 457 458 459 460
ElementPtr
CfgSubnets4::toElement() const {
    ElementPtr result = Element::createList();
    // Iterate subnets
    for (Subnet4Collection::const_iterator subnet = subnets_.cbegin();
         subnet != subnets_.cend(); ++subnet) {
461
        result->add((*subnet)->toElement());
462 463 464 465
    }
    return (result);
}

466 467
} // end of namespace isc::dhcp
} // end of namespace isc