</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.
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.
is "true". 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
<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>
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 "Client_foo" is defined.
It is comprised of all clients whose client ids (option 61) start with the
an old PXEClient vendor:
<screen>
"Dhcp4": {
- "client-class": [
+ "client-classes": [
{
<userinput>"name": "pxeclient",
"test": "option[vendor-class-identifier].text == 'PXEClient'",
},</userinput>
...
],
- "client-class": [
+ "client-classes": [
{
<userinput>"name": "APC",
"test": "(option[vendor-class-identifier].text == 'APC'",
</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>
<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>
}</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">
<screen>
"shared-networks": [
- {"
+ {
"name": "kakapo",
<userinput>"relay": {
"ip-address": "192.3.5.6"
</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>
</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">
<screen>
"shared-networks": [
- {"
+ {
"name": "kakapo",
<userinput>"relay": {
"ip-address": "2001:db8::abcd"
/// 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\": {"
" \"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.
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\": [ {"
+ " \"name\": \"frog\","
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"eval-client-classes\": [ \"for-subnet\" ],"
+ " \"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.2", addrs[0].toText());
+}
+
+// This test checks the precedence order in requested late evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNetwork) {
+ 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\","
+ " \"eval-client-classes\": [ \"for-network\" ],"
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"eval-client-classes\": [ \"for-subnet\" ],"
+ " \"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.3", addrs[0].toText());
+}
+
} // end of anonymous namespace
EXPECT_EQ("192.0.2.2", addrs[0].toText());
}
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceClasses) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"beta\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set MAC address to the one that has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Request domain-name-servers
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform a DORA
+ doDORA(client, "192.0.2.28", "192.0.2.28");
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ(DHCPACK, resp->getType());
+ EXPECT_EQ("192.0.2.28", 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());
+ // Class order is the insert order
+ EXPECT_EQ("192.0.2.2", addrs[0].toText());
+}
+
// Verify option processing precedence
// Order is global < class < shared-network < subnet < pool < host reservation
TEST_F(Dhcpv4SharedNetworkTest, precedenceNetwork) {
EXPECT_FALSE(opt3);
}
+// Check that on-demand classes are not evaluated by classifyPacket
+TEST_F(ClassifyTest, onDemand) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"eval-on-demand\": true, "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[host-name].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to the first 2 queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // No packet is in the router class
+ EXPECT_FALSE(query1->inClass("router"));
+ EXPECT_FALSE(query2->inClass("router"));
+ EXPECT_FALSE(query3->inClass("router"));
+
+ // Process queries
+ Pkt6Ptr response1 = srv.processSolicit(query1);
+ Pkt6Ptr response2 = srv.processSolicit(query2);
+ Pkt6Ptr response3 = srv.processSolicit(query3);
+
+ // Classification processing should do nothing
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_FALSE(opt1);
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_FALSE(opt3);
+}
+
+// Checks that when requested on-demand classes are still (but late) evaluated
+TEST_F(ClassifyTest, lateClassification) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"eval-client-classes\": [ \"router\" ], "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"eval-on-demand\": true, "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[host-name].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to the first 2 queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // No packet is in the router class yet
+ EXPECT_FALSE(query1->inClass("router"));
+ EXPECT_FALSE(query2->inClass("router"));
+ EXPECT_FALSE(query3->inClass("router"));
+
+ // Process queries
+ Pkt6Ptr response1 = srv.processSolicit(query1);
+ Pkt6Ptr response2 = srv.processSolicit(query2);
+ Pkt6Ptr response3 = srv.processSolicit(query3);
+
+ // Classification processing should add an ip-forwarding option
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_TRUE(opt1);
+
+ // But only for the first query: second was not classified
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+
+ // But only for the first query: third has no ORO
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_FALSE(opt3);
+}
+
// Checks subnet options have the priority over class options
TEST_F(ClassifyTest, subnetClassPriority) {
IfaceMgrTestConfig test_config(true);
EXPECT_FALSE(ipf3->readBoolean());
}
+// 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\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_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\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"eval-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::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\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"eval-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"eval-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
+}
+
+// This test checks the precedence order in requested late evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNetwork) {
+ 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\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"eval-on-demand\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"eval-client-classes\": [ \"for-network\" ],"
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"eval-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"eval-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::3", addrs[0].toText());
+}
+
} // end of anonymous namespace
#include <dhcp/option.h>
#include <dhcp/option_int.h>
#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_addrlst.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfg_subnets6.h>
#include <dhcpsrv/cfgmgr.h>
EXPECT_EQ(1, client2.getLeasesWithNonZeroLifetime().size());
}
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceGlobal) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform SARR
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::1", addrs[0].toText());
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceClass) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform SARR
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceClasses) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"beta\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform SARR
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ // Class order is the insert order
+ EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceNetworkClass) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform SARR
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::3", addrs[0].toText());
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceSubnet) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform SARR
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::4", addrs[0].toText());
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedencePool) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform SARR
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::5", addrs[0].toText());
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceReservation) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ],"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::6\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration
+ configure(config, *client.getServer());
+
+ // Perform SARR
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::6", addrs[0].toText());
+}
+
} // end of anonymous namespace