#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>
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());
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);
}
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
}
// 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);
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));
}
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);
}
///
/// @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;
/// @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.
#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;
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.
// 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
// 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()));
}
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);
}
// 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());
}