]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5425a] Applied changes - need regen
authorFrancis Dupont <fdupont@isc.org>
Thu, 18 Jan 2018 23:13:48 +0000 (00:13 +0100)
committerFrancis Dupont <fdupont@isc.org>
Thu, 18 Jan 2018 23:13:48 +0000 (00:13 +0100)
16 files changed:
doc/examples/kea4/classify.json
doc/examples/kea6/classify.json
doc/guide/classify.xml
doc/guide/dhcp4-srv.xml
doc/guide/dhcp6-srv.xml
src/bin/dhcp4/dhcp4_lexer.ll
src/bin/dhcp4/dhcp4_parser.yy
src/bin/dhcp4/tests/config_parser_unittest.cc
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/shared_network_unittest.cc
src/bin/dhcp6/dhcp6_lexer.ll
src/bin/dhcp6/dhcp6_messages.mes
src/bin/dhcp6/dhcp6_parser.yy
src/bin/dhcp6/tests/classify_unittests.cc
src/bin/dhcp6/tests/config_parser_unittest.cc
src/bin/dhcp6/tests/shared_network_unittest.cc

index 6964960e22a92da1e19126118af3c03d2e54b257..e769e581f0873a01a3530dc3b7c4610697210ce4 100644 (file)
             "client-classes": [ "VoIP" ]
         } ],
         "interface": "ethX"
-    }
+    },
+
+// The following list defines a subnet with pools. For some pools
+// we defined a class that is allowed in that pool. If not specified
+// everyone is allowed. When a class is specified, only packets belonging
+// to that class are allowed for that pool.
+     {
+        "pools": [
+           {
+// This one is for VoIP devices only.
+              "pool":  "192.0.4.1 - 192.0.4.200",
+              "client-class": "VoIP"
+           },
+// This one doesn't have any client-class specified, so everyone
+// is allowed in.
+           {
+               "pool":  "192.0.5.1 - 192.0.5.200"
+           } ],
+           "subnet": "192.0.4.0/23",
+           "interface": "ethY"
+     }
   ]
 },
 
index eaa37beec4f20c055b1cf2930b52af8fe5f1e9c6..b6e184b61867cfc72cf46849a3c59d2cf4858b4d 100644 (file)
             "client-classes": [ "cable-modems" ]
         } ],
         "interface": "ethX"
+    },
+// The following subnet contains a pool with a class constraint: only
+// clients which belong to the class are allowed to use this pool.
+    {
+        "pools": [
+           {
+               "pool": "2001:db8:3::/80",
+               "client-class": "cable-modems"
+           } ],
+         "subnet": "2001:db8:4::/64",
+         "interface": "ethY"
     }
+
   ]
 },
 
index cfc515f9a14de655b9eb5bf96960e91c0895bf50..bd5ab03907f8e8d1e544e793abc632b6931b860c 100644 (file)
@@ -801,6 +801,56 @@ concatenation of the strings</entry></row>
       </para>
   </section>
 
+ <section id="classification-pools">
+   <title>Configuring Pools With Class Information</title>
+     <para>
+       Similar to subnets in certain cases access to certain address or
+       prefix pools must be restricted to only clients that belong to a
+       given class, using the "client-class" when defining the pool.
+     </para>
+
+     <para>
+       Let's assume that the server is connected to a network segment that uses
+       the 192.0.2.0/24 prefix. The Administrator of that network has decided
+       that addresses from range 192.0.2.10 to 192.0.2.20 are going to be
+       managed by the DHCP4 server. Only clients belonging to client class
+       Client_foo are allowed to use this pool. Such a
+       configuration can be achieved in the following way:
+       <screen>
+"Dhcp4": {
+    "client-classes": [
+        {
+            "name": "Client_foo",
+            "test": "substring(option[61].hex,0,3) == 'foo'",
+            "option-data": [
+                {
+                    "name": "domain-name-servers",
+                    "code": 6,
+                    "space": "dhcp4",
+                    "csv-format": true,
+                    "data": "192.0.2.1, 192.0.2.2"
+                }
+            ]
+        },
+        ...
+    ],<userinput>
+    "subnet4": [
+        {
+            "subnet": "192.0.2.0/24",
+            "pools": [
+                {
+                    "pool": "192.0.2.10 - 192.0.2.20",
+                    "client-class": "Client_foo"
+                }
+            ]
+        },
+        ...
+    ],</userinput>,
+    ...
+}</screen>
+      </para>
+  </section>
+
   <section>
     <title>Using Classes</title>
       <para>
index 1f7a1eaf371bc32cadc665607418367630ee93e2..c83ce6333d786014d2773fb7ab4877bbe50049fd 100644 (file)
@@ -2092,6 +2092,15 @@ It is merely echoed by the server
       class restrictions on subnets, see <xref linkend="classification-subnets"/>.
       </para>
 
+      <para>
+      When subnets belong to a shared network the classification applies
+      to subnet selection but not to pools, e.g., a pool in a subnet
+      limited to a particular class can still be used by clients which do not
+      belong to the class if the pool they are expected to use is exhausted.
+      So the limit access based on class information is also available
+      at the pool level, see <xref linkend="classification-pools"/>.
+      </para>
+
       <para>
       The process of doing classification is conducted in three steps. The first step
       is to assess an incoming packet and assign it to zero or more classes.  The
index f0b96148c747ffc2205bd0652fcaa283d70df0db..74ec99a5c164c72eb2ef754683a0b9194f37d8de 100644 (file)
@@ -1950,6 +1950,16 @@ should include options from the isc option space:
       class restrictions on subnets, see <xref linkend="classification-subnets"/>.
       </para>
 
+      <para>
+      When subnets belong to a shared network the classification applies
+      to subnet selection but not to pools, e.g., a pool in a subnet
+      limited to a particular class can still be used by clients which do not
+      belong to the class if the pool they are expected to use is exhausted.
+      So the limit access based on class information is also available
+      at the address/prefix pool level, see <xref
+      linkend="classification-pools"/>.
+      </para>
+
       <para>
       The process of doing classification is conducted in three steps. The first step
       is to assess an incoming packet and assign it to zero or more classes.  The
index d2b4b59153b1d05dfcb1900b5693e8354d63db2a..d5d88ff41ae63c9feeef56ea46fd2810946915f0 100644 (file)
@@ -823,6 +823,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"client-class\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::POOLS:
     case isc::dhcp::Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
         return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASS(driver.loc_);
index 6d4c574992355ea161e48ebd135736c97234bb02..9c5f94262a3ef7e5c24b7b55f7755158e843488a 100644 (file)
@@ -1344,6 +1344,7 @@ pool_params: pool_param
 
 pool_param: pool_entry
           | option_data_list
+          | client_class
           | user_context
           | comment
           | unknown_map_entry
@@ -1598,11 +1599,11 @@ client_classes: CLIENT_CLASSES {
     ctx.leave();
 };
 
-client_classes_list: client_class
-                   | client_classes_list COMMA client_class
+client_classes_list: client_class_entry
+                   | client_classes_list COMMA client_class_entry
                    ;
 
