Kea 3.0.2-git
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 // DHCPv4-over-DHCPv6 is a special (and complex) case
779 if (query->isDhcp4o6()) {
780 return (selectSubnet4o6(query, drop, sanity_only, allow_answer_park));
781 }
782
783 ConstSubnet4Ptr subnet;
784
785 const SubnetSelector& selector = CfgSubnets4::initSelector(query);
786
787 CfgMgr& cfgmgr = CfgMgr::instance();
788 subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
789
790 // Let's execute all callouts registered for subnet4_select
791 // (skip callouts if the selectSubnet was called to do sanity checks only)
792 if (!sanity_only &&
793 HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
794 CalloutHandlePtr callout_handle = getCalloutHandle(query);
795
796 // Use the RAII wrapper to make sure that the callout handle state is
797 // reset when this object goes out of scope. All hook points must do
798 // it to prevent possible circular dependency between the callout
799 // handle and its arguments.
800 shared_ptr<ScopedCalloutHandleState> callout_handle_state(
801 std::make_shared<ScopedCalloutHandleState>(callout_handle));
802
803 // Enable copying options from the packet within hook library.
804 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
805
806 // Set new arguments
807 callout_handle->setArgument("query4", query);
808 callout_handle->setArgument("subnet4", subnet);
809 callout_handle->setArgument("subnet4collection",
810 cfgmgr.getCurrentCfg()->
811 getCfgSubnets4()->getAll());
812
813 auto const tpl(parkingLimitExceeded("subnet4_select"));
814 bool const exceeded(get<0>(tpl));
815 if (exceeded) {
816 uint32_t const limit(get<1>(tpl));
817 // We can't park it so we're going to throw it on the floor.
820 .arg(limit)
821 .arg(query->getLabel());
822 return (ConstSubnet4Ptr());
823 }
824
825 // We proactively park the packet.
827 "subnet4_select", query, [this, query, allow_answer_park, callout_handle_state]() {
828 if (MultiThreadingMgr::instance().getMode()) {
829 boost::shared_ptr<function<void()>> callback(
830 boost::make_shared<function<void()>>(
831 [this, query, allow_answer_park]() mutable {
832 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
833 }));
834 callout_handle_state->on_completion_ = [callback]() {
836 };
837 } else {
838 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
839 }
840 });
841
842 // Call user (and server-side) callouts
843 try {
844 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
845 *callout_handle);
846 } catch (...) {
847 // Make sure we don't orphan a parked packet.
848 HooksManager::drop("subnet4_select", query);
849 throw;
850 }
851
852 // Callouts parked the packet. Same as drop but callouts will resume
853 // processing or drop the packet later.
854 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
857 .arg(query->getLabel());
858 drop = true;
859 return (ConstSubnet4Ptr());
860 } else {
861 HooksManager::drop("subnet4_select", query);
862 }
863
864 // Callouts decided to skip this step. This means that no subnet
865 // will be selected. Packet processing will continue, but it will
866 // be severely limited (i.e. only global options will be assigned)
867 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
870 .arg(query->getLabel());
871 return (ConstSubnet4Ptr());
872 }
873
874 // Callouts decided to drop the packet. It is a superset of the
875 // skip case so no subnet will be selected.
876 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
879 .arg(query->getLabel());
880 drop = true;
881 return (ConstSubnet4Ptr());
882 }
883
884 // Use whatever subnet was specified by the callout
885 callout_handle->getArgument("subnet4", subnet);
886 }
887
888 if (subnet) {
889 // Log at higher debug level that subnet has been found.
891 .arg(query->getLabel())
892 .arg(subnet->getID());
893 // Log detailed information about the selected subnet at the
894 // lower debug level.
896 .arg(query->getLabel())
897 .arg(subnet->toText());
898
899 } else {
902 .arg(query->getLabel());
903 }
904
905 return (subnet);
906}
907
909Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop,
910 bool sanity_only, bool allow_answer_park) {
911 ConstSubnet4Ptr subnet;
912
913 SubnetSelector selector;
914 selector.ciaddr_ = query->getCiaddr();
915 selector.giaddr_ = query->getGiaddr();
916 selector.local_address_ = query->getLocalAddr();
917 selector.client_classes_ = query->classes_;
918 selector.iface_name_ = query->getIface();
919 // Mark it as DHCPv4-over-DHCPv6
920 selector.dhcp4o6_ = true;
921 // Now the DHCPv6 part
922 selector.remote_address_ = query->getRemoteAddr();
923 selector.first_relay_linkaddr_ = IOAddress("::");
924
925 // Handle a DHCPv6 relayed query
926 Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast<Pkt4o6>(query);
927 if (!query4o6) {
928 isc_throw(Unexpected, "Can't get DHCP4o6 message");
929 }
930 const Pkt6Ptr& query6 = query4o6->getPkt6();
931
932 // Initialize fields specific to relayed messages.
933 if (query6 && !query6->relay_info_.empty()) {
934 for (auto const& relay : boost::adaptors::reverse(query6->relay_info_)) {
935 if (!relay.linkaddr_.isV6Zero() &&
936 !relay.linkaddr_.isV6LinkLocal()) {
937 selector.first_relay_linkaddr_ = relay.linkaddr_;
938 break;
939 }
940 }
941 selector.interface_id_ =
942 query6->getAnyRelayOption(D6O_INTERFACE_ID,
944 }
945
946 // If the Subnet Selection option is present, extract its value.
947 OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
948 if (sbnsel) {
949 OptionCustomPtr oc = boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
950 if (oc) {
951 selector.option_select_ = oc->readAddress();
952 }
953 }
954
955 CfgMgr& cfgmgr = CfgMgr::instance();
956 subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet4o6(selector);
957
958 // Let's execute all callouts registered for subnet4_select.
959 // (skip callouts if the selectSubnet was called to do sanity checks only)
960 if (!sanity_only &&
961 HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
962 CalloutHandlePtr callout_handle = getCalloutHandle(query);
963
964 // Use the RAII wrapper to make sure that the callout handle state is
965 // reset when this object goes out of scope. All hook points must do
966 // it to prevent possible circular dependency between the callout
967 // handle and its arguments.
968 shared_ptr<ScopedCalloutHandleState> callout_handle_state(
969 std::make_shared<ScopedCalloutHandleState>(callout_handle));
970
971 // Enable copying options from the packet within hook library.
972 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
973
974 // Set new arguments
975 callout_handle->setArgument("query4", query);
976 callout_handle->setArgument("subnet4", subnet);
977 callout_handle->setArgument("subnet4collection",
978 cfgmgr.getCurrentCfg()->
979 getCfgSubnets4()->getAll());
980
981 auto const tpl(parkingLimitExceeded("subnet4_select"));
982 bool const exceeded(get<0>(tpl));
983 if (exceeded) {
984 uint32_t const limit(get<1>(tpl));
985 // We can't park it so we're going to throw it on the floor.
988 .arg(limit)
989 .arg(query->getLabel());
990 return (ConstSubnet4Ptr());
991 }
992
993 // We proactively park the packet.
995 "subnet4_select", query, [this, query, allow_answer_park, callout_handle_state]() {
996 if (MultiThreadingMgr::instance().getMode()) {
997 boost::shared_ptr<function<void()>> callback(
998 boost::make_shared<function<void()>>(
999 [this, query, allow_answer_park]() mutable {
1000 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
1001 }));
1002 callout_handle_state->on_completion_ = [callback]() {
1004 };
1005 } else {
1006 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
1007 }
1008 });
1009
1010 // Call user (and server-side) callouts
1011 try {
1012 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
1013 *callout_handle);
1014 } catch (...) {
1015 // Make sure we don't orphan a parked packet.
1016 HooksManager::drop("subnet4_select", query);
1017 throw;
1018 }
1019
1020 // Callouts parked the packet. Same as drop but callouts will resume
1021 // processing or drop the packet later.
1022 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
1025 .arg(query->getLabel());
1026 drop = true;
1027 return (ConstSubnet4Ptr());
1028 } else {
1029 HooksManager::drop("subnet4_select", query);
1030 }
1031
1032 // Callouts decided to skip this step. This means that no subnet
1033 // will be selected. Packet processing will continue, but it will
1034 // be severely limited (i.e. only global options will be assigned)
1035 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1038 .arg(query->getLabel());
1039 return (ConstSubnet4Ptr());
1040 }
1041
1042 // Callouts decided to drop the packet. It is a superset of the
1043 // skip case so no subnet will be selected.
1044 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1047 .arg(query->getLabel());
1048 drop = true;
1049 return (ConstSubnet4Ptr());
1050 }
1051
1052 // Use whatever subnet was specified by the callout
1053 callout_handle->getArgument("subnet4", subnet);
1054 }
1055
1056 if (subnet) {
1057 // Log at higher debug level that subnet has been found.
1060 .arg(query->getLabel())
1061 .arg(subnet->getID());
1062 // Log detailed information about the selected subnet at the
1063 // lower debug level.
1066 .arg(query->getLabel())
1067 .arg(subnet->toText());
1068
1069 } else {
1072 .arg(query->getLabel());
1073 }
1074
1075 return (subnet);
1076}
1077
1078Pkt4Ptr
1080 return (IfaceMgr::instance().receive4(timeout));
1081}
1082
1083void
1085 IfaceMgr::instance().send(packet);
1086}
1087
1088void
1091 // Pointer to client's query.
1092 ctx->query_ = query;
1093
1094 // Hardware address.
1095 ctx->hwaddr_ = query->getHWAddr();
1096}
1097
1098bool
1101
1102 // First part of context initialization.
1103 initContext0(query, ctx);
1104
1105 // Get the early-global-reservations-lookup flag value.
1108 if (egrl) {
1109 ctx->early_global_reservations_lookup_ = egrl->boolValue();
1110 }
1111
1112 // Perform early global reservations lookup when wanted.
1113 if (ctx->early_global_reservations_lookup_) {
1114 // Retrieve retrieve client identifier.
1115 OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
1116 if (opt_clientid) {
1117 ctx->clientid_.reset(new ClientId(opt_clientid->getData()));
1118 }
1119
1120 // Get the host identifiers.
1122
1123 // Check for global host reservations.
1124 ConstHostPtr global_host = alloc_engine_->findGlobalReservation(*ctx);
1125
1126 if (global_host && !global_host->getClientClasses4().empty()) {
1127 // Remove dependent evaluated classes.
1129
1130 // Add classes from the global reservations.
1131 const ClientClasses& classes = global_host->getClientClasses4();
1132 for (auto const& cclass : classes) {
1133 query->addClass(cclass);
1134 }
1135
1136 // Evaluate classes before KNOWN.
1137 Dhcpv4Exchange::evaluateClasses(query, false);
1138 }
1139
1140 if (global_host) {
1141 // Add the KNOWN class;
1142 query->addClass("KNOWN");
1144 .arg(query->getLabel())
1145 .arg("KNOWN");
1146
1147 // Evaluate classes after KNOWN.
1149
1150 // Check the DROP special class.
1151 if (query->inClass("DROP")) {
1154 .arg(query->getHWAddrLabel())
1155 .arg(query->toText());
1156 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1157 static_cast<int64_t>(1));
1158 return (false);
1159 }
1160
1161 // Store the reservation.
1162 ctx->hosts_[SUBNET_ID_GLOBAL] = global_host;
1163 }
1164 }
1165
1166 return (true);
1167}
1168
1169int
1171#ifdef HAVE_AFL
1172 // Get the values of the environment variables used to control the
1173 // fuzzing.
1174
1175 // Specfies the interface to be used to pass packets from AFL to Kea.
1176 const char* interface = getenv("KEA_AFL_INTERFACE");
1177 if (!interface) {
1178 isc_throw(FuzzInitFail, "no fuzzing interface has been set");
1179 }
1180
1181 // The address on the interface to be used.
1182 const char* address = getenv("KEA_AFL_ADDRESS");
1183 if (!address) {
1184 isc_throw(FuzzInitFail, "no fuzzing address has been set");
1185 }
1186
1187 // Set up structures needed for fuzzing.
1188 PacketFuzzer fuzzer(server_port_, interface, address);
1189
1190 // The next line is needed as a signature for AFL to recognize that we are
1191 // running persistent fuzzing. This has to be in the main image file.
1192 while (__AFL_LOOP(fuzzer.maxLoopCount())) {
1193 // Read from stdin and put the data read into an address/port on which
1194 // Kea is listening, read for Kea to read it via asynchronous I/O.
1195 fuzzer.transfer();
1196#else
1197 while (!shutdown_) {
1198#endif // HAVE_AFL
1199 try {
1200 runOne();
1201 // Handle events registered by hooks using external IOService objects.
1203 getIOService()->poll();
1204 } catch (const std::exception& e) {
1205 // General catch-all standard exceptions that are not caught by more
1206 // specific catches.
1208 .arg(e.what());
1209 } catch (...) {
1210 // General catch-all exception that are not caught by more specific
1211 // catches. This one is for other exceptions, not derived from
1212 // std::exception.
1214 }
1215 }
1216
1217 // Stop everything before we change into single-threaded mode.
1219
1220 // destroying the thread pool
1221 MultiThreadingMgr::instance().apply(false, 0, 0);
1222
1223 return (getExitValue());
1224}
1225
1226void
1228 // client's message and server's response
1229 Pkt4Ptr query;
1230
1231 try {
1232 // Set select() timeout to 1s. This value should not be modified
1233 // because it is important that the select() returns control
1234 // frequently so as the IOService can be polled for ready handlers.
1235 uint32_t timeout = 1;
1236 query = receivePacket(timeout);
1237
1238 // Log if packet has arrived. We can't log the detailed information
1239 // about the DHCP message because it hasn't been unpacked/parsed
1240 // yet, and it can't be parsed at this point because hooks will
1241 // have to process it first. The only information available at this
1242 // point are: the interface, source address and destination addresses
1243 // and ports.
1244 if (query) {
1246 .arg(query->getRemoteAddr().toText())
1247 .arg(query->getRemotePort())
1248 .arg(query->getLocalAddr().toText())
1249 .arg(query->getLocalPort())
1250 .arg(query->getIface());
1251 }
1252
1253 // We used to log that the wait was interrupted, but this is no longer
1254 // the case. Our wait time is 1s now, so the lack of query packet more
1255 // likely means that nothing new appeared within a second, rather than
1256 // we were interrupted. And we don't want to print a message every
1257 // second.
1258
1259 } catch (const SignalInterruptOnSelect&) {
1260 // Packet reception interrupted because a signal has been received.
1261 // This is not an error because we might have received a SIGTERM,
1262 // SIGINT, SIGHUP or SIGCHLD which are handled by the server. For
1263 // signals that are not handled by the server we rely on the default
1264 // behavior of the system.
1266 } catch (const std::exception& e) {
1267 // Log all other errors.
1269 .arg(e.what());
1270 }
1271
1272 // Timeout may be reached or signal received, which breaks select()
1273 // with no reception occurred. No need to log anything here because
1274 // we have logged right after the call to receivePacket().
1275 if (!query) {
1276 return;
1277 }
1278
1279 // If the DHCP service has been globally disabled, drop the packet.
1280 if (!network_state_->isServiceEnabled()) {
1282 .arg(query->getLabel());
1283 return;
1284 } else {
1285 if (MultiThreadingMgr::instance().getMode()) {
1286 query->addPktEvent("mt_queued");
1287 typedef function<void()> CallBack;
1288 boost::shared_ptr<CallBack> call_back =
1289 boost::make_shared<CallBack>(std::bind(&Dhcpv4Srv::processPacketAndSendResponseNoThrow,
1290 this, query));
1291 if (!MultiThreadingMgr::instance().getThreadPool().add(call_back)) {
1293 }
1294 } else {
1296 }
1297 }
1298}
1299
1300void
1302 try {
1304 } catch (const std::exception& e) {
1306 .arg(query->getLabel())
1307 .arg(e.what());
1308 } catch (...) {
1310 }
1311}
1312
1313void
1315 Pkt4Ptr rsp = processPacket(query);
1316 if (!rsp) {
1317 return;
1318 }
1319
1320 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1321
1322 processPacketBufferSend(callout_handle, rsp);
1323}
1324
1325Pkt4Ptr
1326Dhcpv4Srv::processPacket(Pkt4Ptr query, bool allow_answer_park) {
1327 query->addPktEvent("process_started");
1328
1329 // All packets belong to ALL.
1330 query->addClass("ALL");
1331
1332 // Log reception of the packet. We need to increase it early, as any
1333 // failures in unpacking will cause the packet to be dropped. We
1334 // will increase type specific statistic further down the road.
1335 // See processStatsReceived().
1336 isc::stats::StatsMgr::instance().addValue("pkt4-received",
1337 static_cast<int64_t>(1));
1338
1339 bool skip_unpack = false;
1340
1341 // The packet has just been received so contains the uninterpreted wire
1342 // data; execute callouts registered for buffer4_receive.
1343 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_receive_)) {
1344 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1345
1346 // Use the RAII wrapper to make sure that the callout handle state is
1347 // reset when this object goes out of scope. All hook points must do
1348 // it to prevent possible circular dependency between the callout
1349 // handle and its arguments.
1350 ScopedCalloutHandleState callout_handle_state(callout_handle);
1351
1352 // Enable copying options from the packet within hook library.
1353 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1354
1355 // Pass incoming packet as argument
1356 callout_handle->setArgument("query4", query);
1357
1358 // Call callouts
1359 HooksManager::callCallouts(Hooks.hook_index_buffer4_receive_,
1360 *callout_handle);
1361
1362 // Callouts decided to drop the received packet.
1363 // The response (rsp) is null so the caller (runOne) will
1364 // immediately return too.
1365 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1368 .arg(query->getRemoteAddr().toText())
1369 .arg(query->getLocalAddr().toText())
1370 .arg(query->getIface());
1371 return (Pkt4Ptr());;
1372 }
1373
1374 // Callouts decided to skip the next processing step. The next
1375 // processing step would be to parse the packet, so skip at this
1376 // stage means that callouts did the parsing already, so server
1377 // should skip parsing.
1378 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1381 .arg(query->getRemoteAddr().toText())
1382 .arg(query->getLocalAddr().toText())
1383 .arg(query->getIface());
1384 skip_unpack = true;
1385 }
1386
1387 callout_handle->getArgument("query4", query);
1388 }
1389
1390 // Unpack the packet information unless the buffer4_receive callouts
1391 // indicated they did it
1392 if (!skip_unpack) {
1393 try {
1395 .arg(query->getRemoteAddr().toText())
1396 .arg(query->getLocalAddr().toText())
1397 .arg(query->getIface());
1398 query->unpack();
1399 } catch (const SkipRemainingOptionsError& e) {
1400 // An option failed to unpack but we are to attempt to process it
1401 // anyway. Log it and let's hope for the best.
1404 .arg(query->getLabel())
1405 .arg(e.what());
1406 } catch (const std::exception& e) {
1407 // Failed to parse the packet.
1409 .arg(query->getLabel())
1410 .arg(query->getRemoteAddr().toText())
1411 .arg(query->getLocalAddr().toText())
1412 .arg(query->getIface())
1413 .arg(e.what())
1414 .arg(query->getHWAddrLabel());
1415
1416 // Increase the statistics of parse failures and dropped packets.
1417 isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
1418 static_cast<int64_t>(1));
1419 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1420 static_cast<int64_t>(1));
1421 return (Pkt4Ptr());
1422 }
1423 }
1424
1425 // Classify can emit INFO logs so help to track the query.
1427 .arg(query->getLabel());
1428
1429 // Update statistics accordingly for received packet.
1430 processStatsReceived(query);
1431
1432 // Recover stashed RAI from client address lease.
1433 try {
1435 } catch (const std::exception&) {
1436 // Ignore exceptions.
1437 }
1438
1439 // Assign this packet to one or more classes if needed. We need to do
1440 // this before calling accept(), because getSubnet4() may need client
1441 // class information.
1442 classifyPacket(query);
1443
1444 // Now it is classified the deferred unpacking can be done.
1445 deferredUnpack(query);
1446
1447 // Check whether the message should be further processed or discarded.
1448 // There is no need to log anything here. This function logs by itself.
1449 if (!accept(query)) {
1450 // Increase the statistic of dropped packets.
1451 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1452 static_cast<int64_t>(1));
1453 return (Pkt4Ptr());
1454 }
1455
1456 // We have sanity checked (in accept() that the Message Type option
1457 // exists, so we can safely get it here.
1458 int type = query->getType();
1460 .arg(query->getLabel())
1461 .arg(query->getName())
1462 .arg(type)
1463 .arg(query->getRemoteAddr())
1464 .arg(query->getLocalAddr())
1465 .arg(query->getIface());
1467 .arg(query->getLabel())
1468 .arg(query->toText());
1469
1470 // Let's execute all callouts registered for pkt4_receive
1471 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_receive_)) {
1472 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1473
1474 // Use the RAII wrapper to make sure that the callout handle state is
1475 // reset when this object goes out of scope. All hook points must do
1476 // it to prevent possible circular dependency between the callout
1477 // handle and its arguments.
1478 ScopedCalloutHandleState callout_handle_state(callout_handle);
1479
1480 // Enable copying options from the packet within hook library.
1481 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1482
1483 // Pass incoming packet as argument
1484 callout_handle->setArgument("query4", query);
1485
1486 // Call callouts
1487 HooksManager::callCallouts(Hooks.hook_index_pkt4_receive_,
1488 *callout_handle);
1489
1490 // Callouts decided to skip the next processing step. The next
1491 // processing step would be to process the packet, so skip at this
1492 // stage means drop.
1493 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
1494 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
1497 .arg(query->getLabel());
1498 return (Pkt4Ptr());
1499 }
1500
1501 callout_handle->getArgument("query4", query);
1502 }
1503
1504 // Check the DROP special class.
1505 if (query->inClass("DROP")) {
1507 .arg(query->getHWAddrLabel())
1508 .arg(query->toText());
1509 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1510 static_cast<int64_t>(1));
1511 return (Pkt4Ptr());
1512 }
1513
1514 return (processDhcp4Query(query, allow_answer_park));
1515}
1516
1517void
1519 bool allow_answer_park) {
1520 try {
1521 Pkt4Ptr rsp = processDhcp4Query(query, allow_answer_park);
1522 if (!rsp) {
1523 return;
1524 }
1525
1526 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1527 processPacketBufferSend(callout_handle, rsp);
1528 } catch (const std::exception& e) {
1530 .arg(query->getLabel())
1531 .arg(e.what());
1532 } catch (...) {
1534 }
1535}
1536
1537Pkt4Ptr
1538Dhcpv4Srv::processDhcp4Query(Pkt4Ptr query, bool allow_answer_park) {
1539 // Create a client race avoidance RAII handler.
1540 ClientHandler client_handler;
1541
1542 // Check for lease modifier queries from the same client being processed.
1543 if (MultiThreadingMgr::instance().getMode() &&
1544 ((query->getType() == DHCPDISCOVER) ||
1545 (query->getType() == DHCPREQUEST) ||
1546 (query->getType() == DHCPRELEASE) ||
1547 (query->getType() == DHCPDECLINE))) {
1548 ContinuationPtr cont =
1550 this, query, allow_answer_park));
1551 if (!client_handler.tryLock(query, cont)) {
1552 return (Pkt4Ptr());
1553 }
1554 }
1555
1557 if (!earlyGHRLookup(query, ctx)) {
1558 return (Pkt4Ptr());
1559 }
1560
1561 try {
1562 sanityCheck(query);
1563 if ((query->getType() == DHCPDISCOVER) ||
1564 (query->getType() == DHCPREQUEST) ||
1565 (query->getType() == DHCPINFORM)) {
1566 bool drop = false;
1567 ctx->subnet_ = selectSubnet(query, drop, false, allow_answer_park);
1568 // Stop here if selectSubnet decided to drop the packet
1569 if (drop) {
1570 return (Pkt4Ptr());
1571 }
1572 }
1573 } catch (const std::exception& e) {
1574
1575 // Catch-all exception (we used to call only isc::Exception, but
1576 // std::exception could potentially be raised and if we don't catch
1577 // it here, it would be caught in main() and the process would
1578 // terminate). Just log the problem and ignore the packet.
1579 // (The problem is logged as a debug message because debug is
1580 // disabled by default - it prevents a DDOS attack based on the
1581 // sending of problem packets.)
1583 .arg(query->getLabel())
1584 .arg(e.what());
1585
1586 // Increase the statistic of dropped packets.
1587 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1588 static_cast<int64_t>(1));
1589 }
1590
1591 return (processLocalizedQuery4(ctx, allow_answer_park));
1592}
1593
1594void
1597 bool allow_answer_park) {
1598 try {
1599 Pkt4Ptr rsp = processLocalizedQuery4(ctx, allow_answer_park);
1600 if (!rsp) {
1601 return;
1602 }
1603
1604 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1605
1606 processPacketBufferSend(callout_handle, rsp);
1607 } catch (const std::exception& e) {
1609 .arg(query->getLabel())
1610 .arg(e.what());
1611 } catch (...) {
1613 }
1614}
1615
1616void
1618 bool allow_answer_park) {
1619 // Initialize context.
1621 initContext0(query, ctx);
1622
1623 // Subnet is cached in the callout context associated to the query.
1624 try {
1625 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1626 callout_handle->getContext("subnet4", ctx->subnet_);
1627 } catch (const Exception&) {
1628 // No subnet, leave it to null...
1629 }
1630
1631 processLocalizedQuery4AndSendResponse(query, ctx, allow_answer_park);
1632}
1633
1634Pkt4Ptr
1636 bool allow_answer_park) {
1637 if (!ctx) {
1638 isc_throw(Unexpected, "null context");
1639 }
1640 Pkt4Ptr query = ctx->query_;
1641 Pkt4Ptr rsp;
1642 try {
1643 switch (query->getType()) {
1644 case DHCPDISCOVER:
1645 rsp = processDiscover(query, ctx);
1646 break;
1647
1648 case DHCPREQUEST:
1649 // Note that REQUEST is used for many things in DHCPv4: for
1650 // requesting new leases, renewing existing ones and even
1651 // for rebinding.
1652 rsp = processRequest(query, ctx);
1653 break;
1654
1655 case DHCPRELEASE:
1656 processRelease(query, ctx);
1657 break;
1658
1659 case DHCPDECLINE:
1660 processDecline(query, ctx);
1661 break;
1662
1663 case DHCPINFORM:
1664 rsp = processInform(query, ctx);
1665 break;
1666
1667 default:
1668 // Only action is to output a message if debug is enabled,
1669 // and that is covered by the debug statement before the
1670 // "switch" statement.
1671 ;
1672 }
1673 } catch (const std::exception& e) {
1674
1675 // Catch-all exception (we used to call only isc::Exception, but
1676 // std::exception could potentially be raised and if we don't catch
1677 // it here, it would be caught in main() and the process would
1678 // terminate). Just log the problem and ignore the packet.
1679 // (The problem is logged as a debug message because debug is
1680 // disabled by default - it prevents a DDOS attack based on the
1681 // sending of problem packets.)
1683 .arg(query->getLabel())
1684 .arg(e.what());
1685
1686 // Increase the statistic of dropped packets.
1687 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1688 static_cast<int64_t>(1));
1689 }
1690
1691 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1692 if (ctx) {
1693 // leases4_committed and lease4_offer callouts are treated in the same way,
1694 // so prepare correct set of variables basing on the packet context.
1695 int hook_idx = Hooks.hook_index_leases4_committed_;
1696 std::string hook_label = "leases4_committed";
1700 if (ctx->fake_allocation_) {
1701 hook_idx = Hooks.hook_index_lease4_offer_;
1702 hook_label = "lease4_offer";
1703 pkt_park_msg = DHCP4_HOOK_LEASE4_OFFER_PARK;
1704 pkt_drop_msg = DHCP4_HOOK_LEASE4_OFFER_DROP;
1705 parking_lot_full_msg = DHCP4_HOOK_LEASE4_OFFER_PARKING_LOT_FULL;
1706 }
1707
1708 if (HooksManager::calloutsPresent(hook_idx)) {
1709 // The ScopedCalloutHandleState class which guarantees that the task
1710 // is added to the thread pool after the response is reset (if needed)
1711 // and CalloutHandle state is reset. In ST it does nothing.
1712 // A smart pointer is used to store the ScopedCalloutHandleState so that
1713 // a copy of the pointer is created by the lambda and only on the
1714 // destruction of the last reference the task is added.
1715 // In MT there are 2 cases:
1716 // 1. packet is unparked before current thread smart pointer to
1717 // ScopedCalloutHandleState is destroyed:
1718 // - the lambda uses the smart pointer to set the callout which adds the
1719 // task, but the task is added after ScopedCalloutHandleState is
1720 // destroyed, on the destruction of the last reference which is held
1721 // by the current thread.
1722 // 2. packet is unparked after the current thread smart pointer to
1723 // ScopedCalloutHandleState is destroyed:
1724 // - the current thread reference to ScopedCalloutHandleState is
1725 // destroyed, but the reference in the lambda keeps it alive until
1726 // the lambda is called and the last reference is released, at which
1727 // time the task is actually added.
1728 // Use the RAII wrapper to make sure that the callout handle state is
1729 // reset when this object goes out of scope. All hook points must do
1730 // it to prevent possible circular dependency between the callout
1731 // handle and its arguments.
1732 std::shared_ptr<ScopedCalloutHandleState> callout_handle_state =
1733 std::make_shared<ScopedCalloutHandleState>(callout_handle);
1734
1735 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1736
1737 // Also pass the corresponding query packet as argument
1738 callout_handle->setArgument("query4", query);
1739
1740 // Also pass the corresponding response packet as argument
1741 ScopedEnableOptionsCopy<Pkt4> response4_options_copy(rsp);
1742 callout_handle->setArgument("response4", rsp);
1743
1744 Lease4CollectionPtr new_leases(new Lease4Collection());
1745 // Filter out the new lease if it was reused so not committed.
1746 if (ctx->new_lease_ && (ctx->new_lease_->reuseable_valid_lft_ == 0)) {
1747 new_leases->push_back(ctx->new_lease_);
1748 }
1749 callout_handle->setArgument("leases4", new_leases);
1750
1751 if (ctx->fake_allocation_) {
1752 // Arguments required only for lease4_offer callout.
1753 callout_handle->setArgument("offer_lifetime", ctx->offer_lft_);
1754 callout_handle->setArgument("old_lease", ctx->old_lease_);
1755 callout_handle->setArgument("host", ctx->currentHost());
1756 } else {
1757 // Arguments required only for leases4_committed callout.
1758 Lease4CollectionPtr deleted_leases(new Lease4Collection());
1759 if (ctx->old_lease_) {
1760 if ((!ctx->new_lease_) || (ctx->new_lease_->addr_ != ctx->old_lease_->addr_)) {
1761 deleted_leases->push_back(ctx->old_lease_);
1762 }
1763 }
1764 callout_handle->setArgument("deleted_leases4", deleted_leases);
1765 }
1766
1767 if (allow_answer_park) {
1768 auto const tpl(parkingLimitExceeded(hook_label));
1769 bool const exceeded(get<0>(tpl));
1770 if (exceeded) {
1771 uint32_t const limit(get<1>(tpl));
1772 // We can't park it so we're going to throw it on the floor.
1773 LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING, parking_lot_full_msg)
1774 .arg(limit)
1775 .arg(query->getLabel());
1776 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1777 static_cast<int64_t>(1));
1778 return (Pkt4Ptr());
1779 }
1780
1781 // We proactively park the packet. We'll unpark it without invoking
1782 // the callback (i.e. drop) unless the callout status is set to
1783 // NEXT_STEP_PARK. Otherwise the callback we bind here will be
1784 // executed when the hook library unparks the packet.
1786 hook_label, query,
1787 [this, callout_handle, query, rsp, callout_handle_state, hook_idx, ctx]() mutable {
1788 if (hook_idx == Hooks.hook_index_lease4_offer_) {
1789 bool offer_address_in_use = false;
1790 try {
1791 callout_handle->getArgument("offer_address_in_use", offer_address_in_use);
1792 } catch (const NoSuchArgument& ex) {
1794 .arg(query->getLabel())
1795 .arg(ex.what());
1796 }
1797
1798 if (offer_address_in_use) {
1799 Lease4Ptr lease = ctx->new_lease_;
1800 bool lease_exists = (ctx->offer_lft_ > 0);
1801 if (MultiThreadingMgr::instance().getMode()) {
1802 typedef function<void()> CallBack;
1803 // We need to pass in the lease and flag as the callback handle state
1804 // gets reset prior to the invocation of the on_completion_ callback.
1805 boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(
1806 std::bind(&Dhcpv4Srv::serverDeclineNoThrow, this,
1807 callout_handle, query, lease, lease_exists));
1808 callout_handle_state->on_completion_ = [call_back]() {
1810 };
1811 } else {
1812 serverDecline(callout_handle, query, lease, lease_exists);
1813 }
1814
1815 return;
1816 }
1817 }
1818
1819 // Send the response to the client.
1820 if (MultiThreadingMgr::instance().getMode()) {
1821 typedef function<void()> CallBack;
1822 boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(
1823 std::bind(&Dhcpv4Srv::sendResponseNoThrow, this, callout_handle,
1824 query, rsp, ctx->subnet_));
1825 callout_handle_state->on_completion_ = [call_back]() {
1827 };
1828 } else {
1829 processPacketPktSend(callout_handle, query, rsp, ctx->subnet_);
1830 processPacketBufferSend(callout_handle, rsp);
1831 }
1832 });
1833 }
1834
1835 try {
1836 // Call all installed callouts
1837 HooksManager::callCallouts(hook_idx, *callout_handle);
1838 } catch (...) {
1839 // Make sure we don't orphan a parked packet.
1840 if (allow_answer_park) {
1841 HooksManager::drop(hook_label, query);
1842 }
1843
1844 throw;
1845 }
1846
1847 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) &&
1848 allow_answer_park) {
1849 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, pkt_park_msg)
1850 .arg(query->getLabel());
1851 // Since the hook library(ies) are going to do the unparking, then
1852 // reset the pointer to the response to indicate to the caller that
1853 // it should return, as the packet processing will continue via
1854 // the callback.
1855 rsp.reset();
1856 } else {
1857 // Drop the park job on the packet, it isn't needed.
1858 HooksManager::drop(hook_label, query);
1859 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1861 .arg(query->getLabel());
1862 rsp.reset();
1863 }
1864 }
1865 }
1866 }
1867
1868 // If we have a response prep it for shipment.
1869 if (rsp) {
1870 ConstSubnet4Ptr subnet = (ctx ? ctx->subnet_ : ConstSubnet4Ptr());
1871 processPacketPktSend(callout_handle, query, rsp, subnet);
1872 }
1873 return (rsp);
1874}
1875
1876void
1878 Pkt4Ptr& query, Pkt4Ptr& rsp,
1879 ConstSubnet4Ptr& subnet) {
1880 try {
1881 processPacketPktSend(callout_handle, query, rsp, subnet);
1882 processPacketBufferSend(callout_handle, rsp);
1883 } catch (const std::exception& e) {
1885 .arg(query->getLabel())
1886 .arg(e.what());
1887 } catch (...) {
1889 }
1890}
1891
1892void
1894 Pkt4Ptr& query, Pkt4Ptr& rsp,
1895 ConstSubnet4Ptr& subnet) {
1896 query->addPktEvent("process_completed");
1897 if (!rsp) {
1898 return;
1899 }
1900
1901 // Specifies if server should do the packing
1902 bool skip_pack = false;
1903
1904 // Execute all callouts registered for pkt4_send
1905 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) {
1906
1907 // Use the RAII wrapper to make sure that the callout handle state is
1908 // reset when this object goes out of scope. All hook points must do
1909 // it to prevent possible circular dependency between the callout
1910 // handle and its arguments.
1911 ScopedCalloutHandleState callout_handle_state(callout_handle);
1912
1913 // Enable copying options from the query and response packets within
1914 // hook library.
1915 ScopedEnableOptionsCopy<Pkt4> query_resp_options_copy(query, rsp);
1916
1917 // Pass incoming packet as argument
1918 callout_handle->setArgument("query4", query);
1919
1920 // Set our response
1921 callout_handle->setArgument("response4", rsp);
1922
1923 // Pass in the selected subnet.
1924 callout_handle->setArgument("subnet4", subnet);
1925
1926 // Call all installed callouts
1927 HooksManager::callCallouts(Hooks.hook_index_pkt4_send_,
1928 *callout_handle);
1929
1930 // Callouts decided to skip the next processing step. The next
1931 // processing step would be to pack the packet (create wire data).
1932 // That step will be skipped if any callout sets skip flag.
1933 // It essentially means that the callout already did packing,
1934 // so the server does not have to do it again.
1935 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1937 .arg(query->getLabel());
1938 skip_pack = true;
1939 }
1940
1942 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1944 .arg(rsp->getLabel());
1945 rsp.reset();
1946 return;
1947 }
1948 }
1949
1950 if (!skip_pack) {
1951 try {
1953 .arg(rsp->getLabel());
1954 rsp->pack();
1955 } catch (const std::exception& e) {
1957 .arg(rsp->getLabel())
1958 .arg(e.what());
1959 }
1960 }
1961}
1962
1963void
1965 Pkt4Ptr& rsp) {
1966 if (!rsp) {
1967 return;
1968 }
1969
1970 try {
1971 // Now all fields and options are constructed into output wire buffer.
1972 // Option objects modification does not make sense anymore. Hooks
1973 // can only manipulate wire buffer at this stage.
1974 // Let's execute all callouts registered for buffer4_send
1975 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
1976
1977 // Use the RAII wrapper to make sure that the callout handle state is
1978 // reset when this object goes out of scope. All hook points must do
1979 // it to prevent possible circular dependency between the callout
1980 // handle and its arguments.
1981 ScopedCalloutHandleState callout_handle_state(callout_handle);
1982
1983 // Enable copying options from the packet within hook library.
1984 ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
1985
1986 // Pass incoming packet as argument
1987 callout_handle->setArgument("response4", rsp);
1988
1989 // Call callouts
1990 HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
1991 *callout_handle);
1992
1993 // Callouts decided to skip the next processing step. The next
1994 // processing step would be to parse the packet, so skip at this
1995 // stage means drop.
1996 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
1997 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
2000 .arg(rsp->getLabel());
2001 return;
2002 }
2003
2004 callout_handle->getArgument("response4", rsp);
2005 }
2006
2008 .arg(rsp->getLabel())
2009 .arg(rsp->getName())
2010 .arg(static_cast<int>(rsp->getType()))
2011 .arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
2012 .arg(rsp->getLocalPort())
2013 .arg(rsp->getRemoteAddr())
2014 .arg(rsp->getRemotePort())
2015 .arg(rsp->getIface().empty() ? "to be determined from routing" :
2016 rsp->getIface());
2017
2020 .arg(rsp->getLabel())
2021 .arg(rsp->getName())
2022 .arg(static_cast<int>(rsp->getType()))
2023 .arg(rsp->toText());
2024 sendPacket(rsp);
2025
2026 // Update statistics accordingly for sent packet.
2027 processStatsSent(rsp);
2028
2029 } catch (const std::exception& e) {
2031 .arg(rsp->getLabel())
2032 .arg(e.what());
2033 }
2034}
2035
2036string
2038 if (!srvid) {
2039 isc_throw(BadValue, "NULL pointer passed to srvidToString()");
2040 }
2041 boost::shared_ptr<Option4AddrLst> generated =
2042 boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
2043 if (!srvid) {
2044 isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
2045 }
2046
2047 Option4AddrLst::AddressContainer addrs = generated->getAddresses();
2048 if (addrs.size() != 1) {
2049 isc_throw(BadValue, "Malformed option passed to srvidToString(). "
2050 << "Expected to contain a single IPv4 address.");
2051 }
2052
2053 return (addrs[0].toText());
2054}
2055
2056void
2058
2059 // Do not append generated server identifier if there is one appended already.
2060 // This is when explicitly configured server identifier option is present.
2061 if (ex.getResponse()->getOption(DHO_DHCP_SERVER_IDENTIFIER)) {
2062 return;
2063 }
2064
2065 // Use local address on which the packet has been received as a
2066 // server identifier. In some cases it may be a different address,
2067 // e.g. broadcast packet or DHCPv4o6 packet.
2068 IOAddress local_addr = ex.getQuery()->getLocalAddr();
2069 Pkt4Ptr query = ex.getQuery();
2070
2071 if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
2072 local_addr = IfaceMgr::instance().getSocket(query).addr_;
2073 }
2074
2075 static const OptionDefinition& server_id_def = LibDHCP::DHO_DHCP_SERVER_IDENTIFIER_DEF();
2076 OptionCustomPtr opt_srvid(new OptionCustom(server_id_def, Option::V4));
2077 opt_srvid->writeAddress(local_addr);
2078 ex.getResponse()->addOption(opt_srvid);
2079}
2080
2081void
2083 CfgOptionList& co_list = ex.getCfgOptionList();
2084
2085 // Retrieve subnet.
2086 ConstSubnet4Ptr subnet = ex.getContext()->subnet_;
2087 if (!subnet) {
2088 // All methods using the CfgOptionList object return soon when
2089 // there is no subnet so do the same
2090 return;
2091 }
2092
2093 // Firstly, host specific options.
2094 const ConstHostPtr& host = ex.getContext()->currentHost();
2095 if (host && !host->getCfgOption4()->empty()) {
2096 co_list.push_back(host->getCfgOption4());
2097 }
2098
2099 // Secondly, pool specific options.
2100 Pkt4Ptr resp = ex.getResponse();
2102 if (resp) {
2103 addr = resp->getYiaddr();
2104 }
2105 if (!addr.isV4Zero()) {
2106 PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
2107 if (pool && !pool->getCfgOption()->empty()) {
2108 co_list.push_back(pool->getCfgOption());
2109 }
2110 }
2111
2112 // Thirdly, subnet configured options.
2113 if (!subnet->getCfgOption()->empty()) {
2114 co_list.push_back(subnet->getCfgOption());
2115 }
2116
2117 // Fourthly, shared network specific options.
2118 SharedNetwork4Ptr network;
2119 subnet->getSharedNetwork(network);
2120 if (network && !network->getCfgOption()->empty()) {
2121 co_list.push_back(network->getCfgOption());
2122 }
2123
2124 // Each class in the incoming packet
2125 const ClientClasses& classes = ex.getQuery()->getClasses();
2126 for (auto const& cclass : classes) {
2127 // Find the client class definition for this class
2129 getClientClassDictionary()->findClass(cclass);
2130 if (!ccdef) {
2131 // Not found: the class is built-in or not configured
2132 if (!isClientClassBuiltIn(cclass)) {
2134 .arg(ex.getQuery()->getLabel())
2135 .arg(cclass);
2136 }
2137 // Skip it
2138 continue;
2139 }
2140
2141 if (ccdef->getCfgOption()->empty()) {
2142 // Skip classes which don't configure options
2143 continue;
2144 }
2145
2146 co_list.push_back(ccdef->getCfgOption());
2147 }
2148
2149 // Last global options
2150 if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
2151 co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
2152 }
2153}
2154
2155void
2157 // Get the subnet relevant for the client. We will need it
2158 // to get the options associated with it.
2159 ConstSubnet4Ptr subnet = ex.getContext()->subnet_;
2160 // If we can't find the subnet for the client there is no way
2161 // to get the options to be sent to a client. We don't log an
2162 // error because it will be logged by the assignLease method
2163 // anyway.
2164 if (!subnet) {
2165 return;
2166 }
2167
2168 // Unlikely short cut
2169 const CfgOptionList& co_list = ex.getCfgOptionList();
2170 if (co_list.empty()) {
2171 return;
2172 }
2173
2174 Pkt4Ptr query = ex.getQuery();
2175 Pkt4Ptr resp = ex.getResponse();
2176 set<uint8_t> requested_opts;
2177
2178 // try to get the 'Parameter Request List' option which holds the
2179 // codes of requested options.
2180 OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
2182
2183 // Get the list of options that client requested.
2184 if (option_prl) {
2185 for (uint16_t code : option_prl->getValues()) {
2186 static_cast<void>(requested_opts.insert(code));
2187 }
2188 }
2189
2190 std::set<uint8_t> cancelled_opts;
2191 const auto& cclasses = query->getClasses();
2192
2193 // Iterate on the configured option list to add persistent and
2194 // cancelled options.
2195 for (auto const& copts : co_list) {
2196 const OptionContainerPtr& opts = copts->getAll(DHCP4_OPTION_SPACE);
2197 if (!opts) {
2198 continue;
2199 }
2200 // Get persistent options.
2201 const OptionContainerPersistIndex& pidx = opts->get<2>();
2202 const OptionContainerPersistRange& prange = pidx.equal_range(true);
2203 BOOST_FOREACH(auto const& desc, prange) {
2204 // Add the persistent option code to requested options.
2205 if (desc.option_) {
2206 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2207 static_cast<void>(requested_opts.insert(code));
2208 }
2209 }
2210 // Get cancelled options.
2211 const OptionContainerCancelIndex& cidx = opts->get<5>();
2212 const OptionContainerCancelRange& crange = cidx.equal_range(true);
2213 BOOST_FOREACH(auto const& desc, crange) {
2214 // Add the cancelled option code to cancelled options.
2215 if (desc.option_) {
2216 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2217 static_cast<void>(cancelled_opts.insert(code));
2218 }
2219 }
2220 }
2221
2222 // For each requested option code get the first instance of the option
2223 // to be returned to the client.
2224 for (uint8_t opt : requested_opts) {
2225 if (cancelled_opts.count(opt) > 0) {
2226 continue;
2227 }
2228 // Skip special cases: DHO_VIVSO_SUBOPTIONS.
2229 if (opt == DHO_VIVSO_SUBOPTIONS) {
2230 continue;
2231 }
2232 // Add nothing when it is already there.
2233 if (!resp->getOption(opt)) {
2234 // Iterate on the configured option list
2235 for (auto const& copts : co_list) {
2237 opt, cclasses);
2238 if (desc.option_) {
2239 // Got it: add it and jump to the outer loop
2240 resp->addOption(desc.option_);
2241 break;
2242 }
2243 }
2244 }
2245 }
2246
2247 // Special cases for vendor class and options which are identified
2248 // by the code/type and the vendor/enterprise id vs. the code/type only.
2249 if ((requested_opts.count(DHO_VIVCO_SUBOPTIONS) > 0) &&
2250 (cancelled_opts.count(DHO_VIVCO_SUBOPTIONS) == 0)) {
2251 // Keep vendor ids which are already in the response to insert
2252 // VIVCO options at most once per vendor.
2253 set<uint32_t> vendor_ids;
2254 // Get what already exists in the response.
2255 for (auto const& opt : resp->getOptions(DHO_VIVCO_SUBOPTIONS)) {
2256 OptionVendorClassPtr vendor_opts;
2257 vendor_opts = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
2258 if (vendor_opts) {
2259 uint32_t vendor_id = vendor_opts->getVendorId();
2260 static_cast<void>(vendor_ids.insert(vendor_id));
2261 }
2262 }
2263 // Iterate on the configured option list.
2264 for (auto const& copts : co_list) {
2265 for (auto const& desc : copts->getList(DHCP4_OPTION_SPACE,
2267 if (!desc.option_ || !desc.allowedForClientClasses(cclasses)) {
2268 continue;
2269 }
2270 OptionVendorClassPtr vendor_opts =
2271 boost::dynamic_pointer_cast<OptionVendorClass>(desc.option_);
2272 if (!vendor_opts) {
2273 continue;
2274 }
2275 // Is the vendor id already in the response?
2276 uint32_t vendor_id = vendor_opts->getVendorId();
2277 if (vendor_ids.count(vendor_id) > 0) {
2278 continue;
2279 }
2280 // Got it: add it.
2281 resp->Pkt::addOption(desc.option_);
2282 static_cast<void>(vendor_ids.insert(vendor_id));
2283 }
2284 }
2285 }
2286
2287 if ((requested_opts.count(DHO_VIVSO_SUBOPTIONS) > 0) &&
2288 (cancelled_opts.count(DHO_VIVSO_SUBOPTIONS) == 0)) {
2289 // Keep vendor ids which are already in the response to insert
2290 // VIVSO options at most once per vendor.
2291 set<uint32_t> vendor_ids;
2292 // Get what already exists in the response.
2293 for (auto const& opt : resp->getOptions(DHO_VIVSO_SUBOPTIONS)) {
2294 OptionVendorPtr vendor_opts;
2295 vendor_opts = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
2296 if (vendor_opts) {
2297 uint32_t vendor_id = vendor_opts->getVendorId();
2298 static_cast<void>(vendor_ids.insert(vendor_id));
2299 }
2300 }
2301 // Iterate on the configured option list
2302 for (auto const& copts : co_list) {
2303 for (auto const& desc : copts->getList(DHCP4_OPTION_SPACE,
2305 if (!desc.option_ || !desc.allowedForClientClasses(cclasses)) {
2306 continue;
2307 }
2308 OptionVendorPtr vendor_opts =
2309 boost::dynamic_pointer_cast<OptionVendor>(desc.option_);
2310 if (!vendor_opts) {
2311 continue;
2312 }
2313 // Is the vendor id already in the response?
2314 uint32_t vendor_id = vendor_opts->getVendorId();
2315 if (vendor_ids.count(vendor_id) > 0) {
2316 continue;
2317 }
2318 // Append a fresh vendor option as the next method should
2319 // add suboptions to it.
2320 vendor_opts.reset(new OptionVendor(Option::V4, vendor_id));
2321 resp->Pkt::addOption(vendor_opts);
2322 static_cast<void>(vendor_ids.insert(vendor_id));
2323 }
2324 }
2325 }
2326}
2327
2328void
2330 // Get the configured subnet suitable for the incoming packet.
2331 ConstSubnet4Ptr subnet = ex.getContext()->subnet_;
2332
2333 const CfgOptionList& co_list = ex.getCfgOptionList();
2334
2335 // Leave if there is no subnet matching the incoming packet.
2336 // There is no need to log the error message here because
2337 // it will be logged in the assignLease() when it fails to
2338 // pick the suitable subnet. We don't want to duplicate
2339 // error messages in such case.
2340 //
2341 // Also, if there's no options to possibly assign, give up.
2342 if (!subnet || co_list.empty()) {
2343 return;
2344 }
2345
2346 Pkt4Ptr query = ex.getQuery();
2347 Pkt4Ptr resp = ex.getResponse();
2348 set<uint32_t> vendor_ids;
2349
2350 // The server could have provided the option using client classification or
2351 // hooks. If there're vendor info options in the response already, use them.
2352 map<uint32_t, OptionVendorPtr> vendor_rsps;
2353 for (auto const& opt : resp->getOptions(DHO_VIVSO_SUBOPTIONS)) {
2354 OptionVendorPtr vendor_rsp;
2355 vendor_rsp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
2356 if (vendor_rsp) {
2357 uint32_t vendor_id = vendor_rsp->getVendorId();
2358 vendor_rsps[vendor_id] = vendor_rsp;
2359 static_cast<void>(vendor_ids.insert(vendor_id));
2360 }
2361 }
2362
2363 // Next, try to get the vendor-id from the client packet's
2364 // vendor-specific information option (125).
2365 map<uint32_t, OptionVendorPtr> vendor_reqs;
2366 for (auto const& opt : query->getOptions(DHO_VIVSO_SUBOPTIONS)) {
2367 OptionVendorPtr vendor_req;
2368 vendor_req = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
2369 if (vendor_req) {
2370 uint32_t vendor_id = vendor_req->getVendorId();
2371 vendor_reqs[vendor_id] = vendor_req;
2372 static_cast<void>(vendor_ids.insert(vendor_id));
2373 }
2374 }
2375
2376 // Finally, try to get the vendor-id from the client packet's
2377 // vendor-specific class option (124).
2378 for (auto const& opt : query->getOptions(DHO_VIVCO_SUBOPTIONS)) {
2379 OptionVendorClassPtr vendor_class;
2380 vendor_class = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
2381 if (vendor_class) {
2382 uint32_t vendor_id = vendor_class->getVendorId();
2383 static_cast<void>(vendor_ids.insert(vendor_id));
2384 }
2385 }
2386
2387 // If there's no vendor option in either request or response, then there's no way
2388 // to figure out what the vendor-id values are and we give up.
2389 if (vendor_ids.empty()) {
2390 return;
2391 }
2392
2393 map<uint32_t, set<uint8_t> > requested_opts;
2394
2395 // Let's try to get ORO within that vendor-option.
2396 // This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
2397 // different policies.
2399 if (vendor_reqs.count(VENDOR_ID_CABLE_LABS) > 0) {
2400 OptionVendorPtr vendor_req = vendor_reqs[VENDOR_ID_CABLE_LABS];
2401 OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V4_ORO);
2402 if (oro_generic) {
2403 // Vendor ID 4491 makes Kea look at DOCSIS3_V4_OPTION_DEFINITIONS
2404 // when parsing options. Based on that, oro_generic will have been
2405 // created as an OptionUint8Array, but might not be for other
2406 // vendor IDs.
2407 oro = boost::dynamic_pointer_cast<OptionUint8Array>(oro_generic);
2408 }
2409 if (oro) {
2410 set<uint8_t> oro_req_opts;
2411 for (uint8_t code : oro->getValues()) {
2412 static_cast<void>(oro_req_opts.insert(code));
2413 }
2414 requested_opts[VENDOR_ID_CABLE_LABS] = oro_req_opts;
2415 }
2416 }
2417
2418 const auto& cclasses = query->getClasses();
2419 for (uint32_t vendor_id : vendor_ids) {
2420
2421 std::set<uint8_t> cancelled_opts;
2422
2423 // Iterate on the configured option list to add persistent and
2424 // cancelled options,
2425 for (auto const& copts : co_list) {
2426 const OptionContainerPtr& opts = copts->getAll(vendor_id);
2427 if (!opts) {
2428 continue;
2429 }
2430
2431 // Get persistent options.
2432 const OptionContainerPersistIndex& pidx = opts->get<2>();
2433 const OptionContainerPersistRange& prange = pidx.equal_range(true);
2434 BOOST_FOREACH(auto const& desc, prange) {
2435 // Add the persistent option code to requested options.
2436 if (desc.option_) {
2437 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2438 static_cast<void>(requested_opts[vendor_id].insert(code));
2439 }
2440 }
2441
2442 // Get cancelled options.
2443 const OptionContainerCancelIndex& cidx = opts->get<5>();
2444 const OptionContainerCancelRange& crange = cidx.equal_range(true);
2445 BOOST_FOREACH(auto const& desc, crange) {
2446 // Add the cancelled option code to cancelled options.
2447 if (desc.option_) {
2448 uint8_t code = static_cast<uint8_t>(desc.option_->getType());
2449 static_cast<void>(cancelled_opts.insert(code));
2450 }
2451 }
2452 }
2453
2454 // If there is nothing to add don't do anything with this vendor.
2455 // This will explicitly not echo back vendor options from the request
2456 // that either correspond to a vendor not known to Kea even if the
2457 // option encapsulates data or there are no persistent options
2458 // configured for this vendor so Kea does not send any option back.
2459 if (requested_opts[vendor_id].empty()) {
2460 continue;
2461 }
2462
2463
2464 // It's possible that vivso was inserted already by client class or
2465 // a hook. If that is so, let's use it.
2466 OptionVendorPtr vendor_rsp;
2467 if (vendor_rsps.count(vendor_id) > 0) {
2468 vendor_rsp = vendor_rsps[vendor_id];
2469 } else {
2470 vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id));
2471 }
2472
2473 // Get the list of options that client requested.
2474 bool added = false;
2475
2476 for (uint8_t opt : requested_opts[vendor_id]) {
2477 if (cancelled_opts.count(opt) > 0) {
2478 continue;
2479 }
2480 if (!vendor_rsp->getOption(opt)) {
2481 for (auto const& copts : co_list) {
2482 OptionDescriptor desc = copts->allowedForClientClasses(vendor_id,
2483 opt, cclasses);
2484 if (desc.option_) {
2485 vendor_rsp->addOption(desc.option_);
2486 added = true;
2487 break;
2488 }
2489 }
2490 }
2491 }
2492
2493 // If we added some sub-options and the vendor opts option is not in
2494 // the response already, then add it.
2495 if (added && (vendor_rsps.count(vendor_id) == 0)) {
2496 resp->Pkt::addOption(vendor_rsp);
2497 }
2498 }
2499}
2500
2501void
2503 // Identify options that we always want to send to the
2504 // client (if they are configured).
2505 static const std::vector<uint16_t> required_options = {
2510
2511 // Get the subnet.
2512 ConstSubnet4Ptr subnet = ex.getContext()->subnet_;
2513 if (!subnet) {
2514 return;
2515 }
2516
2517 // Unlikely short cut
2518 const CfgOptionList& co_list = ex.getCfgOptionList();
2519 if (co_list.empty()) {
2520 return;
2521 }
2522
2523 Pkt4Ptr resp = ex.getResponse();
2524 const auto& cclasses = ex.getQuery()->getClasses();
2525
2526 // Try to find all 'required' options in the outgoing
2527 // message. Those that are not present will be added.
2528 for (auto const& required : required_options) {
2529 OptionPtr opt = resp->getOption(required);
2530 if (!opt) {
2531 // Check whether option has been configured.
2532 for (auto const& copts : co_list) {
2534 required, cclasses);
2535 if (desc.option_) {
2536 resp->addOption(desc.option_);
2537 break;
2538 }
2539 }
2540 }
2541 }
2542}
2543
2544void
2546 // It is possible that client has sent both Client FQDN and Hostname
2547 // option. In that the server should prefer Client FQDN option and
2548 // ignore the Hostname option.
2549 try {
2550 Pkt4Ptr query = ex.getQuery();
2551 Pkt4Ptr resp = ex.getResponse();
2552 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>
2553 (query->getOption(DHO_FQDN));
2554 if (fqdn) {
2556 .arg(query->getLabel());
2557 processClientFqdnOption(ex);
2558
2559 } else {
2562 .arg(query->getLabel());
2563 processHostnameOption(ex);
2564 }
2565
2566 // Based on the output option added to the response above, we figure out
2567 // the values for the hostname and dns flags to set in the context. These
2568 // will be used to populate the lease.
2569 std::string hostname;
2570 bool fqdn_fwd = false;
2571 bool fqdn_rev = false;
2572
2573 OptionStringPtr opt_hostname;
2574 fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
2575 if (fqdn) {
2576 hostname = fqdn->getDomainName();
2577 CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, fqdn_fwd, fqdn_rev);
2578 } else {
2579 opt_hostname = boost::dynamic_pointer_cast<OptionString>
2580 (resp->getOption(DHO_HOST_NAME));
2581
2582 if (opt_hostname) {
2583 hostname = opt_hostname->getValue();
2584 // DHO_HOST_NAME is string option which cannot be blank,
2585 // we use "." to know we should replace it with a fully
2586 // generated name. The local string variable needs to be
2587 // blank in logic below.
2588 if (hostname == ".") {
2589 hostname = "";
2590 }
2591
2594 if (ex.getContext()->getDdnsParams()->getEnableUpdates()) {
2595 fqdn_fwd = true;
2596 fqdn_rev = true;
2597 }
2598 }
2599 }
2600
2601 // Optionally, call a hook that may possibly override the decisions made
2602 // earlier.
2603 if (HooksManager::calloutsPresent(Hooks.hook_index_ddns4_update_)) {
2604 CalloutHandlePtr callout_handle = getCalloutHandle(query);
2605
2606 // Use the RAII wrapper to make sure that the callout handle state is
2607 // reset when this object goes out of scope. All hook points must do
2608 // it to prevent possible circular dependency between the callout
2609 // handle and its arguments.
2610 ScopedCalloutHandleState callout_handle_state(callout_handle);
2611
2612 // Setup the callout arguments.
2613 ConstSubnet4Ptr subnet = ex.getContext()->subnet_;
2614 callout_handle->setArgument("query4", query);
2615 callout_handle->setArgument("response4", resp);
2616 callout_handle->setArgument("subnet4", subnet);
2617 callout_handle->setArgument("hostname", hostname);
2618 callout_handle->setArgument("fwd-update", fqdn_fwd);
2619 callout_handle->setArgument("rev-update", fqdn_rev);
2620 callout_handle->setArgument("ddns-params", ex.getContext()->getDdnsParams());
2621
2622 // Call callouts
2623 HooksManager::callCallouts(Hooks.hook_index_ddns4_update_, *callout_handle);
2624
2625 // Let's get the parameters returned by hook.
2626 string hook_hostname;
2627 bool hook_fqdn_fwd = false;
2628 bool hook_fqdn_rev = false;
2629 callout_handle->getArgument("hostname", hook_hostname);
2630 callout_handle->getArgument("fwd-update", hook_fqdn_fwd);
2631 callout_handle->getArgument("rev-update", hook_fqdn_rev);
2632
2633 // If there's anything changed by the hook, log it and then update
2634 // the parameters.
2635 if ((hostname != hook_hostname) || (fqdn_fwd != hook_fqdn_fwd) ||
2636 (fqdn_rev != hook_fqdn_rev)) {
2638 .arg(hostname).arg(hook_hostname).arg(fqdn_fwd).arg(hook_fqdn_fwd)
2639 .arg(fqdn_rev).arg(hook_fqdn_rev);
2640 hostname = hook_hostname;
2641 fqdn_fwd = hook_fqdn_fwd;
2642 fqdn_rev = hook_fqdn_rev;
2643
2644 // If there's an outbound host-name option in the response we
2645 // need to updated it with the new host name.
2646 OptionStringPtr hostname_opt = boost::dynamic_pointer_cast<OptionString>
2647 (resp->getOption(DHO_HOST_NAME));
2648 if (hostname_opt) {
2649 hostname_opt->setValue(hook_hostname);
2650 }
2651
2652 // If there's an outbound FQDN option in the response we need
2653 // to update it with the new host name.
2654 fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
2655 if (fqdn) {
2656 fqdn->setDomainName(hook_hostname, Option4ClientFqdn::FULL);
2657 // Hook disabled updates, Set flags back to client accordingly.
2658 fqdn->setFlag(Option4ClientFqdn::FLAG_S, 0);
2659 fqdn->setFlag(Option4ClientFqdn::FLAG_N, 1);
2660 }
2661 }
2662 }
2663
2664 // Update the context
2665 auto ctx = ex.getContext();
2666 ctx->fwd_dns_update_ = fqdn_fwd;
2667 ctx->rev_dns_update_ = fqdn_rev;
2668 ctx->hostname_ = hostname;
2669
2670 } catch (const Exception& e) {
2671 // In some rare cases it is possible that the client's name processing
2672 // fails. For example, the Hostname option may be malformed, or there
2673 // may be an error in the server's logic which would cause multiple
2674 // attempts to add the same option to the response message. This
2675 // error message aggregates all these errors so they can be diagnosed
2676 // from the log. We don't want to throw an exception here because,
2677 // it will impact the processing of the whole packet. We rather want
2678 // the processing to continue, even if the client's name is wrong.
2680 .arg(ex.getQuery()->getLabel())
2681 .arg(e.what());
2682 }
2683}
2684
2685void
2686Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) {
2687 // Obtain the FQDN option from the client's message.
2688 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
2689 Option4ClientFqdn>(ex.getQuery()->getOption(DHO_FQDN));
2690
2692 .arg(ex.getQuery()->getLabel())
2693 .arg(fqdn->toText());
2694
2695 // Create the DHCPv4 Client FQDN Option to be included in the server's
2696 // response to a client.
2697 Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
2698
2699 // Set the server S, N, and O flags based on client's flags and
2700 // current configuration.
2702 d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp,
2703 *(ex.getContext()->getDdnsParams()));
2704 // Carry over the client's E flag.
2707
2708 if (ex.getContext()->currentHost() &&
2709 !ex.getContext()->currentHost()->getHostname().empty()) {
2710 fqdn_resp->setDomainName(d2_mgr.qualifyName(ex.getContext()->currentHost()->getHostname(),
2711 *(ex.getContext()->getDdnsParams()), true),
2713
2714 } else {
2715 // Adjust the domain name based on domain name value and type sent by the
2716 // client and current configuration.
2717 try {
2718 d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp,
2719 *(ex.getContext()->getDdnsParams()));
2720 } catch (const FQDNScrubbedEmpty& scrubbed) {
2722 .arg(ex.getQuery()->getLabel())
2723 .arg(scrubbed.what());
2724 return;
2725 }
2726 }
2727
2728 // Add FQDN option to the response message. Note that, there may be some
2729 // cases when server may choose not to include the FQDN option in a
2730 // response to a client. In such cases, the FQDN should be removed from the
2731 // outgoing message. In theory we could cease to include the FQDN option
2732 // in this function until it is confirmed that it should be included.
2733 // However, we include it here for simplicity. Functions used to acquire
2734 // lease for a client will scan the response message for FQDN and if it
2735 // is found they will take necessary actions to store the FQDN information
2736 // in the lease database as well as to generate NameChangeRequests to DNS.
2737 // If we don't store the option in the response message, we will have to
2738 // propagate it in the different way to the functions which acquire the
2739 // lease. This would require modifications to the API of this class.
2741 .arg(ex.getQuery()->getLabel())
2742 .arg(fqdn_resp->toText());
2743 ex.getResponse()->addOption(fqdn_resp);
2744}
2745
2746void
2747Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
2748 // Fetch D2 configuration.
2749 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
2750
2751 // Obtain the Hostname option from the client's message.
2752 OptionStringPtr opt_hostname = boost::dynamic_pointer_cast<OptionString>
2753 (ex.getQuery()->getOption(DHO_HOST_NAME));
2754
2755 if (opt_hostname) {
2757 .arg(ex.getQuery()->getLabel())
2758 .arg(opt_hostname->getValue());
2759 }
2760
2762
2763 // Hostname reservations take precedence over any other configuration,
2764 // i.e. DDNS configuration. If we have a reserved hostname we should
2765 // use it and send it back.
2766 if (ctx->currentHost() && !ctx->currentHost()->getHostname().empty()) {
2767 // Qualify if there is a suffix configured.
2768 std::string hostname = d2_mgr.qualifyName(ctx->currentHost()->getHostname(),
2769 *(ex.getContext()->getDdnsParams()), false);
2770 // Convert it to lower case.
2771 boost::algorithm::to_lower(hostname);
2773 .arg(ex.getQuery()->getLabel())
2774 .arg(hostname);
2775
2776 // Add it to the response
2777 OptionStringPtr opt_hostname_resp(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
2778 ex.getResponse()->addOption(opt_hostname_resp);
2779
2780 // We're done here.
2781 return;
2782 }
2783
2784 // There is no reservation for this client however there is still a
2785 // possibility that we'll have to send hostname option to this client
2786 // if the client has included hostname option or the configuration of
2787 // the server requires that we send the option regardless.
2788 D2ClientConfig::ReplaceClientNameMode replace_name_mode =
2789 ex.getContext()->getDdnsParams()->getReplaceClientNameMode();
2790
2791 // If we don't have a hostname then either we'll supply it or do nothing.
2792 if (!opt_hostname) {
2793 // If we're configured to supply it then add it to the response.
2794 // Use the root domain to signal later on that we should replace it.
2795 if (replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
2796 replace_name_mode == D2ClientConfig::RCM_WHEN_NOT_PRESENT) {
2799 .arg(ex.getQuery()->getLabel());
2800 OptionStringPtr opt_hostname_resp(new OptionString(Option::V4,
2802 "."));
2803 ex.getResponse()->addOption(opt_hostname_resp);
2804 }
2805
2806 return;
2807 }
2808
2809 // Client sent us a hostname option so figure out what to do with it.
2811 .arg(ex.getQuery()->getLabel())
2812 .arg(opt_hostname->getValue());
2813
2814 std::string hostname = isc::util::str::trim(opt_hostname->getValue());
2815 unsigned int label_count;
2816
2817 try {
2818 // Parsing into labels can throw on malformed content so we're
2819 // going to explicitly catch that here.
2820 label_count = OptionDataTypeUtil::getLabelCount(hostname);
2821 } catch (const std::exception& exc) {
2823 .arg(ex.getQuery()->getLabel())
2824 .arg(exc.what());
2825 return;
2826 }
2827
2828 // The hostname option sent by the client should be at least 1 octet long.
2829 // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
2832 if (label_count == 0) {
2834 .arg(ex.getQuery()->getLabel());
2835 return;
2836 }
2837
2838 // Stores the value we eventually use, so we can send it back.
2839 OptionStringPtr opt_hostname_resp;
2840
2841 // The hostname option may be unqualified or fully qualified. The lab_count
2842 // holds the number of labels for the name. The number of 1 means that
2843 // there is only root label "." (even for unqualified names, as the
2844 // getLabelCount function treats each name as a fully qualified one).
2845 // By checking the number of labels present in the hostname we may infer
2846 // whether client has sent the fully qualified or unqualified hostname.
2847
2848 if ((replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
2849 replace_name_mode == D2ClientConfig::RCM_WHEN_PRESENT)
2850 || label_count < 2) {
2851 // Set to root domain to signal later on that we should replace it.
2852 // DHO_HOST_NAME is a string option which cannot be empty.
2860 opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, "."));
2861 } else {
2862 // Sanitize the name the client sent us, if we're configured to do so.
2864 ex.getContext()->getDdnsParams()->getHostnameSanitizer();
2865
2866 if (sanitizer) {
2867 auto tmp = sanitizer->scrub(hostname);
2868 if (tmp.empty()) {
2870 .arg(ex.getQuery()->getLabel())
2871 .arg(hostname);
2872 return;
2873 }
2874
2875 hostname = tmp;
2876 }
2877
2878 // Convert hostname to lower case.
2879 boost::algorithm::to_lower(hostname);
2880
2881 if (label_count == 2) {
2882 // If there are two labels, it means that the client has specified
2883 // the unqualified name. We have to concatenate the unqualified name
2884 // with the domain name. The false value passed as a second argument
2885 // indicates that the trailing dot should not be appended to the
2886 // hostname. We don't want to append the trailing dot because
2887 // we don't know whether the hostname is partial or not and some
2888 // clients do not handle the hostnames with the trailing dot.
2889 opt_hostname_resp.reset(
2890 new OptionString(Option::V4, DHO_HOST_NAME,
2891 d2_mgr.qualifyName(hostname, *(ex.getContext()->getDdnsParams()),
2892 false)));
2893 } else {
2894 opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
2895 }
2896 }
2897
2899 .arg(ex.getQuery()->getLabel())
2900 .arg(opt_hostname_resp->getValue());
2901 ex.getResponse()->addOption(opt_hostname_resp);
2902}
2903
2904void
2906 const Lease4Ptr& old_lease,
2907 const DdnsParams& ddns_params) {
2908 if (!lease) {
2910 "NULL lease specified when creating NameChangeRequest");
2911 }
2912
2913 // Nothing to do if updates are not enabled.
2914 if (!ddns_params.getEnableUpdates()) {
2915 return;
2916 }
2917
2918 if ((lease->reuseable_valid_lft_ == 0) &&
2919 (!old_lease || ddns_params.getUpdateOnRenew() ||
2920 !lease->hasIdenticalFqdn(*old_lease))) {
2921 if (old_lease) {
2922 // Queue's up a remove of the old lease's DNS (if needed)
2923 queueNCR(CHG_REMOVE, old_lease);
2924 }
2925
2926 // We may need to generate the NameChangeRequest for the new lease. It
2927 // will be generated only if hostname is set and if forward or reverse
2928 // update has been requested.
2929 queueNCR(CHG_ADD, lease);
2930 }
2931}
2932
2933bool
2935 const ClientClasses& client_classes) {
2936 ConstSubnet4Ptr current_subnet = subnet;
2937 // Try subnets.
2938 while (current_subnet) {
2939 const ConstCfgOptionPtr& co = current_subnet->getCfgOption();
2940 if (!co->empty()) {
2941 OptionDescriptor desc = co->get(DHCP4_OPTION_SPACE,
2943 if (desc.option_) {
2944 subnet = current_subnet;
2945 return (true);
2946 }
2947 }
2948 current_subnet = current_subnet->getNextSubnet(subnet, client_classes);
2949 }
2950 // Try the shared network.
2951 SharedNetwork4Ptr network;
2952 subnet->getSharedNetwork(network);
2953 if (network) {
2954 const ConstCfgOptionPtr& co = network->getCfgOption();
2955 if (!co->empty()) {
2956 OptionDescriptor desc = co->get(DHCP4_OPTION_SPACE,
2958 if (desc.option_) {
2959 return (true);
2960 }
2961 }
2962 }
2963 return (false);
2964}
2965
2966void
2968 // Get the pointers to the query and the response messages.
2969 Pkt4Ptr query = ex.getQuery();
2970 Pkt4Ptr resp = ex.getResponse();
2971
2972 // Get the context.
2974
2975 // Subnet should have been already selected when the context was created.
2976 ConstSubnet4Ptr subnet = ctx->subnet_;
2977
2978 // "Fake" allocation is processing of DISCOVER message. We pretend to do an
2979 // allocation, but we do not put the lease in the database. That is ok,
2980 // because we do not guarantee that the user will get that exact lease. If
2981 // the user selects this server to do actual allocation (i.e. sends REQUEST)
2982 // it should include this hint. That will help us during the actual lease
2983 // allocation.
2984 bool fake_allocation = (query->getType() == DHCPDISCOVER);
2985
2986 if (subnet) {
2987 // Check if IPv6-Only Preferred was requested.
2988 OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
2990 if (option_prl) {
2991 auto const& requested_opts = option_prl->getValues();
2992 if ((std::find(requested_opts.cbegin(), requested_opts.cend(),
2993 DHO_V6_ONLY_PREFERRED) != requested_opts.cend()) &&
2994 assignZero(subnet, query->getClasses())) {
2995 ex.setIPv6OnlyPreferred(true);
2996 ctx->subnet_ = subnet;
2997 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
2998 if (!fake_allocation) {
2999 resp->setCiaddr(query->getCiaddr());
3000 }
3001 return;
3002 }
3003 }
3004 }
3005
3006 // Get the server identifier. It will be used to determine the state
3007 // of the client.
3008 OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
3009 OptionCustom>(query->getOption(DHO_DHCP_SERVER_IDENTIFIER));
3010
3011 // Check if the client has sent a requested IP address option or
3012 // ciaddr.
3013 OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
3014 OptionCustom>(query->getOption(DHO_DHCP_REQUESTED_ADDRESS));
3016 if (opt_requested_address) {
3017 hint = opt_requested_address->readAddress();
3018
3019 } else if (!query->getCiaddr().isV4Zero()) {
3020 hint = query->getCiaddr();
3021
3022 }
3023
3024 // This flag controls whether or not the server should respond to the clients
3025 // in the INIT-REBOOT state. We will initialize it to a configured value only
3026 // when the client is in that state.
3027 auto authoritative = false;
3028
3029 // If there is no server id and there is a Requested IP Address option
3030 // the client is in the INIT-REBOOT state in which the server has to
3031 // determine whether the client's notion of the address is correct
3032 // and whether the client is known, i.e., has a lease.
3033 auto init_reboot = (!fake_allocation && !opt_serverid && opt_requested_address);
3034 if (init_reboot) {
3036 .arg(query->getLabel())
3037 .arg(hint.toText());
3038
3039 // Find the authoritative flag configuration.
3040 if (subnet) {
3041 authoritative = subnet->getAuthoritative();
3042 } else {
3043 // If there is no subnet, use the global value.
3044 auto flag = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()->
3046 if (flag && (flag->getType() == data::Element::boolean)) {
3047 authoritative = flag->boolValue();
3048 }
3049 }
3050 } else if (fake_allocation) {
3052 .arg(query->getLabel())
3053 .arg(hint != IOAddress::IPV4_ZERO_ADDRESS() ? hint.toText() : "(no hint)");
3054 } else {
3056 .arg(query->getLabel())
3057 .arg(hint != IOAddress::IPV4_ZERO_ADDRESS() ? hint.toText() : "(no hint)");
3058 }
3059
3060 // If there is no subnet configuration for that client we ignore the
3061 // request from the INIT-REBOOT client if we're not authoritative, because
3062 // we don't know whether the network configuration is correct for this
3063 // client. We return DHCPNAK if we're authoritative, though.
3064 if (!subnet && (!init_reboot || authoritative)) {
3065 // This particular client is out of luck today. We do not have
3066 // information about the subnet he is connected to. This likely means
3067 // misconfiguration of the server (or some relays).
3068
3069 // Perhaps this should be logged on some higher level?
3071 .arg(query->getLabel())
3072 .arg(query->getRemoteAddr().toText())
3073 .arg(query->getName());
3074 resp->setType(DHCPNAK);
3075 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
3076 return;
3077 }
3078
3079 HWAddrPtr hwaddr = query->getHWAddr();
3080
3081 ConstSubnet4Ptr original_subnet = subnet;
3082
3083 // Get client-id. It is not mandatory in DHCPv4.
3084 ClientIdPtr client_id = ex.getContext()->clientid_;
3085
3086 // In the INIT-REBOOT state, a client remembering its previously assigned
3087 // address is trying to confirm whether or not this address is still usable.
3088 if (init_reboot) {
3089 Lease4Ptr lease;
3090
3091 auto const& classes = query->getClasses();
3092
3093 // We used to issue a separate query (two actually: one for client-id
3094 // and another one for hw-addr for) each subnet in the shared network.
3095 // That was horribly inefficient if the client didn't have any lease
3096 // (or there were many subnets and the client happened to be in one
3097 // of the last subnets).
3098 //
3099 // We now issue at most two queries: get all the leases for specific
3100 // client-id and then get all leases for specific hw-address.
3101 if (original_subnet && client_id) {
3102
3103 // Get all the leases for this client-id
3104 Lease4Collection leases_client_id = LeaseMgrFactory::instance().getLease4(*client_id);
3105 if (!leases_client_id.empty()) {
3106 ConstSubnet4Ptr s = original_subnet;
3107
3108 // Among those returned try to find a lease that belongs to
3109 // current shared network.
3110 while (s) {
3111 for (auto const& l : leases_client_id) {
3112 if (l->subnet_id_ == s->getID()) {
3113 lease = l;
3114 break;
3115 }
3116 }
3117
3118 if (lease) {
3119 break;
3120
3121 } else {
3122 s = s->getNextSubnet(original_subnet, classes);
3123 }
3124 }
3125 }
3126 }
3127
3128 // If we haven't found a lease yet, try again by hardware-address.
3129 // The logic is the same.
3130 if (original_subnet && !lease && hwaddr) {
3131
3132 // Get all leases for this particular hw-address.
3133 Lease4Collection leases_hwaddr = LeaseMgrFactory::instance().getLease4(*hwaddr);
3134 if (!leases_hwaddr.empty()) {
3135 ConstSubnet4Ptr s = original_subnet;
3136
3137 // Pick one that belongs to a subnet in this shared network.
3138 while (s) {
3139 for (auto const& l : leases_hwaddr) {
3140 if (l->subnet_id_ == s->getID()) {
3141 lease = l;
3142 break;
3143 }
3144 }
3145
3146 if (lease) {
3147 break;
3148
3149 } else {
3150 s = s->getNextSubnet(original_subnet, classes);
3151 }
3152 }
3153 }
3154 }
3155
3156 // Check the first error case: unknown client. We check this before
3157 // validating the address sent because we don't want to respond if
3158 // we don't know this client, except if we're authoritative.
3159 bool known_client = lease && lease->belongsToClient(hwaddr, client_id);
3160 if (!authoritative && !known_client) {
3163 .arg(query->getLabel())
3164 .arg(hint.toText());
3165
3166 ex.deleteResponse();
3167 return;
3168 }
3169
3170 // If we know this client, check if his notion of the IP address is
3171 // correct, if we don't know him, check if we are authoritative.
3172 if ((known_client && (lease->addr_ != hint)) ||
3173 (!known_client && authoritative) ||
3174 (!original_subnet)) {
3177 .arg(query->getLabel())
3178 .arg(hint.toText());
3179
3180 resp->setType(DHCPNAK);
3181 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
3182 return;
3183 }
3184 }
3185
3186 CalloutHandlePtr callout_handle = getCalloutHandle(query);
3187
3188 // We need to set these values in the context as they haven't been set yet.
3189 ctx->requested_address_ = hint;
3190 ctx->fake_allocation_ = fake_allocation;
3191 ctx->callout_handle_ = callout_handle;
3192
3193 // If client query contains an FQDN or Hostname option, server
3194 // should respond to the client with the appropriate FQDN or Hostname
3195 // option to indicate if it takes responsibility for the DNS updates.
3196 // This is also the source for the hostname and dns flags that are
3197 // initially added to the lease. In most cases, this information is
3198 // good now. If we end up changing subnets in allocation we'll have to
3199 // do it again and then update the lease.
3201
3202 // Get a lease.
3203 Lease4Ptr lease = alloc_engine_->allocateLease4(*ctx);
3204
3205 bool reprocess_client_name = false;
3206 if (lease) {
3207 // Since we have a lease check for pool-level DDNS parameters.
3208 // If there are any we need to call processClientName() again.
3209 auto ddns_params = ex.getContext()->getDdnsParams();
3210 auto pool = ddns_params->setPoolFromAddress(lease->addr_);
3211 if (pool) {
3212 reprocess_client_name = pool->hasDdnsParameters();
3213 }
3214 }
3215
3216 // Subnet may be modified by the allocation engine, if the initial subnet
3217 // belongs to a shared network.
3218 if (subnet && ctx->subnet_ && subnet->getID() != ctx->subnet_->getID()) {
3219 // We changed subnets and that means DDNS parameters might be different
3220 // so we need to rerun client name processing logic. Arguably we could
3221 // compare DDNS parameters for both subnets and then decide if we need
3222 // to rerun the name logic, but that's not likely to be any faster than
3223 // just re-running the name logic. @todo When inherited parameter
3224 // performance is improved this argument could be revisited.
3225 // Another case is the new subnet has a reserved hostname.
3226 SharedNetwork4Ptr network;
3227 subnet->getSharedNetwork(network);
3229 .arg(query->getLabel())
3230 .arg(subnet->toText())
3231 .arg(ctx->subnet_->toText())
3232 .arg(network ? network->getName() : "<no network?>");
3233
3234 subnet = ctx->subnet_;
3235 if (lease) {
3236 reprocess_client_name = true;
3237 }
3238 }
3239
3240 // Tracks whether or not the client name (FQDN or host) has changed since
3241 // the lease was allocated.
3242 bool client_name_changed = false;
3243
3244 if (reprocess_client_name) {
3245 // First, we need to remove the prior values from the response and reset
3246 // those in context, to give processClientName a clean slate.
3247 resp->delOption(DHO_FQDN);
3248 resp->delOption(DHO_HOST_NAME);
3249 ctx->hostname_ = "";
3250 ctx->fwd_dns_update_ = false;
3251 ctx->rev_dns_update_ = false;
3252
3253 // Regenerate the name and dns flags.
3255
3256 // If the results are different from the values already on the
3257 // lease, flag it so the lease gets updated down below.
3258 if ((lease->hostname_ != ctx->hostname_) ||
3259 (lease->fqdn_fwd_ != ctx->fwd_dns_update_) ||
3260 (lease->fqdn_rev_ != ctx->rev_dns_update_)) {
3261 lease->hostname_ = ctx->hostname_;
3262 lease->fqdn_fwd_ = ctx->fwd_dns_update_;
3263 lease->fqdn_rev_ = ctx->rev_dns_update_;
3264 client_name_changed = true;
3265 }
3266 }
3267
3268 if (lease) {
3269 // We have a lease! Let's set it in the packet and send it back to
3270 // the client.
3271 if (fake_allocation) {
3273 .arg(query->getLabel())
3274 .arg(lease->addr_.toText());
3275 } else {
3277 .arg(query->getLabel())
3278 .arg(lease->addr_.toText())
3279 .arg(Lease::lifetimeToText(lease->valid_lft_));
3280 }
3281
3282 // We're logging this here, because this is the place where we know
3283 // which subnet has been actually used for allocation. If the
3284 // client identifier matching is disabled, we want to make sure that
3285 // the user is notified.
3286 if (!ctx->subnet_->getMatchClientId()) {
3288 .arg(ctx->query_->getLabel())
3289 .arg(ctx->subnet_->getID());
3290 }
3291
3292 resp->setYiaddr(lease->addr_);
3293
3298 if (!fake_allocation) {
3299 // If this is a renewing client it will set a ciaddr which the
3300 // server may include in the response. If this is a new allocation
3301 // the client will set ciaddr to 0 and this will also be propagated
3302 // to the server's resp.
3303 resp->setCiaddr(query->getCiaddr());
3304 }
3305
3306 // We may need to update FQDN or hostname if the server is to generate
3307 // a new name from the allocated IP address or if the allocation engine
3308 // switched to a different subnet within a shared network.
3309 postAllocateNameUpdate(ctx, lease, query, resp, client_name_changed);
3310
3311 // Reuse the lease if possible.
3312 if (lease->reuseable_valid_lft_ > 0) {
3313 lease->valid_lft_ = lease->reuseable_valid_lft_;
3315 .arg(query->getLabel())
3316 .arg(lease->addr_.toText())
3317 .arg(Lease::lifetimeToText(lease->valid_lft_));
3318
3319 // Increment the reuse statistics.
3320 StatsMgr::instance().addValue("v4-lease-reuses", int64_t(1));
3321 StatsMgr::instance().addValue(StatsMgr::generateName("subnet", lease->subnet_id_,
3322 "v4-lease-reuses"),
3323 int64_t(1));
3324 }
3325
3326 // IP Address Lease time (type 51)
3327 // If we're not allocating on discover then we just sent the lifetime on the lease.
3328 // Otherwise (i.e. offer_lft > 0), the lease's lifetime has been set to offer_lft but
3329 // we want to send the client the proper valid lifetime so we have to fetch it.
3330 auto send_lft = (ctx->offer_lft_ ? AllocEngine::getValidLft(*ctx) : lease->valid_lft_);
3332
3333 resp->addOption(opt);
3334
3335 // Subnet mask (type 1)
3336 resp->addOption(getNetmaskOption(subnet));
3337
3338 // Set T1 and T2 per configuration.
3339 setTeeTimes(lease, subnet, resp);
3340
3341 // Create NameChangeRequests if this is a real allocation.
3342 if (!fake_allocation) {
3343 try {
3344 createNameChangeRequests(lease, ctx->old_lease_,
3345 *ex.getContext()->getDdnsParams());
3346 } catch (const Exception& ex) {
3348 .arg(query->getLabel())
3349 .arg(ex.what());
3350 }
3351 }
3352
3353 } else {
3354 // Allocation engine did not allocate a lease. The engine logged
3355 // cause of that failure.
3356 if (ctx->unknown_requested_addr_) {
3357 ConstSubnet4Ptr s = original_subnet;
3358 // Address might have been rejected via class guard (i.e. not
3359 // allowed for this client). We need to determine if we truly
3360 // do not know about the address or whether this client just
3361 // isn't allowed to have that address. We should only DHCPNAK
3362 // For the latter.
3363 while (s) {
3364 if (s->inPool(Lease::TYPE_V4, hint)) {
3365 break;
3366 }
3367
3368 s = s->getNextSubnet(original_subnet);
3369 }
3370
3371 // If we didn't find a subnet, it's not an address we know about
3372 // so we drop the DHCPNAK.
3373 if (!s) {
3376 .arg(query->getLabel())
3377 .arg(query->getCiaddr().toText())
3378 .arg(opt_requested_address ?
3379 opt_requested_address->readAddress().toText() : "(no address)");
3380 ex.deleteResponse();
3381 return;
3382 }
3383 }
3384
3387 .arg(query->getLabel())
3388 .arg(query->getCiaddr().toText())
3389 .arg(opt_requested_address ?
3390 opt_requested_address->readAddress().toText() : "(no address)");
3391
3392 resp->setType(DHCPNAK);
3393 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
3394
3395 resp->delOption(DHO_FQDN);
3396 resp->delOption(DHO_HOST_NAME);
3397 }
3398}
3399
3400void
3402 const Pkt4Ptr& query, const Pkt4Ptr& resp, bool client_name_changed) {
3403 // We may need to update FQDN or hostname if the server is to generate
3404 // new name from the allocated IP address or if the allocation engine
3405 // has switched to a different subnet within a shared network. Get
3406 // FQDN and hostname options from the response.
3407 OptionStringPtr opt_hostname;
3408 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
3409 Option4ClientFqdn>(resp->getOption(DHO_FQDN));
3410 if (!fqdn) {
3411 opt_hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
3412 if (!opt_hostname) {
3413 // We don't have either one, nothing to do.
3414 return;
3415 }
3416 }
3417
3418 // Empty hostname on the lease means we need to generate it.
3419 if (lease->hostname_.empty()) {
3420 // Note that if we have received the hostname option, rather than
3421 // Client FQDN the trailing dot is not appended to the generated
3422 // hostname because some clients don't handle the trailing dot in
3423 // the hostname. Whether the trailing dot is appended or not is
3424 // controlled by the second argument to the generateFqdn().
3425 lease->hostname_ = CfgMgr::instance().getD2ClientMgr()
3426 .generateFqdn(lease->addr_, *(ctx->getDdnsParams()), static_cast<bool>(fqdn));
3427
3429 .arg(query->getLabel())
3430 .arg(lease->hostname_);
3431
3432 client_name_changed = true;
3433 }
3434
3435 if (client_name_changed) {
3436 // The operations below are rather safe, but we want to catch
3437 // any potential exceptions (e.g. invalid lease database backend
3438 // implementation) and log an error.
3439 try {
3441 if (!ctx->fake_allocation_ || (ctx->offer_lft_ > 0)) {
3442 // The lease can't be reused.
3443 lease->reuseable_valid_lft_ = 0;
3444
3445 // The lease update should be safe, because the lease should
3446 // be already in the database. In most cases the exception
3447 // would be thrown if the lease was missing.
3449 }
3450
3451 // The name update in the outbound option should be also safe,
3452 // because the generated name is well formed.
3453 if (fqdn) {
3454 fqdn->setDomainName(lease->hostname_, Option4ClientFqdn::FULL);
3455 } else {
3456 opt_hostname->setValue(lease->hostname_);
3457 }
3458 } catch (const Exception& ex) {
3460 .arg(query->getLabel())
3461 .arg(lease->hostname_)
3462 .arg(ex.what());
3463 }
3464 }
3465}
3466
3468void
3469Dhcpv4Srv::setTeeTimes(const Lease4Ptr& lease, const ConstSubnet4Ptr& subnet, Pkt4Ptr resp) {
3470
3471 uint32_t t2_time = 0;
3472 // If T2 is explicitly configured we'll use try value.
3473 if (!subnet->getT2().unspecified()) {
3474 t2_time = subnet->getT2();
3475 } else if (subnet->getCalculateTeeTimes()) {
3476 // Calculating tee times is enabled, so calculated it.
3477 t2_time = static_cast<uint32_t>(round(subnet->getT2Percent() * (lease->valid_lft_)));
3478 }
3479
3480 // Send the T2 candidate value only if it's sane: to be sane it must be less than
3481 // the valid life time.
3482 uint32_t timer_ceiling = lease->valid_lft_;
3483 if (t2_time > 0 && t2_time < timer_ceiling) {
3485 resp->addOption(t2);
3486 // When we send T2, timer ceiling for T1 becomes T2.
3487 timer_ceiling = t2_time;
3488 }
3489
3490 uint32_t t1_time = 0;
3491 // If T1 is explicitly configured we'll use try value.
3492 if (!subnet->getT1().unspecified()) {
3493 t1_time = subnet->getT1();
3494 } else if (subnet->getCalculateTeeTimes()) {
3495 // Calculating tee times is enabled, so calculate it.
3496 t1_time = static_cast<uint32_t>(round(subnet->getT1Percent() * (lease->valid_lft_)));
3497 }
3498
3499 // Send T1 if it's sane: If we sent T2, T1 must be less than that. If not it must be
3500 // less than the valid life time.
3501 if (t1_time > 0 && t1_time < timer_ceiling) {
3503 resp->addOption(t1);
3504 }
3505}
3506
3507uint16_t
3509
3510 // Look for a relay-port RAI sub-option in the query.
3511 const Pkt4Ptr& query = ex.getQuery();
3512 const OptionPtr& rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
3513 if (rai && rai->getOption(RAI_OPTION_RELAY_PORT)) {
3514 // Got the sub-option so use the remote port set by the relay.
3515 return (query->getRemotePort());
3516 }
3517 return (0);
3518}
3519
3520void
3522 adjustRemoteAddr(ex);
3523
3524 // Initialize the pointers to the client's message and the server's
3525 // response.
3526 Pkt4Ptr query = ex.getQuery();
3527 Pkt4Ptr response = ex.getResponse();
3528
3529 // The DHCPINFORM is generally unicast to the client. The only situation
3530 // when the server is unable to unicast to the client is when the client
3531 // doesn't include ciaddr and the message is relayed. In this case the
3532 // server has to reply via relay agent. For other messages we send back
3533 // through relay if message is relayed, and unicast to the client if the
3534 // message is not relayed.
3535 // If client port was set from the command line enforce all responses
3536 // to it. Of course it is only for testing purposes.
3537 // Note that the call to this function may throw if invalid combination
3538 // of hops and giaddr is found (hops = 0 if giaddr = 0 and hops != 0 if
3539 // giaddr != 0). The exception will propagate down and eventually cause the
3540 // packet to be discarded.
3541 if (client_port_) {
3542 response->setRemotePort(client_port_);
3543 } else if (((query->getType() == DHCPINFORM) &&
3544 ((!query->getCiaddr().isV4Zero()) ||
3545 (!query->isRelayed() && !query->getRemoteAddr().isV4Zero()))) ||
3546 ((query->getType() != DHCPINFORM) && !query->isRelayed())) {
3547 response->setRemotePort(DHCP4_CLIENT_PORT);
3548
3549 } else {
3550 // RFC 8357 section 5.1
3551 uint16_t relay_port = checkRelayPort(ex);
3552 response->setRemotePort(relay_port ? relay_port : DHCP4_SERVER_PORT);
3553 }
3554
3555 CfgIfacePtr cfg_iface = CfgMgr::instance().getCurrentCfg()->getCfgIface();
3556 if (query->isRelayed() &&
3557 (cfg_iface->getSocketType() == CfgIface::SOCKET_UDP) &&
3558 (cfg_iface->getOutboundIface() == CfgIface::USE_ROUTING)) {
3559
3560 // Mark the response to follow routing
3561 response->setLocalAddr(IOAddress::IPV4_ZERO_ADDRESS());
3562 response->resetIndex();
3563 // But keep the interface name
3564 response->setIface(query->getIface());
3565
3566 } else {
3567
3568 IOAddress local_addr = query->getLocalAddr();
3569
3570 // In many cases the query is sent to a broadcast address. This address
3571 // appears as a local address in the query message. We can't simply copy
3572 // this address to a response message and use it as a source address.
3573 // Instead we will need to use the address assigned to the interface
3574 // on which the query has been received. In other cases, we will just
3575 // use this address as a source address for the response.
3576 // Do the same for DHCPv4-over-DHCPv6 exchanges.
3577 if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
3578 local_addr = IfaceMgr::instance().getSocket(query).addr_;
3579 }
3580
3581 // We assume that there is an appropriate socket bound to this address
3582 // and that the address is correct. This is safe assumption because
3583 // the local address of the query is set when the query is received.
3584 // The query sent to an incorrect address wouldn't have been received.
3585 // However, if socket is closed for this address between the reception
3586 // of the query and sending a response, the IfaceMgr should detect it
3587 // and return an error.
3588 response->setLocalAddr(local_addr);
3589 // In many cases the query is sent to a broadcast address. This address
3590 // appears as a local address in the query message. Therefore we can't
3591 // simply copy local address from the query and use it as a source
3592 // address for the response. Instead, we have to check what address our
3593 // socket is bound to and use it as a source address. This operation
3594 // may throw if for some reason the socket is closed.
3597 response->setIndex(query->getIndex());
3598 response->setIface(query->getIface());
3599 }
3600
3601 if (server_port_) {
3602 response->setLocalPort(server_port_);
3603 } else {
3604 response->setLocalPort(DHCP4_SERVER_PORT);
3605 }
3606}
3607
3608void
3610 // Initialize the pointers to the client's message and the server's
3611 // response.
3612 Pkt4Ptr query = ex.getQuery();
3613 Pkt4Ptr response = ex.getResponse();
3614
3615 // DHCPv4-over-DHCPv6 is simple
3616 if (query->isDhcp4o6()) {
3617 response->setRemoteAddr(query->getRemoteAddr());
3618 return;
3619 }
3620
3621 // The DHCPINFORM is slightly different than other messages in a sense
3622 // that the server should always unicast the response to the ciaddr.
3623 // It appears however that some clients don't set the ciaddr. We still
3624 // want to provision these clients and we do what we can't to send the
3625 // packet to the address where client can receive it.
3626 if (query->getType() == DHCPINFORM) {
3627 // If client adheres to RFC2131 it will set the ciaddr and in this
3628 // case we always unicast our response to this address.
3629 if (!query->getCiaddr().isV4Zero()) {
3630 response->setRemoteAddr(query->getCiaddr());
3631
3632 // If we received DHCPINFORM via relay and the ciaddr is not set we
3633 // will try to send the response via relay. The caveat is that the
3634 // relay will not have any idea where to forward the packet because
3635 // the yiaddr is likely not set. So, the broadcast flag is set so
3636 // as the response may be broadcast.
3637 } else if (query->isRelayed()) {
3638 response->setRemoteAddr(query->getGiaddr());
3639 response->setFlags(response->getFlags() | BOOTP_BROADCAST);
3640
3641 // If there is no ciaddr and no giaddr the only thing we can do is
3642 // to use the source address of the packet.
3643 } else {
3644 response->setRemoteAddr(query->getRemoteAddr());
3645 }
3646 // Remote address is now set so return.
3647 return;
3648 }
3649
3650 // If received relayed message, server responds to the relay address.
3651 if (query->isRelayed()) {
3652 // The client should set the ciaddr when sending the DHCPINFORM
3653 // but in case he didn't, the relay may not be able to determine the
3654 // address of the client, because yiaddr is not set when responding
3655 // to Confirm and the only address available was the source address
3656 // of the client. The source address is however not used here because
3657 // the message is relayed. Therefore, we set the BROADCAST flag so
3658 // as the relay can broadcast the packet.
3659 if ((query->getType() == DHCPINFORM) &&
3660 query->getCiaddr().isV4Zero()) {
3661 response->setFlags(BOOTP_BROADCAST);
3662 }
3663 response->setRemoteAddr(query->getGiaddr());
3664
3665 // If giaddr is 0 but client set ciaddr, server should unicast the
3666 // response to ciaddr.
3667 } else if (!query->getCiaddr().isV4Zero()) {
3668 response->setRemoteAddr(query->getCiaddr());
3669
3670 // We can't unicast the response to the client when sending DHCPNAK,
3671 // because we haven't allocated address for him. Therefore,
3672 // DHCPNAK is broadcast.
3673 } else if (response->getType() == DHCPNAK) {
3674 response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
3675
3676 // If yiaddr is set it means that we have created a lease for a client.
3677 } else if (!response->getYiaddr().isV4Zero()) {
3678 // If the broadcast bit is set in the flags field, we have to
3679 // send the response to broadcast address. Client may have requested it
3680 // because it doesn't support reception of messages on the interface
3681 // which doesn't have an address assigned. The other case when response
3682 // must be broadcasted is when our server does not support responding
3683 // directly to a client without address assigned.
3684 const bool bcast_flag = ((query->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
3685 if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
3686 response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
3687
3688 // Client cleared the broadcast bit and we support direct responses
3689 // so we should unicast the response to a newly allocated address -
3690 // yiaddr.
3691 } else {
3692 response->setRemoteAddr(response ->getYiaddr());
3693
3694 }
3695
3696 // In most cases, we should have the remote address found already. If we
3697 // found ourselves at this point, the rational thing to do is to respond
3698 // to the address we got the query from.
3699 } else {
3700 response->setRemoteAddr(query->getRemoteAddr());
3701 }
3702
3703 // For testing *only*.
3705 response->setRemoteAddr(query->getRemoteAddr());
3706 }
3707}
3708
3709void
3711 Pkt4Ptr query = ex.getQuery();
3712 Pkt4Ptr response = ex.getResponse();
3713
3714 // Step 1: Start with fixed fields defined on subnet level.
3715 ConstSubnet4Ptr subnet = ex.getContext()->subnet_;
3716 if (subnet) {
3717 IOAddress subnet_next_server = subnet->getSiaddr();
3718 if (!subnet_next_server.isV4Zero()) {
3719 response->setSiaddr(subnet_next_server);
3720 }
3721
3722 const string& sname = subnet->getSname();
3723 if (!sname.empty()) {
3724 // Converting string to (const uint8_t*, size_t len) format is
3725 // tricky. reinterpret_cast is not the most elegant solution,
3726 // but it does avoid us making unnecessary copy. We will convert
3727 // sname and file fields in Pkt4 to string one day and life
3728 // will be easier.
3729 response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
3730 sname.size());
3731 }
3732
3733 const string& filename = subnet->getFilename();
3734 if (!filename.empty()) {
3735 // Converting string to (const uint8_t*, size_t len) format is
3736 // tricky. reinterpret_cast is not the most elegant solution,
3737 // but it does avoid us making unnecessary copy. We will convert
3738 // sname and file fields in Pkt4 to string one day and life
3739 // will be easier.
3740 response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
3741 filename.size());
3742 }
3743 }
3744
3745 // Step 2: Try to set the values based on classes.
3746 // Any values defined in classes will override those from subnet level.
3747 const ClientClasses& classes = query->getClasses();
3748 if (!classes.empty()) {
3749
3750 // Let's get class definitions
3751 const ClientClassDictionaryPtr& dict =
3752 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
3753
3754 // Now we need to iterate over the classes assigned to the
3755 // query packet and find corresponding class definitions for it.
3756 // We want the first value found for each field. We track how
3757 // many we've found so we can stop if we have all three.
3759 string sname;
3760 string filename;
3761 size_t found_cnt = 0; // How many fields we have found.
3762 for (auto const& name : classes) {
3763
3764 if (found_cnt >= 3) {
3765 break;
3766 }
3767
3768 ClientClassDefPtr cl = dict->findClass(name);
3769 if (!cl) {
3770 // Let's skip classes that don't have definitions. Currently
3771 // these are automatic classes VENDOR_CLASS_something, but there
3772 // may be other classes assigned under other circumstances, e.g.
3773 // by hooks.
3774 continue;
3775 }
3776
3777 if (next_server == IOAddress::IPV4_ZERO_ADDRESS()) {
3778 next_server = cl->getNextServer();
3779 if (!next_server.isV4Zero()) {
3780 response->setSiaddr(next_server);
3781 found_cnt++;
3782 }
3783 }
3784
3785 if (sname.empty()) {
3786 sname = cl->getSname();
3787 if (!sname.empty()) {
3788 // Converting string to (const uint8_t*, size_t len) format is
3789 // tricky. reinterpret_cast is not the most elegant solution,
3790 // but it does avoid us making unnecessary copy. We will convert
3791 // sname and file fields in Pkt4 to string one day and life
3792 // will be easier.
3793 response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
3794 sname.size());
3795 found_cnt++;
3796 }
3797 }
3798
3799 if (filename.empty()) {
3800 filename = cl->getFilename();
3801 if (!filename.empty()) {
3802 // Converting string to (const uint8_t*, size_t len) format is
3803 // tricky. reinterpret_cast is not the most elegant solution,
3804 // but it does avoid us making unnecessary copy. We will convert
3805 // sname and file fields in Pkt4 to string one day and life
3806 // will be easier.
3807 response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
3808 filename.size());
3809 found_cnt++;
3810 }
3811 }
3812 }
3813 }
3814
3815 // Step 3: try to set values using HR. Any values coming from there will override
3816 // the subnet or class values.
3818}
3819
3821Dhcpv4Srv::getNetmaskOption(const ConstSubnet4Ptr& subnet) {
3822 uint32_t netmask = getNetmask4(subnet->get().second).toUint32();
3823
3825 DHO_SUBNET_MASK, netmask));
3826
3827 return (opt);
3828}
3829
3830tuple<bool, uint32_t>
3831Dhcpv4Srv::parkingLimitExceeded(string const& hook_label) {
3832 // Get the parking limit. Parsing should ensure the value is present.
3833 uint32_t parked_packet_limit(0);
3834 ConstElementPtr const& ppl(
3835 CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::PARKED_PACKET_LIMIT));
3836 if (ppl) {
3837 parked_packet_limit = ppl->intValue();
3838 }
3839
3840 if (parked_packet_limit) {
3841 ParkingLotPtr const& parking_lot(
3842 ServerHooks::getServerHooks().getParkingLotPtr(hook_label));
3843
3844 if (parking_lot && parked_packet_limit <= parking_lot->size()) {
3845 return make_tuple(true, parked_packet_limit);
3846 }
3847 }
3848 return make_tuple(false, parked_packet_limit);
3849}
3850
3851Pkt4Ptr
3853 bool drop = false;
3854 Dhcpv4Exchange ex(alloc_engine_, discover, context, context->subnet_, drop);
3855
3856 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
3857 if (drop) {
3858 return (Pkt4Ptr());
3859 }
3860
3861 if (MultiThreadingMgr::instance().getMode()) {
3862 // The lease reclamation cannot run at the same time.
3863 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3864
3865 assignLease(ex);
3866 } else {
3867 assignLease(ex);
3868 }
3869
3870 if (!ex.getResponse()) {
3871 // The offer is empty so return it *now*!
3872 return (Pkt4Ptr());
3873 }
3874
3875 // Adding any other options makes sense only when we got the lease
3876 // or it is for an IPv6-Only client.
3877 if (!ex.getResponse()->getYiaddr().isV4Zero() || ex.getIPv6OnlyPreferred()) {
3878 // If this is global reservation or the subnet doesn't belong to a shared
3879 // network we have already fetched it and evaluated the classes.
3881
3882 // Evaluate additional classes.
3884
3886 .arg(discover->getLabel())
3887 .arg(discover->getName())
3888 .arg(discover->getClasses().toText());
3889
3892 // Sanity check for IPv6-Only clients.
3893 if (ex.getIPv6OnlyPreferred()) {
3894 if (!ex.getResponse()->getOption(DHO_V6_ONLY_PREFERRED)) {
3895 // Better to drop the packet than to send an insane response.
3897 .arg(discover->getLabel());
3898 return (Pkt4Ptr());
3899 }
3900 }
3902 // There are a few basic options that we always want to
3903 // include in the response. If client did not request
3904 // them we append them for him.
3906
3907 // Set fixed fields (siaddr, sname, filename) if defined in
3908 // the reservation, class or subnet specific configuration.
3909 setFixedFields(ex);
3910
3911 } else {
3912 // If the server can't offer an address, it drops the packet.
3913 return (Pkt4Ptr());
3914
3915 }
3916
3917 // Set the src/dest IP address, port and interface for the outgoing
3918 // packet.
3919 adjustIfaceData(ex);
3920
3921 appendServerID(ex);
3922
3923 // Return the pointer to the context, which will be required by the
3924 // lease4_offer callouts.
3925 context = ex.getContext();
3926
3927 return (ex.getResponse());
3928}
3929
3930Pkt4Ptr
3932 bool drop = false;
3933 Dhcpv4Exchange ex(alloc_engine_, request, context, context->subnet_, drop);
3934
3935 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
3936 if (drop) {
3937 return (Pkt4Ptr());
3938 }
3939
3940 // Note that we treat REQUEST message uniformly, regardless if this is a
3941 // first request (requesting for new address), renewing existing address
3942 // or even rebinding.
3943 if (MultiThreadingMgr::instance().getMode()) {
3944 // The lease reclamation cannot run at the same time.
3945 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3946
3947 assignLease(ex);
3948 } else {
3949 assignLease(ex);
3950 }
3951
3952 Pkt4Ptr response = ex.getResponse();
3953 if (!response) {
3954 // The ack is empty so return it *now*!
3955 return (Pkt4Ptr());
3956 } else if (request->inClass("BOOTP")) {
3957 // Put BOOTP responses in the BOOTP class.
3958 response->addClass("BOOTP");
3959 }
3960
3961 // Adding any other options makes sense only when we got the lease
3962 // or it is for an IPv6-Only client.
3963 if (!response->getYiaddr().isV4Zero() || ex.getIPv6OnlyPreferred()) {
3964 // If this is global reservation or the subnet doesn't belong to a shared
3965 // network we have already fetched it and evaluated the classes.
3967
3968 // Evaluate additional classes.
3970
3972 .arg(request->getLabel())
3973 .arg(request->getName())
3974 .arg(request->getClasses().toText());
3975
3978 // Sanity check for IPv6-Only clients.
3979 if (ex.getIPv6OnlyPreferred()) {
3980 if (!response->getOption(DHO_V6_ONLY_PREFERRED)) {
3981 // Better to drop the packet than to send an insane response.
3983 .arg(request->getLabel());
3984 return (Pkt4Ptr());
3985 }
3986 }
3988 // There are a few basic options that we always want to
3989 // include in the response. If client did not request
3990 // them we append them for him.
3992
3993 // Set fixed fields (siaddr, sname, filename) if defined in
3994 // the reservation, class or subnet specific configuration.
3995 setFixedFields(ex);
3996 }
3997
3998 // Set the src/dest IP address, port and interface for the outgoing
3999 // packet.
4000 adjustIfaceData(ex);
4001
4002 appendServerID(ex);
4003
4004 // Return the pointer to the context, which will be required by the
4005 // leases4_committed callouts.
4006 context = ex.getContext();
4007
4008 return (ex.getResponse());
4009}
4010
4011void
4013 // Try to find client-id. Note that for the DHCPRELEASE we don't check if the
4014 // match-client-id configuration parameter is disabled because this parameter
4015 // is configured for subnets and we don't select subnet for the DHCPRELEASE.
4016 // Bogus clients usually generate new client identifiers when they first
4017 // connect to the network, so whatever client identifier has been used to
4018 // acquire the lease, the client identifier carried in the DHCPRELEASE is
4019 // likely to be the same and the lease will be correctly identified in the
4020 // lease database. If supplied client identifier differs from the one used
4021 // to acquire the lease then the lease will remain in the database and
4022 // simply expire.
4023 ClientIdPtr client_id;
4024 OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
4025 if (opt) {
4026 client_id = ClientIdPtr(new ClientId(opt->getData()));
4027 }
4028
4029 try {
4030 // Do we have a lease for that particular address?
4031 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getCiaddr());
4032
4033 if (!lease) {
4034 // No such lease - bogus release
4036 .arg(release->getLabel())
4037 .arg(release->getCiaddr().toText());
4038 return;
4039 }
4040
4041 if (!lease->belongsToClient(release->getHWAddr(), client_id)) {
4043 .arg(release->getLabel())
4044 .arg(release->getCiaddr().toText());
4045 return;
4046 }
4047
4048 bool skip = false;
4049
4050 // Execute all callouts registered for lease4_release
4051 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_release_)) {
4052 CalloutHandlePtr callout_handle = getCalloutHandle(release);
4053
4054 // Use the RAII wrapper to make sure that the callout handle state is
4055 // reset when this object goes out of scope. All hook points must do
4056 // it to prevent possible circular dependency between the callout
4057 // handle and its arguments.
4058 ScopedCalloutHandleState callout_handle_state(callout_handle);
4059
4060 // Enable copying options from the packet within hook library.
4061 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(release);
4062
4063 // Pass the original packet
4064 callout_handle->setArgument("query4", release);
4065
4066 // Pass the lease to be updated
4067 callout_handle->setArgument("lease4", lease);
4068
4069 // Call all installed callouts
4070 HooksManager::callCallouts(Hooks.hook_index_lease4_release_,
4071 *callout_handle);
4072
4073 // Callouts decided to skip the next processing step. The next
4074 // processing step would be to send the packet, so skip at this
4075 // stage means "drop response".
4076 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
4077 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
4078 skip = true;
4081 .arg(release->getLabel());
4082 }
4083 }
4084
4085 // Callout didn't indicate to skip the release process. Let's release
4086 // the lease.
4087 if (!skip) {
4088 // Ok, we've passed all checks. Let's release this address.
4089 bool success = false; // was the removal operation successful?
4090 bool expired = false; // explicitly expired instead of removed?
4091 auto expiration_cfg = CfgMgr::instance().getCurrentCfg()->getCfgExpiration();
4092
4093 // Delete lease only if affinity is disabled.
4094 if (expiration_cfg->getFlushReclaimedTimerWaitTime() &&
4095 expiration_cfg->getHoldReclaimedTime() &&
4096 lease->valid_lft_ != Lease::INFINITY_LFT) {
4097 // Expire the lease.
4098 lease->valid_lft_ = 0;
4099 // Set the lease state to released to indicate that this lease
4100 // must be preserved in the database. It is particularly useful
4101 // in HA to differentiate between the leases that should be
4102 // updated in the partner's database and deleted from the partner's
4103 // database.
4104 lease->state_ = Lease4::STATE_RELEASED;
4106 expired = true;
4107 success = true;
4108 } else {
4109 success = LeaseMgrFactory::instance().deleteLease(lease);
4110 }
4111
4112 if (success) {
4113 context.reset(new AllocEngine::ClientContext4());
4114 context->old_lease_ = lease;
4115
4116 // Release successful
4118 .arg(release->getLabel())
4119 .arg(lease->addr_.toText());
4120
4121 if (expired) {
4123 .arg(release->getLabel())
4124 .arg(lease->addr_.toText());
4125 } else {
4127 .arg(release->getLabel())
4128 .arg(lease->addr_.toText());
4129
4130 // Remove existing DNS entries for the lease, if any.
4131 queueNCR(CHG_REMOVE, lease);
4132 }
4133
4134 // Need to decrease statistic for assigned addresses.
4136 StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
4137 static_cast<int64_t>(-1));
4138
4139 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
4140 if (subnet) {
4141 auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
4142 if (pool) {
4144 StatsMgr::generateName("subnet", subnet->getID(),
4145 StatsMgr::generateName("pool", pool->getID(), "assigned-addresses")),
4146 static_cast<int64_t>(-1));
4147 }
4148 }
4149
4150 } else {
4151 // Release failed
4153 .arg(release->getLabel())
4154 .arg(lease->addr_.toText());
4155 }
4156 }
4157 } catch (const isc::Exception& ex) {
4159 .arg(release->getLabel())
4160 .arg(release->getCiaddr())
4161 .arg(ex.what());
4162 }
4163}
4164
4165void
4167 // Client is supposed to specify the address being declined in
4168 // Requested IP address option, but must not set its ciaddr.
4169 // (again, see table 5 in RFC2131).
4170
4171 OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
4172 OptionCustom>(decline->getOption(DHO_DHCP_REQUESTED_ADDRESS));
4173 if (!opt_requested_address) {
4174
4175 isc_throw(RFCViolation, "Mandatory 'Requested IP address' option missing"
4176 " in DHCPDECLINE sent from " << decline->getLabel());
4177 }
4178 IOAddress addr(opt_requested_address->readAddress());
4179
4180 // We could also extract client's address from ciaddr, but that's clearly
4181 // against RFC2131.
4182
4183 // Now we need to check whether this address really belongs to the client
4184 // that attempts to decline it.
4185 const Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
4186
4187 if (!lease) {
4188 // Client tried to decline an address, but we don't have a lease for
4189 // that address. Let's ignore it.
4190 //
4191 // We could assume that we're recovering from a mishandled migration
4192 // to a new server and mark the address as declined, but the window of
4193 // opportunity for that to be useful is small and the attack vector
4194 // would be pretty severe.
4196 .arg(addr.toText()).arg(decline->getLabel());
4197 return;
4198 }
4199
4200 // Get client-id, if available.
4201 OptionPtr opt_clientid = decline->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
4202 ClientIdPtr client_id;
4203 if (opt_clientid) {
4204 client_id.reset(new ClientId(opt_clientid->getData()));
4205 }
4206
4207 // Check if the client attempted to decline an expired lease or a lease
4208 // it doesn't own. Declining expired leases is typically a client
4209 // misbehavior and may lead to pool exhaustion in case of a storm of
4210 // such declines. Only decline the lease if the lease has been recently
4211 // allocated to the client.
4212 if (lease->expired() || lease->state_ != Lease::STATE_DEFAULT ||
4213 !lease->belongsToClient(decline->getHWAddr(), client_id)) {
4214
4215 // Get printable hardware addresses
4216 string client_hw = decline->getHWAddr() ?
4217 decline->getHWAddr()->toText(false) : "(none)";
4218 string lease_hw = lease->hwaddr_ ?
4219 lease->hwaddr_->toText(false) : "(none)";
4220
4221 // Get printable client-ids
4222 string client_id_txt = client_id ? client_id->toText() : "(none)";
4223 string lease_id_txt = lease->client_id_ ?
4224 lease->client_id_->toText() : "(none)";
4225
4226 // Print the warning and we're done here.
4228 .arg(addr.toText()).arg(decline->getLabel())
4229 .arg(client_hw).arg(lease_hw).arg(client_id_txt).arg(lease_id_txt);
4230
4231 return;
4232 }
4233
4234 // Ok, all is good. The client is reporting its own address. Let's
4235 // process it.
4236 declineLease(lease, decline, context);
4237}
4238
4239void
4240Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline,
4242
4243 // Let's check if there are hooks installed for decline4 hook point.
4244 // If they are, let's pass the lease and client's packet. If the hook
4245 // sets status to drop, we reject this Decline.
4246 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_decline_)) {
4247 CalloutHandlePtr callout_handle = getCalloutHandle(decline);
4248
4249 // Use the RAII wrapper to make sure that the callout handle state is
4250 // reset when this object goes out of scope. All hook points must do
4251 // it to prevent possible circular dependency between the callout
4252 // handle and its arguments.
4253 ScopedCalloutHandleState callout_handle_state(callout_handle);
4254
4255 // Enable copying options from the packet within hook library.
4256 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(decline);
4257
4258 // Pass the original packet
4259 callout_handle->setArgument("query4", decline);
4260
4261 // Pass the lease to be updated
4262 callout_handle->setArgument("lease4", lease);
4263
4264 // Call callouts
4265 HooksManager::callCallouts(Hooks.hook_index_lease4_decline_,
4266 *callout_handle);
4267
4268 // Check if callouts decided to skip the next processing step.
4269 // If any of them did, we will drop the packet.
4270 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
4271 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
4273 .arg(decline->getLabel()).arg(lease->addr_.toText());
4274 return;
4275 }
4276 }
4277
4278 Lease4Ptr old_values = boost::make_shared<Lease4>(*lease);
4279
4280 // @todo: Call hooks.
4281
4282 // We need to disassociate the lease from the client. Once we move a lease
4283 // to declined state, it is no longer associated with the client in any
4284 // way.
4285 lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
4286
4287 try {
4289 } catch (const Exception& ex) {
4290 // Update failed.
4292 .arg(decline->getLabel())
4293 .arg(lease->addr_.toText())
4294 .arg(ex.what());
4295 return;
4296 }
4297
4298 // Remove existing DNS entries for the lease, if any.
4299 // queueNCR will do the necessary checks and will skip the update, if not needed.
4300 queueNCR(CHG_REMOVE, old_values);
4301
4302 // Bump up the statistics.
4303
4304 // Per subnet declined addresses counter.
4306 StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
4307 static_cast<int64_t>(1));
4308
4309 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
4310 if (subnet) {
4311 auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
4312 if (pool) {
4314 StatsMgr::generateName("subnet", subnet->getID(),
4315 StatsMgr::generateName("pool", pool->getID(), "declined-addresses")),
4316 static_cast<int64_t>(1));
4317 }
4318 }
4319
4320 // Global declined addresses counter.
4321 StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
4322
4323 // We do not want to decrease the assigned-addresses at this time. While
4324 // technically a declined address is no longer allocated, the primary usage
4325 // of the assigned-addresses statistic is to monitor pool utilization. Most
4326 // people would forget to include declined-addresses in the calculation,
4327 // and simply do assigned-addresses/total-addresses. This would have a bias
4328 // towards under-representing pool utilization, if we decreased allocated
4329 // immediately after receiving DHCPDECLINE, rather than later when we recover
4330 // the address.
4331
4332 context.reset(new AllocEngine::ClientContext4());
4333 context->new_lease_ = lease;
4334
4335 LOG_INFO(lease4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText())
4336 .arg(decline->getLabel()).arg(lease->valid_lft_);
4337}
4338
4339void
4341 Lease4Ptr lease, bool lease_exists) {
4343 .arg(query->getLabel())
4344 .arg(lease->addr_.toText())
4345 .arg(lease->valid_lft_);
4346
4347 {
4348 // Check if the resource is busy i.e. can be modified by another thread
4349 // for another client. Highly unlikely.
4350 ResourceHandler4 resource_handler;
4351 if (MultiThreadingMgr::instance().getMode() && !resource_handler.tryLock4(lease->addr_)) {
4353 .arg(query->getLabel())
4354 .arg(lease->addr_.toText());
4355 return;
4356 }
4357
4358 // We need to disassociate the lease from the client. Once we move a lease
4359 // to declined state, it is no longer associated with the client in any
4360 // way.
4361 lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
4362
4363 // If the lease already exists, update it in the database.
4364 if (lease_exists) {
4365 try {
4367 } catch (const NoSuchLease& ex) {
4368 // We expected the lease to exist but it doesn't so let's try
4369 // to add it.
4370 lease_exists = false;
4371 } catch (const Exception& ex) {
4372 // Update failed.
4374 .arg(query->getLabel())
4375 .arg(lease->addr_.toText());
4376 return;
4377 }
4378 }
4379
4380 if (!lease_exists) {
4381 try {
4383 } catch (const Exception& ex) {
4385 .arg(query->getLabel())
4386 .arg(lease->addr_.toText());
4387 return;
4388 }
4389 }
4390 }
4391
4392 // Bump up the statistics. If the lease does not exist (i.e. offer-lifetime == 0) we
4393 // need to increment assigned address stats, otherwise the accounting will be off.
4394 // This saves us from having to determine later, when declined leases are reclaimed,
4395 // whether or not we need to decrement assigned stats. In other words, this keeps
4396 // a declined lease always counted also as an assigned lease, regardless of how
4397 // it was declined, until it is reclaimed at which point both groups of stats
4398 // are decremented.
4399
4400 // Per subnet declined addresses counter.
4402 StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
4403 static_cast<int64_t>(1));
4404
4405 if (!lease_exists) {
4407 StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
4408 static_cast<int64_t>(1));
4409 }
4410
4411 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
4412 if (subnet) {
4413 auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
4414 if (pool) {
4416 StatsMgr::generateName("subnet", subnet->getID(),
4417 StatsMgr::generateName("pool", pool->getID(), "declined-addresses")),
4418 static_cast<int64_t>(1));
4419 if (!lease_exists) {
4421 StatsMgr::generateName("subnet", subnet->getID(),
4422 StatsMgr::generateName("pool", pool->getID(), "assigned-addresses")),
4423 static_cast<int64_t>(1));
4424 }
4425 }
4426 }
4427
4428 // Global declined addresses counter.
4429 StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
4430 if (!lease_exists) {
4431 StatsMgr::instance().addValue("assigned-addresses", static_cast<int64_t>(1));
4432 }
4433
4434 // Let's check if there are hooks installed for server decline hook point.
4435 // If there are, let's pass the DHCPDISCOVER and the declined lease .
4436 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_server_decline_)) {
4437 // Use the RAII wrapper to make sure that the callout handle state is
4438 // reset when this object goes out of scope. All hook points must do
4439 // it to prevent possible circular dependency between the callout
4440 // handle and its arguments.
4441 ScopedCalloutHandleState callout_handle_state(callout_handle);
4442
4443 // Pass in the original DHCPDISCOVER
4444 callout_handle->setArgument("query4", query);
4445
4446 // Pass in the declined lease.
4447 callout_handle->setArgument("lease4", lease);
4448
4449 // Call callouts
4450 HooksManager::callCallouts(Hooks.hook_index_lease4_server_decline_,
4451 *callout_handle);
4452 }
4453}
4454
4455void
4457 Lease4Ptr lease, bool lease_exists) {
4458 try {
4459 serverDecline(callout_handle, query, lease, lease_exists);
4460 } catch (...) {
4462 }
4463}
4464
4465Pkt4Ptr
4467 bool drop = false;
4468 Dhcpv4Exchange ex(alloc_engine_, inform, context, context->subnet_, drop);
4469
4470 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
4471 if (drop) {
4472 return (Pkt4Ptr());
4473 }
4474
4475 Pkt4Ptr ack = ex.getResponse();
4476
4477 // If this is global reservation or the subnet doesn't belong to a shared
4478 // network we have already fetched it and evaluated the classes.
4480
4481 // Evaluate additional classes.
4483
4485 .arg(inform->getLabel())
4486 .arg(inform->getName())
4487 .arg(inform->getClasses().toText());
4488
4493 adjustIfaceData(ex);
4494
4495 // Set fixed fields (siaddr, sname, filename) if defined in
4496 // the reservation, class or subnet specific configuration.
4497 setFixedFields(ex);
4498
4499 // There are cases for the DHCPINFORM that the server receives it via
4500 // relay but will send the response to the client's unicast address
4501 // carried in the ciaddr. In this case, the giaddr and hops field should
4502 // be cleared (these fields were copied by the copyDefaultFields function).
4503 // Also Relay Agent Options should be removed if present.
4504 if (ack->getRemoteAddr() != inform->getGiaddr()) {
4506 .arg(inform->getLabel())
4507 .arg(ack->getRemoteAddr())
4508 .arg(ack->getIface());
4509 ack->setHops(0);
4510 ack->setGiaddr(IOAddress::IPV4_ZERO_ADDRESS());
4511 ack->delOption(DHO_DHCP_AGENT_OPTIONS);
4512 }
4513
4514 // The DHCPACK must contain server id.
4515 appendServerID(ex);
4516
4517 return (ex.getResponse());
4518}
4519
4520void
4522 if (query->getCiaddr().isV4Zero() || !query->getGiaddr().isV4Zero()) {
4523 return;
4524 }
4526 getConfiguredGlobal(CfgGlobals::STASH_AGENT_OPTIONS);
4527 if (!sao || (sao->getType() != Element::boolean) || !sao->boolValue()) {
4528 return;
4529 }
4530 if (query->getType() != DHCPREQUEST) {
4531 return;
4532 }
4533 OptionPtr rai_opt = query->getOption(DHO_DHCP_AGENT_OPTIONS);
4534 if (rai_opt && (rai_opt->len() > Option::OPTION4_HDR_LEN)) {
4535 return;
4536 }
4537 // Should not happen but makes sense to check and gives a trivial way
4538 // to disable the feature from previous callout points.
4539 if (query->inClass("STASH_AGENT_OPTIONS")) {
4540 return;
4541 }
4542 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(query->getCiaddr());
4543 if (!lease || lease->expired()) {
4544 return;
4545 }
4546 ConstElementPtr user_context = lease->getContext();
4547 if (!user_context || (user_context->getType() != Element::map)) {
4548 return;
4549 }
4550 ConstElementPtr isc = user_context->get("ISC");
4551 if (!isc || (isc->getType() != Element::map)) {
4552 return;
4553 }
4554 ConstElementPtr relay_agent_info = isc->get("relay-agent-info");
4555 if (!relay_agent_info) {
4556 return;
4557 }
4558 // Compatibility with the old layout.
4559 if (relay_agent_info->getType() == Element::map) {
4560 relay_agent_info = relay_agent_info->get("sub-options");
4561 if (!relay_agent_info) {
4562 return;
4563 }
4564 }
4565 if (relay_agent_info->getType() != Element::string) {
4566 return;
4567 }
4568 // Check ownership before going further.
4569 ClientIdPtr client_id;
4570 OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
4571 if (opt_clientid) {
4572 client_id.reset(new ClientId(opt_clientid->getData()));
4573 }
4574 if (!lease->belongsToClient(query->getHWAddr(), client_id)) {
4575 return;
4576 }
4577 // Extract the RAI.
4578 string rai_hex = relay_agent_info->stringValue();
4579 if (rai_hex.empty()) {
4580 return;
4581 }
4582 vector<uint8_t> rai_data;
4583 str::decodeFormattedHexString(rai_hex, rai_data);
4584 static const OptionDefinition& rai_def = LibDHCP::DHO_DHCP_AGENT_OPTIONS_DEF();
4585 OptionCustomPtr rai(new OptionCustom(rai_def, Option::V4, rai_data));
4586 // unpackOptions is a bit too flexible so check if it got something...
4587 if (!rai || rai->getOptions().empty()) {
4588 return;
4589 }
4590 // Remove an existing empty RAI.
4591 if (rai_opt) {
4592 query->delOption(DHO_DHCP_AGENT_OPTIONS);
4593 }
4594 query->addOption(rai);
4595 query->addClass("STASH_AGENT_OPTIONS");
4598 .arg(query->getLabel())
4599 .arg(query->getCiaddr())
4600 .arg(rai->toText());
4601}
4602
4603bool
4605 // Check that the message type is accepted by the server. We rely on the
4606 // function called to log a message if needed.
4607 if (!acceptMessageType(query)) {
4608 return (false);
4609 }
4610 // Check if the message from directly connected client (if directly
4611 // connected) should be dropped or processed.
4612 if (!acceptDirectRequest(query)) {
4614 .arg(query->getLabel())
4615 .arg(query->getIface());
4616 return (false);
4617 }
4618
4619 // Check if the DHCPv4 packet has been sent to us or to someone else.
4620 // If it hasn't been sent to us, drop it!
4621 if (!acceptServerId(query)) {
4623 .arg(query->getLabel())
4624 .arg(query->getIface());
4625 return (false);
4626 }
4627
4628 return (true);
4629}
4630
4631bool
4633 // Accept all relayed messages.
4634 if (pkt->isRelayed()) {
4635 return (true);
4636 }
4637
4638 // Accept all DHCPv4-over-DHCPv6 messages.
4639 if (pkt->isDhcp4o6()) {
4640 return (true);
4641 }
4642
4643 // The source address must not be zero for the DHCPINFORM message from
4644 // the directly connected client because the server will not know where
4645 // to respond if the ciaddr was not present.
4646 try {
4647 if (pkt->getType() == DHCPINFORM) {
4648 if (pkt->getRemoteAddr().isV4Zero() &&
4649 pkt->getCiaddr().isV4Zero()) {
4650 return (false);
4651 }
4652 }
4653 } catch (...) {
4654 // If we got here, it is probably because the message type hasn't
4655 // been set. But, this should not really happen assuming that
4656 // we validate the message type prior to calling this function.
4657 return (false);
4658 }
4659 bool drop = false;
4660 bool result = (!pkt->getLocalAddr().isV4Bcast() ||
4661 selectSubnet(pkt, drop, true));
4662 if (drop) {
4663 // The packet must be dropped but as sanity_only is true it is dead code.
4664 return (false);
4665 }
4666 return (result);
4667}
4668
4669bool
4671 // When receiving a packet without message type option, getType() will
4672 // throw.
4673 int type;
4674 try {
4675 type = query->getType();
4676
4677 } catch (...) {
4679 .arg(query->getLabel())
4680 .arg(query->getIface());
4681 return (false);
4682 }
4683
4684 // Once we know that the message type is within a range of defined DHCPv4
4685 // messages, we do a detailed check to make sure that the received message
4686 // is targeted at server. Note that we could have received some Offer
4687 // message broadcasted by the other server to a relay. Even though, the
4688 // server would rather unicast its response to a relay, let's be on the
4689 // safe side. Also, we want to drop other messages which we don't support.
4690 // All these valid messages that we are not going to process are dropped
4691 // silently.
4692
4693 switch(type) {
4694 case DHCPDISCOVER:
4695 case DHCPREQUEST:
4696 case DHCPRELEASE:
4697 case DHCPDECLINE:
4698 case DHCPINFORM:
4699 return (true);
4700 break;
4701
4702 case DHCP_NOTYPE:
4704 .arg(query->getLabel());
4705 break;
4706
4707 default:
4708 // If we receive a message with a non-existing type, we are logging it.
4709 if (type >= DHCP_TYPES_EOF) {
4711 .arg(query->getLabel())
4712 .arg(type);
4713 } else {
4714 // Exists but we don't support it.
4716 .arg(query->getLabel())
4717 .arg(type);
4718 }
4719 break;
4720 }
4721
4722 return (false);
4723}
4724
4725bool
4727 // This function is meant to be called internally by the server class, so
4728 // we rely on the caller to sanity check the pointer and we don't check
4729 // it here.
4730
4731 // Check if server identifier option is present. If it is not present
4732 // we accept the message because it is targeted to all servers.
4733 // Note that we don't check cases that server identifier is mandatory
4734 // but not present. This is meant to be sanity checked in other
4735 // functions.
4736 OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
4737 if (!option) {
4738 return (true);
4739 }
4740 // Server identifier is present. Let's convert it to 4-byte address
4741 // and try to match with server identifiers used by the server.
4742 OptionCustomPtr option_custom =
4743 boost::dynamic_pointer_cast<OptionCustom>(option);
4744 // Unable to convert the option to the option type which encapsulates it.
4745 // We treat this as non-matching server id.
4746 if (!option_custom) {
4747 return (false);
4748 }
4749 // The server identifier option should carry exactly one IPv4 address.
4750 // If the option definition for the server identifier doesn't change,
4751 // the OptionCustom object should have exactly one IPv4 address and
4752 // this check is somewhat redundant. On the other hand, if someone
4753 // breaks option it may be better to check that here.
4754 if (option_custom->getDataFieldsNum() != 1) {
4755 return (false);
4756 }
4757
4758 // The server identifier MUST be an IPv4 address. If given address is
4759 // v6, it is wrong.
4760 IOAddress server_id = option_custom->readAddress();
4761 if (!server_id.isV4()) {
4762 return (false);
4763 }
4764
4765 // According to RFC5107, the RAI_OPTION_SERVER_ID_OVERRIDE option if
4766 // present, should match DHO_DHCP_SERVER_IDENTIFIER option.
4767 OptionPtr rai_option = query->getOption(DHO_DHCP_AGENT_OPTIONS);
4768 if (rai_option) {
4769 OptionPtr rai_suboption = rai_option->getOption(RAI_OPTION_SERVER_ID_OVERRIDE);
4770 if (rai_suboption && (server_id.toBytes() == rai_suboption->toBinary())) {
4771 return (true);
4772 }
4773 }
4774
4775 // Skip address check if configured to ignore the server id.
4777 if (cfg->getIgnoreServerIdentifier()) {
4778 return (true);
4779 }
4780
4781 // This function iterates over all interfaces on which the
4782 // server is listening to find the one which has a socket bound
4783 // to the address carried in the server identifier option.
4784 // This has some performance implications. However, given that
4785 // typically there will be just a few active interfaces the
4786 // performance hit should be acceptable. If it turns out to
4787 // be significant, we will have to cache server identifiers
4788 // when sockets are opened.
4789 if (IfaceMgr::instance().hasOpenSocket(server_id)) {
4790 return (true);
4791 }
4792
4793 // There are some cases when an administrator explicitly sets server
4794 // identifier (option 54) that should be used for a given, subnet,
4795 // network etc. It doesn't have to be an address assigned to any of
4796 // the server interfaces. Thus, we have to check if the server
4797 // identifier received is the one that we explicitly set in the
4798 // server configuration. At this point, we don't know which subnet
4799 // the client belongs to so we can't match the server id with any
4800 // subnet. We simply check if this server identifier is configured
4801 // anywhere. This should be good enough to eliminate exchanges
4802 // with other servers in the same network.
4803
4811
4812 // Check if there is at least one subnet configured with this server
4813 // identifier.
4814 ConstCfgSubnets4Ptr cfg_subnets = cfg->getCfgSubnets4();
4815 if (cfg_subnets->hasSubnetWithServerId(server_id)) {
4816 return (true);
4817 }
4818
4819 // This server identifier is not configured for any of the subnets, so
4820 // check on the shared network level.
4821 CfgSharedNetworks4Ptr cfg_networks = cfg->getCfgSharedNetworks4();
4822 if (cfg_networks->hasNetworkWithServerId(server_id)) {
4823 return (true);
4824 }
4825
4826 // Check if the server identifier is configured at client class level.
4827 const ClientClasses& classes = query->getClasses();
4828 for (auto const& cclass : classes) {
4829 // Find the client class definition for this class
4831 getClientClassDictionary()->findClass(cclass);
4832 if (!ccdef) {
4833 continue;
4834 }
4835
4836 if (ccdef->getCfgOption()->empty()) {
4837 // Skip classes which don't configure options
4838 continue;
4839 }
4840
4841 OptionCustomPtr context_opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
4842 (ccdef->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
4843 if (context_opt_server_id && (context_opt_server_id->readAddress() == server_id)) {
4844 return (true);
4845 }
4846 }
4847
4848 // Finally, it is possible that the server identifier is specified
4849 // on the global level.
4850 ConstCfgOptionPtr cfg_global_options = cfg->getCfgOption();
4851 OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
4852 (cfg_global_options->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
4853
4854 return (opt_server_id && (opt_server_id->readAddress() == server_id));
4855}
4856
4857void
4859 switch (query->getType()) {
4860 case DHCPDISCOVER:
4861 // server-id is forbidden.
4862 sanityCheck(query, FORBIDDEN);
4863 break;
4864 case DHCPREQUEST:
4865 // Since we cannot distinguish between client states
4866 // we'll make server-id is optional for REQUESTs.
4867 sanityCheck(query, OPTIONAL);
4868 break;
4869 case DHCPRELEASE:
4870 // Server-id is mandatory in DHCPRELEASE (see table 5, RFC2131)
4871 // but ISC DHCP does not enforce this, so we'll follow suit.
4872 sanityCheck(query, OPTIONAL);
4873 break;
4874 case DHCPDECLINE:
4875 // Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131)
4876 // but ISC DHCP does not enforce this, so we'll follow suit.
4877 sanityCheck(query, OPTIONAL);
4878 break;
4879 case DHCPINFORM:
4880 // server-id is supposed to be forbidden (as is requested address)
4881 // but ISC DHCP does not enforce either. So neither will we.
4882 sanityCheck(query, OPTIONAL);
4883 break;
4884 }
4885}
4886
4887void
4889 OptionPtr server_id = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
4890 switch (serverid) {
4891 case FORBIDDEN:
4892 if (server_id) {
4893 isc_throw(RFCViolation, "Server-id option was not expected, but"
4894 << " received in message "
4895 << query->getName());
4896 }
4897 break;
4898
4899 case MANDATORY:
4900 if (!server_id) {
4901 isc_throw(RFCViolation, "Server-id option was expected, but not"
4902 " received in message "
4903 << query->getName());
4904 }
4905 break;
4906
4907 case OPTIONAL:
4908 // do nothing here
4909 ;
4910 }
4911
4912 // If there is HWAddress set and it is non-empty, then we're good
4913 if (query->getHWAddr() && !query->getHWAddr()->hwaddr_.empty()) {
4914 return;
4915 }
4916
4917 // There has to be something to uniquely identify the client:
4918 // either non-zero MAC address or client-id option present (or both)
4919 OptionPtr client_id = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
4920
4921 // If there's no client-id (or a useless one is provided, i.e. 0 length)
4922 if (!client_id || client_id->len() == client_id->getHeaderLen()) {
4923 isc_throw(RFCViolation, "Missing or useless client-id and no HW address"
4924 " provided in message "
4925 << query->getName());
4926 }
4927}
4928
4932
4934 // First collect required classes
4935 Pkt4Ptr query = ex.getQuery();
4936 ClientClasses classes = query->getAdditionalClasses();
4937 ConstSubnet4Ptr subnet = ex.getContext()->subnet_;
4938
4939 if (subnet) {
4940 // host reservation???
4941
4942 // Begin by the pool
4943 Pkt4Ptr resp = ex.getResponse();
4945 if (resp) {
4946 addr = resp->getYiaddr();
4947 }
4948 if (!addr.isV4Zero()) {
4949 PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
4950 if (pool) {
4951 const ClientClasses& pool_to_add = pool->getAdditionalClasses();
4952 for (auto const& cclass : pool_to_add) {
4953 classes.insert(cclass);
4954 }
4955 }
4956 }
4957
4958 // Followed by the subnet
4959 const ClientClasses& to_add = subnet->getAdditionalClasses();
4960 for (auto const& cclass : to_add) {
4961 classes.insert(cclass);
4962 }
4963
4964 // And finish by the shared-network
4965 SharedNetwork4Ptr network;
4966 subnet->getSharedNetwork(network);
4967 if (network) {
4968 const ClientClasses& net_to_add = network->getAdditionalClasses();
4969 for (auto const& cclass : net_to_add) {
4970 classes.insert(cclass);
4971 }
4972 }
4973 }
4974
4975 // Run match expressions
4976 // Note getClientClassDictionary() cannot be null
4977 const ClientClassDictionaryPtr& dict =
4978 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
4979 for (auto const& cclass : classes) {
4980 const ClientClassDefPtr class_def = dict->findClass(cclass);
4981 if (!class_def) {
4984 .arg(cclass);
4985 // Ignore it as it can't have an attached action
4986 continue;
4987 }
4988 const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
4989 // Add a class without an expression to evaluate
4990 if (!expr_ptr) {
4993 .arg(cclass);
4994 query->addClass(cclass);
4995 continue;
4996 }
4997 // Evaluate the expression which can return false (no match),
4998 // true (match) or raise an exception (error)
4999 try {
5000 bool status = evaluateBool(*expr_ptr, *query);
5002 .arg(query->getLabel())
5003 .arg(cclass)
5004 .arg(status ? "true" : "false");
5005 if (status) {
5006 // Matching: add the class
5007 query->addClass(cclass);
5008 }
5009 } catch (const Exception& ex) {
5011 .arg(query->getLabel())
5012 .arg(cclass)
5013 .arg(ex.what());
5014 }
5015 }
5016}
5017
5018void
5020 // Iterate on the list of deferred option codes
5021 for (auto const& code : query->getDeferredOptions()) {
5023 // Iterate on client classes
5024 const ClientClasses& classes = query->getClasses();
5025 for (auto const& cclass : classes) {
5026 // Get the client class definition for this class
5027 const ClientClassDefPtr& ccdef =
5029 getClientClassDictionary()->findClass(cclass);
5030 // If not found skip it
5031 if (!ccdef) {
5032 continue;
5033 }
5034 // If there is no option definition skip it
5035 if (!ccdef->getCfgOptionDef()) {
5036 continue;
5037 }
5038 def = ccdef->getCfgOptionDef()->get(DHCP4_OPTION_SPACE, code);
5039 // Stop at the first client class with a definition
5040 if (def) {
5041 break;
5042 }
5043 }
5044 // If not found try the global definition
5045 if (!def) {
5047 }
5048 if (!def) {
5050 }
5051 // Finish by last resort definition
5052 if (!def) {
5054 }
5055 // If not defined go to the next option
5056 if (!def) {
5057 continue;
5058 }
5059 // Get the existing option for its content and remove all
5060 OptionPtr opt = query->getOption(code);
5061 if (!opt) {
5062 // should not happen but do not crash anyway
5065 .arg(query->getLabel())
5066 .arg(code);
5067 continue;
5068 }
5069 // Because options have already been fused, the buffer contains entire
5070 // data.
5071 const OptionBuffer buf = opt->getData();
5072 try {
5073 // Unpack the option
5074 opt = def->optionFactory(Option::V4, code, buf);
5075 } catch (const std::exception& e) {
5076 // Failed to parse the option.
5079 .arg(query->getLabel())
5080 .arg(code)
5081 .arg(e.what());
5082 continue;
5083 }
5084 while (query->delOption(code)) {
5085 // continue
5086 }
5087 // Add the unpacked option.
5088 query->addOption(opt);
5089 }
5090}
5091
5092void
5095 if (d2_mgr.ddnsEnabled()) {
5096 // Updates are enabled, so lets start the sender, passing in
5097 // our error handler.
5098 // This may throw so wherever this is called needs to ready.
5100 this, ph::_1, ph::_2));
5101 }
5102}
5103
5104void
5107 if (d2_mgr.ddnsEnabled()) {
5108 // Updates are enabled, so lets stop the sender
5109 d2_mgr.stop();
5110 d2_mgr.stopSender();
5111 }
5112}
5113
5114void
5119 arg(result).arg((ncr ? ncr->toText() : " NULL "));
5120 // We cannot communicate with kea-dhcp-ddns, suspend further updates.
5124}
5125
5126std::string
5128 std::stringstream tmp;
5129
5130 tmp << VERSION;
5131 if (extended) {
5132 tmp << " (" << EXTENDED_VERSION << ")" << endl;
5133 tmp << "premium: " << PREMIUM_EXTENDED_VERSION << endl;
5134 tmp << "linked with:" << endl;
5135 tmp << "- " << Logger::getVersion() << endl;
5136 tmp << "- " << CryptoLink::getVersion();
5138 if (info.size()) {
5139 tmp << endl << "lease backends:";
5140 for (auto const& version : info) {
5141 tmp << endl << "- " << version;
5142 }
5143 }
5145 if (info.size()) {
5146 tmp << endl << "host backends:";
5147 for (auto const& version : info) {
5148 tmp << endl << "- " << version;
5149 }
5150 }
5152 if (info.size()) {
5153 tmp << endl << "forensic backends:";
5154 for (auto const& version : info) {
5155 tmp << endl << "- " << version;
5156 }
5157 }
5158 // @todo: more details about database runtime
5159 }
5160
5161 return (tmp.str());
5162}
5163
5165 // Note that we're not bumping pkt4-received statistic as it was
5166 // increased early in the packet reception code.
5167
5168 string stat_name = "pkt4-unknown-received";
5169 try {
5170 switch (query->getType()) {
5171 case DHCPDISCOVER:
5172 stat_name = "pkt4-discover-received";
5173 break;
5174 case DHCPOFFER:
5175 // Should not happen, but let's keep a counter for it
5176 stat_name = "pkt4-offer-received";
5177 break;
5178 case DHCPREQUEST:
5179 stat_name = "pkt4-request-received";
5180 break;
5181 case DHCPACK:
5182 // Should not happen, but let's keep a counter for it
5183 stat_name = "pkt4-ack-received";
5184 break;
5185 case DHCPNAK:
5186 // Should not happen, but let's keep a counter for it
5187 stat_name = "pkt4-nak-received";
5188 break;
5189 case DHCPRELEASE:
5190 stat_name = "pkt4-release-received";
5191 break;
5192 case DHCPDECLINE:
5193 stat_name = "pkt4-decline-received";
5194 break;
5195 case DHCPINFORM:
5196 stat_name = "pkt4-inform-received";
5197 break;
5198 default:
5199 ; // do nothing
5200 }
5201 }
5202 catch (...) {
5203 // If the incoming packet doesn't have option 53 (message type)
5204 // or a hook set pkt4_receive_skip, then Pkt4::getType() may
5205 // throw an exception. That's ok, we'll then use the default
5206 // name of pkt4-unknown-received.
5207 }
5208
5210 static_cast<int64_t>(1));
5211}
5212
5214 // Increase generic counter for sent packets.
5216 static_cast<int64_t>(1));
5217
5218 // Increase packet type specific counter for packets sent.
5219 string stat_name;
5220 switch (response->getType()) {
5221 case DHCPOFFER:
5222 stat_name = "pkt4-offer-sent";
5223 break;
5224 case DHCPACK:
5225 stat_name = "pkt4-ack-sent";
5226 break;
5227 case DHCPNAK:
5228 stat_name = "pkt4-nak-sent";
5229 break;
5230 default:
5231 // That should never happen
5232 return;
5233 }
5234
5236 static_cast<int64_t>(1));
5237}
5238
5240 return (Hooks.hook_index_buffer4_receive_);
5241}
5242
5244 return (Hooks.hook_index_pkt4_receive_);
5245}
5246
5248 return (Hooks.hook_index_subnet4_select_);
5249}
5250
5252 return (Hooks.hook_index_lease4_release_);
5253}
5254
5256 return (Hooks.hook_index_pkt4_send_);
5257}
5258
5260 return (Hooks.hook_index_buffer4_send_);
5261}
5262
5264 return (Hooks.hook_index_lease4_decline_);
5265}
5266
5268 // Dump all of our current packets, anything that is mid-stream
5270}
5271
5273#ifdef FUZZING
5274 char const* const rotate(getenv("KEA_DHCP4_FUZZING_ROTATE_PORT"));
5275 if (rotate) {
5276 InterprocessSyncFile file("kea-dhcp4-fuzzing-rotate-port");
5278 while (!locker.lock()) {
5279 this_thread::sleep_for(1s);
5280 }
5281 fstream port_file;
5282 port_file.open("/tmp/port4.txt", ios::in);
5283 string line;
5284 int port;
5285 getline(port_file, line);
5286 port_file.close();
5287 if (line.empty()) {
5288 port = 2000;
5289 } else {
5290 port = stoi(line);
5291 if (port < 3000) {
5292 ++port;
5293 } else {
5294 port = 2000;
5295 }
5296 }
5297 port_file.open("/tmp/port4.txt", ios::out | ios::trunc);
5298 port_file << to_string(port) << endl;
5299 port_file.close();
5300 locker.unlock();
5301 return port;
5302 }
5303#endif // FUZZING
5304 return server_port_;
5305}
5306
5307std::list<std::list<std::string>> Dhcpv4Srv::jsonPathsToRedact() const {
5308 static std::list<std::list<std::string>> const list({
5309 {"config-control", "config-databases", "[]"},
5310 {"hooks-libraries", "[]", "parameters", "*"},
5311 {"hosts-database"},
5312 {"hosts-databases", "[]"},
5313 {"lease-database"},
5314 });
5315 return list;
5316}
5317
5318} // namespace dhcp
5319} // 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:909
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.
Exception thrown when host name sanitizing reduces the domain name to an empty string.
static std::list< std::string > getDBVersions()
Return extended version info for registered backends.
static void create()
Creates new instance of the HostMgr.
Definition host_mgr.cc:52
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
const isc::log::MessageID DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY
isc::log::Logger options4_logger(DHCP4_OPTIONS_LOGGER_NAME)
Logger for options parser.
Definition dhcp4_log.h:109
const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_SKIP
const int DBG_DHCP4_DETAIL_DATA
This level is used to log the contents of packets received and sent.
Definition dhcp4_log.h:56
const isc::log::MessageID DHCP4_PACKET_PACK
boost::shared_ptr< AllocEngine > AllocEnginePtr
A pointer to the AllocEngine object.
ContinuationPtr makeContinuation(Continuation &&cont)
Continuation factory.
const isc::log::MessageID DHCP4_DECLINE_FAIL
const isc::log::MessageID DHCP4_RECOVERED_STASHED_RELAY_AGENT_INFO
const isc::log::MessageID DHCP4_LEASE_REUSE
const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_PARK
boost::shared_ptr< const CfgHostOperations > ConstCfgHostOperationsPtr
Pointer to the const object.
boost::shared_ptr< CfgIface > CfgIfacePtr
A pointer to the CfgIface .
Definition cfg_iface.h:501
const isc::log::MessageID DHCP4_PACKET_PACK_FAIL
boost::shared_ptr< ClientClassDef > ClientClassDefPtr
a pointer to an ClientClassDef
const isc::log::MessageID DHCP4_DDNS_REQUEST_SEND_FAILED
const isc::log::MessageID DHCP4_GENERATE_FQDN
const isc::log::MessageID DHCP4_CLASS_ASSIGNED
const isc::log::MessageID DHCP4_PACKET_PROCESS_STD_EXCEPTION
boost::shared_ptr< SrvConfig > SrvConfigPtr
Non-const pointer to the SrvConfig.
const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_DATA
const isc::log::MessageID DHCP4_BUFFER_WAIT_SIGNAL
const isc::log::MessageID DHCP4_HOOK_LEASE4_RELEASE_SKIP
const isc::log::MessageID DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL
const isc::log::MessageID DHCP4_PACKET_NAK_0001
const isc::log::MessageID DHCP4_HOOK_DECLINE_SKIP
const isc::log::MessageID DHCP4_HOOK_LEASE4_OFFER_PARK
const isc::log::MessageID DHCP4_DECLINE_LEASE_MISMATCH
const isc::log::MessageID DHCP4_SRV_UNLOAD_LIBRARIES_ERROR
const isc::log::MessageID DHCP4_ADDITIONAL_CLASS_EVAL_ERROR
const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_GENERATE
boost::shared_ptr< HWAddr > HWAddrPtr
Shared pointer to a hardware address structure.
Definition hwaddr.h:154
const isc::log::MessageID DHCP4_PACKET_DROP_0013
const isc::log::MessageID DHCP4_PACKET_QUEUE_FULL
const isc::log::MessageID DHCP4_LEASE_OFFER
const isc::log::MessageID DHCP4_PACKET_DROP_0009
const isc::log::MessageID DHCP4_PACKET_RECEIVED
boost::shared_ptr< Pkt4o6 > Pkt4o6Ptr
A pointer to Pkt4o6 object.
Definition pkt4o6.h:82
const isc::log::MessageID DHCP4_RELEASE_DELETED
const isc::log::MessageID DHCP4_BUFFER_UNPACK
const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_DATA
OptionContainer::nth_index< 5 >::type OptionContainerCancelIndex
Type of the index #5 - option cancellation flag.
Definition cfg_option.h: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
const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY
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