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