summaryrefslogtreecommitdiffstats
path: root/src/bin/dhcp6/tests/dhcp6_client.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/dhcp6/tests/dhcp6_client.cc
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/dhcp6/tests/dhcp6_client.cc')
-rw-r--r--src/bin/dhcp6/tests/dhcp6_client.cc1053
1 files changed, 1053 insertions, 0 deletions
diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc
new file mode 100644
index 0000000..7021176
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_client.cc
@@ -0,0 +1,1053 @@
+// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/pool.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <util/buffer.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
+#include <algorithm>
+#include <cstdlib>
+#include <time.h>
+#include <utility>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Functor searching for the leases using a specified property.
+///
+/// @tparam BaseType Base type to which the property belongs: @c Lease or
+/// @c Lease6.
+/// @tparam PropertyType A type of the property, e.g. @c uint32_t for IAID.
+/// @tparam MemberPointer A pointer to the member, e.g. @c &Lease6::iaid_.
+template<typename BaseType, typename PropertyType,
+ PropertyType BaseType::*MemberPointer>
+struct getLeasesByPropertyFun {
+
+ /// @brief Returns leases matching the specified condition.
+ ///
+ /// @param config DHCP client configuration structure holding leases.
+ /// @param property A value of the lease property used to search the lease.
+ /// @param equals A flag which indicates if the operator should search for
+ /// the leases which property is equal to the value of @c property parameter
+ /// (if true), or unequal (if false).
+ /// @param [out] leases A vector in which the operator will store leases
+ /// found.
+ void operator()(const Dhcp6Client::Configuration& config,
+ const PropertyType& property, const bool equals,
+ std::vector<Lease6>& leases) {
+
+ // Iterate over the leases and match the property with a given lease
+ //field.
+ for (typename std::vector<Lease6>::const_iterator lease =
+ config.leases_.begin(); lease != config.leases_.end();
+ ++lease) {
+ // Check if fulfills the condition.
+ if ((equals && ((*lease).*MemberPointer) == property) ||
+ (!equals && ((*lease).*MemberPointer) != property)) {
+ // Found the matching lease.
+ leases.push_back(*lease);
+ }
+ }
+ }
+};
+
+/// @brief Returns leases which belong to specified pool.
+///
+/// @param config DHCP client configuration structure holding leases.
+/// @param pool Pool to which returned leases belong.
+/// @param [out] leases A vector in which the function will store leases
+/// found.
+void getLeasesByPool(const Dhcp6Client::Configuration& config,
+ const Pool6& pool, std::vector<Lease6>& leases) {
+ for (std::vector<Lease6>::const_iterator lease =
+ config.leases_.begin(); lease != config.leases_.end();
+ ++lease) {
+ // Check if prefix in range.
+ if (pool.inRange(lease->addr_)) {
+ // Found the matching lease.
+ leases.push_back(*lease);
+ }
+ }
+}
+
+}; // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Dhcp6Client::Dhcp6Client() :
+ relay_link_addr_("3000:1::1"),
+ curr_transid_(0),
+ dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+ duid_(generateDUID(DUID::DUID_LLT)),
+ link_local_("fe80::3a60:77ff:fed5:cdef"),
+ iface_name_("eth0"),
+ iface_index_(ETH0_INDEX),
+ srv_(boost::shared_ptr<NakedDhcpv6Srv>(new NakedDhcpv6Srv(0))),
+ use_relay_(false),
+ use_oro_(false),
+ use_docsis_oro_(false),
+ use_client_id_(true),
+ use_rapid_commit_(false),
+ client_ias_(),
+ fqdn_(),
+ interface_id_() {
+}
+
+Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
+ relay_link_addr_("3000:1::1"),
+ curr_transid_(0),
+ dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+ duid_(generateDUID(DUID::DUID_LLT)),
+ link_local_("fe80::3a60:77ff:fed5:cdef"),
+ iface_name_("eth0"),
+ iface_index_(ETH0_INDEX),
+ srv_(srv),
+ use_relay_(false),
+ use_oro_(false),
+ use_docsis_oro_(false),
+ use_client_id_(true),
+ use_rapid_commit_(false),
+ client_ias_(),
+ fqdn_(),
+ interface_id_() {
+}
+
+void
+Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply, uint32_t state) {
+ // Let's try to get a MAC
+ HWAddrPtr hwaddr = reply->getMAC(HWAddr::HWADDR_SOURCE_ANY);
+
+ // Get all options in the reply message and pick IA_NA, IA_PD and
+ // Status code.
+ for (const auto& opt : reply->options_) {
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt.second);
+ if (!ia) {
+ // This is not IA, so let's just store it.
+ config_.options_.insert(opt);
+ continue;
+ }
+
+ const auto& ia_opts = ia->getOptions();
+ for (const auto& iter_ia_opt : ia_opts) {
+ OptionPtr ia_opt = iter_ia_opt.second;
+ Lease6 lease;
+ lease.type_ = (ia->getType() == D6O_IA_NA ? Lease::TYPE_NA : Lease::TYPE_PD);
+ lease.iaid_ = ia->getIAID();
+
+ switch (ia_opt->getType()) {
+ case D6O_IAADDR:
+ {
+ Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+ Option6IAAddr>(ia_opt);
+
+ if (iaaddr) {
+ lease = Lease6(Lease::TYPE_NA,
+ iaaddr->getAddress(),
+ duid_, ia->getIAID(),
+ iaaddr->getPreferred(),
+ iaaddr->getValid(), 0,
+ hwaddr);
+ lease.cltt_ = time(NULL);
+ lease.state_ = state;
+ applyLease(lease);
+ }
+ }
+ break;
+
+ case D6O_IAPREFIX:
+ {
+ Option6IAPrefixPtr iaprefix = boost::dynamic_pointer_cast<
+ Option6IAPrefix>(ia_opt);
+
+ if (iaprefix) {
+ lease = Lease6(Lease::TYPE_PD,
+ iaprefix->getAddress(), duid_,
+ ia->getIAID(),
+ iaprefix->getPreferred(),
+ iaprefix->getValid(), 0,
+ hwaddr,
+ iaprefix->getLength());
+ lease.cltt_ = time(NULL);
+ applyLease(lease);
+ }
+ }
+ break;
+
+ case D6O_STATUS_CODE:
+ {
+ // Check if the server has sent status code. If no status
+ // code, assume the status code to be 0.
+ Option6StatusCodePtr status_code = boost::dynamic_pointer_cast<
+ Option6StatusCode>(ia->getOption(D6O_STATUS_CODE));
+ config_.status_codes_[ia->getIAID()] =
+ (status_code ? status_code->getStatusCode() : 0);
+ }
+ break;
+
+ default:
+ ; // no-op
+ }
+
+ }
+ }
+
+ // Get the global status code.
+ Option6StatusCodePtr status_code = boost::dynamic_pointer_cast<
+ Option6StatusCode>(reply->getOption(D6O_STATUS_CODE));
+ // If status code has been sent, we override the default status code:
+ // Success and record that we have received the status code.
+ if (status_code) {
+ config_.received_status_code_ = true;
+ config_.status_code_ = status_code->getStatusCode();
+ }
+}
+
+void
+Dhcp6Client::applyLease(const Lease6& lease) {
+ // Go over existing leases and try to match the one that we have.
+ for (size_t i = 0; i < config_.leases_.size(); ++i) {
+ Lease6 existing_lease = config_.leases_[i];
+ // If IAID is matching and there is an actual address assigned
+ // replace the current lease. The default address is :: if the
+ // server hasn't sent the IA option. In this case, there is no
+ // lease assignment so we keep what we have.
+ if ((existing_lease.iaid_ == lease.iaid_)
+ && (existing_lease.type_ == lease.type_)
+ && (lease.addr_ != asiolink::IOAddress("::"))
+ && (existing_lease.addr_ == lease.addr_)) {
+ config_.leases_[i] = lease;
+ return;
+ }
+ }
+
+ // It is a new lease. Add it.
+ config_.leases_.push_back(lease);
+}
+
+void
+Dhcp6Client::appendFQDN() {
+ if (fqdn_) {
+ context_.query_->addOption(fqdn_);
+ }
+}
+
+void
+Dhcp6Client::appendRequestedIAs(const Pkt6Ptr& query) const {
+ BOOST_FOREACH(const ClientIA& ia, client_ias_) {
+ OptionCollection options =
+ query->getOptions(ia.type_ == Lease::TYPE_NA ?
+ D6O_IA_NA : D6O_IA_PD);
+ std::pair<unsigned int, OptionPtr> option_pair;
+ Option6IAPtr existing_ia;
+ BOOST_FOREACH(option_pair, options) {
+ Option6IAPtr ia_opt =
+ boost::dynamic_pointer_cast<Option6IA>(option_pair.second);
+ // This shouldn't happen.
+ if (!ia_opt) {
+ isc_throw(Unexpected,
+ "Dhcp6Client: IA option has an invalid C++ type;"
+ " this is a programming issue");
+ }
+ if (ia_opt->getIAID() == ia.iaid_) {
+ existing_ia = ia_opt;
+ }
+ }
+ if (!existing_ia) {
+ existing_ia.reset(new Option6IA(ia.type_ == Lease::TYPE_NA ?
+ D6O_IA_NA : D6O_IA_PD, ia.iaid_));
+ query->addOption(existing_ia);
+ }
+
+ bool option_exists = false;
+ if ((ia.type_ == Lease::TYPE_NA) && !ia.prefix_.isV6Zero()) {
+ Option6IAAddrPtr ia_addr(new Option6IAAddr(D6O_IAADDR, ia.prefix_,
+ 0, 0));
+ BOOST_FOREACH(option_pair, existing_ia->getOptions()) {
+ Option6IAAddrPtr existing_addr = boost::dynamic_pointer_cast<
+ Option6IAAddr>(option_pair.second);
+ if (existing_addr &&
+ (existing_addr->getAddress() == ia.prefix_)) {
+ option_exists = true;
+ }
+ }
+
+ if (!option_exists) {
+ existing_ia->addOption(ia_addr);
+ }
+
+ } else if ((ia.type_ == Lease::TYPE_PD) &&
+ (!ia.prefix_.isV6Zero() || (ia.prefix_len_ > 0))) {
+ Option6IAPrefixPtr ia_prefix(new Option6IAPrefix(D6O_IAPREFIX,
+ ia.prefix_,
+ ia.prefix_len_,
+ 0, 0));
+ BOOST_FOREACH(option_pair, existing_ia->getOptions()) {
+ Option6IAPrefixPtr existing_prefix =
+ boost::dynamic_pointer_cast<Option6IAPrefix>(option_pair.second);
+ if (existing_prefix &&
+ (existing_prefix->getAddress() == ia.prefix_) &&
+ existing_prefix->getLength()) {
+ option_exists = true;
+ }
+ }
+
+ if (!option_exists) {
+ existing_ia->addOption(ia_prefix);
+ }
+ }
+ }
+}
+
+void
+Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
+ typedef OptionCollection Opts;
+ // Copy IA_NAs.
+ Opts opts = source->getOptions(D6O_IA_NA);
+ for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
+ // Only copy the entire IA_NA if there is at lease one IA Address option.
+ if (opt->second->getOption(D6O_IAADDR)) {
+ dest->addOption(opt->second);
+ }
+ }
+ // Copy IA_PDs.
+ opts = source->getOptions(D6O_IA_PD);
+ for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
+ // Only copy the entire IA_PD if there is at least one IA Prefix option
+ // in it.
+ if (opt->second->getOption(D6O_IAPREFIX)) {
+ dest->addOption(opt->second);
+ }
+ }
+}
+
+void
+Dhcp6Client::copyIAsFromLeases(const Pkt6Ptr& dest) const {
+ // Go over leases and create IA_NA and IA_PD options from them.
+ // Create one IA per lease.
+ std::set<uint32_t> iaids = getIAIDs();
+ for (std::set<uint32_t>::const_iterator iaid = iaids.begin();
+ iaid != iaids.end(); ++iaid) {
+ std::vector<Lease6> leases = getLeasesByIAID(*iaid);
+ // Only a valid lease should be included. Do not copy a
+ // lease which have been marked by the server as invalid.
+ if (leases[0].valid_lft_ == 0) {
+ continue;
+ }
+ Option6IAPtr opt(new Option6IA(leases[0].type_ == Lease::TYPE_NA ?
+ D6O_IA_NA : D6O_IA_PD, *iaid));
+ for (std::vector<Lease6>::const_iterator lease = leases.begin();
+ lease != leases.end(); ++lease) {
+ if ((lease->preferred_lft_ != 0) && (lease->valid_lft_ != 0)) {
+ if (lease->type_ == Lease::TYPE_NA) {
+ opt->addOption(Option6IAAddrPtr(new Option6IAAddr(
+ D6O_IAADDR,
+ lease->addr_,
+ lease->preferred_lft_,
+ lease->valid_lft_)));
+ } else if (lease->type_ == Lease::TYPE_PD) {
+ opt->addOption(Option6IAAddrPtr(new Option6IAPrefix(
+ D6O_IAPREFIX,
+ lease->addr_,
+ lease->prefixlen_,
+ lease->preferred_lft_,
+ lease->valid_lft_)));
+ }
+ }
+ }
+ dest->addOption(opt);
+ }
+}
+
+void
+Dhcp6Client::createLease(const Lease6& lease) {
+ applyLease(lease);
+}
+
+Pkt6Ptr
+Dhcp6Client::createMsg(const uint8_t msg_type) {
+ Pkt6Ptr msg(new Pkt6(msg_type, curr_transid_++));
+
+ if (use_client_id_) {
+ msg->addOption(getClientId());
+ }
+
+ if (use_oro_) {
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ oro->setValues(oro_);
+
+ msg->addOption(oro);
+ };
+
+ if (use_docsis_oro_) {
+ OptionUint16ArrayPtr vendor_oro(new OptionUint16Array(Option::V6,
+ DOCSIS3_V6_ORO));
+ vendor_oro->setValues(docsis_oro_);
+ OptionVendorPtr vendor(new OptionVendor(Option::V6, VENDOR_ID_CABLE_LABS));
+ vendor->addOption(vendor_oro);
+ msg->addOption(vendor);
+ }
+
+ // If there are any custom options specified, add them all to the message.
+ if (!extra_options_.empty()) {
+ for (OptionCollection::iterator opt = extra_options_.begin();
+ opt != extra_options_.end(); ++opt) {
+ msg->addOption(opt->second);
+ }
+ }
+
+ // Add classes.
+ for (ClientClasses::const_iterator cclass = classes_.cbegin();
+ cclass != classes_.cend(); ++cclass) {
+ msg->addClass(*cclass);
+ }
+
+ return (msg);
+}
+
+void
+Dhcp6Client::doSARR() {
+ doSolicit();
+ // Don't send the Request if there was no Advertise.
+ if (context_.response_) {
+ doRequest();
+ }
+}
+
+void
+Dhcp6Client::doSolicit(const bool always_apply_config) {
+ context_.query_ = createMsg(DHCPV6_SOLICIT);
+ if (forced_server_id_) {
+ context_.query_->addOption(forced_server_id_);
+ }
+
+ // Append requested (empty) IAs.
+ appendRequestedIAs(context_.query_);
+
+ if (use_rapid_commit_) {
+ context_.query_->addOption(OptionPtr(new Option(Option::V6,
+ D6O_RAPID_COMMIT)));
+ }
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // If using Rapid Commit and the server has responded with Reply,
+ // let's apply received configuration. We also apply the configuration
+ // for the Advertise if instructed to do so.
+ if (context_.response_ &&
+ (always_apply_config ||
+ (use_rapid_commit_ &&
+ context_.response_->getType() == DHCPV6_REPLY))) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRequest() {
+ Pkt6Ptr query = createMsg(DHCPV6_REQUEST);
+ if (!forced_server_id_) {
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ } else {
+ query->addOption(forced_server_id_);
+ }
+ copyIAs(context_.response_, query);
+ appendRequestedIAs(query);
+
+ context_.query_ = query;
+
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ /// @todo sanity check here.
+
+ // Apply new configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doInfRequest() {
+ context_.query_ = createMsg(DHCPV6_INFORMATION_REQUEST);
+
+ // IA_NA, IA_TA and IA_PD options are not allowed in INF-REQUEST,
+ // but hey! Let's test it.
+ appendRequestedIAs(context_.query_);
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+ // Apply new configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRenew() {
+ Pkt6Ptr query = createMsg(DHCPV6_RENEW);
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ copyIAsFromLeases(query);
+
+ // During the Renew the client may request additional bindings per
+ // RFC 8415.
+ appendRequestedIAs(query);
+
+ context_.query_ = query;
+
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // Apply configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRebind() {
+ Pkt6Ptr query = createMsg(DHCPV6_REBIND);
+ copyIAsFromLeases(query);
+
+ // During the Rebind the client may request additional bindings per
+ // RFC 8415.
+ appendRequestedIAs(query);
+
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ context_.query_ = query;
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+ // Apply configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doConfirm() {
+ context_.query_ = createMsg(DHCPV6_CONFIRM);
+ copyIAsFromLeases(context_.query_);
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+ // Set the global status code to default: success and not received.
+ config_.resetGlobalStatusCode();
+ if (context_.response_) {
+ config_.options_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doDecline(const bool include_address) {
+ Pkt6Ptr query = createMsg(DHCPV6_DECLINE);
+ if (!forced_server_id_) {
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ } else {
+ query->addOption(forced_server_id_);
+ }
+
+ generateIAFromLeases(query, include_address);
+
+ context_.query_ = query;
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // Apply new configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRelease() {
+ Pkt6Ptr query = createMsg(DHCPV6_RELEASE);
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ copyIAsFromLeases(query);
+
+ context_.query_ = query;
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // Apply configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::generateIAFromLeases(const Pkt6Ptr& query,
+ const bool include_address) {
+ /// @todo: add support for IAPREFIX here.
+
+ for (std::vector<Lease6>::const_iterator lease = config_.leases_.begin();
+ lease != config_.leases_.end(); ++lease) {
+ if (lease->type_ != Lease::TYPE_NA) {
+ continue;
+ }
+
+ Option6IAPtr ia(new Option6IA(D6O_IA_NA, lease->iaid_));
+
+ if (include_address) {
+ ia->addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR,
+ lease->addr_, lease->preferred_lft_, lease->valid_lft_)));
+ }
+ query->addOption(ia);
+ }
+}
+
+void
+Dhcp6Client::fastFwdTime(const uint32_t secs, const bool update_server) {
+ // Iterate over all leases and move their cltt backwards.
+ for (size_t i = 0; i < config_.leases_.size(); ++i) {
+ config_.leases_[i].cltt_ -= secs;
+ if (update_server) {
+ Lease6Ptr lease(new Lease6(config_.leases_[i]));
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+ }
+}
+
+DuidPtr
+Dhcp6Client::generateDUID(DUID::DUIDType duid_type) const {
+ std::vector<uint8_t> duid;
+
+ /// @todo remove this check once other DUID types are implemented.
+ if (duid_type != DUID::DUID_LLT) {
+ isc_throw(NotImplemented, "currently the Dhcp6Client only supports"
+ " generation of DUID LLT");
+ }
+ duid.push_back(static_cast<uint8_t>(duid_type));
+ for (int i = 0; i < 4; ++i) {
+ duid.push_back(static_cast<uint8_t>(rand() % 255));
+ }
+ for (int i = 0; i < 6; ++i) {
+ duid.push_back(static_cast<uint8_t>(i));
+ }
+
+ return (DuidPtr(new DUID(duid)));
+}
+
+OptionPtr
+Dhcp6Client::getClientId() const {
+ OptionPtr opt_client_id(new Option(Option::V6,
+ D6O_CLIENTID,
+ duid_->getDuid().begin(),
+ duid_->getDuid().end()));
+ return (opt_client_id);
+}
+
+std::set<uint32_t>
+Dhcp6Client::getIAIDs() const {
+ std::set<uint32_t> iaids;
+ for (std::vector<Lease6>::const_iterator lease = config_.leases_.begin();
+ lease != config_.leases_.end(); ++lease) {
+ iaids.insert(lease->iaid_);
+ }
+ return (iaids);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByIAID(const uint32_t iaid) const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease6, uint32_t, &Lease6::iaid_>(iaid, true, leases);
+ return (leases);
+}
+
+template<typename BaseType, typename PropertyType, PropertyType BaseType::*MemberPointer>
+void
+Dhcp6Client::getLeasesByProperty(const PropertyType& property, const bool equals,
+ std::vector<Lease6>& leases) const {
+ getLeasesByPropertyFun<BaseType, PropertyType, MemberPointer> fun;
+ fun(config_, property, equals, leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByType(const Lease6::Type& lease_type) const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease6, Lease6::Type, &Lease6::type_>(lease_type, true, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesWithNonZeroLifetime() const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease, uint32_t, &Lease::valid_lft_>(0, false, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesWithZeroLifetime() const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease, uint32_t, &Lease::valid_lft_>(0, true, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddress(const IOAddress& address) const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease, IOAddress, &Lease::addr_>(address, true, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddressRange(const IOAddress& first,
+ const IOAddress& second) const {
+ std::vector<Lease6> leases;
+ getLeasesByPool(config_, Pool6(Lease::TYPE_NA, first, second), leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const {
+ std::vector<Lease6> leases;
+ getLeasesByPool(config_, Pool6(Lease::TYPE_PD, prefix, prefix_len,
+ delegated_len), leases);
+ return (leases);
+}
+
+bool
+Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address) const {
+ std::vector<Lease6> leases = getLeasesByAddress(address);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address,
+ const IAID& iaid) const {
+ std::vector<Lease6> leases = getLeasesByAddress(address);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if (lease.iaid_ == iaid) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForAddressRange(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& last) const {
+ std::vector<Lease6> leases = getLeasesByAddressRange(first, last);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseWithZeroLifetimeForAddress(const asiolink::IOAddress& address) const {
+ std::vector<Lease6> leases = getLeasesByAddress(address);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if ((lease.preferred_lft_ == 0) && (lease.valid_lft_ == 0)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+
+bool
+Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ std::vector<Lease6> leases = getLeasesByAddress(prefix);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if (lease.prefixlen_ == prefix_len) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const IAID& iaid) const {
+ std::vector<Lease6> leases = getLeasesByAddress(prefix);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if ((lease.prefixlen_ == prefix_len) &&
+ (lease.iaid_ == iaid)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const {
+ std::vector<Lease6> leases = getLeasesByPrefixPool(prefix, prefix_len,
+ delegated_len);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ std::vector<Lease6> leases = getLeasesByAddress(prefix);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if ((lease.prefixlen_ == prefix_len) && (lease.preferred_lft_ == 0) &&
+ (lease.valid_lft_ == 0)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasOptionWithAddress(const uint16_t option_type,
+ const std::string& expected_address) const {
+ Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(config_.findOption(option_type));
+ if (opt) {
+ Option6AddrLst::AddressContainer addrs = opt->getAddresses();
+ if (!addrs.empty()) {
+ return (std::find(addrs.begin(), addrs.end(),
+ IOAddress(expected_address)) != addrs.end());
+ }
+ }
+ return (false);
+}
+
+uint16_t
+Dhcp6Client::getStatusCode(const uint32_t iaid) const {
+ std::map<uint32_t, uint16_t>::const_iterator status_code =
+ config_.status_codes_.find(iaid);
+ if (status_code == config_.status_codes_.end()) {
+ if (!getLeasesByIAID(iaid).empty()) {
+ return (STATUS_Success);
+ }
+
+ } else {
+ return (status_code->second);
+ }
+
+ return (0xFFFF);
+}
+
+bool
+Dhcp6Client::getTeeTimes(const uint32_t iaid, uint32_t& t1, uint32_t& t2) const {
+ // Sanity check.
+ if (!context_.response_) {
+ return (false);
+ }
+
+ // Get all options in the response message and pick IA_NA, IA_PD.
+ for (const auto& opt : context_.response_->options_) {
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt.second);
+ if (!ia) {
+ // This is not IA, so let's just skip it.
+ continue;
+ }
+ if (ia->getIAID() != iaid) {
+ // This is not the right IA, so let's just skip it.
+ continue;
+ }
+ // Found the IA.
+ t1 = ia->getT1();
+ t2 = ia->getT2();
+ return (true);
+ }
+
+ // Not found the IA.
+ return (false);
+}
+
+void
+Dhcp6Client::setDUID(const std::string& str) {
+ DUID d = DUID::fromText(str);
+ duid_.reset(new DUID(d));
+}
+
+void
+Dhcp6Client::modifyDUID() {
+ if (!duid_) {
+ duid_ = generateDUID(DUID::DUID_LLT);
+ return;
+ }
+ std::vector<uint8_t> new_duid = duid_->getDuid();
+ // Modify the DUID by adding 1 to its last byte.
+ ++new_duid[new_duid.size() - 1];
+ duid_.reset(new DUID(new_duid));
+}
+
+Pkt6Ptr
+Dhcp6Client::receiveOneMsg() {
+ return (srv_->receiveOneMsg());
+}
+
+void
+Dhcp6Client::receiveResponse() {
+ context_.response_ = receiveOneMsg();
+ // If the server has responded, store the configuration received.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
+ srv_->shutdown_ = false;
+ // The client is configured to send through the relay. We achieve that
+ // adding the relay structure.
+ if (use_relay_ || !relay_info_.empty()) {
+ if (relay_info_.empty()) {
+ // Let's craft the relay info by hand
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = relay_link_addr_;
+ relay.peeraddr_ = asiolink::IOAddress("fe80::1");
+ relay.msg_type_ = DHCPV6_RELAY_FORW;
+ relay.hop_count_ = 1;
+
+ // Interface identifier, if specified.
+ if (interface_id_) {
+ relay.options_.insert(std::make_pair(D6O_INTERFACE_ID, interface_id_));
+ }
+
+ msg->relay_info_.push_back(relay);
+
+ } else {
+ // The test provided relay_info_, let's use that.
+ msg->relay_info_ = relay_info_;
+ }
+ }
+ // Repack the message to simulate wire-data parsing.
+ msg->pack();
+
+ Pkt6Ptr msg_copy(new Pkt6(static_cast<const uint8_t*>
+ (msg->getBuffer().getData()),
+ msg->getBuffer().getLength()));
+ msg_copy->setRemoteAddr(link_local_);
+ msg_copy->setLocalAddr(dest_addr_);
+ msg_copy->setIface(iface_name_);
+ msg_copy->setIndex(iface_index_);
+
+ // Copy classes
+ const ClientClasses& classes = msg->getClasses();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ msg_copy->addClass(*cclass);
+ }
+
+ srv_->fakeReceive(msg_copy);
+
+ try {
+ // Invoke run_one instead of run, because we want to avoid triggering
+ // IO service.
+ srv_->run_one();
+ } catch (...) {
+ // Suppress errors, as the DHCPv6 server does.
+ }
+
+ // Make sure the server processed all packets in MT.
+ isc::util::MultiThreadingMgr::instance().getThreadPool().wait(3);
+}
+
+void
+Dhcp6Client::requestAddress(const uint32_t iaid,
+ const asiolink::IOAddress& address) {
+ client_ias_.push_back(ClientIA(Lease::TYPE_NA, iaid, address, 128));
+}
+
+void
+Dhcp6Client::requestPrefix(const uint32_t iaid,
+ const uint8_t prefix_len,
+ const asiolink::IOAddress& prefix) {
+ client_ias_.push_back(ClientIA(Lease::TYPE_PD, iaid, prefix, prefix_len));
+}
+
+void
+Dhcp6Client::useInterfaceId(const std::string& interface_id) {
+ OptionBuffer buf(interface_id.begin(), interface_id.end());
+ interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, buf));
+}
+
+void
+Dhcp6Client::useFQDN(const uint8_t flags, const std::string& fqdn_name,
+ Option6ClientFqdn::DomainNameType fqdn_type) {
+ fqdn_.reset(new Option6ClientFqdn(flags, fqdn_name, fqdn_type));
+}
+
+void
+Dhcp6Client::addExtraOption(const OptionPtr& opt) {
+ extra_options_.insert(std::make_pair(opt->getType(), opt));
+}
+
+void
+Dhcp6Client::clearExtraOptions() {
+ extra_options_.clear();
+}
+
+void
+Dhcp6Client::addClass(const ClientClass& client_class) {
+ if (!classes_.contains(client_class)) {
+ classes_.insert(client_class);
+ }
+}
+
+void
+Dhcp6Client::clearClasses() {
+ classes_.clear();
+}
+
+void
+Dhcp6Client::printConfiguration() const {
+
+ // Print DUID
+ std::cout << "Client " << (duid_ ? duid_->toText() : "(without duid)")
+ << " got " << getLeaseNum() << " lease(s): ";
+
+ // Print leases
+ for (int i = 0; i < getLeaseNum(); i++) {
+ Lease6 lease = getLease(i);
+ std::cout << lease.addr_.toText() << " ";
+ }
+
+ /// @todo: Print many other parameters.
+ std::cout << std::endl;
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc