From: Tomek Mrugalski Date: Mon, 17 Sep 2012 16:28:09 +0000 (+0200) Subject: [2238] CfgMgr (+tests) implemented. X-Git-Tag: trac2402_base~5^2~17^2~7 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=daf7bbcc05e24e2e6894ac2cf59fa738cfb199bc;p=thirdparty%2Fkea.git [2238] CfgMgr (+tests) implemented. --- diff --git a/ChangeLog b/ChangeLog index da4a82666a..7fc1f23325 100644 --- a/ChangeLog +++ b/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 diff --git a/src/lib/dhcp/cfgmgr.cc b/src/lib/dhcp/cfgmgr.cc index a4a4b5fd69..c3098b6de4 100644 --- a/src/lib/dhcp/cfgmgr.cc +++ b/src/lib/dhcp/cfgmgr.cc @@ -27,8 +27,8 @@ Pool::Pool(const isc::asiolink::IOAddress& first, const Triplet& t1, const Triplet& t2, const Triplet& 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& t1, const Triplet& t2, const Triplet& preferred_lifetime, const Triplet& 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 diff --git a/src/lib/dhcp/cfgmgr.h b/src/lib/dhcp/cfgmgr.h index 989801fc5a..ff58a54ef9 100644 --- a/src/lib/dhcp/cfgmgr.h +++ b/src/lib/dhcp/cfgmgr.h @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include 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 Subnet6Ptr; + +typedef std::vector 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 diff --git a/src/lib/dhcp/tests/cfgmgr_unittest.cc b/src/lib/dhcp/tests/cfgmgr_unittest.cc index 3b08e66412..d810cd227b 100644 --- a/src/lib/dhcp/tests/cfgmgr_unittest.cc +++ b/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