Browse Source

[master] Merge branch 'trac3705'

Marcin Siodelski 10 years ago
parent
commit
4772ee5897

+ 10 - 0
doc/examples/kea6/advanced.json

@@ -39,6 +39,16 @@
 # If mac-sources are not specified, a default value of 'any' is used.
 # If mac-sources are not specified, a default value of 'any' is used.
   "mac-sources": [ "client-link-addr-option", "duid", "ipv6-link-local" ],
   "mac-sources": [ "client-link-addr-option", "duid", "ipv6-link-local" ],
 
 
+# RFC6422 defines a mechanism called relay-supplied options option. The relay
+# agent may insert certain options that the server will echo back to the
+# client, if certain criteria are met. One condition is that the option must
+# be RSOO-enabled (i.e. allowed to be echoed back). IANA maintains a list
+# of those options here:
+# http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#options-relay-supplied
+# However, it is possible to allow the server to echo back additional options.
+# This entry marks options 110, 120 and 130 as RSOO-enabled.
+   "relay-supplied-options": [ "110", "120", "130" ],"
+
 # Addresses will be assigned with preferred and valid lifetimes
 # Addresses will be assigned with preferred and valid lifetimes
 # being 3000 and 4000, respectively. Client is told to start
 # being 3000 and 4000, respectively. Client is told to start
 # renewing after 1000 seconds. If the server does not repond
 # renewing after 1000 seconds. If the server does not repond

+ 49 - 1
doc/guide/dhcp6-srv.xml

@@ -885,6 +885,9 @@ temporarily override a list of interface names and listen on all interfaces.
 <row><entry>clt-time</entry><entry>46</entry><entry>uint32</entry><entry>false</entry></row>
 <row><entry>clt-time</entry><entry>46</entry><entry>uint32</entry><entry>false</entry></row>
 <row><entry>lq-relay-data</entry><entry>47</entry><entry>record</entry><entry>false</entry></row>
 <row><entry>lq-relay-data</entry><entry>47</entry><entry>record</entry><entry>false</entry></row>
 <row><entry>lq-client-link</entry><entry>48</entry><entry>ipv6-address</entry><entry>true</entry></row>
 <row><entry>lq-client-link</entry><entry>48</entry><entry>ipv6-address</entry><entry>true</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>client-linklayer-addr</entry><entry>79</entry><entry>binary</entry><entry>false</entry></row>
         </tbody>
         </tbody>
         </tgroup>
         </tgroup>
       </table>
       </table>
@@ -1368,6 +1371,43 @@ should include options from the isc option space:
         </para>
         </para>
       </section>
       </section>
 
 
+    <section id="dhcp6-rsoo">
+      <title>Relay-Supplied Options</title>
+      <para><ulink url="http://tools.ietf.org/html/rfc6422">RFC 6422</ulink>
+      defines a mechanism called Relay-Supplied DHCP Options. In certain cases relay
+      agents are the only entities that may have specific information. They can
+      insert options when relaying messages from the client to the server. The
+      server will then do certain checks and copy those options to the response
+      that will be sent to the client.</para>
+
+      <para>There are certain conditions that must be met for the option to be
+      included. First, the server must not provide the option by itself. In
+      other words, if both relay and server provide an option, the server always
+      takes precedence. Second, the option must be RSOO-enabled. IANA mantains a
+      list of RSOO-enabled options <ulink url="http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#options-relay-supplied">here</ulink>.
+      However, there may be cases when system administrators want to echo other
+      options. Kea can be instructed to treat other options as RSOO-enabled.
+      For example, to mark options 110, 120 and 130 as RSOO-enabled, the following
+      syntax should be used:
+<screen>
+"Dhcp6": {
+    <userinput>"relay-supplied-options": [ "110", "120", "130" ],</userinput>
+    ...
+}
+</screen>
+      </para>
+      <para>As of March 2015, only option 65 is RSOO-enabled by IANA. This
+      option will always be treated as such and there's no need to explicitly
+      mark it. Also, when enabling standard options, it is possible to use their
+      names, rather than option code, e.g. (e.g. use
+      <command>dns-servers</command> instead of <command>23</command>). See
+      <xref linkend="dhcp6-std-options-list" /> for the names. In certain cases
+      it could also work for custom options, but due to the nature of the parser
+      code this may be unreliable and should be avoided.
+      </para>
+
+    </section>
+
     <section id="dhcp6-client-classifier">
     <section id="dhcp6-client-classifier">
       <title>Client Classification in DHCPv6</title>
       <title>Client Classification in DHCPv6</title>
       <note>
       <note>
@@ -2425,7 +2465,8 @@ should include options from the isc option space:
             <simpara><emphasis>Dynamic Host Configuration Protocol for IPv6</emphasis>,
             <simpara><emphasis>Dynamic Host Configuration Protocol for IPv6</emphasis>,
             <ulink url="http://tools.ietf.org/html/rfc3315">RFC 3315</ulink>:
             <ulink url="http://tools.ietf.org/html/rfc3315">RFC 3315</ulink>:
             Supported messages are SOLICIT,
             Supported messages are SOLICIT,
-            ADVERTISE, REQUEST, RELEASE, RENEW, REBIND, CONFIRM and REPLY.</simpara>
+            ADVERTISE, REQUEST, RELEASE, RENEW, REBIND, INFORMATION-REQUEST,
+            CONFIRM and REPLY.</simpara>
           </listitem>
           </listitem>
           <listitem>
           <listitem>
             <simpara><emphasis>IPv6 Prefix Options for
             <simpara><emphasis>IPv6 Prefix Options for
@@ -2452,6 +2493,13 @@ should include options from the isc option space:
             <ulink url="http://tools.ietf.org/html/rfc4704">RFC 4704</ulink>:
             <ulink url="http://tools.ietf.org/html/rfc4704">RFC 4704</ulink>:
             Supported option is CLIENT_FQDN.</simpara>
             Supported option is CLIENT_FQDN.</simpara>
           </listitem>
           </listitem>
+          <listitem>
+            <simpara><emphasis>Relay-Supplied DHCP Options</emphasis>,
+            <ulink url="http://tools.ietf.org/html/rfc6422">RFC 6422</ulink>:
+            Full functionality is supported: OPTION_RSOO, ability of the server
+            to echo back the options, checks whether an option is RSOO-enabled,
+            ability to mark additional options as RSOO-enabled.</simpara>
+          </listitem>
 	  <listitem>
 	  <listitem>
 	    <simpara><emphasis>Client Link-Layer Address Option in
 	    <simpara><emphasis>Client Link-Layer Address Option in
 	    DHCPv6</emphasis>,
 	    DHCPv6</emphasis>,

+ 48 - 1
src/bin/dhcp6/dhcp6_srv.cc

@@ -256,7 +256,7 @@ bool Dhcpv6Srv::run() {
         // Handle next signal received by the process. It must be called after
         // Handle next signal received by the process. It must be called after
         // an attempt to receive a packet to properly handle server shut down.
         // an attempt to receive a packet to properly handle server shut down.
         // The SIGTERM or SIGINT will be received prior to, or during execution
         // The SIGTERM or SIGINT will be received prior to, or during execution
-        // of select() (select is invoked by recivePacket()). When that happens,
+        // of select() (select is invoked by receivePacket()). When that happens,
         // select will be interrupted. The signal handler will be invoked
         // select will be interrupted. The signal handler will be invoked
         // immediately after select(). The handler will set the shutdown flag
         // immediately after select(). The handler will set the shutdown flag
         // and cause the process to terminate before the next select() function
         // and cause the process to terminate before the next select() function
@@ -444,6 +444,19 @@ bool Dhcpv6Srv::run() {
         }
         }
 
 
         if (rsp) {
         if (rsp) {
+
+            // Process relay-supplied options. It is important to call this very
+            // late in the process, because we now have all the options the
+            // server wanted to send already set. This is important, because
+            // RFC6422, section 6 states:
+            //
+            //   The server SHOULD discard any options that appear in the RSOO
+            //   for which it already has one or more candidates.
+            //
+            // So we ignore any RSOO options if there's an option with the same
+            // code already present.
+            processRSOO(query, rsp);
+
             rsp->setRemoteAddr(query->getRemoteAddr());
             rsp->setRemoteAddr(query->getRemoteAddr());
             rsp->setLocalAddr(query->getLocalAddr());
             rsp->setLocalAddr(query->getLocalAddr());
 
 
@@ -2711,5 +2724,39 @@ Daemon::getVersion(bool extended) {
     return (tmp.str());
     return (tmp.str());
 }
 }
 
 
+void Dhcpv6Srv::processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp) {
+
+    if (query->relay_info_.empty()) {
+        // RSOO is inserted by relay agents, nothing to do here if it's
+        // a direct message.
+        return;
+    }
+
+    // Get RSOO configuration.
+    ConstCfgRSOOPtr cfg_rsoo  = CfgMgr::instance().getCurrentCfg()->getCfgRSOO();
+
+    // Let's get over all relays (encapsulation levels). We need to do
+    // it in the same order as the client packet traversed the relays.
+    for (int i = query->relay_info_.size(); i > 0 ; --i) {
+        OptionPtr rsoo_container = query->getRelayOption(D6O_RSOO, i - 1);
+        if (rsoo_container) {
+            // There are RSOO options. Let's get through them one by one
+            // and if it's RSOO-enabled and there's no such option provided yet,
+            // copy it to the server's response
+            const OptionCollection& rsoo = rsoo_container->getOptions();
+            for (OptionCollection::const_iterator opt = rsoo.begin();
+                 opt != rsoo.end(); ++opt) {
+
+                // Echo option if it is RSOO enabled option and there is no such
+                // option added yet.
+                if (cfg_rsoo->enabled(opt->second->getType()) &&
+                    !rsp->getOption(opt->second->getType())) {
+                    rsp->addOption(opt->second);
+                }
+            }
+        }
+    }
+}
+
 };
 };
 };
 };

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

@@ -606,6 +606,16 @@ protected:
     /// @return HWaddr pointer (or NULL if configured methods fail)
     /// @return HWaddr pointer (or NULL if configured methods fail)
     static HWAddrPtr getMAC(const Pkt6Ptr& pkt);
     static HWAddrPtr getMAC(const Pkt6Ptr& pkt);
 
 
+    /// @brief Processes Relay-supplied options, if present
+    ///
+    /// This method implements RFC6422. It checks if there are any RSOO options
+    /// inserted by the relay agents in the query message. If there are, they
+    /// are copied over to the response if they meet the following criteria:
+    /// - the option is marked as RSOO-enabled (see relay-supplied-options
+    ///   configuration parameter)
+    /// - there is no such option provided by the server)
+    void processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp);
+
     /// @brief this is a prefix added to the contend of vendor-class option
     /// @brief this is a prefix added to the contend of vendor-class option
     ///
     ///
     /// If incoming packet has a vendor class option, its content is
     /// If incoming packet has a vendor class option, its content is

+ 84 - 0
src/bin/dhcp6/json_config_parser.cc

@@ -41,6 +41,7 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
 #include <iostream>
 #include <iostream>
+#include <limits>
 #include <map>
 #include <map>
 #include <vector>
 #include <vector>
 
 
@@ -554,6 +555,87 @@ public:
     ParserCollection subnets_;
     ParserCollection subnets_;
 };
 };
 
 
+/// @brief Parser for list of RSOO options
+///
+/// This parser handles a Dhcp6/relay-supplied-options entry. It contains a
+/// list of RSOO-enabled options which should be sent back to the client.
+///
+/// The options on this list can be specified using an option code or option
+/// name. Therefore, the values on the list should always be enclosed in
+/// "quotes".
+class RSOOListConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    ///
+    /// As this is a dedicated parser, it must be used to parse
+    /// "relay-supplied-options" parameter only. All other types will throw exception.
+    ///
+    /// @param param_name name of the configuration parameter being parsed
+    /// @throw BadValue if supplied parameter name is not "relay-supplied-options"
+    RSOOListConfigParser(const std::string& param_name) {
+        if (param_name != "relay-supplied-options") {
+            isc_throw(BadValue, "Internal error. RSOO configuration "
+                      "parser called for the wrong parameter: " << param_name);
+        }
+    }
+
+    /// @brief parses parameters value
+    ///
+    /// Parses configuration entry (list of sources) and adds each element
+    /// to the RSOO list.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(isc::data::ConstElementPtr value) {
+        try {
+            BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) {
+                std::string option_str = source_elem->stringValue();
+                // This option can be either code (integer) or name. Let's try code first
+                int64_t code = 0;
+                try {
+                    code = boost::lexical_cast<int64_t>(option_str);
+                    // Protect against the negative value and too high value.
+                    if (code < 0) {
+                        isc_throw(BadValue, "invalid option code value specified '"
+                                  << option_str << "', the option code must be a"
+                                  " non-negative value");
+
+                    } else if (code > std::numeric_limits<uint16_t>::max()) {
+                        isc_throw(BadValue, "invalid option code value specified '"
+                                  << option_str << "', the option code must not be"
+                                  " greater than '" << std::numeric_limits<uint16_t>::max()
+                                  << "'");
+                    }
+
+                } catch (const boost::bad_lexical_cast &) {
+                    // Oh well, it's not a number
+                }
+
+                if (!code) {
+                    OptionDefinitionPtr def = LibDHCP::getOptionDef(Option::V6, option_str);
+                    if (def) {
+                        code = def->getCode();
+                    } else {
+                        isc_throw(BadValue, "unable to find option code for the "
+                                  " specified option name '" << option_str << "'"
+                                  " while parsing the list of enabled"
+                                  " relay-supplied-options");
+                    }
+                }
+                CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enable(code);
+            }
+        } catch (const std::exception& ex) {
+            // Rethrow exception with the appended position of the parsed
+            // element.
+            isc_throw(DhcpConfigError, ex.what() << " (" << value->getPosition() << ")");
+        }
+    }
+
+    /// @brief Does nothing.
+    virtual void commit() {}
+};
+
+
 } // anonymous namespace
 } // anonymous namespace
 
 
 namespace isc {
 namespace isc {
@@ -597,6 +679,8 @@ namespace dhcp {
     } else if (config_id.compare("mac-sources") == 0) {
     } else if (config_id.compare("mac-sources") == 0) {
         parser = new MACSourcesListConfigParser(config_id,
         parser = new MACSourcesListConfigParser(config_id,
                                                 globalContext());
                                                 globalContext());
+    } else if (config_id.compare("relay-supplied-options") == 0) {
+        parser = new RSOOListConfigParser(config_id);
     } else {
     } else {
         isc_throw(DhcpConfigError,
         isc_throw(DhcpConfigError,
                 "unsupported global configuration parameter: "
                 "unsupported global configuration parameter: "

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

@@ -3780,4 +3780,120 @@ TEST_F(Dhcp6ParserTest, hostReservationPerSubnet) {
     EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
     EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
 }
 }
 
 
+/// The goal of this test is to verify that configuration can include
+/// Relay Supplied options (specified as numbers).
+TEST_F(Dhcp6ParserTest, rsooNumbers) {
+
+    ConstElementPtr status;
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
+        Element::fromJSON("{ " + genIfaceConfig() + ","
+                          "\"relay-supplied-options\": [ \"10\", \"20\", \"30\" ],"
+                          "\"preferred-lifetime\": 3000,"
+                          "\"rebind-timer\": 2000, "
+                          "\"renew-timer\": 1000, "
+                          "\"subnet6\": [  ], "
+                          "\"valid-lifetime\": 4000 }")));
+
+    // returned value should be 0 (success)
+    checkResult(status, 0);
+
+    // The following codes should be enabled now
+    EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(10));
+    EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(20));
+    EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(30));
+
+    // This option is on the IANA list, so it should be allowed all the time
+    // (http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml)
+    EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                ->enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+    // Those options are not enabled
+    EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(25));
+    EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(1));
+}
+
+/// The goal of this test is to verify that configuration can include
+/// Relay Supplied options (specified as names).
+TEST_F(Dhcp6ParserTest, rsooNames) {
+
+    ConstElementPtr status;
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
+        Element::fromJSON("{ " + genIfaceConfig() + ","
+                          "\"relay-supplied-options\": [ \"dns-servers\", \"remote-id\" ],"
+                          "\"preferred-lifetime\": 3000,"
+                          "\"rebind-timer\": 2000, "
+                          "\"renew-timer\": 1000, "
+                          "\"subnet6\": [  ], "
+                          "\"valid-lifetime\": 4000 }")));
+
+    // returned value should be 0 (success)
+    checkResult(status, 0);
+
+    for (uint16_t code = 0; code < D6O_NAME_SERVERS; ++code) {
+        EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                     ->enabled(code)) << " for option code " << code;
+    }
+
+    // The following code should be enabled now
+    EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                ->enabled(D6O_NAME_SERVERS));
+
+    for (uint16_t code = D6O_NAME_SERVERS + 1; code < D6O_REMOTE_ID; ++code) {
+        EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                     ->enabled(code)) << " for option code " << code;
+    }
+
+    // Check remote-id. It should be enabled.
+    EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                ->enabled(D6O_REMOTE_ID));
+    for (uint16_t code = D6O_REMOTE_ID + 1; code < D6O_ERP_LOCAL_DOMAIN_NAME; ++code) {
+        EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                     ->enabled(code)) << " for option code " << code;
+    }
+
+    // This option is on the IANA list, so it should be allowed all the time
+    // (http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml)
+    EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                ->enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+    for (uint16_t code = D6O_ERP_LOCAL_DOMAIN_NAME + 1; code < 300; ++code) {
+        EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+                     ->enabled(code)) << " for option code " << code;
+    }
+}
+
+TEST_F(Dhcp6ParserTest, rsooNegativeNumber) {
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
+        Element::fromJSON("{ " + genIfaceConfig() + ","
+                          "\"relay-supplied-options\": [ \"80\", \"-2\" ],"
+                          "\"preferred-lifetime\": 3000,"
+                          "\"rebind-timer\": 2000, "
+                          "\"renew-timer\": 1000, "
+                          "\"subnet6\": [  ], "
+                          "\"valid-lifetime\": 4000 }")));
+
+    // returned value should be 0 (success)
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+TEST_F(Dhcp6ParserTest, rsooBogusName) {
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
+        Element::fromJSON("{ " + genIfaceConfig() + ","
+                          "\"relay-supplied-options\": [ \"bogus\", \"dns-servers\" ],"
+                          "\"preferred-lifetime\": 3000,"
+                          "\"rebind-timer\": 2000, "
+                          "\"renew-timer\": 1000, "
+                          "\"subnet6\": [  ], "
+                          "\"valid-lifetime\": 4000 }")));
+
+    // returned value should be 0 (success)
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
 };
 };

+ 15 - 7
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -76,6 +76,8 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
     for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
     for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
         Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt->second);
         Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt->second);
         if (!ia) {
         if (!ia) {
+            // This is not IA, so let's just store it.
+            config_.options_.insert(*opt);
             continue;
             continue;
         }
         }
 
 
@@ -453,13 +455,19 @@ Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
     srv_->shutdown_ = false;
     srv_->shutdown_ = false;
     // The client is configured to send through the relay. We achieve that
     // The client is configured to send through the relay. We achieve that
     // adding the relay structure.
     // adding the relay structure.
-    if (use_relay_) {
-        Pkt6::RelayInfo relay;
-        relay.linkaddr_ = relay_link_addr_;
-        relay.peeraddr_ = asiolink::IOAddress("fe80::1");
-        relay.msg_type_ = DHCPV6_RELAY_FORW;
-        relay.hop_count_ = 1;
-        msg->relay_info_.push_back(relay);
+    if (use_relay_ || !relay_info_.empty()) {
+        if (relay_info_.empty()) {
+            // Let's craft the relay info by hand
+            Pkt6::RelayInfo relay;
+            relay.linkaddr_ = relay_link_addr_;
+            relay.peeraddr_ = asiolink::IOAddress("fe80::1");
+            relay.msg_type_ = DHCPV6_RELAY_FORW;
+            relay.hop_count_ = 1;
+            msg->relay_info_.push_back(relay);
+        } else {
+            // The test provided relay_info_, let's use that.
+            msg->relay_info_ = relay_info_;
+        }
     }
     }
     // Repack the message to simulate wire-data parsing.
     // Repack the message to simulate wire-data parsing.
     msg->pack();
     msg->pack();

+ 29 - 1
src/bin/dhcp6/tests/dhcp6_client.h

@@ -74,10 +74,17 @@ public:
     /// @brief Holds the current client configuration obtained from the
     /// @brief Holds the current client configuration obtained from the
     /// server over DHCP.
     /// server over DHCP.
     ///
     ///
-    /// Currently it simply contains the collection of leases acquired.
+    /// Currently it simply contains the collection of leases acquired
+    /// and a list of options. Note: this is a simple copy of all
+    /// non-IA options and often includes "protocol" options, like
+    /// server-id and client-id.
     struct Configuration {
     struct Configuration {
+        /// @brief List of received leases
         std::vector<LeaseInfo> leases_;
         std::vector<LeaseInfo> leases_;
 
 
+        /// @brief List of received options
+        OptionCollection options_;
+
         /// @brief Status code received in the global option scope.
         /// @brief Status code received in the global option scope.
         uint16_t status_code_;
         uint16_t status_code_;
 
 
@@ -103,6 +110,21 @@ public:
             status_code_ = 0;
             status_code_ = 0;
             received_status_code_ = false;
             received_status_code_ = false;
         }
         }
+
+        /// @brief Finds an option with the specific code in the received
+        /// configuration.
+        ///
+        /// @param code Option code.
+        ///
+        /// @return Pointer to the option if the option exists, or NULL if
+        /// the option doesn't exist.
+        OptionPtr findOption(const uint16_t code) const {
+            std::multimap<unsigned int, OptionPtr>::const_iterator it = options_.find(code);
+            if (it != options_.end()) {
+                return (it->second);
+            }
+            return (OptionPtr());
+        }
     };
     };
 
 
     /// @brief Holds the DHCPv6 messages taking part in transaction between
     /// @brief Holds the DHCPv6 messages taking part in transaction between
@@ -388,6 +410,12 @@ public:
     /// @brief Link address of the relay to be used for relayed messages.
     /// @brief Link address of the relay to be used for relayed messages.
     asiolink::IOAddress relay_link_addr_;
     asiolink::IOAddress relay_link_addr_;
 
 
+    /// @brief RelayInfo (information about relays)
+    ///
+    /// Dhcp6Client will typically contruct this info itself, but if
+    /// it is provided here by the test, this data will be used as is.
+    std::vector<Pkt6::RelayInfo> relay_info_;
+
     /// @brief Controls whether the client will send ORO
     /// @brief Controls whether the client will send ORO
     ///
     ///
     /// The actual content of the ORO is specified in oro_.
     /// The actual content of the ORO is specified in oro_.

+ 257 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -40,6 +40,7 @@
 #include <hooks/server_hooks.h>
 #include <hooks/server_hooks.h>
 
 
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp/tests/pkt_captures.h>
 #include <dhcp/tests/pkt_captures.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 #include <boost/pointer_cast.hpp>
 #include <boost/pointer_cast.hpp>
@@ -2094,6 +2095,262 @@ TEST_F(Dhcpv6SrvTest, relayOverrideAndClientClass) {
     EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
     EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
 }
 }
 
 
