Browse Source

[5016] Merged pull request #24 from github24 and squashed it.

This change includes the code implemented by the pull request
submitter as well as Marcin's fixes/changes on top of it, but
without a review.
Marcin Siodelski 8 years ago
parent
commit
0c0ca2fde7
67 changed files with 3559 additions and 509 deletions
  1. 6 0
      AUTHORS
  2. 1 0
      doc/Makefile.am
  3. 91 0
      doc/examples/kea6/softwire46.json
  4. 3 1
      doc/guide/dhcp4-srv.xml
  5. 204 3
      doc/guide/dhcp6-srv.xml
  6. 3 2
      src/bin/dhcp4/dhcp4_srv.cc
  7. 25 25
      src/bin/dhcp4/tests/config_parser_unittest.cc
  8. 4 4
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  9. 5 5
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  10. 12 0
      src/bin/dhcp6/dhcp6.spec
  11. 65 9
      src/bin/dhcp6/dhcp6_srv.cc
  12. 5 0
      src/bin/dhcp6/dhcp6_srv.h
  13. 15 8
      src/bin/dhcp6/json_config_parser.cc
  14. 78 24
      src/bin/dhcp6/tests/config_parser_unittest.cc
  15. 1 1
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  16. 116 1
      src/bin/dhcp6/tests/renew_unittest.cc
  17. 68 1
      src/bin/dhcp6/tests/sarr_unittest.cc
  18. 1 0
      src/lib/dhcp/Makefile.am
  19. 10 10
      src/lib/dhcp/dhcp6.h
  20. 3 2
      src/lib/dhcp/docsis3_option_defs.h
  21. 2 2
      src/lib/dhcp/duid_factory.cc
  22. 50 55
      src/lib/dhcp/libdhcp++.cc
  23. 12 27
      src/lib/dhcp/libdhcp++.h
  24. 4 3
      src/lib/dhcp/option6_ia.cc
  25. 3 2
      src/lib/dhcp/option6_iaaddr.cc
  26. 3 2
      src/lib/dhcp/option6_iaprefix.cc
  27. 227 0
      src/lib/dhcp/option6_pdexclude.cc
  28. 134 0
      src/lib/dhcp/option6_pdexclude.h
  29. 124 20
      src/lib/dhcp/option_custom.cc
  30. 53 0
      src/lib/dhcp/option_custom.h
  31. 212 1
      src/lib/dhcp/option_data_types.cc
  32. 194 2
      src/lib/dhcp/option_data_types.h
  33. 82 1
      src/lib/dhcp/option_definition.cc
  34. 7 1
      src/lib/dhcp/option_definition.h
  35. 3 2
      src/lib/dhcp/option_int.h
  36. 9 4
      src/lib/dhcp/option_space.h
  37. 1 1
      src/lib/dhcp/pkt4.cc
  38. 3 2
      src/lib/dhcp/pkt6.cc
  39. 89 24
      src/lib/dhcp/std_option_defs.h
  40. 1 0
      src/lib/dhcp/tests/Makefile.am
  41. 65 74
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  42. 175 0
      src/lib/dhcp/tests/option6_pdexclude_unittest.cc
  43. 360 11
      src/lib/dhcp/tests/option_custom_unittest.cc
  44. 260 1
      src/lib/dhcp/tests/option_data_types_unittest.cc
  45. 222 1
      src/lib/dhcp/tests/option_definition_unittest.cc
  46. 5 5
      src/lib/dhcp/tests/option_unittest.cc
  47. 5 4
      src/lib/dhcpsrv/alloc_engine.cc
  48. 4 0
      src/lib/dhcpsrv/alloc_engine.h
  49. 25 13
      src/lib/dhcpsrv/cfg_option.cc
  50. 10 0
      src/lib/dhcpsrv/cfg_option.h
  51. 1 6
      src/lib/dhcpsrv/cfg_option_def.cc
  52. 2 7
      src/lib/dhcpsrv/mysql_host_data_source.cc
  53. 7 11
      src/lib/dhcpsrv/parsers/dhcp_parsers.cc
  54. 97 12
      src/lib/dhcpsrv/pool.cc
  55. 79 1
      src/lib/dhcpsrv/pool.h
  56. 1 1
      src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
  57. 138 61
      src/lib/dhcpsrv/tests/cfg_option_unittest.cc
  58. 4 4
      src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
  59. 7 6
      src/lib/dhcpsrv/tests/client_class_def_unittest.cc
  60. 60 15
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  61. 0 1
      src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
  62. 8 8
      src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
  63. 9 8
      src/lib/dhcpsrv/tests/host_unittest.cc
  64. 70 1
      src/lib/dhcpsrv/tests/pool_unittest.cc
  65. 1 1
      src/lib/dhcpsrv/tests/srv_config_unittest.cc
  66. 10 9
      src/lib/dhcpsrv/tests/subnet_unittest.cc
  67. 5 3
      src/lib/eval/eval_context.cc

+ 6 - 0
AUTHORS

@@ -117,6 +117,12 @@ We have received the following contributions:
             strings to avoid issues with creation of the database
             when MySQL server operates in ANSI_QUOTES mode.
 
+ - Cristian Secareanu, Qualitance
+   2016-10: Support for IPv6 prefix and PDEXCLUDE option
+
+ - Andrei Pavel, Qualitance
+   2016-10: Support for DHCPv6 options defined in RFC6603 and RFC7598
+
 Kea uses log4cplus (http://sourceforge.net/projects/log4cplus/) for logging,
 Boost (http://www.boost.org/) library for almost everything, and can use Botan
 (http://botan.randombit.net/) or OpenSSL (https://www.openssl.org/) for

+ 1 - 0
doc/Makefile.am

@@ -29,6 +29,7 @@ nobase_dist_doc_DATA += examples/kea6/pgsql-reservations.json
 nobase_dist_doc_DATA += examples/kea6/reservations.json
 nobase_dist_doc_DATA += examples/kea6/several-subnets.json
 nobase_dist_doc_DATA += examples/kea6/simple.json
+nobase_dist_doc_DATA += examples/kea6/softwire46.json
 nobase_dist_doc_DATA += examples/kea6/stateless.json
 
 devel:

+ 91 - 0
doc/examples/kea6/softwire46.json

@@ -0,0 +1,91 @@
+# This is an example configuration file for DHCPv6 server in Kea.
+# It demonstrates how user can specify values for Softwire options
+# defined in RFC 7598.
+
+{ "Dhcp6":
+
+{
+# Kea is told to listen on ethX interface only.
+  "interfaces-config": {
+    "interfaces": [ "ethX" ]
+  },
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with preferred and valid lifetimes
+# being 3000 and 4000, respectively. Client is told to start
+# renewing after 1000 seconds. If the server does not respond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+  "preferred-lifetime": 3000,
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# The following list defines subnets. Each subnet consists of at
+# least subnet and pool entries.
+  "subnet6": [
+    {
+      "pools": [ { "pool": "2001:db8:1::/80" } ],
+      "subnet": "2001:db8:1::/64",
+      "interface": "ethX",
+# Include MAP-E Container option for hosts connected to this subnet.
+      "option-data": [
+        {
+            "name": "s46-cont-mape"
+        }
+      ],
+# Send host specific softwire options.
+      "reservations": [
+        {
+            "duid": "01:02:03:04:05:06:07:08:09:0A",
+            "option-data": [
+# These two options will be included in the MAP-E Container
+              {
+                "space": "s46-cont-mape-options",
+                "name": "s46-rule",
+                "data": "1, 0, 24, 192.0.2.0, 2001:db8:1::/64"
+              },
+              {
+                "space": "s46-cont-mape-options",
+                "name": "s46-br",
+                "data": "2001:db8:cafe::1"
+              },
+# This option will be included in the S46 Rule option.
+              {
+                "space": "s46-rule-options",
+                "name": "s46-portparams",
+                "data": "0, 3/4"
+              }
+            ]
+        }
+      ]
+    }
+  ]
+},
+
+# The following configures logging. Kea will log all debug messages
+# to /var/log/kea-debug.log file.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "/var/log/kea-debug.log"
+                }
+            ],
+            "debuglevel": 99,
+            "severity": "DEBUG"
+        }
+    ]
+}
+
+}
+

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


+ 204 - 3
doc/guide/dhcp6-srv.xml

@@ -799,7 +799,6 @@ temporarily override a list of interface names and listen on all interfaces.
     </section>
 
     <section>
-<!-- @todo: add real meat to the prefix delegation config this is just place holder stuff -->
       <title>Subnet and Prefix Delegation Pools</title>
       <para>
         Subnets may also be configured to delegate prefixes, as defined in
@@ -833,6 +832,42 @@ temporarily override a list of interface names and listen on all interfaces.
     ...
 }</screen>
       </para>
+
+    </section>
+
+    <section id="pd-exclude-option">
+        <title>Prefix Exclude Option</title>
+        <para>
+          For each delegated prefix the delegating router may choose to exclude
+          a single prefix out of the delegated prefix as specified in the
+          <ulink url="http://tools.ietf.org/html/rfc6603"> RFC 6603</ulink>.
+          The requesting router must not assign the excluded prefix to any
+          of its downstream interfaces and it is intended to be used on a
+          link through which the delegating router exchanges DHCPv6 messages with
+          the requesting router. The configuration example below demonstrates how
+          to specify an excluded prefix within a prefix pool definition. The
+          excluded prefix "2001:db8:1:babe:cafe:80::/72" will be sent to a
+          requesting router which includes Prefix Exclude option in the ORO, and
+          which is delegated a prefix from this pool.
+        </para>
+<screen>
+"Dhcp6": {
+    "subnet6": [
+        {
+            "subnet": "2001:db8:1::/48",
+            "pd-pools": [
+                {
+                    "prefix": "2001:db8:1:8000::",
+                    "prefix-len": 48,
+                    "delegated-len": 64,
+                    "excluded-prefix": "2001:db8:1:babe:cafe:80::",
+                    "excluded-prefix-len": 72
+                }
+            ]
+        }
+    ]
+}
+</screen>
     </section>
 
     <section id="dhcp6-std-options">
@@ -1110,11 +1145,21 @@ temporarily override a list of interface names and listen on all interfaces.
 <row><entry>bootfile-param</entry><entry>60</entry><entry>binary</entry><entry>false</entry></row>
 <row><entry>client-arch-type</entry><entry>61</entry><entry>uint16</entry><entry>true</entry></row>
 <row><entry>nii</entry><entry>62</entry><entry>record (uint8, uint8, uint8)</entry><entry>false</entry></row>
+<row><entry>aftr-name</entry><entry>64</entry><entry>fqdn</entry><entry>false</entry></row>
 <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>client-linklayer-addr</entry><entry>79</entry><entry>binary</entry><entry>false</entry></row>
 <!-- <row><entry>dhcpv4-message</entry><entry>87</entry><entry>binary</entry><entry>false</entry></row> -->
 <row><entry>dhcp4o6-server-addr</entry><entry>88</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>s46-rule</entry><entry>89</entry><entry>record (uint8, uint8, uint8, ipv4-address, ipv6-prefix)</entry><entry>false</entry></row>
+<row><entry>s46-br</entry><entry>90</entry><entry>ipv6-address</entry><entry>false</entry></row>
+<row><entry>s46-dmr</entry><entry>91</entry><entry>ipv6-prefix</entry><entry>false</entry></row>
+<row><entry>s46-v4v6bind</entry><entry>92</entry><entry>record (ipv4-address, ipv6-prefix)</entry><entry>false</entry></row>
+<row><entry>s46-portparams</entry><entry>93</entry><entry>record(uint8, psid)</entry><entry>false</entry></row>
+<row><entry>s46-cont-mape</entry><entry>94</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>s46-cont-mapt</entry><entry>95</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>s46-cont-lw</entry><entry>96</entry><entry>empty</entry><entry>false</entry></row>
         </tbody>
         </tgroup>
       </table>
@@ -1142,6 +1187,149 @@ temporarily override a list of interface names and listen on all interfaces.
     </para>
     </section>
 
+    <section id="s46-options">
+      <title>Common Softwire46 Options</title>
+      <para>
+        Softwire46 options are involved in IPv4 over IPv6 provisioning by
+        means of tunneling or translation as specified in the
+        <ulink url="http://tools.ietf.org/html/rfc7598">RFC 7598</ulink>.
+        The following sections provide configuration examples of these
+        options.
+      </para>
+
+      <section id="s46-containers">
+      <title>Softwire46 Container Options</title>
+      <para>
+        S46 container options group rules and optional port parameters
+        for a specified domain. There are three container options specified
+        in the "dhcp6" (top level) option space: MAP-E Container option,
+        MAP-T Container option and S46 Lieghtweight 4over6 Container option.
+        These options only contain encapsulated options specified below.
+        They do not include any data fields.
+      </para>
+
+      <para>
+        In order to configure the server to send specific container option
+        along with all encapsulated options, the container option must be
+        included in the server configuration as shown below:
+<screen>
+"Dhcp6": {
+    ...
+    "option-data": [
+        {
+            "name": "s46-cont-mape"
+        } ],
+    ...
+}
+</screen>
+
+        This configuration will cause the server to include MAP-E Container
+        option to the client. Use "s46-cont-mapt" or "s46-cont-lw" for the
+        MAP-T Container and S46 Lightweight 4over6 Container options
+        respectively.
+      </para>
+
+      <para>
+        All remaining softwire options described below are included in one
+        of the container options. Thus, they have to be included in appropriate
+        option spaces by selecting a "space" name, which specifies in which
+        option they are supposed to be included.
+      </para>
+    </section>
+
+      <section>
+        <title>S46 Rule Option</title>
+        <para>
+          The S46 Rule option is used for conveying the Basic Mapping Rule (BMR)
+          and Forwarding Mapping Rule (FMR).
+<screen>
+{
+    "space": "s46-cont-mape-options",
+    "name": "s46-rule",
+    "data": "1, 0, 24, 192.0.2.0, 2001:db8:1::/64"
+}
+</screen>
+         Other possible "space" value is "s46-cont-mapt-options".
+        </para>
+
+      </section>
+        <section>
+            <title>S46 BR Option</title>
+            <para>
+              The S46 BR option is used to convey the IPv6 address of the
+              Border Relay. This option is mandatory in the MAP-E
+              Container option and not permitted in the MAP-T and
+              S46 Lightweight 4over6 Container options.
+<screen>
+{
+    "space": "s46-cont-mape-options",
+    "name": "s46-br",
+    "data": "2001:db8:cafe::1",
+}
+</screen>
+           Other possible "space" value is "s46-cont-lw-options".
+          </para>
+        </section>
+
+        <section>
+            <title>S46 DMR Option</title>
+            <para>
+              The S46 DMR option is used to convey values for the Default
+              Mapping Rule (DMR). This option is mandatory in the MAP-T
+              container option and not permitted in the MAP-E and S46
+              Lightweight 4over6 Container options.
+<screen>
+{
+    "space": "s46-cont-mapt-options",
+    "name": "s46-dmr",
+    "data": "2001:db8:cafe::/64",
+}
+</screen>
+              This option must not be included in other containers.
+            </para>
+        </section>
+
+        <section>
+            <title>S46 IPv4/IPv6 Address Binding option.</title>
+            <para>
+              The S46 IPv4/IPv6 Address Binding option may be used to specify
+              the full or shared IPv4 address of the Customer Edge (CE).
+              The IPv6 prefix field is used by the CE to identify the
+              correct prefix to use for the tunnel source.
+<screen>
+{
+    "space": "s46-cont-lw",
+    "name": "s46-v4v6bind",
+    "data": "192.0.2.3, 2001:db8:1:cafe::/64"
+}
+</screen>
+            This option must not be included in other containers.
+          </para>
+        </section>
+        <section>
+            <title>S46 Port Parameters</title>
+            <para>
+              The S46 Port Parameters option specifies optional port set
+              information that MAY be provided to CEs
+<screen>
+{
+    "space": "s46-rule-options",
+    "name": "s46-portparams",
+    "data": "2, 3/4",
+}
+</screen>
+              Other possible "space" value is "s46-v4v6bind" to include
+              this option in the S46 IPv4/IPv6 Address Binding option.
+            </para>
+            <para>
+              Note that the second value in the example above specifies the
+              PSID and PSID length fields in the format of PSID/PSID length.
+              This is equivalent to the values of PSID-len=4 and
+              PSID=12288 conveyed in the S46 Port Parameters option.
+            </para>
+        </section>
+    </section>
+
     <section id="dhcp6-custom-options">
       <title>Custom DHCPv6 Options</title>
       <para>It is possible to define options in addition to the standard ones.
@@ -2371,8 +2559,8 @@ should include options from the isc option space:
         "pd-pools": [
             {
                 "prefix": "2001:db8:1:8000::",
-                "prefix-len": 56,
-                "delegated-len": 64
+                "prefix-len": 48,
+                "delegated-len": 64,
             }
         ],
         <userinput>"reservations": [
@@ -3896,6 +4084,12 @@ If not specified, the default value is:
             ability to mark additional options as RSOO-enabled.</simpara>
           </listitem>
           <listitem>
+            <simpara><emphasis>Prefix Exclude Option for DHCPv6-based Prefix
+            Delegation</emphasis>,
+            <ulink url="http://tools.ietf.org/html/rfc6603">RFC
+            6603</ulink>: Prefix Exclude option is supported.</simpara>
+          </listitem>
+          <listitem>
             <simpara><emphasis>Client Link-Layer Address Option in
             DHCPv6</emphasis>,
             <ulink url="http://tools.ietf.org/html/rfc6939">RFC
@@ -3909,6 +4103,13 @@ If not specified, the default value is:
             7550</ulink>: All recommendations related to the DHCPv6 server
             operation are supported.</simpara>
           </listitem>
+          <listitem>
+            <simpara><emphasis>DHCPv6 Options for Configuration of Softwire
+            Address and Port-Mapped Clients</emphasis>,
+            <ulink url="http://tools.ietf.org/html/rfc7598">RFC
+            7598</ulink>: All options specified in this specification are
+            supported by the DHCPv6 server.</simpara>
+          </listitem>
       </itemizedlist>
     </section>
 

+ 3 - 2
src/bin/dhcp4/dhcp4_srv.cc

@@ -1209,7 +1209,7 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
             // Iterate on the configured option list
             for (CfgOptionList::const_iterator copts = co_list.begin();
                  copts != co_list.end(); ++copts) {
-                OptionDescriptor desc = (*copts)->get("dhcp4", *opt);
+                OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE, *opt);
                 // Got it: add it and jump to the outer loop
                 if (desc.option_) {
                     resp->addOption(desc.option_);
@@ -1320,7 +1320,8 @@ Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
             // Check whether option has been configured.
             for (CfgOptionList::const_iterator copts = co_list.begin();
                  copts != co_list.end(); ++copts) {
-                OptionDescriptor desc = (*copts)->get("dhcp4", required_options[i]);
+                OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE,
+                                                      required_options[i]);
                 if (desc.option_) {
                     resp->addOption(desc.option_);
                     break;

+ 25 - 25
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -146,7 +146,7 @@ public:
         std::map<std::string, std::string> params;
         if (parameter == "name") {
             params["name"] = param_value;
-            params["space"] = "dhcp4";
+            params["space"] = DHCP4_OPTION_SPACE;
             params["code"] = "56";
             params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
@@ -158,19 +158,19 @@ public:
             params["csv-format"] = "False";
         } else if (parameter == "code") {
             params["name"] = "dhcp-message";
-            params["space"] = "dhcp4";
+            params["space"] = DHCP4_OPTION_SPACE;
             params["code"] = param_value;
             params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "data") {
             params["name"] = "dhcp-message";
-            params["space"] = "dhcp4";
+            params["space"] = DHCP4_OPTION_SPACE;
             params["code"] = "56";
             params["data"] = param_value;
             params["csv-format"] = "False";
         } else if (parameter == "csv-format") {
             params["name"] = "dhcp-message";
-            params["space"] = "dhcp4";
+            params["space"] = DHCP4_OPTION_SPACE;
             params["code"] = "56";
             params["data"] = "ABCDEF0105";
             params["csv-format"] = param_value;
@@ -251,7 +251,7 @@ public:
                           << "does not exist in Config Manager";
         }
         OptionContainerPtr options =
-            subnet->getCfgOption()->getAll("dhcp4");
+            subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
         if (expected_options_count != options->size()) {
             ADD_FAILURE() << "The number of options in the subnet '"
                           << subnet_address.toText() << "' is different "
@@ -475,7 +475,7 @@ public:
     template<typename ReturnType>
     ReturnType
     retrieveOption(const Host& host, const uint16_t option_code) const {
-        return (retrieveOption<ReturnType>(host, "dhcp4", option_code));
+        return (retrieveOption<ReturnType>(host, DHCP4_OPTION_SPACE, option_code));
     }
 
     /// @brief Retrieve an option associated with a host.
@@ -1823,7 +1823,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
     ElementPtr json = Element::fromJSON(config);
 
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
-        getCfgOptionDef()->get("dhcp4", 109);
+        getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
     ASSERT_FALSE(def);
 
     // Use the configuration string to create new option definition.
@@ -1834,7 +1834,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
 
     // The option definition should now be available in the CfgMgr.
     def = CfgMgr::instance().getStagingCfg()->
-        getCfgOptionDef()->get("dhcp4", 109);
+        getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
     ASSERT_TRUE(def);
 
     // Check the option data.
@@ -1885,7 +1885,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
     checkResult(status, 0);
 
     def = CfgMgr::instance().getStagingCfg()->
-        getCfgOptionDef()->get("dhcp4", 213);
+        getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 213);
     ASSERT_TRUE(def);
 
     // Check the option data.
@@ -1927,10 +1927,10 @@ TEST_F(Dhcp4ParserTest, optionDataDefaultsGlobal) {
     Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
     ASSERT_TRUE(subnet);
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_EQ(0, options->size());
 
-    options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+    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.
@@ -1994,13 +1994,13 @@ TEST_F(Dhcp4ParserTest, optionDataDefaultsSubnet) {
 
     // These options are subnet options
     OptionContainerPtr options =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_EQ(0, options->size());
 
     Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
     ASSERT_TRUE(subnet);
-    options = subnet->getCfgOption()->getAll("dhcp4");
+    options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
@@ -2078,7 +2078,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     // Options should be now available
     // Try to get the option from the space dhcp4.
     OptionDescriptor desc1 =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp4", 56);
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP4_OPTION_SPACE, 56);
     ASSERT_TRUE(desc1.option_);
     EXPECT_EQ(56, desc1.option_->getType());
     // Try to get the option from the space isc.
@@ -2207,13 +2207,13 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
 
     // We should have one option available.
     OptionContainerPtr options =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
     OptionDescriptor desc =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp4", 222);
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP4_OPTION_SPACE, 222);
     EXPECT_TRUE(desc.option_);
     EXPECT_EQ(222, desc.option_->getType());
 
@@ -2267,7 +2267,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.24"));
     ASSERT_TRUE(subnet);
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
@@ -2303,7 +2303,7 @@ TEST_F(Dhcp4ParserTest, optionDataBoolean) {
     // Create configuration. Use standard option 19 (ip-forwarding).
     std::map<std::string, std::string> params;
     params["name"] = "ip-forwarding";
-    params["space"] = "dhcp4";
+    params["space"] = DHCP4_OPTION_SPACE;
     params["code"] = "19";
     params["data"] = "true";
     params["csv-format"] = "true";
@@ -2414,7 +2414,7 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     Subnet4Ptr subnet1 = CfgMgr::instance().getStagingCfg()->
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.100"));
     ASSERT_TRUE(subnet1);
-    OptionContainerPtr options1 = subnet1->getCfgOption()->getAll("dhcp4");
+    OptionContainerPtr options1 = subnet1->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_EQ(1, options1->size());
 
     // Get the search index. Index #1 is to search using option code.
@@ -2439,7 +2439,7 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     Subnet4Ptr subnet2 = CfgMgr::instance().getStagingCfg()->
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.3.102"));
     ASSERT_TRUE(subnet2);
-    OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp4");
+    OptionContainerPtr options2 = subnet2->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_EQ(1, options2->size());
 
     const OptionContainerTypeIndex& idx2 = options2->get<1>();
@@ -2517,7 +2517,7 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
     Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5"));
     ASSERT_TRUE(subnet);
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_EQ(1, options->size());
 
     // Get the search index. Index #1 is to search using option code.
@@ -2544,7 +2544,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
     ConstElementPtr x;
     std::map<std::string, std::string> params;
     params["name"] = "nis-servers";
-    params["space"] = "dhcp4";
+    params["space"] = DHCP4_OPTION_SPACE;
     // Option code 41 means nis-servers.
     params["code"] = "41";
     // Specify option values in a CSV (user friendly) format.
@@ -2561,7 +2561,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5"));
     ASSERT_TRUE(subnet);
     OptionContainerPtr options =
-        subnet->getCfgOption()->getAll("dhcp4");
+        subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
@@ -2746,13 +2746,13 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
 
     // We should have one option available.
     OptionContainerPtr options =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
     OptionDescriptor desc = CfgMgr::instance().getStagingCfg()->
-        getCfgOption()->get("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+        getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
     EXPECT_TRUE(desc.option_);
     EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option_->getType());
 

+ 4 - 4
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -402,7 +402,7 @@ TEST_F(Dhcpv4SrvTest, initResponse) {
     // client-id echo is optional
     // rai echo is done in relayAgentInfoEcho
     // Do subnet selection option
-    OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(Option::V4,
+    OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                            DHO_SUBNET_SELECTION);
     ASSERT_TRUE(sbnsel_def);
     OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
@@ -2170,7 +2170,7 @@ TEST_F(Dhcpv4SrvTest, relayLinkSelect) {
     dis->addOption(clientid);
 
     // Let's create a Relay Agent Information option
-    OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(Option::V4,
+    OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                         DHO_DHCP_AGENT_OPTIONS);
     ASSERT_TRUE(rai_def);
     OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
@@ -2197,7 +2197,7 @@ TEST_F(Dhcpv4SrvTest, relayLinkSelect) {
     EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis));
 
     // Subnet select option has a lower precedence
-    OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(Option::V4,
+    OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                            DHO_SUBNET_SELECTION);
     ASSERT_TRUE(sbnsel_def);
     OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
@@ -2280,7 +2280,7 @@ TEST_F(Dhcpv4SrvTest, subnetSelect) {
     dis->addOption(clientid);
 
     // Let's create a Subnet Selection option
-    OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(Option::V4,
+    OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                            DHO_SUBNET_SELECTION);
     ASSERT_TRUE(sbnsel_def);
     OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));

+ 5 - 5
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -62,7 +62,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     // Add Router option.
     Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
     opt_routers->setAddress(IOAddress("192.0.2.2"));
-    subnet_->getCfgOption()->add(opt_routers, false, "dhcp4");
+    subnet_->getCfgOption()->add(opt_routers, false, DHCP4_OPTION_SPACE);
 
     CfgMgr::instance().clear();
     CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
@@ -110,24 +110,24 @@ void Dhcpv4SrvTest::configureRequestedOptions() {
         option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS));
     option_dns_servers->addAddress(IOAddress("192.0.2.1"));
     option_dns_servers->addAddress(IOAddress("192.0.2.100"));
-    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, "dhcp4"));
+    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, DHCP4_OPTION_SPACE));
 
     // domain-name
     OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
     OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
     option_domain_name->writeFqdn("example.com");
-    subnet_->getCfgOption()->add(option_domain_name, false, "dhcp4");
+    subnet_->getCfgOption()->add(option_domain_name, false, DHCP4_OPTION_SPACE);
 
     // log-servers
     Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS));
     option_log_servers->addAddress(IOAddress("192.0.2.2"));
     option_log_servers->addAddress(IOAddress("192.0.2.10"));
-    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, "dhcp4"));
+    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, DHCP4_OPTION_SPACE));
 
     // cookie-servers
     Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS));
     option_cookie_servers->addAddress(IOAddress("192.0.2.1"));
