Browse Source

[2237] Support for Subnet4 implemented.

Tomek Mrugalski 12 years ago
parent
commit
46c03d26e2

+ 6 - 0
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

+ 33 - 0
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() {
 }
 

+ 29 - 4
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

+ 44 - 0
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<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,

+ 51 - 0
src/lib/dhcp/subnet.h

@@ -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.

+ 27 - 1
src/lib/dhcp/tests/cfgmgr_unittest.cc

@@ -34,6 +34,33 @@ 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) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
@@ -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

+ 77 - 1
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")));