#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/lease.h>
+#include <dhcpsrv/pool.h>
#include <dhcp6/tests/dhcp6_client.h>
#include <util/buffer.h>
#include <boost/foreach.hpp>
#include <cstdlib>
#include <time.h>
+using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
}
};
+/// @brief Returns leases which belong to specified pool.
+///
+/// @param config DHCP client configuration structure holding leases.
+/// @param pool Pool to which returned leases belong.
+/// @param [out] leases A vector in which the function will store leases
+/// found.
+void getLeasesByPool(const Dhcp6Client::Configuration& config,
+ const Pool6& pool, std::vector<Lease6>& leases) {
+ for (std::vector<Lease6>::const_iterator lease =
+ config.leases_.begin(); lease != config.leases_.end();
+ ++lease) {
+ // Check if prefix in range.
+ if (pool.inRange(lease->addr_)) {
+ // Found the matching lease.
+ leases.push_back(*lease);
+ }
+ }
+}
+
}; // end of anonymous namespace
namespace isc {
}
void
-Dhcp6Client::doSolicit() {
+Dhcp6Client::doSolicit(const bool always_apply_config) {
context_.query_ = createMsg(DHCPV6_SOLICIT);
if (forced_server_id_) {
context_.query_->addOption(forced_server_id_);
context_.response_ = receiveOneMsg();
// If using Rapid Commit and the server has responded with Reply,
- // let's apply received configuration.
- if (use_rapid_commit_ && context_.response_ &&
- context_.response_->getType() == DHCPV6_REPLY) {
+ // let's apply received configuration. We also apply the configuration
+ // for the Advertise if instructed to do so.
+ if (context_.response_ && (always_apply_config || (use_rapid_commit_ &&
+ context_.response_->getType() == DHCPV6_REPLY))) {
config_.clear();
applyRcvdConfiguration(context_.response_);
}
return (leases);
}
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddress(const IOAddress& address) const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease, IOAddress, &Lease::addr_>(address, true, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddressRange(const IOAddress& first,
+ const IOAddress& second) const {
+ std::vector<Lease6> leases;
+ getLeasesByPool(config_, Pool6(Lease::TYPE_NA, first, second), leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const {
+ std::vector<Lease6> leases;
+ getLeasesByPool(config_, Pool6(Lease::TYPE_PD, prefix, prefix_len,
+ delegated_len), leases);
+ return (leases);
+}
+
+bool
+Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address) const {
+ std::vector<Lease6> leases = getLeasesByAddress(address);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseForAddressRange(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& last) const {
+ std::vector<Lease6> leases = getLeasesByAddressRange(first, last);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ std::vector<Lease6> leases = getLeasesByAddress(prefix);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if (lease.prefixlen_ == prefix_len) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const {
+ std::vector<Lease6> leases = getLeasesByPrefixPool(prefix, prefix_len,
+ delegated_len);
+ return (!leases.empty());
+}
+
+
uint16_t
Dhcp6Client::getStatusCode(const uint32_t iaid) const {
std::map<uint32_t, uint16_t>::const_iterator status_code =
#include <dhcp/option6_client_fqdn.h>
#include <dhcpsrv/lease.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <util/staged_value.h>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <list>
/// i.e. sends a Solicit to the server and receives Advertise. It doesn't
/// set the lease configuration in the @c config_.
///
+ /// @param always_apply_config Apply received configuration even if the
+ /// Advertise message is received. Default value is false.
+ ///
/// @throw This function doesn't throw exceptions on its own, but it calls
/// functions that are not exception safe, so it may throw exceptions if
/// error occurs.
///
/// @todo Perform sanity checks on returned messages.
- void doSolicit();
+ void doSolicit(const bool always_apply_config = false);
/// @brief Sends a Renew to the server and receives the Reply.
///
/// @brief Returns leases with zero lifetimes.
std::vector<Lease6> getLeasesWithZeroLifetime() const;
+ /// @brief Returns leases by lease address/prefix.
+ ///
+ /// @param address Leased address.
+ ///
+ /// @return Vector containing leases for the specified address.
+ std::vector<Lease6> getLeasesByAddress(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns leases belonging to specified address range.
+ ///
+ /// @param first Lower bound of the address range.
+ /// @param second Upper bound of the address range.
+ ///
+ /// @return Vector containing leases belonging to specified address range.
+ std::vector<Lease6> getLeasesByAddressRange(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& second) const;
+
+ /// @brief Returns leases belonging to prefix pool.
+ ///
+ /// @param prefix Prefix of the pool.
+ /// @param prefix_len Prefix length.
+ /// @param delegated_len Delegated prefix length.
+ ///
+ /// @return Vector containing leases belonging to specified prefix pool.
+ std::vector<Lease6> getLeasesByPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const;
+
+ /// @brief Checks if client has lease for the specified address.
+ ///
+ /// @param address Address for which lease should be found.
+ ///
+ /// @return true if client has lease for the address, false otherwise.
+ bool hasLeaseForAddress(const asiolink::IOAddress& address) const;
+
+ /// @brief Checks if client has a lease for an address within range.
+ ///
+ /// @param first Lower bound of the address range.
+ /// @param last Upper bound of the address range.
+ ///
+ /// @return true if client has lease for the address within the range,
+ /// false otherwise.
+ bool hasLeaseForAddressRange(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& last) const;
+
+ /// @brief Checks if client has a lease for a prefix.
+ ///
+ /// @param prefix Prefix.
+ /// @param prefix_len Prefix length.
+ ///
+ /// @return true if client has a lease for the specified prefix, false
+ /// otherwise.
+ bool hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const;
+
+ /// @brief Checks if client has a lease belonging to a prefix pool.
+ ///
+ /// @param prefix Pool prefix.
+ /// @param prefix_len Prefix length.
+ /// @param delegated_len Delegated prefix length.
+ ///
+ /// @return true if client has a lease belonging to specified pool,
+ /// false otherwise.
+ bool hasLeaseForPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const;
+
/// @brief Returns the value of the global status code for the last
/// transaction.
uint16_t getStatusCode() const {
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
#include <dhcp6/tests/dhcp6_client.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/lexical_cast.hpp>
+#include <list>
+#include <sstream>
using namespace isc;
using namespace isc::asiolink;
" \"ip-addresses\": [ \"2001:db8:1::2\" ]"
" } ]"
" } ]"
+ "}",
+
+ // Configuration 3:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+ " \"interface\" : \"eth0\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04\","
+ " \"ip-addresses\": [ \"2001:db8:1:1::1\", \"2001:db8:1:1::2\","
+ "\"2001:db8:1:1::3\" ],"
+ " \"prefixes\": [ \"3000:1:1::/32\", \"3000:1:2::/32\","
+ "\"3000:1:3::/32\" ]"
+ " } ]"
+ " } ]"
"}"
};
+class Reservation {
+public:
+ Reservation(const std::string& resource);
+
+ bool isEmpty() const;
+
+ bool isPrefix() const;
+
+ static const Reservation& UNSPEC();
+
+ operator std::string() const;
+
+private:
+ IOAddress prefix_;
+ uint8_t prefix_len_;
+};
+
+Reservation::Reservation(const std::string& resource)
+ : prefix_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_len_(0) {
+ size_t slash_pos = resource.find("/");
+ if ((slash_pos != std::string::npos) && (slash_pos < resource.size() - 1)) {
+ prefix_len_ = boost::lexical_cast<unsigned int>(resource.substr(slash_pos + 1));
+ }
+ prefix_ = IOAddress(resource.substr(0, slash_pos));
+}
+
+bool
+Reservation::isEmpty() const {
+ return (prefix_.isV6Zero());
+}
+
+bool
+Reservation::isPrefix() const {
+ return (!isEmpty() && (prefix_len_ > 0));
+}
+
+const Reservation& Reservation::UNSPEC() {
+ static Reservation unspec("::/0");
+ return (unspec);
+}
+
+Reservation::operator std::string() const {
+ std::ostringstream s;
+ s << "\"" << prefix_;
+ if (prefix_len_ > 0) {
+ s << "/" << static_cast<int>(prefix_len_);
+ }
+ s << "\"";
+ return (s.str());
+}
+
/// @brief Test fixture class for testing host reservations
class HostTest : public Dhcpv6SrvTest {
public:
+
+
/// @brief Constructor.
///
/// Sets up fake interfaces.
EXPECT_EQ(exp_ip_address, lease_client.addr_.toText());
}
+ static void storeReservation(const Reservation& r,
+ std::list<std::string>& address_list,
+ std::list<std::string>& prefix_list);
+
+ std::string configString(const DUID& duid,
+ const Reservation& r1 = Reservation::UNSPEC(),
+ const Reservation& r2 = Reservation::UNSPEC(),
+ const Reservation& r3 = Reservation::UNSPEC(),
+ const Reservation& r4 = Reservation::UNSPEC(),
+ const Reservation& r5 = Reservation::UNSPEC(),
+ const Reservation& r6 = Reservation::UNSPEC()) const;
+
/// @brief Interface Manager's fake configuration control.
IfaceMgrTestConfig iface_mgr_test_config_;
};
+void
+HostTest::storeReservation(const Reservation& r,
+ std::list<std::string>& address_list,
+ std::list<std::string>& prefix_list) {
+ if (!r.isEmpty()) {
+ if (r.isPrefix()) {
+ prefix_list.push_back(r);
+ } else {
+ address_list.push_back(r);
+ }
+ }
+}
+
+std::string
+HostTest::configString(const DUID& duid,
+ const Reservation& r1, const Reservation& r2,
+ const Reservation& r3, const Reservation& r4,
+ const Reservation& r5, const Reservation& r6) const {
+ std::list<std::string> address_list;
+ std::list<std::string> prefix_list;
+ storeReservation(r1, address_list, prefix_list);
+ storeReservation(r2, address_list, prefix_list);
+ storeReservation(r3, address_list, prefix_list);
+ storeReservation(r4, address_list, prefix_list);
+ storeReservation(r5, address_list, prefix_list);
+ storeReservation(r6, address_list, prefix_list);
+
+ std::ostringstream s;
+ s << "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+ " \"pd-pools\": [ { \"prefix\": \"3001::\", \"prefix-len\": 32,"
+ " \"delegated-len\": 64 } ],"
+ " \"interface\" : \"eth0\"";
+
+ if (!address_list.empty() || !prefix_list.empty()) {
+ s << ","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": ";
+ s << "\"" << duid.toText() << "\",";
+
+ if (!address_list.empty()) {
+ s << " \"ip-addresses\": [ "
+ << boost::algorithm::join(address_list, ", ")
+ << "]";
+ }
+
+ if (!prefix_list.empty()) {
+ if (!address_list.empty()) {
+ s << ", ";
+ }
+ s << " \"prefixes\": [ "
+ << boost::algorithm::join(prefix_list, ", ")
+ << "]";
+ }
+
+ s << " } ]";
+ }
+
+ s << " } ]"
+ "}";
+
+ return (s.str());
+}
+
+
// Test basic SARR scenarios against a server configured with one subnet
// containing two reservations. One reservation with a hostname, one
// without a hostname. Scenarios:
testReservationByIdentifier(client, 2, "2001:db8:1::2");
}
+TEST_F(HostTest, reservationMultipleIASolicit) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:04");
+
+ const std::string c = configString(*client.getDuid(),
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/32"),
+ Reservation("3000:1:2::/32"),
+ Reservation("3000:1:3::/32"));
+
+ ASSERT_NO_THROW(configure(c, *client.getServer()));
+
+ client.requestAddress(1234);
+ client.requestAddress(2345);
+ client.requestAddress(3456);
+ client.requestPrefix(5678);
+ client.requestPrefix(6789);
+ client.requestPrefix(7890);
+
+ // Send Solicit and require that the client saves received configuration
+ // so as we can test that advertised configuration is correct.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ ASSERT_EQ(6, client.getLeaseNum());
+
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::1")));
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:2::"), 32));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32));
+}
+
+TEST_F(HostTest, reservationMultipleIARequest) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:04");
+
+ const std::string c = configString(*client.getDuid(),
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/32"),
+ Reservation("3000:1:2::/32"),
+ Reservation("3000:1:3::/32"));
+
+ ASSERT_NO_THROW(configure(c, *client.getServer()));
+
+ client.requestAddress(1234);
+ client.requestAddress(2345);
+ client.requestAddress(3456);
+ client.requestPrefix(5678);
+ client.requestPrefix(6789);
+ client.requestPrefix(7890);
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ ASSERT_EQ(6, client.getLeaseNum());
+
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::1")));
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:2::"), 32));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32));
+}
+
+TEST_F(HostTest, reservationAndDynamicIAs) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:04");
+
+ const std::string c = configString(*client.getDuid(),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/32"),
+ Reservation("3000:1:3::/32"));
+
+ ASSERT_NO_THROW(configure(c, *client.getServer()));
+
+ client.requestAddress(1234);
+ client.requestAddress(2345);
+ client.requestAddress(3456);
+ client.requestPrefix(5678);
+ client.requestPrefix(6789);
+ client.requestPrefix(7890);
+
+ // Send Solicit and require that the client saves received configuration
+ // so as we can test that advertised configuration is correct.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ ASSERT_EQ(6, client.getLeaseNum());
+
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+ EXPECT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:1::"), 32));
+ EXPECT_TRUE(client.hasLeaseForPrefix(IOAddress("3000:1:3::"), 32));
+
+ EXPECT_TRUE(client.hasLeaseForAddressRange(IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::10")));
+ EXPECT_TRUE(client.hasLeaseForPrefixPool(IOAddress("3001::"), 32, 64));
+}
+
} // end of anonymous namespace