-    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, "dhcp4"));
+    ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, DHCP4_OPTION_SPACE));
 }
 
 void Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {

+ 12 - 0
src/bin/dhcp6/dhcp6.spec

@@ -561,6 +561,18 @@
                            "item_default": 128
                        },
                        {
+                          "item_name": "excluded-prefix",
+                          "item_type": "string",
+                          "item_optional": true,
+                          "item_default": ""
+                       },
+                       {
+                           "item_name": "excluded-prefix-len",
+                           "item_type": "integer",
+                           "item_optional": true,
+                           "item_default": 128
+                       },
+                       {
                            "item_name": "option-data",
                            "item_type": "list",
                            "item_optional": false,

+ 65 - 9
src/bin/dhcp6/dhcp6_srv.cc

@@ -20,6 +20,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaprefix.h>
 #include <dhcp/option6_status_code.h>
+#include <dhcp/option6_pdexclude.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_vendor_class.h>
@@ -70,6 +71,7 @@
 #include <boost/algorithm/string/join.hpp>
 #include <boost/algorithm/string/split.hpp>
 
+#include <algorithm>
 #include <stdlib.h>
 #include <time.h>
 #include <iomanip>
@@ -873,6 +875,7 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
 
 void
 Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                  AllocEngine::ClientContext6& ctx,
                                   const CfgOptionList& co_list) {
 
     // Client requests some options using ORO option. Try to
@@ -881,18 +884,39 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
         boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
         (question->getOption(D6O_ORO));
 
-    // Option ORO not found? We're done here then.
-    if (!option_oro || co_list.empty()) {
+    // If there is no ORO option, there is nothing more to do.
+    if (!option_oro) {
         return;
+
     }
 
     // Get the list of options that client requested.
     const std::vector<uint16_t>& requested_opts = option_oro->getValues();
+
+    if (co_list.empty()) {
+        // If there are no options configured, we at least have to check if
+        // the client has requested PD exclude, which is configured as
+        // part of the pool configuration.
+        ctx.pd_exclude_requested_ = (std::find(requested_opts.begin(),
+                                               requested_opts.end(),
+                                               D6O_PD_EXCLUDE) !=
+                                     requested_opts.end());
+        return;
+    }
+
     BOOST_FOREACH(uint16_t opt, requested_opts) {
+        // Prefix Exclude option requires special handling, as it can
+        // be configured as part of the pool configuration.
+        if (opt == D6O_PD_EXCLUDE) {
+            ctx.pd_exclude_requested_ = true;
+            // Prefix Exclude can only be included in the IA Prefix option
+            // of IA_PD. Thus there is nothing more to do here.
+            continue;
+        }
         // Iterate on the configured option list
         for (CfgOptionList::const_iterator copts = co_list.begin();
              copts != co_list.end(); ++copts) {
-            OptionDescriptor desc = (*copts)->get("dhcp6", opt);
+            OptionDescriptor desc = (*copts)->get(DHCP6_OPTION_SPACE, opt);
             // Got it: add it and jump to the outer loop
             if (desc.option_) {
                 answer->addOption(desc.option_);
@@ -1569,6 +1593,20 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, const Pkt6Ptr& answer,
                                          (*l)->prefixlen_, (*l)->preferred_lft_,
                                          (*l)->valid_lft_));
             ia_rsp->addOption(addr);
+
+            if (ctx.pd_exclude_requested_) {
+                // PD exclude option has been requested via ORO, thus we need to
+                // include it if the pool configuration specifies this option.
+                Pool6Ptr pool = boost::dynamic_pointer_cast<
+                    Pool6>(subnet->getPool(Lease::TYPE_PD, (*l)->addr_));
+                if (pool && pool->getExcludedPrefixLength() > 0) {
+                    OptionPtr opt(new Option6PDExclude((*l)->addr_,
+                                                       (*l)->prefixlen_,
+                                                       pool->getExcludedPrefix(),
+                                                       pool->getExcludedPrefixLength()));
+                    addr->addOption(opt);
+                }
+            }
         }
 
         // It would be possible to insert status code=0(success) as well,
@@ -1839,10 +1877,28 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
 
     // For all the leases we have now, add the IAPPREFIX with non-zero lifetimes
     for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
+
         Option6IAPrefixPtr prf(new Option6IAPrefix(D6O_IAPREFIX,
                                (*l)->addr_, (*l)->prefixlen_,
                                (*l)->preferred_lft_, (*l)->valid_lft_));
         ia_rsp->addOption(prf);
+
+        if (ctx.pd_exclude_requested_) {
+            // PD exclude option has been requested via ORO, thus we need to
+            // include it if the pool configuration specifies this option.
+            Pool6Ptr pool = boost::dynamic_pointer_cast<
+                Pool6>(subnet->getPool(Lease::TYPE_PD, (*l)->addr_));
+
+            if (pool && pool->getExcludedPrefixLength() > 0) {
+                OptionPtr opt(new Option6PDExclude((*l)->addr_,
+                                                   (*l)->prefixlen_,
+                                                   pool->getExcludedPrefix(),
+                                                   pool->getExcludedPrefixLength()));
+                prf->addOption(opt);
+            }
+        }
+
+
         LOG_INFO(lease6_logger, DHCP6_PD_LEASE_RENEW)
             .arg(query->getLabel())
             .arg((*l)->addr_.toText())
@@ -2337,7 +2393,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     CfgOptionList co_list;
     buildCfgOptionList(solicit, ctx, co_list);
     appendDefaultOptions(solicit, response, co_list);
-    appendRequestedOptions(solicit, response, co_list);
+    appendRequestedOptions(solicit, response, ctx, co_list);
     appendRequestedVendorOptions(solicit, response, ctx, co_list);
 
     // Only generate name change requests if sending a Reply as a result
@@ -2368,7 +2424,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     CfgOptionList co_list;
     buildCfgOptionList(request, ctx, co_list);
     appendDefaultOptions(request, reply, co_list);
-    appendRequestedOptions(request, reply, co_list);
+    appendRequestedOptions(request, reply, ctx, co_list);
     appendRequestedVendorOptions(request, reply, ctx, co_list);
 
     generateFqdn(reply);
@@ -2396,7 +2452,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     CfgOptionList co_list;
     buildCfgOptionList(renew, ctx, co_list);
     appendDefaultOptions(renew, reply, co_list);
-    appendRequestedOptions(renew, reply, co_list);
+    appendRequestedOptions(renew, reply, ctx, co_list);
     appendRequestedVendorOptions(renew, reply, ctx, co_list);
 
     generateFqdn(reply);
@@ -2424,7 +2480,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
     CfgOptionList co_list;
     buildCfgOptionList(rebind, ctx, co_list);
     appendDefaultOptions(rebind, reply, co_list);
-    appendRequestedOptions(rebind, reply, co_list);
+    appendRequestedOptions(rebind, reply, ctx, co_list);
     appendRequestedVendorOptions(rebind, reply, ctx, co_list);
 
     generateFqdn(reply);
@@ -2457,7 +2513,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
     CfgOptionList co_list;
     buildCfgOptionList(confirm, ctx, co_list);
     appendDefaultOptions(confirm, reply, co_list);
-    appendRequestedOptions(confirm, reply, co_list);
+    appendRequestedOptions(confirm, reply, ctx, co_list);
     appendRequestedVendorOptions(confirm, reply, ctx, co_list);
     // Indicates if at least one address has been verified. If no addresses
     // are verified it means that the client has sent no IA_NA options
@@ -2862,7 +2918,7 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
     appendDefaultOptions(inf_request, reply, co_list);
 
     // Try to assign options that were requested by the client.
-    appendRequestedOptions(inf_request, reply, co_list);
+    appendRequestedOptions(inf_request, reply, ctx, co_list);
 
     // Try to assigne vendor options that were requested by the client.
     appendRequestedVendorOptions(inf_request, reply, ctx, co_list);

+ 5 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -475,8 +475,13 @@ protected:
     ///
     /// @param question client's message
     /// @param answer server's message (options will be added here)
+    /// @param [out] ctx client context. This method sets the
+    /// ctx.pd_exclude_requested_ field to 'true' if the Prefix Exclude
+    /// option has been requested.
+    ///
     /// @param co_list configured option list
     void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                AllocEngine::ClientContext6& ctx,
                                 const CfgOptionList& co_list);
 
     /// @brief Appends requested vendor options to server's answer.

+ 15 - 8
src/bin/dhcp6/json_config_parser.cc

@@ -174,11 +174,12 @@ public:
         BOOST_FOREACH(ConfigPair param, pd_pool_->mapValue()) {
             std::string entry(param.first);
             ParserPtr parser;
-            if (entry == "prefix") {
+            if (entry == "prefix" || entry =="excluded-prefix") {
                 StringParserPtr str_parser(new StringParser(entry,
                                                             string_values_));
                 parser = str_parser;
-            } else if (entry == "prefix-len" || entry == "delegated-len") {
+            } else if (entry == "prefix-len" || entry == "delegated-len" ||
+                       entry == "excluded-prefix-len") {
                 Uint32ParserPtr code_parser(new Uint32Parser(entry,
                                                              uint32_values_));
                 parser = code_parser;
@@ -200,13 +201,18 @@ public:
         // Try to obtain the pool parameters. It will throw an exception if any
         // of the required parameters are not present or invalid.
         try {
-            std::string addr_str = string_values_->getParam("prefix");
-            uint32_t prefix_len = uint32_values_->getParam("prefix-len");
-            uint32_t delegated_len = uint32_values_->getParam("delegated-len");
+            const std::string addr_str = string_values_->getParam("prefix");
+            const uint32_t prefix_len = uint32_values_->getParam("prefix-len");
+            const uint32_t delegated_len = uint32_values_->getParam("delegated-len");
+            const std::string excluded_prefix_str =
+                string_values_->getOptionalParam("excluded-prefix", "::");
+            const uint32_t excluded_prefix_len =
+                uint32_values_->getOptionalParam("excluded-prefix-len", 0);
 
             // Attempt to construct the local pool.
-            pool_.reset(new Pool6(Lease::TYPE_PD, IOAddress(addr_str),
-                                  prefix_len, delegated_len));
+            pool_.reset(new Pool6(IOAddress(addr_str), prefix_len,
+                                  delegated_len, IOAddress(excluded_prefix_str),
+                                  excluded_prefix_len));
             // Merge options specified for a pool into pool configuration.
             options_->copyTo(*pool_->getCfgOption());
         } catch (const std::exception& ex) {
@@ -634,7 +640,8 @@ public:
                 }
 
                 if (!code) {
-                    OptionDefinitionPtr def = LibDHCP::getOptionDef(Option::V6, option_str);
+                    const OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE,
+                                                                          option_str);
                     if (def) {
                         code = def->getCode();
                     } else {

+ 78 - 24
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -146,7 +146,7 @@ public:
         std::map<std::string, std::string> params;
         if (parameter == "name") {
             params["name"] = param_value;
-            params["space"] = "dhcp6";
+            params["space"] = DHCP6_OPTION_SPACE;
             params["code"] = "38";
             params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
@@ -158,19 +158,19 @@ public:
             params["csv-format"] = "False";
         } else if (parameter == "code") {
             params["name"] = "subscriber-id";
-            params["space"] = "dhcp6";
+            params["space"] = DHCP6_OPTION_SPACE;
             params["code"] = param_value;
             params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "data") {
             params["name"] = "subscriber-id";
-            params["space"] = "dhcp6";
+            params["space"] = DHCP6_OPTION_SPACE;
             params["code"] = "38";
             params["data"] = param_value;
             params["csv-format"] = "False";
         } else if (parameter == "csv-format") {
             params["name"] = "subscriber-id";
-            params["space"] = "dhcp6";
+            params["space"] = DHCP6_OPTION_SPACE;
             params["code"] = "38";
             params["data"] = "ABCDEF0105";
             params["csv-format"] = param_value;
@@ -260,7 +260,7 @@ public:
                           << " does not exist in Config Manager";
         }
         OptionContainerPtr options =
-            subnet->getCfgOption()->getAll("dhcp6");
+            subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
         if (expected_options_count != options->size()) {
             ADD_FAILURE() << "The number of options in the subnet '"
                           << subnet_address.toText() << "' is different "
@@ -383,7 +383,7 @@ public:
     template<typename ReturnType>
     ReturnType
     retrieveOption(const Host& host, const uint16_t option_code) const {
-        return (retrieveOption<ReturnType>(host, "dhcp6", option_code));
+        return (retrieveOption<ReturnType>(host, DHCP6_OPTION_SPACE, option_code));
     }
 
     /// @brief Retrieve an option associated with a host.
@@ -1378,6 +1378,60 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
 }
 
+// This test verifies that it is possible to specify a prefix pool with an
+// excluded prefix (see RFC6603).
+TEST_F(Dhcp6ParserTest, pdPoolPrefixExclude) {
+
+    ConstElementPtr x;
+
+    // Define a single valid pd pool.
+    string config =
+        "{ " + genIfaceConfig() + ","
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"subnet\": \"2001:db8:1::/64\","
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"3000::\", "
+        "          \"prefix-len\": 48, "
+        "          \"delegated-len\": 64,"
+        "          \"excluded-prefix\": \"3000:1::\","
+        "          \"excluded-prefix-len\": 72"
+        "        } ],"
+        "\"valid-lifetime\": 4000 }"
+        "] }";
+
+    // Convert the JSON string into Elements.
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(config));
+
+    // Verify that DHCP6 configuration processing succeeds.
+    // Returned value must be non-empty ConstElementPtr to config result.
+    // rcode should be 0 which indicates successful configuration processing.
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 0);
+
+    // Test that we can retrieve the subnet.
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+    ASSERT_TRUE(subnet);
+
+    // Fetch the collection of PD pools.  It should have 1 entry.
+    PoolCollection pc;
+    ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+    EXPECT_EQ(1, pc.size());
+
+    // Get a pointer to the pd pool instance, and verify its contents.
+    Pool6Ptr p6;
+    ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[0]));
+    ASSERT_TRUE(p6);
+    EXPECT_EQ("3000::", p6->getFirstAddress().toText());
+    EXPECT_EQ(64, p6->getLength());
+    EXPECT_EQ("3000:1::", p6->getExcludedPrefix().toText());
+    EXPECT_EQ(72, static_cast<unsigned>(p6->getExcludedPrefixLength()));
+}
+
 // Goal of this test is verify that a list of PD pools can be configured.
 // It also verifies that a subnet may be configured with both regular pools
 // and pd pools.
@@ -2059,7 +2113,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     ElementPtr json = Element::fromJSON(config);
 
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
-        getCfgOptionDef()->get("dhcp6", 100);
+        getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 100);
     ASSERT_FALSE(def);
 
     // Use the configuration string to create new option definition.
@@ -2070,7 +2124,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
 
     // The option definition should now be available in the CfgMgr.
     def = CfgMgr::instance().getStagingCfg()->
-        getCfgOptionDef()->get("dhcp6", 100);
+        getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 100);
     ASSERT_TRUE(def);
 
     // Check the option data.
@@ -2121,7 +2175,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     checkResult(status, 0);
 
     def = CfgMgr::instance().getStagingCfg()->
-        getCfgOptionDef()->get("dhcp6", 63);
+        getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 63);
     ASSERT_TRUE(def);
 
     // Check the option data.
@@ -2162,10 +2216,10 @@ TEST_F(Dhcp6ParserTest, optionDataDefaultsGlobal) {
     Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
         selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(0, options->size());
 
-    options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6");
+    options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
@@ -2231,13 +2285,13 @@ TEST_F(Dhcp6ParserTest, optionDataDefaultsSubnet) {
 
     // These options are subnet options
     OptionContainerPtr options =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6");
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(0, options->size());
 
     Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
         selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
-    options = subnet->getCfgOption()->getAll("dhcp6");
+    options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
@@ -2324,7 +2378,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     // Options should be now available
     // Try to get the option from the space dhcp6.
     OptionDescriptor desc1 =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp6", 38);
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP6_OPTION_SPACE, 38);
     ASSERT_TRUE(desc1.option_);
     EXPECT_EQ(38, desc1.option_->getType());
     // Try to get the option from the space isc.
@@ -2456,13 +2510,13 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
 
     // We should have one option available.
     OptionContainerPtr options =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6");
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
     OptionDescriptor desc =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp6", 100);
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP6_OPTION_SPACE, 100);
     EXPECT_TRUE(desc.option_);
     EXPECT_EQ(100, desc.option_->getType());
 
@@ -2514,7 +2568,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     Subnet6Ptr subnet1 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
         selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet1);
-    OptionContainerPtr options1 = subnet1->getCfgOption()->getAll("dhcp6");
+    OptionContainerPtr options1 = subnet1->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(1, options1->size());
 
     // Get the search index. Index #1 is to search using option code.
@@ -2540,7 +2594,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     Subnet6Ptr subnet2 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
         selectSubnet(IOAddress("2001:db8:2::4"), classify_);
     ASSERT_TRUE(subnet2);
-    OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp6");
+    OptionContainerPtr options2 = subnet2->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(1, options2->size());
 
     const OptionContainerTypeIndex& idx2 = options2->get<1>();
@@ -2711,7 +2765,7 @@ TEST_F(Dhcp6ParserTest, optionDataBoolean) {
     // Create configuration. Use standard option 1000.
     std::map<std::string, std::string> params;
     params["name"] = "bool-option";
-    params["space"] = "dhcp6";
+    params["space"] = DHCP6_OPTION_SPACE;
     params["code"] = "1000";
     params["data"] = "true";
     params["csv-format"] = "true";
@@ -2857,7 +2911,7 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
         selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+    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.
@@ -2885,7 +2939,7 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     ConstElementPtr x;
     std::map<std::string, std::string> params;
     params["name"] = "ia-na";
-    params["space"] = "dhcp6";
+    params["space"] = DHCP6_OPTION_SPACE;
     // Option code 3 means OPTION_IA_NA.
     params["code"] = "3";
     params["data"] = "12345, 6789, 1516";
@@ -2900,7 +2954,7 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
         selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+    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.
@@ -3155,12 +3209,12 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(1, options->size());
 
     // Get the option.
-    OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", D6O_VENDOR_OPTS);
+    OptionDescriptor desc = subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_VENDOR_OPTS);
     EXPECT_TRUE(desc.option_);
     EXPECT_EQ(D6O_VENDOR_OPTS, desc.option_->getType());
 