+/// @brief Creates RSOO option with suboptions
+///
+/// Creates Relay-Supplied Options option that includes nested options. The
+/// codes of those nested options are specified in codes parameter. Content of
+/// the options is controlled with payload parameter. When it is zero, option
+/// code will be used (e.g. option 100 will contain repeating bytes of value 100).
+/// When non-zero is used, payload will be used. Each suboption length is always
+/// set to the arbitrarily chosen value of 10.
+///
+/// @param codes a vector of option codes to be created
+/// @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);
+    if (!def) {
+        isc_throw(BadValue, "Can't find RSOO definition");
+    }
+    OptionPtr rsoo_container(new OptionCustom(*def, Option::V6));
+
+    for (int i = 0; i < codes.size(); ++i) {
+        OptionBuffer buf(10, payload ? payload : codes[i]); // let's make the option 10 bytes long
+        rsoo_container->addOption(OptionPtr(new Option(Option::V6, codes[i], buf)));
+    }
+
+    return (rsoo_container);
+}
+
+// Test that the server processes RSOO (Relay Supplied Options option) correctly,
+// i.e. it includes in its response the options that are inserted by the relay.
+// The server must do this only for options that are RSOO-enabled.
+TEST_F(Dhcpv6SrvTest, rsoo) {
+
+    Dhcp6Client client;
+
+    string config =
+        "{"
+        "    \"relay-supplied-options\": [ \"110\", \"120\", \"130\" ],"
+        "    \"preferred-lifetime\": 3000,"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"subnet6\": [ { "
+        "        \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+        "        \"subnet\": \"2001:db8::/48\" "
+        "     } ],"
+        "    \"valid-lifetime\": 4000"
+        "}";
+
+    EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+    // Now pretend the packet came via one relay.
+    Pkt6::RelayInfo relay;
+    relay.msg_type_ = DHCPV6_RELAY_FORW;
+    relay.hop_count_ = 1;
+    relay.linkaddr_ = IOAddress("2001:db8::1");
+    relay.peeraddr_ = IOAddress("fe80::1");
+    vector<uint16_t> rsoo1;
+    rsoo1.push_back(109);
+    rsoo1.push_back(110);
+    rsoo1.push_back(111);
+
+    // The relay will request echoing back 3 options: 109, 110, 111.
+    // The configuration allows echoing back only 110.
+    OptionPtr opt = createRSOO(rsoo1);
+    relay.options_.insert(make_pair(opt->getType(), opt));
+    client.relay_info_.push_back(relay);
+
+    client.doSARR();
+
+    // Option 110 should be copied to the client
+    EXPECT_NE(client.config_.options_.find(110), client.config_.options_.end());
+
+    // Options 109 and 111 should not be copied (they are not RSOO-enabled)
+    EXPECT_EQ(client.config_.options_.find(109), client.config_.options_.end());
+    EXPECT_EQ(client.config_.options_.find(111), client.config_.options_.end());
+}
+
+// Test that the server processes RSOO (Relay Supplied Options option) correctly
+// when there are more relays. In particular, the following case is tested:
+// if relay1 inserts option A and B, relay2 inserts option B and C, the response
+// should include options A, B and C. The server must use instance of option B
+// that comes from the first relay, not the second one.
+TEST_F(Dhcpv6SrvTest, rsoo2relays) {
+
+    Dhcp6Client client;
+
+    string config =
+        "{"
+        "    \"relay-supplied-options\": [ \"110\", \"120\", \"130\" ],"
+        "    \"preferred-lifetime\": 3000,"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"subnet6\": [ { "
+        "        \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+        "        \"subnet\": \"2001:db8::/48\" "
+        "     } ],"
+        "    \"valid-lifetime\": 4000"
+        "}";
+
+    EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+    // Now pretend the packet came via two relays.
+
+    // This situation reflects the following case:
+    // client----relay1----relay2----server
+
+    // Fabricate the first relay.
+    Pkt6::RelayInfo relay1;
+    relay1.msg_type_ = DHCPV6_RELAY_FORW;
+    relay1.hop_count_ = 1;
+    relay1.linkaddr_ = IOAddress("2001:db8::1");
+    relay1.peeraddr_ = IOAddress("fe80::1");
+    vector<uint16_t> rsoo1;
+    rsoo1.push_back(110); // The relay1 will send 2 options: 110, 120
+    rsoo1.push_back(120);
+    OptionPtr opt = createRSOO(rsoo1, 1); // use 0x1 as payload
+    relay1.options_.insert(make_pair(opt->getType(), opt));
+
+    // Now the second relay.
+    Pkt6::RelayInfo relay2;
+    relay2.msg_type_ = DHCPV6_RELAY_FORW;
+    relay2.hop_count_ = 2;
+    relay2.linkaddr_ = IOAddress("2001:db8::2");
+    relay2.peeraddr_ = IOAddress("fe80::2");
+    vector<uint16_t> rsoo2;
+    rsoo2.push_back(120); // The relay2 will send 2 options: 120, 130
+    rsoo2.push_back(130);
+    opt = createRSOO(rsoo2, 2); // use 0x2 as payload
+    relay2.options_.insert(make_pair(opt->getType(), opt));
+
+    // The relays ecapsulate packet in this order: relay1, relay2, but the server
+    // decapsulates the packet in reverse order.
+    client.relay_info_.push_back(relay2);
+    client.relay_info_.push_back(relay1);
+
+    // There's a conflict here. Both relays want the server to echo back option
+    // 120. According to RFC6422, section 6:
+    //
+    // When such a conflict exists, the DHCP server MUST choose no more than
+    // one of these options to forward to the client.  The DHCP server MUST
+    // NOT forward more than one of these options to the client.
+    //
+    // By default, the DHCP server MUST choose the innermost value -- the
+    // value supplied by the relay agent closest to the DHCP client -- to
+    // forward to the DHCP client.
+
+    // Let the client do his thing.
+    client.doSARR();
+
+    int count110 = 0; // Let's count how many times option 110 was echoed back
+    int count120 = 0; // Let's count how many times option 120 was echoed back
+    int count130 = 0; // Let's count how many times option 130 was echoed back
+    OptionPtr opt120;
+    for (OptionCollection::const_iterator it = client.config_.options_.begin();
+         it != client.config_.options_.end(); ++it) {
+        switch (it->second->getType()) {
+        case 110:
+            count110++;
+            break;
+        case 120:
+            count120++;
+            opt120 = it->second;
+            break;
+        case 130:
+            count130++;
+            break;
+        default:
+            break;
+        }
+    }
+
+    // We expect to have exactly one instance of each option code.
+    EXPECT_EQ(1, count110);
+    EXPECT_EQ(1, count120);
+    EXPECT_EQ(1, count130);
+
+    // Now, let's check if the proper instance of option 120 was sent. It should
+    // match the content of what the first relay had sent.
+    ASSERT_TRUE(opt120);
+    vector<uint8_t> expected(10, 1);
+    EXPECT_EQ(expected, opt120->getData());
+}
+
+// This test verifies that the server will send the option for which it
+// has a candidate, rather than the option sent by the relay in the RSOO.
+TEST_F(Dhcpv6SrvTest, rsooOverride) {
+    Dhcp6Client client;
+    // The client will be requesting specific options.
+    client.useORO(true);
+
+    // The following configuration enables RSOO options: 110 and 120.
+    // It also configures the server with option 120 which should
+    // "override" the option 120 sent in the RSOO by the relay.
+    string config =
+        "{"
+        "    \"relay-supplied-options\": [ \"110\", \"120\" ],"
+        "    \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 120,"
+        "      \"type\": \"binary\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp6\","
+        "      \"encapsulate\": \"\""
+        "    } ],"
+        "    \"option-data\": [ {"
+        "      \"code\": 120,"
+        "      \"data\": \"05\""
+        "    } ],"
+        "    \"preferred-lifetime\": 3000,"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"subnet6\": [ { "
+        "        \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+        "        \"subnet\": \"2001:db8::/48\" "
+        "     } ],"
+        "    \"valid-lifetime\": 4000"
+        "}";
+
+    EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+    // Fabricate the relay.
+    Pkt6::RelayInfo relay;
+    relay.msg_type_ = DHCPV6_RELAY_FORW;
+    relay.hop_count_ = 1;
+    relay.linkaddr_ = IOAddress("2001:db8::1");
+    relay.peeraddr_ = IOAddress("fe80::1");
+    vector<uint16_t> rsoo;
+    // The relay will send 2 options: 110, 120
+    rsoo.push_back(110);
+    rsoo.push_back(120);
+    // Use 0x1 as payload
+    OptionPtr opt = createRSOO(rsoo, 1);
+    relay.options_.insert(make_pair(opt->getType(), opt));
+    client.relay_info_.push_back(relay);
+
+    // Client should request option 120 in the ORO so as the server
+    // sends the configured option 120 to the client.
+    client.requestOption(120);
+    client.doSARR();
+
+    // The option 110 should be the one injected by the relay.
+    opt = client.config_.findOption(110);
+    ASSERT_TRUE(opt);
+    // We check that this is the option injected by the relay by
+    // checking option length. It should have 10 bytes long payload.
+    ASSERT_EQ(10, opt->getData().size());
+
+    // The second option should be the one configured on the server,
+    // rather than the one injected by the relay.
+    opt = client.config_.findOption(120);
+    ASSERT_TRUE(opt);
+    // It should have the size of 1.
+    ASSERT_EQ(1, opt->getData().size());
+}
+
+
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 /// to call processX() methods.
 
 

