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