+ 1 - 1
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1838,7 +1838,7 @@ TEST_F(Dhcpv6SrvTest, relayOverride) {
 /// @param payload specified payload (0 = fill payload with repeating option code)
 /// @return RSOO with nested options
 OptionPtr createRSOO(const std::vector<uint16_t>& codes, uint8_t payload = 0) {
-    OptionDefinitionPtr def = LibDHCP::getOptionDef(Option::V6, D6O_RSOO);
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_RSOO);
     if (!def) {
         isc_throw(BadValue, "Can't find RSOO definition");
     }

+ 116 - 1
src/bin/dhcp6/tests/renew_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
+#include <dhcp/option6_pdexclude.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/tests/dhcp6_message_test.h>
@@ -53,6 +54,13 @@ namespace {
 ///   - an option with unique value specified for each pool, so as it is
 ///     possible to test that pool specific options can be assigned.
 ///
+/// - Configuration 5:
+///   - addresses and prefixes
+///   - 1 subnet with one address pool and one prefix pool
+///   - address pool: 2001:db8:1::/64
+///   - prefix pool: 3000::/72
+///   - excluded prefix 3000::1000/120 in a prefix pool.
+///
 const char* RENEW_CONFIGS[] = {
 // Configuration 0
     "{ \"interfaces-config\": {"
@@ -188,7 +196,29 @@ const char* RENEW_CONFIGS[] = {
         "    \"interface\": \"eth0\""
         " } ],"
         "\"valid-lifetime\": 4000"
-    "}"
+    "}",
+
+// Configuration 5
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"3000::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80,"
+        "          \"excluded-prefix\": \"3000::1000\","
+        "          \"excluded-prefix-len\": 120"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }
 };
 
 /// @brief Test fixture class for testing Renew.
@@ -269,6 +299,91 @@ TEST_F(RenewTest, requestPrefixInRenew) {
     EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
 }
 
+// Test that it is possible to renew a prefix lease with a Prefix Exclude
+// option being included during renew.
+TEST_F(RenewTest, renewWithExcludedPrefix) {
+    Dhcp6Client client;
+
+    // Configure client to request IA_NA and IA_PD.
+    client.requestAddress(na_iaid_);
+    client.requestPrefix(pd_iaid_);
+
+    // Request Prefix Exclude option.
+    client.requestOption(D6O_PD_EXCLUDE);
+
+    // Configure the server with NA pools only.
+    ASSERT_NO_THROW(configure(RENEW_CONFIGS[2], *client.getServer()));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Simulate aging of leases.
+    client.fastFwdTime(1000);
+
+    // Make sure that the client has acquired NA lease.
+    std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+    // The client should also acquire a PD lease.
+    std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_EQ(1, leases_client_pd.size());
+    ASSERT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+    // Send Renew message to the server, including IA_NA and IA_PD.
+    ASSERT_NO_THROW(client.doRenew());
+
+    std::vector<Lease6> leases_client_na_renewed =
+        client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na_renewed.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+    std::vector<Lease6> leases_client_pd_renewed =
+        client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_EQ(1, leases_client_pd_renewed.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+    // Make sure that Prefix Exclude option hasn't been included.
+    OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+    ASSERT_TRUE(option);
+    option = option->getOption(D6O_IAPREFIX);
+    ASSERT_TRUE(option);
+    option = option->getOption(D6O_PD_EXCLUDE);
+    ASSERT_FALSE(option);
+
+    // Reconfigure the server to use the prefix pool with excluded prefix.
+    configure(RENEW_CONFIGS[4], *client.getServer());
+
+    // Send Renew message to the server, including IA_NA and IA_PD.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // Make sure that the client has acquired NA lease.
+    leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na_renewed.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+    // Make sure that the client has acquired PD lease.
+    leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_EQ(1, leases_client_pd_renewed.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+    // The leases should have been renewed.
+    EXPECT_EQ(1000, leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_);
+    EXPECT_EQ(1000, leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_);
+
+    // This time, the Prefix Exclude option should be included.
+    option = client.getContext().response_->getOption(D6O_IA_PD);
+    ASSERT_TRUE(option);
+    option = option->getOption(D6O_IAPREFIX);
+    ASSERT_TRUE(option);
+    option = option->getOption(D6O_PD_EXCLUDE);
+    ASSERT_TRUE(option);
+    Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+    ASSERT_TRUE(pd_exclude);
+    EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix().toText());
+    EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
 // This test verifies that the client can request a prefix delegation
 // with a hint, while it is renewing an address lease.
 TEST_F(RenewTest, requestPrefixInRenewUseHint) {

+ 68 - 1
src/bin/dhcp6/tests/sarr_unittest.cc

@@ -7,6 +7,7 @@
 #include <config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_pdexclude.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -48,6 +49,11 @@ namespace {
 ///   - an option with unique value specified for each pool, so as it is
 ///     possible to test that pool specific options can be assigned.
 ///
+/// Configuration 3:
+///   - one subnet 3000::/32 used on eth0 interface
+///   - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
+///   - Excluded Prefix specified (RFC 6603).
+///
 const char* CONFIGS[] = {
     // Configuration 0
     "{ \"interfaces-config\": {"
@@ -153,7 +159,28 @@ const char* CONFIGS[] = {
         "    \"interface\": \"eth0\""
         " } ],"
         "\"valid-lifetime\": 4000"
-    "}"
+    "}",
+
+    // Configuration 3
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:3::\", "
+        "          \"prefix-len\": 48, "
+        "          \"delegated-len\": 64,"
+        "          \"excluded-prefix\": \"2001:db8:3::1000\","
+        "          \"excluded-prefix-len\": 120"
+        "        } ],"
+        "    \"subnet\": \"3000::/32\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
 };
 
 /// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
@@ -305,6 +332,46 @@ TEST_F(SARRTest, optionsInheritance) {
     ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
 }
 
+/// This test verifies that it is possible to specify an excluded prefix
+/// (RFC 6603) and send it back to the client requesting prefix delegation.
+TEST_F(SARRTest, directClientExcludedPrefix) {
+    Dhcp6Client client;
+    // Configure client to request IA_PD.
+    client.requestPrefix();
+    client.requestOption(D6O_PD_EXCLUDE);
+    configure(CONFIGS[2], *client.getServer());
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(1, subnets->size());
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+    // Server should have assigned a prefix.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client = client.getLease(0);
+    EXPECT_EQ(64, lease_client.prefixlen_);
+    EXPECT_EQ(3000, lease_client.preferred_lft_);
+    EXPECT_EQ(4000, lease_client.valid_lft_);
+    Lease6Ptr lease_server = checkLease(lease_client);
+    // Check that the server recorded the lease.
+    ASSERT_TRUE(lease_server);
+
+    OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+    ASSERT_TRUE(option);
+    Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(option);
+    ASSERT_TRUE(ia);
+    option = ia->getOption(D6O_IAPREFIX);
+    ASSERT_TRUE(option);
+    Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast<Option6IAPrefix>(option);
+    ASSERT_TRUE(pd_option);
+    option = pd_option->getOption(D6O_PD_EXCLUDE);
+    ASSERT_TRUE(option);
+    Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+    ASSERT_TRUE(pd_exclude);
+    EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix().toText());
+    EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
 // Check that when the client includes the Rapid Commit option in its
 // Solicit, the server responds with Reply and commits the lease.
 TEST_F(SARRTest, rapidCommitEnable) {

+ 1 - 0
src/lib/dhcp/Makefile.am

@@ -25,6 +25,7 @@ libkea_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
 libkea_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
 libkea_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
 libkea_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
+libkea_dhcp___la_SOURCES += option6_pdexclude.cc option6_pdexclude.h
 libkea_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
 libkea_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
 libkea_dhcp___la_SOURCES += option6_status_code.cc option6_status_code.h

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

@@ -79,10 +79,10 @@
 #define D6O_CLIENT_ARCH_TYPE                    61 /* RFC5970 */
 #define D6O_NII                                 62 /* RFC5970 */
 //#define D6O_GEOLOCATION                         63 /* RFC6225 */
-//#define D6O_AFTR_NAME                           64 /* RFC6334 */
+#define D6O_AFTR_NAME                           64 /* RFC6334 */
 #define D6O_ERP_LOCAL_DOMAIN_NAME               65 /* RFC6440 */
 #define D6O_RSOO                                66 /* RFC6422 */
-//#define D6O_PD_EXCLUDE                          67 /* RFC6603 */
+#define D6O_PD_EXCLUDE                          67 /* RFC6603 */
 //#define D6O_VSS                                 68 /* RFC6607 */
 //#define D6O_MIP6_IDINF                          69 /* RFC6610 */
 //#define D6O_MIP6_UDINF                          70 /* RFC6610 */
@@ -104,14 +104,14 @@
 //#define D6O_V6_PCP_SERVER                       86 /* RFC7291 */
 #define D6O_DHCPV4_MSG                          87 /* RFC7341 */
 #define D6O_DHCPV4_O_DHCPV6_SERVER              88 /* RFC7341 */
-//#define D6O_S46_RULE                            89 /* RFC7598 */
-//#define D6O_S46_BR                              90 /* RFC7598 */
-//#define D6O_S46_DMR                             91 /* RFC7598 */
-//#define D6O_S46_V4V6BIND                        92 /* RFC7598 */
-//#define D6O_S46_PORTPARAMS                      93 /* RFC7598 */
-//#define D6O_S46_CONT_MAPE                       94 /* RFC7598 */
-//#define D6O_S46_CONT_MAPT                       95 /* RFC7598 */
-//#define D6O_S46_CONT_LW                         96 /* RFC7598 */
+#define D6O_S46_RULE                            89 /* RFC7598 */
+#define D6O_S46_BR                              90 /* RFC7598 */
+#define D6O_S46_DMR                             91 /* RFC7598 */
+#define D6O_S46_V4V6BIND                        92 /* RFC7598 */
+#define D6O_S46_PORTPARAMS                      93 /* RFC7598 */
+#define D6O_S46_CONT_MAPE                       94 /* RFC7598 */
+#define D6O_S46_CONT_MAPT                       95 /* RFC7598 */
+#define D6O_S46_CONT_LW                         96 /* RFC7598 */
 //#define D6O_4RD                                 97 /* RFC7600 */
 //#define D6O_4RD_MAP_RULE                        98 /* RFC7600 */
 //#define D6O_4RD_NON_MAP_RULE                    99 /* RFC7600 */

+ 3 - 2
src/lib/dhcp/docsis3_option_defs.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -58,7 +58,8 @@ const OptionDefParams DOCSIS3_V6_DEFS[] = {
 };
 
 /// Number of option definitions defined.
-const int DOCSIS3_V6_DEFS_SIZE  = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams);
+const int DOCSIS3_V6_DEFS_SIZE =
+    sizeof(DOCSIS3_V6_DEFS) / sizeof(DOCSIS3_V6_DEFS[0]);
 
 /// The class as specified in vendor-class option by the devices
 extern const char* DOCSIS3_CLASS_EROUTER;

+ 2 - 2
src/lib/dhcp/duid_factory.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -28,7 +28,7 @@ const size_t DUID_TYPE_LEN = 2;
 /// @brief Minimal length of the MAC address.
 const size_t MIN_MAC_LEN = 6;
 
-/// @brief Length of the enterprise if field.
+/// @brief Length of the enterprise ID field.
 const size_t ENTERPRISE_ID_LEN = 4;
 
 /// @brief Default length of the variable length identifier in the DUID-EN.

+ 50 - 55
src/lib/dhcp/libdhcp++.cc

@@ -45,8 +45,13 @@ OptionDefContainerPtr LibDHCP::v4option_defs_(new OptionDefContainer());
 // Static container with DHCPv6 option definitions.
 OptionDefContainerPtr LibDHCP::v6option_defs_(new OptionDefContainer());
 
+// Static container with option definitions grouped by option space.
+OptionDefContainers LibDHCP::option_defs_;
+
+// Static container with vendor option definitions for DHCPv4.
 VendorOptionDefContainers LibDHCP::vendor4_defs_;
 
+// Static container with vendor option definitions for DHCPv6.
 VendorOptionDefContainers LibDHCP::vendor6_defs_;
 
 // Static container with option definitions created in runtime.
@@ -70,23 +75,28 @@ void initOptionSpace(OptionDefContainerPtr& defs,
                      size_t params_size);
 
 const OptionDefContainerPtr&
-LibDHCP::getOptionDefs(const Option::Universe u) {
-    switch (u) {
-    case Option::V4:
-        if (v4option_defs_->empty()) {
-            initStdOptionDefs4();
-            initVendorOptsDocsis4();
-        }
+LibDHCP::getOptionDefs(const std::string& space) {
+    // If any of the containers is not initialized, it means that we haven't
+    // initialized option definitions at all.
+    if (v4option_defs_->empty()) {
+        initStdOptionDefs4();
+        initVendorOptsDocsis4();
+        initStdOptionDefs6();
+        initVendorOptsDocsis6();
+    }
+
+    if (space == DHCP4_OPTION_SPACE) {
         return (v4option_defs_);
-    case Option::V6:
-        if (v6option_defs_->empty()) {
-            initStdOptionDefs6();
-            initVendorOptsDocsis6();
-        }
+
+    } else if (space == DHCP6_OPTION_SPACE) {
         return (v6option_defs_);
-    default:
-        isc_throw(isc::BadValue, "invalid universe " << u << " specified");
     }
+
+    OptionDefContainers::const_iterator container = option_defs_.find(space);
+    if (container != option_defs_.end()) {
+        return (container->second);
+    }
+    return (null_option_def_container_);
 }
 
 const OptionDefContainerPtr&
@@ -127,8 +137,8 @@ LibDHCP::getVendorOption6Defs(const uint32_t vendor_id) {
 }
 
 OptionDefinitionPtr
-LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
-    const OptionDefContainerPtr& defs = getOptionDefs(u);
+LibDHCP::getOptionDef(const std::string& space, const uint16_t code) {
+    const OptionDefContainerPtr& defs = getOptionDefs(space);
     const OptionDefContainerTypeIndex& idx = defs->get<1>();
     const OptionDefContainerTypeRange& range = idx.equal_range(code);
     if (range.first != range.second) {
@@ -138,18 +148,16 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
 }
 
 OptionDefinitionPtr
-LibDHCP::getOptionDef(const Option::Universe u, const std::string& name) {
-    const OptionDefContainerPtr defs = getOptionDefs(u);
+LibDHCP::getOptionDef(const std::string& space, const std::string& name) {
+    const OptionDefContainerPtr defs = getOptionDefs(space);
     const OptionDefContainerNameIndex& idx = defs->get<2>();
     const OptionDefContainerNameRange& range = idx.equal_range(name);
     if (range.first != range.second) {
         return (*range.first);
     }
     return (OptionDefinitionPtr());
-
 }
 
-
 OptionDefinitionPtr
 LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
                             const std::string& name) {
@@ -249,34 +257,6 @@ LibDHCP::commitRuntimeOptionDefs() {
     runtime_option_defs_.commit();
 }
 
-bool
-LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
-    if (u == Option::V6) {
-        if (code < 79 &&
-            code != 10 &&
-            code != 35) {
-            return (true);
-        }
-
-    } else if (u == Option::V4) {
-        if (!(code == 84 ||
-              code == 96 ||
-              (code > 101 && code < 112) ||
-              code == 115 ||
-              code == 126 ||
-              code == 127 ||
-              (code > 146 && code < 150) ||
-              (code > 177 && code < 208) ||
-              (code > 213 && code <  220) ||
-              (code > 221 && code < 255))) {
-                return (true);
-            }
-
-    }
-
-    return (false);
-}
-
 OptionPtr
 LibDHCP::optionFactory(Option::Universe u,
                        uint16_t type,
@@ -312,7 +292,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
     size_t last_offset = 0;
 
     // Get the list of standard option definitions.
-    const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(Option::V6);
+    const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
     // Runtime option definitions for non standard option space and if
     // the definition doesn't exist within the standard option definitions.
     const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
@@ -448,7 +428,7 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
     size_t last_offset = 0;
 
     // Get the list of standard option definitions.
-    const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(Option::V4);
+    const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
     // Runtime option definitions for non standard option space and if
     // the definition doesn't exist within the standard option definitions.
     const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
@@ -832,27 +812,42 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
 
 void
 LibDHCP::initStdOptionDefs4() {
-    initOptionSpace(v4option_defs_, OPTION_DEF_PARAMS4, OPTION_DEF_PARAMS_SIZE4);
+    initOptionSpace(v4option_defs_, STANDARD_V4_OPTION_DEFINITIONS,
+                    STANDARD_V4_OPTION_DEFINITIONS_SIZE);
 }
 
 void
 LibDHCP::initStdOptionDefs6() {
-    initOptionSpace(v6option_defs_, OPTION_DEF_PARAMS6, OPTION_DEF_PARAMS_SIZE6);
+    initOptionSpace(v6option_defs_, STANDARD_V6_OPTION_DEFINITIONS,
+                    STANDARD_V6_OPTION_DEFINITIONS_SIZE);
+    initOptionSpace(option_defs_[MAPE_V6_OPTION_SPACE], MAPE_V6_OPTION_DEFINITIONS,
+                    MAPE_V6_OPTION_DEFINITIONS_SIZE);
+    initOptionSpace(option_defs_[MAPT_V6_OPTION_SPACE], MAPT_V6_OPTION_DEFINITIONS,
+                    MAPT_V6_OPTION_DEFINITIONS_SIZE);
+    initOptionSpace(option_defs_[LW_V6_OPTION_SPACE], LW_V6_OPTION_DEFINITIONS,
+                    LW_V6_OPTION_DEFINITIONS_SIZE);
+    initOptionSpace(option_defs_[V4V6_RULE_OPTION_SPACE], V4V6_RULE_OPTION_DEFINITIONS,
+                    V4V6_RULE_OPTION_DEFINITIONS_SIZE);
+    initOptionSpace(option_defs_[V4V6_BIND_OPTION_SPACE], V4V6_BIND_OPTION_DEFINITIONS,
+                    V4V6_BIND_OPTION_DEFINITIONS_SIZE);
 }
 
 void
 LibDHCP::initVendorOptsDocsis4() {
-    initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS, DOCSIS3_V4_DEFS_SIZE);
+    initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS,
+                    DOCSIS3_V4_DEFS_SIZE);
 }
 
 void
 LibDHCP::initVendorOptsDocsis6() {
-    initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS, DOCSIS3_V6_DEFS_SIZE);
+    initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS,
+                    DOCSIS3_V6_DEFS_SIZE);
 }
 
 void
 LibDHCP::initVendorOptsIsc6() {
-    initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_DEFS, ISC_V6_DEFS_SIZE);
+    initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_OPTION_DEFINITIONS,
+                    ISC_V6_OPTION_DEFINITIONS_SIZE);
 }
 
 uint32_t

+ 12 - 27
src/lib/dhcp/libdhcp++.h

@@ -27,38 +27,35 @@ public:
     /// Map of factory functions.
     typedef std::map<unsigned short, Option::Factory*>  FactoryMap;
 
-    /// @brief Return collection of option definitions.
+    /// @brief Returns collection of option definitions.
     ///
-    /// Method returns the collection of DHCP standard DHCP
-    /// option definitions.
-    /// @todo DHCPv4 option definitions are not implemented. For now
-    /// this function will throw isc::NotImplemented in case of attempt
-    /// to get option definitions for V4 universe.
+    /// This method returns a collection of option definitions for a specified
+    /// option space.
     ///
-    /// @param u universe of the options (V4 or V6).
+    /// @param space Option space.
     ///
     /// @return Pointer to a collection of option definitions.
-    static const OptionDefContainerPtr& getOptionDefs(const Option::Universe u);
+    static const OptionDefContainerPtr& getOptionDefs(const std::string& space);
 
     /// @brief Return the first option definition matching a
     /// particular option code.
     ///
-    /// @param u universe (V4 or V6)
+    /// @param space option space.
     /// @param code option code.
     ///
     /// @return reference to an option definition being requested
     /// or NULL pointer if option definition has not been found.
-    static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+    static OptionDefinitionPtr getOptionDef(const std::string& space,
                                             const uint16_t code);
 
     /// @brief Return the definition of option having a specified name.
     ///
-    /// @param u universe (v4 or V6)
+    /// @param space option space.
     /// @param name Option name.
     ///
     /// @return Pointer to the option definition or NULL pointer if option
     /// definition has not been found.
-    static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+    static OptionDefinitionPtr getOptionDef(const std::string& option_space,
                                             const std::string& name);
 
     /// @brief Returns vendor option definition for a given vendor-id and code
@@ -115,21 +112,6 @@ public:
     static OptionDefContainerPtr
     getRuntimeOptionDefs(const std::string& space);
 
-    /// @brief Check if the specified option is a standard option.
-    ///
-    /// @param u universe (V4 or V6)
-    /// @param code option code.
-    ///
-    /// @return true if the specified option is a standard option.
-    /// @todo We already create option definitions for the subset if
-    /// standard options. We are aiming that this function checks
-    /// the presence of the standard option definition and if it finds
-    /// it, then the true value is returned. However, at this point
-    /// this is not doable because some of the definitions (for less
-    /// important options) are not created yet.
-    static bool isStandardOption(const Option::Universe u,
-                                 const uint16_t code);
-
     /// @brief Factory function to create instance of option.
     ///
     /// Factory method creates instance of specified option. The option
@@ -377,6 +359,9 @@ private:
     /// Container with DHCPv6 option definitions.
     static OptionDefContainerPtr v6option_defs_;
 
+    /// Container that holds option definitions for various option spaces.
+    static OptionDefContainers option_defs_;
+
     /// Container for v4 vendor option definitions
     static VendorOptionDefContainers vendor4_defs_;
 

+ 4 - 3
src/lib/dhcp/option6_ia.cc

@@ -8,6 +8,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_ia.h>
+#include <dhcp/option_space.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 
@@ -24,13 +25,13 @@ namespace dhcp {
 Option6IA::Option6IA(uint16_t type, uint32_t iaid)
     :Option(Option::V6, type), iaid_(iaid), t1_(0), t2_(0) {
 
-    // IA_TA has different layout than IA_NA and IA_PD. We can't sue this class
+    // IA_TA has different layout than IA_NA and IA_PD. We can't use this class
     if (type == D6O_IA_TA) {
         isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has "
                   "a different layout");
     }
 
-    setEncapsulatedSpace("dhcp6");
+    setEncapsulatedSpace(DHCP6_OPTION_SPACE);
 }
 
 Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
@@ -43,7 +44,7 @@ Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
                   "a different layout");
     }
 
-    setEncapsulatedSpace("dhcp6");
+    setEncapsulatedSpace(DHCP6_OPTION_SPACE);
 
     unpack(begin, end);
 }

+ 3 - 2
src/lib/dhcp/option6_iaaddr.cc

@@ -10,6 +10,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_space.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 
@@ -29,7 +30,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr
                              uint32_t pref, uint32_t valid)
     :Option(V6, type), addr_(addr), preferred_(pref),
      valid_(valid) {
-    setEncapsulatedSpace("dhcp6");
+    setEncapsulatedSpace(DHCP6_OPTION_SPACE);
     if (!addr.isV6()) {
         isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
     }
@@ -38,7 +39,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr
 Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
                              OptionBuffer::const_iterator end)
     :Option(V6, type), addr_("::") {
-    setEncapsulatedSpace("dhcp6");
+    setEncapsulatedSpace(DHCP6_OPTION_SPACE);
     unpack(begin, end);
 }
 

+ 3 - 2
src/lib/dhcp/option6_iaprefix.cc

