]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2536] Implementing DNRv6 Option with TDD
authorPiotrek Zadroga <piotrek@isc.org>
Fri, 14 Apr 2023 15:38:12 +0000 (17:38 +0200)
committerPiotrek Zadroga <piotrek@isc.org>
Thu, 4 May 2023 21:17:18 +0000 (23:17 +0200)
src/lib/dhcp/option_dnr.cc
src/lib/dhcp/option_dnr.h
src/lib/dhcp/tests/option_dnr_unittest.cc

index 314327a32ca590ba4a4e97dd43ca07e990354f61..6a1d6a95a34c600287a0e46795cb1fde9e9cfef4 100644 (file)
@@ -12,6 +12,8 @@
 #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;
 
@@ -23,6 +25,21 @@ OptionDnr6::OptionDnr6(OptionBufferConstIter begin, OptionBufferConstIter end)
     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>());
@@ -205,8 +222,137 @@ OptionDnr6::getAdn() const {
 }
 
 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) {
index efc1c40c5bc6395a333027f0d57dd292bad7fb36..537548508a0c432fea8c8a41f836b8f2af6e4de0 100644 (file)
@@ -22,6 +22,14 @@ public:
     }
 };
 
+/// @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
@@ -58,8 +66,12 @@ public:
     /// @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);
+
+    OptionDnr6(const uint16_t service_priority, const std::string& adn);
+
     virtual OptionPtr clone() const;
-    virtual void pack(util::OutputBuffer& buf, bool check) 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;
@@ -190,7 +202,18 @@ private:
     ///
     /// The field should be encoded following the rules in
     /// Section 2.1 of [I-D.ietf-dnsop-svcb-https].
-    void checkSvcParams() const;
+    void checkSvcParams(bool from_wire_data = true);
+
+    /// @brief Sets Authentication domain name from given string.
+    ///
+    /// 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.
+    ///
+    /// @param adn string representation of ADN FQDN
+    void setAdn(const std::string& adn);
+    void checkFields();
 };
 
 class OptionDnr4 : public Option {
index 75ff6556e964aaa4e96950687f740651a859a449..37894ea273b0cc08e8be3d3d0ba7399f7e0fd872 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <config.h>
 
+#include <asiolink/io_address.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/opaque_data_tuple.h>
 #include <dhcp/option_dnr.h>
@@ -23,7 +24,7 @@ namespace {
 // 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
@@ -64,7 +65,7 @@ TEST(OptionDnr6Test, constructorAdnOnlyMode) {
               "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
@@ -77,7 +78,7 @@ TEST(OptionDnr6Test, constructorDataTruncated) {
     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
@@ -92,7 +93,7 @@ TEST(OptionDnr6Test, onlyWhitespaceFqdn) {
     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
@@ -108,7 +109,7 @@ TEST(OptionDnr6Test, noAdnFqdn) {
     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
@@ -123,7 +124,7 @@ TEST(OptionDnr6Test, truncatedFqdn) {
     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
@@ -141,7 +142,7 @@ TEST(OptionDnr6Test, addrLenTruncated) {
     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
@@ -159,7 +160,7 @@ TEST(OptionDnr6Test, addrLenZero) {
     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
@@ -177,7 +178,7 @@ TEST(OptionDnr6Test, addrLenNot16Modulo) {
     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
@@ -232,7 +233,7 @@ TEST(OptionDnr6Test, validIpV6Addresses) {
               "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
@@ -253,7 +254,7 @@ TEST(OptionDnr6Test, truncatedIpV6Addresses) {
     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
@@ -261,7 +262,7 @@ TEST(OptionDnr6Test, svcParamsIncluded) {
         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
@@ -302,4 +303,181 @@ TEST(OptionDnr6Test, svcParamsIncluded) {
               "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