Browse Source

[master] Merge branch 'trac3070'

Marcin Siodelski 10 years ago
parent
commit
a6b6156aaa

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

@@ -1304,6 +1304,40 @@ should include options from the isc option space:
         </para>
         </para>
       </section>
       </section>
 
 
+      <section id="dhcp6-rapid-commit">
+        <title>Rapid Commit</title>
+        <para>The Rapid Commit option, described in
+        <ulink url="http://tools.ietf.org/html/rfc3315">RFC 3315</ulink>, is supported
+        by the Kea DHCPv6 server. However, support is disabled by default for
+        all subnets. It can be enabled for a particular subnet using the
+        <command>rapid-commit</command> parameter as shown below:
+<screen>
+"Dhcp6": {
+    "subnet6": [
+        {
+            "subnet": "2001:db8:beef::/48",
+            <userinput>"rapid-commit": true</userinput>,
+            "pools": [
+                 {
+                     "pool": "2001:db8:beef::1-2001:db8:beef::10"
+                 }
+             ],
+        }
+    ],
+    ...
+}
+</screen>
+        </para>
+        <para>
+          This setting only affects the subnet for which the
+          <command>rapid-commit</command> is set to <command>true</command>.
+          For clients connected to other subnets, the server will ignore the
+          Rapid Commit option sent by the client and will follow the 4-way
+          exchange procedure, i.e. respond with the Advertise for the Solicit
+          containing Rapid Commit option.
+        </para>
+      </section>
+
       <section id="dhcp6-relays">
       <section id="dhcp6-relays">
         <title>DHCPv6 Relays</title>
         <title>DHCPv6 Relays</title>
         <para>
         <para>

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

@@ -245,6 +245,12 @@
                   "item_default": ""
                   "item_default": ""
                 },
                 },
 
 
