]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5016] Option6PDExclude holds subnet id rather than entire prefix.
authorMarcin Siodelski <marcin@isc.org>
Tue, 25 Oct 2016 13:14:43 +0000 (15:14 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 26 Oct 2016 06:31:02 +0000 (08:31 +0200)
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/tests/renew_unittest.cc
src/bin/dhcp6/tests/sarr_unittest.cc
src/lib/dhcp/option6_pdexclude.cc
src/lib/dhcp/option6_pdexclude.h
src/lib/dhcp/tests/option6_pdexclude_unittest.cc

index ede67d9d2bc69793bb46cc823c5729f400f6cbb7..1c8ffdcc4372428438f19469e6be118185a77878 100644 (file)
@@ -1600,6 +1600,8 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, const Pkt6Ptr& answer,
                 Pool6Ptr pool = boost::dynamic_pointer_cast<
                     Pool6>(subnet->getPool(Lease::TYPE_PD, (*l)->addr_));
                 if (pool && pool->getExcludedPrefixLength() > 0) {
+                    std::cout << pool->getExcludedPrefix() << "/"
+                        << (unsigned)pool->getExcludedPrefixLength() << std::endl;
                     OptionPtr opt(new Option6PDExclude((*l)->addr_,
                                                        (*l)->prefixlen_,
                                                        pool->getExcludedPrefix(),
index 808bfa2e1c3648bb254fb9560f74ee1c822a69cc..07324f24332ccd3ee0631ede21f6d05a7391fe73 100644 (file)
@@ -380,7 +380,8 @@ TEST_F(RenewTest, renewWithExcludedPrefix) {
     ASSERT_TRUE(option);
     Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
     ASSERT_TRUE(pd_exclude);
-    EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix().toText());
+    EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix(IOAddress("3000::"),
+                                                          80).toText());
     EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
 }
 
index 6a9e6960e0d50ac79dccafa406dbd26abfd622f5..1a085f802fa1ef179deebba76b5b65885e41b86b 100644 (file)
@@ -368,7 +368,8 @@ TEST_F(SARRTest, directClientExcludedPrefix) {
     ASSERT_TRUE(option);
     Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
     ASSERT_TRUE(pd_exclude);
-    EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix().toText());
+    EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix(IOAddress("2001:db8:3::"),
+                                                                64).toText());
     EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
 }
 
index a29b7bbfec6b6adc8a6de68eb1c1d5ed6696d046..d8b78d4a2039eb0ec01777eeab62fc90cf679d2f 100644 (file)
@@ -11,6 +11,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/option6_pdexclude.h>
 #include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
 #include <util/io_utilities.h>
 
 #include <boost/dynamic_bitset.hpp>
@@ -31,38 +32,36 @@ Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_pre
                                    const isc::asiolink::IOAddress& excluded_prefix,
                                    const uint8_t excluded_prefix_length)
     : Option(V6, D6O_PD_EXCLUDE),
-      delegated_prefix_(delegated_prefix),
-      delegated_prefix_length_(delegated_prefix_length),
-      excluded_prefix_(excluded_prefix),
-      excluded_prefix_length_(excluded_prefix_length) {
+      excluded_prefix_length_(excluded_prefix_length),
+      subnet_id_() {
 
     // Expecting v6 prefixes of sane length.
-    if (!delegated_prefix_.isV6() || !excluded_prefix_.isV6() ||
-        (delegated_prefix_length_ > 128) || (excluded_prefix_length_ > 128)) {
+    if (!delegated_prefix.isV6() || !excluded_prefix.isV6() ||
+        (delegated_prefix_length > 128) || (excluded_prefix_length_ > 128)) {
         isc_throw(BadValue, "invalid delegated or excluded prefix values specified: "
-                  << delegated_prefix_ << "/"
-                  << static_cast<int>(delegated_prefix_length_) << ", "
-                  << excluded_prefix_ << "/"
+                  << delegated_prefix << "/"
+                  << static_cast<int>(delegated_prefix_length) << ", "
+                  << excluded_prefix << "/"
                   << static_cast<int>(excluded_prefix_length_));
     }
 
     // Excluded prefix must be longer than the delegated prefix.
-    if (excluded_prefix_length_ <= delegated_prefix_length_) {
+    if (excluded_prefix_length_ <= delegated_prefix_length) {
         isc_throw(BadValue, "length of the excluded prefix "
-                  << excluded_prefix_ << "/"
+                  << excluded_prefix << "/"
                   << static_cast<int>(excluded_prefix_length_)
                   << " must be greater than the length of the"
-                  " delegated prefix " << delegated_prefix_ << "/"
-                  << static_cast<int>(delegated_prefix_length_));
+                  " delegated prefix " << delegated_prefix << "/"
+                  << static_cast<int>(delegated_prefix_length));
     }
 
     // Both prefixes must share common part with a length equal to the
     // delegated prefix length.
-    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
     boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(),
                                                          delegated_prefix_bytes.rend());
 
-    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
+    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix.toBytes();
     boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(),
                                                         excluded_prefix_bytes.rend());
 
@@ -72,26 +71,53 @@ Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_pre
 
     if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) {
         isc_throw(BadValue, "excluded prefix "
-                  << excluded_prefix_ << "/"
+                  << excluded_prefix << "/"
                   << static_cast<int>(excluded_prefix_length_)
                   << " must have the same common prefix part of "
                   << static_cast<int>(delegated_prefix_length)
                   << " as the delegated prefix "
-                  << delegated_prefix_ << "/"
-                  << static_cast<int>(delegated_prefix_length_));
+                  << delegated_prefix << "/"
+                  << static_cast<int>(delegated_prefix_length));
     }
 
