diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/dhcp/option4_dnr.cc | 2 | ||||
-rw-r--r-- | src/lib/dhcp/option4_dnr.h | 5 | ||||
-rw-r--r-- | src/lib/dhcp/option6_dnr.cc | 175 |
3 files changed, 180 insertions, 2 deletions
diff --git a/src/lib/dhcp/option4_dnr.cc b/src/lib/dhcp/option4_dnr.cc index f3985324c6..d5dab27d09 100644 --- a/src/lib/dhcp/option4_dnr.cc +++ b/src/lib/dhcp/option4_dnr.cc @@ -132,6 +132,8 @@ Option4Dnr::parseConfigData(const std::string& config_txt) { const std::unordered_set<std::string> DnrInstance::FORBIDDEN_SVC_PARAMS = {"ipv4hint", "ipv6hint"}; +const std::unordered_set<uint8_t> DnrInstance::SUPPORTED_SVC_PARAMS = {1, 3, 7}; + DnrInstance::DnrInstance(Option::Universe universe) : universe_(universe), dnr_instance_data_length_(0), service_priority_(0), adn_length_(0), addr_length_(0), svc_params_length_(0), diff --git a/src/lib/dhcp/option4_dnr.h b/src/lib/dhcp/option4_dnr.h index 9ef1bdc786..1f0ef4088a 100644 --- a/src/lib/dhcp/option4_dnr.h +++ b/src/lib/dhcp/option4_dnr.h @@ -13,6 +13,7 @@ #include <dhcp/option.h> #include <dhcp/option_data_types.h> #include <dns/name.h> +#include <util/strutil.h> #include <unordered_set> #include <string> @@ -34,7 +35,7 @@ const std::map<std::string, uint16_t> SVC_PARAMS = { "ipv4hint", 4}, // RFC 9460, Section 14.3.2, forbidden in DNR { "ech", 5}, // RFC 9460, Section 14.3.2, not used in DNR { "ipv6hint", 6}, // RFC 9460, Section 14.3.2, forbidden in DNR - { "dotpath", 7}, // RFC 9461, optional in DNR + { "dohpath", 7}, // RFC 9461, optional in DNR { "ohttp", 8} // https://datatracker.ietf.org/doc/draft-ietf-ohai-svcb-config, // not used in DNR }; @@ -122,6 +123,8 @@ public: /// included IP addresses. static const std::unordered_set<std::string> FORBIDDEN_SVC_PARAMS; + static const std::unordered_set<uint8_t> SUPPORTED_SVC_PARAMS; + /// @brief Constructor of the empty DNR Instance. /// /// @param universe either V4 or V6 Option universe diff --git a/src/lib/dhcp/option6_dnr.cc b/src/lib/dhcp/option6_dnr.cc index e4daefce53..2a6ac71300 100644 --- a/src/lib/dhcp/option6_dnr.cc +++ b/src/lib/dhcp/option6_dnr.cc @@ -9,6 +9,7 @@ #include <dhcp/option6_dnr.h> using namespace isc::asiolink; +using namespace isc::util; namespace isc { namespace dhcp { @@ -151,7 +152,179 @@ Option6Dnr::unpackAddresses(OptionBufferConstIter& begin, OptionBufferConstIter void Option6Dnr::parseConfigData(const std::string& config_txt){ - // TBD + // This parses convenient option config notation. + // The config to be parsed may contain escaped characters like "\\," or "\\|". + // Example configs are below (first contains recommended resolvers' IP addresses, and SvcParams; + // second is an example of ADN-only mode): + // + // "name": "v6-dnr", + // "data": "100, dot1.example.org., 2001:db8::1 2001:db8::2, alpn=dot\\,doq\\,h2\\,h3 port=8530 dohpath=/q{?dns}" + // + // "name": "v6-dnr", + // "data": "200, resolver.example." + + // get tokens using comma separator with double backslash escaping enabled + std::vector<std::string> tokens = str::tokens(config_txt, std::string(","), true); + + if (tokens.size() < 2) { + isc_throw(BadValue, getLogPrefix() << "Option config requires at least comma separated " + << "Service Priority and ADN"); + } + + if (tokens.size() > 4) { + isc_throw(BadValue, getLogPrefix() << "Option config supports maximum 4 comma separated " + << "fields: Service Priority, ADN, resolver IP " + << "address/es and SvcParams"); + } + + // parse Service Priority + std::string txt_svc_priority = str::trim(tokens[0]); + try { + service_priority_ = boost::lexical_cast<uint16_t>(txt_svc_priority); + } catch (const std::exception& e) { + isc_throw(BadValue, getLogPrefix() << "Given service priority " + txt_svc_priority + << " cannot be parsed into uint_16 integer. " + << "Error: " << e.what()); + } + + // parse ADN + std::string txt_adn = str::trim(tokens[1]); + try { + adn_.reset(new isc::dns::Name(txt_adn, true)); + } catch (const std::exception& e) { + isc_throw(InvalidOptionDnrDomainName, getLogPrefix() + << "Given ADN " + txt_adn + << " cannot be parsed into fully qualified " + << "domain-name. " + << "Error: " << e.what()); + } + + adn_length_ = adn_->getLength(); + if (adn_length_ == 0) { + isc_throw(InvalidOptionDnrDomainName, getLogPrefix() + << "Mandatory Authentication Domain Name fully " + "qualified domain-name is missing"); + } + + if (tokens.size() > 2) { + setAdnOnlyMode(false); + + // parse resolver IP address/es + std::string txt_addresses = str::trim(tokens[2]); + + // IP addresses are separated with space + std::vector<std::string> addresses = str::tokens(txt_addresses, std::string(" ")); + for (auto const& txt_addr : addresses) { + try { + ip_addresses_.push_back(IOAddress(str::trim(txt_addr))); + } catch (const Exception& e) { + isc_throw(BadValue, getLogPrefix() << "Given string " + txt_addr + << " cannot be parsed into IPv6 address. " + << "Error: " << e.what()); + } + } + + // As per RFC9463 section 3.1.8: + // (If ADN-only mode is not used) + // The option includes at least one valid IP address. + if (ip_addresses_.empty()) { + isc_throw(BadValue, getLogPrefix() << "Option config requires at least one valid IP " + << "address."); + } + + addr_length_ = ip_addresses_.size() * V6ADDRESS_LEN; + } + + if (tokens.size() == 4) { + // parse Service Parameters + std::string txt_svc_params = str::trim(tokens[3]); + + // SvcParamKey=SvcParamValue pairs are separated with space + std::vector<std::string> svc_params = str::tokens(txt_svc_params, std::string(" ")); + std::vector<std::string> alpn_ids; + std::vector<OpaqueDataTuple> alpn_ids_container; + for (auto const& txt_svc_param : svc_params) { + std::vector<std::string> key_val = str::tokens(str::trim(txt_svc_param), "="); + if (key_val.size() != 2) { + isc_throw(InvalidOptionDnrSvcParams, + getLogPrefix() << "Wrong Svc Params syntax - SvcParamKey=SvcParamValue " + << "pair syntax must be used"); + } + + // SvcParam Key related checks come below. + std::string key = str::trim(key_val[0]); + + if (FORBIDDEN_SVC_PARAMS.find(key) != FORBIDDEN_SVC_PARAMS.end()) { + isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key " + << key << " must not be used"); + } + + auto svc_params_iterator = SVC_PARAMS.find(key); + if (svc_params_iterator == SVC_PARAMS.end()) { + isc_throw(InvalidOptionDnrSvcParams, + getLogPrefix() << "Wrong Svc Params syntax - key " + << key + << " not found in SvcParamKeys registry"); + } + + uint8_t num_svc_param_key = svc_params_iterator->second; + if (SUPPORTED_SVC_PARAMS.find(num_svc_param_key) == SUPPORTED_SVC_PARAMS.end()) { + isc_throw(InvalidOptionDnrSvcParams, + getLogPrefix() << "Wrong Svc Params syntax - key " + << key + << " not supported in DNR option SvcParams"); + } + + // SvcParam Val check. + std::string val = str::trim(key_val[1]); + if (val.empty()) { + isc_throw(InvalidOptionDnrSvcParams, + getLogPrefix() << "Wrong Svc Params syntax - empty SvcParamValue for key " + << key); + } + + + + switch (num_svc_param_key) { + case 1: + // alpn + // The wire-format value for "alpn" consists of at least one alpn-id prefixed by its + // length as a single octet, and these length-value pairs are concatenated to form + // the SvcParamValue. + + alpn_ids = str::tokens(val, std::string(",")); + for (auto const& alpn_id : alpn_ids) { + if (ALPN_IDS.find(alpn_id) == ALPN_IDS.end()) { + isc_throw(InvalidOptionDnrSvcParams, + getLogPrefix() << "Wrong Svc Params syntax - alpn-id " + << alpn_id + << " not found in ALPN-IDs registry"); + } + + OpaqueDataTuple alpn_id_tuple(OpaqueDataTuple::LENGTH_1_BYTE); + alpn_id_tuple.append(alpn_id); + alpn_ids_container.push_back(alpn_id_tuple); + } + + break; + case 3: + // port + break; + case 7: + // dohpath + break; + } + } + + std::ostringstream stream; + for (auto const& t : alpn_ids_container) { + stream << t.getLength() << "-" << t.getText() << " "; + } + isc_throw(BadValue, getLogPrefix() << "SvcParams: " + txt_svc_params + ", parsed " + "alpn-ids " + + stream.str()); + } + } } // namespace dhcp |