summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/option6_client_fqdn.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/lib/dhcp/option6_client_fqdn.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/lib/dhcp/option6_client_fqdn.cc')
-rw-r--r--src/lib/dhcp/option6_client_fqdn.cc456
1 files changed, 456 insertions, 0 deletions
diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc
new file mode 100644
index 0000000..573a6ba
--- /dev/null
+++ b/src/lib/dhcp/option6_client_fqdn.cc
@@ -0,0 +1,456 @@
+// Copyright (C) 2013-2022 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/option6_client_fqdn.h>
+#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements the logic for the Option6ClientFqdn class.
+///
+/// The purpose of the class is to separate the implementation details
+/// of the Option6ClientFqdn class from the interface. This implementation
+/// uses kea-libdns classes to process FQDNs. At some point it may be
+/// desired to split kea-libdhcp++ from kea-libdns. In such case the
+/// implementation of this class may be changed. The declaration of the
+/// Option6ClientFqdn class holds the pointer to implementation, so
+/// the transition to a different implementation would not affect the
+/// header file.
+class Option6ClientFqdnImpl {
+public:
+ /// Holds flags carried by the option.
+ uint8_t flags_;
+ /// Holds the pointer to a domain name carried in the option.
+ boost::shared_ptr<isc::dns::Name> domain_name_;
+ /// Indicates whether domain name is partial or fully qualified.
+ Option6ClientFqdn::DomainNameType domain_name_type_;
+
+ /// @brief Constructor, from domain name.
+ ///
+ /// @param flags A value of the flags option field.
+ /// @param domain_name A domain name carried by the option given in the
+ /// textual format.
+ /// @param name_type A value which indicates whether domain-name
+ /// is partial or fully qualified.
+ Option6ClientFqdnImpl(const uint8_t flags,
+ const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ /// @brief Constructor, from wire data.
+ ///
+ /// @param first An iterator pointing to the beginning of the option data
+ /// in the wire format.
+ /// @param last An iterator pointing to the end of the option data in the
+ /// wire format.
+ Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor.
+ ///
+ /// @param source An object being copied.
+ Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source);
+
+ /// @brief Assignment operator.
+ ///
+ /// @param source An object which is being assigned.
+ Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source);
+
+ /// @brief Set a new domain name for the option.
+ ///
+ /// @param domain_name A new domain name to be assigned.
+ /// @param name_type A value which indicates whether the domain-name is
+ /// partial or fully qualified.
+ void setDomainName(const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ /// @brief Check if flags are valid.
+ ///
+ /// In particular, this function checks if the N and S bits are not
+ /// set to 1 in the same time.
+ ///
+ /// @param flags A value carried by the flags field of the option.
+ /// @param check_mbz A boolean value which indicates if this function should
+ /// check if the MBZ bits are set (if true). This parameter should be set
+ /// to false when validating flags in the received message. This is because
+ /// server should ignore MBZ bits in received messages.
+ /// @throw InvalidOption6FqdnFlags if flags are invalid.
+ static void checkFlags(const uint8_t flags, const bool check_mbz);
+
+ /// @brief Parse the Option provided in the wire format.
+ ///
+ /// @param first An iterator pointing to the beginning of the option data
+ /// in the wire format.
+ /// @param last An iterator pointing to the end of the option data in the
+ /// wire format.
+ void parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+};
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const uint8_t flags,
+ const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not
+ // passed by reference. Note that it accepts the non-const
+ // enum to be passed by value. In both cases it is
+ // unnecessary to pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option6ClientFqdn::DomainNameType name_type)
+ : flags_(flags),
+ domain_name_(),
+ domain_name_type_(name_type) {
+
+ // Check if flags are correct. Also check if MBZ bits are set.
+ checkFlags(flags_, true);
+ // Set domain name. It may throw an exception if domain name has wrong
+ // format.
+ setDomainName(domain_name, name_type);
+}
+
+Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ parseWireData(first, last);
+ // Verify that flags value was correct. Do not check if MBZ bits are
+ // set because we should ignore those bits in received message.
+ checkFlags(flags_, false);
+}
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
+ : flags_(source.flags_),
+ domain_name_(),
+ domain_name_type_(source.domain_name_type_) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+ }
+}
+
+Option6ClientFqdnImpl&
+// This assignment operator handles assignment to self, it copies all
+// required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+ } else {
+ domain_name_.reset();
+
+ }
+
+ // This assignment should be exception safe.
+ flags_ = source.flags_;
+ domain_name_type_ = source.domain_name_type_;
+
+ return (*this);
+}
+
+void
+Option6ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not
+ // passed by reference. Note that it accepts the non-const
+ // enum to be passed by value. In both cases it is
+ // unnecessary to pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option6ClientFqdn::DomainNameType name_type) {
+ // domain-name must be trimmed. Otherwise, string comprising spaces only
+ // would be treated as a fully qualified name.
+ std::string name = isc::util::str::trim(domain_name);
+ if (name.empty()) {
+ if (name_type == Option6ClientFqdn::FULL) {
+ isc_throw(InvalidOption6FqdnDomainName,
+ "fully qualified domain-name must not be empty"
+ << " when setting new domain-name for DHCPv6 Client"
+ << " FQDN Option");
+ }
+ // The special case when domain-name is empty is marked by setting the
+ // pointer to the domain-name object to NULL.
+ domain_name_.reset();
+
+ } else {
+ try {
+ domain_name_.reset(new isc::dns::Name(name, true));
+
+ } catch (const Exception&) {
+ isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
+ << domain_name << "' when setting new domain-name for"
+ << " DHCPv6 Client FQDN Option");
+
+ }
+ }
+
+ domain_name_type_ = name_type;
+}
+
+void
+Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
+ // The Must Be Zero (MBZ) bits must not be set.
+ if (check_mbz && ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0)) {
+ isc_throw(InvalidOption6FqdnFlags,
+ "invalid DHCPv6 Client FQDN Option flags: 0x"
+ << std::hex << static_cast<int>(flags) << std::dec);
+ }
+
+ // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
+ // MUST be 0. Checking it here.
+ if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
+ == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
+ isc_throw(InvalidOption6FqdnFlags,
+ "both N and S flag of the DHCPv6 Client FQDN Option are set."
+ << " According to RFC 4704, if the N bit is 1 the S bit"
+ << " MUST be 0");
+ }
+}
+
+void
+Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+
+ // Buffer must comprise at least one byte with the flags.
+ // The domain-name may be empty.
+ if (std::distance(first, last) < Option6ClientFqdn::FLAG_FIELD_LEN) {
+ isc_throw(OutOfRange, "DHCPv6 Client FQDN Option ("
+ << D6O_CLIENT_FQDN << ") is truncated. Minimal option"
+ << " size is " << Option6ClientFqdn::FLAG_FIELD_LEN
+ << ", got option with size " << std::distance(first, last));
+ }
+
+ // Parse flags
+ flags_ = *(first++);
+
+ // Parse domain-name if any.
+ if (std::distance(first, last) > 0) {
+ // The FQDN may comprise a partial domain-name. In this case it lacks
+ // terminating 0. If this is the case, we will need to add zero at
+ // the end because Name object constructor requires it.
+ if (*(last - 1) != 0) {
+ // Create temporary buffer and add terminating zero.
+ OptionBuffer buf(first, last);
+ buf.push_back(0);
+ // Reset domain name.
+ isc::util::InputBuffer name_buf(&buf[0], buf.size());
+ try {
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception&) {
+ isc_throw(InvalidOption6FqdnDomainName, "failed to parse "
+ "partial domain-name from wire format");
+ }
+ // Terminating zero was missing, so set the domain-name type
+ // to partial.
+ domain_name_type_ = Option6ClientFqdn::PARTIAL;
+ } else {
+ // We are dealing with fully qualified domain name so there is
+ // no need to add terminating zero. Simply pass the buffer to
+ // Name object constructor.
+ isc::util::InputBuffer name_buf(&(*first),
+ std::distance(first, last));
+ try {
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception&) {
+ isc_throw(InvalidOption6FqdnDomainName, "failed to parse "
+ "fully qualified domain-name from wire format");
+ }
+ // Set the domain-type to fully qualified domain name.
+ domain_name_type_ = Option6ClientFqdn::FULL;
+ }
+ }
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
+ impl_(new Option6ClientFqdnImpl(first, last)) {
+}
+
+Option6ClientFqdn::~Option6ClientFqdn() {
+ delete(impl_);
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
+ : Option(source),
+ impl_(new Option6ClientFqdnImpl(*source.impl_)) {
+}
+
+OptionPtr
+Option6ClientFqdn::clone() const {
+ return (cloneInternal<Option6ClientFqdn>());
+}
+
+Option6ClientFqdn&
+// This assignment operator handles assignment to self, it uses copy
+// constructor of Option6ClientFqdnImpl to copy all required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+ Option::operator=(source);
+ Option6ClientFqdnImpl* old_impl = impl_;
+ impl_ = new Option6ClientFqdnImpl(*source.impl_);
+ delete(old_impl);
+ return (*this);
+}
+
+bool
+Option6ClientFqdn::getFlag(const uint8_t flag) const {
+ // Caller should query for one of the: N, S or O flags. Any other
+ // value is invalid.
+ if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
+ isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+ << " Option flag specified, expected N, S or O");
+ }
+
+ return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option6ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
+ // Check that flag is in range between 0x1 and 0x7. Note that this
+ // allows to set or clear multiple flags concurrently. Setting
+ // concurrent bits is discouraged (see header file) but it is not
+ // checked here so it will work.
+ if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+ isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+ << " Option flag 0x" << std::hex
+ << static_cast<int>(flag) << std::dec
+ << " is being set. Expected: N, S or O");
+ }
+
+ // Copy the current flags into local variable. That way we will be able
+ // to test new flags settings before applying them.
+ uint8_t new_flag = impl_->flags_;
+ if (set_flag) {
+ new_flag |= flag;
+ } else {
+ new_flag &= ~flag;
+ }
+
+ // Check new flags. If they are valid, apply them.
+ Option6ClientFqdnImpl::checkFlags(new_flag, true);
+ impl_->flags_ = new_flag;
+}
+
+void
+Option6ClientFqdn::resetFlags() {
+ impl_->flags_ = 0;
+}
+
+std::string
+Option6ClientFqdn::getDomainName() const {
+ if (impl_->domain_name_) {
+ return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+ PARTIAL));
+ }
+ // If an object holding domain-name is NULL it means that the domain-name
+ // is empty.
+ return ("");
+}
+
+void
+Option6ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+ // There is nothing to do if domain-name is empty.
+ if (!impl_->domain_name_) {
+ return;
+ }
+
+ // Domain name, encoded as a set of labels.
+ isc::dns::LabelSequence labels(*impl_->domain_name_);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ if (impl_->domain_name_type_ == PARTIAL) {
+ --read_len;
+ }
+ buf.writeData(data, read_len);
+ }
+}
+
+void
+Option6ClientFqdn::setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type) {
+ impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option6ClientFqdn::resetDomainName() {
+ setDomainName("", PARTIAL);
+}
+
+Option6ClientFqdn::DomainNameType
+Option6ClientFqdn::getDomainNameType() const {
+ return (impl_->domain_name_type_);
+}
+
+void
+Option6ClientFqdn::pack(isc::util::OutputBuffer& buf, bool) const {
+ // Header = option code and length.
+ packHeader(buf);
+ // Flags field.
+ buf.writeUint8(impl_->flags_);
+ // Domain name.
+ packDomainName(buf);
+}
+
+void
+Option6ClientFqdn::unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ setData(first, last);
+ impl_->parseWireData(first, last);
+ // Check that the flags in the received option are valid. Ignore MBZ bits
+ // because we don't want to discard the whole option because of MBZ bits
+ // being set.
+ impl_->checkFlags(impl_->flags_, false);
+}
+
+std::string
+Option6ClientFqdn::toText(int indent) const {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ stream << in << "type=" << type_ << "(CLIENT_FQDN), "
+ << "flags: ("
+ << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+ << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+ << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+ << "domain-name='" << getDomainName() << "' ("
+ << (getDomainNameType() == PARTIAL ? "partial" : "full")
+ << ")";
+
+ return (stream.str());
+}
+
+uint16_t
+Option6ClientFqdn::len() const {
+ uint16_t domain_name_length = 0;
+ if (impl_->domain_name_) {
+ // If domain name is partial, the NULL terminating character
+ // is not included and the option. Length has to be adjusted.
+ domain_name_length = impl_->domain_name_type_ == FULL ?
+ impl_->domain_name_->getLength() :
+ impl_->domain_name_->getLength() - 1;
+ }
+ return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace