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