+                { "item_name": "rapid-commit",
+                  "item_type": "boolean",
+                  "item_optional": false,
+                  "item_default": false
+                },
+
                 { "item_name": "renew-timer",
                 { "item_name": "renew-timer",
                   "item_type": "integer",
                   "item_type": "integer",
                   "item_optional": false,
                   "item_optional": false,

+ 7 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -534,6 +534,13 @@ A debug message printing the details of the received packet. The first
 argument includes the client and the transaction identification
 argument includes the client and the transaction identification
 information.
 information.
 
 
+% DHCP6_RAPID_COMMIT %1: Rapid Commit option received, following 2-way exchange
+This debug messgage is issued when the server found a Rapid Commit option
+in the client's message and 2-way exchanges are supported by the
+server for the subnet on which the client is connected. The argument
+specifies the client and transaction identification information.
+
+
 % DHCP6_RELEASE_NA %1: binding for address %2 and iaid=%3 was released properly
 % DHCP6_RELEASE_NA %1: binding for address %2 and iaid=%3 was released properly
 This debug message indicates that an address was released properly. It
 This debug message indicates that an address was released properly. It
 is a normal operation during client shutdown.
 is a normal operation during client shutdown.

+ 58 - 35
src/bin/dhcp6/dhcp6_srv.cc

@@ -1088,7 +1088,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
             break;
             break;
         }
         }
         case D6O_IA_PD: {
         case D6O_IA_PD: {
-            OptionPtr answer_opt = assignIA_PD(question, ctx,
+            OptionPtr answer_opt = assignIA_PD(question, answer, ctx,
                                                boost::dynamic_pointer_cast<
                                                boost::dynamic_pointer_cast<
                                                Option6IA>(opt->second));
                                                Option6IA>(opt->second));
             if (answer_opt) {
             if (answer_opt) {
@@ -1354,7 +1354,7 @@ Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
     const Subnet6Ptr& subnet = orig_ctx.subnet_;
     const Subnet6Ptr& subnet = orig_ctx.subnet_;
     const DuidPtr& duid = orig_ctx.duid_;
     const DuidPtr& duid = orig_ctx.duid_;
 
 
-    // If there is no subnet selected for handling this IA_NA, the only thing to do left is
+    // If there is no subnet selected for handling this IA_NA, the only thing left to do is
     // to say that we are sorry, but the user won't get an address. As a convenience, we
     // to say that we are sorry, but the user won't get an address. As a convenience, we
     // use a different status text to indicate that (compare to the same status code,
     // use a different status text to indicate that (compare to the same status code,
     // but different wording below)
     // but different wording below)
@@ -1373,17 +1373,18 @@ Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
         return (ia_rsp);
         return (ia_rsp);
     }
     }
 
 
-    // "Fake" allocation is processing of SOLICIT message. We pretend to do an
-    // allocation, but we do not put the lease in the database. That is ok,
-    // because we do not guarantee that the user will get that exact lease. If
-    // the user selects this server to do actual allocation (i.e. sends REQUEST)
-    // it should include this hint. That will help us during the actual lease
-    // allocation.
-    bool fake_allocation = false;
-    if (query->getType() == DHCPV6_SOLICIT) {
-        /// @todo: Check if we support rapid commit
-        fake_allocation = true;
-    }
+    // "Fake" allocation is the case when the server is processing the Solicit
+    // message without the Rapid Commit option and advertises a lease to
+    // the client, but doesn't commit this lease to the lease database. If
+    // the Solicit contains the Rapid Commit option and the server is
+    // configured to honor the Rapid Commit option, or the client has sent
+    // the Request message, the lease will be committed to the lease
+    // database. The type of the server's response may be used to determine
+    // if this is the fake allocation case or not. When the server sends
+    // Reply message it means that it is committing leases. Other message
+    // type (Advertise) means that server is not committing leases (fake
+    // allocation).
+    bool fake_allocation = (answer->getType() != DHCPV6_REPLY);
 
 
     // Get DDNS update direction flags
     // Get DDNS update direction flags
     bool do_fwd = false;
     bool do_fwd = false;
@@ -1501,7 +1502,7 @@ Dhcpv6Srv::conditionalNCRRemoval(const Pkt6Ptr& query, Lease6Ptr& old_lease,
 }
 }
 
 
 OptionPtr
 OptionPtr
-Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query,
+Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, const Pkt6Ptr& answer,
                        AllocEngine::ClientContext6& orig_ctx,
                        AllocEngine::ClientContext6& orig_ctx,
                        boost::shared_ptr<Option6IA> ia) {
                        boost::shared_ptr<Option6IA> ia) {
 
 
@@ -1530,8 +1531,8 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query,
     // as we can initialize IAID using a constructor.
     // as we can initialize IAID using a constructor.
     boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
     boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
 
 
-    // If there is no subnet selected for handling this IA_PD, the only thing to
-    // do left is to say that we are sorry, but the user won't get an address.
+    // If there is no subnet selected for handling this IA_PD, the only thing
+    // left to do is to say that we are sorry, but the user won't get an address.
     // As a convenience, we use a different status text to indicate that
     // As a convenience, we use a different status text to indicate that
     // (compare to the same status code, but different wording below)
     // (compare to the same status code, but different wording below)
     if (!subnet) {
     if (!subnet) {
@@ -1542,13 +1543,18 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query,
         return (ia_rsp);
         return (ia_rsp);
     }
     }
 
 
-    // "Fake" allocation is processing of SOLICIT message. We pretend to do an
-    // allocation, but we do not put the lease in the database. That is ok,
-    // because we do not guarantee that the user will get that exact lease. If
-    // the user selects this server to do actual allocation (i.e. sends REQUEST)
-    // it should include this hint. That will help us during the actual lease
-    // allocation.
-    bool fake_allocation = (query->getType() == DHCPV6_SOLICIT);
+    // "Fake" allocation is the case when the server is processing the Solicit
+    // message without the Rapid Commit option and advertises a lease to
+    // the client, but doesn't commit this lease to the lease database. If
+    // the Solicit contains the Rapid Commit option and the server is
+    // configured to honor the Rapid Commit option, or the client has sent
+    // the Request message, the lease will be committed to the lease
+    // database. The type of the server's response may be used to determine
+    // if this is the fake allocation case or not. When the server sends
+    // Reply message it means that it is committing leases. Other message
+    // type (Advertise) means that server is not committing leases (fake
+    // allocation).
+    bool fake_allocation = (answer->getType() != DHCPV6_REPLY);
 
 
     // Use allocation engine to pick a lease for this client. Allocation engine
     // Use allocation engine to pick a lease for this client. Allocation engine
     // will try to honour the hint, but it is just a hint - some other address
     // will try to honour the hint, but it is just a hint - some other address
@@ -2359,22 +2365,39 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx = createContext(solicit);
     AllocEngine::ClientContext6 ctx = createContext(solicit);
 
 
-    Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
+    Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
+
+    // Handle Rapid Commit option, if present.
+    if (ctx.subnet_ && ctx.subnet_->getRapidCommit()) {
+        OptionPtr opt_rapid_commit = solicit->getOption(D6O_RAPID_COMMIT);
+        if (opt_rapid_commit) {
 
 
-    copyClientOptions(solicit, advertise);
-    appendDefaultOptions(solicit, advertise);
-    appendRequestedOptions(solicit, advertise, ctx);
-    appendRequestedVendorOptions(solicit, advertise, ctx);
+            /// @todo uncomment when #3807 is merged!
+/*            LOG_DEBUG(options_logger, DBG_DHCP6_DETAIL, DHCP6_RAPID_COMMIT)
+                .arg(solicit->getLabel()); */
 
 
-    processClientFqdn(solicit, advertise, ctx);
-    assignLeases(solicit, advertise, ctx);
-    // Note, that we don't create NameChangeRequests here because we don't
-    // perform DNS Updates for Solicit. Client must send Request to update
-    // DNS.
+            // If Rapid Commit has been sent by the client, change the
+            // response type to Reply and include Rapid Commit option.
+            response->setType(DHCPV6_REPLY);
+            response->addOption(opt_rapid_commit);
+        }
+    }
 
 
-    generateFqdn(advertise);
+    copyClientOptions(solicit, response);
+    appendDefaultOptions(solicit, response);
+    appendRequestedOptions(solicit, response, ctx);
+    appendRequestedVendorOptions(solicit, response, ctx);
+
+    processClientFqdn(solicit, response, ctx);
+    assignLeases(solicit, response, ctx);
+
+    // Only generate name change requests if sending a Reply as a result
+    // of receiving Rapid Commit option.
+    if (response->getType() == DHCPV6_REPLY) {
+        createNameChangeRequests(response);
+    }
 
 
-    return (advertise);
+    return (response);
 }
 }
 
 
 Pkt6Ptr
 Pkt6Ptr

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

@@ -291,10 +291,12 @@ protected:
     /// allocation failure.
     /// allocation failure.
     ///
     ///
     /// @param query client's message (typically SOLICIT or REQUEST)
     /// @param query client's message (typically SOLICIT or REQUEST)
+    /// @param answer server's response to the client's message.
     /// @param orig_ctx client context (contains subnet, duid and other parameters)
     /// @param orig_ctx client context (contains subnet, duid and other parameters)
     /// @param ia pointer to client's IA_PD option (client's request)
     /// @param ia pointer to client's IA_PD option (client's request)
     /// @return IA_PD option (server's response)
     /// @return IA_PD option (server's response)
     OptionPtr assignIA_PD(const Pkt6Ptr& query,
     OptionPtr assignIA_PD(const Pkt6Ptr& query,
+                          const isc::dhcp::Pkt6Ptr& answer,
                           AllocEngine::ClientContext6& orig_ctx,
                           AllocEngine::ClientContext6& orig_ctx,
                           boost::shared_ptr<Option6IA> ia);
                           boost::shared_ptr<Option6IA> ia);
 
 

+ 14 - 5
src/bin/dhcp6/json_config_parser.cc

@@ -404,6 +404,8 @@ protected:
             parser = new PdPoolListParser(config_id, pools_);
             parser = new PdPoolListParser(config_id, pools_);
         } else if (config_id.compare("option-data") == 0) {
         } else if (config_id.compare("option-data") == 0) {
             parser = new OptionDataListParser(config_id, options_, AF_INET6);
             parser = new OptionDataListParser(config_id, options_, AF_INET6);
+        } else if (config_id.compare("rapid-commit") == 0) {
+            parser = new BooleanParser(config_id, boolean_values_);
         } else {
         } else {
             isc_throw(NotImplemented, "unsupported parameter: " << config_id);
             isc_throw(NotImplemented, "unsupported parameter: " << config_id);
         }
         }
@@ -473,12 +475,16 @@ protected:
             }
             }
         }
         }
 
 
-        stringstream tmp;
-        tmp << addr << "/" << static_cast<int>(len)
-            << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
-            << pref << ", valid=" << valid;
+        bool rapid_commit = boolean_values_->getOptionalParam("rapid-commit", false);
 
 
-        LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
+        std::ostringstream output;
+        output << addr << "/" << static_cast<int>(len)
+               << " with params t1=" << t1 << ", t2="
+               << t2 << ", preferred-lifetime=" << pref
+               << ", valid-lifetime=" << valid
+               << ", rapid-commit is " << (rapid_commit ? "enabled" : "disabled");
+
+        LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(output.str());
 
 
         // Create a new subnet.
         // Create a new subnet.
         Subnet6* subnet6 = new Subnet6(addr, len, t1, t2, pref, valid,
         Subnet6* subnet6 = new Subnet6(addr, len, t1, t2, pref, valid,
@@ -491,6 +497,9 @@ protected:
             subnet6->setInterfaceId(opt);
             subnet6->setInterfaceId(opt);
         }
         }
 
 
+        // Enable or disable Rapid Commit option support for the subnet.
+        subnet6->setRapidCommit(rapid_commit);
+
         // Try setting up client class (if specified)
         // Try setting up client class (if specified)
         try {
         try {
             string client_class = string_values_->getParam("client-class");
             string client_class = string_values_->getParam("client-class");

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

@@ -496,6 +496,41 @@ public:
         CfgMgr::instance().clear();
         CfgMgr::instance().clear();
     }
     }
 
 
+    /// @brief Tests the Rapid Commit configuration for a subnet.
+    ///
+    /// This test configures the server with a given configuration and
+    /// verifies if the Rapid Commit has been configured successfully
+    /// for a subnet.
+    ///
+    /// @param config Server configuration, possibly including the
+    /// 'rapid-commit' parameter.
+    /// @param exp_rapid_commit Expected value of the Rapid Commit flag
+    /// within a subnet.
+    void testRapidCommit(const std::string& config,
+                         const bool exp_rapid_commit) {
+        // Clear any existing configuration.
+        CfgMgr::instance().clear();
+
+        // Configure the server.
+        ElementPtr json = Element::fromJSON(config);
+
+        // Make sure that the configuration was successful.
+        ConstElementPtr status;
+        EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+        checkResult(status, 0);
+
+        // Get the subnet.
+        Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+            selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+        ASSERT_TRUE(subnet);
+
+        // Check the Rapid Commit flag for the subnet.
+        EXPECT_EQ(exp_rapid_commit, subnet->getRapidCommit());
+
+        // Clear any existing configuration.
+        CfgMgr::instance().clear();
+    }
+
     int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
     int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
     Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
     Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
