Commit 300cb636 authored by Francis Dupont's avatar Francis Dupont
Browse files

[5425a] Applied changes - need regen

parent 0b6ecc7e
......@@ -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.