Browse Source

[master] Finished merge of trac5226 (options with record and array)

Francis Dupont 7 years ago
parent
commit
f66951dbda

+ 8 - 0
ChangeLog

@@ -1,3 +1,11 @@
+1307.	[func]		fdupont
+	When an option is defined with a record type and the array
+	flag is true the last record field is an array. All standard
+	options supported by ISC DHCP and using this are now supported
+	by Kea (so now there is no standard option suported only by
+	ISC DHCP).
+	(Trac #5226, git xxx)
+
 1306.	[bug]*		marcin
 	Respective Kea daemons now use their own default configuration
 	files: kea-dhcp4.conf, kea-dhcp6.conf, kea-dhcp-ddns.conf and

File diff suppressed because it is too large
+ 28 - 1
doc/guide/dhcp4-srv.xml


+ 26 - 0
doc/guide/dhcp6-srv.xml

@@ -1201,6 +1201,7 @@ temporarily override a list of interface names and listen on all interfaces.
     </para>
 
 <!-- @todo: describe record types -->
+<!-- @todo: describe array in record types -->
 
       <para>
         The <xref linkend="dhcp6-custom-options"/> describes the configuration
@@ -1297,6 +1298,7 @@ temporarily override a list of interface names and listen on all interfaces.
 <row><entry>erp-local-domain-name</entry><entry>65</entry><entry>fqdn</entry><entry>false</entry></row>
 <row><entry>rsoo</entry><entry>66</entry><entry>empty</entry><entry>false</entry></row>
 <row><entry>pd-exclude</entry><entry>67</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>rdnss-selection</entry><entry>74</entry><entry>record (ipv6-address, uint8, fqdn)</entry><entry>true</entry></row>
 <row><entry>client-linklayer-addr</entry><entry>79</entry><entry>binary</entry><entry>false</entry></row>
 <row><entry>link-address</entry><entry>80</entry><entry>ipv6-address</entry><entry>false</entry></row>
 <row><entry>solmax-rt</entry><entry>82</entry><entry>uint32</entry><entry>false</entry></row>
@@ -1446,6 +1448,30 @@ temporarily override a list of interface names and listen on all interfaces.
       the "record-types" field of the option definition.
       </para>
 
+      <para>
+      When <command>array</command> is set to <command>true</command>
+      and <command>type</command> is set to "record", the last field
+      is an array, i.e., it can contain more than one value as in:
+<screen>
+"Dhcp6": {
+    "option-def": [
+        {
+            <userinput>"name": "bar",
+            "code": 101,
+            "space": "dhcp6",
+            "type": "record",
+            "array": true,
+            "record-types": "ipv6-address, uint16",
+            "encapsulate": ""</userinput>
+        }, ...
+    ],
+    ...
+}
+</screen>
+      The new option content is one IPv6 address followed by one or more 16
+      bit unsigned integers.
+      </para>
+
       <note>
        <para>In the general case, boolean values are specified as <command>true</command> or
        <command>false</command>, without quotes. Some specific boolean parameters may

+ 60 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -3175,6 +3175,66 @@ TEST_F(Dhcp4ParserTest, domainSearchOption) {
                                      " domain-search option"));
 }
 
+// The goal of this test is to verify that the slp-directory-agent
+// option can be set using a trailing array of addresses and
+// slp-service-scope without option scope list
+TEST_F(Dhcp4ParserTest, slpOptions) {
+    ConstElementPtr x;
+    string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"slp-directory-agent\","
+        "    \"data\": \"true, 10.0.0.3, 127.0.0.1\""
+        " },"
+        " {"
+        "    \"name\": \"slp-service-scope\","
+        "    \"data\": \"false, \""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
+    extractConfig(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 0);
+
+    // Get options
+    OptionContainerPtr options = CfgMgr::instance().getStagingCfg()->
+        getCfgOption()->getAll(DHCP4_OPTION_SPACE);
+    ASSERT_EQ(2, options->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const OptionContainerTypeIndex& idx = options->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(DHO_DIRECTORY_AGENT);
+    // Expect a single option with the code equal to 78.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t sda_expected[] = {
+        0x01, 0x0a, 0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x01
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 78, sda_expected, sizeof(sda_expected));
+
+    range = idx.equal_range(DHO_SERVICE_SCOPE);
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // Do another round of testing with second option.
+    const uint8_t sss_expected[] = {
+        0x00
+    };
+    testOption(*range.first, 79, sss_expected, sizeof(sss_expected));
+}
+
 // The goal of this test is to verify that the standard option can
 // be configured to encapsulate multiple other options.
 TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {

+ 166 - 50
src/bin/dhcp4/tests/get_config_unittest.cc

@@ -885,6 +885,35 @@ const char* EXTRACTED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"data\": \"true, 10.0.0.3, 127.0.0.1\",\n"
+"                \"name\": \"slp-directory-agent\"\n"
+"            },\n"
+"            {\n"
+"                \"data\": \"false, \",\n"
+"                \"name\": \"slp-service-scope\"\n"
+"            }\n"
+"        ],\n"
+"        \"rebind-timer\": 2000,\n"
+"        \"renew-timer\": 1000,\n"
+"        \"subnet4\": [\n"
+"            {\n"
+"                \"pools\": [\n"
+"                    {\n"
+"                        \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+"                    }\n"
+"                ],\n"
+"                \"subnet\": \"192.0.2.0/24\"\n"
+"            }\n"
+"        ],\n"
+"        \"valid-lifetime\": 4000\n"
+"    }\n",
+    // CONFIGURATION 33
+"{\n"
+"        \"interfaces-config\": {\n"
+"            \"interfaces\": [ \"*\" ]\n"
+"        },\n"
+"        \"option-data\": [\n"
+"            {\n"
 "                \"data\": \"1234\",\n"
 "                \"name\": \"foo\",\n"
 "                \"space\": \"vendor-encapsulated-options-space\"\n"
@@ -913,7 +942,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        \"renew-timer\": 1000,\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 33
+    // CONFIGURATION 34
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -964,7 +993,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 3000\n"
 "    }\n",
-    // CONFIGURATION 34
+    // CONFIGURATION 35
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -999,7 +1028,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 35
+    // CONFIGURATION 36
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1034,7 +1063,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 36
+    // CONFIGURATION 37
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"eth0\", \"eth1\" ]\n"
@@ -1043,7 +1072,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        \"renew-timer\": 1000,\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 37
+    // CONFIGURATION 38
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"eth0\", \"*\", \"eth1\" ]\n"
@@ -1052,7 +1081,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        \"renew-timer\": 1000,\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 38
+    // CONFIGURATION 39
 "{\n"
 "        \"dhcp-ddns\": {\n"
 "            \"always-include-fqdn\": true,\n"
@@ -1087,7 +1116,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 39
+    // CONFIGURATION 40
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1112,7 +1141,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 40
+    // CONFIGURATION 41
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1210,7 +1239,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 41
+    // CONFIGURATION 42
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1251,7 +1280,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 42
+    // CONFIGURATION 43
 "{\n"
 "        \"rebind-timer\": 2000,\n"
 "        \"renew-timer\": 1000,\n"
