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