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