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