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 68
    for (auto other_subnet = other_subnets->begin();
         other_subnet != other_subnets->end();
69 70 71 72 73 74 75
         ++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.
76
            auto existing_subnet = *subnet_it;
77

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

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

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

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

100 101 102 103 104 105 106 107 108 109
        // 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.
110
                isc_throw(InvalidOperation, "Cannot assign subnet ID of "
111 112 113 114
                          << (*other_subnet)->getID()
                          << " to shared network: " << network_name
                          << ", network does not exist");
            }
115 116 117 118
        }
    }
}

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

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

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

188 189 190 191 192 193 194 195 196 197 198 199 200 201
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();
202
        if (!pref.first.isV6Zero()) {
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 229

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

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

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

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

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
264
            // If a subnet meets the client class criteria return it.
265
            if ((*subnet)->clientSupported(selector.client_classes_)) {
266 267 268 269 270 271 272 273 274 275
                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
276
    IOAddress address = IOAddress::IPV4_ZERO_ADDRESS();
277
    // If there is a giaddr, use it for subnet selection.
Francis Dupont's avatar
Francis Dupont committed
278
    if (!selector.giaddr_.isV4Zero()) {
279 280 281
        address = selector.giaddr_;

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

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

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

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

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

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

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

334 335 336 337 338 339 340
        Subnet4Ptr subnet_selected;

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

342 343 344 345 346 347 348 349 350 351
        } 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);
            }
352 353
        }

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

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

371 372 373 374 375 376 377 378 379 380 381 382 383
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());
}

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

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

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

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

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

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

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

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

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

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

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

438 439 440 441 442 443 444 445 446 447 448
    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
449

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

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

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