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