]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1387] Checkpoint: added PPexclude
authorFrancis Dupont <fdupont@isc.org>
Mon, 1 Jul 2024 13:43:47 +0000 (15:43 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 4 Sep 2024 13:09:40 +0000 (15:09 +0200)
src/lib/dhcpsrv/host.cc
src/lib/dhcpsrv/host.h
src/lib/dhcpsrv/pool.cc
src/lib/dhcpsrv/tests/host_unittest.cc
src/lib/dhcpsrv/tests/pool_unittest.cc

index 5d45192ee422a921d2ee9addf4c8687c133111eb..a1ea354c557db682718214b202caf0d6b9dfa9a5 100644 (file)
@@ -86,7 +86,8 @@ AuthKey::operator!=(const AuthKey& other) const {
 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);
 }
@@ -112,19 +113,55 @@ IPv6Resrv::set(const Type& type, const asiolink::IOAddress& prefix,
     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_ &&
@@ -607,10 +644,22 @@ Host::toElement6() const {
     // 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));
index fd9eb7541fa202b36e07ce471124166b4c977022..13c98ff36464b8014906ce0a8f734f6013070824 100644 (file)
@@ -1,4 +1,4 @@
-// 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
@@ -13,6 +13,7 @@
 #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>
@@ -157,7 +158,8 @@ private:
 ///
 /// 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:
 
@@ -205,8 +207,17 @@ 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.
@@ -216,11 +227,29 @@ public:
     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;
 
@@ -231,9 +260,10 @@ public:
 
 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.
index b726cc282e2adb6204961e14ee9e0a17b46dd0cb..f7a53f6d8d6a76791de41f0e415763609dee0f26 100644 (file)
@@ -427,7 +427,9 @@ Pool6::toText() const {
       << 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());
index 4ac51e112bb281714f235e8e0bf28dc171bdfd08..f2509f10787c9dc577bc40a01b0b1850fbb47e85 100644 (file)
@@ -119,6 +119,48 @@ TEST(IPv6ResrvTest, setPrefix) {
                      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) ==
@@ -1104,8 +1146,9 @@ TEST_F(HostTest, toText) {
                                        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")));
     );
@@ -1125,7 +1168,8 @@ TEST_F(HostTest, toText) {
               " 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.
@@ -1144,6 +1188,7 @@ TEST_F(HostTest, toText) {
               " 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());
 
@@ -1212,14 +1257,12 @@ TEST_F(HostTest, unparse) {
                                         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")));
     );
@@ -1248,11 +1291,18 @@ TEST_F(HostTest, unparse) {
               "\"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();
@@ -1272,6 +1322,7 @@ TEST_F(HostTest, unparse) {
 
     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\" ], "
index ee0dced878909645f546ff704c8af68f85de0a4d..7c01098911c45784e6ab42ed17ac395c5f3f06be 100644 (file)
@@ -473,7 +473,7 @@ TEST(Pool6Test, toText) {
     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::"),