"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"
+ }
]
},
"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"
}
+
]
},
</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>
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
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
\"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_);
pool_param: pool_entry
| option_data_list
+ | client_class
| user_context
| comment
| unknown_map_entry
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);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
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) {
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);
// 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\": [ \"*\" ]"
" ]"
" }"
" ]"
+ "}",
+
+// 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\""
+ " }"
+ " ]"
+ " }"
+ " ]"
"}"
};
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
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_);
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_);
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_);
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_);
\"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_);
\"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_);
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_);
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_);
\"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_);
\"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_);
\"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_);
\"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_);
\"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_);
\"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_);
\"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_);
}
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
pool_param: pool_entry
| option_data_list
+ | client_class
| user_context
| comment
| unknown_map_entry
| pd_prefix_len
| pd_delegated_len
| option_data_list
+ | client_class
| excluded_prefix
| excluded_prefix_len
| user_context
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);
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) {
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) {
" ]"
" }"
" ]"
+ "}",
+
+// 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.
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