-client_class: LCURLY_BRACKET {
+client_class_entry: LCURLY_BRACKET {
     ElementPtr m(new MapElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->add(m);
     ctx.stack_.push_back(m);
index 0653b5c7e1be2b2995e6991e6958595863b8a170..da4b250be76cf2159af0ce0c6ab28f55bf53cc30 100644 (file)
@@ -4100,6 +4100,7 @@ TEST_F(Dhcp4ParserTest, classifySubnets) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -4157,6 +4158,95 @@ TEST_F(Dhcp4ParserTest, classifySubnets) {
     EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
 }
 
+// Goal of this test is to verify that multiple pools can be configured
+// with defined client classes.
+TEST_F(Dhcp4ParserTest, classifyPools) {
+    ConstElementPtr x;
+    string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { "
+        "        \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+        "        \"client-class\": \"alpha\" "
+        "     },"
+        "     {"
+        "        \"pool\": \"192.0.3.101 - 192.0.3.150\", "
+        "        \"client-class\": \"beta\" "
+        "     },"
+        "     {"
+        "        \"pool\": \"192.0.4.101 - 192.0.4.150\", "
+        "        \"client-class\": \"gamma\" "
+        "     },"
+        "     {"
+        "        \"pool\": \"192.0.5.101 - 192.0.5.150\" "
+        "     } ],"
+        "    \"subnet\": \"192.0.0.0/16\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
+    extractConfig(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 0);
+
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+    const PoolCollection& pools = subnets->at(0)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+    // Let's check if client belonging to alpha class is supported in pool[0]
+    // and not supported in any other pool (except pool[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in pool[1]
+    // and not supported in any other pool  (except pools[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_TRUE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in pool[2]
+    // and not supported in any other pool  (except pool[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_TRUE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in pool[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last pool, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+}
+
 // This test verifies that the host reservations can be specified for
 // respective IPv4 subnets.
 TEST_F(Dhcp4ParserTest, reservations) {
index df1ae36fb93cbb02abc2a8f95c4e051673f81ee5..f650d33867f0acbfe3779d4ac5ae2d42c55d0fde 100644 (file)
@@ -2318,6 +2318,75 @@ TEST_F(Dhcpv4SrvTest, clientClassify) {
     EXPECT_TRUE(srv_.selectSubnet(dis));
 }
 
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(Dhcpv4SrvTest, clientPoolClassify) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    // This test configures 2 pools.
+    // The second pool does not play any role here. The client's
+    // IP address belongs to the first pool, so only that first
+    // pool is being tested.
+    string config = "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ "
+        "{   \"pools\": [ { "
+        "      \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+        "      \"client-class\": \"foo\" }, "
+        "    { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
+        "      \"client-class\": \"xyzzy\" } ], "
+        "    \"subnet\": \"192.0.0.0/16\" } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+    CfgMgr::instance().commit();
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    // Create a simple packet that we'll use for classification
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    dis->setCiaddr(IOAddress("192.0.2.1"));
+    dis->setIface("eth0");
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // This discover does not belong to foo class, so it will not
+    // be serviced
+    Pkt4Ptr offer = srv.processDiscover(dis);
+    EXPECT_FALSE(offer);
+
+    // Let's add the packet to bar class and try again.
+    dis->addClass("bar");
+
+    // Still not supported, because it belongs to wrong class.
+    offer = srv.processDiscover(dis);
+    EXPECT_FALSE(offer);
+
+    // Let's add it to matching class.
+    dis->addClass("foo");
+
+    // This time it should work
+    offer = srv.processDiscover(dis);
+    ASSERT_TRUE(offer);
+    EXPECT_EQ(DHCPOFFER, offer->getType());
+    EXPECT_FALSE(offer->getYiaddr().isV4Zero());
+}
+
 // Verifies last resort option 43 is backward compatible
 TEST_F(Dhcpv4SrvTest, option43LastResort) {
     IfaceMgrTestConfig test_config(true);
index c5c47007dad177816dbad2bf3654bead8826a384..64240ec6f5a9041cbc6937bf629188a2e6e80d3c 100644 (file)
@@ -700,7 +700,7 @@ const char* NETWORKS_CONFIG[] = {
 
 // Configuration #13.
 // - 2 classes
-// - 2 shared networks, each with 1 subnet and client class restricton
+// - 2 shared networks, each with 1 subnet and client class restriction
     "{"
     "    \"interfaces-config\": {"
     "        \"interfaces\": [ \"*\" ]"
@@ -861,6 +861,158 @@ const char* NETWORKS_CONFIG[] = {
     "            ]"
     "        }"
     "    ]"
+    "}",
+
+// Configuration #16
+// - 1 shared network with 1 subnet and 2 pools (first pool has class restriction)
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[93].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[93].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/24\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\","
+    "                            \"client-class\": \"a-devices\""
+    "                        },"
+    "                        {"
+    "                            \"pool\": \"192.0.2.100 - 192.0.2.100\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #17
+// - 1 shared network with 1 subnet and 2 pools (each with class restriction)
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[93].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[93].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/24\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\","
+    "                            \"client-class\": \"a-devices\""
+    "                        },"
+    "                        {"
+    "                            \"pool\": \"192.0.2.100 - 192.0.2.100\","
+    "                            \"client-class\": \"b-devices\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #18
+// - plain subnet and 2 pools (first pool has class restriction)
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[93].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[93].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"valid-lifetime\": 600,"
+    "    \"subnet4\": ["
+    "        {"
+    "            \"subnet\": \"192.0.2.0/24\","
+    "            \"id\": 10,"
+    "            \"interface\": \"eth1\","
+    "            \"pools\": ["
+    "                {"
+    "                    \"pool\": \"192.0.2.1 - 192.0.2.63\","
+    "                    \"client-class\": \"a-devices\""
+    "                },"
+    "                {"
+    "                    \"pool\": \"192.0.2.100 - 192.0.2.100\""
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #19
+// - plain subnet and 2 pools (each with class restriction)
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[93].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[93].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"valid-lifetime\": 600,"
+    "    \"subnet4\": ["
+    "        {"
+    "            \"subnet\": \"192.0.2.0/24\","
+    "            \"id\": 10,"
+    "            \"interface\": \"eth1\","
+    "            \"pools\": ["
+    "                {"
+    "                    \"pool\": \"192.0.2.1 - 192.0.2.63\","
+    "                    \"client-class\": \"a-devices\""
+    "                },"
+    "                {"
+    "                    \"pool\": \"192.0.2.100 - 192.0.2.100\","
+    "                    \"client-class\": \"b-devices\""
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
     "}"
 
 };
@@ -1813,4 +1965,123 @@ TEST_F(Dhcpv4SharedNetworkTest, customServerIdentifier) {
     EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
 }
 
+// Access to a pool within shared network is restricted by client
+// classification.
+TEST_F(Dhcpv4SharedNetworkTest, poolInSharedNetworkSelectedByClass) {
+    // Create client #1
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setIfaceName("eth1");
+
+    // Configure the server with one shared network including one subnet and
+    // in 2 pools in it. The access to one of the pools is restricted
+    // by client classification.
+    configure(NETWORKS_CONFIG[16], *client1.getServer());
+
+    // Client #1 requests an address in the restricted pool but can't be assigned
+    // this address because the client doesn't belong to a certain class.
+    testAssigned([this, &client1] {
+        doDORA(client1, "192.0.2.100", "192.0.2.63");
+    });
+
+    // Release the lease that the client has got, because we'll need this address
+    // further in the test.
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doRelease());
+    });
+
+    // Add option93 which would cause the client to be classified as "a-devices".
+    OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+    client1.addExtraOption(option93);
+
+    // This time, the allocation of the address provided as hint should be successful.
+    testAssigned([this, &client1] {
+        doDORA(client1, "192.0.2.63", "192.0.2.63");
+    });
+
+    // Client 2 should be assigned an address from the unrestricted pool.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth1");
+    testAssigned([this, &client2] {
+        doDORA(client2, "192.0.2.100");
+    });
+
+    // Now, let's reconfigure the server to also apply restrictions on the
+    // pool to which client2 now belongs.
+    configure(NETWORKS_CONFIG[17], *client1.getServer());
+
+    // The client should be refused to renew the lease because it doesn't belong
+    // to "b-devices" class.
+    client2.setState(Dhcp4Client::RENEWING);
+    testAssigned([this, &client2] {
+        doRequest(client2, "");
+    });
+
+    // If we add option93 with a value matching this class, the lease should
+    // get renewed.
+    OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
+    client2.addExtraOption(option93_bis);
+
+    testAssigned([this, &client2] {
+        doRequest(client2, "192.0.2.100");
+    });
+}
+
+// Access to a pool within plain subnet is restricted by client classification.
+TEST_F(Dhcpv4SharedNetworkTest, poolInSubnetSelectedByClass) {
+    // Create client #1
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setIfaceName("eth1");
+
+    // Configure the server with one plain subnet including two pools.
+    // The access to one of the pools is restricted by client classification.
+    configure(NETWORKS_CONFIG[18], *client1.getServer());
+
+    // Client #1 requests an address in the restricted pool but can't be assigned
+    // this address because the client doesn't belong to a certain class.
+    testAssigned([this, &client1] {
+        doDORA(client1, "192.0.2.100", "192.0.2.63");
+    });
+
+    // Release the lease that the client has got, because we'll need this address
+    // further in the test.
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doRelease());
+    });
+
+    // Add option93 which would cause the client to be classified as "a-devices".
+    OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+    client1.addExtraOption(option93);
+
+    // This time, the allocation of the address provided as hint should be successful.
+    testAssigned([this, &client1] {
+        doDORA(client1, "192.0.2.63", "192.0.2.63");
+    });
+
+    // Client 2 should be assigned an address from the unrestricted pool.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth1");
+    testAssigned([this, &client2] {
+        doDORA(client2, "192.0.2.100");
+    });
+
+    // Now, let's reconfigure the server to also apply restrictions on the
+    // pool to which client2 now belongs.
+    configure(NETWORKS_CONFIG[19], *client1.getServer());
+
+    // The client should be refused to renew the lease because it doesn't belong
+    // to "b-devices" class.
+    client2.setState(Dhcp4Client::RENEWING);
+    testAssigned([this, &client2] {
+        doRequest(client2, "");
+    });
+
+    // If we add option93 with a value matching this class, the lease should
+    // get renewed.
+    OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
+    client2.addExtraOption(option93_bis);
+
+    testAssigned([this, &client2] {
+        doRequest(client2, "192.0.2.100");
+    });
+}
 } // end of anonymous namespace
index 4805a86bfca8b2d2ac93c051892e76b08d2929a0..58808613eaa1476a0922ad99248794ef0e430c41 100644 (file)
@@ -609,7 +609,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_PREFERRED_LIFETIME(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("preferred-lifetime", driver.loc_);
@@ -620,7 +620,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_VALID_LIFETIME(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("valid-lifetime", driver.loc_);
@@ -631,7 +631,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RENEW_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("renew-timer", driver.loc_);
@@ -642,7 +642,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_REBIND_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("rebind-timer", driver.loc_);
@@ -661,7 +661,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"subnet6\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_SUBNET6(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("subnet6", driver.loc_);
@@ -670,7 +670,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 
 \"shared-networks\" {
     switch (driver.ctx_) {
-    case Parser6Context::DHCP6:
+    case isc::dhcp::Parser6Context::DHCP6:
         return Dhcp6Parser::make_SHARED_NETWORKS(driver.loc_);
     default:
         return Dhcp6Parser::make_STRING("shared-networks", driver.loc_);
@@ -695,7 +695,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser6Context::RESERVATIONS:
     case isc::dhcp::Parser6Context::CLIENT_CLASSES:
     case isc::dhcp::Parser6Context::CLIENT_CLASS:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_OPTION_DATA(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("option-data", driver.loc_);
@@ -711,7 +711,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser6Context::CLIENT_CLASSES:
     case isc::dhcp::Parser6Context::CLIENT_CLASS:
     case isc::dhcp::Parser6Context::LOGGERS:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_NAME(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("name", driver.loc_);
@@ -864,7 +864,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"interface\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_INTERFACE(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("interface", driver.loc_);
@@ -874,7 +874,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"interface-id\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_INTERFACE_ID(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("interface-id", driver.loc_);
@@ -893,7 +893,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"rapid-commit\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RAPID_COMMIT(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("rapid-commit", driver.loc_);
@@ -903,7 +903,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"reservation-mode\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RESERVATION_MODE(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("reservation-mode", driver.loc_);
@@ -1078,8 +1078,10 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"client-class\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
+    case isc::dhcp::Parser6Context::POOLS:
+    case isc::dhcp::Parser6Context::PD_POOLS:
     case isc::dhcp::Parser6Context::CLIENT_CLASSES:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_CLIENT_CLASS(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("client-class", driver.loc_);
@@ -1212,7 +1214,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"relay\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
-    case Parser6Context::SHARED_NETWORK:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RELAY(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("relay", driver.loc_);
@@ -1222,7 +1224,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"ip-address\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::RELAY:
-    return isc::dhcp::Dhcp6Parser::make_IP_ADDRESS(driver.loc_);
+        return isc::dhcp::Dhcp6Parser::make_IP_ADDRESS(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("ip-address", driver.loc_);
     }
index 0d6c9caebe892e93ddd4261d678ec869610e3672..1653dec66d41305099f889635149a233bf6edfa4 100644 (file)
@@ -60,7 +60,7 @@ The first argument specifies the client and transaction identification
 information. The second argument includes all classes to which the
 packet has been assigned.
 
-% DHCP6_CLASS_UNCONFIGURED %1: client packet belongs to an unconfigured class: %1
+% DHCP6_CLASS_UNCONFIGURED %1: client packet belongs to an unconfigured class: %2
 This debug message informs that incoming packet belongs to a class
 which cannot be found in the configuration. Either a hook written
 before the classification was added to Kea is used, or class naming is
index 15c0e8ebb5f4f6ba5e7631894d42188a64ddb02e..5fd8df6db60b1d51779c3c5272380a4f6e73d265 100644 (file)
@@ -1307,6 +1307,7 @@ pool_params: pool_param
 
 pool_param: pool_entry
           | option_data_list
+          | client_class
           | user_context
           | comment
           | unknown_map_entry
@@ -1427,6 +1428,7 @@ pd_pool_param: pd_prefix
              | pd_prefix_len
              | pd_delegated_len
              | option_data_list
+             | client_class
              | excluded_prefix
              | excluded_prefix_len
              | user_context
@@ -1622,11 +1624,11 @@ client_classes: CLIENT_CLASSES {
     ctx.leave();
 };
 
-client_classes_list: client_class
-                   | client_classes_list COMMA client_class
+client_classes_list: client_class_entry
+                   | client_classes_list COMMA client_class_entry
                    ;
 
-client_class: LCURLY_BRACKET {
+client_class_entry: LCURLY_BRACKET {
     ElementPtr m(new MapElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->add(m);
     ctx.stack_.push_back(m);
index 9b04d035c240e503e6bbfccc12829bd7f0032264..7a81310f927c2991a5be1b8150a5248775c8a040 100644 (file)
@@ -643,6 +643,98 @@ TEST_F(ClassifyTest, clientClassifySubnet) {
     EXPECT_TRUE(srv_.selectSubnet(sol));
 }
 
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(ClassifyTest, clientClassifyPool) {
+    IfaceMgrTestConfig test_config(true);
+
+    NakedDhcpv6Srv srv(0);
+
+    // This test configures 2 pools.
+    // The second pool does not play any role here. The client's
+    // IP address belongs to the first pool, so only that first
+    // pool is being tested.
+    std::string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"client-classes\": [ "
+        " { "
+        "    \"name\": \"foo\" "
+        " }, "
+        " { "
+        "    \"name\": \"bar\" "
+        " } "
+        "], "
+        "\"subnet6\": [ "
+        " {  \"pools\": [ "
+        "    { "
+        "       \"pool\": \"2001:db8:1::/64\", "
+        "       \"client-class\": \"foo\" "
+        "    }, "
+        "    { "
+        "       \"pool\": \"2001:db8:2::/64\", "
+        "       \"client-class\": \"xyzzy\" "
+        "    } "
+        "   ], "
+        "   \"subnet\": \"2001:db8:2::/40\" "
+        " } "
+        "], "
+        "\"valid-lifetime\": 4000 }";
+
+    ASSERT_NO_THROW(configure(config));
+
+    OptionPtr clientid = generateClientId();
+    Pkt6Ptr query1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    query1->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    query1->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    Pkt6Ptr query2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    query2->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    query2->addOption(clientid);
+    query2->setIface("eth1");
+    Pkt6Ptr query3 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    query3->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    query3->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    query3->addOption(clientid);
+    query3->setIface("eth1");
+
+    // This discover does not belong to foo class, so it will not
+    // be serviced
+    srv.classifyPacket(query1);
+    Pkt6Ptr response1 = srv.processSolicit(query1);
+    ASSERT_TRUE(response1);
+    OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
+    ASSERT_TRUE(ia_na1);
+    EXPECT_TRUE(ia_na1->getOption(D6O_STATUS_CODE));
+    EXPECT_FALSE(ia_na1->getOption(D6O_IAADDR));
+
+    // Let's add the packet to bar class and try again.
+    query2->addClass("bar");
+    // Still not supported, because it belongs to wrong class.
+    srv.classifyPacket(query2);
+    Pkt6Ptr response2 = srv.processSolicit(query2);
+    ASSERT_TRUE(response2);
+    OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
+    ASSERT_TRUE(ia_na2);
+    EXPECT_TRUE(ia_na2->getOption(D6O_STATUS_CODE));
+    EXPECT_FALSE(ia_na2->getOption(D6O_IAADDR));
+
+    // Let's add it to matching class.
+    query3->addClass("foo");
+    // This time it should work
+    srv.classifyPacket(query3);
+    Pkt6Ptr response3 = srv.processSolicit(query3);
+    ASSERT_TRUE(response3);
+    OptionPtr ia_na3 = response3->getOption(D6O_IA_NA);
+    ASSERT_TRUE(ia_na3);
+    EXPECT_FALSE(ia_na3->getOption(D6O_STATUS_CODE));
+    EXPECT_TRUE(ia_na3->getOption(D6O_IAADDR));
+}
+
 // Tests whether a packet with custom vendor-class (not erouter or docsis)
 // is classified properly.
 TEST_F(ClassifyTest, vendorClientClassification2) {
index fd18d544355ee08d146b495ade60c7f219557471..ead3a0d9585b170c279d1a554024a1c234b5c9dd 100644 (file)
@@ -4211,6 +4211,194 @@ TEST_F(Dhcp6ParserTest, classifySubnets) {
     EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
 }
 
+// Goal of this test is to verify that multiple pools can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifyPools) {
+    ConstElementPtr x;
+    string config = "{ " + genIfaceConfig() + ","
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { "
+        "        \"pool\": \"2001:db8:1::/80\", "
+        "        \"client-class\": \"alpha\" "
+        "     },"
+        "     {"
+        "        \"pool\": \"2001:db8:2::/80\", "
+        "        \"client-class\": \"beta\" "
+        "     },"
+        "     {"
+        "        \"pool\": \"2001:db8:3::/80\", "
+        "        \"client-class\": \"gamma\" "
+        "     },"
+        "     {"
+        "         \"pool\": \"2001:db8:4::/80\" "
+        "     } ],"
+        "    \"subnet\": \"2001:db8:0::/40\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP6(config, true));
+    extractConfig(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 0);
+
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+    const PoolCollection& pools = subnets->at(0)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+    // Let's check if client belonging to alpha class is supported in pool[0]
+    // and not supported in any other pool (except pool[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in pool[1]
+    // and not supported in any other pool  (except pool[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in pool[2]
+    // and not supported in any other pool  (except pool[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in pool[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last pool, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+}
+
+// Goal of this test is to verify that multiple pdpools can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifyPdPools) {
+    ConstElementPtr x;
+    string config = "{ " + genIfaceConfig() + ","
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pd-pools\": [ { "
+        "        \"prefix-len\": 48, "
+        "        \"delegated-len\": 64, "
+        "        \"prefix\": \"2001:db8:1::\", "
+        "        \"client-class\": \"alpha\" "
+        "     },"
+        "     {"
+        "        \"prefix-len\": 48, "
+        "        \"delegated-len\": 64, "
+        "        \"prefix\": \"2001:db8:2::\", "
+        "        \"client-class\": \"beta\" "
+        "     },"
+        "     {"
+        "        \"prefix-len\": 48, "
+        "        \"delegated-len\": 64, "
+        "        \"prefix\": \"2001:db8:3::\", "
+        "        \"client-class\": \"gamma\" "
+        "     },"
+        "     {"
+        "        \"prefix-len\": 48, "
+        "        \"delegated-len\": 64, "
+        "         \"prefix\": \"2001:db8:4::\" "
+        "     } ],"
+        "    \"subnet\": \"2001:db8::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP6(config, true));
+    extractConfig(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 0);
+
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+    const PoolCollection& pools = subnets->at(0)->getPools(Lease::TYPE_PD);
+    ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+    // Let's check if client belonging to alpha class is supported in pool[0]
+    // and not supported in any other pool (except pool[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in pool[1]
+    // and not supported in any other pool  (except pool[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in pool[2]
+    // and not supported in any other pool  (except pool[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in pool[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last pool, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+    EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+    EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+}
+
 // This test checks the ability of the server to parse a configuration
 // containing a full, valid dhcp-ddns (D2ClientConfig) entry.
 TEST_F(Dhcp6ParserTest, d2ClientConfig) {
index dc8cd2ff3163ed0382fbbff8529194c4ea6755d2..32fa5d53ec60a8b9d8298ced8e88ee4ac0e1292e 100644 (file)
@@ -957,7 +957,151 @@ const char* NETWORKS_CONFIG[] = {
     "            ]"
     "        }"
     "    ]"
+    "}",
+
+// Configuration #19.
+// - one shared network with one subnet and two pools (the first has
+//   class restrictions)
+    "{"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[1234].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[1234].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"subnet6\": ["
+    "                {"
+    "                    \"subnet\": \"2001:db8:1::/64\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+    "                            \"client-class\": \"a-devices\""
+    "                        },"
+    "                        {"
+    "                            \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #20.
+// - one shared network with one subnet and two pools (each with class
+//   restriction)
+    "{"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[1234].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[1234].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"subnet6\": ["
+    "                {"
+    "                    \"subnet\": \"2001:db8:1::/64\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+    "                            \"client-class\": \"a-devices\""
+    "                        },"
+    "                        {"
+    "                            \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\","
+    "                            \"client-class\": \"b-devices\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #21.
+// - one plain subnet with two pools (the first has class restrictions)
+    "{"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[1234].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[1234].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"subnet6\": ["
+    "        {"
+    "            \"subnet\": \"2001:db8:1::/64\","
+    "            \"id\": 10,"
+    "            \"interface\": \"eth1\","
+    "            \"pools\": ["
+    "                {"
+    "                    \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+    "                    \"client-class\": \"a-devices\""
+    "                },"
+    "                {"
+    "                    \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\""
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #22.
+// - one plain subnet with two pools (each with class restriction)
+    "{"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[1234].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[1234].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"subnet6\": ["
+    "                {"
+    "                    \"subnet\": \"2001:db8:1::/64\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+    "                            \"client-class\": \"a-devices\""
+    "                        },"
+    "                        {"
+    "                            \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\","
+    "                            \"client-class\": \"b-devices\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
     "}"
+
 };
 
 /// @Brief Test fixture class for DHCPv6 server using shared networks.
@@ -2191,4 +2335,131 @@ TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkRapidCommit3) {
     testRapidCommit(NETWORKS_CONFIG[1], false, "", "");
 }
 
+// Pool is selected based on the client class specified.
+TEST_F(Dhcpv6SharedNetworkTest, poolInSharedNetworkSelectedByClass) {
+    // Create client #1.
+    Dhcp6Client client1;
+    client1.setInterface("eth1");
+
+    // Configure the server with one shared network including one subnet and
+    // two pools. The access to one of the pools is restricted by
+    // by client classification.
+    ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[19], *client1.getServer()));
+
+    // Client #1 requests an address in the restricted pool but can't be assigned
+    // this address because the client doesn't belong to a certain class.
+    ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doSARR());
+    });
+    ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::50")));
+
+    // Release the lease that the client has got, because we'll need this address
+    // further in the test.
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doRelease());
+    });
+
+    // Add option 1234 which would cause the client to be classified as "a-devices".
+    OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0001));
+    client1.addExtraOption(option1234);
+
+    // This time, the allocation of the address provided as hint should be successful.
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doSARR());
+    });
+    ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+    // Client 2 should be assigned an address from the unrestricted pool.
+    Dhcp6Client client2(client1.getServer());
+    client2.setInterface("eth1");
+    ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+    testAssigned([this, &client2] {
+        ASSERT_NO_THROW(client2.doSARR());
+    });
+    ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:1::50")));
+
+    // Now, let's reconfigure the server to also apply restrictions on the
+    // pool to which client2 now belongs.
+    ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[20], *client1.getServer()));
+
+    testAssigned([this, &client2] {
+        ASSERT_NO_THROW(client2.doRenew());
+    });
+    EXPECT_EQ(0, client2.getLeasesWithNonZeroLifetime().size());
+
+    // If we add option 1234 with a value matching this class, the lease should
+    // get renewed.
+    OptionPtr option1234_bis(new OptionUint16(Option::V6, 1234, 0x0002));
+    client2.addExtraOption(option1234_bis);
+    testAssigned([this, &client2] {
+        ASSERT_NO_THROW(client2.doRenew());
+    });
+    EXPECT_EQ(1, client2.getLeaseNum());
+    EXPECT_EQ(1, client2.getLeasesWithNonZeroLifetime().size());
+}
+
+// Pool is selected based on the client class specified using a plain subnet.
+TEST_F(Dhcpv6SharedNetworkTest, poolInSubnetSelectedByClass) {
+    // Create client #1.
+    Dhcp6Client client1;
+    client1.setInterface("eth1");
+
+    // Configure the server with one plain subnet including two pools.
+    // The access to one of the pools is restricted by client classification.
+    ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[21], *client1.getServer()));
+
+    // Client #1 requests an address in the restricted pool but can't be assigned
+    // this address because the client doesn't belong to a certain class.
+    ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doSARR());
+    });
+    ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::50")));
+
+    // Release the lease that the client has got, because we'll need this address
+    // further in the test.
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doRelease());
+    });
+
+    // Add option 1234 which would cause the client to be classified as "a-devices".
+    OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0001));
+    client1.addExtraOption(option1234);
+
+    // This time, the allocation of the address provided as hint should be successful.
+    testAssigned([this, &client1] {
+        ASSERT_NO_THROW(client1.doSARR());
+    });
+    ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+    // Client 2 should be assigned an address from the unrestricted pool.
+    Dhcp6Client client2(client1.getServer());
+    client2.setInterface("eth1");
+    ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+    testAssigned([this, &client2] {
+        ASSERT_NO_THROW(client2.doSARR());
+    });
+    ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:1::50")));
+
+    // Now, let's reconfigure the server to also apply restrictions on the
+    // pool to which client2 now belongs.
+    ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[22], *client1.getServer()));
+
+    testAssigned([this, &client2] {
+        ASSERT_NO_THROW(client2.doRenew());
+    });
+    EXPECT_EQ(0, client2.getLeasesWithNonZeroLifetime().size());
+
+    // If we add option 1234 with a value matching this class, the lease should
+    // get renewed.
+    OptionPtr option1234_bis(new OptionUint16(Option::V6, 1234, 0x0002));
+    client2.addExtraOption(option1234_bis);
+    testAssigned([this, &client2] {
+        ASSERT_NO_THROW(client2.doRenew());
+    });
+    EXPECT_EQ(1, client2.getLeaseNum());
+    EXPECT_EQ(1, client2.getLeasesWithNonZeroLifetime().size());
+}
+
 } // end of anonymous namespace