Browse Source

[2238] CfgMgr (+tests) implemented.

Tomek Mrugalski 12 years ago
parent
commit
daf7bbcc05
4 changed files with 249 additions and 25 deletions
  1. 7 0
      ChangeLog
  2. 94 18
      src/lib/dhcp/cfgmgr.cc
  3. 48 1
      src/lib/dhcp/cfgmgr.h
  4. 100 6
      src/lib/dhcp/tests/cfgmgr_unittest.cc

+ 7 - 0
ChangeLog

@@ -1,3 +1,10 @@
+4XX.	[func]		tomek
+	Configuration Manager for DHCP servers has been
+	implemented. Currently only supports basic configuration storage
+	for DHCPv6 server, but that capability is expected to be expanded
+	in a near future.
+	(Trac #2238, git TBD)
+
 473.	[bug]		jelte
 	TCP connections now time out in b10-auth if no (or not all) query
 	data is sent by the client. The timeout value defaults to 5000

+ 94 - 18
src/lib/dhcp/cfgmgr.cc

@@ -27,8 +27,8 @@ Pool::Pool(const isc::asiolink::IOAddress& first,
            const Triplet<uint32_t>& t1,
            const Triplet<uint32_t>& t2,
            const Triplet<uint32_t>& valid_lifetime)
-    :id_(getNextID()), first_(first), last_(last), t1_(t1), t2_(t2), valid_(valid_lifetime) {
-
+    :id_(getNextID()), first_(first), last_(last), t1_(t1), t2_(t2),
+     valid_(valid_lifetime) {
 }
 
 bool Pool::inRange(const isc::asiolink::IOAddress& addr) {
@@ -44,19 +44,23 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
     :Pool(first, last, t1, t2, valid_lifetime),
      type_(type), prefix_len_(0), preferred_(preferred_lifetime) {
 
+    // check if specified address boundaries are sane
+    if (first.getFamily() != AF_INET6 || last.getFamily() != AF_INET6) {
+        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
+    }
+
     if (last < first) {
         isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
-        // This check is strict. If we decide that it is too strict,
+        // This check is a bit strict. If we decide that it is too strict,
         // we need to comment it and uncomment lines below.
+        // On one hand, letting the user specify 2001::f - 2001::1 is nice, but
+        // on the other hand, 2001::1 may be a typo and the user really meant
+        // 2001::1:0 (or 1something), so a at least a warning would be useful.
 
         // first_  = last;
         // last_ = first;
     }
 
-    if (first.getFamily() != AF_INET6 || last.getFamily() != AF_INET6) {
-        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
-    }
-
     // TYPE_PD is not supported by this constructor. first-last style
     // parameters are for IA and TA only. There is another dedicated
     // constructor for that (it uses prefix/length)
@@ -65,30 +69,31 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
     }
 }
 
-Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& addr,
+Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
              uint8_t prefix_len,
              const Triplet<uint32_t>& t1,
              const Triplet<uint32_t>& t2,
              const Triplet<uint32_t>& preferred_lifetime,
              const Triplet<uint32_t>& valid_lifetime)
-    :Pool(addr, IOAddress("::"), t1, t2, valid_lifetime),
+    :Pool(prefix, IOAddress("::"), t1, t2, valid_lifetime),
      type_(type), prefix_len_(prefix_len), preferred_(preferred_lifetime) {
 
-    if (prefix_len == 0 || prefix_len > 128) {
-        isc_throw(BadValue, "Invalid prefix length");
+    // check if the prefix is sane
+    if (prefix.getFamily() != AF_INET6) {
+        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
     }
 
-    if (addr.getFamily() != AF_INET6) {
-        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
+    // check if the prefix length is sane
+    if (prefix_len == 0 || prefix_len > 128) {
+        isc_throw(BadValue, "Invalid prefix length");
     }
 
     // Let's now calculate the last address in defined pool
-    last_ = lastAddrInPrefix(addr, prefix_len);
+    last_ = lastAddrInPrefix(prefix, prefix_len);
 }
 
-
 Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len)
-    :id_(getNextID()), prefix_(prefix), len_(len) {
+    :id_(getNextID()), prefix_(prefix), prefix_len_(len) {
     if ( (prefix.getFamily() == AF_INET6 && len > 128) ||
          (prefix.getFamily() == AF_INET && len > 32) ) {
         isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
@@ -96,8 +101,8 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len)
 }
 
 bool Subnet::inRange(const isc::asiolink::IOAddress& addr) {
-    IOAddress first = firstAddrInPrefix(prefix_, len_);
-    IOAddress last = lastAddrInPrefix(prefix_, len_);
+    IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
+    IOAddress last = lastAddrInPrefix(prefix_, prefix_len_);
 
     return ( (first <= addr) && (addr <= last) );
 }
@@ -109,8 +114,79 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length)
     }
 }
 
+void Subnet6::addPool6(const Pool6Ptr& pool) {
+    IOAddress first_addr = pool->getFirstAddress();
+    IOAddress last_addr = pool->getLastAddress();
+
+    if (!inRange(first_addr) || !inRange(last_addr)) {
+        isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
+                  << " does not belong in this (" << prefix_ << "/" << prefix_len_
+                  << ") subnet6");
+    }
+
+    pools_.push_back(pool);
+}
+
 
+Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
+    Pool6Ptr candidate;
+    for (Pool6Collection::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);
+}
+
+
+CfgMgr&
+CfgMgr::instance() {
+    static CfgMgr cfg_mgr;
+    return (cfg_mgr);
+}
+
+Subnet6Ptr
+CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
+
+    // If there's only one subnet configured, let's just use it
+    if (subnets6_.size() == 1) {
+        return (subnets6_[0]);
+    }
+
+    // If there is more than one, we need to choose the proper one
+    for (Subnet6Collection::iterator subnet = subnets6_.begin();
+         subnet != subnets6_.end(); ++subnet) {
+        if ((*subnet)->inRange(hint)) {
+            return (*subnet);
+        }
+    }
+
+    // sorry, we don't support that subnet
+    return (Subnet6Ptr());
+}
+
+Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
+    /// @todo: Implement get subnet6 by interface-id (for relayed traffic)
+    isc_throw(NotImplemented, "Relayed DHCPv6 traffic is not supported yet.");
+}
+
+void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
+    subnets6_.push_back(subnet);
+}
+
+CfgMgr::CfgMgr() {
+}
+
+CfgMgr::~CfgMgr() {
+}
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 48 - 1
src/lib/dhcp/cfgmgr.h

@@ -19,8 +19,10 @@
 #include <map>
 #include <vector>
 #include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
 #include <asiolink/io_address.h>
 #include <util/buffer.h>
+#include <dhcp/option.h>
 
 namespace isc {
 namespace dhcp {
@@ -241,18 +243,63 @@ protected:
 
     isc::asiolink::IOAddress prefix_;
 
-    uint8_t len_;
+    uint8_t prefix_len_;
+
+    Pool6Collection pool_;
 };
 
 class Subnet6 : public Subnet {
 public:
     Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length);
 
+    Pool6Ptr getPool6(const isc::asiolink::IOAddress& hint = isc::asiolink::IOAddress("::"));
+
+    void addPool6(const Pool6Ptr& pool);
+
+    const Pool6Collection& getPools() const {
+        return pools_;
+    }
+
 protected:
     /// collection of pools in that list
     Pool6Collection pools_;
 
 };
+typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
+
+typedef std::vector<Subnet6Ptr> Subnet6Collection;
+
+class CfgMgr : public boost::noncopyable {
+public:
+    static CfgMgr& instance();
+
+    /// @brief get 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)
+    Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
+
+    /// @brief get subnet by interface-id
+    ///
+    /// Another possibility is to find a subnet based on interface-id.
+    /// @todo This method is not currently supported.
+    Subnet6Ptr getSubnet6(OptionPtr interfaceId);
+
+
+    void addSubnet6(const Subnet6Ptr& subnet);
+
+protected:
+
+    /// @brief Protected constructor.
+    CfgMgr();
+
+    virtual ~CfgMgr();
+
+    Subnet6Collection subnets6_;
+};
 
 } // namespace isc::dhcp
 } // namespace isc

+ 100 - 6
src/lib/dhcp/tests/cfgmgr_unittest.cc

@@ -187,15 +187,109 @@ TEST(Pool6Test, unique_id) {
 }
 
 
+TEST(Subnet6Test, constructor) {
+
+    EXPECT_NO_THROW(Subnet6 subnet1(IOAddress("2001:db8:1::"), 64));
+
+    EXPECT_THROW(Subnet6 subnet2(IOAddress("2001:db8:1::"), 129),
+                BadValue); // invalid prefix length
+    EXPECT_THROW(Subnet6 subnet3(IOAddress("192.168.0.0"), 32),
+                BadValue); // IPv4 addresses are not allowed in Subnet6
+}
+
 TEST(Subnet6Test, in_range) {
     Subnet6 subnet(IOAddress("2001:db8:1::"), 64);
 
-   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")));
-   EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::ffff:ffff:ffff:ffff")));
-   EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:1:1::")));
-   EXPECT_FALSE(subnet.inRange(IOAddress("::")));
+    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")));
+    EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::ffff:ffff:ffff:ffff")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:1:1::")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("::")));
+}
+
+TEST(Subnet6Test, Pool6InSubnet6) {
+
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56));
+
+    Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"),
+                             64, 101, 102, 103, 104));
+    Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"),
+                             64, 201, 202, 203, 204));
+    Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"),
+                             64, 301, 302, 303, 304));
+
+
+    subnet->addPool6(pool1);
+
+    // If there's only one pool, get that pool
+    Pool6Ptr mypool = subnet->getPool6();
+    EXPECT_EQ(mypool, pool1);
+
+
+    subnet->addPool6(pool2);
+    subnet->addPool6(pool3);
+
+    // If there are more than one pool and we didn't provide hint, we
+    // should get the first pool
+    mypool = subnet->getPool6();
+
+    EXPECT_EQ(mypool, pool1);
+
+    // If we provide a hint, we should get a pool that this hint belongs to
+    mypool = subnet->getPool6(IOAddress("2001:db8:1:3::dead:beef"));
+
+    EXPECT_EQ(mypool, pool3);
+
+}
+
+TEST(Subnet6Test, Subnet6_Pool6_checks) {
+
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56));
+
+    // this one is in subnet
+    Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"),
+                             64, 101, 102, 103, 104));
+    subnet->addPool6(pool1);
+
+    Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::"),
+                             48, 201, 202, 203, 204)); // this one is larger than the subnet!
+    EXPECT_THROW(subnet->addPool6(pool2), BadValue);
+
+
+    // this one is totally out of blue
+    Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"),
+                             16, 301, 302, 303, 304));
+    EXPECT_THROW(subnet->addPool6(pool3), BadValue);
+
+}
+
+// This test verifies if the configuration manager is able to hold and return
+// valid leases
+TEST(CfgMgrTest, subnet6) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    ASSERT_TRUE(&cfg_mgr != 0);
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48));
+
+    // there shouldn't be any subnet configured at this stage
+    EXPECT_EQ( Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("2000::1")));
+
+    cfg_mgr.addSubnet6(subnet1);
+
+    // Now we have only one subnet, any request will be served from it
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2001:db8::1")));
+
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    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