Commit 82c0737d authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[2316] Added option collection to the Subnet class.

parent 932edbb0
......@@ -41,6 +41,17 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
return ((first <= addr) && (addr <= last));
}
void
Subnet::addOption(OptionPtr option, bool persistent /* = false */) {
validateOption(option);
options_.push_back(OptionDescriptor(option, persistent));
}
void
Subnet::delOptions() {
options_.clear();
}
Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
......@@ -85,6 +96,15 @@ Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("
return (candidate);
}
void
Subnet4::validateOption(OptionPtr option) {
if (!option) {
isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
} else if (option->getUniverse() != Option::V4) {
isc_throw(isc::BadValue, "epected V4 option to be added to the subnet");
}
}
Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
......@@ -131,5 +151,13 @@ Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("
return (candidate);
}
void
Subnet6::validateOption(OptionPtr option) {
if (!option) {
isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
} else if (option->getUniverse() != Option::V6) {
isc_throw(isc::BadValue, "epected V6 option to be added to the subnet");
}
}
} // end of isc::dhcp namespace
} // end of isc namespace
......@@ -15,10 +15,16 @@
#ifndef SUBNET_H
#define SUBNET_H
#include <boost/shared_ptr.hpp>
#include <asiolink/io_address.h>
#include <dhcp/pool.h>
#include <dhcp/triplet.h>
#include <dhcp/option.h>
#include <boost/shared_ptr.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/member.hpp>
namespace isc {
namespace dhcp {
......@@ -30,14 +36,160 @@ namespace dhcp {
/// attached to it. In most cases all devices attached to a single link can
/// share the same parameters. Therefore Subnet holds several values that are
/// typically shared by all hosts: renew timer (T1), rebind timer (T2) and
/// leased addresses lifetime (valid-lifetime).
///
/// @todo: Implement support for options here
/// leased addresses lifetime (valid-lifetime). It also holds the set
/// of DHCP option instances configured for the subnet. These options are
/// included in DHCP messages being sent to clients which are connected
/// to the particular subnet.
class Subnet {
public:
/// @brief Option descriptor.
///
/// Option descriptor holds information about option configured for
/// a particular subnet. This information comprises the actual option
/// instance and information whether this option is sent to DHCP client
/// only on request (persistent = false) or always (persistent = true).
struct OptionDescriptor {
/// Option instance.
OptionPtr option;
/// Persistent flag, if true option is always sent to the client,
/// if false option is sent to the client on request.
bool persistent;
/// @brief Constructor.
///
/// @opt option
/// persist if true option is always sent.
OptionDescriptor(OptionPtr& opt, bool persist)
: option(opt), persistent(persist) {};
};
/// @brief Extractor class to extract key with another key.
///
/// This class extracts one multi_index_container key with
/// another key. Within Subnet class it is used to access
/// data inside the OptionDescriptor::option object and use
/// this data as a index key. The standard key extractor such
/// as mem_fun is not sufficient because it can't operate on
/// objects wrapped with structure.
///
/// @tparam KeyExtractor1 extractor used to access data in
/// OptionDescriptor::option
/// @tparam KeyExtractor2 extractor used to access
/// OptionDescriptor::option member.
template<typename KeyExtractor1, typename KeyExtractor2>
struct KeyFromKey {
typedef typename KeyExtractor1::result_type result_type;
/// @brief Constructor.
KeyFromKey(const KeyExtractor1 key1 = KeyExtractor1(),
const KeyExtractor2 key2 = KeyExtractor2())
: key1_(key1), key2_(key2) { };
/// @brief Extract key with another key.
///
/// @param arg the key value.
///
/// @tparam key value type.
template<typename T>
result_type operator() (T& arg) const {
return (key1_(key2_(arg)));
}
private:
KeyExtractor1 key1_; ///< key 1.
KeyExtractor2 key2_; ///< key 2.
};
/// @brief Multi index container for DHCP option descriptors.
///
/// This container comprises three indexes to access option
/// descriptors:
/// - sequenced index: used to access elements in the order they
/// have been added to the container,
/// - option type index: used to search option descriptors containing
/// options with specific option code (aka option type).
/// - persistency flag index: used to search option descriptors with
/// 'persistent' flag set to true.
///
/// This container is the equivalent of three separate STL containers:
/// - std::list of all options,
/// - std::multimap of options with option code used as a multimap key,
/// - std::multimap of option descriptors with option persistency flag
/// used as a multimap key.
/// The major advantage of this container over 3 separate STL containers
/// is automatic synchronization of all indexes when elements are added,
/// removed or modified in the container. With separate containers,
/// the synchronization would have to be guaranteed by the Subnet class
/// code. This would increase code complexity and presumably it would
/// be much harder to add new search criteria (indexes).
///
/// @todo we may want to search for options using option spaces when
/// they are implemented.
///
/// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html
typedef boost::multi_index_container<
// Container comprises elements of OptionDescriptor type.
OptionDescriptor,
// Here we start enumerating various indexes.
boost::multi_index::indexed_by<
// Sequenced index allows accessing elements in the same way
// as elements in std::list.
boost::multi_index::sequenced<>,
// Start definition of index #1.
boost::multi_index::hashed_non_unique<
// KeyFromKey is the index key extractor that allows accessing
// option type being held by the OptionPtr through
// OptionDescriptor structure.
KeyFromKey<
// Use option type as the index key. The type is held
// in OptionPtr object so we have to call Option::getType
// to retrieve this key for each element.
boost::multi_index::mem_fun<
Option,
uint16_t,
&Option::getType
>,
// Indicate that OptionPtr is a member of
// OptionDescriptor structure.
boost::multi_index::member<
OptionDescriptor,
OptionPtr,
&OptionDescriptor::option
>
>
>,
// Start definition of index #2.
// Use 'persistent' struct member as a key.
boost::multi_index::hashed_non_unique<
boost::multi_index::member<
OptionDescriptor,
bool,
&OptionDescriptor::persistent
>
>
>
> OptionContainer;
/// Type of the index #1 - option type.
typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
/// Type of the index #2 - option persistency flag.
typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
/// @brief checks if specified address is in range
bool inRange(const isc::asiolink::IOAddress& addr) const;
/// @brief Add new option instance to the collection.
///
/// @param option option instance.
/// @param persistent if true, send an option regardless if client
/// requested it or not.
///
/// @throw isc::BadValue if invalid option provided.
void addOption(OptionPtr option, bool persistent = false);
/// @brief Delete all options configured for the subnet.
void delOptions();
/// @brief return valid-lifetime for addresses in that prefix
Triplet<uint32_t> getValid() const {
return (valid_);
......@@ -53,6 +205,13 @@ public:
return (t2_);
}
/// @brief Return a collection of options.
///
/// @return collection of options configured for a subnet.
const OptionContainer& getOptions() {
return (options_);
}
protected:
/// @brief protected constructor
//
......@@ -63,6 +222,12 @@ protected:
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime);
/// @brief virtual destructor
///
/// A virtual destructor is needed because other classes
/// derive from this class.
virtual ~Subnet() { };
/// @brief returns the next unique Subnet-ID
///
/// @return the next unique Subnet-ID
......@@ -71,6 +236,11 @@ protected:
return (id++);
}
/// @brief Check if option is valid and can be added to a subnet.
///
/// @param option option to be validated.
virtual void validateOption(OptionPtr option) = 0;
/// @brief subnet-id
///
/// Subnet-id is a unique value that can be used to find or identify
......@@ -91,6 +261,9 @@ protected:
/// @brief a tripet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_;
/// @brief a collection of DHCP options configured for a subnet.
OptionContainer options_;
};
/// @brief A configuration holder for IPv4 subnet.
......@@ -133,6 +306,14 @@ public:
}
protected:
/// @brief Check if option is valid and can be added to a subnet.
///
/// @param option option to be validated.
///
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(OptionPtr option);
/// @brief collection of pools in that list
Pool4Collection pools_;
};
......@@ -193,6 +374,14 @@ public:
}
protected:
/// @brief Check if option is valid and can be added to a subnet.
///
/// @param option option to be validated.
///
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(OptionPtr option);
/// @brief collection of pools in that list
Pool6Collection pools_;
......
......@@ -54,6 +54,7 @@ libdhcpsrv_unittests_LDADD = $(GTEST_LDADD)
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
......
......@@ -15,6 +15,7 @@
#include <config.h>
#include <dhcp/subnet.h>
#include <dhcp/option.h>
#include <exceptions/exceptions.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
......@@ -104,6 +105,24 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) {
EXPECT_THROW(subnet->addPool4(pool3), BadValue);
}
TEST(Subnet4Test, addInvalidOption) {
// Create the V4 subnet.
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
// Some dummy option code.
uint16_t code = 100;
// Create option with invalid universe (V6 instead of V4).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
}
// Tests for Subnet6
TEST(Subnet6Test, constructor) {
......@@ -187,4 +206,141 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
EXPECT_THROW(subnet->addPool6(pool4), BadValue);
}
TEST(Subnet6Test, addOptions) {
// Create as subnet to add options to it.
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
ASSERT_NO_THROW(subnet->addOption(option));
}
// Get options from the Subnet and check if all 10 are there.
Subnet::OptionContainer options = subnet->getOptions();
ASSERT_EQ(10, options.size());
// Validate codes of added options.
uint16_t expected_code = 100;
for (Subnet::OptionContainer::const_iterator option_desc = options.begin();
option_desc != options.end(); ++option_desc) {
ASSERT_TRUE(option_desc->option);
EXPECT_EQ(expected_code, option_desc->option->getType());
++expected_code;
}
subnet->delOptions();
options = subnet->getOptions();
EXPECT_EQ(0, options.size());
}
TEST(Subnet6Test, addNonUniqueOptions) {
// Create as subnet to add options to it.
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
// Create a set of options with non-unique codes.
for (int i = 0; i < 2; ++i) {
// In the inner loop we create options with unique codes (100-109).
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
ASSERT_NO_THROW(subnet->addOption(option));
}
}
// Sanity check that all options are there.
Subnet::OptionContainer options = subnet->getOptions();
ASSERT_EQ(20, options.size());
// Use container index #1 to get the options by their codes.
Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
// For each code we should get two instances of options.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(code);
// Distance between iterators indicates how many options
// have been retured for the particular code.
ASSERT_EQ(2, distance(range.first, range.second));
// Check that returned options actually have the expected option code.
for (Subnet::OptionContainerTypeIndex::const_iterator option_desc = range.first;
option_desc != range.second; ++option_desc) {
ASSERT_TRUE(option_desc->option);
EXPECT_EQ(code, option_desc->option->getType());
}
}
// Let's try to find some non-exiting option.
const uint16_t non_existing_code = 150;
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(non_existing_code);
// Empty set is expected.
EXPECT_EQ(0, distance(range.first, range.second));
subnet->delOptions();
options = subnet->getOptions();
EXPECT_EQ(0, options.size());
}
TEST(Subnet6Test, addInvalidOption) {
// Create as subnet to add options to it.
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
// Some dummy option code.
uint16_t code = 100;
// Create option with invalid universe (V4 instead of V6).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
}
TEST(Subnet6Test, addPersistentOption) {
// Create as subnet to add options to it.
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
// Add 10 options to the subnet with option codes 100 - 109.
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
// Options with even codes will be flagged persistent. Persistent
// options are those that server sends to clients regardless if
// they ask for them or not.
bool persistent = (code % 2) ? true : false;
ASSERT_NO_THROW(subnet->addOption(option, persistent));
}
// Get added options from the subnet.
Subnet::OptionContainer options = subnet->getOptions();
// options.get<2> returns reference to container index #2. This
// index is used to access options by the 'persistent' flag.
Subnet::OptionContainerPersistIndex& idx = options.get<2>();
// Get all persistent options.
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_persistent =
idx.equal_range(true);
// 5 out of 10 options have been flagged persistent.
ASSERT_EQ(5, distance(range_persistent.first, range_persistent.second));
// Get all non-persistent options.
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
idx.equal_range(false);
// 5 out of 10 options have been flagged persistent.
ASSERT_EQ(5, distance(range_non_persistent.first, range_non_persistent.second));
subnet->delOptions();
options = subnet->getOptions();
EXPECT_EQ(0, options.size());
}
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment