IPv6Resrv::IPv6Resrv(const Type& type,
const asiolink::IOAddress& prefix,
const uint8_t prefix_len)
- : type_(type), prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
+ : type_(type), prefix_(asiolink::IOAddress("::")), prefix_len_(128),
+ pd_exclude_option_() {
// Validate and set the actual values.
set(type, prefix, prefix_len);
}
type_ = type;
prefix_ = prefix;
prefix_len_ = prefix_len;
+ pd_exclude_option_.reset();
+}
+
+void
+IPv6Resrv::setPDExclude(const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len) {
+ if (excluded_prefix_len == 0) {
+ pd_exclude_option_.reset();
+ } else {
+ pd_exclude_option_.reset(new Option6PDExclude(prefix_, prefix_len_,
+ excluded_prefix,
+ excluded_prefix_len));
+ }
}
std::string
-IPv6Resrv::toText() const {
+IPv6Resrv::toText(bool display_pd_exclude_option) const {
std::ostringstream s;
s << prefix_;
// For PD, append prefix length.
- if (getType() == TYPE_PD) {
+ if (type_ == TYPE_PD) {
s << "/" << static_cast<int>(prefix_len_);
+ // If there is a Prefix Exclude option append it.
+ if (display_pd_exclude_option && pd_exclude_option_) {
+ s << " (excluded_prefix="
+ << pd_exclude_option_->getExcludedPrefix(prefix_,
+ prefix_len_).toText()
+ << "/"
+ << static_cast<int>(pd_exclude_option_->getExcludedPrefixLength())
+ << ")";
+ }
}
return (s.str());
}
+std::string
+IPv6Resrv::PDExcludetoText() const {
+ if (pd_exclude_option_) {
+ std::ostringstream s;
+ s << pd_exclude_option_->getExcludedPrefix(prefix_,
+ prefix_len_).toText()
+ << "/"
+ << static_cast<int>(pd_exclude_option_->getExcludedPrefixLength());
+ return (s.str());
+ } else {
+ return ("");
+ }
+}
+
bool
IPv6Resrv::operator==(const IPv6Resrv& other) const {
return (type_ == other.type_ &&
// Set reservations (prefixes)
const IPv6ResrvRange& pd_resv = getIPv6Reservations(IPv6Resrv::TYPE_PD);
resvs = Element::createList();
+ bool has_pd_exclude_option(false);
BOOST_FOREACH(auto const& resv, pd_resv) {
- resvs->add(Element::create(resv.second.toText()));
+ if (resv.second.getPDExclude()) {
+ has_pd_exclude_option = true;
+ }
+ resvs->add(Element::create(resv.second.toText(false)));
}
map->set("prefixes", resvs);
+ // Set Prefix Exclude options.
+ if (has_pd_exclude_option) {
+ ElementPtr options = Element::createList();
+ BOOST_FOREACH(auto const& resv, pd_resv) {
+ options->add(Element::create(resv.second.PDExcludetoText()));
+ }
+ map->set("excluded-prefixes", options);
+ }
// Set the hostname
const std::string& hostname = getHostname();
map->set("hostname", Element::create(hostname));
-// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
#include <dhcp/classify.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
+#include <dhcp/option6_pdexclude.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/subnet_id.h>
#include <boost/shared_ptr.hpp>
///
/// The class holds the address and prefix length, a value of 128
/// for the latter implying that the reservation is for a single
-/// IPv6 address.
+/// IPv6 address. For prefix delegations it includes an optional
+/// Prefix Exclude option.
class IPv6Resrv {
public:
return (type_);
}
+ /// @brief Returns the Prefix Exclude option.
+ ///
+ /// @return Prefix Exclude option.
+ Option6PDExcludePtr getPDExclude() const {
+ return (pd_exclude_option_);
+ }
+
/// @brief Sets a new prefix and prefix length.
///
+ /// @note This removes the Prefix Exclude option when it exists.
+ ///
/// @param type Reservation type: NA or PD.
/// @param prefix New prefix.
/// @param prefix_len New prefix length.
void set(const Type& type, const asiolink::IOAddress& prefix,
const uint8_t prefix_len);
+ /// @brief Sets the Prefix Exclude option.
+ ///
+ /// @note excluded_prefix_len == 0 means there's no excluded prefix at all.
+ ///
+ /// @param excluded_prefix specifies an excluded prefix as per RFC6603.
+ /// @param excluded_prefix_len specifies length of an excluded prefix.
+ void setPDExclude(const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len);
+
/// @brief Returns information about the reservation in the textual format.
- std::string toText() const;
+ ///
+ /// @param display_pd_exclude_option When true (default) add the Prefix
+ /// Exclude option if it exists.
+ std::string toText(bool display_pd_exclude_option = true) const;
+
+ /// @brief Returns information about the Prefix Exclude option of
+ /// the reservation the textual format.
+ std::string PDExcludetoText() const;
/// @brief Equality operator.
///
+ /// @note this compares only type, prefix and prefix length.
+ ///
/// @param other Reservation to compare to.
bool operator==(const IPv6Resrv& other) const;
private:
- Type type_; ///< Reservation type.
- asiolink::IOAddress prefix_; ///< Prefix
- uint8_t prefix_len_; ///< Prefix length.
+ Type type_; ///< Reservation type.
+ asiolink::IOAddress prefix_; ///< Prefix.
+ uint8_t prefix_len_; ///< Prefix length.
+ Option6PDExcludePtr pd_exclude_option_; ///< Prefix Exclude option.
};
/// @brief Collection of IPv6 reservations for the host.
<< static_cast<unsigned>(prefix_len_);
if (pd_exclude_option_) {
- s << ", excluded_prefix_len="
+ s << ", excluded_prefix="
+ << pd_exclude_option_->getExcludedPrefix(first_, prefix_len_).toText()
+ << "/"
<< static_cast<unsigned>(pd_exclude_option_->getExcludedPrefixLength());
}
return (s.str());
isc::BadValue, expected);
}
+// This test verifies that it is possible to manage prefix exclude option.
+TEST(IPv6ResrvTest, setPrefixExcludeOption) {
+ // Create a reservation using a prefix having a length of 48.
+ IPv6Resrv resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48);
+
+ // Add a Prefix Exclude option.
+ ASSERT_NO_THROW(resrv.setPDExclude(IOAddress("2001:db8:0:1::"), 64));
+ Option6PDExcludePtr opt = resrv.getPDExclude();
+ EXPECT_TRUE(opt);
+ EXPECT_EQ("2001:db8:0:1::",
+ opt->getExcludedPrefix(resrv.getPrefix(),
+ resrv.getPrefixLen()).toText());
+ EXPECT_EQ(64, opt->getExcludedPrefixLength());
+ string expected = "2001:db8::/48 (excluded_prefix=2001:db8:0:1::/64)";
+ EXPECT_EQ(expected, resrv.toText());
+
+ // Length 0 means remove.
+ EXPECT_NO_THROW(resrv.setPDExclude(IOAddress("1.2.3.4"), 0));
+ EXPECT_FALSE(resrv.getPDExclude());
+
+ // Set also removes the option.
+ EXPECT_NO_THROW(resrv.setPDExclude(IOAddress("2001:db8:0:1::"), 64));
+ EXPECT_TRUE(resrv.getPDExclude());
+ ASSERT_NO_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+ EXPECT_FALSE(resrv.getPDExclude());
+
+ // Error case: the excluded prefix not in the (delegated) prefix.
+ expected = "excluded prefix 2001:db8:1::/64 must have the same common";
+ expected += " prefix part of 48 as the delegated prefix";
+ expected += " 2001:db8::/48";
+ EXPECT_THROW_MSG(resrv.setPDExclude(IOAddress("2001:db8:1::"), 64),
+ BadValue, expected);
+
+ // Error case: the excluded prefix length must be less than the
+ // (delegated) prefix.
+ expected = "length of the excluded prefix 2001:db8::/48";
+ expected += " must be greater than the length of the delegated prefix";
+ expected += " 2001:db8::/48";
+ EXPECT_THROW_MSG(resrv.setPDExclude(IOAddress("2001:db8::"), 48),
+ BadValue, expected);
+}
+
// This test checks that the equality operators work fine.
TEST(IPv6ResrvTest, equal) {
EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
IOAddress("2001:db8:1::cafe")));
host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
IOAddress("2001:db8:1:1::"), 64));
- host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
- IOAddress("2001:db8:1:2::"), 64));
+ IPv6Resrv res(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:2::"), 64);
+ res.setPDExclude(IOAddress("2001:db8:1:2:3::"), 80);
+ host->addReservation(IPv6Resrv(res));
host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
IOAddress("2001:db8:1::1")));
);
" ipv6_reservation0=2001:db8:1::cafe"
" ipv6_reservation1=2001:db8:1::1"
" ipv6_reservation2=2001:db8:1:1::/64"
- " ipv6_reservation3=2001:db8:1:2::/64",
+ " ipv6_reservation3=2001:db8:1:2::/64"
+ " (excluded_prefix=2001:db8:1:2:3::/80)",
host->toText());
// Reset some of the data and make sure that the output is affected.
" ipv6_reservation1=2001:db8:1::1"
" ipv6_reservation2=2001:db8:1:1::/64"
" ipv6_reservation3=2001:db8:1:2::/64"
+ " (excluded_prefix=2001:db8:1:2:3::/80)"
" negative cached",
host->toText());
IOAddress("192.0.2.3"),
"myhost.example.com")));
- // Add 4 reservations: 2 for NAs, 2 for PDs.
+ // Add 3 reservations: 2 for NAs, 1 for PDs.
ASSERT_NO_THROW(
host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
IOAddress("2001:db8:1::cafe")));
host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
IOAddress("2001:db8:1:1::"), 64));
- host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
- IOAddress("2001:db8:1:2::"), 64));
host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
IOAddress("2001:db8:1::1")));
);
"\"hw-address\": \"01:02:03:04:05:06\", "
"\"ip-addresses\": [ \"2001:db8:1::cafe\", \"2001:db8:1::1\" ], "
"\"option-data\": [ ], "
- "\"prefixes\": [ \"2001:db8:1:1::/64\", \"2001:db8:1:2::/64\" ], "
+ "\"prefixes\": [ \"2001:db8:1:1::/64\" ], "
"\"user-context\": { \"comment\": \"a host reservation\" } "
"}",
host->toElement6()->str());
+ // Add a Prefix Exclude option.
+ ASSERT_NO_THROW(
+ IPv6Resrv res(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:2::"), 64);
+ res.setPDExclude(IOAddress("2001:db8:1:2:3::"), 80);
+ host->addReservation(res);
+ );
+
// Reset some of the data and make sure that the output is affected.
host->setHostname("");
host->removeIPv4Reservation();
EXPECT_EQ("{ "
"\"client-classes\": [ ], "
+ "\"excluded-prefixes\": [ \"\", \"2001:db8:1:2:3::/80\" ], "
"\"hostname\": \"\", "
"\"hw-address\": \"01:02:03:04:05:06\", "
"\"ip-addresses\": [ \"2001:db8:1::cafe\", \"2001:db8:1::1\" ], "
Pool6 pool3(IOAddress("2001:db8:1::"), 96, 112,
IOAddress("2001:db8:1::1000"), 120);
EXPECT_EQ("type=IA_PD, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=112,"
- " excluded_prefix_len=120",
+ " excluded_prefix=2001:db8:1::1000/120",
pool3.toText());
Pool6 pool4(Lease::TYPE_NA, IOAddress("2001:db8::"),