Kea 2.7.4
dhcp4_srv.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2024 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#include <kea_version.h>
9
12#include <dhcp/dhcp4.h>
13#include <dhcp/duid.h>
14#include <dhcp/hwaddr.h>
15#include <dhcp/iface_mgr.h>
16#include <dhcp/libdhcp++.h>
18#include <dhcp/option_custom.h>
19#include <dhcp/option_int.h>
21#include <dhcp/option_vendor.h>
23#include <dhcp/option_string.h>
24#include <dhcp/pkt4.h>
25#include <dhcp/pkt4o6.h>
26#include <dhcp/pkt6.h>
29#include <dhcp4/dhcp4to6_ipc.h>
30#include <dhcp4/dhcp4_log.h>
31#include <dhcp4/dhcp4_srv.h>
33#include <dhcpsrv/cfgmgr.h>
35#include <dhcpsrv/cfg_iface.h>
39#include <dhcpsrv/fuzz.h>
41#include <dhcpsrv/lease_mgr.h>
46#include <dhcpsrv/subnet.h>
48#include <dhcpsrv/utils.h>
49#include <eval/evaluate.h>
50#include <eval/eval_messages.h>
52#include <hooks/hooks_log.h>
53#include <hooks/hooks_manager.h>
54#include <stats/stats_mgr.h>
55#include <util/encode/encode.h>
56#include <util/str.h>
57#include <log/logger.h>
60
61#include <boost/algorithm/string.hpp>
62#include <boost/foreach.hpp>
63#include <boost/range/adaptor/reversed.hpp>
64#include <boost/pointer_cast.hpp>
65#include <boost/shared_ptr.hpp>
66
67#include <functional>
68#include <iomanip>
69#include <set>
70#include <cstdlib>
71
72using namespace isc;
73using namespace isc::asiolink;
74using namespace isc::cryptolink;
75using namespace isc::data;
76using namespace isc::dhcp;
77using namespace isc::dhcp_ddns;
78using namespace isc::hooks;
79using namespace isc::log;
80using namespace isc::stats;
81using namespace isc::util;
82using namespace std;
83namespace ph = std::placeholders;
84
85namespace {
86
88struct Dhcp4Hooks {
89 int hook_index_buffer4_receive_;
90 int hook_index_pkt4_receive_;
91 int hook_index_subnet4_select_;
92 int hook_index_leases4_committed_;
93 int hook_index_lease4_release_;
94 int hook_index_pkt4_send_;
95 int hook_index_buffer4_send_;
96 int hook_index_lease4_decline_;
97 int hook_index_host4_identifier_;
98 int hook_index_ddns4_update_;
99 int hook_index_lease4_offer_;
100 int hook_index_lease4_server_decline_;
101
103 Dhcp4Hooks() {
104 hook_index_buffer4_receive_ = HooksManager::registerHook("buffer4_receive");
105 hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive");
106 hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
107 hook_index_leases4_committed_ = HooksManager::registerHook("leases4_committed");
108 hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
109 hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send");
110 hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send");
111 hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
112 hook_index_host4_identifier_ = HooksManager::registerHook("host4_identifier");
113 hook_index_ddns4_update_ = HooksManager::registerHook("ddns4_update");
114 hook_index_lease4_offer_ = HooksManager::registerHook("lease4_offer");
115 hook_index_lease4_server_decline_ = HooksManager::registerHook("lease4_server_decline");
116 }
117};
118
121std::set<std::string> dhcp4_statistics = {
122 "pkt4-received",
123 "pkt4-discover-received",
124 "pkt4-offer-received",
125 "pkt4-request-received",
126 "pkt4-ack-received",
127 "pkt4-nak-received",
128 "pkt4-release-received",
129 "pkt4-decline-received",
130 "pkt4-inform-received",
131 "pkt4-unknown-received",
132 "pkt4-sent",
133 "pkt4-offer-sent",
134 "pkt4-ack-sent",
135 "pkt4-nak-sent",
136 "pkt4-parse-failed",
137 "pkt4-receive-drop",
138 "v4-allocation-fail",
139 "v4-allocation-fail-shared-network",
140 "v4-allocation-fail-subnet",
141 "v4-allocation-fail-no-pools",
142 "v4-allocation-fail-classes",
143 "v4-reservation-conflicts",
144 "v4-lease-reuses",
145};
146
147} // end of anonymous namespace
148
149// Declare a Hooks object. As this is outside any function or method, it
150// will be instantiated (and the constructor run) when the module is loaded.
151// As a result, the hook indexes will be defined before any method in this
152// module is called.
153Dhcp4Hooks Hooks;
154
155namespace isc {
156namespace dhcp {
157
159 const Pkt4Ptr& query,
161 const Subnet4Ptr& subnet,
162 bool& drop)
163 : alloc_engine_(alloc_engine), query_(query), resp_(),
164 context_(context), ipv6_only_preferred_(false) {
165
166 if (!alloc_engine_) {
167 isc_throw(BadValue, "alloc_engine value must not be NULL"
168 " when creating an instance of the Dhcpv4Exchange");
169 }
170
171 if (!query_) {
172 isc_throw(BadValue, "query value must not be NULL when"
173 " creating an instance of the Dhcpv4Exchange");
174 }
175
176 // Reset the given context argument.
177 context.reset();
178
179 // Create response message.
180 initResponse();
181 // Select subnet for the query message.
182 context_->subnet_ = subnet;
183
184 // If subnet found, retrieve client identifier which will be needed
185 // for allocations and search for reservations associated with a
186 // subnet/shared network.
188 if (subnet && !context_->early_global_reservations_lookup_) {
189 OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
190 if (opt_clientid) {
191 context_->clientid_.reset(new ClientId(opt_clientid->getData()));
192 }
193 }
194
195 if (subnet) {
196 // Find static reservations if not disabled for our subnet.
197 if (subnet->getReservationsInSubnet() ||
198 subnet->getReservationsGlobal()) {
199 // Before we can check for static reservations, we need to prepare a set
200 // of identifiers to be used for this.
201 if (!context_->early_global_reservations_lookup_) {
202 setHostIdentifiers(context_);
203 }
204
205 // Check for static reservations.
206 alloc_engine->findReservation(*context_);
207
208 // Get shared network to see if it is set for a subnet.
209 subnet->getSharedNetwork(sn);
210 }
211 }
212
213 // Global host reservations are independent of a selected subnet. If the
214 // global reservations contain client classes we should use them in case
215 // they are meant to affect pool selection. Also, if the subnet does not
216 // belong to a shared network we can use the reserved client classes
217 // because there is no way our subnet could change. Such classes may
218 // affect selection of a pool within the selected subnet.
219 auto global_host = context_->globalHost();
220 auto current_host = context_->currentHost();
221 if ((!context_->early_global_reservations_lookup_ &&
222 global_host && !global_host->getClientClasses4().empty()) ||
223 (!sn && current_host && !current_host->getClientClasses4().empty())) {
224 // We have already evaluated client classes and some of them may
225 // be in conflict with the reserved classes. Suppose there are
226 // two classes defined in the server configuration: first_class
227 // and second_class and the test for the second_class it looks
228 // like this: "not member('first_class')". If the first_class
229 // initially evaluates to false, the second_class evaluates to
230 // true. If the first_class is now set within the hosts reservations
231 // and we don't remove the previously evaluated second_class we'd
232 // end up with both first_class and second_class evaluated to
233 // true. In order to avoid that, we have to remove the classes
234 // evaluated in the first pass and evaluate them again. As
235 // a result, the first_class set via the host reservation will
236 // replace the second_class because the second_class will this
237 // time evaluate to false as desired.
239 setReservedClientClasses(context_);
240 evaluateClasses(query, false);
241 }
242
243 // Set KNOWN builtin class if something was found, UNKNOWN if not.
244 if (!context_->hosts_.empty()) {
245 query->addClass("KNOWN");
247 .arg(query->getLabel())
248 .arg("KNOWN");
249 } else {
250 query->addClass("UNKNOWN");
252 .arg(query->getLabel())
253 .arg("UNKNOWN");
254 }
255
256 // Perform second pass of classification.
257 evaluateClasses(query, true);
258
259 const ClientClasses& classes = query_->getClasses();
261 .arg(query_->getLabel())
262 .arg(classes.toText());
263
264 // Check the DROP special class.
265 if (query_->inClass("DROP")) {
267 .arg(query_->getHWAddrLabel())
268 .arg(query_->toText());
269 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
270 static_cast<int64_t>(1));
271 drop = true;
272 }
273}
274
275void
277 uint8_t resp_type = 0;
278 switch (getQuery()->getType()) {
279 case DHCPDISCOVER:
280 resp_type = DHCPOFFER;
281 break;
282 case DHCPREQUEST:
283 case DHCPINFORM:
284 resp_type = DHCPACK;
285 break;
286 default:
287 ;
288 }
289 // Only create a response if one is required.
290 if (resp_type > 0) {
291 resp_.reset(new Pkt4(resp_type, getQuery()->getTransid()));
292 copyDefaultFields();
293 copyDefaultOptions();
294
295 if (getQuery()->isDhcp4o6()) {
297 }
298 }
299}
300
301void
303 Pkt4o6Ptr query = boost::dynamic_pointer_cast<Pkt4o6>(getQuery());
304 if (!query) {
305 return;
306 }
307 const Pkt6Ptr& query6 = query->getPkt6();
308 Pkt6Ptr resp6(new Pkt6(DHCPV6_DHCPV4_RESPONSE, query6->getTransid()));
309 // Don't add client-id or server-id
310 // But copy relay info
311 if (!query6->relay_info_.empty()) {
312 resp6->copyRelayInfo(query6);
313 }
314 // Copy interface, and remote address and port
315 resp6->setIface(query6->getIface());
316 resp6->setIndex(query6->getIndex());
317 resp6->setRemoteAddr(query6->getRemoteAddr());
318 resp6->setRemotePort(query6->getRemotePort());
319 resp_.reset(new Pkt4o6(resp_, resp6));
320}
321
322void
323Dhcpv4Exchange::copyDefaultFields() {
324 resp_->setIface(query_->getIface());
325 resp_->setIndex(query_->getIndex());
326
327 // explicitly set this to 0
328 resp_->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
329 // ciaddr is always 0, except for the Renew/Rebind state and for
330 // Inform when it may be set to the ciaddr sent by the client.
331 if (query_->getType() == DHCPINFORM) {
332 resp_->setCiaddr(query_->getCiaddr());
333 } else {
334 resp_->setCiaddr(IOAddress::IPV4_ZERO_ADDRESS());
335 }
336 resp_->setHops(query_->getHops());
337
338 // copy MAC address
339 resp_->setHWAddr(query_->getHWAddr());
340
341 // relay address
342 resp_->setGiaddr(query_->getGiaddr());
343
344 // If src/dest HW addresses are used by the packet filtering class
345 // we need to copy them as well. There is a need to check that the
346 // address being set is not-NULL because an attempt to set the NULL
347 // HW would result in exception. If these values are not set, the
348 // the default HW addresses (zeroed) should be generated by the
349 // packet filtering class when creating Ethernet header for
350 // outgoing packet.
351 HWAddrPtr src_hw_addr = query_->getLocalHWAddr();
352 if (src_hw_addr) {
353 resp_->setLocalHWAddr(src_hw_addr);
354 }
355 HWAddrPtr dst_hw_addr = query_->getRemoteHWAddr();
356 if (dst_hw_addr) {
357 resp_->setRemoteHWAddr(dst_hw_addr);
358 }
359
360 // Copy flags from the request to the response per RFC 2131
361 resp_->setFlags(query_->getFlags());
362}
363
364void
365Dhcpv4Exchange::copyDefaultOptions() {
366 // Let's copy client-id to response. See RFC6842.
367 // It is possible to disable RFC6842 to keep backward compatibility
368 bool echo = CfgMgr::instance().getCurrentCfg()->getEchoClientId();
369 OptionPtr client_id = query_->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
370 if (client_id && echo) {
371 resp_->addOption(client_id);
372 }
373
374 // RFC 3011 states about the Subnet Selection Option
375
376 // "Servers configured to support this option MUST return an
377 // identical copy of the option to any client that sends it,
378 // regardless of whether or not the client requests the option in
379 // a parameter request list. Clients using this option MUST
380 // discard DHCPOFFER or DHCPACK packets that do not contain this
381 // option."
382 OptionPtr subnet_sel = query_->getOption(DHO_SUBNET_SELECTION);
383 if (subnet_sel) {
384 resp_->addOption(subnet_sel);
385 }
386
387 // If this packet is relayed, we want to copy Relay Agent Info option
388 // when it is not empty.
389 OptionPtr rai = query_->getOption(DHO_DHCP_AGENT_OPTIONS);
390 if (!rai || (rai->len() <= Option::OPTION4_HDR_LEN)) {
391 return;
392 }
393 // Do not copy recovered stashed RAI.
394 ConstElementPtr sao = CfgMgr::instance().getCurrentCfg()->
395 getConfiguredGlobal(CfgGlobals::STASH_AGENT_OPTIONS);
396 if (sao && (sao->getType() == Element::boolean) &&
397 sao->boolValue() && query_->inClass("STASH_AGENT_OPTIONS")) {
398 return;
399 }
400 resp_->addOption(rai);
401}
402
403void
405 const ConstCfgHostOperationsPtr cfg =
406 CfgMgr::instance().getCurrentCfg()->getCfgHostOperations4();
407
408 // Collect host identifiers. The identifiers are stored in order of preference.
409 // The server will use them in that order to search for host reservations.
410 for (auto const& id_type : cfg->getIdentifierTypes()) {
411 switch (id_type) {
413 if (context->hwaddr_ && !context->hwaddr_->hwaddr_.empty()) {
414 context->addHostIdentifier(id_type, context->hwaddr_->hwaddr_);
415 }
416 break;
417
418 case Host::IDENT_DUID:
419 if (context->clientid_) {
420 const std::vector<uint8_t>& vec = context->clientid_->getClientId();
421 if (!vec.empty()) {
422 // Client identifier type = DUID? Client identifier holding a DUID
423 // comprises Type (1 byte), IAID (4 bytes), followed by the actual
424 // DUID. Thus, the minimal length is 6.
425 if ((vec[0] == CLIENT_ID_OPTION_TYPE_DUID) && (vec.size() > 5)) {
426 // Extract DUID, skip IAID.
427 context->addHostIdentifier(id_type,
428 std::vector<uint8_t>(vec.begin() + 5,
429 vec.end()));
430 }
431 }
432 }
433 break;
434
436 {
437 OptionPtr rai = context->query_->getOption(DHO_DHCP_AGENT_OPTIONS);
438 if (rai) {
439 OptionPtr circuit_id_opt = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID);
440 if (circuit_id_opt) {
441 const OptionBuffer& circuit_id_vec = circuit_id_opt->getData();
442 if (!circuit_id_vec.empty()) {
443 context->addHostIdentifier(id_type, circuit_id_vec);
444 }
445 }
446 }
447 }
448 break;
449
451 if (context->clientid_) {
452 const std::vector<uint8_t>& vec = context->clientid_->getClientId();
453 if (!vec.empty()) {
454 context->addHostIdentifier(id_type, vec);
455 }
456 }
457 break;
458 case Host::IDENT_FLEX:
459 {
460 if (!HooksManager::calloutsPresent(Hooks.hook_index_host4_identifier_)) {
461 break;
462 }
463
464 CalloutHandlePtr callout_handle = getCalloutHandle(context->query_);
465
467 std::vector<uint8_t> id;
468
469 // Use the RAII wrapper to make sure that the callout handle state is
470 // reset when this object goes out of scope. All hook points must do
471 // it to prevent possible circular dependency between the callout
472 // handle and its arguments.
473 ScopedCalloutHandleState callout_handle_state(callout_handle);
474
475 // Pass incoming packet as argument
476 callout_handle->setArgument("query4", context->query_);
477 callout_handle->setArgument("id_type", type);
478 callout_handle->setArgument("id_value", id);
479
480 // Call callouts
481 HooksManager::callCallouts(Hooks.hook_index_host4_identifier_,
482 *callout_handle);
483
484 callout_handle->getArgument("id_type", type);
485 callout_handle->getArgument("id_value", id);
486
487 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_CONTINUE) &&
488 !id.empty()) {
489
491 .arg(context->query_->getLabel())
492 .arg(Host::getIdentifierAsText(type, &id[0], id.size()));
493
494 context->addHostIdentifier(type, id);
495 }
496 break;
497 }
498 default:
499 ;
500 }
501 }
502}
503
504void
506 const ClientClassDictionaryPtr& dict =
507 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
508 const ClientClassDefListPtr& defs_ptr = dict->getClasses();
509 for (auto const& def : *defs_ptr) {
510 // Only remove evaluated classes. Other classes can be
511 // assigned via hooks libraries and we should not remove
512 // them because there is no way they can be added back.
513 if (def->getMatchExpr()) {
514 query->classes_.erase(def->getName());
515 }
516 }
517}
518
519void
521 if (context->currentHost() && context->query_) {
522 const ClientClasses& classes = context->currentHost()->getClientClasses4();
523 for (auto const& cclass : classes) {
524 context->query_->addClass(cclass);
525 }
526 }
527}
528
529void
531 if (context_->subnet_) {
532 SharedNetwork4Ptr shared_network;
533 context_->subnet_->getSharedNetwork(shared_network);
534 if (shared_network) {
535 ConstHostPtr host = context_->currentHost();
536 if (host && (host->getIPv4SubnetID() != SUBNET_ID_GLOBAL)) {
537 setReservedClientClasses(context_);
538 }
539 }
540 }
541}
542
543void
545 ConstHostPtr host = context_->currentHost();
546 // Nothing to do if host reservations not specified for this client.
547 if (host) {
548 if (!host->getNextServer().isV4Zero()) {
549 resp_->setSiaddr(host->getNextServer());
550 }
551
552 std::string sname = host->getServerHostname();
553 if (!sname.empty()) {
554 resp_->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
555 sname.size());
556 }
557
558 std::string bootfile = host->getBootFileName();
559 if (!bootfile.empty()) {
560 resp_->setFile(reinterpret_cast<const uint8_t*>(bootfile.c_str()),
561 bootfile.size());
562 }
563 }
564}
565
567 // Built-in vendor class processing
568 boost::shared_ptr<OptionString> vendor_class =
569 boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
570
571 if (!vendor_class) {
572 return;
573 }
574
575 pkt->addClass(Dhcpv4Srv::VENDOR_CLASS_PREFIX + vendor_class->getValue());
576}
577
579 // All packets belong to ALL.
580 pkt->addClass("ALL");
581
582 // First: built-in vendor class processing.
583 classifyByVendor(pkt);
584
585 // Run match expressions on classes not depending on KNOWN/UNKNOWN.
586 evaluateClasses(pkt, false);
587}
588
589void Dhcpv4Exchange::evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known) {
590 // Note getClientClassDictionary() cannot be null
591 const ClientClassDictionaryPtr& dict =
592 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
593 const ClientClassDefListPtr& defs_ptr = dict->getClasses();
594 for (auto const& it : *defs_ptr) {
595 // Note second cannot be null
596 const ExpressionPtr& expr_ptr = it->getMatchExpr();
597 // Nothing to do without an expression to evaluate
598 if (!expr_ptr) {
599 continue;
600 }
601 // Not the right time if only when required
602 if (it->getRequired()) {
603 continue;
604 }
605 // Not the right pass.
606 if (it->getDependOnKnown() != depend_on_known) {
607 continue;
608 }
609 it->test(pkt, expr_ptr);
610 }
611}
612
613const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
614
615Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port,
616 const bool use_bcast, const bool direct_response_desired)
617 : io_service_(new IOService()), server_port_(server_port),
618 client_port_(client_port), shutdown_(true),
619 alloc_engine_(), use_bcast_(use_bcast),
620 network_state_(new NetworkState()),
621 cb_control_(new CBControlDHCPv4()),
622 test_send_responses_to_source_(false) {
623
624 const char* env = std::getenv("KEA_TEST_SEND_RESPONSES_TO_SOURCE");
625 if (env) {
627 test_send_responses_to_source_ = true;
628 }
629
631 .arg(server_port);
632
633 try {
634 // Port 0 is used for testing purposes where we don't open broadcast
635 // capable sockets. So, set the packet filter handling direct traffic
636 // only if we are in non-test mode.
637 if (server_port) {
638 // First call to instance() will create IfaceMgr (it's a singleton)
639 // it may throw something if things go wrong.
640 // The 'true' value of the call to setMatchingPacketFilter imposes
641 // that IfaceMgr will try to use the mechanism to respond directly
642 // to the client which doesn't have address assigned. This capability
643 // may be lacking on some OSes, so there is no guarantee that server
644 // will be able to respond directly.
645 IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
646 }
647
648 // Instantiate allocation engine. The number of allocation attempts equal
649 // to zero indicates that the allocation engine will use the number of
650 // attempts depending on the pool size.
651 alloc_engine_.reset(new AllocEngine(0));
652
654
655 } catch (const std::exception &e) {
657 shutdown_ = true;
658 return;
659 }
660
661 // Initializing all observations with default value
663 shutdown_ = false;
664}
665
668
669 // Iterate over set of observed statistics
670 for (auto const& it : dhcp4_statistics) {
671 // Initialize them with default value 0
672 stats_mgr.setValue(it, static_cast<int64_t>(0));
673 }
674}
675
677 // Discard any parked packets
679
680 try {
681 stopD2();
682 } catch (const std::exception& ex) {
683 // Highly unlikely, but lets Report it but go on
685 }
686
687 try {
688 Dhcp4to6Ipc::instance().close();
689 } catch (const std::exception& ex) {
690 // Highly unlikely, but lets Report it but go on
692 }
693
694 IfaceMgr::instance().closeSockets();
695
696 // The lease manager was instantiated during DHCPv4Srv configuration,
697 // so we should clean up after ourselves.
699
700 // Explicitly unload hooks
703 auto names = HooksManager::getLibraryNames();
704 std::string msg;
705 if (!names.empty()) {
706 msg = names[0];
707 for (size_t i = 1; i < names.size(); ++i) {
708 msg += std::string(", ") + names[i];
709 }
710 }
712 }
713 IOServiceMgr::instance().clearIOServices();
714 io_service_->stopAndPoll();
715}
716
717void
722
724Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query, bool& drop,
725 bool sanity_only, bool allow_answer_park) {
726
727 // DHCPv4-over-DHCPv6 is a special (and complex) case
728 if (query->isDhcp4o6()) {
729 return (selectSubnet4o6(query, drop, sanity_only, allow_answer_park));
730 }
731
732 Subnet4Ptr subnet;
733
734 const SubnetSelector& selector = CfgSubnets4::initSelector(query);
735
736 CfgMgr& cfgmgr = CfgMgr::instance();
737 subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
738
739 // Let's execute all callouts registered for subnet4_select
740 // (skip callouts if the selectSubnet was called to do sanity checks only)
741 if (!sanity_only &&
742 HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
743 CalloutHandlePtr callout_handle = getCalloutHandle(query);
744
745 // Use the RAII wrapper to make sure that the callout handle state is
746 // reset when this object goes out of scope. All hook points must do
747 // it to prevent possible circular dependency between the callout
748 // handle and its arguments.
749 shared_ptr<ScopedCalloutHandleState> callout_handle_state(
750 std::make_shared<ScopedCalloutHandleState>(callout_handle));
751
752 // Enable copying options from the packet within hook library.
753 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
754
755 // Set new arguments
756 callout_handle->setArgument("query4", query);
757 callout_handle->setArgument("subnet4", subnet);
758 callout_handle->setArgument("subnet4collection",
759 cfgmgr.getCurrentCfg()->
760 getCfgSubnets4()->getAll());
761
762 auto const tpl(parkingLimitExceeded("subnet4_select"));
763 bool const exceeded(get<0>(tpl));
764 if (exceeded) {
765 uint32_t const limit(get<1>(tpl));
766 // We can't park it so we're going to throw it on the floor.
769 .arg(limit)
770 .arg(query->getLabel());
771 return (Subnet4Ptr());
772 }
773
774 // We proactively park the packet.
776 "subnet4_select", query, [this, query, allow_answer_park, callout_handle_state]() {
777 if (MultiThreadingMgr::instance().getMode()) {
778 boost::shared_ptr<function<void()>> callback(
779 boost::make_shared<function<void()>>(
780 [this, query, allow_answer_park]() mutable {
781 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
782 }));
783 callout_handle_state->on_completion_ = [callback]() {
784 MultiThreadingMgr::instance().getThreadPool().add(callback);
785 };
786 } else {
787 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
788 }
789 });
790
791 // Call user (and server-side) callouts
792 try {
793 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
794 *callout_handle);
795 } catch (...) {
796 // Make sure we don't orphan a parked packet.
797 HooksManager::drop("subnet4_select", query);
798 throw;
799 }
800
801 // Callouts parked the packet. Same as drop but callouts will resume
802 // processing or drop the packet later.
803 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
806 .arg(query->getLabel());
807 drop = true;
808 return (Subnet4Ptr());
809 } else {
810 HooksManager::drop("subnet4_select", query);
811 }
812
813 // Callouts decided to skip this step. This means that no subnet
814 // will be selected. Packet processing will continue, but it will
815 // be severely limited (i.e. only global options will be assigned)
816 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
819 .arg(query->getLabel());
820 return (Subnet4Ptr());
821 }
822
823 // Callouts decided to drop the packet. It is a superset of the
824 // skip case so no subnet will be selected.
825 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
828 .arg(query->getLabel());
829 drop = true;
830 return (Subnet4Ptr());
831 }
832
833 // Use whatever subnet was specified by the callout
834 callout_handle->getArgument("subnet4", subnet);
835 }
836
837 if (subnet) {
838 // Log at higher debug level that subnet has been found.
840 .arg(query->getLabel())
841 .arg(subnet->getID());
842 // Log detailed information about the selected subnet at the
843 // lower debug level.
845 .arg(query->getLabel())
846 .arg(subnet->toText());
847
848 } else {
851 .arg(query->getLabel());
852 }
853
854 return (subnet);
855}
856
858Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop,
859 bool sanity_only, bool allow_answer_park) {
860 Subnet4Ptr subnet;
861
862 SubnetSelector selector;
863 selector.ciaddr_ = query->getCiaddr();
864 selector.giaddr_ = query->getGiaddr();
865 selector.local_address_ = query->getLocalAddr();
866 selector.client_classes_ = query->classes_;
867 selector.iface_name_ = query->getIface();
868 // Mark it as DHCPv4-over-DHCPv6
869 selector.dhcp4o6_ = true;
870 // Now the DHCPv6 part
871 selector.remote_address_ = query->getRemoteAddr();
872 selector.first_relay_linkaddr_ = IOAddress("::");
873
874 // Handle a DHCPv6 relayed query
875 Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast<Pkt4o6>(query);
876 if (!query4o6) {
877 isc_throw(Unexpected, "Can't get DHCP4o6 message");
878 }
879 const Pkt6Ptr& query6 = query4o6->getPkt6();
880
881 // Initialize fields specific to relayed messages.
882 if (query6 && !query6->relay_info_.empty()) {
883 for (auto const& relay : boost::adaptors::reverse(query6->relay_info_)) {
884 if (!relay.linkaddr_.isV6Zero() &&
885 !relay.linkaddr_.isV6LinkLocal()) {
886 selector.first_relay_linkaddr_ = relay.linkaddr_;
887 break;
888 }
889 }
890 selector.interface_id_ =
891 query6->getAnyRelayOption(D6O_INTERFACE_ID,
893 }
894
895 // If the Subnet Selection option is present, extract its value.
896 OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
897 if (sbnsel) {
898 OptionCustomPtr oc = boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
899 if (oc) {
900 selector.option_select_ = oc->readAddress();
901 }
902 }
903
904 CfgMgr& cfgmgr = CfgMgr::instance();
905 subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet4o6(selector);
906
907 // Let's execute all callouts registered for subnet4_select.
908 // (skip callouts if the selectSubnet was called to do sanity checks only)
909 if (!sanity_only &&
910 HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
911 CalloutHandlePtr callout_handle = getCalloutHandle(query);
912
913 // Use the RAII wrapper to make sure that the callout handle state is
914 // reset when this object goes out of scope. All hook points must do
915 // it to prevent possible circular dependency between the callout
916 // handle and its arguments.
917 shared_ptr<ScopedCalloutHandleState> callout_handle_state(
918 std::make_shared<ScopedCalloutHandleState>(callout_handle));
919
920 // Enable copying options from the packet within hook library.
921 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
922
923 // Set new arguments
924 callout_handle->setArgument("query4", query);
925 callout_handle->setArgument("subnet4", subnet);
926 callout_handle->setArgument("subnet4collection",
927 cfgmgr.getCurrentCfg()->
928 getCfgSubnets4()->getAll());
929
930 auto const tpl(parkingLimitExceeded("subnet4_select"));
931 bool const exceeded(get<0>(tpl));
932 if (exceeded) {
933 uint32_t const limit(get<1>(tpl));
934 // We can't park it so we're going to throw it on the floor.
937 .arg(limit)
938 .arg(query->getLabel());
939 return (Subnet4Ptr());
940 }
941
942 // We proactively park the packet.
944 "subnet4_select", query, [this, query, allow_answer_park, callout_handle_state]() {
945 if (MultiThreadingMgr::instance().getMode()) {
946 boost::shared_ptr<function<void()>> callback(
947 boost::make_shared<function<void()>>(
948 [this, query, allow_answer_park]() mutable {
949 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
950 }));
951 callout_handle_state->on_completion_ = [callback]() {
952 MultiThreadingMgr::instance().getThreadPool().add(callback);
953 };
954 } else {
955 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
956 }
957 });
958
959 // Call user (and server-side) callouts
960 try {
961 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
962 *callout_handle);
963 } catch (...) {
964 // Make sure we don't orphan a parked packet.
965 HooksManager::drop("subnet4_select", query);
966 throw;
967 }
968
969 // Callouts parked the packet. Same as drop but callouts will resume
970 // processing or drop the packet later.
971 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
974 .arg(query->getLabel());
975 drop = true;
976 return (Subnet4Ptr());
977 } else {
978 HooksManager::drop("subnet4_select", query);
979 }
980
981 // Callouts decided to skip this step. This means that no subnet
982 // will be selected. Packet processing will continue, but it will
983 // be severely limited (i.e. only global options will be assigned)
984 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
987 .arg(query->getLabel());
988 return (Subnet4Ptr());
989 }
990
991 // Callouts decided to drop the packet. It is a superset of the
992 // skip case so no subnet will be selected.
993 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
996 .arg(query->getLabel());
997 drop = true;
998 return (Subnet4Ptr());
999 }
1000
1001 // Use whatever subnet was specified by the callout
1002 callout_handle->getArgument("subnet4", subnet);
1003 }
1004
1005 if (subnet) {
1006 // Log at higher debug level that subnet has been found.
1009 .arg(query->getLabel())
1010 .arg(subnet->getID());
1011 // Log detailed information about the selected subnet at the
1012 // lower debug level.
1015 .arg(query->getLabel())
1016 .arg(subnet->toText());
1017
1018 } else {
1021 .arg(query->getLabel());
1022 }
1023
1024 return (subnet);
1025}
1026
1027Pkt4Ptr
1029 return (IfaceMgr::instance().receive4(timeout));
1030}
1031
1032void
1034 IfaceMgr::instance().send(packet);
1035}
1036
1037void
1040 // Pointer to client's query.
1041 ctx->query_ = query;
1042
1043 // Hardware address.
1044 ctx->hwaddr_ = query->getHWAddr();
1045}
1046
1047bool
1050
1051 // First part of context initialization.
1052 initContext0(query, ctx);
1053
1054 // Get the early-global-reservations-lookup flag value.
1055 data::ConstElementPtr egrl = CfgMgr::instance().getCurrentCfg()->
1057 if (egrl) {
1058 ctx->early_global_reservations_lookup_ = egrl->boolValue();
1059 }
1060
1061 // Perform early global reservations lookup when wanted.
1062 if (ctx->early_global_reservations_lookup_) {
1063 // Retrieve retrieve client identifier.
1064 OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
1065 if (opt_clientid) {
1066 ctx->clientid_.reset(new ClientId(opt_clientid->getData()));
1067 }
1068
1069 // Get the host identifiers.
1071
1072 // Check for global host reservations.
1073 ConstHostPtr global_host = alloc_engine_->findGlobalReservation(*ctx);
1074
1075 if (global_host && !global_host->getClientClasses4().empty()) {
1076 // Remove dependent evaluated classes.
1078
1079 // Add classes from the global reservations.
1080 const ClientClasses& classes = global_host->getClientClasses4();
1081 for (auto const& cclass : classes) {
1082 query->addClass(cclass);
1083 }
1084
1085 // Evaluate classes before KNOWN.
1086 Dhcpv4Exchange::evaluateClasses(query, false);
1087 }
1088
1089 if (global_host) {
1090 // Add the KNOWN class;
1091 query->addClass("KNOWN");
1093 .arg(query->getLabel())
1094 .arg("KNOWN");
1095
1096 // Evaluate classes after KNOWN.
1098
1099 // Check the DROP special class.
1100 if (query->inClass("DROP")) {
1103 .arg(query->getHWAddrLabel())
1104 .arg(query->toText());
1105 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1106 static_cast<int64_t>(1));
1107 return (false);
1108 }
1109
1110 // Store the reservation.
1111 ctx->hosts_[SUBNET_ID_GLOBAL] = global_host;
1112 }
1113 }
1114
1115 return (true);
1116}
1117
1118int
1120#ifdef ENABLE_AFL
1121 // Set up structures needed for fuzzing.
1122 Fuzz fuzzer(4, server_port_);
1123 //
1124 // The next line is needed as a signature for AFL to recognize that we are
1125 // running persistent fuzzing. This has to be in the main image file.
1126 while (__AFL_LOOP(fuzzer.maxLoopCount())) {
1127 // Read from stdin and put the data read into an address/port on which
1128 // Kea is listening, read for Kea to read it via asynchronous I/O.
1129 fuzzer.transfer();
1130#else
1131 while (!shutdown_) {
1132#endif // ENABLE_AFL
1133 try {
1134 runOne();
1135 // Handle events registered by hooks using external IOService objects.
1136 IOServiceMgr::instance().pollIOServices();
1137 getIOService()->poll();
1138 config::HttpCommandMgr::instance().garbageCollectListeners();
1139 } catch (const std::exception& e) {
1140 // General catch-all exception that are not caught by more specific
1141 // catches. This one is for exceptions derived from std::exception.
1143 .arg(e.what());
1144 } catch (...) {
1145 // General catch-all exception that are not caught by more specific
1146 // catches. This one is for other exceptions, not derived from
1147 // std::exception.
1149 }
1150 }
1151
1152 // Stop everything before we change into single-threaded mode.
1154
1155 // destroying the thread pool
1156 MultiThreadingMgr::instance().apply(false, 0, 0);
1157
1158 return (getExitValue());
1159}
1160
1161void
1163 // client's message and server's response
1164 Pkt4Ptr query;
1165
1166 try {
1167 // Set select() timeout to 1s. This value should not be modified
1168 // because it is important that the select() returns control
1169 // frequently so as the IOService can be polled for ready handlers.
1170 uint32_t timeout = 1;
1171 query = receivePacket(timeout);
1172
1173 // Log if packet has arrived. We can't log the detailed information
1174 // about the DHCP message because it hasn't been unpacked/parsed
1175 // yet, and it can't be parsed at this point because hooks will
1176 // have to process it first. The only information available at this
1177 // point are: the interface, source address and destination addresses
1178 // and ports.
1179 if (query) {
1181 .arg(query->getRemoteAddr().toText())
1182 .arg(query->getRemotePort())
1183 .arg(query->getLocalAddr().toText())
1184 .arg(query->getLocalPort())
1185 .arg(query->getIface());
1186 }
1187
1188 // We used to log that the wait was interrupted, but this is no longer
1189 // the case. Our wait time is 1s now, so the lack of query packet more
1190 // likely means that nothing new appeared within a second, rather than
1191 // we were interrupted. And we don't want to print a message every
1192 // second.
1193
1194 } catch (const SignalInterruptOnSelect&) {
1195 // Packet reception interrupted because a signal has been received.
1196 // This is not an error because we might have received a SIGTERM,
1197 // SIGINT, SIGHUP or SIGCHLD which are handled by the server. For
1198 // signals that are not handled by the server we rely on the default
1199 // behavior of the system.
1201 } catch (const std::exception& e) {
1202 // Log all other errors.
1204 .arg(e.what());
1205 }
1206
1207 // Timeout may be reached or signal received, which breaks select()
1208 // with no reception occurred. No need to log anything here because
1209 // we have logged right after the call to receivePacket().
1210 if (!query) {
1211 return;
1212 }
1213
1214 // If the DHCP service has been globally disabled, drop the packet.
1215 if (!network_state_->isServiceEnabled()) {
1217 .arg(query->getLabel());
1218 return;
1219 } else {
1220 if (MultiThreadingMgr::instance().getMode()) {
1221 query->addPktEvent("mt_queued");
1222 typedef function<void()> CallBack;
1223 boost::shared_ptr<CallBack> call_back =
1224 boost::make_shared<CallBack>(std::bind(&Dhcpv4Srv::processPacketAndSendResponseNoThrow,
1225 this, query));
1226 if (!MultiThreadingMgr::instance().getThreadPool().add(call_back)) {
1228 }
1229 } else {
1231 }
1232 }
1233}
1234
1235void
1237 try {
1239 } catch (const std::exception& e) {
1241 .arg(query->getLabel())
1242 .arg(e.what());
1243 } catch (...) {
1245 }
1246}
1247
1248void
1250 Pkt4Ptr rsp = processPacket(query);
1251 if (!rsp) {
1252 return;
1253 }
1254
1255 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1256
1257 processPacketBufferSend(callout_handle, rsp);
1258}
1259
1260Pkt4Ptr
1261Dhcpv4Srv::processPacket(Pkt4Ptr query, bool allow_answer_park) {
1262 query->addPktEvent("process_started");
1263
1264 // All packets belong to ALL.
1265 query->addClass("ALL");
1266
1267 // Log reception of the packet. We need to increase it early, as any
1268 // failures in unpacking will cause the packet to be dropped. We
1269 // will increase type specific statistic further down the road.
1270 // See processStatsReceived().
1271 isc::stats::StatsMgr::instance().addValue("pkt4-received",
1272 static_cast<int64_t>(1));
1273
1274 bool skip_unpack = false;
1275
1276 // The packet has just been received so contains the uninterpreted wire
1277 // data; execute callouts registered for buffer4_receive.
1278 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_receive_)) {
1279 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1280
1281 // Use the RAII wrapper to make sure that the callout handle state is
1282 // reset when this object goes out of scope. All hook points must do
1283 // it to prevent possible circular dependency between the callout
1284 // handle and its arguments.
1285 ScopedCalloutHandleState callout_handle_state(callout_handle);
1286
1287 // Enable copying options from the packet within hook library.
1288 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1289
1290 // Pass incoming packet as argument
1291 callout_handle->setArgument("query4", query);
1292
1293 // Call callouts
1294 HooksManager::callCallouts(Hooks.hook_index_buffer4_receive_,
1295 *callout_handle);
1296
1297 // Callouts decided to drop the received packet.
1298 // The response (rsp) is null so the caller (runOne) will
1299 // immediately return too.
1300 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1303 .arg(query->getRemoteAddr().toText())
1304 .arg(query->getLocalAddr().toText())
1305 .arg(query->getIface());
1306 return (Pkt4Ptr());;
1307 }
1308
1309 // Callouts decided to skip the next processing step. The next
1310 // processing step would be to parse the packet, so skip at this
1311 // stage means that callouts did the parsing already, so server
1312 // should skip parsing.
1313 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1316 .arg(query->getRemoteAddr().toText())
1317 .arg(query->getLocalAddr().toText())
1318 .arg(query->getIface());
1319 skip_unpack = true;
1320 }
1321
1322 callout_handle->getArgument("query4", query);
1323 }
1324
1325 // Unpack the packet information unless the buffer4_receive callouts
1326 // indicated they did it
1327 if (!skip_unpack) {
1328 try {
1330 .arg(query->getRemoteAddr().toText())
1331 .arg(query->getLocalAddr().toText())
1332 .arg(query->getIface());
1333 query->unpack();
1334 } catch (const SkipRemainingOptionsError& e) {
1335 // An option failed to unpack but we are to attempt to process it
1336 // anyway. Log it and let's hope for the best.
1339 .arg(query->getLabel())
1340 .arg(e.what());
1341 } catch (const std::exception& e) {
1342 // Failed to parse the packet.
1344 .arg(query->getLabel())
1345 .arg(query->getRemoteAddr().toText())
1346 .arg(query->getLocalAddr().toText())
1347 .arg(query->getIface())
1348 .arg(e.what())
1349 .arg(query->getHWAddrLabel());
1350
1351 // Increase the statistics of parse failures and dropped packets.
1352 isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
1353 static_cast<int64_t>(1));
1354 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1355 static_cast<int64_t>(1));
1356 return (Pkt4Ptr());
1357 }
1358 }
1359
1360 // Classify can emit INFO logs so help to track the query.
1362 .arg(query->getLabel());
1363
1364 // Update statistics accordingly for received packet.
1365 processStatsReceived(query);
1366
1367 // Recover stashed RAI from client address lease.
1368 try {
1370 } catch (const std::exception&) {
1371 // Ignore exceptions.
1372 }
1373
1374 // Assign this packet to one or more classes if needed. We need to do
1375 // this before calling accept(), because getSubnet4() may need client
1376 // class information.
1377 classifyPacket(query);
1378
1379 // Now it is classified the deferred unpacking can be done.
1380 deferredUnpack(query);
1381
1382 // Check whether the message should be further processed or discarded.
1383 // There is no need to log anything here. This function logs by itself.
1384 if (!accept(query)) {
1385 // Increase the statistic of dropped packets.
1386 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1387 static_cast<int64_t>(1));
1388 return (Pkt4Ptr());
1389 }
1390
1391 // We have sanity checked (in accept() that the Message Type option
1392 // exists, so we can safely get it here.
1393 int type = query->getType();
1395 .arg(query->getLabel())
1396 .arg(query->getName())
1397 .arg(type)
1398 .arg(query->getRemoteAddr())
1399 .arg(query->getLocalAddr())
1400 .arg(query->getIface());
1402 .arg(query->getLabel())
1403 .arg(query->toText());
1404
1405 // Let's execute all callouts registered for pkt4_receive
1406 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_receive_)) {
1407 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1408
1409 // Use the RAII wrapper to make sure that the callout handle state is
1410 // reset when this object goes out of scope. All hook points must do
1411 // it to prevent possible circular dependency between the callout
1412 // handle and its arguments.
1413 ScopedCalloutHandleState callout_handle_state(callout_handle);
1414
1415 // Enable copying options from the packet within hook library.
1416 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1417
1418 // Pass incoming packet as argument
1419 callout_handle->setArgument("query4", query);
1420
1421 // Call callouts
1422 HooksManager::callCallouts(Hooks.hook_index_pkt4_receive_,
1423 *callout_handle);
1424
1425 // Callouts decided to skip the next processing step. The next
1426 // processing step would be to process the packet, so skip at this
1427 // stage means drop.
1428 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
1429 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
1432 .arg(query->getLabel());
1433 return (Pkt4Ptr());
1434 }
1435
1436 callout_handle->getArgument("query4", query);
1437 }
1438
1439 // Check the DROP special class.
1440 if (query->inClass("DROP")) {
1442 .arg(query->getHWAddrLabel())
1443 .arg(query->toText());
1444 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1445 static_cast<int64_t>(1));
1446 return (Pkt4Ptr());
1447 }
1448
1449 return (processDhcp4Query(query, allow_answer_park));
1450}
1451
1452void
1454 bool allow_answer_park) {
1455 try {
1456 Pkt4Ptr rsp = processDhcp4Query(query, allow_answer_park);
1457 if (!rsp) {
1458 return;
1459 }
1460
1461 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1462 processPacketBufferSend(callout_handle, rsp);
1463 } catch (const std::exception& e) {
1465 .arg(query->getLabel())
1466 .arg(e.what());
1467 } catch (...) {
1469 }
1470}
1471
1472Pkt4Ptr
1473Dhcpv4Srv::processDhcp4Query(Pkt4Ptr query, bool allow_answer_park) {
1474 // Create a client race avoidance RAII handler.
1475 ClientHandler client_handler;
1476
1477 // Check for lease modifier queries from the same client being processed.
1478 if (MultiThreadingMgr::instance().getMode() &&
1479 ((query->getType() == DHCPDISCOVER) ||
1480 (query->getType() == DHCPREQUEST) ||
1481 (query->getType() == DHCPRELEASE) ||
1482 (query->getType() == DHCPDECLINE))) {
1483 ContinuationPtr cont =
1485 this, query, allow_answer_park));
1486 if (!client_handler.tryLock(query, cont)) {
1487 return (Pkt4Ptr());
1488 }
1489 }
1490
1492 if (!earlyGHRLookup(query, ctx)) {
1493 return (Pkt4Ptr());
1494 }
1495
1496 try {
1497 sanityCheck(query);
1498 if ((query->getType() == DHCPDISCOVER) ||
1499 (query->getType() == DHCPREQUEST) ||
1500 (query->getType() == DHCPINFORM)) {
1501 bool drop = false;
1502 ctx->subnet_ = selectSubnet(query, drop, false, allow_answer_park);
1503 // Stop here if selectSubnet decided to drop the packet
1504 if (drop) {
1505 return (Pkt4Ptr());
1506 }
1507 }
1508 } catch (const std::exception& e) {
1509
1510 // Catch-all exception (we used to call only isc::Exception, but
1511 // std::exception could potentially be raised and if we don't catch
1512 // it here, it would be caught in main() and the process would
1513 // terminate). Just log the problem and ignore the packet.
1514 // (The problem is logged as a debug message because debug is
1515 // disabled by default - it prevents a DDOS attack based on the
1516 // sending of problem packets.)
1518 .arg(query->getLabel())
1519 .arg(e.what());
1520
1521 // Increase the statistic of dropped packets.
1522 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1523 static_cast<int64_t>(1));
1524 }
1525
1526 return (processLocalizedQuery4(ctx, allow_answer_park));
1527}
1528
1529void
1532 bool allow_answer_park) {
1533 try {
1534 Pkt4Ptr rsp = processLocalizedQuery4(ctx, allow_answer_park);
1535 if (!rsp) {
1536 return;
1537 }
1538
1539 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1540
1541 processPacketBufferSend(callout_handle, rsp);
1542 } catch (const std::exception& e) {
1544 .arg(query->getLabel())
1545 .arg(e.what());
1546 } catch (...) {
1548 }
1549}
1550
1551void
1553 bool allow_answer_park) {
1554 // Initialize context.
1556 initContext0(query, ctx);
1557
1558 // Subnet is cached in the callout context associated to the query.
1559 try {
1560 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1561 callout_handle->getContext("subnet4", ctx->subnet_);
1562 } catch (const Exception&) {
1563 // No subnet, leave it to null...
1564 }
1565
1566 processLocalizedQuery4AndSendResponse(query, ctx, allow_answer_park);
1567}
1568
1569Pkt4Ptr
1571 bool allow_answer_park) {
1572 if (!ctx) {
1573 isc_throw(Unexpected, "null context");
1574 }
1575 Pkt4Ptr query = ctx->query_;
1576 Pkt4Ptr rsp;
1577 try {
1578 switch (query->getType()) {
1579 case DHCPDISCOVER:
1580 rsp = processDiscover(query, ctx);
1581 break;
1582
1583 case DHCPREQUEST:
1584 // Note that REQUEST is used for many things in DHCPv4: for
1585 // requesting new leases, renewing existing ones and even
1586 // for rebinding.
1587 rsp = processRequest(query, ctx);
1588 break;
1589
1590 case DHCPRELEASE:
1591 processRelease(query, ctx);
1592 break;
1593
1594 case DHCPDECLINE:
1595 processDecline(query, ctx);
1596 break;
1597
1598 case DHCPINFORM:
1599 rsp = processInform(query, ctx);
1600 break;
1601
1602 default:
1603 // Only action is to output a message if debug is enabled,
1604 // and that is covered by the debug statement before the
1605 // "switch" statement.
1606 ;
1607 }
1608 } catch (const std::exception& e) {
1609
1610 // Catch-all exception (we used to call only isc::Exception, but
1611 // std::exception could potentially be raised and if we don't catch
1612 // it here, it would be caught in main() and the process would
1613 // terminate). Just log the problem and ignore the packet.
1614 // (The problem is logged as a debug message because debug is
1615 // disabled by default - it prevents a DDOS attack based on the
1616 // sending of problem packets.)
1618 .arg(query->getLabel())
1619 .arg(e.what());
1620
1621 // Increase the statistic of dropped packets.
1622 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1623 static_cast<int64_t>(1));
1624 }
1625
1626 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1627 if (ctx) {
1628 // leases4_committed and lease4_offer callouts are treated in the same way,
1629 // so prepare correct set of variables basing on the packet context.
1630 int hook_idx = Hooks.hook_index_leases4_committed_;
1631 std::string hook_label = "leases4_committed";
1635 if (ctx->fake_allocation_) {
1636 hook_idx = Hooks.hook_index_lease4_offer_;
1637 hook_label = "lease4_offer";
1638 pkt_park_msg = DHCP4_HOOK_LEASE4_OFFER_PARK;
1639 pkt_drop_msg = DHCP4_HOOK_LEASE4_OFFER_DROP;
1640 parking_lot_full_msg = DHCP4_HOOK_LEASE4_OFFER_PARKING_LOT_FULL;
1641 }
1642
1643 if (HooksManager::calloutsPresent(hook_idx)) {
1644 // The ScopedCalloutHandleState class which guarantees that the task
1645 // is added to the thread pool after the response is reset (if needed)
1646 // and CalloutHandle state is reset. In ST it does nothing.
1647 // A smart pointer is used to store the ScopedCalloutHandleState so that
1648 // a copy of the pointer is created by the lambda and only on the
1649 // destruction of the last reference the task is added.
1650 // In MT there are 2 cases:
1651 // 1. packet is unparked before current thread smart pointer to
1652 // ScopedCalloutHandleState is destroyed:
1653 // - the lambda uses the smart pointer to set the callout which adds the
1654 // task, but the task is added after ScopedCalloutHandleState is
1655 // destroyed, on the destruction of the last reference which is held
1656 // by the current thread.
1657 // 2. packet is unparked after the current thread smart pointer to
1658 // ScopedCalloutHandleState is destroyed:
1659 // - the current thread reference to ScopedCalloutHandleState is
1660 // destroyed, but the reference in the lambda keeps it alive until
1661 // the lambda is called and the last reference is released, at which
1662 // time the task is actually added.
1663 // Use the RAII wrapper to make sure that the callout handle state is
1664 // reset when this object goes out of scope. All hook points must do
1665 // it to prevent possible circular dependency between the callout
1666 // handle and its arguments.
1667 std::shared_ptr<ScopedCalloutHandleState> callout_handle_state =
1668 std::make_shared<ScopedCalloutHandleState>(callout_handle);
1669
1670 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1671
1672 // Also pass the corresponding query packet as argument
1673 callout_handle->setArgument("query4", query);
1674
1675 Lease4CollectionPtr new_leases(new Lease4Collection());
1676 // Filter out the new lease if it was reused so not committed.
1677 if (ctx->new_lease_ && (ctx->new_lease_->reuseable_valid_lft_ == 0)) {
1678 new_leases->push_back(ctx->new_lease_);
1679 }
1680 callout_handle->setArgument("leases4", new_leases);
1681
1682 if (ctx->fake_allocation_) {
1683 // Arguments required only for lease4_offer callout.
1684 callout_handle->setArgument("offer_lifetime", ctx->offer_lft_);
1685 callout_handle->setArgument("old_lease", ctx->old_lease_);
1686 } else {
1687 // Arguments required only for leases4_committed callout.
1688 Lease4CollectionPtr deleted_leases(new Lease4Collection());
1689 if (ctx->old_lease_) {
1690 if ((!ctx->new_lease_) || (ctx->new_lease_->addr_ != ctx->old_lease_->addr_)) {
1691 deleted_leases->push_back(ctx->old_lease_);
1692 }
1693 }
1694 callout_handle->setArgument("deleted_leases4", deleted_leases);
1695 }
1696
1697 if (allow_answer_park) {
1698 auto const tpl(parkingLimitExceeded(hook_label));
1699 bool const exceeded(get<0>(tpl));
1700 if (exceeded) {
1701 uint32_t const limit(get<1>(tpl));
1702 // We can't park it so we're going to throw it on the floor.
1703 LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING, parking_lot_full_msg)
1704 .arg(limit)
1705 .arg(query->getLabel());
1706 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1707 static_cast<int64_t>(1));
1708 return (Pkt4Ptr());
1709 }
1710
1711 // We proactively park the packet. We'll unpark it without invoking
1712 // the callback (i.e. drop) unless the callout status is set to
1713 // NEXT_STEP_PARK. Otherwise the callback we bind here will be
1714 // executed when the hook library unparks the packet.
1716 hook_label, query,
1717 [this, callout_handle, query, rsp, callout_handle_state, hook_idx, ctx]() mutable {
1718 if (hook_idx == Hooks.hook_index_lease4_offer_) {
1719 bool offer_address_in_use = false;
1720 try {
1721 callout_handle->getArgument("offer_address_in_use", offer_address_in_use);
1722 } catch (const NoSuchArgument& ex) {
1724 .arg(query->getLabel())
1725 .arg(ex.what());
1726 }
1727
1728 if (offer_address_in_use) {
1729 Lease4Ptr lease = ctx->new_lease_;
1730 bool lease_exists = (ctx->offer_lft_ > 0);
1731 if (MultiThreadingMgr::instance().getMode()) {
1732 typedef function<void()> CallBack;
1733 // We need to pass in the lease and flag as the callback handle state
1734 // gets reset prior to the invocation of the on_completion_ callback.
1735 boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(
1736 std::bind(&Dhcpv4Srv::serverDeclineNoThrow, this,
1737 callout_handle, query, lease, lease_exists));
1738 callout_handle_state->on_completion_ = [call_back]() {
1739 MultiThreadingMgr::instance().getThreadPool().add(call_back);
1740 };
1741 } else {
1742 serverDecline(callout_handle, query, lease, lease_exists);
1743 }
1744
1745 return;
1746 }
1747 }
1748
1749 // Send the response to the client.
1750 if (MultiThreadingMgr::instance().getMode()) {
1751 typedef function<void()> CallBack;
1752 boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(
1753 std::bind(&Dhcpv4Srv::sendResponseNoThrow, this, callout_handle,
1754 query, rsp, ctx->subnet_));
1755 callout_handle_state->on_completion_ = [call_back]() {
1756 MultiThreadingMgr::instance().getThreadPool().add(call_back);
1757 };
1758 } else {
1759 processPacketPktSend(callout_handle, query, rsp, ctx->subnet_);
1760 processPacketBufferSend(callout_handle, rsp);
1761 }
1762 });
1763 }
1764
1765 try {
1766 // Call all installed callouts
1767 HooksManager::callCallouts(hook_idx, *callout_handle);
1768 } catch (...) {
1769 // Make sure we don't orphan a parked packet.
1770 if (allow_answer_park) {
1771 HooksManager::drop(hook_label, query);
1772 }
1773
1774 throw;
1775 }
1776
1777 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) &&
1778 allow_answer_park) {
1779 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, pkt_park_msg)
1780 .arg(query->getLabel());
1781 // Since the hook library(ies) are going to do the unparking, then
1782 // reset the pointer to the response to indicate to the caller that
1783 // it should return, as the packet processing will continue via
1784 // the callback.
1785 rsp.reset();
1786 } else {
1787 // Drop the park job on the packet, it isn't needed.
1788 HooksManager::drop(hook_label, query);
1789 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1791 .arg(query->getLabel());
1792 rsp.reset();
1793 }
1794 }
1795 }
1796 }
1797
1798 // If we have a response prep it for shipment.
1799 if (rsp) {
1800 Subnet4Ptr subnet = (ctx ? ctx->subnet_ : Subnet4Ptr());
1801 processPacketPktSend(callout_handle, query, rsp, subnet);
1802 }
1803 return (rsp);
1804}
1805
1806void
1808 Pkt4Ptr& query, Pkt4Ptr& rsp, Subnet4Ptr& subnet) {
1809 try {
1810 processPacketPktSend(callout_handle, query, rsp, subnet);
1811 processPacketBufferSend(callout_handle, rsp);
1812 } catch (const std::exception& e) {
1814 .arg(query->getLabel())
1815 .arg(e.what());
1816 } catch (...) {
1818 }
1819}
1820
1821void
1823 Pkt4Ptr& query, Pkt4Ptr& rsp, Subnet4Ptr& subnet) {
1824 query->addPktEvent("process_completed");
1825 if (!rsp) {
1826 return;
1827 }
1828
1829 // Specifies if server should do the packing
1830 bool skip_pack = false;
1831
1832 // Execute all callouts registered for pkt4_send
1833 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) {
1834
1835 // Use the RAII wrapper to make sure that the callout handle state is
1836 // reset when this object goes out of scope. All hook points must do
1837 // it to prevent possible circular dependency between the callout
1838 // handle and its arguments.
1839 ScopedCalloutHandleState callout_handle_state(callout_handle);
1840
1841 // Enable copying options from the query and response packets within
1842 // hook library.
1843 ScopedEnableOptionsCopy<Pkt4> query_resp_options_copy(query, rsp);
1844
1845 // Pass incoming packet as argument
1846 callout_handle->setArgument("query4", query);
1847
1848 // Set our response
1849 callout_handle->setArgument("response4", rsp);
1850
1851 // Pass in the selected subnet.
1852 callout_handle->setArgument("subnet4", subnet);
1853
1854 // Call all installed callouts
1855 HooksManager::callCallouts(Hooks.hook_index_pkt4_send_,
1856 *callout_handle);
1857
1858 // Callouts decided to skip the next processing step. The next
1859 // processing step would be to pack the packet (create wire data).
1860 // That step will be skipped if any callout sets skip flag.
1861 // It essentially means that the callout already did packing,
1862 // so the server does not have to do it again.
1863 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1865 .arg(query->getLabel());
1866 skip_pack = true;
1867 }
1868
1870 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1872 .arg(rsp->getLabel());
1873 rsp.reset();
1874 return;
1875 }
1876 }
1877
1878 if (!skip_pack) {
1879 try {
1881 .arg(rsp->getLabel());
1882 rsp->pack();
1883 } catch (const std::exception& e) {
1885 .arg(rsp->getLabel())
1886 .arg(e.what());
1887 }
1888 }
1889}
1890
1891void
1893 Pkt4Ptr& rsp) {
1894 if (!rsp) {
1895 return;
1896 }
1897
1898 try {
1899 // Now all fields and options are constructed into output wire buffer.
1900 // Option objects modification does not make sense anymore. Hooks
1901 // can only manipulate wire buffer at this stage.
1902 // Let's execute all callouts registered for buffer4_send
1903 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
1904
1905 // Use the RAII wrapper to make sure that the callout handle state is
1906 // reset when this object goes out of scope. All hook points must do
1907 // it to prevent possible circular dependency between the callout
1908 // handle and its arguments.
1909 ScopedCalloutHandleState callout_handle_state(callout_handle);
1910
1911 // Enable copying options from the packet within hook library.
1912 ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
1913
1914 // Pass incoming packet as argument
1915 callout_handle->setArgument("response4", rsp);
1916
1917 // Call callouts
1918 HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
1919 *callout_handle);
1920
1921 // Callouts decided to skip the next processing step. The next
1922 // processing step would be to parse the packet, so skip at this
1923 // stage means drop.
1924 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
1925 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
1928 .arg(rsp->getLabel());
1929 return;
1930 }
1931
1932 callout_handle->getArgument("response4", rsp);
1933 }
1934
1936 .arg(rsp->getLabel())
1937 .arg(rsp->getName())
1938 .arg(static_cast<int>(rsp->getType()))
1939 .arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
1940 .arg(rsp->getLocalPort())
1941 .arg(rsp->getRemoteAddr())
1942 .arg(rsp->getRemotePort())
1943 .arg(rsp->getIface().empty() ? "to be determined from routing" :
1944 rsp->getIface());
1945
1948 .arg(rsp->getLabel())
1949 .arg(rsp->getName())
1950 .arg(static_cast<int>(rsp->getType()))
1951 .arg(rsp->toText());
1952 sendPacket(rsp);
1953
1954 // Update statistics accordingly for sent packet.
1955 processStatsSent(rsp);
1956
1957 } catch (const std::exception& e) {
1959 .arg(rsp->getLabel())
1960 .arg(e.what());
1961 }
1962}
1963
1964string
1966 if (!srvid) {
1967 isc_throw(BadValue, "NULL pointer passed to srvidToString()");
1968 }
1969 boost::shared_ptr<Option4AddrLst> generated =
1970 boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
1971 if (!srvid) {
1972 isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
1973 }
1974
1975 Option4AddrLst::AddressContainer addrs = generated->getAddresses();
1976 if (addrs.size() != 1) {
1977 isc_throw(BadValue, "Malformed option passed to srvidToString(). "
1978 << "Expected to contain a single IPv4 address.");
1979 }
1980
1981 return (addrs[0].toText());
1982}
1983
1984void
1986
1987 // Do not append generated server identifier if there is one appended already.
1988 // This is when explicitly configured server identifier option is present.
1989 if (ex.getResponse()->getOption(DHO_DHCP_SERVER_IDENTIFIER)) {
1990 return;
1991 }
1992
1993 // Use local address on which the packet has been received as a
1994 // server identifier. In some cases it may be a different address,
1995 // e.g. broadcast packet or DHCPv4o6 packet.
1996 IOAddress local_addr = ex.getQuery()->getLocalAddr();
1997 Pkt4Ptr query = ex.getQuery();
1998
1999 if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
2000 local_addr = IfaceMgr::instance().getSocket(query).addr_;
2001 }
2002
2004 local_addr));
2005 ex.getResponse()->addOption(opt_srvid);
2006}
2007
2008void
2010 CfgOptionList& co_list = ex.getCfgOptionList();
2011
2012 // Retrieve subnet.
2013 Subnet4Ptr subnet = ex.getContext()->subnet_;
2014 if (!subnet) {
2015 // All methods using the CfgOptionList object return soon when
2016 // there is no subnet so do the same
2017 return;
2018 }
2019
2020 // Firstly, host specific options.
2021 const ConstHostPtr& host = ex.getContext()->currentHost();
2022 if (host && !host->getCfgOption4()->empty()) {
2023 co_list.push_back(host->getCfgOption4());
2024 }
2025
2026 // Secondly, pool specific options.
2027 Pkt4Ptr resp = ex.getResponse();
2029 if (resp) {
2030 addr = resp->getYiaddr();
2031 }
2032 if (!addr.isV4Zero()) {
2033 PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
2034 if (pool && !pool->getCfgOption()->empty()) {
2035 co_list.push_back(pool->getCfgOption());
2036 }
2037 }
2038
2039 // Thirdly, subnet configured options.
2040 if (!subnet->getCfgOption()->empty()) {
2041 co_list.push_back(subnet->getCfgOption());
2042 }
2043
2044 // Fourthly, shared network specific options.
2045 SharedNetwork4Ptr network;
2046 subnet->getSharedNetwork(network);
2047 if (network && !network->getCfgOption()->empty()) {
2048 co_list.push_back(network->getCfgOption());
2049 }
2050
2051 // Each class in the incoming packet
2052 const ClientClasses& classes = ex.getQuery()->getClasses();
2053 for (auto const& cclass : classes) {
2054 // Find the client class definition for this class
2055 const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
2056 getClientClassDictionary()->findClass(cclass);
2057 if (!ccdef) {
2058 // Not found: the class is built-in or not configured
2059 if (!isClientClassBuiltIn(cclass)) {
2061 .arg(ex.getQuery()->getLabel())
2062 .arg(cclass);
2063 }
2064 // Skip it
2065 continue;
2066 }
2067
2068 if (ccdef->getCfgOption()->empty()) {
2069 // Skip classes which don't configure options
2070 continue;
2071 }
2072
2073 co_list.push_back(ccdef->getCfgOption());
2074 }
2075
2076 // Last global options
2077 if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
2078 co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
2079 }
2080}
2081
2082void
2084 // Get the subnet relevant for the client. We will need it
2085 // to get the options associated with it.
2086 Subnet4Ptr subnet = ex.getContext()->subnet_;
2087 // If we can't find the subnet for the client there is no way
2088 // to get the options to be sent to a client. We don't log an
2089 // error because it will be logged by the assignLease method
2090 // anyway.
2091 if (!subnet) {
2092 return;
2093 }
2094
2095 // Unlikely short cut
2096 const CfgOptionList& co_list = ex.getCfgOptionList();
2097 if (co_list.empty()) {
2098 return;
2099 }
2100
2101 Pkt4Ptr query = ex.getQuery();
2102 Pkt4Ptr resp = ex.getResponse();
2103 set<uint8_t> requested_opts;
2104
2105 // try to get the 'Parameter Request List' option which holds the
2106 // codes of requested options.
2107 OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
2109
2110 // Get the list of options that client requested.
2111 if (option_prl) {
2112 for (uint16_t code : option_prl->getValues()) {
2113 static_cast<void>(requested_opts.insert(code));
2114 }
2115 }
2116
2117 std::set<uint8_t> cancelled_opts;
2118
2119 // Iterate on the configured option list to add persistent and
2120 // cancelled options.
2121 for (auto const& copts : co_list) {
2122 const OptionContainerPtr& opts = copts->getAll(DHCP4_OPTION_SPACE);
2123 if (!opts) {
2124 continue;
2125 }
2126 // Get persistent options.
2127 const OptionContainerPersistIndex& pidx = opts->get<2>();
2128 const OptionContainerPersistRange& prange = pidx.equal_range(true);
2129 BOOST_FOREACH(auto const& desc, prange) {
2130 // Add the persistent option code to requested options.
2131 if (desc.option_) {
2132 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2133 static_cast<void>(requested_opts.insert(code));
2134 }
2135 }
2136 // Get cancelled options.
2137 const OptionContainerCancelIndex& cidx = opts->get<5>();
2138 const OptionContainerCancelRange& crange = cidx.equal_range(true);
2139 BOOST_FOREACH(auto const& desc, crange) {
2140 // Add the cancelled option code to cancelled options.
2141 if (desc.option_) {
2142 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2143 static_cast<void>(cancelled_opts.insert(code));
2144 }
2145 }
2146 }
2147
2148 // For each requested option code get the first instance of the option
2149 // to be returned to the client.
2150 for (uint8_t opt : requested_opts) {
2151 if (cancelled_opts.count(opt) > 0) {
2152 continue;
2153 }
2154 // Skip special cases: DHO_VIVSO_SUBOPTIONS.
2155 if (opt == DHO_VIVSO_SUBOPTIONS) {
2156 continue;
2157 }
2158 // Add nothing when it is already there.
2159 if (!resp->getOption(opt)) {
2160 // Iterate on the configured option list
2161 for (auto const& copts : co_list) {
2162 OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, opt);
2163 // Got it: add it and jump to the outer loop
2164 if (desc.option_) {
2165 resp->addOption(desc.option_);
2166 break;
2167 }
2168 }
2169 }
2170 }
2171
2172 // Special cases for vendor class and options which are identified
2173 // by the code/type and the vendor/enterprise id vs. the code/type only.
2174 if ((requested_opts.count(DHO_VIVCO_SUBOPTIONS) > 0) &&
2175 (cancelled_opts.count(DHO_VIVCO_SUBOPTIONS) == 0)) {
2176 // Keep vendor ids which are already in the response to insert
2177 // VIVCO options at most once per vendor.
2178 set<uint32_t> vendor_ids;
2179 // Get what already exists in the response.
2180 for (auto const& opt : resp->getOptions(DHO_VIVCO_SUBOPTIONS)) {
2181 OptionVendorClassPtr vendor_opts;
2182 vendor_opts = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
2183 if (vendor_opts) {
2184 uint32_t vendor_id = vendor_opts->getVendorId();
2185 static_cast<void>(vendor_ids.insert(vendor_id));
2186 }
2187 }
2188 // Iterate on the configured option list.
2189 for (auto const& copts : co_list) {
2190 for (auto const& desc : copts->getList(DHCP4_OPTION_SPACE,
2192 if (!desc.option_) {
2193 continue;
2194 }
2195 OptionVendorClassPtr vendor_opts =
2196 boost::dynamic_pointer_cast<OptionVendorClass>(desc.option_);
2197 if (!vendor_opts) {
2198 continue;
2199 }
2200 // Is the vendor id already in the response?
2201 uint32_t vendor_id = vendor_opts->getVendorId();
2202 if (vendor_ids.count(vendor_id) > 0) {
2203 continue;
2204 }
2205 // Got it: add it.
2206 resp->Pkt::addOption(desc.option_);
2207 static_cast<void>(vendor_ids.insert(vendor_id));
2208 }
2209 }
2210 }
2211
2212 if ((requested_opts.count(DHO_VIVSO_SUBOPTIONS) > 0) &&
2213 (cancelled_opts.count(DHO_VIVSO_SUBOPTIONS) == 0)) {
2214 // Keep vendor ids which are already in the response to insert
2215 // VIVSO options at most once per vendor.
2216 set<uint32_t> vendor_ids;
2217 // Get what already exists in the response.
2218 for (auto const& opt : resp->getOptions(DHO_VIVSO_SUBOPTIONS)) {
2219 OptionVendorPtr vendor_opts;
2220 vendor_opts = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
2221 if (vendor_opts) {
2222 uint32_t vendor_id = vendor_opts->getVendorId();
2223 static_cast<void>(vendor_ids.insert(vendor_id));
2224 }
2225 }
2226 // Iterate on the configured option list
2227 for (auto const& copts : co_list) {
2228 for (auto const& desc : copts->getList(DHCP4_OPTION_SPACE,
2230 if (!desc.option_) {
2231 continue;
2232 }
2233 OptionVendorPtr vendor_opts =
2234 boost::dynamic_pointer_cast<OptionVendor>(desc.option_);
2235 if (!vendor_opts) {
2236 continue;
2237 }
2238 // Is the vendor id already in the response?
2239 uint32_t vendor_id = vendor_opts->getVendorId();
2240 if (vendor_ids.count(vendor_id) > 0) {
2241 continue;
2242 }
2243 // Append a fresh vendor option as the next method should
2244 // add suboptions to it.
2245 vendor_opts.reset(new OptionVendor(Option::V4, vendor_id));
2246 resp->Pkt::addOption(vendor_opts);
2247 static_cast<void>(vendor_ids.insert(vendor_id));
2248 }
2249 }
2250 }
2251}
2252
2253void
2255 // Get the configured subnet suitable for the incoming packet.
2256 Subnet4Ptr subnet = ex.getContext()->subnet_;
2257
2258 const CfgOptionList& co_list = ex.getCfgOptionList();
2259
2260 // Leave if there is no subnet matching the incoming packet.
2261 // There is no need to log the error message here because
2262 // it will be logged in the assignLease() when it fails to
2263 // pick the suitable subnet. We don't want to duplicate
2264 // error messages in such case.
2265 //
2266 // Also, if there's no options to possibly assign, give up.
2267 if (!subnet || co_list.empty()) {
2268 return;
2269 }
2270
2271 Pkt4Ptr query = ex.getQuery();
2272 Pkt4Ptr resp = ex.getResponse();
2273 set<uint32_t> vendor_ids;
2274
2275 // The server could have provided the option using client classification or
2276 // hooks. If there're vendor info options in the response already, use them.
2277 map<uint32_t, OptionVendorPtr> vendor_rsps;
2278 for (auto const& opt : resp->getOptions(DHO_VIVSO_SUBOPTIONS)) {
2279 OptionVendorPtr vendor_rsp;
2280 vendor_rsp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
2281 if (vendor_rsp) {
2282 uint32_t vendor_id = vendor_rsp->getVendorId();
2283 vendor_rsps[vendor_id] = vendor_rsp;
2284 static_cast<void>(vendor_ids.insert(vendor_id));
2285 }
2286 }
2287
2288 // Next, try to get the vendor-id from the client packet's
2289 // vendor-specific information option (125).
2290 map<uint32_t, OptionVendorPtr> vendor_reqs;
2291 for (auto const& opt : query->getOptions(DHO_VIVSO_SUBOPTIONS)) {
2292 OptionVendorPtr vendor_req;
2293 vendor_req = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
2294 if (vendor_req) {
2295 uint32_t vendor_id = vendor_req->getVendorId();
2296 vendor_reqs[vendor_id] = vendor_req;
2297 static_cast<void>(vendor_ids.insert(vendor_id));
2298 }
2299 }
2300
2301 // Finally, try to get the vendor-id from the client packet's
2302 // vendor-specific class option (124).
2303 for (auto const& opt : query->getOptions(DHO_VIVCO_SUBOPTIONS)) {
2304 OptionVendorClassPtr vendor_class;
2305 vendor_class = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
2306 if (vendor_class) {
2307 uint32_t vendor_id = vendor_class->getVendorId();
2308 static_cast<void>(vendor_ids.insert(vendor_id));
2309 }
2310 }
2311
2312 // If there's no vendor option in either request or response, then there's no way
2313 // to figure out what the vendor-id values are and we give up.
2314 if (vendor_ids.empty()) {
2315 return;
2316 }
2317
2318 map<uint32_t, set<uint8_t> > requested_opts;
2319
2320 // Let's try to get ORO within that vendor-option.
2321 // This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
2322 // different policies.
2324 if (vendor_reqs.count(VENDOR_ID_CABLE_LABS) > 0) {
2325 OptionVendorPtr vendor_req = vendor_reqs[VENDOR_ID_CABLE_LABS];
2326 OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V4_ORO);
2327 if (oro_generic) {
2328 // Vendor ID 4491 makes Kea look at DOCSIS3_V4_OPTION_DEFINITIONS
2329 // when parsing options. Based on that, oro_generic will have been
2330 // created as an OptionUint8Array, but might not be for other
2331 // vendor IDs.
2332 oro = boost::dynamic_pointer_cast<OptionUint8Array>(oro_generic);
2333 }
2334 if (oro) {
2335 set<uint8_t> oro_req_opts;
2336 for (uint8_t code : oro->getValues()) {
2337 static_cast<void>(oro_req_opts.insert(code));
2338 }
2339 requested_opts[VENDOR_ID_CABLE_LABS] = oro_req_opts;
2340 }
2341 }
2342
2343 for (uint32_t vendor_id : vendor_ids) {
2344
2345 std::set<uint8_t> cancelled_opts;
2346
2347 // Iterate on the configured option list to add persistent and
2348 // cancelled options,
2349 for (auto const& copts : co_list) {
2350 const OptionContainerPtr& opts = copts->getAll(vendor_id);
2351 if (!opts) {
2352 continue;
2353 }
2354
2355 // Get persistent options.
2356 const OptionContainerPersistIndex& pidx = opts->get<2>();
2357 const OptionContainerPersistRange& prange = pidx.equal_range(true);
2358 BOOST_FOREACH(auto const& desc, prange) {
2359 // Add the persistent option code to requested options.
2360 if (desc.option_) {
2361 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2362 static_cast<void>(requested_opts[vendor_id].insert(code));
2363 }
2364 }
2365
2366 // Get cancelled options.
2367 const OptionContainerCancelIndex& cidx = opts->get<5>();
2368 const OptionContainerCancelRange& crange = cidx.equal_range(true);
2369 BOOST_FOREACH(auto const& desc, crange) {
2370 // Add the cancelled option code to cancelled options.
2371 if (desc.option_) {
2372 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2373 static_cast<void>(cancelled_opts.insert(code));
2374 }
2375 }
2376 }
2377
2378 // If there is nothing to add don't do anything with this vendor.
2379 // This will explicitly not echo back vendor options from the request
2380 // that either correspond to a vendor not known to Kea even if the
2381 // option encapsulates data or there are no persistent options
2382 // configured for this vendor so Kea does not send any option back.
2383 if (requested_opts[vendor_id].empty()) {
2384 continue;
2385 }
2386
2387
2388 // It's possible that vivso was inserted already by client class or
2389 // a hook. If that is so, let's use it.
2390 OptionVendorPtr vendor_rsp;
2391 if (vendor_rsps.count(vendor_id) > 0) {
2392 vendor_rsp = vendor_rsps[vendor_id];
2393 } else {
2394 vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id));
2395 }
2396
2397 // Get the list of options that client requested.
2398 bool added = false;
2399
2400 for (uint8_t opt : requested_opts[vendor_id]) {
2401 if (cancelled_opts.count(opt) > 0) {
2402 continue;
2403 }
2404 if (!vendor_rsp->getOption(opt)) {
2405 for (auto const& copts : co_list) {
2406 OptionDescriptor desc = copts->get(vendor_id, opt);
2407 if (desc.option_) {
2408 vendor_rsp->addOption(desc.option_);
2409 added = true;
2410 break;
2411 }
2412 }
2413 }
2414 }
2415
2416 // If we added some sub-options and the vendor opts option is not in
2417 // the response already, then add it.
2418 if (added && (vendor_rsps.count(vendor_id) == 0)) {
2419 resp->Pkt::addOption(vendor_rsp);
2420 }
2421 }
2422}
2423
2424void
2426 // Identify options that we always want to send to the
2427 // client (if they are configured).
2428 static const std::vector<uint16_t> required_options = {
2433
2434 // Get the subnet.
2435 Subnet4Ptr subnet = ex.getContext()->subnet_;
2436 if (!subnet) {
2437 return;
2438 }
2439
2440 // Unlikely short cut
2441 const CfgOptionList& co_list = ex.getCfgOptionList();
2442 if (co_list.empty()) {
2443 return;
2444 }
2445
2446 Pkt4Ptr resp = ex.getResponse();
2447
2448 // Try to find all 'required' options in the outgoing
2449 // message. Those that are not present will be added.
2450 for (auto const& required : required_options) {
2451 OptionPtr opt = resp->getOption(required);
2452 if (!opt) {
2453 // Check whether option has been configured.
2454 for (auto const& copts : co_list) {
2455 OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, required);
2456 if (desc.option_) {
2457 resp->addOption(desc.option_);
2458 break;
2459 }
2460 }
2461 }
2462 }
2463}
2464
2465void
2467 // It is possible that client has sent both Client FQDN and Hostname
2468 // option. In that the server should prefer Client FQDN option and
2469 // ignore the Hostname option.
2470 try {
2471 Pkt4Ptr query = ex.getQuery();
2472 Pkt4Ptr resp = ex.getResponse();
2473 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>
2474 (query->getOption(DHO_FQDN));
2475 if (fqdn) {
2477 .arg(query->getLabel());
2478 processClientFqdnOption(ex);
2479
2480 } else {
2483 .arg(query->getLabel());
2484 processHostnameOption(ex);
2485 }
2486
2487 // Based on the output option added to the response above, we figure out
2488 // the values for the hostname and dns flags to set in the context. These
2489 // will be used to populate the lease.
2490 std::string hostname;
2491 bool fqdn_fwd = false;
2492 bool fqdn_rev = false;
2493
2494 OptionStringPtr opt_hostname;
2495 fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
2496 if (fqdn) {
2497 hostname = fqdn->getDomainName();
2498 CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, fqdn_fwd, fqdn_rev);
2499 } else {
2500 opt_hostname = boost::dynamic_pointer_cast<OptionString>
2501 (resp->getOption(DHO_HOST_NAME));
2502
2503 if (opt_hostname) {
2504 hostname = opt_hostname->getValue();
2505 // DHO_HOST_NAME is string option which cannot be blank,
2506 // we use "." to know we should replace it with a fully
2507 // generated name. The local string variable needs to be
2508 // blank in logic below.
2509 if (hostname == ".") {
2510 hostname = "";
2511 }
2512
2515 if (ex.getContext()->getDdnsParams()->getEnableUpdates()) {
2516 fqdn_fwd = true;
2517 fqdn_rev = true;
2518 }
2519 }
2520 }
2521
2522 // Optionally, call a hook that may possibly override the decisions made
2523 // earlier.
2524 if (HooksManager::calloutsPresent(Hooks.hook_index_ddns4_update_)) {
2525 CalloutHandlePtr callout_handle = getCalloutHandle(query);
2526
2527 // Use the RAII wrapper to make sure that the callout handle state is
2528 // reset when this object goes out of scope. All hook points must do
2529 // it to prevent possible circular dependency between the callout
2530 // handle and its arguments.
2531 ScopedCalloutHandleState callout_handle_state(callout_handle);
2532
2533 // Setup the callout arguments.
2534 Subnet4Ptr subnet = ex.getContext()->subnet_;
2535 callout_handle->setArgument("query4", query);
2536 callout_handle->setArgument("response4", resp);
2537 callout_handle->setArgument("subnet4", subnet);
2538 callout_handle->setArgument("hostname", hostname);
2539 callout_handle->setArgument("fwd-update", fqdn_fwd);
2540 callout_handle->setArgument("rev-update", fqdn_rev);
2541 callout_handle->setArgument("ddns-params", ex.getContext()->getDdnsParams());
2542
2543 // Call callouts
2544 HooksManager::callCallouts(Hooks.hook_index_ddns4_update_, *callout_handle);
2545
2546 // Let's get the parameters returned by hook.
2547 string hook_hostname;
2548 bool hook_fqdn_fwd = false;
2549 bool hook_fqdn_rev = false;
2550 callout_handle->getArgument("hostname", hook_hostname);
2551 callout_handle->getArgument("fwd-update", hook_fqdn_fwd);
2552 callout_handle->getArgument("rev-update", hook_fqdn_rev);
2553
2554 // If there's anything changed by the hook, log it and then update
2555 // the parameters.
2556 if ((hostname != hook_hostname) || (fqdn_fwd != hook_fqdn_fwd) ||
2557 (fqdn_rev != hook_fqdn_rev)) {
2559 .arg(hostname).arg(hook_hostname).arg(fqdn_fwd).arg(hook_fqdn_fwd)
2560 .arg(fqdn_rev).arg(hook_fqdn_rev);
2561 hostname = hook_hostname;
2562 fqdn_fwd = hook_fqdn_fwd;
2563 fqdn_rev = hook_fqdn_rev;
2564
2565 // If there's an outbound host-name option in the response we
2566 // need to updated it with the new host name.
2567 OptionStringPtr hostname_opt = boost::dynamic_pointer_cast<OptionString>
2568 (resp->getOption(DHO_HOST_NAME));
2569 if (hostname_opt) {
2570 hostname_opt->setValue(hook_hostname);
2571 }
2572
2573 // If there's an outbound FQDN option in the response we need
2574 // to update it with the new host name.
2575 fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
2576 if (fqdn) {
2577 fqdn->setDomainName(hook_hostname, Option4ClientFqdn::FULL);
2578 // Hook disabled updates, Set flags back to client accordingly.
2579 fqdn->setFlag(Option4ClientFqdn::FLAG_S, 0);
2580 fqdn->setFlag(Option4ClientFqdn::FLAG_N, 1);
2581 }
2582 }
2583 }
2584
2585 // Update the context
2586 auto ctx = ex.getContext();
2587 ctx->fwd_dns_update_ = fqdn_fwd;
2588 ctx->rev_dns_update_ = fqdn_rev;
2589 ctx->hostname_ = hostname;
2590
2591 } catch (const Exception& e) {
2592 // In some rare cases it is possible that the client's name processing
2593 // fails. For example, the Hostname option may be malformed, or there
2594 // may be an error in the server's logic which would cause multiple
2595 // attempts to add the same option to the response message. This
2596 // error message aggregates all these errors so they can be diagnosed
2597 // from the log. We don't want to throw an exception here because,
2598 // it will impact the processing of the whole packet. We rather want
2599 // the processing to continue, even if the client's name is wrong.
2601 .arg(ex.getQuery()->getLabel())
2602 .arg(e.what());
2603 }
2604}
2605
2606void
2607Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) {
2608 // Obtain the FQDN option from the client's message.
2609 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
2610 Option4ClientFqdn>(ex.getQuery()->getOption(DHO_FQDN));
2611
2613 .arg(ex.getQuery()->getLabel())
2614 .arg(fqdn->toText());
2615
2616 // Create the DHCPv4 Client FQDN Option to be included in the server's
2617 // response to a client.
2618 Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
2619
2620 // Set the server S, N, and O flags based on client's flags and
2621 // current configuration.
2622 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
2623 d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp,
2624 *(ex.getContext()->getDdnsParams()));
2625 // Carry over the client's E flag.
2626 fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
2627 fqdn->getFlag(Option4ClientFqdn::FLAG_E));
2628
2629 if (ex.getContext()->currentHost() &&
2630 !ex.getContext()->currentHost()->getHostname().empty()) {
2631 fqdn_resp->setDomainName(d2_mgr.qualifyName(ex.getContext()->currentHost()->getHostname(),
2632 *(ex.getContext()->getDdnsParams()), true),
2634
2635 } else {
2636 // Adjust the domain name based on domain name value and type sent by the
2637 // client and current configuration.
2638 d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp,
2639 *(ex.getContext()->getDdnsParams()));
2640 }
2641
2642 // Add FQDN option to the response message. Note that, there may be some
2643 // cases when server may choose not to include the FQDN option in a
2644 // response to a client. In such cases, the FQDN should be removed from the
2645 // outgoing message. In theory we could cease to include the FQDN option
2646 // in this function until it is confirmed that it should be included.
2647 // However, we include it here for simplicity. Functions used to acquire
2648 // lease for a client will scan the response message for FQDN and if it
2649 // is found they will take necessary actions to store the FQDN information
2650 // in the lease database as well as to generate NameChangeRequests to DNS.
2651 // If we don't store the option in the response message, we will have to
2652 // propagate it in the different way to the functions which acquire the
2653 // lease. This would require modifications to the API of this class.
2655 .arg(ex.getQuery()->getLabel())
2656 .arg(fqdn_resp->toText());
2657 ex.getResponse()->addOption(fqdn_resp);
2658}
2659
2660void
2661Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
2662 // Fetch D2 configuration.
2663 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
2664
2665 // Obtain the Hostname option from the client's message.
2666 OptionStringPtr opt_hostname = boost::dynamic_pointer_cast<OptionString>
2667 (ex.getQuery()->getOption(DHO_HOST_NAME));
2668
2669 if (opt_hostname) {
2671 .arg(ex.getQuery()->getLabel())
2672 .arg(opt_hostname->getValue());
2673 }
2674
2675 AllocEngine::ClientContext4Ptr ctx = ex.getContext();
2676
2677 // Hostname reservations take precedence over any other configuration,
2678 // i.e. DDNS configuration. If we have a reserved hostname we should
2679 // use it and send it back.
2680 if (ctx->currentHost() && !ctx->currentHost()->getHostname().empty()) {
2681 // Qualify if there is a suffix configured.
2682 std::string hostname = d2_mgr.qualifyName(ctx->currentHost()->getHostname(),
2683 *(ex.getContext()->getDdnsParams()), false);
2684 // Convert it to lower case.
2685 boost::algorithm::to_lower(hostname);
2687 .arg(ex.getQuery()->getLabel())
2688 .arg(hostname);
2689
2690 // Add it to the response
2691 OptionStringPtr opt_hostname_resp(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
2692 ex.getResponse()->addOption(opt_hostname_resp);
2693
2694 // We're done here.
2695 return;
2696 }
2697
2698 // There is no reservation for this client however there is still a
2699 // possibility that we'll have to send hostname option to this client
2700 // if the client has included hostname option or the configuration of
2701 // the server requires that we send the option regardless.
2702 D2ClientConfig::ReplaceClientNameMode replace_name_mode =
2703 ex.getContext()->getDdnsParams()->getReplaceClientNameMode();
2704
2705 // If we don't have a hostname then either we'll supply it or do nothing.
2706 if (!opt_hostname) {
2707 // If we're configured to supply it then add it to the response.
2708 // Use the root domain to signal later on that we should replace it.
2709 if (replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
2710 replace_name_mode == D2ClientConfig::RCM_WHEN_NOT_PRESENT) {
2713 .arg(ex.getQuery()->getLabel());
2714 OptionStringPtr opt_hostname_resp(new OptionString(Option::V4,
2716 "."));
2717 ex.getResponse()->addOption(opt_hostname_resp);
2718 }
2719
2720 return;
2721 }
2722
2723 // Client sent us a hostname option so figure out what to do with it.
2725 .arg(ex.getQuery()->getLabel())
2726 .arg(opt_hostname->getValue());
2727
2728 std::string hostname = isc::util::str::trim(opt_hostname->getValue());
2729 unsigned int label_count;
2730
2731 try {
2732 // Parsing into labels can throw on malformed content so we're
2733 // going to explicitly catch that here.
2734 label_count = OptionDataTypeUtil::getLabelCount(hostname);
2735 } catch (const std::exception& exc) {
2737 .arg(ex.getQuery()->getLabel())
2738 .arg(exc.what());
2739 return;
2740 }
2741
2742 // The hostname option sent by the client should be at least 1 octet long.
2743 // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
2746 if (label_count == 0) {
2748 .arg(ex.getQuery()->getLabel());
2749 return;
2750 }
2751
2752 // Stores the value we eventually use, so we can send it back.
2753 OptionStringPtr opt_hostname_resp;
2754
2755 // The hostname option may be unqualified or fully qualified. The lab_count
2756 // holds the number of labels for the name. The number of 1 means that
2757 // there is only root label "." (even for unqualified names, as the
2758 // getLabelCount function treats each name as a fully qualified one).
2759 // By checking the number of labels present in the hostname we may infer
2760 // whether client has sent the fully qualified or unqualified hostname.
2761
2762 if ((replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
2763 replace_name_mode == D2ClientConfig::RCM_WHEN_PRESENT)
2764 || label_count < 2) {
2765 // Set to root domain to signal later on that we should replace it.
2766 // DHO_HOST_NAME is a string option which cannot be empty.
2774 opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, "."));
2775 } else {
2776 // Sanitize the name the client sent us, if we're configured to do so.
2778 ex.getContext()->getDdnsParams()->getHostnameSanitizer();
2779
2780 if (sanitizer) {
2781 hostname = sanitizer->scrub(hostname);
2782 }
2783
2784 // Convert hostname to lower case.
2785 boost::algorithm::to_lower(hostname);
2786
2787 if (label_count == 2) {
2788 // If there are two labels, it means that the client has specified
2789 // the unqualified name. We have to concatenate the unqualified name
2790 // with the domain name. The false value passed as a second argument
2791 // indicates that the trailing dot should not be appended to the
2792 // hostname. We don't want to append the trailing dot because
2793 // we don't know whether the hostname is partial or not and some
2794 // clients do not handle the hostnames with the trailing dot.
2795 opt_hostname_resp.reset(
2797 d2_mgr.qualifyName(hostname, *(ex.getContext()->getDdnsParams()),
2798 false)));
2799 } else {
2800 opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
2801 }
2802 }
2803
2805 .arg(ex.getQuery()->getLabel())
2806 .arg(opt_hostname_resp->getValue());
2807 ex.getResponse()->addOption(opt_hostname_resp);
2808}
2809
2810void
2812 const Lease4Ptr& old_lease,
2813 const DdnsParams& ddns_params) {
2814 if (!lease) {
2816 "NULL lease specified when creating NameChangeRequest");
2817 }
2818
2819 // Nothing to do if updates are not enabled.
2820 if (!ddns_params.getEnableUpdates()) {
2821 return;
2822 }
2823
2824 if (!old_lease || ddns_params.getUpdateOnRenew() || !lease->hasIdenticalFqdn(*old_lease)) {
2825 if (old_lease) {
2826 // Queue's up a remove of the old lease's DNS (if needed)
2827 queueNCR(CHG_REMOVE, old_lease);
2828 }
2829
2830 // We may need to generate the NameChangeRequest for the new lease. It
2831 // will be generated only if hostname is set and if forward or reverse
2832 // update has been requested.
2833 queueNCR(CHG_ADD, lease);
2834 }
2835}
2836
2837bool
2838Dhcpv4Srv::assignZero(Subnet4Ptr& subnet, const ClientClasses& client_classes) {
2839 Subnet4Ptr current_subnet = subnet;
2840 // Try subnets.
2841 while (current_subnet) {
2842 const ConstCfgOptionPtr& co = current_subnet->getCfgOption();
2843 if (!co->empty()) {
2844 OptionDescriptor desc = co->get(DHCP4_OPTION_SPACE,
2846 if (desc.option_) {
2847 subnet = current_subnet;
2848 return (true);
2849 }
2850 }
2851 current_subnet = current_subnet->getNextSubnet(subnet, client_classes);
2852 }
2853 // Try the shared network.
2854 SharedNetwork4Ptr network;
2855 subnet->getSharedNetwork(network);
2856 if (network) {
2857 const ConstCfgOptionPtr& co = network->getCfgOption();
2858 if (!co->empty()) {
2859 OptionDescriptor desc = co->get(DHCP4_OPTION_SPACE,
2861 if (desc.option_) {
2862 return (true);
2863 }
2864 }
2865 }
2866 return (false);
2867}
2868
2869void
2871 // Get the pointers to the query and the response messages.
2872 Pkt4Ptr query = ex.getQuery();
2873 Pkt4Ptr resp = ex.getResponse();
2874
2875 // Get the context.
2876 AllocEngine::ClientContext4Ptr ctx = ex.getContext();
2877
2878 // Subnet should have been already selected when the context was created.
2879 Subnet4Ptr subnet = ctx->subnet_;
2880
2881 // "Fake" allocation is processing of DISCOVER message. We pretend to do an
2882 // allocation, but we do not put the lease in the database. That is ok,
2883 // because we do not guarantee that the user will get that exact lease. If
2884 // the user selects this server to do actual allocation (i.e. sends REQUEST)
2885 // it should include this hint. That will help us during the actual lease
2886 // allocation.
2887 bool fake_allocation = (query->getType() == DHCPDISCOVER);
2888
2889 // Check if IPv6-Only Preferred was requested.
2890 OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
2892 if (option_prl) {
2893 auto const& requested_opts = option_prl->getValues();
2894 if ((std::find(requested_opts.cbegin(), requested_opts.cend(),
2895 DHO_V6_ONLY_PREFERRED) != requested_opts.cend()) &&
2896 assignZero(subnet, query->getClasses())) {
2897 ex.setIPv6OnlyPreferred(true);
2898 ctx->subnet_ = subnet;
2899 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
2900 if (!fake_allocation) {
2901 resp->setCiaddr(query->getCiaddr());
2902 }
2903 return;
2904 }
2905 }
2906
2907 // Get the server identifier. It will be used to determine the state
2908 // of the client.
2909 OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
2910 OptionCustom>(query->getOption(DHO_DHCP_SERVER_IDENTIFIER));
2911
2912 // Check if the client has sent a requested IP address option or
2913 // ciaddr.
2914 OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
2915 OptionCustom>(query->getOption(DHO_DHCP_REQUESTED_ADDRESS));
2917 if (opt_requested_address) {
2918 hint = opt_requested_address->readAddress();
2919
2920 } else if (!query->getCiaddr().isV4Zero()) {
2921 hint = query->getCiaddr();
2922
2923 }
2924
2925 // This flag controls whether or not the server should respond to the clients
2926 // in the INIT-REBOOT state. We will initialize it to a configured value only
2927 // when the client is in that state.
2928 auto authoritative = false;
2929
2930 // If there is no server id and there is a Requested IP Address option
2931 // the client is in the INIT-REBOOT state in which the server has to
2932 // determine whether the client's notion of the address is correct
2933 // and whether the client is known, i.e., has a lease.
2934 auto init_reboot = (!fake_allocation && !opt_serverid && opt_requested_address);
2935 if (init_reboot) {
2937 .arg(query->getLabel())
2938 .arg(hint.toText());
2939
2940 // Find the authoritative flag configuration.
2941 if (subnet) {
2942 authoritative = subnet->getAuthoritative();
2943 } else {
2944 // If there is no subnet, use the global value.
2945 auto flag = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()->
2947 if (flag && (flag->getType() == data::Element::boolean)) {
2948 authoritative = flag->boolValue();
2949 }
2950 }
2951 } else if (fake_allocation) {
2953 .arg(query->getLabel())
2954 .arg(hint != IOAddress::IPV4_ZERO_ADDRESS() ? hint.toText() : "(no hint)");
2955 } else {
2957 .arg(query->getLabel())
2958 .arg(hint != IOAddress::IPV4_ZERO_ADDRESS() ? hint.toText() : "(no hint)");
2959 }
2960
2961 // If there is no subnet configuration for that client we ignore the
2962 // request from the INIT-REBOOT client if we're not authoritative, because
2963 // we don't know whether the network configuration is correct for this
2964 // client. We return DHCPNAK if we're authoritative, though.
2965 if (!subnet && (!init_reboot || authoritative)) {
2966 // This particular client is out of luck today. We do not have
2967 // information about the subnet he is connected to. This likely means
2968 // misconfiguration of the server (or some relays).
2969
2970 // Perhaps this should be logged on some higher level?
2972 .arg(query->getLabel())
2973 .arg(query->getRemoteAddr().toText())
2974 .arg(query->getName());
2975 resp->setType(DHCPNAK);
2976 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
2977 return;
2978 }
2979
2980 HWAddrPtr hwaddr = query->getHWAddr();
2981
2982 Subnet4Ptr original_subnet = subnet;
2983
2984 // Get client-id. It is not mandatory in DHCPv4.
2985 ClientIdPtr client_id = ex.getContext()->clientid_;
2986
2987 // In the INIT-REBOOT state, a client remembering its previously assigned
2988 // address is trying to confirm whether or not this address is still usable.
2989 if (init_reboot) {
2990 Lease4Ptr lease;
2991
2992 auto const& classes = query->getClasses();
2993
2994 // We used to issue a separate query (two actually: one for client-id
2995 // and another one for hw-addr for) each subnet in the shared network.
2996 // That was horribly inefficient if the client didn't have any lease
2997 // (or there were many subnets and the client happened to be in one
2998 // of the last subnets).
2999 //
3000 // We now issue at most two queries: get all the leases for specific
3001 // client-id and then get all leases for specific hw-address.
3002 if (original_subnet && client_id) {
3003
3004 // Get all the leases for this client-id
3005 Lease4Collection leases_client_id = LeaseMgrFactory::instance().getLease4(*client_id);
3006 if (!leases_client_id.empty()) {
3007 Subnet4Ptr s = original_subnet;
3008
3009 // Among those returned try to find a lease that belongs to
3010 // current shared network.
3011 while (s) {
3012 for (auto const& l : leases_client_id) {
3013 if (l->subnet_id_ == s->getID()) {
3014 lease = l;
3015 break;
3016 }
3017 }
3018
3019 if (lease) {
3020 break;
3021
3022 } else {
3023 s = s->getNextSubnet(original_subnet, classes);
3024 }
3025 }
3026 }
3027 }
3028
3029 // If we haven't found a lease yet, try again by hardware-address.
3030 // The logic is the same.
3031 if (original_subnet && !lease && hwaddr) {
3032
3033 // Get all leases for this particular hw-address.
3034 Lease4Collection leases_hwaddr = LeaseMgrFactory::instance().getLease4(*hwaddr);
3035 if (!leases_hwaddr.empty()) {
3036 Subnet4Ptr s = original_subnet;
3037
3038 // Pick one that belongs to a subnet in this shared network.
3039 while (s) {
3040 for (auto const& l : leases_hwaddr) {
3041 if (l->subnet_id_ == s->getID()) {
3042 lease = l;
3043 break;
3044 }
3045 }
3046
3047 if (lease) {
3048 break;
3049
3050 } else {
3051 s = s->getNextSubnet(original_subnet, classes);
3052 }
3053 }
3054 }
3055 }
3056
3057 // Check the first error case: unknown client. We check this before
3058 // validating the address sent because we don't want to respond if
3059 // we don't know this client, except if we're authoritative.
3060 bool known_client = lease && lease->belongsToClient(hwaddr, client_id);
3061 if (!authoritative && !known_client) {
3064 .arg(query->getLabel())
3065 .arg(hint.toText());
3066
3067 ex.deleteResponse();
3068 return;
3069 }
3070
3071 // If we know this client, check if his notion of the IP address is
3072 // correct, if we don't know him, check if we are authoritative.
3073 if ((known_client && (lease->addr_ != hint)) ||
3074 (!known_client && authoritative) ||
3075 (!original_subnet)) {
3078 .arg(query->getLabel())
3079 .arg(hint.toText());
3080
3081 resp->setType(DHCPNAK);
3082 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
3083 return;
3084 }
3085 }
3086
3087 CalloutHandlePtr callout_handle = getCalloutHandle(query);
3088
3089 // We need to set these values in the context as they haven't been set yet.
3090 ctx->requested_address_ = hint;
3091 ctx->fake_allocation_ = fake_allocation;
3092 ctx->callout_handle_ = callout_handle;
3093
3094 // If client query contains an FQDN or Hostname option, server
3095 // should respond to the client with the appropriate FQDN or Hostname
3096 // option to indicate if it takes responsibility for the DNS updates.
3097 // This is also the source for the hostname and dns flags that are
3098 // initially added to the lease. In most cases, this information is
3099 // good now. If we end up changing subnets in allocation we'll have to
3100 // do it again and then update the lease.
3102
3103 // Get a lease.
3104 Lease4Ptr lease = alloc_engine_->allocateLease4(*ctx);
3105
3106 // Tracks whether or not the client name (FQDN or host) has changed since
3107 // the lease was allocated.
3108 bool client_name_changed = false;
3109
3110 // Subnet may be modified by the allocation engine, if the initial subnet
3111 // belongs to a shared network.
3112 if (subnet && ctx->subnet_ && subnet->getID() != ctx->subnet_->getID()) {
3113 SharedNetwork4Ptr network;
3114 subnet->getSharedNetwork(network);
3116 .arg(query->getLabel())
3117 .arg(subnet->toText())
3118 .arg(ctx->subnet_->toText())
3119 .arg(network ? network->getName() : "<no network?>");
3120
3121 subnet = ctx->subnet_;
3122
3123 if (lease) {
3124 // We changed subnets and that means DDNS parameters might be different
3125 // so we need to rerun client name processing logic. Arguably we could
3126 // compare DDNS parameters for both subnets and then decide if we need
3127 // to rerun the name logic, but that's not likely to be any faster than
3128 // just re-running the name logic. @todo When inherited parameter
3129 // performance is improved this argument could be revisited.
3130 // Another case is the new subnet has a reserved hostname.
3131
3132 // First, we need to remove the prior values from the response and reset
3133 // those in context, to give processClientName a clean slate.
3134 resp->delOption(DHO_FQDN);
3135 resp->delOption(DHO_HOST_NAME);
3136 ctx->hostname_ = "";
3137 ctx->fwd_dns_update_ = false;
3138 ctx->rev_dns_update_ = false;
3139
3140 // Regenerate the name and dns flags.
3142
3143 // If the results are different from the values already on the
3144 // lease, flag it so the lease gets updated down below.
3145 if ((lease->hostname_ != ctx->hostname_) ||
3146 (lease->fqdn_fwd_ != ctx->fwd_dns_update_) ||
3147 (lease->fqdn_rev_ != ctx->rev_dns_update_)) {
3148 lease->hostname_ = ctx->hostname_;
3149 lease->fqdn_fwd_ = ctx->fwd_dns_update_;
3150 lease->fqdn_rev_ = ctx->rev_dns_update_;
3151 client_name_changed = true;
3152 }
3153 }
3154 }
3155
3156 if (lease) {
3157 // We have a lease! Let's set it in the packet and send it back to
3158 // the client.
3159 if (fake_allocation) {
3161 .arg(query->getLabel())
3162 .arg(lease->addr_.toText());
3163 } else {
3165 .arg(query->getLabel())
3166 .arg(lease->addr_.toText())
3167 .arg(Lease::lifetimeToText(lease->valid_lft_));
3168 }
3169
3170 // We're logging this here, because this is the place where we know
3171 // which subnet has been actually used for allocation. If the
3172 // client identifier matching is disabled, we want to make sure that
3173 // the user is notified.
3174 if (!ctx->subnet_->getMatchClientId()) {
3176 .arg(ctx->query_->getLabel())
3177 .arg(ctx->subnet_->getID());
3178 }
3179
3180 resp->setYiaddr(lease->addr_);
3181
3186 if (!fake_allocation) {
3187 // If this is a renewing client it will set a ciaddr which the
3188 // server may include in the response. If this is a new allocation
3189 // the client will set ciaddr to 0 and this will also be propagated
3190 // to the server's resp.
3191 resp->setCiaddr(query->getCiaddr());
3192 }
3193
3194 // We may need to update FQDN or hostname if the server is to generate
3195 // a new name from the allocated IP address or if the allocation engine
3196 // switched to a different subnet within a shared network.
3197 postAllocateNameUpdate(ctx, lease, query, resp, client_name_changed);
3198
3199 // Reuse the lease if possible.
3200 if (lease->reuseable_valid_lft_ > 0) {
3201 lease->valid_lft_ = lease->reuseable_valid_lft_;
3203 .arg(query->getLabel())
3204 .arg(lease->addr_.toText())
3205 .arg(Lease::lifetimeToText(lease->valid_lft_));
3206
3207 // Increment the reuse statistics.
3208 StatsMgr::instance().addValue("v4-lease-reuses", int64_t(1));
3209 StatsMgr::instance().addValue(StatsMgr::generateName("subnet", lease->subnet_id_,
3210 "v4-lease-reuses"),
3211 int64_t(1));
3212 }
3213
3214 // IP Address Lease time (type 51)
3215 // If we're not allocating on discover then we just sent the lifetime on the lease.
3216 // Otherwise (i.e. offer_lft > 0), the lease's lifetime has been set to offer_lft but
3217 // we want to send the client the proper valid lifetime so we have to fetch it.
3218 auto send_lft = (ctx->offer_lft_ ? AllocEngine::getValidLft(*ctx) : lease->valid_lft_);
3220
3221 resp->addOption(opt);
3222
3223 // Subnet mask (type 1)
3224 resp->addOption(getNetmaskOption(subnet));
3225
3226 // Set T1 and T2 per configuration.
3227 setTeeTimes(lease, subnet, resp);
3228
3229 // Create NameChangeRequests if this is a real allocation.
3230 if (!fake_allocation) {
3231 try {
3232 createNameChangeRequests(lease, ctx->old_lease_,
3233 *ex.getContext()->getDdnsParams());
3234 } catch (const Exception& ex) {
3236 .arg(query->getLabel())
3237 .arg(ex.what());
3238 }
3239 }
3240
3241 } else {
3242 // Allocation engine did not allocate a lease. The engine logged
3243 // cause of that failure.
3244 if (ctx->unknown_requested_addr_) {
3245 Subnet4Ptr s = original_subnet;
3246 // Address might have been rejected via class guard (i.e. not
3247 // allowed for this client). We need to determine if we truly
3248 // do not know about the address or whether this client just
3249 // isn't allowed to have that address. We should only DHCPNAK
3250 // For the latter.
3251 while (s) {
3252 if (s->inPool(Lease::TYPE_V4, hint)) {
3253 break;
3254 }
3255
3256 s = s->getNextSubnet(original_subnet);
3257 }
3258
3259 // If we didn't find a subnet, it's not an address we know about
3260 // so we drop the DHCPNAK.
3261 if (!s) {
3264 .arg(query->getLabel())
3265 .arg(query->getCiaddr().toText())
3266 .arg(opt_requested_address ?
3267 opt_requested_address->readAddress().toText() : "(no address)");
3268 ex.deleteResponse();
3269 return;
3270 }
3271 }
3272
3275 .arg(query->getLabel())
3276 .arg(query->getCiaddr().toText())
3277 .arg(opt_requested_address ?
3278 opt_requested_address->readAddress().toText() : "(no address)");
3279
3280 resp->setType(DHCPNAK);
3281 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
3282
3283 resp->delOption(DHO_FQDN);
3284 resp->delOption(DHO_HOST_NAME);
3285 }
3286}
3287
3288void
3290 const Pkt4Ptr& query, const Pkt4Ptr& resp, bool client_name_changed) {
3291 // We may need to update FQDN or hostname if the server is to generate
3292 // new name from the allocated IP address or if the allocation engine
3293 // has switched to a different subnet within a shared network. Get
3294 // FQDN and hostname options from the response.
3295 OptionStringPtr opt_hostname;
3296 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
3297 Option4ClientFqdn>(resp->getOption(DHO_FQDN));
3298 if (!fqdn) {
3299 opt_hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
3300 if (!opt_hostname) {
3301 // We don't have either one, nothing to do.
3302 return;
3303 }
3304 }
3305
3306 // Empty hostname on the lease means we need to generate it.
3307 if (lease->hostname_.empty()) {
3308 // Note that if we have received the hostname option, rather than
3309 // Client FQDN the trailing dot is not appended to the generated
3310 // hostname because some clients don't handle the trailing dot in
3311 // the hostname. Whether the trailing dot is appended or not is
3312 // controlled by the second argument to the generateFqdn().
3313 lease->hostname_ = CfgMgr::instance().getD2ClientMgr()
3314 .generateFqdn(lease->addr_, *(ctx->getDdnsParams()), static_cast<bool>(fqdn));
3315
3317 .arg(query->getLabel())
3318 .arg(lease->hostname_);
3319
3320 client_name_changed = true;
3321 }
3322
3323 if (client_name_changed) {
3324 // The operations below are rather safe, but we want to catch
3325 // any potential exceptions (e.g. invalid lease database backend
3326 // implementation) and log an error.
3327 try {
3329 if (!ctx->fake_allocation_ || (ctx->offer_lft_ > 0)) {
3330 // The lease can't be reused.
3331 lease->reuseable_valid_lft_ = 0;
3332
3333 // The lease update should be safe, because the lease should
3334 // be already in the database. In most cases the exception
3335 // would be thrown if the lease was missing.
3336 LeaseMgrFactory::instance().updateLease4(lease);
3337 }
3338
3339 // The name update in the outbound option should be also safe,
3340 // because the generated name is well formed.
3341 if (fqdn) {
3342 fqdn->setDomainName(lease->hostname_, Option4ClientFqdn::FULL);
3343 } else {
3344 opt_hostname->setValue(lease->hostname_);
3345 }
3346 } catch (const Exception& ex) {
3348 .arg(query->getLabel())
3349 .arg(lease->hostname_)
3350 .arg(ex.what());
3351 }
3352 }
3353}
3354
3356void
3357Dhcpv4Srv::setTeeTimes(const Lease4Ptr& lease, const Subnet4Ptr& subnet, Pkt4Ptr resp) {
3358
3359 uint32_t t2_time = 0;
3360 // If T2 is explicitly configured we'll use try value.
3361 if (!subnet->getT2().unspecified()) {
3362 t2_time = subnet->getT2();
3363 } else if (subnet->getCalculateTeeTimes()) {
3364 // Calculating tee times is enabled, so calculated it.
3365 t2_time = static_cast<uint32_t>(round(subnet->getT2Percent() * (lease->valid_lft_)));
3366 }
3367
3368 // Send the T2 candidate value only if it's sane: to be sane it must be less than
3369 // the valid life time.
3370 uint32_t timer_ceiling = lease->valid_lft_;
3371 if (t2_time > 0 && t2_time < timer_ceiling) {
3373 resp->addOption(t2);
3374 // When we send T2, timer ceiling for T1 becomes T2.
3375 timer_ceiling = t2_time;
3376 }
3377
3378 uint32_t t1_time = 0;
3379 // If T1 is explicitly configured we'll use try value.
3380 if (!subnet->getT1().unspecified()) {
3381 t1_time = subnet->getT1();
3382 } else if (subnet->getCalculateTeeTimes()) {
3383 // Calculating tee times is enabled, so calculate it.
3384 t1_time = static_cast<uint32_t>(round(subnet->getT1Percent() * (lease->valid_lft_)));
3385 }
3386
3387 // Send T1 if it's sane: If we sent T2, T1 must be less than that. If not it must be
3388 // less than the valid life time.
3389 if (t1_time > 0 && t1_time < timer_ceiling) {
3391 resp->addOption(t1);
3392 }
3393}
3394
3395uint16_t
3397
3398 // Look for a relay-port RAI sub-option in the query.
3399 const Pkt4Ptr& query = ex.getQuery();
3400 const OptionPtr& rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
3401 if (rai && rai->getOption(RAI_OPTION_RELAY_PORT)) {
3402 // Got the sub-option so use the remote port set by the relay.
3403 return (query->getRemotePort());
3404 }
3405 return (0);
3406}
3407
3408void
3410 adjustRemoteAddr(ex);
3411
3412 // Initialize the pointers to the client's message and the server's
3413 // response.
3414 Pkt4Ptr query = ex.getQuery();
3415 Pkt4Ptr response = ex.getResponse();
3416
3417 // The DHCPINFORM is generally unicast to the client. The only situation
3418 // when the server is unable to unicast to the client is when the client
3419 // doesn't include ciaddr and the message is relayed. In this case the
3420 // server has to reply via relay agent. For other messages we send back
3421 // through relay if message is relayed, and unicast to the client if the
3422 // message is not relayed.
3423 // If client port was set from the command line enforce all responses
3424 // to it. Of course it is only for testing purposes.
3425 // Note that the call to this function may throw if invalid combination
3426 // of hops and giaddr is found (hops = 0 if giaddr = 0 and hops != 0 if
3427 // giaddr != 0). The exception will propagate down and eventually cause the
3428 // packet to be discarded.
3429 if (client_port_) {
3430 response->setRemotePort(client_port_);
3431 } else if (((query->getType() == DHCPINFORM) &&
3432 ((!query->getCiaddr().isV4Zero()) ||
3433 (!query->isRelayed() && !query->getRemoteAddr().isV4Zero()))) ||
3434 ((query->getType() != DHCPINFORM) && !query->isRelayed())) {
3435 response->setRemotePort(DHCP4_CLIENT_PORT);
3436
3437 } else {
3438 // RFC 8357 section 5.1
3439 uint16_t relay_port = checkRelayPort(ex);
3440 response->setRemotePort(relay_port ? relay_port : DHCP4_SERVER_PORT);
3441 }
3442
3443 CfgIfacePtr cfg_iface = CfgMgr::instance().getCurrentCfg()->getCfgIface();
3444 if (query->isRelayed() &&
3445 (cfg_iface->getSocketType() == CfgIface::SOCKET_UDP) &&
3446 (cfg_iface->getOutboundIface() == CfgIface::USE_ROUTING)) {
3447
3448 // Mark the response to follow routing
3449 response->setLocalAddr(IOAddress::IPV4_ZERO_ADDRESS());
3450 response->resetIndex();
3451 // But keep the interface name
3452 response->setIface(query->getIface());
3453
3454 } else {
3455
3456 IOAddress local_addr = query->getLocalAddr();
3457
3458 // In many cases the query is sent to a broadcast address. This address
3459 // appears as a local address in the query message. We can't simply copy
3460 // this address to a response message and use it as a source address.
3461 // Instead we will need to use the address assigned to the interface
3462 // on which the query has been received. In other cases, we will just
3463 // use this address as a source address for the response.
3464 // Do the same for DHCPv4-over-DHCPv6 exchanges.
3465 if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
3466 local_addr = IfaceMgr::instance().getSocket(query).addr_;
3467 }
3468
3469 // We assume that there is an appropriate socket bound to this address
3470 // and that the address is correct. This is safe assumption because
3471 // the local address of the query is set when the query is received.
3472 // The query sent to an incorrect address wouldn't have been received.
3473 // However, if socket is closed for this address between the reception
3474 // of the query and sending a response, the IfaceMgr should detect it
3475 // and return an error.
3476 response->setLocalAddr(local_addr);
3477 // In many cases the query is sent to a broadcast address. This address
3478 // appears as a local address in the query message. Therefore we can't
3479 // simply copy local address from the query and use it as a source
3480 // address for the response. Instead, we have to check what address our
3481 // socket is bound to and use it as a source address. This operation
3482 // may throw if for some reason the socket is closed.
3485 response->setIndex(query->getIndex());
3486 response->setIface(query->getIface());
3487 }
3488
3489 if (server_port_) {
3490 response->setLocalPort(server_port_);
3491 } else {
3492 response->setLocalPort(DHCP4_SERVER_PORT);
3493 }
3494}
3495
3496void
3498 // Initialize the pointers to the client's message and the server's
3499 // response.
3500 Pkt4Ptr query = ex.getQuery();
3501 Pkt4Ptr response = ex.getResponse();
3502
3503 // DHCPv4-over-DHCPv6 is simple
3504 if (query->isDhcp4o6()) {
3505 response->setRemoteAddr(query->getRemoteAddr());
3506 return;
3507 }
3508
3509 // The DHCPINFORM is slightly different than other messages in a sense
3510 // that the server should always unicast the response to the ciaddr.
3511 // It appears however that some clients don't set the ciaddr. We still
3512 // want to provision these clients and we do what we can't to send the
3513 // packet to the address where client can receive it.
3514 if (query->getType() == DHCPINFORM) {
3515 // If client adheres to RFC2131 it will set the ciaddr and in this
3516 // case we always unicast our response to this address.
3517 if (!query->getCiaddr().isV4Zero()) {
3518 response->setRemoteAddr(query->getCiaddr());
3519
3520 // If we received DHCPINFORM via relay and the ciaddr is not set we
3521 // will try to send the response via relay. The caveat is that the
3522 // relay will not have any idea where to forward the packet because
3523 // the yiaddr is likely not set. So, the broadcast flag is set so
3524 // as the response may be broadcast.
3525 } else if (query->isRelayed()) {
3526 response->setRemoteAddr(query->getGiaddr());
3527 response->setFlags(response->getFlags() | BOOTP_BROADCAST);
3528
3529 // If there is no ciaddr and no giaddr the only thing we can do is
3530 // to use the source address of the packet.
3531 } else {
3532 response->setRemoteAddr(query->getRemoteAddr());
3533 }
3534 // Remote address is now set so return.
3535 return;
3536 }
3537
3538 // If received relayed message, server responds to the relay address.
3539 if (query->isRelayed()) {
3540 // The client should set the ciaddr when sending the DHCPINFORM
3541 // but in case he didn't, the relay may not be able to determine the
3542 // address of the client, because yiaddr is not set when responding
3543 // to Confirm and the only address available was the source address
3544 // of the client. The source address is however not used here because
3545 // the message is relayed. Therefore, we set the BROADCAST flag so
3546 // as the relay can broadcast the packet.
3547 if ((query->getType() == DHCPINFORM) &&
3548 query->getCiaddr().isV4Zero()) {
3549 response->setFlags(BOOTP_BROADCAST);
3550 }
3551 response->setRemoteAddr(query->getGiaddr());
3552
3553 // If giaddr is 0 but client set ciaddr, server should unicast the
3554 // response to ciaddr.
3555 } else if (!query->getCiaddr().isV4Zero()) {
3556 response->setRemoteAddr(query->getCiaddr());
3557
3558 // We can't unicast the response to the client when sending DHCPNAK,
3559 // because we haven't allocated address for him. Therefore,
3560 // DHCPNAK is broadcast.
3561 } else if (response->getType() == DHCPNAK) {
3562 response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
3563
3564 // If yiaddr is set it means that we have created a lease for a client.
3565 } else if (!response->getYiaddr().isV4Zero()) {
3566 // If the broadcast bit is set in the flags field, we have to
3567 // send the response to broadcast address. Client may have requested it
3568 // because it doesn't support reception of messages on the interface
3569 // which doesn't have an address assigned. The other case when response
3570 // must be broadcasted is when our server does not support responding
3571 // directly to a client without address assigned.
3572 const bool bcast_flag = ((query->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
3573 if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
3574 response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
3575
3576 // Client cleared the broadcast bit and we support direct responses
3577 // so we should unicast the response to a newly allocated address -
3578 // yiaddr.
3579 } else {
3580 response->setRemoteAddr(response ->getYiaddr());
3581
3582 }
3583
3584 // In most cases, we should have the remote address found already. If we
3585 // found ourselves at this point, the rational thing to do is to respond
3586 // to the address we got the query from.
3587 } else {
3588 response->setRemoteAddr(query->getRemoteAddr());
3589 }
3590
3591 // For testing *only*.
3593 response->setRemoteAddr(query->getRemoteAddr());
3594 }
3595}
3596
3597void
3599 Pkt4Ptr query = ex.getQuery();
3600 Pkt4Ptr response = ex.getResponse();
3601
3602 // Step 1: Start with fixed fields defined on subnet level.
3603 Subnet4Ptr subnet = ex.getContext()->subnet_;
3604 if (subnet) {
3605 IOAddress subnet_next_server = subnet->getSiaddr();
3606 if (!subnet_next_server.isV4Zero()) {
3607 response->setSiaddr(subnet_next_server);
3608 }
3609
3610 const string& sname = subnet->getSname();
3611 if (!sname.empty()) {
3612 // Converting string to (const uint8_t*, size_t len) format is
3613 // tricky. reinterpret_cast is not the most elegant solution,
3614 // but it does avoid us making unnecessary copy. We will convert
3615 // sname and file fields in Pkt4 to string one day and life
3616 // will be easier.
3617 response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
3618 sname.size());
3619 }
3620
3621 const string& filename = subnet->getFilename();
3622 if (!filename.empty()) {
3623 // Converting string to (const uint8_t*, size_t len) format is
3624 // tricky. reinterpret_cast is not the most elegant solution,
3625 // but it does avoid us making unnecessary copy. We will convert
3626 // sname and file fields in Pkt4 to string one day and life
3627 // will be easier.
3628 response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
3629 filename.size());
3630 }
3631 }
3632
3633 // Step 2: Try to set the values based on classes.
3634 // Any values defined in classes will override those from subnet level.
3635 const ClientClasses classes = query->getClasses();
3636 if (!classes.empty()) {
3637
3638 // Let's get class definitions
3639 const ClientClassDictionaryPtr& dict =
3640 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
3641
3642 // Now we need to iterate over the classes assigned to the
3643 // query packet and find corresponding class definitions for it.
3644 // We want the first value found for each field. We track how
3645 // many we've found so we can stop if we have all three.
3647 string sname;
3648 string filename;
3649 size_t found_cnt = 0; // How many fields we have found.
3650 for (auto const& name : classes) {
3651
3652 if (found_cnt >= 3) {
3653 break;
3654 }
3655
3656 ClientClassDefPtr cl = dict->findClass(name);
3657 if (!cl) {
3658 // Let's skip classes that don't have definitions. Currently
3659 // these are automatic classes VENDOR_CLASS_something, but there
3660 // may be other classes assigned under other circumstances, e.g.
3661 // by hooks.
3662 continue;
3663 }
3664
3665 if (next_server == IOAddress::IPV4_ZERO_ADDRESS()) {
3666 next_server = cl->getNextServer();
3667 if (!next_server.isV4Zero()) {
3668 response->setSiaddr(next_server);
3669 found_cnt++;
3670 }
3671 }
3672
3673 if (sname.empty()) {
3674 sname = cl->getSname();
3675 if (!sname.empty()) {
3676 // Converting string to (const uint8_t*, size_t len) format is
3677 // tricky. reinterpret_cast is not the most elegant solution,
3678 // but it does avoid us making unnecessary copy. We will convert
3679 // sname and file fields in Pkt4 to string one day and life
3680 // will be easier.
3681 response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
3682 sname.size());
3683 found_cnt++;
3684 }
3685 }
3686
3687 if (filename.empty()) {
3688 filename = cl->getFilename();
3689 if (!filename.empty()) {
3690 // Converting string to (const uint8_t*, size_t len) format is
3691 // tricky. reinterpret_cast is not the most elegant solution,
3692 // but it does avoid us making unnecessary copy. We will convert
3693 // sname and file fields in Pkt4 to string one day and life
3694 // will be easier.
3695 response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
3696 filename.size());
3697 found_cnt++;
3698 }
3699 }
3700 }
3701 }
3702
3703 // Step 3: try to set values using HR. Any values coming from there will override
3704 // the subnet or class values.
3705 ex.setReservedMessageFields();
3706}
3707
3709Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
3710 uint32_t netmask = getNetmask4(subnet->get().second).toUint32();
3711
3712 OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
3713 DHO_SUBNET_MASK, netmask));
3714
3715 return (opt);
3716}
3717
3718tuple<bool, uint32_t>
3719Dhcpv4Srv::parkingLimitExceeded(string const& hook_label) {
3720 // Get the parking limit. Parsing should ensure the value is present.
3721 uint32_t parked_packet_limit(0);
3722 ConstElementPtr const& ppl(
3723 CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::PARKED_PACKET_LIMIT));
3724 if (ppl) {
3725 parked_packet_limit = ppl->intValue();
3726 }
3727
3728 if (parked_packet_limit) {
3729 ParkingLotPtr const& parking_lot(
3730 ServerHooks::getServerHooks().getParkingLotPtr(hook_label));
3731
3732 if (parking_lot && parked_packet_limit <= parking_lot->size()) {
3733 return make_tuple(true, parked_packet_limit);
3734 }
3735 }
3736 return make_tuple(false, parked_packet_limit);
3737}
3738
3739Pkt4Ptr
3741 bool drop = false;
3742 Dhcpv4Exchange ex(alloc_engine_, discover, context, context->subnet_, drop);
3743
3744 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
3745 if (drop) {
3746 return (Pkt4Ptr());
3747 }
3748
3749 if (MultiThreadingMgr::instance().getMode()) {
3750 // The lease reclamation cannot run at the same time.
3751 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3752
3753 assignLease(ex);
3754 } else {
3755 assignLease(ex);
3756 }
3757
3758 if (!ex.getResponse()) {
3759 // The offer is empty so return it *now*!
3760 return (Pkt4Ptr());
3761 }
3762
3763 // Adding any other options makes sense only when we got the lease
3764 // or it is for an IPv6-Only client.
3765 if (!ex.getResponse()->getYiaddr().isV4Zero() || ex.getIPv6OnlyPreferred()) {
3766 // If this is global reservation or the subnet doesn't belong to a shared
3767 // network we have already fetched it and evaluated the classes.
3768 ex.conditionallySetReservedClientClasses();
3769
3770 // Required classification
3771 requiredClassify(ex);
3772
3774 .arg(discover->getLabel())
3775 .arg(discover->getName())
3776 .arg(discover->getClasses().toText());
3777
3780 // Sanity check for IPv6-Only clients.
3781 if (ex.getIPv6OnlyPreferred()) {
3782 if (!ex.getResponse()->getOption(DHO_V6_ONLY_PREFERRED)) {
3783 // Better to drop the packet than to send an insane response.
3785 .arg(discover->getLabel());
3786 return (Pkt4Ptr());
3787 }
3788 }
3790 // There are a few basic options that we always want to
3791 // include in the response. If client did not request
3792 // them we append them for him.
3794
3795 // Set fixed fields (siaddr, sname, filename) if defined in
3796 // the reservation, class or subnet specific configuration.
3797 setFixedFields(ex);
3798
3799 } else {
3800 // If the server can't offer an address, it drops the packet.
3801 return (Pkt4Ptr());
3802
3803 }
3804
3805 // Set the src/dest IP address, port and interface for the outgoing
3806 // packet.
3807 adjustIfaceData(ex);
3808
3809 appendServerID(ex);
3810
3811 // Return the pointer to the context, which will be required by the
3812 // lease4_offer callouts.
3813 context = ex.getContext();
3814
3815 return (ex.getResponse());
3816}
3817
3818Pkt4Ptr
3820 bool drop = false;
3821 Dhcpv4Exchange ex(alloc_engine_, request, context, context->subnet_, drop);
3822
3823 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
3824 if (drop) {
3825 return (Pkt4Ptr());
3826 }
3827
3828 // Note that we treat REQUEST message uniformly, regardless if this is a
3829 // first request (requesting for new address), renewing existing address
3830 // or even rebinding.
3831 if (MultiThreadingMgr::instance().getMode()) {
3832 // The lease reclamation cannot run at the same time.
3833 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3834
3835 assignLease(ex);
3836 } else {
3837 assignLease(ex);
3838 }
3839
3840 Pkt4Ptr response = ex.getResponse();
3841 if (!response) {
3842 // The ack is empty so return it *now*!
3843 return (Pkt4Ptr());
3844 } else if (request->inClass("BOOTP")) {
3845 // Put BOOTP responses in the BOOTP class.
3846 response->addClass("BOOTP");
3847 }
3848
3849 // Adding any other options makes sense only when we got the lease
3850 // or it is for an IPv6-Only client.
3851 if (!response->getYiaddr().isV4Zero() || ex.getIPv6OnlyPreferred()) {
3852 // If this is global reservation or the subnet doesn't belong to a shared
3853 // network we have already fetched it and evaluated the classes.
3854 ex.conditionallySetReservedClientClasses();
3855
3856 // Required classification
3857 requiredClassify(ex);
3858
3860 .arg(request->getLabel())
3861 .arg(request->getName())
3862 .arg(request->getClasses().toText());
3863
3866 // Sanity check for IPv6-Only clients.
3867 if (ex.getIPv6OnlyPreferred()) {
3868 if (!response->getOption(DHO_V6_ONLY_PREFERRED)) {
3869 // Better to drop the packet than to send an insane response.
3871 .arg(request->getLabel());
3872 return (Pkt4Ptr());
3873 }
3874 }
3876 // There are a few basic options that we always want to
3877 // include in the response. If client did not request
3878 // them we append them for him.
3880
3881 // Set fixed fields (siaddr, sname, filename) if defined in
3882 // the reservation, class or subnet specific configuration.
3883 setFixedFields(ex);
3884 }
3885
3886 // Set the src/dest IP address, port and interface for the outgoing
3887 // packet.
3888 adjustIfaceData(ex);
3889
3890 appendServerID(ex);
3891
3892 // Return the pointer to the context, which will be required by the
3893 // leases4_committed callouts.
3894 context = ex.getContext();
3895
3896 return (ex.getResponse());
3897}
3898
3899void
3901 // Try to find client-id. Note that for the DHCPRELEASE we don't check if the
3902 // match-client-id configuration parameter is disabled because this parameter
3903 // is configured for subnets and we don't select subnet for the DHCPRELEASE.
3904 // Bogus clients usually generate new client identifiers when they first
3905 // connect to the network, so whatever client identifier has been used to
3906 // acquire the lease, the client identifier carried in the DHCPRELEASE is
3907 // likely to be the same and the lease will be correctly identified in the
3908 // lease database. If supplied client identifier differs from the one used
3909 // to acquire the lease then the lease will remain in the database and
3910 // simply expire.
3911 ClientIdPtr client_id;
3912 OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
3913 if (opt) {
3914 client_id = ClientIdPtr(new ClientId(opt->getData()));
3915 }
3916
3917 try {
3918 // Do we have a lease for that particular address?
3919 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getCiaddr());
3920
3921 if (!lease) {
3922 // No such lease - bogus release
3924 .arg(release->getLabel())
3925 .arg(release->getCiaddr().toText());
3926 return;
3927 }
3928
3929 if (!lease->belongsToClient(release->getHWAddr(), client_id)) {
3931 .arg(release->getLabel())
3932 .arg(release->getCiaddr().toText());
3933 return;
3934 }
3935
3936 bool skip = false;
3937
3938 // Execute all callouts registered for lease4_release
3939 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_release_)) {
3940 CalloutHandlePtr callout_handle = getCalloutHandle(release);
3941
3942 // Use the RAII wrapper to make sure that the callout handle state is
3943 // reset when this object goes out of scope. All hook points must do
3944 // it to prevent possible circular dependency between the callout
3945 // handle and its arguments.
3946 ScopedCalloutHandleState callout_handle_state(callout_handle);
3947
3948 // Enable copying options from the packet within hook library.
3949 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(release);
3950
3951 // Pass the original packet
3952 callout_handle->setArgument("query4", release);
3953
3954 // Pass the lease to be updated
3955 callout_handle->setArgument("lease4", lease);
3956
3957 // Call all installed callouts
3958 HooksManager::callCallouts(Hooks.hook_index_lease4_release_,
3959 *callout_handle);
3960
3961 // Callouts decided to skip the next processing step. The next
3962 // processing step would be to send the packet, so skip at this
3963 // stage means "drop response".
3964 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
3965 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
3966 skip = true;
3969 .arg(release->getLabel());
3970 }
3971 }
3972
3973 // Callout didn't indicate to skip the release process. Let's release
3974 // the lease.
3975 if (!skip) {
3976 // Ok, we've passed all checks. Let's release this address.
3977 bool success = false; // was the removal operation successful?
3978 bool expired = false; // explicitly expired instead of removed?
3979 auto expiration_cfg = CfgMgr::instance().getCurrentCfg()->getCfgExpiration();
3980
3981 // Delete lease only if affinity is disabled.
3982 if (expiration_cfg->getFlushReclaimedTimerWaitTime() &&
3983 expiration_cfg->getHoldReclaimedTime() &&
3984 lease->valid_lft_ != Lease::INFINITY_LFT) {
3985 // Expire the lease.
3986 lease->valid_lft_ = 0;
3987 // Set the lease state to released to indicate that this lease
3988 // must be preserved in the database. It is particularly useful
3989 // in HA to differentiate between the leases that should be
3990 // updated in the partner's database and deleted from the partner's
3991 // database.
3992 lease->state_ = Lease4::STATE_RELEASED;
3993 LeaseMgrFactory::instance().updateLease4(lease);
3994 expired = true;
3995 success = true;
3996 } else {
3997 success = LeaseMgrFactory::instance().deleteLease(lease);
3998 }
3999
4000 if (success) {
4001 context.reset(new AllocEngine::ClientContext4());
4002 context->old_lease_ = lease;
4003
4004 // Release successful
4006 .arg(release->getLabel())
4007 .arg(lease->addr_.toText());
4008
4009 if (expired) {
4011 .arg(release->getLabel())
4012 .arg(lease->addr_.toText());
4013 } else {
4015 .arg(release->getLabel())
4016 .arg(lease->addr_.toText());
4017
4018 // Remove existing DNS entries for the lease, if any.
4019 queueNCR(CHG_REMOVE, lease);
4020 }
4021
4022 // Need to decrease statistic for assigned addresses.
4023 StatsMgr::instance().addValue(
4024 StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
4025 static_cast<int64_t>(-1));
4026
4027 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
4028 if (subnet) {
4029 auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
4030 if (pool) {
4031 StatsMgr::instance().addValue(
4032 StatsMgr::generateName("subnet", subnet->getID(),
4033 StatsMgr::generateName("pool", pool->getID(), "assigned-addresses")),
4034 static_cast<int64_t>(-1));
4035 }
4036 }
4037
4038 } else {
4039 // Release failed
4041 .arg(release->getLabel())
4042 .arg(lease->addr_.toText());
4043 }
4044 }
4045 } catch (const isc::Exception& ex) {
4047 .arg(release->getLabel())
4048 .arg(release->getCiaddr())
4049 .arg(ex.what());
4050 }
4051}
4052
4053void
4055 // Client is supposed to specify the address being declined in
4056 // Requested IP address option, but must not set its ciaddr.
4057 // (again, see table 5 in RFC2131).
4058
4059 OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
4060 OptionCustom>(decline->getOption(DHO_DHCP_REQUESTED_ADDRESS));
4061 if (!opt_requested_address) {
4062
4063 isc_throw(RFCViolation, "Mandatory 'Requested IP address' option missing"
4064 " in DHCPDECLINE sent from " << decline->getLabel());
4065 }
4066 IOAddress addr(opt_requested_address->readAddress());
4067
4068 // We could also extract client's address from ciaddr, but that's clearly
4069 // against RFC2131.
4070
4071 // Now we need to check whether this address really belongs to the client
4072 // that attempts to decline it.
4073 const Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
4074
4075 if (!lease) {
4076 // Client tried to decline an address, but we don't have a lease for
4077 // that address. Let's ignore it.
4078 //
4079 // We could assume that we're recovering from a mishandled migration
4080 // to a new server and mark the address as declined, but the window of
4081 // opportunity for that to be useful is small and the attack vector
4082 // would be pretty severe.
4084 .arg(addr.toText()).arg(decline->getLabel());
4085 return;
4086 }
4087
4088 // Get client-id, if available.
4089 OptionPtr opt_clientid = decline->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
4090 ClientIdPtr client_id;
4091 if (opt_clientid) {
4092 client_id.reset(new ClientId(opt_clientid->getData()));
4093 }
4094
4095 // Check if the client attempted to decline an expired lease or a lease
4096 // it doesn't own. Declining expired leases is typically a client
4097 // misbehavior and may lead to pool exhaustion in case of a storm of
4098 // such declines. Only decline the lease if the lease has been recently
4099 // allocated to the client.
4100 if (lease->expired() || lease->state_ != Lease::STATE_DEFAULT ||
4101 !lease->belongsToClient(decline->getHWAddr(), client_id)) {
4102
4103 // Get printable hardware addresses
4104 string client_hw = decline->getHWAddr() ?
4105 decline->getHWAddr()->toText(false) : "(none)";
4106 string lease_hw = lease->hwaddr_ ?
4107 lease->hwaddr_->toText(false) : "(none)";
4108
4109 // Get printable client-ids
4110 string client_id_txt = client_id ? client_id->toText() : "(none)";
4111 string lease_id_txt = lease->client_id_ ?
4112 lease->client_id_->toText() : "(none)";
4113
4114 // Print the warning and we're done here.
4116 .arg(addr.toText()).arg(decline->getLabel())
4117 .arg(client_hw).arg(lease_hw).arg(client_id_txt).arg(lease_id_txt);
4118
4119 return;
4120 }
4121
4122 // Ok, all is good. The client is reporting its own address. Let's
4123 // process it.
4124 declineLease(lease, decline, context);
4125}
4126
4127void
4128Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline,
4130
4131 // Let's check if there are hooks installed for decline4 hook point.
4132 // If they are, let's pass the lease and client's packet. If the hook
4133 // sets status to drop, we reject this Decline.
4134 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_decline_)) {
4135 CalloutHandlePtr callout_handle = getCalloutHandle(decline);
4136
4137 // Use the RAII wrapper to make sure that the callout handle state is
4138 // reset when this object goes out of scope. All hook points must do
4139 // it to prevent possible circular dependency between the callout
4140 // handle and its arguments.
4141 ScopedCalloutHandleState callout_handle_state(callout_handle);
4142
4143 // Enable copying options from the packet within hook library.
4144 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(decline);
4145
4146 // Pass the original packet
4147 callout_handle->setArgument("query4", decline);
4148
4149 // Pass the lease to be updated
4150 callout_handle->setArgument("lease4", lease);
4151
4152 // Call callouts
4153 HooksManager::callCallouts(Hooks.hook_index_lease4_decline_,
4154 *callout_handle);
4155
4156 // Check if callouts decided to skip the next processing step.
4157 // If any of them did, we will drop the packet.
4158 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
4159 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
4161 .arg(decline->getLabel()).arg(lease->addr_.toText());
4162 return;
4163 }
4164 }
4165
4166 Lease4Ptr old_values = boost::make_shared<Lease4>(*lease);
4167
4168 // @todo: Call hooks.
4169
4170 // We need to disassociate the lease from the client. Once we move a lease
4171 // to declined state, it is no longer associated with the client in any
4172 // way.
4173 lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
4174
4175 try {
4176 LeaseMgrFactory::instance().updateLease4(lease);
4177 } catch (const Exception& ex) {
4178 // Update failed.
4180 .arg(decline->getLabel())
4181 .arg(lease->addr_.toText())
4182 .arg(ex.what());
4183 return;
4184 }
4185
4186 // Remove existing DNS entries for the lease, if any.
4187 // queueNCR will do the necessary checks and will skip the update, if not needed.
4188 queueNCR(CHG_REMOVE, old_values);
4189
4190 // Bump up the statistics.
4191
4192 // Per subnet declined addresses counter.
4193 StatsMgr::instance().addValue(
4194 StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
4195 static_cast<int64_t>(1));
4196
4197 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
4198 if (subnet) {
4199 auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
4200 if (pool) {
4201 StatsMgr::instance().addValue(
4202 StatsMgr::generateName("subnet", subnet->getID(),
4203 StatsMgr::generateName("pool", pool->getID(), "declined-addresses")),
4204 static_cast<int64_t>(1));
4205 }
4206 }
4207
4208 // Global declined addresses counter.
4209 StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
4210
4211 // We do not want to decrease the assigned-addresses at this time. While
4212 // technically a declined address is no longer allocated, the primary usage
4213 // of the assigned-addresses statistic is to monitor pool utilization. Most
4214 // people would forget to include declined-addresses in the calculation,
4215 // and simply do assigned-addresses/total-addresses. This would have a bias
4216 // towards under-representing pool utilization, if we decreased allocated
4217 // immediately after receiving DHCPDECLINE, rather than later when we recover
4218 // the address.
4219
4220 context.reset(new AllocEngine::ClientContext4());
4221 context->new_lease_ = lease;
4222
4223 LOG_INFO(lease4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText())
4224 .arg(decline->getLabel()).arg(lease->valid_lft_);
4225}
4226
4227void
4229 Lease4Ptr lease, bool lease_exists) {
4231 .arg(query->getLabel())
4232 .arg(lease->addr_.toText())
4233 .arg(lease->valid_lft_);
4234
4235 {
4236 // Check if the resource is busy i.e. can be modified by another thread
4237 // for another client. Highly unlikely.
4238 ResourceHandler4 resource_handler;
4239 if (MultiThreadingMgr::instance().getMode() && !resource_handler.tryLock4(lease->addr_)) {
4241 .arg(query->getLabel())
4242 .arg(lease->addr_.toText());
4243 return;
4244 }
4245
4246 // We need to disassociate the lease from the client. Once we move a lease
4247 // to declined state, it is no longer associated with the client in any
4248 // way.
4249 lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
4250
4251 // If the lease already exists, update it in the database.
4252 if (lease_exists) {
4253 try {
4254 LeaseMgrFactory::instance().updateLease4(lease);
4255 } catch (const NoSuchLease& ex) {
4256 // We expected the lease to exist but it doesn't so let's try
4257 // to add it.
4258 lease_exists = false;
4259 } catch (const Exception& ex) {
4260 // Update failed.
4262 .arg(query->getLabel())
4263 .arg(lease->addr_.toText());
4264 return;
4265 }
4266 }
4267
4268 if (!lease_exists) {
4269 try {
4270 LeaseMgrFactory::instance().addLease(lease);
4271 } catch (const Exception& ex) {
4273 .arg(query->getLabel())
4274 .arg(lease->addr_.toText());
4275 return;
4276 }
4277 }
4278 }
4279
4280 // Bump up the statistics. If the lease does not exist (i.e. offer-lifetime == 0) we
4281 // need to increment assigned address stats, otherwise the accounting will be off.
4282 // This saves us from having to determine later, when declined leases are reclaimed,
4283 // whether or not we need to decrement assigned stats. In other words, this keeps
4284 // a declined lease always counted also as an assigned lease, regardless of how
4285 // it was declined, until it is reclaimed at which point both groups of stats
4286 // are decremented.
4287
4288 // Per subnet declined addresses counter.
4289 StatsMgr::instance().addValue(
4290 StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
4291 static_cast<int64_t>(1));
4292
4293 if (!lease_exists) {
4294 StatsMgr::instance().addValue(
4295 StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
4296 static_cast<int64_t>(1));
4297 }
4298
4299 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
4300 if (subnet) {
4301 auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
4302 if (pool) {
4303 StatsMgr::instance().addValue(
4304 StatsMgr::generateName("subnet", subnet->getID(),
4305 StatsMgr::generateName("pool", pool->getID(), "declined-addresses")),
4306 static_cast<int64_t>(1));
4307 if (!lease_exists) {
4308 StatsMgr::instance().addValue(
4309 StatsMgr::generateName("subnet", subnet->getID(),
4310 StatsMgr::generateName("pool", pool->getID(), "assigned-addresses")),
4311 static_cast<int64_t>(1));
4312 }
4313 }
4314 }
4315
4316 // Global declined addresses counter.
4317 StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
4318 if (!lease_exists) {
4319 StatsMgr::instance().addValue("assigned-addresses", static_cast<int64_t>(1));
4320 }
4321
4322 // Let's check if there are hooks installed for server decline hook point.
4323 // If there are, let's pass the DHCPDISCOVER and the declined lease .
4324 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_server_decline_)) {
4325 // Use the RAII wrapper to make sure that the callout handle state is
4326 // reset when this object goes out of scope. All hook points must do
4327 // it to prevent possible circular dependency between the callout
4328 // handle and its arguments.
4329 ScopedCalloutHandleState callout_handle_state(callout_handle);
4330
4331 // Pass in the original DHCPDISCOVER
4332 callout_handle->setArgument("query4", query);
4333
4334 // Pass in the declined lease.
4335 callout_handle->setArgument("lease4", lease);
4336
4337 // Call callouts
4338 HooksManager::callCallouts(Hooks.hook_index_lease4_server_decline_,
4339 *callout_handle);
4340 }
4341}
4342
4343void
4345 Lease4Ptr lease, bool lease_exists) {
4346 try {
4347 serverDecline(callout_handle, query, lease, lease_exists);
4348 } catch (...) {
4350 }
4351}
4352
4353Pkt4Ptr
4355 bool drop = false;
4356 Dhcpv4Exchange ex(alloc_engine_, inform, context, context->subnet_, drop);
4357
4358 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
4359 if (drop) {
4360 return (Pkt4Ptr());
4361 }
4362
4363 Pkt4Ptr ack = ex.getResponse();
4364
4365 // If this is global reservation or the subnet doesn't belong to a shared
4366 // network we have already fetched it and evaluated the classes.
4367 ex.conditionallySetReservedClientClasses();
4368
4369 requiredClassify(ex);
4370
4372 .arg(inform->getLabel())
4373 .arg(inform->getName())
4374 .arg(inform->getClasses().toText());
4375
4380 adjustIfaceData(ex);
4381
4382 // Set fixed fields (siaddr, sname, filename) if defined in
4383 // the reservation, class or subnet specific configuration.
4384 setFixedFields(ex);
4385
4386 // There are cases for the DHCPINFORM that the server receives it via
4387 // relay but will send the response to the client's unicast address
4388 // carried in the ciaddr. In this case, the giaddr and hops field should
4389 // be cleared (these fields were copied by the copyDefaultFields function).
4390 // Also Relay Agent Options should be removed if present.
4391 if (ack->getRemoteAddr() != inform->getGiaddr()) {
4393 .arg(inform->getLabel())
4394 .arg(ack->getRemoteAddr())
4395 .arg(ack->getIface());
4396 ack->setHops(0);
4397 ack->setGiaddr(IOAddress::IPV4_ZERO_ADDRESS());
4398 ack->delOption(DHO_DHCP_AGENT_OPTIONS);
4399 }
4400
4401 // The DHCPACK must contain server id.
4402 appendServerID(ex);
4403
4404 return (ex.getResponse());
4405}
4406
4407void
4409 if (query->getCiaddr().isV4Zero() || !query->getGiaddr().isV4Zero()) {
4410 return;
4411 }
4412 ConstElementPtr sao = CfgMgr::instance().getCurrentCfg()->
4413 getConfiguredGlobal(CfgGlobals::STASH_AGENT_OPTIONS);
4414 if (!sao || (sao->getType() != Element::boolean) || !sao->boolValue()) {
4415 return;
4416 }
4417 if (query->getType() != DHCPREQUEST) {
4418 return;
4419 }
4420 OptionPtr rai_opt = query->getOption(DHO_DHCP_AGENT_OPTIONS);
4421 if (rai_opt && (rai_opt->len() > Option::OPTION4_HDR_LEN)) {
4422 return;
4423 }
4424 // Should not happen but makes sense to check and gives a trivial way
4425 // to disable the feature from previous callout points.
4426 if (query->inClass("STASH_AGENT_OPTIONS")) {
4427 return;
4428 }
4429 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(query->getCiaddr());
4430 if (!lease || lease->expired()) {
4431 return;
4432 }
4433 ConstElementPtr user_context = lease->getContext();
4434 if (!user_context || (user_context->getType() != Element::map)) {
4435 return;
4436 }
4437 ConstElementPtr isc = user_context->get("ISC");
4438 if (!isc || (isc->getType() != Element::map)) {
4439 return;
4440 }
4441 ConstElementPtr relay_agent_info = isc->get("relay-agent-info");
4442 if (!relay_agent_info) {
4443 return;
4444 }
4445 // Compatibility with the old layout.
4446 if (relay_agent_info->getType() == Element::map) {
4447 relay_agent_info = relay_agent_info->get("sub-options");
4448 if (!relay_agent_info) {
4449 return;
4450 }
4451 }
4452 if (relay_agent_info->getType() != Element::string) {
4453 return;
4454 }
4455 // Check ownership before going further.
4456 ClientIdPtr client_id;
4457 OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
4458 if (opt_clientid) {
4459 client_id.reset(new ClientId(opt_clientid->getData()));
4460 }
4461 if (!lease->belongsToClient(query->getHWAddr(), client_id)) {
4462 return;
4463 }
4464 // Extract the RAI.
4465 string rai_hex = relay_agent_info->stringValue();
4466 if (rai_hex.empty()) {
4467 return;
4468 }
4469 vector<uint8_t> rai_data;
4470 str::decodeFormattedHexString(rai_hex, rai_data);
4471 static const OptionDefinition& rai_def = LibDHCP::DHO_DHCP_AGENT_OPTIONS_DEF();
4472 OptionCustomPtr rai(new OptionCustom(rai_def, Option::V4, rai_data));
4473 // unpackOptions is a bit too flexible so check if it got something...
4474 if (!rai || rai->getOptions().empty()) {
4475 return;
4476 }
4477 // Remove an existing empty RAI.
4478 if (rai_opt) {
4479 query->delOption(DHO_DHCP_AGENT_OPTIONS);
4480 }
4481 query->addOption(rai);
4482 query->addClass("STASH_AGENT_OPTIONS");
4485 .arg(query->getLabel())
4486 .arg(query->getCiaddr())
4487 .arg(rai->toText());
4488}
4489
4490bool
4492 // Check that the message type is accepted by the server. We rely on the
4493 // function called to log a message if needed.
4494 if (!acceptMessageType(query)) {
4495 return (false);
4496 }
4497 // Check if the message from directly connected client (if directly
4498 // connected) should be dropped or processed.
4499 if (!acceptDirectRequest(query)) {
4501 .arg(query->getLabel())
4502 .arg(query->getIface());
4503 return (false);
4504 }
4505
4506 // Check if the DHCPv4 packet has been sent to us or to someone else.
4507 // If it hasn't been sent to us, drop it!
4508 if (!acceptServerId(query)) {
4510 .arg(query->getLabel())
4511 .arg(query->getIface());
4512 return (false);
4513 }
4514
4515 return (true);
4516}
4517
4518bool
4520 // Accept all relayed messages.
4521 if (pkt->isRelayed()) {
4522 return (true);
4523 }
4524
4525 // Accept all DHCPv4-over-DHCPv6 messages.
4526 if (pkt->isDhcp4o6()) {
4527 return (true);
4528 }
4529
4530 // The source address must not be zero for the DHCPINFORM message from
4531 // the directly connected client because the server will not know where
4532 // to respond if the ciaddr was not present.
4533 try {
4534 if (pkt->getType() == DHCPINFORM) {
4535 if (pkt->getRemoteAddr().isV4Zero() &&
4536 pkt->getCiaddr().isV4Zero()) {
4537 return (false);
4538 }
4539 }
4540 } catch (...) {
4541 // If we got here, it is probably because the message type hasn't
4542 // been set. But, this should not really happen assuming that
4543 // we validate the message type prior to calling this function.
4544 return (false);
4545 }
4546 bool drop = false;
4547 bool result = (!pkt->getLocalAddr().isV4Bcast() ||
4548 selectSubnet(pkt, drop, true));
4549 if (drop) {
4550 // The packet must be dropped but as sanity_only is true it is dead code.
4551 return (false);
4552 }
4553 return (result);
4554}
4555
4556bool
4558 // When receiving a packet without message type option, getType() will
4559 // throw.
4560 int type;
4561 try {
4562 type = query->getType();
4563
4564 } catch (...) {
4566 .arg(query->getLabel())
4567 .arg(query->getIface());
4568 return (false);
4569 }
4570
4571 // Once we know that the message type is within a range of defined DHCPv4
4572 // messages, we do a detailed check to make sure that the received message
4573 // is targeted at server. Note that we could have received some Offer
4574 // message broadcasted by the other server to a relay. Even though, the
4575 // server would rather unicast its response to a relay, let's be on the
4576 // safe side. Also, we want to drop other messages which we don't support.
4577 // All these valid messages that we are not going to process are dropped
4578 // silently.
4579
4580 switch(type) {
4581 case DHCPDISCOVER:
4582 case DHCPREQUEST:
4583 case DHCPRELEASE:
4584 case DHCPDECLINE:
4585 case DHCPINFORM:
4586 return (true);
4587 break;
4588
4589 case DHCP_NOTYPE:
4591 .arg(query->getLabel());
4592 break;
4593
4594 default:
4595 // If we receive a message with a non-existing type, we are logging it.
4596 if (type >= DHCP_TYPES_EOF) {
4598 .arg(query->getLabel())
4599 .arg(type);
4600 } else {
4601 // Exists but we don't support it.
4603 .arg(query->getLabel())
4604 .arg(type);
4605 }
4606 break;
4607 }
4608
4609 return (false);
4610}
4611
4612bool
4614 // This function is meant to be called internally by the server class, so
4615 // we rely on the caller to sanity check the pointer and we don't check
4616 // it here.
4617
4618 // Check if server identifier option is present. If it is not present
4619 // we accept the message because it is targeted to all servers.
4620 // Note that we don't check cases that server identifier is mandatory
4621 // but not present. This is meant to be sanity checked in other
4622 // functions.
4623 OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
4624 if (!option) {
4625 return (true);
4626 }
4627 // Server identifier is present. Let's convert it to 4-byte address
4628 // and try to match with server identifiers used by the server.
4629 OptionCustomPtr option_custom =
4630 boost::dynamic_pointer_cast<OptionCustom>(option);
4631 // Unable to convert the option to the option type which encapsulates it.
4632 // We treat this as non-matching server id.
4633 if (!option_custom) {
4634 return (false);
4635 }
4636 // The server identifier option should carry exactly one IPv4 address.
4637 // If the option definition for the server identifier doesn't change,
4638 // the OptionCustom object should have exactly one IPv4 address and
4639 // this check is somewhat redundant. On the other hand, if someone
4640 // breaks option it may be better to check that here.
4641 if (option_custom->getDataFieldsNum() != 1) {
4642 return (false);
4643 }
4644
4645 // The server identifier MUST be an IPv4 address. If given address is
4646 // v6, it is wrong.
4647 IOAddress server_id = option_custom->readAddress();
4648 if (!server_id.isV4()) {
4649 return (false);
4650 }
4651
4652 // According to RFC5107, the RAI_OPTION_SERVER_ID_OVERRIDE option if
4653 // present, should match DHO_DHCP_SERVER_IDENTIFIER option.
4654 OptionPtr rai_option = query->getOption(DHO_DHCP_AGENT_OPTIONS);
4655 if (rai_option) {
4656 OptionPtr rai_suboption = rai_option->getOption(RAI_OPTION_SERVER_ID_OVERRIDE);
4657 if (rai_suboption && (server_id.toBytes() == rai_suboption->toBinary())) {
4658 return (true);
4659 }
4660 }
4661
4662 // Skip address check if configured to ignore the server id.
4663 SrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
4664 if (cfg->getIgnoreServerIdentifier()) {
4665 return (true);
4666 }
4667
4668 // This function iterates over all interfaces on which the
4669 // server is listening to find the one which has a socket bound
4670 // to the address carried in the server identifier option.
4671 // This has some performance implications. However, given that
4672 // typically there will be just a few active interfaces the
4673 // performance hit should be acceptable. If it turns out to
4674 // be significant, we will have to cache server identifiers
4675 // when sockets are opened.
4676 if (IfaceMgr::instance().hasOpenSocket(server_id)) {
4677 return (true);
4678 }
4679
4680 // There are some cases when an administrator explicitly sets server
4681 // identifier (option 54) that should be used for a given, subnet,
4682 // network etc. It doesn't have to be an address assigned to any of
4683 // the server interfaces. Thus, we have to check if the server
4684 // identifier received is the one that we explicitly set in the
4685 // server configuration. At this point, we don't know which subnet
4686 // the client belongs to so we can't match the server id with any
4687 // subnet. We simply check if this server identifier is configured
4688 // anywhere. This should be good enough to eliminate exchanges
4689 // with other servers in the same network.
4690
4698
4699 // Check if there is at least one subnet configured with this server
4700 // identifier.
4701 ConstCfgSubnets4Ptr cfg_subnets = cfg->getCfgSubnets4();
4702 if (cfg_subnets->hasSubnetWithServerId(server_id)) {
4703 return (true);
4704 }
4705
4706 // This server identifier is not configured for any of the subnets, so
4707 // check on the shared network level.
4708 CfgSharedNetworks4Ptr cfg_networks = cfg->getCfgSharedNetworks4();
4709 if (cfg_networks->hasNetworkWithServerId(server_id)) {
4710 return (true);
4711 }
4712
4713 // Check if the server identifier is configured at client class level.
4714 const ClientClasses& classes = query->getClasses();
4715 for (auto const& cclass : classes) {
4716 // Find the client class definition for this class
4717 const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
4718 getClientClassDictionary()->findClass(cclass);
4719 if (!ccdef) {
4720 continue;
4721 }
4722
4723 if (ccdef->getCfgOption()->empty()) {
4724 // Skip classes which don't configure options
4725 continue;
4726 }
4727
4728 OptionCustomPtr context_opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
4729 (ccdef->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
4730 if (context_opt_server_id && (context_opt_server_id->readAddress() == server_id)) {
4731 return (true);
4732 }
4733 }
4734
4735 // Finally, it is possible that the server identifier is specified
4736 // on the global level.
4737 ConstCfgOptionPtr cfg_global_options = cfg->getCfgOption();
4738 OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
4739 (cfg_global_options->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
4740
4741 return (opt_server_id && (opt_server_id->readAddress() == server_id));
4742}
4743
4744void
4746 switch (query->getType()) {
4747 case DHCPDISCOVER:
4748 // server-id is forbidden.
4749 sanityCheck(query, FORBIDDEN);
4750 break;
4751 case DHCPREQUEST:
4752 // Since we cannot distinguish between client states
4753 // we'll make server-id is optional for REQUESTs.
4754 sanityCheck(query, OPTIONAL);
4755 break;
4756 case DHCPRELEASE:
4757 // Server-id is mandatory in DHCPRELEASE (see table 5, RFC2131)
4758 // but ISC DHCP does not enforce this, so we'll follow suit.
4759 sanityCheck(query, OPTIONAL);
4760 break;
4761 case DHCPDECLINE:
4762 // Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131)
4763 // but ISC DHCP does not enforce this, so we'll follow suit.
4764 sanityCheck(query, OPTIONAL);
4765 break;
4766 case DHCPINFORM:
4767 // server-id is supposed to be forbidden (as is requested address)
4768 // but ISC DHCP does not enforce either. So neither will we.
4769 sanityCheck(query, OPTIONAL);
4770 break;
4771 }
4772}
4773
4774void
4776 OptionPtr server_id = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
4777 switch (serverid) {
4778 case FORBIDDEN:
4779 if (server_id) {
4780 isc_throw(RFCViolation, "Server-id option was not expected, but"
4781 << " received in message "
4782 << query->getName());
4783 }
4784 break;
4785
4786 case MANDATORY:
4787 if (!server_id) {
4788 isc_throw(RFCViolation, "Server-id option was expected, but not"
4789 " received in message "
4790 << query->getName());
4791 }
4792 break;
4793
4794 case OPTIONAL:
4795 // do nothing here
4796 ;
4797 }
4798
4799 // If there is HWAddress set and it is non-empty, then we're good
4800 if (query->getHWAddr() && !query->getHWAddr()->hwaddr_.empty()) {
4801 return;
4802 }
4803
4804 // There has to be something to uniquely identify the client:
4805 // either non-zero MAC address or client-id option present (or both)
4806 OptionPtr client_id = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
4807
4808 // If there's no client-id (or a useless one is provided, i.e. 0 length)
4809 if (!client_id || client_id->len() == client_id->getHeaderLen()) {
4810 isc_throw(RFCViolation, "Missing or useless client-id and no HW address"
4811 " provided in message "
4812 << query->getName());
4813 }
4814}
4815
4819
4821 // First collect required classes
4822 Pkt4Ptr query = ex.getQuery();
4823 ClientClasses classes = query->getClasses(true);
4824 Subnet4Ptr subnet = ex.getContext()->subnet_;
4825
4826 if (subnet) {
4827 // host reservation???
4828
4829 // Begin by the pool
4830 Pkt4Ptr resp = ex.getResponse();
4832 if (resp) {
4833 addr = resp->getYiaddr();
4834 }
4835 if (!addr.isV4Zero()) {
4836 PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
4837 if (pool) {
4838 const ClientClasses& pool_to_add = pool->getRequiredClasses();
4839 for (auto const& cclass : pool_to_add) {
4840 classes.insert(cclass);
4841 }
4842 }
4843 }
4844
4845 // Followed by the subnet
4846 const ClientClasses& to_add = subnet->getRequiredClasses();
4847 for (auto const& cclass : to_add) {
4848 classes.insert(cclass);
4849 }
4850
4851 // And finish by the shared-network
4852 SharedNetwork4Ptr network;
4853 subnet->getSharedNetwork(network);
4854 if (network) {
4855 const ClientClasses& net_to_add = network->getRequiredClasses();
4856 for (auto const& cclass : net_to_add) {
4857 classes.insert(cclass);
4858 }
4859 }
4860 }
4861
4862 // Run match expressions
4863 // Note getClientClassDictionary() cannot be null
4864 const ClientClassDictionaryPtr& dict =
4865 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
4866 for (auto const& cclass : classes) {
4867 const ClientClassDefPtr class_def = dict->findClass(cclass);
4868 if (!class_def) {
4870 .arg(cclass);
4871 continue;
4872 }
4873 const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
4874 // Nothing to do without an expression to evaluate
4875 if (!expr_ptr) {
4877 .arg(cclass);
4878 continue;
4879 }
4880 // Evaluate the expression which can return false (no match),
4881 // true (match) or raise an exception (error)
4882 try {
4883 bool status = evaluateBool(*expr_ptr, *query);
4885 .arg(query->getLabel())
4886 .arg(cclass)
4887 .arg(status ? "true" : "false");
4888 if (status) {
4889 // Matching: add the class
4890 query->addClass(cclass);
4891 }
4892 } catch (const Exception& ex) {
4894 .arg(query->getLabel())
4895 .arg(cclass)
4896 .arg(ex.what());
4897 }
4898 }
4899}
4900
4901void
4903 // Iterate on the list of deferred option codes
4904 for (auto const& code : query->getDeferredOptions()) {
4906 // Iterate on client classes
4907 const ClientClasses& classes = query->getClasses();
4908 for (auto const& cclass : classes) {
4909 // Get the client class definition for this class
4910 const ClientClassDefPtr& ccdef =
4911 CfgMgr::instance().getCurrentCfg()->
4912 getClientClassDictionary()->findClass(cclass);
4913 // If not found skip it
4914 if (!ccdef) {
4915 continue;
4916 }
4917 // If there is no option definition skip it
4918 if (!ccdef->getCfgOptionDef()) {
4919 continue;
4920 }
4921 def = ccdef->getCfgOptionDef()->get(DHCP4_OPTION_SPACE, code);
4922 // Stop at the first client class with a definition
4923 if (def) {
4924 break;
4925 }
4926 }
4927 // If not found try the global definition
4928 if (!def) {
4930 }
4931 if (!def) {
4933 }
4934 // Finish by last resort definition
4935 if (!def) {
4937 }
4938 // If not defined go to the next option
4939 if (!def) {
4940 continue;
4941 }
4942 // Get the existing option for its content and remove all
4943 OptionPtr opt = query->getOption(code);
4944 if (!opt) {
4945 // should not happen but do not crash anyway
4948 .arg(query->getLabel())
4949 .arg(code);
4950 continue;
4951 }
4952 // Because options have already been fused, the buffer contains entire
4953 // data.
4954 const OptionBuffer buf = opt->getData();
4955 try {
4956 // Unpack the option
4957 opt = def->optionFactory(Option::V4, code, buf);
4958 } catch (const std::exception& e) {
4959 // Failed to parse the option.
4962 .arg(query->getLabel())
4963 .arg(code)
4964 .arg(e.what());
4965 continue;
4966 }
4967 while (query->delOption(code)) {
4968 // continue
4969 }
4970 // Add the unpacked option.
4971 query->addOption(opt);
4972 }
4973}
4974
4975void
4977 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
4978 if (d2_mgr.ddnsEnabled()) {
4979 // Updates are enabled, so lets start the sender, passing in
4980 // our error handler.
4981 // This may throw so wherever this is called needs to ready.
4982 d2_mgr.startSender(std::bind(&Dhcpv4Srv::d2ClientErrorHandler,
4983 this, ph::_1, ph::_2));
4984 }
4985}
4986
4987void
4989 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
4990 if (d2_mgr.ddnsEnabled()) {
4991 // Updates are enabled, so lets stop the sender
4992 d2_mgr.stop();
4993 d2_mgr.stopSender();
4994 }
4995}
4996
4997void
5002 arg(result).arg((ncr ? ncr->toText() : " NULL "));
5003 // We cannot communicate with kea-dhcp-ddns, suspend further updates.
5006 CfgMgr::instance().getD2ClientMgr().suspendUpdates();
5007}
5008
5009std::string
5011 std::stringstream tmp;
5012
5013 tmp << VERSION;
5014 if (extended) {
5015 tmp << " (" << EXTENDED_VERSION << ")" << endl;
5016 tmp << "premium: " << PREMIUM_EXTENDED_VERSION << endl;
5017 tmp << "linked with:" << endl;
5018 tmp << "- " << Logger::getVersion() << endl;
5019 tmp << "- " << CryptoLink::getVersion();
5021 if (info.size()) {
5022 tmp << endl << "lease backends:";
5023 for (auto const& version : info) {
5024 tmp << endl << "- " << version;
5025 }
5026 }
5028 if (info.size()) {
5029 tmp << endl << "host backends:";
5030 for (auto const& version : info) {
5031 tmp << endl << "- " << version;
5032 }
5033 }
5034 // @todo: more details about database runtime
5035 }
5036
5037 return (tmp.str());
5038}
5039
5041 // Note that we're not bumping pkt4-received statistic as it was
5042 // increased early in the packet reception code.
5043
5044 string stat_name = "pkt4-unknown-received";
5045 try {
5046 switch (query->getType()) {
5047 case DHCPDISCOVER:
5048 stat_name = "pkt4-discover-received";
5049 break;
5050 case DHCPOFFER:
5051 // Should not happen, but let's keep a counter for it
5052 stat_name = "pkt4-offer-received";
5053 break;
5054 case DHCPREQUEST:
5055 stat_name = "pkt4-request-received";
5056 break;
5057 case DHCPACK:
5058 // Should not happen, but let's keep a counter for it
5059 stat_name = "pkt4-ack-received";
5060 break;
5061 case DHCPNAK:
5062 // Should not happen, but let's keep a counter for it
5063 stat_name = "pkt4-nak-received";
5064 break;
5065 case DHCPRELEASE:
5066 stat_name = "pkt4-release-received";
5067 break;
5068 case DHCPDECLINE:
5069 stat_name = "pkt4-decline-received";
5070 break;
5071 case DHCPINFORM:
5072 stat_name = "pkt4-inform-received";
5073 break;
5074 default:
5075 ; // do nothing
5076 }
5077 }
5078 catch (...) {
5079 // If the incoming packet doesn't have option 53 (message type)
5080 // or a hook set pkt4_receive_skip, then Pkt4::getType() may
5081 // throw an exception. That's ok, we'll then use the default
5082 // name of pkt4-unknown-received.
5083 }
5084
5085 isc::stats::StatsMgr::instance().addValue(stat_name,
5086 static_cast<int64_t>(1));
5087}
5088
5090 // Increase generic counter for sent packets.
5091 isc::stats::StatsMgr::instance().addValue("pkt4-sent",
5092 static_cast<int64_t>(1));
5093
5094 // Increase packet type specific counter for packets sent.
5095 string stat_name;
5096 switch (response->getType()) {
5097 case DHCPOFFER:
5098 stat_name = "pkt4-offer-sent";
5099 break;
5100 case DHCPACK:
5101 stat_name = "pkt4-ack-sent";
5102 break;
5103 case DHCPNAK:
5104 stat_name = "pkt4-nak-sent";
5105 break;
5106 default:
5107 // That should never happen
5108 return;
5109 }
5110
5111 isc::stats::StatsMgr::instance().addValue(stat_name,
5112 static_cast<int64_t>(1));
5113}
5114
5116 return (Hooks.hook_index_buffer4_receive_);
5117}
5118
5120 return (Hooks.hook_index_pkt4_receive_);
5121}
5122
5124 return (Hooks.hook_index_subnet4_select_);
5125}
5126
5128 return (Hooks.hook_index_lease4_release_);
5129}
5130
5132 return (Hooks.hook_index_pkt4_send_);
5133}
5134
5136 return (Hooks.hook_index_buffer4_send_);
5137}
5138
5140 return (Hooks.hook_index_lease4_decline_);
5141}
5142
5144 // Dump all of our current packets, anything that is mid-stream
5146}
5147
5148std::list<std::list<std::string>> Dhcpv4Srv::jsonPathsToRedact() const {
5149 static std::list<std::list<std::string>> const list({
5150 {"config-control", "config-databases", "[]"},
5151 {"hooks-libraries", "[]", "parameters", "*"},
5152 {"hosts-database"},
5153 {"hosts-databases", "[]"},
5154 {"lease-database"},
5155 });
5156 return list;
5157}
5158
5159} // namespace dhcp
5160} // namespace isc
int version()
returns Kea hooks version.
CtrlAgentHooks Hooks
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
This is a base class for exceptions thrown from the DNS library module.
A generic exception that is thrown when an unexpected error condition occurs.
static HttpCommandMgr & instance()
HttpCommandMgr is a singleton class.
DHCPv4 and DHCPv6 allocation engine.
boost::shared_ptr< ClientContext4 > ClientContext4Ptr
Pointer to the ClientContext4.
static uint32_t getValidLft(const ClientContext4 &ctx)
Returns the valid lifetime based on the v4 context.
Implementation of the mechanisms to control the use of the Configuration Backends by the DHCPv4 serve...
@ USE_ROUTING
Server uses routing to determine the right interface to send response.
Definition cfg_iface.h:148
@ SOCKET_UDP
Datagram socket, i.e. IP/UDP socket.
Definition cfg_iface.h:139
Configuration Manager.
Definition cfgmgr.h:70
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:25
static SubnetSelector initSelector(const Pkt4Ptr &query)
Build selector from a client's message.
Container for storing client class names.
Definition classify.h:108
void insert(const ClientClass &class_name)
Insert an element.
Definition classify.h:128
Client race avoidance RAII handler.
Holds Client identifier or client IPv4 address.
Definition duid.h:222
ReplaceClientNameMode
Defines the client name replacement modes.
D2ClientMgr isolates Kea from the details of being a D2 client.
Convenience container for conveying DDNS behavioral parameters It is intended to be created per Packe...
Definition srv_config.h:49
static Dhcp4to6Ipc & instance()
Returns pointer to the sole instance of Dhcp4to6Ipc.
DHCPv4 message exchange.
Definition dhcp4_srv.h:62
Pkt4Ptr getQuery() const
Returns the pointer to the query from the client.
Definition dhcp4_srv.h:96
static void setHostIdentifiers(AllocEngine::ClientContext4Ptr context)
Set host identifiers within a context.
Definition dhcp4_srv.cc:404
Dhcpv4Exchange(const AllocEnginePtr &alloc_engine, const Pkt4Ptr &query, AllocEngine::ClientContext4Ptr &context, const Subnet4Ptr &subnet, bool &drop)
Constructor.
Definition dhcp4_srv.cc:158
static void classifyByVendor(const Pkt4Ptr &pkt)
Assign class using vendor-class-identifier option.
Definition dhcp4_srv.cc:566
void initResponse()
Initializes the instance of the response message.
Definition dhcp4_srv.cc:276
void setReservedMessageFields()
Sets reserved values of siaddr, sname and file in the server's response.
Definition dhcp4_srv.cc:544
static void setReservedClientClasses(AllocEngine::ClientContext4Ptr context)
Assigns classes retrieved from host reservation database.
Definition dhcp4_srv.cc:520
void initResponse4o6()
Initializes the DHCPv6 part of the response message.
Definition dhcp4_srv.cc:302
static void evaluateClasses(const Pkt4Ptr &pkt, bool depend_on_known)
Evaluate classes.
Definition dhcp4_srv.cc:589
static void classifyPacket(const Pkt4Ptr &pkt)
Assigns incoming packet to zero or more classes.
Definition dhcp4_srv.cc:578
static void removeDependentEvaluatedClasses(const Pkt4Ptr &query)
Removed evaluated client classes.
Definition dhcp4_srv.cc:505
void conditionallySetReservedClientClasses()
Assigns classes retrieved from host reservation database if they haven't been yet set.
Definition dhcp4_srv.cc:530
void initContext0(const Pkt4Ptr &query, AllocEngine::ClientContext4Ptr ctx)
Initialize client context (first part).
int run()
Main server processing loop.
void declineLease(const Lease4Ptr &lease, const Pkt4Ptr &decline, AllocEngine::ClientContext4Ptr &context)
Marks lease as declined.
void processPacketAndSendResponse(Pkt4Ptr query)
Process a single incoming DHCPv4 packet and sends the response.
void classifyPacket(const Pkt4Ptr &pkt)
Assigns incoming packet to zero or more classes.
void appendRequestedVendorOptions(Dhcpv4Exchange &ex)
Appends requested vendor options as requested by client.
void adjustIfaceData(Dhcpv4Exchange &ex)
Set IP/UDP and interface parameters for the DHCPv4 response.
isc::dhcp::Subnet4Ptr selectSubnet4o6(const Pkt4Ptr &query, bool &drop, bool sanity_only=false, bool allow_answer_park=true)
Selects a subnet for a given client's DHCP4o6 packet.
Definition dhcp4_srv.cc:858
static uint16_t checkRelayPort(const Dhcpv4Exchange &ex)
Check if the relay port RAI sub-option was set in the query.
virtual ~Dhcpv4Srv()
Destructor. Used during DHCPv4 service shutdown.
Definition dhcp4_srv.cc:676
virtual Pkt4Ptr receivePacket(int timeout)
dummy wrapper around IfaceMgr::receive4
bool accept(const Pkt4Ptr &query)
Checks whether received message should be processed or discarded.
static void appendServerID(Dhcpv4Exchange &ex)
Adds server identifier option to the server's response.
void postAllocateNameUpdate(const AllocEngine::ClientContext4Ptr &ctx, const Lease4Ptr &lease, const Pkt4Ptr &query, const Pkt4Ptr &resp, bool client_name_changed)
Update client name and DNS flags in the lease and response.
isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr &query, bool &drop, bool sanity_only=false, bool allow_answer_park=true)
Selects a subnet for a given client's packet.
Definition dhcp4_srv.cc:724
void runOne()
Main server processing step.
void startD2()
Starts DHCP_DDNS client IO if DDNS updates are enabled.
static int getHookIndexBuffer4Receive()
Returns the index for "buffer4_receive" hook point.
Pkt4Ptr processRequest(Pkt4Ptr &request, AllocEngine::ClientContext4Ptr &context)
Processes incoming REQUEST and returns REPLY response.
static void processStatsReceived(const Pkt4Ptr &query)
Class methods for DHCPv4-over-DHCPv6 handler.
static int getHookIndexPkt4Send()
Returns the index for "pkt4_send" hook point.
void processDecline(Pkt4Ptr &decline, AllocEngine::ClientContext4Ptr &context)
Process incoming DHCPDECLINE messages.
Dhcpv4Srv(uint16_t server_port=DHCP4_SERVER_PORT, uint16_t client_port=0, const bool use_bcast=true, const bool direct_response_desired=true)
Default constructor.
Definition dhcp4_srv.cc:615
static int getHookIndexSubnet4Select()
Returns the index for "subnet4_select" hook point.
static void processStatsSent(const Pkt4Ptr &response)
Updates statistics for transmitted packets.
void shutdown() override
Instructs the server to shut down.
Definition dhcp4_srv.cc:718
static int getHookIndexLease4Release()
Returns the index for "lease4_release" hook point.
void adjustRemoteAddr(Dhcpv4Exchange &ex)
Sets remote addresses for outgoing packet.
static int getHookIndexPkt4Receive()
Returns the index for "pkt4_receive" hook point.
void assignLease(Dhcpv4Exchange &ex)
Assigns a lease and appends corresponding options.
Pkt4Ptr processDhcp4Query(Pkt4Ptr query, bool allow_answer_park)
Process a single incoming DHCPv4 query.
asiolink::IOServicePtr & getIOService()
Returns pointer to the IO service used by the server.
Definition dhcp4_srv.h:319
void setFixedFields(Dhcpv4Exchange &ex)
Sets fixed fields of the outgoing packet.
void appendBasicOptions(Dhcpv4Exchange &ex)
Append basic options if they are not present.
void recoverStashedAgentOption(const Pkt4Ptr &query)
Recover stashed agent options from client address lease.
void processClientName(Dhcpv4Exchange &ex)
Processes Client FQDN and Hostname Options sent by a client.
boost::shared_ptr< AllocEngine > alloc_engine_
Allocation Engine.
Definition dhcp4_srv.h:1270
void serverDecline(hooks::CalloutHandlePtr &callout_handle, Pkt4Ptr &query, Lease4Ptr lease, bool lease_exists)
Renders a lease declined after the server has detected, via ping-check or other means,...
void requiredClassify(Dhcpv4Exchange &ex)
Assigns incoming packet to zero or more classes (required pass).
Pkt4Ptr processInform(Pkt4Ptr &inform, AllocEngine::ClientContext4Ptr &context)
Processes incoming DHCPINFORM messages.
uint16_t client_port_
UDP port number to which server sends all responses.
Definition dhcp4_srv.h:1260
void serverDeclineNoThrow(hooks::CalloutHandlePtr &callout_handle, Pkt4Ptr &query, Lease4Ptr lease, bool lease_exists)
Exception safe wrapper around serverDecline()
void processPacketAndSendResponseNoThrow(Pkt4Ptr query)
Process a single incoming DHCPv4 packet and sends the response.
std::list< std::list< std::string > > jsonPathsToRedact() const final override
Return a list of all paths that contain passwords or secrets for kea-dhcp4.
static std::string srvidToString(const OptionPtr &opt)
converts server-id to text Converts content of server-id option to a text representation,...
bool acceptServerId(const Pkt4Ptr &pkt) const
Verifies if the server id belongs to our server.
static const std::string VENDOR_CLASS_PREFIX
this is a prefix added to the content of vendor-class option
Definition dhcp4_srv.h:931
void createNameChangeRequests(const Lease4Ptr &lease, const Lease4Ptr &old_lease, const DdnsParams &ddns_params)
Creates NameChangeRequests which correspond to the lease which has been acquired.
void sendResponseNoThrow(hooks::CalloutHandlePtr &callout_handle, Pkt4Ptr &query, Pkt4Ptr &rsp, Subnet4Ptr &subnet)
Process an unparked DHCPv4 packet and sends the response.
void appendRequestedOptions(Dhcpv4Exchange &ex)
Appends options requested by client.
void setPacketStatisticsDefaults()
This function sets statistics related to DHCPv4 packets processing to their initial values.
Definition dhcp4_srv.cc:666
void processLocalizedQuery4AndSendResponse(Pkt4Ptr query, AllocEngine::ClientContext4Ptr &ctx, bool allow_answer_park)
Process a localized incoming DHCPv4 query.
static std::string getVersion(bool extended)
returns Kea version on stdout and exit.
bool assignZero(Subnet4Ptr &subnet, const ClientClasses &client_classes)
Assign the 0.0.0.0 address to an IPv6-Only client.
void buildCfgOptionList(Dhcpv4Exchange &ex)
Build the configured option list.
volatile bool shutdown_
Indicates if shutdown is in progress.
Definition dhcp4_srv.h:1264
uint16_t server_port_
UDP port number on which server listens.
Definition dhcp4_srv.h:1257
bool earlyGHRLookup(const Pkt4Ptr &query, AllocEngine::ClientContext4Ptr ctx)
Initialize client context and perform early global reservations lookup.
NetworkStatePtr network_state_
Holds information about disabled DHCP service and/or disabled subnet/network scopes.
Definition dhcp4_srv.h:1277
void setTeeTimes(const Lease4Ptr &lease, const Subnet4Ptr &subnet, Pkt4Ptr resp)
Adds the T1 and T2 timers to the outbound response as appropriate.
void processDhcp4QueryAndSendResponse(Pkt4Ptr query, bool allow_answer_park)
Process a single incoming DHCPv4 query.
bool getSendResponsesToSource() const
Returns value of the test_send_responses_to_source_ flag.
Definition dhcp4_srv.h:510
Pkt4Ptr processDiscover(Pkt4Ptr &discover, AllocEngine::ClientContext4Ptr &context)
Processes incoming DISCOVER and returns response.
virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender::Result result, dhcp_ddns::NameChangeRequestPtr &ncr)
Implements the error handler for DHCP_DDNS IO errors.
virtual void sendPacket(const Pkt4Ptr &pkt)
dummy wrapper around IfaceMgr::send()