@@ -10,6 +10,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_iaprefix.h>
+#include <dhcp/option_space.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 
@@ -28,7 +29,7 @@ namespace dhcp {
 Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& prefix,
                                  uint8_t prefix_len, uint32_t pref, uint32_t valid)
     :Option6IAAddr(type, prefix, pref, valid), prefix_len_(prefix_len) {
-    setEncapsulatedSpace("dhcp6");
+    setEncapsulatedSpace(DHCP6_OPTION_SPACE);
     // Option6IAAddr will check if prefix is IPv6 and will throw if it is not
     if (prefix_len > 128) {
         isc_throw(BadValue, static_cast<unsigned>(prefix_len)
@@ -40,7 +41,7 @@ Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress&
 Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
                              OptionBuffer::const_iterator end)
     :Option6IAAddr(type, begin, end) {
-    setEncapsulatedSpace("dhcp6");
+    setEncapsulatedSpace(DHCP6_OPTION_SPACE);
     unpack(begin, end);
 }
 

+ 227 - 0
src/lib/dhcp/option6_pdexclude.cc

@@ -0,0 +1,227 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option6_pdexclude.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <boost/dynamic_bitset.hpp>
+#include <iostream>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                                   const uint8_t delegated_prefix_length,
+                                   const isc::asiolink::IOAddress& excluded_prefix,
+                                   const uint8_t excluded_prefix_length)
+    : Option(V6, D6O_PD_EXCLUDE),
+      delegated_prefix_(delegated_prefix),
+      delegated_prefix_length_(delegated_prefix_length),
+      excluded_prefix_(excluded_prefix),
+      excluded_prefix_length_(excluded_prefix_length) {
+
+    // Expecting v6 prefixes of sane length.
+    if (!delegated_prefix_.isV6() || !excluded_prefix_.isV6() ||
+        (delegated_prefix_length_ > 128) || (excluded_prefix_length_ > 128)) {
+        isc_throw(BadValue, "invalid delegated or excluded prefix values specified: "
+                  << delegated_prefix_ << "/"
+                  << static_cast<int>(delegated_prefix_length_) << ", "
+                  << excluded_prefix_ << "/"
+                  << static_cast<int>(excluded_prefix_length_));
+    }
+
+    // Excluded prefix must be longer than the delegated prefix.
+    if (excluded_prefix_length_ <= delegated_prefix_length_) {
+        isc_throw(BadValue, "length of the excluded prefix "
+                  << excluded_prefix_ << "/"
+                  << static_cast<int>(excluded_prefix_length_)
+                  << " must be greater than the length of the"
+                  " delegated prefix " << delegated_prefix_ << "/"
+                  << static_cast<int>(delegated_prefix_length_));
+    }
+
+    // Both prefixes must share common part with a length equal to the
+    // delegated prefix length.
+    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+    boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(),
+                                                         delegated_prefix_bytes.rend());
+
+    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
+    boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(),
+                                                        excluded_prefix_bytes.rend());
+
+
+    // See RFC6603, section 4.2: assert(p1>>s == p2>>s)
+    const uint8_t delta = 128 - delegated_prefix_length;
+
+    if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) {
+        isc_throw(BadValue, "excluded prefix "
+                  << excluded_prefix_ << "/"
+                  << static_cast<int>(excluded_prefix_length_)
+                  << " must have the same common prefix part of "
+                  << static_cast<int>(delegated_prefix_length)
+                  << " as the delegated prefix "
+                  << delegated_prefix_ << "/"
+                  << static_cast<int>(delegated_prefix_length_));
+    }
+
+}
+
+Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                                   const uint8_t delegated_prefix_length,
+                                   OptionBufferConstIter begin,
+                                   OptionBufferConstIter end)
+    : Option(V6, D6O_PD_EXCLUDE),
+      delegated_prefix_(delegated_prefix),
+      delegated_prefix_length_(delegated_prefix_length),
+      excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
+      excluded_prefix_length_(0) {
+    unpack(begin, end);
+}
+
+OptionPtr
+Option6PDExclude::clone() const {
+    return (cloneInternal<Option6PDExclude>());
+}
+
+void
+Option6PDExclude::pack(isc::util::OutputBuffer& buf) const {
+    // Header = option code and length.
+    packHeader(buf);
+
+    // Excluded prefix length is always 1 byte long field.
+    buf.writeUint8(excluded_prefix_length_);
+
+    // Retrieve entire prefix and convert it to bit representation.
+    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
+    boost::dynamic_bitset<uint8_t> bits(excluded_prefix_bytes.rbegin(),
+                                        excluded_prefix_bytes.rend());
+
+    // Shifting prefix by delegated prefix length leaves us with only a
+    // subnet id part of the excluded prefix.
+    bits = bits << delegated_prefix_length_;
+
+    // Calculate subnet id length.
+    const uint8_t subnet_id_length = getSubnetIDLength();
+    for (uint8_t i = 0; i < subnet_id_length; ++i) {
+        // Retrieve bit representation of the current byte.
+        const boost::dynamic_bitset<uint8_t> first_byte = bits >> 120;
+        // Convert it to a numeric value.
+        uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());
+
+        // Zero padded bits follow when excluded_prefix_length_ is not divisible by 8.
+        if (i == subnet_id_length - 1) {
+            uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length_;
+            uint8_t mask = 0xFF;
+            mask <<= (8 - (length_delta % 8));
+            val &= mask;
+        }
+        // Store calculated value in a buffer.
+        buf.writeUint8(val);
+
+        // Go to the next byte.
+        bits <<= 8;
+    }
+}
+
+void
+Option6PDExclude::unpack(OptionBufferConstIter begin,
+                         OptionBufferConstIter end) {
+
+    // At this point we don't know the excluded prefix length, but the
+    // minimum requirement is that reminder of this option includes the
+    // excluded prefix length and at least 1 byte of the IPv6 subnet id.
+    if (std::distance(begin, end) < 2) {
+        isc_throw(BadValue, "truncated Prefix Exclude option");
+    }
+
+    // We can safely read the excluded prefix length and move forward.
+    excluded_prefix_length_ = *begin++;
+
+    // We parsed the excluded prefix length so we can now determine the
+    // size of the IPv6 subnet id. The reminder of the option should
+    // include data of that size. If the option size is lower than the
+    // subnet id length we report an error.
+    const unsigned int subnet_id_length = getSubnetIDLength();
+    if (subnet_id_length > std::distance(begin, end)) {
+        isc_throw(BadValue, "truncated Prefix Exclude option, expected "
+                  "IPv6 subnet id length is " << subnet_id_length);
+    }
+
+    // Get binary representation of the delegated prefix.
+    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+    //  We need to calculate how many bytes include the useful data and assign
+    // zeros to remaining bytes (beyond the prefix length).
+    const uint8_t bytes_length = (delegated_prefix_length_ / 8) +
+        static_cast<uint8_t>(delegated_prefix_length_ % 8 != 0);
+    std::fill(delegated_prefix_bytes.begin() + bytes_length,
+              delegated_prefix_bytes.end(), 0);
+
+    // Convert the delegated prefix to bit format.
+    boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(),
+                                        delegated_prefix_bytes.rend());
+
+    // Convert subnet id to bit format.
+    std::vector<uint8_t> subnet_id_bytes(begin, end);
+    boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_bytes.rbegin(),
+                                                  subnet_id_bytes.rend());
+
+    // Subnet id parsed, proceed to the end of the option.
+    begin = end;
+
+    // Concatenate the delegated prefix with subnet id. The resulting prefix
+    // is an excluded prefix in bit format.
+    for (int i = subnet_id_bits.size() - 1; i >= 0; --i) {
+        bits.set(128 - delegated_prefix_length_ - subnet_id_bits.size() + i,
+                 subnet_id_bits.test(i));
+    }
+
+    // Convert the prefix to binary format.
+    std::vector<uint8_t> bytes(V6ADDRESS_LEN);
+    boost::to_block_range(bits, bytes.rbegin());
+
+    // And create a prefix object from bytes.
+    excluded_prefix_ = IOAddress::fromBytes(AF_INET6, &bytes[0]);
+}
+
+uint16_t
+Option6PDExclude::len() const {
+    return (getHeaderLen() + sizeof(excluded_prefix_length_)
+            + getSubnetIDLength());
+}
+
+std::string
+Option6PDExclude::toText(int indent) const {
+    std::ostringstream s;
+    s << headerToText(indent) << ": ";
+    s << excluded_prefix_ << "/"
+      << static_cast<int>(excluded_prefix_length_);
+    return (s.str());
+}
+
+uint8_t
+Option6PDExclude::getSubnetIDLength() const {
+    uint8_t subnet_id_length_bits = excluded_prefix_length_ -
+        delegated_prefix_length_ - 1;
+    uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1;
+    return (subnet_id_length);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 134 - 0
src/lib/dhcp/option6_pdexclude.h

@@ -0,0 +1,134 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef OPTION6_PDEXCLUDE_H
+#define OPTION6_PDEXCLUDE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief DHCPv6 option class representing Prefix Exclude Option (RFC 6603).
+///
+/// This class represents DHCPv6 Prefix Exclude option (67). This option is
+/// carried in the IA Prefix option and it conveys a single prefix which is
+/// used by the delegating router to communicate with a requesting router on
+/// the requesting router's uplink. This prefix is not used on the
+/// requesting router's downlinks (is excluded from other delegated prefixes).
+class Option6PDExclude: public Option {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param delegated_prefix Delagated prefix.
+    /// @param delegated_prefix_length Delegated prefix length.
+    /// @param excluded_prefix Excluded prefix.
+    /// @param excluded_prefix_length Excluded prefix length.
+    ///
+    /// @throw BadValue if prefixes are invalid, if excluded prefix length
+    /// is not greater than delegated prefix length or if common parts of
+    /// prefixes does not match.
+    Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                     const uint8_t delegated_prefix_length,
+                     const isc::asiolink::IOAddress& excluded_prefix,
+                     const uint8_t excluded_prefix_length);
+
+    /// @brief Constructor, creates option instance from part of the buffer.
+    ///
+    /// This constructor is mostly used to parse Prefix Exclude options in the
+    /// received messages.
+    ///
+    /// @param begin Lower bound of the buffer to create option from.
+    /// @param end Upper bound of the buffer to create option from.
+    Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                     const uint8_t delegated_prefix_length,
+                     OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
+    /// @brief Writes option in wire-format to a buffer.
+    ///
+    /// Writes option in wire-format to buffer, returns pointer to first unused
+    /// byte after stored option (that is useful for writing options one after
+    /// another).
+    ///
+    /// The format of the option includes excluded prefix length specified as
+    /// a number of bits. It also includes IPv6 subnet ID field which is
+    /// computed from the delegated and excluded prefixes, according to the
+    /// section 4.2 of RFC 6603.
+    ///
+    /// @param [out] buf Pointer to a buffer.
+    virtual void pack(isc::util::OutputBuffer& buf) const;
+
+    /// @brief Parses received buffer.
+    ///
+    /// @param begin iterator to first byte of option data
+    /// @param end iterator to end of option data (first byte after option end)
+    virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Returns length of the complete option (data length + DHCPv6
+    /// option header)
+    ///
+    /// @return length of the option
+    virtual uint16_t len() const;
+
+    /// @brief Returns Prefix Exclude option in textual format.
+    ///
+    /// @param ident Number of spaces to be inserted before the text.
+    virtual std::string toText(int indent = 0) const;
+
+    /// @brief Returns delegated prefix.
+    isc::asiolink::IOAddress getDelegatedPrefix() const {
+        return (delegated_prefix_);
+    }
+
+    /// @brief Returns delegated prefix length.
+    uint8_t getDelegatedPrefixLength() const {
+        return (delegated_prefix_length_);
+    }
+
+    /// @brief Returns excluded prefix.
+    isc::asiolink::IOAddress getExcludedPrefix() const {
+        return (excluded_prefix_);
+    }
+
+    /// @brief Returns excluded prefix length.
+    uint8_t getExcludedPrefixLength() const {
+        return (excluded_prefix_length_);
+    }
+
+private:
+
+    /// @brief Returns IPv6 subnet ID length in octets.
+    ///
+    /// The IPv6 subnet ID length is between 1 and 16 octets.
+    uint8_t getSubnetIDLength() const;
+
+    /// @brief Holds delegated prefix.
+    isc::asiolink::IOAddress delegated_prefix_;
+
+    /// @brief Holds delegated prefix length,
+    uint8_t delegated_prefix_length_;
+
+    /// @brief Holds excluded prefix.
+    isc::asiolink::IOAddress excluded_prefix_;
+
+    /// @brief Holds excluded prefix length.
+    uint8_t excluded_prefix_length_;
+};
+
+/// @brief Pointer to the @ref Option6PDExclude object.
+typedef boost::shared_ptr<Option6PDExclude> Option6PDExcludePtr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION6_PDEXCLUDE_H

+ 124 - 20
src/lib/dhcp/option_custom.cc

@@ -10,6 +10,8 @@
 #include <dhcp/option_custom.h>
 #include <util/encode/hex.h>
 
+using namespace isc::asiolink;
+
 namespace isc {
 namespace dhcp {
 
@@ -46,7 +48,7 @@ OptionCustom::clone() const {
 }
 
 void
-OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+OptionCustom::addArrayDataField(const IOAddress& address) {
     checkArrayType();
 
     if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
@@ -72,6 +74,36 @@ OptionCustom::addArrayDataField(const bool value) {
 }
 
 void
+OptionCustom::addArrayDataField(const PrefixLen& prefix_len,
+                                const asiolink::IOAddress& prefix) {
+    checkArrayType();
+
+    if (definition_.getType() != OPT_IPV6_PREFIX_TYPE) {
+        isc_throw(BadDataTypeCast, "IPv6 prefix can be specified only for"
+                  " an option comprising an array of IPv6 prefix values");
+    }
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+    buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const PSIDLen& psid_len, const PSID& psid) {
+    checkArrayType();
+
+    if (definition_.getType() != OPT_PSID_TYPE) {
+        isc_throw(BadDataTypeCast, "PSID value can be specified onlu for"
+                  " an option comprising an array of PSID length / value"
+                  " tuples");
+    }
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+    buffers_.push_back(buf);
+}
+
+void
 OptionCustom::checkIndex(const uint32_t index) const {
     if (index >= buffers_.size()) {
         isc_throw(isc::OutOfRange, "specified data field index " << index
@@ -110,10 +142,17 @@ OptionCustom::createBuffers() {
             // 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 '.'.
-            if (data_size == 0 &&
-                *field == OPT_FQDN_TYPE) {
-                OptionDataTypeUtil::writeFqdn(".", buf);
+            // 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
@@ -135,9 +174,15 @@ OptionCustom::createBuffers() {
         // so we have to allocate exactly one buffer.
         OptionBuffer buf;
         size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
-        if (data_size == 0 &&
-            data_type == OPT_FQDN_TYPE) {
-            OptionDataTypeUtil::writeFqdn(".", buf);
+        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.
@@ -191,7 +236,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 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) ) {
+                } 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
@@ -199,19 +244,28 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // that the validate() function in OptionDefinition object
                     // should have checked wheter 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 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");
                 }
-            } else {
-                // Our data field requires that there is a certain chunk of
-                // data left in the buffer. If not, option is truncated.
-                if (std::distance(data, data_buf.end()) < data_size) {
-                    isc_throw(OutOfRange, "option buffer truncated");
-                }
             }
+
+            // Our data field requires that there is a certain chunk of
+            // data left in the buffer. If not, option is truncated.
+            if (std::distance(data, data_buf.end()) < data_size) {
+                isc_throw(OutOfRange, "option buffer truncated");
+            }
+
             // Store the created buffer.
             buffers.push_back(OptionBuffer(data, data + data_size));
             // Proceed to the next data field.
@@ -253,6 +307,13 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 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;
                 }
                 // We don't perform other checks for data types that can't be
                 // used together with array indicator such as strings, empty field
@@ -284,11 +345,17 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 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 {
                     data_size = std::distance(data, data_buf.end());
                 }
             }
-            if (data_size > 0) {
+            if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) {
                 buffers.push_back(OptionBuffer(data, data + data_size));
                 data += data_size;
             } else {
@@ -383,7 +450,7 @@ OptionCustom::pack(isc::util::OutputBuffer& buf) const {
 }
 
 
-asiolink::IOAddress
+IOAddress
 OptionCustom::readAddress(const uint32_t index) const {
     checkIndex(index);
 
@@ -402,10 +469,8 @@ OptionCustom::readAddress(const uint32_t index) const {
 }
 
 void
-OptionCustom::writeAddress(const asiolink::IOAddress& address,
+OptionCustom::writeAddress(const IOAddress& address,
                            const uint32_t index) {
-    using namespace isc::asiolink;
-
     checkIndex(index);
 
     if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
@@ -471,6 +536,45 @@ OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
     std::swap(buffers_[index], buf);
 }
 
+PrefixTuple
+OptionCustom::readPrefix(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readPrefix(buffers_[index]));
+}
+
+void
+OptionCustom::writePrefix(const PrefixLen& prefix_len,
+                          const IOAddress& prefix,
+                          const uint32_t index) {
+    checkIndex(index);
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+    // If there are no errors while writing PSID to a buffer, we can
+    // replace the current buffer with a new buffer.
+    std::swap(buffers_[index], buf);
+}
+
+
+PSIDTuple
+OptionCustom::readPsid(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readPsid(buffers_[index]));
+}
+
+void
+OptionCustom::writePsid(const PSIDLen& psid_len, const PSID& psid,
+                        const uint32_t index) {
+    checkIndex(index);
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+    // If there are no errors while writing PSID to a buffer, we can
+    // replace the current buffer with a new buffer.
+    std::swap(buffers_[index], buf);
+}
+
+
 std::string
 OptionCustom::readString(const uint32_t index) const {
     checkIndex(index);

+ 53 - 0
src/lib/dhcp/option_custom.h

@@ -7,6 +7,7 @@
 #ifndef OPTION_CUSTOM_H
 #define OPTION_CUSTOM_H
 
+#include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
 #include <util/io_utilities.h>
@@ -112,6 +113,19 @@ public:
         buffers_.push_back(buf);
     }
 
+    /// @brief Create new buffer and store variable length prefix in it.
+    ///
+    /// @param prefix_len Prefix length.
+    /// @param prefix Prefix.
+    void addArrayDataField(const PrefixLen& prefix_len,
+                           const asiolink::IOAddress& prefix);
+
+    /// @brief Create new buffer and store PSID length / value in it.
+    ///
+    /// @param psid_len PSID length.
+    /// @param psid PSID.
+    void addArrayDataField(const PSIDLen& psid_len, const PSID& psid);
+
     /// @brief Return a number of the data fields.
     ///
     /// @return number of data fields held by the option.
@@ -228,6 +242,45 @@ public:
         std::swap(buffers_[index], buf);
     }
 
+    /// @brief Read a buffer as variable length prefix.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @return Prefix length / value tuple.
+    /// @throw isc::OutOfRange of index is out of range.
+    PrefixTuple readPrefix(const uint32_t index = 0) const;
+
+    /// @brief Write prefix length and value into a buffer.
+    ///
+    /// @param prefix_len Prefix length.
+    /// @param prefix Prefix value.
+    /// @param index Buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void writePrefix(const PrefixLen& prefix_len,
+                     const asiolink::IOAddress& prefix,
+                     const uint32_t index = 0);
+
+    /// @brief Read a buffer as a PSID length / value tuple.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @return PSID length / value tuple.
+    /// @throw isc::OutOfRange of index is out of range.
+    PSIDTuple readPsid(const uint32_t index = 0) const;
+
+    /// @brief Write PSID length / value into a buffer.
+    ///
+    /// @param psid_len PSID length value.
+    /// @param psid PSID value in the range of 0 .. 2^(PSID length).
+    /// @param index buffer index.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast if PSID length or value is
+    /// invalid.
+    /// @throw isc::OutOfRange if index is out of range.
+    void writePsid(const PSIDLen& psid_len, const PSID& psid,
+                   const uint32_t index = 0);
+
     /// @brief Read a buffer as string value.
     ///
     /// @param index buffer index.

+ 212 - 1
src/lib/dhcp/option_data_types.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,9 @@
 #include <dns/labelsequence.h>
 #include <dns/name.h>
 #include <util/encode/hex.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
 
 namespace isc {
 namespace dhcp {
@@ -24,6 +27,8 @@ OptionDataTypeUtil::OptionDataTypeUtil() {
     data_types_["uint32"] = OPT_UINT32_TYPE;
     data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
     data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
+    data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE;
+    data_types_["psid"] = OPT_PSID_TYPE;
     data_types_["string"] = OPT_STRING_TYPE;
     data_types_["fqdn"] = OPT_FQDN_TYPE;
     data_types_["record"] = OPT_RECORD_TYPE;
@@ -39,6 +44,8 @@ OptionDataTypeUtil::OptionDataTypeUtil() {
     data_type_names_[OPT_UINT32_TYPE] = "uint32";
     data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
     data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
+    data_type_names_[OPT_IPV6_PREFIX_TYPE] = "ipv6-prefix";
+    data_type_names_[OPT_PSID_TYPE] = "psid";
     data_type_names_[OPT_STRING_TYPE] = "string";
     data_type_names_[OPT_FQDN_TYPE] = "fqdn";
     data_type_names_[OPT_RECORD_TYPE] = "record";
@@ -86,6 +93,9 @@ OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
     case OPT_IPV6_ADDRESS_TYPE:
         return (asiolink::V6ADDRESS_LEN);
 
+    case OPT_PSID_TYPE:
+        return (3);
+
     default:
         ;
     }
@@ -237,6 +247,207 @@ OptionDataTypeUtil::getLabelCount(const std::string& text_name) {
     }
 }
 
+PrefixTuple
+OptionDataTypeUtil::readPrefix(const std::vector<uint8_t>& buf) {
+    // Prefix typically consists of the prefix length and the
+    // actual value. If prefix length is 0, the buffer length should
+    // be at least 1 byte to hold this length value.
+    if (buf.empty()) {
+        isc_throw(BadDataTypeCast, "unable to read prefix length from "
+                  "a truncated buffer");
+    }
+
+    // Surround everything with try-catch to unify exceptions being
+    // thrown by various functions and constructors.
+    try {
+        // Try to create PrefixLen object from the prefix length held
+        // in the buffer. This may cause an exception if the length is
+        // invalid (greater than 128).
+        PrefixLen prefix_len(buf.at(0));
+
+        // Convert prefix length to bytes, because we operate on bytes,
+        // rather than bits.
+        uint8_t prefix_len_bytes = (prefix_len.asUint8() / 8);
+        // Check if we need to zero pad any bits. This is the case when
+        // the prefix length is not divisible by 8 (bits per byte). The
+        // calculations below may require some explanations. We first
+        // perform prefix_len % 8 to get the number of useful bits beyond
+        // the current prefix_len_bytes value. By substracting it from 8
+        // we get the number of zero padded bits, but with the special
+        // case of 8 when the result of substraction is 0. The value of
+        // 8 really means no padding so we make a modulo division once
+        // again to turn 8s to 0s.
+        const uint8_t zero_padded_bits =
+            static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+        // If there are zero padded bits, it means that we need an extra
+        // byte to be retrieved from the buffer.
+        if (zero_padded_bits > 0) {
+            ++prefix_len_bytes;
+        }
+
+        // Make sure that the buffer is long enough. We substract 1 to
+        // also account for the fact that the buffer includes a prefix
+        // length besides a prefix.
+        if ((buf.size() - 1) < prefix_len_bytes) {
+            isc_throw(BadDataTypeCast, "unable to read a prefix having length of "
+                      << prefix_len.asUnsigned() << " from a truncated buffer");
+        }
+
+        // It is possible for a prefix to be zero if the prefix length
+        // is zero.
+        IOAddress prefix(IOAddress::IPV6_ZERO_ADDRESS());
+
+        // If there is anything more than prefix length is this buffer
+        // we need to read it.
+        if (buf.size() > 1) {
+            // Buffer has to be copied, because we will modify its
+            // contents by setting certain bits to 0, if necessary.
+            std::vector<uint8_t> prefix_buf(buf.begin() + 1, buf.end());
+            // All further conversions require that the buffer length is
+            // 16 bytes.
+            if (prefix_buf.size() < V6ADDRESS_LEN) {
+                prefix_buf.resize(V6ADDRESS_LEN);
+                if (prefix_len_bytes < prefix_buf.size()) {
+                    // Zero all bits in the buffer beyond prefix length
+                    // position.
+                    std::fill(prefix_buf.begin() + prefix_len_bytes,
+                              prefix_buf.end(), 0);
+
+                    if (zero_padded_bits) {
+                        // There is a byte that require zero padding. We
+                        // achieve that by shifting the value of that byte
+                        // back and forth by the number of zeroed bits.
+                        prefix_buf.at(prefix_len_bytes - 1) =
+                            (prefix_buf.at(prefix_len_bytes - 1)
+                             >> zero_padded_bits)
+                            << zero_padded_bits;
+                    }
+                }
+            }
+            // Convert the buffer to the IOAddress object.
+            prefix = IOAddress::fromBytes(AF_INET6, &prefix_buf[0]);
+        }
+
+        return (std::make_pair(prefix_len, prefix));
+
+    } catch (const BadDataTypeCast& ex) {
+        // Pass through the BadDataTypeCast exceptions.
+        throw;
+
+    } catch (const std::exception& ex) {
+        // If an exception of a different type has been thrown, insert
+        // a text that indicates that the failure occurred during reading
+        // the prefix and modify exception type to BadDataTypeCast.
+        isc_throw(BadDataTypeCast, "unable to read a prefix from a buffer: "
+                  << ex.what());
+    }
+}
+
+void
+OptionDataTypeUtil::writePrefix(const PrefixLen& prefix_len,
+                                const IOAddress& prefix,
+                                std::vector<uint8_t>& buf) {
+    // Prefix must be an IPv6 prefix.
+    if (!prefix.isV6()) {
+        isc_throw(BadDataTypeCast, "illegal prefix value "
+                  << prefix);
+    }
+
+    // We don't need to validate the prefix_len value, because it is
+    // already validated by the PrefixLen class.
+    buf.push_back(prefix_len.asUint8());
+
+    // Convert the prefix length to a number of bytes.
+    uint8_t prefix_len_bytes = prefix_len.asUint8() / 8;
+    // Check if there are any bits that require zero padding. See the
+    // commentary in readPrefix to see how this is calculated.
+    const uint8_t zero_padded_bits =
+        static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+    // If zero padding is needed it means that we need to extend the
+    // buffer to hold the "partially occupied" byte.
+    if (zero_padded_bits > 0) {
+        ++prefix_len_bytes;
+    }
+
+    // Convert the prefix to byte representation and append it to
+    // our output buffer.
+    std::vector<uint8_t> prefix_bytes = prefix.toBytes();
+    buf.insert(buf.end(), prefix_bytes.begin(),
+               prefix_bytes.begin() + prefix_len_bytes);
+    // If the last byte requires zero padding we achieve that by shifting
+    // bits back and forth by the number of insignificant bits.
+    if (zero_padded_bits) {
+        *buf.rbegin() = (*buf.rbegin() >> zero_padded_bits) << zero_padded_bits;
+    }
+}
+
+PSIDTuple
+OptionDataTypeUtil::readPsid(const std::vector<uint8_t>& buf) {
+    if (buf.size() < 3) {
+        isc_throw(BadDataTypeCast, "unable to read PSID from the buffer."
+                  << " Invalid buffer size " << buf.size()
+                  << ". Expected 3 bytes (PSID length and PSID value)");
+    }
+
+    // Read PSID length.
+    uint8_t psid_len = buf[0];
+
+    // PSID length must not be greater than 16 bits.
+    if (psid_len > sizeof(uint16_t) * 8) {
+        isc_throw(BadDataTypeCast, "invalid PSID length value "
+                  << static_cast<unsigned>(psid_len)
+                  << ", this value is expected to be in range of 0 to 16");
+    }
+
+    // Read two bytes of PSID value.
+    uint16_t psid = isc::util::readUint16(&buf[1], 2);
+
+    // We need to check that the PSID value does not exceed the maximum value
+    // for a specified PSID length. That means that all bits placed further than
+    // psid_len from the left must be set to 0. So, we create a bit mask
+    // by shifting a value of 0xFFFF to the left and right by psid_len. This
+    // leaves us with psid_len leftmost bits unset and the rest set. Next, we
+    // apply the mask on the PSID value from the buffer and make sure the result
+    // is 0. Otherwise, it means that there are some bits set in the PSID which
+    // aren't supposed to be set.
+    if ((psid_len > 0) &&
+        ((psid & static_cast<uint16_t>(static_cast<uint16_t>(0xFFFF << psid_len)
+                                       >> psid_len)) != 0)) {
+        isc_throw(BadDataTypeCast, "invalid PSID value " << psid
+                  << " for a specified PSID length "
+                  << static_cast<unsigned>(psid_len));
+    }
+
+    // All is good, so we can convert the PSID value read from the buffer to
+    // the port set number.
+    psid = psid >> (sizeof(psid) * 8 - psid_len);
+    return (std::make_pair(PSIDLen(psid_len), PSID(psid)));
+}
+
+void
+OptionDataTypeUtil::writePsid(const PSIDLen& psid_len, const PSID& psid,
+                              std::vector<uint8_t>& buf) {
+    if (psid_len.asUint8() > sizeof(psid) * 8) {
+        isc_throw(BadDataTypeCast, "invalid PSID length value "
+                  << psid_len.asUnsigned()
+                  << ", this value is expected to be in range of 0 to 16");
+    }
+
+    if (psid_len.asUint8() > 0 &&
+        (psid.asUint16() > (0xFFFF >> (sizeof(uint16_t) * 8 - psid_len.asUint8())))) {
+        isc_throw(BadDataTypeCast, "invalid PSID value " << psid.asUint16()
+                  << " for a specified PSID length "
+                  << psid_len.asUnsigned());
+    }
+
+    buf.resize(buf.size() + 3);
+    buf.at(buf.size() - 3) = psid_len.asUint8();
+    isc::util::writeUint16(static_cast<uint16_t>
+                           (psid.asUint16() << (sizeof(uint16_t) * 8 - psid_len.asUint8())),
+                           &buf[buf.size() - 2], 2);
+}
+
+
 std::string
 OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
     std::string value;

+ 194 - 2
src/lib/dhcp/option_data_types.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,6 +13,7 @@
 #include <util/io_utilities.h>
 
 #include <stdint.h>
+#include <utility>
 
 namespace isc {
 namespace dhcp {
@@ -53,12 +54,33 @@ enum OptionDataType {
     OPT_ANY_ADDRESS_TYPE,
     OPT_IPV4_ADDRESS_TYPE,
     OPT_IPV6_ADDRESS_TYPE,
+    OPT_IPV6_PREFIX_TYPE,
+    OPT_PSID_TYPE,
     OPT_STRING_TYPE,
     OPT_FQDN_TYPE,
     OPT_RECORD_TYPE,
     OPT_UNKNOWN_TYPE
 };
 
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+    const char*             name;           // option name
+    uint16_t                code;           // option code
+    OptionDataType          type;           // data type
+    bool                    array;          // is array
+    const OptionDataType*   records;        // record fields
+    size_t                  records_size;   // number of fields in a record
+    const char*             encapsulates;   // option space encapsulated by the
+                                            // particular option.
+};
+
+/// @brief Encapsulation of option definition parameters and the structure size.
+struct OptionDefParamsEncapsulation {
+    const struct OptionDefParams*   optionDefParams;    // parameters structure
+    const int                       size;               // structure size
+    const char*                     space;              // option space
+};
+
 /// @brief Trait class for data types supported in DHCP option definitions.
 ///
 /// This is useful to check whether the type specified as template parameter
@@ -173,6 +195,123 @@ struct OptionDataTypeTraits<std::string> {
     static const OptionDataType type = OPT_STRING_TYPE;
 };
 
+/// @brief Encapsulates PSID length.
+class PSIDLen {
+public:
+
+    /// @brief Default constructor.
+    PSIDLen() : psid_len_(0) { }
+
+    /// @brief Constructor.
+    ///
+    /// It checks that the specified value is not greater than
+    /// 16, which is a maximum value for the PSID length.
+    ///
+    /// @param psid_len PSID length.
+    /// @throw isc::OutOfRange If specified PSID length is greater than 16.
+    explicit PSIDLen(const uint8_t psid_len)
+        : psid_len_(psid_len) {
+        if (psid_len_ > sizeof(uint16_t) * 8) {
+            isc_throw(isc::OutOfRange, "invalid value "
+                      << asUnsigned() << " of PSID length");
+        }
+    }
+
+    /// @brief Returns PSID length as uint8_t value.
+    uint8_t asUint8() const {
+        return (psid_len_);
+    }
+
+    /// @brief Returns PSID length as unsigned int.
+    ///
+    /// This is useful to convert the value to a numeric type which
+    /// can be logged directly. Note that the uint8_t value has to
+    /// be cast to an integer value to be logged as a number. This
+    /// is because the uint8_t is often implemented as char, in which
+    /// case directly loggingan uint8_t value prints a character rather
+    /// than a number.
+    unsigned int asUnsigned() const {
+        return (static_cast<unsigned>(psid_len_));
+    }
+
+private:
+
+    /// @brief PSID length.
+    uint8_t psid_len_;
+};
+
+/// @brief Encapsulates PSID value.
+class PSID {
+public:
+
+    /// @brief Default constructor.
+    PSID() : psid_(0) { }
+
+    /// @brief Constructor.
+    ///
+    /// This constructor doesn't perform any checks on the input data.
+    ///
+    /// @param psid PSID value.
+    explicit PSID(const uint16_t psid)
+        : psid_(psid) {
+    }
+
+    /// @brief Returns PSID value as a number.
+    uint16_t asUint16() const {
+        return (psid_);
+    }
+
+private:
+
+    /// @brief PSID value.
+    uint16_t psid_;
+
+};
+
+/// @brief Defines a pair of PSID length / value.
+typedef std::pair<PSIDLen, PSID> PSIDTuple;
+
+/// @brief Encapsulates prefix length.
+class PrefixLen {
+public:
+
+    /// @brief Default constructor.
+    PrefixLen() : prefix_len_(0) { }
+
+    /// @brief Constructor.
+    ///
+    /// This constructor checks if the specified prefix length is
+    /// in the range of 0 to 128.
+    ///
+    /// @param prefix_len Prefix length value.
+    /// @throw isc::OutOfRange If specified prefix length is greater than 128.
+    explicit PrefixLen(const uint8_t prefix_len)
+        : prefix_len_(prefix_len) {
+    }
+
+    /// @brief Returns prefix length as uint8_t value.
+    uint8_t asUint8() const {
+        return (prefix_len_);
+    }
+
+    /// @brief Returns prefix length as unsigned int.
+    ///
+    /// This is useful to convert the value to a numeric type which
+    /// can be logged directly. See @ref PSIDLen::asUnsigned for the
+    /// use cases of this accessor.
+    unsigned int asUnsigned() const {
+        return (static_cast<unsigned>(prefix_len_));
+    }
+
+private:
+
+    /// @brief Prefix length.
+    uint8_t prefix_len_;
+};
+
+/// @brief Defines a pair of prefix length / value.
+typedef std::pair<PrefixLen, asiolink::IOAddress> PrefixTuple;
+
 /// @brief Utility class for option data types.
 ///
 /// This class provides a set of utility functions to operate on
@@ -221,7 +360,7 @@ public:
     ///
     /// @param buf input buffer.
     /// @param family address family: AF_INET or AF_INET6.
-    /// 
+    ///
     /// @throw isc::dhcp::BadDataTypeCast when the data being read
     /// is truncated.
     /// @return address being read.
@@ -378,6 +517,59 @@ public:
     /// @throw isc::dhcp::BadDataTypeCast if provided name is malformed.
     static unsigned int getLabelCount(const std::string& text_name);
 
+    /// @brief Read prefix from a buffer.
+    ///
+    /// This method reads prefix length and a prefix value from a buffer.
+    /// The prefix value has variable length and this length is determined
+    /// from the first byte of the buffer. If the length is not divisible
+    /// by 8, the prefix is padded with zeros to the next byte boundary.
+    ///
+    /// @param buf input buffer holding a prefix length / prefix tuple.
+    ///
+    /// @return Prefix length and value.
+    static PrefixTuple readPrefix(const std::vector<uint8_t>& buf);
+
+    /// @brief Append prefix into a buffer.
+    ///
+    /// This method writes prefix length (1 byte) followed by a variable
+    /// length prefix.
+    ///
+    /// @param prefix_len Prefix length in bits (0 to 128).
+    /// @param prefix Prefix value.
+    /// @param [out] Output buffer.
+    static void writePrefix(const PrefixLen& prefix_len,
+                            const asiolink::IOAddress& prefix,
+                            std::vector<uint8_t>& buf);
+
+    /// @brief Read PSID length / value tuple from a buffer.
+    ///
+    /// This method reads three bytes from a buffer. The first byte
+    /// holds a PSID length value. The remaining two bytes contain a
+    /// zero padded PSID value.
+    ///
+    /// @return PSID length / value tuple.
+    /// @throw isc::dhcp::BadDataTypeCast if PSID length or value held
+    /// in the buffer is incorrect or the buffer is truncated.
+    static PSIDTuple readPsid(const std::vector<uint8_t>& buf);
+
+    /// @brief Append PSID length/value into a buffer.
+    ///
+    /// This method appends 1 byte of PSID length and 2 bytes of PSID
+    /// value into a buffer. The PSID value contains a PSID length
+    /// number of significant bits, followed by 16 - PSID length
+    /// zero bits.
+    ///
+    /// @param psid_len PSID length in the range of 0 to 16 holding the
+    /// number of significant bits within the PSID value.
+    /// @param psid PSID value, where the lowest value is 0, and the
+    /// highest value is 2^(PSID length).
+    /// @param [out] buf output buffer.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast if specified psid_len or
+    /// psid value is incorrect.
+    static void writePsid(const PSIDLen& psid_len, const PSID& psid,
+                          std::vector<uint8_t>& buf);
+
     /// @brief Read string value from a buffer.
     ///
     /// @param buf input buffer.

+ 82 - 1
src/lib/dhcp/option_definition.cc

@@ -20,7 +20,6 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_opaque_data_tuples.h>
-#include <dhcp/option_space.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_vendor_class.h>
@@ -28,6 +27,7 @@
 #include <util/strutil.h>
 #include <boost/algorithm/string/classification.hpp>
 #include <boost/algorithm/string/predicate.hpp>
+#include <boost/dynamic_bitset.hpp>
 
 using namespace std;
 using namespace isc::util;
@@ -565,6 +565,87 @@ OptionDefinition::writeToBuffer(const std::string& value,
             OptionDataTypeUtil::writeAddress(address, buf);
             return;
         }
+    case OPT_IPV6_PREFIX_TYPE:
+        {
+            std::string txt = value;
+
+            // first let's remove any whitespaces
+            boost::erase_all(txt, " "); // space
+            boost::erase_all(txt, "\t"); // tabulation
+
+            // Is this prefix/len notation?
+            size_t pos = txt.find("/");
+
+            if (pos == string::npos) {
+                isc_throw(BadDataTypeCast, "provided address/prefix "
+                          << value
+                          << " is not valid.");
+            }
+
+            std::string txt_address = txt.substr(0, pos);
+            isc::asiolink::IOAddress address = isc::asiolink::IOAddress(txt_address);
+            if (!address.isV6()) {
+                isc_throw(BadDataTypeCast, "provided address "
+                          << txt_address
+                          << " is not a valid IPv4 or IPv6 address.");
+            }
+
+            std::string txt_prefix = txt.substr(pos + 1);
+            uint8_t len = 0;
+            try {
+                // start with the first character after /
+                len = lexicalCastWithRangeCheck<uint8_t>(txt_prefix);
+            } catch (...)  {
+                isc_throw(BadDataTypeCast, "provided prefix "
+                          << txt_prefix
+                          << " is not valid.");
+            }
+
+
+            // Write a prefix.
+            OptionDataTypeUtil::writePrefix(PrefixLen(len), address, buf);
+
+            return;
+    }
+    case OPT_PSID_TYPE:
+    {
+        std::string txt = value;
+
+        // first let's remove any whitespaces
+        boost::erase_all(txt, " "); // space
+        boost::erase_all(txt, "\t"); // tabulation
+
+        // Is this prefix/len notation?
+        size_t pos = txt.find("/");
+
+        if (pos == string::npos) {
+            isc_throw(BadDataTypeCast, "provided PSID value "
+                      << value << " is not valid");
+        }
+
+        const std::string txt_psid = txt.substr(0, pos);
+        const std::string txt_psid_len = txt.substr(pos + 1);
+
+        uint16_t psid = 0;
+        uint8_t psid_len = 0;
+
+        try {
+            psid = lexicalCastWithRangeCheck<uint16_t>(txt_psid);
+        } catch (...)  {
+            isc_throw(BadDataTypeCast, "provided PSID "
+                      << txt_psid << " is not valid");
+        }
+
+        try {
+            psid_len = lexicalCastWithRangeCheck<uint8_t>(txt_psid_len);
+        } catch (...)  {
+            isc_throw(BadDataTypeCast, "provided PSID length "
+                      << txt_psid_len << " is not valid");
+        }
+
+        OptionDataTypeUtil::writePsid(PSIDLen(psid_len), PSID(psid), buf);
+        return;
+    }
     case OPT_STRING_TYPE:
         OptionDataTypeUtil::writeString(value, buf);
         return;

+ 7 - 1
src/lib/dhcp/option_definition.h

@@ -17,6 +17,7 @@
 #include <boost/multi_index_container.hpp>
 #include <boost/shared_ptr.hpp>
 #include <map>
+#include <string>
 
 namespace isc {
 namespace dhcp {
@@ -117,7 +118,9 @@ class OptionIntArray;
 /// - "uint16"
 /// - "uint32"
 /// - "ipv4-address" (IPv4 Address)
-/// - "ipv6-address" (IPV6 Address)
+/// - "ipv6-address" (IPv6 Address)
+/// - "ipv6-prefix" (IPv6 variable length prefix)
+/// - "psid" (PSID length / value)
 /// - "string"
 /// - "fqdn" (fully qualified name)
 /// - "record" (set of data fields of different types)
@@ -717,6 +720,9 @@ typedef boost::multi_index_container<
 /// Pointer to an option definition container.
 typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
 
+/// Container that holds option definitions for various option spaces.
+typedef std::map<std::string, OptionDefContainerPtr> OptionDefContainers;
+
 /// Container that holds various vendor option containers
 typedef std::map<uint32_t, OptionDefContainerPtr> VendorOptionDefContainers;
 

+ 3 - 2
src/lib/dhcp/option_int.h

@@ -10,6 +10,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
 #include <dhcp/option_data_types.h>
+#include <dhcp/option_space.h>
 #include <util/io_utilities.h>
 
 #include <stdint.h>
@@ -66,7 +67,7 @@ public:
         if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
-        setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
+        setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
     }
 
     /// @brief Constructor.
@@ -90,7 +91,7 @@ public:
         if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
-        setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
+        setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
         unpack(begin, end);
     }
 

+ 9 - 4
src/lib/dhcp/option_space.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,8 +13,13 @@
 #include <stdint.h>
 #include <string>
 
-#define DHCP4_OPTION_SPACE "dhcp4"
-#define DHCP6_OPTION_SPACE "dhcp6"
+#define DHCP4_OPTION_SPACE      "dhcp4"
+#define DHCP6_OPTION_SPACE      "dhcp6"
+#define MAPE_V6_OPTION_SPACE    "s46-cont-mape-options"
+#define MAPT_V6_OPTION_SPACE    "s46-cont-mapt-options"
+#define LW_V6_OPTION_SPACE      "s46-cont-lw-options"
+#define V4V6_RULE_OPTION_SPACE  "s46-rule-options"
+#define V4V6_BIND_OPTION_SPACE  "s46-v4v6bind-options"
 
 namespace isc {
 namespace dhcp {
@@ -174,7 +179,7 @@ public:
     void setVendorSpace(const uint32_t enterprise_number);
 
 private:
-    
+
     uint32_t enterprise_number_; ///< IANA assigned enterprise number.
 };
 

+ 1 - 1
src/lib/dhcp/pkt4.cc

@@ -203,7 +203,7 @@ Pkt4::unpack() {
     // a vector as an input.
     buffer_in.readVector(opts_buffer, opts_len);
 
-    size_t offset = LibDHCP::unpackOptions4(opts_buffer, "dhcp4", options_);
+    size_t offset = LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, options_);
 
     // If offset is not equal to the size and there is no DHO_END,
     // then something is wrong here. We either parsed past input

+ 3 - 2
src/lib/dhcp/pkt6.cc

@@ -9,6 +9,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
+#include <dhcp/option_space.h>
 #include <dhcp/option_vendor_class.h>
 #include <dhcp/option_vendor.h>
 #include <dhcp/pkt6.h>
@@ -403,7 +404,7 @@ Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
 
     // If custom option parsing function has been set, use this function
     // to parse options. Otherwise, use standard function from libdhcp.
-    size_t offset = LibDHCP::unpackOptions6(opt_buffer, "dhcp6", options_);
+    size_t offset = LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, options_);
 
     // If offset is not equal to the size, then something is wrong here. We
     // either parsed past input buffer (bug in our code) or we haven't parsed
@@ -453,7 +454,7 @@ Pkt6::unpackRelayMsg() {
 
         // If custom option parsing function has been set, use this function
         // to parse options. Otherwise, use standard function from libdhcp.
-        LibDHCP::unpackOptions6(opt_buffer, "dhcp6", relay.options_,
+        LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, relay.options_,
                                 &relay_msg_offset, &relay_msg_len);
 
         /// @todo: check that each option appears at most once

+ 89 - 24
src/lib/dhcp/std_option_defs.h

@@ -10,6 +10,7 @@
 #include <dhcp/option_data_types.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/option_space.h>
 
 namespace isc {
 namespace dhcp {
@@ -36,18 +37,6 @@ namespace {
 #define NO_RECORD_DEF 0, 0
 #endif
 
-/// @brief Parameters being used to make up an option definition.
-struct OptionDefParams {
-    const char* name;              // option name
-    uint16_t code;                 // option code
-    OptionDataType type;           // data type
-    bool array;                    // is array
-    const OptionDataType* records; // record fields
-    size_t records_size;           // number of fields in a record
-    const char* encapsulates;      // option space encapsulated by
-                                   // the particular option.
-};
-
 // fqdn option record fields.
 //
 // Note that the flags field indicates the type of domain
@@ -72,7 +61,7 @@ RECORD_DECL(CLIENT_NDI_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
 RECORD_DECL(UUID_GUID_RECORDS, OPT_UINT8_TYPE, OPT_BINARY_TYPE);
 
 /// @brief Definitions of standard DHCPv4 options.
-const OptionDefParams OPTION_DEF_PARAMS4[] = {
+const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
     { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
     { "time-offset", DHO_TIME_OFFSET, OPT_INT32_TYPE, false, NO_RECORD_DEF, "" },
     { "routers", DHO_ROUTERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
@@ -230,9 +219,8 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
 };
 
 /// Number of option definitions defined.
-const int OPTION_DEF_PARAMS_SIZE4  =
-    sizeof(OPTION_DEF_PARAMS4) / sizeof(OPTION_DEF_PARAMS4[0]);
-
+const int STANDARD_V4_OPTION_DEFINITIONS_SIZE =
+    sizeof(STANDARD_V4_OPTION_DEFINITIONS) / sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]);
 
 /// Start Definition of DHCPv6 options
 
@@ -257,6 +245,13 @@ RECORD_DECL(LQ_QUERY_RECORDS, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
 RECORD_DECL(LQ_RELAY_DATA_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
 // remote-id
 RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// s46-rule
+RECORD_DECL(S46_RULE, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+            OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE);
+// s46-v4v6bind
+RECORD_DECL(S46_V4V6BIND, OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE);
+// s46-portparams
+RECORD_DECL(S46_PORTPARAMS, OPT_UINT8_TYPE, OPT_PSID_TYPE);
 // status-code
 RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
 // vendor-class
@@ -280,7 +275,7 @@ RECORD_DECL(CLIENT_NII_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
 /// This however does not work on Solaris (GCC) which issues a
 /// warning about lack of initializers for some struct members
 /// causing build to fail.
-const OptionDefParams OPTION_DEF_PARAMS6[] = {
+const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
     { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(IA_NA_RECORDS), "" },
@@ -366,6 +361,7 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
     { "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, OPT_FQDN_TYPE, false,
       NO_RECORD_DEF, "" },
     { "rsoo", D6O_RSOO, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "rsoo-opts" },
+    { "pd-exclude", D6O_PD_EXCLUDE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, OPT_BINARY_TYPE, false,
       NO_RECORD_DEF, "" },
     { "dhcpv4-message", D6O_DHCPV4_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
@@ -378,7 +374,14 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
     { "signature", D6O_SIGNATURE, OPT_RECORD_TYPE, false,
       RECORD_DEF(SIGNATURE_RECORDS), "" },
     { "timestamp", D6O_TIMESTAMP, OPT_BINARY_TYPE, false,
-      NO_RECORD_DEF, "" }
+      NO_RECORD_DEF, "" },
+    { "aftr-name", D6O_AFTR_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+    { "s46-cont-mape", D6O_S46_CONT_MAPE, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
+        MAPE_V6_OPTION_SPACE },
+    { "s46-cont-mapt", D6O_S46_CONT_MAPT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
+        MAPT_V6_OPTION_SPACE },
+    { "s46-cont-lw", D6O_S46_CONT_LW, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
+        LW_V6_OPTION_SPACE }
 
     // @todo There is still a bunch of options for which we have to provide
     // definitions but we don't do it because they are not really
@@ -386,8 +389,17 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
 };
 
 /// Number of option definitions defined.
-const int OPTION_DEF_PARAMS_SIZE6  =
-    sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
+const int STANDARD_V6_OPTION_DEFINITIONS_SIZE =
+    sizeof(STANDARD_V6_OPTION_DEFINITIONS) /
+    sizeof(STANDARD_V6_OPTION_DEFINITIONS[0]);
+
+// Option definitions that belong to two or more option spaces are defined here.
+const OptionDefParams OPTION_DEF_PARAMS_S46_BR = { "s46-br", D6O_S46_BR,
+    OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" };
+const OptionDefParams OPTION_DEF_PARAMS_S46_RULE = { "s46-rule", D6O_S46_RULE,
+    OPT_RECORD_TYPE, false, RECORD_DEF(S46_RULE), V4V6_RULE_OPTION_SPACE };
+const OptionDefParams OPTION_DEF_PARAMS_S46_PORTPARAMS = { "s46-portparams",
+    D6O_S46_PORTPARAMS, OPT_RECORD_TYPE, false, RECORD_DEF(S46_PORTPARAMS), "" };
 
 /// @brief Definitions of vendor-specific DHCPv6 options, defined by ISC.
 /// 4o6-* options are used for inter-process communication. For details, see
@@ -395,12 +407,65 @@ const int OPTION_DEF_PARAMS_SIZE6  =
 ///
 /// @todo: As those options are defined by ISC, they do not belong in std_option_defs.h.
 ///        We need to move them to a separate file, e.g. isc_option_defs.h
-const OptionDefParams ISC_V6_DEFS[] = {
-    { "4o6-interface", ISC_V6_4O6_INTERFACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
-    { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }
+const OptionDefParams ISC_V6_OPTION_DEFINITIONS[] = {
+    { "4o6-interface", ISC_V6_4O6_INTERFACE, OPT_STRING_TYPE, false,
+        NO_RECORD_DEF, "" },
+    { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, OPT_IPV6_ADDRESS_TYPE,
+        false, NO_RECORD_DEF, "" }
+};
+
+const int ISC_V6_OPTION_DEFINITIONS_SIZE =
+    sizeof(ISC_V6_OPTION_DEFINITIONS) /
+    sizeof(ISC_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief MAPE option definitions
+const OptionDefParams MAPE_V6_OPTION_DEFINITIONS[] = {
+    OPTION_DEF_PARAMS_S46_BR,
+    OPTION_DEF_PARAMS_S46_RULE
+};
+
+const int MAPE_V6_OPTION_DEFINITIONS_SIZE =
+    sizeof(MAPE_V6_OPTION_DEFINITIONS) /
+    sizeof(MAPE_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief MAPT option definitions
+const OptionDefParams MAPT_V6_OPTION_DEFINITIONS[] = {
+    OPTION_DEF_PARAMS_S46_RULE,
+    { "s46-dmr", D6O_S46_DMR, OPT_IPV6_PREFIX_TYPE, false, NO_RECORD_DEF, "" }
+};
+
+const int MAPT_V6_OPTION_DEFINITIONS_SIZE =
+    sizeof(MAPT_V6_OPTION_DEFINITIONS) /
+    sizeof(MAPT_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief LW option definitions
+const OptionDefParams LW_V6_OPTION_DEFINITIONS[] = {
+    OPTION_DEF_PARAMS_S46_BR,
+    { "s46-v4v6bind", D6O_S46_V4V6BIND, OPT_RECORD_TYPE, false,
+        RECORD_DEF(S46_V4V6BIND), V4V6_BIND_OPTION_SPACE }
+};
+
+const int LW_V6_OPTION_DEFINITIONS_SIZE =
+    sizeof(LW_V6_OPTION_DEFINITIONS) /
+    sizeof(LW_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief Rule option definitions
+const OptionDefParams V4V6_RULE_OPTION_DEFINITIONS[] = {
+    OPTION_DEF_PARAMS_S46_PORTPARAMS
+};
+
+const int V4V6_RULE_OPTION_DEFINITIONS_SIZE =
+    sizeof(V4V6_RULE_OPTION_DEFINITIONS) /
+    sizeof(V4V6_RULE_OPTION_DEFINITIONS[0]);
+
+/// @brief Bind option definitions
+const OptionDefParams V4V6_BIND_OPTION_DEFINITIONS[] = {
+    OPTION_DEF_PARAMS_S46_PORTPARAMS
 };
 
-const int ISC_V6_DEFS_SIZE = sizeof(ISC_V6_DEFS) / sizeof(OptionDefParams);
+const int V4V6_BIND_OPTION_DEFINITIONS_SIZE =
+    sizeof(V4V6_BIND_OPTION_DEFINITIONS) /
+    sizeof(V4V6_BIND_OPTION_DEFINITIONS[0]);
 
 } // unnamed namespace
 

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -54,6 +54,7 @@ libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaprefix_unittest.cc
+libdhcp___unittests_SOURCES += option6_pdexclude_unittest.cc
 libdhcp___unittests_SOURCES += option6_status_code_unittest.cc
 libdhcp___unittests_SOURCES += option_int_unittest.cc
 libdhcp___unittests_SOURCES += option_int_array_unittest.cc

+ 65 - 74
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -101,8 +101,8 @@ public:
                                    const std::type_info& expected_type,
                                    const std::string& encapsulates = "") {
         // Use V4 universe.
-        testStdOptionDefs(Option::V4, code, begin, end, expected_type,
-                          encapsulates);
+        testStdOptionDefs(Option::V4, DHCP4_OPTION_SPACE, code, begin, end,
+                          expected_type, encapsulates);
     }
 
     /// @brief Test DHCPv6 option definition.
@@ -125,8 +125,33 @@ public:
                                    const std::type_info& expected_type,
                                    const std::string& encapsulates = "") {
         // Use V6 universe.
-        testStdOptionDefs(Option::V6, code, begin, end, expected_type,
-                          encapsulates);
+        testStdOptionDefs(Option::V6, DHCP6_OPTION_SPACE, code, begin,
+                          end, expected_type, encapsulates);
+    }
+
+    /// @brief Test DHCPv6 option definition in a given option space.
+    ///
+    /// This function tests if option definition for an option from a
+    /// given option space has been initialized correctly.
+    ///
+    /// @param option_space option space.
+    /// @param code option code.
+    /// @param begin iterator pointing at beginning of a buffer to
+    /// be used to create option instance.
+    /// @param end iterator pointing at end of a buffer to be
+    /// used to create option instance.
+    /// @param expected_type type of the option created by the
+    /// factory function returned by the option definition.
+    /// @param encapsulates name of the option space being encapsulated
+    /// by the option.
+    static void testOptionDefs6(const std::string& option_space,
+                                const uint16_t code,
+                                const OptionBufferConstIter begin,
+                                const OptionBufferConstIter end,
+                                const std::type_info& expected_type,
+                                const std::string& encapsulates = "") {
+        testStdOptionDefs(Option::V6, option_space, code, begin,
+                          end, expected_type, encapsulates);
     }
 
     /// @brief Create a sample DHCPv4 option 43 with suboptions.
@@ -216,6 +241,7 @@ private:
     /// This function tests if option definition for standard
     /// option has been initialized correctly.
     ///
+    /// @param option_space option space.
     /// @param code option code.
     /// @param begin iterator pointing at beginning of a buffer to
     /// be used to create option instance.
@@ -225,7 +251,8 @@ private:
     /// factory function returned by the option definition.
     /// @param encapsulates name of the option space being encapsulated
     /// by the option.
-    static void testStdOptionDefs(const Option::Universe u,
+    static void testStdOptionDefs(const Option::Universe& u,
+                                  const std::string& option_space,
                                   const uint16_t code,
                                   const OptionBufferConstIter begin,
                                   const OptionBufferConstIter end,
@@ -235,7 +262,7 @@ private:
         // the definition for a particular option code.
         // We don't have to initialize option definitions here because they
         // are initialized in the class's constructor.
-        OptionDefContainerPtr options = LibDHCP::getOptionDefs(u);
+        OptionDefContainerPtr options = LibDHCP::getOptionDefs(option_space);
         // Get the container index #1. This one allows for searching
         // option definitions using option code.
         const OptionDefContainerTypeIndex& idx = options->get<1>();
@@ -680,7 +707,7 @@ TEST_F(LibDhcpTest, packOptions4) {
     // Get the option definition for RAI option. This option is represented
     // by OptionCustom which requires a definition to be passed to
     // the constructor.
-    OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(Option::V4,
+    OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                         DHO_DHCP_AGENT_OPTIONS);
     ASSERT_TRUE(rai_def);
     // Create RAI option.
@@ -1012,69 +1039,6 @@ TEST_F(LibDhcpTest, unpackSubOptions4) {
     EXPECT_EQ(0x0, option_bar->getValue());
 }
 
-TEST_F(LibDhcpTest, isStandardOption4) {
-    // Get all option codes that are not occupied by standard options.
-    const uint16_t unassigned_codes[] = { 84, 96, 102, 103, 104, 105, 106, 107, 108,
-                                          109, 110, 111, 115, 126, 127, 147, 148, 149,
-                                          178, 179, 180, 181, 182, 183, 184, 185, 186,
-                                          187, 188, 189, 190, 191, 192, 193, 194, 195,
-                                          196, 197, 198, 199, 200, 201, 202, 203, 204,
-                                          205, 206, 207, 214, 215, 216, 217, 218, 219,
-                                          222, 223, 224, 225, 226, 227, 228, 229, 230,
-                                          231, 232, 233, 234, 235, 236, 237, 238, 239,
-                                          240, 241, 242, 243, 244, 245, 246, 247, 248,
-                                          249, 250, 251, 252, 253, 254 };
-    const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
-
-    // Try all possible option codes.
-    for (size_t i = 0; i < 256; ++i) {
-        // Some ranges of option codes are unassigned and thus the isStandardOption
-        // should return false for them.
-        bool check_unassigned = false;
-        // Check the array of unassigned options to find out whether option code
-        // is assigned to standard option or unassigned.
-        for (size_t j = 0; j < unassigned_num; ++j) {
-            // If option code is found within the array of unassigned options
-            // we the isStandardOption function should return false.
-            if (unassigned_codes[j] == i) {
-                check_unassigned = true;
-                EXPECT_FALSE(LibDHCP::isStandardOption(Option::V4,
-                                                       unassigned_codes[j]))
-                    << "Test failed for option code " << unassigned_codes[j];
-                break;
-            }
-        }
-        // If the option code belongs to the standard option then the
-        // isStandardOption should return true.
-        if (!check_unassigned) {
-            EXPECT_TRUE(LibDHCP::isStandardOption(Option::V4, i))
-                << "Test failed for the option code " << i;
-        }
-    }
-}
-
-TEST_F(LibDhcpTest, isStandardOption6) {
-    // All option codes in the range from 0 to 78 (except 10 and 35)
-    // identify the standard options.
-    for (uint16_t code = 0; code < 79; ++code) {
-        if (code != 10 && code != 35) {
-            EXPECT_TRUE(LibDHCP::isStandardOption(Option::V6, code))
-                << "Test failed for option code " << code;
-        }
-    }
-
-    // Check the option codes 10 and 35. They are unassigned.
-    EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 10));
-    EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 35));
-
-    // Check a range of option codes above 78. Those are option codes
-    // identifying non-standard options.
-    for (uint16_t code = 79; code < 512; ++code) {
-        EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, code))
-            << "Test failed for option code " << code;
-    }
-}
-
 TEST_F(LibDhcpTest, stdOptionDefs4) {
 
     // Create a buffer that holds dummy option data.
@@ -1619,18 +1583,45 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
 
     LibDhcpTest::testStdOptionDefs6(D6O_TIMESTAMP, begin, begin + 8,
                                     typeid(Option));
+
+    // RFC7598 options
+    LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
+                                 typeid(OptionCustom), "s46-rule-options");
+    LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
+                                 typeid(OptionCustom), "s46-rule-options");
+    LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
+                                 typeid(OptionCustom));
+    LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
+                                 typeid(OptionCustom));
+    LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_DMR, begin, end,
+                                 typeid(OptionCustom));
+    LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_V4V6BIND, begin,
+                                 end, typeid(OptionCustom),
+                                 "s46-v4v6bind-options");
+    LibDhcpTest::testOptionDefs6(V4V6_RULE_OPTION_SPACE, D6O_S46_PORTPARAMS,
+                                 begin, end, typeid(OptionCustom), "");
+    LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPE, begin, end,
+                                    typeid(OptionCustom),
+                                    "s46-cont-mape-options");
+    LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPT, begin, end,
+                                    typeid(OptionCustom),
+                                    "s46-cont-mapt-options");
+    LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_LW, begin, end,
+                                    typeid(OptionCustom),
+                                    "s46-cont-lw-options");
+
 }
 
 // This test checks if the DHCPv6 option definition can be searched by
 // an option name.
 TEST_F(LibDhcpTest, getOptionDefByName6) {
     // Get all definitions.
-    const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(Option::V6);
+    const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP6_OPTION_SPACE);
     // For each definition try to find it using option name.
     for (OptionDefContainer::const_iterator def = defs->begin();
          def != defs->end(); ++def) {
         OptionDefinitionPtr def_by_name =
-            LibDHCP::getOptionDef(Option::V6, (*def)->getName());
+            LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, (*def)->getName());
         ASSERT_TRUE(def_by_name);
         ASSERT_TRUE(**def == *def_by_name);
     }
@@ -1641,12 +1632,12 @@ TEST_F(LibDhcpTest, getOptionDefByName6) {
 // an option name.
 TEST_F(LibDhcpTest, getOptionDefByName4) {
     // Get all definitions.
-    const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(Option::V4);
+    const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP4_OPTION_SPACE);
     // For each definition try to find it using option name.
     for (OptionDefContainer::const_iterator def = defs->begin();
          def != defs->end(); ++def) {
         OptionDefinitionPtr def_by_name =
-            LibDHCP::getOptionDef(Option::V4, (*def)->getName());
+            LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, (*def)->getName());
         ASSERT_TRUE(def_by_name);
         ASSERT_TRUE(**def == *def_by_name);
     }

+ 175 - 0
src/lib/dhcp/tests/option6_pdexclude_unittest.cc

@@ -0,0 +1,175 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// Author: Andrei Pavel <andrei.pavel@qualitance.com>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcpsrv/pool.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace asiolink;
+
+namespace {
+
+// Prefix constants used in unit tests.
+const IOAddress v4("192.0.2.0");
+const IOAddress bee0("2001:db8:dead:bee0::");
+const IOAddress beef("2001:db8:dead:beef::");
+const IOAddress cafe("2001:db8:dead:cafe::");
+const IOAddress beef01("2001:db8:dead:beef::01");
+
+// This test verifies that the constructor sets parameters appropriately.
+TEST(Option6PDExcludeTest, constructor) {
+    Option6PDExclude option = Option6PDExclude(beef, 56, beef01, 60);
+
+    EXPECT_EQ(beef, option.getDelegatedPrefix());
+    EXPECT_EQ(56, option.getDelegatedPrefixLength());
+    EXPECT_EQ(beef01, option.getExcludedPrefix());
+    EXPECT_EQ(60, option.getExcludedPrefixLength());
+    // Total length is a sum of option header length, excluded prefix
+    // length (always 1 byte) and delegated prefix length - excluded prefix
+    // length rounded to bytes.
+    EXPECT_EQ(Option::OPTION6_HDR_LEN + 1 + 1, option.len());
+
+    // v4 prefix is not accepted.
+    EXPECT_THROW(Option6PDExclude(v4, 56, beef01, 64), BadValue);
+    EXPECT_THROW(Option6PDExclude(beef, 56, v4, 64), BadValue);
+    // Length greater than 128 is not accepted.
+    EXPECT_THROW(Option6PDExclude(beef, 128, beef01, 129), BadValue);
+    // Excluded prefix length must be greater than delegated prefix length.
+    EXPECT_THROW(Option6PDExclude(beef, 56, beef01, 56), BadValue);
+    // Both prefixes shifted by 56 must be equal (see RFC6603, section 4.2).
+    EXPECT_THROW(Option6PDExclude(cafe, 56, beef01, 64), BadValue);
+}
+
+// This test verifies that on-wire format of the Prefix Exclude option is
+// created properly.
+TEST(Option6PDExcludeTest, pack) {
+    // Expected wire format of the option.
+    const uint8_t expected_data[] = {
+        0x00, 0x43, // option code 67
+        0x00, 0x02, // option length 2
+        0x3F, 0x70  // excluded prefix length 59 + subnet id
+    };
+    std::vector<uint8_t> expected_vec(expected_data,
+                                      expected_data + sizeof(expected_data));
+    // Generate wire format of the option.
+    util::OutputBuffer buf(128);
+    Option6PDExcludePtr option;
+    ASSERT_NO_THROW(option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
+                                                      59,
+                                                      IOAddress("2001:db8:dead:beef::"),
+                                                      63)));
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Check that size matches.
+    ASSERT_EQ(expected_vec.size(), buf.getLength());
+
+    // Check that the generated wire format is correct.
+    const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+    std::vector<uint8_t> vec(data, data + buf.getLength());
+    ASSERT_TRUE(std::equal(vec.begin(), vec.end(), expected_vec.begin()));
+}
+
+// This test verifies parsing option wire format with subnet id of
+// 1 byte.
+TEST(Option6PDExcludeTest, unpack1ByteSubnetId) {
+    const uint8_t data[] = {
+        0x00, 0x43, // option code 67
+        0x00, 0x02, // option length 2
+        0x40, 0x78  // excluded prefix length 60 + subnet id
+    };
+    std::vector<uint8_t> vec(data, data + sizeof(data));
+
+    // Parse option.
+    Option6PDExcludePtr option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::1"),
+                                          59, vec.begin() + 4, vec.end()))
+    );
+
+    // Make sure that the option has been parsed correctly.
+    EXPECT_EQ("2001:db8:dead:bee0::1", option->getDelegatedPrefix().toText());
+    EXPECT_EQ(59, static_cast<int>(option->getDelegatedPrefixLength()));
+    EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+    EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
+
+// This test verifies parsing option wire format with subnet id of
+// 1 bytes.
+TEST(Option6PDExcludeTest, unpack2ByteSubnetId) {
+    const uint8_t data[] = {
+        0x00, 0x43,       // option code 67
+        0x00, 0x02,       // option length
+        0x40, 0xbe, 0xef  // excluded prefix length 60 + subnet id
+    };
+    std::vector<uint8_t> vec(data, data + sizeof(data));
+
+    // Parse option.
+    Option6PDExcludePtr option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6PDExclude(IOAddress("2001:db8:dead::"),
+                                          48, vec.begin() + 4, vec.end()))
+    );
+
+    // Make sure that the option has been parsed correctly.
+    EXPECT_EQ("2001:db8:dead::", option->getDelegatedPrefix().toText());
+    EXPECT_EQ(48, static_cast<int>(option->getDelegatedPrefixLength()));
+    EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+    EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
+
+// This test verifies that errors are reported when option buffer contains
+// invalid option data.
+TEST(Option6PDExcludeTest, unpackErrors) {
+    const uint8_t data[] = {
+        0x00, 0x43,
+        0x00, 0x02,
+        0x40, 0x78
+    };
+    std::vector<uint8_t> vec(data, data + sizeof(data));
+
+    // Option has no IPv6 subnet id.
+    EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
+                                  59, vec.begin() + 4, vec.end() - 1),
+                 BadValue);
+
+    // Option has IPv6 subnet id of 1 byte, but it should have 2 bytes.
+    EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead::"), 48,
+                                  vec.begin() + 4, vec.end()),
+                 BadValue);
+}
+
+// This test verifies conversion of the Prefix Exclude option to the
+// textual format.
+TEST(Option6PDExcludeTest, toText) {
+    Option6PDExclude option(bee0, 59, beef, 64);
+    EXPECT_EQ("type=00067, len=00002: 2001:db8:dead:beef::/64",
+              option.toText());
+}
+
+// This test verifies calculation of the Prefix Exclude option length.
+TEST(Option6PDExcludeTest, len) {
+   Option6PDExcludePtr option;
+   // The IPv6 subnet id is 2 bytes long. Hence the total length is
+   // 2 bytes (option code) +  2 bytes (option length) + 1 byte
+   // (excluded prefix length) + 2 bytes (IPv6 subnet id) = 7 bytes.
+   ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 48, beef, 64)));
+   EXPECT_EQ(7, option->len());
+
+   // IPv6 subnet id is 1 byte long. The total length is 6.
+   ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 59, beef, 64)));
+   EXPECT_EQ(6, option->len());
+}
+
+} // anonymous namespace