+ 2 - 0
src/lib/dhcp/dhcp6.h

@@ -65,6 +65,8 @@
 #define D6O_CLT_TIME                            46 /* RFC5007 */
 #define D6O_CLT_TIME                            46 /* RFC5007 */
 #define D6O_LQ_RELAY_DATA                       47 /* RFC5007 */
 #define D6O_LQ_RELAY_DATA                       47 /* RFC5007 */
 #define D6O_LQ_CLIENT_LINK                      48 /* RFC5007 */
 #define D6O_LQ_CLIENT_LINK                      48 /* RFC5007 */
+#define D6O_ERP_LOCAL_DOMAIN_NAME               65 /* RFC6440 */
+#define D6O_RSOO                                66 /* RFC6422 */
 #define D6O_CLIENT_LINKLAYER_ADDR               79 /* RFC6939 */
 #define D6O_CLIENT_LINKLAYER_ADDR               79 /* RFC6939 */
 
 
 /*
 /*

+ 4 - 1
src/lib/dhcp/std_option_defs.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -327,6 +327,9 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
       RECORD_DEF(LQ_RELAY_DATA_RECORDS), "" },
       RECORD_DEF(LQ_RELAY_DATA_RECORDS), "" },
     { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
     { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
       NO_RECORD_DEF, "" },
       NO_RECORD_DEF, "" },
+    { "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" },
     { "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, OPT_BINARY_TYPE, false,
     { "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, OPT_BINARY_TYPE, false,
         NO_RECORD_DEF, "" }
         NO_RECORD_DEF, "" }
 
 

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

@@ -1140,6 +1140,14 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
 
 
     LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end,
     LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end,
                                     typeid(Option6AddrLst));
                                     typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RSOO, begin, end,
+                                    typeid(OptionCustom),
+                                    "rsoo-opts");
+
+    LibDhcpTest::testStdOptionDefs6(D6O_ERP_LOCAL_DOMAIN_NAME,
+                                    fqdn_buf.begin(), fqdn_buf.end(),
+                                    typeid(OptionCustom));
 }
 }
 
 
 // This test checks if the DHCPv6 option definition can be searched by
 // This test checks if the DHCPv6 option definition can be searched by

+ 34 - 0
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -1294,4 +1294,38 @@ TEST_F(Pkt6Test, getMACFromRemoteIdRelayOption) {
     EXPECT_EQ(tmp.str(), mac->toText(true));
     EXPECT_EQ(tmp.str(), mac->toText(true));
 }
 }
 
 
+// This test verifies that a solicit that passed through two relays is parsed
+// properly. In particular the second relay (outer encapsulation) included RSOO
+// (Relay Supplied Options option). This test checks whether it was parsed
+// properly. See captureRelayed2xRSOO() description for details.
+TEST_F(Pkt6Test, rsoo) {
+    Pkt6Ptr msg = test::PktCaptures::captureRelayed2xRSOO();
+
+    EXPECT_NO_THROW(msg->unpack());
+
+    EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+    EXPECT_EQ(217, msg->len());
+
+    ASSERT_EQ(2, msg->relay_info_.size());
+
+    // There should be an RSOO option in the outermost relay
+    OptionPtr opt = msg->getRelayOption(D6O_RSOO, 1);
+    ASSERT_TRUE(opt);
+
+    EXPECT_EQ(D6O_RSOO, opt->getType());
+    const OptionCollection& rsoo = opt->getOptions();
+    ASSERT_EQ(2, rsoo.size());
+
+    OptionPtr rsoo1 = opt->getOption(255);
+    OptionPtr rsoo2 = opt->getOption(256);
+
+    ASSERT_TRUE(rsoo1);
+    ASSERT_TRUE(rsoo2);
+
+    EXPECT_EQ(8, rsoo1->len()); // 4 bytes of data + header
+    EXPECT_EQ(13, rsoo2->len()); // 9 bytes of data + header
+
+}
+
+
 }
 }

+ 1 - 0
src/lib/dhcp/tests/pkt_captures.h

@@ -45,6 +45,7 @@ public:
     static isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit();
     static isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit();
     static isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit();
     static isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit();
     static isc::dhcp::Pkt6Ptr captureCableLabsShortVendorClass();
     static isc::dhcp::Pkt6Ptr captureCableLabsShortVendorClass();
+    static isc::dhcp::Pkt6Ptr captureRelayed2xRSOO();
 
 
 protected:
 protected:
     /// @brief Auxiliary method that sets Pkt6 fields
     /// @brief Auxiliary method that sets Pkt6 fields

+ 72 - 0
src/lib/dhcp/tests/pkt_captures6.cc

@@ -328,5 +328,77 @@ Pkt6Ptr isc::test::PktCaptures::captureCableLabsShortVendorClass() {
 
 
 }
 }
 
 
+/// @brief creates doubly relayed solicit message
+///
+/// This is a traffic capture exported from wireshark and manually modified
+/// to include necessary options (RSOO). It includes a SOLICIT message
+/// that passed through two relays. It is especially interesting,
+/// because of the following properties:
+/// - double encapsulation
+/// - first relay inserts relay-msg before extra options
+/// - second relay inserts relay-msg after extra options
+/// - both relays are from different vendors
+/// - interface-id are different for each relay
+/// - first relay inserts valid remote-id
+/// - second relay inserts remote-id with empty vendor data
+/// - the solicit message requests for custom options in ORO
+/// - there are option types in RELAY-FORW that do not appear in SOLICIT
+/// - there are option types in SOLICT that do not appear in RELAY-FORW
+///
+/// RELAY-FORW
+///  - relay message option
+///      - RELAY-FORW
+///          - rsoo (66)
+///              - option 255 (len 4)
+///              - option 256 (len 9)
+///          - remote-id option (37)
+///          - relay message option
+///             - SOLICIT
+///                  - client-id option
+///                  - ia_na option
+///                  - elapsed time
+///                  - ORO
+/// - interface-id option (18)
+/// - remote-id option (37)
+///
+/// The original capture was posted to dibbler users mailing list.
+///
+/// @return created double relayed SOLICIT message
+isc::dhcp::Pkt6Ptr isc::test::PktCaptures::captureRelayed2xRSOO() {
+
+    // string exported from Wireshark
+    string hex_string =
+        "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a9"
+        "0009007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c18a9"
+        "00420015" // RSOO (includes ...
+        "00ff000401020304" // ... option 255, len 4, content 0x01020304
+        "01000009010203040506070809" // ... option 256, len 9, content 0x010203040506070809
+        "0025000400000de9" // remote-id
+        "00090036" // relay-msg, len 54
+        "016b4fe2" // solicit"
+        "0001000e0001000118b033410000215c18a9" // client-id
+        "0003000c00000001ffffffffffffffff" // ia-na
+        "000800020000"
+        "00060006001700f200f3"
+        "0012001c4953414d3134347c3239397c697076367c6e743a76703a313a" // vendor-class
+        "313130002500120000197f0001000118b033410000215c18a9";
+
+    std::vector<uint8_t> bin;
+
+    // Decode the hex string and store it in bin (which happens
+    // to be OptionBuffer format)
+    isc::util::encode::decodeHex(hex_string, bin);
+
+    Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+    pkt->setRemotePort(547);
+    pkt->setRemoteAddr(IOAddress("fe80::1234"));
+    pkt->setLocalPort(547);
+    pkt->setLocalAddr(IOAddress("ff05::1:3"));
+    pkt->setIndex(2);
+    pkt->setIface("eth0");
+    return (pkt);
+}
+
+
 }; // end of isc::test namespace
 }; // end of isc::test namespace
 }; // end of isc namespace
 }; // end of isc namespace

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

@@ -70,6 +70,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
 libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
+libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
 libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h
 libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h

+ 5 - 2
src/lib/dhcpsrv/cfg_option.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,6 +15,7 @@
 #include <dhcp/option_space.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option.h>
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
+#include <dhcp/dhcp6.h>
 #include <limits>
 #include <limits>
 #include <string>
 #include <string>
 
 
@@ -27,6 +28,9 @@ OptionDescriptor::equals(const OptionDescriptor& other) const {
             option_->equals(other.option_));
             option_->equals(other.option_));
 }
 }
 
 
+CfgOption::CfgOption() {
+}
+
 bool
 bool
 CfgOption::equals(const CfgOption& other) const {
 CfgOption::equals(const CfgOption& other) const {
     return (options_.equals(other.options_) &&
     return (options_.equals(other.options_) &&
@@ -190,6 +194,5 @@ CfgOption::optionSpaceToVendorId(const std::string& option_space) {
     return (static_cast<uint32_t>(check));
     return (static_cast<uint32_t>(check));
 }
 }
 
 
-
 } // end of namespace isc::dhcp
 } // end of namespace isc::dhcp
 } // end of namespace isc
 } // end of namespace isc

+ 5 - 1
src/lib/dhcpsrv/cfg_option.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,6 +26,7 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include <stdint.h>
 #include <string>
 #include <string>
+#include <set>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -197,6 +198,9 @@ typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
 class CfgOption {
 class CfgOption {
 public:
 public:
 
 
+    /// @brief default constructor
+    CfgOption();
+
     /// @name Methods and operators used for comparing objects.
     /// @name Methods and operators used for comparing objects.
     ///
     ///
     //@{
     //@{

+ 46 - 0
src/lib/dhcpsrv/cfg_rsoo.cc

@@ -0,0 +1,46 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/dhcp6.h>
+#include <dhcpsrv/cfg_rsoo.h>
+
+namespace isc {
+namespace dhcp {
+
+CfgRSOO::CfgRSOO()
+    : rsoo_options_() {
+    rsoo_options_.insert(D6O_ERP_LOCAL_DOMAIN_NAME);
+}
+
+void
+CfgRSOO::clear() {
+    rsoo_options_.clear();
+}
+
+bool
+CfgRSOO::enabled(const uint16_t code) const {
+    return (rsoo_options_.find(code) != rsoo_options_.end());
+}
+
+void
+CfgRSOO::enable(const uint16_t code) {
+    if (rsoo_options_.find(code) == rsoo_options_.end()) {
+        // If there's no such code added yet, let's add it
+        rsoo_options_.insert(code);
+    }
+}
+
+
+}
+}

+ 82 - 0
src/lib/dhcpsrv/cfg_rsoo.h

@@ -0,0 +1,82 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CFG_RSOO_H
+#define CFG_RSOO_H
+
+#include <boost/shared_ptr.hpp>
+#include <set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents configuration of the RSOO options for the DHCP server.
+///
+/// This class holds the set of RSOO-enabled options (see RFC6422). The list
+/// of RSOO-enabled options is maintained by IANA and currently the option
+/// 65 is officially RSSO-enabled. The list may be extended in the future
+/// and this class allows for specifying any future RSOO-enabled options.
+/// The administrator may also use existing options as RSOO-enabled.
+class CfgRSOO {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// It adds the default (officially) RSOO-enabled options:
+    /// - OPTION_ERP_LOCAL_DOMAIN_NAME
+    CfgRSOO();
+
+    /// @brief Removes designation of all options as RSOO_enabled.
+    ///
+    /// This method removes all designations of all options as being RSOO-enabled.
+    void clear();
+
+    /// @brief Returns whether specific option code is RSOO-enabled.
+    ///
+    /// @param code Option code to check
+    /// @return true, if it is allowed in Relay-Supplied Options option
+    bool enabled(const uint16_t code) const;
+
+    /// @brief Marks specified option code as RSOO-enabled.
+    ///
+    /// @param code option to be enabled in RSOO
+    void enable(const uint16_t code);
+
+private:
+
+    /// @brief Contains a set of options that are allowed in RSOO option
+    ///
+    /// RSOO stands for Relay-Supplied Options option. This is an option that
+    /// is inserted by the relay agent with the intention that the server will
+    /// echo those options back to the client. Only those options marked as
+    /// RSOO-enabled may appear in the RSOO. Currently only option 65 is marked
+    /// as such, but more options may be added in the future. See RFC6422 for details.
+    std::set<uint16_t> rsoo_options_;
+
+};
+
+/// @name Pointers to the @c CfgRSOO objects.
+//@{
+/// @brief Pointer to the Non-const object.
+typedef boost::shared_ptr<CfgRSOO> CfgRSOOPtr;
+
+/// @brief Pointer to the const object.
+typedef boost::shared_ptr<const CfgRSOO> ConstCfgRSOOPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_RSOO_H

+ 1 - 1
src/lib/dhcpsrv/option_space_container.h

@@ -23,7 +23,7 @@ namespace dhcp {
 
 
 /// @brief Simple container for option spaces holding various items.
 /// @brief Simple container for option spaces holding various items.
 ///
 ///
-/// This helper class is used to store items of various types in
+/// This helper class is used to store items of various types
 /// that are grouped by option space names. Each option space is
 /// that are grouped by option space names. Each option space is
 /// mapped to a container that holds items which specifically can
 /// mapped to a container that holds items which specifically can
 /// be OptionDefinition objects or Subnet::OptionDescriptor structures.
 /// be OptionDefinition objects or Subnet::OptionDescriptor structures.

+ 2 - 2
src/lib/dhcpsrv/srv_config.cc

@@ -29,14 +29,14 @@ SrvConfig::SrvConfig()
     : sequence_(0), cfg_iface_(new CfgIface()),
     : sequence_(0), cfg_iface_(new CfgIface()),
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
-      cfg_hosts_(new CfgHosts()) {
+      cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()) {
 }
 }
 
 
 SrvConfig::SrvConfig(const uint32_t sequence)
 SrvConfig::SrvConfig(const uint32_t sequence)
     : sequence_(sequence), cfg_iface_(new CfgIface()),
     : sequence_(sequence), cfg_iface_(new CfgIface()),
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
-      cfg_hosts_(new CfgHosts()) {
+      cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()) {
 }
 }
 
 
 std::string
 std::string

+ 25 - 0
src/lib/dhcpsrv/srv_config.h

@@ -19,6 +19,7 @@
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option_def.h>
 #include <dhcpsrv/cfg_option_def.h>
+#include <dhcpsrv/cfg_rsoo.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/cfg_subnets6.h>
 #include <dhcpsrv/cfg_subnets6.h>
 #include <dhcpsrv/cfg_mac_source.h>
 #include <dhcpsrv/cfg_mac_source.h>
@@ -239,6 +240,24 @@ public:
         return (cfg_hosts_);
         return (cfg_hosts_);
     }
     }
 
 
+    /// @brief Returns pointer to the non-const object representing
+    /// set of RSOO-enabled options.
+    ///
+    /// @return Pointer to the non-const object holding RSOO-enabled
+    /// options.
+    CfgRSOOPtr getCfgRSOO() {
+        return (cfg_rsoo_);
+    }
+
+    /// @brief Returns pointer to the const object representing set
+    /// of RSOO-enabled options.
+    ///
+    /// @return Pointer to the const object holding RSOO-enabled
+    /// options.
+    ConstCfgRSOOPtr getCfgRSOO() const {
+        return (cfg_rsoo_);
+    }
+
     //@}
     //@}
 
 
     /// @brief Returns non-const reference to an array that stores
     /// @brief Returns non-const reference to an array that stores
@@ -371,6 +390,12 @@ private:
 
 
     /// @brief A list of configured MAC sources.
     /// @brief A list of configured MAC sources.
     CfgMACSource cfg_mac_source_;
     CfgMACSource cfg_mac_source_;
+
+    /// @brief Pointer to the configuration for RSOO-enabled options.
+    ///
+    /// This object holds a set of RSOO-enabled options. See
+    /// RFC 6422 for the definition of the RSOO-enabled option.
+    CfgRSOOPtr cfg_rsoo_;
 };
 };
 
 
 /// @name Pointers to the @c SrvConfig object.
 /// @name Pointers to the @c SrvConfig object.

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

@@ -64,6 +64,7 @@ libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_mac_source_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_mac_source_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_rsoo_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc

+ 2 - 1
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
+#include <dhcp/dhcp6.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_space.h>
 #include <dhcp/option_space.h>

+ 99 - 0
src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc

@@ -0,0 +1,99 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/dhcp6.h>
+#include <dhcpsrv/cfg_rsoo.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that the RSOO configuration holds the default
+// RSOO-enabled options.
+TEST(CfgRSOOTest, defaults) {
+    CfgRSOO rsoo;
+    EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+    for (uint16_t code = 0; code < 200; ++code) {
+        if (code != D6O_ERP_LOCAL_DOMAIN_NAME) {
+            EXPECT_FALSE(rsoo.enabled(code))
+                << "expected that the option with code "
+                << code << " is by default RSOO-disabled, but"
+                " it is enabled";
+        }
+    }
+
+    // Now, let's see if we can remove the default options.
+    ASSERT_NO_THROW(rsoo.clear());
+    EXPECT_FALSE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+    // Make sure it can be added again.
+    ASSERT_NO_THROW(rsoo.enable(D6O_ERP_LOCAL_DOMAIN_NAME));
+    EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+}
+
+// This test verifies that it is possible to enable more RSOO options
+// and later remove all of them.
+TEST(CfgRSOOTest, enableAndClear) {
+    CfgRSOO rsoo;
+    EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+    // Enable option 88.
+    ASSERT_FALSE(rsoo.enabled(88));
+    ASSERT_NO_THROW(rsoo.enable(88));
+    EXPECT_TRUE(rsoo.enabled(88));
+
+    // Enable option 89.
+    ASSERT_FALSE(rsoo.enabled(89));
+    ASSERT_NO_THROW(rsoo.enable(89));
+    EXPECT_TRUE(rsoo.enabled(89));
+
+    // Remove them and make sure they have been removed.
+    ASSERT_NO_THROW(rsoo.clear());
+    for (uint16_t code = 0; code < 200; ++code) {
+        EXPECT_FALSE(rsoo.enabled(code))
+            << "expected that the option with code "
+            << code << " is RSOO-disabled after clearing"
+            " the RSOO configuration, but it is not";
+    }
+}
+
+// This test verfies that the same option may be specified
+// multiple times and that the code doesn't fail.
+TEST(CfgRSOOTest, enableTwice) {
+    CfgRSOO rsoo;
+    // By default there should be the default option enabled.
+    // Let's try to enable it again. It should pass.
+    ASSERT_NO_THROW(rsoo.enable(D6O_ERP_LOCAL_DOMAIN_NAME));
+    EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+    // Enable option 88.
+    ASSERT_FALSE(rsoo.enabled(88));
+    ASSERT_NO_THROW(rsoo.enable(88));
+    EXPECT_TRUE(rsoo.enabled(88));
+
+    // And enable it again.
+    ASSERT_NO_THROW(rsoo.enabled(88));
+    EXPECT_TRUE(rsoo.enabled(88));
+
+    // Remove all.
+    ASSERT_NO_THROW(rsoo.clear());
+    ASSERT_FALSE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+    ASSERT_FALSE(rsoo.enabled(88));
+}
+
+} // end of anonymous namespace