@@ -1089,6 +1124,54 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
     EXPECT_TRUE(errorContainsPosition(status, "<string>"));
     EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 }
 
 
+// This test checks the configuration of the Rapid Commit option
+// support for the subnet.
+TEST_F(Dhcp6ParserTest, subnetRapidCommit) {
+    {
+        // rapid-commit implicitly set to false.
+        SCOPED_TRACE("Default Rapid Commit setting");
+        testRapidCommit("{ \"preferred-lifetime\": 3000,"
+                        "\"rebind-timer\": 2000, "
+                        "\"renew-timer\": 1000, "
+                        "\"subnet6\": [ { "
+                        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - "
+                        "2001:db8:1::ffff\" } ],"
+                        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+                        "\"valid-lifetime\": 4000 }",
+                        false);
+    }
+
+    {
+        // rapid-commit explicitly set to true.
+        SCOPED_TRACE("Enable Rapid Commit");
+        testRapidCommit("{ \"preferred-lifetime\": 3000,"
+                        "\"rebind-timer\": 2000, "
+                        "\"renew-timer\": 1000, "
+                        "\"subnet6\": [ { "
+                        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - "
+                        "2001:db8:1::ffff\" } ],"
+                        "    \"rapid-commit\": True,"
+                        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+                        "\"valid-lifetime\": 4000 }",
+                        true);
+    }
+
+    {
+        // rapid-commit explicitly set to false.
+        SCOPED_TRACE("Disable Rapid Commit");
+        testRapidCommit("{ \"preferred-lifetime\": 3000,"
+                        "\"rebind-timer\": 2000, "
+                        "\"renew-timer\": 1000, "
+                        "\"subnet6\": [ { "
+                        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - "
+                        "2001:db8:1::ffff\" } ],"
+                        "    \"rapid-commit\": False,"
+                        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+                        "\"valid-lifetime\": 4000 }",
+                        false);
+    }
+}
+
 // This test checks that multiple pools can be defined and handled properly.
 // This test checks that multiple pools can be defined and handled properly.
 // The test defines 2 subnets, each with 2 pools.
 // The test defines 2 subnets, each with 2 pools.
 TEST_F(Dhcp6ParserTest, multiplePools) {
 TEST_F(Dhcp6ParserTest, multiplePools) {

+ 1 - 0
src/bin/dhcp6/tests/d2_unittest.cc

@@ -45,6 +45,7 @@ const bool Dhcp6SrvD2Test::SHOULD_PASS;
 const bool Dhcp6SrvD2Test::SHOULD_FAIL;
 const bool Dhcp6SrvD2Test::SHOULD_FAIL;
 
 
 Dhcp6SrvD2Test::Dhcp6SrvD2Test() : rcode_(-1) {
 Dhcp6SrvD2Test::Dhcp6SrvD2Test() : rcode_(-1) {
+    reset();
 }
 }
 
 
 Dhcp6SrvD2Test::~Dhcp6SrvD2Test() {
 Dhcp6SrvD2Test::~Dhcp6SrvD2Test() {

+ 48 - 4
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -39,13 +39,16 @@ Dhcp6Client::Dhcp6Client() :
     dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
     dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
     duid_(generateDUID(DUID::DUID_LLT)),
     duid_(generateDUID(DUID::DUID_LLT)),
     link_local_("fe80::3a60:77ff:fed5:cdef"),
     link_local_("fe80::3a60:77ff:fed5:cdef"),
+    iface_name_("eth0"),
     srv_(boost::shared_ptr<NakedDhcpv6Srv>(new NakedDhcpv6Srv(0))),
     srv_(boost::shared_ptr<NakedDhcpv6Srv>(new NakedDhcpv6Srv(0))),
     use_na_(false),
     use_na_(false),
     use_pd_(false),
     use_pd_(false),
     use_relay_(false),
     use_relay_(false),
     use_oro_(false),
     use_oro_(false),
     use_client_id_(true),
     use_client_id_(true),
-    prefix_hint_() {
+    use_rapid_commit_(false),
+    prefix_hint_(),
+    fqdn_() {
 }
 }
 
 
 Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
 Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
@@ -54,13 +57,16 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
     dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
     dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
     duid_(generateDUID(DUID::DUID_LLT)),
     duid_(generateDUID(DUID::DUID_LLT)),
     link_local_("fe80::3a60:77ff:fed5:cdef"),
     link_local_("fe80::3a60:77ff:fed5:cdef"),
+    iface_name_("eth0"),
     srv_(srv),
     srv_(srv),
     use_na_(false),
     use_na_(false),
     use_pd_(false),
     use_pd_(false),
     use_relay_(false),
     use_relay_(false),
     use_oro_(false),
     use_oro_(false),
     use_client_id_(true),
     use_client_id_(true),
-    prefix_hint_() {
+    use_rapid_commit_(false),
+    prefix_hint_(),
+    fqdn_() {
 }
 }
 
 
 void
 void
@@ -197,6 +203,13 @@ Dhcp6Client::applyLease(const LeaseInfo& lease_info) {
 }
 }
 
 
 void
 void
+Dhcp6Client::appendFQDN() {
+    if (fqdn_) {
+        context_.query_->addOption(fqdn_);
+    }
+}
+
+void
 Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
 Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
     typedef OptionCollection Opts;
     typedef OptionCollection Opts;
     // Copy IA_NAs.
     // Copy IA_NAs.
@@ -293,10 +306,22 @@ Dhcp6Client::doSolicit() {
         }
         }
         context_.query_->addOption(ia);
         context_.query_->addOption(ia);
     }
     }