+ 360 - 11
src/lib/dhcp/tests/option_custom_unittest.cc

@@ -18,6 +18,11 @@ using namespace isc::dhcp;
 
 namespace {
 
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+                                 IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
 /// @brief OptionCustomTest test class.
 class OptionCustomTest : public ::testing::Test {
 public:
@@ -476,6 +481,82 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
     );
 }
 
+// The purpose of this test is to verify that the option definition comprising
+// single variable length prefix can be used to create an instance of custom
+// option.
+TEST_F(OptionCustomTest, prefixData) {
+    OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix",
+                             "option-foo-space");
+
+    // Initialize input buffer.
+    OptionBuffer buf;
+    writeInt<uint8_t>(32, buf);
+    writeInt<uint32_t>(0x30000001, buf);
+
+    // Append suboption.
+    appendV6Suboption(buf);
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Custom option should comprise exactly one buffer that represents
+    // a prefix.
+    PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+    // Read prefix from buffer #0.
+    ASSERT_NO_THROW(prefix = option->readPrefix(0));
+
+    // The prefix comprises a prefix length and prefix value.
+    EXPECT_EQ(32, prefix.first.asUnsigned());
+    EXPECT_EQ("3000:1::", prefix.second.toText());
+
+    // Parsed option should have one suboption.
+    EXPECT_TRUE(hasV6Suboption(option.get()));
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single PSID can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, psidData) {
+    OptionDefinition opt_def("option-foo", 1000, "psid",
+                             "option-foo-space");
+
+    // Initialize input buffer.
+    OptionBuffer buf;
+    writeInt<uint8_t>(4, buf);
+    writeInt<uint16_t>(0x8000, buf);
+
+    // Append suboption.
+    appendV6Suboption(buf);
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Custom option should comprise exactly one buffer that represents
+    // a PSID length / PSID value tuple.
+    PSIDTuple psid;
+    // Read PSID length / PSID value from buffer #0.
+    ASSERT_NO_THROW(psid = option->readPsid(0));
+
+    // The PSID comprises a PSID length and PSID value.
+    EXPECT_EQ(4, psid.first.asUnsigned());
+    EXPECT_EQ(0x08, psid.second.asUint16());
+
+    // Parsed option should have one suboption.
+    EXPECT_TRUE(hasV6Suboption(option.get()));
+}
 
 // The purpose of this test is to verify that the option definition comprising
 // string value can be used to create an instance of custom option.