+
+    // Shifting prefix by delegated prefix length leaves us with only a
+    // subnet id part of the excluded prefix.
+    excluded_prefix_bits <<= delegated_prefix_length;
+
+    // Calculate subnet id length.
+    const uint8_t subnet_id_length = getSubnetIDLength(delegated_prefix_length,
+                                                       excluded_prefix_length);
+    for (uint8_t i = 0; i < subnet_id_length; ++i) {
+        // Retrieve bit representation of the current byte.
+        const boost::dynamic_bitset<uint8_t> first_byte = excluded_prefix_bits >> 120;
+
+        // Convert it to a numeric value.
+        uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());
+
+        // Zero padded excluded_prefix_bits follow when excluded_prefix_length_ is
+        // not divisible by 8.
+        if (i == subnet_id_length - 1) {
+            uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length;
+            if (length_delta % 8 != 0) {
+                uint8_t mask = 0xFF;
+                mask <<= (8 - (length_delta % 8));
+                val &= mask;
+            }
+        }
+        // Store calculated value in a buffer.
+        subnet_id_.push_back(val);
+
+        // Go to the next byte.
+        excluded_prefix_bits <<= 8;
+    }
 }
 
-Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
-                                   const uint8_t delegated_prefix_length,
-                                   OptionBufferConstIter begin,
+Option6PDExclude::Option6PDExclude(OptionBufferConstIter begin,
                                    OptionBufferConstIter end)
     : Option(V6, D6O_PD_EXCLUDE),
