/src/kea/src/lib/dhcpsrv/alloc_engine.cc
Line | Count | Source |
1 | | // Copyright (C) 2012-2026 Internet Systems Consortium, Inc. ("ISC") |
2 | | // |
3 | | // This Source Code Form is subject to the terms of the Mozilla Public |
4 | | // License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
6 | | |
7 | | #include <config.h> |
8 | | |
9 | | #include <asiolink/addr_utilities.h> |
10 | | #include <database/db_exceptions.h> |
11 | | #include <dhcp/dhcp6.h> |
12 | | #include <dhcp/pkt4.h> |
13 | | #include <dhcp/pkt6.h> |
14 | | #include <dhcp/option_int.h> |
15 | | #include <dhcp_ddns/ncr_msg.h> |
16 | | #include <dhcpsrv/alloc_engine.h> |
17 | | #include <dhcpsrv/alloc_engine_log.h> |
18 | | #include <dhcpsrv/cfgmgr.h> |
19 | | #include <dhcpsrv/dhcpsrv_exceptions.h> |
20 | | #include <dhcpsrv/dhcpsrv_log.h> |
21 | | #include <dhcpsrv/host_mgr.h> |
22 | | #include <dhcpsrv/host.h> |
23 | | #include <dhcpsrv/iterative_allocator.h> |
24 | | #include <dhcpsrv/lease_mgr_factory.h> |
25 | | #include <dhcpsrv/ncr_generator.h> |
26 | | #include <dhcpsrv/network.h> |
27 | | #include <dhcpsrv/resource_handler.h> |
28 | | #include <dhcpsrv/shared_network.h> |
29 | | #include <hooks/callout_handle.h> |
30 | | #include <hooks/hooks_manager.h> |
31 | | #include <dhcpsrv/callout_handle_store.h> |
32 | | #include <stats/stats_mgr.h> |
33 | | #include <util/encode/encode.h> |
34 | | #include <util/stopwatch.h> |
35 | | #include <hooks/server_hooks.h> |
36 | | |
37 | | #include <boost/foreach.hpp> |
38 | | #include <boost/make_shared.hpp> |
39 | | |
40 | | #include <algorithm> |
41 | | #include <sstream> |
42 | | #include <stdint.h> |
43 | | #include <string.h> |
44 | | #include <utility> |
45 | | #include <vector> |
46 | | |
47 | | using namespace isc::asiolink; |
48 | | using namespace isc::db; |
49 | | using namespace isc::dhcp; |
50 | | using namespace isc::dhcp_ddns; |
51 | | using namespace isc::hooks; |
52 | | using namespace isc::stats; |
53 | | using namespace isc::util; |
54 | | using namespace isc::data; |
55 | | namespace ph = std::placeholders; |
56 | | |
57 | | namespace { |
58 | | |
59 | | /// Structure that holds registered hook indexes |
60 | | struct AllocEngineHooks { |
61 | | int hook_index_lease4_select_; ///< index for "lease4_select" hook point |
62 | | int hook_index_lease4_renew_; ///< index for "lease4_renew" hook point |
63 | | int hook_index_lease4_expire_; ///< index for "lease4_expire" hook point |
64 | | int hook_index_lease4_recover_;///< index for "lease4_recover" hook point |
65 | | int hook_index_lease6_select_; ///< index for "lease6_select" hook point |
66 | | int hook_index_lease6_renew_; ///< index for "lease6_renew" hook point |
67 | | int hook_index_lease6_rebind_; ///< index for "lease6_rebind" hook point |
68 | | int hook_index_lease6_expire_; ///< index for "lease6_expire" hook point |
69 | | int hook_index_lease6_recover_;///< index for "lease6_recover" hook point |
70 | | |
71 | | /// Constructor that registers hook points for AllocationEngine |
72 | 40 | AllocEngineHooks() { |
73 | 40 | hook_index_lease4_select_ = HooksManager::registerHook("lease4_select"); |
74 | 40 | hook_index_lease4_renew_ = HooksManager::registerHook("lease4_renew"); |
75 | 40 | hook_index_lease4_expire_ = HooksManager::registerHook("lease4_expire"); |
76 | 40 | hook_index_lease4_recover_= HooksManager::registerHook("lease4_recover"); |
77 | 40 | hook_index_lease6_select_ = HooksManager::registerHook("lease6_select"); |
78 | 40 | hook_index_lease6_renew_ = HooksManager::registerHook("lease6_renew"); |
79 | 40 | hook_index_lease6_rebind_ = HooksManager::registerHook("lease6_rebind"); |
80 | 40 | hook_index_lease6_expire_ = HooksManager::registerHook("lease6_expire"); |
81 | 40 | hook_index_lease6_recover_= HooksManager::registerHook("lease6_recover"); |
82 | 40 | } |
83 | | }; |
84 | | |
85 | | // Declare a Hooks object. As this is outside any function or method, it |
86 | | // will be instantiated (and the constructor run) when the module is loaded. |
87 | | // As a result, the hook indexes will be defined before any method in this |
88 | | // module is called. |
89 | | AllocEngineHooks Hooks; |
90 | | |
91 | | } // namespace |
92 | | |
93 | | namespace isc { |
94 | | namespace dhcp { |
95 | | |
96 | | AllocEngine::AllocEngine(uint128_t const& attempts) |
97 | 52.5k | : attempts_(attempts), incomplete_v4_reclamations_(0), |
98 | 52.5k | incomplete_v6_reclamations_(0) { |
99 | | |
100 | | // Register hook points |
101 | 52.5k | hook_index_lease4_select_ = Hooks.hook_index_lease4_select_; |
102 | 52.5k | hook_index_lease6_select_ = Hooks.hook_index_lease6_select_; |
103 | 52.5k | } |
104 | | |
105 | | } // end of namespace isc::dhcp |
106 | | } // end of namespace isc |
107 | | |
108 | | namespace { |
109 | | |
110 | | /// @brief Find reservations for the given subnet and address or delegated prefix. |
111 | | /// |
112 | | /// @param subnet_id Subnet identifier for which the reservations should |
113 | | /// be found. |
114 | | /// @param address Address or delegated prefix for which the reservations |
115 | | /// should be found. |
116 | | /// |
117 | | /// @return Collection of host reservations. |
118 | | ConstHostCollection |
119 | 0 | getIPv6Resrv(const SubnetID& subnet_id, const IOAddress& address) { |
120 | 0 | ConstHostCollection reserved; |
121 | | // The global parameter ip-reservations-unique controls whether it is allowed |
122 | | // to specify multiple reservations for the same IP address or delegated prefix |
123 | | // or IP reservations must be unique. Some host backends do not support the |
124 | | // former, thus we can't always use getAll6 calls to get the reservations |
125 | | // for the given IP. When we're in the default mode, when IP reservations |
126 | | // are unique, we should call get6 (supported by all backends). If we're in |
127 | | // the mode in which non-unique reservations are allowed the backends which |
128 | | // don't support it are not used and we can safely call getAll6. |
129 | 0 | if (CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique()) { |
130 | 0 | try { |
131 | | // Reservations are unique. It is safe to call get6 to get the unique host. |
132 | 0 | auto host = HostMgr::instance().get6(subnet_id, address); |
133 | 0 | if (host) { |
134 | 0 | reserved.push_back(host); |
135 | 0 | } |
136 | 0 | } catch (const MultipleRecords& ex) { |
137 | 0 | LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_DETECTED) |
138 | 0 | .arg(address) |
139 | 0 | .arg(subnet_id) |
140 | 0 | .arg(ex.what()); |
141 | 0 | throw; |
142 | 0 | } |
143 | 0 | } else { |
144 | 0 | auto hosts = HostMgr::instance().getAll6(subnet_id, address); |
145 | 0 | reserved.insert(reserved.end(), hosts.begin(), hosts.end()); |
146 | 0 | } |
147 | 0 | return (reserved); |
148 | 0 | } |
149 | | |
150 | | /// @brief Checks if the specified address belongs to one of the subnets |
151 | | /// within a shared network. |
152 | | /// |
153 | | /// @param ctx Client context. Current subnet may be modified by this |
154 | | /// function when it belongs to a shared network. |
155 | | /// @param lease_type Type of the lease. |
156 | | /// @param address IPv6 address or prefix to be checked. |
157 | | /// @param check_subnet if true only subnets are checked else both subnets |
158 | | /// and pools are checked |
159 | | /// |
160 | | /// @return true if address belongs to a pool in a selected subnet or in |
161 | | /// a pool within any of the subnets belonging to the current shared network. |
162 | | bool |
163 | | inAllowedPool(AllocEngine::ClientContext6& ctx, const Lease::Type& lease_type, |
164 | 0 | const IOAddress& address, bool check_subnet) { |
165 | | // If the subnet belongs to a shared network we will be iterating |
166 | | // over the subnets that belong to this shared network. |
167 | 0 | ConstSubnet6Ptr current_subnet = ctx.subnet_; |
168 | 0 | auto const& classes = ctx.query_->getClasses(); |
169 | |
|
170 | 0 | while (current_subnet) { |
171 | 0 | if (current_subnet->clientSupported(classes)) { |
172 | 0 | if (check_subnet) { |
173 | 0 | if (current_subnet->inPool(lease_type, address)) { |
174 | 0 | return (true); |
175 | 0 | } |
176 | 0 | } else { |
177 | 0 | if (current_subnet->inPool(lease_type, address, classes)) { |
178 | 0 | return (true); |
179 | 0 | } |
180 | 0 | } |
181 | 0 | } |
182 | | |
183 | 0 | current_subnet = current_subnet->getNextSubnet(ctx.subnet_); |
184 | 0 | } |
185 | | |
186 | 0 | return (false); |
187 | 0 | } |
188 | | |
189 | | } |
190 | | |
191 | | // ########################################################################## |
192 | | // # DHCPv6 lease allocation code starts here. |
193 | | // ########################################################################## |
194 | | |
195 | | namespace isc { |
196 | | namespace dhcp { |
197 | | |
198 | | AllocEngine::ClientContext6::ClientContext6() |
199 | 6.75k | : query_(), fake_allocation_(false), |
200 | 6.75k | early_global_reservations_lookup_(false), subnet_(), host_subnet_(), |
201 | 6.75k | duid_(), hwaddr_(), host_identifiers_(), hosts_(), |
202 | 6.75k | fwd_dns_update_(false), rev_dns_update_(false), hostname_(), |
203 | 6.75k | callout_handle_(), ias_(), ddns_params_() { |
204 | 6.75k | } |
205 | | |
206 | | AllocEngine::ClientContext6::ClientContext6(const ConstSubnet6Ptr& subnet, |
207 | | const DuidPtr& duid, |
208 | | const bool fwd_dns, |
209 | | const bool rev_dns, |
210 | | const std::string& hostname, |
211 | | const bool fake_allocation, |
212 | | const Pkt6Ptr& query, |
213 | | const CalloutHandlePtr& callout_handle) |
214 | 0 | : query_(query), fake_allocation_(fake_allocation), |
215 | 0 | early_global_reservations_lookup_(false), subnet_(subnet), |
216 | 0 | duid_(duid), hwaddr_(), host_identifiers_(), hosts_(), |
217 | 0 | fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns), hostname_(hostname), |
218 | 0 | callout_handle_(callout_handle), allocated_resources_(), new_leases_(), |
219 | 0 | ias_(), ddns_params_() { |
220 | | |
221 | | // Initialize host identifiers. |
222 | 0 | if (duid) { |
223 | 0 | addHostIdentifier(Host::IDENT_DUID, duid->getDuid()); |
224 | 0 | } |
225 | 0 | } |
226 | | |
227 | | AllocEngine::ClientContext6::IAContext::IAContext() |
228 | 0 | : iaid_(0), type_(Lease::TYPE_NA), hints_(), old_leases_(), |
229 | 0 | changed_leases_(),reused_leases_(), new_resources_(), ia_rsp_() { |
230 | 0 | } |
231 | | |
232 | | void |
233 | | AllocEngine::ClientContext6:: |
234 | | IAContext::addHint(const asiolink::IOAddress& prefix, |
235 | | const uint8_t prefix_len, |
236 | | const uint32_t preferred, |
237 | 0 | const uint32_t valid) { |
238 | 0 | hints_.push_back(Resource(prefix, prefix_len, preferred, valid)); |
239 | 0 | } |
240 | | |
241 | | void |
242 | | AllocEngine::ClientContext6:: |
243 | 0 | IAContext::addHint(const Option6IAAddrPtr& iaaddr) { |
244 | 0 | if (!iaaddr) { |
245 | 0 | isc_throw(BadValue, "IAADDR option pointer is null."); |
246 | 0 | } |
247 | 0 | addHint(iaaddr->getAddress(), 128, |
248 | 0 | iaaddr->getPreferred(), iaaddr->getValid()); |
249 | 0 | } |
250 | | |
251 | | void |
252 | | AllocEngine::ClientContext6:: |
253 | 0 | IAContext::addHint(const Option6IAPrefixPtr& iaprefix) { |
254 | 0 | if (!iaprefix) { |
255 | 0 | isc_throw(BadValue, "IAPREFIX option pointer is null."); |
256 | 0 | } |
257 | 0 | addHint(iaprefix->getAddress(), iaprefix->getLength(), |
258 | 0 | iaprefix->getPreferred(), iaprefix->getValid()); |
259 | 0 | } |
260 | | |
261 | | void |
262 | | AllocEngine::ClientContext6:: |
263 | | IAContext::addNewResource(const asiolink::IOAddress& prefix, |
264 | 0 | const uint8_t prefix_len) { |
265 | 0 | static_cast<void>(new_resources_.insert(Resource(prefix, prefix_len))); |
266 | 0 | } |
267 | | |
268 | | bool |
269 | | AllocEngine::ClientContext6:: |
270 | | IAContext::isNewResource(const asiolink::IOAddress& prefix, |
271 | 0 | const uint8_t prefix_len) const { |
272 | 0 | return (static_cast<bool>(new_resources_.count(Resource(prefix, |
273 | 0 | prefix_len)))); |
274 | 0 | } |
275 | | |
276 | | void |
277 | | AllocEngine::ClientContext6:: |
278 | | addAllocatedResource(const asiolink::IOAddress& prefix, |
279 | 0 | const uint8_t prefix_len) { |
280 | 0 | static_cast<void>(allocated_resources_.insert(Resource(prefix, |
281 | 0 | prefix_len))); |
282 | 0 | } |
283 | | |
284 | | bool |
285 | | AllocEngine::ClientContext6:: |
286 | 0 | isAllocated(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const { |
287 | 0 | return (static_cast<bool> |
288 | 0 | (allocated_resources_.count(Resource(prefix, prefix_len)))); |
289 | 0 | } |
290 | | |
291 | | ConstHostPtr |
292 | 2.68k | AllocEngine::ClientContext6::currentHost() const { |
293 | 2.68k | ConstSubnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_; |
294 | 2.68k | if (subnet && subnet->getReservationsInSubnet()) { |
295 | 0 | auto host = hosts_.find(subnet->getID()); |
296 | 0 | if (host != hosts_.cend()) { |
297 | 0 | return (host->second); |
298 | 0 | } |
299 | 0 | } |
300 | | |
301 | 2.68k | return (globalHost()); |
302 | 2.68k | } |
303 | | |
304 | | ConstHostPtr |
305 | 4.84k | AllocEngine::ClientContext6::globalHost() const { |
306 | 4.84k | ConstSubnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_; |
307 | 4.84k | if (subnet && subnet_->getReservationsGlobal()) { |
308 | 0 | auto host = hosts_.find(SUBNET_ID_GLOBAL); |
309 | 0 | if (host != hosts_.cend()) { |
310 | 0 | return (host->second); |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | 4.84k | return (ConstHostPtr()); |
315 | 4.84k | } |
316 | | |
317 | | bool |
318 | 0 | AllocEngine::ClientContext6::hasGlobalReservation(const IPv6Resrv& resv) const { |
319 | 0 | ConstHostPtr ghost = globalHost(); |
320 | 0 | return (ghost && ghost->hasReservation(resv)); |
321 | 0 | } |
322 | | |
323 | | DdnsParamsPtr |
324 | 1.45k | AllocEngine::ClientContext6::getDdnsParams() { |
325 | | // We already have it return it unless the context subnet has changed. |
326 | 1.45k | if (ddns_params_ && subnet_ && (subnet_->getID() == ddns_params_->getSubnetId())) { |
327 | 0 | return (ddns_params_); |
328 | 0 | } |
329 | | |
330 | | // Doesn't exist yet or is stale, (re)create it. |
331 | 1.45k | if (subnet_) { |
332 | 0 | ddns_params_ = CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_); |
333 | 0 | return (ddns_params_); |
334 | 0 | } |
335 | | |
336 | | // Asked for it without a subnet? This case really shouldn't occur but |
337 | | // for now let's return an instance with default values. |
338 | 1.45k | return (DdnsParamsPtr(new DdnsParams())); |
339 | 1.45k | } |
340 | | |
341 | | void |
342 | 0 | AllocEngine::findReservation(ClientContext6& ctx) { |
343 | | // If there is no subnet, there is nothing to do. |
344 | 0 | if (!ctx.subnet_) { |
345 | 0 | return; |
346 | 0 | } |
347 | | |
348 | 0 | auto subnet = ctx.subnet_; |
349 | | |
350 | | // If already done just return. |
351 | 0 | if (ctx.early_global_reservations_lookup_ && |
352 | 0 | !subnet->getReservationsInSubnet()) { |
353 | 0 | return; |
354 | 0 | } |
355 | | |
356 | | // @todo: This code can be trivially optimized. |
357 | 0 | if (!ctx.early_global_reservations_lookup_ && |
358 | 0 | subnet->getReservationsGlobal()) { |
359 | 0 | ConstHostPtr ghost = findGlobalReservation(ctx); |
360 | 0 | if (ghost) { |
361 | 0 | ctx.hosts_[SUBNET_ID_GLOBAL] = ghost; |
362 | | |
363 | | // If we had only to fetch global reservations it is done. |
364 | 0 | if (!subnet->getReservationsInSubnet()) { |
365 | 0 | return; |
366 | 0 | } |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | 0 | std::map<SubnetID, ConstHostPtr> host_map; |
371 | 0 | SharedNetwork6Ptr network; |
372 | 0 | subnet->getSharedNetwork(network); |
373 | | |
374 | | // If the subnet belongs to a shared network it is usually going to be |
375 | | // more efficient to make a query for all reservations for a particular |
376 | | // client rather than a query for each subnet within this shared network. |
377 | | // The only case when it is going to be less efficient is when there are |
378 | | // more host identifier types in use than subnets within a shared network. |
379 | | // As it breaks RADIUS use of host caching this can be disabled by the |
380 | | // host manager. |
381 | 0 | const bool use_single_query = network && |
382 | 0 | !HostMgr::instance().getDisableSingleQuery() && |
383 | 0 | (network->getAllSubnets()->size() > ctx.host_identifiers_.size()); |
384 | |
|
385 | 0 | if (use_single_query) { |
386 | 0 | for (const IdentifierPair& id_pair : ctx.host_identifiers_) { |
387 | 0 | ConstHostCollection hosts = HostMgr::instance().getAll(id_pair.first, |
388 | 0 | &id_pair.second[0], |
389 | 0 | id_pair.second.size()); |
390 | | // Store the hosts in the temporary map, because some hosts may |
391 | | // belong to subnets outside of the shared network. We'll need |
392 | | // to eliminate them. |
393 | 0 | for (auto const& host : hosts) { |
394 | 0 | if (host->getIPv6SubnetID() != SUBNET_ID_GLOBAL) { |
395 | 0 | host_map[host->getIPv6SubnetID()] = host; |
396 | 0 | } |
397 | 0 | } |
398 | 0 | } |
399 | 0 | } |
400 | |
|
401 | 0 | auto const& classes = ctx.query_->getClasses(); |
402 | | |
403 | | // We can only search for the reservation if a subnet has been selected. |
404 | 0 | while (subnet) { |
405 | | |
406 | | // Only makes sense to get reservations if the client has access |
407 | | // to the class and host reservations are enabled for this subnet. |
408 | 0 | if (subnet->clientSupported(classes) && subnet->getReservationsInSubnet()) { |
409 | | // Iterate over configured identifiers in the order of preference |
410 | | // and try to use each of them to search for the reservations. |
411 | 0 | if (use_single_query) { |
412 | 0 | if (host_map.count(subnet->getID()) > 0) { |
413 | 0 | ctx.hosts_[subnet->getID()] = host_map[subnet->getID()]; |
414 | 0 | } |
415 | 0 | } else { |
416 | 0 | for (const IdentifierPair& id_pair : ctx.host_identifiers_) { |
417 | | // Attempt to find a host using a specified identifier. |
418 | 0 | ConstHostPtr host = HostMgr::instance().get6(subnet->getID(), |
419 | 0 | id_pair.first, |
420 | 0 | &id_pair.second[0], |
421 | 0 | id_pair.second.size()); |
422 | | // If we found matching host for this subnet. |
423 | 0 | if (host) { |
424 | 0 | ctx.hosts_[subnet->getID()] = host; |
425 | 0 | break; |
426 | 0 | } |
427 | 0 | } |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | | // We need to get to the next subnet if this is a shared network. If it |
432 | | // is not (a plain subnet), getNextSubnet will return NULL and we're |
433 | | // done here. |
434 | 0 | subnet = subnet->getNextSubnet(ctx.subnet_, classes); |
435 | 0 | } |
436 | | |
437 | | // The hosts can be used by the server to return reserved options to |
438 | | // the DHCP client. Such options must be encapsulated (i.e., they must |
439 | | // include suboptions). |
440 | 0 | for (auto const& host : ctx.hosts_) { |
441 | 0 | host.second->encapsulateOptions(); |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | ConstHostPtr |
446 | 0 | AllocEngine::findGlobalReservation(ClientContext6& ctx) { |
447 | 0 | ConstHostPtr host; |
448 | 0 | for (const IdentifierPair& id_pair : ctx.host_identifiers_) { |
449 | | // Attempt to find a host using a specified identifier. |
450 | 0 | host = HostMgr::instance().get6(SUBNET_ID_GLOBAL, id_pair.first, |
451 | 0 | &id_pair.second[0], id_pair.second.size()); |
452 | | |
453 | | // If we found matching global host we're done. |
454 | 0 | if (host) { |
455 | 0 | break; |
456 | 0 | } |
457 | 0 | } |
458 | |
|
459 | 0 | return (host); |
460 | 0 | } |
461 | | |
462 | | Lease6Collection |
463 | 0 | AllocEngine::allocateLeases6(ClientContext6& ctx) { |
464 | |
|
465 | 0 | try { |
466 | 0 | if (!ctx.subnet_) { |
467 | 0 | isc_throw(InvalidOperation, "Subnet is required for IPv6 lease allocation"); |
468 | 0 | } else |
469 | 0 | if (!ctx.duid_) { |
470 | 0 | isc_throw(InvalidOperation, "DUID is mandatory for IPv6 lease allocation"); |
471 | 0 | } |
472 | | |
473 | | // Check if there are existing leases for that shared network and |
474 | | // DUID/IAID. |
475 | 0 | ConstSubnet6Ptr subnet = ctx.subnet_; |
476 | 0 | Lease6Collection all_leases = |
477 | 0 | LeaseMgrFactory::instance().getLeases6(ctx.currentIA().type_, |
478 | 0 | *ctx.duid_, |
479 | 0 | ctx.currentIA().iaid_); |
480 | | |
481 | | // Iterate over the leases and eliminate those that are outside of |
482 | | // our shared network or registered. |
483 | 0 | Lease6Collection leases; |
484 | 0 | while (subnet) { |
485 | 0 | for (auto const& l : all_leases) { |
486 | 0 | if (((l)->state_ != Lease::STATE_REGISTERED) && |
487 | 0 | ((l)->subnet_id_ == subnet->getID())) { |
488 | 0 | leases.push_back(l); |
489 | 0 | } |
490 | 0 | } |
491 | |
|
492 | 0 | subnet = subnet->getNextSubnet(ctx.subnet_); |
493 | 0 | } |
494 | | |
495 | | // Now do the checks: |
496 | | // Case 1. if there are no leases, and there are reservations... |
497 | | // 1.1. are the reserved addresses are used by someone else? |
498 | | // yes: we have a problem |
499 | | // no: assign them => done |
500 | | // Case 2. if there are leases and there are no reservations... |
501 | | // 2.1 are the leases reserved for someone else? |
502 | | // yes: release them, assign something else |
503 | | // no: renew them => done |
504 | | // Case 3. if there are leases and there are reservations... |
505 | | // 3.1 are the leases matching reservations? |
506 | | // yes: renew them => done |
507 | | // no: release existing leases, assign new ones based on reservations |
508 | | // Case 4/catch-all. if there are no leases and no reservations... |
509 | | // assign new leases |
510 | | |
511 | | // Case 1: There are no leases and there's a reservation for this host. |
512 | 0 | if (leases.empty() && !ctx.hosts_.empty()) { |
513 | |
|
514 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
515 | 0 | ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR) |
516 | 0 | .arg(ctx.query_->getLabel()); |
517 | | |
518 | | // Try to allocate leases that match reservations. Typically this will |
519 | | // succeed, except cases where the reserved addresses are used by |
520 | | // someone else. |
521 | 0 | allocateReservedLeases6(ctx, leases); |
522 | |
|
523 | 0 | leases = updateLeaseData(ctx, leases); |
524 | | |
525 | | // If not, we'll need to continue and will eventually fall into case 4: |
526 | | // getting a regular lease. That could happen when we're processing |
527 | | // request from client X, there's a reserved address A for X, but |
528 | | // A is currently used by client Y. We can't immediately reassign A |
529 | | // from X to Y, because Y keeps using it, so X would send Decline right |
530 | | // away. Need to wait till Y renews, then we can release A, so it |
531 | | // will become available for X. |
532 | | |
533 | | // Case 2: There are existing leases and there are no reservations. |
534 | | // |
535 | | // There is at least one lease for this client and there are no reservations. |
536 | | // We will return these leases for the client, but we may need to update |
537 | | // FQDN information. |
538 | 0 | } else if (!leases.empty() && ctx.hosts_.empty()) { |
539 | |
|
540 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
541 | 0 | ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR) |
542 | 0 | .arg(ctx.query_->getLabel()); |
543 | | |
544 | | // Check if the existing leases are reserved for someone else. |
545 | | // If they're not, we're ok to keep using them. |
546 | 0 | removeNonmatchingReservedLeases6(ctx, leases); |
547 | |
|
548 | 0 | leases = updateLeaseData(ctx, leases); |
549 | | |
550 | | // If leases are empty at this stage, it means that we used to have |
551 | | // leases for this client, but we checked and those leases are reserved |
552 | | // for someone else, so we lost them. We will need to continue and |
553 | | // will finally end up in case 4 (no leases, no reservations), so we'll |
554 | | // assign something new. |
555 | | |
556 | | // Case 3: There are leases and there are reservations. |
557 | 0 | } else if (!leases.empty() && !ctx.hosts_.empty()) { |
558 | |
|
559 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
560 | 0 | ALLOC_ENGINE_V6_ALLOC_LEASES_HR) |
561 | 0 | .arg(ctx.query_->getLabel()); |
562 | | |
563 | | // First, check if have leases matching reservations, and add new |
564 | | // leases if we don't have them. |
565 | 0 | allocateReservedLeases6(ctx, leases); |
566 | | |
567 | | // leases now contain both existing and new leases that were created |
568 | | // from reservations. |
569 | | |
570 | | // Second, let's remove leases that are reserved for someone else. |
571 | | // This applies to any existing leases. This will not happen frequently, |
572 | | // but it may happen with the following chain of events: |
573 | | // 1. client A gets address X; |
574 | | // 2. reservation for client B for address X is made by a administrator; |
575 | | // 3. client A reboots |
576 | | // 4. client A requests the address (X) he got previously |
577 | 0 | removeNonmatchingReservedLeases6(ctx, leases); |
578 | | |
579 | | // leases now contain existing and new leases, but we removed those |
580 | | // leases that are reserved for someone else (non-matching reserved). |
581 | | |
582 | | // There's one more check to do. Let's remove leases that are not |
583 | | // matching reservations, i.e. if client X has address A, but there's |
584 | | // a reservation for address B, we should release A and reassign B. |
585 | | // Caveat: do this only if we have at least one reserved address. |
586 | 0 | removeNonreservedLeases6(ctx, leases); |
587 | | |
588 | | // All checks are done. Let's hope we have some leases left. |
589 | | |
590 | | // Update any leases we have left. |
591 | 0 | leases = updateLeaseData(ctx, leases); |
592 | | |
593 | | // If we don't have any leases at this stage, it means that we hit |
594 | | // one of the following cases: |
595 | | // - we have a reservation, but it's not for this IAID/ia-type and |
596 | | // we had to return the address we were using |
597 | | // - we have a reservation for this iaid/ia-type, but the reserved |
598 | | // address is currently used by someone else. We can't assign it |
599 | | // yet. |
600 | | // - we had an address, but we just discovered that it's reserved for |
601 | | // someone else, so we released it. |
602 | 0 | } |
603 | |
|
604 | 0 | if (leases.empty()) { |
605 | | // Case 4/catch-all: One of the following is true: |
606 | | // - we don't have leases and there are no reservations |
607 | | // - we used to have leases, but we lost them, because they are now |
608 | | // reserved for someone else |
609 | | // - we have a reservation, but it is not usable yet, because the address |
610 | | // is still used by someone else |
611 | | // |
612 | | // In any case, we need to go through normal lease assignment process |
613 | | // for now. This is also a catch-all or last resort approach, when we |
614 | | // couldn't find any reservations (or couldn't use them). |
615 | |
|
616 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
617 | 0 | ALLOC_ENGINE_V6_ALLOC_UNRESERVED) |
618 | 0 | .arg(ctx.query_->getLabel()); |
619 | |
|
620 | 0 | leases = allocateUnreservedLeases6(ctx); |
621 | 0 | } |
622 | |
|
623 | 0 | if (!leases.empty()) { |
624 | | // If there are any leases allocated, let's store in them in the |
625 | | // IA context so as they are available when we process subsequent |
626 | | // IAs. |
627 | 0 | for (auto const& lease : leases) { |
628 | 0 | ctx.addAllocatedResource(lease->addr_, lease->prefixlen_); |
629 | 0 | ctx.new_leases_.push_back(lease); |
630 | 0 | } |
631 | 0 | return (leases); |
632 | 0 | } |
633 | |
|
634 | 0 | } catch (const NoSuchLease& e) { |
635 | 0 | isc_throw(NoSuchLease, "detected data race in AllocEngine::allocateLeases6: " << e.what()); |
636 | |
|
637 | 0 | } catch (const isc::Exception& e) { |
638 | | |
639 | | // Some other error, return an empty lease. |
640 | 0 | LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_ERROR) |
641 | 0 | .arg(ctx.query_->getLabel()) |
642 | 0 | .arg(e.what()); |
643 | 0 | } |
644 | | |
645 | 0 | return (Lease6Collection()); |
646 | 0 | } |
647 | | |
648 | | Lease6Collection |
649 | 0 | AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { |
650 | |
|
651 | 0 | Lease6Collection leases; |
652 | |
|
653 | 0 | IOAddress hint = IOAddress::IPV6_ZERO_ADDRESS(); |
654 | 0 | uint8_t hint_prefix_length = 128; |
655 | 0 | if (!ctx.currentIA().hints_.empty()) { |
656 | | /// @todo: We support only one hint for now |
657 | 0 | hint = ctx.currentIA().hints_[0].getAddress(); |
658 | 0 | hint_prefix_length = ctx.currentIA().hints_[0].getPrefixLength(); |
659 | 0 | } |
660 | |
|
661 | 0 | ConstSubnet6Ptr original_subnet = ctx.subnet_; |
662 | |
|
663 | 0 | ConstSubnet6Ptr subnet = original_subnet; |
664 | |
|
665 | 0 | SharedNetwork6Ptr network; |
666 | |
|
667 | 0 | uint64_t total_attempts = 0; |
668 | | |
669 | | // The following counter tracks the number of subnets with matching client |
670 | | // classes from which the allocation engine attempted to assign leases. |
671 | 0 | uint64_t subnets_with_unavail_leases = 0; |
672 | | // The following counter tracks the number of subnets in which there were |
673 | | // no matching pools for the client. |
674 | 0 | uint64_t subnets_with_unavail_pools = 0; |
675 | |
|
676 | 0 | CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE; |
677 | | |
678 | | // In the case of PDs, the allocation engine will try to match pools with |
679 | | // the delegated prefix length matching the one provided in the hint. If the |
680 | | // hint does not provide a preferred delegated prefix length (value is 0), |
681 | | // the allocation engine will match any pool (any greater delegated prefix |
682 | | // length pool). The match type for the pools is ignored for non PDs. |
683 | 0 | Lease6Ptr hint_lease; |
684 | 0 | bool search_hint_lease = true; |
685 | 0 | Allocator::PrefixLenMatchType prefix_length_match = Allocator::PREFIX_LEN_EQUAL; |
686 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_PD) { |
687 | | // If the hint has a value of 128, the code might be broken as the hint |
688 | | // was added with the default value 128 for prefix_len by the addHint |
689 | | // function instead of 0. However 128 is not a valid value anyway so it |
690 | | // is reset to 0 (use any delegated prefix length available). |
691 | 0 | if (hint_prefix_length == 128) { |
692 | 0 | hint_prefix_length = 0; |
693 | 0 | } |
694 | 0 | if (!hint_prefix_length) { |
695 | 0 | prefix_length_match = Allocator::PREFIX_LEN_HIGHER; |
696 | 0 | } |
697 | 0 | } |
698 | | |
699 | | // Try the first allocation using PREFIX_LEN_EQUAL (or in case of PDs, |
700 | | // PREFIX_LEN_HIGHER when there is no valid delegated prefix length in the |
701 | | // provided hint) |
702 | 0 | Lease6Ptr lease = allocateBestMatch(ctx, hint_lease, search_hint_lease, |
703 | 0 | hint, hint_prefix_length, subnet, |
704 | 0 | network, total_attempts, |
705 | 0 | subnets_with_unavail_leases, |
706 | 0 | subnets_with_unavail_pools, |
707 | 0 | callout_status, prefix_length_match); |
708 | | |
709 | | // Try the second allocation using PREFIX_LEN_LOWER only for PDs if the |
710 | | // first allocation using PREFIX_LEN_EQUAL failed (there was a specific |
711 | | // delegated prefix length hint requested). |
712 | 0 | if (!lease && ctx.currentIA().type_ == Lease::TYPE_PD && |
713 | 0 | prefix_length_match == Allocator::PREFIX_LEN_EQUAL) { |
714 | 0 | prefix_length_match = Allocator::PREFIX_LEN_LOWER; |
715 | 0 | lease = allocateBestMatch(ctx, hint_lease, search_hint_lease, hint, |
716 | 0 | hint_prefix_length, subnet, network, |
717 | 0 | total_attempts, subnets_with_unavail_leases, |
718 | 0 | subnets_with_unavail_pools, callout_status, |
719 | 0 | prefix_length_match); |
720 | 0 | } |
721 | | |
722 | | // Try the third allocation using PREFIX_LEN_HIGHER only for PDs if the |
723 | | // second allocation using PREFIX_LEN_LOWER failed (there was a specific |
724 | | // delegated prefix length hint requested). |
725 | 0 | if (!lease && ctx.currentIA().type_ == Lease::TYPE_PD && |
726 | 0 | prefix_length_match == Allocator::PREFIX_LEN_LOWER) { |
727 | 0 | prefix_length_match = Allocator::PREFIX_LEN_HIGHER; |
728 | 0 | lease = allocateBestMatch(ctx, hint_lease, search_hint_lease, hint, |
729 | 0 | hint_prefix_length, subnet, network, |
730 | 0 | total_attempts, subnets_with_unavail_leases, |
731 | 0 | subnets_with_unavail_pools, callout_status, |
732 | 0 | prefix_length_match); |
733 | 0 | } |
734 | |
|
735 | 0 | if (lease) { |
736 | 0 | leases.push_back(lease); |
737 | 0 | return (leases); |
738 | 0 | } |
739 | | |
740 | 0 | auto const& classes = ctx.query_->getClasses(); |
741 | |
|
742 | 0 | if (network) { |
743 | | // The client is in the shared network. Let's log the high level message |
744 | | // indicating which shared network the client belongs to. |
745 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_SHARED_NETWORK) |
746 | 0 | .arg(ctx.query_->getLabel()) |
747 | 0 | .arg(network->getName()) |
748 | 0 | .arg(subnets_with_unavail_leases) |
749 | 0 | .arg(subnets_with_unavail_pools); |
750 | 0 | StatsMgr::instance().addValue("v6-allocation-fail-shared-network", |
751 | 0 | static_cast<int64_t>(1)); |
752 | 0 | StatsMgr::instance().addValue( |
753 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
754 | 0 | "v6-allocation-fail-shared-network"), |
755 | 0 | static_cast<int64_t>(1)); |
756 | 0 | } else { |
757 | | // The client is not connected to a shared network. It is connected |
758 | | // to a subnet. Let's log the ID of that subnet. |
759 | 0 | std::string shared_network = ctx.subnet_->getSharedNetworkName(); |
760 | 0 | if (shared_network.empty()) { |
761 | 0 | shared_network = "(none)"; |
762 | 0 | } |
763 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_SUBNET) |
764 | 0 | .arg(ctx.query_->getLabel()) |
765 | 0 | .arg(ctx.subnet_->toText()) |
766 | 0 | .arg(ctx.subnet_->getID()) |
767 | 0 | .arg(shared_network); |
768 | 0 | StatsMgr::instance().addValue("v6-allocation-fail-subnet", |
769 | 0 | static_cast<int64_t>(1)); |
770 | 0 | StatsMgr::instance().addValue( |
771 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
772 | 0 | "v6-allocation-fail-subnet"), |
773 | 0 | static_cast<int64_t>(1)); |
774 | 0 | } |
775 | 0 | if (total_attempts == 0) { |
776 | | // In this case, it seems that none of the pools in the subnets could |
777 | | // be used for that client, both in case the client is connected to |
778 | | // a shared network or to a single subnet. Apparently, the client was |
779 | | // rejected to use the pools because of the client classes' mismatch. |
780 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_NO_POOLS) |
781 | 0 | .arg(ctx.query_->getLabel()); |
782 | 0 | StatsMgr::instance().addValue("v6-allocation-fail-no-pools", |
783 | 0 | static_cast<int64_t>(1)); |
784 | 0 | StatsMgr::instance().addValue( |
785 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
786 | 0 | "v6-allocation-fail-no-pools"), |
787 | 0 | static_cast<int64_t>(1)); |
788 | 0 | } else { |
789 | | // This is an old log message which provides a number of attempts |
790 | | // made by the allocation engine to allocate a lease. The only case |
791 | | // when we don't want to log this message is when the number of |
792 | | // attempts is zero (condition above), because it would look silly. |
793 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL) |
794 | 0 | .arg(ctx.query_->getLabel()) |
795 | 0 | .arg(total_attempts); |
796 | 0 | StatsMgr::instance().addValue("v6-allocation-fail", |
797 | 0 | static_cast<int64_t>(1)); |
798 | 0 | StatsMgr::instance().addValue( |
799 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
800 | 0 | "v6-allocation-fail"), |
801 | 0 | static_cast<int64_t>(1)); |
802 | 0 | } |
803 | |
|
804 | 0 | if (!classes.empty()) { |
805 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES) |
806 | 0 | .arg(ctx.query_->getLabel()) |
807 | 0 | .arg(classes.toText()); |
808 | 0 | StatsMgr::instance().addValue("v6-allocation-fail-classes", |
809 | 0 | static_cast<int64_t>(1)); |
810 | 0 | StatsMgr::instance().addValue( |
811 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
812 | 0 | "v6-allocation-fail-classes"), |
813 | 0 | static_cast<int64_t>(1)); |
814 | 0 | } |
815 | | |
816 | | // We failed to allocate anything. Let's return empty collection. |
817 | 0 | return (Lease6Collection()); |
818 | 0 | } |
819 | | |
820 | | Lease6Ptr |
821 | | AllocEngine::allocateBestMatch(ClientContext6& ctx, |
822 | | Lease6Ptr& hint_lease, |
823 | | bool& search_hint_lease, |
824 | | const isc::asiolink::IOAddress& hint_addr, |
825 | | uint8_t hint_prefix_length, |
826 | | ConstSubnet6Ptr original_subnet, |
827 | | SharedNetwork6Ptr& network, |
828 | | uint64_t& total_attempts, |
829 | | uint64_t& subnets_with_unavail_leases, |
830 | | uint64_t& subnets_with_unavail_pools, |
831 | | hooks::CalloutHandle::CalloutNextStep& callout_status, |
832 | 0 | Allocator::PrefixLenMatchType prefix_length_match) { |
833 | 0 | auto const& classes = ctx.query_->getClasses(); |
834 | 0 | Pool6Ptr pool; |
835 | 0 | ConstSubnet6Ptr subnet = original_subnet; |
836 | |
|
837 | 0 | Lease6Ptr usable_hint_lease; |
838 | 0 | if (!search_hint_lease) { |
839 | 0 | usable_hint_lease = hint_lease; |
840 | 0 | } |
841 | 0 | for (; subnet; subnet = subnet->getNextSubnet(original_subnet)) { |
842 | 0 | if (!subnet->clientSupported(classes)) { |
843 | 0 | continue; |
844 | 0 | } |
845 | | |
846 | 0 | ctx.subnet_ = subnet; |
847 | | |
848 | | // check if the hint is in pool and is available |
849 | | // This is equivalent of subnet->inPool(hint), but returns the pool |
850 | 0 | pool = boost::dynamic_pointer_cast<Pool6> |
851 | 0 | (subnet->getPool(ctx.currentIA().type_, classes, hint_addr)); |
852 | | |
853 | | // check if the pool is allowed |
854 | 0 | if (!pool || !pool->clientSupported(classes)) { |
855 | 0 | continue; |
856 | 0 | } |
857 | | |
858 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_PD && |
859 | 0 | !Allocator::isValidPrefixPool(prefix_length_match, pool, |
860 | 0 | hint_prefix_length)) { |
861 | 0 | continue; |
862 | 0 | } |
863 | | |
864 | 0 | isc::asiolink::IOAddress hint = hint_addr; |
865 | | |
866 | | // Adjust the hint to current pool. The client might have a different idea |
867 | | // of the prefix length. The configuration might have changed since the lease |
868 | | // has been used. |
869 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_PD) { |
870 | 0 | hint = asiolink::firstAddrInPrefix(hint_addr, pool->getLength()); |
871 | 0 | } |
872 | |
|
873 | 0 | bool in_subnet = subnet->getReservationsInSubnet(); |
874 | | |
875 | | /// @todo: We support only one hint for now |
876 | 0 | if (search_hint_lease) { |
877 | 0 | search_hint_lease = false; |
878 | 0 | hint_lease = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint); |
879 | 0 | usable_hint_lease = hint_lease; |
880 | 0 | } |
881 | 0 | if (!usable_hint_lease) { |
882 | | |
883 | | // In-pool reservations: Check if this address is reserved for someone |
884 | | // else. There is no need to check for whom it is reserved, because if |
885 | | // it has been reserved for us we would have already allocated a lease. |
886 | |
|
887 | 0 | ConstHostCollection hosts; |
888 | | // When out-of-pool flag is true the server may assume that all host |
889 | | // reservations are for addresses that do not belong to the dynamic |
890 | | // pool. Therefore, it can skip the reservation checks when dealing |
891 | | // with in-pool addresses. |
892 | 0 | if (in_subnet && |
893 | 0 | (!subnet->getReservationsOutOfPool() || |
894 | 0 | !subnet->inPool(ctx.currentIA().type_, hint))) { |
895 | 0 | hosts = getIPv6Resrv(subnet->getID(), hint); |
896 | 0 | } |
897 | |
|
898 | 0 | if (hosts.empty()) { |
899 | | |
900 | | // If the in-pool reservations are disabled, or there is no |
901 | | // reservation for a given hint, we're good to go. |
902 | | |
903 | | // The hint is valid and not currently used, let's create a |
904 | | // lease for it |
905 | 0 | Lease6Ptr new_lease = createLease6(ctx, hint, pool->getLength(), callout_status); |
906 | | |
907 | | // It can happen that the lease allocation failed (we could |
908 | | // have lost the race condition. That means that the hint is |
909 | | // no longer usable and we need to continue the regular |
910 | | // allocation path. |
911 | 0 | if (new_lease) { |
912 | | /// @todo: We support only one lease per ia for now |
913 | 0 | return (new_lease); |
914 | 0 | } |
915 | 0 | } else { |
916 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
917 | 0 | ALLOC_ENGINE_V6_HINT_RESERVED) |
918 | 0 | .arg(ctx.query_->getLabel()) |
919 | 0 | .arg(hint.toText()); |
920 | 0 | } |
921 | |
|
922 | 0 | } else if (usable_hint_lease->expired() && |
923 | 0 | (usable_hint_lease->state_ != Lease::STATE_REGISTERED)) { |
924 | | |
925 | | // If the lease is expired, we may likely reuse it, but... |
926 | 0 | ConstHostCollection hosts; |
927 | | // When out-of-pool flag is true the server may assume that all host |
928 | | // reservations are for addresses that do not belong to the dynamic |
929 | | // pool. Therefore, it can skip the reservation checks when dealing |
930 | | // with in-pool addresses. |
931 | 0 | if (in_subnet && |
932 | 0 | (!subnet->getReservationsOutOfPool() || |
933 | 0 | !subnet->inPool(ctx.currentIA().type_, hint))) { |
934 | 0 | hosts = getIPv6Resrv(subnet->getID(), hint); |
935 | 0 | } |
936 | | |
937 | | // Let's check if there is a reservation for this address. |
938 | 0 | if (hosts.empty()) { |
939 | | |
940 | | // Copy an existing, expired lease so as it can be returned |
941 | | // to the caller. |
942 | 0 | Lease6Ptr old_lease(new Lease6(*usable_hint_lease)); |
943 | 0 | ctx.currentIA().old_leases_.push_back(old_lease); |
944 | | |
945 | | /// We found a lease and it is expired, so we can reuse it |
946 | 0 | Lease6Ptr lease = reuseExpiredLease(usable_hint_lease, ctx, |
947 | 0 | pool->getLength(), |
948 | 0 | callout_status); |
949 | | |
950 | | /// @todo: We support only one lease per ia for now |
951 | 0 | return (lease); |
952 | |
|
953 | 0 | } else { |
954 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
955 | 0 | ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED) |
956 | 0 | .arg(ctx.query_->getLabel()) |
957 | 0 | .arg(hint.toText()); |
958 | 0 | } |
959 | 0 | } |
960 | 0 | } |
961 | | |
962 | | // We have the choice in the order checking the lease and |
963 | | // the reservation. The default is to begin by the lease |
964 | | // if the multi-threading is disabled. |
965 | 0 | bool check_reservation_first = MultiThreadingMgr::instance().getMode(); |
966 | | // If multi-threading is disabled, honor the configured order for host |
967 | | // reservations lookup. |
968 | 0 | if (!check_reservation_first) { |
969 | 0 | check_reservation_first = CfgMgr::instance().getCurrentCfg()->getReservationsLookupFirst(); |
970 | 0 | } |
971 | | |
972 | | // Need to check if the subnet belongs to a shared network. If so, |
973 | | // we might be able to find a better subnet for lease allocation, |
974 | | // for which it is more likely that there are some leases available. |
975 | | // If we stick to the selected subnet, we may end up walking over |
976 | | // the entire subnet (or more subnets) to discover that the pools |
977 | | // have been exhausted. Using a subnet from which a lease was |
978 | | // assigned most recently is an optimization which increases |
979 | | // the likelihood of starting from the subnet which pools are not |
980 | | // exhausted. |
981 | |
|
982 | 0 | original_subnet->getSharedNetwork(network); |
983 | 0 | if (network) { |
984 | | // This would try to find a subnet with the same set of classes |
985 | | // as the current subnet, but with the more recent "usage timestamp". |
986 | | // This timestamp is only updated for the allocations made with an |
987 | | // allocator (unreserved lease allocations), not the static |
988 | | // allocations or requested addresses. |
989 | 0 | original_subnet = network->getPreferredSubnet(original_subnet, ctx.currentIA().type_); |
990 | 0 | } |
991 | |
|
992 | 0 | ctx.subnet_ = subnet = original_subnet; |
993 | |
|
994 | 0 | for (; subnet; subnet = subnet->getNextSubnet(original_subnet)) { |
995 | 0 | if (!subnet->clientSupported(classes)) { |
996 | 0 | continue; |
997 | 0 | } |
998 | | |
999 | | // The hint was useless (it was not provided at all, was used by someone else, |
1000 | | // was out of pool or reserved for someone else). Search the pool until first |
1001 | | // of the following occurs: |
1002 | | // - we find a free address |
1003 | | // - we find an address for which the lease has expired |
1004 | | // - we exhaust number of tries |
1005 | 0 | uint128_t const possible_attempts = |
1006 | 0 | subnet->getPoolCapacity(ctx.currentIA().type_, |
1007 | 0 | classes, |
1008 | 0 | prefix_length_match, |
1009 | 0 | hint_prefix_length); |
1010 | | |
1011 | | // If the number of tries specified in the allocation engine constructor |
1012 | | // is set to 0 (unlimited) or the pools capacity is lower than that number, |
1013 | | // let's use the pools capacity as the maximum number of tries. Trying |
1014 | | // more than the actual pools capacity is a waste of time. If the specified |
1015 | | // number of tries is lower than the pools capacity, use that number. |
1016 | 0 | uint128_t const max_attempts = |
1017 | 0 | (attempts_ == 0 || possible_attempts < attempts_) ? |
1018 | 0 | possible_attempts : |
1019 | 0 | attempts_; |
1020 | |
|
1021 | 0 | if (max_attempts > 0) { |
1022 | | // If max_attempts is greater than 0, there are some pools in this subnet |
1023 | | // from which we can potentially get a lease. |
1024 | 0 | ++subnets_with_unavail_leases; |
1025 | 0 | } else { |
1026 | | // If max_attempts is 0, it is an indication that there are no pools |
1027 | | // in the subnet from which we can get a lease. |
1028 | 0 | ++subnets_with_unavail_pools; |
1029 | 0 | continue; |
1030 | 0 | } |
1031 | | |
1032 | 0 | bool in_subnet = subnet->getReservationsInSubnet(); |
1033 | 0 | bool out_of_pool = subnet->getReservationsOutOfPool(); |
1034 | | |
1035 | | // Set the default status code in case the lease6_select callouts |
1036 | | // do not exist and the callout handle has a status returned by |
1037 | | // any of the callouts already invoked for this packet. |
1038 | 0 | if (ctx.callout_handle_) { |
1039 | 0 | ctx.callout_handle_->setStatus(CalloutHandle::NEXT_STEP_CONTINUE); |
1040 | 0 | } |
1041 | |
|
1042 | 0 | for (uint64_t i = 0; i < max_attempts; ++i) { |
1043 | 0 | ++total_attempts; |
1044 | |
|
1045 | 0 | auto allocator = subnet->getAllocator(ctx.currentIA().type_); |
1046 | 0 | IOAddress candidate = IOAddress::IPV6_ZERO_ADDRESS(); |
1047 | | |
1048 | | // The first step is to find out prefix length. It is 128 for |
1049 | | // non-PD leases. |
1050 | 0 | uint8_t prefix_len = 128; |
1051 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_PD) { |
1052 | 0 | candidate = allocator->pickPrefix(classes, pool, ctx.duid_, |
1053 | 0 | prefix_length_match, hint_addr, |
1054 | 0 | hint_prefix_length); |
1055 | 0 | if (pool) { |
1056 | 0 | prefix_len = pool->getLength(); |
1057 | 0 | } |
1058 | 0 | } else { |
1059 | 0 | candidate = allocator->pickAddress(classes, ctx.duid_, hint_addr); |
1060 | 0 | } |
1061 | | |
1062 | | // An allocator may return zero address when it has pools exhausted. |
1063 | 0 | if (candidate.isV6Zero()) { |
1064 | 0 | break; |
1065 | 0 | } |
1066 | | |
1067 | | // First check for reservation when it is the choice. |
1068 | 0 | if (check_reservation_first && in_subnet && !out_of_pool) { |
1069 | 0 | auto hosts = getIPv6Resrv(subnet->getID(), candidate); |
1070 | 0 | if (!hosts.empty()) { |
1071 | | // Don't allocate. |
1072 | 0 | continue; |
1073 | 0 | } |
1074 | 0 | } |
1075 | | |
1076 | | // Check if the resource is busy i.e. can be being allocated |
1077 | | // by another thread to another client. |
1078 | 0 | ResourceHandler resource_handler; |
1079 | 0 | if (MultiThreadingMgr::instance().getMode() && |
1080 | 0 | !resource_handler.tryLock(ctx.currentIA().type_, candidate)) { |
1081 | | // Don't allocate. |
1082 | 0 | continue; |
1083 | 0 | } |
1084 | | |
1085 | | // Look for an existing lease for the candidate. |
1086 | 0 | Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, |
1087 | 0 | candidate); |
1088 | |
|
1089 | 0 | if (!existing) { |
1090 | | /// In-pool reservations: Check if this address is reserved for someone |
1091 | | /// else. There is no need to check for whom it is reserved, because if |
1092 | | /// it has been reserved for us we would have already allocated a lease. |
1093 | 0 | if (!check_reservation_first && in_subnet && !out_of_pool) { |
1094 | 0 | auto hosts = getIPv6Resrv(subnet->getID(), candidate); |
1095 | 0 | if (!hosts.empty()) { |
1096 | | // Don't allocate. |
1097 | 0 | continue; |
1098 | 0 | } |
1099 | 0 | } |
1100 | | |
1101 | | // there's no existing lease for selected candidate, so it is |
1102 | | // free. Let's allocate it. |
1103 | | |
1104 | 0 | ctx.subnet_ = subnet; |
1105 | 0 | Lease6Ptr new_lease = createLease6(ctx, candidate, prefix_len, callout_status); |
1106 | 0 | if (new_lease) { |
1107 | | // We are allocating a new lease (not renewing). So, the |
1108 | | // old lease should be NULL. |
1109 | 0 | ctx.currentIA().old_leases_.clear(); |
1110 | |
|
1111 | 0 | return (new_lease); |
1112 | |
|
1113 | 0 | } else if (ctx.callout_handle_ && |
1114 | 0 | (callout_status != CalloutHandle::NEXT_STEP_CONTINUE)) { |
1115 | | // Don't retry when the callout status is not continue. |
1116 | 0 | break; |
1117 | 0 | } |
1118 | | |
1119 | | // Although the address was free just microseconds ago, it may have |
1120 | | // been taken just now. If the lease insertion fails, we continue |
1121 | | // allocation attempts. |
1122 | 0 | } else if (existing->expired() && |
1123 | 0 | (existing->state_ != Lease::STATE_REGISTERED)) { |
1124 | | // Make sure it's not reserved. |
1125 | 0 | if (!check_reservation_first && in_subnet && !out_of_pool) { |
1126 | 0 | auto hosts = getIPv6Resrv(subnet->getID(), candidate); |
1127 | 0 | if (!hosts.empty()) { |
1128 | | // Don't allocate. |
1129 | 0 | continue; |
1130 | 0 | } |
1131 | 0 | } |
1132 | | |
1133 | | // Copy an existing, expired lease so as it can be returned |
1134 | | // to the caller. |
1135 | 0 | Lease6Ptr old_lease(new Lease6(*existing)); |
1136 | 0 | ctx.currentIA().old_leases_.push_back(old_lease); |
1137 | |
|
1138 | 0 | ctx.subnet_ = subnet; |
1139 | 0 | existing = reuseExpiredLease(existing, ctx, prefix_len, |
1140 | 0 | callout_status); |
1141 | |
|
1142 | 0 | return (existing); |
1143 | 0 | } |
1144 | 0 | } |
1145 | 0 | } |
1146 | 0 | return (Lease6Ptr()); |
1147 | 0 | } |
1148 | | |
1149 | | void |
1150 | | AllocEngine::allocateReservedLeases6(ClientContext6& ctx, |
1151 | 0 | Lease6Collection& existing_leases) { |
1152 | | // If there are no reservations or the reservation is v4, there's nothing to do. |
1153 | 0 | if (ctx.hosts_.empty()) { |
1154 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
1155 | 0 | ALLOC_ENGINE_V6_ALLOC_NO_V6_HR) |
1156 | 0 | .arg(ctx.query_->getLabel()); |
1157 | 0 | return; |
1158 | 0 | } |
1159 | | |
1160 | | // Let's convert this from Lease::Type to IPv6Reserv::Type |
1161 | 0 | IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ? |
1162 | 0 | IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD; |
1163 | | |
1164 | | // We want to avoid allocating new lease for an IA if there is already |
1165 | | // a valid lease for which client has reservation. So, we first check if |
1166 | | // we already have a lease for a reserved address or prefix. |
1167 | 0 | for (auto const& lease : existing_leases) { |
1168 | 0 | if (lease->valid_lft_ != 0 || |
1169 | 0 | (lease->state_ == Lease::STATE_EXPIRED_RECLAIMED || |
1170 | 0 | lease->state_ == Lease::STATE_RELEASED)) { |
1171 | 0 | if ((ctx.hosts_.count(lease->subnet_id_) > 0) && |
1172 | 0 | ctx.hosts_[lease->subnet_id_]->hasReservation(makeIPv6Resrv(*lease))) { |
1173 | | // We found existing lease for a reserved address or prefix. |
1174 | | // We'll simply extend the lifetime of the lease. |
1175 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
1176 | 0 | ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS) |
1177 | 0 | .arg(ctx.query_->getLabel()) |
1178 | 0 | .arg(lease->typeToText(lease->type_)) |
1179 | 0 | .arg(lease->addr_.toText()); |
1180 | | |
1181 | | // Besides IP reservations we're also going to return other reserved |
1182 | | // parameters, such as hostname. We want to hand out the hostname value |
1183 | | // from the same reservation entry as IP addresses. Thus, let's see if |
1184 | | // there is any hostname reservation. |
1185 | 0 | if (!ctx.host_subnet_) { |
1186 | 0 | SharedNetwork6Ptr network; |
1187 | 0 | ctx.subnet_->getSharedNetwork(network); |
1188 | 0 | if (network) { |
1189 | | // Remember the subnet that holds this preferred host |
1190 | | // reservation. The server will use it to return appropriate |
1191 | | // FQDN, classes etc. |
1192 | 0 | ctx.host_subnet_ = network->getSubnet(lease->subnet_id_); |
1193 | 0 | ConstHostPtr host = ctx.hosts_[lease->subnet_id_]; |
1194 | | // If there is a hostname reservation here we should stick |
1195 | | // to this reservation. By updating the hostname in the |
1196 | | // context we make sure that the database is updated with |
1197 | | // this new value and the server doesn't need to do it and |
1198 | | // its processing performance is not impacted by the hostname |
1199 | | // updates. |
1200 | 0 | if (host) { |
1201 | | // We need to update the selected subnet so we get the |
1202 | | // correct options, parameters, and classes. |
1203 | 0 | ctx.subnet_ = ctx.host_subnet_; |
1204 | |
|
1205 | 0 | if (!host->getHostname().empty()) { |
1206 | | // We have to determine whether the hostname is generated |
1207 | | // in response to client's FQDN or not. If yes, we will |
1208 | | // need to qualify the hostname. Otherwise, we just use |
1209 | | // the hostname as it is specified for the reservation. |
1210 | 0 | OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN); |
1211 | 0 | ctx.hostname_ = CfgMgr::instance().getD2ClientMgr(). |
1212 | 0 | qualifyName(host->getHostname(), *ctx.getDdnsParams(), |
1213 | 0 | static_cast<bool>(fqdn)); |
1214 | 0 | } |
1215 | 0 | } |
1216 | 0 | } |
1217 | 0 | } |
1218 | | |
1219 | | // Got a lease for a reservation in this IA. |
1220 | 0 | return; |
1221 | 0 | } |
1222 | 0 | } |
1223 | 0 | } |
1224 | | |
1225 | | // There is no lease for a reservation in this IA. So, let's now iterate |
1226 | | // over reservations specified and try to allocate one of them for the IA. |
1227 | | |
1228 | 0 | auto const& classes = ctx.query_->getClasses(); |
1229 | 0 | for (ConstSubnet6Ptr subnet = ctx.subnet_; subnet; |
1230 | 0 | subnet = subnet->getNextSubnet(ctx.subnet_)) { |
1231 | |
|
1232 | 0 | SubnetID subnet_id = subnet->getID(); |
1233 | | |
1234 | | // No hosts for this subnet or the subnet not supported. |
1235 | 0 | if (!subnet->clientSupported(classes) || ctx.hosts_.count(subnet_id) == 0) { |
1236 | 0 | continue; |
1237 | 0 | } |
1238 | | |
1239 | 0 | ConstHostPtr host = ctx.hosts_[subnet_id]; |
1240 | |
|
1241 | 0 | bool in_subnet = subnet->getReservationsInSubnet(); |
1242 | | |
1243 | | // Get the IPv6 reservations of specified type. |
1244 | 0 | const IPv6ResrvRange& reservs = host->getIPv6Reservations(type); |
1245 | 0 | BOOST_FOREACH(auto const& type_lease_tuple, reservs) { |
1246 | | // We do have a reservation for address or prefix. |
1247 | 0 | const IOAddress& addr = type_lease_tuple.second.getPrefix(); |
1248 | 0 | uint8_t prefix_len = type_lease_tuple.second.getPrefixLen(); |
1249 | | |
1250 | | // We have allocated this address/prefix while processing one of the |
1251 | | // previous IAs, so let's try another reservation. |
1252 | 0 | if (ctx.isAllocated(addr, prefix_len)) { |
1253 | 0 | continue; |
1254 | 0 | } |
1255 | | |
1256 | | // The out-of-pool flag indicates that no client should be assigned |
1257 | | // reserved addresses from within the dynamic pool, and for that |
1258 | | // reason look only for reservations that are outside the pools, |
1259 | | // hence the inPool check. |
1260 | 0 | if (!in_subnet || |
1261 | 0 | (subnet->getReservationsOutOfPool() && |
1262 | 0 | subnet->inPool(ctx.currentIA().type_, addr))) { |
1263 | 0 | continue; |
1264 | 0 | } |
1265 | | |
1266 | | // If there's a lease for this address, let's not create it. |
1267 | | // It doesn't matter whether it is for this client or for someone else. |
1268 | 0 | if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, |
1269 | 0 | addr)) { |
1270 | | |
1271 | | // Let's remember the subnet from which the reserved address has been |
1272 | | // allocated. We'll use this subnet for allocating other reserved |
1273 | | // resources. |
1274 | 0 | ctx.subnet_ = subnet; |
1275 | |
|
1276 | 0 | if (!ctx.host_subnet_) { |
1277 | 0 | ctx.host_subnet_ = subnet; |
1278 | 0 | if (!host->getHostname().empty()) { |
1279 | | // If there is a hostname reservation here we should stick |
1280 | | // to this reservation. By updating the hostname in the |
1281 | | // context we make sure that the database is updated with |
1282 | | // this new value and the server doesn't need to do it and |
1283 | | // its processing performance is not impacted by the hostname |
1284 | | // updates. |
1285 | | |
1286 | | // We have to determine whether the hostname is generated |
1287 | | // in response to client's FQDN or not. If yes, we will |
1288 | | // need to qualify the hostname. Otherwise, we just use |
1289 | | // the hostname as it is specified for the reservation. |
1290 | 0 | OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN); |
1291 | 0 | ctx.hostname_ = CfgMgr::instance().getD2ClientMgr(). |
1292 | 0 | qualifyName(host->getHostname(), *ctx.getDdnsParams(), |
1293 | 0 | static_cast<bool>(fqdn)); |
1294 | 0 | } |
1295 | 0 | } |
1296 | | |
1297 | | // Ok, let's create a new lease... |
1298 | 0 | CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE; |
1299 | 0 | Lease6Ptr lease = createLease6(ctx, addr, prefix_len, callout_status); |
1300 | |
|
1301 | 0 | if (!lease) { |
1302 | 0 | continue; |
1303 | 0 | } |
1304 | | |
1305 | | // ... and add it to the existing leases list. |
1306 | 0 | existing_leases.push_back(lease); |
1307 | |
|
1308 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_NA) { |
1309 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED) |
1310 | 0 | .arg(addr.toText()) |
1311 | 0 | .arg(ctx.query_->getLabel()); |
1312 | 0 | } else { |
1313 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED) |
1314 | 0 | .arg(addr.toText()) |
1315 | 0 | .arg(static_cast<int>(prefix_len)) |
1316 | 0 | .arg(ctx.query_->getLabel()); |
1317 | 0 | } |
1318 | | |
1319 | | // We found a lease for this client and this IA. Let's return. |
1320 | | // Returning after the first lease was assigned is useful if we |
1321 | | // have multiple reservations for the same client. If the client |
1322 | | // sends 2 IAs, the first time we call allocateReservedLeases6 will |
1323 | | // use the first reservation and return. The second time, we'll |
1324 | | // go over the first reservation, but will discover that there's |
1325 | | // a lease corresponding to it and will skip it and then pick |
1326 | | // the second reservation and turn it into the lease. This approach |
1327 | | // would work for any number of reservations. |
1328 | 0 | return; |
1329 | 0 | } |
1330 | 0 | } |
1331 | 0 | } |
1332 | | |
1333 | | // Found no subnet reservations so now try the global reservation. |
1334 | 0 | allocateGlobalReservedLeases6(ctx, existing_leases); |
1335 | 0 | } |
1336 | | |
1337 | | void |
1338 | | AllocEngine::allocateGlobalReservedLeases6(ClientContext6& ctx, |
1339 | 0 | Lease6Collection& existing_leases) { |
1340 | | // Get the global host |
1341 | 0 | ConstHostPtr ghost = ctx.globalHost(); |
1342 | 0 | if (!ghost) { |
1343 | 0 | return; |
1344 | 0 | } |
1345 | | |
1346 | | // We want to avoid allocating a new lease for an IA if there is already |
1347 | | // a valid lease for which client has reservation. So, we first check if |
1348 | | // we already have a lease for a reserved address or prefix. |
1349 | 0 | for (auto const& lease : existing_leases) { |
1350 | 0 | if ((lease->valid_lft_ != 0) && |
1351 | 0 | (ghost->hasReservation(makeIPv6Resrv(*lease)))) { |
1352 | | // We found existing lease for a reserved address or prefix. |
1353 | | // We'll simply extend the lifetime of the lease. |
1354 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
1355 | 0 | ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS) |
1356 | 0 | .arg(ctx.query_->getLabel()) |
1357 | 0 | .arg(lease->typeToText(lease->type_)) |
1358 | 0 | .arg(lease->addr_.toText()); |
1359 | | |
1360 | | // Besides IP reservations we're also going to return other reserved |
1361 | | // parameters, such as hostname. We want to hand out the hostname value |
1362 | | // from the same reservation entry as IP addresses. Thus, let's see if |
1363 | | // there is any hostname reservation. |
1364 | 0 | if (!ghost->getHostname().empty()) { |
1365 | | // We have to determine whether the hostname is generated |
1366 | | // in response to client's FQDN or not. If yes, we will |
1367 | | // need to qualify the hostname. Otherwise, we just use |
1368 | | // the hostname as it is specified for the reservation. |
1369 | 0 | OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN); |
1370 | 0 | ctx.hostname_ = CfgMgr::instance().getD2ClientMgr(). |
1371 | 0 | qualifyName(ghost->getHostname(), *ctx.getDdnsParams(), |
1372 | 0 | static_cast<bool>(fqdn)); |
1373 | 0 | } |
1374 | | |
1375 | | // Got a lease for a reservation in this IA. |
1376 | 0 | return; |
1377 | 0 | } |
1378 | 0 | } |
1379 | | |
1380 | | // There is no lease for a reservation in this IA. So, let's now iterate |
1381 | | // over reservations specified and try to allocate one of them for the IA. |
1382 | | |
1383 | | // Let's convert this from Lease::Type to IPv6Reserv::Type |
1384 | 0 | IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ? |
1385 | 0 | IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD; |
1386 | |
|
1387 | 0 | const IPv6ResrvRange& reservs = ghost->getIPv6Reservations(type); |
1388 | 0 | BOOST_FOREACH(auto const& type_lease_tuple, reservs) { |
1389 | | // We do have a reservation for address or prefix. |
1390 | 0 | const IOAddress& addr = type_lease_tuple.second.getPrefix(); |
1391 | 0 | uint8_t prefix_len = type_lease_tuple.second.getPrefixLen(); |
1392 | | |
1393 | | // We have allocated this address/prefix while processing one of the |
1394 | | // previous IAs, so let's try another reservation. |
1395 | 0 | if (ctx.isAllocated(addr, prefix_len)) { |
1396 | 0 | continue; |
1397 | 0 | } |
1398 | | |
1399 | | // If there's a lease for this address, let's not create it. |
1400 | | // It doesn't matter whether it is for this client or for someone else. |
1401 | 0 | if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, addr)) { |
1402 | | |
1403 | | // Check the feasibility of this address within this shared-network. |
1404 | | // Assign the context's subnet accordingly. |
1405 | | // Only necessary for IA_NA |
1406 | 0 | if (type == IPv6Resrv::TYPE_NA) { |
1407 | 0 | bool valid_subnet = false; |
1408 | 0 | auto subnet = ctx.subnet_; |
1409 | 0 | while (subnet) { |
1410 | 0 | if (subnet->inRange(addr)) { |
1411 | 0 | valid_subnet = true; |
1412 | 0 | break; |
1413 | 0 | } |
1414 | | |
1415 | 0 | subnet = subnet->getNextSubnet(ctx.subnet_); |
1416 | 0 | } |
1417 | |
|
1418 | 0 | if (!valid_subnet) { |
1419 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
1420 | 0 | ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS6) |
1421 | 0 | .arg(ctx.query_->getLabel()) |
1422 | 0 | .arg(addr.toText()) |
1423 | 0 | .arg(labelNetworkOrSubnet(ctx.subnet_)); |
1424 | 0 | continue; |
1425 | 0 | } |
1426 | | |
1427 | 0 | ctx.subnet_ = subnet; |
1428 | 0 | } |
1429 | | |
1430 | 0 | if (!ghost->getHostname().empty()) { |
1431 | | // If there is a hostname reservation here we should stick |
1432 | | // to this reservation. By updating the hostname in the |
1433 | | // context we make sure that the database is updated with |
1434 | | // this new value and the server doesn't need to do it and |
1435 | | // its processing performance is not impacted by the hostname |
1436 | | // updates. |
1437 | | |
1438 | | // We have to determine whether the hostname is generated |
1439 | | // in response to client's FQDN or not. If yes, we will |
1440 | | // need to qualify the hostname. Otherwise, we just use |
1441 | | // the hostname as it is specified for the reservation. |
1442 | 0 | OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN); |
1443 | 0 | ctx.hostname_ = CfgMgr::instance().getD2ClientMgr(). |
1444 | 0 | qualifyName(ghost->getHostname(), *ctx.getDdnsParams(), |
1445 | 0 | static_cast<bool>(fqdn)); |
1446 | 0 | } |
1447 | | |
1448 | | // Ok, let's create a new lease... |
1449 | 0 | CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE; |
1450 | 0 | Lease6Ptr lease = createLease6(ctx, addr, prefix_len, callout_status); |
1451 | |
|
1452 | 0 | if (!lease) { |
1453 | 0 | continue; |
1454 | 0 | } |
1455 | | |
1456 | | // ... and add it to the existing leases list. |
1457 | 0 | existing_leases.push_back(lease); |
1458 | |
|
1459 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_NA) { |
1460 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED) |
1461 | 0 | .arg(addr.toText()) |
1462 | 0 | .arg(ctx.query_->getLabel()); |
1463 | 0 | } else { |
1464 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED) |
1465 | 0 | .arg(addr.toText()) |
1466 | 0 | .arg(static_cast<int>(prefix_len)) |
1467 | 0 | .arg(ctx.query_->getLabel()); |
1468 | 0 | } |
1469 | | |
1470 | | // We found a lease for this client and this IA. Let's return. |
1471 | | // Returning after the first lease was assigned is useful if we |
1472 | | // have multiple reservations for the same client. If the client |
1473 | | // sends 2 IAs, the first time we call allocateReservedLeases6 will |
1474 | | // use the first reservation and return. The second time, we'll |
1475 | | // go over the first reservation, but will discover that there's |
1476 | | // a lease corresponding to it and will skip it and then pick |
1477 | | // the second reservation and turn it into the lease. This approach |
1478 | | // would work for any number of reservations. |
1479 | 0 | return; |
1480 | 0 | } |
1481 | 0 | } |
1482 | 0 | } |
1483 | | |
1484 | | void |
1485 | | AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, |
1486 | 0 | Lease6Collection& existing_leases) { |
1487 | | // If there are no leases (so nothing to remove) just return. |
1488 | 0 | if (existing_leases.empty() || !ctx.subnet_) { |
1489 | 0 | return; |
1490 | 0 | } |
1491 | | // If host reservation is disabled (so there are no reserved leases) |
1492 | | // use the simplified version. |
1493 | 0 | if (!ctx.subnet_->getReservationsInSubnet() && |
1494 | 0 | !ctx.subnet_->getReservationsGlobal()) { |
1495 | 0 | removeNonmatchingReservedNoHostLeases6(ctx, existing_leases); |
1496 | 0 | return; |
1497 | 0 | } |
1498 | | |
1499 | | // We need a copy, so we won't be iterating over a container and |
1500 | | // removing from it at the same time. It's only a copy of pointers, |
1501 | | // so the operation shouldn't be that expensive. |
1502 | 0 | Lease6Collection copy = existing_leases; |
1503 | |
|
1504 | 0 | for (auto const& candidate : copy) { |
1505 | | // If we have reservation we should check if the reservation is for |
1506 | | // the candidate lease. If so, we simply accept the lease. |
1507 | 0 | IPv6Resrv resv = makeIPv6Resrv(*candidate); |
1508 | 0 | if ((ctx.hasGlobalReservation(resv)) || |
1509 | 0 | ((ctx.hosts_.count(candidate->subnet_id_) > 0) && |
1510 | 0 | (ctx.hosts_[candidate->subnet_id_]->hasReservation(resv)))) { |
1511 | | // We have a subnet reservation |
1512 | 0 | continue; |
1513 | 0 | } |
1514 | | |
1515 | | // The candidate address doesn't appear to be reserved for us. |
1516 | | // We have to make a bit more expensive operation here to retrieve |
1517 | | // the reservation for the candidate lease and see if it is |
1518 | | // reserved for someone else. |
1519 | 0 | auto hosts = getIPv6Resrv(ctx.subnet_->getID(), candidate->addr_); |
1520 | | // If lease is not reserved to someone else, it means that it can |
1521 | | // be allocated to us from a dynamic pool, but we must check if |
1522 | | // this lease belongs to any pool. If it does, we can proceed to |
1523 | | // checking the next lease. |
1524 | 0 | if (hosts.empty() && inAllowedPool(ctx, candidate->type_, |
1525 | 0 | candidate->addr_, false)) { |
1526 | 0 | continue; |
1527 | 0 | } |
1528 | | |
1529 | 0 | if (!hosts.empty()) { |
1530 | | // Ok, we have a problem. This host has a lease that is reserved |
1531 | | // for someone else. We need to recover from this. |
1532 | 0 | if (hosts.size() == 1) { |
1533 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_NA) { |
1534 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE) |
1535 | 0 | .arg(ctx.query_->getLabel()) |
1536 | 0 | .arg(candidate->addr_.toText()) |
1537 | 0 | .arg(ctx.duid_->toText()) |
1538 | 0 | .arg(hosts.front()->getIdentifierAsText()); |
1539 | 0 | } else { |
1540 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE) |
1541 | 0 | .arg(ctx.query_->getLabel()) |
1542 | 0 | .arg(candidate->addr_.toText()) |
1543 | 0 | .arg(static_cast<int>(candidate->prefixlen_)) |
1544 | 0 | .arg(ctx.duid_->toText()) |
1545 | 0 | .arg(hosts.front()->getIdentifierAsText()); |
1546 | 0 | } |
1547 | 0 | } else { |
1548 | 0 | if (ctx.currentIA().type_ == Lease::TYPE_NA) { |
1549 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE) |
1550 | 0 | .arg(ctx.query_->getLabel()) |
1551 | 0 | .arg(candidate->addr_.toText()) |
1552 | 0 | .arg(ctx.duid_->toText()) |
1553 | 0 | .arg(hosts.size()); |
1554 | 0 | } else { |
1555 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE) |
1556 | 0 | .arg(ctx.query_->getLabel()) |
1557 | 0 | .arg(candidate->addr_.toText()) |
1558 | 0 | .arg(static_cast<int>(candidate->prefixlen_)) |
1559 | 0 | .arg(ctx.duid_->toText()) |
1560 | 0 | .arg(hosts.size()); |
1561 | 0 | } |
1562 | 0 | } |
1563 | 0 | } |
1564 | | |
1565 | | // Remove this lease from LeaseMgr as it is reserved to someone |
1566 | | // else or doesn't belong to a pool. |
1567 | 0 | if (!LeaseMgrFactory::instance().deleteLease(candidate)) { |
1568 | | // Concurrent delete performed by other instance which should |
1569 | | // properly handle dns and stats updates. |
1570 | 0 | continue; |
1571 | 0 | } |
1572 | | |
1573 | | // Update DNS if needed. |
1574 | 0 | queueNCR(CHG_REMOVE, candidate); |
1575 | | |
1576 | | // Need to decrease statistic for assigned addresses. |
1577 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1578 | 0 | "assigned-nas" : "assigned-pds", |
1579 | 0 | static_cast<int64_t>(-1)); |
1580 | |
|
1581 | 0 | StatsMgr::instance().addValue( |
1582 | 0 | StatsMgr::generateName("subnet", candidate->subnet_id_, |
1583 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1584 | 0 | "assigned-nas" : "assigned-pds"), |
1585 | 0 | static_cast<int64_t>(-1)); |
1586 | |
|
1587 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(candidate->subnet_id_); |
1588 | 0 | if (subnet) { |
1589 | 0 | auto const& pool = subnet->getPool(ctx.currentIA().type_, candidate->addr_, false); |
1590 | 0 | if (pool) { |
1591 | 0 | StatsMgr::instance().addValue( |
1592 | 0 | StatsMgr::generateName("subnet", subnet->getID(), |
1593 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1594 | 0 | "pool" : "pd-pool", pool->getID(), |
1595 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1596 | 0 | "assigned-nas" : "assigned-pds")), |
1597 | 0 | static_cast<int64_t>(-1)); |
1598 | 0 | } |
1599 | 0 | } |
1600 | | |
1601 | | // In principle, we could trigger a hook here, but we will do this |
1602 | | // only if we get serious complaints from actual users. We want the |
1603 | | // conflict resolution procedure to really work and user libraries |
1604 | | // should not interfere with it. |
1605 | | |
1606 | | // Add this to the list of removed leases. |
1607 | 0 | ctx.currentIA().old_leases_.push_back(candidate); |
1608 | | |
1609 | | // Let's remove this candidate from existing leases |
1610 | 0 | removeLeases(existing_leases, candidate->addr_); |
1611 | 0 | } |
1612 | 0 | } |
1613 | | |
1614 | | void |
1615 | | AllocEngine::removeNonmatchingReservedNoHostLeases6(ClientContext6& ctx, |
1616 | 0 | Lease6Collection& existing_leases) { |
1617 | | // We need a copy, so we won't be iterating over a container and |
1618 | | // removing from it at the same time. It's only a copy of pointers, |
1619 | | // so the operation shouldn't be that expensive. |
1620 | 0 | Lease6Collection copy = existing_leases; |
1621 | |
|
1622 | 0 | for (auto const& candidate : copy) { |
1623 | | // Lease can be allocated to us from a dynamic pool, but we must |
1624 | | // check if this lease belongs to any allowed pool. If it does, |
1625 | | // we can proceed to checking the next lease. |
1626 | 0 | if (inAllowedPool(ctx, candidate->type_, |
1627 | 0 | candidate->addr_, false)) { |
1628 | 0 | continue; |
1629 | 0 | } |
1630 | | |
1631 | | // Remove this lease from LeaseMgr as it doesn't belong to a pool. |
1632 | 0 | if (!LeaseMgrFactory::instance().deleteLease(candidate)) { |
1633 | | // Concurrent delete performed by other instance which should |
1634 | | // properly handle dns and stats updates. |
1635 | 0 | continue; |
1636 | 0 | } |
1637 | | |
1638 | | // Update DNS if needed. |
1639 | 0 | queueNCR(CHG_REMOVE, candidate); |
1640 | | |
1641 | | // Need to decrease statistic for assigned addresses. |
1642 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1643 | 0 | "assigned-nas" : "assigned-pds", |
1644 | 0 | static_cast<int64_t>(-1)); |
1645 | |
|
1646 | 0 | StatsMgr::instance().addValue( |
1647 | 0 | StatsMgr::generateName("subnet", candidate->subnet_id_, |
1648 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1649 | 0 | "assigned-nas" : "assigned-pds"), |
1650 | 0 | static_cast<int64_t>(-1)); |
1651 | |
|
1652 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(candidate->subnet_id_); |
1653 | 0 | if (subnet) { |
1654 | 0 | auto const& pool = subnet->getPool(candidate->type_, candidate->addr_, false); |
1655 | 0 | if (pool) { |
1656 | 0 | StatsMgr::instance().addValue( |
1657 | 0 | StatsMgr::generateName("subnet", subnet->getID(), |
1658 | 0 | StatsMgr::generateName(candidate->type_ == Lease::TYPE_NA ? |
1659 | 0 | "pool" : "pd-pool", pool->getID(), |
1660 | 0 | candidate->type_ == Lease::TYPE_NA ? |
1661 | 0 | "assigned-nas" : "assigned-pds")), |
1662 | 0 | static_cast<int64_t>(-1)); |
1663 | 0 | } |
1664 | 0 | } |
1665 | | |
1666 | | // Add this to the list of removed leases. |
1667 | 0 | ctx.currentIA().old_leases_.push_back(candidate); |
1668 | | |
1669 | | // Let's remove this candidate from existing leases |
1670 | 0 | removeLeases(existing_leases, candidate->addr_); |
1671 | 0 | } |
1672 | 0 | } |
1673 | | |
1674 | | bool |
1675 | 0 | AllocEngine::removeLeases(Lease6Collection& container, const asiolink::IOAddress& addr) { |
1676 | |
|
1677 | 0 | bool removed = false; |
1678 | 0 | for (Lease6Collection::iterator lease = container.begin(); |
1679 | 0 | lease != container.end(); ++lease) { |
1680 | 0 | if ((*lease)->addr_ == addr) { |
1681 | 0 | lease->reset(); |
1682 | 0 | removed = true; |
1683 | 0 | } |
1684 | 0 | } |
1685 | | |
1686 | | // Remove all elements that have NULL value |
1687 | 0 | container.erase(std::remove(container.begin(), container.end(), Lease6Ptr()), |
1688 | 0 | container.end()); |
1689 | |
|
1690 | 0 | return (removed); |
1691 | 0 | } |
1692 | | |
1693 | | void |
1694 | | AllocEngine::removeNonreservedLeases6(ClientContext6& ctx, |
1695 | 0 | Lease6Collection& existing_leases) { |
1696 | | // This method removes leases that are not reserved for this host. |
1697 | | // It will keep at least one lease, though, as a fallback. |
1698 | 0 | int total = existing_leases.size(); |
1699 | 0 | if (total <= 1) { |
1700 | 0 | return; |
1701 | 0 | } |
1702 | | |
1703 | | // This is officially not scary code anymore. iterates and marks specified |
1704 | | // leases for deletion, by setting appropriate pointers to NULL. |
1705 | 0 | for (Lease6Collection::iterator lease = existing_leases.begin(); |
1706 | 0 | lease != existing_leases.end(); ++lease) { |
1707 | | |
1708 | | // If there is reservation for this keep it. |
1709 | 0 | IPv6Resrv resv = makeIPv6Resrv(**lease); |
1710 | 0 | if (ctx.hasGlobalReservation(resv) || |
1711 | 0 | ((ctx.hosts_.count((*lease)->subnet_id_) > 0) && |
1712 | 0 | (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv)))) { |
1713 | 0 | continue; |
1714 | 0 | } |
1715 | | |
1716 | | // @todo - If this is for a fake_allocation, we should probably |
1717 | | // not be deleting the lease or removing DNS entries. We should |
1718 | | // simply remove it from the list. |
1719 | | // We have reservations, but not for this lease. Release it. |
1720 | | // Remove this lease from LeaseMgr |
1721 | 0 | if (!LeaseMgrFactory::instance().deleteLease(*lease)) { |
1722 | | // Concurrent delete performed by other instance which should |
1723 | | // properly handle dns and stats updates. |
1724 | 0 | continue; |
1725 | 0 | } |
1726 | | |
1727 | | // Update DNS if required. |
1728 | 0 | queueNCR(CHG_REMOVE, *lease); |
1729 | | |
1730 | | // Need to decrease statistic for assigned addresses. |
1731 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1732 | 0 | "assigned-nas" : "assigned-pds", |
1733 | 0 | static_cast<int64_t>(-1)); |
1734 | |
|
1735 | 0 | StatsMgr::instance().addValue( |
1736 | 0 | StatsMgr::generateName("subnet", (*lease)->subnet_id_, |
1737 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1738 | 0 | "assigned-nas" : "assigned-pds"), |
1739 | 0 | static_cast<int64_t>(-1)); |
1740 | |
|
1741 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId((*lease)->subnet_id_); |
1742 | 0 | if (subnet) { |
1743 | 0 | auto const& pool = subnet->getPool(ctx.currentIA().type_, (*lease)->addr_, false); |
1744 | 0 | if (pool) { |
1745 | 0 | StatsMgr::instance().addValue( |
1746 | 0 | StatsMgr::generateName("subnet", subnet->getID(), |
1747 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1748 | 0 | "pool" : "pd-pool", pool->getID(), |
1749 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1750 | 0 | "assigned-nas" : "assigned-pds")), |
1751 | 0 | static_cast<int64_t>(-1)); |
1752 | 0 | } |
1753 | 0 | } |
1754 | | |
1755 | | /// @todo: Probably trigger a hook here |
1756 | | |
1757 | | // Add this to the list of removed leases. |
1758 | 0 | ctx.currentIA().old_leases_.push_back(*lease); |
1759 | | |
1760 | | // Set this pointer to NULL. The pointer is still valid. We're just |
1761 | | // setting the Lease6Ptr to NULL value. We'll remove all NULL |
1762 | | // pointers once the loop is finished. |
1763 | 0 | lease->reset(); |
1764 | |
|
1765 | 0 | if (--total == 1) { |
1766 | | // If there's only one lease left, break the loop. |
1767 | 0 | break; |
1768 | 0 | } |
1769 | 0 | } |
1770 | | |
1771 | | // Remove all elements that we previously marked for deletion (those that |
1772 | | // have NULL value). |
1773 | 0 | existing_leases.erase(std::remove(existing_leases.begin(), |
1774 | 0 | existing_leases.end(), Lease6Ptr()), existing_leases.end()); |
1775 | 0 | } |
1776 | | |
1777 | | namespace { |
1778 | | bool |
1779 | | useMinLifetimes6(AllocEngine::ClientContext6& ctx, const IOAddress& addr, |
1780 | 0 | uint8_t prefix_len) { |
1781 | 0 | auto const& threshold = ctx.subnet_->getAdaptiveLeaseTimeThreshold(); |
1782 | 0 | if (!threshold.unspecified() && (threshold < 1.0)) { |
1783 | 0 | auto const& occupancy = ctx.subnet_->getAllocator(Lease::TYPE_PD)-> |
1784 | 0 | getOccupancyRate(addr, prefix_len, ctx.query_->getClasses()); |
1785 | 0 | if (occupancy >= threshold) { |
1786 | 0 | return (true); |
1787 | 0 | } |
1788 | 0 | } |
1789 | 0 | return (false); |
1790 | 0 | } |
1791 | | } // end of anonymous namespace. |
1792 | | |
1793 | | Lease6Ptr |
1794 | | AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx, |
1795 | | uint8_t prefix_len, |
1796 | 0 | CalloutHandle::CalloutNextStep& callout_status) { |
1797 | |
|
1798 | 0 | if (!expired->expired()) { |
1799 | 0 | isc_throw(BadValue, "Attempt to recycle lease that is still valid"); |
1800 | 0 | } |
1801 | | |
1802 | 0 | if (expired->state_ == Lease::STATE_REGISTERED) { |
1803 | 0 | isc_throw(BadValue, "Attempt to recycle registered address"); |
1804 | 0 | } |
1805 | | |
1806 | 0 | if (expired->type_ != Lease::TYPE_PD) { |
1807 | 0 | prefix_len = 128; // non-PD lease types must be always /128 |
1808 | 0 | } |
1809 | |
|
1810 | 0 | if (!ctx.fake_allocation_) { |
1811 | | // The expired lease needs to be reclaimed before it can be reused. |
1812 | | // This includes declined leases for which probation period has |
1813 | | // elapsed. |
1814 | 0 | reclaimExpiredLease(expired, ctx.callout_handle_); |
1815 | 0 | } |
1816 | | |
1817 | | // address, lease type and prefixlen (0) stay the same |
1818 | 0 | expired->iaid_ = ctx.currentIA().iaid_; |
1819 | 0 | expired->duid_ = ctx.duid_; |
1820 | 0 | expired->hwaddr_ = ctx.hwaddr_; |
1821 | | |
1822 | | // Calculate life times. |
1823 | 0 | expired->preferred_lft_ = 0; |
1824 | 0 | expired->valid_lft_ = 0; |
1825 | 0 | if ((expired->type_ == Lease::TYPE_PD) && |
1826 | 0 | useMinLifetimes6(ctx, expired->addr_, prefix_len)) { |
1827 | 0 | getMinLifetimes6(ctx, expired->preferred_lft_, expired->valid_lft_); |
1828 | 0 | } else { |
1829 | 0 | getLifetimes6(ctx, expired->preferred_lft_, expired->valid_lft_); |
1830 | 0 | } |
1831 | 0 | expired->reuseable_valid_lft_ = 0; |
1832 | |
|
1833 | 0 | expired->cltt_ = time(0); |
1834 | 0 | expired->subnet_id_ = ctx.subnet_->getID(); |
1835 | 0 | expired->hostname_ = ctx.hostname_; |
1836 | 0 | expired->fqdn_fwd_ = ctx.fwd_dns_update_; |
1837 | 0 | expired->fqdn_rev_ = ctx.rev_dns_update_; |
1838 | 0 | expired->prefixlen_ = prefix_len; |
1839 | 0 | expired->state_ = Lease::STATE_DEFAULT; |
1840 | |
|
1841 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA, |
1842 | 0 | ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA) |
1843 | 0 | .arg(ctx.query_->getLabel()) |
1844 | 0 | .arg(expired->toText()); |
1845 | | |
1846 | | // Let's execute all callouts registered for lease6_select |
1847 | 0 | if (ctx.callout_handle_ && |
1848 | 0 | HooksManager::calloutsPresent(hook_index_lease6_select_)) { |
1849 | | |
1850 | | // Use the RAII wrapper to make sure that the callout handle state is |
1851 | | // reset when this object goes out of scope. All hook points must do |
1852 | | // it to prevent possible circular dependency between the callout |
1853 | | // handle and its arguments. |
1854 | 0 | ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_); |
1855 | | |
1856 | | // Enable copying options from the packet within hook library. |
1857 | 0 | ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_); |
1858 | | |
1859 | | // Pass necessary arguments |
1860 | | |
1861 | | // Pass the original packet |
1862 | 0 | ctx.callout_handle_->setArgument("query6", ctx.query_); |
1863 | | |
1864 | | // Subnet from which we do the allocation |
1865 | 0 | ctx.callout_handle_->setArgument("subnet6", ctx.subnet_); |
1866 | | |
1867 | | // Is this solicit (fake = true) or request (fake = false) |
1868 | 0 | ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_); |
1869 | | |
1870 | | // The lease that will be assigned to a client |
1871 | 0 | ctx.callout_handle_->setArgument("lease6", expired); |
1872 | | |
1873 | | // Call the callouts |
1874 | 0 | HooksManager::callCallouts(hook_index_lease6_select_, *ctx.callout_handle_); |
1875 | |
|
1876 | 0 | callout_status = ctx.callout_handle_->getStatus(); |
1877 | | |
1878 | | // Callouts decided to skip the action. This means that the lease is not |
1879 | | // assigned, so the client will get NoAddrAvail as a result. The lease |
1880 | | // won't be inserted into the database. |
1881 | 0 | if (callout_status == CalloutHandle::NEXT_STEP_SKIP) { |
1882 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP); |
1883 | 0 | return (Lease6Ptr()); |
1884 | 0 | } |
1885 | | |
1886 | | /// DROP status does not make sense here: |
1887 | | /// In general as the lease cannot be dropped the DROP action |
1888 | | /// has no object so SKIP is the right "cancel" status and |
1889 | | /// DROP should not be a synonym as it introduces ambiguity. |
1890 | | |
1891 | | // Let's use whatever callout returned. Hopefully it is the same lease |
1892 | | // we handed to it. |
1893 | 0 | ctx.callout_handle_->getArgument("lease6", expired); |
1894 | 0 | } |
1895 | | |
1896 | 0 | if (!ctx.fake_allocation_) { |
1897 | | // Add (update) the extended information on the lease. |
1898 | 0 | updateLease6ExtendedInfo(expired, ctx); |
1899 | |
|
1900 | 0 | auto const& pool = ctx.subnet_->getPool(ctx.currentIA().type_, expired->addr_, false); |
1901 | 0 | if (pool) { |
1902 | 0 | expired->pool_id_ = pool->getID(); |
1903 | 0 | } |
1904 | | |
1905 | | // for REQUEST we do update the lease |
1906 | 0 | LeaseMgrFactory::instance().updateLease6(expired); |
1907 | | |
1908 | | // If the lease is in the current subnet we need to account |
1909 | | // for the re-assignment of The lease. |
1910 | 0 | if (ctx.subnet_->inPool(ctx.currentIA().type_, expired->addr_)) { |
1911 | 0 | StatsMgr::instance().addValue( |
1912 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
1913 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1914 | 0 | "assigned-nas" : "assigned-pds"), |
1915 | 0 | static_cast<int64_t>(1)); |
1916 | |
|
1917 | 0 | StatsMgr::instance().addValue( |
1918 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
1919 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1920 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds"), |
1921 | 0 | static_cast<int64_t>(1)); |
1922 | |
|
1923 | 0 | if (pool) { |
1924 | 0 | StatsMgr::instance().addValue( |
1925 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
1926 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1927 | 0 | "pool" : "pd-pool", pool->getID(), |
1928 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1929 | 0 | "assigned-nas" : "assigned-pds")), |
1930 | 0 | static_cast<int64_t>(1)); |
1931 | |
|
1932 | 0 | StatsMgr::instance().addValue( |
1933 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
1934 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1935 | 0 | "pool" : "pd-pool", pool->getID(), |
1936 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
1937 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds")), |
1938 | 0 | static_cast<int64_t>(1)); |
1939 | 0 | } |
1940 | |
|
1941 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1942 | 0 | "assigned-nas" : "assigned-pds", |
1943 | 0 | static_cast<int64_t>(1)); |
1944 | |
|
1945 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
1946 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds", |
1947 | 0 | static_cast<int64_t>(1)); |
1948 | 0 | } |
1949 | 0 | } |
1950 | | |
1951 | | // We do nothing for SOLICIT. We'll just update database when |
1952 | | // the client gets back to us with REQUEST message. |
1953 | | |
1954 | | // it's not really expired at this stage anymore - let's return it as |
1955 | | // an updated lease |
1956 | 0 | return (expired); |
1957 | 0 | } |
1958 | | |
1959 | | namespace { |
1960 | | void sanitizeLifetimes6(AllocEngine::ClientContext6& ctx, |
1961 | 0 | uint32_t& preferred, uint32_t& valid) { |
1962 | | // If preferred isn't set or insane, calculate it as valid_lft * 0.625. |
1963 | 0 | if (!preferred || preferred > valid) { |
1964 | 0 | preferred = ((valid * 5)/8); |
1965 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
1966 | 0 | ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME) |
1967 | 0 | .arg(ctx.query_->getLabel()) |
1968 | 0 | .arg(preferred); |
1969 | 0 | } |
1970 | 0 | } |
1971 | | } // end of anonymous namespace. |
1972 | | |
1973 | | void |
1974 | 0 | AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& valid) { |
1975 | | // If the triplets are specified in one of our classes use it. |
1976 | | // We use the first one we find for each lifetime. |
1977 | 0 | Triplet<uint32_t> candidate_preferred; |
1978 | 0 | Triplet<uint32_t> candidate_valid; |
1979 | 0 | const ClientClasses classes = ctx.query_->getClasses(); |
1980 | 0 | if (!classes.empty()) { |
1981 | | // Let's get class definitions |
1982 | 0 | const ClientClassDictionaryPtr& dict = |
1983 | 0 | CfgMgr::instance().getCurrentCfg()->getClientClassDictionary(); |
1984 | | |
1985 | | // Iterate over the assigned class definitions. |
1986 | 0 | int have_both = 0; |
1987 | 0 | for (auto const& name : classes) { |
1988 | 0 | ClientClassDefPtr cl = dict->findClass(name); |
1989 | 0 | if (candidate_preferred.unspecified() && |
1990 | 0 | (cl && (!cl->getPreferred().unspecified()))) { |
1991 | 0 | candidate_preferred = cl->getPreferred(); |
1992 | 0 | ++have_both; |
1993 | 0 | } |
1994 | |
|
1995 | 0 | if (candidate_valid.unspecified() && |
1996 | 0 | (cl && (!cl->getValid().unspecified()))) { |
1997 | 0 | candidate_valid = cl->getValid(); |
1998 | 0 | ++have_both; |
1999 | 0 | } |
2000 | 0 | if (have_both == 2) { |
2001 | 0 | break; |
2002 | 0 | } |
2003 | 0 | } |
2004 | 0 | } |
2005 | | |
2006 | | // If no classes specified preferred lifetime, get it from the subnet. |
2007 | 0 | if (!candidate_preferred) { |
2008 | 0 | candidate_preferred = ctx.subnet_->getPreferred(); |
2009 | 0 | } |
2010 | | |
2011 | | // If no classes specified valid lifetime, get it from the subnet. |
2012 | 0 | if (!candidate_valid) { |
2013 | 0 | candidate_valid = ctx.subnet_->getValid(); |
2014 | 0 | } |
2015 | | |
2016 | | // Set the outbound parameters to the values we have so far. |
2017 | 0 | preferred = candidate_preferred; |
2018 | 0 | valid = candidate_valid; |
2019 | | |
2020 | | // If client requested either value, use the requested value(s) bounded by |
2021 | | // the candidate triplet(s). |
2022 | 0 | if (!ctx.currentIA().hints_.empty()) { |
2023 | 0 | if (ctx.currentIA().hints_[0].getPreferred()) { |
2024 | 0 | preferred = candidate_preferred.get(ctx.currentIA().hints_[0].getPreferred()); |
2025 | 0 | } |
2026 | |
|
2027 | 0 | if (ctx.currentIA().hints_[0].getValid()) { |
2028 | 0 | valid = candidate_valid.get(ctx.currentIA().hints_[0].getValid()); |
2029 | 0 | } |
2030 | 0 | } |
2031 | |
|
2032 | 0 | sanitizeLifetimes6(ctx, preferred, valid); |
2033 | 0 | } |
2034 | | |
2035 | | void |
2036 | | AllocEngine::getMinLifetimes6(ClientContext6& ctx, uint32_t& preferred, |
2037 | 0 | uint32_t& valid) { |
2038 | | // If the triplets are specified in one of our classes use it. |
2039 | | // We use the first one we find for each lifetime. |
2040 | 0 | Triplet<uint32_t> candidate_preferred; |
2041 | 0 | Triplet<uint32_t> candidate_valid; |
2042 | 0 | const ClientClasses classes = ctx.query_->getClasses(); |
2043 | 0 | if (!classes.empty()) { |
2044 | | // Let's get class definitions |
2045 | 0 | const ClientClassDictionaryPtr& dict = |
2046 | 0 | CfgMgr::instance().getCurrentCfg()->getClientClassDictionary(); |
2047 | | |
2048 | | // Iterate over the assigned class definitions. |
2049 | 0 | int have_both = 0; |
2050 | 0 | for (auto const& name : classes) { |
2051 | 0 | ClientClassDefPtr cl = dict->findClass(name); |
2052 | 0 | if (candidate_preferred.unspecified() && |
2053 | 0 | (cl && (!cl->getPreferred().unspecified()))) { |
2054 | 0 | candidate_preferred = cl->getPreferred(); |
2055 | 0 | ++have_both; |
2056 | 0 | } |
2057 | |
|
2058 | 0 | if (candidate_valid.unspecified() && |
2059 | 0 | (cl && (!cl->getValid().unspecified()))) { |
2060 | 0 | candidate_valid = cl->getValid(); |
2061 | 0 | ++have_both; |
2062 | 0 | } |
2063 | 0 | if (have_both == 2) { |
2064 | 0 | break; |
2065 | 0 | } |
2066 | 0 | } |
2067 | 0 | } |
2068 | | |
2069 | | // If no classes specified preferred lifetime, get it from the subnet. |
2070 | 0 | if (!candidate_preferred) { |
2071 | 0 | candidate_preferred = ctx.subnet_->getPreferred(); |
2072 | 0 | } |
2073 | | |
2074 | | // If no classes specified valid lifetime, get it from the subnet. |
2075 | 0 | if (!candidate_valid) { |
2076 | 0 | candidate_valid = ctx.subnet_->getValid(); |
2077 | 0 | } |
2078 | | |
2079 | | // Save remaining values. |
2080 | 0 | uint32_t remain_preferred(preferred); |
2081 | 0 | uint32_t remain_valid(valid); |
2082 | | |
2083 | | // Set the outbound parameters to the minimal values. |
2084 | 0 | preferred = candidate_preferred.getMin(); |
2085 | 0 | valid = candidate_valid.getMin(); |
2086 | | |
2087 | | // Return at least the remaining values. |
2088 | 0 | if (remain_preferred > preferred) { |
2089 | 0 | preferred = remain_preferred; |
2090 | 0 | } |
2091 | 0 | if (remain_valid > valid) { |
2092 | 0 | valid = remain_valid; |
2093 | 0 | } |
2094 | |
|
2095 | 0 | sanitizeLifetimes6(ctx, preferred, valid); |
2096 | 0 | } |
2097 | | |
2098 | | Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx, |
2099 | | const IOAddress& addr, |
2100 | | uint8_t prefix_len, |
2101 | 0 | CalloutHandle::CalloutNextStep& callout_status) { |
2102 | |
|
2103 | 0 | if (ctx.currentIA().type_ != Lease::TYPE_PD) { |
2104 | 0 | prefix_len = 128; // non-PD lease types must be always /128 |
2105 | 0 | } |
2106 | |
|
2107 | 0 | uint32_t preferred = 0; |
2108 | 0 | uint32_t valid = 0; |
2109 | 0 | if ((ctx.currentIA().type_ == Lease::TYPE_PD) && |
2110 | 0 | useMinLifetimes6(ctx, addr, prefix_len)) { |
2111 | 0 | getMinLifetimes6(ctx, preferred, valid); |
2112 | 0 | } else { |
2113 | 0 | getLifetimes6(ctx, preferred, valid); |
2114 | 0 | } |
2115 | |
|
2116 | 0 | Lease6Ptr lease(new Lease6(ctx.currentIA().type_, addr, ctx.duid_, |
2117 | 0 | ctx.currentIA().iaid_, preferred, |
2118 | 0 | valid, ctx.subnet_->getID(), |
2119 | 0 | ctx.hwaddr_, prefix_len)); |
2120 | |
|
2121 | 0 | lease->fqdn_fwd_ = ctx.fwd_dns_update_; |
2122 | 0 | lease->fqdn_rev_ = ctx.rev_dns_update_; |
2123 | 0 | lease->hostname_ = ctx.hostname_; |
2124 | | |
2125 | | // Let's execute all callouts registered for lease6_select |
2126 | 0 | if (ctx.callout_handle_ && |
2127 | 0 | HooksManager::calloutsPresent(hook_index_lease6_select_)) { |
2128 | | |
2129 | | // Use the RAII wrapper to make sure that the callout handle state is |
2130 | | // reset when this object goes out of scope. All hook points must do |
2131 | | // it to prevent possible circular dependency between the callout |
2132 | | // handle and its arguments. |
2133 | 0 | ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_); |
2134 | | |
2135 | | // Enable copying options from the packet within hook library. |
2136 | 0 | ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_); |
2137 | | |
2138 | | // Pass necessary arguments |
2139 | | |
2140 | | // Pass the original packet |
2141 | 0 | ctx.callout_handle_->setArgument("query6", ctx.query_); |
2142 | | |
2143 | | // Subnet from which we do the allocation |
2144 | 0 | ctx.callout_handle_->setArgument("subnet6", ctx.subnet_); |
2145 | | |
2146 | | // Is this solicit (fake = true) or request (fake = false) |
2147 | 0 | ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_); |
2148 | | |
2149 | | // The lease that will be assigned to a client |
2150 | 0 | ctx.callout_handle_->setArgument("lease6", lease); |
2151 | | |
2152 | | // This is the first callout, so no need to clear any arguments |
2153 | 0 | HooksManager::callCallouts(hook_index_lease6_select_, *ctx.callout_handle_); |
2154 | |
|
2155 | 0 | callout_status = ctx.callout_handle_->getStatus(); |
2156 | | |
2157 | | // Callouts decided to skip the action. This means that the lease is not |
2158 | | // assigned, so the client will get NoAddrAvail as a result. The lease |
2159 | | // won't be inserted into the database. |
2160 | 0 | if (callout_status == CalloutHandle::NEXT_STEP_SKIP) { |
2161 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP); |
2162 | 0 | return (Lease6Ptr()); |
2163 | 0 | } |
2164 | | |
2165 | | // Let's use whatever callout returned. Hopefully it is the same lease |
2166 | | // we handed to it. |
2167 | 0 | ctx.callout_handle_->getArgument("lease6", lease); |
2168 | 0 | } |
2169 | | |
2170 | 0 | if (!ctx.fake_allocation_) { |
2171 | | // Add (update) the extended information on the lease. |
2172 | 0 | updateLease6ExtendedInfo(lease, ctx); |
2173 | |
|
2174 | 0 | auto const& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false); |
2175 | 0 | if (pool) { |
2176 | 0 | lease->pool_id_ = pool->getID(); |
2177 | 0 | } |
2178 | | |
2179 | | // That is a real (REQUEST) allocation |
2180 | 0 | bool status = LeaseMgrFactory::instance().addLease(lease); |
2181 | |
|
2182 | 0 | if (status) { |
2183 | | // The lease insertion succeeded - if the lease is in the |
2184 | | // current subnet lets bump up the statistic. |
2185 | 0 | if (ctx.subnet_->inPool(ctx.currentIA().type_, addr)) { |
2186 | 0 | StatsMgr::instance().addValue( |
2187 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2188 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2189 | 0 | "assigned-nas" : "assigned-pds"), |
2190 | 0 | static_cast<int64_t>(1)); |
2191 | |
|
2192 | 0 | StatsMgr::instance().addValue( |
2193 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2194 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2195 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds"), |
2196 | 0 | static_cast<int64_t>(1)); |
2197 | |
|
2198 | 0 | if (pool) { |
2199 | 0 | StatsMgr::instance().addValue( |
2200 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2201 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2202 | 0 | "pool" : "pd-pool", pool->getID(), |
2203 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2204 | 0 | "assigned-nas" : "assigned-pds")), |
2205 | 0 | static_cast<int64_t>(1)); |
2206 | |
|
2207 | 0 | StatsMgr::instance().addValue( |
2208 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2209 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2210 | 0 | "pool" : "pd-pool", pool->getID(), |
2211 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2212 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds")), |
2213 | 0 | static_cast<int64_t>(1)); |
2214 | 0 | } |
2215 | |
|
2216 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2217 | 0 | "assigned-nas" : "assigned-pds", |
2218 | 0 | static_cast<int64_t>(1)); |
2219 | |
|
2220 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2221 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds", |
2222 | 0 | static_cast<int64_t>(1)); |
2223 | 0 | } |
2224 | | |
2225 | | // Record it so it won't be updated twice. |
2226 | 0 | ctx.currentIA().addNewResource(addr, prefix_len); |
2227 | |
|
2228 | 0 | return (lease); |
2229 | 0 | } else { |
2230 | | // One of many failures with LeaseMgr (e.g. lost connection to the |
2231 | | // database, database failed etc.). One notable case for that |
2232 | | // is that we are working in multi-process mode and we lost a race |
2233 | | // (some other process got that address first) |
2234 | 0 | return (Lease6Ptr()); |
2235 | 0 | } |
2236 | 0 | } else { |
2237 | | // That is only fake (SOLICIT without rapid-commit) allocation |
2238 | | |
2239 | | // It is for advertise only. We should not insert the lease and callers |
2240 | | // have already verified the lease does not exist in the database. |
2241 | 0 | return (lease); |
2242 | 0 | } |
2243 | 0 | } |
2244 | | |
2245 | | void |
2246 | | AllocEngine::getRemaining(const Lease6Ptr& lease, uint32_t& preferred, |
2247 | 0 | uint32_t& valid) { |
2248 | 0 | valid = 0; |
2249 | 0 | preferred = 0; |
2250 | 0 | if (!lease || (lease->state_ != Lease::STATE_DEFAULT)) { |
2251 | 0 | return; |
2252 | 0 | } |
2253 | 0 | time_t now = time(0); |
2254 | | // Refuse time not going forward. |
2255 | 0 | if (lease->cltt_ > now) { |
2256 | 0 | return; |
2257 | 0 | } |
2258 | 0 | uint32_t age = now - lease->cltt_; |
2259 | | // Already expired. |
2260 | 0 | if (age >= lease->valid_lft_) { |
2261 | 0 | return; |
2262 | 0 | } |
2263 | 0 | valid = lease->valid_lft_ - age; |
2264 | 0 | if (age >= lease->preferred_lft_) { |
2265 | 0 | return; |
2266 | 0 | } |
2267 | 0 | preferred = lease->preferred_lft_ - age; |
2268 | 0 | } |
2269 | | |
2270 | | Lease6Collection |
2271 | 0 | AllocEngine::renewLeases6(ClientContext6& ctx) { |
2272 | 0 | try { |
2273 | 0 | if (!ctx.subnet_) { |
2274 | 0 | isc_throw(InvalidOperation, "Subnet is required for allocation"); |
2275 | 0 | } |
2276 | | |
2277 | 0 | if (!ctx.duid_) { |
2278 | 0 | isc_throw(InvalidOperation, "DUID is mandatory for allocation"); |
2279 | 0 | } |
2280 | | |
2281 | | // Check if there are any leases for this client. |
2282 | 0 | ConstSubnet6Ptr subnet = ctx.subnet_; |
2283 | 0 | Lease6Collection leases; |
2284 | 0 | while (subnet) { |
2285 | 0 | Lease6Collection leases_subnet = |
2286 | 0 | LeaseMgrFactory::instance().getLeases6(ctx.currentIA().type_, |
2287 | 0 | *ctx.duid_, |
2288 | 0 | ctx.currentIA().iaid_, |
2289 | 0 | subnet->getID()); |
2290 | 0 | for (auto const& l : leases_subnet) { |
2291 | 0 | if (l->state_ != Lease::STATE_REGISTERED) { |
2292 | 0 | leases.push_back(l); |
2293 | 0 | } |
2294 | 0 | } |
2295 | 0 | subnet = subnet->getNextSubnet(ctx.subnet_); |
2296 | 0 | } |
2297 | |
|
2298 | 0 | if (!leases.empty()) { |
2299 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2300 | 0 | ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED) |
2301 | 0 | .arg(ctx.query_->getLabel()); |
2302 | | |
2303 | | // Check if the existing leases are reserved for someone else. |
2304 | | // If they're not, we're ok to keep using them. |
2305 | 0 | removeNonmatchingReservedLeases6(ctx, leases); |
2306 | 0 | } |
2307 | |
|
2308 | 0 | if (!ctx.hosts_.empty()) { |
2309 | |
|
2310 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2311 | 0 | ALLOC_ENGINE_V6_RENEW_HR) |
2312 | 0 | .arg(ctx.query_->getLabel()); |
2313 | | |
2314 | | // If we have host reservation, allocate those leases. |
2315 | 0 | allocateReservedLeases6(ctx, leases); |
2316 | | |
2317 | | // There's one more check to do. Let's remove leases that are not |
2318 | | // matching reservations, i.e. if client X has address A, but there's |
2319 | | // a reservation for address B, we should release A and reassign B. |
2320 | | // Caveat: do this only if we have at least one reserved address. |
2321 | 0 | removeNonreservedLeases6(ctx, leases); |
2322 | 0 | } |
2323 | | |
2324 | | // If we happen to removed all leases, get something new for this guy. |
2325 | | // Depending on the configuration, we may enable or disable granting |
2326 | | // new leases during renewals. This is controlled with the |
2327 | | // allow_new_leases_in_renewals_ field. |
2328 | 0 | if (leases.empty()) { |
2329 | |
|
2330 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2331 | 0 | ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED) |
2332 | 0 | .arg(ctx.query_->getLabel()); |
2333 | |
|
2334 | 0 | leases = allocateUnreservedLeases6(ctx); |
2335 | 0 | } |
2336 | | |
2337 | | // Extend all existing leases that passed all checks. |
2338 | 0 | for (auto const& l : leases) { |
2339 | 0 | if (ctx.currentIA().isNewResource(l->addr_, |
2340 | 0 | l->prefixlen_)) { |
2341 | | // This lease was just created so is already extended. |
2342 | 0 | continue; |
2343 | 0 | } |
2344 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL, |
2345 | 0 | ALLOC_ENGINE_V6_EXTEND_LEASE) |
2346 | 0 | .arg(ctx.query_->getLabel()) |
2347 | 0 | .arg(l->typeToText(l->type_)) |
2348 | 0 | .arg(l->addr_); |
2349 | 0 | extendLease6(ctx, l); |
2350 | 0 | } |
2351 | |
|
2352 | 0 | if (!leases.empty()) { |
2353 | | // If there are any leases allocated, let's store in them in the |
2354 | | // IA context so as they are available when we process subsequent |
2355 | | // IAs. |
2356 | 0 | for (auto const& lease : leases) { |
2357 | 0 | ctx.addAllocatedResource(lease->addr_, lease->prefixlen_); |
2358 | 0 | ctx.new_leases_.push_back(lease); |
2359 | 0 | } |
2360 | 0 | } |
2361 | |
|
2362 | 0 | return (leases); |
2363 | |
|
2364 | 0 | } catch (const NoSuchLease& e) { |
2365 | 0 | isc_throw(NoSuchLease, "detected data race in AllocEngine::renewLeases6: " << e.what()); |
2366 | |
|
2367 | 0 | } catch (const isc::Exception& e) { |
2368 | | |
2369 | | // Some other error, return an empty lease. |
2370 | 0 | LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_EXTEND_ERROR) |
2371 | 0 | .arg(ctx.query_->getLabel()) |
2372 | 0 | .arg(e.what()); |
2373 | 0 | } |
2374 | | |
2375 | 0 | return (Lease6Collection()); |
2376 | 0 | } |
2377 | | |
2378 | | void |
2379 | 0 | AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) { |
2380 | |
|
2381 | 0 | if (!lease || !ctx.subnet_) { |
2382 | 0 | return; |
2383 | 0 | } |
2384 | | |
2385 | | // It is likely that the lease for which we're extending the lifetime doesn't |
2386 | | // belong to the current but a sibling subnet. |
2387 | 0 | if (ctx.subnet_->getID() != lease->subnet_id_) { |
2388 | 0 | SharedNetwork6Ptr network; |
2389 | 0 | ctx.subnet_->getSharedNetwork(network); |
2390 | 0 | if (network) { |
2391 | 0 | ConstSubnet6Ptr subnet = |
2392 | 0 | network->getSubnet(SubnetID(lease->subnet_id_)); |
2393 | | // Found the actual subnet this lease belongs to. Stick to this |
2394 | | // subnet. |
2395 | 0 | if (subnet) { |
2396 | 0 | ctx.subnet_ = subnet; |
2397 | 0 | } |
2398 | 0 | } |
2399 | 0 | } |
2400 | | |
2401 | | // If the lease is not global and it is either out of range (NAs only) or it |
2402 | | // is not permitted by subnet client classification, delete it. |
2403 | 0 | if (!(ctx.hasGlobalReservation(makeIPv6Resrv(*lease))) && |
2404 | 0 | (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) || |
2405 | 0 | !ctx.subnet_->clientSupported(ctx.query_->getClasses()))) { |
2406 | | // Oh dear, the lease is no longer valid. We need to get rid of it. |
2407 | | |
2408 | | // Remove this lease from LeaseMgr |
2409 | 0 | if (!LeaseMgrFactory::instance().deleteLease(lease)) { |
2410 | | // Concurrent delete performed by other instance which should |
2411 | | // properly handle dns and stats updates. |
2412 | 0 | return; |
2413 | 0 | } |
2414 | | |
2415 | | // Updated DNS if required. |
2416 | 0 | queueNCR(CHG_REMOVE, lease); |
2417 | | |
2418 | | // Need to decrease statistic for assigned addresses. |
2419 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2420 | 0 | "assigned-nas" : "assigned-pds", static_cast<int64_t>(-1)); |
2421 | |
|
2422 | 0 | StatsMgr::instance().addValue( |
2423 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2424 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2425 | 0 | "assigned-nas" : "assigned-pds"), |
2426 | 0 | static_cast<int64_t>(-1)); |
2427 | |
|
2428 | 0 | auto const& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false); |
2429 | 0 | if (pool) { |
2430 | 0 | StatsMgr::instance().addValue( |
2431 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2432 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2433 | 0 | "pool" : "pd-pool", pool->getID(), |
2434 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2435 | 0 | "assigned-nas" : "assigned-pds")), |
2436 | 0 | static_cast<int64_t>(-1)); |
2437 | 0 | } |
2438 | | |
2439 | | // Add it to the removed leases list. |
2440 | 0 | ctx.currentIA().old_leases_.push_back(lease); |
2441 | |
|
2442 | 0 | return; |
2443 | 0 | } |
2444 | | |
2445 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA, |
2446 | 0 | ALLOC_ENGINE_V6_EXTEND_LEASE_DATA) |
2447 | 0 | .arg(ctx.query_->getLabel()) |
2448 | 0 | .arg(lease->toText()); |
2449 | | |
2450 | | // Keep the old data in case the callout tells us to skip update. |
2451 | 0 | Lease6Ptr old_data(new Lease6(*lease)); |
2452 | |
|
2453 | 0 | bool changed = false; |
2454 | | |
2455 | | // Calculate life times. |
2456 | 0 | uint32_t current_preferred_lft = lease->preferred_lft_; |
2457 | 0 | if ((lease->type_ == Lease::TYPE_PD) && |
2458 | 0 | useMinLifetimes6(ctx, lease->addr_, lease->prefixlen_)) { |
2459 | 0 | uint32_t remain_preferred_lft(0); |
2460 | 0 | uint32_t remain_valid_lft(0); |
2461 | 0 | getRemaining(lease, remain_preferred_lft, remain_valid_lft); |
2462 | 0 | lease->preferred_lft_ = remain_preferred_lft; |
2463 | 0 | lease->valid_lft_ = remain_valid_lft; |
2464 | 0 | getMinLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_); |
2465 | 0 | } else { |
2466 | 0 | getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_); |
2467 | 0 | } |
2468 | | |
2469 | | // If either has changed set the changed flag. |
2470 | 0 | if ((lease->preferred_lft_ != current_preferred_lft) || |
2471 | 0 | (lease->valid_lft_ != lease->current_valid_lft_)) { |
2472 | 0 | changed = true; |
2473 | 0 | } |
2474 | |
|
2475 | 0 | lease->cltt_ = time(NULL); |
2476 | 0 | if ((lease->fqdn_fwd_ != ctx.fwd_dns_update_) || |
2477 | 0 | (lease->fqdn_rev_ != ctx.rev_dns_update_) || |
2478 | 0 | (lease->hostname_ != ctx.hostname_)) { |
2479 | 0 | changed = true; |
2480 | 0 | lease->hostname_ = ctx.hostname_; |
2481 | 0 | lease->fqdn_fwd_ = ctx.fwd_dns_update_; |
2482 | 0 | lease->fqdn_rev_ = ctx.rev_dns_update_; |
2483 | 0 | } |
2484 | 0 | if ((!ctx.hwaddr_ && lease->hwaddr_) || |
2485 | 0 | (ctx.hwaddr_ && |
2486 | 0 | (!lease->hwaddr_ || (*ctx.hwaddr_ != *lease->hwaddr_)))) { |
2487 | 0 | changed = true; |
2488 | 0 | lease->hwaddr_ = ctx.hwaddr_; |
2489 | 0 | } |
2490 | 0 | if (lease->state_ != Lease::STATE_DEFAULT) { |
2491 | 0 | changed = true; |
2492 | 0 | lease->state_ = Lease::STATE_DEFAULT; |
2493 | 0 | } |
2494 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA, |
2495 | 0 | ALLOC_ENGINE_V6_EXTEND_NEW_LEASE_DATA) |
2496 | 0 | .arg(ctx.query_->getLabel()) |
2497 | 0 | .arg(lease->toText()); |
2498 | |
|
2499 | 0 | bool skip = false; |
2500 | | // Get the callouts specific for the processed message and execute them. |
2501 | 0 | int hook_point = ctx.query_->getType() == DHCPV6_RENEW ? |
2502 | 0 | Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_; |
2503 | 0 | if (HooksManager::calloutsPresent(hook_point)) { |
2504 | 0 | CalloutHandlePtr callout_handle = ctx.callout_handle_; |
2505 | | |
2506 | | // Use the RAII wrapper to make sure that the callout handle state is |
2507 | | // reset when this object goes out of scope. All hook points must do |
2508 | | // it to prevent possible circular dependency between the callout |
2509 | | // handle and its arguments. |
2510 | 0 | ScopedCalloutHandleState callout_handle_state(callout_handle); |
2511 | | |
2512 | | // Enable copying options from the packet within hook library. |
2513 | 0 | ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_); |
2514 | | |
2515 | | // Pass the original packet |
2516 | 0 | callout_handle->setArgument("query6", ctx.query_); |
2517 | | |
2518 | | // Pass the lease to be updated |
2519 | 0 | callout_handle->setArgument("lease6", lease); |
2520 | | |
2521 | | // Pass the IA option to be sent in response |
2522 | 0 | if (lease->type_ == Lease::TYPE_NA) { |
2523 | 0 | callout_handle->setArgument("ia_na", ctx.currentIA().ia_rsp_); |
2524 | 0 | } else { |
2525 | 0 | callout_handle->setArgument("ia_pd", ctx.currentIA().ia_rsp_); |
2526 | 0 | } |
2527 | | |
2528 | | // Call all installed callouts |
2529 | 0 | HooksManager::callCallouts(hook_point, *callout_handle); |
2530 | | |
2531 | | // Callouts decided to skip the next processing step. The next |
2532 | | // processing step would actually renew the lease, so skip at this |
2533 | | // stage means "keep the old lease as it is". |
2534 | 0 | if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) { |
2535 | 0 | skip = true; |
2536 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, |
2537 | 0 | DHCPSRV_HOOK_LEASE6_EXTEND_SKIP) |
2538 | 0 | .arg(ctx.query_->getName()); |
2539 | 0 | } |
2540 | | |
2541 | | /// DROP status does not make sense here. |
2542 | 0 | } |
2543 | |
|
2544 | 0 | if (!skip) { |
2545 | 0 | bool update_stats = false; |
2546 | | |
2547 | | // If the lease we're renewing has expired, we need to reclaim this |
2548 | | // lease before we can renew it. |
2549 | 0 | if (old_data->expired()) { |
2550 | 0 | reclaimExpiredLease(old_data, ctx.callout_handle_); |
2551 | | |
2552 | | // If the lease is in the current subnet we need to account |
2553 | | // for the re-assignment of the lease. |
2554 | 0 | if (ctx.subnet_->inPool(ctx.currentIA().type_, old_data->addr_)) { |
2555 | 0 | update_stats = true; |
2556 | 0 | } |
2557 | 0 | changed = true; |
2558 | 0 | } |
2559 | | |
2560 | | // @todo should we call storeLease6ExtendedInfo() here ? |
2561 | 0 | updateLease6ExtendedInfo(lease, ctx); |
2562 | 0 | if (lease->extended_info_action_ == Lease6::ACTION_UPDATE) { |
2563 | 0 | changed = true; |
2564 | 0 | } |
2565 | | |
2566 | | // Try to reuse the lease. |
2567 | 0 | if (!changed) { |
2568 | 0 | setLeaseReusable(lease, current_preferred_lft, ctx); |
2569 | 0 | } |
2570 | | |
2571 | | // Now that the lease has been reclaimed, we can go ahead and update it |
2572 | | // in the lease database. |
2573 | 0 | if (lease->reuseable_valid_lft_ == 0) { |
2574 | 0 | auto const& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false); |
2575 | 0 | if (pool) { |
2576 | 0 | lease->pool_id_ = pool->getID(); |
2577 | 0 | } |
2578 | 0 | LeaseMgrFactory::instance().updateLease6(lease); |
2579 | 0 | } else { |
2580 | | // Server looks at changed_leases_ (i.e. old_data) when deciding |
2581 | | // on DNS updates etc. |
2582 | 0 | old_data->reuseable_valid_lft_ = lease->reuseable_valid_lft_; |
2583 | 0 | } |
2584 | |
|
2585 | 0 | if (update_stats) { |
2586 | 0 | StatsMgr::instance().addValue( |
2587 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2588 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2589 | 0 | "assigned-nas" : "assigned-pds"), |
2590 | 0 | static_cast<int64_t>(1)); |
2591 | |
|
2592 | 0 | StatsMgr::instance().addValue( |
2593 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2594 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2595 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds"), |
2596 | 0 | static_cast<int64_t>(1)); |
2597 | |
|
2598 | 0 | auto const& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false); |
2599 | 0 | if (pool) { |
2600 | 0 | StatsMgr::instance().addValue( |
2601 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2602 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2603 | 0 | "pool" : "pd-pool", pool->getID(), |
2604 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2605 | 0 | "assigned-nas" : "assigned-pds")), |
2606 | 0 | static_cast<int64_t>(1)); |
2607 | |
|
2608 | 0 | StatsMgr::instance().addValue( |
2609 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2610 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2611 | 0 | "pool" : "pd-pool", pool->getID(), |
2612 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2613 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds")), |
2614 | 0 | static_cast<int64_t>(1)); |
2615 | 0 | } |
2616 | |
|
2617 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2618 | 0 | "assigned-nas" : "assigned-pds", |
2619 | 0 | static_cast<int64_t>(1)); |
2620 | | |
2621 | |
|
2622 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2623 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds", |
2624 | 0 | static_cast<int64_t>(1)); |
2625 | 0 | } |
2626 | |
|
2627 | 0 | } else { |
2628 | | // Copy back the original date to the lease. For MySQL it doesn't make |
2629 | | // much sense, but for memfile, the Lease6Ptr points to the actual lease |
2630 | | // in memfile, so the actual update is performed when we manipulate |
2631 | | // fields of returned Lease6Ptr, the actual updateLease6() is no-op. |
2632 | 0 | *lease = *old_data; |
2633 | 0 | } |
2634 | | |
2635 | | // Add the old lease to the changed lease list. This allows the server |
2636 | | // to make decisions regarding DNS updates. |
2637 | 0 | ctx.currentIA().changed_leases_.push_back(old_data); |
2638 | 0 | } |
2639 | | |
2640 | | Lease6Collection |
2641 | 0 | AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases) { |
2642 | 0 | Lease6Collection updated_leases; |
2643 | 0 | for (auto const& lease_it : leases) { |
2644 | 0 | Lease6Ptr lease(new Lease6(*lease_it)); |
2645 | 0 | if (ctx.currentIA().isNewResource(lease->addr_, lease->prefixlen_)) { |
2646 | | // This lease was just created so is already up to date. |
2647 | 0 | updated_leases.push_back(lease); |
2648 | 0 | continue; |
2649 | 0 | } |
2650 | | |
2651 | 0 | lease->reuseable_valid_lft_ = 0; |
2652 | 0 | lease->fqdn_fwd_ = ctx.fwd_dns_update_; |
2653 | 0 | lease->fqdn_rev_ = ctx.rev_dns_update_; |
2654 | 0 | lease->hostname_ = ctx.hostname_; |
2655 | 0 | uint32_t current_preferred_lft = lease->preferred_lft_; |
2656 | 0 | if (lease->valid_lft_ == 0) { |
2657 | | // The lease was expired by a release: reset zero lifetimes. |
2658 | 0 | lease->preferred_lft_ = 0; |
2659 | 0 | if ((lease->type_ == Lease::TYPE_PD) && |
2660 | 0 | useMinLifetimes6(ctx, lease->addr_, lease->prefixlen_)) { |
2661 | 0 | getMinLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_); |
2662 | 0 | } else { |
2663 | 0 | getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_); |
2664 | 0 | } |
2665 | 0 | } |
2666 | 0 | if (!ctx.fake_allocation_) { |
2667 | 0 | bool update_stats = false; |
2668 | |
|
2669 | 0 | if (lease->state_ == Lease::STATE_EXPIRED_RECLAIMED || lease->state_ == Lease::STATE_RELEASED) { |
2670 | | // Transition lease state to default (aka assigned) |
2671 | 0 | lease->state_ = Lease::STATE_DEFAULT; |
2672 | | |
2673 | | // If the lease is in the current subnet we need to account |
2674 | | // for the re-assignment of the lease. |
2675 | 0 | if (inAllowedPool(ctx, ctx.currentIA().type_, |
2676 | 0 | lease->addr_, true)) { |
2677 | 0 | update_stats = true; |
2678 | 0 | } |
2679 | 0 | } |
2680 | |
|
2681 | 0 | bool fqdn_changed = ((lease->type_ != Lease::TYPE_PD) && |
2682 | 0 | !(lease->hasIdenticalFqdn(*lease_it))); |
2683 | |
|
2684 | 0 | lease->cltt_ = time(NULL); |
2685 | 0 | if (!fqdn_changed) { |
2686 | 0 | setLeaseReusable(lease, current_preferred_lft, ctx); |
2687 | 0 | } |
2688 | |
|
2689 | 0 | if (lease->reuseable_valid_lft_ == 0) { |
2690 | 0 | ctx.currentIA().changed_leases_.push_back(lease_it); |
2691 | 0 | LeaseMgrFactory::instance().updateLease6(lease); |
2692 | 0 | } else { |
2693 | | // Server needs to know about resused leases to avoid DNS updates. |
2694 | 0 | ctx.currentIA().reused_leases_.push_back(lease_it); |
2695 | 0 | } |
2696 | |
|
2697 | 0 | if (update_stats) { |
2698 | 0 | StatsMgr::instance().addValue( |
2699 | 0 | StatsMgr::generateName("subnet", lease->subnet_id_, |
2700 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2701 | 0 | "assigned-nas" : "assigned-pds"), |
2702 | 0 | static_cast<int64_t>(1)); |
2703 | |
|
2704 | 0 | StatsMgr::instance().addValue( |
2705 | 0 | StatsMgr::generateName("subnet", lease->subnet_id_, |
2706 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2707 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds"), |
2708 | 0 | static_cast<int64_t>(1)); |
2709 | |
|
2710 | 0 | auto const& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false); |
2711 | 0 | if (pool) { |
2712 | 0 | StatsMgr::instance().addValue( |
2713 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2714 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2715 | 0 | "pool" : "pd-pool", pool->getID(), |
2716 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2717 | 0 | "assigned-nas" : "assigned-pds")), |
2718 | 0 | static_cast<int64_t>(1)); |
2719 | |
|
2720 | 0 | StatsMgr::instance().addValue( |
2721 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
2722 | 0 | StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2723 | 0 | "pool" : "pd-pool", pool->getID(), |
2724 | 0 | ctx.currentIA().type_ == Lease::TYPE_NA ? |
2725 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds")), |
2726 | 0 | static_cast<int64_t>(1)); |
2727 | 0 | } |
2728 | |
|
2729 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2730 | 0 | "assigned-nas" : "assigned-pds", |
2731 | 0 | static_cast<int64_t>(1)); |
2732 | |
|
2733 | 0 | StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ? |
2734 | 0 | "cumulative-assigned-nas" : "cumulative-assigned-pds", |
2735 | 0 | static_cast<int64_t>(1)); |
2736 | 0 | } |
2737 | 0 | } |
2738 | |
|
2739 | 0 | updated_leases.push_back(lease); |
2740 | 0 | } |
2741 | |
|
2742 | 0 | return (updated_leases); |
2743 | 0 | } |
2744 | | |
2745 | | void |
2746 | | AllocEngine::reclaimExpiredLeases6(const size_t max_leases, |
2747 | | const uint16_t timeout, |
2748 | | const bool remove_lease, |
2749 | 44 | const uint16_t max_unwarned_cycles) { |
2750 | | |
2751 | 44 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2752 | 0 | ALLOC_ENGINE_V6_LEASES_RECLAMATION_START) |
2753 | 0 | .arg(max_leases) |
2754 | 0 | .arg(timeout); |
2755 | | |
2756 | 44 | try { |
2757 | 44 | reclaimExpiredLeases6Internal(max_leases, timeout, remove_lease, |
2758 | 44 | max_unwarned_cycles); |
2759 | 44 | } catch (const std::exception& ex) { |
2760 | 0 | LOG_ERROR(alloc_engine_logger, |
2761 | 0 | ALLOC_ENGINE_V6_LEASES_RECLAMATION_FAILED) |
2762 | 0 | .arg(ex.what()); |
2763 | 0 | } |
2764 | 44 | } |
2765 | | |
2766 | | void |
2767 | | AllocEngine::reclaimExpiredLeases6Internal(const size_t max_leases, |
2768 | | const uint16_t timeout, |
2769 | | const bool remove_lease, |
2770 | 44 | const uint16_t max_unwarned_cycles) { |
2771 | | |
2772 | | // Create stopwatch and automatically start it to measure the time |
2773 | | // taken by the routine. |
2774 | 44 | util::Stopwatch stopwatch; |
2775 | | |
2776 | 44 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
2777 | | |
2778 | | // This value indicates if we have been able to deal with all expired |
2779 | | // leases in this pass. |
2780 | 44 | bool incomplete_reclamation = false; |
2781 | 44 | Lease6Collection leases; |
2782 | | // The value of 0 has a special meaning - reclaim all. |
2783 | 44 | if (max_leases > 0) { |
2784 | | // If the value is non-zero, the caller has limited the number of |
2785 | | // leases to reclaim. We obtain one lease more to see if there will |
2786 | | // be still leases left after this pass. |
2787 | 44 | lease_mgr.getExpiredLeases6(leases, max_leases + 1); |
2788 | | // There are more leases expired leases than we will process in this |
2789 | | // pass, so we should mark it as an incomplete reclamation. We also |
2790 | | // remove this extra lease (which we don't want to process anyway) |
2791 | | // from the collection. |
2792 | 44 | if (leases.size() > max_leases) { |
2793 | 0 | leases.pop_back(); |
2794 | 0 | incomplete_reclamation = true; |
2795 | 0 | } |
2796 | | |
2797 | 44 | } else { |
2798 | | // If there is no limitation on the number of leases to reclaim, |
2799 | | // we will try to process all. Hence, we don't mark it as incomplete |
2800 | | // reclamation just yet. |
2801 | 0 | lease_mgr.getExpiredLeases6(leases, max_leases); |
2802 | 0 | } |
2803 | | |
2804 | | // Do not initialize the callout handle until we know if there are any |
2805 | | // lease6_expire callouts installed. |
2806 | 44 | CalloutHandlePtr callout_handle; |
2807 | 44 | if (!leases.empty() && |
2808 | 0 | HooksManager::calloutsPresent(Hooks.hook_index_lease6_expire_)) { |
2809 | 0 | callout_handle = HooksManager::createCalloutHandle(); |
2810 | 0 | } |
2811 | | |
2812 | 44 | size_t leases_processed = 0; |
2813 | 44 | for (auto const& lease : leases) { |
2814 | |
|
2815 | 0 | try { |
2816 | | // Reclaim the lease. |
2817 | 0 | if (MultiThreadingMgr::instance().getMode()) { |
2818 | | // The reclamation is exclusive of packet processing. |
2819 | 0 | WriteLockGuard exclusive(rw_mutex_); |
2820 | |
|
2821 | 0 | reclaimExpiredLease(lease, remove_lease, callout_handle); |
2822 | 0 | ++leases_processed; |
2823 | 0 | } else { |
2824 | 0 | reclaimExpiredLease(lease, remove_lease, callout_handle); |
2825 | 0 | ++leases_processed; |
2826 | 0 | } |
2827 | |
|
2828 | 0 | } catch (const std::exception& ex) { |
2829 | 0 | LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED) |
2830 | 0 | .arg(lease->addr_.toText()) |
2831 | 0 | .arg(ex.what()); |
2832 | 0 | } |
2833 | | |
2834 | | // Check if we have hit the timeout for running reclamation routine and |
2835 | | // return if we have. We're checking it here, because we always want to |
2836 | | // allow reclaiming at least one lease. |
2837 | 0 | if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) { |
2838 | | // Timeout. This will likely mean that we haven't been able to process |
2839 | | // all leases we wanted to process. The reclamation pass will be |
2840 | | // probably marked as incomplete. |
2841 | 0 | if (!incomplete_reclamation) { |
2842 | 0 | if (leases_processed < leases.size()) { |
2843 | 0 | incomplete_reclamation = true; |
2844 | 0 | } |
2845 | 0 | } |
2846 | |
|
2847 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2848 | 0 | ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT) |
2849 | 0 | .arg(timeout); |
2850 | 0 | break; |
2851 | 0 | } |
2852 | 0 | } |
2853 | | |
2854 | | // Stop measuring the time. |
2855 | 44 | stopwatch.stop(); |
2856 | | |
2857 | | // Mark completion of the lease reclamation routine and present some stats. |
2858 | 44 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2859 | 0 | ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE) |
2860 | 0 | .arg(leases_processed) |
2861 | 0 | .arg(stopwatch.logFormatTotalDuration()); |
2862 | | |
2863 | | // Check if this was an incomplete reclamation and increase the number of |
2864 | | // consecutive incomplete reclamations. |
2865 | 44 | if (incomplete_reclamation) { |
2866 | 0 | ++incomplete_v6_reclamations_; |
2867 | | // If the number of incomplete reclamations is beyond the threshold, we |
2868 | | // need to issue a warning. |
2869 | 0 | if ((max_unwarned_cycles > 0) && |
2870 | 0 | (incomplete_v6_reclamations_ > max_unwarned_cycles)) { |
2871 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW) |
2872 | 0 | .arg(max_unwarned_cycles); |
2873 | | // We issued a warning, so let's now reset the counter. |
2874 | 0 | incomplete_v6_reclamations_ = 0; |
2875 | 0 | } |
2876 | |
|
2877 | 44 | } else { |
2878 | | // This was a complete reclamation, so let's reset the counter. |
2879 | 44 | incomplete_v6_reclamations_ = 0; |
2880 | | |
2881 | 44 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2882 | 44 | ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES); |
2883 | 44 | } |
2884 | 44 | } |
2885 | | |
2886 | | void |
2887 | 44 | AllocEngine::deleteExpiredReclaimedLeases6(const uint32_t secs) { |
2888 | 44 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2889 | 0 | ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE) |
2890 | 0 | .arg(secs); |
2891 | | |
2892 | 44 | uint64_t deleted_leases = 0; |
2893 | 44 | try { |
2894 | | // Try to delete leases from the lease database. |
2895 | 44 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
2896 | 44 | deleted_leases = lease_mgr.deleteExpiredReclaimedLeases6(secs); |
2897 | | |
2898 | 44 | } catch (const std::exception& ex) { |
2899 | 0 | LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED) |
2900 | 0 | .arg(ex.what()); |
2901 | 0 | } |
2902 | | |
2903 | 44 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2904 | 0 | ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE) |
2905 | 0 | .arg(deleted_leases); |
2906 | 44 | } |
2907 | | |
2908 | | void |
2909 | | AllocEngine::reclaimExpiredLeases4(const size_t max_leases, |
2910 | | const uint16_t timeout, |
2911 | | const bool remove_lease, |
2912 | 46 | const uint16_t max_unwarned_cycles) { |
2913 | | |
2914 | 46 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
2915 | 0 | ALLOC_ENGINE_V4_LEASES_RECLAMATION_START) |
2916 | 0 | .arg(max_leases) |
2917 | 0 | .arg(timeout); |
2918 | | |
2919 | 46 | try { |
2920 | 46 | reclaimExpiredLeases4Internal(max_leases, timeout, remove_lease, |
2921 | 46 | max_unwarned_cycles); |
2922 | 46 | } catch (const std::exception& ex) { |
2923 | 0 | LOG_ERROR(alloc_engine_logger, |
2924 | 0 | ALLOC_ENGINE_V4_LEASES_RECLAMATION_FAILED) |
2925 | 0 | .arg(ex.what()); |
2926 | 0 | } |
2927 | 46 | } |
2928 | | |
2929 | | void |
2930 | | AllocEngine::reclaimExpiredLeases4Internal(const size_t max_leases, |
2931 | | const uint16_t timeout, |
2932 | | const bool remove_lease, |
2933 | 46 | const uint16_t max_unwarned_cycles) { |
2934 | | |
2935 | | // Create stopwatch and automatically start it to measure the time |
2936 | | // taken by the routine. |
2937 | 46 | util::Stopwatch stopwatch; |
2938 | | |
2939 | 46 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
2940 | | |
2941 | | // This value indicates if we have been able to deal with all expired |
2942 | | // leases in this pass. |
2943 | 46 | bool incomplete_reclamation = false; |
2944 | 46 | Lease4Collection leases; |
2945 | | // The value of 0 has a special meaning - reclaim all. |
2946 | 46 | if (max_leases > 0) { |
2947 | | // If the value is non-zero, the caller has limited the number of |
2948 | | // leases to reclaim. We obtain one lease more to see if there will |
2949 | | // be still leases left after this pass. |
2950 | 46 | lease_mgr.getExpiredLeases4(leases, max_leases + 1); |
2951 | | // There are more leases expired leases than we will process in this |
2952 | | // pass, so we should mark it as an incomplete reclamation. We also |
2953 | | // remove this extra lease (which we don't want to process anyway) |
2954 | | // from the collection. |
2955 | 46 | if (leases.size() > max_leases) { |
2956 | 0 | leases.pop_back(); |
2957 | 0 | incomplete_reclamation = true; |
2958 | 0 | } |
2959 | | |
2960 | 46 | } else { |
2961 | | // If there is no limitation on the number of leases to reclaim, |
2962 | | // we will try to process all. Hence, we don't mark it as incomplete |
2963 | | // reclamation just yet. |
2964 | 0 | lease_mgr.getExpiredLeases4(leases, max_leases); |
2965 | 0 | } |
2966 | | |
2967 | | // Do not initialize the callout handle until we know if there are any |
2968 | | // lease4_expire callouts installed. |
2969 | 46 | CalloutHandlePtr callout_handle; |
2970 | 46 | if (!leases.empty() && |
2971 | 0 | HooksManager::calloutsPresent(Hooks.hook_index_lease4_expire_)) { |
2972 | 0 | callout_handle = HooksManager::createCalloutHandle(); |
2973 | 0 | } |
2974 | | |
2975 | 46 | size_t leases_processed = 0; |
2976 | 46 | for (auto const& lease : leases) { |
2977 | |
|
2978 | 0 | try { |
2979 | | // Reclaim the lease. |
2980 | 0 | if (MultiThreadingMgr::instance().getMode()) { |
2981 | | // The reclamation is exclusive of packet processing. |
2982 | 0 | WriteLockGuard exclusive(rw_mutex_); |
2983 | |
|
2984 | 0 | reclaimExpiredLease(lease, remove_lease, callout_handle); |
2985 | 0 | ++leases_processed; |
2986 | 0 | } else { |
2987 | 0 | reclaimExpiredLease(lease, remove_lease, callout_handle); |
2988 | 0 | ++leases_processed; |
2989 | 0 | } |
2990 | |
|
2991 | 0 | } catch (const std::exception& ex) { |
2992 | 0 | LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED) |
2993 | 0 | .arg(lease->addr_.toText()) |
2994 | 0 | .arg(ex.what()); |
2995 | 0 | } |
2996 | | |
2997 | | // Check if we have hit the timeout for running reclamation routine and |
2998 | | // return if we have. We're checking it here, because we always want to |
2999 | | // allow reclaiming at least one lease. |
3000 | 0 | if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) { |
3001 | | // Timeout. This will likely mean that we haven't been able to process |
3002 | | // all leases we wanted to process. The reclamation pass will be |
3003 | | // probably marked as incomplete. |
3004 | 0 | if (!incomplete_reclamation) { |
3005 | 0 | if (leases_processed < leases.size()) { |
3006 | 0 | incomplete_reclamation = true; |
3007 | 0 | } |
3008 | 0 | } |
3009 | |
|
3010 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3011 | 0 | ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT) |
3012 | 0 | .arg(timeout); |
3013 | 0 | break; |
3014 | 0 | } |
3015 | 0 | } |
3016 | | |
3017 | | // Stop measuring the time. |
3018 | 46 | stopwatch.stop(); |
3019 | | |
3020 | | // Mark completion of the lease reclamation routine and present some stats. |
3021 | 46 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3022 | 0 | ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE) |
3023 | 0 | .arg(leases_processed) |
3024 | 0 | .arg(stopwatch.logFormatTotalDuration()); |
3025 | | |
3026 | | // Check if this was an incomplete reclamation and increase the number of |
3027 | | // consecutive incomplete reclamations. |
3028 | 46 | if (incomplete_reclamation) { |
3029 | 0 | ++incomplete_v4_reclamations_; |
3030 | | // If the number of incomplete reclamations is beyond the threshold, we |
3031 | | // need to issue a warning. |
3032 | 0 | if ((max_unwarned_cycles > 0) && |
3033 | 0 | (incomplete_v4_reclamations_ > max_unwarned_cycles)) { |
3034 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW) |
3035 | 0 | .arg(max_unwarned_cycles); |
3036 | | // We issued a warning, so let's now reset the counter. |
3037 | 0 | incomplete_v4_reclamations_ = 0; |
3038 | 0 | } |
3039 | |
|
3040 | 46 | } else { |
3041 | | // This was a complete reclamation, so let's reset the counter. |
3042 | 46 | incomplete_v4_reclamations_ = 0; |
3043 | | |
3044 | 46 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3045 | 46 | ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES); |
3046 | 46 | } |
3047 | 46 | } |
3048 | | |
3049 | | template<typename LeasePtrType> |
3050 | | void |
3051 | | AllocEngine::reclaimExpiredLease(const LeasePtrType& lease, const bool remove_lease, |
3052 | 0 | const CalloutHandlePtr& callout_handle) { |
3053 | 0 | reclaimExpiredLease(lease, remove_lease ? DB_RECLAIM_REMOVE : DB_RECLAIM_UPDATE, |
3054 | 0 | callout_handle); |
3055 | 0 | } Unexecuted instantiation: void isc::dhcp::AllocEngine::reclaimExpiredLease<boost::shared_ptr<isc::dhcp::Lease6> >(boost::shared_ptr<isc::dhcp::Lease6> const&, bool, boost::shared_ptr<isc::hooks::CalloutHandle> const&) Unexecuted instantiation: void isc::dhcp::AllocEngine::reclaimExpiredLease<boost::shared_ptr<isc::dhcp::Lease4> >(boost::shared_ptr<isc::dhcp::Lease4> const&, bool, boost::shared_ptr<isc::hooks::CalloutHandle> const&) |
3056 | | |
3057 | | template<typename LeasePtrType> |
3058 | | void |
3059 | | AllocEngine::reclaimExpiredLease(const LeasePtrType& lease, |
3060 | 0 | const CalloutHandlePtr& callout_handle) { |
3061 | | // This variant of the method is used by the code which allocates or |
3062 | | // renews leases. It may be the case that the lease has already been |
3063 | | // reclaimed, so there is nothing to do. |
3064 | 0 | if (!lease->stateExpiredReclaimed()) { |
3065 | 0 | reclaimExpiredLease(lease, DB_RECLAIM_LEAVE_UNCHANGED, callout_handle); |
3066 | 0 | } |
3067 | 0 | } Unexecuted instantiation: void isc::dhcp::AllocEngine::reclaimExpiredLease<boost::shared_ptr<isc::dhcp::Lease6> >(boost::shared_ptr<isc::dhcp::Lease6> const&, boost::shared_ptr<isc::hooks::CalloutHandle> const&) Unexecuted instantiation: void isc::dhcp::AllocEngine::reclaimExpiredLease<boost::shared_ptr<isc::dhcp::Lease4> >(boost::shared_ptr<isc::dhcp::Lease4> const&, boost::shared_ptr<isc::hooks::CalloutHandle> const&) |
3068 | | |
3069 | | void |
3070 | | AllocEngine::reclaimExpiredLease(const Lease6Ptr& lease, |
3071 | | const DbReclaimMode& reclaim_mode, |
3072 | 0 | const CalloutHandlePtr& callout_handle) { |
3073 | |
|
3074 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3075 | 0 | ALLOC_ENGINE_V6_LEASE_RECLAIM) |
3076 | 0 | .arg(Pkt6::makeLabel(lease->duid_, lease->hwaddr_)) |
3077 | 0 | .arg(lease->addr_.toText()) |
3078 | 0 | .arg(static_cast<int>(lease->prefixlen_)); |
3079 | | |
3080 | | // The skip flag indicates if the callouts have taken responsibility |
3081 | | // for reclaiming the lease. The callout will set this to true if |
3082 | | // it reclaims the lease itself. In this case the reclamation routine |
3083 | | // will not update DNS nor update the database. |
3084 | 0 | bool skipped = false; |
3085 | 0 | bool released = (lease->state_ == Lease::STATE_RELEASED); |
3086 | 0 | bool registered = (lease->state_ == Lease::STATE_REGISTERED); |
3087 | 0 | if (callout_handle) { |
3088 | | |
3089 | | // Use the RAII wrapper to make sure that the callout handle state is |
3090 | | // reset when this object goes out of scope. All hook points must do |
3091 | | // it to prevent possible circular dependency between the callout |
3092 | | // handle and its arguments. |
3093 | 0 | ScopedCalloutHandleState callout_handle_state(callout_handle); |
3094 | |
|
3095 | 0 | callout_handle->deleteAllArguments(); |
3096 | 0 | callout_handle->setArgument("lease6", lease); |
3097 | 0 | callout_handle->setArgument("remove_lease", reclaim_mode == DB_RECLAIM_REMOVE); |
3098 | |
|
3099 | 0 | HooksManager::callCallouts(Hooks.hook_index_lease6_expire_, |
3100 | 0 | *callout_handle); |
3101 | |
|
3102 | 0 | skipped = callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP; |
3103 | 0 | } |
3104 | | |
3105 | | /// DROP status does not make sense here. |
3106 | | /// Not sure if we need to support every possible status everywhere. |
3107 | |
|
3108 | 0 | if (!skipped) { |
3109 | | |
3110 | | // Generate removal name change request for D2, if required. |
3111 | | // This will return immediately if the DNS wasn't updated |
3112 | | // when the lease was created. |
3113 | 0 | queueNCR(CHG_REMOVE, lease); |
3114 | | |
3115 | | // Let's check if the lease that just expired is in DECLINED state. |
3116 | | // If it is, we need to perform a couple extra steps. |
3117 | 0 | bool remove_lease = (reclaim_mode == DB_RECLAIM_REMOVE); |
3118 | 0 | if (lease->state_ == Lease::STATE_DECLINED) { |
3119 | | // Do extra steps required for declined lease reclamation: |
3120 | | // - call the recover hook |
3121 | | // - bump decline-related stats |
3122 | | // - log separate message |
3123 | | // There's no point in keeping a declined lease after its |
3124 | | // reclamation. A declined lease doesn't have any client |
3125 | | // identifying information anymore. So we'll flag it for |
3126 | | // removal unless the hook has set the skip flag. |
3127 | 0 | remove_lease = reclaimDeclined(lease); |
3128 | 0 | } else if (lease->state_ == Lease::STATE_REGISTERED) { |
3129 | 0 | if (reclaim_mode == DB_RECLAIM_LEAVE_UNCHANGED) { |
3130 | 0 | isc_throw(Unexpected, "attempt to reuse a registered lease"); |
3131 | 0 | } |
3132 | | // Remove (vs reclaim) expired registered leases. |
3133 | 0 | remove_lease = true; |
3134 | 0 | } |
3135 | | |
3136 | 0 | if (reclaim_mode != DB_RECLAIM_LEAVE_UNCHANGED) { |
3137 | | // Reclaim the lease - depending on the configuration, set the |
3138 | | // expired-reclaimed state or simply remove it. |
3139 | 0 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
3140 | 0 | reclaimLeaseInDatabase<Lease6Ptr>(lease, remove_lease, |
3141 | 0 | std::bind(&LeaseMgr::updateLease6, |
3142 | 0 | &lease_mgr, ph::_1)); |
3143 | 0 | } |
3144 | 0 | } |
3145 | | |
3146 | | // Update statistics. |
3147 | | |
3148 | | // Increase total number of reclaimed leases. |
3149 | 0 | StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1)); |
3150 | | |
3151 | | // Increase number of reclaimed leases for a subnet. |
3152 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", |
3153 | 0 | lease->subnet_id_, |
3154 | 0 | "reclaimed-leases"), |
3155 | 0 | static_cast<int64_t>(1)); |
3156 | |
|
3157 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_); |
3158 | |
|
3159 | 0 | if (lease->type_ == Lease::TYPE_NA || lease->type_ == Lease::TYPE_PD) { |
3160 | 0 | if (subnet) { |
3161 | 0 | auto const& pool = subnet->getPool(lease->type_, lease->addr_, false); |
3162 | 0 | if (pool) { |
3163 | 0 | StatsMgr::instance().addValue( |
3164 | 0 | StatsMgr::generateName("subnet", subnet->getID(), |
3165 | 0 | StatsMgr::generateName(lease->type_ == Lease::TYPE_NA ? |
3166 | 0 | "pool" : "pd-pool", |
3167 | 0 | pool->getID(), "reclaimed-leases")), |
3168 | 0 | static_cast<int64_t>(1)); |
3169 | 0 | } |
3170 | 0 | } |
3171 | 0 | } |
3172 | | |
3173 | | // Statistics must have been updated during the release. |
3174 | 0 | if (released) { |
3175 | 0 | return; |
3176 | 0 | } |
3177 | | |
3178 | | // Decrease number of registered or assigned leases. |
3179 | | |
3180 | 0 | if (registered) { |
3181 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", |
3182 | 0 | lease->subnet_id_, |
3183 | 0 | "registered-nas"), |
3184 | 0 | static_cast<int64_t>(-1)); |
3185 | 0 | } else if (lease->type_ == Lease::TYPE_NA || lease->type_ == Lease::TYPE_PD) { |
3186 | | // Decrease number of assigned addresses. |
3187 | 0 | StatsMgr::instance().addValue(lease->type_ == Lease::TYPE_NA ? |
3188 | 0 | "assigned-nas" : "assigned-pds", |
3189 | 0 | static_cast<int64_t>(-1)); |
3190 | |
|
3191 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", |
3192 | 0 | lease->subnet_id_, |
3193 | 0 | lease->type_ == Lease::TYPE_NA ? |
3194 | 0 | "assigned-nas" : "assigned-pds"), |
3195 | 0 | static_cast<int64_t>(-1)); |
3196 | |
|
3197 | 0 | if (subnet) { |
3198 | 0 | auto const& pool = subnet->getPool(lease->type_, lease->addr_, false); |
3199 | 0 | if (pool) { |
3200 | 0 | StatsMgr::instance().addValue( |
3201 | 0 | StatsMgr::generateName("subnet", subnet->getID(), |
3202 | 0 | StatsMgr::generateName(lease->type_ == Lease::TYPE_NA ? |
3203 | 0 | "pool" : "pd-pool", pool->getID(), |
3204 | 0 | lease->type_ == Lease::TYPE_NA ? |
3205 | 0 | "assigned-nas" : "assigned-pds")), |
3206 | 0 | static_cast<int64_t>(-1)); |
3207 | 0 | } |
3208 | 0 | } |
3209 | 0 | } |
3210 | 0 | } |
3211 | | |
3212 | | void |
3213 | | AllocEngine::reclaimExpiredLease(const Lease4Ptr& lease, |
3214 | | const DbReclaimMode& reclaim_mode, |
3215 | 0 | const CalloutHandlePtr& callout_handle) { |
3216 | |
|
3217 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3218 | 0 | ALLOC_ENGINE_V4_LEASE_RECLAIM) |
3219 | 0 | .arg(Pkt4::makeLabel(lease->hwaddr_, lease->client_id_)) |
3220 | 0 | .arg(lease->addr_.toText()); |
3221 | | |
3222 | | // The skip flag indicates if the callouts have taken responsibility |
3223 | | // for reclaiming the lease. The callout will set this to true if |
3224 | | // it reclaims the lease itself. In this case the reclamation routine |
3225 | | // will not update DNS nor update the database. |
3226 | 0 | bool skipped = false; |
3227 | 0 | bool released = (lease->state_ == Lease::STATE_RELEASED); |
3228 | 0 | if (callout_handle) { |
3229 | | |
3230 | | // Use the RAII wrapper to make sure that the callout handle state is |
3231 | | // reset when this object goes out of scope. All hook points must do |
3232 | | // it to prevent possible circular dependency between the callout |
3233 | | // handle and its arguments. |
3234 | 0 | ScopedCalloutHandleState callout_handle_state(callout_handle); |
3235 | |
|
3236 | 0 | callout_handle->setArgument("lease4", lease); |
3237 | 0 | callout_handle->setArgument("remove_lease", reclaim_mode == DB_RECLAIM_REMOVE); |
3238 | |
|
3239 | 0 | HooksManager::callCallouts(Hooks.hook_index_lease4_expire_, |
3240 | 0 | *callout_handle); |
3241 | |
|
3242 | 0 | skipped = callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP; |
3243 | 0 | } |
3244 | | |
3245 | | /// DROP status does not make sense here. |
3246 | | /// Not sure if we need to support every possible status everywhere. |
3247 | |
|
3248 | 0 | if (!skipped) { |
3249 | | |
3250 | | // Generate removal name change request for D2, if required. |
3251 | | // This will return immediately if the DNS wasn't updated |
3252 | | // when the lease was created. |
3253 | 0 | queueNCR(CHG_REMOVE, lease); |
3254 | | // Clear DNS fields so we avoid redundant removes. |
3255 | 0 | lease->hostname_.clear(); |
3256 | 0 | lease->fqdn_fwd_ = false; |
3257 | 0 | lease->fqdn_rev_ = false; |
3258 | | |
3259 | | // Let's check if the lease that just expired is in DECLINED state. |
3260 | | // If it is, we need to perform a couple extra steps. |
3261 | 0 | bool remove_lease = (reclaim_mode == DB_RECLAIM_REMOVE); |
3262 | 0 | if (lease->state_ == Lease::STATE_DECLINED) { |
3263 | | // Do extra steps required for declined lease reclamation: |
3264 | | // - call the recover hook |
3265 | | // - bump decline-related stats |
3266 | | // - log separate message |
3267 | | // There's no point in keeping a declined lease after its |
3268 | | // reclamation. A declined lease doesn't have any client |
3269 | | // identifying information anymore. So we'll flag it for |
3270 | | // removal unless the hook has set the skip flag. |
3271 | 0 | remove_lease = reclaimDeclined(lease); |
3272 | 0 | } |
3273 | |
|
3274 | 0 | if (reclaim_mode != DB_RECLAIM_LEAVE_UNCHANGED) { |
3275 | | // Reclaim the lease - depending on the configuration, set the |
3276 | | // expired-reclaimed state or simply remove it. |
3277 | 0 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
3278 | 0 | reclaimLeaseInDatabase<Lease4Ptr>(lease, remove_lease, |
3279 | 0 | std::bind(&LeaseMgr::updateLease4, |
3280 | 0 | &lease_mgr, ph::_1)); |
3281 | 0 | } |
3282 | 0 | } |
3283 | | |
3284 | | // Update statistics. |
3285 | | |
3286 | | // Increase total number of reclaimed leases. |
3287 | 0 | StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1)); |
3288 | | |
3289 | | // Increase number of reclaimed leases for a subnet. |
3290 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", |
3291 | 0 | lease->subnet_id_, |
3292 | 0 | "reclaimed-leases"), |
3293 | 0 | static_cast<int64_t>(1)); |
3294 | |
|
3295 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_); |
3296 | |
|
3297 | 0 | if (subnet) { |
3298 | 0 | auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false); |
3299 | 0 | if (pool) { |
3300 | 0 | StatsMgr::instance().addValue( |
3301 | 0 | StatsMgr::generateName("subnet", subnet->getID(), |
3302 | 0 | StatsMgr::generateName("pool" , pool->getID(), |
3303 | 0 | "reclaimed-leases")), |
3304 | 0 | static_cast<int64_t>(1)); |
3305 | 0 | } |
3306 | 0 | } |
3307 | | |
3308 | | // Statistics must have been updated during the release. |
3309 | 0 | if (released) { |
3310 | 0 | return; |
3311 | 0 | } |
3312 | | |
3313 | | // Decrease number of assigned addresses. |
3314 | 0 | StatsMgr::instance().addValue("assigned-addresses", static_cast<int64_t>(-1)); |
3315 | |
|
3316 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", |
3317 | 0 | lease->subnet_id_, |
3318 | 0 | "assigned-addresses"), |
3319 | 0 | static_cast<int64_t>(-1)); |
3320 | |
|
3321 | 0 | if (subnet) { |
3322 | 0 | auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false); |
3323 | 0 | if (pool) { |
3324 | 0 | StatsMgr::instance().addValue( |
3325 | 0 | StatsMgr::generateName("subnet", subnet->getID(), |
3326 | 0 | StatsMgr::generateName("pool" , pool->getID(), |
3327 | 0 | "assigned-addresses")), |
3328 | 0 | static_cast<int64_t>(-1)); |
3329 | 0 | } |
3330 | 0 | } |
3331 | 0 | } |
3332 | | |
3333 | | void |
3334 | 46 | AllocEngine::deleteExpiredReclaimedLeases4(const uint32_t secs) { |
3335 | 46 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3336 | 0 | ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE) |
3337 | 0 | .arg(secs); |
3338 | | |
3339 | 46 | uint64_t deleted_leases = 0; |
3340 | 46 | try { |
3341 | | // Try to delete leases from the lease database. |
3342 | 46 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
3343 | 46 | deleted_leases = lease_mgr.deleteExpiredReclaimedLeases4(secs); |
3344 | | |
3345 | 46 | } catch (const std::exception& ex) { |
3346 | 0 | LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED) |
3347 | 0 | .arg(ex.what()); |
3348 | 0 | } |
3349 | | |
3350 | 46 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3351 | 0 | ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE) |
3352 | 0 | .arg(deleted_leases); |
3353 | 46 | } |
3354 | | |
3355 | | bool |
3356 | 0 | AllocEngine::reclaimDeclined(const Lease4Ptr& lease) { |
3357 | 0 | if (!lease || (lease->state_ != Lease::STATE_DECLINED) ) { |
3358 | 0 | return (true); |
3359 | 0 | } |
3360 | | |
3361 | 0 | if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_recover_)) { |
3362 | 0 | CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); |
3363 | | |
3364 | | // Use the RAII wrapper to make sure that the callout handle state is |
3365 | | // reset when this object goes out of scope. All hook points must do |
3366 | | // it to prevent possible circular dependency between the callout |
3367 | | // handle and its arguments. |
3368 | 0 | ScopedCalloutHandleState callout_handle_state(callout_handle); |
3369 | | |
3370 | | // Pass necessary arguments |
3371 | 0 | callout_handle->setArgument("lease4", lease); |
3372 | | |
3373 | | // Call the callouts |
3374 | 0 | HooksManager::callCallouts(Hooks.hook_index_lease4_recover_, *callout_handle); |
3375 | | |
3376 | | // Callouts decided to skip the action. This means that the lease is not |
3377 | | // assigned, so the client will get NoAddrAvail as a result. The lease |
3378 | | // won't be inserted into the database. |
3379 | 0 | if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) { |
3380 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_RECOVER_SKIP) |
3381 | 0 | .arg(lease->addr_.toText()); |
3382 | 0 | return (false); |
3383 | 0 | } |
3384 | 0 | } |
3385 | | |
3386 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V4_DECLINED_RECOVERED) |
3387 | 0 | .arg(lease->addr_.toText()) |
3388 | 0 | .arg(lease->valid_lft_); |
3389 | |
|
3390 | 0 | StatsMgr& stats_mgr = StatsMgr::instance(); |
3391 | | |
3392 | | // Decrease subnet specific counter for currently declined addresses |
3393 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_, |
3394 | 0 | "declined-addresses"), |
3395 | 0 | static_cast<int64_t>(-1)); |
3396 | |
|
3397 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_, |
3398 | 0 | "reclaimed-declined-addresses"), |
3399 | 0 | static_cast<int64_t>(1)); |
3400 | |
|
3401 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_); |
3402 | 0 | if (subnet) { |
3403 | 0 | auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false); |
3404 | 0 | if (pool) { |
3405 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(), |
3406 | 0 | StatsMgr::generateName("pool" , pool->getID(), |
3407 | 0 | "declined-addresses")), |
3408 | 0 | static_cast<int64_t>(-1)); |
3409 | |
|
3410 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(), |
3411 | 0 | StatsMgr::generateName("pool" , pool->getID(), |
3412 | 0 | "reclaimed-declined-addresses")), |
3413 | 0 | static_cast<int64_t>(1)); |
3414 | 0 | } |
3415 | 0 | } |
3416 | | |
3417 | | // Decrease global counter for declined addresses |
3418 | 0 | stats_mgr.addValue("declined-addresses", static_cast<int64_t>(-1)); |
3419 | |
|
3420 | 0 | stats_mgr.addValue("reclaimed-declined-addresses", static_cast<int64_t>(1)); |
3421 | | |
3422 | | // Note that we do not touch assigned-addresses counters. Those are |
3423 | | // modified in whatever code calls this method. |
3424 | 0 | return (true); |
3425 | 0 | } |
3426 | | |
3427 | | bool |
3428 | 0 | AllocEngine::reclaimDeclined(const Lease6Ptr& lease) { |
3429 | 0 | if (!lease || (lease->state_ != Lease::STATE_DECLINED) ) { |
3430 | 0 | return (true); |
3431 | 0 | } |
3432 | | |
3433 | 0 | if (HooksManager::calloutsPresent(Hooks.hook_index_lease6_recover_)) { |
3434 | 0 | CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); |
3435 | | |
3436 | | // Use the RAII wrapper to make sure that the callout handle state is |
3437 | | // reset when this object goes out of scope. All hook points must do |
3438 | | // it to prevent possible circular dependency between the callout |
3439 | | // handle and its arguments. |
3440 | 0 | ScopedCalloutHandleState callout_handle_state(callout_handle); |
3441 | | |
3442 | | // Pass necessary arguments |
3443 | 0 | callout_handle->setArgument("lease6", lease); |
3444 | | |
3445 | | // Call the callouts |
3446 | 0 | HooksManager::callCallouts(Hooks.hook_index_lease6_recover_, *callout_handle); |
3447 | | |
3448 | | // Callouts decided to skip the action. This means that the lease is not |
3449 | | // assigned, so the client will get NoAddrAvail as a result. The lease |
3450 | | // won't be inserted into the database. |
3451 | 0 | if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) { |
3452 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_RECOVER_SKIP) |
3453 | 0 | .arg(lease->addr_.toText()); |
3454 | 0 | return (false); |
3455 | 0 | } |
3456 | 0 | } |
3457 | | |
3458 | 0 | LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_DECLINED_RECOVERED) |
3459 | 0 | .arg(lease->addr_.toText()) |
3460 | 0 | .arg(lease->valid_lft_); |
3461 | |
|
3462 | 0 | StatsMgr& stats_mgr = StatsMgr::instance(); |
3463 | | |
3464 | | // Decrease subnet specific counter for currently declined addresses |
3465 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_, |
3466 | 0 | "declined-addresses"), |
3467 | 0 | static_cast<int64_t>(-1)); |
3468 | |
|
3469 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_, |
3470 | 0 | "reclaimed-declined-addresses"), |
3471 | 0 | static_cast<int64_t>(1)); |
3472 | |
|
3473 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_); |
3474 | 0 | if (subnet) { |
3475 | 0 | auto const& pool = subnet->getPool(lease->type_, lease->addr_, false); |
3476 | 0 | if (pool) { |
3477 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(), |
3478 | 0 | StatsMgr::generateName("pool" , pool->getID(), |
3479 | 0 | "declined-addresses")), |
3480 | 0 | static_cast<int64_t>(-1)); |
3481 | |
|
3482 | 0 | stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(), |
3483 | 0 | StatsMgr::generateName("pool" , pool->getID(), |
3484 | 0 | "reclaimed-declined-addresses")), |
3485 | 0 | static_cast<int64_t>(1)); |
3486 | 0 | } |
3487 | 0 | } |
3488 | | |
3489 | | // Decrease global counter for declined addresses |
3490 | 0 | stats_mgr.addValue("declined-addresses", static_cast<int64_t>(-1)); |
3491 | |
|
3492 | 0 | stats_mgr.addValue("reclaimed-declined-addresses", static_cast<int64_t>(1)); |
3493 | | |
3494 | | // Note that we do not touch assigned-nas counters. Those are |
3495 | | // modified in whatever code calls this method. |
3496 | 0 | return (true); |
3497 | 0 | } |
3498 | | |
3499 | | void |
3500 | 0 | AllocEngine::clearReclaimedExtendedInfo(const Lease4Ptr& lease) const { |
3501 | 0 | lease->relay_id_.clear(); |
3502 | 0 | lease->remote_id_.clear(); |
3503 | 0 | if (lease->getContext()) { |
3504 | 0 | lease->setContext(ElementPtr()); |
3505 | 0 | } |
3506 | 0 | } |
3507 | | |
3508 | | void |
3509 | 0 | AllocEngine::clearReclaimedExtendedInfo(const Lease6Ptr& lease) const { |
3510 | 0 | if (lease->getContext()) { |
3511 | 0 | lease->extended_info_action_ = Lease6::ACTION_DELETE; |
3512 | 0 | lease->setContext(ElementPtr()); |
3513 | 0 | } |
3514 | 0 | } |
3515 | | |
3516 | | template<typename LeasePtrType> |
3517 | | void AllocEngine::reclaimLeaseInDatabase(const LeasePtrType& lease, |
3518 | | const bool remove_lease, |
3519 | | const std::function<void (const LeasePtrType&)>& |
3520 | 0 | lease_update_fun) const { |
3521 | |
|
3522 | 0 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
3523 | | |
3524 | | // Reclaim the lease - depending on the configuration, set the |
3525 | | // expired-reclaimed state or simply remove it. |
3526 | 0 | if (remove_lease) { |
3527 | 0 | static_cast<void>(lease_mgr.deleteLease(lease)); |
3528 | 0 | } else if (lease_update_fun) { |
3529 | | // Clear FQDN information as we have already sent the |
3530 | | // name change request to remove the DNS record. |
3531 | 0 | lease->reuseable_valid_lft_ = 0; |
3532 | 0 | lease->hostname_.clear(); |
3533 | 0 | lease->fqdn_fwd_ = false; |
3534 | 0 | lease->fqdn_rev_ = false; |
3535 | 0 | lease->state_ = Lease::STATE_EXPIRED_RECLAIMED; |
3536 | 0 | clearReclaimedExtendedInfo(lease); |
3537 | 0 | lease_update_fun(lease); |
3538 | |
|
3539 | 0 | } else { |
3540 | 0 | return; |
3541 | 0 | } |
3542 | | |
3543 | | // Lease has been reclaimed. |
3544 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3545 | 0 | ALLOC_ENGINE_LEASE_RECLAIMED) |
3546 | 0 | .arg(lease->addr_.toText()); |
3547 | 0 | } Unexecuted instantiation: void isc::dhcp::AllocEngine::reclaimLeaseInDatabase<boost::shared_ptr<isc::dhcp::Lease6> >(boost::shared_ptr<isc::dhcp::Lease6> const&, bool, std::__1::function<void (boost::shared_ptr<isc::dhcp::Lease6> const&)> const&) const Unexecuted instantiation: void isc::dhcp::AllocEngine::reclaimLeaseInDatabase<boost::shared_ptr<isc::dhcp::Lease4> >(boost::shared_ptr<isc::dhcp::Lease4> const&, bool, std::__1::function<void (boost::shared_ptr<isc::dhcp::Lease4> const&)> const&) const |
3548 | | |
3549 | | std::string |
3550 | 0 | AllocEngine::labelNetworkOrSubnet(ConstSubnetPtr subnet) { |
3551 | 0 | if (!subnet) { |
3552 | 0 | return("<empty subnet>"); |
3553 | 0 | } |
3554 | | |
3555 | 0 | SharedNetwork4Ptr network; |
3556 | 0 | subnet->getSharedNetwork(network); |
3557 | 0 | std::ostringstream ss; |
3558 | 0 | if (network) { |
3559 | 0 | ss << "shared-network: " << network->getName(); |
3560 | 0 | } else { |
3561 | 0 | ss << "subnet id: " << subnet->getID(); |
3562 | 0 | } |
3563 | |
|
3564 | 0 | return(ss.str()); |
3565 | 0 | } |
3566 | | |
3567 | | } // namespace dhcp |
3568 | | } // namespace isc |
3569 | | |
3570 | | // ########################################################################## |
3571 | | // # DHCPv4 lease allocation code starts here. |
3572 | | // ########################################################################## |
3573 | | |
3574 | | namespace { |
3575 | | |
3576 | | /// @brief Check if the specific address is reserved for another client. |
3577 | | /// |
3578 | | /// This function finds a host reservation for a given address and then |
3579 | | /// it verifies if the host identifier for this reservation is matching |
3580 | | /// a host identifier found for the current client. If it does not, the |
3581 | | /// address is assumed to be reserved for another client. |
3582 | | /// |
3583 | | /// @note If reservations-out-of-pool flag is enabled, dynamic address that |
3584 | | /// match reservations from within the dynamic pool will not be prevented to |
3585 | | /// be assigned to any client. |
3586 | | /// |
3587 | | /// @param address An address for which the function should check if |
3588 | | /// there is a reservation for the different client. |
3589 | | /// @param ctx Client context holding the data extracted from the |
3590 | | /// client's message. |
3591 | | /// |
3592 | | /// @return true if the address is reserved for another client. |
3593 | | bool |
3594 | 0 | addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx) { |
3595 | | // When out-of-pool flag is true the server may assume that all host |
3596 | | // reservations are for addresses that do not belong to the dynamic pool. |
3597 | | // Therefore, it can skip the reservation checks when dealing with in-pool |
3598 | | // addresses. |
3599 | 0 | if (ctx.subnet_ && ctx.subnet_->getReservationsInSubnet() && |
3600 | 0 | (!ctx.subnet_->getReservationsOutOfPool() || |
3601 | 0 | !ctx.subnet_->inPool(Lease::TYPE_V4, address))) { |
3602 | | // The global parameter ip-reservations-unique controls whether it is allowed |
3603 | | // to specify multiple reservations for the same IP address or delegated prefix |
3604 | | // or IP reservations must be unique. Some host backends do not support the |
3605 | | // former, thus we can't always use getAll4 calls to get the reservations |
3606 | | // for the given IP. When we're in the default mode, when IP reservations |
3607 | | // are unique, we should call get4 (supported by all backends). If we're in |
3608 | | // the mode in which non-unique reservations are allowed the backends which |
3609 | | // don't support it are not used and we can safely call getAll4. |
3610 | 0 | ConstHostCollection hosts; |
3611 | 0 | if (CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique()) { |
3612 | 0 | try { |
3613 | | // Reservations are unique. It is safe to call get4 to get the unique host. |
3614 | 0 | ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(), address); |
3615 | 0 | if (host) { |
3616 | 0 | hosts.push_back(host); |
3617 | 0 | } |
3618 | 0 | } catch (const MultipleRecords& ex) { |
3619 | 0 | LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_DETECTED) |
3620 | 0 | .arg(address) |
3621 | 0 | .arg(ctx.subnet_->getID()) |
3622 | 0 | .arg(ex.what()); |
3623 | 0 | throw; |
3624 | 0 | } |
3625 | 0 | } else { |
3626 | | // Reservations can be non-unique. Need to get all reservations for that address. |
3627 | 0 | hosts = HostMgr::instance().getAll4(ctx.subnet_->getID(), address); |
3628 | 0 | } |
3629 | | |
3630 | 0 | for (auto const& host : hosts) { |
3631 | 0 | for (const AllocEngine::IdentifierPair& id_pair : ctx.host_identifiers_) { |
3632 | | // If we find the matching host we know that this address is reserved |
3633 | | // for us and we can return immediately. |
3634 | 0 | if (id_pair.first == host->getIdentifierType() && |
3635 | 0 | id_pair.second == host->getIdentifier()) { |
3636 | 0 | return (false); |
3637 | 0 | } |
3638 | 0 | } |
3639 | 0 | } |
3640 | | // We didn't find a matching host. If there are any reservations it means that |
3641 | | // address is reserved for another client or multiple clients. If there are |
3642 | | // no reservations address is not reserved for another client. |
3643 | 0 | return (!hosts.empty()); |
3644 | 0 | } |
3645 | 0 | return (false); |
3646 | 0 | } |
3647 | | |
3648 | | /// @brief Check if the context contains the reservation for the |
3649 | | /// IPv4 address. |
3650 | | /// |
3651 | | /// This convenience function checks if the context contains the reservation |
3652 | | /// for the IPv4 address. Note that some reservations may not assign a |
3653 | | /// static IPv4 address to the clients, but may rather reserve a hostname. |
3654 | | /// Allocation engine should check if the existing reservation is made |
3655 | | /// for the IPv4 address and if it is not, allocate the address from the |
3656 | | /// dynamic pool. The allocation engine uses this function to check if |
3657 | | /// the reservation is made for the IPv4 address. |
3658 | | /// |
3659 | | /// @param [out] ctx Client context holding the data extracted from the |
3660 | | /// client's message. |
3661 | | /// |
3662 | | /// @return true if the context contains the reservation for the IPv4 address. |
3663 | | bool |
3664 | 0 | hasAddressReservation(AllocEngine::ClientContext4& ctx) { |
3665 | 0 | if (ctx.hosts_.empty()) { |
3666 | 0 | return (false); |
3667 | 0 | } |
3668 | | |
3669 | | // Fetch the globally reserved address if there is one. |
3670 | 0 | auto global_host = ctx.hosts_.find(SUBNET_ID_GLOBAL); |
3671 | 0 | auto global_host_address = ((global_host != ctx.hosts_.end() && global_host->second) ? |
3672 | 0 | global_host->second->getIPv4Reservation() : |
3673 | 0 | IOAddress::IPV4_ZERO_ADDRESS()); |
3674 | | |
3675 | | // Start with currently selected subnet. |
3676 | 0 | ConstSubnet4Ptr subnet = ctx.subnet_; |
3677 | 0 | while (subnet) { |
3678 | | // If global reservations are enabled for this subnet and there is |
3679 | | // globally reserved address and that address is feasible for this |
3680 | | // subnet, update the selected subnet and return true. |
3681 | 0 | if (subnet->getReservationsGlobal() && |
3682 | 0 | (global_host_address != IOAddress::IPV4_ZERO_ADDRESS()) && |
3683 | 0 | (subnet->inRange(global_host_address))) { |
3684 | 0 | ctx.subnet_ = subnet; |
3685 | 0 | return (true); |
3686 | 0 | } |
3687 | | |
3688 | 0 | if (subnet->getReservationsInSubnet()) { |
3689 | 0 | auto host = ctx.hosts_.find(subnet->getID()); |
3690 | | // The out-of-pool flag indicates that no client should be assigned |
3691 | | // reserved addresses from within the dynamic pool, and for that |
3692 | | // reason look only for reservations that are outside the pools, |
3693 | | // hence the inPool check. |
3694 | 0 | if (host != ctx.hosts_.end() && host->second) { |
3695 | 0 | auto reservation = host->second->getIPv4Reservation(); |
3696 | 0 | if (!reservation.isV4Zero() && |
3697 | 0 | (!subnet->getReservationsOutOfPool() || |
3698 | 0 | !subnet->inPool(Lease::TYPE_V4, reservation))) { |
3699 | 0 | ctx.subnet_ = subnet; |
3700 | 0 | return (true); |
3701 | 0 | } |
3702 | 0 | } |
3703 | 0 | } |
3704 | | |
3705 | | // No address reservation found here, so let's try another subnet |
3706 | | // within the same shared network. |
3707 | 0 | subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses()); |
3708 | 0 | } |
3709 | | |
3710 | 0 | if (global_host_address != IOAddress::IPV4_ZERO_ADDRESS()) { |
3711 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
3712 | 0 | ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS) |
3713 | 0 | .arg(ctx.query_->getLabel()) |
3714 | 0 | .arg(global_host_address.toText()) |
3715 | 0 | .arg(AllocEngine::labelNetworkOrSubnet(ctx.subnet_)); |
3716 | 0 | } |
3717 | |
|
3718 | 0 | return (false); |
3719 | 0 | } |
3720 | | |
3721 | | /// @brief Finds existing lease in the database. |
3722 | | /// |
3723 | | /// This function searches for the lease in the database which belongs to the |
3724 | | /// client requesting allocation. If the client has supplied the client |
3725 | | /// identifier this identifier is used to look up the lease. If the lease is |
3726 | | /// not found using the client identifier, an additional lookup is performed |
3727 | | /// using the HW address, if supplied. If the lease is found using the HW |
3728 | | /// address, the function also checks if the lease belongs to the client, i.e. |
3729 | | /// there is no conflict between the client identifiers. |
3730 | | /// |
3731 | | /// @param [out] ctx Context holding data extracted from the client's message, |
3732 | | /// including the HW address and client identifier. The current subnet may be |
3733 | | /// modified by this function if it belongs to a shared network. |
3734 | | /// @param [out] client_lease A pointer to the lease returned by this function |
3735 | | /// or null value if no has been lease found. |
3736 | 0 | void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) { |
3737 | 0 | LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); |
3738 | |
|
3739 | 0 | ConstSubnet4Ptr original_subnet = ctx.subnet_; |
3740 | |
|
3741 | 0 | auto const& classes = ctx.query_->getClasses(); |
3742 | | |
3743 | | // Client identifier is optional. First check if we can try to lookup |
3744 | | // by client-id. |
3745 | 0 | bool try_clientid_lookup = (ctx.clientid_ && |
3746 | 0 | SharedNetwork4::subnetsIncludeMatchClientId(original_subnet, classes)); |
3747 | | |
3748 | | // If it is possible to use client identifier to try to find client's lease. |
3749 | 0 | if (try_clientid_lookup) { |
3750 | | // Get all leases for this client identifier. When shared networks are |
3751 | | // in use it is more efficient to make a single query rather than |
3752 | | // multiple queries, one for each subnet. |
3753 | 0 | Lease4Collection leases_client_id = lease_mgr.getLease4(*ctx.clientid_); |
3754 | | |
3755 | | // Iterate over the subnets within the shared network to see if any client's |
3756 | | // lease belongs to them. |
3757 | 0 | for (ConstSubnet4Ptr subnet = original_subnet; subnet; |
3758 | 0 | subnet = subnet->getNextSubnet(original_subnet, classes)) { |
3759 | | |
3760 | | // If client identifier has been supplied and the server wasn't |
3761 | | // explicitly configured to ignore client identifiers for this subnet |
3762 | | // check if there is a lease within this subnet. |
3763 | 0 | if (subnet->getMatchClientId()) { |
3764 | 0 | for (auto const& l : leases_client_id) { |
3765 | 0 | if (l->subnet_id_ == subnet->getID()) { |
3766 | | // Lease found, so stick to this lease. |
3767 | 0 | client_lease = l; |
3768 | 0 | ctx.subnet_ = subnet; |
3769 | 0 | return; |
3770 | 0 | } |
3771 | 0 | } |
3772 | 0 | } |
3773 | 0 | } |
3774 | 0 | } |
3775 | | |
3776 | | // If no lease found using the client identifier, try the lookup using |
3777 | | // the HW address. |
3778 | 0 | if (!client_lease && ctx.hwaddr_) { |
3779 | | |
3780 | | // Get all leases for this HW address. |
3781 | 0 | Lease4Collection leases_hw_address = lease_mgr.getLease4(*ctx.hwaddr_); |
3782 | |
|
3783 | 0 | for (ConstSubnet4Ptr subnet = original_subnet; subnet; |
3784 | 0 | subnet = subnet->getNextSubnet(original_subnet, classes)) { |
3785 | 0 | ClientIdPtr client_id; |
3786 | 0 | if (subnet->getMatchClientId()) { |
3787 | 0 | client_id = ctx.clientid_; |
3788 | 0 | } |
3789 | | |
3790 | | // Try to find the lease that matches current subnet and belongs to |
3791 | | // this client, so both HW address and client identifier match. |
3792 | 0 | for (auto const& client_lease_it : leases_hw_address) { |
3793 | 0 | Lease4Ptr existing_lease = client_lease_it; |
3794 | 0 | if ((existing_lease->subnet_id_ == subnet->getID()) && |
3795 | 0 | existing_lease->belongsToClient(ctx.hwaddr_, client_id)) { |
3796 | | // Found the lease of this client, so return it. |
3797 | 0 | client_lease = existing_lease; |
3798 | | // We got a lease but the subnet it belongs to may differ from |
3799 | | // the original subnet. Let's now stick to this subnet. |
3800 | 0 | ctx.subnet_ = subnet; |
3801 | 0 | return; |
3802 | 0 | } |
3803 | 0 | } |
3804 | 0 | } |
3805 | 0 | } |
3806 | 0 | } |
3807 | | |
3808 | | /// @brief Checks if the specified address belongs to one of the subnets |
3809 | | /// within a shared network. |
3810 | | /// |
3811 | | /// @todo Update this function to take client classification into account. |
3812 | | /// @note client classification in pools (vs subnets) is checked |
3813 | | /// |
3814 | | /// @param ctx Client context. Current subnet may be modified by this |
3815 | | /// function when it belongs to a shared network. |
3816 | | /// @param address IPv4 address to be checked. |
3817 | | /// |
3818 | | /// @return true if address belongs to a pool in a selected subnet or in |
3819 | | /// a pool within any of the subnets belonging to the current shared network. |
3820 | | bool |
3821 | 0 | inAllowedPool(AllocEngine::ClientContext4& ctx, const IOAddress& address) { |
3822 | | // If the subnet belongs to a shared network we will be iterating |
3823 | | // over the subnets that belong to this shared network. |
3824 | 0 | ConstSubnet4Ptr current_subnet = ctx.subnet_; |
3825 | 0 | auto const& classes = ctx.query_->getClasses(); |
3826 | |
|
3827 | 0 | while (current_subnet) { |
3828 | 0 | if (current_subnet->inPool(Lease::TYPE_V4, address, classes)) { |
3829 | | // We found a subnet that this address belongs to, so it |
3830 | | // seems that this subnet is the good candidate for allocation. |
3831 | | // Let's update the selected subnet. |
3832 | 0 | ctx.subnet_ = current_subnet; |
3833 | 0 | return (true); |
3834 | 0 | } |
3835 | | |
3836 | 0 | current_subnet = current_subnet->getNextSubnet(ctx.subnet_, classes); |
3837 | 0 | } |
3838 | | |
3839 | 0 | return (false); |
3840 | 0 | } |
3841 | | |
3842 | | } // namespace |
3843 | | |
3844 | | namespace isc { |
3845 | | namespace dhcp { |
3846 | | |
3847 | | AllocEngine::ClientContext4::ClientContext4() |
3848 | 8.71k | : early_global_reservations_lookup_(false), |
3849 | 8.71k | subnet_(), clientid_(), hwaddr_(), |
3850 | 8.71k | requested_address_(IOAddress::IPV4_ZERO_ADDRESS()), |
3851 | 8.71k | fwd_dns_update_(false), rev_dns_update_(false), |
3852 | 8.71k | hostname_(""), callout_handle_(), fake_allocation_(false), offer_lft_(0), |
3853 | 8.71k | old_lease_(), new_lease_(), hosts_(), conflicting_lease_(), |
3854 | 8.71k | query_(), host_identifiers_(), unknown_requested_addr_(false), |
3855 | 8.71k | ddns_params_() { |
3856 | | |
3857 | 8.71k | } |
3858 | | |
3859 | | AllocEngine::ClientContext4::ClientContext4(const ConstSubnet4Ptr& subnet, |
3860 | | const ClientIdPtr& clientid, |
3861 | | const HWAddrPtr& hwaddr, |
3862 | | const asiolink::IOAddress& requested_addr, |
3863 | | const bool fwd_dns_update, |
3864 | | const bool rev_dns_update, |
3865 | | const std::string& hostname, |
3866 | | const bool fake_allocation, |
3867 | | const uint32_t offer_lft) |
3868 | 0 | : early_global_reservations_lookup_(false), |
3869 | 0 | subnet_(subnet), clientid_(clientid), hwaddr_(hwaddr), |
3870 | 0 | requested_address_(requested_addr), |
3871 | 0 | fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update), |
3872 | 0 | hostname_(hostname), callout_handle_(), |
3873 | 0 | fake_allocation_(fake_allocation), offer_lft_(offer_lft), old_lease_(), new_lease_(), |
3874 | 0 | hosts_(), host_identifiers_(), unknown_requested_addr_(false), |
3875 | 0 | ddns_params_(new DdnsParams()) { |
3876 | | |
3877 | | // Initialize host identifiers. |
3878 | 0 | if (hwaddr) { |
3879 | 0 | addHostIdentifier(Host::IDENT_HWADDR, hwaddr->hwaddr_); |
3880 | 0 | } |
3881 | 0 | } |
3882 | | |
3883 | | ConstHostPtr |
3884 | 2.27k | AllocEngine::ClientContext4::currentHost() const { |
3885 | 2.27k | if (subnet_ && subnet_->getReservationsInSubnet()) { |
3886 | 0 | auto host = hosts_.find(subnet_->getID()); |
3887 | 0 | if (host != hosts_.cend()) { |
3888 | 0 | return (host->second); |
3889 | 0 | } |
3890 | 0 | } |
3891 | | |
3892 | 2.27k | return (globalHost()); |
3893 | 2.27k | } |
3894 | | |
3895 | | ConstHostPtr |
3896 | 2.65k | AllocEngine::ClientContext4::globalHost() const { |
3897 | 2.65k | if (subnet_ && subnet_->getReservationsGlobal()) { |
3898 | 0 | auto host = hosts_.find(SUBNET_ID_GLOBAL); |
3899 | 0 | if (host != hosts_.cend()) { |
3900 | 0 | return (host->second); |
3901 | 0 | } |
3902 | 0 | } |
3903 | | |
3904 | 2.65k | return (ConstHostPtr()); |
3905 | 2.65k | } |
3906 | | |
3907 | | DdnsParamsPtr |
3908 | 1.62k | AllocEngine::ClientContext4::getDdnsParams() { |
3909 | | // We already have it return it unless the context subnet has changed. |
3910 | 1.62k | if (ddns_params_ && subnet_ && (subnet_->getID() == ddns_params_->getSubnetId())) { |
3911 | 0 | return (ddns_params_); |
3912 | 0 | } |
3913 | | |
3914 | | // Doesn't exist yet or is stale, (re)create it. |
3915 | 1.62k | if (subnet_) { |
3916 | 0 | ddns_params_ = CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_); |
3917 | 0 | return (ddns_params_); |
3918 | 0 | } |
3919 | | |
3920 | | // Asked for it without a subnet? This case really shouldn't occur but |
3921 | | // for now let's return an instance with default values. |
3922 | 1.62k | return (DdnsParamsPtr(new DdnsParams())); |
3923 | 1.62k | } |
3924 | | |
3925 | | Lease4Ptr |
3926 | 0 | AllocEngine::allocateLease4(ClientContext4& ctx) { |
3927 | | // The NULL pointer indicates that the old lease didn't exist. It may |
3928 | | // be later set to non NULL value if existing lease is found in the |
3929 | | // database. |
3930 | 0 | ctx.old_lease_.reset(); |
3931 | 0 | ctx.new_lease_.reset(); |
3932 | | |
3933 | | // Before we start allocation process, we need to make sure that the |
3934 | | // selected subnet is allowed for this client. If not, we'll try to |
3935 | | // use some other subnet within the shared network. If there are no |
3936 | | // subnets allowed for this client within the shared network, we |
3937 | | // can't allocate a lease. |
3938 | 0 | ConstSubnet4Ptr subnet = ctx.subnet_; |
3939 | 0 | auto const& classes = ctx.query_->getClasses(); |
3940 | 0 | if (subnet && !subnet->clientSupported(classes)) { |
3941 | 0 | ctx.subnet_ = subnet->getNextSubnet(subnet, classes); |
3942 | 0 | } |
3943 | |
|
3944 | 0 | try { |
3945 | 0 | if (!ctx.subnet_) { |
3946 | 0 | isc_throw(BadValue, "Can't allocate IPv4 address without subnet"); |
3947 | 0 | } |
3948 | | |
3949 | 0 | if (!ctx.hwaddr_) { |
3950 | 0 | isc_throw(BadValue, "HWAddr must be defined"); |
3951 | 0 | } |
3952 | | |
3953 | 0 | if (ctx.fake_allocation_) { |
3954 | 0 | ctx.new_lease_ = discoverLease4(ctx); |
3955 | 0 | } else { |
3956 | 0 | ctx.new_lease_ = requestLease4(ctx); |
3957 | 0 | } |
3958 | |
|
3959 | 0 | } catch (const NoSuchLease& e) { |
3960 | 0 | isc_throw(NoSuchLease, "detected data race in AllocEngine::allocateLease4: " << e.what()); |
3961 | |
|
3962 | 0 | } catch (const isc::Exception& e) { |
3963 | | // Some other error, return an empty lease. |
3964 | 0 | LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_ERROR) |
3965 | 0 | .arg(ctx.query_->getLabel()) |
3966 | 0 | .arg(e.what()); |
3967 | 0 | } |
3968 | | |
3969 | 0 | return (ctx.new_lease_); |
3970 | 0 | } |
3971 | | |
3972 | | void |
3973 | 0 | AllocEngine::findReservation(ClientContext4& ctx) { |
3974 | | // If there is no subnet, there is nothing to do. |
3975 | 0 | if (!ctx.subnet_) { |
3976 | 0 | return; |
3977 | 0 | } |
3978 | | |
3979 | 0 | auto subnet = ctx.subnet_; |
3980 | | |
3981 | | // If already done just return. |
3982 | 0 | if (ctx.early_global_reservations_lookup_ && |
3983 | 0 | !subnet->getReservationsInSubnet()) { |
3984 | 0 | return; |
3985 | 0 | } |
3986 | | |
3987 | | // @todo: This code can be trivially optimized. |
3988 | 0 | if (!ctx.early_global_reservations_lookup_ && |
3989 | 0 | subnet->getReservationsGlobal()) { |
3990 | 0 | ConstHostPtr ghost = findGlobalReservation(ctx); |
3991 | 0 | if (ghost) { |
3992 | 0 | ctx.hosts_[SUBNET_ID_GLOBAL] = ghost; |
3993 | | |
3994 | | // If we had only to fetch global reservations it is done. |
3995 | 0 | if (!subnet->getReservationsInSubnet()) { |
3996 | 0 | return; |
3997 | 0 | } |
3998 | 0 | } |
3999 | 0 | } |
4000 | | |
4001 | 0 | std::map<SubnetID, ConstHostPtr> host_map; |
4002 | 0 | SharedNetwork4Ptr network; |
4003 | 0 | subnet->getSharedNetwork(network); |
4004 | | |
4005 | | // If the subnet belongs to a shared network it is usually going to be |
4006 | | // more efficient to make a query for all reservations for a particular |
4007 | | // client rather than a query for each subnet within this shared network. |
4008 | | // The only case when it is going to be less efficient is when there are |
4009 | | // more host identifier types in use than subnets within a shared network. |
4010 | | // As it breaks RADIUS use of host caching this can be disabled by the |
4011 | | // host manager. |
4012 | 0 | const bool use_single_query = network && |
4013 | 0 | !HostMgr::instance().getDisableSingleQuery() && |
4014 | 0 | (network->getAllSubnets()->size() > ctx.host_identifiers_.size()); |
4015 | |
|
4016 | 0 | if (use_single_query) { |
4017 | 0 | for (const IdentifierPair& id_pair : ctx.host_identifiers_) { |
4018 | 0 | ConstHostCollection hosts = HostMgr::instance().getAll(id_pair.first, |
4019 | 0 | &id_pair.second[0], |
4020 | 0 | id_pair.second.size()); |
4021 | | // Store the hosts in the temporary map, because some hosts may |
4022 | | // belong to subnets outside of the shared network. We'll need |
4023 | | // to eliminate them. |
4024 | 0 | for (auto const& host : hosts) { |
4025 | 0 | if (host->getIPv4SubnetID() != SUBNET_ID_GLOBAL) { |
4026 | 0 | host_map[host->getIPv4SubnetID()] = host; |
4027 | 0 | } |
4028 | 0 | } |
4029 | 0 | } |
4030 | 0 | } |
4031 | |
|
4032 | 0 | auto const& classes = ctx.query_->getClasses(); |
4033 | | // We can only search for the reservation if a subnet has been selected. |
4034 | 0 | while (subnet) { |
4035 | | |
4036 | | // Only makes sense to get reservations if the client has access |
4037 | | // to the class and host reservations are enabled for this subnet. |
4038 | 0 | if (subnet->clientSupported(classes) && subnet->getReservationsInSubnet()) { |
4039 | | // Iterate over configured identifiers in the order of preference |
4040 | | // and try to use each of them to search for the reservations. |
4041 | 0 | if (use_single_query) { |
4042 | 0 | if (host_map.count(subnet->getID()) > 0) { |
4043 | 0 | ctx.hosts_[subnet->getID()] = host_map[subnet->getID()]; |
4044 | 0 | } |
4045 | 0 | } else { |
4046 | 0 | for (const IdentifierPair& id_pair : ctx.host_identifiers_) { |
4047 | | // Attempt to find a host using a specified identifier. |
4048 | 0 | ConstHostPtr host = HostMgr::instance().get4(subnet->getID(), |
4049 | 0 | id_pair.first, |
4050 | 0 | &id_pair.second[0], |
4051 | 0 | id_pair.second.size()); |
4052 | | // If we found matching host for this subnet. |
4053 | 0 | if (host) { |
4054 | 0 | ctx.hosts_[subnet->getID()] = host; |
4055 | 0 | break; |
4056 | 0 | } |
4057 | 0 | } |
4058 | 0 | } |
4059 | 0 | } |
4060 | | |
4061 | | // We need to get to the next subnet if this is a shared network. If it |
4062 | | // is not (a plain subnet), getNextSubnet will return NULL and we're |
4063 | | // done here. |
4064 | 0 | subnet = subnet->getNextSubnet(ctx.subnet_, classes); |
4065 | 0 | } |
4066 | | |
4067 | | // The hosts can be used by the server to return reserved options to |
4068 | | // the DHCP client. Such options must be encapsulated (i.e., they must |
4069 | | // include suboptions). |
4070 | 0 | for (auto const& host : ctx.hosts_) { |
4071 | 0 | host.second->encapsulateOptions(); |
4072 | 0 | } |
4073 | 0 | } |
4074 | | |
4075 | | ConstHostPtr |
4076 | 0 | AllocEngine::findGlobalReservation(ClientContext4& ctx) { |
4077 | 0 | ConstHostPtr host; |
4078 | 0 | for (const IdentifierPair& id_pair : ctx.host_identifiers_) { |
4079 | | // Attempt to find a host using a specified identifier. |
4080 | 0 | host = HostMgr::instance().get4(SUBNET_ID_GLOBAL, id_pair.first, |
4081 | 0 | &id_pair.second[0], id_pair.second.size()); |
4082 | | |
4083 | | // If we found matching global host we're done. |
4084 | 0 | if (host) { |
4085 | 0 | break; |
4086 | 0 | } |
4087 | 0 | } |
4088 | |
|
4089 | 0 | return (host); |
4090 | 0 | } |
4091 | | |
4092 | | Lease4Ptr |
4093 | 0 | AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) { |
4094 | | // Find an existing lease for this client. This function will return null |
4095 | | // if there is a conflict with existing lease and the allocation should |
4096 | | // not be continued. |
4097 | 0 | Lease4Ptr client_lease; |
4098 | 0 | findClientLease(ctx, client_lease); |
4099 | | |
4100 | | // Fetch offer_lft to see if we're allocating on DISCOVER. |
4101 | 0 | ctx.offer_lft_ = getOfferLft(ctx); |
4102 | | |
4103 | | // new_lease will hold the pointer to the lease that we will offer to the |
4104 | | // caller. |
4105 | 0 | Lease4Ptr new_lease; |
4106 | |
|
4107 | 0 | CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE; |
4108 | | |
4109 | | // Check if there is a reservation for the client. If there is, we want to |
4110 | | // assign the reserved address, rather than any other one. |
4111 | 0 | if (hasAddressReservation(ctx)) { |
4112 | |
|
4113 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4114 | 0 | ALLOC_ENGINE_V4_DISCOVER_HR) |
4115 | 0 | .arg(ctx.query_->getLabel()) |
4116 | 0 | .arg(ctx.currentHost()->getIPv4Reservation().toText()); |
4117 | | |
4118 | | // If the client doesn't have a lease or the leased address is different |
4119 | | // than the reserved one then let's try to allocate the reserved address. |
4120 | | // Otherwise the address that the client has is the one for which it |
4121 | | // has a reservation, so just renew it. |
4122 | 0 | if (!client_lease || (client_lease->addr_ != ctx.currentHost()->getIPv4Reservation())) { |
4123 | | // The call below will return a pointer to the lease for the address |
4124 | | // reserved to this client, if the lease is available, i.e. is not |
4125 | | // currently assigned to any other client. |
4126 | | // Note that we don't remove the existing client's lease at this point |
4127 | | // because this is not a real allocation, we just offer what we can |
4128 | | // allocate in the DHCPREQUEST time. |
4129 | 0 | new_lease = allocateOrReuseLease4(ctx.currentHost()->getIPv4Reservation(), ctx, |
4130 | 0 | callout_status); |
4131 | 0 | if (!new_lease) { |
4132 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT) |
4133 | 0 | .arg(ctx.query_->getLabel()) |
4134 | 0 | .arg(ctx.currentHost()->getIPv4Reservation().toText()) |
4135 | 0 | .arg(ctx.conflicting_lease_ ? ctx.conflicting_lease_->toText() : |
4136 | 0 | "(no lease info)"); |
4137 | 0 | if (ctx.conflicting_lease_) { |
4138 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", |
4139 | 0 | ctx.conflicting_lease_->subnet_id_, |
4140 | 0 | "v4-reservation-conflicts"), |
4141 | 0 | static_cast<int64_t>(1)); |
4142 | 0 | StatsMgr::instance().addValue("v4-reservation-conflicts", |
4143 | 0 | static_cast<int64_t>(1)); |
4144 | 0 | } |
4145 | 0 | } |
4146 | |
|
4147 | 0 | } else { |
4148 | 0 | new_lease = renewLease4(client_lease, ctx); |
4149 | 0 | } |
4150 | 0 | } |
4151 | | |
4152 | | // Client does not have a reservation or the allocation of the reserved |
4153 | | // address has failed, probably because the reserved address is in use |
4154 | | // by another client. If the client has a lease, we will check if we can |
4155 | | // offer this lease to the client. The lease can't be offered in the |
4156 | | // situation when it is reserved for another client or when the address |
4157 | | // is not in the dynamic pool. The former may be the result of adding the |
4158 | | // new reservation for the address used by this client. The latter may |
4159 | | // be due to the client using the reserved out-of-the pool address, for |
4160 | | // which the reservation has just been removed. |
4161 | 0 | if (!new_lease && client_lease && inAllowedPool(ctx, client_lease->addr_) && |
4162 | 0 | !addressReserved(client_lease->addr_, ctx)) { |
4163 | |
|
4164 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4165 | 0 | ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE) |
4166 | 0 | .arg(ctx.query_->getLabel()); |
4167 | | |
4168 | | // If offer-lifetime is shorter than the existing expiration, reset |
4169 | | // offer-lifetime to zero. This allows us to simply return the |
4170 | | // existing lease without updating it in the lease store. |
4171 | 0 | if ((ctx.offer_lft_) && |
4172 | 0 | (time(NULL) + ctx.offer_lft_ < client_lease->getExpirationTime())) { |
4173 | 0 | ctx.offer_lft_ = 0; |
4174 | 0 | } |
4175 | |
|
4176 | 0 | new_lease = renewLease4(client_lease, ctx); |
4177 | 0 | } |
4178 | | |
4179 | | // The client doesn't have any lease or the lease can't be offered |
4180 | | // because it is either reserved for some other client or the |
4181 | | // address is not in the dynamic pool. |
4182 | | // Let's use the client's hint (requested IP address), if the client |
4183 | | // has provided it, and try to offer it. This address must not be |
4184 | | // reserved for another client, and must be in the range of the |
4185 | | // dynamic pool. |
4186 | 0 | if (!new_lease && !ctx.requested_address_.isV4Zero() && |
4187 | 0 | inAllowedPool(ctx, ctx.requested_address_) && |
4188 | 0 | !addressReserved(ctx.requested_address_, ctx)) { |
4189 | |
|
4190 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4191 | 0 | ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE) |
4192 | 0 | .arg(ctx.requested_address_.toText()) |
4193 | 0 | .arg(ctx.query_->getLabel()); |
4194 | |
|
4195 | 0 | new_lease = allocateOrReuseLease4(ctx.requested_address_, ctx, |
4196 | 0 | callout_status); |
4197 | 0 | } |
4198 | | |
4199 | | // The allocation engine failed to allocate all of the candidate |
4200 | | // addresses. We will now use the allocator to pick the address |
4201 | | // from the dynamic pool. |
4202 | 0 | if (!new_lease) { |
4203 | |
|
4204 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4205 | 0 | ALLOC_ENGINE_V4_OFFER_NEW_LEASE) |
4206 | 0 | .arg(ctx.query_->getLabel()); |
4207 | |
|
4208 | 0 | new_lease = allocateUnreservedLease4(ctx); |
4209 | 0 | } |
4210 | | |
4211 | | // Some of the methods like reuseExpiredLease4 may set the old lease to point |
4212 | | // to the lease which they remove/override. If it is not set, but we have |
4213 | | // found that the client has the lease the client's lease is the one |
4214 | | // to return as an old lease. |
4215 | 0 | if (!ctx.old_lease_ && client_lease) { |
4216 | 0 | ctx.old_lease_ = client_lease; |
4217 | 0 | } |
4218 | |
|
4219 | 0 | return (new_lease); |
4220 | 0 | } |
4221 | | |
4222 | 0 | void deleteAssignedLease(Lease4Ptr lease) { |
4223 | 0 | if (LeaseMgrFactory::instance().deleteLease(lease) && |
4224 | 0 | (lease->state_ != Lease4::STATE_RELEASED)) { |
4225 | | // Need to decrease statistic for assigned addresses. |
4226 | 0 | StatsMgr::instance().addValue("assigned-addresses", static_cast<int64_t>(-1)); |
4227 | |
|
4228 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", lease->subnet_id_, |
4229 | 0 | "assigned-addresses"), |
4230 | 0 | static_cast<int64_t>(-1)); |
4231 | |
|
4232 | 0 | auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4() |
4233 | 0 | ->getBySubnetId(lease->subnet_id_); |
4234 | 0 | if (subnet) { |
4235 | 0 | auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false); |
4236 | 0 | if (pool) { |
4237 | 0 | StatsMgr::instance().addValue(StatsMgr::generateName("subnet", subnet->getID(), |
4238 | 0 | StatsMgr::generateName("pool", pool->getID(), |
4239 | 0 | "assigned-addresses")), |
4240 | 0 | static_cast<int64_t>(-1)); |
4241 | 0 | } |
4242 | 0 | } |
4243 | 0 | } |
4244 | 0 | } |
4245 | | |
4246 | | Lease4Ptr |
4247 | 0 | AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) { |
4248 | | // Find an existing lease for this client. This function will return null |
4249 | | // if there is a conflict with existing lease and the allocation should |
4250 | | // not be continued. |
4251 | 0 | Lease4Ptr client_lease; |
4252 | 0 | findClientLease(ctx, client_lease); |
4253 | | |
4254 | | // When the client sends the DHCPREQUEST, it should always specify the |
4255 | | // address which it is requesting or renewing. That is, the client should |
4256 | | // either use the requested IP address option or set the ciaddr. However, |
4257 | | // we try to be liberal and allow the clients to not specify an address |
4258 | | // in which case the allocation engine will pick a suitable address |
4259 | | // for the client. |
4260 | 0 | if (!ctx.requested_address_.isV4Zero()) { |
4261 | | // If the client has specified an address, make sure this address |
4262 | | // is not reserved for another client. If it is, stop here because |
4263 | | // we can't allocate this address. |
4264 | 0 | if (addressReserved(ctx.requested_address_, ctx)) { |
4265 | |
|
4266 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4267 | 0 | ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED) |
4268 | 0 | .arg(ctx.query_->getLabel()) |
4269 | 0 | .arg(ctx.requested_address_.toText()); |
4270 | |
|
4271 | 0 | return (Lease4Ptr()); |
4272 | 0 | } |
4273 | |
|
4274 | 0 | } else if (hasAddressReservation(ctx)) { |
4275 | | // The client hasn't specified an address to allocate, so the |
4276 | | // allocation engine needs to find an appropriate address. |
4277 | | // If there is a reservation for the client, let's try to |
4278 | | // allocate the reserved address. |
4279 | 0 | ctx.requested_address_ = ctx.currentHost()->getIPv4Reservation(); |
4280 | |
|
4281 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4282 | 0 | ALLOC_ENGINE_V4_REQUEST_USE_HR) |
4283 | 0 | .arg(ctx.query_->getLabel()) |
4284 | 0 | .arg(ctx.requested_address_.toText()); |
4285 | 0 | } |
4286 | | |
4287 | 0 | if (!ctx.requested_address_.isV4Zero()) { |
4288 | | // There is a specific address to be allocated. Let's find out if |
4289 | | // the address is in use. |
4290 | 0 | Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(ctx.requested_address_); |
4291 | | // If the address is in use (allocated and not expired), we check |
4292 | | // if the address is in use by our client or another client. |
4293 | | // If it is in use by another client, the address can't be |
4294 | | // allocated. |
4295 | 0 | if (existing && !existing->expired() && |
4296 | 0 | !existing->belongsToClient(ctx.hwaddr_, ctx.subnet_->getMatchClientId() ? |
4297 | 0 | ctx.clientid_ : ClientIdPtr())) { |
4298 | |
|
4299 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4300 | 0 | ALLOC_ENGINE_V4_REQUEST_IN_USE) |
4301 | 0 | .arg(ctx.query_->getLabel()) |
4302 | 0 | .arg(ctx.requested_address_.toText()); |
4303 | |
|
4304 | 0 | return (Lease4Ptr()); |
4305 | 0 | } |
4306 | | |
4307 | | // If the client has a reservation but it is requesting a different |
4308 | | // address it is possible that the client was offered this different |
4309 | | // address because the reserved address is in use. We will have to |
4310 | | // check if the address is in use. |
4311 | 0 | if (hasAddressReservation(ctx) && |
4312 | 0 | (ctx.currentHost()->getIPv4Reservation() != ctx.requested_address_)) { |
4313 | 0 | existing = |
4314 | 0 | LeaseMgrFactory::instance().getLease4(ctx.currentHost()->getIPv4Reservation()); |
4315 | | // If the reserved address is not in use, i.e. the lease doesn't |
4316 | | // exist or is expired, and the client is requesting a different |
4317 | | // address, return NULL. The client should go back to the |
4318 | | // DHCPDISCOVER and the reserved address will be offered. |
4319 | 0 | if (!existing || existing->expired()) { |
4320 | |
|
4321 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4322 | 0 | ALLOC_ENGINE_V4_REQUEST_INVALID) |
4323 | 0 | .arg(ctx.query_->getLabel()) |
4324 | 0 | .arg(ctx.currentHost()->getIPv4Reservation().toText()) |
4325 | 0 | .arg(ctx.requested_address_.toText()); |
4326 | |
|
4327 | 0 | return (Lease4Ptr()); |
4328 | 0 | } |
4329 | 0 | } |
4330 | | |
4331 | | // The use of the out-of-pool addresses is only allowed when the requested |
4332 | | // address is reserved for the client. If the address is not reserved one |
4333 | | // and it doesn't belong to the dynamic pool, do not allocate it. |
4334 | 0 | if ((!hasAddressReservation(ctx) || |
4335 | 0 | (ctx.currentHost()->getIPv4Reservation() != ctx.requested_address_)) && |
4336 | 0 | !inAllowedPool(ctx, ctx.requested_address_)) { |
4337 | |
|
4338 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4339 | 0 | ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL) |
4340 | 0 | .arg(ctx.query_->getLabel()) |
4341 | 0 | .arg(ctx.requested_address_); |
4342 | |
|
4343 | 0 | ctx.unknown_requested_addr_ = true; |
4344 | 0 | return (Lease4Ptr()); |
4345 | 0 | } |
4346 | | |
4347 | | // During a prior discover the allocation engine deemed that the |
4348 | | // client's current lease (client_lease) should no longer be used and |
4349 | | // so offered a different lease (existing) that was temporarily |
4350 | | // allocated because offer-lifetime is greater than zero. We need to |
4351 | | // delete the unusable lease and renew the temporary lease. |
4352 | 0 | if (((client_lease && existing) && (client_lease->addr_ != existing->addr_) && |
4353 | 0 | (existing->addr_ == ctx.requested_address_) && |
4354 | 0 | (existing->belongsToClient(ctx.hwaddr_, ctx.subnet_->getMatchClientId() ? |
4355 | 0 | ctx.clientid_ : ClientIdPtr()))) |
4356 | 0 | && getOfferLft(ctx, false)) { |
4357 | 0 | auto conflicted_lease = client_lease; |
4358 | 0 | client_lease = existing; |
4359 | 0 | deleteAssignedLease(conflicted_lease); |
4360 | 0 | } |
4361 | 0 | } |
4362 | | |
4363 | | // We have gone through all the checks, so we can now allocate the address |
4364 | | // for the client. |
4365 | | |
4366 | | // If the client is requesting an address which is assigned to the client |
4367 | | // let's just renew this address. Also, renew this address if the client |
4368 | | // doesn't request any specific address. |
4369 | | // Added extra checks: the address is reserved for this client or belongs |
4370 | | // to the dynamic pool for the case the pool class has changed before the |
4371 | | // request. |
4372 | 0 | if (client_lease) { |
4373 | 0 | if (((client_lease->addr_ == ctx.requested_address_) || |
4374 | 0 | ctx.requested_address_.isV4Zero()) && |
4375 | 0 | ((hasAddressReservation(ctx) && |
4376 | 0 | (ctx.currentHost()->getIPv4Reservation() == ctx.requested_address_)) || |
4377 | 0 | inAllowedPool(ctx, client_lease->addr_))) { |
4378 | |
|
4379 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4380 | 0 | ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE) |
4381 | 0 | .arg(ctx.query_->getLabel()) |
4382 | 0 | .arg(ctx.requested_address_); |
4383 | 0 | return (renewLease4(client_lease, ctx)); |
4384 | 0 | } |
4385 | 0 | } |
4386 | | |
4387 | | // new_lease will hold the pointer to the allocated lease if we allocate |
4388 | | // successfully. |
4389 | 0 | Lease4Ptr new_lease; |
4390 | | |
4391 | | // The client doesn't have the lease or it is requesting an address |
4392 | | // which it doesn't have. Let's try to allocate the requested address. |
4393 | 0 | if (!ctx.requested_address_.isV4Zero()) { |
4394 | |
|
4395 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4396 | 0 | ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED) |
4397 | 0 | .arg(ctx.query_->getLabel()) |
4398 | 0 | .arg(ctx.requested_address_.toText()); |
4399 | | |
4400 | | // The call below will return a pointer to the lease allocated |
4401 | | // for the client if there is no lease for the requested address, |
4402 | | // or the existing lease has expired. If the allocation fails, |
4403 | | // e.g. because the lease is in use, we will return NULL to |
4404 | | // indicate that we were unable to allocate the lease. |
4405 | 0 | CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE; |
4406 | 0 | new_lease = allocateOrReuseLease4(ctx.requested_address_, ctx, |
4407 | 0 | callout_status); |
4408 | 0 | } else { |
4409 | |
|
4410 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4411 | 0 | ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS) |
4412 | 0 | .arg(ctx.query_->getLabel()); |
4413 | | |
4414 | | // We will only get here if the client didn't specify which |
4415 | | // address it wanted to be allocated. The allocation engine will |
4416 | | // to pick the address from the dynamic pool. |
4417 | 0 | new_lease = allocateUnreservedLease4(ctx); |
4418 | 0 | } |
4419 | | |
4420 | | // If we allocated the lease for the client, but the client already had a |
4421 | | // lease, we will need to return the pointer to the previous lease and |
4422 | | // the previous lease needs to be removed from the lease database. |
4423 | 0 | if (new_lease && client_lease) { |
4424 | 0 | ctx.old_lease_ = Lease4Ptr(new Lease4(*client_lease)); |
4425 | |
|
4426 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, |
4427 | 0 | ALLOC_ENGINE_V4_REQUEST_REMOVE_LEASE) |
4428 | 0 | .arg(ctx.query_->getLabel()) |
4429 | 0 | .arg(client_lease->addr_.toText()); |
4430 | 0 | deleteAssignedLease(client_lease); |
4431 | 0 | } |
4432 | | |
4433 | | // Return the allocated lease or NULL pointer if allocation was |
4434 | | // unsuccessful. |
4435 | 0 | return (new_lease); |
4436 | 0 | } |
4437 | | |
4438 | | uint32_t |
4439 | 0 | AllocEngine::getOfferLft(const ClientContext4& ctx, bool only_on_discover /* = true */) { |
4440 | | // Not a DISCOVER or it's BOOTP, punt. |
4441 | 0 | if (only_on_discover && ((!ctx.fake_allocation_) || (ctx.query_->inClass("BOOTP")))) { |
4442 | 0 | return (0); |
4443 | 0 | } |
4444 | | |
4445 | 0 | util::Optional<uint32_t> offer_lft; |
4446 | | |
4447 | | // If specified in one of our classes use it. |
4448 | | // We use the first one we find. |
4449 | 0 | const ClientClasses classes = ctx.query_->getClasses(); |
4450 | 0 | if (!classes.empty()) { |
4451 | | // Let's get class definitions |
4452 | 0 | const ClientClassDictionaryPtr& dict = |
4453 | 0 | CfgMgr::instance().getCurrentCfg()->getClientClassDictionary(); |
4454 | | |
4455 | | // Iterate over the assigned class definitions. |
4456 | 0 | for (auto const& name : classes) { |
4457 | 0 | ClientClassDefPtr cl = dict->findClass(name); |
4458 | 0 | if (cl && (!cl->getOfferLft().unspecified())) { |
4459 | 0 | offer_lft = cl->getOfferLft(); |
4460 | 0 | break; |
4461 | 0 | } |
4462 | 0 | } |
4463 | 0 | } |
4464 | | |
4465 | | // If no classes specified it, get it from the subnet. |
4466 | 0 | if (offer_lft.unspecified()) { |
4467 | 0 | offer_lft = ctx.subnet_->getOfferLft(); |
4468 | 0 | } |
4469 | |
|
4470 | 0 | return (offer_lft.unspecified() ? 0 : offer_lft.get()); |
4471 | 0 | } |
4472 | | |
4473 | | uint32_t |
4474 | 0 | AllocEngine::getValidLft(const ClientContext4& ctx) { |
4475 | | // If it's BOOTP, use infinite valid lifetime. |
4476 | 0 | if (ctx.query_->inClass("BOOTP")) { |
4477 | 0 | return (Lease::INFINITY_LFT); |
4478 | 0 | } |
4479 | | |
4480 | | // Use the dhcp-lease-time content from the client if it's there. |
4481 | 0 | uint32_t requested_lft = 0; |
4482 | 0 | OptionPtr opt = ctx.query_->getOption(DHO_DHCP_LEASE_TIME); |
4483 | 0 | if (opt) { |
4484 | 0 | OptionUint32Ptr opt_lft = boost::dynamic_pointer_cast<OptionInt<uint32_t> >(opt); |
4485 | 0 | if (opt_lft) { |
4486 | 0 | requested_lft = opt_lft->getValue(); |
4487 | 0 | } |
4488 | 0 | } |
4489 | | |
4490 | | // If the triplet is specified in one of our classes use it. |
4491 | | // We use the first one we find. |
4492 | 0 | Triplet<uint32_t> candidate_lft; |
4493 | 0 | const ClientClasses classes = ctx.query_->getClasses(); |
4494 | 0 | if (!classes.empty()) { |
4495 | | // Let's get class definitions |
4496 | 0 | const ClientClassDictionaryPtr& dict = |
4497 | 0 | CfgMgr::instance().getCurrentCfg()->getClientClassDictionary(); |
4498 | | |
4499 | | // Iterate over the assigned class definitions. |
4500 | 0 | for (auto const& name : classes) { |
4501 | 0 | ClientClassDefPtr cl = dict->findClass(name); |
4502 | 0 | if (cl && (!cl->getValid().unspecified())) { |
4503 | 0 | candidate_lft = cl->getValid(); |
4504 | 0 | break; |
4505 | 0 | } |
4506 | 0 | } |
4507 | 0 | } |
4508 | | |
4509 | | // If no classes specified it, get it from the subnet. |
4510 | 0 | if (!candidate_lft) { |
4511 | 0 | candidate_lft = ctx.subnet_->getValid(); |
4512 | 0 | } |
4513 | | |
4514 | | // If client requested a value, use the value bounded by |
4515 | | // the candidate triplet. |
4516 | 0 | if (requested_lft > 0) { |
4517 | 0 | return (candidate_lft.get(requested_lft)); |
4518 | 0 | } |
4519 | | |
4520 | | // Use the candidate's default value. |
4521 | 0 | return (candidate_lft.get()); |
4522 | 0 | } |
4523 | | |
4524 | | void |
4525 | 0 | AllocEngine::getMinValidLft(const ClientContext4& ctx, uint32_t& valid) { |
4526 | | // If it's BOOTP, use infinite valid lifetime. |
4527 | 0 | if (ctx.query_->inClass("BOOTP")) { |
4528 | 0 | valid = Lease::INFINITY_LFT; |
4529 | 0 | return; |
4530 | 0 | } |
4531 | | |
4532 | | // If the triplet is specified in one of our classes use it. |
4533 | | // We use the first one we find. |
4534 | 0 | Triplet<uint32_t> candidate_lft; |
4535 | 0 | const ClientClasses classes = ctx.query_->getClasses(); |
4536 | 0 | if (!classes.empty()) { |
4537 | | // Let's get class definitions |
4538 | 0 | const ClientClassDictionaryPtr& dict = |
4539 | 0 | CfgMgr::instance().getCurrentCfg()->getClientClassDictionary(); |
4540 | | |
4541 | | // Iterate over the assigned class definitions. |
4542 | 0 | for (auto const& name : classes) { |
4543 | 0 | ClientClassDefPtr cl = dict->findClass(name); |
4544 | 0 | if (cl && (!cl->getValid().unspecified())) { |
4545 | 0 | candidate_lft = cl->getValid(); |
4546 | 0 | break; |
4547 | 0 | } |
4548 | 0 | } |
4549 | 0 | } |
4550 | | |
4551 | | // If no classes specified it, get it from the subnet. |
4552 | 0 | if (!candidate_lft) { |
4553 | 0 | candidate_lft = ctx.subnet_->getValid(); |
4554 | 0 | } |
4555 | | |
4556 | | // Save remaining value. |
4557 | 0 | uint32_t remain(valid); |
4558 | | |
4559 | | // Set to the minimal value. |
4560 | 0 | valid = candidate_lft.getMin(); |
4561 | | |
4562 | | // Return at least the remaining value. |
4563 | 0 | if (remain > valid) { |
4564 | 0 | valid = remain; |
4565 | 0 | } |
4566 | 0 | } |
4567 | | |
4568 | | namespace { |
4569 | | bool |
4570 | 0 | useMinValidLft(const AllocEngine::ClientContext4& ctx, const IOAddress&addr) { |
4571 | 0 | auto const& threshold = ctx.subnet_->getAdaptiveLeaseTimeThreshold(); |
4572 | 0 | if (!threshold.unspecified() && (threshold < 1.0)) { |
4573 | 0 | auto const& occupancy = ctx.subnet_->getAllocator(Lease::TYPE_V4)-> |
4574 | 0 | getOccupancyRate(addr, ctx.query_->getClasses()); |
4575 | 0 | if (occupancy >= threshold) { |
4576 | 0 | return (true); |
4577 | 0 | } |
4578 | 0 | } |
4579 | 0 | return (false); |
4580 | 0 | } |
4581 | | } // end of anonymous namespace. |
4582 | | |
4583 | | Lease4Ptr |
4584 | | AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr, |
4585 | 0 | CalloutHandle::CalloutNextStep& callout_status) { |
4586 | 0 | if (!ctx.hwaddr_) { |
4587 | 0 | isc_throw(BadValue, "Can't create a lease with NULL HW address"); |
4588 | 0 | } |
4589 | 0 | if (!ctx.subnet_) { |
4590 | 0 | isc_throw(BadValue, "Can't create a lease without a subnet"); |
4591 | 0 | } |
4592 | | |
4593 | | // Get the context appropriate lifetime. |
4594 | 0 | uint32_t valid_lft = ctx.offer_lft_; |
4595 | 0 | if (!valid_lft) { |
4596 | 0 | if (useMinValidLft(ctx, addr)) { |
4597 | 0 | getMinValidLft(ctx, valid_lft); |
4598 | 0 | } else { |
4599 | 0 | valid_lft = getValidLft(ctx); |
4600 | 0 | } |
4601 | 0 | } |
4602 | |
|
4603 | 0 | time_t now = time(0); |
4604 | |
|
4605 | 0 | ClientIdPtr client_id; |
4606 | 0 | if (ctx.subnet_->getMatchClientId()) { |
4607 | 0 | client_id = ctx.clientid_; |
4608 | 0 | } |
4609 | |
|
4610 | 0 | Lease4Ptr lease(new Lease4(addr, ctx.hwaddr_, client_id, |
4611 | 0 | valid_lft, now, ctx.subnet_->getID())); |
4612 | | |
4613 | | // Set FQDN specific lease parameters. |
4614 | 0 | lease->fqdn_fwd_ = ctx.fwd_dns_update_; |
4615 | 0 | lease->fqdn_rev_ = ctx.rev_dns_update_; |
4616 | 0 | lease->hostname_ = ctx.hostname_; |
4617 | | |
4618 | | // Add (update) the extended information on the lease. |
4619 | 0 | static_cast<void>(updateLease4ExtendedInfo(lease, ctx)); |
4620 | | |
4621 | | // Let's execute all callouts registered for lease4_select |
4622 | 0 | if (ctx.callout_handle_ && |
4623 | 0 | HooksManager::calloutsPresent(hook_index_lease4_select_)) { |
4624 | | |
4625 | | // Use the RAII wrapper to make sure that the callout handle state is |
4626 | | // reset when this object goes out of scope. All hook points must do |
4627 | | // it to prevent possible circular dependency between the callout |
4628 | | // handle and its arguments. |
4629 | 0 | ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_); |
4630 | | |
4631 | | // Enable copying options from the packet within hook library. |
4632 | 0 | ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_); |
4633 | | |
4634 | | // Pass necessary arguments |
4635 | | // Pass the original client query |
4636 | 0 | ctx.callout_handle_->setArgument("query4", ctx.query_); |
4637 | | |
4638 | | // Subnet from which we do the allocation (That's as far as we can go |
4639 | | // with using SubnetPtr to point to Subnet4 object. Users should not |
4640 | | // be confused with dynamic_pointer_casts. They should get a concrete |
4641 | | // pointer (ConstSubnet4Ptr) pointing to a Subnet4 object. |
4642 | 0 | ConstSubnet4Ptr subnet4 = |
4643 | 0 | boost::dynamic_pointer_cast<const Subnet4>(ctx.subnet_); |
4644 | 0 | ctx.callout_handle_->setArgument("subnet4", subnet4); |
4645 | | |
4646 | | // Is this solicit (fake = true) or request (fake = false) |
4647 | 0 | ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_); |
4648 | | |
4649 | | // Are we allocating on DISCOVER? (i.e. offer_lft > 0). |
4650 | 0 | ctx.callout_handle_->setArgument("offer_lft", ctx.offer_lft_); |
4651 | | |
4652 | | // Pass the intended lease as well |
4653 | 0 | ctx.callout_handle_->setArgument("lease4", lease); |
4654 | | |
4655 | | // This is the first callout, so no need to clear any arguments |
4656 | 0 | HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_); |
4657 | |
|
4658 | 0 | callout_status = ctx.callout_handle_->getStatus(); |
4659 | | |
4660 | | // Callouts decided to skip the action. This means that the lease is not |
4661 | | // assigned, so the client will get NoAddrAvail as a result. The lease |
4662 | | // won't be inserted into the database. |
4663 | 0 | if (callout_status == CalloutHandle::NEXT_STEP_SKIP) { |
4664 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP); |
4665 | 0 | return (Lease4Ptr()); |
4666 | 0 | } |
4667 | | |
4668 | | // Let's use whatever callout returned. Hopefully it is the same lease |
4669 | | // we handled to it. |
4670 | 0 | ctx.callout_handle_->getArgument("lease4", lease); |
4671 | 0 | } |
4672 | | |
4673 | 0 | if (ctx.fake_allocation_ && ctx.offer_lft_) { |
4674 | | // Turn them off before we persist, so we'll see it as different when |
4675 | | // we extend it in the REQUEST. This should cause us to do DDNS (if |
4676 | | // it's enabled). |
4677 | 0 | lease->fqdn_fwd_ = false; |
4678 | 0 | lease->fqdn_rev_ = false; |
4679 | 0 | } |
4680 | |
|
4681 | 0 | if (!ctx.fake_allocation_ || ctx.offer_lft_) { |
4682 | 0 | auto const& pool = ctx.subnet_->getPool(Lease::TYPE_V4, lease->addr_, false); |
4683 | 0 | if (pool) { |
4684 | 0 | lease->pool_id_ = pool->getID(); |
4685 | 0 | } |
4686 | | // That is a real (REQUEST) allocation |
4687 | 0 | bool status = LeaseMgrFactory::instance().addLease(lease); |
4688 | 0 | if (status) { |
4689 | | |
4690 | | // The lease insertion succeeded, let's bump up the statistic. |
4691 | 0 | StatsMgr::instance().addValue( |
4692 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4693 | 0 | "assigned-addresses"), |
4694 | 0 | static_cast<int64_t>(1)); |
4695 | |
|
4696 | 0 | StatsMgr::instance().addValue( |
4697 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4698 | 0 | "cumulative-assigned-addresses"), |
4699 | 0 | static_cast<int64_t>(1)); |
4700 | |
|
4701 | 0 | if (pool) { |
4702 | 0 | StatsMgr::instance().addValue( |
4703 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4704 | 0 | StatsMgr::generateName("pool", pool->getID(), |
4705 | 0 | "assigned-addresses")), |
4706 | 0 | static_cast<int64_t>(1)); |
4707 | |
|
4708 | 0 | StatsMgr::instance().addValue( |
4709 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4710 | 0 | StatsMgr::generateName("pool", pool->getID(), |
4711 | 0 | "cumulative-assigned-addresses")), |
4712 | 0 | static_cast<int64_t>(1)); |
4713 | 0 | } |
4714 | | |
4715 | |
|
4716 | 0 | StatsMgr::instance().addValue("assigned-addresses", static_cast<int64_t>(1)); |
4717 | |
|
4718 | 0 | StatsMgr::instance().addValue("cumulative-assigned-addresses", static_cast<int64_t>(1)); |
4719 | |
|
4720 | 0 | return (lease); |
4721 | 0 | } else { |
4722 | | // One of many failures with LeaseMgr (e.g. lost connection to the |
4723 | | // database, database failed etc.). One notable case for that |
4724 | | // is that we are working in multi-process mode and we lost a race |
4725 | | // (some other process got that address first) |
4726 | 0 | return (Lease4Ptr()); |
4727 | 0 | } |
4728 | 0 | } else { |
4729 | | // That is only fake (DISCOVER) allocation |
4730 | | // It is for OFFER only. We should not insert the lease and callers |
4731 | | // have already verified the lease does not exist in the database. |
4732 | 0 | return (lease); |
4733 | 0 | } |
4734 | 0 | } |
4735 | | |
4736 | | void |
4737 | 0 | AllocEngine::getRemaining(const Lease4Ptr& lease, uint32_t& valid) { |
4738 | 0 | valid = 0; |
4739 | 0 | if (!lease || (lease->state_ != Lease::STATE_DEFAULT)) { |
4740 | 0 | return; |
4741 | 0 | } |
4742 | | // Always remain infinite lifetime leases. |
4743 | 0 | if (lease->valid_lft_ == Lease::INFINITY_LFT) { |
4744 | 0 | valid = Lease::INFINITY_LFT; |
4745 | 0 | return; |
4746 | 0 | } |
4747 | 0 | time_t now = time(0); |
4748 | | // Refuse time not going forward. |
4749 | 0 | if (lease->cltt_ > now) { |
4750 | 0 | return; |
4751 | 0 | } |
4752 | 0 | uint32_t age = now - lease->cltt_; |
4753 | | // Already expired. |
4754 | 0 | if (age >= lease->valid_lft_) { |
4755 | 0 | return; |
4756 | 0 | } |
4757 | 0 | valid = lease->valid_lft_ - age; |
4758 | 0 | } |
4759 | | |
4760 | | Lease4Ptr |
4761 | | AllocEngine::renewLease4(const Lease4Ptr& lease, |
4762 | 0 | AllocEngine::ClientContext4& ctx) { |
4763 | 0 | if (!lease) { |
4764 | 0 | isc_throw(BadValue, "null lease specified for renewLease4"); |
4765 | 0 | } |
4766 | | |
4767 | | // Let's keep the old data. This is essential if we are using memfile |
4768 | | // (the lease returned points directly to the lease4 object in the database) |
4769 | | // We'll need it if we want to skip update (i.e. roll back renewal) |
4770 | | /// @todo: remove this? |
4771 | 0 | Lease4Ptr old_values = boost::make_shared<Lease4>(*lease); |
4772 | 0 | ctx.old_lease_.reset(new Lease4(*old_values)); |
4773 | | |
4774 | | // Update the lease with the information from the context. |
4775 | | // If there was no significant changes, try reuse. |
4776 | 0 | lease->reuseable_valid_lft_ = 0; |
4777 | 0 | if (!updateLease4Information(lease, ctx)) { |
4778 | 0 | setLeaseReusable(lease, ctx); |
4779 | 0 | } |
4780 | |
|
4781 | 0 | if (!ctx.fake_allocation_ || ctx.offer_lft_) { |
4782 | | // If the lease is expired we have to reclaim it before |
4783 | | // re-assigning it to the client. The lease reclamation |
4784 | | // involves execution of hooks and DNS update. |
4785 | 0 | if (ctx.old_lease_->expired()) { |
4786 | 0 | reclaimExpiredLease(ctx.old_lease_, ctx.callout_handle_); |
4787 | 0 | } |
4788 | |
|
4789 | 0 | lease->state_ = Lease::STATE_DEFAULT; |
4790 | 0 | } |
4791 | |
|
4792 | 0 | bool skip = false; |
4793 | | // Execute all callouts registered for lease4_renew. |
4794 | 0 | if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_renew_)) { |
4795 | | |
4796 | | // Use the RAII wrapper to make sure that the callout handle state is |
4797 | | // reset when this object goes out of scope. All hook points must do |
4798 | | // it to prevent possible circular dependency between the callout |
4799 | | // handle and its arguments. |
4800 | 0 | ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_); |
4801 | | |
4802 | | // Enable copying options from the packet within hook library. |
4803 | 0 | ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_); |
4804 | | |
4805 | | // Subnet from which we do the allocation. Convert the general subnet |
4806 | | // pointer to a pointer to a Subnet4. Note that because we are using |
4807 | | // boost smart pointers here, we need to do the cast using the boost |
4808 | | // version of dynamic_pointer_cast. |
4809 | 0 | ConstSubnet4Ptr subnet4 = |
4810 | 0 | boost::dynamic_pointer_cast<const Subnet4>(ctx.subnet_); |
4811 | | |
4812 | | // Pass the parameters. Note the clientid is passed only if match-client-id |
4813 | | // is set. This is done that way, because the lease4-renew hook point is |
4814 | | // about renewing a lease and the configuration parameter says the |
4815 | | // client-id should be ignored. Hence no clientid value if match-client-id |
4816 | | // is false. |
4817 | 0 | ctx.callout_handle_->setArgument("query4", ctx.query_); |
4818 | 0 | ctx.callout_handle_->setArgument("subnet4", subnet4); |
4819 | 0 | ctx.callout_handle_->setArgument("clientid", subnet4->getMatchClientId() ? |
4820 | 0 | ctx.clientid_ : ClientIdPtr()); |
4821 | 0 | ctx.callout_handle_->setArgument("hwaddr", ctx.hwaddr_); |
4822 | | |
4823 | | // Pass the lease to be updated |
4824 | 0 | ctx.callout_handle_->setArgument("lease4", lease); |
4825 | | |
4826 | | // Call all installed callouts |
4827 | 0 | HooksManager::callCallouts(Hooks.hook_index_lease4_renew_, |
4828 | 0 | *ctx.callout_handle_); |
4829 | | |
4830 | | // Callouts decided to skip the next processing step. The next |
4831 | | // processing step would actually renew the lease, so skip at this |
4832 | | // stage means "keep the old lease as it is". |
4833 | 0 | if (ctx.callout_handle_->getStatus() == CalloutHandle::NEXT_STEP_SKIP) { |
4834 | 0 | skip = true; |
4835 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, |
4836 | 0 | DHCPSRV_HOOK_LEASE4_RENEW_SKIP); |
4837 | 0 | } |
4838 | | |
4839 | | /// DROP status does not make sense here. |
4840 | 0 | } |
4841 | |
|
4842 | 0 | if ((!ctx.fake_allocation_ || ctx.offer_lft_) && !skip && (lease->reuseable_valid_lft_ == 0)) { |
4843 | 0 | auto const& pool = ctx.subnet_->getPool(Lease::TYPE_V4, lease->addr_, false); |
4844 | 0 | if (pool) { |
4845 | 0 | lease->pool_id_ = pool->getID(); |
4846 | 0 | } |
4847 | | |
4848 | | // for REQUEST we do update the lease |
4849 | 0 | LeaseMgrFactory::instance().updateLease4(lease); |
4850 | | |
4851 | | // We need to account for the re-assignment of the lease. |
4852 | 0 | if (ctx.old_lease_->expired() || ctx.old_lease_->state_ == Lease::STATE_EXPIRED_RECLAIMED) { |
4853 | 0 | StatsMgr::instance().addValue( |
4854 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4855 | 0 | "assigned-addresses"), |
4856 | 0 | static_cast<int64_t>(1)); |
4857 | |
|
4858 | 0 | StatsMgr::instance().addValue( |
4859 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4860 | 0 | "cumulative-assigned-addresses"), |
4861 | 0 | static_cast<int64_t>(1)); |
4862 | |
|
4863 | 0 | if (pool) { |
4864 | 0 | StatsMgr::instance().addValue( |
4865 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4866 | 0 | StatsMgr::generateName("pool", pool->getID(), |
4867 | 0 | "assigned-addresses")), |
4868 | 0 | static_cast<int64_t>(1)); |
4869 | |
|
4870 | 0 | StatsMgr::instance().addValue( |
4871 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4872 | 0 | StatsMgr::generateName("pool", pool->getID(), |
4873 | 0 | "cumulative-assigned-addresses")), |
4874 | 0 | static_cast<int64_t>(1)); |
4875 | 0 | } |
4876 | |
|
4877 | 0 | StatsMgr::instance().addValue("assigned-addresses", static_cast<int64_t>(1)); |
4878 | |
|
4879 | 0 | StatsMgr::instance().addValue("cumulative-assigned-addresses", static_cast<int64_t>(1)); |
4880 | 0 | } |
4881 | 0 | } |
4882 | 0 | if (skip) { |
4883 | | // Rollback changes (really useful only for memfile) |
4884 | | /// @todo: remove this? |
4885 | 0 | *lease = *old_values; |
4886 | 0 | } |
4887 | |
|
4888 | 0 | return (lease); |
4889 | 0 | } |
4890 | | |
4891 | | Lease4Ptr |
4892 | | AllocEngine::reuseExpiredLease4(Lease4Ptr& expired, |
4893 | | AllocEngine::ClientContext4& ctx, |
4894 | 0 | CalloutHandle::CalloutNextStep& callout_status) { |
4895 | 0 | if (!expired) { |
4896 | 0 | isc_throw(BadValue, "null lease specified for reuseExpiredLease"); |
4897 | 0 | } |
4898 | | |
4899 | 0 | if (!ctx.subnet_) { |
4900 | 0 | isc_throw(BadValue, "null subnet specified for the reuseExpiredLease"); |
4901 | 0 | } |
4902 | | |
4903 | 0 | if (!ctx.fake_allocation_ || ctx.offer_lft_) { |
4904 | | // The expired lease needs to be reclaimed before it can be reused. |
4905 | | // This includes declined leases for which probation period has |
4906 | | // elapsed. |
4907 | 0 | reclaimExpiredLease(expired, ctx.callout_handle_); |
4908 | 0 | expired->state_ = Lease::STATE_DEFAULT; |
4909 | 0 | } |
4910 | |
|
4911 | 0 | expired->reuseable_valid_lft_ = 0; |
4912 | 0 | static_cast<void>(updateLease4Information(expired, ctx)); |
4913 | |
|
4914 | 0 | LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA, |
4915 | 0 | ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA) |
4916 | 0 | .arg(ctx.query_->getLabel()) |
4917 | 0 | .arg(expired->toText()); |
4918 | | |
4919 | | // Let's execute all callouts registered for lease4_select |
4920 | 0 | if (ctx.callout_handle_ && |
4921 | 0 | HooksManager::calloutsPresent(hook_index_lease4_select_)) { |
4922 | | |
4923 | | // Enable copying options from the packet within hook library. |
4924 | 0 | ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_); |
4925 | | |
4926 | | // Use the RAII wrapper to make sure that the callout handle state is |
4927 | | // reset when this object goes out of scope. All hook points must do |
4928 | | // it to prevent possible circular dependency between the callout |
4929 | | // handle and its arguments. |
4930 | 0 | ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_); |
4931 | | |
4932 | | // Pass necessary arguments |
4933 | | // Pass the original client query |
4934 | 0 | ctx.callout_handle_->setArgument("query4", ctx.query_); |
4935 | | |
4936 | | // Subnet from which we do the allocation. Convert the general subnet |
4937 | | // pointer to a pointer to a Subnet4. Note that because we are using |
4938 | | // boost smart pointers here, we need to do the cast using the boost |
4939 | | // version of dynamic_pointer_cast. |
4940 | 0 | ConstSubnet4Ptr subnet4 = |
4941 | 0 | boost::dynamic_pointer_cast<const Subnet4>(ctx.subnet_); |
4942 | 0 | ctx.callout_handle_->setArgument("subnet4", subnet4); |
4943 | | |
4944 | | // Is this solicit (fake = true) or request (fake = false) |
4945 | 0 | ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_); |
4946 | 0 | ctx.callout_handle_->setArgument("offer_lft", ctx.offer_lft_); |
4947 | | |
4948 | | // The lease that will be assigned to a client |
4949 | 0 | ctx.callout_handle_->setArgument("lease4", expired); |
4950 | | |
4951 | | // Call the callouts |
4952 | 0 | HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_); |
4953 | |
|
4954 | 0 | callout_status = ctx.callout_handle_->getStatus(); |
4955 | | |
4956 | | // Callouts decided to skip the action. This means that the lease is not |
4957 | | // assigned, so the client will get NoAddrAvail as a result. The lease |
4958 | | // won't be inserted into the database. |
4959 | 0 | if (callout_status == CalloutHandle::NEXT_STEP_SKIP) { |
4960 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, |
4961 | 0 | DHCPSRV_HOOK_LEASE4_SELECT_SKIP); |
4962 | 0 | return (Lease4Ptr()); |
4963 | 0 | } |
4964 | | |
4965 | | /// DROP status does not make sense here. |
4966 | | |
4967 | | // Let's use whatever callout returned. Hopefully it is the same lease |
4968 | | // we handed to it. |
4969 | 0 | ctx.callout_handle_->getArgument("lease4", expired); |
4970 | 0 | } |
4971 | | |
4972 | 0 | if (!ctx.fake_allocation_ || ctx.offer_lft_) { |
4973 | 0 | auto const& pool = ctx.subnet_->getPool(Lease::TYPE_V4, expired->addr_, false); |
4974 | 0 | if (pool) { |
4975 | 0 | expired->pool_id_ = pool->getID(); |
4976 | 0 | } |
4977 | | // for REQUEST we do update the lease |
4978 | 0 | LeaseMgrFactory::instance().updateLease4(expired); |
4979 | | |
4980 | | // We need to account for the re-assignment of the lease. |
4981 | 0 | StatsMgr::instance().addValue( |
4982 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4983 | 0 | "assigned-addresses"), |
4984 | 0 | static_cast<int64_t>(1)); |
4985 | |
|
4986 | 0 | StatsMgr::instance().addValue( |
4987 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4988 | 0 | "cumulative-assigned-addresses"), |
4989 | 0 | static_cast<int64_t>(1)); |
4990 | |
|
4991 | 0 | if (pool) { |
4992 | 0 | StatsMgr::instance().addValue( |
4993 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
4994 | 0 | StatsMgr::generateName("pool", pool->getID(), |
4995 | 0 | "assigned-addresses")), |
4996 | 0 | static_cast<int64_t>(1)); |
4997 | |
|
4998 | 0 | StatsMgr::instance().addValue( |
4999 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
5000 | 0 | StatsMgr::generateName("pool", pool->getID(), |
5001 | 0 | "cumulative-assigned-addresses")), |
5002 | 0 | static_cast<int64_t>(1)); |
5003 | 0 | } |
5004 | |
|
5005 | 0 | StatsMgr::instance().addValue("assigned-addresses", static_cast<int64_t>(1)); |
5006 | |
|
5007 | 0 | StatsMgr::instance().addValue("cumulative-assigned-addresses", static_cast<int64_t>(1)); |
5008 | 0 | } |
5009 | | |
5010 | | // We do nothing for SOLICIT. We'll just update database when |
5011 | | // the client gets back to us with REQUEST message. |
5012 | | |
5013 | | // it's not really expired at this stage anymore - let's return it as |
5014 | | // an updated lease |
5015 | 0 | return (expired); |
5016 | 0 | } |
5017 | | |
5018 | | Lease4Ptr |
5019 | | AllocEngine::allocateOrReuseLease4(const IOAddress& candidate, ClientContext4& ctx, |
5020 | 0 | CalloutHandle::CalloutNextStep& callout_status) { |
5021 | 0 | ctx.conflicting_lease_.reset(); |
5022 | |
|
5023 | 0 | Lease4Ptr exist_lease = LeaseMgrFactory::instance().getLease4(candidate); |
5024 | 0 | if (exist_lease) { |
5025 | 0 | if (exist_lease->expired()) { |
5026 | 0 | ctx.old_lease_ = Lease4Ptr(new Lease4(*exist_lease)); |
5027 | | // reuseExpiredLease4() will reclaim the use which will |
5028 | | // queue an NCR remove it needed. Clear the DNS fields in |
5029 | | // the old lease to avoid a redundant remove in server logic. |
5030 | 0 | ctx.old_lease_->hostname_.clear(); |
5031 | 0 | ctx.old_lease_->fqdn_fwd_ = false; |
5032 | 0 | ctx.old_lease_->fqdn_rev_ = false; |
5033 | 0 | return (reuseExpiredLease4(exist_lease, ctx, callout_status)); |
5034 | |
|
5035 | 0 | } else { |
5036 | | // If there is a lease and it is not expired, pass this lease back |
5037 | | // to the caller in the context. The caller may need to know |
5038 | | // which lease we're conflicting with. |
5039 | 0 | ctx.conflicting_lease_ = exist_lease; |
5040 | 0 | } |
5041 | |
|
5042 | 0 | } else { |
5043 | 0 | return (createLease4(ctx, candidate, callout_status)); |
5044 | 0 | } |
5045 | 0 | return (Lease4Ptr()); |
5046 | 0 | } |
5047 | | |
5048 | | Lease4Ptr |
5049 | 0 | AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) { |
5050 | 0 | Lease4Ptr new_lease; |
5051 | 0 | ConstSubnet4Ptr subnet = ctx.subnet_; |
5052 | | |
5053 | | // Need to check if the subnet belongs to a shared network. If so, |
5054 | | // we might be able to find a better subnet for lease allocation, |
5055 | | // for which it is more likely that there are some leases available. |
5056 | | // If we stick to the selected subnet, we may end up walking over |
5057 | | // the entire subnet (or more subnets) to discover that the address |
5058 | | // pools have been exhausted. Using a subnet from which an address |
5059 | | // was assigned most recently is an optimization which increases |
5060 | | // the likelihood of starting from the subnet which address pools |
5061 | | // are not exhausted. |
5062 | 0 | SharedNetwork4Ptr network; |
5063 | 0 | ctx.subnet_->getSharedNetwork(network); |
5064 | 0 | if (network) { |
5065 | | // This would try to find a subnet with the same set of classes |
5066 | | // as the current subnet, but with the more recent "usage timestamp". |
5067 | | // This timestamp is only updated for the allocations made with an |
5068 | | // allocator (unreserved lease allocations), not the static |
5069 | | // allocations or requested addresses. |
5070 | 0 | ctx.subnet_ = subnet = network->getPreferredSubnet(ctx.subnet_); |
5071 | 0 | } |
5072 | | |
5073 | | // We have the choice in the order checking the lease and |
5074 | | // the reservation. The default is to begin by the lease |
5075 | | // if the multi-threading is disabled. |
5076 | 0 | bool check_reservation_first = MultiThreadingMgr::instance().getMode(); |
5077 | |
|
5078 | 0 | ConstSubnet4Ptr original_subnet = subnet; |
5079 | |
|
5080 | 0 | uint128_t total_attempts = 0; |
5081 | | |
5082 | | // The following counter tracks the number of subnets with matching client |
5083 | | // classes from which the allocation engine attempted to assign leases. |
5084 | 0 | uint64_t subnets_with_unavail_leases = 0; |
5085 | | // The following counter tracks the number of subnets in which there were |
5086 | | // no matching pools for the client. |
5087 | 0 | uint64_t subnets_with_unavail_pools = 0; |
5088 | |
|
5089 | 0 | auto const& classes = ctx.query_->getClasses(); |
5090 | |
|
5091 | 0 | while (subnet) { |
5092 | 0 | ClientIdPtr client_id; |
5093 | 0 | if (subnet->getMatchClientId()) { |
5094 | 0 | client_id = ctx.clientid_; |
5095 | 0 | } |
5096 | |
|
5097 | 0 | uint128_t const possible_attempts = |
5098 | 0 | subnet->getPoolCapacity(Lease::TYPE_V4, classes); |
5099 | | |
5100 | | // If the number of tries specified in the allocation engine constructor |
5101 | | // is set to 0 (unlimited) or the pools capacity is lower than that number, |
5102 | | // let's use the pools capacity as the maximum number of tries. Trying |
5103 | | // more than the actual pools capacity is a waste of time. If the specified |
5104 | | // number of tries is lower than the pools capacity, use that number. |
5105 | 0 | uint128_t const max_attempts = |
5106 | 0 | (attempts_ == 0 || possible_attempts < attempts_) ? |
5107 | 0 | possible_attempts : |
5108 | 0 | attempts_; |
5109 | |
|
5110 | 0 | if (max_attempts > 0) { |
5111 | | // If max_attempts is greater than 0, there are some pools in this subnet |
5112 | | // from which we can potentially get a lease. |
5113 | 0 | ++subnets_with_unavail_leases; |
5114 | 0 | } else { |
5115 | | // If max_attempts is 0, it is an indication that there are no pools |
5116 | | // in the subnet from which we can get a lease. |
5117 | 0 | ++subnets_with_unavail_pools; |
5118 | 0 | } |
5119 | |
|
5120 | 0 | bool exclude_first_last_24 = ((subnet->get().second <= 24) && |
5121 | 0 | CfgMgr::instance().getCurrentCfg()->getExcludeFirstLast24()); |
5122 | |
|
5123 | 0 | CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE; |
5124 | |
|
5125 | 0 | for (uint128_t i = 0; i < max_attempts; ++i) { |
5126 | |
|
5127 | 0 | ++total_attempts; |
5128 | |
|
5129 | 0 | auto allocator = subnet->getAllocator(Lease::TYPE_V4); |
5130 | 0 | IOAddress candidate = allocator->pickAddress(classes, |
5131 | 0 | client_id, |
5132 | 0 | ctx.requested_address_); |
5133 | | |
5134 | | // An allocator may return zero address when it has pools exhausted. |
5135 | 0 | if (candidate.isV4Zero()) { |
5136 | 0 | break; |
5137 | 0 | } |
5138 | | |
5139 | 0 | if (exclude_first_last_24) { |
5140 | | // Exclude .0 and .255 addresses. |
5141 | 0 | auto const& bytes = candidate.toBytes(); |
5142 | 0 | if ((bytes.size() != 4) || |
5143 | 0 | (bytes[3] == 0) || (bytes[3] == 255U)) { |
5144 | | // Don't allocate. |
5145 | 0 | continue; |
5146 | 0 | } |
5147 | 0 | } |
5148 | | |
5149 | | // First check for reservation when it is the choice. |
5150 | 0 | if (check_reservation_first && addressReserved(candidate, ctx)) { |
5151 | | // Don't allocate. |
5152 | 0 | continue; |
5153 | 0 | } |
5154 | | |
5155 | | // Check if the resource is busy i.e. can be being allocated |
5156 | | // by another thread to another client. |
5157 | 0 | ResourceHandler4 resource_handler; |
5158 | 0 | if (MultiThreadingMgr::instance().getMode() && |
5159 | 0 | !resource_handler.tryLock4(candidate)) { |
5160 | | // Don't allocate. |
5161 | 0 | continue; |
5162 | 0 | } |
5163 | | |
5164 | | // Check for an existing lease for the candidate address. |
5165 | 0 | Lease4Ptr exist_lease = LeaseMgrFactory::instance().getLease4(candidate); |
5166 | 0 | if (!exist_lease) { |
5167 | | // No existing lease, is it reserved? |
5168 | 0 | if (check_reservation_first || !addressReserved(candidate, ctx)) { |
5169 | | // Not reserved use it. |
5170 | 0 | new_lease = createLease4(ctx, candidate, callout_status); |
5171 | 0 | } |
5172 | 0 | } else { |
5173 | | // An lease exists, is expired, and not reserved use it. |
5174 | 0 | if (exist_lease->expired() && |
5175 | 0 | (check_reservation_first || !addressReserved(candidate, ctx))) { |
5176 | 0 | ctx.old_lease_ = Lease4Ptr(new Lease4(*exist_lease)); |
5177 | 0 | new_lease = reuseExpiredLease4(exist_lease, ctx, callout_status); |
5178 | 0 | } |
5179 | 0 | } |
5180 | | |
5181 | | // We found a lease we can use, return it. |
5182 | 0 | if (new_lease) { |
5183 | 0 | return (new_lease); |
5184 | 0 | } |
5185 | | |
5186 | 0 | if (ctx.callout_handle_ && (callout_status != CalloutHandle::NEXT_STEP_CONTINUE)) { |
5187 | | // Don't retry when the callout status is not continue. |
5188 | 0 | subnet.reset(); |
5189 | 0 | break; |
5190 | 0 | } |
5191 | 0 | } |
5192 | | |
5193 | | // This pointer may be set to NULL if hooks set SKIP status. |
5194 | 0 | if (subnet) { |
5195 | 0 | subnet = subnet->getNextSubnet(original_subnet, classes); |
5196 | |
|
5197 | 0 | if (subnet) { |
5198 | 0 | ctx.subnet_ = subnet; |
5199 | 0 | } |
5200 | 0 | } |
5201 | 0 | } |
5202 | | |
5203 | 0 | if (network) { |
5204 | | // The client is in the shared network. Let's log the high level message |
5205 | | // indicating which shared network the client belongs to. |
5206 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_SHARED_NETWORK) |
5207 | 0 | .arg(ctx.query_->getLabel()) |
5208 | 0 | .arg(network->getName()) |
5209 | 0 | .arg(subnets_with_unavail_leases) |
5210 | 0 | .arg(subnets_with_unavail_pools); |
5211 | 0 | StatsMgr::instance().addValue("v4-allocation-fail-shared-network", |
5212 | 0 | static_cast<int64_t>(1)); |
5213 | 0 | StatsMgr::instance().addValue( |
5214 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
5215 | 0 | "v4-allocation-fail-shared-network"), |
5216 | 0 | static_cast<int64_t>(1)); |
5217 | 0 | } else { |
5218 | | // The client is not connected to a shared network. It is connected |
5219 | | // to a subnet. Let's log some details about the subnet. |
5220 | 0 | std::string shared_network = ctx.subnet_->getSharedNetworkName(); |
5221 | 0 | if (shared_network.empty()) { |
5222 | 0 | shared_network = "(none)"; |
5223 | 0 | } |
5224 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_SUBNET) |
5225 | 0 | .arg(ctx.query_->getLabel()) |
5226 | 0 | .arg(ctx.subnet_->toText()) |
5227 | 0 | .arg(ctx.subnet_->getID()) |
5228 | 0 | .arg(shared_network); |
5229 | 0 | StatsMgr::instance().addValue("v4-allocation-fail-subnet", |
5230 | 0 | static_cast<int64_t>(1)); |
5231 | 0 | StatsMgr::instance().addValue( |
5232 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
5233 | 0 | "v4-allocation-fail-subnet"), |
5234 | 0 | static_cast<int64_t>(1)); |
5235 | 0 | } |
5236 | 0 | if (total_attempts == 0) { |
5237 | | // In this case, it seems that none of the pools in the subnets could |
5238 | | // be used for that client, both in case the client is connected to |
5239 | | // a shared network or to a single subnet. Apparently, the client was |
5240 | | // rejected to use the pools because of the client classes' mismatch. |
5241 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_NO_POOLS) |
5242 | 0 | .arg(ctx.query_->getLabel()); |
5243 | 0 | StatsMgr::instance().addValue("v4-allocation-fail-no-pools", |
5244 | 0 | static_cast<int64_t>(1)); |
5245 | 0 | StatsMgr::instance().addValue( |
5246 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
5247 | 0 | "v4-allocation-fail-no-pools"), |
5248 | 0 | static_cast<int64_t>(1)); |
5249 | 0 | } else { |
5250 | | // This is an old log message which provides a number of attempts |
5251 | | // made by the allocation engine to allocate a lease. The only case |
5252 | | // when we don't want to log this message is when the number of |
5253 | | // attempts is zero (condition above), because it would look silly. |
5254 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL) |
5255 | 0 | .arg(ctx.query_->getLabel()) |
5256 | 0 | .arg(total_attempts); |
5257 | 0 | StatsMgr::instance().addValue("v4-allocation-fail", |
5258 | 0 | static_cast<int64_t>(1)); |
5259 | 0 | StatsMgr::instance().addValue( |
5260 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
5261 | 0 | "v4-allocation-fail"), |
5262 | 0 | static_cast<int64_t>(1)); |
5263 | 0 | } |
5264 | |
|
5265 | 0 | if (!classes.empty()) { |
5266 | 0 | LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES) |
5267 | 0 | .arg(ctx.query_->getLabel()) |
5268 | 0 | .arg(classes.toText()); |
5269 | 0 | StatsMgr::instance().addValue("v4-allocation-fail-classes", |
5270 | 0 | static_cast<int64_t>(1)); |
5271 | 0 | StatsMgr::instance().addValue( |
5272 | 0 | StatsMgr::generateName("subnet", ctx.subnet_->getID(), |
5273 | 0 | "v4-allocation-fail-classes"), |
5274 | 0 | static_cast<int64_t>(1)); |
5275 | 0 | } |
5276 | |
|
5277 | 0 | return (new_lease); |
5278 | 0 | } |
5279 | | |
5280 | | bool |
5281 | | AllocEngine::updateLease4Information(const Lease4Ptr& lease, |
5282 | 0 | AllocEngine::ClientContext4& ctx) const { |
5283 | 0 | bool changed = false; |
5284 | 0 | if (lease->subnet_id_ != ctx.subnet_->getID()) { |
5285 | 0 | changed = true; |
5286 | 0 | lease->subnet_id_ = ctx.subnet_->getID(); |
5287 | 0 | } |
5288 | 0 | if ((!ctx.hwaddr_ && lease->hwaddr_) || |
5289 | 0 | (ctx.hwaddr_ && |
5290 | 0 | (!lease->hwaddr_ || (*ctx.hwaddr_ != *lease->hwaddr_)))) { |
5291 | 0 | changed = true; |
5292 | 0 | lease->hwaddr_ = ctx.hwaddr_; |
5293 | 0 | } |
5294 | 0 | if (ctx.subnet_->getMatchClientId() && ctx.clientid_) { |
5295 | 0 | if (!lease->client_id_ || (*ctx.clientid_ != *lease->client_id_)) { |
5296 | 0 | changed = true; |
5297 | 0 | lease->client_id_ = ctx.clientid_; |
5298 | 0 | } |
5299 | 0 | } else if (lease->client_id_) { |
5300 | 0 | changed = true; |
5301 | 0 | lease->client_id_ = ClientIdPtr(); |
5302 | 0 | } |
5303 | 0 | uint32_t remain_lft(0); |
5304 | 0 | getRemaining(lease, remain_lft); |
5305 | 0 | lease->cltt_ = time(0); |
5306 | | |
5307 | | // Get the context appropriate valid lifetime. |
5308 | 0 | lease->valid_lft_ = ctx.offer_lft_; |
5309 | 0 | if (!lease->valid_lft_) { |
5310 | 0 | if (useMinValidLft(ctx, lease->addr_)) { |
5311 | 0 | lease->valid_lft_ = remain_lft; |
5312 | 0 | getMinValidLft(ctx, lease->valid_lft_); |
5313 | 0 | } else { |
5314 | 0 | lease->valid_lft_ = getValidLft(ctx); |
5315 | 0 | } |
5316 | 0 | } |
5317 | | |
5318 | | // Valid lifetime has changed. |
5319 | 0 | if (lease->valid_lft_ != lease->current_valid_lft_) { |
5320 | 0 | changed = true; |
5321 | 0 | } |
5322 | |
|
5323 | 0 | if ((lease->fqdn_fwd_ != ctx.fwd_dns_update_) || |
5324 | 0 | (lease->fqdn_rev_ != ctx.rev_dns_update_) || |
5325 | 0 | (lease->hostname_ != ctx.hostname_)) { |
5326 | 0 | changed = true; |
5327 | 0 | lease->fqdn_fwd_ = ctx.fwd_dns_update_; |
5328 | 0 | lease->fqdn_rev_ = ctx.rev_dns_update_; |
5329 | 0 | lease->hostname_ = ctx.hostname_; |
5330 | 0 | } |
5331 | | |
5332 | | // Add (update) the extended information on the lease. |
5333 | 0 | if (updateLease4ExtendedInfo(lease, ctx)) { |
5334 | 0 | changed = true; |
5335 | 0 | } |
5336 | |
|
5337 | 0 | return (changed); |
5338 | 0 | } |
5339 | | |
5340 | | bool |
5341 | | AllocEngine::updateLease4ExtendedInfo(const Lease4Ptr& lease, |
5342 | 0 | const AllocEngine::ClientContext4& ctx) const { |
5343 | 0 | bool changed = false; |
5344 | | |
5345 | | // If storage is not enabled then punt. |
5346 | 0 | if (!ctx.subnet_->getStoreExtendedInfo()) { |
5347 | 0 | return (changed); |
5348 | 0 | } |
5349 | | |
5350 | | // Look for relay agent information option (option 82) |
5351 | 0 | OptionPtr rai = ctx.query_->getOption(DHO_DHCP_AGENT_OPTIONS); |
5352 | 0 | if (!rai) { |
5353 | | // Pkt4 doesn't have it, so nothing to store (or update). |
5354 | 0 | return (changed); |
5355 | 0 | } |
5356 | | |
5357 | | // Check if the RAI was recovered from stashed agent options. |
5358 | 0 | ConstElementPtr sao = CfgMgr::instance().getCurrentCfg()-> |
5359 | 0 | getConfiguredGlobal(CfgGlobals::STASH_AGENT_OPTIONS); |
5360 | 0 | if (sao && (sao->getType() == data::Element::boolean) && |
5361 | 0 | sao->boolValue() && ctx.query_->inClass("STASH_AGENT_OPTIONS")) { |
5362 | 0 | return (changed); |
5363 | 0 | } |
5364 | | |
5365 | | // Create a StringElement with the hex string for relay-agent-info. |
5366 | 0 | ElementPtr relay_agent(new StringElement(rai->toHexString())); |
5367 | | |
5368 | | // Now we wrap the agent info in a map. This allows for future expansion. |
5369 | 0 | ElementPtr extended_info = Element::createMap(); |
5370 | 0 | extended_info->set("sub-options", relay_agent); |
5371 | |
|
5372 | 0 | OptionPtr remote_id = rai->getOption(RAI_OPTION_REMOTE_ID); |
5373 | 0 | if (remote_id) { |
5374 | 0 | std::vector<uint8_t> bytes = remote_id->toBinary(); |
5375 | 0 | lease->remote_id_ = bytes; |
5376 | 0 | if (bytes.size() > 0) { |
5377 | 0 | extended_info->set("remote-id", |
5378 | 0 | Element::create(encode::encodeHex(bytes))); |
5379 | 0 | } |
5380 | 0 | } |
5381 | |
|
5382 | 0 | OptionPtr relay_id = rai->getOption(RAI_OPTION_RELAY_ID); |
5383 | 0 | if (relay_id) { |
5384 | 0 | std::vector<uint8_t> bytes = relay_id->toBinary(false); |
5385 | 0 | lease->relay_id_ = bytes; |
5386 | 0 | if (bytes.size() > 0) { |
5387 | 0 | extended_info->set("relay-id", |
5388 | 0 | Element::create(encode::encodeHex(bytes))); |
5389 | 0 | } |
5390 | 0 | } |
5391 | | |
5392 | | // Return true if the extended-info on the lease changed. |
5393 | 0 | return (lease->updateUserContextISC("relay-agent-info", extended_info)); |
5394 | 0 | } |
5395 | | |
5396 | | void |
5397 | | AllocEngine::updateLease6ExtendedInfo(const Lease6Ptr& lease, |
5398 | 0 | const AllocEngine::ClientContext6& ctx) const { |
5399 | | // The extended info action is a transient value but be safe so reset it. |
5400 | 0 | lease->extended_info_action_ = Lease6::ACTION_IGNORE; |
5401 | | |
5402 | | // If storage is not enabled then punt. |
5403 | 0 | if (!ctx.subnet_->getStoreExtendedInfo()) { |
5404 | 0 | return; |
5405 | 0 | } |
5406 | | |
5407 | | // If we do not have relay information, then punt. |
5408 | 0 | if (ctx.query_->relay_info_.empty()) { |
5409 | 0 | return; |
5410 | 0 | } |
5411 | | |
5412 | | // We need to convert the vector of RelayInfo instances in |
5413 | | // into an Element hierarchy like this: |
5414 | | // "relay-info": [ |
5415 | | // { |
5416 | | // "hop": 123, |
5417 | | // "link": "2001:db8::1", |
5418 | | // "peer": "2001:db8::2", |
5419 | | // "options": "0x..." |
5420 | | // },..] |
5421 | | // |
5422 | 0 | ElementPtr extended_info = Element::createList(); |
5423 | 0 | for (auto const& relay : ctx.query_->relay_info_) { |
5424 | 0 | ElementPtr relay_elem = Element::createMap(); |
5425 | 0 | relay_elem->set("hop", ElementPtr(new IntElement(relay.hop_count_))); |
5426 | 0 | relay_elem->set("link", ElementPtr(new StringElement(relay.linkaddr_.toText()))); |
5427 | 0 | relay_elem->set("peer", ElementPtr(new StringElement(relay.peeraddr_.toText()))); |
5428 | | |
5429 | | // If there are relay options, we'll pack them into a buffer and then |
5430 | | // convert that into a hex string. If there are no options, we omit |
5431 | | // then entry. |
5432 | 0 | if (!relay.options_.empty()) { |
5433 | 0 | OutputBuffer buf(128); |
5434 | 0 | LibDHCP::packOptions6(buf, relay.options_); |
5435 | |
|
5436 | 0 | if (buf.getLength() > 0) { |
5437 | 0 | const uint8_t* cp = buf.getData(); |
5438 | 0 | std::vector<uint8_t> bytes; |
5439 | 0 | std::stringstream ss; |
5440 | |
|
5441 | 0 | bytes.assign(cp, cp + buf.getLength()); |
5442 | 0 | ss << "0x" << encode::encodeHex(bytes); |
5443 | 0 | relay_elem->set("options", ElementPtr(new StringElement(ss.str()))); |
5444 | 0 | } |
5445 | |
|
5446 | 0 | auto remote_id_it = relay.options_.find(D6O_REMOTE_ID); |
5447 | 0 | if (remote_id_it != relay.options_.end()) { |
5448 | 0 | OptionPtr remote_id = remote_id_it->second; |
5449 | 0 | if (remote_id) { |
5450 | 0 | std::vector<uint8_t> bytes = remote_id->toBinary(); |
5451 | 0 | if (bytes.size() > 0) { |
5452 | 0 | relay_elem->set("remote-id", |
5453 | 0 | Element::create(encode::encodeHex(bytes))); |
5454 | 0 | } |
5455 | 0 | } |
5456 | 0 | } |
5457 | |
|
5458 | 0 | auto relay_id_it = relay.options_.find(D6O_RELAY_ID); |
5459 | 0 | if (relay_id_it != relay.options_.end()) { |
5460 | 0 | OptionPtr relay_id = relay_id_it->second; |
5461 | 0 | if (relay_id) { |
5462 | 0 | std::vector<uint8_t> bytes = relay_id->toBinary(false); |
5463 | 0 | if (bytes.size() > 0) { |
5464 | 0 | relay_elem->set("relay-id", |
5465 | 0 | Element::create(encode::encodeHex(bytes))); |
5466 | 0 | } |
5467 | 0 | } |
5468 | 0 | } |
5469 | 0 | } |
5470 | |
|
5471 | 0 | extended_info->add(relay_elem); |
5472 | 0 | } |
5473 | | |
5474 | | // If extended info changed set the action to UPDATE. |
5475 | 0 | if (lease->updateUserContextISC("relay-info", extended_info)) { |
5476 | 0 | lease->extended_info_action_ = Lease6::ACTION_UPDATE; |
5477 | 0 | } |
5478 | 0 | } |
5479 | | |
5480 | | void |
5481 | | AllocEngine::setLeaseReusable(const Lease4Ptr& lease, |
5482 | 0 | const ClientContext4& ctx) const { |
5483 | | // Sanity. |
5484 | 0 | lease->reuseable_valid_lft_ = 0; |
5485 | 0 | const ConstSubnet4Ptr& subnet = ctx.subnet_; |
5486 | 0 | if (!subnet) { |
5487 | 0 | return; |
5488 | 0 | } |
5489 | 0 | if (lease->state_ != Lease::STATE_DEFAULT) { |
5490 | 0 | return; |
5491 | 0 | } |
5492 | | |
5493 | | // Always reuse infinite lifetime leases. |
5494 | 0 | if (lease->valid_lft_ == Lease::INFINITY_LFT) { |
5495 | 0 | lease->reuseable_valid_lft_ = Lease::INFINITY_LFT; |
5496 | 0 | return; |
5497 | 0 | } |
5498 | | |
5499 | | // Refuse time not going forward. |
5500 | 0 | if (lease->cltt_ < lease->current_cltt_) { |
5501 | 0 | return; |
5502 | 0 | } |
5503 | | |
5504 | 0 | uint32_t age = lease->cltt_ - lease->current_cltt_; |
5505 | | // Already expired. |
5506 | 0 | if (age >= lease->current_valid_lft_) { |
5507 | 0 | return; |
5508 | 0 | } |
5509 | | |
5510 | | // Try cache max age. |
5511 | 0 | uint32_t max_age = 0; |
5512 | 0 | if (!subnet->getCacheMaxAge().unspecified()) { |
5513 | 0 | max_age = subnet->getCacheMaxAge().get(); |
5514 | 0 | if ((max_age == 0) || (age > max_age)) { |
5515 | 0 | return; |
5516 | 0 | } |
5517 | 0 | } |
5518 | | |
5519 | | // Try cache threshold. |
5520 | 0 | if (!subnet->getCacheThreshold().unspecified()) { |
5521 | 0 | double threshold = subnet->getCacheThreshold().get(); |
5522 | 0 | if ((threshold <= 0.) || (threshold > 1.)) { |
5523 | 0 | return; |
5524 | 0 | } |
5525 | 0 | max_age = lease->valid_lft_ * threshold; |
5526 | 0 | if (age > max_age) { |
5527 | 0 | return; |
5528 | 0 | } |
5529 | 0 | } |
5530 | | |
5531 | | // No cache. |
5532 | 0 | if (max_age == 0) { |
5533 | 0 | return; |
5534 | 0 | } |
5535 | | |
5536 | | // Seems to be reusable. |
5537 | 0 | lease->reuseable_valid_lft_ = lease->current_valid_lft_ - age; |
5538 | 0 | } |
5539 | | |
5540 | | void |
5541 | | AllocEngine::setLeaseReusable(const Lease6Ptr& lease, |
5542 | | uint32_t current_preferred_lft, |
5543 | 0 | const ClientContext6& ctx) const { |
5544 | | // Sanity. |
5545 | 0 | lease->reuseable_valid_lft_ = 0; |
5546 | 0 | lease->reuseable_preferred_lft_ = 0; |
5547 | 0 | const ConstSubnet6Ptr& subnet = ctx.subnet_; |
5548 | 0 | if (!subnet) { |
5549 | 0 | return; |
5550 | 0 | } |
5551 | 0 | if (lease->state_ != Lease::STATE_DEFAULT) { |
5552 | 0 | return; |
5553 | 0 | } |
5554 | | |
5555 | | // Refuse time not going forward. |
5556 | 0 | if (lease->cltt_ < lease->current_cltt_) { |
5557 | 0 | return; |
5558 | 0 | } |
5559 | | |
5560 | 0 | uint32_t age = lease->cltt_ - lease->current_cltt_; |
5561 | | // Already expired. |
5562 | 0 | if (age >= lease->current_valid_lft_) { |
5563 | 0 | return; |
5564 | 0 | } |
5565 | | |
5566 | | // Try cache max age. |
5567 | 0 | uint32_t max_age = 0; |
5568 | 0 | if (!subnet->getCacheMaxAge().unspecified()) { |
5569 | 0 | max_age = subnet->getCacheMaxAge().get(); |
5570 | 0 | if ((max_age == 0) || (age > max_age)) { |
5571 | 0 | return; |
5572 | 0 | } |
5573 | 0 | } |
5574 | | |
5575 | | // Try cache threshold. |
5576 | 0 | if (!subnet->getCacheThreshold().unspecified()) { |
5577 | 0 | double threshold = subnet->getCacheThreshold().get(); |
5578 | 0 | if ((threshold <= 0.) || (threshold > 1.)) { |
5579 | 0 | return; |
5580 | 0 | } |
5581 | 0 | max_age = lease->valid_lft_ * threshold; |
5582 | 0 | if (age > max_age) { |
5583 | 0 | return; |
5584 | 0 | } |
5585 | 0 | } |
5586 | | |
5587 | | // No cache. |
5588 | 0 | if (max_age == 0) { |
5589 | 0 | return; |
5590 | 0 | } |
5591 | | |
5592 | | // Seems to be reusable. |
5593 | 0 | if ((current_preferred_lft == Lease::INFINITY_LFT) || |
5594 | 0 | (current_preferred_lft == 0)) { |
5595 | | // Keep these values. |
5596 | 0 | lease->reuseable_preferred_lft_ = current_preferred_lft; |
5597 | 0 | } else if (current_preferred_lft > age) { |
5598 | 0 | lease->reuseable_preferred_lft_ = current_preferred_lft - age; |
5599 | 0 | } else { |
5600 | | // Can be a misconfiguration so stay safe... |
5601 | 0 | return; |
5602 | 0 | } |
5603 | 0 | if (lease->current_valid_lft_ == Lease::INFINITY_LFT) { |
5604 | 0 | lease->reuseable_valid_lft_ = Lease::INFINITY_LFT; |
5605 | 0 | } else { |
5606 | 0 | lease->reuseable_valid_lft_ = lease->current_valid_lft_ - age; |
5607 | 0 | } |
5608 | 0 | } |
5609 | | |
5610 | | } // namespace dhcp |
5611 | | } // namespace isc |