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