]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5374] Checkpoint: nearly finished: still some cleanups
authorFrancis Dupont <fdupont@isc.org>
Mon, 27 Nov 2017 18:12:28 +0000 (19:12 +0100)
committerFrancis Dupont <fdupont@isc.org>
Mon, 27 Nov 2017 18:12:28 +0000 (19:12 +0100)
doc/guide/classify.xml
doc/guide/dhcp4-srv.xml
doc/guide/dhcp6-srv.xml
src/bin/dhcp4/tests/classify_unittest.cc
src/bin/dhcp4/tests/shared_network_unittest.cc
src/bin/dhcp6/tests/classify_unittests.cc
src/bin/dhcp6/tests/shared_network_unittest.cc

index 49fd96d01fc9f3336ced5236294dd390f1309dc8..29765165f1335c22dbc6632315df40bd5e148bb1 100644 (file)
@@ -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.
       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 &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
index 34f2fd9e81fe8131be60a466d135f58607572032..6a55a915636bed9b4426ff3800f6889f0fef9fe3 100644 (file)
@@ -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"
index 1ac0cf5aacd55d987a02586cd12c398318026b86..d14b177db2a14e184a7fbd6bd065457dae29f1e4 100644 (file)
@@ -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"
index 3f48b2013cb24f9c6a937b89f1b97128579c39e5..18a223093743a2397169797590ca427f8ff210fe 100644 (file)
@@ -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\": [ {"
+        "    \"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
index fc7d00452ab3ed6bc6468d811946b4e8d79b5073..2c92453ab851c66b98e842cc4cb9a4ed6720dc90 100644 (file)
@@ -2203,6 +2203,99 @@ TEST_F(Dhcpv4SharedNetworkTest, precedenceClass) {
     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) {
index 99058ac014f6cbe4f6e703ab69de1a517fb669f9..a2b16ee50d6eb9aac6be251b39eed0b127b1ed2c 100644 (file)
@@ -302,6 +302,189 @@ TEST_F(ClassifyTest, matchClassification) {
     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);
@@ -1125,4 +1308,334 @@ TEST_F(ClassifyTest, member) {
     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
index bca8a1e08f5e565ded514e31752ef35cd6070d8f..1f12e76e9f874e6d829db18afa01686dad4208b9 100644 (file)
@@ -9,6 +9,7 @@
 #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>
@@ -2432,4 +2433,609 @@ TEST_F(Dhcpv6SharedNetworkTest, poolInSubnetSelectedByClass) {
     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