@@ -768,6 +849,97 @@ TEST_F(OptionCustomTest, fqdnDataArray) {
     EXPECT_EQ("example.com.", domain1);
 }
 
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 prefixes can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, prefixDataArray) {
+    OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix", true);
+
+    // The following buffer comprises three prefixes with different
+    // prefix lengths.
+    const uint8_t data[] = {
+        32, 0x30, 0x01, 0x00, 0x01,             // 3001:1::/32
+        16, 0x30, 0x00,                         // 3000::/16
+        48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01  // 2001:db8:1::/48
+    };
+
+    // Initialize input buffer
+    OptionBuffer buf(data,
+                     data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields with 3 prefixes.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    PrefixTuple prefix0(ZERO_PREFIX_TUPLE);
+    PrefixTuple prefix1(ZERO_PREFIX_TUPLE);
+    PrefixTuple prefix2(ZERO_PREFIX_TUPLE);
+
+    ASSERT_NO_THROW(prefix0 = option->readPrefix(0));
+    ASSERT_NO_THROW(prefix1 = option->readPrefix(1));
+    ASSERT_NO_THROW(prefix2 = option->readPrefix(2));
+
+    EXPECT_EQ(32, prefix0.first.asUnsigned());
+    EXPECT_EQ("3001:1::", prefix0.second.toText());
+
+    EXPECT_EQ(16, prefix1.first.asUnsigned());
+    EXPECT_EQ("3000::", prefix1.second.toText());
+
+    EXPECT_EQ(48, prefix2.first.asUnsigned());
+    EXPECT_EQ("2001:db8:1::", prefix2.second.toText());
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of PSIDs can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, psidDataArray) {
+    OptionDefinition opt_def("option-foo", 1000, "psid", true);
+
+    // The following buffer comprises three PSIDs.
+    const uint8_t data[] = {
+        4, 0x80, 0x00,    // PSID len = 4, PSID = '1000 000000000000b'
+        6, 0xD4, 0x00,    // PSID len = 6, PSID = '110101 0000000000b'
+        1, 0x80, 0x00     // PSID len = 1, PSID = '1 000000000000000b'
+    };
+    // Initialize input buffer.
+    OptionBuffer buf(data,
+                     data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields with 3 PSIDs.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    PSIDTuple psid0;
+    PSIDTuple psid1;
+    PSIDTuple psid2;
+
+    ASSERT_NO_THROW(psid0 = option->readPsid(0));
+    ASSERT_NO_THROW(psid1 = option->readPsid(1));
+    ASSERT_NO_THROW(psid2 = option->readPsid(2));
+
+    // PSID value is equal to '1000b' (8).
+    EXPECT_EQ(4, psid0.first.asUnsigned());
+    EXPECT_EQ(0x08, psid0.second.asUint16());
+
+    // PSID value is equal to '110101b' (0x35)
+    EXPECT_EQ(6, psid1.first.asUnsigned());
+    EXPECT_EQ(0x35, psid1.second.asUint16());
+
+    // PSID value is equal to '1b' (1).
+    EXPECT_EQ(1, psid2.first.asUnsigned());
+    EXPECT_EQ(0x01, psid2.second.asUint16());
+}
+
 // The purpose of this test is to verify that the opton definition comprising
 // a record of fixed-size fields can be used to create an option with a
 // suboption.
@@ -821,6 +993,7 @@ TEST_F(OptionCustomTest, recordData) {
     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("string"));
 
     const char fqdn_data[] = {
@@ -841,6 +1014,9 @@ TEST_F(OptionCustomTest, recordData) {
     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 field 5 to string value.
     writeString("ABCD", buf);
 
@@ -851,7 +1027,7 @@ TEST_F(OptionCustomTest, recordData) {
     ASSERT_TRUE(option);
 
     // We should have 6 data fields.
-    ASSERT_EQ(6, option->getDataFieldsNum());
+    ASSERT_EQ(7, option->getDataFieldsNum());
 
     // Verify value in the field 0.
     uint16_t value0 = 0;
@@ -879,9 +1055,15 @@ TEST_F(OptionCustomTest, recordData) {
     EXPECT_EQ("2001:db8:1::1", value4.toText());
 
     // Verify value in the field 5.
-    std::string value5;
-    ASSERT_NO_THROW(value5 = option->readString(5));
-    EXPECT_EQ("ABCD", value5);
+    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.
+    std::string value6;
+    ASSERT_NO_THROW(value6 = option->readString(6));
+    EXPECT_EQ("ABCD", value6);
 }
 
 // The purpose of this test is to verify that truncated buffer
@@ -1074,6 +1256,64 @@ TEST_F(OptionCustomTest, setIpv6AddressData) {
 }
 
 // The purpose of this test is to verify that an option comprising
+// a prefix can be created and that the prefix can be overriden by
+// a new value.
+TEST_F(OptionCustomTest, setPrefixData) {
+    OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix");
+
+    // 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);
+
+    // Make sure the default prefix is set.
+    PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+    ASSERT_NO_THROW(prefix = option->readPrefix());
+    EXPECT_EQ(0, prefix.first.asUnsigned());
+    EXPECT_EQ("::", prefix.second.toText());
+
+    // Write prefix.
+    ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), IOAddress("2001:db8:1::")));
+
+    // Read prefix back and make sure it is the one we just set.
+    ASSERT_NO_THROW(prefix = option->readPrefix());
+    EXPECT_EQ(48, prefix.first.asUnsigned());
+    EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// a single PSID can be created and that the PSID can be overriden
+// by a new value.
+TEST_F(OptionCustomTest, setPsidData) {
+    OptionDefinition opt_def("option-foo", 1000, "psid");
+
+    // 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);
+
+    // Make sure the default PSID is set.
+    PSIDTuple psid;
+    ASSERT_NO_THROW(psid = option->readPsid());
+    EXPECT_EQ(0, psid.first.asUnsigned());
+    EXPECT_EQ(0, psid.second.asUint16());
+
+    // Write PSID.
+    ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8)));
+
+    // Read PSID back and make sure it is the one we just set.
+    ASSERT_NO_THROW(psid = option->readPsid());
+    EXPECT_EQ(4, psid.first.asUnsigned());
+    EXPECT_EQ(8, psid.second.asUint16());
+}
+
+// The purpose of this test is to verify that an option comprising
 // single string value can be created and that this value
 // is initialized to the default value. Also, this test checks that
 // this value can be overwritten by a new value.
@@ -1280,6 +1520,96 @@ TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
     );
 }
 
+/// The purpose of this test is to verify that an option comprising an
+/// array of PSIDs can be created with no PSIDs and that PSIDs can be
+/// later added after the option has been created.
+TEST_F(OptionCustomTest, setPSIDPrefixArray) {
+    OptionDefinition opt_def("option-foo", 1000, "psid", true);
+
+    // 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);
+
+    // Initially, the array does not contain any data fields.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 new PSIDs
+    ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(4), PSID(1)));
+    ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(0), PSID(123)));
+    ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(1), PSID(1)));
+
+    // Verify the stored values.
+    ASSERT_NO_THROW({
+         PSIDTuple psid0 = option->readPsid(0);
+         EXPECT_EQ(4, psid0.first.asUnsigned());
+         EXPECT_EQ(1, psid0.second.asUint16());
+    });
+
+    ASSERT_NO_THROW({
+         PSIDTuple psid1 = option->readPsid(1);
+         EXPECT_EQ(0, psid1.first.asUnsigned());
+         EXPECT_EQ(0, psid1.second.asUint16());
+    });
+
+    ASSERT_NO_THROW({
+         PSIDTuple psid2 = option->readPsid(2);
+         EXPECT_EQ(1, psid2.first.asUnsigned());
+         EXPECT_EQ(1, psid2.second.asUint16());
+    });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of IPv6 prefixes can be created with no prefixes and that
+/// prefixes can be later added after the option has been created.
+TEST_F(OptionCustomTest, setIPv6PrefixDataArray) {
+    OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix", true);
+
+    // 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);
+
+    // Initially, the array does not contain any data fields.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 new IPv6 prefixes into the array.
+    ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(64),
+                                              IOAddress("2001:db8:1::")));
+    ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(32),
+                                              IOAddress("3001:1::")));
+    ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(16),
+                                              IOAddress("3000::")));
+
+    // We should have now 3 addresses added.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Verify the stored values.
+    ASSERT_NO_THROW({
+         PrefixTuple prefix0 = option->readPrefix(0);
+         EXPECT_EQ(64, prefix0.first.asUnsigned());
+         EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+    });
+
+    ASSERT_NO_THROW({
+         PrefixTuple prefix1 = option->readPrefix(1);
+         EXPECT_EQ(32, prefix1.first.asUnsigned());
+         EXPECT_EQ("3001:1::", prefix1.second.toText());
+    });
+
+    ASSERT_NO_THROW({
+         PrefixTuple prefix2 = option->readPrefix(2);
+         EXPECT_EQ(16, prefix2.first.asUnsigned());
+         EXPECT_EQ("3000::", prefix2.second.toText());
+    });
+}
+
 TEST_F(OptionCustomTest, setRecordData) {
     OptionDefinition opt_def("OPTION_FOO", 1000, "record");
 
@@ -1288,6 +1618,8 @@ TEST_F(OptionCustomTest, setRecordData) {
     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("string"));
 
     // Create an option and let the data field be initialized
@@ -1300,7 +1632,7 @@ TEST_F(OptionCustomTest, setRecordData) {
 
     // The number of elements should be equal to number of elements
     // in the record.
-    ASSERT_EQ(6, option->getDataFieldsNum());
+    ASSERT_EQ(8, option->getDataFieldsNum());
 
     // Check that the default values have been correctly set.
     uint16_t value0;
@@ -1318,9 +1650,17 @@ TEST_F(OptionCustomTest, setRecordData) {
     IOAddress value4("2001:db8:1::1");
     ASSERT_NO_THROW(value4 = option->readAddress(4));
     EXPECT_EQ("::", value4.toText());
-    std::string value5 = "xyz";
-    ASSERT_NO_THROW(value5 = option->readString(5));
-    EXPECT_TRUE(value5.empty());
+    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 = "xyz";
+    ASSERT_NO_THROW(value7 = option->readString(7));
+    EXPECT_TRUE(value7.empty());
 
     // Override each value with a new value.
     ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
@@ -1328,7 +1668,10 @@ TEST_F(OptionCustomTest, setRecordData) {
     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->writeString("hello world", 5));
+    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->writeString("hello world", 7));
 
     // Check that the new values have been correctly set.
     ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
@@ -1341,8 +1684,14 @@ TEST_F(OptionCustomTest, setRecordData) {
     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->readString(5));
-    EXPECT_EQ(value5, "hello world");
+    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->readString(7));
+    EXPECT_EQ(value7, "hello world");
 }
 
 // The purpose of this test is to verify that pack function for

+ 260 - 1
src/lib/dhcp/tests/option_data_types_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,12 +7,19 @@
 #include <config.h>
 #include <dhcp/option_data_types.h>
 #include <gtest/gtest.h>
+#include <utility>
 
 using namespace isc;
+using namespace isc::asiolink;
 using namespace isc::dhcp;
 
 namespace {
 
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+                                 IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
 /// @brief Test class for option data type utilities.
 class OptionDataTypesTest : public ::testing::Test {
 public:
@@ -456,6 +463,258 @@ TEST_F(OptionDataTypesTest, writeFqdn) {
     );
 }
 
+// The purpose of this test is to verify that the variable length prefix
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readPrefix) {
+    std::vector<uint8_t> buf;
+
+    // Prefix 2001:db8::/64
+    writeInt<uint8_t>(64, buf);
+    writeInt<uint32_t>(0x20010db8, buf);
+    writeInt<uint32_t>(0, buf);
+
+    PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+    ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+    EXPECT_EQ(64, prefix.first.asUnsigned());
+    EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+    buf.clear();
+
+    // Prefix 2001:db8::/63
+    writeInt<uint8_t>(63, buf);
+    writeInt<uint32_t>(0x20010db8, buf);
+    writeInt<uint32_t>(0, buf);
+
+    ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+    EXPECT_EQ(63, prefix.first.asUnsigned());
+    EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+    buf.clear();
+
+    // Prefix 2001:db8:c0000. Note that the last four bytes are filled with
+    // 0xFF (all bits set). When the prefix is read those non-significant
+    // bits (beyond prefix length) should be ignored (read as 0). Only first
+    // two bits of 0xFFFFFFFF should be read, thus 0xC000, rather than 0xFFFF.
+    writeInt<uint8_t>(34, buf);
+    writeInt<uint32_t>(0x20010db8, buf);
+    writeInt<uint32_t>(0xFFFFFFFF, buf);
+
+    ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+    EXPECT_EQ(34, prefix.first.asUnsigned());
+    EXPECT_EQ("2001:db8:c000::", prefix.second.toText());
+
+    buf.clear();
+
+    // Prefix having a length of 0.
+    writeInt<uint8_t>(0, buf);
+    writeInt<uint16_t>(0x2001, buf);
+
+    ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+    EXPECT_EQ(0, prefix.first.asUnsigned());
+    EXPECT_EQ("::", prefix.second.toText());
+
+    buf.clear();
+
+    // Prefix having a maximum length of 128.
+    writeInt<uint8_t>(128, buf);
+    buf.insert(buf.end(), 16, 0x11);
+
+    ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+    EXPECT_EQ(128, prefix.first.asUnsigned());
+    EXPECT_EQ("1111:1111:1111:1111:1111:1111:1111:1111",
+              prefix.second.toText());
+
+    buf.clear();
+
+    // Prefix length is greater than 128. This should result in an
+    // error.
+    writeInt<uint8_t>(129, buf);
+    writeInt<uint16_t>(0x3000, buf);
+    buf.resize(17);
+
+    EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+                 BadDataTypeCast);
+
+    buf.clear();
+
+    // Buffer truncated. Prefix length of 10 requires at least 2 bytes,
+    // but there is only one byte.
+    writeInt<uint8_t>(10, buf);
+    writeInt<uint8_t>(1, buf);
+
+    EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+                 BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the variable length prefix
+// is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePrefix) {
+    // Initialize a buffer and store some value in it. We'll want to make
+    // sure that the prefix being written will not override this value, but
+    // will rather be appended.
+    std::vector<uint8_t> buf(1, 1);
+
+    // Prefix 2001:db8:FFFF::/34 is equal to 2001:db8:C000::/34 because
+    // there are only 34 significant bits. All other bits must be zeroed.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(34),
+                                                    IOAddress("2001:db8:FFFF::"),
+                                                    buf));
+    ASSERT_EQ(7, buf.size());
+
+    EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+    EXPECT_EQ(34, static_cast<unsigned>(buf[1]));
+    EXPECT_EQ(0x20, static_cast<unsigned>(buf[2]));
+    EXPECT_EQ(0x01, static_cast<unsigned>(buf[3]));
+    EXPECT_EQ(0x0D, static_cast<unsigned>(buf[4]));
+    EXPECT_EQ(0xB8, static_cast<unsigned>(buf[5]));
+    EXPECT_EQ(0xC0, static_cast<unsigned>(buf[6]));
+
+    buf.clear();
+
+    // Prefix length is 0. The entire prefix should be ignored.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(0),
+                                                    IOAddress("2001:db8:FFFF::"),
+                                                    buf));
+    ASSERT_EQ(1, buf.size());
+    EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+
+    buf.clear();
+
+    // Prefix having a maximum length of 128.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(128),
+                                                    IOAddress("2001:db8::FF"),
+                                                    buf));
+
+    // We should now have a 17 bytes long buffer. 1 byte goes for a prefix
+    // length field, the remaining ones hold the prefix.
+    ASSERT_EQ(17, buf.size());
+    // Because the prefix is 16 bytes long, we can simply use the
+    // IOAddress convenience function to read it back and compare
+    // it with the textual representation. This is simpler than
+    // comparing each byte separately.
+    IOAddress prefix_read = IOAddress::fromBytes(AF_INET6, &buf[1]);
+    EXPECT_EQ("2001:db8::ff", prefix_read.toText());
+
+    buf.clear();
+
+    // It is illegal to use IPv4 address as prefix.
+    EXPECT_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(4),
+                                                 IOAddress("10.0.0.1"), buf),
+                 BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the
+// PSID-len/PSID tuple can be read from a buffer.
+TEST_F(OptionDataTypesTest, readPsid) {
+    std::vector<uint8_t> buf;
+
+    // PSID length is 6 (bits)
+    writeInt<uint8_t>(6, buf);
+    // 0xA400 is represented as 1010010000000000b, which is equivalent
+    // of portset 0x29 (101001b).
+    writeInt<uint16_t>(0xA400, buf);
+
+    PSIDTuple psid;
+    ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+    EXPECT_EQ(6, psid.first.asUnsigned());
+    EXPECT_EQ(0x29, psid.second.asUint16());
+
+    buf.clear();
+
+    // PSID length is 0, in which case PSID should be ignored.
+    writeInt<uint8_t>(0, buf);
+    // Let's put some junk into the PSID field to make sure it will
+    // be ignored.
+    writeInt<uint16_t>(0x1234, buf);
+    ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+    EXPECT_EQ(0, psid.first.asUnsigned());
+    EXPECT_EQ(0, psid.second.asUint16());
+
+    buf.clear();
+
+    // PSID length greater than 16 is not allowed.
+    writeInt<uint8_t>(17, buf);
+    writeInt<uint16_t>(0, buf);
+    EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+                 BadDataTypeCast);
+
+    buf.clear();
+
+    // PSID length is 3 bits, but the PSID value is 11 (1011b), so it
+    // is encoded on 4 bits, rather than 3.
+    writeInt<uint8_t>(3, buf);
+    writeInt<uint16_t>(0xB000, buf);
+    EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+                 BadDataTypeCast);
+
+    buf.clear();
+
+    // Buffer is truncated -  2 bytes instead of 3.
+    writeInt<uint8_t>(4, buf);
+    writeInt<uint8_t>(0xF0, buf);
+    EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+                 BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the PSID-len/PSID
+// tuple is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePsid) {
+    // Let's create a buffer with some data in it. We want to make
+    // sure that the existing data remain untouched when we write
+    // PSID to the buffer.
+    std::vector<uint8_t> buf(1, 1);
+    // PSID length is 4 (bits), PSID value is 8.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(4), PSID(8), buf));
+    ASSERT_EQ(4, buf.size());
+    // The byte which existed in the buffer should still hold the
+    // same value.
+    EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+    // PSID length should be written as specified in the function call.
+    EXPECT_EQ(4, static_cast<unsigned>(buf[1]));
+    // The PSID structure is as follows:
+    // UUUUPPPPPPPPPPPP, where "U" are useful bits on which we code
+    // the PSID. "P" are zero padded bits. The PSID value 8 is coded
+    // on four useful bits as '1000b'. That means that the PSID value
+    // encoded in the PSID field is: '1000000000000000b', which is
+    // 0x8000. The next two EXPECT_EQ statements verify that.
+    EXPECT_EQ(0x80, static_cast<unsigned>(buf[2]));
+    EXPECT_EQ(0x00, static_cast<unsigned>(buf[3]));
+
+    // Clear the buffer to make sure we don't append to the
+    // existing data.
+    buf.clear();
+
+    // The PSID length of 0 causes the PSID value (of 6) to be ignored.
+    // As a result, the buffer should hold only zeros.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(0), PSID(6), buf));
+    ASSERT_EQ(3, buf.size());
+    EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+    EXPECT_EQ(0, static_cast<unsigned>(buf[1]));
+    EXPECT_EQ(0, static_cast<unsigned>(buf[2]));
+
+    buf.clear();
+
+    // Another test case, to verify that we can use the maximum length
+    // of PSID (16 bits).
+    ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(16), PSID(5), buf));
+    ASSERT_EQ(3, buf.size());
+    // PSID length should be written with no change.
+    EXPECT_EQ(16, static_cast<unsigned>(buf[0]));
+    // Check PSID value.
+    EXPECT_EQ(0x00, static_cast<unsigned>(buf[1]));
+    EXPECT_EQ(0x05, static_cast<unsigned>(buf[2]));
+
+    // PSID length of 17 exceeds the maximum allowed value of 16.
+    EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(17), PSID(1), buf),
+                 OutOfRange);
+
+    // PSID length is 1, which allows for coding up to two (2^1)
+    // port sets. These are namely port set 0 and port set 1. The
+    // value of 2 is out of that range.
+    EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(1), PSID(2), buf),
+                 BadDataTypeCast);
+}
+
 // The purpose of this test is to verify that the string
 // can be read from a buffer correctly.
 TEST_F(OptionDataTypesTest, readString) {

+ 222 - 1
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -1241,7 +1241,7 @@ TEST_F(OptionDefinitionTest, integerInvalidType) {
     // see if it rejects it.
     OptionBuffer buf(1);
     EXPECT_THROW(
-        OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, "dhcp6",
+        OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, DHCP6_OPTION_SPACE,
                                                buf.begin(), buf.end()),
         isc::dhcp::InvalidDataType
     );
@@ -1289,4 +1289,225 @@ TEST_F(OptionDefinitionTest, haveClientFqdnFormat) {
     EXPECT_FALSE(opt_def_invalid.haveClientFqdnFormat());
 }
 
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, prefix) {
+    OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix");
+
+    // Create a buffer holding a prefix.
+    OptionBuffer buf;
+    buf.push_back(32);
+    buf.push_back(0x30);
+    buf.push_back(0x00);
+    buf.resize(5);
+
+    OptionPtr option_v6;
+
+    // Create an instance of this option from the definition.
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+    );
+
+    // Make sure that the returned option class is correct.
+    const Option* optptr = option_v6.get();
+    ASSERT_TRUE(optptr);
+    ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+    // Validate the value.
+    OptionCustomPtr option_cast_v6 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+    ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+    PrefixTuple prefix = option_cast_v6->readPrefix();
+    EXPECT_EQ(32, prefix.first.asUnsigned());
+    EXPECT_EQ("3000::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and that the instance of this option can be
+// created by specifying the prefix in the textual format.
+TEST_F(OptionDefinitionTest, prefixTokenized) {
+    OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix");
+
+    OptionPtr option_v6;
+    // Specify a single prefix.
+    std::vector<std::string> values(1, "2001:db8:1::/64");
+
+    // Create an instance of the option using the definition.
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+    );
+
+    // Make sure that the returned option class is correct.
+    const Option* optptr = option_v6.get();
+    ASSERT_TRUE(optptr);
+    ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+    // Validate the value.
+    OptionCustomPtr option_cast_v6 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+    ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+    PrefixTuple prefix = option_cast_v6->readPrefix();
+    EXPECT_EQ(64, prefix.first.asUnsigned());
+    EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with an array
+// of IPv6 prefixes can be created and that the instance of this
+// option can be created by specifying multiple prefixes in the
+// textual format.
+TEST_F(OptionDefinitionTest, prefixArrayTokenized) {
+    OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix", true);
+
+    OptionPtr option_v6;
+
+    // Specify 3 prefixes
+    std::vector<std::string> values;
+    values.push_back("2001:db8:1:: /64");
+    values.push_back("3000::/ 32");
+    values.push_back("3001:1:: /  48");
+
+    // Create an instance of an option using the definition.
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+    );
+
+    // Make sure that the option class returned is correct.
+    const Option* optptr = option_v6.get();
+    ASSERT_TRUE(optptr);
+    ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+    OptionCustomPtr option_cast_v6 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+    // There should be 3 prefixes in this option.
+    ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+    ASSERT_NO_THROW({
+        PrefixTuple prefix0 = option_cast_v6->readPrefix(0);
+        EXPECT_EQ(64, prefix0.first.asUnsigned());
+        EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+    });
+
+    ASSERT_NO_THROW({
+        PrefixTuple prefix1 = option_cast_v6->readPrefix(1);
+        EXPECT_EQ(32, prefix1.first.asUnsigned());
+        EXPECT_EQ("3000::", prefix1.second.toText());
+    });
+
+    ASSERT_NO_THROW({
+        PrefixTuple prefix2 = option_cast_v6->readPrefix(2);
+        EXPECT_EQ(48, prefix2.first.asUnsigned());
+        EXPECT_EQ("3001:1::", prefix2.second.toText());
+    });
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, psid) {
+    OptionDefinition opt_def("option-psid", 1000, "psid");
+
+    OptionPtr option_v6;
+
+    // Create a buffer holding PSID.
+    OptionBuffer buf;
+    buf.push_back(6);
+    buf.push_back(0x4);
+    buf.push_back(0x0);
+
+    // Create an instance of this option from the definition.
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+    );
+
+    // Make sure that the returned option class is correct.
+    const Option* optptr = option_v6.get();
+    ASSERT_TRUE(optptr);
+    ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+    // Validate the value.
+    OptionCustomPtr option_cast_v6 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+    ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+    PSIDTuple psid = option_cast_v6->readPsid();
+    EXPECT_EQ(6, psid.first.asUnsigned());
+    EXPECT_EQ(1, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and that the instance of this option can be
+// created by specifying PSID length and value in the textual format.
+TEST_F(OptionDefinitionTest, psidTokenized) {
+    OptionDefinition opt_def("option-psid", 1000, "psid");
+
+    OptionPtr option_v6;
+    // Specify a single PSID with a length of 6 and value of 3.
+    std::vector<std::string> values(1, "3 / 6");
+
+    // Create an instance of the option using the definition.
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+    );
+
+    // Make sure that the returned option class is correct.
+    const Option* optptr = option_v6.get();
+    ASSERT_TRUE(optptr);
+    ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+    // Validate the value.
+    OptionCustomPtr option_cast_v6 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+    ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+    PSIDTuple psid = option_cast_v6->readPsid();
+    EXPECT_EQ(6, psid.first.asUnsigned());
+    EXPECT_EQ(3, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with an array
+// of PSIDs can be created and that the instance of this option can be
+// created by specifying multiple PSIDs in the textual format.
+TEST_F(OptionDefinitionTest, psidArrayTokenized) {
+    OptionDefinition opt_def("option-psid", 1000, "psid", true);
+
+    OptionPtr option_v6;
+
+    // Specify 3 PSIDs.
+    std::vector<std::string> values;
+    values.push_back("3 / 6");
+    values.push_back("0/1");
+    values.push_back("7     /   3");
+
+    // Create an instance of an option using the definition.
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+    );
+
+    // Make sure that the option class returned is correct.
+    const Option* optptr = option_v6.get();
+    ASSERT_TRUE(optptr);
+    ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+    OptionCustomPtr option_cast_v6 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+    // There should be 3 PSIDs in this option.
+    ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+    // Check their values.
+    PSIDTuple psid0;
+    PSIDTuple psid1;
+    PSIDTuple psid2;
+
+    psid0 = option_cast_v6->readPsid(0);
+    EXPECT_EQ(6, psid0.first.asUnsigned());
+    EXPECT_EQ(3, psid0.second.asUint16());
+
+    psid1 = option_cast_v6->readPsid(1);
+    EXPECT_EQ(1, psid1.first.asUnsigned());
+    EXPECT_EQ(0, psid1.second.asUint16());
+
+    psid2 = option_cast_v6->readPsid(2);
+    EXPECT_EQ(3, psid2.first.asUnsigned());
+    EXPECT_EQ(7, psid2.second.asUint16());
+}
+
 } // anonymous namespace

+ 5 - 5
src/lib/dhcp/tests/option_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
 #include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 
@@ -593,15 +594,14 @@ TEST_F(OptionTest, setEncapsulatedSpace) {
     Option optv6(Option::V6, 258);
     EXPECT_TRUE(optv6.getEncapsulatedSpace().empty());
 
-    optv6.setEncapsulatedSpace("dhcp6");
-    EXPECT_EQ("dhcp6", optv6.getEncapsulatedSpace());
+    optv6.setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+    EXPECT_EQ(DHCP6_OPTION_SPACE, optv6.getEncapsulatedSpace());
 
     Option optv4(Option::V4, 125);
     EXPECT_TRUE(optv4.getEncapsulatedSpace().empty());
 
-    optv4.setEncapsulatedSpace("dhcp4");
-    EXPECT_EQ("dhcp4", optv4.getEncapsulatedSpace());
-
+    optv4.setEncapsulatedSpace(DHCP4_OPTION_SPACE);
+    EXPECT_EQ(DHCP4_OPTION_SPACE, optv4.getEncapsulatedSpace());
 }
 
 // This test verifies that cloneInternal returns NULL pointer if

+ 5 - 4
src/lib/dhcpsrv/alloc_engine.cc

