From: Marcin Siodelski Date: Tue, 25 Oct 2016 13:14:43 +0000 (+0200) Subject: [5016] Option6PDExclude holds subnet id rather than entire prefix. X-Git-Tag: trac5057_base~1^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9145a3c0bc0b35e319753ef70f6ea0a97e1c6eee;p=thirdparty%2Fkea.git [5016] Option6PDExclude holds subnet id rather than entire prefix. --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index ede67d9d2b..1c8ffdcc43 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -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(), diff --git a/src/bin/dhcp6/tests/renew_unittest.cc b/src/bin/dhcp6/tests/renew_unittest.cc index 808bfa2e1c..07324f2433 100644 --- a/src/bin/dhcp6/tests/renew_unittest.cc +++ b/src/bin/dhcp6/tests/renew_unittest.cc @@ -380,7 +380,8 @@ TEST_F(RenewTest, renewWithExcludedPrefix) { ASSERT_TRUE(option); Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast(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(pd_exclude->getExcludedPrefixLength())); } diff --git a/src/bin/dhcp6/tests/sarr_unittest.cc b/src/bin/dhcp6/tests/sarr_unittest.cc index 6a9e6960e0..1a085f802f 100644 --- a/src/bin/dhcp6/tests/sarr_unittest.cc +++ b/src/bin/dhcp6/tests/sarr_unittest.cc @@ -368,7 +368,8 @@ TEST_F(SARRTest, directClientExcludedPrefix) { ASSERT_TRUE(option); Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast(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(pd_exclude->getExcludedPrefixLength())); } diff --git a/src/lib/dhcp/option6_pdexclude.cc b/src/lib/dhcp/option6_pdexclude.cc index a29b7bbfec..d8b78d4a20 100644 --- a/src/lib/dhcp/option6_pdexclude.cc +++ b/src/lib/dhcp/option6_pdexclude.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -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(delegated_prefix_length_) << ", " - << excluded_prefix_ << "/" + << delegated_prefix << "/" + << static_cast(delegated_prefix_length) << ", " + << excluded_prefix << "/" << static_cast(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(excluded_prefix_length_) << " must be greater than the length of the" - " delegated prefix " << delegated_prefix_ << "/" - << static_cast(delegated_prefix_length_)); + " delegated prefix " << delegated_prefix << "/" + << static_cast(delegated_prefix_length)); } // Both prefixes must share common part with a length equal to the // delegated prefix length. - std::vector delegated_prefix_bytes = delegated_prefix_.toBytes(); + std::vector delegated_prefix_bytes = delegated_prefix.toBytes(); boost::dynamic_bitset delegated_prefix_bits(delegated_prefix_bytes.rbegin(), delegated_prefix_bytes.rend()); - std::vector excluded_prefix_bytes = excluded_prefix_.toBytes(); + std::vector excluded_prefix_bytes = excluded_prefix.toBytes(); boost::dynamic_bitset 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(excluded_prefix_length_) << " must have the same common prefix part of " << static_cast(delegated_prefix_length) << " as the delegated prefix " - << delegated_prefix_ << "/" - << static_cast(delegated_prefix_length_)); + << delegated_prefix << "/" + << static_cast(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 first_byte = excluded_prefix_bits >> 120; + + // Convert it to a numeric value. + uint8_t val = static_cast(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 excluded_prefix_bytes = excluded_prefix_.toBytes(); - boost::dynamic_bitset 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 first_byte = bits >> 120; - // Convert it to a numeric value. - uint8_t val = static_cast(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(&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 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(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 delegated_prefix_bytes = delegated_prefix_.toBytes(); + std::vector 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(delegated_prefix_length_ % 8 != 0); + const uint8_t bytes_length = (delegated_prefix_length / 8) + + static_cast(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 bits(delegated_prefix_bytes.rbegin(), delegated_prefix_bytes.rend()); - // Convert subnet id to bit format. - std::vector subnet_id_bytes(begin, end); - boost::dynamic_bitset 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 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(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); } diff --git a/src/lib/dhcp/option6_pdexclude.h b/src/lib/dhcp/option6_pdexclude.h index ffcfd1566e..511f38669d 100644 --- a/src/lib/dhcp/option6_pdexclude.h +++ b/src/lib/dhcp/option6_pdexclude.h @@ -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& 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 subnet_id_; }; /// @brief Pointer to the @ref Option6PDExclude object. diff --git a/src/lib/dhcp/tests/option6_pdexclude_unittest.cc b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc index b7dfa62dfa..7626991c00 100644 --- a/src/lib/dhcp/tests/option6_pdexclude_unittest.cc +++ b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include 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(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(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(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(option->getExcludedPrefixLength())); } @@ -140,13 +137,12 @@ TEST(Option6PDExcludeTest, unpackErrors) { std::vector 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()); }