From: Tomek Mrugalski Date: Thu, 4 Oct 2012 12:43:43 +0000 (+0200) Subject: [2237] Support for Subnet4 implemented. X-Git-Tag: trac2402_base~17^2~9^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=46c03d26e27417bdce132b90eef18686443bd5a7;p=thirdparty%2Fkea.git [2237] Support for Subnet4 implemented. --- diff --git a/ChangeLog b/ChangeLog index 53a79e1fa8..8e6d84ed82 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +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 diff --git a/src/lib/dhcp/cfgmgr.cc b/src/lib/dhcp/cfgmgr.cc index 15e3ad97d6..d06544c559 100644 --- a/src/lib/dhcp/cfgmgr.cc +++ b/src/lib/dhcp/cfgmgr.cc @@ -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() { } diff --git a/src/lib/dhcp/cfgmgr.h b/src/lib/dhcp/cfgmgr.h index 5b73f2b966..2e127c088c 100644 --- a/src/lib/dhcp/cfgmgr.h +++ b/src/lib/dhcp/cfgmgr.h @@ -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 diff --git a/src/lib/dhcp/subnet.cc b/src/lib/dhcp/subnet.cc index a999295690..d0c4cb35fe 100644 --- a/src/lib/dhcp/subnet.cc +++ b/src/lib/dhcp/subnet.cc @@ -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& t1, + const Triplet& t2, + const Triplet& 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& t1, const Triplet& t2, diff --git a/src/lib/dhcp/subnet.h b/src/lib/dhcp/subnet.h index aa59010ee6..7e4e0b7f07 100644 --- a/src/lib/dhcp/subnet.h +++ b/src/lib/dhcp/subnet.h @@ -93,6 +93,57 @@ protected: Triplet 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& t1, + const Triplet& t2, + const Triplet& 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 Subnet4Ptr; + +/// @brief A collection of Subnet6 objects +typedef std::vector Subnet4Collection; + + /// @brief A configuration holder for IPv6 subnet. /// /// This class represents an IPv6 subnet. diff --git a/src/lib/dhcp/tests/cfgmgr_unittest.cc b/src/lib/dhcp/tests/cfgmgr_unittest.cc index a11acbfdd7..75e60010aa 100644 --- a/src/lib/dhcp/tests/cfgmgr_unittest.cc +++ b/src/lib/dhcp/tests/cfgmgr_unittest.cc @@ -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 diff --git a/src/lib/dhcp/tests/subnet_unittest.cc b/src/lib/dhcp/tests/subnet_unittest.cc index 6afebb8e60..34982b595d 100644 --- a/src/lib/dhcp/tests/subnet_unittest.cc +++ b/src/lib/dhcp/tests/subnet_unittest.cc @@ -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")));