Browse Source

[5306] Client classification for subnets added to allocation engine.

Marcin Siodelski 7 years ago
parent
commit
c27b6b2475
2 changed files with 166 additions and 15 deletions
  1. 41 15
      src/lib/dhcpsrv/alloc_engine.cc
  2. 125 0
      src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

+ 41 - 15
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;
             }

+ 125 - 0
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)