2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-28 20:47:48 +00:00

[5425a] Applied changes - need regen

This commit is contained in:
Francis Dupont 2018-01-19 00:13:48 +01:00
parent 0b6ecc7e4c
commit 300cb63670
16 changed files with 1112 additions and 24 deletions

View File

@ -97,7 +97,27 @@
"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"
}
]
},

View File

@ -73,7 +73,19 @@
"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"
}
]
},

View 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>

View 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

View 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

View 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_);

View 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);

View 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) {

View 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);

View 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

View 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_);
}

View 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

View 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);

View 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) {

View 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) {

View 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