@@ -350,7 +350,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet,
       duid_(duid), hwaddr_(), host_identifiers_(), host_(),
       fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
       hostname_(hostname), callout_handle_(callout_handle),
-      allocated_resources_(), ias_() {
+      allocated_resources_(), ias_(), pd_exclude_requested_(false) {
 
     // Initialize host identifiers.
     if (duid) {
@@ -688,10 +688,11 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
         // non-PD leases.
         uint8_t prefix_len = 128;
         if (ctx.currentIA().type_ == Lease::TYPE_PD) {
-            Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
+            pool = boost::dynamic_pointer_cast<Pool6>(
                 ctx.subnet_->getPool(ctx.currentIA().type_, candidate, false));
-            /// @todo: verify that the pool is non-null
-            prefix_len = pool->getLength();
+            if (pool) {
+                prefix_len = pool->getLength();
+            }
         }
 
         Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,

+ 4 - 0
src/lib/dhcpsrv/alloc_engine.h

@@ -397,6 +397,10 @@ public:
         /// @brief Container holding IA specific contexts.
         std::vector<IAContext> ias_;
 
+        /// @brief Indicates if PD exclude option has been requested by a
+        /// client.
+        bool pd_exclude_requested_;
+
         /// @brief Convenience method adding allocated prefix or address.
         ///
         /// @param prefix Prefix or address.

+ 25 - 13
src/lib/dhcpsrv/cfg_option.cc

@@ -105,19 +105,31 @@ CfgOption::encapsulateInternal(const std::string& option_space) {
     // from the option spaces they encapsulate.
     for (OptionContainer::const_iterator opt = options->begin();
          opt != options->end(); ++opt) {
-        // Get encapsulated option space for the option.
-        const std::string& encap_space = opt->option_->getEncapsulatedSpace();
-        // Empty value means that no option space is encapsulated.
-        if (!encap_space.empty()) {
-            // Retrieve all options from the encapsulated option space.
-            OptionContainerPtr encap_options = getAll(encap_space);
-            for (OptionContainer::const_iterator encap_opt =
-                     encap_options->begin(); encap_opt != encap_options->end();
-                 ++encap_opt) {
-                // Add sub-option if there isn't one added already.
-                if (!opt->option_->getOption(encap_opt->option_->getType())) {
-                    opt->option_->addOption(encap_opt->option_);
-                }
+        encapsulateInternal(opt->option_);
+    }
+}
+
+void
+CfgOption::encapsulateInternal(const OptionPtr& option) {
+    // Get encapsulated option space for the option.
+    const std::string& encap_space = option->getEncapsulatedSpace();
+    // Empty value means that no option space is encapsulated.
+    if (!encap_space.empty()) {
+        // Retrieve all options from the encapsulated option space.
+        OptionContainerPtr encap_options = getAll(encap_space);
+        for (OptionContainer::const_iterator encap_opt =
+                 encap_options->begin(); encap_opt != encap_options->end();
+             ++encap_opt) {
+            // Add sub-option if there isn't one added already.
+            if (!option->getOption(encap_opt->option_->getType())) {
+                option->addOption(encap_opt->option_);
+            }
+            // This is a workaround for preventing infinite recursion when
+            // trying to encapsulate options created with default global option
+            // spaces.
+            if (encap_space != DHCP4_OPTION_SPACE &&
+                encap_space != DHCP6_OPTION_SPACE) {
+                encapsulateInternal(encap_opt->option_);
             }
         }
     }

+ 10 - 0
src/lib/dhcpsrv/cfg_option.h

@@ -409,6 +409,16 @@ private:
     /// which encapsulated options are appended.
     void encapsulateInternal(const std::string& option_space);
 
+    /// @brief Appends encapsulated options from the option space encapsulated
+    /// by the specified option.
+    ///
+    /// This method will go over all options belonging to the encapsulated space
+    /// and will check which option spaces they encapsulate recursively,
+    /// adding these options to the current option
+    ///
+    /// @param option which encapsulated options.
+    void encapsulateInternal(const OptionPtr& option);
+
     /// @brief Merges data from two option containers.
     ///
     /// This method merges options from one option container to another

+ 1 - 6
src/lib/dhcpsrv/cfg_option_def.cc

@@ -89,12 +89,7 @@ CfgOptionDef::add(const OptionDefinitionPtr& def,
                   " space '" << option_space << "'");
 
     // Must not override standard option definition.
-    } else if (((option_space == DHCP4_OPTION_SPACE) &&
-                LibDHCP::isStandardOption(Option::V4, def->getCode()) &&
-                LibDHCP::getOptionDef(Option::V4, def->getCode())) ||
-               ((option_space == DHCP6_OPTION_SPACE) &&
-                LibDHCP::isStandardOption(Option::V6, def->getCode()) &&
-                LibDHCP::getOptionDef(Option::V6, def->getCode()))) {
+    } else if (LibDHCP::getOptionDef(option_space, def->getCode())) {
         isc_throw(BadValue, "unable to override definition of option '"
                   << def->getCode() << "' in standard option space '"
                   << option_space << "'");

+ 2 - 7
src/lib/dhcpsrv/mysql_host_data_source.cc

@@ -840,13 +840,8 @@ private:
             // class, using option definition. Thus, we need to find the
             // option definition for this option code and option space.
 
-            // If the option space is a standard DHCPv4 or DHCPv6 option space,
-            // this is most likely a standard option, for which we have a
-            // definition created within libdhcp++.
-            OptionDefinitionPtr def;
-            if ((space == DHCP4_OPTION_SPACE) || (space == DHCP6_OPTION_SPACE)) {
-                def = LibDHCP::getOptionDef(universe_, code_);
-            }
+            // Check if this is a standard option.
+            OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code_);
 
             // Otherwise, we may check if this an option encapsulated within the
             // vendor space.

+ 7 - 11
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -525,7 +525,8 @@ OptionDataParser::extractCSVFormat() const {
 
 std::string
 OptionDataParser::extractSpace() const {
-    std::string space = address_family_ == AF_INET ? "dhcp4" : "dhcp6";
+    std::string space = address_family_ == AF_INET ?
+        DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
     try {
         space = string_values_->getParam("space");
 
@@ -565,21 +566,15 @@ template<typename SearchKey>
 OptionDefinitionPtr
 OptionDataParser::findOptionDefinition(const std::string& option_space,
                                        const SearchKey& search_key) const {
-    const Option::Universe u = address_family_ == AF_INET ?
-        Option::V4 : Option::V6;
-    OptionDefinitionPtr def;
-
-    if ((option_space == DHCP4_OPTION_SPACE) ||
-        (option_space == DHCP6_OPTION_SPACE)) {
-        def = LibDHCP::getOptionDef(u, search_key);
-
-    }
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(option_space, search_key);
 
     if (!def) {
         // Check if this is a vendor-option. If it is, get vendor-specific
         // definition.
         uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
         if (vendor_id) {
+            const Option::Universe u = address_family_ == AF_INET ?
+                Option::V4 : Option::V6;
             def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
         }
     }
@@ -838,7 +833,8 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
     std::string record_types =
         string_values_->getOptionalParam("record-types", "");
     std::string space = string_values_->getOptionalParam("space",
-              global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6");
+              global_context_->universe_ == Option::V4 ? DHCP4_OPTION_SPACE :
+                                                         DHCP6_OPTION_SPACE);
     std::string encapsulates =
         string_values_->getOptionalParam("encapsulate", "");
 

+ 97 - 12
src/lib/dhcpsrv/pool.cc

@@ -76,7 +76,9 @@ Pool4::Pool4( const isc::asiolink::IOAddress& prefix, uint8_t prefix_len)
 
 Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& first,
              const isc::asiolink::IOAddress& last)
-    :Pool(type, first, last), prefix_len_(128) {
+    : Pool(type, first, last), prefix_len_(128),
+      excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
+      excluded_prefix_len_(0) {
 
     // check if specified address boundaries are sane
     if (!first.isV6() || !last.isV6()) {
@@ -117,21 +119,91 @@ Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& first,
 }
 
 Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
-             uint8_t prefix_len, uint8_t delegated_len /* = 128 */)
-    :Pool(type, prefix, IOAddress("::")), prefix_len_(delegated_len) {
+             const uint8_t prefix_len, const uint8_t delegated_len /* = 128 */)
+    : Pool(type, prefix, IOAddress::IPV6_ZERO_ADDRESS()),
+      prefix_len_(delegated_len),
+      excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
+      excluded_prefix_len_(0) {
+
+    init(type, prefix, prefix_len, delegated_len,
+         IOAddress::IPV6_ZERO_ADDRESS(), 0);
+}
 
-    // check if the prefix is sane
+Pool6::Pool6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
+             const uint8_t delegated_len,
+             const asiolink::IOAddress& excluded_prefix,
+             const uint8_t excluded_prefix_len)
+    : Pool(Lease::TYPE_PD, prefix, IOAddress::IPV6_ZERO_ADDRESS()),
+      prefix_len_(delegated_len),
+      excluded_prefix_(excluded_prefix),
+      excluded_prefix_len_(excluded_prefix_len) {
+
+    init(Lease::TYPE_PD, prefix, prefix_len, delegated_len, excluded_prefix,
+         excluded_prefix_len);
+
+    // The excluded prefix can only be specified using this constructor.
+    // Therefore, the initialization of the excluded prefix is takes place
+    // here, rather than in the init(...) function.
+    if (!excluded_prefix_.isV6()) {
+        isc_throw(BadValue, "excluded prefix must be an IPv6 prefix");
+    }
+
+    // An "unspecified" prefix should have both value and length equal to 0.
+    if ((excluded_prefix_.isV6Zero() && (excluded_prefix_len_ != 0)) ||
+        (!excluded_prefix_.isV6Zero() && (excluded_prefix_len_ == 0))) {
+        isc_throw(BadValue, "invalid excluded prefix "
+                  << excluded_prefix_ << "/"
+                  << static_cast<unsigned>(excluded_prefix_len_));
+    }
+
+    // If excluded prefix has been specified.
+    if (!excluded_prefix_.isV6Zero() && (excluded_prefix_len_ != 0)) {
+
+        // Excluded prefix length must not be greater than 128.
+        if (excluded_prefix_len_ > 128) {
+            isc_throw(BadValue, "excluded prefix length "
+                      << static_cast<unsigned>(excluded_prefix_len_)
+                      << " must not be greater than 128");
+        }
+
+        // Excluded prefix must be a sub-prefix of a delegated prefix. First
+        // check the prefix length as it is less involved.
+        if (excluded_prefix_len_ <= prefix_len_) {
+            isc_throw(BadValue, "excluded prefix length "
+                      << static_cast<unsigned>(excluded_prefix_len_)
+                      << " must be lower than the delegated prefix length "
+                      << static_cast<unsigned>(prefix_len_));
+        }
+
+        /// @todo Check that the prefixes actually match. Theoretically, a
+        /// user could specify a prefix which sets insgnificant bits. We should
+        /// clear insignificant bits based on the prefix length but this
+        /// should be considered a part of the IOAddress class, perhaps and
+        /// requires a bit of work (mainly in terms of testing).
+    }
+}
+
+void
+Pool6::init(const Lease::Type& type,
+            const asiolink::IOAddress& prefix,
+            const uint8_t prefix_len,
+            const uint8_t delegated_len,
+            const asiolink::IOAddress& /*excluded_prefix*/,
+            const uint8_t excluded_prefix_len) {
+    // Check if the prefix is sane
     if (!prefix.isV6()) {
         isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
     }
 
-    // check if the prefix length is sane
+    // Check if the prefix length is sane
     if (prefix_len == 0 || prefix_len > 128) {
-        isc_throw(BadValue, "Invalid prefix length: " << static_cast<unsigned>(prefix_len));
+        isc_throw(BadValue, "Invalid prefix length: "
+                  << static_cast<unsigned>(prefix_len));
     }
 
     if (prefix_len > delegated_len) {
-        isc_throw(BadValue, "Delegated length (" << static_cast<int>(delegated_len)
+        isc_throw(BadValue, "Delegated length ("
+                  << static_cast<int>(delegated_len)
                   << ") must be longer than or equal to prefix length ("
                   << static_cast<int>(prefix_len) << ")");
     }
@@ -142,6 +214,13 @@ Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
                   << " be 128.");
     }
 
+    // excluded_prefix_len == 0 means there's no excluded prefix at all.
+    if (excluded_prefix_len && (excluded_prefix_len < delegated_len)) {
+        isc_throw(BadValue, "Excluded prefix (" << static_cast<int>(excluded_prefix_len)
+                  << ") must be longer than the delegated prefix length ("
+                  << static_cast<int>(delegated_len));
+    }
+
     /// @todo: We should probably implement checks against weird addresses
     /// here, like ::, starting with fe80, starting with ff etc. .
 
@@ -156,11 +235,17 @@ Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
 
 std::string
 Pool6::toText() const {
-    std::stringstream tmp;
-    tmp << "type=" << Lease::typeToText(type_) << ", " << first_
-        << "-" << last_ << ", delegated_len="
-        << static_cast<int>(prefix_len_);
-    return (tmp.str());
+    std::ostringstream s;
+    s << "type=" << Lease::typeToText(type_) << ", " << first_
+      << "-" << last_ << ", delegated_len="
+      << static_cast<int>(prefix_len_);
+
+    if (excluded_prefix_len_ > 0) {
+       s << ", excluded_prefix=" << excluded_prefix_
+         << ", excluded_prefix_len="
+         << static_cast<unsigned>(excluded_prefix_len_);
+    }
+    return (s.str());
 }
 
 }; // end of isc::dhcp namespace

+ 79 - 1
src/lib/dhcpsrv/pool.h

@@ -213,10 +213,27 @@ public:
     /// @param type type of the pool (IA, TA or PD)
     /// @param prefix specifies prefix of the pool
     /// @param prefix_len specifies prefix length of the pool
-    /// @param delegated_len specifies lenght of the delegated prefixes
+    /// @param delegated_len specifies length of the delegated prefixes
     Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
           uint8_t prefix_len, uint8_t delegated_len = 128);
 
+    /// @brief Constructor for DHCPv6 prefix pool with an excluded prefix.
+    ///
+    /// If @c excluded_prefix is equal to '::' and the @c excluded_prefix_len
+    /// is equal to 0, the excluded prefix is assumed to be unspecified for
+    /// the pool. In this case, the server will not send the Prefix Exclude
+    /// option to a client.
+    ///
+    /// @param prefix specified a prefix of the pool.
+    /// @param prefix_Leb specifies prefix length of the pool.
+    /// @param delegated_len specifies length of the delegated prefixes.
+    /// @param excluded_prefix specifies an excluded prefix as per RFC6603.
+    /// @param excluded_prefix_len specifies length of an excluded prefix.
+    Pool6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
+          const uint8_t delegated_len,
+          const asiolink::IOAddress& excluded_prefix,
+          const uint8_t excluded_prefix_len);
+
     /// @brief returns pool type
     ///
     /// @return pool type
@@ -233,14 +250,75 @@ public:
         return (prefix_len_);
     }
 
+    /// @brief Returns an excluded prefix.
+    ///
+    /// An excluded prefix can be specified for a prefix pool as specified
+    /// in RFC6603.
+    ///
+    /// @return Reference to an IOAddress object representing an excluded
+    /// prefix pool. A value of '::' if the excluded prefix is not specified.
+    const isc::asiolink::IOAddress& getExcludedPrefix() const{
+        return (excluded_prefix_);
+    }
+
+    /// @brief Returns an excluded prefix length.
+    ///
+    /// An excluded prefix can be specified for a prefix pool as specified
+    /// in RFC6603.
+    ///
+    /// @return Excluded prefix length in the range of 2 to 128, if the
+    /// excluded prefix is specified. A value of 0 if the excluded prefix
+    /// is not specified.
+    uint8_t getExcludedPrefixLength() const{
+        return (excluded_prefix_len_);
+    }
+
     /// @brief returns textual representation of the pool
     ///
     /// @return textual representation
     virtual std::string toText() const;
 
 private:
+
+    /// @brief Generic method initializing a DHCPv6 pool.
+    ///
+    /// This method should be called by the constructors to initialize
+    /// DHCPv6 pools.
+    ///
+    /// @param Lease/pool type.
+    /// @param prefix An address or delegated prefix (depending on the
+    /// pool type specified as @c type).
+    /// @param prefix_len Prefix length. If a pool is an address pool,
+    /// this value should be set to 128.
+    /// @param delegated_len Length of the delegated prefixes. If a pool
+    /// is an address pool, this value should be set to 128.
+    /// @param excluded_prefix An excluded prefix as per RFC6603. This
+    /// value should only be specified for prefix pools. The value of
+    /// '::' means "unspecified".
+    /// @param excluded_prefix_len Length of the excluded prefix. This
+    /// is only specified for prefix pools. The value of 0 should be
+    /// used when @c excluded_prefix is not specified.
+    void init(const Lease::Type& type,
+              const asiolink::IOAddress& prefix,
+              const uint8_t prefix_len,
+              const uint8_t delegated_len,
+              const asiolink::IOAddress& excluded_prefix,
+              const uint8_t excluded_prefix_len);
+
     /// @brief Defines prefix length (for TYPE_PD only)
     uint8_t prefix_len_;
+
+    /// @brief The excluded prefix (RFC6603).
+    ///
+    /// This prefix can only be specified for DHCPv6 prefix pools.
+    /// An "unspecified" prefix has a value of '::'.
+    isc::asiolink::IOAddress excluded_prefix_;
+
+    /// @brief The excluded prefix length (RFC6603).
+    ///
+    /// This value can only be specified for DHCPv6 prefix pool.
+    /// An "unspecified" prefix has a length of 0.
+    uint8_t excluded_prefix_len_;
 };
 
 /// @brief a pointer an IPv6 Pool

+ 1 - 1
src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc

@@ -182,7 +182,7 @@ TEST(CfgOptionDefTest, get) {
     // Check that an option definition can be added to the standard
     // (dhcp4 and dhcp6) option spaces when the option code is not
     // reserved by the standard option.
-    OptionDefinitionPtr def6(new OptionDefinition("option-foo", 79, "uint16"));
+    OptionDefinitionPtr def6(new OptionDefinition("option-foo", 1000, "uint16"));
     EXPECT_NO_THROW(cfg.add(def6, DHCP6_OPTION_SPACE));
 
     OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222, "uint16"));

+ 138 - 61
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/cfg_option.h>
+#include <boost/foreach.hpp>
 #include <boost/pointer_cast.hpp>
 #include <gtest/gtest.h>
 #include <iterator>
@@ -22,8 +23,73 @@ using namespace isc::dhcp;
 
 namespace {
 
+/// This class fixture for testing @c CfgOption class, holding option
+/// configuration.
+class CfgOptionTest : public ::testing::Test {
+public:
+
+    /// @brief Generates encapsulated options and adds them to CfgOption
+    ///
+    /// This method generates the following options:
+    /// - 1000-1019 options: uint16 with value 1234, encapsulate "foo"
+    ///   - 1-19 options: uint8 with value 1, encapsulate "foo-subs"
+    ///     - 1-9 options: uint8 with value 3
+    /// - 1020-1039 options: uint16 with value 2345, encapsulate "bar"
+    ///   - 100-119 options: uint8 with value 2, encapsulate "bar-subs"
+    ///     - 501-509 options: uint8 with value 4
+    void generateEncapsulatedOptions(CfgOption& cfg) {
+        // Create top-level options encapsulating "foo" option space.
+        for (uint16_t code = 1000; code < 1020; ++code) {
+            OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+                                                                      code, 1234));
+            option->setEncapsulatedSpace("foo");
+            ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
+        }
+
+        // Create top level options encapsulating "bar" option space.
+        for (uint16_t code = 1020; code < 1040; ++code) {
+            OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+                                                                      code, 2345));
+            option->setEncapsulatedSpace("bar");
+            ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
+        }
+
+        // Create sub-options belonging to "foo" option space and encapsulating
+        // foo-subs option space.
+        for (uint16_t code = 1; code < 20; ++code) {
+            OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
+                                                                   0x01));
+            option->setEncapsulatedSpace("foo-subs");
+            ASSERT_NO_THROW(cfg.add(option, false, "foo"));
+        }
+
+        // Create sub-options belonging to "bar" option space and encapsulating
+        // bar-subs option space.
+        for (uint16_t code = 100;  code < 119; ++code) {
+            OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
+                                                                   code, 0x02));
+            option->setEncapsulatedSpace("bar-subs");
+            ASSERT_NO_THROW(cfg.add(option, false, "bar"));
+        }
+
+        // Create sub-options belonging to "foo-subs" option space.
+        for (uint16_t code = 1; code < 10; ++code) {
+            OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
+                                                                   0x03));
+            ASSERT_NO_THROW(cfg.add(option, false, "foo-subs"));
+        }
+
+        // Create sub-options belonging to "bar-subs" option space.
+        for (uint16_t code = 501;  code < 510; ++code) {
+            OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
+                                                                   code, 0x04));
+            ASSERT_NO_THROW(cfg.add(option, false, "bar-subs"));
+        }
+    }
+};
+
 // This test verifies the empty predicate.
