Commit 46c03d26 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[2237] Support for Subnet4 implemented.

parent 574a6f88
4XX. [func] tomek
libdhcpsrv: The DHCP Configuration Manager is now able to store
information about IPv4 subnets and pool. It is still not possible
to configure that information. This will be implemented in a near
future.
484. [func] tomek
A new library (libb10-dhcpsrv) has been created. At present, it
only holds the code for the DHCP Configuration Manager. Currently
......
......@@ -68,6 +68,39 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
subnets6_.push_back(subnet);
}
Subnet4Ptr
CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
// If there's only one subnet configured, let's just use it
// The idea is to keep small deployments easy. In a small network - one
// router that also runs DHCPv6 server. Users specifies a single pool and
// expects it to just work. Without this, the server would complain that it
// doesn't have IP address on its interfaces that matches that
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if (subnets4_.size() == 1) {
return (subnets4_[0]);
}
// If there is more than one, we need to choose the proper one
for (Subnet4Collection::iterator subnet = subnets4_.begin();
subnet != subnets4_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
return (*subnet);
}
}
// sorry, we don't support that subnet
return (Subnet4Ptr());
}
void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
subnets4_.push_back(subnet);
}
CfgMgr::CfgMgr() {
}
......
......@@ -72,7 +72,7 @@ public:
/// accessing it.
static CfgMgr& instance();
/// @brief get subnet by address
/// @brief get IPv6 subnet by address
///
/// Finds a matching subnet, based on an address. This can be used
/// in two cases: when trying to find an appropriate lease based on
......@@ -83,7 +83,7 @@ public:
/// @param hint an address that belongs to a searched subnet
Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
/// @brief get subnet by interface-id
/// @brief get IPv6 subnet by interface-id
///
/// Another possibility to find a subnet is based on interface-id.
///
......@@ -91,13 +91,30 @@ public:
/// @todo This method is not currently supported.
Subnet6Ptr getSubnet6(OptionPtr interface_id);
/// @brief adds a subnet6
/// @brief adds an IPv6 subnet
void addSubnet6(const Subnet6Ptr& subnet);
/// @todo: Add subnet6 removal routines. Currently it is not possible
/// to remove subnets. The only case where subnet6 removal would be
/// needed is a dynamic server reconfiguration - a use case that is not
/// planned to be supported any time soon.
/// @brief get IPv4 subnet by address
///
/// Finds a matching subnet, based on an address. This can be used
/// in two cases: when trying to find an appropriate lease based on
/// a) relay link address (that must be the address that is on link)
/// b) our global address on the interface the message was received on
/// (for directly connected clients)
///
/// @param hint an address that belongs to a searched subnet
Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
/// @brief adds a subnet4
void addSubnet4(const Subnet4Ptr& subnet);
/// @brief removes all IPv4 subnets
void removeSubnets4();
protected:
/// @brief Protected constructor.
......@@ -111,13 +128,21 @@ protected:
/// @brief virtual desctructor
virtual ~CfgMgr();
/// @brief a container for Subnet6
/// @brief a container for IPv6 subnets.
///
/// That is a simple vector of pointers. It does not make much sense to
/// optimize access time (e.g. using a map), because typical search
/// pattern will use calling inRange() method on each subnet until
/// a match is found.
Subnet6Collection subnets6_;
/// @brief a container for IPv4 subnets.
///
/// That is a simple vector of pointers. It does not make much sense to
/// optimize access time (e.g. using a map), because typical search
/// pattern will use calling inRange() method on each subnet until
/// a match is found.
Subnet4Collection subnets4_;
};
} // namespace isc::dhcp
......
......@@ -41,6 +41,50 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
return ((first <= addr) && (addr <= last));
}
Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime)
:Subnet(prefix, length, t1, t2, valid_lifetime) {
if (prefix.getFamily() != AF_INET) {
isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
<< " specified in subnet4");
}
}
void Subnet4::addPool4(const Pool4Ptr& pool) {
IOAddress first_addr = pool->getFirstAddress();
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
<< " does not belong in this (" << prefix_ << "/" << prefix_len_
<< ") subnet4");
}
/// @todo: Check that pools do not overlap
pools_.push_back(pool);
}
Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
Pool4Ptr candidate;
for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
// if we won't find anything better, then let's just use the first pool
if (!candidate) {
candidate = *pool;
}
// if the client provided a pool and there's a pool that hint is valid in,
// then let's use that pool
if ((*pool)->inRange(hint)) {
return (*pool);
}
}
return (candidate);
}
Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
......
......@@ -93,6 +93,57 @@ protected:
Triplet<uint32_t> valid_;
};
/// @brief A configuration holder for IPv4 subnet.
///
/// This class represents an IPv4 subnet.
class Subnet4 : public Subnet {
public:
/// @brief Constructor with all parameters
///
/// @param prefix Subnet4 prefix
/// @param length prefix length
/// @param t1 renewal timer (in seconds)
/// @param t2 rebind timer (in seconds)
/// @param valid_lifetime preferred lifetime of leases (in seconds)
Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime);
/// @brief Returns a pool that specified address belongs to
///
/// @param hint address that the returned pool should cover (optional)
/// @return Pointer to found pool4 (or NULL)
Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
isc::asiolink::IOAddress("0.0.0.0"));
/// @brief Adds a new pool.
/// @param pool pool to be added
void addPool4(const Pool4Ptr& pool);
/// @brief returns all pools
///
/// The reference is only valid as long as the object that
/// returned it.
///
/// @return a collection of all pools
const Pool4Collection& getPools() const {
return pools_;
}
protected:
/// @brief collection of pools in that list
Pool4Collection pools_;
};
/// @brief A pointer to a Subnet4 object
typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
/// @brief A collection of Subnet6 objects
typedef std::vector<Subnet4Ptr> Subnet4Collection;
/// @brief A configuration holder for IPv6 subnet.
///
/// This class represents an IPv6 subnet.
......
......@@ -32,6 +32,33 @@ using boost::scoped_ptr;
namespace {
// This test verifies if the configuration manager is able to hold and return
// valid leases
TEST(CfgMgrTest, subnet4) {
CfgMgr& cfg_mgr = CfgMgr::instance();
ASSERT_TRUE(&cfg_mgr != 0);
Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
// there shouldn't be any subnet configured at this stage
EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
cfg_mgr.addSubnet4(subnet1);
// Now we have only one subnet, any request will be served from it
EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63")));
cfg_mgr.addSubnet4(subnet2);
cfg_mgr.addSubnet4(subnet3);
EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
}
// This test verifies if the configuration manager is able to hold and return
// valid leases
TEST(CfgMgrTest, subnet6) {
......@@ -57,7 +84,6 @@ TEST(CfgMgrTest, subnet6) {
EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
}
} // end of anonymous namespace
......@@ -29,6 +29,83 @@ using namespace isc::asiolink;
namespace {
TEST(Subnet4Test, constructor) {
EXPECT_NO_THROW(Subnet4 subnet1(IOAddress("192.0.2.2"), 16,
1, 2, 3));
EXPECT_THROW(Subnet4 subnet2(IOAddress("192.0.2.0"), 33, 1, 2, 3),
BadValue); // invalid prefix length
EXPECT_THROW(Subnet4 subnet3(IOAddress("2001:db8::1"), 24, 1, 2, 3),
BadValue); // IPv6 addresses are not allowed in Subnet4
}
TEST(Subnet4Test, in_range) {
Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
EXPECT_EQ(1000, subnet.getT1());
EXPECT_EQ(2000, subnet.getT2());
EXPECT_EQ(3000, subnet.getValid());
EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.255")));
EXPECT_FALSE(subnet.inRange(IOAddress("192.0.3.0")));
EXPECT_FALSE(subnet.inRange(IOAddress("0.0.0.0")));
EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
}
TEST(Subnet4Test, Pool4InSubnet4) {
Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
subnet->addPool4(pool1);
// If there's only one pool, get that pool
Pool4Ptr mypool = subnet->getPool4();
EXPECT_EQ(mypool, pool1);
subnet->addPool4(pool2);
subnet->addPool4(pool3);
// If there are more than one pool and we didn't provide hint, we
// should get the first pool
mypool = subnet->getPool4();
EXPECT_EQ(mypool, pool1);
// If we provide a hint, we should get a pool that this hint belongs to
mypool = subnet->getPool4(IOAddress("192.1.2.195"));
EXPECT_EQ(mypool, pool3);
}
TEST(Subnet4Test, Subnet4_Pool4_checks) {
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
// this one is in subnet
Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
subnet->addPool4(pool1);
// this one is larger than the subnet!
Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
EXPECT_THROW(subnet->addPool4(pool2), BadValue);
// this one is totally out of blue
Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
EXPECT_THROW(subnet->addPool4(pool3), BadValue);
}
// Tests for Subnet6
TEST(Subnet6Test, constructor) {
EXPECT_NO_THROW(Subnet6 subnet1(IOAddress("2001:db8:1::"), 64,
......@@ -48,7 +125,6 @@ TEST(Subnet6Test, in_range) {
EXPECT_EQ(3000, subnet.getPreferred());
EXPECT_EQ(4000, subnet.getValid());
EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:0:ffff:ffff:ffff:ffff:ffff")));
EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::0")));
EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::1")));
......
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