-      delegated_prefix_(delegated_prefix),
-      delegated_prefix_length_(delegated_prefix_length),
-      excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
-      excluded_prefix_length_(0) {
+      excluded_prefix_length_(0),
+      subnet_id_() {
     unpack(begin, end);
 }
 
@@ -102,42 +128,21 @@ Option6PDExclude::clone() const {
 
 void
 Option6PDExclude::pack(isc::util::OutputBuffer& buf) const {
+    // Make sure that the subnet identifier is valid. It should never
+    // be empty.
+    if ((excluded_prefix_length_ == 0) || subnet_id_.empty()) {
+        isc_throw(BadValue, "subnet identifier of a Prefix Exclude option"
+                  " must not be empty");
+    }
+
     // Header = option code and length.
     packHeader(buf);
 
     // Excluded prefix length is always 1 byte long field.
     buf.writeUint8(excluded_prefix_length_);
 
-    // Retrieve entire prefix and convert it to bit representation.
-    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
-    boost::dynamic_bitset<uint8_t> bits(excluded_prefix_bytes.rbegin(),
-                                        excluded_prefix_bytes.rend());
-
-    // Shifting prefix by delegated prefix length leaves us with only a
-    // subnet id part of the excluded prefix.
-    bits = bits << delegated_prefix_length_;
-
-    // Calculate subnet id length.
-    const uint8_t subnet_id_length = getSubnetIDLength();
-    for (uint8_t i = 0; i < subnet_id_length; ++i) {
-        // Retrieve bit representation of the current byte.
-        const boost::dynamic_bitset<uint8_t> first_byte = bits >> 120;
-        // Convert it to a numeric value.
-        uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());
-
-        // Zero padded bits follow when excluded_prefix_length_ is not divisible by 8.
-        if (i == subnet_id_length - 1) {
-            uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length_;
-            uint8_t mask = 0xFF;
-            mask <<= (8 - (length_delta % 8));
-            val &= mask;
-        }
-        // Store calculated value in a buffer.
-        buf.writeUint8(val);
-
-        // Go to the next byte.
-        bits <<= 8;
-    }
+    // Write the subnet identifier.
+    buf.writeData(static_cast<const void*>(&subnet_id_[0]), subnet_id_.size());
 }
 
 void
@@ -152,24 +157,49 @@ Option6PDExclude::unpack(OptionBufferConstIter begin,
     }
 
     // We can safely read the excluded prefix length and move forward.
-    excluded_prefix_length_ = *begin++;
-
-    // We parsed the excluded prefix length so we can now determine the
-    // size of the IPv6 subnet id. The reminder of the option should
-    // include data of that size. If the option size is lower than the
-    // subnet id length we report an error.
-    const unsigned int subnet_id_length = getSubnetIDLength();
-    if (subnet_id_length > std::distance(begin, end)) {
-        isc_throw(BadValue, "truncated Prefix Exclude option, expected "
-                  "IPv6 subnet id length is " << subnet_id_length);
+    uint8_t excluded_prefix_length = *begin++;
+    if (excluded_prefix_length == 0) {
+        isc_throw(BadValue, "excluded prefix length must not be 0");
     }
 
+    std::vector<uint8_t> subnet_id_bytes(begin, end);
+
+    // Subnet id parsed, proceed to the end of the option.
+    begin = end;
+
+    uint8_t last_bits_num = excluded_prefix_length % 8;
+    if (last_bits_num > 0) {
+        *subnet_id_bytes.rbegin() = (*subnet_id_bytes.rbegin() >> (8 - last_bits_num)
+                                     << (8 - (last_bits_num)));
+    }
+
+    excluded_prefix_length_ = excluded_prefix_length;
+    subnet_id_.swap(subnet_id_bytes);
+}
+
+uint16_t
+Option6PDExclude::len() const {
+    return (getHeaderLen() + sizeof(excluded_prefix_length_) + subnet_id_.size());
+}
+
+std::string
+Option6PDExclude::toText(int indent) const {
+    std::ostringstream s;
+    s << headerToText(indent) << ": ";
+    s << "excluded-prefix-len=" << static_cast<unsigned>(excluded_prefix_length_)
+      << ", subnet-id=0x" << util::encode::encodeHex(subnet_id_);
+    return (s.str());
+}
+
+asiolink::IOAddress
+Option6PDExclude::getExcludedPrefix(const IOAddress& delegated_prefix,
+                                    const uint8_t delegated_prefix_length) const {
     // Get binary representation of the delegated prefix.
-    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
     //  We need to calculate how many bytes include the useful data and assign
     // zeros to remaining bytes (beyond the prefix length).
-    const uint8_t bytes_length = (delegated_prefix_length_ / 8) +
-        static_cast<uint8_t>(delegated_prefix_length_ % 8 != 0);
+    const uint8_t bytes_length = (delegated_prefix_length / 8) +
+        static_cast<uint8_t>(delegated_prefix_length % 8 != 0);
     std::fill(delegated_prefix_bytes.begin() + bytes_length,
               delegated_prefix_bytes.end(), 0);
 
@@ -177,18 +207,13 @@ Option6PDExclude::unpack(OptionBufferConstIter begin,
     boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(),
                                         delegated_prefix_bytes.rend());
 
-    // Convert subnet id to bit format.
-    std::vector<uint8_t> subnet_id_bytes(begin, end);
-    boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_bytes.rbegin(),
-                                                  subnet_id_bytes.rend());
-
-    // Subnet id parsed, proceed to the end of the option.
-    begin = end;
+    boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_.rbegin(),
+                                                  subnet_id_.rend());
 
     // Concatenate the delegated prefix with subnet id. The resulting prefix
     // is an excluded prefix in bit format.
     for (int i = subnet_id_bits.size() - 1; i >= 0; --i) {
-        bits.set(128 - delegated_prefix_length_ - subnet_id_bits.size() + i,
+        bits.set(128 - delegated_prefix_length - subnet_id_bits.size() + i,
                  subnet_id_bits.test(i));
     }
 
@@ -197,28 +222,14 @@ Option6PDExclude::unpack(OptionBufferConstIter begin,
     boost::to_block_range(bits, bytes.rbegin());
 
     // And create a prefix object from bytes.
-    excluded_prefix_ = IOAddress::fromBytes(AF_INET6, &bytes[0]);
-}
-
-uint16_t
-Option6PDExclude::len() const {
-    return (getHeaderLen() + sizeof(excluded_prefix_length_)
-            + getSubnetIDLength());
-}
-
-std::string
-Option6PDExclude::toText(int indent) const {
-    std::ostringstream s;
-    s << headerToText(indent) << ": ";
-    s << excluded_prefix_ << "/"
-      << static_cast<int>(excluded_prefix_length_);
-    return (s.str());
+    return (IOAddress::fromBytes(AF_INET6, &bytes[0]));
 }
 
 uint8_t
-Option6PDExclude::getSubnetIDLength() const {
-    uint8_t subnet_id_length_bits = excluded_prefix_length_ -
-        delegated_prefix_length_ - 1;
+Option6PDExclude::getSubnetIDLength(const uint8_t delegated_prefix_length,
+                                    const uint8_t excluded_prefix_length) const {
+    uint8_t subnet_id_length_bits = excluded_prefix_length -
+        delegated_prefix_length - 1;
     uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1;
     return (subnet_id_length);
 }
index ffcfd1566e95275feb9382d65079118c8685c855..511f38669d201f184e1b589a2fa27ce74b552ec4 100644 (file)
@@ -47,9 +47,7 @@ public:
     ///
     /// @param begin Lower bound of the buffer to create option from.
     /// @param end Upper bound of the buffer to create option from.
-    Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
-                     const uint8_t delegated_prefix_length,
-                     OptionBufferConstIter begin, OptionBufferConstIter end);
+    Option6PDExclude(OptionBufferConstIter begin, OptionBufferConstIter end);
 
     /// @brief Copies this option and returns a pointer to the copy.
     virtual OptionPtr clone() const;
@@ -85,44 +83,41 @@ public:
     /// @param ident Number of spaces to be inserted before the text.
     virtual std::string toText(int indent = 0) const;
 
-    /// @brief Returns delegated prefix.
-    isc::asiolink::IOAddress getDelegatedPrefix() const {
-        return (delegated_prefix_);
-    }
-
-    /// @brief Returns delegated prefix length.
-    uint8_t getDelegatedPrefixLength() const {
-        return (delegated_prefix_length_);
-    }
-
     /// @brief Returns excluded prefix.
-    isc::asiolink::IOAddress getExcludedPrefix() const {
-        return (excluded_prefix_);
-    }
+    ///
+    /// Assembles excluded prefix from a delegated prefix and IPv6 subnet id
+    /// specified as in RFC6603, section 4.2.
+    ///
+    /// @param delegated_prefix Delegated prefix for which excluded prefix will
+    /// be returned.
+    /// @param delegated_prefix_length Delegated prefix length.
+    asiolink::IOAddress
+    getExcludedPrefix(const asiolink::IOAddress& delegated_prefix,
+                      const uint8_t delegated_prefix_length) const;
 
     /// @brief Returns excluded prefix length.
     uint8_t getExcludedPrefixLength() const {
         return (excluded_prefix_length_);
     }
 
+    /// @brief Returns an excluded prefix in a binary format.
+    const std::vector<uint8_t>& getExcludedPrefixSubnetID() const {
+        return (subnet_id_);
+    }
+
 private:
 
     /// @brief Returns IPv6 subnet ID length in octets.
     ///
     /// The IPv6 subnet ID length is between 1 and 16 octets.
-    uint8_t getSubnetIDLength() const;
-
-    /// @brief Holds delegated prefix.
-    isc::asiolink::IOAddress delegated_prefix_;
-
-    /// @brief Holds delegated prefix length,
-    uint8_t delegated_prefix_length_;
-
-    /// @brief Holds excluded prefix.
-    isc::asiolink::IOAddress excluded_prefix_;
+    uint8_t getSubnetIDLength(const uint8_t delegated_prefix_length,
+                              const uint8_t excluded_prefix_length) const;
 
     /// @brief Holds excluded prefix length.
     uint8_t excluded_prefix_length_;
+
+    /// @brief Subnet identifier as described in RFC6603, section 4.2.
+    std::vector<uint8_t> subnet_id_;
 };
 
 /// @brief Pointer to the @ref Option6PDExclude object.
