cfg_subnets4.cc 14.3 KB
Newer Older
1
// Copyright (C) 2014-2017 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
9
10
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/dhcpsrv_log.h>
11
#include <dhcpsrv/lease_mgr_factory.h>
12
#include <dhcpsrv/subnet_id.h>
13
14
#include <dhcpsrv/addr_utilities.h>
#include <asiolink/io_address.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
15
#include <stats/stats_mgr.h>
16
#include <sstream>
17
18

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

namespace isc {
namespace dhcp {

void
CfgSubnets4::add(const Subnet4Ptr& subnet) {
    /// @todo: Check that this new subnet does not cross boundaries of any
    /// other already defined subnet.
    if (isDuplicate(*subnet)) {
29
        isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv4 subnet '"
30
31
32
33
34
35
36
                  << subnet->getID() << "' is already in use");
    }
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
              .arg(subnet->toText());
    subnets_.push_back(subnet);
}

37
38
39
40
41
42
43
44
45
46
47
48
49
50
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();
51
        if (!pref.first.isV6Zero()) {
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

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

79
Subnet4Ptr
80
CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
81

82
83
84
85
86
87
    // First use RAI link select sub-option or subnet select option
    if (!selector.option_select_.isV4Zero()) {
        return (selectSubnet(selector.option_select_,
                             selector.client_classes_));
    }

88
89
    // If relayed message has been received, try to match the giaddr with the
    // relay address specified for a subnet. It is also possible that the relay
Andrei Pavel's avatar
Andrei Pavel committed
90
    // address will not match with any of the relay addresses across all
91
92
    // 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
93
    if (!selector.giaddr_.isV4Zero()) {
94
95
        for (Subnet4Collection::const_iterator subnet = subnets_.begin();
             subnet != subnets_.end(); ++subnet) {
96
97
98

            // Check if the giaddr is equal to the one defined for the subnet.
            if (selector.giaddr_ != (*subnet)->getRelayInfo().addr_) {
99
100
101
                continue;
            }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
102
            // If a subnet meets the client class criteria return it.
103
            if ((*subnet)->clientSupported(selector.client_classes_)) {
104
105
106
107
108
109
110
111
112
113
                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
114
    IOAddress address = IOAddress::IPV4_ZERO_ADDRESS();
115
    // If there is a giaddr, use it for subnet selection.
Francis Dupont's avatar
Francis Dupont committed
116
    if (!selector.giaddr_.isV4Zero()) {
117
118
119
        address = selector.giaddr_;

    // If it is a Renew or Rebind, use the ciaddr.
Francis Dupont's avatar
Francis Dupont committed
120
121
    } else if (!selector.ciaddr_.isV4Zero() &&
               !selector.local_address_.isV4Bcast()) {
122
123
124
        address = selector.ciaddr_;

    // If ciaddr is not specified, use the source address.
Francis Dupont's avatar
Francis Dupont committed
125
126
    } else if (!selector.remote_address_.isV4Zero() &&
               !selector.local_address_.isV4Bcast()) {
127
128
129
130
        address = selector.remote_address_;

    // If local interface name is known, use the local address on this
    // interface.
131
    } else if (!selector.iface_name_.empty()) {
132
        IfacePtr iface = IfaceMgr::instance().getIface(selector.iface_name_);
133
134
135
        // This should never happen in the real life. Hence we throw an
        // exception.
        if (iface == NULL) {
136
            isc_throw(isc::BadValue, "interface " << selector.iface_name_
137
138
139
                      << " doesn't exist and therefore it is impossible"
                      " to find a suitable subnet for its IPv4 address");
        }
140
141
142
143
144
145
146
147
148

        // 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);
149
150
151
152
        } else {
            // Let's try to get an address from the local interface and
            // try to match it to defined subnet.
            iface->getAddress4(address);
153
        }
154
155
156
    }

    // Unable to find a suitable address to use for subnet selection.
Francis Dupont's avatar
Francis Dupont committed
157
    if (address.isV4Zero()) {
158
159
160
161
        return (Subnet4Ptr());
    }

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

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
Subnet4Ptr
CfgSubnets4::selectSubnet(const std::string& iface,
                 const ClientClasses& client_classes) const {
    for (Subnet4Collection::const_iterator subnet = subnets_.begin();
         subnet != subnets_.end(); ++subnet) {

        // If there's no interface specified for this subnet, proceed to
        // the next subnet.
        if ((*subnet)->getIface().empty()) {
            continue;
        }

        // If it's specified, but does not match, proceed to the next
        // subnet.
        if ((*subnet)->getIface() != iface) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
184
        // If a subnet meets the client class criteria return it.
185
        if ((*subnet)->clientSupported(client_classes)) {
186
187
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                      DHCPSRV_CFGMGR_SUBNET4_IFACE)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
188
189
                .arg((*subnet)->toText())
                .arg(iface);
190
191
192
193
194
195
196
197
            return (*subnet);
        }
    }

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

198
Subnet4Ptr
199
CfgSubnets4::selectSubnet(const IOAddress& address,
200
                 const ClientClasses& client_classes) const {
201
202
    for (Subnet4Collection::const_iterator subnet = subnets_.begin();
         subnet != subnets_.end(); ++subnet) {
203

204
205
        // Address is in range for the subnet prefix, so return it.
        if (!(*subnet)->inRange(address)) {
206
207
208
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
209
        // If a subnet meets the client class criteria return it.
210
        if ((*subnet)->clientSupported(client_classes)) {
211
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET4_ADDR)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
212
213
                .arg((*subnet)->toText())
                .arg(address.toText());
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
            return (*subnet);
        }
    }

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

bool
CfgSubnets4::isDuplicate(const Subnet4& subnet) const {
    for (Subnet4Collection::const_iterator subnet_it = subnets_.begin();
         subnet_it != subnets_.end(); ++subnet_it) {
        if ((*subnet_it)->getID() == subnet.getID()) {
            return (true);
        }
    }
    return (false);
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
233
234
235
236
237
void
CfgSubnets4::removeStatistics() {
    using namespace isc::stats;

    // For each v4 subnet currently configured, remove the statistic.
238
    StatsMgr& stats_mgr = StatsMgr::instance();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
239
240
    for (Subnet4Collection::const_iterator subnet4 = subnets_.begin();
         subnet4 != subnets_.end(); ++subnet4) {
241
242
243
        SubnetID subnet_id = (*subnet4)->getID();
        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "total-addresses"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
244

245
246
        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "assigned-addresses"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
247

248
249
250
251
252
        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "declined-addresses"));

        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
                                             "declined-reclaimed-addresses"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
253
254
    }
}
255

Tomek Mrugalski's avatar
Tomek Mrugalski committed
256
257
258
void
CfgSubnets4::updateStatistics() {
    using namespace isc::stats;
259

260
261
262
263
264
265
266
267
268
269
270
    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
271

272
273
    // Only recount the stats if we have subnets.
    if (subnets_.begin() != subnets_.end()) {
274
            LeaseMgrFactory::instance().recountLeaseStats4();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
275
276
    }
}
277

278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
ElementPtr
CfgSubnets4::toElement() const {
    ElementPtr result = Element::createList();
    // Iterate subnets
    for (Subnet4Collection::const_iterator subnet = subnets_.cbegin();
         subnet != subnets_.cend(); ++subnet) {
        // Prepare the map
        ElementPtr map = Element::createMap();
        // Set subnet id
        SubnetID id = (*subnet)->getID();
        map->set("id", Element::create(static_cast<long long>(id)));
        // Set relay info
        const Subnet::RelayInfo& relay_info = (*subnet)->getRelayInfo();
        ElementPtr relay = Element::createMap();
        relay->set("ip-address", Element::create(relay_info.addr_.toText()));
        map->set("relay", relay);
        // Set subnet
        map->set("subnet", Element::create((*subnet)->toText()));
        // Set interface
        const std::string& iface = (*subnet)->getIface();
        map->set("interface", Element::create(iface));
        // Set renew-timer
        map->set("renew-timer",
                 Element::create(static_cast<long long>
                                 ((*subnet)->getT1().get())));
        // Set rebind-timer
        map->set("rebind-timer",
                 Element::create(static_cast<long long>
                                 ((*subnet)->getT2().get())));
        // Set valid-lifetime
        map->set("valid-lifetime",
                 Element::create(static_cast<long long>
                                 ((*subnet)->getValid().get())));
        // Set pools
        const PoolCollection& pools = (*subnet)->getPools(Lease::TYPE_V4);
        ElementPtr pool_list = Element::createList();
        for (PoolCollection::const_iterator pool = pools.cbegin();
             pool != pools.cend(); ++pool) {
316
            // Prepare the map for a pool (@todo move this code to pool.cc)
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
            ElementPtr pool_map = Element::createMap();
            // Set pool
            const IOAddress& first = (*pool)->getFirstAddress();
            const IOAddress& last = (*pool)->getLastAddress();
            std::string range = first.toText() + "-" + last.toText();
            // Try to output a prefix (vs a range)
            int prefix_len = prefixLengthFromRange(first, last);
            if (prefix_len >= 0) {
                std::ostringstream oss;
                oss << first.toText() << "/" << prefix_len;
                range = oss.str();
            }
            pool_map->set("pool", Element::create(range));
            // Set user-context
            ConstElementPtr context = (*pool)->getContext();
            if (!isNull(context)) {
                pool_map->set("user-context", context);
            }
335
            // Set pool options (not yet supported)
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
            // Push on the pool list
            pool_list->add(pool_map);
        }
        map->set("pools", pool_list);
        // Set host reservation-mode
        Subnet::HRMode hrmode = (*subnet)->getHostReservationMode();
        std::string mode;
        switch (hrmode) {
        case Subnet::HR_DISABLED:
            mode = "disabled";
            break;
        case Subnet::HR_OUT_OF_POOL:
            mode = "out-of-pool";
            break;
        case Subnet::HR_ALL:
            mode = "all";
            break;
        default:
            isc_throw(ToElementError,
                      "invalid host reservation mode: " << hrmode);
        }
        map->set("reservation-mode", Element::create(mode));
        // Set match-client-id
        map->set("match-client-id",
                 Element::create((*subnet)->getMatchClientId()));
        // Set next-server
        map->set("next-server",
                 Element::create((*subnet)->getSiaddr().toText()));
        // Set DHCP4o6
        const Cfg4o6& d4o6 = (*subnet)->get4o6();
366
        isc::data::merge(map, d4o6.toElement());
367
368
369
        // Set client-class
        const ClientClasses& cclasses = (*subnet)->getClientClasses();
        if (cclasses.size() > 1) {
370
            isc_throw(ToElementError, "client-class has too many items: "
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
                      << cclasses.size());
        } else if (!cclasses.empty()) {
            map->set("client-class", Element::create(*cclasses.cbegin()));
        }
        // Set options
        ConstCfgOptionPtr opts = (*subnet)->getCfgOption();
        map->set("option-data", opts->toElement());
        // Not supported: interface-id
        // Not supported: rapid-commit
        // Push on the list
        result->add(map);
    }
    return (result);
}

386
387
} // end of namespace isc::dhcp
} // end of namespace isc