Kea 2.7.6
dhcp6_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
10#include <asiolink/io_address.h>
11#include <asiolink/io_service.h>
13#include <cc/data.h>
16#include <dhcp/classify.h>
17#include <dhcp/dhcp6.h>
19#include <dhcp/duid.h>
20#include <dhcp/duid_factory.h>
21#include <dhcp/hwaddr.h>
22#include <dhcp/iface_mgr.h>
23#include <dhcp/libdhcp++.h>
25#include <dhcp/option.h>
27#include <dhcp/option6_ia.h>
28#include <dhcp/option6_iaaddr.h>
32#include <dhcp/option_vendor.h>
34#include <dhcp/pkt.h>
35#include <dhcp/pkt6.h>
38#include <dhcp6/dhcp6_log.h>
39#include <dhcp6/dhcp6_srv.h>
40#include <dhcp6/dhcp6to4_ipc.h>
41#include <dhcp_ddns/ncr_io.h>
42#include <dhcp_ddns/ncr_msg.h>
47#include <dhcpsrv/cfg_globals.h>
50#include <dhcpsrv/cfg_option.h>
52#include <dhcpsrv/cfg_rsoo.h>
54#include <dhcpsrv/cfgmgr.h>
58#include <dhcpsrv/host.h>
60#include <dhcpsrv/host_mgr.h>
61#include <dhcpsrv/lease.h>
66#include <dhcpsrv/pool.h>
69#include <dhcpsrv/srv_config.h>
70#include <dhcpsrv/subnet.h>
71#include <dhcpsrv/subnet_id.h>
73#include <dhcpsrv/utils.h>
74#include <eval/evaluate.h>
75#include <eval/token.h>
78#include <hooks/hooks_log.h>
79#include <hooks/hooks_manager.h>
80#include <hooks/parking_lots.h>
81#include <hooks/server_hooks.h>
83#include <log/log_dbglevels.h>
84#include <log/log_formatter.h>
85#include <log/logger.h>
86#include <log/macros.h>
87#include <stats/stats_mgr.h>
88#include <util/buffer.h>
90#include <util/optional.h>
91#include <util/pointer_util.h>
93#include <util/thread_pool.h>
94#include <util/triplet.h>
95
96#include <algorithm>
97#include <cmath>
98#include <cstdint>
99#include <cstdlib>
100#include <exception>
101#include <functional>
102#include <iomanip>
103#include <limits>
104#include <list>
105#include <map>
106#include <memory>
107#include <set>
108#include <sstream>
109#include <string>
110#include <tuple>
111#include <utility>
112#include <vector>
113
114#include <boost/algorithm/string/erase.hpp>
115#include <boost/algorithm/string/join.hpp>
116#include <boost/algorithm/string/split.hpp>
117#include <boost/foreach.hpp>
118#include <boost/tokenizer.hpp>
119
120using namespace isc;
121using namespace isc::asiolink;
122using namespace isc::cryptolink;
123using namespace isc::data;
124using namespace isc::dhcp;
125using namespace isc::dhcp_ddns;
126using namespace isc::hooks;
127using namespace isc::log;
128using namespace isc::log::interprocess;
129using namespace isc::stats;
130using namespace isc::util;
131using namespace std;
132namespace ph = std::placeholders;
133
134namespace {
135
137struct Dhcp6Hooks {
138 int hook_index_buffer6_receive_;
139 int hook_index_pkt6_receive_;
140 int hook_index_subnet6_select_;
141 int hook_index_leases6_committed_;
142 int hook_index_lease6_release_;
143 int hook_index_pkt6_send_;
144 int hook_index_buffer6_send_;
145 int hook_index_lease6_decline_;
146 int hook_index_host6_identifier_;
147 int hook_index_ddns6_update_;
148
150 Dhcp6Hooks() {
151 hook_index_buffer6_receive_ = HooksManager::registerHook("buffer6_receive");
152 hook_index_pkt6_receive_ = HooksManager::registerHook("pkt6_receive");
153 hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
154 hook_index_leases6_committed_ = HooksManager::registerHook("leases6_committed");
155 hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
156 hook_index_pkt6_send_ = HooksManager::registerHook("pkt6_send");
157 hook_index_buffer6_send_ = HooksManager::registerHook("buffer6_send");
158 hook_index_lease6_decline_ = HooksManager::registerHook("lease6_decline");
159 hook_index_host6_identifier_ = HooksManager::registerHook("host6_identifier");
160 hook_index_ddns6_update_ = HooksManager::registerHook("ddns6_update");
161 }
162};
163
164// Declare a Hooks object. As this is outside any function or method, it
165// will be instantiated (and the constructor run) when the module is loaded.
166// As a result, the hook indexes will be defined before any method in this
167// module is called.
168Dhcp6Hooks Hooks;
169
182createStatusCode(const Pkt6& pkt, const uint16_t status_code,
183 const std::string& status_message) {
184 Option6StatusCodePtr option_status(new Option6StatusCode(status_code,
185 status_message));
187 .arg(pkt.getLabel())
188 .arg(option_status->dataToText());
189 return (option_status);
190}
191
207createStatusCode(const Pkt6& pkt, const Option6IA& ia, const uint16_t status_code,
208 const std::string& status_message) {
209 Option6StatusCodePtr option_status(new Option6StatusCode(status_code,
210 status_message));
212 .arg(pkt.getLabel())
213 .arg(ia.getIAID())
214 .arg(option_status->dataToText());
215 return (option_status);
216}
217
220std::set<std::string> dhcp6_statistics = {
221 "pkt6-received",
222 "pkt6-solicit-received",
223 "pkt6-advertise-received",
224 "pkt6-request-received",
225 "pkt6-reply-received",
226 "pkt6-renew-received",
227 "pkt6-rebind-received",
228 "pkt6-decline-received",
229 "pkt6-release-received",
230 "pkt6-infrequest-received",
231 "pkt6-dhcpv4-query-received",
232 "pkt6-dhcpv4-response-received",
233 "pkt6-unknown-received",
234 "pkt6-sent",
235 "pkt6-advertise-sent",
236 "pkt6-reply-sent",
237 "pkt6-dhcpv4-response-sent",
238 "pkt6-parse-failed",
239 "pkt6-receive-drop",
240 "v6-allocation-fail",
241 "v6-allocation-fail-shared-network",
242 "v6-allocation-fail-subnet",
243 "v6-allocation-fail-no-pools",
244 "v6-allocation-fail-classes",
245 "v6-ia-na-lease-reuses",
246 "v6-ia-pd-lease-reuses",
247};
248
249} // namespace
250
251namespace isc {
252namespace dhcp {
253
254const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
255
256Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port, uint16_t client_port)
257 : io_service_(new IOService()), server_port_(server_port),
258 client_port_(client_port), serverid_(), shutdown_(true),
259 alloc_engine_(), name_change_reqs_(),
260 network_state_(new NetworkState()),
261 cb_control_(new CBControlDHCPv6()) {
263 .arg(server_port);
264
265 Dhcp6to4Ipc::instance().client_port = client_port;
266
267 // Initialize objects required for DHCP server operation.
268 try {
269 // Port 0 is used for testing purposes where in most cases we don't
270 // rely on the physical interfaces. Therefore, it should be possible
271 // to create an object even when there are no usable interfaces.
272 if ((server_port > 0) && (IfaceMgr::instance().countIfaces() == 0)) {
274 return;
275 }
276
277 // Create a DUID instance but do not store it into a file.
278 DUIDFactory duid_factory;
279 DuidPtr duid = duid_factory.get();
280 serverid_.reset(new Option(Option::V6, D6O_SERVERID, duid->getDuid()));
281
282 // Instantiate allocation engine. The number of allocation attempts equal
283 // to zero indicates that the allocation engine will use the number of
284 // attempts depending on the pool size.
285 alloc_engine_.reset(new AllocEngine(0));
286
288
289 } catch (const std::exception &e) {
291 return;
292 }
293
294 // Initializing all observations with default value
296
297 // All done, so can proceed
298 shutdown_ = false;
299}
300
303
304 // Iterate over set of observed statistics
305 for (auto const& it : dhcp6_statistics) {
306 // Initialize them with default value 0
307 stats_mgr.setValue(it, static_cast<int64_t>(0));
308 }
309}
310
312 // Discard any parked packets
314
315 try {
316 stopD2();
317 } catch (const std::exception& ex) {
318 // Highly unlikely, but lets Report it but go on
320 }
321
322 try {
324 } catch (const std::exception& ex) {
325 // Highly unlikely, but lets Report it but go on
326 // LOG_ERROR(dhcp6_logger, DHCP6_SRV_DHCP4O6_ERROR).arg(ex.what());
327 }
328
330
331 // The lease manager was instantiated during DHCPv6Srv configuration,
332 // so we should clean up after ourselves.
334
335 // Destroy the host manager before hooks unload.
337
338 // Explicitly unload hooks
341 auto names = HooksManager::getLibraryNames();
342 std::string msg;
343 if (!names.empty()) {
344 msg = names[0];
345 for (size_t i = 1; i < names.size(); ++i) {
346 msg += std::string(", ") + names[i];
347 }
348 }
350 }
352 io_service_->stopAndPoll();
353}
354
359
361 return (IfaceMgr::instance().receive6(timeout));
362}
363
364void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
365 IfaceMgr::instance().send(packet);
366}
367
368bool
374 OptionPtr server_id = pkt->getOption(D6O_SERVERID);
375 if (server_id){
376 // Let us test received ServerID if it is same as ServerID
377 // which is being used by server
378 if (getServerID()->getData() != server_id->getData()){
380 .arg(pkt->getLabel())
381 .arg(duidToString(server_id))
382 .arg(duidToString(getServerID()));
383 return (false);
384 }
385 }
386 // return True if: no serverid received or ServerIDs matching
387 return (true);
388}
389
390bool
392 switch (pkt->getType()) {
393 case DHCPV6_SOLICIT:
394 case DHCPV6_CONFIRM:
395 case DHCPV6_REBIND:
397 if (pkt->relay_info_.empty() && !pkt->getLocalAddr().isV6Multicast()) {
399 .arg(pkt->getLabel())
400 .arg(pkt->getName());
401 return (false);
402 }
403 break;
404 default:
405 // do nothing
406 ;
407 }
408 return (true);
409}
410
411void
413 const ConstCfgHostOperationsPtr cfg =
414 CfgMgr::instance().getCurrentCfg()->getCfgHostOperations6();
415 for (auto const& id_type : cfg->getIdentifierTypes()) {
416 switch (id_type) {
417 case Host::IDENT_DUID:
418 if (ctx.duid_) {
419 ctx.addHostIdentifier(id_type, ctx.duid_->getDuid());
420 }
421 break;
422
424 if (ctx.hwaddr_) {
425 ctx.addHostIdentifier(id_type, ctx.hwaddr_->hwaddr_);
426 }
427 break;
428 case Host::IDENT_FLEX:
429 // At this point the information in the packet has been unpacked into
430 // the various packet fields and option objects has been created.
431 // Execute callouts registered for host6_identifier.
432 if (HooksManager::calloutsPresent(Hooks.hook_index_host6_identifier_)) {
433 CalloutHandlePtr callout_handle = getCalloutHandle(ctx.query_);
434
436 std::vector<uint8_t> id;
437
438 // Use the RAII wrapper to make sure that the callout handle state is
439 // reset when this object goes out of scope. All hook points must do
440 // it to prevent possible circular dependency between the callout
441 // handle and its arguments.
442 ScopedCalloutHandleState callout_handle_state(callout_handle);
443
444 // Pass incoming packet as argument
445 callout_handle->setArgument("query6", ctx.query_);
446 callout_handle->setArgument("id_type", type);
447 callout_handle->setArgument("id_value", id);
448
449 // Call callouts
450 HooksManager::callCallouts(Hooks.hook_index_host6_identifier_,
451 *callout_handle);
452
453 callout_handle->getArgument("id_type", type);
454 callout_handle->getArgument("id_value", id);
455
456 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_CONTINUE) &&
457 !id.empty()) {
458
460 .arg(ctx.query_->getLabel())
461 .arg(Host::getIdentifierAsText(type, &id[0], id.size()));
462
463 ctx.addHostIdentifier(type, id);
464 }
465 }
466 break;
467 default:
468 ;
469 }
470 }
471}
472
473void
476 // Pointer to client's query.
477 ctx.query_ = query;
478
479 // DUID.
480 ctx.duid_ = query->getClientId();
481
482 // Hardware address.
483 ctx.hwaddr_ = getMAC(query);
484}
485
486bool
489 // First part of context initialization.
490 initContext0(query, ctx);
491
492 // Get the early-global-reservations-lookup flag value.
495 if (egrl) {
496 ctx.early_global_reservations_lookup_ = egrl->boolValue();
497 }
498
499 // Perform early global reservations lookup when wanted.
501 // Get the host identifiers.
503
504 // Check for global host reservations.
505 ConstHostPtr global_host = alloc_engine_->findGlobalReservation(ctx);
506
507 if (global_host && !global_host->getClientClasses6().empty()) {
508 // Remove dependent evaluated classes.
510
511 // Add classes from the global reservations.
512 const ClientClasses& classes = global_host->getClientClasses6();
513 for (auto const& cclass : classes) {
514 query->addClass(cclass);
515 }
516
517 // Evaluate classes before KNOWN.
518 evaluateClasses(query, false);
519 }
520
521 if (global_host) {
522 // Add the KNOWN class;
523 query->addClass("KNOWN");
525 .arg(query->getLabel())
526 .arg("KNOWN");
527
528 // Evaluate classes after KNOWN.
529 evaluateClasses(query, true);
530
531 // Check the DROP special class.
532 if (query->inClass("DROP")) {
535 .arg(query->makeLabel(query->getClientId(), nullptr))
536 .arg(query->toText());
537 StatsMgr::instance().addValue("pkt6-receive-drop",
538 static_cast<int64_t>(1));
539 return (false);
540 }
541
542 // Store the reservation.
543 ctx.hosts_[SUBNET_ID_GLOBAL] = global_host;
544 }
545 }
546
547 return (true);
548}
549
550void
552 // Sanity check.
553 if (!ctx.query_) {
554 drop = true;
555 return;
556 }
557 ctx.fwd_dns_update_ = false;
558 ctx.rev_dns_update_ = false;
559 ctx.hostname_ = "";
561
562 // Collect host identifiers if host reservations enabled. The identifiers
563 // are stored in order of preference. The server will use them in that
564 // order to search for host reservations.
566 if (ctx.subnet_) {
567 // Before we can check for static reservations, we need to prepare
568 // a set of identifiers to be used for this.
571 }
572
573 // Find host reservations using specified identifiers.
574 alloc_engine_->findReservation(ctx);
575
576 // Get shared network to see if it is set for a subnet.
577 ctx.subnet_->getSharedNetwork(sn);
578 }
579
580 // Global host reservations are independent of a selected subnet. If the
581 // global reservations contain client classes we should use them in case
582 // they are meant to affect pool selection. Also, if the subnet does not
583 // belong to a shared network we can use the reserved client classes
584 // because there is no way our subnet could change. Such classes may
585 // affect selection of a pool within the selected subnet.
586 auto global_host = ctx.globalHost();
587 auto current_host = ctx.currentHost();
589 global_host && !global_host->getClientClasses6().empty()) ||
590 (!sn && current_host && !current_host->getClientClasses6().empty())) {
591 // We have already evaluated client classes and some of them may
592 // be in conflict with the reserved classes. Suppose there are
593 // two classes defined in the server configuration: first_class
594 // and second_class and the test for the second_class it looks
595 // like this: "not member('first_class')". If the first_class
596 // initially evaluates to false, the second_class evaluates to
597 // true. If the first_class is now set within the hosts reservations
598 // and we don't remove the previously evaluated second_class we'd
599 // end up with both first_class and second_class evaluated to
600 // true. In order to avoid that, we have to remove the classes
601 // evaluated in the first pass and evaluate them again. As
602 // a result, the first_class set via the host reservation will
603 // replace the second_class because the second_class will this
604 // time evaluate to false as desired.
607 evaluateClasses(ctx.query_, false);
608 }
609
610 // Set KNOWN builtin class if something was found, UNKNOWN if not.
611 if (!ctx.hosts_.empty()) {
612 ctx.query_->addClass("KNOWN");
614 .arg(ctx.query_->getLabel())
615 .arg("KNOWN");
616 } else {
617 ctx.query_->addClass("UNKNOWN");
619 .arg(ctx.query_->getLabel())
620 .arg("UNKNOWN");
621 }
622
623 // Perform second pass of classification.
624 evaluateClasses(ctx.query_, true);
625
626 const ClientClasses& classes = ctx.query_->getClasses();
628 .arg(ctx.query_->getLabel())
629 .arg(classes.toText());
630
631 // Check the DROP special class.
632 if (ctx.query_->inClass("DROP")) {
634 .arg(ctx.query_->makeLabel(ctx.query_->getClientId(), 0))
635 .arg(ctx.query_->toText());
636 StatsMgr::instance().addValue("pkt6-receive-drop",
637 static_cast<int64_t>(1));
638 drop = true;
639 }
640}
641
642int
644#ifdef HAVE_AFL
645 // Get the values of the environment variables used to control the
646 // fuzzing.
647
648 // Specfies the interface to be used to pass packets from AFL to Kea.
649 const char* interface = getenv("KEA_AFL_INTERFACE");
650 if (!interface) {
651 isc_throw(FuzzInitFail, "no fuzzing interface has been set");
652 }
653
654 // The address on the interface to be used.
655 const char* address = getenv("KEA_AFL_ADDRESS");
656 if (!address) {
657 isc_throw(FuzzInitFail, "no fuzzing address has been set");
658 }
659
660 // Set up structures needed for fuzzing.
661 PacketFuzzer fuzzer(server_port_, interface, address);
662
663 // The next line is needed as a signature for AFL to recognize that we are
664 // running persistent fuzzing. This has to be in the main image file.
665 while (__AFL_LOOP(fuzzer.maxLoopCount())) {
666 // Read from stdin and put the data read into an address/port on which
667 // Kea is listening, read for Kea to read it via asynchronous I/O.
668 fuzzer.transfer();
669#else
670 while (!shutdown_) {
671#endif // HAVE_AFL
672 try {
673 runOne();
674 // Handle events registered by hooks using external IOService objects.
676 getIOService()->poll();
677 } catch (const std::exception& e) {
678 // General catch-all standard exceptions that are not caught by more
679 // specific catches.
681 .arg(e.what());
682
683 } catch (...) {
684 // General catch-all non-standard exception that are not caught
685 // by more specific catches.
687 }
688 }
689
690 // Stop everything before we change into single-threaded mode.
692
693 // destroying the thread pool
694 MultiThreadingMgr::instance().apply(false, 0, 0);
695
696 return (getExitValue());
697}
698
699void
701 // client's message and server's response
702 Pkt6Ptr query;
703
704 try {
705 // Set select() timeout to 1s. This value should not be modified
706 // because it is important that the select() returns control
707 // frequently so as the IOService can be polled for ready handlers.
708 uint32_t timeout = 1;
709 query = receivePacket(timeout);
710
711 // Log if packet has arrived. We can't log the detailed information
712 // about the DHCP message because it hasn't been unpacked/parsed
713 // yet, and it can't be parsed at this point because hooks will
714 // have to process it first. The only information available at this
715 // point are: the interface, source address and destination addresses
716 // and ports.
717 if (query) {
719 .arg(query->getRemoteAddr().toText())
720 .arg(query->getRemotePort())
721 .arg(query->getLocalAddr().toText())
722 .arg(query->getLocalPort())
723 .arg(query->getIface());
724
725 // Log reception of the packet. We need to increase it early, as
726 // any failures in unpacking will cause the packet to be dropped.
727 // we will increase type specific packets further down the road.
728 // See processStatsReceived().
729 StatsMgr::instance().addValue("pkt6-received", static_cast<int64_t>(1));
730 }
731
732 // We used to log that the wait was interrupted, but this is no longer
733 // the case. Our wait time is 1s now, so the lack of query packet more
734 // likely means that nothing new appeared within a second, rather than
735 // we were interrupted. And we don't want to print a message every
736 // second.
737
738 } catch (const SignalInterruptOnSelect&) {
739 // Packet reception interrupted because a signal has been received.
740 // This is not an error because we might have received a SIGTERM,
741 // SIGINT, SIGHUP or SIGCHLD which are handled by the server. For
742 // signals that are not handled by the server we rely on the default
743 // behavior of the system.
745 } catch (const std::exception& e) {
747 .arg(e.what());
748 }
749
750 // Timeout may be reached or signal received, which breaks select()
751 // with no packet received
752 if (!query) {
753 return;
754 }
755
756 // If the DHCP service has been globally disabled, drop the packet.
757 if (!network_state_->isServiceEnabled()) {
759 .arg(query->getLabel());
760 return;
761 } else {
762 if (MultiThreadingMgr::instance().getMode()) {
763 query->addPktEvent("mt_queued");
764 typedef function<void()> CallBack;
765 boost::shared_ptr<CallBack> call_back =
766 boost::make_shared<CallBack>(std::bind(&Dhcpv6Srv::processPacketAndSendResponseNoThrow,
767 this, query));
768 if (!MultiThreadingMgr::instance().getThreadPool().add(call_back)) {
770 }
771 } else {
773 }
774 }
775}
776
777void
779 try {
781 } catch (const std::exception& e) {
783 .arg(query->getLabel())
784 .arg(e.what());
785 } catch (...) {
787 .arg(query->getLabel());
788 }
789}
790
791void
793 Pkt6Ptr rsp = processPacket(query);
794 if (!rsp) {
795 return;
796 }
797
798 CalloutHandlePtr callout_handle = getCalloutHandle(query);
799 processPacketBufferSend(callout_handle, rsp);
800}
801
804 query->addPktEvent("process_started");
805
806 // All packets belong to ALL.
807 query->addClass("ALL");
808
809 bool skip_unpack = false;
810
811 // The packet has just been received so contains the uninterpreted wire
812 // data; execute callouts registered for buffer6_receive.
813 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
814 CalloutHandlePtr callout_handle = getCalloutHandle(query);
815
816 // Use the RAII wrapper to make sure that the callout handle state is
817 // reset when this object goes out of scope. All hook points must do
818 // it to prevent possible circular dependency between the callout
819 // handle and its arguments.
820 ScopedCalloutHandleState callout_handle_state(callout_handle);
821
822 // Enable copying options from the packet within hook library.
823 ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
824
825 // Pass incoming packet as argument
826 callout_handle->setArgument("query6", query);
827
828 // Call callouts
829 HooksManager::callCallouts(Hooks.hook_index_buffer6_receive_, *callout_handle);
830
831 // Callouts decided to skip the next processing step. The next
832 // processing step would be to parse the packet, so skip at this
833 // stage means that callouts did the parsing already, so server
834 // should skip parsing.
835 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
837 .arg(query->getRemoteAddr().toText())
838 .arg(query->getLocalAddr().toText())
839 .arg(query->getIface());
840 skip_unpack = true;
841 }
842
843 // Callouts decided to drop the received packet
844 // The response (rsp) is null so the caller (runOne) will
845 // immediately return too.
846 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
848 .arg(query->getRemoteAddr().toText())
849 .arg(query->getLocalAddr().toText())
850 .arg(query->getIface());
851
852 // Not increasing the statistics of the dropped packets because it
853 // is the callouts' responsibility to increase it. There are some
854 // cases when the callouts may elect to not increase the statistics.
855 // For example, packets dropped by the load-balancing algorithm must
856 // not increase the statistics.
857 return (Pkt6Ptr());
858 }
859
860 callout_handle->getArgument("query6", query);
861 if (!query) {
862 // Please use the status instead of resetting query!
863 return (Pkt6Ptr());
864 }
865 }
866
867 // Unpack the packet information unless the buffer6_receive callouts
868 // indicated they did it
869 if (!skip_unpack) {
870 try {
872 .arg(query->getRemoteAddr().toText())
873 .arg(query->getLocalAddr().toText())
874 .arg(query->getIface());
875 query->unpack();
876 } catch (const SkipRemainingOptionsError& e) {
877 // An option failed to unpack but we are to attempt to process it
878 // anyway. Log it and let's hope for the best.
881 .arg(query->getLabel())
882 .arg(e.what());
883 } catch (const std::exception &e) {
884 // Failed to parse the packet.
886 .arg(query->getLabel())
887 .arg(query->getRemoteAddr().toText())
888 .arg(query->getLocalAddr().toText())
889 .arg(query->getIface())
890 .arg(e.what())
891 .arg(query->makeLabel(query->getClientId(), nullptr));
892
893 // Increase the statistics of parse failures and dropped packets.
894 StatsMgr::instance().addValue("pkt6-parse-failed",
895 static_cast<int64_t>(1));
896 StatsMgr::instance().addValue("pkt6-receive-drop",
897 static_cast<int64_t>(1));
898 return (Pkt6Ptr());
899 }
900 }
901
902 // Classify can emit INFO logs so help to track the query.
904 .arg(query->getLabel());
905
906 // Update statistics accordingly for received packet.
907 processStatsReceived(query);
908
909 // Check if received query carries server identifier matching
910 // server identifier being used by the server.
911 if (!testServerID(query)) {
912
913 // Increase the statistic of dropped packets.
914 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
915 return (Pkt6Ptr());
916 }
917
918 // Check if the received query has been sent to unicast or multicast.
919 // The Solicit, Confirm, Rebind and Information Request will be
920 // discarded if sent to unicast address.
921 if (!testUnicast(query)) {
922
923 // Increase the statistic of dropped packets.
924 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
925 return (Pkt6Ptr());
926 }
927
928 // Assign this packet to a class, if possible
929 classifyPacket(query);
930
932 .arg(query->getLabel())
933 .arg(query->getName())
934 .arg(static_cast<int>(query->getType()))
935 .arg(query->getRemoteAddr())
936 .arg(query->getLocalAddr())
937 .arg(query->getIface());
939 .arg(query->getLabel())
940 .arg(query->toText());
941
942 // At this point the information in the packet has been unpacked into
943 // the various packet fields and option objects has been created.
944 // Execute callouts registered for packet6_receive.
945 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
946 CalloutHandlePtr callout_handle = getCalloutHandle(query);
947
948 // Use the RAII wrapper to make sure that the callout handle state is
949 // reset when this object goes out of scope. All hook points must do
950 // it to prevent possible circular dependency between the callout
951 // handle and its arguments.
952 ScopedCalloutHandleState callout_handle_state(callout_handle);
953
954 // Enable copying options from the packet within hook library.
955 ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
956
957 // Pass incoming packet as argument
958 callout_handle->setArgument("query6", query);
959
960 // Call callouts
961 HooksManager::callCallouts(Hooks.hook_index_pkt6_receive_, *callout_handle);
962
963 // Callouts decided to skip the next processing step. The next
964 // processing step would be to process the packet, so skip at this
965 // stage means drop.
966 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
967 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
969 .arg(query->getLabel());
970 // Not increasing the statistics of the dropped packets because it
971 // is the callouts' responsibility to increase it. There are some
972 // cases when the callouts may elect to not increase the statistics.
973 // For example, packets dropped by the load-balancing algorithm must
974 // not increase the statistics.
975 return (Pkt6Ptr());
976 }
977
978 callout_handle->getArgument("query6", query);
979 if (!query) {
980 // Please use the status instead of resetting query!
981 return (Pkt6Ptr());
982 }
983 }
984
985 // Reject the message if it doesn't pass the sanity check.
986 if (!sanityCheck(query)) {
987 return (Pkt6Ptr());
988 }
989
990 // Check the DROP special class.
991 if (query->inClass("DROP")) {
993 .arg(query->makeLabel(query->getClientId(), nullptr))
994 .arg(query->toText());
995 StatsMgr::instance().addValue("pkt6-receive-drop",
996 static_cast<int64_t>(1));
997 return (Pkt6Ptr());
998 }
999
1000 return (processDhcp6Query(query));
1001}
1002
1003void
1005 try {
1006 Pkt6Ptr rsp = processDhcp6Query(query);
1007 if (!rsp) {
1008 return;
1009 }
1010
1011 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1012 processPacketBufferSend(callout_handle, rsp);
1013 } catch (const std::exception& e) {
1015 .arg(query->getLabel())
1016 .arg(e.what());
1017 } catch (...) {
1019 .arg(query->getLabel());
1020 }
1021}
1022
1023Pkt6Ptr
1025 // Create a client race avoidance RAII handler.
1026 ClientHandler client_handler;
1027
1028 // Check for lease modifier queries from the same client being processed.
1029 if (MultiThreadingMgr::instance().getMode() &&
1030 ((query->getType() == DHCPV6_SOLICIT) ||
1031 (query->getType() == DHCPV6_REQUEST) ||
1032 (query->getType() == DHCPV6_RENEW) ||
1033 (query->getType() == DHCPV6_REBIND) ||
1034 (query->getType() == DHCPV6_RELEASE) ||
1035 (query->getType() == DHCPV6_DECLINE))) {
1036 ContinuationPtr cont =
1038 this, query));
1039 if (!client_handler.tryLock(query, cont)) {
1040 return (Pkt6Ptr());
1041 }
1042 }
1043
1044 // Let's create a simplified client context here.
1046 if (!earlyGHRLookup(query, ctx)) {
1047 return (Pkt6Ptr());
1048 }
1049
1050 if (query->getType() == DHCPV6_DHCPV4_QUERY) {
1051 // This call never throws. Should this change, this section must be
1052 // enclosed in try-catch.
1053 processDhcp4Query(query);
1054 return (Pkt6Ptr());
1055 }
1056
1057 // Complete the client context initialization.
1058 bool drop = false;
1059 ctx.subnet_ = selectSubnet(query, drop);
1060 if (drop) {
1061 // Caller will immediately drop the packet so simply return now.
1062 return (Pkt6Ptr());
1063 }
1064
1065 return (processLocalizedQuery6(ctx));
1066}
1067
1068
1069void
1072 try {
1074 if (!rsp) {
1075 return;
1076 }
1077
1078 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1079 processPacketBufferSend(callout_handle, rsp);
1080 } catch (const std::exception& e) {
1082 .arg(query->getLabel())
1083 .arg(e.what());
1084 } catch (...) {
1086 .arg(query->getLabel());
1087 }
1088}
1089
1090void
1092 // Initialize context.
1094 initContext0(query, ctx);
1095
1096 // Subnet is cached in the callout context associated to the query.
1097 try {
1098 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1099 callout_handle->getContext("subnet6", ctx.subnet_);
1100 } catch (const Exception&) {
1101 // No subnet, leave it to null...
1102 }
1103
1105}
1106
1107Pkt6Ptr
1109 Pkt6Ptr query = ctx.query_;
1110 bool drop = false;
1111 initContext(ctx, drop);
1112 // Stop here if initContext decided to drop the packet.
1113 if (drop) {
1114 return (Pkt6Ptr());
1115 }
1116
1117 Pkt6Ptr rsp;
1118 try {
1119 switch (query->getType()) {
1120 case DHCPV6_SOLICIT:
1121 rsp = processSolicit(ctx);
1122 break;
1123
1124 case DHCPV6_REQUEST:
1125 rsp = processRequest(ctx);
1126 break;
1127
1128 case DHCPV6_RENEW:
1129 rsp = processRenew(ctx);
1130 break;
1131
1132 case DHCPV6_REBIND:
1133 rsp = processRebind(ctx);
1134 break;
1135
1136 case DHCPV6_CONFIRM:
1137 rsp = processConfirm(ctx);
1138 break;
1139
1140 case DHCPV6_RELEASE:
1141 rsp = processRelease(ctx);
1142 break;
1143
1144 case DHCPV6_DECLINE:
1145 rsp = processDecline(ctx);
1146 break;
1147
1149 rsp = processInfRequest(ctx);
1150 break;
1151
1152 default:
1153 return (rsp);
1154 }
1155
1156 } catch (const std::exception& e) {
1157
1158 // Catch-all exception (at least for ones based on the isc Exception
1159 // class, which covers more or less all that are explicitly raised
1160 // in the Kea code), but also the standard one, which may possibly be
1161 // thrown from boost code. Just log the problem and ignore the packet.
1162 // (The problem is logged as a debug message because debug is
1163 // disabled by default - it prevents a DDOS attack based on the
1164 // sending of problem packets.)
1166 .arg(query->getLabel())
1167 .arg(query->getName())
1168 .arg(query->getRemoteAddr().toText())
1169 .arg(e.what());
1170
1171 // Increase the statistic of dropped packets.
1172 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
1173 }
1174
1175 if (!rsp) {
1176 return (rsp);
1177 }
1178
1179 // Process relay-supplied options. It is important to call this very
1180 // late in the process, because we now have all the options the
1181 // server wanted to send already set. This is important, because
1182 // RFC6422, section 6 states:
1183 //
1184 // The server SHOULD discard any options that appear in the RSOO
1185 // for which it already has one or more candidates.
1186 //
1187 // So we ignore any RSOO options if there's an option with the same
1188 // code already present.
1189 processRSOO(query, rsp);
1190
1191 rsp->setRemoteAddr(query->getRemoteAddr());
1192 rsp->setLocalAddr(query->getLocalAddr());
1193
1194 if (client_port_) {
1195 // A command line option enforces a specific client port
1196 rsp->setRemotePort(client_port_);
1197 } else if (rsp->relay_info_.empty()) {
1198 // Direct traffic, send back to the client directly
1199 rsp->setRemotePort(DHCP6_CLIENT_PORT);
1200 } else {
1201 // Relayed traffic, send back to the relay agent
1202 uint16_t relay_port = checkRelaySourcePort(query);
1203 rsp->setRemotePort(relay_port ? relay_port : DHCP6_SERVER_PORT);
1204 }
1205
1206 if (server_port_) {
1207 rsp->setLocalPort(server_port_);
1208 } else {
1209 rsp->setLocalPort(DHCP6_SERVER_PORT);
1210 }
1211 rsp->setIndex(query->getIndex());
1212 rsp->setIface(query->getIface());
1213
1214 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1215 if (!ctx.fake_allocation_ && (ctx.query_->getType() != DHCPV6_CONFIRM) &&
1216 (ctx.query_->getType() != DHCPV6_INFORMATION_REQUEST) &&
1217 HooksManager::calloutsPresent(Hooks.hook_index_leases6_committed_)) {
1218 // The ScopedCalloutHandleState class which guarantees that the task
1219 // is added to the thread pool after the response is reset (if needed)
1220 // and CalloutHandle state is reset. In ST it does nothing.
1221 // A smart pointer is used to store the ScopedCalloutHandleState so that
1222 // a copy of the pointer is created by the lambda and only on the
1223 // destruction of the last reference the task is added.
1224 // In MT there are 2 cases:
1225 // 1. packet is unparked before current thread smart pointer to
1226 // ScopedCalloutHandleState is destroyed:
1227 // - the lambda uses the smart pointer to set the callout which adds the
1228 // task, but the task is added after ScopedCalloutHandleState is
1229 // destroyed, on the destruction of the last reference which is held
1230 // by the current thread.
1231 // 2. packet is unparked after the current thread smart pointer to
1232 // ScopedCalloutHandleState is destroyed:
1233 // - the current thread reference to ScopedCalloutHandleState is
1234 // destroyed, but the reference in the lambda keeps it alive until
1235 // the lambda is called and the last reference is released, at which
1236 // time the task is actually added.
1237 // Use the RAII wrapper to make sure that the callout handle state is
1238 // reset when this object goes out of scope. All hook points must do
1239 // it to prevent possible circular dependency between the callout
1240 // handle and its arguments.
1241 std::shared_ptr<ScopedCalloutHandleState> callout_handle_state =
1242 std::make_shared<ScopedCalloutHandleState>(callout_handle);
1243
1244 ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
1245
1246 // Also pass the corresponding query packet as argument
1247 callout_handle->setArgument("query6", query);
1248
1249 Lease6CollectionPtr new_leases(new Lease6Collection());
1250 if (!ctx.new_leases_.empty()) {
1251 // Filter out reused leases as they were not committed.
1252 for (auto const& new_lease : ctx.new_leases_) {
1253 if (new_lease->reuseable_valid_lft_ == 0) {
1254 new_leases->push_back(new_lease);
1255 }
1256 }
1257 }
1258 callout_handle->setArgument("leases6", new_leases);
1259
1260 Lease6CollectionPtr deleted_leases(new Lease6Collection());
1261
1262 // Do per IA lists
1263 for (auto const& iac : ctx.ias_) {
1264 if (!iac.old_leases_.empty()) {
1265 for (auto const& old_lease : iac.old_leases_) {
1266 if (ctx.new_leases_.empty()) {
1267 deleted_leases->push_back(old_lease);
1268 continue;
1269 }
1270 bool in_new = false;
1271 for (auto const& new_lease : ctx.new_leases_) {
1272 if ((new_lease->addr_ == old_lease->addr_) &&
1273 ((new_lease->type_ != Lease::TYPE_PD) ||
1274 (new_lease->prefixlen_ == old_lease->prefixlen_))) {
1275 in_new = true;
1276 break;
1277 }
1278 }
1279 if (!in_new) {
1280 deleted_leases->push_back(old_lease);
1281 }
1282 }
1283 }
1284 }
1285 callout_handle->setArgument("deleted_leases6", deleted_leases);
1286
1287 // Get the parking limit. Parsing should ensure the value is present.
1288 uint32_t parked_packet_limit = 0;
1290 getConfiguredGlobal(CfgGlobals::PARKED_PACKET_LIMIT);
1291 if (ppl) {
1292 parked_packet_limit = ppl->intValue();
1293 }
1294
1295 if (parked_packet_limit) {
1296 auto const& parking_lot = ServerHooks::getServerHooks().
1297 getParkingLotPtr("leases6_committed");
1298 if (parking_lot && (parking_lot->size() >= parked_packet_limit)) {
1299 // We can't park it so we're going to throw it on the floor.
1302 .arg(parked_packet_limit)
1303 .arg(query->getLabel());
1304 isc::stats::StatsMgr::instance().addValue("pkt6-receive-drop",
1305 static_cast<int64_t>(1));
1306 rsp.reset();
1307 return (rsp);
1308 }
1309 }
1310
1311 // We proactively park the packet. We'll unpark it without invoking
1312 // the callback (i.e. drop) unless the callout status is set to
1313 // NEXT_STEP_PARK. Otherwise the callback we bind here will be
1314 // executed when the hook library unparks the packet.
1315 ConstSubnet6Ptr subnet = ctx.subnet_;
1316 HooksManager::park("leases6_committed", query,
1317 [this, callout_handle, query, rsp, callout_handle_state, subnet]() mutable {
1318 if (MultiThreadingMgr::instance().getMode()) {
1319 typedef function<void()> CallBack;
1320 boost::shared_ptr<CallBack> call_back =
1321 boost::make_shared<CallBack>(std::bind(&Dhcpv6Srv::sendResponseNoThrow,
1322 this, callout_handle, query, rsp, subnet));
1323 callout_handle_state->on_completion_ = [call_back]() {
1325 };
1326 } else {
1327 processPacketPktSend(callout_handle, query, rsp, subnet);
1328 processPacketBufferSend(callout_handle, rsp);
1329 }
1330 });
1331
1332 try {
1333 // Call all installed callouts
1334 HooksManager::callCallouts(Hooks.hook_index_leases6_committed_,
1335 *callout_handle);
1336 } catch (...) {
1337 // Make sure we don't orphan a parked packet.
1338 HooksManager::drop("leases6_committed", query);
1339 throw;
1340 }
1341
1342 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
1344 .arg(query->getLabel());
1345 // Since the hook library(ies) are going to do the unparking, then
1346 // reset the pointer to the response to indicate to the caller that
1347 // it should return, as the packet processing will continue via
1348 // the callback.
1349 rsp.reset();
1350 } else {
1351 // Drop the park job on the packet, it isn't needed.
1352 HooksManager::drop("leases6_committed", query);
1353 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1355 .arg(query->getLabel());
1356 rsp.reset();
1357 }
1358 }
1359 }
1360
1361 // If we have a response prep it for shipment.
1362 if (rsp) {
1363 processPacketPktSend(callout_handle, query, rsp, ctx.subnet_);
1364 }
1365
1366 return (rsp);
1367}
1368
1369void
1371 Pkt6Ptr query, Pkt6Ptr& rsp,
1372 ConstSubnet6Ptr& subnet) {
1373 try {
1374 processPacketPktSend(callout_handle, query, rsp, subnet);
1375 processPacketBufferSend(callout_handle, rsp);
1376 } catch (const std::exception& e) {
1378 .arg(query->getLabel())
1379 .arg(e.what());
1380 } catch (...) {
1382 .arg(query->getLabel());
1383 }
1384}
1385
1386void
1388 Pkt6Ptr& query, Pkt6Ptr& rsp,
1389 ConstSubnet6Ptr& subnet) {
1390 query->addPktEvent("process_completed");
1391 if (!rsp) {
1392 return;
1393 }
1394
1395 // Specifies if server should do the packing
1396 bool skip_pack = false;
1397
1398 // Server's reply packet now has all options and fields set.
1399 // Options are represented by individual objects, but the
1400 // output wire data has not been prepared yet.
1401 // Execute all callouts registered for packet6_send
1402 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
1403
1404 // Use the RAII wrapper to make sure that the callout handle state is
1405 // reset when this object goes out of scope. All hook points must do
1406 // it to prevent possible circular dependency between the callout
1407 // handle and its arguments.
1408 ScopedCalloutHandleState callout_handle_state(callout_handle);
1409
1410 // Enable copying options from the packets within hook library.
1411 ScopedEnableOptionsCopy<Pkt6> query_resp_options_copy(query, rsp);
1412
1413 // Pass incoming packet as argument
1414 callout_handle->setArgument("query6", query);
1415
1416 // Set our response
1417 callout_handle->setArgument("response6", rsp);
1418
1419 // Pass the selected subnet as an argument.
1420 callout_handle->setArgument("subnet6", subnet);
1421
1422 // Call all installed callouts
1423 HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
1424
1425 // Callouts decided to skip the next processing step. The next
1426 // processing step would be to pack the packet (create wire data).
1427 // That step will be skipped if any callout sets skip flag.
1428 // It essentially means that the callout already did packing,
1429 // so the server does not have to do it again.
1430 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1432 .arg(rsp->getLabel());
1433 skip_pack = true;
1434 }
1435
1437 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1439 .arg(rsp->getLabel());
1440 rsp.reset();
1441 return;
1442 }
1443 }
1444
1445 if (!skip_pack) {
1446 try {
1447 LibDHCP::splitNtpServerOptions6(rsp->options_);
1448 rsp->pack();
1449 } catch (const std::exception& e) {
1451 .arg(query->getLabel())
1452 .arg(e.what());
1453 return;
1454 }
1455
1456 }
1457}
1458
1459void
1461 Pkt6Ptr& rsp) {
1462 if (!rsp) {
1463 return;
1464 }
1465
1466 try {
1467 // Now all fields and options are constructed into output wire buffer.
1468 // Option objects modification does not make sense anymore. Hooks
1469 // can only manipulate wire buffer at this stage.
1470 // Let's execute all callouts registered for buffer6_send
1471 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_send_)) {
1472
1473 // Use the RAII wrapper to make sure that the callout handle state is
1474 // reset when this object goes out of scope. All hook points must do
1475 // it to prevent possible circular dependency between the callout
1476 // handle and its arguments.
1477 ScopedCalloutHandleState callout_handle_state(callout_handle);
1478
1479 // Enable copying options from the packet within hook library.
1480 ScopedEnableOptionsCopy<Pkt6> response6_options_copy(rsp);
1481
1482 // Pass incoming packet as argument
1483 callout_handle->setArgument("response6", rsp);
1484
1485 // Call callouts
1486 HooksManager::callCallouts(Hooks.hook_index_buffer6_send_,
1487 *callout_handle);
1488
1489 // Callouts decided to skip the next processing step. The next
1490 // processing step would be to parse the packet, so skip at this
1491 // stage means drop.
1492 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
1493 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
1496 .arg(rsp->getLabel());
1497 return;
1498 }
1499
1500 callout_handle->getArgument("response6", rsp);
1501 }
1502
1504 .arg(rsp->getLabel())
1505 .arg(rsp->getName())
1506 .arg(static_cast<int>(rsp->getType()))
1507 .arg(rsp->getLocalAddr().isV6Zero() ? "*" : rsp->getLocalAddr().toText())
1508 .arg(rsp->getLocalPort())
1509 .arg(rsp->getRemoteAddr())
1510 .arg(rsp->getRemotePort())
1511 .arg(rsp->getIface());
1512
1514 .arg(rsp->getLabel())
1515 .arg(rsp->getName())
1516 .arg(static_cast<int>(rsp->getType()))
1517 .arg(rsp->toText());
1518 sendPacket(rsp);
1519
1520 // Update statistics accordingly for sent packet.
1521 processStatsSent(rsp);
1522
1523 } catch (const std::exception& e) {
1525 .arg(rsp->getLabel())
1526 .arg(e.what());
1527 }
1528}
1529
1530std::string
1532 stringstream tmp;
1533
1534 OptionBuffer data = opt->getData();
1535
1536 bool colon = false;
1537 for (auto const& it : data) {
1538 if (colon) {
1539 tmp << ":";
1540 }
1541 tmp << hex << setw(2) << setfill('0') << static_cast<uint16_t>(it);
1542 if (!colon) {
1543 colon = true;
1544 }
1545 }
1546
1547 return tmp.str();
1548}
1549
1550void
1552 // Add client-id.
1553 OptionPtr clientid = question->getOption(D6O_CLIENTID);
1554 if (clientid) {
1555 answer->addOption(clientid);
1556 }
1558
1559 // If this is a relayed message, we need to copy relay information
1560 if (!question->relay_info_.empty()) {
1561 answer->copyRelayInfo(question);
1562 }
1563
1564}
1565
1566void
1568 const CfgOptionList&) {
1569 // add server-id
1570 answer->addOption(getServerID());
1571}
1572
1573void
1576 CfgOptionList& co_list) {
1577 // Firstly, host specific options.
1578 if (ctx.currentHost() && !ctx.currentHost()->getCfgOption6()->empty()) {
1579 co_list.push_back(ctx.currentHost()->getCfgOption6());
1580 }
1581
1582 // Secondly, pool specific options. Pools are defined within a subnet, so
1583 // if there is no subnet, there is nothing to do.
1584 if (ctx.subnet_) {
1585 for (auto const& resource : ctx.allocated_resources_) {
1586 PoolPtr pool =
1587 ctx.subnet_->getPool(resource.getPrefixLength() == 128 ?
1589 resource.getAddress(),
1590 false);
1591 if (pool && !pool->getCfgOption()->empty()) {
1592 co_list.push_back(pool->getCfgOption());
1593 }
1594 }
1595
1596 // Thirdly, subnet configured options.
1597 if (!ctx.subnet_->getCfgOption()->empty()) {
1598 co_list.push_back(ctx.subnet_->getCfgOption());
1599 }
1600
1601 // Fourthly, shared network specific options.
1602 SharedNetwork6Ptr network;
1603 ctx.subnet_->getSharedNetwork(network);
1604 if (network && !network->getCfgOption()->empty()) {
1605 co_list.push_back(network->getCfgOption());
1606 }
1607 }
1608
1609 // Each class in the incoming packet
1610 const ClientClasses& classes = question->getClasses();
1611 for (auto const& cclass : classes) {
1612 // Find the client class definition for this class
1614 getClientClassDictionary()->findClass(cclass);
1615 if (!ccdef) {
1616 // Not found: the class is built-in or not configured
1617 if (!isClientClassBuiltIn(cclass)) {
1619 .arg(question->getLabel())
1620 .arg(cclass);
1621 }
1622 // Skip it
1623 continue;
1624 }
1625
1626 if (ccdef->getCfgOption()->empty()) {
1627 // Skip classes which don't configure options
1628 continue;
1629 }
1630
1631 co_list.push_back(ccdef->getCfgOption());
1632 }
1633
1634 // Last global options
1635 if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
1636 co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
1637 }
1638}
1639
1640void
1642 const CfgOptionList& co_list) {
1643 // Unlikely short cut
1644 if (co_list.empty()) {
1645 return;
1646 }
1647
1648 set<uint16_t> requested_opts;
1649
1650 // Client requests some options using ORO option. Try to
1651 // get this option from client's message.
1652 OptionUint16ArrayPtr option_oro = boost::dynamic_pointer_cast<
1653 OptionUint16Array>(question->getOption(D6O_ORO));
1654
1655 // Get the list of options that client requested.
1656 if (option_oro) {
1657 for (uint16_t code : option_oro->getValues()) {
1658 static_cast<void>(requested_opts.insert(code));
1659 }
1660 }
1661
1662 set<uint16_t> cancelled_opts;
1663
1664 // Iterate on the configured option list to add persistent and
1665 // cancelled options.
1666 for (auto const& copts : co_list) {
1667 const OptionContainerPtr& opts = copts->getAll(DHCP6_OPTION_SPACE);
1668 if (!opts) {
1669 continue;
1670 }
1671 // Get persistent options.
1672 const OptionContainerPersistIndex& pidx = opts->get<2>();
1673 const OptionContainerPersistRange& prange = pidx.equal_range(true);
1674 BOOST_FOREACH(auto const& desc, prange) {
1675 // Add the persistent option code to requested options.
1676 if (desc.option_) {
1677 uint16_t code = desc.option_->getType();
1678 static_cast<void>(requested_opts.insert(code));
1679 }
1680 }
1681 // Get cancelled options.
1682 const OptionContainerCancelIndex& cidx = opts->get<5>();
1683 const OptionContainerCancelRange& crange = cidx.equal_range(true);
1684 BOOST_FOREACH(auto const& desc, crange) {
1685 // Add the cancelled option code to the cancelled options.
1686 if (desc.option_) {
1687 uint16_t code = desc.option_->getType();
1688 static_cast<void>(cancelled_opts.insert(code));
1689 }
1690 }
1691 }
1692
1693 const auto& cclasses = question->getClasses();
1694 // For each requested option code get the first instance of the option
1695 // to be returned to the client.
1696 for (uint16_t opt : requested_opts) {
1697 // Skip if cancelled.
1698 if (cancelled_opts.count(opt) > 0) {
1699 continue;
1700 }
1701 // Add nothing when it is already there.
1702 // Skip special cases: D6O_VENDOR_OPTS
1703 if (opt == D6O_VENDOR_OPTS) {
1704 continue;
1705 }
1706 if (!answer->getOption(opt)) {
1707 // Iterate on the configured option list
1708 for (auto const& copts : co_list) {
1709 OptionDescriptor desc = copts->get(DHCP6_OPTION_SPACE, opt);
1710 // Got it: if allowed add it and jump to the outer loop.
1711 if (desc.option_ && desc.allowedForClientClasses(cclasses)) {
1712 answer->addOption(desc.option_);
1713 break;
1714 }
1715 }
1716 }
1717 }
1718
1719 // Special cases for vendor class and options which are identified
1720 // by the code/type and the vendor/enterprise id vs. the code/type only.
1721 if ((requested_opts.count(D6O_VENDOR_CLASS) > 0) &&
1722 (cancelled_opts.count(D6O_VENDOR_CLASS) == 0)) {
1723 // Keep vendor ids which are already in the response to insert
1724 // D6O_VENDOR_CLASS options at most once per vendor.
1725 set<uint32_t> vendor_ids;
1726 // Get what already exists in the response.
1727 for (auto const& opt : answer->getOptions(D6O_VENDOR_CLASS)) {
1728 OptionVendorClassPtr vendor_class;
1729 vendor_class = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
1730 if (vendor_class) {
1731 uint32_t vendor_id = vendor_class->getVendorId();
1732 static_cast<void>(vendor_ids.insert(vendor_id));
1733 }
1734 }
1735 // Iterate on the configured option list.
1736 for (auto const& copts : co_list) {
1737 for (auto const& desc : copts->getList(DHCP6_OPTION_SPACE, D6O_VENDOR_CLASS)) {
1738 // Empty or not allowed, skip i.
1739 if (!desc.option_ || !desc.allowedForClientClasses(cclasses)) {
1740 continue;
1741 }
1742 OptionVendorClassPtr vendor_class =
1743 boost::dynamic_pointer_cast<OptionVendorClass>(desc.option_);
1744 if (!vendor_class) {
1745 continue;
1746 }
1747 // Is the vendor id already in the response?
1748 uint32_t vendor_id = vendor_class->getVendorId();
1749 if (vendor_ids.count(vendor_id) > 0) {
1750 continue;
1751 }
1752 // Got it: add it.
1753 answer->addOption(desc.option_);
1754 static_cast<void>(vendor_ids.insert(vendor_id));
1755 }
1756 }
1757 }
1758
1759 if ((requested_opts.count(D6O_VENDOR_OPTS) > 0) &&
1760 (cancelled_opts.count(D6O_VENDOR_OPTS) == 0)) {
1761 // Keep vendor ids which are already in the response to insert
1762 // D6O_VENDOR_OPTS options at most once per vendor.
1763 set<uint32_t> vendor_ids;
1764 // Get what already exists in the response.
1765 for (auto const& opt : answer->getOptions(D6O_VENDOR_OPTS)) {
1766 OptionVendorPtr vendor_opts;
1767 vendor_opts = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
1768 if (vendor_opts) {
1769 uint32_t vendor_id = vendor_opts->getVendorId();
1770 static_cast<void>(vendor_ids.insert(vendor_id));
1771 }
1772 }
1773 // Iterate on the configured option list
1774 for (auto const& copts : co_list) {
1775 for (auto const& desc : copts->getList(DHCP6_OPTION_SPACE, D6O_VENDOR_OPTS)) {
1776 // Empty or not allowed, skip i.
1777 if (!desc.option_ || !desc.allowedForClientClasses(cclasses)) {
1778 continue;
1779 }
1780 OptionVendorPtr vendor_opts =
1781 boost::dynamic_pointer_cast<OptionVendor>(desc.option_);
1782 if (!vendor_opts) {
1783 continue;
1784 }
1785 // Is the vendor id already in the response?
1786 uint32_t vendor_id = vendor_opts->getVendorId();
1787 if (vendor_ids.count(vendor_id) > 0) {
1788 continue;
1789 }
1790 // Append a fresh vendor option as the next method should
1791 // add suboptions to it.
1792 vendor_opts.reset(new OptionVendor(Option::V6, vendor_id));
1793 answer->addOption(vendor_opts);
1794 static_cast<void>(vendor_ids.insert(vendor_id));
1795 }
1796 }
1797 }
1798}
1799
1800void
1802 Pkt6Ptr& answer,
1804 const CfgOptionList& co_list) {
1805
1806 // Leave if there is no subnet matching the incoming packet.
1807 // There is no need to log the error message here because
1808 // it will be logged in the assignLease() when it fails to
1809 // pick the suitable subnet. We don't want to duplicate
1810 // error messages in such case.
1811 //
1812 // Also, if there's no options to possibly assign, give up.
1813 if (!ctx.subnet_ || co_list.empty()) {
1814 return;
1815 }
1816
1817 set<uint32_t> vendor_ids;
1818
1819 // The server could have provided the option using client classification or
1820 // hooks. If there're vendor info options in the response already, use them.
1821 map<uint32_t, OptionVendorPtr> vendor_rsps;
1822 for (auto const& opt : answer->getOptions(D6O_VENDOR_OPTS)) {
1823 OptionVendorPtr vendor_rsp;
1824 vendor_rsp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
1825 if (vendor_rsp) {
1826 uint32_t vendor_id = vendor_rsp->getVendorId();
1827 vendor_rsps[vendor_id] = vendor_rsp;
1828 static_cast<void>(vendor_ids.insert(vendor_id));
1829 }
1830 }
1831
1832 // Next, try to get the vendor-id from the client packet's
1833 // vendor-specific information option (17).
1834 map<uint32_t, OptionVendorPtr> vendor_reqs;
1835 for (auto const& opt : question->getOptions(D6O_VENDOR_OPTS)) {
1836 OptionVendorPtr vendor_req;
1837 vendor_req = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
1838 if (vendor_req) {
1839 uint32_t vendor_id = vendor_req->getVendorId();
1840 vendor_reqs[vendor_id] = vendor_req;
1841 static_cast<void>(vendor_ids.insert(vendor_id));
1842 }
1843 }
1844
1845 // Finally, try to get the vendor-id from the client packet's vendor-class
1846 // option (16).
1847 for (auto const& opt : question->getOptions(D6O_VENDOR_CLASS)) {
1848 OptionVendorClassPtr vendor_class;
1849 vendor_class = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
1850 if (vendor_class) {
1851 uint32_t vendor_id = vendor_class->getVendorId();
1852 static_cast<void>(vendor_ids.insert(vendor_id));
1853 }
1854 }
1855
1856 // If there's no vendor option in either request or response, then there's no way
1857 // to figure out what the vendor-id values are and we give up.
1858 if (vendor_ids.empty()) {
1859 return;
1860 }
1861
1862 map<uint32_t, set<uint16_t> > requested_opts;
1863
1864 // Let's try to get ORO within that vendor-option.
1865 // This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
1866 // different policies.
1868 if (vendor_reqs.count(VENDOR_ID_CABLE_LABS) > 0) {
1869 OptionVendorPtr vendor_req = vendor_reqs[VENDOR_ID_CABLE_LABS];
1870 OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V6_ORO);
1871 if (oro_generic) {
1872 // Vendor ID 4491 makes Kea look at DOCSIS3_V6_OPTION_DEFINITIONS
1873 // when parsing options. Based on that, oro_generic will have been
1874 // created as an OptionUint16Array, but might not be for other
1875 // vendor IDs.
1876 oro = boost::dynamic_pointer_cast<OptionUint16Array>(oro_generic);
1877 }
1878 if (oro) {
1879 set<uint16_t> oro_req_opts;
1880 for (uint16_t code : oro->getValues()) {
1881 static_cast<void>(oro_req_opts.insert(code));
1882 }
1883 requested_opts[VENDOR_ID_CABLE_LABS] = oro_req_opts;
1884 }
1885 }
1886
1887 map<uint32_t, set<uint16_t> > cancelled_opts;
1888 const auto& cclasses = question->getClasses();
1889
1890 // Iterate on the configured option list to add persistent and
1891 // cancelled options.
1892 for (uint32_t vendor_id : vendor_ids) {
1893 for (auto const& copts : co_list) {
1894 const OptionContainerPtr& opts = copts->getAll(vendor_id);
1895 if (!opts) {
1896 continue;
1897 }
1898 // Get persistent options.
1899 const OptionContainerPersistIndex& pidx = opts->get<2>();
1900 const OptionContainerPersistRange& prange = pidx.equal_range(true);
1901 BOOST_FOREACH(auto const& desc, prange) {
1902 if (!desc.option_) {
1903 continue;
1904 }
1905 // Add the persistent option code to requested options
1906 uint16_t code = desc.option_->getType();
1907 static_cast<void>(requested_opts[vendor_id].insert(code));
1908 }
1909 // Get cancelled options.
1910 const OptionContainerCancelIndex& cidx = opts->get<5>();
1911 const OptionContainerCancelRange& crange = cidx.equal_range(true);
1912 BOOST_FOREACH(auto const& desc, crange) {
1913 if (!desc.option_) {
1914 continue;
1915 }
1916 // Add the cancelled option code to cancelled options
1917 uint16_t code = desc.option_->getType();
1918 static_cast<void>(cancelled_opts[vendor_id].insert(code));
1919 }
1920 }
1921
1922 // If there is nothing to add don't do anything with this vendor.
1923 // This will explicitly not echo back vendor options from the request
1924 // that either correspond to a vendor not known to Kea even if the
1925 // option encapsulates data or there are no persistent options
1926 // configured for this vendor so Kea does not send any option back.
1927 if (requested_opts[vendor_id].empty()) {
1928 continue;
1929 }
1930
1931 // It's possible that the vendor opts option was inserted already
1932 // by client class or a hook. If that is so, let's use it.
1933 OptionVendorPtr vendor_rsp;
1934 if (vendor_rsps.count(vendor_id) > 0) {
1935 vendor_rsp = vendor_rsps[vendor_id];
1936 } else {
1937 vendor_rsp.reset(new OptionVendor(Option::V6, vendor_id));
1938 }
1939
1940 // Get the list of options that client requested.
1941 bool added = false;
1942
1943 for (uint16_t opt : requested_opts[vendor_id]) {
1944 if (cancelled_opts[vendor_id].count(opt) > 0) {
1945 continue;
1946 }
1947 if (!vendor_rsp->getOption(opt)) {
1948 for (auto const& copts : co_list) {
1949 OptionDescriptor desc = copts->get(vendor_id, opt);
1950 // Got it: if allowed add it and jump to outer loop.
1951 if (desc.option_ && desc.allowedForClientClasses(cclasses)) {
1952 vendor_rsp->addOption(desc.option_);
1953 added = true;
1954 break;
1955 }
1956 }
1957 }
1958 }
1959
1960 // If we added some sub-options and the vendor opts option is not in
1961 // the response already, then add it.
1962 if (added && (vendor_rsps.count(vendor_id) == 0)) {
1963 answer->addOption(vendor_rsp);
1964 }
1965 }
1966}
1967
1968bool
1970 try {
1971 switch (pkt->getType()) {
1972 case DHCPV6_SOLICIT:
1973 case DHCPV6_REBIND:
1974 case DHCPV6_CONFIRM:
1976 return (true);
1977
1978 case DHCPV6_REQUEST:
1979 case DHCPV6_RENEW:
1980 case DHCPV6_RELEASE:
1981 case DHCPV6_DECLINE:
1983 return (true);
1984
1988 return (true);
1989
1990 default:
1993 .arg(pkt->getLabel())
1994 .arg(static_cast<int>(pkt->getType()))
1995 .arg(pkt->getIface());
1996 }
1997
1998 } catch (const RFCViolation& e) {
2000 .arg(pkt->getLabel())
2001 .arg(pkt->getName())
2002 .arg(pkt->getRemoteAddr().toText())
2003 .arg(e.what());
2004 }
2005
2006 // Increase the statistic of dropped packets.
2007 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
2008 return (false);
2009}
2010
2011void
2013 RequirementLevel serverid) {
2014 OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
2015 switch (clientid) {
2016 case MANDATORY: {
2017 if (client_ids.size() != 1) {
2018 isc_throw(RFCViolation, "Exactly 1 client-id option expected in "
2019 << pkt->getName() << ", but " << client_ids.size()
2020 << " received");
2021 }
2022 sanityCheckDUID(client_ids.begin()->second, "client-id");
2023 break;
2024 }
2025 case OPTIONAL:
2026 if (client_ids.size() > 1) {
2027 isc_throw(RFCViolation, "Too many (" << client_ids.size()
2028 << ") client-id options received in " << pkt->getName());
2029 }
2030 if (!client_ids.empty()) {
2031 sanityCheckDUID(client_ids.begin()->second, "client-id");
2032 }
2033 break;
2034
2035 case FORBIDDEN:
2036 // doesn't make sense - client-id is always allowed
2037 break;
2038 }
2039
2040 OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
2041 switch (serverid) {
2042 case FORBIDDEN:
2043 if (!server_ids.empty()) {
2044 isc_throw(RFCViolation, "Server-id option was not expected, but "
2045 << server_ids.size() << " received in " << pkt->getName());
2046 }
2047 break;
2048
2049 case MANDATORY:
2050 if (server_ids.size() != 1) {
2051 isc_throw(RFCViolation, "Invalid number of server-id options received ("
2052 << server_ids.size() << "), exactly 1 expected in message "
2053 << pkt->getName());
2054 }
2055 sanityCheckDUID(server_ids.begin()->second, "server-id");
2056 break;
2057
2058 case OPTIONAL:
2059 if (server_ids.size() > 1) {
2060 isc_throw(RFCViolation, "Too many (" << server_ids.size()
2061 << ") server-id options received in " << pkt->getName());
2062 }
2063 if (!server_ids.empty()) {
2064 sanityCheckDUID(server_ids.begin()->second, "server-id");
2065 }
2066 }
2067}
2068
2069void Dhcpv6Srv::sanityCheckDUID(const OptionPtr& opt, const std::string& opt_name) {
2070 if (!opt) {
2071 isc_throw(RFCViolation, "Unable to find expected option " << opt_name);
2072 }
2073
2074 // The client-id or server-id has to have at least 3 bytes of useful data:
2075 // two for duid type and one more for actual duid value.
2076 uint16_t len = opt->len() - opt->getHeaderLen();
2077 if (len < DUID::MIN_DUID_LEN || len > DUID::MAX_DUID_LEN) {
2078 isc_throw(RFCViolation, "Received invalid DUID for " << opt_name << ", received "
2079 << len << " byte(s). It must be at least " << DUID::MIN_DUID_LEN
2080 << " and no more than " << DUID::MAX_DUID_LEN);
2081 }
2082}
2083
2085Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question, bool& drop) {
2086 const SubnetSelector& selector = CfgSubnets6::initSelector(question);
2087
2089 getCfgSubnets6()->selectSubnet(selector);
2090
2091 // Let's execute all callouts registered for subnet6_receive
2092 if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
2093 CalloutHandlePtr callout_handle = getCalloutHandle(question);
2094
2095 // Use the RAII wrapper to make sure that the callout handle state is
2096 // reset when this object goes out of scope. All hook points must do
2097 // it to prevent possible circular dependency between the callout
2098 // handle and its arguments.
2099 shared_ptr<ScopedCalloutHandleState> callout_handle_state(
2100 std::make_shared<ScopedCalloutHandleState>(callout_handle));
2101
2102 // Enable copying options from the packet within hook library.
2103 ScopedEnableOptionsCopy<Pkt6> query6_options_copy(question);
2104
2105 // Set new arguments
2106 callout_handle->setArgument("query6", question);
2107 callout_handle->setArgument("subnet6", subnet);
2108
2109 // We pass pointer to const collection for performance reasons.
2110 // Otherwise we would get a non-trivial performance penalty each
2111 // time subnet6_select is called.
2112 callout_handle->setArgument("subnet6collection",
2113 CfgMgr::instance().getCurrentCfg()->
2114 getCfgSubnets6()->getAll());
2115
2116 auto const tpl(parkingLimitExceeded("subnet6_select"));
2117 bool const exceeded(get<0>(tpl));
2118 if (exceeded) {
2119 uint32_t const limit(get<1>(tpl));
2120 // We can't park it so we're going to throw it on the floor.
2123 .arg(limit)
2124 .arg(question->getLabel());
2125 return (ConstSubnet6Ptr());
2126 }
2127
2128 // We proactively park the packet.
2129 // Not MT compatible because the unparking callback can be called
2130 // before the current thread exists from this block.
2131 HooksManager::park("subnet6_select", question, [this, question, callout_handle_state]() {
2132 if (MultiThreadingMgr::instance().getMode()) {
2133 boost::shared_ptr<function<void()>> callback(
2134 boost::make_shared<function<void()>>([this, question]() mutable {
2136 }));
2137 callout_handle_state->on_completion_ = [callback]() {
2139 };
2140 } else {
2142 }
2143 });
2144
2145 // Call user (and server-side) callouts
2146 try {
2147 HooksManager::callCallouts(Hooks.hook_index_subnet6_select_,
2148 *callout_handle);
2149 } catch (...) {
2150 // Make sure we don't orphan a parked packet.
2151 HooksManager::drop("subnet6_select", question);
2152 throw;
2153 }
2154
2155 // Callouts parked the packet. Same as drop but callouts will resume
2156 // processing or drop the packet later.
2157 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
2159 .arg(question->getLabel());
2160 drop = true;
2161 return (ConstSubnet6Ptr());
2162 } else {
2163 HooksManager::drop("subnet6_select", question);
2164 }
2165
2166 // Callouts decided to skip this step. This means that no
2167 // subnet will be selected. Packet processing will continue,
2168 // but it will be severely limited (i.e. only global options
2169 // will be assigned)
2170 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
2172 .arg(question->getLabel());
2173 return (ConstSubnet6Ptr());
2174 }
2175
2176 // Callouts decided to drop the packet. It is a superset of the
2177 // skip case so no subnet will be selected.
2178 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
2180 .arg(question->getLabel());
2181 drop = true;
2182 return (ConstSubnet6Ptr());
2183 }
2184
2185 // Use whatever subnet was specified by the callout
2186 callout_handle->getArgument("subnet6", subnet);
2187 }
2188
2189 if (subnet) {
2190 // Log at higher debug level that subnet has been found.
2192 .arg(question->getLabel())
2193 .arg(subnet->getID());
2194 // Log detailed information about the selected subnet at the
2195 // lower debug level.
2197 .arg(question->getLabel())
2198 .arg(subnet->toText());
2199
2200 } else {
2202 .arg(question->getLabel());
2203 }
2204
2205 return (subnet);
2206}
2207
2208void
2209Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
2211 // Save the originally selected subnet.
2212 ConstSubnet6Ptr orig_subnet = ctx.subnet_;
2213
2214 // We need to allocate addresses for all IA_NA options in the client's
2215 // question (i.e. SOLICIT or REQUEST) message.
2216 // @todo add support for IA_TA
2217
2218 // For the lease allocation it is critical that the client has sent
2219 // DUID. There is no need to check for the presence of the DUID here
2220 // because we have already checked it in the sanityCheck().
2221
2222 // Now that we have all information about the client, let's iterate over all
2223 // received options and handle IA_NA options one by one and store our
2224 // responses in answer message (ADVERTISE or REPLY).
2225 //
2226 // @todo: IA_TA once we implement support for temporary addresses.
2227 for (auto const& opt : question->options_) {
2228 switch (opt.second->getType()) {
2229 case D6O_IA_NA: {
2230 OptionPtr answer_opt = assignIA_NA(question, ctx,
2231 boost::dynamic_pointer_cast<
2232 Option6IA>(opt.second));
2233 if (answer_opt) {
2234 answer->addOption(answer_opt);
2235 }
2236 break;
2237 }
2238 case D6O_IA_PD: {
2239 OptionPtr answer_opt = assignIA_PD(question, ctx,
2240 boost::dynamic_pointer_cast<
2241 Option6IA>(opt.second));
2242 if (answer_opt) {
2243 answer->addOption(answer_opt);
2244 }
2245 break;
2246 }
2247 default:
2248 break;
2249 }
2250 }
2251
2252 // Need to check for pool-level DDNS parameters and if the
2253 // subnet was modified by the allocation engine, there are things
2254 // we need to do either case.
2255 checkPostAssignmentChanges(question, answer, ctx, orig_subnet);
2256}
2257
2258void
2259Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
2262 DdnsParamsPtr ddns_params = ctx.getDdnsParams();
2263
2264 // Get Client FQDN Option from the client's message. If this option hasn't
2265 // been included, do nothing.
2266 Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
2267 Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
2268 if (!fqdn) {
2269 if (ddns_params->getEnableUpdates() &&
2270 (ddns_params->getReplaceClientNameMode() == D2ClientConfig::RCM_ALWAYS ||
2271 ddns_params->getReplaceClientNameMode() == D2ClientConfig::RCM_WHEN_NOT_PRESENT)) {
2272 // Fabricate an empty "client" FQDN with flags requesting
2273 // the server do all the updates. The flags will get modified
2274 // below according the configuration options, the name will
2275 // be supplied later on.
2279 .arg(question->getLabel());
2280 } else {
2281 // No FQDN so get the lease hostname from the host reservation if
2282 // there is one.
2283 if (ctx.currentHost()) {
2284 ctx.hostname_ = ctx.currentHost()->getHostname();
2285 }
2286
2287 return;
2288 }
2289 }
2290
2292 .arg(question->getLabel())
2293 .arg(fqdn->toText());
2294
2295 // Create the DHCPv6 Client FQDN Option to be included in the server's
2296 // response to a client.
2297 Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
2298
2299 // Set the server S, N, and O flags based on client's flags and
2300 // current configuration.
2301 d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp, *ddns_params);
2302
2303 // Get DDNS update direction flags
2305 ctx.rev_dns_update_);
2306
2307 // If there's a reservation and it has a hostname specified, use it!
2308 if (ctx.currentHost() && !ctx.currentHost()->getHostname().empty()) {
2309 // Add the qualifying suffix.
2310 // After #3765, this will only occur if the suffix is not empty.
2311 fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.currentHost()->getHostname(),
2312 *ddns_params, true),
2314 } else {
2315 // Adjust the domain name based on domain name value and type sent by
2316 // the client and current configuration.
2317 d2_mgr.adjustDomainName<Option6ClientFqdn>(*fqdn, *fqdn_resp, *ddns_params);
2318 }
2319
2320 // Once we have the FQDN setup to use it for the lease hostname. This
2321 // only gets replaced later if the FQDN is to be generated from the address.
2322 ctx.hostname_ = fqdn_resp->getDomainName();
2323
2324 // The FQDN has been processed successfully. Let's append it to the
2325 // response to be sent to a client. Note that the Client FQDN option is
2326 // always sent back to the client if Client FQDN was included in the
2327 // client's message.
2329 .arg(question->getLabel())
2330 .arg(fqdn_resp->toText());
2331 answer->addOption(fqdn_resp);
2332
2333 // Optionally, call a hook that may override the decisions made
2334 // earlier.
2335 if (HooksManager::calloutsPresent(Hooks.hook_index_ddns6_update_)) {
2336 CalloutHandlePtr callout_handle = getCalloutHandle(question);
2337
2338 // Use the RAII wrapper to make sure that the callout handle state is
2339 // reset when this object goes out of scope. All hook points must do
2340 // it to prevent possible circular dependency between the callout
2341 // handle and its arguments.
2342 ScopedCalloutHandleState callout_handle_state(callout_handle);
2343
2344 // Setup the callout arguments.
2345 ConstSubnet6Ptr subnet = ctx.subnet_;
2346 callout_handle->setArgument("query6", question);
2347 callout_handle->setArgument("response6", answer);
2348 callout_handle->setArgument("subnet6", subnet);
2349 callout_handle->setArgument("hostname", ctx.hostname_);
2350 callout_handle->setArgument("fwd-update", ctx.fwd_dns_update_);
2351 callout_handle->setArgument("rev-update", ctx.rev_dns_update_);
2352 callout_handle->setArgument("ddns-params", ddns_params);
2353
2354 // Call callouts
2355 HooksManager::callCallouts(Hooks.hook_index_ddns6_update_, *callout_handle);
2356
2357 // Let's get the parameters returned by hook.
2358 string hook_hostname;
2359 bool hook_fwd_dns_update;
2360 bool hook_rev_dns_update;
2361 callout_handle->getArgument("hostname", hook_hostname);
2362 callout_handle->getArgument("fwd-update", hook_fwd_dns_update);
2363 callout_handle->getArgument("rev-update", hook_rev_dns_update);
2364
2365 // If there's anything changed by the hook, log it and then update the parameters
2366 if ((ctx.hostname_ != hook_hostname) || (ctx.fwd_dns_update_!= hook_fwd_dns_update) ||
2367 (ctx.rev_dns_update_ != hook_rev_dns_update)) {
2369 .arg(ctx.hostname_).arg(hook_hostname)
2370 .arg(ctx.fwd_dns_update_).arg(hook_fwd_dns_update)
2371 .arg(ctx.rev_dns_update_).arg(hook_rev_dns_update);
2372
2373 // Update the FQDN option in the response.
2374 fqdn_resp = boost::dynamic_pointer_cast<Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
2375 if (fqdn) {
2376 fqdn_resp->setDomainName(hook_hostname, Option6ClientFqdn::FULL);
2377 if (!(hook_fwd_dns_update || hook_rev_dns_update)) {
2378 // Hook disabled updates, Set flags back to client accordingly.
2379 fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, 0);
2380 fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, 1);
2381 }
2382 }
2383
2384 ctx.hostname_ = hook_hostname;
2385 ctx.fwd_dns_update_ = hook_fwd_dns_update;
2386 ctx.rev_dns_update_ = hook_rev_dns_update;
2387 }
2388 }
2389}
2390
2391void
2394 // Don't create NameChangeRequests if DNS updates are disabled.
2395 if (!(ctx.getDdnsParams()->getEnableUpdates())) {
2396 return;
2397 }
2398
2399 // The response message instance is always required. For instance it
2400 // holds the Client Identifier. It is a programming error if supplied
2401 // message is NULL.
2402 if (!answer) {
2403 isc_throw(isc::Unexpected, "an instance of the object"
2404 << " encapsulating server's message must not be"
2405 << " NULL when creating DNS NameChangeRequest");
2406 }
2407
2408 // It is likely that client haven't included the FQDN option. In such case,
2409 // FQDN option will be NULL. This is valid state, so we simply return.
2410 Option6ClientFqdnPtr opt_fqdn = boost::dynamic_pointer_cast<
2411 Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
2412 if (!opt_fqdn) {
2413 return;
2414 }
2415
2416 // Get the update directions that should be performed based on our
2417 // response FQDN flags.
2418 bool do_fwd = false;
2419 bool do_rev = false;
2421 do_fwd, do_rev);
2422
2423 // Get the Client Id. It is mandatory and a function creating a response
2424 // would have thrown an exception if it was missing. Thus throwing
2425 // Unexpected if it is missing as it is a programming error.
2426 OptionPtr opt_duid = answer->getOption(D6O_CLIENTID);
2427 if (!opt_duid) {
2429 "client identifier is required when creating a new"
2430 " DNS NameChangeRequest");
2431 }
2432 DuidPtr duid = DuidPtr(new DUID(opt_duid->getData()));
2433
2434 // Get the FQDN in the on-wire format. It will be needed to compute
2435 // DHCID.
2436 OutputBuffer name_buf(1);
2437 opt_fqdn->packDomainName(name_buf);
2438 const std::vector<uint8_t>& buf_vec = name_buf.getVector();
2439 // Compute DHCID from Client Identifier and FQDN.
2440 isc::dhcp_ddns::D2Dhcid dhcid(*duid, buf_vec);
2441
2442 // Iterate over the IAContexts (there should be one per client IA processed).
2443 // For the first address in each IA_NA, create the appropriate NCR(s).
2446 for (auto& ia_ctx : ctx.getIAContexts()) {
2447 if ((ia_ctx.type_ != Lease::TYPE_NA) || !ia_ctx.ia_rsp_) {
2448 continue;
2449 }
2450
2453 Option6IAAddrPtr iaaddr = boost::static_pointer_cast<
2454 Option6IAAddr>(ia_ctx.ia_rsp_->getOption(D6O_IAADDR));
2455
2456 // We need an address to create a name-to-address mapping.
2457 // If address is missing for any reason, go to the next IA.
2458 if (!iaaddr) {
2459 continue;
2460 }
2461
2462 bool extended_only = false;
2463 // If the lease is being reused (i.e. lease caching in effect) skip it.
2464 IOAddress ia_address = iaaddr->getAddress();
2465 for (auto const& l : ia_ctx.reused_leases_) {
2466 if (l->addr_ == ia_address) {
2467 extended_only = true;
2468 break;
2469 }
2470 }
2471
2472 if (extended_only) {
2473 continue;
2474 }
2475
2476 // If the lease for iaaddr is in the list of changed leases, we need
2477 // to determine if the changes included changes to the FQDN. If so
2478 // then we may need to do a CHG_REMOVE.
2479 for (auto const& l : ia_ctx.changed_leases_) {
2480
2481 if (l->addr_ == ia_address) {
2482 // The address is the same so this must be renewal. If we're not
2483 // always updating on renew, then we only renew if DNS info has
2484 // changed.
2485 if ((l->reuseable_valid_lft_ > 0) ||
2486 (!ctx.getDdnsParams()->getUpdateOnRenew() &&
2487 (l->hostname_ == opt_fqdn->getDomainName() &&
2488 l->fqdn_fwd_ == do_fwd && l->fqdn_rev_ == do_rev))) {
2489 extended_only = true;
2490 } else {
2491 // Queue a CHG_REMOVE of the old data.
2492 // NCR will only be created if the lease hostname is not
2493 // empty and at least one of the direction flags is true
2494 queueNCR(CHG_REMOVE, l);
2495 }
2496
2497 break;
2498 }
2499 }
2500
2501 if (!(do_fwd || do_rev) || (extended_only)) {
2502 // Flags indicate no updates needed or it was an extension of
2503 // an existing lease with no FQDN changes. In the case of the
2504 // former, the most likely scenario is that we are honoring the
2505 // client's request that no updates be done.
2506 continue;
2507 }
2508
2509 // Create new NameChangeRequest. Use the domain name from the FQDN.
2510 // This is an FQDN included in the response to the client, so it
2511 // holds a fully qualified domain-name already (not partial).
2512 // Get the IP address from the lease.
2514 auto cr_mode = StringToConflictResolutionMode(ctx.getDdnsParams()->getConflictResolutionMode());
2516 do_fwd, do_rev,
2517 opt_fqdn->getDomainName(),
2518 iaaddr->getAddress().toText(),
2519 dhcid, 0,
2520 calculateDdnsTtl(iaaddr->getValid(),
2521 ctx.getDdnsParams()->getTtlPercent(),
2522 ctx.getDdnsParams()->getTtl(),
2523 ctx.getDdnsParams()->getTtlMin(),
2524 ctx.getDdnsParams()->getTtlMax()),
2525 cr_mode));
2527 .arg(answer->getLabel())
2528 .arg(ncr->toText());
2529
2530 // Post the NCR to the D2ClientMgr.
2532
2537 return;
2538 }
2539}
2540
2543 CfgMACSources mac_sources = CfgMgr::instance().getCurrentCfg()->
2544 getMACSources().get();
2545 HWAddrPtr hwaddr;
2546 for (auto const& it : mac_sources) {
2547 hwaddr = pkt->getMAC(it);
2548 if (hwaddr) {
2549 return (hwaddr);
2550 }
2551 }
2552 return (hwaddr);
2553}
2554
2557 const Lease6Ptr& lease) {
2558
2559 // Search the reservation the prefix is from.
2560 ConstHostPtr host = ctx.currentHost();
2561 if (host) {
2562 IPv6ResrvRange resvs = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
2563 BOOST_FOREACH(auto const& resv, resvs) {
2564 if ((resv.second.getPrefix() == lease->addr_) &&
2565 (resv.second.getPrefixLen() == lease->prefixlen_)) {
2566 return (resv.second.getPDExclude());
2567 }
2568 }
2569 }
2570
2571 // Search the pool the address is from.
2572 const ConstSubnet6Ptr& subnet = ctx.subnet_;
2573 Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
2574 subnet->getPool(Lease::TYPE_PD, lease->addr_));
2575 if (pool) {
2576 return (pool->getPrefixExcludeOption());
2577 }
2578 return (OptionPtr());
2579}
2580
2584 boost::shared_ptr<Option6IA> ia) {
2585
2586 // Check if the client sent us a hint in his IA_NA. Clients may send an
2587 // address in their IA_NA options as a suggestion (e.g. the last address
2588 // they used before).
2589 Option6IAAddrPtr hint_opt =
2590 boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
2592 if (hint_opt) {
2593 hint = hint_opt->getAddress();
2594 }
2595
2596 if (ctx.fake_allocation_) {
2598 .arg(query->getLabel())
2599 .arg(ia->getIAID())
2600 .arg(hint_opt ? hint.toText() : "(no hint)");
2601 } else {
2603 .arg(query->getLabel())
2604 .arg(ia->getIAID())
2605 .arg(hint_opt ? hint.toText() : "(no hint)");
2606 }
2607
2608 // convenience values
2609 const ConstSubnet6Ptr& subnet = ctx.subnet_;
2610
2611 // If there is no subnet selected for handling this IA_NA, the only thing left to do is
2612 // to say that we are sorry, but the user won't get an address. As a convenience, we
2613 // use a different status text to indicate that (compare to the same status code,
2614 // but different wording below)
2615 if (!subnet) {
2616 // Create an empty IA_NA option with IAID matching the request.
2617 // Note that we don't use OptionDefinition class to create this option.
2618 // This is because we prefer using a constructor of Option6IA that
2619 // initializes IAID. Otherwise we would have to use setIAID() after
2620 // creation of the option which has some performance implications.
2621 boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
2622
2623 // Insert status code NoAddrsAvail.
2624 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoAddrsAvail,
2625 "Server could not select subnet for"
2626 " this client"));
2627 return (ia_rsp);
2628 }
2629
2630 // Set per-IA context values.
2631 ctx.createIAContext();
2632 ctx.currentIA().iaid_ = ia->getIAID();
2633 if (hint_opt) {
2634 ctx.currentIA().addHint(hint_opt);
2635 } else {
2636 ctx.currentIA().addHint(hint);
2637 }
2639
2640 // Use allocation engine to pick a lease for this client. Allocation engine
2641 // will try to honor the hint, but it is just a hint - some other address
2642 // may be used instead. If fake_allocation is set to false, the lease will
2643 // be inserted into the LeaseMgr as well.
2644 Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
2645
2647 Lease6Ptr lease;
2648 if (!leases.empty()) {
2649 lease = *leases.begin();
2650 }
2651
2652 // Create IA_NA that we will put in the response.
2653 // Do not use OptionDefinition to create option's instance so
2654 // as we can initialize IAID using a constructor.
2655 Option6IAPtr ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
2656 ctx.currentIA().ia_rsp_ = ia_rsp;
2657
2658 if (lease) {
2659 // We have a lease! Let's wrap its content into IA_NA option
2660 // with IAADDR suboption.
2661 if (ctx.fake_allocation_) {
2663 .arg(query->getLabel())
2664 .arg(lease->addr_.toText())
2665 .arg(ia->getIAID());
2666 } else if (lease->reuseable_valid_lft_ == 0) {
2668 .arg(query->getLabel())
2669 .arg(lease->addr_.toText())
2670 .arg(ia->getIAID())
2671 .arg(Lease::lifetimeToText(lease->valid_lft_));
2672 } else {
2673 lease->valid_lft_ = lease->reuseable_valid_lft_;
2674 lease->preferred_lft_ = lease->reuseable_preferred_lft_;
2675 ctx.currentIA().reused_leases_.push_back(lease);
2677 .arg(query->getLabel())
2678 .arg(lease->addr_.toText())
2679 .arg(ia->getIAID())
2680 .arg(Lease::lifetimeToText(lease->valid_lft_));
2681
2682 // Increment the reuse statistics.
2683 StatsMgr::instance().addValue("v6-ia-na-lease-reuses", int64_t(1));
2684 StatsMgr::instance().addValue(StatsMgr::generateName("subnet", lease->subnet_id_,
2685 "v6-ia-na-lease-reuses"),
2686 int64_t(1));
2687 }
2689 .arg(query->getLabel())
2690 .arg(ia->getIAID())
2691 .arg(lease->toText());
2692
2693 // Set the values for T1 and T2.
2694 setTeeTimes(lease->preferred_lft_, subnet, ia_rsp);
2695
2696 Option6IAAddrPtr addr(new Option6IAAddr(D6O_IAADDR, lease->addr_,
2697 lease->preferred_lft_,
2698 lease->valid_lft_));
2699 ia_rsp->addOption(addr);
2700
2701 // It would be possible to insert status code=0(success) as well,
2702 // but this is considered waste of bandwidth as absence of status
2703 // code is considered a success.
2704
2705 } else {
2706 // Allocation engine did not allocate a lease. The engine logged
2707 // cause of that failure. The only thing left is to insert
2708 // status code to pass the sad news to the client.
2709
2712 .arg(query->getLabel())
2713 .arg(ia->getIAID());
2714
2715 ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
2717 "Sorry, no address could be"
2718 " allocated."));
2719 }
2720 return (ia_rsp);
2721}
2722
2726 boost::shared_ptr<Option6IA> ia) {
2727
2728 // Check if the client sent us a hint in his IA_PD. Clients may send an
2729 // address in their IA_PD options as a suggestion (e.g. the last address
2730 // they used before). While the hint consists of a full prefix (prefix +
2731 // length), getting just the prefix is sufficient to identify a lease.
2732 Option6IAPrefixPtr hint_opt =
2733 boost::dynamic_pointer_cast<Option6IAPrefix>(ia->getOption(D6O_IAPREFIX));
2735 if (hint_opt) {
2736 hint = hint_opt->getAddress();
2737 }
2738
2739 if (ctx.fake_allocation_) {
2741 .arg(query->getLabel())
2742 .arg(ia->getIAID())
2743 .arg(hint_opt ? hint.toText() : "(no hint)");
2744 } else {
2746 .arg(query->getLabel())
2747 .arg(ia->getIAID())
2748 .arg(hint_opt ? hint.toText() : "(no hint)");
2749 }
2750
2751 const ConstSubnet6Ptr& subnet = ctx.subnet_;
2752
2753 // Create IA_PD that we will put in the response.
2754 // Do not use OptionDefinition to create option's instance so
2755 // as we can initialize IAID using a constructor.
2756 boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
2757
2758 // If there is no subnet selected for handling this IA_PD, the only thing
2759 // left to do is to say that we are sorry, but the user won't get an address.
2760 // As a convenience, we use a different status text to indicate that
2761 // (compare to the same status code, but different wording below)
2762 if (!subnet) {
2763
2764 // Insert status code NoAddrsAvail.
2765 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoPrefixAvail,
2766 "Sorry, no subnet available."));
2767 return (ia_rsp);
2768 }
2769
2770 // Set per-IA context values.
2771 ctx.createIAContext();
2772 ctx.currentIA().iaid_ = ia->getIAID();
2773 if (hint_opt) {
2774 ctx.currentIA().addHint(hint_opt);
2775 } else {
2776 ctx.currentIA().addHint(hint, 0);
2777 }
2779 ctx.currentIA().ia_rsp_ = ia_rsp;
2780
2781 // Use allocation engine to pick a lease for this client. Allocation engine
2782 // will try to honor the hint, but it is just a hint - some other address
2783 // may be used instead. If fake_allocation is set to false, the lease will
2784 // be inserted into the LeaseMgr as well.
2785 Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
2786
2787 if (!leases.empty()) {
2788
2789 // Need to retain the shortest preferred lease time to use
2790 // for calculating T1 and T2.
2791 uint32_t min_preferred_lft = (*leases.begin())->preferred_lft_;
2792
2793 const bool pd_exclude_requested = requestedInORO(query, D6O_PD_EXCLUDE);
2794 for (auto const& l : leases) {
2795
2796 // We have a lease! Let's wrap its content into IA_PD option
2797 // with IAADDR suboption.
2798 if (ctx.fake_allocation_) {
2800 .arg(query->getLabel())
2801 .arg(l->addr_.toText())
2802 .arg(static_cast<int>(l->prefixlen_))
2803 .arg(ia->getIAID());
2804 } else if (l->reuseable_valid_lft_ == 0) {
2806 .arg(query->getLabel())
2807 .arg(l->addr_.toText())
2808 .arg(static_cast<int>(l->prefixlen_))
2809 .arg(ia->getIAID())
2810 .arg(Lease::lifetimeToText(l->valid_lft_));
2811 } else {
2812 l->valid_lft_ = l->reuseable_valid_lft_;
2813 l->preferred_lft_ = l->reuseable_preferred_lft_;
2815 .arg(query->getLabel())
2816 .arg(l->addr_.toText())
2817 .arg(static_cast<int>(l->prefixlen_))
2818 .arg(ia->getIAID())
2819 .arg(Lease::lifetimeToText(l->valid_lft_));
2820
2821 // Increment the reuse statistics.
2822 StatsMgr::instance().addValue("v6-ia-pd-lease-reuses", int64_t(1));
2823 StatsMgr::instance().addValue(StatsMgr::generateName("subnet", l->subnet_id_,
2824 "v6-ia-pd-lease-reuses"),
2825 int64_t(1));
2826 }
2827
2828 // Check for new minimum lease time
2829 if ((l->preferred_lft_ > 0) && (min_preferred_lft > l->preferred_lft_)) {
2830 min_preferred_lft = l->preferred_lft_;
2831 }
2832
2833 boost::shared_ptr<Option6IAPrefix>
2834 addr(new Option6IAPrefix(D6O_IAPREFIX, l->addr_,
2835 l->prefixlen_, l->preferred_lft_,
2836 l->valid_lft_));
2837 ia_rsp->addOption(addr);
2838
2839 if (pd_exclude_requested) {
2840 OptionPtr pd_exclude_option = getPDExclude(ctx, l);
2841 if (pd_exclude_option) {
2842 addr->addOption(pd_exclude_option);
2843 }
2844 }
2845 }
2846
2847 // Set T1 and T2, using the shortest preferred lifetime among the leases.
2848 setTeeTimes(min_preferred_lft, subnet, ia_rsp);
2849
2850 // It would be possible to insert status code=0(success) as well,
2851 // but this is considered waste of bandwidth as absence of status
2852 // code is considered a success.
2853
2854 } else {
2855 // Allocation engine did not allocate a lease. The engine logged
2856 // cause of that failure. The only thing left is to insert
2857 // status code to pass the sad news to the client.
2858
2861 .arg(query->getLabel())
2862 .arg(ia->getIAID());
2863
2864 ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
2866 "Sorry, no prefixes could"
2867 " be allocated."));
2868 }
2869 return (ia_rsp);
2870}
2871
2875 boost::shared_ptr<Option6IA> ia) {
2876
2878 .arg(query->getLabel())
2879 .arg(ia->getIAID());
2880
2881 // convenience values
2882 const ConstSubnet6Ptr& subnet = ctx.subnet_;
2883
2884 // Create empty IA_NA option with IAID matching the request.
2885 Option6IAPtr ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
2886
2887 if (!subnet) {
2897 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
2898 "Sorry, no known leases for this duid/iaid."));
2899 return (ia_rsp);
2900 }
2901
2902 // Set per-IA context values.
2903 ctx.createIAContext();
2904 ctx.currentIA().iaid_ = ia->getIAID();
2906 ctx.currentIA().ia_rsp_ = ia_rsp;
2907
2908 // Extract the addresses that the client is trying to obtain.
2909 OptionCollection addrs = ia->getOptions();
2910 for (auto const& it : addrs) {
2911 if (it.second->getType() != D6O_IAADDR) {
2912 continue;
2913 }
2914 Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(it.second);
2915 if (!iaaddr) {
2916 // That's weird. Option code was ok, but the object type was not.
2917 // This should never happen. The only case would be with badly
2918 // mis-implemented hook libraries that insert invalid option objects.
2919 // There's no way to protect against this.
2920 continue;
2921 }
2922 ctx.currentIA().addHint(iaaddr);
2923 }
2924
2925 Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
2926
2927 // Ok, now we have the leases extended. We have:
2928 // - what the client tried to renew in ctx.hints_
2929 // - what we actually assigned in leases
2930 // - old leases that are no longer valid in ctx.old_leases_
2931
2932 // For each IA inserted by the client we have to determine what to do
2933 // about included addresses and notify the client. We will iterate over
2934 // those prefixes and remove those that we have already processed. We
2935 // don't want to remove them from the context, so we need to copy them
2936 // into temporary container.
2938
2939 // Retains the shortest valid lease time to use
2940 // for calculating T1 and T2.
2941 uint32_t min_preferred_lft = std::numeric_limits<uint32_t>::max();
2942
2943 // For all leases we have now, add the IAADDR with non-zero lifetimes.
2944 for (auto const& l : leases) {
2945 if (l->reuseable_valid_lft_ == 0) {
2947 .arg(query->getLabel())
2948 .arg(l->addr_.toText())
2949 .arg(ia->getIAID());
2950 } else {
2951 l->valid_lft_ = l->reuseable_valid_lft_;
2952 l->preferred_lft_ = l->reuseable_preferred_lft_;
2954 .arg(query->getLabel())
2955 .arg(l->addr_.toText())
2956 .arg(ia->getIAID())
2957 .arg(Lease::lifetimeToText(l->valid_lft_));
2958
2959 // Increment the reuse statistics.
2960 StatsMgr::instance().addValue("v6-ia-na-lease-reuses", int64_t(1));
2961 StatsMgr::instance().addValue(StatsMgr::generateName("subnet", l->subnet_id_,
2962 "v6-ia-na-lease-reuses"),
2963 int64_t(1));
2964 }
2965
2967 l->addr_, l->preferred_lft_, l->valid_lft_));
2968 ia_rsp->addOption(iaaddr);
2969
2970 // Check for new minimum lease time
2971 if ((l->preferred_lft_ > 0) && (min_preferred_lft > l->preferred_lft_)) {
2972 min_preferred_lft = l->preferred_lft_;
2973 }
2974
2975 // Now remove this address from the hints list.
2976 AllocEngine::Resource hint_type(l->addr_);
2977 hints.erase(std::remove(hints.begin(), hints.end(), hint_type),
2978 hints.end());
2979 }
2980
2981 // For the leases that we just retired, send the addresses with 0 lifetimes.
2982 for (auto const& l : ctx.currentIA().old_leases_) {
2983
2984 // Send an address with zero lifetimes only when this lease belonged to
2985 // this client. Do not send it when we're reusing an old lease that belonged
2986 // to someone else.
2987 if (equalValues(query->getClientId(), l->duid_)) {
2989 l->addr_, 0, 0));
2990 ia_rsp->addOption(iaaddr);
2991 }
2992
2993 // Now remove this address from the hints list.
2994 AllocEngine::Resource hint_type(l->addr_);
2995 hints.erase(std::remove(hints.begin(), hints.end(), hint_type), hints.end());
2996
2997 // If the new FQDN settings have changed for the lease, we need to
2998 // delete any existing FQDN records for this lease.
2999 if ((l->hostname_ != ctx.hostname_) || (l->fqdn_fwd_ != ctx.fwd_dns_update_) ||
3000 (l->fqdn_rev_ != ctx.rev_dns_update_)) {
3003 .arg(query->getLabel())
3004 .arg(l->toText())
3005 .arg(ctx.hostname_)
3006 .arg(ctx.rev_dns_update_ ? "true" : "false")
3007 .arg(ctx.fwd_dns_update_ ? "true" : "false");
3008
3009 queueNCR(CHG_REMOVE, l);
3010 }
3011 }
3012
3013 // Finally, if there are any addresses requested that we haven't dealt with
3014 // already, inform the client that he can't have them.
3015 for (auto const& hint : hints) {
3017 hint.getAddress(), 0, 0));
3018 ia_rsp->addOption(iaaddr);
3019 }
3020
3021 if (!leases.empty()) {
3022 // We allocated leases so we need to update T1 and T2.
3023 setTeeTimes(min_preferred_lft, subnet, ia_rsp);
3024 } else {
3025 // The server wasn't able allocate new lease and renew an existing
3026 // lease. In that case, the server sends NoAddrsAvail per RFC 8415.
3027 ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
3029 "Sorry, no addresses could be"
3030 " assigned at this time."));
3031 }
3032
3033 return (ia_rsp);
3034}
3035
3039 boost::shared_ptr<Option6IA> ia) {
3040
3042 .arg(query->getLabel())
3043 .arg(ia->getIAID());
3044
3045 const ConstSubnet6Ptr& subnet = ctx.subnet_;
3046 const DuidPtr& duid = ctx.duid_;
3047
3048 // Let's create a IA_PD response and fill it in later
3049 Option6IAPtr ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
3050
3051 // If there is no subnet for the particular client, we can't retrieve
3052 // information about client's leases from lease database. We treat this
3053 // as no binding for the client.
3054 if (!subnet) {
3055 // Per RFC 8415, section 18.3.4, if there is no binding and we are
3056 // processing a Renew, the NoBinding status code should be returned.
3057 if (query->getType() == DHCPV6_RENEW) {
3058 // Insert status code NoBinding
3059 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3060 "Sorry, no known PD leases"
3061 " for this duid/iaid."));
3062 return (ia_rsp);
3063
3064 // Per RFC 8415, section 18.3.5, if there is no binding and we are
3065 // processing Rebind, the message has to be discarded (assuming that
3066 // the server doesn't know if the prefix in the IA_PD option is
3067 // appropriate for the client's link). The exception being thrown
3068 // here should propagate to the main loop and cause the message to
3069 // be discarded.
3070 } else {
3071
3080 isc_throw(DHCPv6DiscardMessageError, "no subnet found for the"
3081 " client sending Rebind to extend lifetime of the"
3082 " prefix (DUID=" << duid->toText() << ", IAID="
3083 << ia->getIAID() << ")");
3084 }
3085 }
3086
3087 // Set per-IA context values.
3088 ctx.createIAContext();
3089 ctx.currentIA().iaid_ = ia->getIAID();
3091 ctx.currentIA().ia_rsp_ = ia_rsp;
3092
3093 // Extract prefixes that the client is trying to renew.
3094 OptionCollection addrs = ia->getOptions();
3095 for (auto const& it : addrs) {
3096 if (it.second->getType() != D6O_IAPREFIX) {
3097 continue;
3098 }
3099 Option6IAPrefixPtr prf = boost::dynamic_pointer_cast<Option6IAPrefix>(it.second);
3100 if (!prf) {
3101 // That's weird. Option code was ok, but the object type was not.
3102 // This should never happen. The only case would be with badly
3103 // mis-implemented hook libraries that insert invalid option objects.
3104 // There's no way to protect against this.
3105 continue;
3106 }
3107
3108 // Put the client's prefix into the hints list.
3109 ctx.currentIA().addHint(prf);
3110 }
3111
3112 // Call Allocation Engine and attempt to renew leases. Number of things
3113 // may happen. Leases may be extended, revoked (if the lease is no longer
3114 // valid or reserved for someone else), or new leases may be added.
3115 // Important parameters are:
3116 // - returned container - current valid leases
3117 // - old_leases - leases that used to be, but are no longer valid
3118 // - changed_leases - leases that have FQDN changed (not really important
3119 // in PD context)
3120 Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
3121
3122 // For each IA inserted by the client we have to determine what to do
3123 // about included prefixes and notify the client. We will iterate over
3124 // those prefixes and remove those that we have already processed. We
3125 // don't want to remove them from the context, so we need to copy them
3126 // into temporary container.
3128
3129 const bool pd_exclude_requested = requestedInORO(query, D6O_PD_EXCLUDE);
3130
3131 // Retains the shortest valid lease time to use
3132 // for calculating T1 and T2.
3133 uint32_t min_preferred_lft = std::numeric_limits<uint32_t>::max();
3134
3135 for (auto const& l : leases) {
3136 if (l->reuseable_valid_lft_ == 0) {
3138 .arg(query->getLabel())
3139 .arg(l->addr_.toText())
3140 .arg(static_cast<int>(l->prefixlen_))
3141 .arg(ia->getIAID());
3142 } else {
3143 l->valid_lft_ = l->reuseable_valid_lft_;
3144 l->preferred_lft_ = l->reuseable_preferred_lft_;
3146 .arg(query->getLabel())
3147 .arg(l->addr_.toText())
3148 .arg(static_cast<int>(l->prefixlen_))
3149 .arg(ia->getIAID())
3150 .arg(Lease::lifetimeToText(l->valid_lft_));
3151
3152 // Increment the reuse statistics.
3153 StatsMgr::instance().addValue("v6-ia-pd-lease-reuses", int64_t(1));
3154 StatsMgr::instance().addValue(StatsMgr::generateName("subnet", l->subnet_id_,
3155 "v6-ia-pd-lease-reuses"),
3156 int64_t(1));
3157 }
3158
3160 l->addr_, l->prefixlen_,
3161 l->preferred_lft_, l->valid_lft_));
3162 ia_rsp->addOption(prf);
3163
3164 if (pd_exclude_requested) {
3165 OptionPtr pd_exclude_option = getPDExclude(ctx, l);
3166 if (pd_exclude_option) {
3167 prf->addOption(pd_exclude_option);
3168 }
3169 }
3170
3171 // Check for new minimum lease time
3172 if ((l->preferred_lft_ > 0) && (l->preferred_lft_ < min_preferred_lft)) {
3173 min_preferred_lft = l->preferred_lft_;
3174 }
3175
3176 // Now remove this prefix from the hints list.
3177 AllocEngine::Resource hint_type(l->addr_, l->prefixlen_);
3178 hints.erase(std::remove(hints.begin(), hints.end(), hint_type),
3179 hints.end());
3180 }
3181
3183 for (auto const& l : ctx.currentIA().old_leases_) {
3184
3185 // Send a prefix with zero lifetimes only when this lease belonged to
3186 // this client. Do not send it when we're reusing an old lease that belonged
3187 // to someone else.
3188 if (equalValues(query->getClientId(), l->duid_)) {
3189 Option6IAPrefixPtr prefix(new Option6IAPrefix(D6O_IAPREFIX, l->addr_,
3190 l->prefixlen_, 0, 0));
3191 ia_rsp->addOption(prefix);
3192 }
3193
3194 // Now remove this prefix from the hints list.
3195 AllocEngine::Resource hint_type(l->addr_, l->prefixlen_);
3196 hints.erase(std::remove(hints.begin(), hints.end(), hint_type), hints.end());
3197 }
3198
3199 // Finally, if there are any prefixes requested that we haven't dealt with
3200 // already, inform the client that he can't have them.
3201 for (auto const& prefix : hints) {
3202
3203 // Send the prefix with the zero lifetimes only if the prefix
3204 // contains non-zero value. A zero value indicates that the hint was
3205 // for the prefix length.
3206 if (!prefix.getAddress().isV6Zero()) {
3207 OptionPtr prefix_opt(new Option6IAPrefix(D6O_IAPREFIX,
3208 prefix.getAddress(),
3209 prefix.getPrefixLength(),
3210 0, 0));
3211 ia_rsp->addOption(prefix_opt);
3212 }
3213 }
3214
3215 if (!leases.empty()) {
3216 // We allocated leases so we need to update T1 and T2.
3217 setTeeTimes(min_preferred_lft, subnet, ia_rsp);
3218 } else {
3219 // All is left is to insert the status code.
3220 // The server wasn't able allocate new lease and renew an existing
3221 // lease. In that case, the server sends NoPrefixAvail per RFC 8415.
3222 ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
3224 "Sorry, no prefixes could be"
3225 " assigned at this time."));
3226 }
3227
3228 return (ia_rsp);
3229}
3230
3231void
3234
3235 // We will try to extend lease lifetime for all IA options in the client's
3236 // Renew or Rebind message.
3238
3239 // For the lease extension it is critical that the client has sent
3240 // DUID. There is no need to check for the presence of the DUID here
3241 // because we have already checked it in the sanityCheck().
3242
3243 // Save the originally selected subnet.
3244 ConstSubnet6Ptr orig_subnet = ctx.subnet_;
3245
3246 for (auto const& opt : query->options_) {
3247 switch (opt.second->getType()) {
3248 case D6O_IA_NA: {
3249 OptionPtr answer_opt = extendIA_NA(query, ctx,
3250 boost::dynamic_pointer_cast<
3251 Option6IA>(opt.second));
3252 if (answer_opt) {
3253 reply->addOption(answer_opt);
3254 }
3255 break;
3256 }
3257
3258 case D6O_IA_PD: {
3259 OptionPtr answer_opt = extendIA_PD(query, ctx,
3260 boost::dynamic_pointer_cast<
3261 Option6IA>(opt.second));
3262 if (answer_opt) {
3263 reply->addOption(answer_opt);
3264 }
3265 break;
3266 }
3267
3268 default:
3269 break;
3270 }
3271 }
3272
3273 // Need to check for pool-level DDNS parameters and if the
3274 // subnet was modified by the allocation engine, there are things
3275 // we need to do either case.
3276 checkPostAssignmentChanges(query, reply, ctx, orig_subnet);
3277}
3278
3279void
3282
3283 // We need to release addresses for all IA options in the client's
3284 // RELEASE message.
3285
3292
3293 // Let's set the status to be success by default. We can override it with
3294 // error status if needed. The important thing to understand here is that
3295 // the global status code may be set to success only if all IA options were
3296 // handled properly. Therefore the releaseIA_NA and releaseIA_PD options
3297 // may turn the status code to some error, but can't turn it back to success.
3298 int general_status = STATUS_Success;
3299 for (auto const& opt : release->options_) {
3300 Lease6Ptr old_lease;
3301 switch (opt.second->getType()) {
3302 case D6O_IA_NA: {
3303 OptionPtr answer_opt = releaseIA_NA(ctx.duid_, release, general_status,
3304 boost::dynamic_pointer_cast<Option6IA>(opt.second),
3305 old_lease);
3306 if (answer_opt) {
3307 reply->addOption(answer_opt);
3308 }
3309 break;
3310 }
3311 case D6O_IA_PD: {
3312 OptionPtr answer_opt = releaseIA_PD(ctx.duid_, release, general_status,
3313 boost::dynamic_pointer_cast<Option6IA>(opt.second),
3314 old_lease);
3315 if (answer_opt) {
3316 reply->addOption(answer_opt);
3317 }
3318 break;
3319 }
3320 // @todo: add support for IA_TA
3321 default:
3322 // remaining options are stateless and thus ignored in this context
3323 ;
3324 }
3325
3326 // Store the old lease.
3327 if (old_lease) {
3328 ctx.currentIA().old_leases_.push_back(old_lease);
3329 }
3330 }
3331
3332 // Include top-level status code as well.
3333 reply->addOption(createStatusCode(*release, general_status,
3334 "Summary status for all processed IA_NAs"));
3335}
3336
3338Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
3339 int& general_status, boost::shared_ptr<Option6IA> ia,
3340 Lease6Ptr& old_lease) {
3341
3343 .arg(query->getLabel())
3344 .arg(ia->getIAID());
3345
3346 // Release can be done in one of two ways:
3347 // Approach 1: extract address from client's IA_NA and see if it belongs
3348 // to this particular client.
3349 // Approach 2: find a subnet for this client, get a lease for
3350 // this subnet/duid/iaid and check if its content matches to what the
3351 // client is asking us to release.
3352 //
3353 // This method implements approach 1.
3354
3355 // That's our response
3356 boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
3357
3358 Option6IAAddrPtr release_addr = boost::dynamic_pointer_cast<Option6IAAddr>
3359 (ia->getOption(D6O_IAADDR));
3360 if (!release_addr) {
3361 ia_rsp->addOption(createStatusCode(*query, STATUS_NoBinding,
3362 "You did not include an address in your RELEASE"));
3363 general_status = STATUS_NoBinding;
3364 return (ia_rsp);
3365 }
3366
3368 release_addr->getAddress());
3369
3370 if (!lease) {
3371 // client releasing a lease that we don't know about.
3372
3373 // Insert status code NoBinding.
3374 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3375 "Sorry, no known leases for this duid/iaid, can't release."));
3376 general_status = STATUS_NoBinding;
3377
3378 return (ia_rsp);
3379 }
3380
3381 if (!lease->duid_) {
3382 // Something is gravely wrong here. We do have a lease, but it does not
3383 // have mandatory DUID information attached. Someone was messing with our
3384 // database.
3385
3387 .arg(query->getLabel())
3388 .arg(release_addr->getAddress().toText());
3389
3390 general_status = STATUS_UnspecFail;
3391 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_UnspecFail,
3392 "Database consistency check failed when trying to RELEASE"));
3393 return (ia_rsp);
3394 }
3395
3396 if (*duid != *(lease->duid_)) {
3397
3398 // Sorry, it's not your address. You can't release it.
3400 .arg(query->getLabel())
3401 .arg(release_addr->getAddress().toText())
3402 .arg(lease->duid_->toText());
3403
3404 general_status = STATUS_NoBinding;
3405 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3406 "This address does not belong to you, you can't release it"));
3407 return (ia_rsp);
3408 }
3409
3410 if (ia->getIAID() != lease->iaid_) {
3411 // This address belongs to this client, but to a different IA
3413 .arg(query->getLabel())
3414 .arg(release_addr->getAddress().toText())
3415 .arg(lease->iaid_)
3416 .arg(ia->getIAID());
3417 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3418 "This is your address, but you used wrong IAID"));
3419 general_status = STATUS_NoBinding;
3420 return (ia_rsp);
3421 }
3422
3423 // It is not necessary to check if the address matches as we used
3424 // getLease6(addr) method that is supposed to return a proper lease.
3425
3426 bool skip = false;
3427 // Execute all callouts registered for packet6_send
3428 if (HooksManager::calloutsPresent(Hooks.hook_index_lease6_release_)) {
3429 CalloutHandlePtr callout_handle = getCalloutHandle(query);
3430
3431 // Use the RAII wrapper to make sure that the callout handle state is
3432 // reset when this object goes out of scope. All hook points must do
3433 // it to prevent possible circular dependency between the callout
3434 // handle and its arguments.
3435 ScopedCalloutHandleState callout_handle_state(callout_handle);
3436
3437 // Enable copying options from the packet within hook library.
3438 ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
3439
3440 // Delete all previous arguments
3441 callout_handle->deleteAllArguments();
3442
3443 // Pass the original packet
3444 callout_handle->setArgument("query6", query);
3445
3446 // Pass the lease to be updated
3447 callout_handle->setArgument("lease6", lease);
3448
3449 // Call all installed callouts
3450 HooksManager::callCallouts(Hooks.hook_index_lease6_release_, *callout_handle);
3451
3452 // Callouts decided to skip the next processing step. The next
3453 // processing step would be to send the packet, so skip at this
3454 // stage means "drop response".
3455 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
3456 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
3457 skip = true;
3459 .arg(query->getLabel());
3460 }
3461 }
3462
3463 // Ok, we've passed all checks. Let's release this address.
3464 bool success = false; // was the removal operation successful?
3465 bool expired = false; // explicitly expired instead of removed?
3466 auto expiration_cfg = CfgMgr::instance().getCurrentCfg()->getCfgExpiration();
3467
3468 // Callout didn't indicate to skip the release process. Let's release
3469 // the lease.
3470 if (!skip) {
3471 // Delete lease only if affinity is disabled.
3472 if (expiration_cfg->getFlushReclaimedTimerWaitTime() &&
3473 expiration_cfg->getHoldReclaimedTime() &&
3474 lease->valid_lft_ != Lease::INFINITY_LFT) {
3475 // Expire the lease.
3476 lease->valid_lft_ = 0;
3477 lease->preferred_lft_ = 0;
3478 // Set the lease state to released to indicate that this lease
3479 // must be preserved in the database. It is particularly useful
3480 // in HA to differentiate between the leases that should be
3481 // updated in the partner's database and deleted from the partner's
3482 // database.
3483 lease->state_ = Lease6::STATE_RELEASED;
3485 expired = true;
3486 success = true;
3487 } else {
3488 success = LeaseMgrFactory::instance().deleteLease(lease);
3489 }
3490 }
3491
3492 // Here the success should be true if we removed lease successfully
3493 // and false if skip flag was set or the removal failed for whatever reason
3494
3495 if (!success) {
3496 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_UnspecFail,
3497 "Server failed to release a lease"));
3498
3500 .arg(query->getLabel())
3501 .arg(lease->addr_.toText())
3502 .arg(lease->iaid_);
3503 general_status = STATUS_UnspecFail;
3504
3505 return (ia_rsp);
3506 } else {
3507 old_lease = lease;
3508
3510 .arg(query->getLabel())
3511 .arg(lease->addr_.toText())
3512 .arg(lease->iaid_);
3513
3514 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_Success,
3515 "Lease released. Thank you, please come again."));
3516
3517 if (expired) {
3519 .arg(query->getLabel())
3520 .arg(lease->addr_.toText())
3521 .arg(lease->iaid_);
3522 } else {
3524 .arg(query->getLabel())
3525 .arg(lease->addr_.toText())
3526 .arg(lease->iaid_);
3527
3528 // Check if a lease has flags indicating that the FQDN update has
3529 // been performed. If so, create NameChangeRequest which removes
3530 // the entries.
3531 queueNCR(CHG_REMOVE, lease);
3532 }
3533
3534 // Need to decrease statistic for assigned addresses.
3536 StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-nas"),
3537 static_cast<int64_t>(-1));
3538
3539 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
3540 if (subnet) {
3541 auto const& pool = subnet->getPool(Lease::TYPE_NA, lease->addr_, false);
3542 if (pool) {
3544 StatsMgr::generateName("subnet", subnet->getID(),
3545 StatsMgr::generateName("pool", pool->getID(), "assigned-nas")),
3546 static_cast<int64_t>(-1));
3547 }
3548 }
3549
3550 return (ia_rsp);
3551 }
3552}
3553
3555Dhcpv6Srv::releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
3556 int& general_status, boost::shared_ptr<Option6IA> ia,
3557 Lease6Ptr& old_lease) {
3558 // Release can be done in one of two ways:
3559 // Approach 1: extract address from client's IA_NA and see if it belongs
3560 // to this particular client.
3561 // Approach 2: find a subnet for this client, get a lease for
3562 // this subnet/duid/iaid and check if its content matches to what the
3563 // client is asking us to release.
3564 //
3565 // This method implements approach 1.
3566
3567 // That's our response. We will fill it in as we check the lease to be
3568 // released.
3569 boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
3570
3571 boost::shared_ptr<Option6IAPrefix> release_prefix =
3572 boost::dynamic_pointer_cast<Option6IAPrefix>(ia->getOption(D6O_IAPREFIX));
3573 if (!release_prefix) {
3574 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3575 "You did not include a prefix in your RELEASE"));
3576 general_status = STATUS_NoBinding;
3577 return (ia_rsp);
3578 }
3579
3581 release_prefix->getAddress());
3582
3583 if (!lease) {
3584 // Client releasing a lease that we don't know about.
3585
3586 // Insert status code NoBinding.
3587 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3588 "Sorry, no known leases for this duid/iaid, can't release."));
3589 general_status = STATUS_NoBinding;
3590
3591 return (ia_rsp);
3592 }
3593
3594 if (!lease->duid_) {
3595 // Something is gravely wrong here. We do have a lease, but it does not
3596 // have mandatory DUID information attached. Someone was messing with our
3597 // database.
3599 .arg(query->getLabel())
3600 .arg(release_prefix->getAddress().toText())
3601 .arg(static_cast<int>(release_prefix->getLength()));
3602
3603 general_status = STATUS_UnspecFail;
3604 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_UnspecFail,
3605 "Database consistency check failed when trying to RELEASE"));
3606 return (ia_rsp);
3607 }
3608
3609 if (*duid != *(lease->duid_)) {
3610 // Sorry, it's not your address. You can't release it.
3612 .arg(query->getLabel())
3613 .arg(release_prefix->getAddress().toText())
3614 .arg(static_cast<int>(release_prefix->getLength()))
3615 .arg(lease->duid_->toText());
3616
3617 general_status = STATUS_NoBinding;
3618 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3619 "This address does not belong to you, you can't release it"));
3620 return (ia_rsp);
3621 }
3622
3623 if (ia->getIAID() != lease->iaid_) {
3624 // This address belongs to this client, but to a different IA
3626 .arg(query->getLabel())
3627 .arg(release_prefix->getAddress().toText())
3628 .arg(static_cast<int>(release_prefix->getLength()))
3629 .arg(lease->iaid_)
3630 .arg(ia->getIAID());
3631 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
3632 "This is your address, but you used wrong IAID"));
3633 general_status = STATUS_NoBinding;
3634 return (ia_rsp);
3635 }
3636
3637 // It is not necessary to check if the address matches as we used
3638 // getLease6(addr) method that is supposed to return a proper lease.
3639
3640 bool skip = false;
3641 // Execute all callouts registered for packet6_send
3642 if (HooksManager::calloutsPresent(Hooks.hook_index_lease6_release_)) {
3643 CalloutHandlePtr callout_handle = getCalloutHandle(query);
3644
3645 // Use the RAII wrapper to make sure that the callout handle state is
3646 // reset when this object goes out of scope. All hook points must do
3647 // it to prevent possible circular dependency between the callout
3648 // handle and its arguments.
3649 ScopedCalloutHandleState callout_handle_state(callout_handle);
3650
3651 // Enable copying options from the packet within hook library.
3652 ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
3653
3654 // Pass the original packet
3655 callout_handle->setArgument("query6", query);
3656
3657 // Pass the lease to be updated
3658 callout_handle->setArgument("lease6", lease);
3659
3660 // Call all installed callouts
3661 HooksManager::callCallouts(Hooks.hook_index_lease6_release_, *callout_handle);
3662
3663 // Callouts decided to skip the next processing step. The next
3664 // processing step would be to send the packet, so skip at this
3665 // stage means "drop response".
3666 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
3667 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
3668 skip = true;
3670 .arg(query->getLabel());
3671 }
3672 }
3673
3674 // Ok, we've passed all checks. Let's release this prefix.
3675 bool success = false; // was the removal operation successful?
3676 bool expired = false; // explicitly expired instead of removed?
3677 auto expiration_cfg = CfgMgr::instance().getCurrentCfg()->getCfgExpiration();
3678
3679 // Callout didn't indicate to skip the release process. Let's release
3680 // the lease.
3681 if (!skip) {
3682 // Delete lease only if affinity is disabled.
3683 if (expiration_cfg->getFlushReclaimedTimerWaitTime() &&
3684 expiration_cfg->getHoldReclaimedTime() &&
3685 lease->valid_lft_ != Lease::INFINITY_LFT) {
3686 // Expire the lease.
3687 lease->valid_lft_ = 0;
3688 lease->preferred_lft_ = 0;
3689 // Set the lease state to released to indicate that this lease
3690 // must be preserved in the database. It is particularly useful
3691 // in HA to differentiate between the leases that should be
3692 // updated in the partner's database and deleted from the partner's
3693 // database.
3694 lease->state_ = Lease6::STATE_RELEASED;
3696 expired = true;
3697 success = true;
3698 } else {
3699 success = LeaseMgrFactory::instance().deleteLease(lease);
3700 }
3701 }
3702
3703 // Here the success should be true if we removed lease successfully
3704 // and false if skip flag was set or the removal failed for whatever reason
3705
3706 if (!success) {
3707 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_UnspecFail,
3708 "Server failed to release a lease"));
3709
3711 .arg(query->getLabel())
3712 .arg(lease->addr_.toText())
3713 .arg(static_cast<int>(lease->prefixlen_))
3714 .arg(lease->iaid_);
3715 general_status = STATUS_UnspecFail;
3716
3717 } else {
3718 old_lease = lease;
3719
3721 .arg(query->getLabel())
3722 .arg(lease->addr_.toText())
3723 .arg(static_cast<int>(lease->prefixlen_))
3724 .arg(lease->iaid_);
3725
3726 ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_Success,
3727 "Lease released. Thank you, please come again."));
3728
3729 if (expired) {
3731 .arg(query->getLabel())
3732 .arg(lease->addr_.toText())
3733 .arg(static_cast<int>(lease->prefixlen_))
3734 .arg(lease->iaid_);
3735 } else {
3737 .arg(query->getLabel())
3738 .arg(lease->addr_.toText())
3739 .arg(static_cast<int>(lease->prefixlen_))
3740 .arg(lease->iaid_);
3741 }
3742
3743 // Need to decrease statistic for assigned prefixes.
3745 StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-pds"),
3746 static_cast<int64_t>(-1));
3747
3748 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
3749 if (subnet) {
3750 auto const& pool = subnet->getPool(Lease::TYPE_PD, lease->addr_, false);
3751 if (pool) {
3753 StatsMgr::generateName("subnet", subnet->getID(),
3754 StatsMgr::generateName("pd-pool", pool->getID(), "assigned-pds")),
3755 static_cast<int64_t>(-1));
3756 }
3757 }
3758 }
3759
3760 return (ia_rsp);
3761}
3762
3763Pkt6Ptr
3765
3766 Pkt6Ptr solicit = ctx.query_;
3767 Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
3768
3769 // Handle Rapid Commit option, if present.
3770 if (ctx.subnet_ && ctx.subnet_->getRapidCommit()) {
3771 OptionPtr opt_rapid_commit = solicit->getOption(D6O_RAPID_COMMIT);
3772 if (opt_rapid_commit) {
3773
3775 .arg(solicit->getLabel());
3776
3777 // If Rapid Commit has been sent by the client, change the
3778 // response type to Reply and include Rapid Commit option.
3779 response->setType(DHCPV6_REPLY);
3780 response->addOption(opt_rapid_commit);
3781 }
3782 }
3783
3784 // "Fake" allocation is the case when the server is processing the Solicit
3785 // message without the Rapid Commit option and advertises a lease to
3786 // the client, but doesn't commit this lease to the lease database. If
3787 // the Solicit contains the Rapid Commit option and the server is
3788 // configured to honor the Rapid Commit option, or the client has sent
3789 // the Request message, the lease will be committed to the lease
3790 // database. The type of the server's response may be used to determine
3791 // if this is the fake allocation case or not. When the server sends
3792 // Reply message it means that it is committing leases. Other message
3793 // type (Advertise) means that server is not committing leases (fake
3794 // allocation).
3795 ctx.fake_allocation_ = (response->getType() != DHCPV6_REPLY);
3796
3797 processClientFqdn(solicit, response, ctx);
3798
3799 if (MultiThreadingMgr::instance().getMode()) {
3800 // The lease reclamation cannot run at the same time.
3801 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3802
3803 assignLeases(solicit, response, ctx);
3804 } else {
3805 assignLeases(solicit, response, ctx);
3806 }
3807
3809 // Evaluate additional classes.
3810 evaluateAdditionalClasses(solicit, ctx);
3811
3813 .arg(solicit->getLabel())
3814 .arg(solicit->getName())
3815 .arg(solicit->getClasses().toText());
3816
3817 copyClientOptions(solicit, response);
3818 CfgOptionList co_list;
3819 buildCfgOptionList(solicit, ctx, co_list);
3820 appendDefaultOptions(solicit, response, co_list);
3821 appendRequestedOptions(solicit, response, co_list);
3822 appendRequestedVendorOptions(solicit, response, ctx, co_list);
3823
3824 updateReservedFqdn(ctx, response);
3825
3826 // Only generate name change requests if sending a Reply as a result
3827 // of receiving Rapid Commit option.
3828 if (response->getType() == DHCPV6_REPLY) {
3829 createNameChangeRequests(response, ctx);
3830 }
3831
3832 return (response);
3833}
3834
3835Pkt6Ptr
3837
3838 Pkt6Ptr request = ctx.query_;
3839 Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
3840
3841 processClientFqdn(request, reply, ctx);
3842
3843 if (MultiThreadingMgr::instance().getMode()) {
3844 // The lease reclamation cannot run at the same time.
3845 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3846
3847 assignLeases(request, reply, ctx);
3848 } else {
3849 assignLeases(request, reply, ctx);
3850 }
3851
3853 // Evaluate additional classes.
3854 evaluateAdditionalClasses(request, ctx);
3855
3857 .arg(request->getLabel())
3858 .arg(request->getName())
3859 .arg(request->getClasses().toText());
3860
3861 copyClientOptions(request, reply);
3862 CfgOptionList co_list;
3863 buildCfgOptionList(request, ctx, co_list);
3864 appendDefaultOptions(request, reply, co_list);
3865 appendRequestedOptions(request, reply, co_list);
3866 appendRequestedVendorOptions(request, reply, ctx, co_list);
3867
3868 updateReservedFqdn(ctx, reply);
3869 generateFqdn(reply, ctx);
3870 createNameChangeRequests(reply, ctx);
3871
3872 return (reply);
3873}
3874
3875Pkt6Ptr
3877
3878 Pkt6Ptr renew = ctx.query_;
3879 Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
3880
3881 processClientFqdn(renew, reply, ctx);
3882
3883 if (MultiThreadingMgr::instance().getMode()) {
3884 // The lease reclamation cannot run at the same time.
3885 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3886
3887 extendLeases(renew, reply, ctx);
3888 } else {
3889 extendLeases(renew, reply, ctx);
3890 }
3891
3893 // Evaluate additional classes.
3894 evaluateAdditionalClasses(renew, ctx);
3895
3897 .arg(renew->getLabel())
3898 .arg(renew->getName())
3899 .arg(renew->getClasses().toText());
3900
3901 copyClientOptions(renew, reply);
3902 CfgOptionList co_list;
3903 buildCfgOptionList(renew, ctx, co_list);
3904 appendDefaultOptions(renew, reply, co_list);
3905 appendRequestedOptions(renew, reply, co_list);
3906 appendRequestedVendorOptions(renew, reply, ctx, co_list);
3907
3908 updateReservedFqdn(ctx, reply);
3909 generateFqdn(reply, ctx);
3910 createNameChangeRequests(reply, ctx);
3911
3912 return (reply);
3913}
3914
3915Pkt6Ptr
3917
3918 Pkt6Ptr rebind = ctx.query_;
3919 Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
3920
3921 processClientFqdn(rebind, reply, ctx);
3922
3923 if (MultiThreadingMgr::instance().getMode()) {
3924 // The lease reclamation cannot run at the same time.
3925 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3926
3927 extendLeases(rebind, reply, ctx);
3928 } else {
3929 extendLeases(rebind, reply, ctx);
3930 }
3931
3933 // Evaluate additional classes.
3934 evaluateAdditionalClasses(rebind, ctx);
3935
3937 .arg(rebind->getLabel())
3938 .arg(rebind->getName())
3939 .arg(rebind->getClasses().toText());
3940
3941 copyClientOptions(rebind, reply);
3942 CfgOptionList co_list;
3943 buildCfgOptionList(rebind, ctx, co_list);
3944 appendDefaultOptions(rebind, reply, co_list);
3945 appendRequestedOptions(rebind, reply, co_list);
3946 appendRequestedVendorOptions(rebind, reply, ctx, co_list);
3947
3948 updateReservedFqdn(ctx, reply);
3949 generateFqdn(reply, ctx);
3950 createNameChangeRequests(reply, ctx);
3951
3952 return (reply);
3953}
3954
3955Pkt6Ptr
3957
3958 Pkt6Ptr confirm = ctx.query_;
3960 // Evaluate additional classes.
3961 evaluateAdditionalClasses(confirm, ctx);
3962
3964 .arg(confirm->getLabel())
3965 .arg(confirm->getName())
3966 .arg(confirm->getClasses().toText());
3967
3968 // Get IA_NAs from the Confirm. If there are none, the message is
3969 // invalid and must be discarded. There is nothing more to do.
3970 OptionCollection ias = confirm->getOptions(D6O_IA_NA);
3971 if (ias.empty()) {
3972 return (Pkt6Ptr());
3973 }
3974
3975 // The server sends Reply message in response to Confirm.
3976 Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, confirm->getTransid()));
3977 // Make sure that the necessary options are included.
3978 copyClientOptions(confirm, reply);
3979 CfgOptionList co_list;
3980 buildCfgOptionList(confirm, ctx, co_list);
3981 appendDefaultOptions(confirm, reply, co_list);
3982 appendRequestedOptions(confirm, reply, co_list);
3983 appendRequestedVendorOptions(confirm, reply, ctx, co_list);
3984 // Indicates if at least one address has been verified. If no addresses
3985 // are verified it means that the client has sent no IA_NA options
3986 // or no IAAddr options and that client's message has to be discarded.
3987 bool verified = false;
3988 // Check if subnet was selected for the message. If no subnet
3989 // has been selected, the client is not on link.
3990 ConstSubnetPtr subnet = ctx.subnet_;
3991
3992 // Regardless if the subnet has been selected or not, we will iterate
3993 // over the IA_NA options to check if they hold any addresses. If there
3994 // are no, the Confirm is discarded.
3995 // Check addresses in IA_NA options and make sure they are appropriate.
3996 for (auto const& ia : ias) {
3997 const OptionCollection& opts = ia.second->getOptions();
3998 for (auto const& opt : opts) {
3999 // Ignore options other than IAAddr.
4000 if (opt.second->getType() == D6O_IAADDR) {
4001 // Check that the address is in range in the subnet selected.
4002 Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
4003 Option6IAAddr>(opt.second);
4004 // If there is subnet selected and the address has been included
4005 // in IA_NA, mark it verified and verify that it belongs to the
4006 // subnet.
4007 if (iaaddr) {
4008 // If at least one address is not in range, then return
4009 // the NotOnLink status code.
4010 if (subnet && !subnet->inRange(iaaddr->getAddress())) {
4011 std::ostringstream status_msg;
4012 status_msg << "Address " << iaaddr->getAddress()
4013 << " is not on link.";
4014 reply->addOption(createStatusCode(*confirm,
4016 status_msg.str()));
4017 return (reply);
4018 }
4019 verified = true;
4020 } else {
4021 isc_throw(Unexpected, "failed to cast the IA Address option"
4022 " to the Option6IAAddrPtr. This is programming"
4023 " error and should be reported");
4024 }
4025 }
4026 }
4027 }
4028
4029 // It seems that the client hasn't included any addresses in which case
4030 // the Confirm must be discarded.
4031 if (!verified) {
4032 return (Pkt6Ptr());
4033 }
4034
4035 // If there is a subnet, there were addresses in IA_NA options and the
4036 // addresses where consistent with the subnet then the client is on link.
4037 if (subnet) {
4038 // All addresses in range, so return success.
4039 reply->addOption(createStatusCode(*confirm, STATUS_Success,
4040 "All addresses are on-link"));
4041 } else {
4042 reply->addOption(createStatusCode(*confirm, STATUS_NotOnLink,
4043 "No subnet selected"));
4044 }
4045
4046 return (reply);
4047}
4048
4049Pkt6Ptr
4051
4052 Pkt6Ptr release = ctx.query_;
4054 // Evaluate additional classes.
4055 evaluateAdditionalClasses(release, ctx);
4056
4058 .arg(release->getLabel())
4059 .arg(release->getName())
4060 .arg(release->getClasses().toText());
4061
4062 // Create an empty Reply message.
4063 Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
4064
4065 // Copy client options (client-id, also relay information if present)
4066 copyClientOptions(release, reply);
4067
4068 // Get the configured option list
4069 CfgOptionList co_list;
4070 // buildCfgOptionList(release, ctx, co_list);
4071 appendDefaultOptions(release, reply, co_list);
4072
4073 releaseLeases(release, reply, ctx);
4074
4077
4078 return (reply);
4079}
4080
4081Pkt6Ptr
4083
4084 Pkt6Ptr decline = ctx.query_;
4086 // Evaluate additional classes.
4087 evaluateAdditionalClasses(decline, ctx);
4088
4090 .arg(decline->getLabel())
4091 .arg(decline->getName())
4092 .arg(decline->getClasses().toText());
4093
4094 // Create an empty Reply message.
4095 Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, decline->getTransid()));
4096
4097 // Copy client options (client-id, also relay information if present)
4098 copyClientOptions(decline, reply);
4099
4100 // Get the configured option list
4101 CfgOptionList co_list;
4102 buildCfgOptionList(decline, ctx, co_list);
4103
4104 // Include server-id
4105 appendDefaultOptions(decline, reply, co_list);
4106
4107 if (declineLeases(decline, reply, ctx)) {
4108 return (reply);
4109 } else {
4110
4111 // declineLeases returns false only if the hooks set the next step
4112 // status to DROP. We'll just doing as requested.
4113 return (Pkt6Ptr());
4114 }
4115}
4116
4117bool
4120
4121 // We need to decline addresses for all IA_NA options in the client's
4122 // DECLINE message.
4123
4124 // Let's set the status to be success by default. We can override it with
4125 // error status if needed. The important thing to understand here is that
4126 // the global status code may be set to success only if all IA options were
4127 // handled properly. Therefore the declineIA options
4128 // may turn the status code to some error, but can't turn it back to success.
4129 int general_status = STATUS_Success;
4130
4131 for (auto const& opt : decline->options_) {
4132 switch (opt.second->getType()) {
4133 case D6O_IA_NA: {
4134 OptionPtr answer_opt = declineIA(decline, ctx.duid_, general_status,
4135 boost::dynamic_pointer_cast<Option6IA>(opt.second),
4136 ctx.new_leases_);
4137 if (answer_opt) {
4138
4139 // We have an answer, let's use it.
4140 reply->addOption(answer_opt);
4141 } else {
4142
4143 // The only case when declineIA could return NULL is if one of the
4144 // hook callouts set next step status to DROP. We just need to drop
4145 // this packet.
4146 return (false);
4147 }
4148 break;
4149 }
4150 default:
4151 // We don't care for the remaining options
4152 ;
4153 }
4154 }
4155
4156 return (true);
4157}
4158
4160Dhcpv6Srv::declineIA(const Pkt6Ptr& decline, const DuidPtr& duid,
4161 int& general_status, boost::shared_ptr<Option6IA> ia,
4162 Lease6Collection& new_leases) {
4163
4165 .arg(decline->getLabel())
4166 .arg(ia->getIAID());
4167
4168 // Decline can be done in one of two ways:
4169 // Approach 1: extract address from client's IA_NA and see if it belongs
4170 // to this particular client.
4171 // Approach 2: find a subnet for this client, get a lease for
4172 // this subnet/duid/iaid and check if its content matches to what the
4173 // client is asking us to decline.
4174 //
4175 // This method implements approach 1.
4176
4177 // That's our response
4178 boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
4179
4180 const OptionCollection& opts = ia->getOptions();
4181 int total_addrs = 0; // Let's count the total number of addresses.
4182 for (auto const& opt : opts) {
4183
4184 // Let's ignore nested options other than IAADDR (there shouldn't be anything
4185 // else in IA_NA in Decline message, but let's be on the safe side).
4186 if (opt.second->getType() != D6O_IAADDR) {
4187 continue;
4188 }
4189 Option6IAAddrPtr decline_addr = boost::dynamic_pointer_cast<Option6IAAddr>
4190 (opt.second);
4191 if (!decline_addr) {
4192 continue;
4193 }
4194
4195 total_addrs++;
4196
4198 decline_addr->getAddress());
4199
4200 if (!lease || lease->expired() || lease->state_ != Lease::STATE_DEFAULT) {
4201 // Client trying to decline a lease that we don't know about.
4203 .arg(decline->getLabel()).arg(decline_addr->getAddress().toText());
4204
4205 // According to RFC 8415, section 18.3.8:
4206 // "For each IA in the Decline message for which the server has no
4207 // binding information, the server adds an IA option using the IAID
4208 // from the Decline message and includes a Status Code option with
4209 // the value NoBinding in the IA option".
4210 setStatusCode(ia_rsp, createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
4211 "Server does not know about such an address."));
4212
4213 // In the same section of RFC 8415:
4214 // "The server ignores addresses not assigned to the IAs (though it may"
4215 // choose to log an error if it finds such addresses)."
4216 continue; // There may be other addresses.
4217 }
4218
4219 if (!lease->duid_) {
4220 // Something is gravely wrong here. We do have a lease, but it does not
4221 // have mandatory DUID information attached. Someone was messing with our
4222 // database.
4223
4225 .arg(decline->getLabel())
4226 .arg(decline_addr->getAddress().toText());
4227
4228 ia_rsp->addOption(createStatusCode(*decline, *ia_rsp, STATUS_UnspecFail,
4229 "Database consistency check failed when attempting Decline."));
4230
4231 continue;
4232 }
4233
4234 // Ok, there's a sane lease with an address. Let's check if DUID matches first.
4235 if (*duid != *(lease->duid_)) {
4236
4237 // Sorry, it's not your address. You can't release it.
4239 .arg(decline->getLabel())
4240 .arg(decline_addr->getAddress().toText())
4241 .arg(lease->duid_->toText());
4242
4243 ia_rsp->addOption(createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
4244 "This address does not belong to you, you can't decline it"));
4245
4246 continue;
4247 }
4248
4249 // Let's check if IAID matches.
4250 if (ia->getIAID() != lease->iaid_) {
4251 // This address belongs to this client, but to a different IA
4253 .arg(decline->getLabel())
4254 .arg(lease->addr_.toText())
4255 .arg(ia->getIAID())
4256 .arg(lease->iaid_);
4257 setStatusCode(ia_rsp, createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
4258 "This is your address, but you used wrong IAID"));
4259
4260 continue;
4261 }
4262
4263 // Ok, all is good. Decline this lease.
4264 if (!declineLease(decline, lease, ia_rsp)) {
4265 // declineLease returns false only when hook callouts set the next
4266 // step status to drop. We just propagate the bad news here.
4267 return (OptionPtr());
4268
4269 } else {
4270 new_leases.push_back(lease);
4271 }
4272 }
4273
4274 if (total_addrs == 0) {
4275 setStatusCode(ia_rsp, createStatusCode(*decline, *ia_rsp, STATUS_NoBinding,
4276 "No addresses sent in IA_NA"));
4277 general_status = STATUS_NoBinding;
4278 }
4279
4280 return (ia_rsp);
4281}
4282
4283void
4284Dhcpv6Srv::setStatusCode(boost::shared_ptr<isc::dhcp::Option6IA>& container,
4285 const OptionPtr& status) {
4286 // Let's delete any old status code we may have.
4287 container->delOption(D6O_STATUS_CODE);
4288
4289 container->addOption(status);
4290}
4291
4292bool
4293Dhcpv6Srv::declineLease(const Pkt6Ptr& decline, const Lease6Ptr lease,
4294 boost::shared_ptr<Option6IA> ia_rsp) {
4295 // We do not want to decrease the assigned-nas at this time. While
4296 // technically a declined address is no longer allocated, the
4297 // primary usage of the assigned-nas statistic is to monitor pool
4298 // utilization. Most people would forget to include declined-addresses
4299 // in the calculation, and simply do assigned-nas/total-nas. This
4300 // would have a bias towards under-representing pool utilization,
4301 // if we decreased allocated immediately after receiving DHCPDECLINE,
4302 // rather than later when we recover the address.
4303
4304 // Let's call lease6_decline hooks if necessary.
4305 if (HooksManager::calloutsPresent(Hooks.hook_index_lease6_decline_)) {
4306 CalloutHandlePtr callout_handle = getCalloutHandle(decline);
4307
4308 // Use the RAII wrapper to make sure that the callout handle state is
4309 // reset when this object goes out of scope. All hook points must do
4310 // it to prevent possible circular dependency between the callout
4311 // handle and its arguments.
4312 ScopedCalloutHandleState callout_handle_state(callout_handle);
4313
4314 // Enable copying options from the packet within hook library.
4315 ScopedEnableOptionsCopy<Pkt6> query6_options_copy(decline);
4316
4317 // Pass the original packet
4318 callout_handle->setArgument("query6", decline);
4319
4320 // Pass the lease to be updated
4321 callout_handle->setArgument("lease6", lease);
4322
4323 // Call callouts
4324 HooksManager::callCallouts(Hooks.hook_index_lease6_decline_,
4325 *callout_handle);
4326
4327 // Callouts decided to SKIP the next processing step. The next
4328 // processing step would be to actually decline the lease, so we'll
4329 // keep the lease as is.
4330 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
4332 .arg(decline->getLabel())
4333 .arg(decline->getIface())
4334 .arg(lease->addr_.toText());
4335 return (true);
4336 }
4337
4338 // Callouts decided to DROP the packet. Let's simply log it and
4339 // return false, so callers will act accordingly.
4340 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
4342 .arg(decline->getLabel())
4343 .arg(decline->getIface())
4344 .arg(lease->addr_.toText());
4345 return (false);
4346 }
4347 }
4348
4349 Lease6Ptr old_values = boost::make_shared<Lease6>(*lease);
4350
4351 // @todo: Call hooks.
4352
4353 // We need to disassociate the lease from the client. Once we move a lease
4354 // to declined state, it is no longer associated with the client in any
4355 // way.
4356 lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
4357
4358 try {
4360 } catch (const Exception& ex) {
4361 // Update failed.
4363 .arg(decline->getLabel())
4364 .arg(lease->addr_.toText())
4365 .arg(ex.what());
4366 return (false);
4367 }
4368
4369 // Check if a lease has flags indicating that the FQDN update has
4370 // been performed. If so, create NameChangeRequest which removes
4371 // the entries. This method does all necessary checks.
4372 queueNCR(CHG_REMOVE, old_values);
4373
4374 // Bump up the subnet-specific statistic.
4376 StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
4377 static_cast<int64_t>(1));
4378
4379 auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
4380 if (subnet) {
4381 auto const& pool = subnet->getPool(Lease::TYPE_NA, lease->addr_, false);
4382 if (pool) {
4384 StatsMgr::generateName("subnet", subnet->getID(),
4385 StatsMgr::generateName("pool", pool->getID(), "declined-addresses")),
4386 static_cast<int64_t>(1));
4387 }
4388 }
4389
4390 // Global declined addresses counter.
4391 StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
4392
4393 LOG_INFO(lease6_logger, DHCP6_DECLINE_LEASE).arg(decline->getLabel())
4394 .arg(lease->addr_.toText()).arg(lease->valid_lft_);
4395
4396 ia_rsp->addOption(createStatusCode(*decline, *ia_rsp, STATUS_Success,
4397 "Lease declined. Hopefully the next one will be better."));
4398
4399 return (true);
4400}
4401
4402Pkt6Ptr
4404
4405 Pkt6Ptr inf_request = ctx.query_;
4406 conditionallySetReservedClientClasses(inf_request, ctx);
4407 // Evaluate additional classes.
4408 evaluateAdditionalClasses(inf_request, ctx);
4409
4411 .arg(inf_request->getLabel())
4412 .arg(inf_request->getName())
4413 .arg(inf_request->getClasses().toText());
4414
4415 // Create a Reply packet, with the same trans-id as the client's.
4416 Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid()));
4417
4418 // Copy client options (client-id, also relay information if present)
4419 copyClientOptions(inf_request, reply);
4420
4421 // Build the configured option list for append methods
4422 CfgOptionList co_list;
4423 buildCfgOptionList(inf_request, ctx, co_list);
4424
4425 // Append default options, i.e. options that the server is supposed
4426 // to put in all messages it sends (server-id for now, but possibly other
4427 // options once we start supporting authentication)
4428 appendDefaultOptions(inf_request, reply, co_list);
4429
4430 // Try to assign options that were requested by the client.
4431 appendRequestedOptions(inf_request, reply, co_list);
4432
4433 // Try to assign vendor options that were requested by the client.
4434 appendRequestedVendorOptions(inf_request, reply, ctx, co_list);
4435
4436 return (reply);
4437}
4438
4439void
4441
4442 // flags are in transid
4443 // uint32_t flags = dhcp4_query->getTransid();
4444 // do nothing with DHCPV4_QUERY_FLAGS_UNICAST
4445
4446 // Get the DHCPv4 message option
4447 OptionPtr dhcp4_msg = dhcp4_query->getOption(D6O_DHCPV4_MSG);
4448 if (dhcp4_msg) {
4449 try {
4450 // Forward the whole message to the DHCPv4 server via IPC
4451 Dhcp6to4Ipc::instance().send(dhcp4_query);
4452 } catch (...) {
4453 // Assume the error was already logged
4454 return;
4455 }
4456 }
4457
4458 // This method does not return anything as we always sent back
4459 // the response via Dhcp6To4Ipc.
4460}
4461
4462void Dhcpv6Srv::classifyByVendor(const Pkt6Ptr& pkt) {
4463 OptionVendorClassPtr vclass;
4464 for (auto const& opt : pkt->getOptions(D6O_VENDOR_CLASS)) {
4465 vclass = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
4466 if (!vclass || vclass->getTuplesNum() == 0) {
4467 continue;
4468 }
4469
4470 if (vclass->hasTuple(DOCSIS3_CLASS_MODEM)) {
4472
4473 } else if (vclass->hasTuple(DOCSIS3_CLASS_EROUTER)) {
4475
4476 } else {
4477 pkt->addClass(VENDOR_CLASS_PREFIX + vclass->getTuple(0).getText());
4478 }
4479 }
4480}
4481
4483 // All packets belong to ALL.
4484 pkt->addClass("ALL");
4485
4486 // First: built-in vendor class processing
4487 classifyByVendor(pkt);
4488
4489 // Run match expressions on classes not depending on KNOWN/UNKNOWN.
4490 evaluateClasses(pkt, false);
4491}
4492
4493void Dhcpv6Srv::evaluateClasses(const Pkt6Ptr& pkt, bool depend_on_known) {
4494 // Note getClientClassDictionary() cannot be null
4495 const ClientClassDictionaryPtr& dict =
4496 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
4497 const ClientClassDefListPtr& defs_ptr = dict->getClasses();
4498 for (auto const& it : *defs_ptr) {
4499 // Note second cannot be null
4500 const ExpressionPtr& expr_ptr = it->getMatchExpr();
4501 // Nothing to do without an expression to evaluate
4502 if (!expr_ptr) {
4503 continue;
4504 }
4505 // Not the right time if only when required
4506 if (it->getAdditional()) {
4507 continue;
4508 }
4509 // Not the right pass.
4510 if (it->getDependOnKnown() != depend_on_known) {
4511 continue;
4512 }
4513 it->test(pkt, expr_ptr);
4514 }
4515}
4516
4517void
4519 const ClientClassDictionaryPtr& dict =
4520 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
4521 const ClientClassDefListPtr& defs_ptr = dict->getClasses();
4522 for (auto const& def : *defs_ptr) {
4523 // Only remove evaluated classes. Other classes can be
4524 // assigned via hooks libraries and we should not remove
4525 // them because there is no way they can be added back.
4526 if (def->getMatchExpr()) {
4527 pkt->classes_.erase(def->getName());
4528 }
4529 }
4530}
4531
4532void
4534 const AllocEngine::ClientContext6& ctx) {
4535 if (ctx.currentHost() && pkt) {
4536 const ClientClasses& classes = ctx.currentHost()->getClientClasses6();
4537 for (auto const& cclass : classes) {
4538 pkt->addClass(cclass);
4539 }
4540 }
4541}
4542
4543void
4545 const AllocEngine::ClientContext6& ctx) {
4546 if (ctx.subnet_) {
4547 SharedNetwork6Ptr shared_network;
4548 ctx.subnet_->getSharedNetwork(shared_network);
4549 if (shared_network) {
4550 ConstHostPtr host = ctx.currentHost();
4551 if (host && (host->getIPv6SubnetID() != SUBNET_ID_GLOBAL)) {
4552 setReservedClientClasses(pkt, ctx);
4553 }
4554 }
4555 }
4556}
4557
4558void
4560 // Get additional classes to evaluate added elsewhere, posssibly by hooks.
4561 ClientClasses classes = pkt->getAdditionalClasses();
4562 ConstSubnet6Ptr subnet = ctx.subnet_;
4563
4564 if (subnet) {
4565 // host reservation???
4566
4567 // Begin by pools
4568 for (auto const& resource : ctx.allocated_resources_) {
4569 PoolPtr pool =
4570 ctx.subnet_->getPool(resource.getPrefixLength() == 128 ?
4572 resource.getAddress(),
4573 false);
4574 if (pool) {
4575 const ClientClasses& pool_to_add = pool->getAdditionalClasses();
4576 for (auto const& cclass : pool_to_add) {
4577 classes.insert(cclass);
4578 }
4579 }
4580 }
4581
4582 // Followed by the subnet
4583 const ClientClasses& to_add = subnet->getAdditionalClasses();
4584 for (auto const& cclass : to_add) {
4585 classes.insert(cclass);
4586 }
4587
4588 // And finish by the shared-network
4589 SharedNetwork6Ptr network;
4590 subnet->getSharedNetwork(network);
4591 if (network) {
4592 const ClientClasses& net_to_add = network->getAdditionalClasses();
4593 for (auto const& cclass : net_to_add) {
4594 classes.insert(cclass);
4595 }
4596 }
4597 }
4598
4599 // Run match expressions
4600 // Note getClientClassDictionary() cannot be null
4601 const ClientClassDictionaryPtr& dict =
4602 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
4603 for (auto const& cclass : classes) {
4604 const ClientClassDefPtr class_def = dict->findClass(cclass);
4605 if (!class_def) {
4608 .arg(cclass);
4609 // Ignore it as it can't have an attached action
4610 continue;
4611 }
4612 const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
4613 // Add a class without an expression to evaluate
4614 if (!expr_ptr) {
4617 .arg(cclass);
4618 pkt->addClass(cclass);
4619 continue;
4620 }
4621 // Evaluate the expression which can return false (no match),
4622 // true (match) or raise an exception (error)
4623 try {
4624 bool status = evaluateBool(*expr_ptr, *pkt);
4626 .arg(pkt->getLabel())
4627 .arg(cclass)
4628 .arg(status ? "true" : "false");
4629 if (status) {
4630 // Matching: add the class
4631 pkt->addClass(cclass);
4632 }
4633 } catch (const Exception& ex) {
4635 .arg(pkt->getLabel())
4636 .arg(cclass)
4637 .arg(ex.what());
4638 }
4639 }
4640}
4641
4642void
4643Dhcpv6Srv::updateReservedFqdn(AllocEngine::ClientContext6& ctx,
4644 const Pkt6Ptr& answer) {
4645 if (!answer) {
4646 isc_throw(isc::Unexpected, "an instance of the object encapsulating"
4647 " a message must not be NULL when updating reserved FQDN");
4648 }
4649
4650 Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>
4651 (answer->getOption(D6O_CLIENT_FQDN));
4652
4653 // If Client FQDN option is not included, there is nothing to do.
4654 if (!fqdn) {
4655 return;
4656 }
4657
4658 std::string name = fqdn->getDomainName();
4659
4660 // If there is a host reservation for this client we have to check whether
4661 // this reservation has the same hostname as the hostname currently
4662 // present in the FQDN option. If not, it indicates that the allocation
4663 // engine picked a different subnet (from within a shared network) for
4664 // reservations and we have to send this new value to the client.
4665 if (ctx.currentHost() &&
4666 !ctx.currentHost()->getHostname().empty()) {
4667 std::string new_name = CfgMgr::instance().getD2ClientMgr().
4668 qualifyName(ctx.currentHost()->getHostname(), *ctx.getDdnsParams(), true);
4669
4670 if (new_name != name) {
4671 fqdn->setDomainName(new_name, Option6ClientFqdn::FULL);
4672
4673 // Replace previous instance of Client FQDN option.
4674 answer->delOption(D6O_CLIENT_FQDN);
4675 answer->addOption(fqdn);
4676 }
4677 }
4678}
4679
4680void
4681Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer,
4683 if (!answer) {
4684 isc_throw(isc::Unexpected, "an instance of the object encapsulating"
4685 " a message must not be NULL when generating FQDN");
4686 }
4687
4690
4691 // It is likely that client hasn't included the FQDN option. In such case,
4692 // FQDN option will be NULL. Also, there is nothing to do if the option
4693 // is present and conveys the non-empty FQDN.
4694 Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
4695 Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
4696 if (!fqdn || !fqdn->getDomainName().empty()) {
4697 return;
4698 }
4699
4700 // Get the first IA_NA acquired for the client.
4701 OptionPtr ia = answer->getOption(D6O_IA_NA);
4702 if (!ia) {
4703 return;
4704 }
4705
4706 // If it has any IAAddr with a valid lifetime > 0, use it to
4707 // generate unique FQDN.
4708 Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
4709 Option6IAAddr>(ia->getOption(D6O_IAADDR));
4710 if (!iaaddr || iaaddr->getValid() == 0) {
4711 return;
4712 }
4713
4714 // Get the IPv6 address acquired by the client.
4715 IOAddress addr = iaaddr->getAddress();
4716 std::string generated_name =
4718
4720 .arg(answer->getLabel())
4721 .arg(generated_name);
4722
4723 try {
4724 // The lease has been acquired but the FQDN for this lease hasn't
4725 // been updated in the lease database. We now have new FQDN
4726 // generated, so the lease database has to be updated here.
4727 // However, never update lease database for Advertise, just send
4728 // our notion of client's FQDN in the Client FQDN option.
4729 if (answer->getType() != DHCPV6_ADVERTISE) {
4730 Lease6Ptr lease;
4731 for (auto const& l : ctx.new_leases_) {
4732 if ((l->type_ == Lease::TYPE_NA) && (l->addr_ == addr)) {
4733 lease = l;
4734 break;
4735 }
4736 }
4737 if (lease) {
4738 lease->hostname_ = generated_name;
4739 lease->reuseable_valid_lft_ = 0;
4741
4742 } else {
4743 isc_throw(isc::Unexpected, "there is no lease in the database "
4744 " for address " << addr << ", so as it is impossible"
4745 " to update FQDN data. This is a programmatic error"
4746 " as the given address is now being handed to the"
4747 " client");
4748 }
4749 }
4750 // Set the generated FQDN in the Client FQDN option.
4751 fqdn->setDomainName(generated_name, Option6ClientFqdn::FULL);
4752
4753 answer->delOption(D6O_CLIENT_FQDN);
4754 answer->addOption(fqdn);
4755 ctx.hostname_ = generated_name;
4756 } catch (const Exception& ex) {
4758 .arg(answer->getLabel())
4759 .arg(addr.toText())
4760 .arg(ex.what());
4761 }
4762}
4763
4764void
4767 if (d2_mgr.ddnsEnabled()) {
4768 // Updates are enabled, so lets start the sender, passing in
4769 // our error handler.
4770 // This may throw so wherever this is called needs to ready.
4772 this, ph::_1, ph::_2));
4773 }
4774}
4775
4776void
4779 if (d2_mgr.ddnsEnabled()) {
4780 // Updates are enabled, so lets stop the sender
4781 d2_mgr.stop();
4782 d2_mgr.stopSender();
4783 }
4784}
4785
4786void
4791 arg(result).arg((ncr ? ncr->toText() : " NULL "));
4792 // We cannot communicate with kea-dhcp-ddns, suspend further updates.
4796}
4797
4798std::string
4800 std::stringstream tmp;
4801
4802 tmp << VERSION;
4803 if (extended) {
4804 tmp << " (" << EXTENDED_VERSION << ")" << endl;
4805 tmp << "premium: " << PREMIUM_EXTENDED_VERSION << endl;
4806 tmp << "linked with:" << endl;
4807 tmp << "- " << Logger::getVersion() << endl;
4808 tmp << "- " << CryptoLink::getVersion();
4810 if (info.size()) {
4811 tmp << endl << "lease backends:";
4812 for (auto const& version : info) {
4813 tmp << endl << "- " << version;
4814 }
4815 }
4817 if (info.size()) {
4818 tmp << endl << "host backends:";
4819 for (auto const& version : info) {
4820 tmp << endl << "- " << version;
4821 }
4822 }
4823 // @todo: more details about database runtime
4824 }
4825
4826 return (tmp.str());
4827}
4828
4829void Dhcpv6Srv::processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp) {
4830
4831 if (query->relay_info_.empty()) {
4832 // RSOO is inserted by relay agents, nothing to do here if it's
4833 // a direct message.
4834 return;
4835 }
4836
4837 // Get RSOO configuration.
4838 ConstCfgRSOOPtr cfg_rsoo = CfgMgr::instance().getCurrentCfg()->getCfgRSOO();
4839
4840 // Let's get over all relays (encapsulation levels). We need to do
4841 // it in the same order as the client packet traversed the relays.
4842 for (int i = query->relay_info_.size(); i > 0 ; --i) {
4843 OptionPtr rsoo_container = query->getRelayOption(D6O_RSOO, i - 1);
4844 if (rsoo_container) {
4845 // There are RSOO options. Let's get through them one by one
4846 // and if it's RSOO-enabled and there's no such option provided yet,
4847 // copy it to the server's response
4848 const OptionCollection& rsoo = rsoo_container->getOptions();
4849 for (auto const& opt : rsoo) {
4850
4851 // Echo option if it is RSOO enabled option and there is no such
4852 // option added yet.
4853 if (cfg_rsoo->enabled(opt.second->getType()) &&
4854 !rsp->getOption(opt.second->getType())) {
4855 rsp->addOption(opt.second);
4856 }
4857 }
4858 }
4859 }
4860}
4861
4863
4864 if (query->relay_info_.empty()) {
4865 // No relay agent
4866 return (0);
4867 }
4868
4869 // Did the last relay agent add a relay-source-port?
4870 if (query->getRelayOption(D6O_RELAY_SOURCE_PORT, 0)) {
4871 // RFC 8357 section 5.2
4872 return (query->getRemotePort());
4873 }
4874
4875 return (0);
4876}
4877
4878void Dhcpv6Srv::processStatsReceived(const Pkt6Ptr& query) {
4879 // Note that we're not bumping pkt6-received statistic as it was
4880 // increased early in the packet reception code.
4881
4882 string stat_name = "pkt6-unknown-received";
4883 switch (query->getType()) {
4884 case DHCPV6_SOLICIT:
4885 stat_name = "pkt6-solicit-received";
4886 break;
4887 case DHCPV6_ADVERTISE:
4888 // Should not happen, but let's keep a counter for it
4889 stat_name = "pkt6-advertise-received";
4890 break;
4891 case DHCPV6_REQUEST:
4892 stat_name = "pkt6-request-received";
4893 break;
4894 case DHCPV6_CONFIRM:
4895 stat_name = "pkt6-confirm-received";
4896 break;
4897 case DHCPV6_RENEW:
4898 stat_name = "pkt6-renew-received";
4899 break;
4900 case DHCPV6_REBIND:
4901 stat_name = "pkt6-rebind-received";
4902 break;
4903 case DHCPV6_REPLY:
4904 // Should not happen, but let's keep a counter for it
4905 stat_name = "pkt6-reply-received";
4906 break;
4907 case DHCPV6_RELEASE:
4908 stat_name = "pkt6-release-received";
4909 break;
4910 case DHCPV6_DECLINE:
4911 stat_name = "pkt6-decline-received";
4912 break;
4913 case DHCPV6_RECONFIGURE:
4914 stat_name = "pkt6-reconfigure-received";
4915 break;
4917 stat_name = "pkt6-infrequest-received";
4918 break;
4920 stat_name = "pkt6-dhcpv4-query-received";
4921 break;
4923 // Should not happen, but let's keep a counter for it
4924 stat_name = "pkt6-dhcpv4-response-received";
4925 break;
4926 default:
4927 ; // do nothing
4928 }
4929
4930 StatsMgr::instance().addValue(stat_name, static_cast<int64_t>(1));
4931}
4932
4934 // Increase generic counter for sent packets.
4935 StatsMgr::instance().addValue("pkt6-sent", static_cast<int64_t>(1));
4936
4937 // Increase packet type specific counter for packets sent.
4938 string stat_name;
4939 switch (response->getType()) {
4940 case DHCPV6_ADVERTISE:
4941 stat_name = "pkt6-advertise-sent";
4942 break;
4943 case DHCPV6_REPLY:
4944 stat_name = "pkt6-reply-sent";
4945 break;
4947 stat_name = "pkt6-dhcpv4-response-sent";
4948 break;
4949 default:
4950 // That should never happen
4951 return;
4952 }
4953
4954 StatsMgr::instance().addValue(stat_name, static_cast<int64_t>(1));
4955}
4956
4958 return (Hooks.hook_index_buffer6_send_);
4959}
4960
4961bool
4962Dhcpv6Srv::requestedInORO(const Pkt6Ptr& query, const uint16_t code) const {
4964 boost::dynamic_pointer_cast<OptionUint16Array>(query->getOption(D6O_ORO));
4965
4966 if (oro) {
4967 const std::vector<uint16_t>& codes = oro->getValues();
4968 return (std::find(codes.begin(), codes.end(), code) != codes.end());
4969 }
4970
4971 return (false);
4972}
4973
4974tuple<bool, uint32_t>
4975Dhcpv6Srv::parkingLimitExceeded(string const& hook_label) {
4976 // Get the parking limit. Parsing should ensure the value is present.
4977 uint32_t parked_packet_limit(0);
4978 ConstElementPtr const& ppl(
4979 CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::PARKED_PACKET_LIMIT));
4980 if (ppl) {
4981 parked_packet_limit = ppl->intValue();
4982 }
4983
4984 if (parked_packet_limit) {
4985 ParkingLotPtr const& parking_lot(
4986 ServerHooks::getServerHooks().getParkingLotPtr(hook_label));
4987
4988 if (parking_lot && parked_packet_limit <= parking_lot->size()) {
4989 return make_tuple(true, parked_packet_limit);
4990 }
4991 }
4992 return make_tuple(false, parked_packet_limit);
4993}
4994
4995
4997 // Dump all of our current packets, anything that is mid-stream
4999}
5000
5002#ifdef FUZZING
5003 char const* const rotate(getenv("KEA_DHCP6_FUZZING_ROTATE_PORT"));
5004 if (rotate) {
5005 InterprocessSyncFile file("kea-dhcp6-fuzzing-rotate-port");
5006 InterprocessSyncLocker locker(file);
5007 while (!locker.lock()) {
5008 this_thread::sleep_for(1s);
5009 }
5010 fstream port_file;
5011 port_file.open("/tmp/port6.txt", ios::in);
5012 string line;
5013 int port;
5014 getline(port_file, line);
5015 port_file.close();
5016 if (line.empty()) {
5017 port = 2000;
5018 } else {
5019 port = stoi(line);
5020 if (port < 3000) {
5021 ++port;
5022 } else {
5023 port = 2000;
5024 }
5025 }
5026 port_file.open("/tmp/port6.txt", ios::out | ios::trunc);
5027 port_file << to_string(port) << endl;
5028 port_file.close();
5029 locker.unlock();
5030 return port;
5031 }
5032#endif // FUZZING
5033 return server_port_;
5034}
5035
5037void
5038Dhcpv6Srv::setTeeTimes(uint32_t preferred_lft,
5039 const ConstSubnet6Ptr& subnet,
5040 Option6IAPtr& resp) {
5041 // Default T2 time to zero.
5042 uint32_t t2_time = 0;
5043
5044 // If T2 is explicitly configured we'll use that value.
5045 if (!subnet->getT2().unspecified()) {
5046 t2_time = subnet->getT2();
5047 } else if (subnet->getCalculateTeeTimes()) {
5048 // Calculating tee times is enabled, so calculate it.
5049 t2_time = static_cast<uint32_t>(round(subnet->getT2Percent() * preferred_lft));
5050 }
5051
5052 // We allow T2 to be any value.
5053 resp->setT2(t2_time);
5054
5055 // Default T1 time to zero.
5056 uint32_t t1_time = 0;
5057
5058 // If T1 is explicitly configured we'll use try value.
5059 if (!subnet->getT1().unspecified()) {
5060 t1_time = subnet->getT1();
5061 } else if (subnet->getCalculateTeeTimes()) {
5062 // Calculating tee times is enabled, so calculate it.
5063 t1_time = static_cast<uint32_t>(round(subnet->getT1Percent() * preferred_lft));
5064 }
5065
5066 // T1 is sane if it is less than or equal to T2.
5067 if (t1_time < t2_time) {
5068 resp->setT1(t1_time);
5069 } else {
5070 // It's either explicitly 0 or insane, leave it to the client
5071 resp->setT1(0);
5072 }
5073}
5074
5075void
5078 const ConstSubnet6Ptr orig_subnet) {
5079 bool reprocess_client_name = false;
5080
5081 // Find the pool to which the first active lease address belongs and use it
5082 // to update DDNS parameters to include those from the pool.
5083 OptionPtr ia = answer->getOption(D6O_IA_NA);
5084 if (ia) {
5085 Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
5086 if (iaaddr && (iaaddr->getValid() > 0)) {
5087 auto ddns_params = ctx.getDdnsParams();
5088 auto pool = ddns_params->setPoolFromAddress(iaaddr->getAddress());
5089 if (pool) {
5090 // If the pool has any DDNS parameters we need to recalculate the FQDN.
5091 reprocess_client_name = pool->hasDdnsParameters();
5092 }
5093 }
5094 }
5095
5096 // Check if the subnet was dynamically changed by the allocation engine.
5097 if (ctx.subnet_ && orig_subnet && (orig_subnet->getID() != ctx.subnet_->getID())) {
5098 // We get the network for logging only. It should always be set as
5099 // this a dynamic change should only happen within shared-networks.
5100 // Not having one might not be an error if a hook changed the subnet?
5101 SharedNetwork6Ptr network;
5102 orig_subnet->getSharedNetwork(network);
5104 .arg(question->getLabel())
5105 .arg(orig_subnet->toText())
5106 .arg(ctx.subnet_->toText())
5107 .arg(network ? network->getName() : "<no network?>");
5108
5109 // The subnet changed so we need to recalculate the FQDN.
5110 reprocess_client_name = true;
5111 }
5112
5113 // Recalulate the FQDN if either the pool has DDNS parameters or the
5114 // selected subnet was changed.
5115 if (reprocess_client_name) {
5116 // Save the current DNS values on the context.
5117 std::string prev_hostname = ctx.hostname_;
5118 bool prev_fwd_dns_update = ctx.fwd_dns_update_;
5119 bool prev_rev_dns_update = ctx.rev_dns_update_;
5120
5121 // Remove the current FQDN option from the answer.
5122 answer->delOption(D6O_CLIENT_FQDN);
5123
5124 // Recalculate the client's FQDN. This will replace the FQDN option and
5125 // update the context values for hostname_ and DNS directions.
5126 processClientFqdn(question, answer, ctx);
5127
5128 // If this is a real allocation and the DNS values changed we need to
5129 // update the leases.
5130 if (!ctx.fake_allocation_ &&
5131 ((prev_hostname != ctx.hostname_) ||
5132 (prev_fwd_dns_update != ctx.fwd_dns_update_) ||
5133 (prev_rev_dns_update != ctx.rev_dns_update_))) {
5134 for (auto const& l : ctx.new_leases_) {
5135 l->hostname_ = ctx.hostname_;
5136 l->fqdn_fwd_ = ctx.fwd_dns_update_;
5137 l->fqdn_rev_ = ctx.rev_dns_update_;
5138 l->reuseable_valid_lft_ = 0;
5140 }
5141 }
5142 }
5143}
5144
5145std::list<std::list<std::string>> Dhcpv6Srv::jsonPathsToRedact() const{
5146 static std::list<std::list<std::string>> const list({
5147 {"config-control", "config-databases", "[]"},
5148 {"hooks-libraries", "[]", "parameters", "*"},
5149 {"hosts-database"},
5150 {"hosts-databases", "[]"},
5151 {"lease-database"},
5152 });
5153 return list;
5154}
5155
5156} // namespace dhcp
5157} // namespace isc
int version()
returns Kea hooks version.
CtrlAgentHooks Hooks
Defines elements for storing the names of client classes.
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.
Defines a single hint.
DHCPv4 and DHCPv6 allocation engine.
std::vector< Resource > HintContainer
Container for client's hints.
Implementation of the mechanisms to control the use of the Configuration Backends by the DHCPv6 serve...
D2ClientMgr & getD2ClientMgr()
Fetches the DHCP-DDNS manager.
Definition cfgmgr.cc:68
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:28
SrvConfigPtr getCurrentCfg()
Returns a pointer to the current configuration.
Definition cfgmgr.cc:115
static SubnetSelector initSelector(const Pkt6Ptr &query)
Build selector from a client's message.
Container for storing client class names.
Definition classify.h:109
void insert(const ClientClass &class_name)
Insert an element.
Definition classify.h:159
std::string toText(const std::string &separator=", ") const
Returns all class names as text.
Definition classify.cc:80
Client race avoidance RAII handler.
bool tryLock(Pkt4Ptr query, ContinuationPtr cont=ContinuationPtr())
Tries to acquires a client.
D2ClientMgr isolates Kea from the details of being a D2 client.
std::string generateFqdn(const asiolink::IOAddress &address, const DdnsParams &ddns_params, const bool trailing_dot=true) const
Builds a FQDN based on the configuration and given IP address.
bool ddnsEnabled()
Convenience method for checking if DHCP-DDNS is enabled.
void getUpdateDirections(const T &fqdn_resp, bool &forward, bool &reverse)
Get directional update flags based on server FQDN flags.
void stop()
Stop the sender.
void suspendUpdates()
Suspends sending requests.
void adjustDomainName(const T &fqdn, T &fqdn_resp, const DdnsParams &ddns_params)
Set server FQDN name based on configuration and a given FQDN.
void sendRequest(dhcp_ddns::NameChangeRequestPtr &ncr)
Send the given NameChangeRequests to kea-dhcp-ddns.