+    if (use_rapid_commit_) {
+        context_.query_->addOption(OptionPtr(new Option(Option::V6,
+                                                        D6O_RAPID_COMMIT)));
+    }
+    // Add Client FQDN if configured.
+    appendFQDN();
+
     sendMsg(context_.query_);
     sendMsg(context_.query_);
     context_.response_ = receiveOneMsg();
     context_.response_ = receiveOneMsg();
 
 
-    /// @todo Sanity check here
+    // If using Rapid Commit and the server has responded with Reply,
+    // let's apply received configuration.
+    if (use_rapid_commit_ && context_.response_ &&
+        context_.response_->getType() == DHCPV6_REPLY) {
+        applyRcvdConfiguration(context_.response_);
+    }
 }
 }
 
 
 void
 void
@@ -304,6 +329,10 @@ Dhcp6Client::doRequest() {
     Pkt6Ptr query = createMsg(DHCPV6_REQUEST);
     Pkt6Ptr query = createMsg(DHCPV6_REQUEST);
     query->addOption(context_.response_->getOption(D6O_SERVERID));
     query->addOption(context_.response_->getOption(D6O_SERVERID));
     copyIAs(context_.response_, query);
     copyIAs(context_.response_, query);
+
+    // Add Client FQDN if configured.
+    appendFQDN();
+
     context_.query_ = query;
     context_.query_ = query;
     sendMsg(context_.query_);
     sendMsg(context_.query_);
     context_.response_ = receiveOneMsg();
     context_.response_ = receiveOneMsg();
@@ -347,6 +376,10 @@ Dhcp6Client::doRenew() {
     Pkt6Ptr query = createMsg(DHCPV6_RENEW);
     Pkt6Ptr query = createMsg(DHCPV6_RENEW);
     query->addOption(context_.response_->getOption(D6O_SERVERID));
     query->addOption(context_.response_->getOption(D6O_SERVERID));
     copyIAsFromLeases(query);
     copyIAsFromLeases(query);
+
+    // Add Client FQDN if configured.
+    appendFQDN();
+
     context_.query_ = query;
     context_.query_ = query;
     sendMsg(context_.query_);
     sendMsg(context_.query_);
     context_.response_ = receiveOneMsg();
     context_.response_ = receiveOneMsg();
@@ -360,6 +393,10 @@ void
 Dhcp6Client::doRebind() {
 Dhcp6Client::doRebind() {
     Pkt6Ptr query = createMsg(DHCPV6_REBIND);
     Pkt6Ptr query = createMsg(DHCPV6_REBIND);
     copyIAsFromLeases(query);
     copyIAsFromLeases(query);
+
+    // Add Client FQDN if configured.
+    appendFQDN();
+
     context_.query_ = query;
     context_.query_ = query;
     sendMsg(context_.query_);
     sendMsg(context_.query_);
     context_.response_ = receiveOneMsg();
     context_.response_ = receiveOneMsg();
@@ -498,7 +535,7 @@ Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
                               msg->getBuffer().getLength()));
                               msg->getBuffer().getLength()));
     msg_copy->setRemoteAddr(link_local_);
     msg_copy->setRemoteAddr(link_local_);
     msg_copy->setLocalAddr(dest_addr_);
     msg_copy->setLocalAddr(dest_addr_);
-    msg_copy->setIface("eth0");
+    msg_copy->setIface(iface_name_);
     srv_->fakeReceive(msg_copy);
     srv_->fakeReceive(msg_copy);
     srv_->run();
     srv_->run();
 }
 }
@@ -511,6 +548,13 @@ Dhcp6Client::useHint(const uint32_t pref_lft, const uint32_t valid_lft,
                                            len, pref_lft, valid_lft));
                                            len, pref_lft, valid_lft));
 }
 }
 
 
+void
+Dhcp6Client::useFQDN(const uint8_t flags, const std::string& fqdn_name,
+                     Option6ClientFqdn::DomainNameType fqdn_type) {
+    fqdn_.reset(new Option6ClientFqdn(flags, fqdn_name, fqdn_type));
+}
+
+
 
 
 } // end of namespace isc::dhcp::test
 } // end of namespace isc::dhcp::test
 } // end of namespace isc::dhcp
 } // end of namespace isc::dhcp

+ 41 - 2
src/bin/dhcp6/tests/dhcp6_client.h

@@ -18,6 +18,7 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -383,6 +384,13 @@ public:
         dest_addr_ = dest_addr;
         dest_addr_ = dest_addr;
     }
     }
 
 
+    /// @brief Sets the interface to be used by the client.
+    ///
+    /// @param iface_name Interface name.
+    void setInterface(const std::string& iface_name) {
+        iface_name_ = iface_name;
+    }
+
     /// @brief Sets a prefix hint to be sent to a server.
     /// @brief Sets a prefix hint to be sent to a server.
     ///
     ///
     /// @param pref_lft Preferred lifetime.
     /// @param pref_lft Preferred lifetime.