index b7dfa62dfa46e68a78f73edb825453047d9c0bc4..7626991c000211bd25c67a0ad8dabd5707f3a118 100644 (file)
@@ -13,6 +13,7 @@
 #include <dhcp/option6_pdexclude.h>
 #include <dhcpsrv/pool.h>
 #include <util/buffer.h>
+#include <util/encode/hex.h>
 #include <gtest/gtest.h>
 
 using namespace isc;
@@ -32,10 +33,10 @@ const IOAddress beef01("2001:db8:dead:beef::01");
 TEST(Option6PDExcludeTest, constructor) {
     Option6PDExclude option = Option6PDExclude(beef, 56, beef01, 60);
 
-    EXPECT_EQ(beef, option.getDelegatedPrefix());
-    EXPECT_EQ(56, option.getDelegatedPrefixLength());
-    EXPECT_EQ(beef01, option.getExcludedPrefix());
+    EXPECT_EQ(bee0, option.getExcludedPrefix(beef, 56));
     EXPECT_EQ(60, option.getExcludedPrefixLength());
+    EXPECT_EQ("E0", util::encode::encodeHex(option.getExcludedPrefixSubnetID()));
+
     // Total length is a sum of option header length, excluded prefix
     // length (always 1 byte) and delegated prefix length - excluded prefix
     // length rounded to bytes.
@@ -94,19 +95,17 @@ TEST(Option6PDExcludeTest, unpack1ByteSubnetId) {
     // Parse option.
     Option6PDExcludePtr option;
     ASSERT_NO_THROW(
-        option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::1"),
-                                          59, vec.begin() + 4, vec.end()))
+        option.reset(new Option6PDExclude(vec.begin() + 4, vec.end()))
     );
 
     // Make sure that the option has been parsed correctly.
