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