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