From: Piotrek Zadroga Date: Tue, 18 Apr 2023 17:59:51 +0000 (+0200) Subject: [#2536] Implementing DNRv4 Option X-Git-Tag: Kea-2.3.8~177 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d999c8efac267966f1312711fa7f2d04e4fee000;p=thirdparty%2Fkea.git [#2536] Implementing DNRv4 Option --- diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index 1a2c5b4181..414363aa25 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -35,7 +35,8 @@ libkea_dhcp___la_SOURCES += option.cc option.h libkea_dhcp___la_SOURCES += option_custom.cc option_custom.h libkea_dhcp___la_SOURCES += option_data_types.cc option_data_types.h libkea_dhcp___la_SOURCES += option_definition.cc option_definition.h -libkea_dhcp___la_SOURCES += option_dnr.cc option_dnr.h +libkea_dhcp___la_SOURCES += option4_dnr.cc option4_dnr.h +libkea_dhcp___la_SOURCES += option6_dnr.cc option6_dnr.h libkea_dhcp___la_SOURCES += option_int.h libkea_dhcp___la_SOURCES += option_int_array.h libkea_dhcp___la_SOURCES += option_opaque_data_tuples.cc option_opaque_data_tuples.h diff --git a/src/lib/dhcp/option4_dnr.cc b/src/lib/dhcp/option4_dnr.cc new file mode 100644 index 0000000000..f8d196dbc5 --- /dev/null +++ b/src/lib/dhcp/option4_dnr.cc @@ -0,0 +1,350 @@ +// Copyright (C) 2018-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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +Option4Dnr::Option4Dnr(OptionBufferConstIter begin, OptionBufferConstIter end) + : Option(V4, DHO_V4_DNR) { + unpack(begin, end); +} + +OptionPtr +Option4Dnr::clone() const { + return (cloneInternal()); +} + +void +Option4Dnr::pack(util::OutputBuffer& buf, bool check) const { + packHeader(buf, check); + for (const DnrInstance& dnr_instance : dnr_instances_) { + buf.writeUint16(dnr_instance.getDnrInstanceDataLength()); + buf.writeUint16(dnr_instance.getServicePriority()); + buf.writeUint8(dnr_instance.getAdnLength()); + dnr_instance.packAdn(buf); + if (dnr_instance.isAdnOnlyMode()) { + return; + } + buf.writeUint8(dnr_instance.getAddrLength()); + dnr_instance.packAddresses(buf); + dnr_instance.packSvcParams(buf); + } +} + +void +Option4Dnr::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + setData(begin, end); + size_t offset = 0; + while (offset < std::distance(begin, end)) { + if (std::distance(begin + offset, end) < getMinimalLength()) { + isc_throw(OutOfRange, "DHCPv4 Encrypted DNS Option (" << type_ << ") malformed: " + "DNR instance data truncated to size " + << std::distance(begin + offset, end)); + } + DnrInstance dnr_instance(V4); + dnr_instance.setDnrInstanceDataLength( + readUint16(&(*(begin + offset)), DNR_INSTANCE_DATA_LENGTH_SIZE)); + OptionBufferConstIter dnr_instance_end = begin + offset + + dnr_instance.getDnrInstanceDataLength(); + offset += DNR_INSTANCE_DATA_LENGTH_SIZE; + dnr_instance.setServicePriority( + readUint16(&(*(begin + offset)), SERVICE_PRIORITY_SIZE)); + offset += SERVICE_PRIORITY_SIZE; + + OpaqueDataTuple adn_tuple(OpaqueDataTuple::LENGTH_1_BYTE, begin + offset, dnr_instance_end); + auto adn_length = adn_tuple.getLength(); + dnr_instance.setAdnLength(adn_length); + + // Encrypted DNS options are designed to always include an authentication domain name, + // so when there is no FQDN included, we shall throw an exception. + if (adn_length == 0) { + isc_throw(InvalidOptionDnrDomainName, "Mandatory Authentication Domain Name fully " + "qualified domain-name is missing"); + } + + // Let's try to extract ADN FQDN data. + InputBuffer name_buf(&(*adn_tuple.getData().begin()), adn_length); + try { + auto adn = dnr_instance.getAdn(); + adn.reset(new isc::dns::Name(name_buf, true)); + } catch (const Exception& ex) { + isc_throw(InvalidOptionDnrDomainName, "failed to parse " + "fully qualified domain-name from wire format " + "- " << ex.what()); + } + + offset += adn_tuple.getTotalLength(); + + if (begin + offset == dnr_instance_end) { + // ADN only mode, other fields are not included. + addDnrInstance(dnr_instance); + continue; + } + dnr_instance.setAdnOnlyMode(false); + + OpaqueDataTuple addr_tuple(OpaqueDataTuple::LENGTH_1_BYTE, begin + offset, + dnr_instance_end); + auto addr_length = addr_tuple.getLength(); + dnr_instance.setAddrLength(addr_length); + // It MUST be a multiple of 4. + if ((addr_length % V4ADDRESS_LEN) != 0) { + isc_throw(OutOfRange, "DHCPv4 Encrypted DNS Option (" + << type_ << ")" + << " malformed: Addr Len=" << addr_length + << " is not divisible by 4"); + } + + // As per draft-ietf-add-dnr 3.1.8: + // If additional data is supplied (i.e. not ADN only mode), + // the option includes at least one valid IP address. + if (addr_length == 0) { + isc_throw(OutOfRange, "DHCPv4 Encrypted DNS Option (" + << type_ << ")" + << " malformed: Addr Len=" << addr_length + << " is not greater than 0"); + } + + offset += ADDR_LENGTH_SIZE; + OptionBufferConstIter addr_begin = begin + offset; + OptionBufferConstIter addr_end = addr_begin + addr_length; + auto ip_addresses = dnr_instance.getIpAddresses(); + + while (addr_begin != addr_end) { + const uint8_t* ptr = &(*addr_begin); + ip_addresses.push_back(IOAddress(readUint32(ptr, std::distance(addr_begin, addr_end)))); + addr_begin += V4ADDRESS_LEN; + offset += V4ADDRESS_LEN; + } + + // SvcParams (variable length) field is last. + auto svc_params_length = std::distance(begin + offset, dnr_instance_end); + if (svc_params_length > 0) { + std::string svc_params = dnr_instance.getSvcParams(); + svc_params.assign(begin + offset, dnr_instance_end); + dnr_instance.checkSvcParams(); + } + + addDnrInstance(dnr_instance); + } +} + +std::string +Option4Dnr::toText(int indent) const { + return Option::toText(indent); +} + +uint16_t +Option4Dnr::len() const { + uint16_t len = OPTION4_HDR_LEN; + for (const DnrInstance& dnr_instance : dnr_instances_) { + len += dnr_instance.getDnrInstanceDataLength() + DNR_INSTANCE_DATA_LENGTH_SIZE; + } + return (len); +} + +void +Option4Dnr::addDnrInstance(DnrInstance& dnr_instance) { + dnr_instances_.push_back(dnr_instance); +} + +void +DnrInstance::packAdn(util::OutputBuffer& buf) const { + if (!adn_) { + // This should not happen since Encrypted DNS options are designed + // to always include an authentication domain name. + isc_throw(InvalidOptionDnrDomainName, "Mandatory Authentication Domain Name fully " + "qualified domain-name is missing"); + } + isc::dns::LabelSequence label_sequence(*adn_); + if (label_sequence.getDataLength() > 0) { + size_t data_length = 0; + const uint8_t* data = label_sequence.getData(&data_length); + buf.writeData(data, data_length); + } +} + +void +DnrInstance::packAddresses(util::OutputBuffer& buf) const { + AddressContainer::const_iterator address = ip_addresses_.begin(); + while (address != ip_addresses_.end()) { + buf.writeUint32(address->toUint32()); + ++address; + } +} + +void +DnrInstance::packSvcParams(util::OutputBuffer& buf) const { + if (svc_params_length_ > 0) { + buf.writeData(&(*svc_params_.begin()), svc_params_length_); + } +} + +std::string +DnrInstance::getAdnAsText() const { + if (adn_) { + return (adn_->toText()); + } + return (""); +} + +void +DnrInstance::setAdn(const std::string& adn) { + std::string trimmed_adn = isc::util::str::trim(adn); + if (trimmed_adn.empty()) { + isc_throw(InvalidOptionDnrDomainName, "Mandatory Authentication Domain Name fully " + "qualified domain-name must not be empty"); + } + try { + adn_.reset(new isc::dns::Name(trimmed_adn, true)); + } catch (const Exception& ex) { + isc_throw(InvalidOptionDnrDomainName, "Failed to parse " + "fully qualified domain-name from string " + "- " << ex.what()); + } + size_t adn_len = 0; + isc::dns::LabelSequence label_sequence(*adn_); + label_sequence.getData(&adn_len); + if (adn_len > std::numeric_limits::max()) { + isc_throw(InvalidOptionDnrDomainName, + "Given ADN FQDN length " << adn_len << " is bigger than uint_16 MAX"); + } + + adn_length_ = adn_len; +} + +void +DnrInstance::checkSvcParams(bool from_wire_data) { + std::string svc_params = isc::util::str::trim(svc_params_); + if (svc_params.empty()) { + isc_throw(InvalidOptionDnrSvcParams, "Provided Svc Params field is empty"); + } + if (!from_wire_data) { + // If Service Params field was not parsed from on-wire data, + // but actually was provided with ctor, let's calculate svc_params_length_. + auto svc_params_len = svc_params.length(); + if (svc_params_len > std::numeric_limits::max()) { + isc_throw(OutOfRange, "Given Svc Params length " << svc_params_len + << " is bigger than uint_16 MAX"); + } + svc_params_length_ = svc_params_len; + // If Service Params field was not parsed from on-wire data, + // but actually was provided with ctor, let's replace it with trimmed value. + svc_params_ = svc_params; + } + + // SvcParams are a whitespace-separated list, with each SvcParam + // consisting of a SvcParamKey=SvcParamValue pair or a standalone SvcParamKey. + // SvcParams in presentation format MAY appear in any order, but keys MUST NOT be repeated. + + // Let's put all elements of a whitespace-separated list into a vector. + std::vector tokens = isc::util::str::tokens(svc_params, " "); + + // Set of keys used to check if a key is not repeated. + std::set keys; + // String sanitizer is used to check keys syntax. + util::str::StringSanitizerPtr sanitizer; + // SvcParamKeys are lower-case alphanumeric strings. Key names + // contain 1-63 characters from the ranges "a"-"z", "0"-"9", and "-". + std::string regex = "[^a-z0-9-]"; + sanitizer.reset(new util::str::StringSanitizer(regex, "")); + // The service parameters MUST NOT include + // "ipv4hint" or "ipv6hint" SvcParams as they are superseded by the + // included IP addresses. + std::set forbidden_keys = {"ipv4hint", "ipv6hint"}; + + // Now let's check each SvcParamKey=SvcParamValue pair. + for (const std::string& token : tokens) { + std::vector key_val = isc::util::str::tokens(token, "="); + if (key_val.size() > 2) { + isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - more than one " + "equals sign found in SvcParamKey=SvcParamValue " + "pair"); + } + + // SvcParam Key related checks come below. + std::string key = key_val[0]; + if (forbidden_keys.find(key) != forbidden_keys.end()) { + isc_throw(InvalidOptionDnrSvcParams, + "Wrong Svc Params syntax - key " << key << " must not be used"); + } + + auto insert_res = keys.insert(key); + if (!insert_res.second) { + isc_throw(InvalidOptionDnrSvcParams, + "Wrong Svc Params syntax - key " << key << " was duplicated"); + } + + if (key.length() > 63) { + isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - key had more than 63 " + "characters - " + << key); + } + + std::string sanitized_key = sanitizer->scrub(key); + if (sanitized_key.size() < key.size()) { + isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - invalid character " + "used in key - " + << key); + } + + if (key_val.size() == 2) { + // tbd Check value syntax + std::string value = key_val[1]; + } + } +} + +void +DnrInstance::checkFields() { + if (svc_params_.empty() && ip_addresses_.empty()) { + // ADN only mode, nothing more to do. + return; + } + if (!svc_params_.empty() && ip_addresses_.empty()) { + // As per draft-ietf-add-dnr 3.1.8: + // If additional data is supplied (i.e. not ADN only mode), + // the option includes at least one valid IP address. + isc_throw(OutOfRange, + getLogPrefix() + << " malformed: No IP address given. Since this is not " + "ADN only mode, at least one valid IP address must be included"); + } + if (!svc_params_.empty()) { + checkSvcParams(false); + } + adn_only_mode_ = false; + auto addr_len = ip_addresses_.size() * V6ADDRESS_LEN; + if (addr_len > std::numeric_limits::max()) { + isc_throw(OutOfRange, + "Given IPv6 addresses length " << addr_len << " is bigger than uint_16 MAX"); + } + addr_length_ = addr_len; +} + +std::string +DnrInstance::getLogPrefix() const { + return (universe_ == Option::V4) ? + ("DHCPv4 Encrypted DNS Option (" + std::to_string(DHO_V4_DNR) + ")") : + ("DHCPv6 Encrypted DNS Option (" + std::to_string(D6O_V6_DNR) + ")"); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcp/option_dnr.h b/src/lib/dhcp/option4_dnr.h similarity index 58% rename from src/lib/dhcp/option_dnr.h rename to src/lib/dhcp/option4_dnr.h index f343854e21..0848dce318 100644 --- a/src/lib/dhcp/option_dnr.h +++ b/src/lib/dhcp/option4_dnr.h @@ -4,8 +4,8 @@ // 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/. -#ifndef OPTION_DNR_H -#define OPTION_DNR_H +#ifndef OPTION4_DNR_H +#define OPTION4_DNR_H #include #include @@ -14,71 +14,30 @@ namespace isc { namespace dhcp { -/// @brief Exception thrown when invalid domain name is specified. -class InvalidOptionDnrDomainName : public Exception { +class DnrInstance { public: - InvalidOptionDnrDomainName(const char* file, size_t line, const char* what) - : isc::Exception(file, line, what) { - } -}; + DnrInstance(Option::Universe universe) : universe_(universe) {} -/// @brief Exception thrown when Service parameters have wrong format. -class InvalidOptionDnrSvcParams : public Exception { -public: - InvalidOptionDnrSvcParams(const char* file, size_t line, const char* what) - : isc::Exception(file, line, what) { - } -}; + virtual ~DnrInstance() {} -/// @brief Represents DHCPv6 Encrypted DNS %Option (code 144). -/// -/// This option has been defined in the draft-ietf-add-dnr-15 (to be replaced -/// with published RFC) and it has a following structure: -/// - option-code = 144 (2 octets) -/// - option-len (2 octets) -/// - Service Priority (2 octets) -/// - ADN Length (2 octets) -/// - Authentication Domain Name (variable length) -/// - Addr Length (2 octets) -/// - IPv6 Address(es) (variable length) -/// - Service Parameters (variable length). -class OptionDnr6 : public Option { -public: - /// @brief A container for (IPv6) addresses. + /// @brief A Type defined for container holding IP addresses. typedef std::vector AddressContainer; - /// @brief Size in octets of Service Priority field. - static const uint8_t SERVICE_PRIORITY_SIZE = 2; - - /// @brief Size in octets of ADN Length field. - static const uint8_t ADN_LENGTH_SIZE = 2; - - /// @brief Size in octets of Addr Length field. - static const uint8_t ADDR_LENGTH_SIZE = 2; - - /// @brief Constructor of the %Option from on-wire data. - /// - /// This constructor creates an instance of the option using a buffer with - /// on-wire data. It may throw an exception if the @c unpack method throws. - /// - /// @param begin Iterator pointing to the beginning of the buffer holding an - /// option. - /// @param end Iterator pointing to the end of the buffer holding an option. - OptionDnr6(OptionBufferConstIter begin, OptionBufferConstIter end); - - OptionDnr6(const uint16_t service_priority, const std::string& adn, const AddressContainer& ipv6_addresses, const std::string& svc_params); + const AddressContainer& getIpAddresses() const { + return ip_addresses_; + } - OptionDnr6(const uint16_t service_priority, const std::string& adn); + const boost::shared_ptr& getAdn() const { + return adn_; + } - virtual OptionPtr clone() const; - virtual void pack(util::OutputBuffer& buf, bool check = false) const; - virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); - virtual std::string toText(int indent = 0) const; - virtual uint16_t len() const; + uint16_t getDnrInstanceDataLength() const { + return (dnr_instance_data_length_); + } /// @brief Getter of the @c service_priority_. /// - /// @return The priority of this OPTION_V6_DNR instance compared to other instances. + /// @return The priority of this DNR instance compared to other instances. uint16_t getServicePriority() const { return (service_priority_); } @@ -95,11 +54,11 @@ public: /// FQDN data stored in @c adn_ is converted into text format and returned. /// /// @return Authentication domain name in the text format. - std::string getAdn() const; + std::string getAdnAsText() const; /// @brief Getter of the @c addr_length_. /// - /// @return Length of enclosed IPv6 addresses in octets. + /// @return Length of enclosed IP addresses in octets. uint16_t getAddrLength() const { return (addr_length_); } @@ -120,66 +79,51 @@ public: /// /// @return Address container with addresses. AddressContainer getAddresses() const { - return (ipv6_addresses_); + return (ip_addresses_); } /// @brief Getter of the @c svc_params_ field. /// /// @return Returns Service Parameters as a string. - std::string getSvcParams() const { - return (svc_params_); + const std::string& getSvcParams() const { + return svc_params_; } -protected: - /// @brief The priority of this OPTION_V6_DNR instance compared to other instances. - uint16_t service_priority_; - - /// @brief Length of the authentication-domain-name data in octets. - uint16_t adn_length_; - - /// @brief Authentication domain name field of variable length. - /// - /// Authentication domain name field of variable length holding - /// a fully qualified domain name of the encrypted DNS resolver. - /// This field is formatted as specified in Section 10 of RFC8415. - boost::shared_ptr adn_; - - /// @brief Length of enclosed IPv6 addresses in octets. - uint16_t addr_length_ = 0; - - /// @brief Vector container holding one or more IPv6 addresses. - /// - /// One or more IPv6 addresses to reach the encrypted DNS resolver. - /// An address can be link-local, ULA, or GUA. - AddressContainer ipv6_addresses_; - - /// @brief Length of Service Parameters field in octets. - uint16_t svc_params_length_ = 0; + bool isAdnOnlyMode() const { + return adn_only_mode_; + } - /// @brief Flag stating whether ADN only mode is used or not. + /// @brief Sets Authentication domain name from given string. /// - /// "Addr Length", "ipv6-address(es)", and "Service Parameters (SvcParams)" - /// fields are not present if the ADN-only mode is used. - bool adn_only_mode_ = true; - - /// @brief Service Parameters (SvcParams) (variable length). + /// Sets FQDN of the encrypted DNS resolver from given string. + /// It may throw an exception if parsing of the FQDN fails or if + /// provided FQDN length is bigger than uint16_t Max. + /// It also calculates and sets value of Addr length field. /// - /// Specifies a set of service parameters that are encoded - /// following the rules in Section 2.1 of [I-D.ietf-dnsop-svcb-https]. - std::string svc_params_; + /// @param adn string representation of ADN FQDN + void setAdn(const std::string& adn); -private: - /// @brief Returns minimal length of the option data (without headers) in octets. - /// - /// If the ADN-only mode is used, then "Addr Length", "ipv6-address(es)", - /// and "Service Parameters (SvcParams)" fields are not present. In this - /// case minimal length of data is 2 octets for Service Priority plus 2 octets - /// for ADN Length. - /// - /// @return Minimal length of the option data (without headers) in octets. - static uint8_t getMinimalLength() { - return (SERVICE_PRIORITY_SIZE + ADN_LENGTH_SIZE); - }; + void setDnrInstanceDataLength(uint16_t dnr_instance_data_length) { + dnr_instance_data_length_ = dnr_instance_data_length; + } + void setServicePriority(uint16_t service_priority) { + service_priority_ = service_priority; + } + void setAdnLength(uint16_t adn_length) { + adn_length_ = adn_length; + } + void setAddrLength(uint16_t addr_length) { + addr_length_ = addr_length; + } + void setSvcParamsLength(uint16_t svc_params_length) { + svc_params_length_ = svc_params_length; + } + void setAdnOnlyMode(bool adn_only_mode) { + adn_only_mode_ = adn_only_mode; + } + void setSvcParams(const std::string& svc_params) { + svc_params_ = svc_params; + } /// @brief Writes the ADN FQDN in the wire format into a buffer. /// @@ -189,13 +133,13 @@ private: /// @param [out] buf buffer where ADN FQDN will be written. void packAdn(isc::util::OutputBuffer& buf) const; - /// @brief Writes the IPv6 address(es) in the wire format into a buffer. + /// @brief Writes the IP address(es) in the wire format into a buffer. /// - /// The IPv6 address(es) (@c ipv6_addresses_) data is appended at the end + /// The IP address(es) (@c ip_addresses_) data is appended at the end /// of the buffer. /// - /// @param [out] buf buffer where IPv6 address(es) will be written. - void packAddresses(isc::util::OutputBuffer& buf) const; + /// @param [out] buf buffer where IP address(es) will be written. + virtual void packAddresses(isc::util::OutputBuffer& buf) const; /// @brief Writes the Service Parameters in the wire format into a buffer. /// @@ -211,36 +155,106 @@ private: /// Section 2.1 of [I-D.ietf-dnsop-svcb-https]. void checkSvcParams(bool from_wire_data = true); - /// @brief Sets Authentication domain name from given string. + void checkFields(); + +protected: + Option::Universe universe_; + + /// @brief Authentication domain name field of variable length. /// - /// Sets FQDN of the encrypted DNS resolver from given string. - /// It may throw an exception if parsing of the FQDN fails or if - /// provided FQDN length is bigger than uint16_t Max. - /// It also calculates and sets value of Addr length field. + /// Authentication domain name field of variable length holding + /// a fully qualified domain name of the encrypted DNS resolver. + /// This field is formatted as specified in Section 10 of RFC8415. + boost::shared_ptr adn_; + + /// @brief Length of all following data in octets. /// - /// @param adn string representation of ADN FQDN - void setAdn(const std::string& adn); - void checkFields(); + /// This field is only used for DHCPv4 Encrypted DNS %Option. + uint16_t dnr_instance_data_length_; + + /// @brief The priority of this instance compared to other DNR instances. + uint16_t service_priority_; + + /// @brief Length of the authentication-domain-name data in octets. + uint16_t adn_length_; + + /// @brief Length of included IP addresses in octets. + uint16_t addr_length_ = 0; + + /// @brief Vector container holding one or more IP addresses. + /// + /// One or more IP addresses to reach the encrypted DNS resolver. + /// In case of DHCPv4, both private and public IPv4 addresses can + /// be included in this field. + /// In case of DHCPv6, an address can be link-local, ULA, or GUA. + AddressContainer ip_addresses_; + + /// @brief Length of Service Parameters field in octets. + uint16_t svc_params_length_ = 0; + + /// @brief Flag stating whether ADN only mode is used or not. + /// + /// "Addr Length", "IP(v4/v6) Address(es)", and "Service Parameters (SvcParams)" + /// fields are not present if the ADN-only mode is used. + bool adn_only_mode_ = true; + + /// @brief Service Parameters (SvcParams) (variable length). + /// + /// Specifies a set of service parameters that are encoded + /// following the rules in Section 2.1 of [I-D.ietf-dnsop-svcb-https]. + std::string svc_params_; + +private: + std::string getLogPrefix() const; }; -class OptionDnr4 : public Option { +class Option4Dnr : public Option { public: - OptionDnr4(); - OptionDnr4(OptionBufferConstIter begin, OptionBufferConstIter end); + /// @brief Size in octets of Service Priority field. + static const uint8_t DNR_INSTANCE_DATA_LENGTH_SIZE = 2; + + /// @brief Size in octets of Service Priority field. + static const uint8_t SERVICE_PRIORITY_SIZE = 2; + + /// @brief Size in octets of ADN Length field. + static const uint8_t ADN_LENGTH_SIZE = 1; + + /// @brief Size in octets of Addr Length field. + static const uint8_t ADDR_LENGTH_SIZE = 1; + + typedef std::vector DnrInstanceContainer; + + Option4Dnr(OptionBufferConstIter begin, OptionBufferConstIter end); + virtual OptionPtr clone() const; - virtual void pack(util::OutputBuffer& buf, bool check) const; + virtual void pack(util::OutputBuffer& buf, bool check = true) const; virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); - virtual std::string toText(int indent) const; + virtual std::string toText(int indent = 0) const; virtual uint16_t len() const; -}; -/// A pointer to the @c OptionDnr6 object. -typedef boost::shared_ptr OptionDnr6Ptr; +protected: + DnrInstanceContainer dnr_instances_; + +private: + /// @brief Returns minimal length of the option data (without headers) in octets. + /// + /// If the ADN-only mode is used, then "Addr Length", "IPv4 Address(es)", + /// and "Service Parameters (SvcParams)" fields are not present. In this + /// case minimal length of data is 2 octets for Service Priority plus 1 octet + /// for ADN Length plus 2 octets for DNR Instance Data Length. + /// + /// @return Minimal length of the option data (without headers) in octets. + static uint8_t getMinimalLength() { + return (DNR_INSTANCE_DATA_LENGTH_SIZE + SERVICE_PRIORITY_SIZE + ADN_LENGTH_SIZE); + }; + + void addDnrInstance(DnrInstance& dnr_instance); +}; /// A pointer to the @c OptionDnr4 object. -typedef boost::shared_ptr OptionDnr4Ptr; +typedef boost::shared_ptr Option4DnrPtr; } // namespace dhcp } // namespace isc -#endif // OPTION_DNR_H +#endif // OPTION4_DNR_H diff --git a/src/lib/dhcp/option6_dnr.cc b/src/lib/dhcp/option6_dnr.cc new file mode 100644 index 0000000000..89266857f7 --- /dev/null +++ b/src/lib/dhcp/option6_dnr.cc @@ -0,0 +1,197 @@ +// Copyright (C) 2018-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 + +#include +#include +#include +#include +#include +#include +#include + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { + +Option6Dnr::Option6Dnr(OptionBufferConstIter begin, OptionBufferConstIter end) + : Option(V6, D6O_V6_DNR), DnrInstance(V6) { + unpack(begin, end); +} + +Option6Dnr::Option6Dnr(const uint16_t service_priority, + const std::string& adn, + const Option6Dnr::AddressContainer& ip_addresses, + const std::string& svc_params) + : Option(V6, D6O_V6_DNR), DnrInstance(V6) { + service_priority_ = service_priority; + ip_addresses_ = ip_addresses; + svc_params_ = svc_params; + setAdn(adn); + checkFields(); +} + +Option6Dnr::Option6Dnr(const uint16_t service_priority, const std::string& adn) + : Option(V6, D6O_V6_DNR), DnrInstance(V6) { + service_priority_ = service_priority; + setAdn(adn); +} + +OptionPtr +Option6Dnr::clone() const { + return (cloneInternal()); +} + +void +Option6Dnr::pack(util::OutputBuffer& buf, bool check) const { + packHeader(buf, check); + + buf.writeUint16(service_priority_); + buf.writeUint16(adn_length_); + packAdn(buf); + if (adn_only_mode_) { + return; + } + buf.writeUint16(addr_length_); + packAddresses(buf); + packSvcParams(buf); +} + +void +Option6Dnr::packAddresses(util::OutputBuffer& buf) const { + for (const auto& address : ip_addresses_) { + if (!address.isV6()) { + isc_throw(isc::BadValue, address.toText() << " is not an IPv6 address"); + } + buf.writeData(&address.toBytes()[0], V6ADDRESS_LEN); + } +} + +void +Option6Dnr::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + if (std::distance(begin, end) < getMinimalLength()) { + isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ") malformed: " + "data truncated to" + " size " << std::distance(begin, end)); + } + setData(begin, end); + // First two octets of Option data is Service Priority - this is mandatory field. + service_priority_ = isc::util::readUint16(&(*begin), SERVICE_PRIORITY_SIZE); + begin += SERVICE_PRIORITY_SIZE; + + // Next come two octets of ADN Length plus the ADN data itself (variable length). + // This is Opaque Data Tuple so let's use this class to retrieve the ADN data. + OpaqueDataTuple adn_tuple(OpaqueDataTuple::LENGTH_2_BYTES, begin, end); + adn_length_ = adn_tuple.getLength(); + + // Encrypted DNS options are designed to always include an authentication domain name, + // so when there is no FQDN included, we shall throw an exception. + if (adn_length_ == 0) { + isc_throw(InvalidOptionDnrDomainName, "Mandatory Authentication Domain Name fully " + "qualified domain-name is missing"); + } + + // Let's try to extract ADN FQDN data. + isc::util::InputBuffer name_buf(&(*adn_tuple.getData().begin()), adn_length_); + try { + adn_.reset(new isc::dns::Name(name_buf, true)); + } catch (const Exception& ex) { + isc_throw(InvalidOptionDnrDomainName, "failed to parse " + "fully qualified domain-name from wire format " + "- " << ex.what()); + } + + begin += adn_tuple.getTotalLength(); + + if (begin == end) { + // ADN only mode, other fields are not included. + return; + } + adn_only_mode_ = false; + if (std::distance(begin, end) < ADDR_LENGTH_SIZE) { + isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ") malformed: after" + " ADN field, there should be at least " + "2 bytes long Addr Length field"); + } + // Next come two octets of Addr Length. + addr_length_ = isc::util::readUint16(&(*begin), ADDR_LENGTH_SIZE); + begin += ADDR_LENGTH_SIZE; + // It MUST be a multiple of 16. + if ((addr_length_ % V6ADDRESS_LEN) != 0) { + isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ")" + << " malformed: Addr Len=" << addr_length_ + << " is not divisible by 16"); + } + + // As per draft-ietf-add-dnr 3.1.8: + // If additional data is supplied (i.e. not ADN only mode), + // the option includes at least one valid IP address. + if (addr_length_ == 0) { + isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ")" + << " malformed: Addr Len=" << addr_length_ + << " is not greater than 0"); + } + + // Check if IPv6 Address(es) field is not truncated. + if (std::distance(begin, end) < addr_length_) { + isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ")" + << " malformed: Addr Len=" << addr_length_ + << " but IPv6 address(es) are truncated to len=" + << std::distance(begin, end)); + } + + // Let's unpack the ipv6-address(es). + auto addr_end = begin + addr_length_; + while (begin != addr_end) { + ip_addresses_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin))); + begin += V6ADDRESS_LEN; + } + + // SvcParams (variable length) field is last. + svc_params_length_ = std::distance(begin, end); + if (svc_params_length_ > 0) { + svc_params_.assign(begin, end); + checkSvcParams(); + } +} + +std::string +Option6Dnr::toText(int indent) const { + std::ostringstream stream; + std::string in(indent, ' '); // base indentation + stream << in << "type=" << type_ << "(V6_DNR), " + << "len=" << (len() - getHeaderLen()) << ", " + << "service_priority=" << service_priority_ << ", " + << "adn_length=" << adn_length_ << ", " + << "adn='" << getAdnAsText() << "'"; + if (!adn_only_mode_) { + stream << ", addr_length=" << addr_length_ + << ", address(es):"; + for (const auto& address : ip_addresses_) { + stream << " " << address.toText(); + } + + if (svc_params_length_ > 0) { + stream << ", svc_params='" << svc_params_ << "'"; + } + } + + return (stream.str()); +} + +uint16_t +Option6Dnr::len() const { + uint16_t len = OPTION6_HDR_LEN + SERVICE_PRIORITY_SIZE + adn_length_ + ADN_LENGTH_SIZE; + if (!adn_only_mode_) { + len += addr_length_ + ADDR_LENGTH_SIZE + svc_params_length_; + } + return (len); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcp/option6_dnr.h b/src/lib/dhcp/option6_dnr.h new file mode 100644 index 0000000000..9c3be4a8fb --- /dev/null +++ b/src/lib/dhcp/option6_dnr.h @@ -0,0 +1,100 @@ +// Copyright (C) 2018-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/. + +#ifndef OPTION6_DNR_H +#define OPTION6_DNR_H + +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Exception thrown when invalid domain name is specified. +class InvalidOptionDnrDomainName : public Exception { +public: + InvalidOptionDnrDomainName(const char* file, size_t line, const char* what) + : isc::Exception(file, line, what) { + } +}; + +/// @brief Exception thrown when Service parameters have wrong format. +class InvalidOptionDnrSvcParams : public Exception { +public: + InvalidOptionDnrSvcParams(const char* file, size_t line, const char* what) + : isc::Exception(file, line, what) { + } +}; + +/// @brief Represents DHCPv6 Encrypted DNS %Option (code 144). +/// +/// This option has been defined in the draft-ietf-add-dnr-15 (to be replaced +/// with published RFC) and it has a following structure: +/// - option-code = 144 (2 octets) +/// - option-len (2 octets) +/// - Service Priority (2 octets) +/// - ADN Length (2 octets) +/// - Authentication Domain Name (variable length) +/// - Addr Length (2 octets) +/// - IPv6 Address(es) (variable length) +/// - Service Parameters (variable length). +class Option6Dnr : public Option, public DnrInstance { +public: + + /// @brief Size in octets of Service Priority field. + static const uint8_t SERVICE_PRIORITY_SIZE = 2; + + /// @brief Size in octets of ADN Length field. + static const uint8_t ADN_LENGTH_SIZE = 2; + + /// @brief Size in octets of Addr Length field. + static const uint8_t ADDR_LENGTH_SIZE = 2; + + /// @brief Constructor of the %Option from on-wire data. + /// + /// This constructor creates an instance of the option using a buffer with + /// on-wire data. It may throw an exception if the @c unpack method throws. + /// + /// @param begin Iterator pointing to the beginning of the buffer holding an + /// option. + /// @param end Iterator pointing to the end of the buffer holding an option. + Option6Dnr(OptionBufferConstIter begin, OptionBufferConstIter end); + + Option6Dnr(const uint16_t service_priority, const std::string& adn, const AddressContainer& ip_addresses, const std::string& svc_params); + + Option6Dnr(const uint16_t service_priority, const std::string& adn); + + virtual OptionPtr clone() const; + virtual void pack(util::OutputBuffer& buf, bool check = false) const; + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end); + virtual std::string toText(int indent = 0) const; + virtual uint16_t len() const; + + virtual void packAddresses(isc::util::OutputBuffer& buf) const; + +private: + /// @brief Returns minimal length of the option data (without headers) in octets. + /// + /// If the ADN-only mode is used, then "Addr Length", "ipv6-address(es)", + /// and "Service Parameters (SvcParams)" fields are not present. In this + /// case minimal length of data is 2 octets for Service Priority plus 2 octets + /// for ADN Length. + /// + /// @return Minimal length of the option data (without headers) in octets. + static uint8_t getMinimalLength() { + return (SERVICE_PRIORITY_SIZE + ADN_LENGTH_SIZE); + }; +}; + +/// A pointer to the @c Option6Dnr object. +typedef boost::shared_ptr Option6DnrPtr; + +} // namespace dhcp +} // namespace isc + +#endif // OPTION6_DNR_H diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc index 835a09e9cb..847e4b0d4a 100644 --- a/src/lib/dhcp/option_definition.cc +++ b/src/lib/dhcp/option_definition.cc @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -19,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -868,7 +869,7 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u, return (OptionPtr(new Option6PDExclude(begin, end))); case D6O_V6_DNR: - return (OptionPtr(new OptionDnr6(begin, end))); + return (OptionPtr(new Option6Dnr(begin, end))); default: break; @@ -899,7 +900,7 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u, return (factoryOpaqueDataTuples(Option::V4, getCode(), begin, end, OpaqueDataTuple::LENGTH_2_BYTES)); case DHO_V4_DNR: - return (OptionPtr(new OptionDnr4(begin, end))); + return (OptionPtr(new Option4Dnr(begin, end))); default: break; diff --git a/src/lib/dhcp/option_dnr.cc b/src/lib/dhcp/option_dnr.cc deleted file mode 100644 index 6a1d6a95a3..0000000000 --- a/src/lib/dhcp/option_dnr.cc +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright (C) 2018-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 - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace isc::asiolink; - -namespace isc { -namespace dhcp { - -OptionDnr6::OptionDnr6(OptionBufferConstIter begin, OptionBufferConstIter end) - : Option(V6, D6O_V6_DNR) { - unpack(begin, end); -} - -OptionDnr6::OptionDnr6(const uint16_t service_priority, - const std::string& adn, - const OptionDnr6::AddressContainer& ipv6_addresses, - const std::string& svc_params) - : Option(V6, D6O_V6_DNR), service_priority_(service_priority), - ipv6_addresses_(ipv6_addresses), svc_params_(svc_params) { - setAdn(adn); - checkFields(); -} - -OptionDnr6::OptionDnr6(const uint16_t service_priority, const std::string& adn) - : Option(V6, D6O_V6_DNR), service_priority_(service_priority) { - setAdn(adn); -} - -OptionPtr -OptionDnr6::clone() const { - return (cloneInternal()); -} - -void -OptionDnr6::pack(util::OutputBuffer& buf, bool check) const { - packHeader(buf, check); - - buf.writeUint16(service_priority_); - buf.writeUint16(adn_length_); - packAdn(buf); - if (adn_only_mode_) { - return; - } - buf.writeUint16(addr_length_); - packAddresses(buf); - packSvcParams(buf); -} - -void -OptionDnr6::packAdn(util::OutputBuffer& buf) const { - if (!adn_) { - // This should not happen since Encrypted DNS options are designed - // to always include an authentication domain name. - return; - } - isc::dns::LabelSequence label_sequence(*adn_); - if (label_sequence.getDataLength() > 0) { - size_t data_length = 0; - const uint8_t* data = label_sequence.getData(&data_length); - buf.writeData(data, data_length); - } -} - -void -OptionDnr6::packAddresses(util::OutputBuffer& buf) const { - for (const auto& address : ipv6_addresses_) { - if (!address.isV6()) { - isc_throw(isc::BadValue, address.toText() << " is not an IPv6 address"); - } - buf.writeData(&address.toBytes()[0], V6ADDRESS_LEN); - } -} - -void -OptionDnr6::packSvcParams(util::OutputBuffer& buf) const { - if (svc_params_length_ > 0) { - buf.writeData(&(*svc_params_.begin()), svc_params_length_); - } -} - -void -OptionDnr6::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { - if (std::distance(begin, end) < getMinimalLength()) { - isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ") malformed: " - "data truncated to" - " size " << std::distance(begin, end)); - } - setData(begin, end); - // First two octets of Option data is Service Priority - this is mandatory field. - service_priority_ = isc::util::readUint16(&(*begin), SERVICE_PRIORITY_SIZE); - begin += SERVICE_PRIORITY_SIZE; - - // Next come two octets of ADN Length plus the ADN data itself (variable length). - // This is Opaque Data Tuple so let's use this class to retrieve the ADN data. - OpaqueDataTuple adn_tuple(OpaqueDataTuple::LENGTH_2_BYTES, begin, end); - adn_length_ = adn_tuple.getLength(); - - // Encrypted DNS options are designed to always include an authentication domain name, - // so when there is no FQDN included, we shall throw an exception. - if (adn_length_ == 0) { - isc_throw(InvalidOptionDnrDomainName, "Mandatory Authentication Domain Name fully " - "qualified domain-name is missing"); - } - - // Let's try to extract ADN FQDN data. - isc::util::InputBuffer name_buf(&(*adn_tuple.getData().begin()), adn_length_); - try { - adn_.reset(new isc::dns::Name(name_buf, true)); - } catch (const Exception& ex) { - isc_throw(InvalidOptionDnrDomainName, "failed to parse " - "fully qualified domain-name from wire format " - "- " << ex.what()); - } - - begin += adn_tuple.getTotalLength(); - - if (begin == end) { - // ADN only mode, other fields are not included. - return; - } - adn_only_mode_ = false; - if (std::distance(begin, end) < ADDR_LENGTH_SIZE) { - isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ") malformed: after" - " ADN field, there should be at least " - "2 bytes long Addr Length field"); - } - // Next come two octets of Addr Length. - addr_length_ = isc::util::readUint16(&(*begin), ADDR_LENGTH_SIZE); - begin += ADDR_LENGTH_SIZE; - // It MUST be a multiple of 16. - if ((addr_length_ % V6ADDRESS_LEN) != 0) { - isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ")" - << " malformed: Addr Len=" << addr_length_ - << " is not divisible by 16"); - } - - // As per draft-ietf-add-dnr 3.1.8: - // If additional data is supplied (i.e. not ADN only mode), - // the option includes at least one valid IP address. - if (addr_length_ == 0) { - isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ")" - << " malformed: Addr Len=" << addr_length_ - << " is not greater than 0"); - } - - // Check if IPv6 Address(es) field is not truncated. - if (std::distance(begin, end) < addr_length_) { - isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ")" - << " malformed: Addr Len=" << addr_length_ - << " but IPv6 address(es) are truncated to len=" - << std::distance(begin, end)); - } - - // Let's unpack the ipv6-address(es). - auto addr_end = begin + addr_length_; - while (begin != addr_end) { - ipv6_addresses_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin))); - begin += V6ADDRESS_LEN; - } - - // SvcParams (variable length) field is last. - svc_params_length_ = std::distance(begin, end); - if (svc_params_length_ > 0) { - svc_params_.assign(begin, end); - checkSvcParams(); - } -} - -std::string -OptionDnr6::toText(int indent) const { - std::ostringstream stream; - std::string in(indent, ' '); // base indentation - stream << in << "type=" << type_ << "(V6_DNR), " - << "len=" << (len() - getHeaderLen()) << ", " - << "service_priority=" << service_priority_ << ", " - << "adn_length=" << adn_length_ << ", " - << "adn='" << getAdn() << "'"; - if (!adn_only_mode_) { - stream << ", addr_length=" << addr_length_ - << ", address(es):"; - for (const auto& address : ipv6_addresses_) { - stream << " " << address.toText(); - } - - if (svc_params_length_ > 0) { - stream << ", svc_params='" << svc_params_ << "'"; - } - } - - return (stream.str()); -} - -uint16_t -OptionDnr6::len() const { - uint16_t len = OPTION6_HDR_LEN + SERVICE_PRIORITY_SIZE + adn_length_ + ADN_LENGTH_SIZE; - if (!adn_only_mode_) { - len += addr_length_ + ADDR_LENGTH_SIZE + svc_params_length_; - } - return (len); -} - -std::string -OptionDnr6::getAdn() const { - if (adn_) { - return (adn_->toText()); - } - return (""); -} - -void -OptionDnr6::checkSvcParams(bool from_wire_data) { - std::string svc_params = isc::util::str::trim(svc_params_); - if (svc_params.empty()) { - isc_throw(InvalidOptionDnrSvcParams, "Provided Svc Params field is empty"); - } - if (!from_wire_data) { - // If Service Params field was not parsed from on-wire data, - // but actually was provided with ctor, let's calculate svc_params_length_. - auto svc_params_len = svc_params.length(); - if (svc_params_len > std::numeric_limits::max()) { - isc_throw(OutOfRange, "Given Svc Params length " - << svc_params_len << " is bigger than uint_16 MAX"); - } - svc_params_length_ = svc_params_len; - // If Service Params field was not parsed from on-wire data, - // but actually was provided with ctor, let's replace it with trimmed value. - svc_params_ = svc_params; - } - - // SvcParams are a whitespace-separated list, with each SvcParam - // consisting of a SvcParamKey=SvcParamValue pair or a standalone SvcParamKey. - // SvcParams in presentation format MAY appear in any order, but keys MUST NOT be repeated. - - // Let's put all elements of a whitespace-separated list into a vector. - std::vector tokens = isc::util::str::tokens(svc_params, " "); - - // Set of keys used to check if a key is not repeated. - std::set keys; - // String sanitizer is used to check keys syntax. - util::str::StringSanitizerPtr sanitizer; - // SvcParamKeys are lower-case alphanumeric strings. Key names - // contain 1-63 characters from the ranges "a"-"z", "0"-"9", and "-". - std::string regex = "[^a-z0-9-]"; - sanitizer.reset(new util::str::StringSanitizer(regex, "")); - // The service parameters MUST NOT include - // "ipv4hint" or "ipv6hint" SvcParams as they are superseded by the - // included IP addresses. - std::set forbidden_keys = {"ipv4hint", "ipv6hint"}; - - // Now let's check each SvcParamKey=SvcParamValue pair. - for (const std::string& token : tokens) { - std::vector key_val = isc::util::str::tokens(token, "="); - if (key_val.size() > 2) { - isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - more than one " - "equals sign found in SvcParamKey=SvcParamValue " - "pair"); - } - - // SvcParam Key related checks come below. - std::string key = key_val[0]; - if (forbidden_keys.find(key) != forbidden_keys.end()) { - isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - key " - << key << " must not be used"); - } - - auto insert_res = keys.insert(key); - if (!insert_res.second) { - isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - key " - << key << " was duplicated"); - } - - if (key.length() > 63) { - isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - key had more than 63 " - "characters - " << key); - } - - std::string sanitized_key = sanitizer->scrub(key); - if (sanitized_key.size() < key.size()) { - isc_throw(InvalidOptionDnrSvcParams, "Wrong Svc Params syntax - invalid character " - "used in key - " << key); - } - - if (key_val.size() == 2) { - // tbd Check value syntax - std::string value = key_val[1]; - } - } - -} - -void -OptionDnr6::setAdn(const std::string& adn) { - std::string trimmed_adn = isc::util::str::trim(adn); - if (trimmed_adn.empty()) { - isc_throw(InvalidOptionDnrDomainName, "Mandatory Authentication Domain Name fully " - "qualified domain-name must not be empty"); - } - try { - adn_.reset(new isc::dns::Name(trimmed_adn, true)); - } catch (const Exception& ex) { - isc_throw(InvalidOptionDnrDomainName, "Failed to parse " - "fully qualified domain-name from string " - "- " << ex.what()); - } - size_t adn_len = 0; - isc::dns::LabelSequence label_sequence(*adn_); - label_sequence.getData(&adn_len); - if (adn_len > std::numeric_limits::max()) { - isc_throw(InvalidOptionDnrDomainName, "Given ADN FQDN length " - << adn_len << " is bigger than uint_16 MAX"); - } - - adn_length_ = adn_len; -} - -void -OptionDnr6::checkFields() { - if (svc_params_.empty() && ipv6_addresses_.empty()) { - // ADN only mode, nothing more to do. - return; - } - if(!svc_params_.empty() && ipv6_addresses_.empty()) { - // As per draft-ietf-add-dnr 3.1.8: - // If additional data is supplied (i.e. not ADN only mode), - // the option includes at least one valid IP address. - isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" - << type_ << ")" - << " malformed: No IPv6 address given. Since this is not " - "ADN only mode, at least one valid IP address must be included"); - - } - if(!svc_params_.empty()) { - checkSvcParams(false); - } - adn_only_mode_ = false; - auto addr_len = ipv6_addresses_.size() * V6ADDRESS_LEN; - if (addr_len > std::numeric_limits::max()) { - isc_throw(OutOfRange, "Given IPv6 addresses length " - << addr_len << " is bigger than uint_16 MAX"); - } - addr_length_ = addr_len; -} - -OptionDnr4::OptionDnr4() : Option(V4, DHO_V4_DNR) { -} - -OptionDnr4::OptionDnr4(OptionBufferConstIter begin, OptionBufferConstIter end) - : Option(V4, DHO_V4_DNR) { - unpack(begin, end); -} - -OptionPtr -OptionDnr4::clone() const { - return Option::clone(); -} - -void -OptionDnr4::pack(util::OutputBuffer& buf, bool check) const { - Option::pack(buf, check); -} - -void -OptionDnr4::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { - Option::unpack(begin, end); -} - -std::string -OptionDnr4::toText(int indent) const { - return Option::toText(indent); -} - -uint16_t -OptionDnr4::len() const { - return Option::len(); -} - -} // namespace dhcp -} // namespace isc diff --git a/src/lib/dhcp/tests/option_dnr_unittest.cc b/src/lib/dhcp/tests/option_dnr_unittest.cc index 60d70bf9c9..f16ecf3db3 100644 --- a/src/lib/dhcp/tests/option_dnr_unittest.cc +++ b/src/lib/dhcp/tests/option_dnr_unittest.cc @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include @@ -36,8 +36,8 @@ TEST(OptionDnr6Test, onWireCtorAdnOnlyMode) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end()))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end()))); ASSERT_TRUE(option); // Check if member variables were correctly set by ctor. @@ -47,7 +47,7 @@ TEST(OptionDnr6Test, onWireCtorAdnOnlyMode) { // Check if data was unpacked correctly from wire data. EXPECT_EQ(0x8001, option->getServicePriority()); EXPECT_EQ(20, option->getAdnLength()); - EXPECT_EQ("myhost.example.com.", option->getAdn()); + EXPECT_EQ("myhost.example.com.", option->getAdnAsText()); // This is ADN only mode, so Addr Length and SvcParams Length // are both expected to be zero. @@ -75,8 +75,8 @@ TEST(OptionDnr6Test, onWireCtorDataTruncated) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor throws OutOfRange exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OutOfRange); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); ASSERT_FALSE(option); } @@ -92,8 +92,8 @@ TEST(OptionDnr6Test, onWireCtorOnlyWhitespaceFqdn) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor throws InvalidOptionDnrDomainName exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), InvalidOptionDnrDomainName); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName); ASSERT_FALSE(option); } @@ -110,8 +110,8 @@ TEST(OptionDnr6Test, onWireCtorNoAdnFqdn) { // Create option instance. Encrypted DNS options are designed to ALWAYS include // an authentication domain name, so check that constructor throws // InvalidOptionDnrDomainName exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), InvalidOptionDnrDomainName); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName); ASSERT_FALSE(option); } @@ -127,8 +127,8 @@ TEST(OptionDnr6Test, onWireCtorTruncatedFqdn) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor throws OpaqueDataTupleError exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OpaqueDataTupleError); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OpaqueDataTupleError); ASSERT_FALSE(option); } @@ -147,8 +147,8 @@ TEST(OptionDnr6Test, onWireCtorAddrLenTruncated) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor throws OutOfRange exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OutOfRange); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); ASSERT_FALSE(option); } @@ -169,8 +169,8 @@ TEST(OptionDnr6Test, onWireCtorAddrLenZero) { // Create option instance. Check that constructor throws OutOfRange exception. // If additional data is supplied (i.e. not ADN only mode), // the option includes at least one valid IP address. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OutOfRange); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); ASSERT_FALSE(option); } @@ -189,8 +189,8 @@ TEST(OptionDnr6Test, onWireCtorAddrLenNot16Modulo) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor throws OutOfRange exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OutOfRange); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); ASSERT_FALSE(option); } @@ -215,8 +215,8 @@ TEST(OptionDnr6Test, onWireCtorValidIpV6Addresses) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end()))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end()))); ASSERT_TRUE(option); // Check if member variables were correctly set by ctor. @@ -226,9 +226,9 @@ TEST(OptionDnr6Test, onWireCtorValidIpV6Addresses) { // Check if data was unpacked correctly from wire data. EXPECT_EQ(0x8001, option->getServicePriority()); EXPECT_EQ(20, option->getAdnLength()); - EXPECT_EQ("myhost.example.com.", option->getAdn()); + EXPECT_EQ("myhost.example.com.", option->getAdnAsText()); EXPECT_EQ(48, option->getAddrLength()); - const OptionDnr6::AddressContainer& addresses = option->getAddresses(); + const Option6Dnr::AddressContainer& addresses = option->getAddresses(); EXPECT_EQ(3, addresses.size()); EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText()); EXPECT_EQ("ff02::face:b00c", addresses[1].toText()); @@ -269,8 +269,8 @@ TEST(OptionDnr6Test, onWireCtorTruncatedIpV6Addresses) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor throws OutOfRange exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OutOfRange); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange); ASSERT_FALSE(option); } @@ -292,8 +292,8 @@ TEST(OptionDnr6Test, onWireCtorSvcParamsIncluded) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end()))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end()))); ASSERT_TRUE(option); // Check if member variables were correctly set by ctor. @@ -303,9 +303,9 @@ TEST(OptionDnr6Test, onWireCtorSvcParamsIncluded) { // Check if data was unpacked correctly from wire data. EXPECT_EQ(0x8001, option->getServicePriority()); EXPECT_EQ(20, option->getAdnLength()); - EXPECT_EQ("myhost.example.com.", option->getAdn()); + EXPECT_EQ("myhost.example.com.", option->getAdnAsText()); EXPECT_EQ(16, option->getAddrLength()); - const OptionDnr6::AddressContainer& addresses = option->getAddresses(); + const Option6Dnr::AddressContainer& addresses = option->getAddresses(); EXPECT_EQ(1, addresses.size()); EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText()); EXPECT_EQ(3, option->getSvcParamsLength()); @@ -344,8 +344,8 @@ TEST(OptionDnr6Test, onWireCtorSvcParamsInvalidCharKey) { OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); // Create option instance. Check that constructor throws InvalidOptionDnrSvcParams exception. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), InvalidOptionDnrSvcParams); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrSvcParams); ASSERT_FALSE(option); } @@ -357,8 +357,8 @@ TEST(OptionDnr6Test, adnOnlyModeCtor) { const std::string adn = "myhost.example.com."; // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(service_priority, adn))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn))); ASSERT_TRUE(option); // Check if member variables were correctly set by ctor. @@ -366,7 +366,7 @@ TEST(OptionDnr6Test, adnOnlyModeCtor) { EXPECT_EQ(D6O_V6_DNR, option->getType()); EXPECT_EQ(service_priority, option->getServicePriority()); EXPECT_EQ(20, option->getAdnLength()); - EXPECT_EQ(adn, option->getAdn()); + EXPECT_EQ(adn, option->getAdnAsText()); // This is ADN only mode, so Addr Length and SvcParams Length // are both expected to be zero. @@ -392,8 +392,8 @@ TEST(OptionDnr6Test, adnOnlyModeCtorNoFqdn) { const std::string adn; // invalid empty ADN // Create option instance. Check that constructor throws. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn)), InvalidOptionDnrDomainName); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn)), InvalidOptionDnrDomainName); ASSERT_FALSE(option); } @@ -404,13 +404,13 @@ TEST(OptionDnr6Test, allFieldsCtor) { // Prepare example parameters const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); const std::string svc_params = "alpn"; // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params))); ASSERT_TRUE(option); // Check if member variables were correctly set by ctor. @@ -418,7 +418,7 @@ TEST(OptionDnr6Test, allFieldsCtor) { EXPECT_EQ(D6O_V6_DNR, option->getType()); EXPECT_EQ(service_priority, option->getServicePriority()); EXPECT_EQ(20, option->getAdnLength()); - EXPECT_EQ(adn, option->getAdn()); + EXPECT_EQ(adn, option->getAdnAsText()); EXPECT_EQ(16, option->getAddrLength()); EXPECT_EQ(4, option->getSvcParamsLength()); EXPECT_EQ(svc_params, option->getSvcParams()); @@ -443,12 +443,12 @@ TEST(OptionDnr6Test, allFieldsCtorNoIpAddress) { // Prepare example parameters const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - const OptionDnr6::AddressContainer addresses; // no IPv6 address in here + const Option6Dnr::AddressContainer addresses; // no IPv6 address in here const std::string svc_params = "alpn"; // Create option instance. Check that constructor throws. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), OutOfRange); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), OutOfRange); ASSERT_FALSE(option); } @@ -459,13 +459,13 @@ TEST(OptionDnr6Test, svcParamsTwoEqualSignsPerParam) { // Prepare example parameters. const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); const std::string svc_params = "key123=val1=val2 key234"; // invalid svc param - 2 equal signs // Create option instance. Check that constructor throws. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); ASSERT_FALSE(option); } @@ -476,13 +476,13 @@ TEST(OptionDnr6Test, svcParamsForbiddenKey) { // Prepare example parameters. const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); const std::string svc_params = "key123=val1 ipv6hint"; // forbidden svc param key - ipv6hint // Create option instance. Check that constructor throws. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); ASSERT_FALSE(option); } @@ -493,13 +493,13 @@ TEST(OptionDnr6Test, svcParamsKeyRepeated) { // Prepare example parameters. const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); const std::string svc_params = "key123=val1 key234 key123"; // svc param key key123 repeated // Create option instance. Check that constructor throws. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); ASSERT_FALSE(option); } @@ -510,15 +510,15 @@ TEST(OptionDnr6Test, svcParamsKeyTooLong) { // Prepare example parameters. const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); const std::string svc_params = "thisisveryveryveryvery" "veryveryveryveryveryvery" "veryveryveryveryvlongkey"; // svc param key longer than 63 // Create option instance. Check that constructor throws. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); ASSERT_FALSE(option); } @@ -529,13 +529,13 @@ TEST(OptionDnr6Test, svcParamsKeyHasInvalidChar) { // Prepare example parameters. const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); const std::string svc_params = "alpn=h2 NOT_ALLOWED_CHARS_KEY=123"; // svc param key has forbidden chars // Create option instance. Check that constructor throws. - scoped_ptr option; - EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); + scoped_ptr option; + EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams); ASSERT_FALSE(option); } @@ -545,13 +545,13 @@ TEST(OptionDnr6Test, toText) { // Prepare example parameters. const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca")); const std::string svc_params = "alpn"; // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params))); ASSERT_TRUE(option); const int indent = 4; @@ -569,8 +569,8 @@ TEST(OptionDnr6Test, packAdnOnlyMode) { const std::string adn = "myhost.example.com."; // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(service_priority, adn))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn))); ASSERT_TRUE(option); // Prepare on-wire format of the option. @@ -602,14 +602,14 @@ TEST(OptionDnr6Test, pack) { // Prepare example parameters. const uint16_t service_priority = 9; const std::string adn = "myhost.example.com."; - OptionDnr6::AddressContainer addresses; + Option6Dnr::AddressContainer addresses; addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::dead:beef")); addresses.push_back(isc::asiolink::IOAddress("ff02::face:b00c")); const std::string svc_params = "alpn"; // Create option instance. Check that constructor doesn't throw. - scoped_ptr option; - EXPECT_NO_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params))); + scoped_ptr option; + EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params))); ASSERT_TRUE(option); // Prepare on-wire format of the option.