Kea 2.7.6
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.
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 {
737 } catch (const std::exception& ex) {
738 // Highly unlikely, but lets Report it but go on
740 }
741
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 }
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]() {
836 };
837 } else {
838 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
839 }
840 });
841
842 // Call user (and server-side) callouts
843 try {
844 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
845 *callout_handle);
846 } catch (...) {
847 // Make sure we don't orphan a parked packet.
848 HooksManager::drop("subnet4_select", query);
849 throw;
850 }
851
852 // Callouts parked the packet. Same as drop but callouts will resume
853 // processing or drop the packet later.
854 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
857 .arg(query->getLabel());
858 drop = true;
859 return (ConstSubnet4Ptr());
860 } else {
861 HooksManager::drop("subnet4_select", query);
862 }
863
864 // Callouts decided to skip this step. This means that no subnet
865 // will be selected. Packet processing will continue, but it will
866 // be severely limited (i.e. only global options will be assigned)
867 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
870 .arg(query->getLabel());
871 return (ConstSubnet4Ptr());
872 }
873
874 // Callouts decided to drop the packet. It is a superset of the
875 // skip case so no subnet will be selected.
876 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
879 .arg(query->getLabel());
880 drop = true;
881 return (ConstSubnet4Ptr());
882 }
883
884 // Use whatever subnet was specified by the callout
885 callout_handle->getArgument("subnet4", subnet);
886 }
887
888 if (subnet) {
889 // Log at higher debug level that subnet has been found.
891 .arg(query->getLabel())
892 .arg(subnet->getID());
893 // Log detailed information about the selected subnet at the
894 // lower debug level.
896 .arg(query->getLabel())
897 .arg(subnet->toText());
898
899 } else {
902 .arg(query->getLabel());
903 }
904
905 return (subnet);
906}
907
909Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop,
910 bool sanity_only, bool allow_answer_park) {
911 ConstSubnet4Ptr subnet;
912
913 SubnetSelector selector;
914 selector.ciaddr_ = query->getCiaddr();
915 selector.giaddr_ = query->getGiaddr();
916 selector.local_address_ = query->getLocalAddr();
917 selector.client_classes_ = query->classes_;
918 selector.iface_name_ = query->getIface();
919 // Mark it as DHCPv4-over-DHCPv6
920 selector.dhcp4o6_ = true;
921 // Now the DHCPv6 part
922 selector.remote_address_ = query->getRemoteAddr();
923 selector.first_relay_linkaddr_ = IOAddress("::");
924
925 // Handle a DHCPv6 relayed query
926 Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast<Pkt4o6>(query);
927 if (!query4o6) {
928 isc_throw(Unexpected, "Can't get DHCP4o6 message");
929 }
930 const Pkt6Ptr& query6 = query4o6->getPkt6();
931
932 // Initialize fields specific to relayed messages.
933 if (query6 && !query6->relay_info_.empty()) {
934 for (auto const& relay : boost::adaptors::reverse(query6->relay_info_)) {
935 if (!relay.linkaddr_.isV6Zero() &&
936 !relay.linkaddr_.isV6LinkLocal()) {
937 selector.first_relay_linkaddr_ = relay.linkaddr_;
938 break;
939 }
940 }
941 selector.interface_id_ =
942 query6->getAnyRelayOption(D6O_INTERFACE_ID,
944 }
945
946 // If the Subnet Selection option is present, extract its value.
947 OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
948 if (sbnsel) {
949 OptionCustomPtr oc = boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
950 if (oc) {
951 selector.option_select_ = oc->readAddress();
952 }
953 }
954
955 CfgMgr& cfgmgr = CfgMgr::instance();
956 subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet4o6(selector);
957
958 // Let's execute all callouts registered for subnet4_select.
959 // (skip callouts if the selectSubnet was called to do sanity checks only)
960 if (!sanity_only &&
961 HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
962 CalloutHandlePtr callout_handle = getCalloutHandle(query);
963
964 // Use the RAII wrapper to make sure that the callout handle state is
965 // reset when this object goes out of scope. All hook points must do
966 // it to prevent possible circular dependency between the callout
967 // handle and its arguments.
968 shared_ptr<ScopedCalloutHandleState> callout_handle_state(
969 std::make_shared<ScopedCalloutHandleState>(callout_handle));
970
971 // Enable copying options from the packet within hook library.
972 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
973
974 // Set new arguments
975 callout_handle->setArgument("query4", query);
976 callout_handle->setArgument("subnet4", subnet);
977 callout_handle->setArgument("subnet4collection",
978 cfgmgr.getCurrentCfg()->
979 getCfgSubnets4()->getAll());
980
981 auto const tpl(parkingLimitExceeded("subnet4_select"));
982 bool const exceeded(get<0>(tpl));
983 if (exceeded) {
984 uint32_t const limit(get<1>(tpl));
985 // We can't park it so we're going to throw it on the floor.
988 .arg(limit)
989 .arg(query->getLabel());
990 return (ConstSubnet4Ptr());
991 }
992
993 // We proactively park the packet.
995 "subnet4_select", query, [this, query, allow_answer_park, callout_handle_state]() {
996 if (MultiThreadingMgr::instance().getMode()) {
997 boost::shared_ptr<function<void()>> callback(
998 boost::make_shared<function<void()>>(
999 [this, query, allow_answer_park]() mutable {
1000 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
1001 }));
1002 callout_handle_state->on_completion_ = [callback]() {
1004 };
1005 } else {
1006 processLocalizedQuery4AndSendResponse(query, allow_answer_park);
1007 }
1008 });
1009
1010 // Call user (and server-side) callouts
1011 try {
1012 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
1013 *callout_handle);
1014 } catch (...) {
1015 // Make sure we don't orphan a parked packet.
1016 HooksManager::drop("subnet4_select", query);
1017 throw;
1018 }
1019
1020 // Callouts parked the packet. Same as drop but callouts will resume
1021 // processing or drop the packet later.
1022 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
1025 .arg(query->getLabel());
1026 drop = true;
1027 return (ConstSubnet4Ptr());
1028 } else {
1029 HooksManager::drop("subnet4_select", query);
1030 }
1031
1032 // Callouts decided to skip this step. This means that no subnet
1033 // will be selected. Packet processing will continue, but it will
1034 // be severely limited (i.e. only global options will be assigned)
1035 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1038 .arg(query->getLabel());
1039 return (ConstSubnet4Ptr());
1040 }
1041
1042 // Callouts decided to drop the packet. It is a superset of the
1043 // skip case so no subnet will be selected.
1044 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1047 .arg(query->getLabel());
1048 drop = true;
1049 return (ConstSubnet4Ptr());
1050 }
1051
1052 // Use whatever subnet was specified by the callout
1053 callout_handle->getArgument("subnet4", subnet);
1054 }
1055
1056 if (subnet) {
1057 // Log at higher debug level that subnet has been found.
1060 .arg(query->getLabel())
1061 .arg(subnet->getID());
1062 // Log detailed information about the selected subnet at the
1063 // lower debug level.
1066 .arg(query->getLabel())
1067 .arg(subnet->toText());
1068
1069 } else {
1072 .arg(query->getLabel());
1073 }
1074
1075 return (subnet);
1076}
1077
1078Pkt4Ptr
1080 return (IfaceMgr::instance().receive4(timeout));
1081}
1082
1083void
1085 IfaceMgr::instance().send(packet);
1086}
1087
1088void
1091 // Pointer to client's query.
1092 ctx->query_ = query;
1093
1094 // Hardware address.
1095 ctx->hwaddr_ = query->getHWAddr();
1096}
1097
1098bool
1101
1102 // First part of context initialization.
1103 initContext0(query, ctx);
1104
1105 // Get the early-global-reservations-lookup flag value.
1108 if (egrl) {
1109 ctx->early_global_reservations_lookup_ = egrl->boolValue();
1110 }
1111
1112 // Perform early global reservations lookup when wanted.
1113 if (ctx->early_global_reservations_lookup_) {
1114 // Retrieve retrieve client identifier.
1115 OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
1116 if (opt_clientid) {
1117 ctx->clientid_.reset(new ClientId(opt_clientid->getData()));
1118 }
1119
1120 // Get the host identifiers.
1122
1123 // Check for global host reservations.
1124 ConstHostPtr global_host = alloc_engine_->findGlobalReservation(*ctx);
1125
1126 if (global_host && !global_host->getClientClasses4().empty()) {
1127 // Remove dependent evaluated classes.
1129
1130 // Add classes from the global reservations.
1131 const ClientClasses& classes = global_host->getClientClasses4();
1132 for (auto const& cclass : classes) {
1133 query->addClass(cclass);
1134 }
1135
1136 // Evaluate classes before KNOWN.
1137 Dhcpv4Exchange::evaluateClasses(query, false);
1138 }
1139
1140 if (global_host) {
1141 // Add the KNOWN class;
1142 query->addClass("KNOWN");
1144 .arg(query->getLabel())
1145 .arg("KNOWN");
1146
1147 // Evaluate classes after KNOWN.
1149
1150 // Check the DROP special class.
1151 if (query->inClass("DROP")) {
1154 .arg(query->getHWAddrLabel())
1155 .arg(query->toText());
1156 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1157 static_cast<int64_t>(1));
1158 return (false);
1159 }
1160
1161 // Store the reservation.
1162 ctx->hosts_[SUBNET_ID_GLOBAL] = global_host;
1163 }
1164 }
1165
1166 return (true);
1167}
1168
1169int
1171#ifdef HAVE_AFL
1172 // Get the values of the environment variables used to control the
1173 // fuzzing.
1174
1175 // Specfies the interface to be used to pass packets from AFL to Kea.
1176 const char* interface = getenv("KEA_AFL_INTERFACE");
1177 if (!interface) {
1178 isc_throw(FuzzInitFail, "no fuzzing interface has been set");
1179 }
1180
1181 // The address on the interface to be used.
1182 const char* address = getenv("KEA_AFL_ADDRESS");
1183 if (!address) {
1184 isc_throw(FuzzInitFail, "no fuzzing address has been set");
1185 }
1186
1187 // Set up structures needed for fuzzing.
1188 PacketFuzzer fuzzer(server_port_, interface, address);
1189
1190 // The next line is needed as a signature for AFL to recognize that we are
1191 // running persistent fuzzing. This has to be in the main image file.
1192 while (__AFL_LOOP(fuzzer.maxLoopCount())) {
1193 // Read from stdin and put the data read into an address/port on which
1194 // Kea is listening, read for Kea to read it via asynchronous I/O.
1195 fuzzer.transfer();
1196#else
1197 while (!shutdown_) {
1198#endif // HAVE_AFL
1199 try {
1200 runOne();
1201 // Handle events registered by hooks using external IOService objects.
1203 getIOService()->poll();
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]() {
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]() {
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
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.
2695 d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp,
2696 *(ex.getContext()->getDdnsParams()));
2697 // Carry over the client's E flag.
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.
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
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.
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.
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.
3781}
3782
3784Dhcpv4Srv::getNetmaskOption(const ConstSubnet4Ptr& subnet) {
3785 uint32_t netmask = getNetmask4(subnet->get().second).toUint32();
3786
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.
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.
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;
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.
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) {
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 {
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.
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) {
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 {
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 {
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.
4365 StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
4366 static_cast<int64_t>(1));
4367
4368 if (!lease_exists) {
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) {
4379 StatsMgr::generateName("subnet", subnet->getID(),
4380 StatsMgr::generateName("pool", pool->getID(), "declined-addresses")),
4381 static_cast<int64_t>(1));
4382 if (!lease_exists) {
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.
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 }
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.
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
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 =
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
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.
5063 this, ph::_1, ph::_2));
5064 }
5065}
5066
5067void
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.
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
5166 static_cast<int64_t>(1));
5167}
5168
5170 // Increase generic counter for sent packets.
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
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.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown when an unexpected error condition occurs.