mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-28 12:37:55 +00:00
[5425a] Applied changes - need regen
This commit is contained in:
parent
0b6ecc7e4c
commit
300cb63670
@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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_);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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_);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user