@@ -1294,21 +1323,21 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 43
+    // CONFIGURATION 44
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
 "        },\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 44
+    // CONFIGURATION 45
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
 "        },\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 45
+    // CONFIGURATION 46
 "{\n"
 "        \"decline-probation-period\": 12345,\n"
 "        \"interfaces-config\": {\n"
@@ -1316,7 +1345,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        },\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 46
+    // CONFIGURATION 47
 "{\n"
 "        \"expired-leases-processing\": {\n"
 "            \"flush-reclaimed-timer-wait-time\": 35,\n"
@@ -1331,7 +1360,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        },\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 47
+    // CONFIGURATION 48
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1350,7 +1379,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 48
+    // CONFIGURATION 49
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1370,7 +1399,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 49
+    // CONFIGURATION 50
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1390,7 +1419,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 50
+    // CONFIGURATION 51
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1411,7 +1440,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 51
+    // CONFIGURATION 52
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1431,7 +1460,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 52
+    // CONFIGURATION 53
 "{\n"
 "        \"client-classes\": [\n"
 "            {\n"
@@ -1461,7 +1490,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 53
+    // CONFIGURATION 54
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1480,7 +1509,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 54
+    // CONFIGURATION 55
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1500,7 +1529,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 55
+    // CONFIGURATION 56
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1524,7 +1553,7 @@ const char* EXTRACTED_CONFIGS[] = {
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
 "    }\n",
-    // CONFIGURATION 56
+    // CONFIGURATION 57
 "{\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
@@ -1547,7 +1576,8 @@ const char* EXTRACTED_CONFIGS[] = {
 "            }\n"
 "        ],\n"
 "        \"valid-lifetime\": 4000\n"
-"    }\n"};
+"    }\n"
+};
 
 /// @brief unparsed configurations
 const char* UNPARSED_CONFIGS[] = {
@@ -4103,6 +4133,91 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"option-data\": [\n"
 "            {\n"
 "                \"always-send\": false,\n"
+"                \"code\": 78,\n"
+"                \"csv-format\": true,\n"
+"                \"data\": \"true, 10.0.0.3, 127.0.0.1\",\n"
+"                \"name\": \"slp-directory-agent\",\n"
+"                \"space\": \"dhcp4\"\n"
+"            },\n"
+"            {\n"
+"                \"always-send\": false,\n"
+"                \"code\": 79,\n"
+"                \"csv-format\": true,\n"
+"                \"data\": \"false, \",\n"
+"                \"name\": \"slp-service-scope\",\n"
+"                \"space\": \"dhcp4\"\n"
+"            }\n"
+"        ],\n"
+"        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
+"        \"subnet4\": [\n"
+"            {\n"
+"                \"4o6-interface\": \"\",\n"
+"                \"4o6-interface-id\": \"\",\n"
+"                \"4o6-subnet\": \"\",\n"
+"                \"id\": 1,\n"
+"                \"match-client-id\": true,\n"
+"                \"next-server\": \"0.0.0.0\",\n"
+"                \"option-data\": [ ],\n"
+"                \"pools\": [\n"
+"                    {\n"
+"                        \"option-data\": [ ],\n"
+"                        \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+"                    }\n"
+"                ],\n"
+"                \"rebind-timer\": 2000,\n"
+"                \"relay\": {\n"
+"                    \"ip-address\": \"0.0.0.0\"\n"
+"                },\n"
+"                \"renew-timer\": 1000,\n"
+"                \"reservation-mode\": \"all\",\n"
+"                \"reservations\": [ ],\n"
+"                \"subnet\": \"192.0.2.0/24\",\n"
+"                \"valid-lifetime\": 4000\n"
+"            }\n"
+"        ]\n"
+"    }\n",
+    // CONFIGURATION 33
+"{\n"
+"        \"decline-probation-period\": 86400,\n"
+"        \"dhcp-ddns\": {\n"
+"            \"always-include-fqdn\": false,\n"
+"            \"enable-updates\": false,\n"
+"            \"generated-prefix\": \"myhost\",\n"
+"            \"max-queue-size\": 1024,\n"
+"            \"ncr-format\": \"JSON\",\n"
+"            \"ncr-protocol\": \"UDP\",\n"
+"            \"override-client-update\": false,\n"
+"            \"override-no-update\": false,\n"
+"            \"qualifying-suffix\": \"\",\n"
+"            \"replace-client-name\": \"never\",\n"
+"            \"sender-ip\": \"0.0.0.0\",\n"
+"            \"sender-port\": 0,\n"
+"            \"server-ip\": \"127.0.0.1\",\n"
+"            \"server-port\": 53001\n"
+"        },\n"
+"        \"dhcp4o6-port\": 0,\n"
+"        \"echo-client-id\": true,\n"
+"        \"expired-leases-processing\": {\n"
+"            \"flush-reclaimed-timer-wait-time\": 25,\n"
+"            \"hold-reclaimed-time\": 3600,\n"
+"            \"max-reclaim-leases\": 100,\n"
+"            \"max-reclaim-time\": 250,\n"
+"            \"reclaim-timer-wait-time\": 10,\n"
+"            \"unwarned-reclaim-cycles\": 5\n"
+"        },\n"
+"        \"hooks-libraries\": [ ],\n"
+"        \"host-reservation-identifiers\": [ \"hw-address\", \"duid\", \"circuit-id\", \"client-id\" ],\n"
+"        \"interfaces-config\": {\n"
+"            \"interfaces\": [ \"*\" ],\n"
+"            \"re-detect\": false\n"
+"        },\n"
+"        \"lease-database\": {\n"
+"            \"type\": \"memfile\"\n"
+"        },\n"
+"        \"option-data\": [\n"
+"            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -4141,7 +4256,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 33
+    // CONFIGURATION 34
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4253,7 +4368,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 34
+    // CONFIGURATION 35
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4336,7 +4451,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 35
+    // CONFIGURATION 36
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4423,7 +4538,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 36
+    // CONFIGURATION 37
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4466,7 +4581,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 37
+    // CONFIGURATION 38
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4509,7 +4624,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 38
+    // CONFIGURATION 39
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4577,7 +4692,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 39
+    // CONFIGURATION 40
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4645,7 +4760,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 40
+    // CONFIGURATION 41
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4864,7 +4979,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 41
+    // CONFIGURATION 42
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -4962,7 +5077,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 42
+    // CONFIGURATION 43
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5102,7 +5217,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 43
+    // CONFIGURATION 44
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5145,7 +5260,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 44
+    // CONFIGURATION 45
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5188,7 +5303,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 45
+    // CONFIGURATION 46
 "{\n"
 "        \"decline-probation-period\": 12345,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5231,7 +5346,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 46
+    // CONFIGURATION 47
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5274,7 +5389,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
-    // CONFIGURATION 47
+    // CONFIGURATION 48
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5342,7 +5457,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 48
+    // CONFIGURATION 49
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5410,7 +5525,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 49
+    // CONFIGURATION 50
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5478,7 +5593,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 50
+    // CONFIGURATION 51
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5546,7 +5661,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 51
+    // CONFIGURATION 52
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5614,7 +5729,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 52
+    // CONFIGURATION 53
 "{\n"
 "        \"client-classes\": [\n"
 "            {\n"
@@ -5708,7 +5823,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 53
+    // CONFIGURATION 54
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5776,7 +5891,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 54
+    // CONFIGURATION 55
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5845,7 +5960,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 55
+    // CONFIGURATION 56
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5918,7 +6033,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ]\n"
 "    }\n",
-    // CONFIGURATION 56
+    // CONFIGURATION 57
 "{\n"
 "        \"decline-probation-period\": 86400,\n"
 "        \"dhcp-ddns\": {\n"
@@ -5990,7 +6105,8 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
 "        ]\n"
-"    }\n"};
+"    }\n"
+};
 
 /// @brief the number of configurations
 const size_t max_config_counter = sizeof(EXTRACTED_CONFIGS) / sizeof(char*);

+ 59 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -3474,6 +3474,65 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     EXPECT_EQ(1516, optionIA->getT2());
 }
 
+// Verify that specific option object is returned for standard
+// option with trailing domain list.
+TEST_F(Dhcp6ParserTest, rdnssOption) {
+    ConstElementPtr x;
+    std::map<std::string, std::string> params;
+    params["name"] = "rdnss-selection";
+    params["space"] = DHCP6_OPTION_SPACE;
+    // Option code 74 is D6O_RDNSS_SELECTION
+    params["code"] = "74";
+    params["data"] = "2001::1, 3, isc.org, example.org, example.com";
+    params["csv-format"] = "true";
+
+    std::string config = createConfigWithOption(params);
+    ConstElementPtr json = parseDHCP6(config, true);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 0);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+    ASSERT_TRUE(subnet);
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+    ASSERT_EQ(1, options->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const OptionContainerTypeIndex& idx = options->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<OptionContainerTypeIndex::const_iterator,
+              OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(D6O_RDNSS_SELECTION);
+    // Expect single option with the code equal to rndnss-selection option code.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // The actual pointer to the option is held in the option field
+    // in the structure returned.
+    OptionPtr option = range.first->option_;
+    ASSERT_TRUE(option);
+    // Option object returned for here is expected to be OptionCustom
+    // which is derived from Option. This class is dedicated to
+    // represent standard option D6O_RDNSS_SELECTION.
+    boost::shared_ptr<OptionCustom> optionCustom =
+        boost::dynamic_pointer_cast<OptionCustom>(option);
+    // If cast is unsuccessful than option returned was of a
+    // different type than optionCustom. This is wrong.
+    ASSERT_TRUE(optionCustom);
+    // If cast was successful we may use accessors exposed by
+    // optionCustom to validate that the content of this option
+    // has been set correctly.
+    ASSERT_EQ(5, optionCustom->getDataFieldsNum());
+    EXPECT_EQ("2001::1", optionCustom->readAddress(0).toText());
+    EXPECT_EQ(3, optionCustom->readInteger<uint8_t>(1));
+    EXPECT_EQ("isc.org.", optionCustom->readFqdn(2));
+    EXPECT_EQ("example.org.", optionCustom->readFqdn(3));
+    EXPECT_EQ("example.com.", optionCustom->readFqdn(4));
+}
+
+
 // This test checks if vendor options can be specified in the config file
 // (in hex format), and later retrieved from configured subnet
 TEST_F(Dhcp6ParserTest, vendorOptionsHex) {

+ 3 - 3
src/lib/dhcp/dhcp4.h

@@ -144,7 +144,7 @@ enum DHCPOptionType {
     DHO_STREETTALK_SERVER            = 75,
     DHO_STDASERVER                   = 76,
     DHO_USER_CLASS                   = 77,
-//  DHO_DIRECTORY_AGENT              = 78,
+    DHO_DIRECTORY_AGENT              = 78,
     DHO_SERVICE_SCOPE                = 79,
 //  DHO_RAPID_COMMIT                 = 80,
     DHO_FQDN                         = 81,
@@ -195,7 +195,7 @@ enum DHCPOptionType {
     // 143 is removed/unassigned
 //  DHO_GEOLOC                       = 144,
 //  DHO_FORCERENEW_NONCE_CAPABLE     = 145,
-//  DHO_RDNSS_SELECT                 = 146,
+    DHO_RDNSS_SELECT                 = 146,
     // 147-149 are removed/unassigned
     // 150 have multiple definitions
 //  DHO_STATUS_CODE                  = 151,
@@ -211,7 +211,7 @@ enum DHCPOptionType {
     // 161-209 are removed/unassigned
 //  DHO_PATH_PREFIX                  = 210,
 //  DHO_REBOOT_TIME                  = 211,    
-//  DHO_6RD                          = 212,
+    DHO_6RD                          = 212,
     DHO_V4_ACCESS_DOMAIN             = 213,
     // 214-219 are removed/unassigned
 //  DHO_SUBNET_ALLOC                 = 220,

+ 1 - 1
src/lib/dhcp/dhcp6.h

@@ -91,7 +91,7 @@ enum DHCPv6OptionType {
 // D6O_MIP6_HNP                            = 71, /* RFC6610 */
 // D6O_MIP6_HAA                            = 72, /* RFC6610 */
 // D6O_MIP6_HAF                            = 73, /* RFC6610 */
-// D6O_RDNSS_SELECTION                     = 74, /* RFC6731 */
+   D6O_RDNSS_SELECTION                     = 74, /* RFC6731 */
 // D6O_KRB_PRINCIPAL_NAME                  = 75, /* RFC6784 */
 // D6O_KRB_REALM_NAME                      = 76, /* RFC6784 */
 // D6O_KRB_DEFAULT_REALM_NAME              = 77, /* RFC6784 */

+ 125 - 159
src/lib/dhcp/option_custom.cc

@@ -132,6 +132,35 @@ OptionCustom::checkIndex(const uint32_t index) const {
 }
 
 void
+OptionCustom::createBuffer(OptionBuffer& buffer,
+                           const OptionDataType data_type) const {
+    // For data types that have a fixed size we can use the
+    // utility function to get the buffer's size.
+    size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+
+    // For variable data sizes the utility function returns zero.
+    // It is ok for string values because the default string
+    // is 'empty'. However for FQDN the empty value is not valid
+    // so we initialize it to '.'. For prefix there is a prefix
+    // length fixed field.
+    if (data_size == 0) {
+        if (data_type == OPT_FQDN_TYPE) {
+            OptionDataTypeUtil::writeFqdn(".", buffer);
+
+        } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+            OptionDataTypeUtil::writePrefix(PrefixLen(0),
+                                            IOAddress::IPV6_ZERO_ADDRESS(),
+                                            buffer);
+        }
+    } else {
+        // At this point we can resize the buffer. Note that
+        // for string values we are setting the empty buffer
+        // here.
+        buffer.resize(data_size);
+    }
+}
+
+void
 OptionCustom::createBuffers() {
     definition_.validate();
 
@@ -154,31 +183,7 @@ OptionCustom::createBuffers() {
         for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
              field != fields.end(); ++field) {
             OptionBuffer buf;
-
-            // For data types that have a fixed size we can use the
-            // utility function to get the buffer's size.
-            size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
-
-            // For variable data sizes the utility function returns zero.
-            // It is ok for string values because the default string
-            // is 'empty'. However for FQDN the empty value is not valid
-            // so we initialize it to '.'. For prefix there is a prefix
-            // length fixed field.
-            if (data_size == 0) {
-                if (*field == OPT_FQDN_TYPE) {
-                    OptionDataTypeUtil::writeFqdn(".", buf);
-
-                } else if (*field == OPT_IPV6_PREFIX_TYPE) {
-                    OptionDataTypeUtil::writePrefix(PrefixLen(0),
-                                                    IOAddress::IPV6_ZERO_ADDRESS(),
-                                                    buf);
-                }
-            } else {
-                // At this point we can resize the buffer. Note that
-                // for string values we are setting the empty buffer
-                // here.
-                buf.resize(data_size);
-            }
+            createBuffer(buf, *field);
             // We have the buffer with default value prepared so we
             // add it to the set of buffers.
             buffers.push_back(buf);
@@ -193,21 +198,7 @@ OptionCustom::createBuffers() {
         // For non-arrays we have a single value being held by the option
         // so we have to allocate exactly one buffer.
         OptionBuffer buf;
-        size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
-        if (data_size == 0) {
-            if (data_type == OPT_FQDN_TYPE) {
-                OptionDataTypeUtil::writeFqdn(".", buf);
-
-            } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
-                OptionDataTypeUtil::writePrefix(PrefixLen(0),
-                                                IOAddress::IPV6_ZERO_ADDRESS(),
-                                                buf);
-            }
-        } else {
-            // Note that if our option holds a string value then
-            // we are making empty buffer here.
-            buf.resize(data_size);
-        }
+        createBuffer(buf, data_type);
         // Add a buffer that we have created and leave.
         buffers.push_back(buf);
     }
@@ -217,6 +208,73 @@ OptionCustom::createBuffers() {
     std::swap(buffers, buffers_);
 }
 
+size_t
+OptionCustom::bufferLength(const OptionDataType data_type, bool in_array,
+                           OptionBuffer::const_iterator begin,
+                           OptionBuffer::const_iterator end) const {
+    // For fixed-size data type such as boolean, integer, even
+    // IP address we can use the utility function to get the required
+    // buffer size.
+    size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+
+    // For variable size types (e.g. string) the function above will
+    // return 0 so we need to do a runtime check of the length.
+    if (data_size == 0) {
+        // FQDN is a special data type as it stores variable length data
+        // but the data length is encoded in the buffer. The easiest way
+        // to obtain the length of the data is to read the FQDN. The
+        // utility function will return the size of the buffer on success.
+        if (data_type == OPT_FQDN_TYPE) {
+            std::string fqdn =
+                OptionDataTypeUtil::readFqdn(OptionBuffer(begin, end));
+            // The size of the buffer holding an FQDN is always
+            // 1 byte larger than the size of the string
+            // representation of this FQDN.
+            data_size = fqdn.size() + 1;
+        } else if (!definition_.getArrayType() &&
+                   ((data_type == OPT_BINARY_TYPE) ||
+                    (data_type == OPT_STRING_TYPE))) {
+            // In other case we are dealing with string or binary value
+            // which size can't be determined. Thus we consume the
+            // remaining part of the buffer for it. Note that variable
+            // size data can be laid at the end of the option only and
+            // that the validate() function in OptionDefinition object
+            // should have checked wheter it is a case for this option.
+            data_size = std::distance(begin, end);
+        } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+            // The size of the IPV6 prefix type is determined as
+            // one byte (which is the size of the prefix in bits)
+            // followed by the prefix bits (right-padded with
+            // zeros to the nearest octet boundary)
+            if ((begin == end) && !in_array)
+                return 0;
+            PrefixTuple prefix =
+                OptionDataTypeUtil::readPrefix(OptionBuffer(begin, end));
+            // Data size comprises 1 byte holding a prefix length and the
+            // prefix length (in bytes) rounded to the nearest byte boundary.
+            data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
+        } else if (data_type == OPT_TUPLE_TYPE) {
+            OpaqueDataTuple::LengthFieldType lft =
+                getUniverse() == Option::V4 ?
+                OpaqueDataTuple::LENGTH_1_BYTE :
+                OpaqueDataTuple::LENGTH_2_BYTES;
+            std::string value =
+                OptionDataTypeUtil::readTuple(OptionBuffer(begin, end), lft);
+            data_size = value.size();
+            // The size of the buffer holding a tuple is always
+            // 1 or 2 byte larger than the size of the string
+            data_size += getUniverse() == Option::V4 ? 1 : 2;
+        } else {
+            // If we reached the end of buffer we assume that this option is
+            // truncated because there is no remaining data to initialize
+            // an option field.
+            isc_throw(OutOfRange, "option buffer truncated");
+        }
+    }
+
+    return data_size;
+}
+
 void
 OptionCustom::createBuffers(const OptionBuffer& data_buf) {
     // Check that the option definition is correct as we are going
@@ -237,60 +295,8 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
         // Go over all data fields within a record.
         for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
              field != fields.end(); ++field) {
-            // For fixed-size data type such as boolean, integer, even
-            // IP address we can use the utility function to get the required
-            // buffer size.
-            size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
-
-            // For variable size types (e.g. string) the function above will
-            // return 0 so we need to do a runtime check of the length.
-            if (data_size == 0) {
-                // FQDN is a special data type as it stores variable length data
-                // but the data length is encoded in the buffer. The easiest way
-                // to obtain the length of the data is to read the FQDN. The
-                // utility function will return the size of the buffer on success.
-                if (*field == OPT_FQDN_TYPE) {
-                    std::string fqdn =
-                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
-                    // The size of the buffer holding an FQDN is always
-                    // 1 byte larger than the size of the string
-                    // representation of this FQDN.
-                    data_size = fqdn.size() + 1;
-                } else if ((*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE)) {
-                    // In other case we are dealing with string or binary value
-                    // which size can't be determined. Thus we consume the
-                    // remaining part of the buffer for it. Note that variable
-                    // size data can be laid at the end of the option only and
-                    // that the validate() function in OptionDefinition object
-                    // should have checked whether it is a case for this option.
-                    data_size = std::distance(data, data_buf.end());
-                } else if (*field == OPT_IPV6_PREFIX_TYPE) {
-                    // The size of the IPV6 prefix type is determined as
-                    // one byte (which is the size of the prefix in bits)
-                    // followed by the prefix bits (right-padded with
-                    // zeros to the nearest octet boundary).
-                    if (std::distance(data, data_buf.end()) > 0) {
-                        data_size = static_cast<size_t>(sizeof(uint8_t) + (*data + 7) / 8);
-                    }
-                } else if (*field == OPT_TUPLE_TYPE) {
-                    OpaqueDataTuple::LengthFieldType lft =
-                        getUniverse() == Option::V4 ?
-                        OpaqueDataTuple::LENGTH_1_BYTE :
-                        OpaqueDataTuple::LENGTH_2_BYTES;
-                    std::string value =
-                        OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
-                                                      lft);
-                    data_size = value.size();
-                    // The size of the buffer holding a tuple is always
-                    // 1 or 2 byte larger than the size of the string
-                    data_size += getUniverse() == Option::V4 ? 1 : 2;
-                } else {
-                    // If we reached the end of buffer we assume that this option is
-                    // truncated because there is no remaining data to initialize
-                    // an option field.
-                    isc_throw(OutOfRange, "option buffer truncated");
-                }
-            }
+            size_t data_size = bufferLength(*field, false,
+                                            data, data_buf.end());
 
             // Our data field requires that there is a certain chunk of
             // data left in the buffer. If not, option is truncated.
@@ -304,8 +310,23 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
             data += data_size;
         }
 
+        // Get extra buffers when the last field is an array.
+        if (definition_.getArrayType()) {
+            while (data != data_buf.end()) {
+                // Code copied from the standard array case
+                size_t data_size = bufferLength(fields.back(), true,
+                                                data, data_buf.end());
+                assert(data_size > 0);
+                if (std::distance(data, data_buf.end()) < data_size) {
+                    break;
+                }
+                buffers.push_back(OptionBuffer(data, data + data_size));
+                data += data_size;
+            }
+        }
+
         // Unpack suboptions if any.
-        if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
+        else if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
             unpackOptions(OptionBuffer(data, data_buf.end()));
         }
 
@@ -328,38 +349,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
         // we have to handle multiple buffers.
         if (definition_.getArrayType()) {
             while (data != data_buf.end()) {
-                // FQDN is a special case because it is of a variable length.
-                // The actual length for a particular FQDN is encoded within
-                // a buffer so we have to actually read the FQDN from a buffer
-                // to get it.
-                if (data_type == OPT_FQDN_TYPE) {
-                    std::string fqdn =
-                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
-                    // The size of the buffer holding an FQDN is always
-                    // 1 byte larger than the size of the string
-                    // representation of this FQDN.
-                    data_size = fqdn.size() + 1;
-
-                } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
-                    PrefixTuple prefix =
-                        OptionDataTypeUtil::readPrefix(OptionBuffer(data, data_buf.end()));
-                    // Data size comprises 1 byte holding a prefix length and the
-                    // prefix length (in bytes) rounded to the nearest byte boundary.
-                    data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
-                } else if (data_type == OPT_TUPLE_TYPE) {
-                    OpaqueDataTuple::LengthFieldType lft =
-                        getUniverse() == Option::V4 ?
-                        OpaqueDataTuple::LENGTH_1_BYTE :
-                        OpaqueDataTuple::LENGTH_2_BYTES;
-                    std::string value =
-                        OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
-                                                      lft);
-                    data_size = value.size();
-                    // The size of the buffer holding a tuple is always
-                    // 1 or 2 byte larger than the size of the string
-                    data_size += getUniverse() == Option::V4 ? 1 : 2;
-
-                }
+                data_size = bufferLength(data_type, true, data, data_buf.end());
                 // We don't perform other checks for data types that can't be
                 // used together with array indicator such as strings, empty field
                 // etc. This is because OptionDefinition::validate function should
@@ -381,38 +371,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
             // For non-arrays the data_size can be zero because
             // getDataTypeLen returns zero for variable size data types
             // such as strings. Simply take whole buffer.
-            if (data_size == 0) {
-                // For FQDN we get the size by actually reading the FQDN.
-                if (data_type == OPT_FQDN_TYPE) {
-                    std::string fqdn =
-                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
-                    // The size of the buffer holding an FQDN is always
-                    // 1 bytes larger than the size of the string
-                    // representation of this FQDN.
-                    data_size = fqdn.size() + 1;
-
-                } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
-                    if (!data_buf.empty()) {
-                        data_size = static_cast<size_t>
-                            (sizeof(uint8_t) + (data_buf[0] + 7) / 8);
-                    }
-                } else if (data_type == OPT_TUPLE_TYPE) {
-                    OpaqueDataTuple::LengthFieldType lft =
-                        getUniverse() == Option::V4 ?
-                        OpaqueDataTuple::LENGTH_1_BYTE :
-                        OpaqueDataTuple::LENGTH_2_BYTES;
-                    std::string value =
-                        OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
-                                                      lft);
-                    data_size = value.size();
-                    // The size of the buffer holding a tuple is always
-                    // 1 or 2 byte larger than the size of the string
-                    data_size += getUniverse() == Option::V4 ? 1 : 2;
-
-                } else {
-                    data_size = std::distance(data, data_buf.end());
-                }
-            }
+            data_size = bufferLength(data_type, false, data, data_buf.end());
             if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) {
                 buffers.push_back(OptionBuffer(data, data + data_size));
                 data += data_size;
@@ -744,6 +703,13 @@ std::string OptionCustom::toText(int indent) const {
             output << " " << dataFieldToText(*field, std::distance(fields.begin(),
                                                                    field));
         }
+
+        // If the last record field is an array iterate on extra buffers
+        if (definition_.getArrayType()) {
+            for (unsigned int i = fields.size(); i < getDataFieldsNum(); ++i) {
+                output << " " << dataFieldToText(fields.back(), i);
+            }
+        }
     } else {
         // For non-record types we iterate over all buffers
         // and print the data type set globally for an option

+ 41 - 6
src/lib/dhcp/option_custom.h

@@ -102,6 +102,10 @@ public:
     void addArrayDataField(const T value) {
         checkArrayType();
         OptionDataType data_type = definition_.getType();
+        // Handle record last field.
+        if (data_type == OPT_RECORD_TYPE) {
+            data_type = definition_.getRecordFields().back();
+        }
         if (OptionDataTypeTraits<T>::type != data_type) {
             isc_throw(isc::dhcp::InvalidDataType,
                       "specified data type " << data_type << " does not"
@@ -405,9 +409,29 @@ private:
     /// @throw isc::OutOfRange if index is out of range.
     void checkIndex(const uint32_t index) const;
 
+    /// @brief Create a non initialized buffer.
+    ///
+    /// @param buffer buffer to update.
+    /// @param data_type data type of buffer.
+    void createBuffer(OptionBuffer& buffer,
+                      const OptionDataType data_type) const;
+
     /// @brief Create a collection of non initialized buffers.
     void createBuffers();
 
+    /// @brief Return length of a buffer.
+    ///
+    /// @param data_type data type of buffer.
+    /// @param in_array true is called from the array case
+    /// @param begin iterator to first byte of input data.
+    /// @param end iterator to end of input data.
+    ///
+    /// @return size of data to copy to the buffer.
+    /// @throw isc::OutOfRange if option buffer is truncated.
+    size_t bufferLength(const OptionDataType data_type, bool in_array,
+                        OptionBuffer::const_iterator begin,
+                        OptionBuffer::const_iterator end) const;
+
     /// @brief Create collection of buffers representing data field values.
     ///
     /// @param data_buf a buffer to be parsed.
@@ -453,12 +477,23 @@ OptionCustom::checkDataType(const uint32_t index) const {
     if (data_type == OPT_RECORD_TYPE) {
         const OptionDefinition::RecordFieldsCollection& record_fields =
             definition_.getRecordFields();
-        // When we initialized buffers we have already checked that
-        // the number of these buffers is equal to number of option
-        // fields in the record so the condition below should be met.
-        assert(index < record_fields.size());
-        // Get the data type to be returned.
-        data_type = record_fields[index];
+        if (definition_.getArrayType()) {
+            // If the array flag is set the last record field is an array.
+            if (index < record_fields.size()) {
+                // Get the data type to be returned.
+                data_type = record_fields[index];
+            } else {
+                // Get the data type to be returned from the last record field.
+                data_type = record_fields.back();
+            }
+        } else {
+            // When we initialized buffers we have already checked that
+            // the number of these buffers is equal to number of option
+            // fields in the record so the condition below should be met.
+            assert(index < record_fields.size());
+            // Get the data type to be returned.
+            data_type = record_fields[index];
+        }
     }
 
     if (OptionDataTypeTraits<T>::type != data_type) {

+ 38 - 16
src/lib/dhcp/option_definition.cc

@@ -258,6 +258,12 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
         for (size_t i = 0; i < records.size(); ++i) {
             writeToBuffer(u, util::str::trim(values[i]), records[i], buf);
         }
+        if (array_type_ && (values.size() > records.size())) {
+            for (size_t i = records.size(); i < values.size(); ++i) {
+                writeToBuffer(u, util::str::trim(values[i]),
+                              records.back(), buf);
+            }
+        }
     }
     return (optionFactory(u, type, buf.begin(), buf.end()));
 }
@@ -292,22 +298,6 @@ OptionDefinition::validate() const {
         // Option definition must be of a known type.
         err_str << "option type " << type_ << " not supported.";
 
-    } else if (array_type_) {
-        if (type_ == OPT_STRING_TYPE) {
-            // Array of strings is not allowed because there is no way
-            // to determine the size of a particular string and thus there
-            // it no way to tell when other data fields begin.
-            err_str << "array of strings is not a valid option definition.";
-        } else if (type_ == OPT_BINARY_TYPE) {
-            err_str << "array of binary values is not"
-                    << " a valid option definition.";
-
-        } else if (type_ == OPT_EMPTY_TYPE) {
-            err_str << "array of empty value is not"
-                    << " a valid option definition.";
-
-        }
-
     } else if (type_ == OPT_RECORD_TYPE) {
         // At least two data fields should be added to the record. Otherwise
         // non-record option definition could be used.
@@ -335,6 +325,7 @@ OptionDefinition::validate() const {
                     it < fields.end() - 1) {
                     err_str << "binary data field can't be laid before data"
                             << " fields of other types.";
+                    break;
                 }
                 /// Empty type is not allowed within a record.
                 if (*it == OPT_EMPTY_TYPE) {
@@ -343,8 +334,35 @@ OptionDefinition::validate() const {
                     break;
                 }
             }
+            // If the array flag is set the last field is an array.
+            if (err_str.str().empty() && array_type_) {
+                const OptionDataType& last_type = fields.back();
+                if (last_type == OPT_STRING_TYPE) {
+                    err_str << "array of strings is not"
+                            << "a valid option definition.";
+                } else if (last_type == OPT_BINARY_TYPE) {
+                    err_str << "array of binary values is not"
+                            << " a valid option definition.";
+                }
+                // Empty type was already checked.
+            }
         }
 
+    } else if (array_type_) {
+        if (type_ == OPT_STRING_TYPE) {
+            // Array of strings is not allowed because there is no way
+            // to determine the size of a particular string and thus there
+            // it no way to tell when other data fields begin.
+            err_str << "array of strings is not a valid option definition.";
+        } else if (type_ == OPT_BINARY_TYPE) {
+            err_str << "array of binary values is not"
+                    << " a valid option definition.";
+
+        } else if (type_ == OPT_EMPTY_TYPE) {
+            err_str << "array of empty value is not"
+                    << " a valid option definition.";
+
+        }
     }
 
     // Non-empty error string means that we have hit the error. We throw
@@ -357,6 +375,7 @@ OptionDefinition::validate() const {
 bool
 OptionDefinition::haveIAx6Format(OptionDataType first_type) const {
    return (haveType(OPT_RECORD_TYPE) &&
+           !getArrayType() &&
            record_fields_.size() == 3 &&
            record_fields_[0] == first_type &&
            record_fields_[1] == OPT_UINT32_TYPE &&
@@ -382,6 +401,7 @@ OptionDefinition::haveIAAddr6Format() const {
 bool
 OptionDefinition::haveIAPrefix6Format() const {
     return (haveType(OPT_RECORD_TYPE) &&
+           !getArrayType() &&
             record_fields_.size() == 4 &&
             record_fields_[0] == OPT_UINT32_TYPE &&
             record_fields_[1] == OPT_UINT32_TYPE &&
@@ -392,6 +412,7 @@ OptionDefinition::haveIAPrefix6Format() const {
 bool
 OptionDefinition::haveFqdn4Format() const {
     return (haveType(OPT_RECORD_TYPE) &&
+           !getArrayType() &&
             record_fields_.size() == 4 &&
             record_fields_[0] == OPT_UINT8_TYPE &&
             record_fields_[1] == OPT_UINT8_TYPE &&
@@ -402,6 +423,7 @@ OptionDefinition::haveFqdn4Format() const {
 bool
 OptionDefinition::haveClientFqdnFormat() const {
     return (haveType(OPT_RECORD_TYPE) &&
+           !getArrayType() &&
             (record_fields_.size() == 2) &&
             (record_fields_[0] == OPT_UINT8_TYPE) &&
             (record_fields_[1] == OPT_FQDN_TYPE));

+ 25 - 0
src/lib/dhcp/std_option_defs.h

@@ -37,6 +37,9 @@ namespace {
 #define NO_RECORD_DEF 0, 0
 #endif
 
+// SLP Directory Agent option.
+RECORD_DECL(DIRECTORY_AGENT_RECORDS, OPT_BOOLEAN_TYPE, OPT_IPV4_ADDRESS_TYPE);
+
 // SLP Service Scope option.
 //
 // The scope list is optional.
@@ -65,11 +68,23 @@ RECORD_DECL(CLIENT_NDI_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
 // A client identifier: a 1 byte type field followed by opaque data depending on the type
 RECORD_DECL(UUID_GUID_RECORDS, OPT_UINT8_TYPE, OPT_BINARY_TYPE);
 
+// RFC6731 DHCPv4 Recursive DNS Server Selection option.
+//
+// Flag, two addresses and domain list
+RECORD_DECL(V4_RDNSS_SELECT_RECORDS, OPT_UINT8_TYPE, OPT_IPV4_ADDRESS_TYPE,
+            OPT_IPV4_ADDRESS_TYPE, OPT_FQDN_TYPE);
+
 // RFC7618 DHCPv4 Port Parameter option.
 //
 // PSID offset, PSID-len and PSID
 RECORD_DECL(V4_PORTPARAMS_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT16_TYPE);
 
+// RFC5969 DHCPv6 6RD option.
+//
+// two 8 bit lengthes, an IPv6 address and one or more IPv4 addresses
+RECORD_DECL(OPT_6RD_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+            OPT_IPV6_ADDRESS_TYPE, OPT_IPV4_ADDRESS_TYPE);
+
 /// @brief Definitions of standard DHCPv4 options.
 const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
     { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
@@ -183,6 +198,8 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
     { "streettalk-server", DHO_STREETTALK_SERVER, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "streettalk-directory-assistance-server", DHO_STDASERVER, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "user-class", DHO_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "slp-directory-agent", DHO_DIRECTORY_AGENT, OPT_RECORD_TYPE, true,
+      RECORD_DEF(DIRECTORY_AGENT_RECORDS), "" },
     { "slp-service-scope", DHO_SERVICE_SCOPE, OPT_RECORD_TYPE, false,
       RECORD_DEF(SERVICE_SCOPE_RECORDS), "" },
     { "fqdn", DHO_FQDN, OPT_RECORD_TYPE, false, RECORD_DEF(FQDN_RECORDS), "" },
@@ -238,9 +255,12 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
     { "v4-lost", DHO_V4_LOST, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
     { "capwap-ac-v4", DHO_CAPWAP_AC_V4, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "sip-ua-cs-domains", DHO_SIP_UA_CONF_SERVICE_DOMAINS, OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+    { "rdnss-selection", DHO_RDNSS_SELECT, OPT_RECORD_TYPE, true,
+      RECORD_DEF(V4_RDNSS_SELECT_RECORDS), "" },
     { "v4-portparams", DHO_V4_PORTPARAMS, OPT_RECORD_TYPE, false,
       RECORD_DEF(V4_PORTPARAMS_RECORDS), "" },
     { "v4-captive-portal", DHO_V4_CAPTIVE_PORTAL, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "option-6rd", DHO_6RD, OPT_RECORD_TYPE, true, RECORD_DEF(OPT_6RD_RECORDS), "" },
     { "v4-access-domain", DHO_V4_ACCESS_DOMAIN, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" }
 
         // @todo add definitions for all remaining options.
@@ -293,6 +313,9 @@ RECORD_DECL(S46_PORTPARAMS, OPT_UINT8_TYPE, OPT_PSID_TYPE);
 RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
 // vendor-class
 RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// rdnss-selection
+RECORD_DECL(V6_RDNSS_SELECT_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_UINT8_TYPE,
+            OPT_FQDN_TYPE);
 // sedhcpv6 signature
 RECORD_DECL(SIGNATURE_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
             OPT_BINARY_TYPE);
@@ -407,6 +430,8 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
       NO_RECORD_DEF, "" },
     { "rsoo", D6O_RSOO, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "rsoo-opts" },
     { "pd-exclude", D6O_PD_EXCLUDE, OPT_IPV6_PREFIX_TYPE, false, NO_RECORD_DEF, "" },
+    { "rdnss-selection", D6O_RDNSS_SELECTION, OPT_RECORD_TYPE, true,
+      RECORD_DEF(V6_RDNSS_SELECT_RECORDS), "" },
     { "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, OPT_BINARY_TYPE, false,
       NO_RECORD_DEF, "" },
     { "link-address", D6O_LINK_ADDRESS, OPT_IPV6_ADDRESS_TYPE, false,

+ 43 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -1252,6 +1252,15 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
     LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end,
                                     typeid(Option));
 
+    LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 5,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 9,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 45,
+                                    typeid(OptionCustom));
+
     LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, end,
                                     typeid(Option4SlpServiceScope));
 
@@ -1398,12 +1407,32 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
                                     fqdn_buf.end(),
                                     typeid(OptionCustom));
 
+    std::vector<uint8_t> rdnss1_buf(begin, begin + 9);
+    rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end());
+
+    LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss1_buf.begin(),
+                                    rdnss1_buf.end(),
+                                    typeid(OptionCustom));
+
+    std::vector<uint8_t> rdnss_buf(begin, begin + 9);
+    rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end());
+
+    LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss_buf.begin(),
+                                    rdnss_buf.end(),
+                                    typeid(OptionCustom));
+
     LibDhcpTest::testStdOptionDefs4(DHO_V4_PORTPARAMS, begin, begin + 4,
                                     typeid(OptionCustom));
 
     LibDhcpTest::testStdOptionDefs4(DHO_V4_CAPTIVE_PORTAL, begin, end,
                                     typeid(OptionString));
 
+    LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 22,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 46,
+                                    typeid(OptionCustom));
+
     LibDhcpTest::testStdOptionDefs4(DHO_V4_ACCESS_DOMAIN, fqdn1_buf.begin(),
                                     fqdn1_buf.end(), typeid(OptionCustom));
 }
@@ -1672,6 +1701,20 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
     LibDhcpTest::testStdOptionDefs6(D6O_PD_EXCLUDE, begin, end,
                                     typeid(Option6PDExclude));
 
+    std::vector<uint8_t> rdnss1_buf(begin, begin + 17);
+    rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end());
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss1_buf.begin(),
+                                    rdnss1_buf.end(),
+                                    typeid(OptionCustom));
+
+    std::vector<uint8_t> rdnss_buf(begin, begin + 17);
+    rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end());
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss_buf.begin(),
+                                    rdnss_buf.end(),
+                                    typeid(OptionCustom));
+
     LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_LINKLAYER_ADDR, begin, end,
                                     typeid(Option));
 

+ 237 - 0
src/lib/dhcp/tests/option_custom_unittest.cc

@@ -1275,6 +1275,97 @@ TEST_F(OptionCustomTest, recordData) {
     EXPECT_EQ("ABCD", value6);
 }
 
+// The purpose of this test is to verify that the option definition comprising
+// a record of various data fields with an array for the last can be used
+// to create an instance of custom option.
+TEST_F(OptionCustomTest, recordArrayData) {
+    // Create the definition of an option which comprises
+    // a record of fields of different types.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record", true);
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("psid"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+    const char fqdn_data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+    };
+
+    OptionBuffer buf;
+    // Initialize field 0 to 8712.
+    writeInt<uint16_t>(8712, buf);
+    // Initialize field 1 to 'true'
+    writeInt<uint8_t>(1, buf);
+    // Initialize field 2 to 'mydomain.example.com'.
+    buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+    // Initialize field 3 to IPv4 address.
+    writeAddress(IOAddress("192.168.0.1"), buf);
+    // Initialize field 4 to IPv6 address.
+    writeAddress(IOAddress("2001:db8:1::1"), buf);
+    // Initialize PSID len and PSID value.
+    writeInt<uint8_t>(6, buf);
+    writeInt<uint16_t>(0xD400, buf);
+    // Initialize last field 6 to a pair of int 12345678 and 87654321.
+    writeInt<uint32_t>(12345678, buf);
+    writeInt<uint32_t>(87654321, buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+         option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 7+1 data fields.
+    ASSERT_EQ(8, option->getDataFieldsNum());
+
+    // Verify value in the field 0.
+    uint16_t value0 = 0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(8712, value0);
+
+    // Verify value in the field 1.
+    bool value1 = false;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_TRUE(value1);
+
+    // Verify value in the field 2.
+    std::string value2 = "";
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ("mydomain.example.com.", value2);
+
+    // Verify value in the field 3.
+    IOAddress value3("127.0.0.1");
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("192.168.0.1", value3.toText());
+
+    // Verify value in the field 4.
+    IOAddress value4("::1");
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+    // Verify value in the field 5.
+    PSIDTuple value5;
+    ASSERT_NO_THROW(value5 = option->readPsid(5));
+    EXPECT_EQ(6, value5.first.asUnsigned());
+    EXPECT_EQ(0x35, value5.second.asUint16());
+
+    // Verify value in the field 6.
+    uint32_t value6;
+    ASSERT_NO_THROW(value6 = option->readInteger<uint32_t>(6));
+    EXPECT_EQ(12345678, value6);
+
+    // Verify value in the extra field 7.
+    uint32_t value7;
+    ASSERT_NO_THROW(value7 = option->readInteger<uint32_t>(7));
+    EXPECT_EQ(87654321, value7);
+}
+
 // The purpose of this test is to verify that truncated buffer
 // can't be used to create an option being a record of value of
 // different types.
@@ -2000,6 +2091,103 @@ TEST_F(OptionCustomTest, setRecordData) {
     EXPECT_EQ(value8, "hello world");
 }
 
+TEST_F(OptionCustomTest, setRecordArrayData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record", true);
+
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("psid"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
+    ASSERT_NO_THROW(opt_def.addRecordField("tuple"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // The number of elements should be equal to number of elements
+    // in the record.
+    ASSERT_EQ(9, option->getDataFieldsNum());
+
+    // Check that the default values have been correctly set.
+    uint16_t value0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(0, value0);
+    bool value1 = true;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_FALSE(value1);
+    std::string value2;
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ(".", value2);
+    IOAddress value3("127.0.0.1");
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("0.0.0.0", value3.toText());
+    IOAddress value4("2001:db8:1::1");
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("::", value4.toText());
+    PSIDTuple value5;
+    ASSERT_NO_THROW(value5 = option->readPsid(5));
+    EXPECT_EQ(0, value5.first.asUnsigned());
+    EXPECT_EQ(0, value5.second.asUint16());
+    PrefixTuple value6(ZERO_PREFIX_TUPLE);
+    ASSERT_NO_THROW(value6 = option->readPrefix(6));
+    EXPECT_EQ(0, value6.first.asUnsigned());
+    EXPECT_EQ("::", value6.second.toText());
+    std::string value7 = "abc";
+    // Tuple has no default value
+    EXPECT_THROW(option->readTuple(7), BadDataTypeCast);
+    uint32_t value8;
+    ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8));
+    EXPECT_EQ(0, value8);
+
+    // Override each value with a new value.
+    ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+    ASSERT_NO_THROW(option->writeBoolean(true, 1));
+    ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+    ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+    ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+    ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5));
+    ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
+                                        IOAddress("2001:db8:1::"), 6));
+    ASSERT_NO_THROW(option->writeTuple("foobar", 7));
+    ASSERT_NO_THROW(option->writeInteger<uint32_t>(12345678, 8));
+    ASSERT_NO_THROW(option->addArrayDataField<uint32_t>(87654321));
+
+    // Check that the new values have been correctly set.
+    ASSERT_EQ(10, option->getDataFieldsNum());
+
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(1234, value0);
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_TRUE(value1);
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ("example.com.", value2);
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("192.168.0.1", value3.toText());
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("2001:db8:1::100", value4.toText());
+    ASSERT_NO_THROW(value5 = option->readPsid(5));
+    EXPECT_EQ(4, value5.first.asUnsigned());
+    EXPECT_EQ(8, value5.second.asUint16());
+    ASSERT_NO_THROW(value6 = option->readPrefix(6));
+    EXPECT_EQ(48, value6.first.asUnsigned());
+    EXPECT_EQ("2001:db8:1::", value6.second.toText());
+    ASSERT_NO_THROW(value7 = option->readTuple(7));
+    EXPECT_EQ(value7, "foobar");
+    ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8));
+    EXPECT_EQ(12345678, value8);
+    uint32_t value9;
+    ASSERT_NO_THROW(value9 = option->readInteger<uint32_t>(9));
+    EXPECT_EQ(87654321, value9);
+}
+
 // The purpose of this test is to verify that pack function for
 // DHCPv4 custom option works correctly.
 TEST_F(OptionCustomTest, pack4) {
@@ -2130,6 +2318,55 @@ TEST_F(OptionCustomTest, unpack) {
     }
 }
 
+// The purpose of this test is to verify that unpack function works
+// correctly for a custom option with record and trailing array.
+TEST_F(OptionCustomTest, unpackRecordArray) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "record", true);
+
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+
+    // Initialize reference data.
+    OptionBuffer buf;
+    writeInt<uint16_t>(8712, buf);
+
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("192.168.0.1"));
+    addresses.push_back(IOAddress("127.0.0.1"));
+    addresses.push_back(IOAddress("10.10.1.2"));
+
+    // Store the collection of IPv4 addresses into the buffer.
+    for (size_t i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 4 data fields.
+    ASSERT_EQ(4, option->getDataFieldsNum());
+
+    // We expect a 16 bit integer
+    uint16_t value0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(8712, value0);
+
+    // ... and 3 IPv4 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i + 1));
+        EXPECT_EQ(addresses[i], address);
+    }
+
+    std::string text = option->toText();
+    EXPECT_EQ("type=231, len=014: 8712 (uint16) 192.168.0.1 (ipv4-address) "
+              "127.0.0.1 (ipv4-address) 10.10.1.2 (ipv4-address)", text);
+}
+
 // The purpose of this test is to verify that new data can be set for
 // a custom option.
 TEST_F(OptionCustomTest, initialize) {

+ 10 - 2
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -325,11 +325,19 @@ TEST_F(OptionDefinitionTest, validate) {
                                "record");
     opt_def16.addRecordField("uint8");
     opt_def16.addRecordField("string");
+    EXPECT_NO_THROW(opt_def16.validate());
+
+    // ... at least if it is not an array.
+    OptionDefinition opt_def17("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+                               "record", true);
+    opt_def17.addRecordField("uint8");
+    opt_def17.addRecordField("string");
+    EXPECT_THROW(opt_def17.validate(), MalformedOptionDefinition);
 
     // Check invalid encapsulated option space name.
-    OptionDefinition opt_def17("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+    OptionDefinition opt_def18("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
                                "uint32", "invalid%space%name");
-    EXPECT_THROW(opt_def17.validate(), MalformedOptionDefinition);
+    EXPECT_THROW(opt_def18.validate(), MalformedOptionDefinition);
 }