-    EXPECT_EQ("2001:db8:dead:bee0::1", option->getDelegatedPrefix().toText());
-    EXPECT_EQ(59, static_cast<int>(option->getDelegatedPrefixLength()));
-    EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+    EXPECT_EQ("2001:db8:dead:beef::",
+              option->getExcludedPrefix(IOAddress("2001:db8:dead:bee0::1"), 59).toText());
     EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
 }
 
 // This test verifies parsing option wire format with subnet id of
-// 1 bytes.
+// 2 bytes.
 TEST(Option6PDExcludeTest, unpack2ByteSubnetId) {
     const uint8_t data[] = {
         0x00, 0x43,       // option code 67
@@ -118,14 +117,12 @@ TEST(Option6PDExcludeTest, unpack2ByteSubnetId) {
     // Parse option.
     Option6PDExcludePtr option;
     ASSERT_NO_THROW(
-        option.reset(new Option6PDExclude(IOAddress("2001:db8:dead::"),
-                                          48, vec.begin() + 4, vec.end()))
+        option.reset(new Option6PDExclude(vec.begin() + 4, vec.end()))
     );
 
     // Make sure that the option has been parsed correctly.
-    EXPECT_EQ("2001:db8:dead::", option->getDelegatedPrefix().toText());
-    EXPECT_EQ(48, static_cast<int>(option->getDelegatedPrefixLength()));
-    EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+    EXPECT_EQ("2001:db8:dead:beef::",
+              option->getExcludedPrefix(IOAddress("2001:db8:dead::"), 48).toText());
     EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
 }
 
@@ -140,13 +137,12 @@ TEST(Option6PDExcludeTest, unpackErrors) {
     std::vector<uint8_t> vec(data, data + sizeof(data));
 
     // Option has no IPv6 subnet id.
-    EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
-                                  59, vec.begin() + 4, vec.end() - 1),
+    EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end() - 1),
                  BadValue);
 
-    // Option has IPv6 subnet id of 1 byte, but it should have 2 bytes.
-    EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead::"), 48,
-                                  vec.begin() + 4, vec.end()),
+    // IPv6 subnet id is 0.
+    vec[4] = 0x00;
+    EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end()),
                  BadValue);
 }
 
@@ -154,7 +150,7 @@ TEST(Option6PDExcludeTest, unpackErrors) {
 // textual format.
 TEST(Option6PDExcludeTest, toText) {
     Option6PDExclude option(bee0, 59, beef, 64);
-    EXPECT_EQ("type=00067, len=00002: 2001:db8:dead:beef::/64",
+    EXPECT_EQ("type=00067, len=00002: excluded-prefix-len=64, subnet-id=0x78",
               option.toText());
 }