@@ -427,10 +435,28 @@ public:
 
 
     /// @brief Controls whether the client should send a client-id or not
     /// @brief Controls whether the client should send a client-id or not
     /// @param send should the client-id be sent?
     /// @param send should the client-id be sent?
-    void useClientId(bool send) {
+    void useClientId(const bool send) {
         use_client_id_ = send;
         use_client_id_ = send;
     }
     }
 
 
+    /// @brief Specifies if the Rapid Commit option should be included in
+    /// the Solicit message.
+    ///
+    /// @param rapid_commit Boolean parameter controlling if the Rapid Commit
+    /// option must be included in the Solicit (if true), or not (if false).
+    void useRapidCommit(const bool rapid_commit) {
+        use_rapid_commit_ = rapid_commit;
+    }
+
+    /// @brief Creates an instance of the Client FQDN option to be included
+    /// in the client's message.
+    ///
+    /// @param flags Flags.
+    /// @param fqdn_name Name in the textual format.
+    /// @param fqdn_type Type of the name (fully qualified or partial).
+    void useFQDN(const uint8_t flags, const std::string& fqdn_name,
+                 Option6ClientFqdn::DomainNameType fqdn_type);
+
     /// @brief Lease configuration obtained by the client.
     /// @brief Lease configuration obtained by the client.
     Configuration config_;
     Configuration config_;
 
 
@@ -492,6 +518,12 @@ private:
     /// @param lease_info Structure holding new lease information.
     /// @param lease_info Structure holding new lease information.
     void applyLease(const LeaseInfo& lease_info);
     void applyLease(const LeaseInfo& lease_info);
 
 
+    /// @brief Includes CLient FQDN in the client's message.
+    ///
+    /// This method checks if @c fqdn_ is specified and includes it in
+    /// the client's message.
+    void appendFQDN();
+
     /// @brief Copy IA options from one message to another.
     /// @brief Copy IA options from one message to another.
     ///
     ///
     /// This method copies IA_NA and IA_PD options from one message to another.
     /// This method copies IA_NA and IA_PD options from one message to another.
@@ -550,7 +582,7 @@ private:
     /// @biref Current transaction id (altered on each send).
     /// @biref Current transaction id (altered on each send).
     uint32_t curr_transid_;
     uint32_t curr_transid_;
 
 
-    /// @brief Currently use destination address.
+    /// @brief Currently used destination address.
     asiolink::IOAddress dest_addr_;
     asiolink::IOAddress dest_addr_;
 
 
     /// @brief Currently used DUID.
     /// @brief Currently used DUID.
@@ -559,6 +591,9 @@ private:
     /// @brief Currently used link local address.
     /// @brief Currently used link local address.
     asiolink::IOAddress link_local_;
     asiolink::IOAddress link_local_;
 
 
+    /// @brief Currently used interface.
+    std::string iface_name_;
+
     /// @brief Pointer to the server that the client is communicating with.
     /// @brief Pointer to the server that the client is communicating with.
     boost::shared_ptr<isc::test::NakedDhcpv6Srv> srv_;
     boost::shared_ptr<isc::test::NakedDhcpv6Srv> srv_;
 
 
@@ -568,6 +603,7 @@ private:
 
 
     bool use_oro_;  ///< Conth
     bool use_oro_;  ///< Conth
     bool use_client_id_;
     bool use_client_id_;
+    bool use_rapid_commit_;
 
 
     /// @brief Pointer to the option holding a prefix hint.
     /// @brief Pointer to the option holding a prefix hint.
     Option6IAPrefixPtr prefix_hint_;
     Option6IAPrefixPtr prefix_hint_;
@@ -577,6 +613,9 @@ private:
     /// Content of this vector will be sent as ORO if use_oro_ is set
     /// Content of this vector will be sent as ORO if use_oro_ is set
     /// to true. See @ref sendORO for details.
     /// to true. See @ref sendORO for details.
     std::vector<uint16_t> oro_;
     std::vector<uint16_t> oro_;
+
+    /// @brief FQDN requested by the client.
+    Option6ClientFqdnPtr fqdn_;
 };
 };
 
 
 } // end of namespace isc::dhcp::test
 } // end of namespace isc::dhcp::test

+ 152 - 2
src/bin/dhcp6/tests/sarr_unittest.cc

@@ -14,8 +14,11 @@
 
 
 #include <config.h>
 #include <config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp6/tests/dhcp6_client.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/d2_client_mgr.h>
 
 
 using namespace isc;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