-TEST(CfgOptionTest, empty) {
+TEST_F(CfgOptionTest, empty) {
     CfgOption cfg1;
     CfgOption cfg2;
 
@@ -33,7 +99,7 @@ TEST(CfgOptionTest, empty) {
 
     // Add an option to each configuration
     OptionPtr option(new Option(Option::V6, 1));
-    ASSERT_NO_THROW(cfg1.add(option, false, "dhcp6"));
+    ASSERT_NO_THROW(cfg1.add(option, false, DHCP6_OPTION_SPACE));
     ASSERT_NO_THROW(cfg2.add(option, true, "isc"));
 
     // The first option configuration has an option
@@ -44,7 +110,7 @@ TEST(CfgOptionTest, empty) {
 }
 
 // This test verifies that the option configurations can be compared.
-TEST(CfgOptionTest, equals) {
+TEST_F(CfgOptionTest, equals) {
     CfgOption cfg1;
     CfgOption cfg2;
 
@@ -97,13 +163,13 @@ TEST(CfgOptionTest, equals) {
 
 // This test verifies that multiple options can be added to the configuration
 // and that they can be retrieved using the option space name.
-TEST(CfgOptionTest, add) {
+TEST_F(CfgOptionTest, add) {
     CfgOption cfg;
 
     // Differentiate options by their codes (100-109)
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+        ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
     }
 
     // Add 7 options to another option space. The option codes partially overlap
@@ -114,7 +180,7 @@ TEST(CfgOptionTest, add) {
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    OptionContainerPtr options = cfg.getAll("dhcp6");
+    OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(10, options->size());
 
@@ -147,7 +213,7 @@ TEST(CfgOptionTest, add) {
 }
 
 // This test verifies that two option configurations can be merged.
-TEST(CfgOptionTest, merge) {
+TEST_F(CfgOptionTest, merge) {
     CfgOption cfg_src;
     CfgOption cfg_dst;
 
@@ -155,7 +221,7 @@ TEST(CfgOptionTest, merge) {
     // from the range of 100 to 109 and holding one byte of data equal to 0xFF.
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
-        ASSERT_NO_THROW(cfg_src.add(option, false, "dhcp6"));
+        ASSERT_NO_THROW(cfg_src.add(option, false, DHCP6_OPTION_SPACE));
     }
 
     // Create collection of options in vendor space 123, with option codes
@@ -172,7 +238,7 @@ TEST(CfgOptionTest, merge) {
     // 100 to 108.
     for (uint16_t code = 100; code < 110; code += 2) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
-        ASSERT_NO_THROW(cfg_dst.add(option, false, "dhcp6"));
+        ASSERT_NO_THROW(cfg_dst.add(option, false, DHCP6_OPTION_SPACE));
     }
 
     // Create collection of options having odd option codes in the range of
@@ -189,7 +255,7 @@ TEST(CfgOptionTest, merge) {
 
     // Validate the options in the dhcp6 option space in the destination.
     for (uint16_t code = 100; code < 110; ++code) {
-        OptionDescriptor desc = cfg_dst.get("dhcp6", code);
+        OptionDescriptor desc = cfg_dst.get(DHCP6_OPTION_SPACE, code);
         ASSERT_TRUE(desc.option_);
         ASSERT_EQ(1, desc.option_->getData().size());
         // The options with even option codes should hold one byte of data
@@ -222,7 +288,7 @@ TEST(CfgOptionTest, merge) {
 
 // This test verifies that the options configuration can be copied between
 // objects.
-TEST(CfgOptionTest, copy) {
+TEST_F(CfgOptionTest, copy) {
     CfgOption cfg_src;
     // Add 10 options to the custom option space in the source configuration.
     for (uint16_t code = 100; code < 110; ++code) {
@@ -262,60 +328,71 @@ TEST(CfgOptionTest, copy) {
 
 // This test verifies that encapsulated options are added as sub-options
 // to the top level options on request.
-TEST(CfgOptionTest, encapsulate) {
+TEST_F(CfgOptionTest, encapsulate) {
     CfgOption cfg;
-    // Create top-level options encapsulating "foo" option space.
-    for (uint16_t code = 1000; code < 1020; ++code) {
-        OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
-                                                                  code, 1234));
-        option->setEncapsulatedSpace("foo");
-        ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
-    }
-
-    // Create top level options encapsulating "bar" option space.
-    for (uint16_t code = 1020; code < 1040; ++code) {
-        OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
-                                                                  code, 2345));
-        option->setEncapsulatedSpace("bar");
-        ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
-    }
-
-    // Create sub-options belonging to "foo" option space.
-    for (uint16_t code = 1; code < 20; ++code) {
-        OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
-                                                               0x01));
-        ASSERT_NO_THROW(cfg.add(option, false, "foo"));
-    }
 
-    // Create sub-options belonging to "bar" option space.
-    for (uint16_t code = 100;  code < 130; ++code) {
-        OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
-                                                               code, 0x02));
-        ASSERT_NO_THROW(cfg.add(option, false, "bar"));
-    }
+    generateEncapsulatedOptions(cfg);
 
-    // Append options from "foo" and "bar" space as sub-options.
+    // Append options from "foo" and "bar" space as sub-options and options
+    // from "foo-subs" and "bar-subs" as sub-options of "foo" and "bar"
+    // options.
     ASSERT_NO_THROW(cfg.encapsulate());
 
     // Verify that we have 40 top-level options.
     OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(40, options->size());
 
+    // Iterate over top level options.
     for (uint16_t code = 1000; code < 1040; ++code) {
+
         OptionUint16Ptr option = boost::dynamic_pointer_cast<
             OptionUint16>(cfg.get(DHCP6_OPTION_SPACE, code).option_);
         ASSERT_TRUE(option) << "option with code " << code << " not found";
-        const OptionCollection& suboptions = option->getOptions();
-        for (OptionCollection::const_iterator suboption =
-                 suboptions.begin(); suboption != suboptions.end();
-             ++suboption) {
-            OptionUint8Ptr opt = boost::dynamic_pointer_cast<
-                OptionUint8>(suboption->second);
-            ASSERT_TRUE(opt);
-            if (code < 1020) {
-                EXPECT_EQ(0x01, opt->getValue());
+
+        // First level sub options. There are 19 sub-options for each top
+        // level option.
+        const OptionCollection& first_level = option->getOptions();
+        ASSERT_EQ(19, first_level.size());
+
+        // Iterate over all first level sub-options.
+        std::pair<unsigned int, OptionPtr> first_level_opt;
+        BOOST_FOREACH(first_level_opt, first_level) {
+            // Each option in this test comprises a single one byte field and
+            // should cast to OptionUint8 type.
+            OptionUint8Ptr first_level_uint8 = boost::dynamic_pointer_cast<
+                OptionUint8>(first_level_opt.second);
+            ASSERT_TRUE(first_level_uint8);
+
+            const unsigned int value = static_cast<unsigned int>(first_level_uint8->getValue());
+            // There are two sets of first level sub-options. Those that include
+            // a value of 1 and those that include a value of 2.
+            if (first_level_uint8->getType() < 20) {
+                EXPECT_EQ(1, value);
             } else {
-                EXPECT_EQ(0x02, opt->getValue());
+                EXPECT_EQ(2, value);
+            }
+
+            // Each first level sub-option should include 9 second level
+            // sub options.
+            const OptionCollection& second_level = first_level_uint8->getOptions();
+            ASSERT_EQ(9, second_level.size());
+
+            // Iterate over sub-options and make sure they include the expected
+            // values.
+            std::pair<unsigned int, OptionPtr> second_level_opt;
+            BOOST_FOREACH(second_level_opt, second_level) {
+                OptionUint8Ptr second_level_uint8 = boost::dynamic_pointer_cast<
+                    OptionUint8>(second_level_opt.second);
+                ASSERT_TRUE(second_level_uint8);
+                const unsigned value = static_cast<
+                    unsigned>(second_level_uint8->getValue());
+                // Certain sub-options should have a value of 3, other the values
+                // of 4.
+                if (second_level_uint8->getType() < 20) {
+                    EXPECT_EQ(3, value);
+                } else {
+                    EXPECT_EQ(4, value);
+                }
             }
         }
     }
@@ -323,13 +400,13 @@ TEST(CfgOptionTest, encapsulate) {
 
 // This test verifies that single option can be retrieved from the configuration
 // using option code and option space.
-TEST(CfgOptionTest, get) {
+TEST_F(CfgOptionTest, get) {
     CfgOption cfg;
 
     // Add 10 options to a "dhcp6" option space in the subnet.
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+        ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
     }
 
     // Check that we can get each added option descriptor using
@@ -341,7 +418,7 @@ TEST(CfgOptionTest, get) {
         // Returned descriptor should contain NULL option ptr.
         EXPECT_FALSE(desc.option_);
         // Now, try the valid option space.
-        desc = cfg.get("dhcp6", code);
+        desc = cfg.get(DHCP6_OPTION_SPACE, code);
         // Test that the option code matches the expected code.
         ASSERT_TRUE(desc.option_);
         EXPECT_EQ(code, desc.option_->getType());
@@ -350,7 +427,7 @@ TEST(CfgOptionTest, get) {
 
 // This test verifies that the same options can be added to the configuration
 // under different option space.
-TEST(CfgOptionTest, addNonUniqueOptions) {
+TEST_F(CfgOptionTest, addNonUniqueOptions) {
     CfgOption cfg;
 
     // Create a set of options with non-unique codes.
@@ -358,12 +435,12 @@ TEST(CfgOptionTest, addNonUniqueOptions) {
         // In the inner loop we create options with unique codes (100-109).
         for (uint16_t code = 100; code < 110; ++code) {
             OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-            ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+            ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
         }
     }
 
     // Sanity check that all options are there.
-    OptionContainerPtr options = cfg.getAll("dhcp6");
+    OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(20, options->size());
 
     // Use container index #1 to get the options by their codes.
@@ -412,11 +489,11 @@ TEST(Subnet6Test, addPersistentOption) {
         // and options with these codes will be flagged non-persistent.
         // Options with other codes will be flagged persistent.
         bool persistent = (code % 3) ? true : false;
-        ASSERT_NO_THROW(cfg.add(option, persistent, "dhcp6"));
+        ASSERT_NO_THROW(cfg.add(option, persistent, DHCP6_OPTION_SPACE));
     }
 
     // Get added options from the subnet.
-    OptionContainerPtr options = cfg.getAll("dhcp6");
+    OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
 
     // options->get<2> returns reference to container index #2. This
     // index is used to access options by the 'persistent' flag.
@@ -438,7 +515,7 @@ TEST(Subnet6Test, addPersistentOption) {
 }
 
 // This test verifies that the vendor option can be added to the configuration.
-TEST(CfgOptionTest, addVendorOptions) {
+TEST_F(CfgOptionTest, addVendorOptions) {
     CfgOption cfg;
 
     // Differentiate options by their codes (100-109)
@@ -494,7 +571,7 @@ TEST(CfgOptionTest, addVendorOptions) {
 
 // This test verifies that option space names for the vendor options are
 // correct.
-TEST(CfgOptionTest, getVendorIdsSpaceNames) {
+TEST_F(CfgOptionTest, getVendorIdsSpaceNames) {
     CfgOption cfg;
 
     // Create 10 options, each goes under a different vendor id.

+ 4 - 4
src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc

@@ -283,7 +283,7 @@ TEST_F(ClientClassDefParserTest, nameOnlyValid) {
     cfg_option = cclass->getCfgOption();
     ASSERT_TRUE(cfg_option);
     OptionContainerPtr oc;
-    ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
+    ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
     EXPECT_EQ(0, oc->size());
 
     // Verify we have no expression.
@@ -314,7 +314,7 @@ TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
     cfg_option = cclass->getCfgOption();
     ASSERT_TRUE(cfg_option);
     OptionContainerPtr oc;
-    ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
+    ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
     EXPECT_EQ(0, oc->size());
 
     // Verify we can retrieve the expression
@@ -357,7 +357,7 @@ TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
     EXPECT_EQ("MICROSOFT", cclass->getName());
 
     // Our one option should exist.
-    OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
+    OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
     ASSERT_TRUE(od.option_);
     EXPECT_EQ(6, od.option_->getType());
 
@@ -393,7 +393,7 @@ TEST_F(ClientClassDefParserTest, basicValidClass) {
     EXPECT_EQ("MICROSOFT", cclass->getName());
 
     // Our one option should exist.
-    OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
+    OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
     ASSERT_TRUE(od.option_);
     EXPECT_EQ(6, od.option_->getType());
 

+ 7 - 6
src/lib/dhcpsrv/tests/client_class_def_unittest.cc

@@ -6,6 +6,7 @@
 
 #include <config.h>
 #include <dhcpsrv/client_class_def.h>
+#include <dhcp/option_space.h>
 #include <exceptions/exceptions.h>
 #include <boost/scoped_ptr.hpp>
 #include <asiolink/io_address.h>
@@ -69,13 +70,13 @@ TEST(ClientClassDef, cfgOptionBasics) {
     OptionPtr option;
     test_options.reset(new CfgOption());
     option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
-    ASSERT_NO_THROW(test_options->add(option, false, "dhcp4"));
+    ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
 
     option.reset(new Option(Option::V6, 101, OptionBuffer(10, 0xFF)));
     ASSERT_NO_THROW(test_options->add(option, false, "isc"));
 
     option.reset(new Option(Option::V6, 100, OptionBuffer(10, 0xFF)));
-    ASSERT_NO_THROW(test_options->add(option, false, "dhcp6"));
+    ASSERT_NO_THROW(test_options->add(option, false, DHCP6_OPTION_SPACE));
 
     // Now remake the client class with cfg_option
     ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
@@ -83,7 +84,7 @@ TEST(ClientClassDef, cfgOptionBasics) {
     ASSERT_TRUE(class_options);
 
     // Now make sure we can find all the options
-    OptionDescriptor opt_desc = class_options->get("dhcp4",17);
+    OptionDescriptor opt_desc = class_options->get(DHCP4_OPTION_SPACE,17);
     ASSERT_TRUE(opt_desc.option_);
     EXPECT_EQ(17, opt_desc.option_->getType());
 
@@ -91,7 +92,7 @@ TEST(ClientClassDef, cfgOptionBasics) {
     ASSERT_TRUE(opt_desc.option_);
     EXPECT_EQ(101, opt_desc.option_->getType());
 
-    opt_desc = class_options->get("dhcp6",100);
+    opt_desc = class_options->get(DHCP6_OPTION_SPACE,100);
     ASSERT_TRUE(opt_desc.option_);
     EXPECT_EQ(100, opt_desc.option_->getType());
 }
@@ -113,7 +114,7 @@ TEST(ClientClassDef, copyAndEquality) {
     OptionPtr option;
     test_options.reset(new CfgOption());
     option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
-    ASSERT_NO_THROW(test_options->add(option, false, "dhcp4"));
+    ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
 
     // Now remake the client class with cfg_option
     ASSERT_NO_THROW(cclass.reset(new ClientClassDef("class_one", expr,
@@ -176,7 +177,7 @@ TEST(ClientClassDef, copyAndEquality) {
     // Make a class that with same name and expression, but different options
     // verify that the equality tools reflect that the classes are not equal.
     option.reset(new Option(Option::V4, 20, OptionBuffer(10, 0xFF)));
-    ASSERT_NO_THROW(test_options->add(option, false, "dhcp4"));
+    ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
     ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_one", expr,
                                                      test_options)));
     EXPECT_FALSE(cclass->equals(*cclass2));

+ 60 - 15
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -31,6 +31,7 @@
 
 using namespace std;
 using namespace isc;
+using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::dhcp;
@@ -571,7 +572,7 @@ TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {
 
     // Verify that the option definition can be retrieved.
     OptionDefinitionPtr def =
-        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("dhcp6", 10000);
+        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 10000);
     ASSERT_TRUE(def);
 
     // Verify that the option definition is correct.
@@ -683,7 +684,7 @@ TEST_F(ParseConfigTest, escapedOptionDataTest) {
     ASSERT_EQ(0, rcode);
 
     // Verify that the option can be retrieved.
-    OptionPtr opt = getOptionPtr("dhcp4", DHO_BOOT_FILE_NAME);
+    OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
     ASSERT_TRUE(opt);
 
     util::OutputBuffer buf(100);
@@ -721,7 +722,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
 
     // Verify that the option data is correct.
     OptionCustomPtr addr_opt = boost::dynamic_pointer_cast<
-        OptionCustom>(getOptionPtr("dhcp4", 16));
+        OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
     ASSERT_TRUE(addr_opt);
     EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
 
@@ -741,7 +742,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
 
     // Verify that the option data is correct.
     addr_opt = boost::dynamic_pointer_cast<
-        OptionCustom>(getOptionPtr("dhcp4", 16));
+        OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
     ASSERT_TRUE(addr_opt);
     EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
 
@@ -761,11 +762,55 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
 
     // Verify that the option data is correct.
     addr_opt = boost::dynamic_pointer_cast<
-        OptionCustom>(getOptionPtr("dhcp4", 16));
+        OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
     ASSERT_TRUE(addr_opt);
     EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
 }
 
+// This test verifies that definitions of standard encapsulated
+// options can be used.
+TEST_F(ParseConfigTest, encapsulatedOptionData) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"space\": \"s46-cont-mape-options\","
+        "    \"name\": \"s46-rule\","
+        "    \"data\": \"1, 0, 24, 192.0.2.0, 2001:db8:1::/64\""
+        " } ]"
+        "}";
+
+    // Make sure that we're using correct universe.
+    parser_context_->universe_ = Option::V6;
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    ASSERT_EQ(0, rcode);
+
+    // Verify that the option data is correct.
+    OptionCustomPtr s46_rule = boost::dynamic_pointer_cast<OptionCustom>
+        (getOptionPtr(MAPE_V6_OPTION_SPACE, D6O_S46_RULE));
+    ASSERT_TRUE(s46_rule);
+
+    uint8_t flags;
+    uint8_t ea_len;
+    uint8_t prefix4_len;
+    IOAddress ipv4_prefix(IOAddress::IPV4_ZERO_ADDRESS());
+    PrefixTuple ipv6_prefix(PrefixLen(0), IOAddress::IPV6_ZERO_ADDRESS());;
+
+    ASSERT_NO_THROW({
+        flags = s46_rule->readInteger<uint8_t>(0);
+        ea_len = s46_rule->readInteger<uint8_t>(1);
+        prefix4_len = s46_rule->readInteger<uint8_t>(2);
+        ipv4_prefix = s46_rule->readAddress(3);
+        ipv6_prefix = s46_rule->readPrefix(4);
+    });
+
+    EXPECT_EQ(1, flags);
+    EXPECT_EQ(0, ea_len);
+    EXPECT_EQ(24, prefix4_len);
+    EXPECT_EQ("192.0.2.0", ipv4_prefix.toText());
+    EXPECT_EQ(64, ipv6_prefix.first.asUnsigned());
+    EXPECT_EQ("2001:db8:1::", ipv6_prefix.second.toText());
+}
+
 // This test checks behavior of the configuration parser for option data
 // for different values of csv-format parameter and when there is no
 // option definition.
@@ -816,7 +861,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
         "}";
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     ASSERT_EQ(0, rcode);
-    OptionPtr opt = getOptionPtr("dhcp6", 25000);
+    OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getData().size());
     EXPECT_EQ(0, opt->getData()[0]);
@@ -835,7 +880,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
         "}";
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
-    opt = getOptionPtr("dhcp6", 25000);
+    opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
     ASSERT_TRUE(opt);
     ASSERT_EQ(3, opt->getData().size());
     EXPECT_EQ(0x12, opt->getData()[0]);
@@ -857,7 +902,7 @@ TEST_F(ParseConfigTest, optionDataNoName) {
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
     Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
-        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getAddresses().size());
     EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
@@ -877,7 +922,7 @@ TEST_F(ParseConfigTest, optionDataNoCode) {
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
     Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
-        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getAddresses().size());
     EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
@@ -896,7 +941,7 @@ TEST_F(ParseConfigTest, optionDataMinimal) {
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
     Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
-        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getAddresses().size());
     EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());
@@ -912,7 +957,7 @@ TEST_F(ParseConfigTest, optionDataMinimal) {
     rcode = 0;
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
-    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
+    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
                                                                    23));
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getAddresses().size());
@@ -942,7 +987,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
     Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
-        Option6AddrLst>(getOptionPtr("dhcp6", 2345));
+        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 2345));
     ASSERT_TRUE(opt);
     ASSERT_EQ(2, opt->getAddresses().size());
     EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
@@ -967,7 +1012,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
     rcode = 0;
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
-    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
+    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
                                                                    2345));
     ASSERT_TRUE(opt);
     ASSERT_EQ(2, opt->getAddresses().size());
@@ -989,7 +1034,7 @@ TEST_F(ParseConfigTest, emptyOptionData) {
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
     const Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
-        Option6AddrLst>(getOptionPtr("dhcp6", D6O_DHCPV4_O_DHCPV6_SERVER));
+        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, D6O_DHCPV4_O_DHCPV6_SERVER));
     ASSERT_TRUE(opt);
     ASSERT_EQ(0, opt->getAddresses().size());
 }
@@ -1009,7 +1054,7 @@ TEST_F(ParseConfigTest, optionDataNoSubOpion) {
     int rcode = 0;
     ASSERT_NO_THROW(rcode = parseConfiguration(config));
     EXPECT_EQ(0, rcode);
-    const OptionPtr opt = getOptionPtr("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+    const OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
     ASSERT_TRUE(opt);
     ASSERT_EQ(0, opt->getOptions().size());
 }

+ 0 - 1
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc

@@ -8,7 +8,6 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option6_addrlst.h>
-#include <dhcp/option_space.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_vendor.h>

+ 8 - 8
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -77,13 +77,13 @@ protected:
     OptionPtr
     retrieveOption(const Host& host, const std::string& option_space,
                    const uint16_t option_code) const {
-        if ((option_space != "dhcp6") && (option_space != "dhcp4")) {
+        if ((option_space != DHCP6_OPTION_SPACE) && (option_space != DHCP4_OPTION_SPACE)) {
             return (OptionPtr());
         }
 
         // Retrieve a pointer to the appropriate container depending if we're
         // interested in DHCPv4 or DHCPv6 options.
-        ConstCfgOptionPtr cfg_option = (option_space == "dhcp4" ?
+        ConstCfgOptionPtr cfg_option = (option_space == DHCP4_OPTION_SPACE ?
                                         host.getCfgOption4() : host.getCfgOption6());
 
         // Retrieve options.
@@ -779,7 +779,7 @@ TEST_F(HostReservationParserTest, options4) {
 
     // Retrieve and sanity check name servers.
     Option4AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
-        Option4AddrLst>(retrieveOption(*hosts[0], "dhcp4", DHO_NAME_SERVERS));
+        Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_NAME_SERVERS));
     ASSERT_TRUE(opt_dns);
     Option4AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
     ASSERT_EQ(2, dns_addrs.size());
@@ -788,7 +788,7 @@ TEST_F(HostReservationParserTest, options4) {
 
     // Retrieve and sanity check log servers.
     Option4AddrLstPtr opt_log = boost::dynamic_pointer_cast<
-        Option4AddrLst>(retrieveOption(*hosts[0], "dhcp4", DHO_LOG_SERVERS));
+        Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_LOG_SERVERS));
     ASSERT_TRUE(opt_log);
     Option4AddrLst::AddressContainer log_addrs = opt_log->getAddresses();
     ASSERT_EQ(1, log_addrs.size());
@@ -796,7 +796,7 @@ TEST_F(HostReservationParserTest, options4) {
 
     // Retrieve and sanity check default IP TTL.
     OptionUint8Ptr opt_ttl = boost::dynamic_pointer_cast<
-        OptionUint8>(retrieveOption(*hosts[0], "dhcp4", DHO_DEFAULT_IP_TTL));
+        OptionUint8>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL));
     ASSERT_TRUE(opt_ttl);
     EXPECT_EQ(64, opt_ttl->getValue());
 }
@@ -837,7 +837,7 @@ TEST_F(HostReservationParserTest, options6) {
 
     // Retrieve and sanity check DNS servers option.
     Option6AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
-        Option6AddrLst>(retrieveOption(*hosts[0], "dhcp6", D6O_NAME_SERVERS));
+        Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NAME_SERVERS));
     ASSERT_TRUE(opt_dns);
     Option6AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
     ASSERT_EQ(2, dns_addrs.size());
@@ -846,7 +846,7 @@ TEST_F(HostReservationParserTest, options6) {
 
     // Retrieve and sanity check NIS servers option.
     Option6AddrLstPtr opt_nis = boost::dynamic_pointer_cast<
-        Option6AddrLst>(retrieveOption(*hosts[0], "dhcp6", D6O_NIS_SERVERS));
+        Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NIS_SERVERS));
     ASSERT_TRUE(opt_nis);
     Option6AddrLst::AddressContainer nis_addrs = opt_nis->getAddresses();
     ASSERT_EQ(1, nis_addrs.size());
@@ -854,7 +854,7 @@ TEST_F(HostReservationParserTest, options6) {
 
     // Retrieve and sanity check preference option.
     OptionUint8Ptr opt_prf = boost::dynamic_pointer_cast<
-        OptionUint8>(retrieveOption(*hosts[0], "dhcp6", D6O_PREFERENCE));
+        OptionUint8>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_PREFERENCE));
     ASSERT_TRUE(opt_prf);
     EXPECT_EQ(11, opt_prf->getValue());
 }

+ 9 - 8
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <dhcpsrv/host.h>
+#include <dhcp/option_space.h>
 #include <util/encode/hex.h>
 #include <util/range_utilities.h>
 #include <boost/scoped_ptr.hpp>
@@ -769,7 +770,7 @@ TEST_F(HostTest, addOptions4) {
     // Differentiate options by their codes (100-109)
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, "dhcp4"));
+        ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, DHCP4_OPTION_SPACE));
     }
 
     // Add 7 options to another option space. The option codes partially overlap
@@ -780,20 +781,20 @@ TEST_F(HostTest, addOptions4) {
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    OptionContainerPtr options = host.getCfgOption4()->getAll("dhcp4");
+    OptionContainerPtr options = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(10, options->size());
 
     // It should be possible to retrieve DHCPv6 options but the container
     // should be empty.
-    OptionContainerPtr options6 = host.getCfgOption6()->getAll("dhcp6");
+    OptionContainerPtr options6 = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_TRUE(options6);
     EXPECT_TRUE(options6->empty());
 
     // Also make sure that for dhcp4 option space no DHCPv6 options are
     // returned. This is to check that containers for DHCPv4 and DHCPv6
     // options do not share information.
-    options6 = host.getCfgOption6()->getAll("dhcp4");
+    options6 = host.getCfgOption6()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_TRUE(options6);
     EXPECT_TRUE(options6->empty());
 
@@ -833,7 +834,7 @@ TEST_F(HostTest, addOptions6) {
     // Differentiate options by their codes (100-109)
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, "dhcp6"));
+        ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, DHCP6_OPTION_SPACE));
     }
 
     // Add 7 options to another option space. The option codes partially overlap
@@ -844,20 +845,20 @@ TEST_F(HostTest, addOptions6) {
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    OptionContainerPtr options = host.getCfgOption6()->getAll("dhcp6");
+    OptionContainerPtr options = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(10, options->size());
 
     // It should be possible to retrieve DHCPv4 options but the container
     // should be empty.
-    OptionContainerPtr options4 = host.getCfgOption4()->getAll("dhcp4");
+    OptionContainerPtr options4 = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
     ASSERT_TRUE(options4);
     EXPECT_TRUE(options4->empty());
 
     // Also make sure that for dhcp6 option space no DHCPv4 options are
     // returned. This is to check that containers for DHCPv4 and DHCPv6
     // options do not share information.
-    options4 = host.getCfgOption4()->getAll("dhcp6");
+    options4 = host.getCfgOption4()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_TRUE(options4);
     EXPECT_TRUE(options4->empty());
 

+ 70 - 1
src/lib/dhcpsrv/tests/pool_unittest.cc

@@ -206,6 +206,68 @@ TEST(Pool6Test, PD) {
                                 77, 77));
 }
 
+// Checks that prefix pools with excluded prefixes are handled properly.
+TEST(Pool6Test, PDExclude) {
+    Pool6Ptr pool;
+
+    // Create a pool with a good excluded prefix. The good excluded prefix
+    // is the one for which is a sub-prefix of the main prefix.
+    ASSERT_NO_THROW(pool.reset(new Pool6(IOAddress("2001:db8:1::"), 96, 112,
+                                         IOAddress("2001:db8:1:2::"), 120)));
+
+    // Verify pool properties.
+    EXPECT_EQ(Lease::TYPE_PD, pool->getType());
+    EXPECT_EQ(112, pool->getLength());
+    EXPECT_EQ("2001:db8:1::", pool->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:1::ffff:ffff", pool->getLastAddress().toText());
+    EXPECT_EQ("2001:db8:1:2::", pool->getExcludedPrefix().toText());
+    EXPECT_EQ(120, static_cast<unsigned>(pool->getExcludedPrefixLength()));
+
+    // Create another pool instance, but with the excluded prefix being
+    // "unspecified".
+    ASSERT_NO_THROW(pool.reset(new Pool6(IOAddress("2001:db8:1::"), 96, 112,
+                                         IOAddress::IPV6_ZERO_ADDRESS(), 0)));
+
+    EXPECT_EQ(Lease::TYPE_PD, pool->getType());
+    EXPECT_EQ(112, pool->getLength());
+    EXPECT_EQ("2001:db8:1::", pool->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:1::ffff:ffff", pool->getLastAddress().toText());
+    EXPECT_TRUE(pool->getExcludedPrefix().isV6Zero());
+    EXPECT_EQ(0, static_cast<unsigned>(pool->getExcludedPrefixLength()));
+
+    // Excluded prefix length must be greater than the main prefix length.
+    EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 96, 112,
+                       IOAddress("2001:db8:1::"), 112),
+                 BadValue);
+
+    // Again, the excluded prefix length must be greater than main prefix
+    // length.
+    EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 96, 112,
+                       IOAddress("2001:db8:1::"), 104),
+                 BadValue);
+
+    // The "unspecified" excluded prefix must have both values set to 0.
+    EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+                       IOAddress("2001:db8:1::"), 0),
+                 BadValue);
+
+    // Similar case as above, but the prefix value is 0 and the length
+    // is non zero.
+    EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+                       IOAddress::IPV6_ZERO_ADDRESS(), 72),
+                 BadValue);
+
+    // Excluded prefix must be an IPv6 prefix.
+    EXPECT_THROW(Pool6(IOAddress("10::"), 8, 16,
+                       IOAddress("10.0.0.0"), 24),
+                 BadValue);
+
+    // Excluded prefix length must not be greater than 128.
+    EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+                       IOAddress("2001:db8:1::"), 129),
+                 BadValue);
+}
+
 // Checks that temporary address pools are handled properly
 TEST(Pool6Test, TA) {
     // Note: since we defined TA pool types during PD work, we can test it
@@ -261,7 +323,7 @@ TEST(Pool6Test, unique_id) {
 }
 
 // Simple check if toText returns reasonable values
-TEST(Poo6Test,toText) {
+TEST(Pool6Test,toText) {
     Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
                 IOAddress("2001:db8::2"));
     EXPECT_EQ("type=IA_NA, 2001:db8::1-2001:db8::2, delegated_len=128",
@@ -270,6 +332,13 @@ TEST(Poo6Test,toText) {
     Pool6 pool2(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 96, 112);
     EXPECT_EQ("type=IA_PD, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=112",
               pool2.toText());
+
+    Pool6 pool3(IOAddress("2001:db8:1::"), 96, 112,
+                IOAddress("2001:db8:1::1000"), 120);
+    EXPECT_EQ("type=IA_PD, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=112,"
+              " excluded_prefix=2001:db8:1::1000, excluded_prefix_len=120",
+              pool3.toText());
+
 }
 
 // Checks if the number of possible leases in range is reported correctly.

+ 1 - 1
src/lib/dhcpsrv/tests/srv_config_unittest.cc

@@ -284,7 +284,7 @@ TEST_F(SrvConfigTest, copy) {
 
     // Add an option.
     OptionPtr option(new Option(Option::V6, 1000, OptionBuffer(10, 0xFF)));
-    conf1.getCfgOption()->add(option, true, "dhcp6");
+    conf1.getCfgOption()->add(option, true, DHCP6_OPTION_SPACE);
 
     // Add a class dictionary
     conf1.setClientClassDictionary(ref_dictionary_);

+ 10 - 9
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -9,6 +9,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/option_space.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 
@@ -350,7 +351,7 @@ TEST(Subnet4Test, addInvalidOption) {
     // should result in exception.
     OptionPtr option2;
     ASSERT_FALSE(option2);
-    EXPECT_THROW(subnet->getCfgOption()->add(option2, false, "dhcp4"),
+    EXPECT_THROW(subnet->getCfgOption()->add(option2, false, DHCP4_OPTION_SPACE),
                  isc::BadValue);
 }
 
@@ -870,7 +871,7 @@ TEST(Subnet6Test, addOptions) {
     // Differentiate options by their codes (100-109)
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, DHCP6_OPTION_SPACE));
     }
 
     // Add 7 options to another option space. The option codes partially overlap
@@ -881,7 +882,7 @@ TEST(Subnet6Test, addOptions) {
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_TRUE(options);
     ASSERT_EQ(10, options->size());
 
@@ -922,12 +923,12 @@ TEST(Subnet6Test, addNonUniqueOptions) {
         // In the inner loop we create options with unique codes (100-109).
         for (uint16_t code = 100; code < 110; ++code) {
             OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-            ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
+            ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, DHCP6_OPTION_SPACE));
         }
     }
 
     // Sanity check that all options are there.
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
     ASSERT_EQ(20, options->size());
 
     // Use container index #1 to get the options by their codes.
@@ -974,11 +975,11 @@ TEST(Subnet6Test, addPersistentOption) {
         // and options with these codes will be flagged non-persistent.
         // Options with other codes will be flagged persistent.
         bool persistent = (code % 3) ? true : false;
-        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, persistent, "dhcp6"));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, persistent, DHCP6_OPTION_SPACE));
     }
 
     // Get added options from the subnet.
-    OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+    OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
 
     // options->get<2> returns reference to container index #2. This
     // index is used to access options by the 'persistent' flag.
@@ -1005,7 +1006,7 @@ TEST(Subnet6Test, getOptions) {
     // Add 10 options to a "dhcp6" option space in the subnet.
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
+        ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, DHCP6_OPTION_SPACE));
     }
 
     // Check that we can get each added option descriptor using
@@ -1017,7 +1018,7 @@ TEST(Subnet6Test, getOptions) {
         // Returned descriptor should contain NULL option ptr.
         EXPECT_FALSE(desc.option_);
         // Now, try the valid option space.
-        desc = subnet->getCfgOption()->get("dhcp6", code);
+        desc = subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, code);
         // Test that the option code matches the expected code.
         ASSERT_TRUE(desc.option_);
         EXPECT_EQ(code, desc.option_->getType());

+ 5 - 3
src/lib/eval/eval_context.cc

@@ -8,6 +8,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/libdhcp++.h>
+#include <dhcp/option_space.h>
 #include <eval/eval_context.h>
 #include <eval/parser.h>
 #include <exceptions/exceptions.h>
@@ -79,11 +80,12 @@ uint16_t
 EvalContext::convertOptionName(const std::string& option_name,
                                const isc::eval::location& loc)
 {
-    OptionDefinitionPtr option_def = LibDHCP::getOptionDef(option_universe_,
+    const std::string global_space = (option_universe_ == Option::V4) ?
+        DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
+
+    OptionDefinitionPtr option_def = LibDHCP::getOptionDef(global_space,
                                                            option_name);
     if (!option_def) {
-        const std::string global_space =
-            (option_universe_ == Option::V4) ? "dhcp4" : "dhcp6";
         option_def = LibDHCP::getRuntimeOptionDef(global_space, option_name);
     }