#include <dhcp/opaque_data_tuple.h>
#include <dhcp/option_dnr.h>
#include <dns/labelsequence.h>
+#include <util/strutil.h>
+#include <set>
using namespace isc::asiolink;
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<OptionDnr6>());
}
void
-OptionDnr6::checkSvcParams() const {
- // TODO: check SvcParams and throw in case something is wrong
+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<uint16_t>::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<std::string> tokens = isc::util::str::tokens(svc_params, " ");
+
+ // Set of keys used to check if a key is not repeated.
+ std::set<std::string> 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<std::string> forbidden_keys = {"ipv4hint", "ipv6hint"};
+
+ // Now let's check each SvcParamKey=SvcParamValue pair.
+ for (const std::string& token : tokens) {
+ std::vector<std::string> 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<uint16_t>::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<uint16_t>::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) {
#include <config.h>
+#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h>
#include <dhcp/opaque_data_tuple.h>
#include <dhcp/option_dnr.h>
// Provided wire data is in the ADN only mode i.e. only
// Service priority and Authentication domain name FQDN
// fields are present.
-TEST(OptionDnr6Test, constructorAdnOnlyMode) {
+TEST(OptionDnr6Test, onWireCtorAdnOnlyMode) {
// Prepare data to decode - ADN only mode.
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
"adn='myhost.example.com.'", option->toText());
}
-TEST(OptionDnr6Test, constructorDataTruncated) {
+TEST(OptionDnr6Test, onWireCtorDataTruncated) {
// Prepare data to decode - data too short.
const uint8_t buf_data[] = {
0x80, 0x01 // Service priority is 32769 dec, other data is missing
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, onlyWhitespaceFqdn) {
+TEST(OptionDnr6Test, onWireCtorOnlyWhitespaceFqdn) {
// Prepare data to decode - ADN only mode.
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, noAdnFqdn) {
+TEST(OptionDnr6Test, onWireCtorNoAdnFqdn) {
// Prepare data to decode - ADN only mode.
const uint8_t buf_data[] = {
0x00, 0x01, // Service priority is 1 dec
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, truncatedFqdn) {
+TEST(OptionDnr6Test, onWireCtorTruncatedFqdn) {
// Prepare data to decode - ADN only mode.
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, addrLenTruncated) {
+TEST(OptionDnr6Test, onWireCtorAddrLenTruncated) {
// Prepare data to decode
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, addrLenZero) {
+TEST(OptionDnr6Test, onWireCtorAddrLenZero) {
// Prepare data to decode
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, addrLenNot16Modulo) {
+TEST(OptionDnr6Test, onWireCtorAddrLenNot16Modulo) {
// Prepare data to decode
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, validIpV6Addresses) {
+TEST(OptionDnr6Test, onWireCtorValidIpV6Addresses) {
// Prepare data to decode
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", option->toText());
}
-TEST(OptionDnr6Test, truncatedIpV6Addresses) {
+TEST(OptionDnr6Test, onWireCtorTruncatedIpV6Addresses) {
// Prepare data to decode
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
ASSERT_FALSE(option);
}
-TEST(OptionDnr6Test, svcParamsIncluded) {
+TEST(OptionDnr6Test, onWireCtorSvcParamsIncluded) {
// Prepare data to decode
const uint8_t buf_data[] = {
0x80, 0x01, // Service priority is 32769 dec
0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
- 0x00, 0x10, // Addr Len field value = 48 dec
+ 0x00, 0x10, // Addr Len field value = 16 dec
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
'a', 'b', 'c' // example SvcParams data
"svc_params='abc'", option->toText());
}
+TEST(OptionDnr6Test, onWireCtorSvcParamsInvalidCharKey) {
+ // Prepare data to decode with invalid SvcParams
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x10, // Addr Len field value = 48 dec
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ 'a', '+', 'c' // Allowed "a"-"z", "0"-"9", and "-".
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws InvalidOptionDnrSvcParams exception.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies option constructor in ADN only mode.
+// Service priority and ADN are provided via ctor.
+TEST(OptionDnr6Test, adnOnlyModeCtor) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+
+ // Create option instance. Check that constructor doesn't throw.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_NO_THROW(option.reset(new OptionDnr6(service_priority, adn)));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+ EXPECT_EQ(service_priority, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ(adn, option->getAdn());
+
+ // This is ADN only mode, so Addr Length and SvcParams Length
+ // are both expected to be zero.
+ EXPECT_EQ(0, option->getAddrLength());
+ EXPECT_EQ(0, option->getSvcParamsLength());
+
+ // BTW let's check if len() works ok.
+ // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28.
+ EXPECT_EQ(28, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=144(V6_DNR), len=24, "
+ "service_priority=9, adn_length=20, "
+ "adn='myhost.example.com.'", option->toText());
+}
+
+TEST(OptionDnr6Test, adnOnlyModeCtorNoFqdn) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = ""; // invalid empty ADN
+
+ // Create option instance. Check that constructor throws.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn)), InvalidOptionDnrDomainName);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies option constructor where all fields
+// i.e. Service priority, ADN, IP address(es) and Service params
+// are provided as ctor parameters.
+TEST(OptionDnr6Test, allFieldsCtor) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ OptionDnr6::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "alpn";
+
+ // Create option instance. Check that constructor throws.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_NO_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ 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(16, option->getAddrLength());
+ EXPECT_EQ(4, option->getSvcParamsLength());
+
+ // BTW let's check if len() works ok.
+ // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28
+ // + 16 (IPv6) + 2 (Addr Len) + 4 (Svc Params) = 50
+ EXPECT_EQ(50, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=144(V6_DNR), len=46, "
+ "service_priority=9, adn_length=20, "
+ "adn='myhost.example.com.', addr_length=16, "
+ "address(es): 2001:db8:1::baca, svc_params='alpn'", option->toText());
+}
+
+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 std::string svc_params = "alpn";
+
+ // Create option instance. Check that constructor throws.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, svcParamsTwoEqualSignsPerParam) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ OptionDnr6::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<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, svcParamsForbiddenKey) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ OptionDnr6::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<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, svcParamsKeyRepeated) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ OptionDnr6::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<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, svcParamsKeyTooLong) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ OptionDnr6::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "thisisveryveryveryvery"
+ "veryveryveryveryveryvery"
+ "veryveryveryveryveryvery"
+ "veryveryverylongkey"; // svc param key longer than 63
+
+ // Create option instance. Check that constructor throws.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(service_priority, adn, addresses, svc_params)), InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
} // namespace
\ No newline at end of file