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