Commit 6b0e2fd8 authored by Francis Dupont's avatar Francis Dupont
Browse files

[5374] Checkpoint: nearly finished: still some cleanups

parent 1db3a965
......@@ -40,7 +40,7 @@
</para>
<para>
The process of doing classification is conducted in four steps:
The process of doing classification is conducted in six steps:
<orderedlist>
<listitem><para>
Assess an incoming packet and assign it to zero or more classes.
......@@ -54,6 +54,13 @@
Choose a subnet, possibly based on the class information.
</para></listitem>
<listitem><para>
Assign classes from host reservations
</para></listitem>
<listitem><para>
Perform a second pass by evaluating match expressions of on-demand
classes.
</para></listitem>
<listitem><para>
Assign options, again possibly based on the class information.
For DHCPv4 private and code 43 options this includes class local
option definitions.
......@@ -142,6 +149,11 @@
is &quot;true&quot;. Expressions are written in standard format and can be nested.
</para>
<para>
When the eval-on-demand flag is set to true in a class definition,
the match expression is skipped during the first evaluation pass.
</para>
<para>
Expressions are pre-processed during the parsing of the configuration file
and converted to an internal representation. This allows certain types of
......@@ -671,9 +683,11 @@ concatenation of the strings</entry></row>
<section id="classification-configuring">
<title>Configuring Classes</title>
<para>
A class contains three items: a name, a test expression and option data.
A class contains five items: a name, a test expression, option data,
option definition and eval on-demand flag.
The name must exist and must be unique amongst all classes. The test
expression and option data are optional.
expression, option data and definition, and eval on-demand flag are
optional.
</para>
<para>
......@@ -687,6 +701,33 @@ concatenation of the strings</entry></row>
to members of this class.
</para>
<para>
The option definition is for DHCPv4 option 43 (<xref
linkend="dhcp4-vendor-opts"/> and DHCPv4 private options
(<xref linkend="dhcp4-private-opts"/>).
</para>
<para>
Usually the test expression is evaluated before subnet selection
but in some cases it is useful to evaluate it later when the
subnet, shared-network or pools are known but output option
processing not yet done. The eval-on-demand flag, false by default,
allows to defer and make only on-demand the evaluation of the
test expression.
</para>
<para>
The eval-client-classes list which is valid for shared-network,
subnet and pool scope specifies the classes which are evaluated
in the second pass before output option processing.
The list is built in the reversed precedence order of option
data, i.e. an option data in a subnet takes precedence on one
in a shared-network but an on-demand class in a subnet is added
after one in a shared-network.
The mechanism is related to the eval-on-demand flag but it is
not required that the flag was set to true.
</para>
<para>
In the following example the class named &quot;Client_foo&quot; is defined.
It is comprised of all clients whose client ids (option 61) start with the
......
......@@ -1613,7 +1613,7 @@ It is merely echoed by the server
an old PXEClient vendor:
<screen>
"Dhcp4": {
"client-class": [
"client-classes": [
{
<userinput>"name": "pxeclient",
"test": "option[vendor-class-identifier].text == 'PXEClient'",
......@@ -1656,7 +1656,7 @@ It is merely echoed by the server
},</userinput>
...
],
"client-class": [
"client-classes": [
{
<userinput>"name": "APC",
"test": "(option[vendor-class-identifier].text == 'APC'",
......@@ -2100,10 +2100,13 @@ It is merely echoed by the server
</para>
<para>
The process of doing classification is conducted in three steps. The first step
The process of doing classification is conducted in five steps. The first step
is to assess an incoming packet and assign it to zero or more classes. The
second step is to choose a subnet, possibly based on the class information.
The third step is to assign options, again possibly based on the class
The third step is to assign classes from host reservations.
The forth step is to build the list of on-demand classes and perform
deferred evaluation for each class of the list.
The last step is to assign options, again possibly based on the class
information.
</para>
......@@ -2161,9 +2164,13 @@ It is merely echoed by the server
<para>
If there are multiple classes defined and an incoming packet is matched
to multiple classes, the class whose name is alphabetically the first
is used.
to multiple classes, the class which is defined first is used.
</para>
<note><para>
In versions before 1.4 the alphabetical order was used.
</para></note>
</section>
<section>
......@@ -2249,6 +2256,56 @@ It is merely echoed by the server
}</screen>
</para>
</section>
<section id="dhcp4-on-demand-class">
<title>On-demand classification</title>
<para>
In some cases it is useful to limit the scope of class.
Two devices are available to perform evaluation of test
expressions so assignment when it returns true only on-demand.
</para>
<para>
The first one is the per-class <command>eval-on-demand</command>
flag which is false by default. When it is set to
<command>true</command> the test expression of the class is not
evaluated at the reception of a new incoming ticket.
</para>
<para>
The second is the <command>eval-client-classes</command> which
takes a list of class names and is valid in shared-network,
subnet and pool scope. Classes in these lists are evaluated
after resource assignment and before output option processing.
</para>
<para>
In this example a class is assigned to the incoming packet
when the specified subnet is used.
<screen>
"Dhcp4": {
"client-classes": [
{<userinput>
"name": "Client_foo",
"test": "'' == ''",
"eval-on-demand": true</userinput>
},
...
],
"subnet4": [
{
"subnet": "192.0.2.0/24",
"pools": [ { "pool": "192.0.2.10 - 192.0.2.20" } ],
<userinput>"eval-client-classes": [ "Client_foo" ],</userinput>
...
},
...
],
...
}</screen>
</para>
</section>
</section>
<section id="dhcp4-ddns-config">
......@@ -3766,7 +3823,7 @@ for each subnet. Here's an example:
<screen>
"shared-networks": [
{"
{
"name": "kakapo",
<userinput>"relay": {
"ip-address": "192.3.5.6"
......
......@@ -1960,10 +1960,13 @@ should include options from the isc option space:
</para>
<para>
The process of doing classification is conducted in three steps. The first step
The process of doing classification is conducted in five steps. The first step
is to assess an incoming packet and assign it to zero or more classes. The
second step is to choose a subnet, possibly based on the class information.
The third step is to assign options again possibly based on the class
The third step is to assign classes from host reservations.
The forth step is to build the list of on-demand classes and perform
deferred evaluation for each class of the list.
The last step is to assign options again possibly based on the class
information.
</para>
......@@ -2046,6 +2049,60 @@ should include options from the isc option space:
</screen>
</para>
</section>
<section id="dhcp6-on-demand-class">
<title>On-demand classification</title>
<para>
In some cases it is useful to limit the scope of class.
Two devices are available to perform evaluation of test
expressions so assignment when it returns true only on-demand.
</para>
<para>
The first one is the per-class <command>eval-on-demand</command>
flag which is false by default. When it is set to
<command>true</command> the test expression of the class is not
evaluated at the reception of a new incoming ticket.
</para>
<para>
The second is the <command>eval-client-classes</command> which
takes a list of class names and is valid in shared-network,
subnet and pool scope. Classes in these lists are evaluated
after resource assignment and before output option processing.
</para>
<para>
In this example a class is assigned to the incoming packet
when the specified subnet is used.
<screen>
"Dhcp6": {
"client-classes": [
{<userinput>
"name": "Client_foo",
"test": "'' == ''",
"eval-on-demand": true</userinput>
},
...
],
"subnet6": [
{
"subnet": "2001:db8:1::/64"
"pools": [
{
"pool": "2001:db8:1::-2001:db8:1::ffff"
}
],
<userinput>"eval-client-classes": [ "Client_foo" ],</userinput>
...
},
...
],
...
}</screen>
</para>
</section>
</section>
<section id="dhcp6-ddns-config">
......@@ -3300,7 +3357,7 @@ for each subnet. Here's an example:
<screen>
"shared-networks": [
{"
{
"name": "kakapo",
<userinput>"relay": {
"ip-address": "2001:db8::abcd"
......
......@@ -54,6 +54,16 @@ namespace {
/// option[93].hex == 0x0006
/// option[93].hex == 0x0001
/// or member(<last two>), set boot-file-name to pxelinux.0
///
/// - Configuration 3:
/// - Used for late/on-demand classification
/// - 1 subnet: 10.0.0.0/24
/// - 1 pool: 10.0.0.10-10.0.0.100
/// - the following classes defined:
/// option[93].hex == 0x0009, next-server set to 1.2.3.4
/// option[93].hex == 0x0007, set server-hostname to deneb
/// option[93].hex == 0x0006, set boot-file-name to pxelinux.0
/// option[93].hex == 0x0001, set boot-file-name to ipxe.efi
const char* CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
......@@ -164,7 +174,45 @@ const char* CONFIGS[] = {
" \"id\": 1,"
" \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
" } ]"
"}",
// Configuration 3
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
"\"client-classes\": ["
"{"
" \"name\": \"pxe1\","
" \"test\": \"option[93].hex == 0x0009\","
" \"eval-on-demand\": true,"
" \"next-server\": \"1.2.3.4\""
"},"
"{"
" \"name\": \"pxe2\","
" \"test\": \"option[93].hex == 0x0007\","
" \"eval-on-demand\": true,"
" \"server-hostname\": \"deneb\""
"},"
"{"
" \"name\": \"pxe3\","
" \"test\": \"option[93].hex == 0x0006\","
" \"eval-on-demand\": false,"
" \"boot-file-name\": \"pxelinux.0\""
"},"
"{"
" \"name\": \"pxe4\","
" \"test\": \"option[93].hex == 0x0001\","
" \"boot-file-name\": \"ipxe.efi\""
"}],"
"\"subnet4\": [ { "
" \"subnet\": \"10.0.0.0/24\", "
" \"id\": 1,"
" \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
" \"eval-client-classes\": [ \"pxe2\" ]"
" } ]"
"}"
};
/// @brief Test fixture class for testing classification.
......@@ -507,4 +555,394 @@ TEST_F(ClassifyTest, fixedFieldsInformFile22) {
testFixedFields(CONFIGS[2], DHCPINFORM, pxe, "0.0.0.0", "", "pxelinux.0");
}
// No class
TEST_F(ClassifyTest, fixedFieldsDiscoverNoClasses3) {
testFixedFields(CONFIGS[3], DHCPDISCOVER, OptionPtr(), "0.0.0.0", "", "");
}
TEST_F(ClassifyTest, fixedFieldsRequestNoClasses3) {
testFixedFields(CONFIGS[3], DHCPREQUEST, OptionPtr(), "0.0.0.0", "", "");
}
TEST_F(ClassifyTest, fixedFieldsInformNoClasses3) {
testFixedFields(CONFIGS[3], DHCPINFORM, OptionPtr(), "0.0.0.0", "", "");
}
// Class 'pxe1' is on-demand and not subject to late evaluation
TEST_F(ClassifyTest, fixedFieldsDiscoverNextServer3) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "");
}
TEST_F(ClassifyTest, fixedFieldsRequestNextServer3) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "");
}
TEST_F(ClassifyTest, fixedFieldsInformNextServer3) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "", "");
}
// Class pxe2 is on-demand but the subnet requests its late evaluation
TEST_F(ClassifyTest, fixedFieldsDiscoverHostname3) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "deneb", "");
}
TEST_F(ClassifyTest, fixedFieldsRequestHostname3) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "deneb", "");
}
TEST_F(ClassifyTest, fixedFieldsInformHostname3) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "deneb", "");
}
// No change from config #0 for pxe3 and pxe4
TEST_F(ClassifyTest, fixedFieldsDiscoverFile31) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
}
TEST_F(ClassifyTest, fixedFieldsRequestFile31) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
}
TEST_F(ClassifyTest, fixedFieldsInformFile31) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
}
TEST_F(ClassifyTest, fixedFieldsDiscoverFile32) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "ipxe.efi");
}
TEST_F(ClassifyTest, fixedFieldsRequestFile32) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "ipxe.efi");
}
TEST_F(ClassifyTest, fixedFieldsInformFile32) {
OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "", "ipxe.efi");
}
// This test checks the precedence order in requested late evaluation.
// This order is: shared-network > subnet > pools
TEST_F(ClassifyTest, precedenceNone) {
std::string config =
"{"
"\"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
"\"client-classes\": ["
" {"
" \"name\": \"all\","
" \"test\": \"'' == ''\""
" },"
" {"
" \"name\": \"for-pool\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.1\""
" } ]"
" },"
" {"
" \"name\": \"for-subnet\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.2\""
" } ]"
" },"
" {"
" \"name\": \"for-network\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.3\""
" } ]"
" }"
"],"
"\"shared-networks\": [ {"
" \"name\": \"frog\","
" \"subnet4\": [ { "
" \"subnet\": \"10.0.0.0/24\","
" \"id\": 1,"
" \"pools\": [ { "
" \"pool\": \"10.0.0.10-10.0.0.100\""
" } ]"
" } ]"
"} ]"
"}";
// Create a client requesting domain-name-servers option
Dhcp4Client client(Dhcp4Client::SELECTING);
client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
// Load the config and perform a DORA
configure(config, *client.getServer());
ASSERT_NO_THROW(client.doDORA());
// Check response
Pkt4Ptr resp = client.getContext().response_;
ASSERT_TRUE(resp);
EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
// Check domain-name-servers option
OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
EXPECT_FALSE(opt);
}
// This test checks the precedence order in requested late evaluation.
// This order is: shared-network > subnet > pools
TEST_F(ClassifyTest, precedencePool) {
std::string config =
"{"
"\"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
"\"client-classes\": ["
" {"
" \"name\": \"all\","
" \"test\": \"'' == ''\""
" },"
" {"
" \"name\": \"for-pool\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.1\""
" } ]"
" },"
" {"
" \"name\": \"for-subnet\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.2\""
" } ]"
" },"
" {"
" \"name\": \"for-network\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.3\""
" } ]"
" }"
"],"
"\"shared-networks\": [ {"
" \"name\": \"frog\","
" \"subnet4\": [ { "
" \"subnet\": \"10.0.0.0/24\","
" \"id\": 1,"
" \"pools\": [ { "
" \"pool\": \"10.0.0.10-10.0.0.100\","
" \"eval-client-classes\": [ \"for-pool\" ]"
" } ]"
" } ]"
"} ]"
"}";
// Create a client requesting domain-name-servers option
Dhcp4Client client(Dhcp4Client::SELECTING);
client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
// Load the config and perform a DORA
configure(config, *client.getServer());
ASSERT_NO_THROW(client.doDORA());
// Check response
Pkt4Ptr resp = client.getContext().response_;
ASSERT_TRUE(resp);
EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
// Check domain-name-servers option
OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
ASSERT_TRUE(opt);
Option4AddrLstPtr servers =
boost::dynamic_pointer_cast<Option4AddrLst>(opt);
ASSERT_TRUE(servers);
auto addrs = servers->getAddresses();
ASSERT_EQ(1, addrs.size());
EXPECT_EQ("10.0.0.1", addrs[0].toText());
}
// This test checks the precedence order in requested late evaluation.
// This order is: shared-network > subnet > pools
TEST_F(ClassifyTest, precedenceSubnet) {
std::string config =
"{"
"\"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
"\"client-classes\": ["
" {"
" \"name\": \"all\","
" \"test\": \"'' == ''\""
" },"
" {"
" \"name\": \"for-pool\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.1\""
" } ]"
" },"
" {"
" \"name\": \"for-subnet\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.2\""
" } ]"
" },"
" {"
" \"name\": \"for-network\","
" \"test\": \"member('all')\","
" \"eval-on-demand\": true,"
" \"option-data\": [ {"
" \"name\": \"domain-name-servers\","
" \"data\": \"10.0.0.3\""
" } ]"
" }"
"],"
"\"shared-networks\": [ {"
"