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