summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/host.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/host.cc')
-rw-r--r--src/lib/dhcpsrv/host.cc724
1 files changed, 724 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/host.cc b/src/lib/dhcpsrv/host.cc
new file mode 100644
index 0000000..30ac476
--- /dev/null
+++ b/src/lib/dhcpsrv/host.cc
@@ -0,0 +1,724 @@
+// 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 <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <cryptolink/crypto_rng.h>
+#include <dhcp/pkt4.h>
+#include <dhcpsrv/host.h>
+#include <exceptions/exceptions.h>
+
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+AuthKey::AuthKey(const std::vector<uint8_t>& key) {
+ setAuthKey(key);
+}
+
+AuthKey::AuthKey(const std::string& key) {
+ setAuthKey(key);
+}
+
+AuthKey::AuthKey() {
+ authKey_ = AuthKey::getRandomKeyString();
+}
+
+std::vector<uint8_t>
+AuthKey::getRandomKeyString() {
+ return (isc::cryptolink::random(AUTH_KEY_LEN));
+}
+
+std::string
+AuthKey::toText() const {
+ if (authKey_.empty()) {
+ return ("");
+ }
+ return (util::encode::encodeHex(authKey_));
+}
+
+void
+AuthKey::setAuthKey(const std::vector<uint8_t>& key) {
+ authKey_ = key;
+ if (authKey_.size() > AUTH_KEY_LEN) {
+ authKey_.resize(AUTH_KEY_LEN);
+ }
+}
+
+void
+AuthKey::setAuthKey(const std::string& key) {
+ if (key.empty()) {
+ authKey_.clear();
+ return;
+ }
+ try {
+ std::vector<uint8_t> bin;
+ util::encode::decodeHex(key, bin);
+ setAuthKey(bin);
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "bad auth key: " << ex.what());
+ }
+}
+
+bool
+AuthKey::operator==(const AuthKey& other) const {
+ return (authKey_ == other.authKey_);
+}
+
+bool
+AuthKey::operator!=(const AuthKey& other) const {
+ return (authKey_ != other.authKey_);
+}
+
+IPv6Resrv::IPv6Resrv(const Type& type,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len)
+ : type_(type), prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
+ // Validate and set the actual values.
+ set(type, prefix, prefix_len);
+}
+
+void
+IPv6Resrv::set(const Type& type, const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) {
+ if (!prefix.isV6() || prefix.isV6Multicast()) {
+ isc_throw(isc::BadValue, "invalid prefix '" << prefix
+ << "' for new IPv6 reservation");
+
+ } else if (prefix_len > 128) {
+ isc_throw(isc::BadValue, "invalid prefix length '"
+ << static_cast<int>(prefix_len)
+ << "' for new IPv6 reservation");
+
+ } else if ((type == TYPE_NA) && (prefix_len != 128)) {
+ isc_throw(isc::BadValue, "invalid prefix length '"
+ << static_cast<int>(prefix_len)
+ << "' for reserved IPv6 address, expected 128");
+ }
+
+ type_ = type;
+ prefix_ = prefix;
+ prefix_len_ = prefix_len;
+}
+
+std::string
+IPv6Resrv::toText() const {
+ std::ostringstream s;
+ s << prefix_;
+ // For PD, append prefix length.
+ if (getType() == TYPE_PD) {
+ s << "/" << static_cast<int>(prefix_len_);
+ }
+ return (s.str());
+}
+
+bool
+IPv6Resrv::operator==(const IPv6Resrv& other) const {
+ return (type_ == other.type_ &&
+ prefix_ == other.prefix_ &&
+ prefix_len_ == other.prefix_len_);
+}
+
+bool
+IPv6Resrv::operator!=(const IPv6Resrv& other) const {
+ return (!operator==(other));
+}
+
+Host::Host(const uint8_t* identifier, const size_t identifier_len,
+ const IdentifierType& identifier_type,
+ const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+ const asiolink::IOAddress& ipv4_reservation,
+ const std::string& hostname,
+ const std::string& dhcp4_client_classes,
+ const std::string& dhcp6_client_classes,
+ const asiolink::IOAddress& next_server,
+ const std::string& server_host_name,
+ const std::string& boot_file_name,
+ const AuthKey& auth_key)
+
+ : identifier_type_(identifier_type),
+ identifier_value_(), ipv4_subnet_id_(ipv4_subnet_id),
+ ipv6_subnet_id_(ipv6_subnet_id),
+ ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
+ dhcp6_client_classes_(dhcp6_client_classes),
+ next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ server_host_name_(server_host_name), boot_file_name_(boot_file_name),
+ host_id_(0), cfg_option4_(new CfgOption()),
+ cfg_option6_(new CfgOption()), negative_(false),
+ key_(auth_key) {
+
+ // Initialize host identifier.
+ setIdentifier(identifier, identifier_len, identifier_type);
+
+ if (!ipv4_reservation.isV4Zero()) {
+ // Validate and set IPv4 address reservation.
+ setIPv4Reservation(ipv4_reservation);
+ }
+
+ if (!next_server.isV4Zero()) {
+ // Validate and set next server address.
+ setNextServer(next_server);
+ }
+}
+
+Host::Host(const std::string& identifier, const std::string& identifier_name,
+ const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+ const asiolink::IOAddress& ipv4_reservation,
+ const std::string& hostname,
+ const std::string& dhcp4_client_classes,
+ const std::string& dhcp6_client_classes,
+ const asiolink::IOAddress& next_server,
+ const std::string& server_host_name,
+ const std::string& boot_file_name,
+ const AuthKey& auth_key)
+ : identifier_type_(IDENT_HWADDR),
+ identifier_value_(), ipv4_subnet_id_(ipv4_subnet_id),
+ ipv6_subnet_id_(ipv6_subnet_id),
+ ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
+ dhcp6_client_classes_(dhcp6_client_classes),
+ next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ server_host_name_(server_host_name), boot_file_name_(boot_file_name),
+ host_id_(0), cfg_option4_(new CfgOption()),
+ cfg_option6_(new CfgOption()), negative_(false),
+ key_(auth_key) {
+
+ // Initialize host identifier.
+ setIdentifier(identifier, identifier_name);
+
+ if (!ipv4_reservation.isV4Zero()) {
+ // Validate and set IPv4 address reservation.
+ setIPv4Reservation(ipv4_reservation);
+ }
+
+ if (!next_server.isV4Zero()) {
+ // Validate and set next server address.
+ setNextServer(next_server);
+ }
+}
+
+size_t
+Host::getIdentifierMaxLength(const IdentifierType& type) {
+ switch (type) {
+ case IDENT_HWADDR:
+ return (HWAddr::MAX_HWADDR_LEN);
+ case IDENT_DUID:
+ return (DUID::MAX_DUID_LEN);
+ case IDENT_CLIENT_ID:
+ return (ClientId::MAX_CLIENT_ID_LEN);
+ default:
+ // In fact it is backend dependent but for compatibility we take
+ // the lowest value.
+ return (128);
+ }
+}
+
+const std::vector<uint8_t>&
+Host::getIdentifier() const {
+ return (identifier_value_);
+}
+
+Host::IdentifierType
+Host::getIdentifierType() const {
+ return (identifier_type_);
+}
+
+Host::IdentifierType
+Host::getIdentifierType(const std::string& identifier_name) {
+ if (identifier_name == "hw-address") {
+ return (IDENT_HWADDR);
+
+ } else if (identifier_name == "duid") {
+ return (IDENT_DUID);
+
+ } else if (identifier_name == "circuit-id") {
+ return (IDENT_CIRCUIT_ID);
+
+ } else if (identifier_name == "client-id") {
+ return (IDENT_CLIENT_ID);
+ } else if (identifier_name == "flex-id") {
+ return (IDENT_FLEX);
+ } else {
+ isc_throw(isc::BadValue, "invalid client identifier type '"
+ << identifier_name << "'");
+ }
+}
+
+HWAddrPtr
+Host::getHWAddress() const {
+ return ((identifier_type_ == IDENT_HWADDR) ?
+ HWAddrPtr(new HWAddr(identifier_value_, HTYPE_ETHER)) : HWAddrPtr());
+}
+
+DuidPtr
+Host::getDuid() const {
+ return ((identifier_type_ == IDENT_DUID) ?
+ DuidPtr(new DUID(identifier_value_)) : DuidPtr());
+}
+
+
+std::string
+Host::getIdentifierAsText() const {
+ return (getIdentifierAsText(identifier_type_, &identifier_value_[0],
+ identifier_value_.size()));
+}
+
+std::string
+Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
+ const size_t length) {
+ // Convert identifier into <type>=<value> form.
+ std::ostringstream s;
+ switch (type) {
+ case IDENT_HWADDR:
+ s << "hwaddr";
+ break;
+ case IDENT_DUID:
+ s << "duid";
+ break;
+ case IDENT_CIRCUIT_ID:
+ s << "circuit-id";
+ break;
+ case IDENT_CLIENT_ID:
+ s << "client-id";
+ break;
+ case IDENT_FLEX:
+ s << "flex-id";
+ break;
+ default:
+ // This should never happen actually, unless we add new identifier
+ // and forget to add a case for it above.
+ s << "(invalid-type)";
+ }
+ std::vector<uint8_t> vec(value, value + length);
+ s << "=" << (length > 0 ? util::encode::encodeHex(vec) : "(null)");
+ return (s.str());
+}
+
+std::string
+Host::getIdentifierName(const IdentifierType& type) {
+ switch (type) {
+ case Host::IDENT_HWADDR:
+ return ("hw-address");
+
+ case Host::IDENT_DUID:
+ return ("duid");
+
+ case Host::IDENT_CIRCUIT_ID:
+ return ("circuit-id");
+
+ case Host::IDENT_CLIENT_ID:
+ return ("client-id");
+
+ case Host::IDENT_FLEX:
+ return ("flex-id");
+
+ default:
+ ;
+ }
+ return ("(unknown)");
+}
+
+
+void
+Host::setIdentifier(const uint8_t* identifier, const size_t len,
+ const IdentifierType& type) {
+ if (len < 1) {
+ isc_throw(BadValue, "invalid client identifier length 0");
+ } else if (len > getIdentifierMaxLength(type)) {
+ isc_throw(BadValue, "too long client identifier type "
+ << getIdentifierName(type)
+ << " length " << len);
+ }
+
+ identifier_type_ = type;
+ identifier_value_.assign(identifier, identifier + len);
+}
+
+void
+Host::setIdentifier(const std::string& identifier, const std::string& name) {
+ // Empty identifier is not allowed.
+ if (identifier.empty()) {
+ isc_throw(isc::BadValue, "empty host identifier used");
+ }
+
+ // Set identifier type.
+ identifier_type_ = getIdentifierType(name);
+
+ // Identifier value can either be specified as string of hexadecimal
+ // digits or a string in quotes. The latter is copied to a vector excluding
+ // quote characters.
+
+ // Try to convert the values in quotes into a vector of ASCII codes.
+ // If the identifier lacks opening and closing quote, this will return
+ // an empty value, in which case we'll try to decode it as a string of
+ // hexadecimal digits.
+ bool too_long = false;
+ try {
+ std::vector<uint8_t> binary = util::str::quotedStringToBinary(identifier);
+ if (binary.empty()) {
+ util::str::decodeFormattedHexString(identifier, binary);
+ }
+
+ size_t len = binary.size();
+ if (len > getIdentifierMaxLength(identifier_type_)) {
+ // Message does not matter as it will be replaced below...
+ too_long = true;
+ isc_throw(BadValue, "too long client identifier type " << name
+ << " length " << len);
+ }
+
+ // Successfully decoded the identifier, so let's use it.
+ identifier_value_.swap(binary);
+
+ } catch (...) {
+ // The string doesn't match any known pattern, so we have to
+ // report an error at this point.
+ if (too_long) {
+ throw;
+ }
+ isc_throw(isc::BadValue, "invalid host identifier value '"
+ << identifier << "'");
+ }
+}
+
+void
+Host::setIdentifierType(const IdentifierType& type) {
+ identifier_type_ = type;
+}
+
+void
+Host::setIPv4Reservation(const asiolink::IOAddress& address) {
+ if (!address.isV4()) {
+ isc_throw(isc::BadValue, "address '" << address << "' is not a valid"
+ " IPv4 address");
+ } else if (address.isV4Zero() || address.isV4Bcast()) {
+ isc_throw(isc::BadValue, "must not make reservation for the '"
+ << address << "' address");
+ }
+ ipv4_reservation_ = address;
+}
+
+void
+Host::removeIPv4Reservation() {
+ ipv4_reservation_ = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+}
+
+void
+Host::addReservation(const IPv6Resrv& reservation) {
+ // Check if it is not duplicating existing reservation.
+ if (hasReservation(reservation)) {
+ isc_throw(isc::InvalidOperation, "failed on attempt to add a duplicated"
+ " host reservation for " << reservation.toText());
+ }
+ // Add it.
+ ipv6_reservations_.insert(IPv6ResrvTuple(reservation.getType(),
+ reservation));
+}
+
+IPv6ResrvRange
+Host::getIPv6Reservations(const IPv6Resrv::Type& type) const {
+ return (ipv6_reservations_.equal_range(type));
+}
+
+IPv6ResrvRange
+Host::getIPv6Reservations() const {
+ return (IPv6ResrvRange(ipv6_reservations_.begin(),
+ ipv6_reservations_.end()));
+}
+
+bool
+Host::hasIPv6Reservation() const {
+ return (!ipv6_reservations_.empty());
+}
+
+bool
+Host::hasReservation(const IPv6Resrv& reservation) const {
+ IPv6ResrvRange reservations = getIPv6Reservations(reservation.getType());
+ if (std::distance(reservations.first, reservations.second) > 0) {
+ for (IPv6ResrvIterator it = reservations.first;
+ it != reservations.second; ++it) {
+ if (it->second == reservation) {
+ return (true);
+ }
+ }
+ }
+
+ // No matching reservations found.
+ return (false);
+}
+
+void
+Host::addClientClass4(const std::string& class_name) {
+ addClientClassInternal(dhcp4_client_classes_, class_name);
+}
+
+
+void
+Host::addClientClass6(const std::string& class_name) {
+ addClientClassInternal(dhcp6_client_classes_, class_name);
+}
+
+void
+Host::addClientClassInternal(ClientClasses& classes,
+ const std::string& class_name) {
+ std::string trimmed = util::str::trim(class_name);
+ if (!trimmed.empty()) {
+ classes.insert(ClientClass(trimmed));
+ }
+}
+
+void
+Host::setNextServer(const asiolink::IOAddress& next_server) {
+ if (!next_server.isV4()) {
+ isc_throw(isc::BadValue, "next server address '" << next_server
+ << "' is not a valid IPv4 address");
+ } else if (next_server.isV4Bcast()) {
+ isc_throw(isc::BadValue, "invalid next server address '"
+ << next_server << "'");
+ }
+
+ next_server_ = next_server;
+}
+
+void
+Host::setServerHostname(const std::string& server_host_name) {
+ if (server_host_name.size() > Pkt4::MAX_SNAME_LEN - 1) {
+ isc_throw(isc::BadValue, "server hostname length must not exceed "
+ << (Pkt4::MAX_SNAME_LEN - 1));
+ }
+ server_host_name_ = server_host_name;
+}
+
+void
+Host::setBootFileName(const std::string& boot_file_name) {
+ if (boot_file_name.size() > Pkt4::MAX_FILE_LEN - 1) {
+ isc_throw(isc::BadValue, "boot file length must not exceed "
+ << (Pkt4::MAX_FILE_LEN - 1));
+ }
+ boot_file_name_ = boot_file_name;
+}
+
+ElementPtr
+Host::toElement4() const {
+
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ // Set the user context
+ contextToElement(map);
+ // Set the identifier
+ Host::IdentifierType id_type = getIdentifierType();
+ if (id_type == Host::IDENT_HWADDR) {
+ HWAddrPtr hwaddr = getHWAddress();
+ map->set("hw-address", Element::create(hwaddr->toText(false)));
+ } else if (id_type == Host::IDENT_DUID) {
+ DuidPtr duid = getDuid();
+ map->set("duid", Element::create(duid->toText()));
+ } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string circuit_id = util::encode::encodeHex(bin);
+ map->set("circuit-id", Element::create(circuit_id));
+ } else if (id_type == Host::IDENT_CLIENT_ID) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string client_id = util::encode::encodeHex(bin);
+ map->set("client-id", Element::create(client_id));
+ } else if (id_type == Host::IDENT_FLEX) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string flex = util::encode::encodeHex(bin);
+ map->set("flex-id", Element::create(flex));
+ } else {
+ isc_throw(ToElementError, "invalid identifier type: " << id_type);
+ }
+ // Set the reservation (if not 0.0.0.0 which may not be re-read)
+ const IOAddress& address = getIPv4Reservation();
+ if (!address.isV4Zero()) {
+ map->set("ip-address", Element::create(address.toText()));
+ }
+ // Set the hostname
+ const std::string& hostname = getHostname();
+ map->set("hostname", Element::create(hostname));
+ // Set next-server
+ const IOAddress& next_server = getNextServer();
+ map->set("next-server", Element::create(next_server.toText()));
+ // Set server-hostname
+ const std::string& server_hostname = getServerHostname();
+ map->set("server-hostname", Element::create(server_hostname));
+ // Set boot-file-name
+ const std::string& boot_file_name = getBootFileName();
+ map->set("boot-file-name", Element::create(boot_file_name));
+ // Set client-classes
+ const ClientClasses& cclasses = getClientClasses4();
+ ElementPtr classes = Element::createList();
+ for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+ cclass != cclasses.cend(); ++cclass) {
+ classes->add(Element::create(*cclass));
+ }
+ map->set("client-classes", classes);
+ // Set option-data
+ ConstCfgOptionPtr opts = getCfgOption4();
+ map->set("option-data", opts->toElement());
+
+ return (map);
+}
+
+ElementPtr
+Host::toElement6() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ // Set the user context
+ contextToElement(map);
+ // Set the identifier
+ Host::IdentifierType id_type = getIdentifierType();
+ if (id_type == Host::IDENT_HWADDR) {
+ HWAddrPtr hwaddr = getHWAddress();
+ map->set("hw-address", Element::create(hwaddr->toText(false)));
+ } else if (id_type == Host::IDENT_DUID) {
+ DuidPtr duid = getDuid();
+ map->set("duid", Element::create(duid->toText()));
+ } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+ isc_throw(ToElementError, "unexpected circuit-id DUID type");
+ } else if (id_type == Host::IDENT_CLIENT_ID) {
+ isc_throw(ToElementError, "unexpected client-id DUID type");
+ } else if (id_type == Host::IDENT_FLEX) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string flex = util::encode::encodeHex(bin);
+ map->set("flex-id", Element::create(flex));
+ } else {
+ isc_throw(ToElementError, "invalid DUID type: " << id_type);
+ }
+ // Set reservations (ip-addresses)
+ IPv6ResrvRange na_resv = getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ElementPtr resvs = Element::createList();
+ for (IPv6ResrvIterator resv = na_resv.first;
+ resv != na_resv.second; ++resv) {
+ resvs->add(Element::create(resv->second.toText()));
+ }
+ map->set("ip-addresses", resvs);
+ // Set reservations (prefixes)
+ IPv6ResrvRange pd_resv = getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ resvs = Element::createList();
+ for (IPv6ResrvIterator resv = pd_resv.first;
+ resv != pd_resv.second; ++resv) {
+ resvs->add(Element::create(resv->second.toText()));
+ }
+ map->set("prefixes", resvs);
+ // Set the hostname
+ const std::string& hostname = getHostname();
+ map->set("hostname", Element::create(hostname));
+ // Set client-classes
+ const ClientClasses& cclasses = getClientClasses6();
+ ElementPtr classes = Element::createList();
+ for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+ cclass != cclasses.cend(); ++cclass) {
+ classes->add(Element::create(*cclass));
+ }
+ map->set("client-classes", classes);
+
+ // Set option-data
+ ConstCfgOptionPtr opts = getCfgOption6();
+ map->set("option-data", opts->toElement());
+
+ // Set auth key
+ //@todo: uncomment once storing in configuration file is enabled
+ //map->set("auth-key", Element::create(getKey().toText()));
+
+ return (map);
+}
+
+void
+Host::encapsulateOptions() const {
+ if (!cfg_option4_->isEncapsulated()) {
+ cfg_option4_->encapsulate();
+ }
+ if (!cfg_option6_->isEncapsulated()) {
+ cfg_option6_->encapsulate();
+ }
+}
+
+std::string
+Host::toText() const {
+ std::ostringstream s;
+
+ // Add HW address or DUID.
+ s << getIdentifierAsText();
+
+ // Add IPv4 subnet id if exists.
+ if (ipv4_subnet_id_ != SUBNET_ID_UNUSED) {
+ s << " ipv4_subnet_id=" << ipv4_subnet_id_;
+ }
+
+ // Add IPv6 subnet id if exists.
+ if (ipv6_subnet_id_ != SUBNET_ID_UNUSED) {
+ s << " ipv6_subnet_id=" << ipv6_subnet_id_;
+ }
+
+ // Add hostname.
+ s << " hostname=" << (hostname_.empty() ? "(empty)" : hostname_);
+
+ // Add IPv4 reservation.
+ s << " ipv4_reservation=" << (ipv4_reservation_.isV4Zero() ? "(no)" :
+ ipv4_reservation_.toText());
+
+ // Add next server.
+ s << " siaddr=" << (next_server_.isV4Zero() ? "(no)" :
+ next_server_.toText());
+
+ // Add server host name.
+ s << " sname=" << (server_host_name_.empty() ? "(empty)" : server_host_name_);
+
+ // Add boot file name.
+ s << " file=" << (boot_file_name_.empty() ? "(empty)" : boot_file_name_);
+
+ s << " key=" << (key_.toText().empty() ? "(empty)" : key_.toText());
+
+ if (ipv6_reservations_.empty()) {
+ s << " ipv6_reservations=(none)";
+
+ } else {
+ // Add all IPv6 reservations.
+ for (IPv6ResrvIterator resrv = ipv6_reservations_.begin();
+ resrv != ipv6_reservations_.end(); ++resrv) {
+ s << " ipv6_reservation"
+ << std::distance(ipv6_reservations_.begin(), resrv)
+ << "=" << resrv->second.toText();
+ }
+ }
+
+ // Add DHCPv4 client classes.
+ for (ClientClasses::const_iterator cclass = dhcp4_client_classes_.cbegin();
+ cclass != dhcp4_client_classes_.cend(); ++cclass) {
+ s << " dhcp4_class"
+ << std::distance(dhcp4_client_classes_.cbegin(), cclass)
+ << "=" << *cclass;
+ }
+
+ // Add DHCPv6 client classes.
+ for (ClientClasses::const_iterator cclass = dhcp6_client_classes_.cbegin();
+ cclass != dhcp6_client_classes_.cend(); ++cclass) {
+ s << " dhcp6_class"
+ << std::distance(dhcp6_client_classes_.cbegin(), cclass)
+ << "=" << *cclass;
+ }
+
+ // Add negative cached.
+ if (negative_) {
+ s << " negative cached";
+ }
+
+ return (s.str());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc