From c27b6b2475944865775f4da863098290e539b5f7 Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Thu, 7 Sep 2017 16:15:09 +0200 Subject: [PATCH] [5306] Client classification for subnets added to allocation engine. --- src/lib/dhcpsrv/alloc_engine.cc | 56 +++++--- .../dhcpsrv/tests/alloc_engine4_unittest.cc | 125 ++++++++++++++++++ 2 files changed, 166 insertions(+), 15 deletions(-) diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 7c328513e7..fdd5fdc68e 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -2181,6 +2181,7 @@ hasAddressReservation(const AllocEngine::ClientContext4& ctx) { void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) { LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); + Subnet4Ptr original_subnet = ctx.subnet_; Subnet4Ptr subnet = ctx.subnet_; SharedNetwork4Ptr network; @@ -2188,6 +2189,15 @@ void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) while (subnet) { + // Some of the subnets within a shared network may not be allowed + // for the client if classification restrictions have been applied. + if (!subnet->clientSupported(ctx.query_->getClasses())) { + if (network) { + subnet = network->getNextSubnet(original_subnet, subnet); + } + continue; + } + // If client identifier has been supplied, use it to lookup the lease. This // search will return no lease if the client doesn't have any lease in the // database or if the client didn't use client identifier to allocate the @@ -2225,7 +2235,7 @@ void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) subnet.reset(); } else { - subnet = network->getNextSubnet(ctx.subnet_, subnet); + subnet = network->getNextSubnet(original_subnet, subnet); } } } @@ -2245,26 +2255,31 @@ bool inAllowedPool(AllocEngine::ClientContext4& ctx, const IOAddress& address) { SharedNetwork4Ptr network; ctx.subnet_->getSharedNetwork(network); - if (!network) { - // If there is no shared network associated with this subnet, we - // simply check if the address is within the pool in this subnet. - return (ctx.subnet_->inPool(Lease::TYPE_V4, address)); - } // If the subnet belongs to a shared network we will be iterating // over the subnets that belong to this shared network. Subnet4Ptr current_subnet = ctx.subnet_; while (current_subnet) { - if (current_subnet->inPool(Lease::TYPE_V4, address)) { - // We found a subnet that this address belongs to, so it - // seems that this subnet is the good candidate for allocation. - // Let's update the selected subnet. - ctx.subnet_ = current_subnet; - return (true); + + if (current_subnet->clientSupported(ctx.query_->getClasses())) { + if (current_subnet->inPool(Lease::TYPE_V4, address)) { + // We found a subnet that this address belongs to, so it + // seems that this subnet is the good candidate for allocation. + // Let's update the selected subnet. + ctx.subnet_ = current_subnet; + return (true); + } + } + + if (network) { + // Address is not within pools or client class not supported, so + // let's proceed to the next subnet. + current_subnet = network->getNextSubnet(ctx.subnet_, current_subnet); + + } else { + // No shared network, so there are no more subnets to try. + current_subnet.reset(); } - // Address is not within pools in this subnet, so let's proceed - // to the next subnet. - current_subnet = network->getNextSubnet(ctx.subnet_, current_subnet); } return (false); @@ -2947,6 +2962,16 @@ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) { subnet->getSharedNetwork(network); uint64_t total_attempts = 0; while (subnet) { + + // Some of the subnets within a shared network may not be allowed + // for the client if classification restrictions have been applied. + if (!subnet->clientSupported(ctx.query_->getClasses())) { + if (network) { + subnet = network->getNextSubnet(original_subnet, subnet); + } + continue; + } + const uint64_t max_attempts = (attempts_ > 0 ? attempts_ : subnet->getPoolCapacity(Lease::TYPE_V4)); for (uint64_t i = 0; i < max_attempts; ++i) { @@ -2976,6 +3001,7 @@ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) { // subnets in the same shared network. if (network) { subnet = network->getNextSubnet(original_subnet, subnet); + if (subnet) { ctx.subnet_ = subnet; } diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index 4ee254137e..b586fd3fc7 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@ -546,6 +546,61 @@ TEST_F(AllocEngine4Test, discoverSharedNetwork) { EXPECT_EQ("192.0.2.17", lease2->addr_.toText()); } +// This test verifies that the server can offer an address from a +// different subnet than orginally selected, when the address pool in +// the first subnet is exhausted. +TEST_F(AllocEngine4Test, discoverSharedNetworkClassification) { + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Create two subnets, each with a single address pool. The first subnet + // has only one address in its address pool to make it easier to simulate + // address exhaustion. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3, SubnetID(2))); + Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.17"), IOAddress("192.0.2.17"))); + Pool4Ptr pool2(new Pool4(IOAddress("10.1.2.5"), IOAddress("10.1.2.100"))); + subnet1->addPool(pool1); + subnet2->addPool(pool2); + + // Both subnets belong to the same network so they can be used + // interchangeably. + SharedNetwork4Ptr network(new SharedNetwork4("test_network")); + network->add(subnet1); + network->add(subnet2); + + // Try to offer address from subnet1. There is one address available + // so it should be offerred. + AllocEngine::ClientContext4 + ctx(subnet1, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), + false, false, "host.example.com.", true); + ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); + Lease4Ptr lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet1->inPool(Lease::TYPE_V4, lease->addr_)); + + // Apply restrictions on the subnet1. This should be only assigned + // to clients belonging to cable-modem class. + subnet1->allowClientClass("cable-modem"); + + // The allocation engine should determine that the subnet1 is not + // available for the client not belonging to the cable-modem class. + // Instead, it should offer an address from subnet2 that belongs + // to the same shared network. + ctx.subnet_ = subnet1; + lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet2->inPool(Lease::TYPE_V4, lease->addr_)); + + // Assign cable-modem class and try again. This time, we should + // offer an address from the subnet1. + ctx.query_->addClass(ClientClass("cable-modem")); + + ctx.subnet_ = subnet1; + lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet1->inPool(Lease::TYPE_V4, lease->addr_)); +} + // This test verifies that the server can allocate an address from a // different subnet than orginally selected, when the address pool in // the first subnet is exhausted. @@ -619,6 +674,76 @@ TEST_F(AllocEngine4Test, reuqestSharedNetwork) { EXPECT_EQ("192.0.2.17", lease2->addr_.toText()); } +// This test verifies that the server can assign an address from a +// different subnet than orginally selected, when the address pool in +// the first subnet is exhausted. +TEST_F(AllocEngine4Test, requestSharedNetworkClassification) { + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Create two subnets, each with a single address pool. The first subnet + // has only one address in its address pool to make it easier to simulate + // address exhaustion. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3, SubnetID(2))); + Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.17"), IOAddress("192.0.2.17"))); + Pool4Ptr pool2(new Pool4(IOAddress("10.1.2.5"), IOAddress("10.1.2.100"))); + subnet1->addPool(pool1); + subnet2->addPool(pool2); + + // Both subnets belong to the same network so they can be used + // interchangeably. + SharedNetwork4Ptr network(new SharedNetwork4("test_network")); + network->add(subnet1); + network->add(subnet2); + + // Try to offer address from subnet1. There is one address available + // so it should be offerred. + AllocEngine::ClientContext4 + ctx(subnet1, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), + false, false, "host.example.com.", false); + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + Lease4Ptr lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet1->inPool(Lease::TYPE_V4, lease->addr_)); + + // Remove the lease so as we can start over. + LeaseMgrFactory::instance().deleteLease(lease->addr_); + + // Apply restrictions on the subnet1. This should be only assigned + // to clients belonging to cable-modem class. + subnet1->allowClientClass("cable-modem"); + + // The allocation engine should determine that the subnet1 is not + // available for the client not belonging to the cable-modem class. + // Instead, it should assign an address from subnet2 that belongs + // to the same shared network. + ctx.subnet_ = subnet1; + lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet2->inPool(Lease::TYPE_V4, lease->addr_)); + + // Remove the lease so as we can start over. + LeaseMgrFactory::instance().deleteLease(lease->addr_); + + // Assign cable-modem class and try again. This time, we should + // offer an address from the subnet1. + ctx.query_->addClass(ClientClass("cable-modem")); + + ctx.subnet_ = subnet1; + lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet1->inPool(Lease::TYPE_V4, lease->addr_)); + + // Let's now remove the client from the cable-modem class and try + // to renew the address. The engine should determine that the + // client doesn't have access to the subnet1 pools anymore and + // assign an address from unrestricted subnet. + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + ctx.subnet_ = subnet1; + lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet2->inPool(Lease::TYPE_V4, lease->addr_)); +} // This test checks if an expired lease can be reused in DHCPDISCOVER (fake // allocation) -- 2.47.2