@@ -32,6 +35,16 @@ namespace {
 ///   - the delegated prefix was intentionally selected to not match the
 ///   - the delegated prefix was intentionally selected to not match the
 ///     subnet prefix, to test that the delegated prefix doesn't need to
 ///     subnet prefix, to test that the delegated prefix doesn't need to
 ///     match the subnet prefix
 ///     match the subnet prefix
+///
+/// - Configuration 1:
+///   - two subnets 2001:db8:1::/48 and 2001:db8:2::/48
+///   - first subnet assigned to interface eth0, another one assigned to eth1
+///   - one pool for subnet in a range of 2001:db8:X::1 - 2001:db8:X::10,
+///     where X is 1 or 2
+///   - enables Rapid Commit for the first subnet and disables for the second
+///     one
+///   - DNS updates enabled
+///
 const char* CONFIGS[] = {
 const char* CONFIGS[] = {
     // Configuration 0
     // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -50,11 +63,36 @@ const char* CONFIGS[] = {
         "    \"interface-id\": \"\","
         "    \"interface-id\": \"\","
         "    \"interface\": \"eth0\""
         "    \"interface\": \"eth0\""
         " } ],"
         " } ],"
-        "\"valid-lifetime\": 4000 }"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 1
+    "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth0\","
+        "    \"rapid-commit\": True"
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"interface\": \"eth1\","
+        "    \"rapid-commit\": False"
+        " } ],"
+        "\"valid-lifetime\": 4000,"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : True, "
+        "     \"qualifying-suffix\" : \"example.com\" }"
+    "}"
 };
 };
 
 
 /// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
 /// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
-/// Request-Reply.
+/// Request-Reply and 2-way exchange: Solicit-Reply.
 class SARRTest : public Dhcpv6SrvTest {
 class SARRTest : public Dhcpv6SrvTest {
 public:
 public:
     /// @brief Constructor.
     /// @brief Constructor.
@@ -65,6 +103,14 @@ public:
           iface_mgr_test_config_(true) {
           iface_mgr_test_config_(true) {
     }
     }
 
 
+    /// @brief Destructor.
+    ///
+    /// Clear the DHCP-DDNS configuration.
+    virtual ~SARRTest() {
+        D2ClientConfigPtr cfg(new D2ClientConfig());
+        CfgMgr::instance().setD2ClientConfig(cfg);
+    }
+
     /// @brief Interface Manager's fake configuration control.
     /// @brief Interface Manager's fake configuration control.
     IfaceMgrTestConfig iface_mgr_test_config_;
     IfaceMgrTestConfig iface_mgr_test_config_;
 };
 };
@@ -127,4 +173,108 @@ TEST_F(SARRTest, directClientPrefixHint) {
     ASSERT_TRUE(lease_server);
     ASSERT_TRUE(lease_server);
 }
 }
 
 
+// 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) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA
+    client.useNA();
+    configure(CONFIGS[1], *client.getServer());
+    ASSERT_NO_THROW(client.getServer()->startD2());
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(2, subnets->size());
+    // Perform 2-way exchange.
+    client.useRapidCommit(true);
+    // Include FQDN to trigger generation of name change requests.
+    ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S,
+                                   "client-name.example.org",
+                                   Option6ClientFqdn::FULL));
+
+    ASSERT_NO_THROW(client.doSolicit());
+    // Server should have committed a lease.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client = client.getLease(0);
+    // Make sure that the address belongs to the subnet configured.
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client.addr_, ClientClasses()));
+    // Make sure that the server responded with Reply.
+    ASSERT_TRUE(client.getContext().response_);
+    EXPECT_EQ(DHCPV6_REPLY, client.getContext().response_->getType());
+    // Rapid Commit option should be included.
+    EXPECT_TRUE(client.getContext().response_->getOption(D6O_RAPID_COMMIT));
+    // Check that the lease has been committed.
+    Lease6Ptr lease_server = checkLease(lease_client);
+    EXPECT_TRUE(lease_server);
+    // There should be one name change request generated.
+    EXPECT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+}
+
+// Check that the server responds with Advertise if the client hasn't
+// included the Rapid Commit option in the Solicit.
+TEST_F(SARRTest, rapidCommitNoOption) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA
+    client.useNA();
+    configure(CONFIGS[1], *client.getServer());
+    ASSERT_NO_THROW(client.getServer()->startD2());
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(2, subnets->size());
+    // Include FQDN to test that the server will not create name change
+    // requests when it sends Advertise (Rapid Commit disabled).
+    ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S,
+                                   "client-name.example.org",
+                                   Option6ClientFqdn::FULL));
+    ASSERT_NO_THROW(client.doSolicit());
+    // There should be no lease because the server should have responded
+    // with Advertise.
+    ASSERT_EQ(0, client.getLeaseNum());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    EXPECT_EQ(DHCPV6_ADVERTISE, client.getContext().response_->getType());
+    // Make sure that the Rapid Commit option is not included.
+    EXPECT_FALSE(client.getContext().response_->getOption(D6O_RAPID_COMMIT));
+    // There should be no name change request generated.
+    EXPECT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+}
+
+// Check that when the Rapid Commit support is disabled for the subnet
+// the server replies with an Advertise and ignores the Rapid Commit
+// option sent by the client.
+TEST_F(SARRTest, rapidCommitDisable) {
+    Dhcp6Client client;
+    // The subnet assigned to eth1 has Rapid Commit disabled.
+    client.setInterface("eth1");
+    // Configure client to request IA_NA
+    client.useNA();
+    configure(CONFIGS[1], *client.getServer());
+    ASSERT_NO_THROW(client.getServer()->startD2());
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(2, subnets->size());
+    // Send Rapid Commit option to the server.
+    client.useRapidCommit(true);
+    // Include FQDN to test that the server will not create name change
+    // requests when it sends Advertise (Rapid Commit disabled).
+    ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S,
+                                   "client-name.example.org",
+                                   Option6ClientFqdn::FULL));
+    ASSERT_NO_THROW(client.doSolicit());
+    // There should be no lease because the server should have responded
+    // with Advertise.
+    ASSERT_EQ(0, client.getLeaseNum());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    EXPECT_EQ(DHCPV6_ADVERTISE, client.getContext().response_->getType());
+    // Make sure that the Rapid Commit option is not included.
+    EXPECT_FALSE(client.getContext().response_->getOption(D6O_RAPID_COMMIT));
+    // There should be no name change request generated.
+    EXPECT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+}
+
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 2 - 1
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -1033,7 +1033,8 @@ SubnetConfigParser::SubnetConfigParser(const std::string&,
     : uint32_values_(new Uint32Storage()),
     : uint32_values_(new Uint32Storage()),
       string_values_(new StringStorage()),
       string_values_(new StringStorage()),
       boolean_values_(new BooleanStorage()),
       boolean_values_(new BooleanStorage()),
-      pools_(new PoolStorage()), global_context_(global_context),
+      pools_(new PoolStorage()),
+      global_context_(global_context),
       relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)),
       relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)),
       options_(new CfgOption()) {
       options_(new CfgOption()) {
     // The first parameter should always be "subnet", but we don't check
     // The first parameter should always be "subnet", but we don't check

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

@@ -329,8 +329,8 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& preferred_lifetime,
                  const Triplet<uint32_t>& preferred_lifetime,
                  const Triplet<uint32_t>& valid_lifetime,
                  const Triplet<uint32_t>& valid_lifetime,
                  const SubnetID id)
                  const SubnetID id)
-:Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("::")), id),
-     preferred_(preferred_lifetime) {
+    :Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("::")), id),
+     preferred_(preferred_lifetime), rapid_commit_(false) {
     if (!prefix.isV6()) {
     if (!prefix.isV6()) {
         isc_throw(BadValue, "Non IPv6 prefix " << prefix
         isc_throw(BadValue, "Non IPv6 prefix " << prefix
                   << " specified in subnet6");
                   << " specified in subnet6");

+ 24 - 1
src/lib/dhcpsrv/subnet.h

@@ -639,7 +639,23 @@ public:
         return interface_id_;
         return interface_id_;
     }
     }
 
 
-protected:
+    /// @brief Enables or disables Rapid Commit option support for the subnet.
+    ///
+    /// @param rapid_commit A boolean value indicating that the Rapid Commit
+    /// option support is enabled (if true), or disabled (if false).
+    void setRapidCommit(const bool rapid_commit) {
+        rapid_commit_ = rapid_commit;
+    };
+
+    /// @brief Returns boolean value indicating that the Rapid Commit option
+    /// is supported or unsupported for the subnet.
+    ///
+    /// @return true if the Rapid Commit option is supported, false otherwise.
+    bool getRapidCommit() const {
+        return (rapid_commit_);
+    }
+
+private:
 
 
     /// @brief Returns default address for pool selection
     /// @brief Returns default address for pool selection
     /// @return ANY IPv6 address
     /// @return ANY IPv6 address
@@ -660,6 +676,13 @@ protected:
 
 
     /// @brief a triplet with preferred lifetime (in seconds)
     /// @brief a triplet with preferred lifetime (in seconds)
     Triplet<uint32_t> preferred_;
     Triplet<uint32_t> preferred_;
+
+    /// @brief A flag indicating if Rapid Commit option is supported
+    /// for this subnet.
+    ///
+    /// It's default value is false, which indicates that the Rapid
+    /// Commit is disabled for the subnet.
+    bool rapid_commit_;
 };
 };
 
 
 /// @brief A pointer to a Subnet6 object
 /// @brief A pointer to a Subnet6 object

+ 18 - 0
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -1041,6 +1041,24 @@ TEST(Subnet6Test, interfaceId) {
 
 
 }
 }
 
 
+// This test checks that the Rapid Commit support can be enabled or
+// disabled for a subnet. It also checks that the Rapid Commit
+// support is disabled by default.
+TEST(Subnet6Test, rapidCommit) {
+    Subnet6 subnet(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4);
+
+    // By default, the RC should be disabled.
+    EXPECT_FALSE(subnet.getRapidCommit());
+
+    // Enable Rapid Commit.
+    subnet.setRapidCommit(true);
+    EXPECT_TRUE(subnet.getRapidCommit());
+
+    // Disable again.
+    subnet.setRapidCommit(false);
+    EXPECT_FALSE(subnet.getRapidCommit());
+}
+
 // Checks if last allocated address/prefix is stored/retrieved properly
 // Checks if last allocated address/prefix is stored/retrieved properly
 TEST(Subnet6Test, lastAllocated) {
 TEST(Subnet6Test, lastAllocated) {
     IOAddress ia("2001:db8:1::1");
     IOAddress ia("2001:db8:1::1");