Parcourir la source

[master] Merge branch 'trac3689'

Thomas Markwalder il y a 10 ans
Parent
commit
c13c824d99

+ 58 - 5
doc/guide/dhcp6-srv.xml

@@ -2071,11 +2071,64 @@ should include options from the isc option space:
 
     <section id="reservation6-hostname">
       <title>Reserving a hostname</title>
-      <!-- @todo: replace this with the actual text once #3689 is implemented -->
-      <para>Reserving a hostname is currently not supported. It is possible
-      to specify that information in the configuration file, but that data
-      is not used by the server engine yet.</para>
-    </section>
+      <para>When the reservation for the client includes the <command>hostname
+      </command>, the server will assign this hostname to the client and send
+      it back in the Client FQDN, if the client sent the FQDN option to the
+      the server. The reserved hostname always takes precedence over the hostname
+       supplied by the client (via the FQDN option) or the autogenerated
+      (from the IPv6 address) hostname.</para>
+
+      <para>The server qualifies the reserved hostname with the value
+      of the <command>qualifying-suffix</command> parameter. For example, the
+      following subnet configuration:
+<screen>
+"subnet6": [
+    {
+        "subnet": "2001:db8:1::/48",
+        "pools": [ { "pool": "2001:db8:1::/80" } ],
+        "reservations": [
+            {
+                "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+                "ip-addresses": [ "2001:db8:1::100" ]
+                "hostname": "alice-laptop"
+            }
+        ]
+    }
+],
+"dhcp-ddns": {
+    "enable-updates": true,
+    "qualifying-suffix": "example.isc.org."
+}
+</screen>
+      will result in assigning the "alice-laptop.example.isc.org." hostname to the
+      client using the DUID "01:02:03:04:05:0A:0B:0C:0D:0E". If the <command>qualifying-suffix
+      </command> is not specified, the default (empty) value will be used, and
+      in this case the value specified as a <command>hostname</command> will
+      be treated as fully qualified name.  Thus, by leaving the
+      <command>qualifying-suffix</command> empty it is possible to qualify
+      hostnames for the different clients with different domain names:
+<screen>
+"subnet6": [
+    {
+        "subnet": "2001:db8:1::/48",
+        "pools": [ { "pool": "2001:db8:1::/80" } ],
+        "reservations": [
+            {
+                "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+                "ip-addresses": [ "2001:db8:1::100" ]
+                "hostname": "mark-desktop.example.org."
+            }
+        ]
+    }
+],
+"dhcp-ddns": {
+    "enable-updates": true,
+}
+</screen>
+      will result in assigning the "mark-desktop.example.org." hostname to the
+      client using the DUID "01:02:03:04:05:0A:0B:0C:0D:0E".
+    </para>
+</section>
 
     <section id="reservation6-options">
       <title>Reserving specific options</title>

+ 191 - 146
src/bin/dhcp6/dhcp6_srv.cc

@@ -224,6 +224,31 @@ Dhcpv6Srv::testUnicast(const Pkt6Ptr& pkt) const {
     return (true);
 }
 
+DuidPtr
+Dhcpv6Srv::extractClientId(const Pkt6Ptr& pkt) {
+    // Let's find client's DUID. Client is supposed to include its client-id
+    // option almost all the time (the only exception is an anonymous inf-request,
+    // but that is mostly a theoretical case). Our allocation engine needs DUID
+    // and will refuse to allocate anything to anonymous clients.
+    OptionPtr opt_duid = pkt->getOption(D6O_CLIENTID);
+    if (opt_duid) {
+        return (DuidPtr(new DUID(opt_duid->getData())));
+    } else {
+        return (DuidPtr());
+    }
+}
+
+AllocEngine::ClientContext6
+Dhcpv6Srv::createContext(const Pkt6Ptr& pkt) {
+    AllocEngine::ClientContext6 ctx;
+    ctx.subnet_ = selectSubnet(pkt);
+    ctx.duid_ = extractClientId(pkt);
+    ctx.hwaddr_ = getMAC(pkt);
+    alloc_engine_->findReservation(ctx);
+
+    return (ctx);
+}
+
 bool Dhcpv6Srv::run() {
     while (!shutdown_) {
         // client's message and server's response
@@ -378,7 +403,8 @@ bool Dhcpv6Srv::run() {
         classifyPacket(query);
 
         try {
-                NameChangeRequestPtr ncr;
+            NameChangeRequestPtr ncr;
+
             switch (query->getType()) {
             case DHCPV6_SOLICIT:
                 rsp = processSolicit(query);
@@ -731,7 +757,8 @@ Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) {
 }
 
 void
-Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                  AllocEngine::ClientContext6& ctx) {
 
     // Client requests some options using ORO option. Try to
     // get this option from client's message.
@@ -749,42 +776,42 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     ConstCfgOptionPtr global_opts = CfgMgr::instance().getCurrentCfg()->
         getCfgOption();
 
-    // Get the configured subnet suitable for the incoming packet.
-    // It may be NULL (if server is misconfigured or the client was rejected
-    // using client classes).
-    Subnet6Ptr subnet = selectSubnet(question);
-
     // Get the list of options that client requested.
     const std::vector<uint16_t>& requested_opts = option_oro->getValues();
     BOOST_FOREACH(uint16_t opt, requested_opts) {
-        // If we found a subnet for this client, try subnet first.
-        if (subnet) {
-            OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", opt);
+        // If we found a subnet for this client, all options (including the
+        // global options) should be available through the options
+        // configuration for the subnet.
+        if (ctx.subnet_) {
+            OptionDescriptor desc = ctx.subnet_->getCfgOption()->get("dhcp6",
+                                                                     opt);
             if (desc.option_) {
                 // Attempt to assign an option from subnet first.
                 answer->addOption(desc.option_);
                 continue;
             }
-        }
 
-        // If subnet specific option is not there, try global.
-        OptionDescriptor desc = global_opts->get("dhcp6", opt);
-        if (desc.option_) {
-            answer->addOption(desc.option_);
+        // If there is no subnet selected (e.g. Information-request message
+        // case) we need to look at the global options.
+        } else {
+            OptionDescriptor desc = global_opts->get("dhcp6", opt);
+            if (desc.option_) {
+                answer->addOption(desc.option_);
+            }
         }
     }
 }
 
 void
-Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
-    // Get the configured subnet suitable for the incoming packet.
-    Subnet6Ptr subnet = selectSubnet(question);
+Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                        AllocEngine::ClientContext6& ctx) {
+
     // Leave if there is no subnet matching the incoming packet.
     // There is no need to log the error message here because
     // it will be logged in the assignLease() when it fails to
     // pick the suitable subnet. We don't want to duplicate
     // error messages in such case.
-    if (!subnet) {
+    if (!ctx.subnet_) {
         return;
     }
 
@@ -814,7 +841,7 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer
     bool added = false;
     const std::vector<uint16_t>& requested_opts = oro->getValues();
     BOOST_FOREACH(uint16_t opt, requested_opts) {
-        OptionDescriptor desc = subnet->getCfgOption()->get(vendor_id, opt);
+        OptionDescriptor desc = ctx.subnet_->getCfgOption()->get(vendor_id, opt);
         if (desc.option_) {
             vendor_rsp->addOption(desc.option_);
             added = true;
@@ -959,15 +986,15 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
 }
 
 void
-Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                        AllocEngine::ClientContext6& ctx) {
 
     // We need to allocate addresses for all IA_NA options in the client's
     // question (i.e. SOLICIT or REQUEST) message.
     // @todo add support for IA_TA
 
     // We need to select a subnet the client is connected in.
-    Subnet6Ptr subnet = selectSubnet(question);
-    if (!subnet) {
+    if (!ctx.subnet_) {
         // This particular client is out of luck today. We do not have
         // information about the subnet he is connected to. This likely means
         // misconfiguration of the server (or some relays). We will continue to
@@ -982,22 +1009,12 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
 
     } else {
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
-            .arg(subnet->toText());
+            .arg(ctx.subnet_->toText());
     }
 
-    // @todo: We should implement Option6Duid some day, but we can do without it
-    // just fine for now
-
-    // Let's find client's DUID. Client is supposed to include its client-id
-    // option almost all the time (the only exception is an anonymous inf-request,
-    // but that is mostly a theoretical case). Our allocation engine needs DUID
-    // and will refuse to allocate anything to anonymous clients.
-    DuidPtr duid;
-    OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
-    if (opt_duid) {
-        duid = DuidPtr(new DUID(opt_duid->getData()));
-    } else {
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
+    if (!ctx.duid_) {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING)
+            .arg(question->getIface());
         // Let's drop the message. This client is not sane.
         isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
     }
@@ -1011,7 +1028,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
          opt != question->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
-            OptionPtr answer_opt = assignIA_NA(subnet, duid, question, answer,
+            OptionPtr answer_opt = assignIA_NA(question, answer, ctx,
                                                boost::dynamic_pointer_cast<
                                                Option6IA>(opt->second));
             if (answer_opt) {
@@ -1020,7 +1037,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
             break;
         }
         case D6O_IA_PD: {
-            OptionPtr answer_opt = assignIA_PD(subnet, duid, question,
+            OptionPtr answer_opt = assignIA_PD(question, ctx,
                                                boost::dynamic_pointer_cast<
                                                Option6IA>(opt->second));
             if (answer_opt) {
@@ -1033,18 +1050,26 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     }
 }
 
+
 void
-Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
+Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
+                             AllocEngine::ClientContext6& ctx) {
     // Get Client FQDN Option from the client's message. If this option hasn't
     // been included, do nothing.
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
     if (!fqdn) {
+        // No FQDN so lease hostname comes from host reservation if one
+        if (ctx.host_) {
+            ctx.hostname_ = ctx.host_->getHostname();
+        }
+
         return;
     }
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
               DHCP6_DDNS_RECEIVE_FQDN).arg(fqdn->toText());
+
     // Create the DHCPv6 Client FQDN Option to be included in the server's
     // response to a client.
     Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
@@ -1054,9 +1079,23 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
     D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
     d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
 
-    // Adjust the domain name based on domain name value and type sent by the
-    // client and current configuration.
-    d2_mgr.adjustDomainName<Option6ClientFqdn>(*fqdn, *fqdn_resp);
+    // If there's a reservation and it has a hostname specified, use it!
+    if (ctx.host_ && !ctx.host_->getHostname().empty()) {
+        D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+        // Add the qualifying suffix.
+        // After #3765, this will only occur if the suffix is not empty.
+        fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(),
+                                                    true),
+                                                    Option6ClientFqdn::FULL);
+    } else {
+        // Adjust the domain name based on domain name value and type sent by
+        // the client and current configuration.
+        d2_mgr.adjustDomainName<Option6ClientFqdn>(*fqdn, *fqdn_resp);
+    }
+
+    // Once we have the FQDN setup to use it for the lease hostname.  This
+    // only gets replaced later if the FQDN is to be generated from the address.
+    ctx.hostname_ = fqdn_resp->getDomainName();
 
     // The FQDN has been processed successfully. Let's append it to the
     // response to be sent to a client. Note that the Client FQDN option is
@@ -1234,15 +1273,20 @@ Dhcpv6Srv::getMAC(const Pkt6Ptr& pkt) {
 }
 
 OptionPtr
-Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                       const Pkt6Ptr& query, const Pkt6Ptr& answer,
+Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                       AllocEngine::ClientContext6& orig_ctx,
                        boost::shared_ptr<Option6IA> ia) {
+
+    // convenience values
+    const Subnet6Ptr& subnet = orig_ctx.subnet_;
+    const DuidPtr& duid = orig_ctx.duid_;
+
     // If there is no subnet selected for handling this IA_NA, the only thing to do left 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 (compare to the same status code,
     // but different wording below)
     if (!subnet) {
-        // Creatasse empty IA_NA option with IAID matching the request.
+        // Create an empty IA_NA option with IAID matching the request.
         // Note that we don't use OptionDefinition class to create this option.
         // This is because we prefer using a constructor of Option6IA that
         // initializes IAID. Otherwise we would have to use setIAID() after
@@ -1280,24 +1324,14 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         fake_allocation = true;
     }
 
-    // At this point, we have to make make some decisions with respect to the
-    // FQDN option that we have generated as a result of receiving client's
-    // FQDN. In particular, we have to get to know if the DNS update will be
-    // performed or not. It is possible that option is NULL, which is valid
-    // condition if client didn't request DNS updates and server didn't force
-    // the update.
+    // Get DDNS update direction flags
     bool do_fwd = false;
     bool do_rev = false;
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
     if (fqdn) {
-        CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn,
-                                                                do_fwd, do_rev);
-    }
-    // Set hostname only in case any of the updates is being performed.
-    std::string hostname;
-    if (do_fwd || do_rev) {
-        hostname = fqdn->getDomainName();
+        CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, do_fwd,
+                                                                do_rev);
     }
 
     // Use allocation engine to pick a lease for this client. Allocation engine
@@ -1306,12 +1340,10 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // be inserted into the LeaseMgr as well.
     AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(),
                                     hint, Lease::TYPE_NA, do_fwd, do_rev,
-                                    hostname, fake_allocation);
+                                    orig_ctx.hostname_, fake_allocation);
     ctx.callout_handle_ = getCalloutHandle(query);
-
-    // Attempt to get MAC address using configured mechanisms.
-    // It's ok if there response is NULL. Hardware address is optional in Lease6.
-    ctx.hwaddr_ = getMAC(query);
+    ctx.hwaddr_ = orig_ctx.hwaddr_;
+    ctx.host_ = orig_ctx.host_;
 
     Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
 
@@ -1358,14 +1390,14 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                 // have to check that the FQDN settings we provided are the same
                 // that were set. If they aren't, we will have to remove existing
                 // DNS records and update the lease with the new settings.
-                conditionalNCRRemoval(old_lease, lease, hostname, do_fwd, do_rev);
+                conditionalNCRRemoval(old_lease, lease, ctx.hostname_, do_fwd, do_rev);
             }
 
             // We need to repeat that check for leases that used to be used, but
             // are no longer valid.
             if (!ctx.old_leases_.empty()) {
                 old_lease = *ctx.old_leases_.begin();
-                conditionalNCRRemoval(old_lease, lease, hostname, do_fwd, do_rev);
+                conditionalNCRRemoval(old_lease, lease, ctx.hostname_, do_fwd, do_rev);
             }
         }
     } else {
@@ -1401,8 +1433,12 @@ Dhcpv6Srv::conditionalNCRRemoval(Lease6Ptr& old_lease, Lease6Ptr& new_lease,
 }
 
 OptionPtr
-Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                       const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) {
+Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query,
+                       AllocEngine::ClientContext6& orig_ctx,
+                       boost::shared_ptr<Option6IA> ia) {
+
+    const Subnet6Ptr& subnet = orig_ctx.subnet_;
+    const DuidPtr& duid = orig_ctx.duid_;
 
     // Create IA_PD that we will put in the response.
     // Do not use OptionDefinition to create option's instance so
@@ -1451,10 +1487,8 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(), hint, Lease::TYPE_PD,
                                     false, false, string(), fake_allocation);
     ctx.callout_handle_ = getCalloutHandle(query);
-
-    // Attempt to get MAC address using any of available mechanisms.
-    // It's ok if there response is NULL. Hardware address is optional in Lease6
-    ctx.hwaddr_ = getMAC(query);
+    ctx.hwaddr_ = orig_ctx.hwaddr_;
+    ctx.host_ = orig_ctx.host_;
 
     Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
 
@@ -1503,9 +1537,12 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 OptionPtr
-Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                       const Pkt6Ptr& query, const Pkt6Ptr& answer,
+Dhcpv6Srv::extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                       AllocEngine::ClientContext6& orig_ctx,
                        boost::shared_ptr<Option6IA> ia) {
+    // convenience values
+    const Subnet6Ptr& subnet = orig_ctx.subnet_;
+    const DuidPtr& duid = orig_ctx.duid_;
 
     // Create empty IA_NA option with IAID matching the request.
     Option6IAPtr ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -1535,38 +1572,27 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     ia_rsp->setT1(subnet->getT1());
     ia_rsp->setT2(subnet->getT2());
 
-    // At this point, we have to make make some decisions with respect to
-    // the FQDN option that we have generated as a result of receiving
-    // client's FQDN. In particular, we have to get to know if the DNS
-    // update will be performed or not. It is possible that option is NULL,
-    // which is valid condition if client didn't request DNS updates and
-    // server didn't force the update.
+    // Get DDNS udpate directions
     bool do_fwd = false;
     bool do_rev = false;
-    std::string hostname;
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
     if (fqdn) {
-        CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, do_fwd, do_rev);
-
-        if (do_fwd || do_rev) {
-            hostname = fqdn->getDomainName();
-        }
+        CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn,
+                                                                do_fwd, do_rev);
     }
 
     // Create client context for this renewal
     static const IOAddress none("::");
     AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(),
                                     none, Lease::TYPE_NA, do_fwd, do_rev,
-                                    hostname, false);
+                                    orig_ctx.hostname_, false);
 
     ctx.callout_handle_ = getCalloutHandle(query);
     ctx.query_ = query;
     ctx.ia_rsp_ = ia_rsp;
-
-    // Attempt to get MAC address using configured mechanisms.
-    // It's ok if there response is NULL. Hardware address is optional in Lease6.
-    ctx.hwaddr_ = getMAC(query);
+    ctx.hwaddr_ = orig_ctx.hwaddr_;
+    ctx.host_ = orig_ctx.host_;
 
     // Extract the addresses that the client is trying to obtain.
     OptionCollection addrs = ia->getOptions();
@@ -1634,12 +1660,12 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 
         // If the new FQDN settings have changed for the lease, we need to
         // delete any existing FQDN records for this lease.
-        if (((*l)->hostname_ != hostname) || ((*l)->fqdn_fwd_ != do_fwd) ||
+        if (((*l)->hostname_ != ctx.hostname_) || ((*l)->fqdn_fwd_ != do_fwd) ||
             ((*l)->fqdn_rev_ != do_rev)) {
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                       DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
                 .arg((*l)->toText())
-                .arg(hostname)
+                .arg(ctx.hostname_)
                 .arg(do_rev ? "true" : "false")
                 .arg(do_fwd ? "true" : "false");
 
@@ -1684,8 +1710,12 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 OptionPtr
-Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                       const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) {
+Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
+                       AllocEngine::ClientContext6& orig_ctx,
+                       boost::shared_ptr<Option6IA> ia) {
+
+    const Subnet6Ptr& subnet = orig_ctx.subnet_;
+    const DuidPtr& duid = orig_ctx.duid_;
 
     // Let's create a IA_PD response and fill it in later
     Option6IAPtr ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
@@ -1738,10 +1768,8 @@ Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     ctx.callout_handle_ = getCalloutHandle(query);
     ctx.query_ = query;
     ctx.ia_rsp_ = ia_rsp;
-
-    // Attempt to get MAC address using configured mechanisms.
-    // It's ok if there response is NULL. Hardware address is optional in Lease6.
-    ctx.hwaddr_ = getMAC(query);
+    ctx.hwaddr_ = orig_ctx.hwaddr_;
+    ctx.host_ = orig_ctx.host_;
 
     // Extract prefixes that the client is trying to renew.
     OptionCollection addrs = ia->getOptions();
@@ -1854,17 +1882,15 @@ Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 void
-Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply) {
+Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply,
+                       AllocEngine::ClientContext6& ctx) {
 
     // We will try to extend lease lifetime for all IA options in the client's
     // Renew or Rebind message.
     /// @todo add support for IA_TA
 
-    // We need to select a subnet the client is connected in. This is needed
-    // to get the client's bindings from the lease database. The subnet id
-    // is one of the lease search parameters.
-    Subnet6Ptr subnet = selectSubnet(query);
-    if (!subnet) {
+    // We need to select a subnet the client is connected in.
+    if (! ctx.subnet_) {
         // This particular client is out of luck today. We do not have
         // information about the subnet he is connected to. This likely means
         // misconfiguration of the server (or some relays). We will continue to
@@ -1879,30 +1905,25 @@ Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply) {
     } else {
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                   DHCP6_EXTEND_LEASE_SUBNET_SELECTED)
-            .arg(subnet->toText());
+            .arg(ctx.subnet_->toText());
     }
 
-    // Let's find client's DUID. Client is supposed to include its client-id
-    // option almost all the time (the only exception is an anonymous
-    // inf-request, but that is mostly a theoretical case). Our allocation
-    // engine needs DUID and will refuse to allocate anything to anonymous
-    // clients.
-    /// @todo Consider removing this check from here and rely on what we have
-    /// checked on the earlier processing stage.
-    OptionPtr opt_duid = query->getOption(D6O_CLIENTID);
-    if (!opt_duid) {
+    /// @todo - assignLeases() drops the packet as RFC violation, shouldn't
+    /// we do that here? Shouldn't sanityCheck defend against this? Maybe
+    /// this should treated as a code error instead. If we're this far with
+    /// no duid that seems wrong.
+    if (!ctx.duid_) {
         // This should not happen. We have checked this before.
         reply->addOption(createStatusCode(STATUS_UnspecFail,
                          "You did not include mandatory client-id"));
         return;
     }
-    DuidPtr duid(new DUID(opt_duid->getData()));
 
     for (OptionCollection::iterator opt = query->options_.begin();
          opt != query->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
-            OptionPtr answer_opt = extendIA_NA(subnet, duid, query, reply,
+            OptionPtr answer_opt = extendIA_NA(query, reply, ctx,
                                                boost::dynamic_pointer_cast<
                                                    Option6IA>(opt->second));
             if (answer_opt) {
@@ -1912,7 +1933,7 @@ Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply) {
         }
 
         case D6O_IA_PD: {
-            OptionPtr answer_opt = extendIA_PD(subnet, duid, query,
+            OptionPtr answer_opt = extendIA_PD(query, ctx,
                                                boost::dynamic_pointer_cast<
                                                    Option6IA>(opt->second));
             if (answer_opt) {
@@ -1928,7 +1949,8 @@ Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply) {
 }
 
 void
-Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
+Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
+                         AllocEngine::ClientContext6& ctx) {
 
     // We need to release addresses for all IA_NA options in the client's
     // RELEASE message.
@@ -1944,8 +1966,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
     // option almost all the time (the only exception is an anonymous inf-request,
     // but that is mostly a theoretical case). Our allocation engine needs DUID
     // and will refuse to allocate anything to anonymous clients.
-    OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
-    if (!opt_duid) {
+    if (!ctx.duid_) {
         // This should not happen. We have checked this before.
         // see sanityCheck() called from processRelease()
         LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
@@ -1955,7 +1976,6 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
                          "You did not include mandatory client-id"));
         return;
     }
-    DuidPtr duid(new DUID(opt_duid->getData()));
 
     // Let's set the status to be success by default. We can override it with
     // error status if needed. The important thing to understand here is that
@@ -1967,7 +1987,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
          opt != release->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
-            OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
+            OptionPtr answer_opt = releaseIA_NA(ctx.duid_, release, general_status,
                                    boost::dynamic_pointer_cast<Option6IA>(opt->second));
             if (answer_opt) {
                 reply->addOption(answer_opt);
@@ -1975,7 +1995,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
             break;
         }
         case D6O_IA_PD: {
-            OptionPtr answer_opt = releaseIA_PD(duid, release, general_status,
+            OptionPtr answer_opt = releaseIA_PD(ctx.duid_, release, general_status,
                                    boost::dynamic_pointer_cast<Option6IA>(opt->second));
             if (answer_opt) {
                 reply->addOption(answer_opt);
@@ -2289,20 +2309,25 @@ Dhcpv6Srv::releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
     return (ia_rsp);
 }
 
+
+
 Pkt6Ptr
 Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
 
     sanityCheck(solicit, MANDATORY, FORBIDDEN);
 
+    // Let's create a simplified client context here.
+    AllocEngine::ClientContext6 ctx = createContext(solicit);
+
     Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
 
     copyClientOptions(solicit, advertise);
     appendDefaultOptions(solicit, advertise);
-    appendRequestedOptions(solicit, advertise);
-    appendRequestedVendorOptions(solicit, advertise);
+    appendRequestedOptions(solicit, advertise, ctx);
+    appendRequestedVendorOptions(solicit, advertise, ctx);
 
-    processClientFqdn(solicit, advertise);
-    assignLeases(solicit, advertise);
+    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.
@@ -2317,15 +2342,18 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
 
     sanityCheck(request, MANDATORY, MANDATORY);
 
+    // Let's create a simplified client context here.
+    AllocEngine::ClientContext6 ctx = createContext(request);
+
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
 
     copyClientOptions(request, reply);
     appendDefaultOptions(request, reply);
-    appendRequestedOptions(request, reply);
-    appendRequestedVendorOptions(request, reply);
+    appendRequestedOptions(request, reply, ctx);
+    appendRequestedVendorOptions(request, reply, ctx);
 
-    processClientFqdn(request, reply);
-    assignLeases(request, reply);
+    processClientFqdn(request, reply, ctx);
+    assignLeases(request, reply, ctx);
     generateFqdn(reply);
     createNameChangeRequests(reply);
 
@@ -2337,14 +2365,17 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
 
     sanityCheck(renew, MANDATORY, MANDATORY);
 
+    // Let's create a simplified client context here.
+    AllocEngine::ClientContext6 ctx = createContext(renew);
+
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
 
     copyClientOptions(renew, reply);
     appendDefaultOptions(renew, reply);
-    appendRequestedOptions(renew, reply);
+    appendRequestedOptions(renew, reply, ctx);
 
-    processClientFqdn(renew, reply);
-    extendLeases(renew, reply);
+    processClientFqdn(renew, reply, ctx);
+    extendLeases(renew, reply, ctx);
     generateFqdn(reply);
     createNameChangeRequests(reply);
 
@@ -2354,14 +2385,17 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
 Pkt6Ptr
 Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
 
+    // Let's create a simplified client context here.
+    AllocEngine::ClientContext6 ctx = createContext(rebind);
+
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
 
     copyClientOptions(rebind, reply);
     appendDefaultOptions(rebind, reply);
-    appendRequestedOptions(rebind, reply);
+    appendRequestedOptions(rebind, reply, ctx);
 
-    processClientFqdn(rebind, reply);
-    extendLeases(rebind, reply);
+    processClientFqdn(rebind, reply, ctx);
+    extendLeases(rebind, reply, ctx);
     generateFqdn(reply);
     createNameChangeRequests(rebind);
 
@@ -2370,6 +2404,10 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
 
 Pkt6Ptr
 Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
+
+    // Let's create a simplified client context here.
+    AllocEngine::ClientContext6 ctx = createContext(confirm);
+
     // Get IA_NAs from the Confirm. If there are none, the message is
     // invalid and must be discarded. There is nothing more to do.
     OptionCollection ias = confirm->getOptions(D6O_IA_NA);
@@ -2386,9 +2424,10 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
     // are verified it means that the client has sent no IA_NA options
     // or no IAAddr options and that client's message has to be discarded.
     bool verified = false;
-    // Check if subnet can be selected for the message. If no subnet
+    // Check if subnet was selected for the message. If no subnet
     // has been selected, the client is not on link.
-    SubnetPtr subnet = selectSubnet(confirm);
+    SubnetPtr subnet = ctx.subnet_;
+
     // Regardless if the subnet has been selected or not, we will iterate
     // over the IA_NA options to check if they hold any addresses. If there
     // are no, the Confirm is discarded.
@@ -2452,12 +2491,15 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
 
     sanityCheck(release, MANDATORY, MANDATORY);
 
+    // Let's create a simplified client context here.
+    AllocEngine::ClientContext6 ctx = createContext(release);
+
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
 
     copyClientOptions(release, reply);
     appendDefaultOptions(release, reply);
 
-    releaseLeases(release, reply);
+    releaseLeases(release, reply, ctx);
 
     // @todo If client sent a release and we should remove outstanding
     // DNS records.
@@ -2473,21 +2515,24 @@ Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) {
 }
 
 Pkt6Ptr
-Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
+Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
+
+    // Let's create a simplified client context here.
+    AllocEngine::ClientContext6 ctx = createContext(inf_request);
 
     // Create a Reply packet, with the same trans-id as the client's.
-    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, infRequest->getTransid()));
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid()));
 
     // Copy client options (client-id, also relay information if present)
-    copyClientOptions(infRequest, reply);
+    copyClientOptions(inf_request, reply);
 
     // Append default options, i.e. options that the server is supposed
     // to put in all messages it sends (server-id for now, but possibly other
     // options once we start supporting authentication)
-    appendDefaultOptions(infRequest, reply);
+    appendDefaultOptions(inf_request, reply);
 
     // Try to assign options that were requested by the client.
-    appendRequestedOptions(infRequest, reply);
+    appendRequestedOptions(inf_request, reply, ctx);
 
     return (reply);
 }

+ 107 - 47
src/bin/dhcp6/dhcp6_srv.h

@@ -166,25 +166,25 @@ protected:
     void sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                      RequirementLevel serverid);
 
-    /// @brief Processes incoming SOLICIT and returns response.
+    /// @brief Processes incoming Solicit and returns response.
     ///
-    /// Processes received SOLICIT message and verifies that its sender
+    /// Processes received Solicit message and verifies that its sender
     /// should be served. In particular IA, TA and PD options are populated
     /// with to-be assigned addresses, temporary addresses and delegated
     /// prefixes, respectively. In the usual 4 message exchange, server is
-    /// expected to respond with ADVERTISE message. However, if client
-    /// requests rapid-commit and server supports it, REPLY will be sent
-    /// instead of ADVERTISE and requested leases will be assigned
+    /// expected to respond with Advertise message. However, if client
+    /// requests rapid-commit and server supports it, Reply will be sent
+    /// instead of Advertise and requested leases will be assigned
     /// immediately.
     ///
-    /// @param solicit SOLICIT message received from client
+    /// @param solicit Solicit message received from client
     ///
-    /// @return ADVERTISE, REPLY message or NULL
+    /// @return Advertise, Reply message or NULL.
     Pkt6Ptr processSolicit(const Pkt6Ptr& solicit);
 
-    /// @brief Processes incoming REQUEST and returns REPLY response.
+    /// @brief Processes incoming Request and returns Reply response.
     ///
-    /// Processes incoming REQUEST message and verifies that its sender
+    /// Processes incoming Request message and verifies that its sender
     /// should be served. In particular IA, TA and PD options are populated
     /// with assigned addresses, temporary addresses and delegated
     /// prefixes, respectively. Uses LeaseMgr to allocate or update existing
@@ -195,14 +195,22 @@ protected:
     /// @return REPLY message or NULL
     Pkt6Ptr processRequest(const Pkt6Ptr& request);
 
-    /// @brief Stub function that will handle incoming RENEW messages.
+    /// @brief Processes incoming Renew message.
     ///
-    /// @param renew message received from client
+    /// @param renew message received from the client
+    /// @return Reply message to be sent to the client.
     Pkt6Ptr processRenew(const Pkt6Ptr& renew);
 
-    /// @brief Stub function that will handle incoming REBIND messages.
+    /// @brief Processes incoming Rebind message.
     ///
-    /// @param rebind message received from client
+    /// @todo There are cases when the Rebind message should be  discarded
+    /// by the DHCP server. One of those is when the server doesn't have a
+    /// record of the client and it is unable to determine whether the
+    /// client is on the appropriate link or not. We don't seem to do it
+    /// now.
+    ///
+    /// @param rebind message received from the client.
+    /// @return Reply message to be sent to the client.
     Pkt6Ptr processRebind(const Pkt6Ptr& rebind);
 
     /// @brief Processes incoming Confirm message and returns Reply.
@@ -226,24 +234,26 @@ protected:
     ///
     /// @param confirm Confirm message sent by a client.
     ///
-    /// @return Reply message from the server al NULL pointer if Confirm
+    /// @return Reply message from the server or NULL pointer if Confirm
     /// message should be discarded by the server.
     Pkt6Ptr processConfirm(const Pkt6Ptr& confirm);
 
-    /// @brief Stub function that will handle incoming RELEASE messages.
+    /// @brief Process incoming Release message.
     ///
     /// @param release message received from client
+    /// @return Reply message to be sent to the client.
     Pkt6Ptr processRelease(const Pkt6Ptr& release);
 
-    /// @brief Stub function that will handle incoming DECLINE messages.
+    /// @brief Stub function that will handle incoming Decline.
     ///
     /// @param decline message received from client
     Pkt6Ptr processDecline(const Pkt6Ptr& decline);
 
-    /// @brief Stub function that will handle incoming INF-REQUEST messages.
+    /// @brief Processes incoming Information-request message.
     ///
-    /// @param infRequest message received from client
-    Pkt6Ptr processInfRequest(const Pkt6Ptr& infRequest);
+    /// @param inf_request message received from client
+    /// @return Reply message to be sent to the client.
+    Pkt6Ptr processInfRequest(const Pkt6Ptr& inf_request);
 
     /// @brief Creates status-code option.
     ///
@@ -266,19 +276,17 @@ protected:
     /// status code option with non-zero status, denoting cause of the
     /// allocation failure.
     ///
-    /// @param subnet subnet the client is connected to
-    /// @param duid client's duid
     /// @param query client's message (typically SOLICIT or REQUEST)
     /// @param answer server's response to the client's message. This
     /// message should contain Client FQDN option being sent by the server
     /// to the client (if the client sent this option to the server).
+    /// @param orig_ctx client context (contains subnet, duid and other parameters)
     /// @param ia pointer to client's IA_NA option (client's request)
     ///
     /// @return IA_NA option (server's response)
-    OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
-                          const isc::dhcp::DuidPtr& duid,
-                          const isc::dhcp::Pkt6Ptr& query,
+    OptionPtr assignIA_NA(const isc::dhcp::Pkt6Ptr& query,
                           const isc::dhcp::Pkt6Ptr& answer,
+                          AllocEngine::ClientContext6& orig_ctx,
                           Option6IAPtr ia);
 
     /// @brief Processes IA_PD option (and assigns prefixes if necessary).
@@ -289,13 +297,12 @@ protected:
     /// status code option with non-zero status denoting the cause of the
     /// allocation failure.
     ///
-    /// @param subnet subnet the client is connected to
-    /// @param duid client's duid
     /// @param query client's message (typically SOLICIT or REQUEST)
+    /// @param orig_ctx client context (contains subnet, duid and other parameters)
     /// @param ia pointer to client's IA_PD option (client's request)
     /// @return IA_PD option (server's response)
-    OptionPtr assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                          const Pkt6Ptr& query,
+    OptionPtr assignIA_PD(const Pkt6Ptr& query,
+                          AllocEngine::ClientContext6& orig_ctx,
                           boost::shared_ptr<Option6IA> ia);
 
     /// @brief Extends lifetime of the specific IA_NA option.
@@ -323,11 +330,12 @@ protected:
     /// @param answer server's response to the client's message. This
     /// message should contain Client FQDN option being sent by the server
     /// to the client (if the client sent this option to the server).
+    /// @param orig_ctx client context (contains subnet, duid and other parameters)
     /// @param ia IA_NA option which carries adress for which lease lifetime
     /// will be extended.
     /// @return IA_NA option (server's response)
-    OptionPtr extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                          const Pkt6Ptr& query, const Pkt6Ptr& answer,
+    OptionPtr extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                          AllocEngine::ClientContext6& orig_ctx,
                           Option6IAPtr ia);
 
     /// @brief Extends lifetime of the prefix.
@@ -341,16 +349,16 @@ protected:
     /// is thrown when there is no binding and the Rebind message is processed
     /// (see RFC3633, section 12.2. for details).
     ///
-    /// @param subnet subnet the sender belongs to
-    /// @param duid client's duid
     /// @param query client's message
+    /// @param orig_ctx client context (contains subnet, duid and other parameters)
     /// @param ia IA_PD option that is being renewed
     /// @return IA_PD option (server's response)
     /// @throw DHCPv6DiscardMessageError when the message being processed should
     /// be discarded by the server, i.e. there is no binding for the client doing
     /// Rebind.
-    OptionPtr extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                          const Pkt6Ptr& query, Option6IAPtr ia);
+    OptionPtr extendIA_PD(const Pkt6Ptr& query,
+                          AllocEngine::ClientContext6& orig_ctx,
+                          Option6IAPtr ia);
 
     /// @brief Releases specific IA_NA option
     ///
@@ -416,7 +424,9 @@ protected:
     ///
     /// @param question client's message
     /// @param answer server's message (options will be added here)
-    void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
+    /// @param ctx client context (contains subnet, duid and other parameters)
+    void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                AllocEngine::ClientContext6& ctx);
 
     /// @brief Appends requested vendor options to server's answer.
     ///
@@ -425,19 +435,22 @@ protected:
     ///
     /// @param question client's message
     /// @param answer server's message (vendor options will be added here)
-    void appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
+    /// @param ctx client context (contains subnet, duid and other parameters)
+    void appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                AllocEngine::ClientContext6& ctx);
 
     /// @brief Assigns leases.
     ///
-    /// It supports addresses (IA_NA) only. It does NOT support temporary
-    /// addresses (IA_TA) nor prefixes (IA_PD).
-    /// @todo: Extend this method once TA and PD becomes supported
+    /// It supports non-temporary addresses (IA_NA) and prefixes (IA_PD). It
+    /// does NOT support temporary addresses (IA_TA).
     ///
-    /// @param question client's message (with requested IA_NA)
-    /// @param answer server's message (IA_NA options will be added here).
-    /// This message should contain Client FQDN option being sent by the server
-    /// to the client (if the client sent this option to the server).
-    void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
+    /// @param question client's message (with requested IA options)
+    /// @param answer server's message (IA options will be added here).
+    ///   This message should contain Client FQDN option being sent by the server
+    ///   to the client (if the client sent this option to the server).
+    /// @param ctx client context (contains subnet, duid and other parameters)
+    void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                      AllocEngine::ClientContext6& ctx);
 
     /// @brief Processes Client FQDN Option.
     ///
@@ -453,6 +466,28 @@ protected:
     /// domain-name, i.e. if the provided domain-name is partial it should
     /// generate the fully qualified domain-name.
     ///
+    /// This function takes into account the host reservation if one is matched
+    /// to this client when forming the FQDN to be used with DNS as well as the
+    /// lease name to be stored with the lease. In the following the term
+    /// "reserved hostname" means a host reservation which includes a
+    /// non-blank hostname.
+    ///
+    /// - If there is no Client FQDN and no reserved hostname then there
+    /// will no be DNS updates and the lease hostname will be empty.
+    ///
+    /// - If there is no Client FQDN but there is reserverd hostname then
+    /// there will be no DNS updates and the lease hostname will be equal
+    /// to reserved hostname.
+    ///
+    /// - If there is a Client FQDN and a reserved hostname, then both the
+    /// FQDN and lease hostname will be equal to reserved hostname with
+    /// the qualifying suffix appended.
+    ///
+    /// - If there is a Client FQDN but no reserverd hostname then both the
+    /// FQDN and lease hostname will be equal to the name provided in the
+    /// client FQDN adjusted according the the DhcpDdns configuration
+    /// parameters (e.g.replace-client-name, qualifying suffix...).
+    ///
     /// All the logic required to form appropriate answer to the client is
     /// held in this function.
     ///
@@ -460,7 +495,9 @@ protected:
     /// @param answer Server's response to a client. If server generated
     /// Client FQDN option for the client, this option is stored in this
     /// object.
-    void processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer);
+    /// @param ctx client context (includes subnet, client-id, hw-addr etc.)
+    void processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
+                           AllocEngine::ClientContext6& ctx);
 
     /// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects
     /// based on the DHCPv6 Client FQDN %Option.
@@ -507,7 +544,9 @@ protected:
     ///
     /// @param query client's Renew or Rebind message
     /// @param reply server's response
-    void extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply);
+    /// @param ctx client context (contains subnet, duid and other parameters)
+    void extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply,
+                      AllocEngine::ClientContext6& ctx);
 
     /// @brief Attempts to release received addresses
     ///
@@ -518,7 +557,9 @@ protected:
     /// to REPLY packet, just its IA_NA containers.
     /// @param release client's message asking to release
     /// @param reply server's response
-    void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply);
+    /// @param ctx client context (includes subnet, client-id, hw-addr etc.)
+    void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
+                       AllocEngine::ClientContext6& ctx);
 
     /// @brief Sets server-identifier.
     ///
@@ -616,6 +657,19 @@ protected:
     /// - there is no such option provided by the server)
     void processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp);
 
+    /// @brief Creates client context for specified packet
+    ///
+    /// Instantiates the ClientContext6 and then:
+    /// - Performs the subnet selection and stores the result in context
+    /// - Extracts the duid from the packet and saves it to the context
+    /// - Extracts the hardware address from the packet and saves it to
+    /// the context
+    /// - Performs host reservation lookup and stores the result in the
+    /// context
+    ///
+    /// @return client context
+    AllocEngine::ClientContext6 createContext(const Pkt6Ptr& pkt);
+
     /// @brief this is a prefix added to the contend of vendor-class option
     ///
     /// If incoming packet has a vendor class option, its content is
@@ -683,6 +737,12 @@ private:
                                const std::string& hostname,
                                bool do_fwd, bool do_rev);
 
+    /// @brief Utility method that extracts DUID from client-id option
+    ///
+    /// @param pkt the message that contains client-id option
+    /// @return extracted DUID (or NULL if client-id is missing)
+    DuidPtr extractClientId(const Pkt6Ptr& pkt);
+
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using
     /// It must be a pointer, because we will support changing engines

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -75,6 +75,7 @@ dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += fqdn_unittest.cc
 dhcp6_unittests_SOURCES += hooks_unittest.cc
+dhcp6_unittests_SOURCES += host_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h
 dhcp6_unittests_SOURCES += marker_file.cc

+ 20 - 0
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -341,6 +341,20 @@ Dhcp6Client::doInfRequest() {
 }
 
 void
+Dhcp6Client::doRenew() {
+    Pkt6Ptr query = createMsg(DHCPV6_RENEW);
+    query->addOption(context_.response_->getOption(D6O_SERVERID));
+    copyIAsFromLeases(query);
+    context_.query_ = query;
+    sendMsg(context_.query_);
+    context_.response_ = receiveOneMsg();
+    // Apply configuration only if the server has responded.
+    if (context_.response_) {
+        applyRcvdConfiguration(context_.response_);
+    }
+}
+
+void
 Dhcp6Client::doRebind() {
     Pkt6Ptr query = createMsg(DHCPV6_REBIND);
     copyIAsFromLeases(query);
@@ -428,6 +442,12 @@ Dhcp6Client::getLeasesByIAID(const uint32_t iaid) const {
 }
 
 void
+Dhcp6Client::setDUID(const std::string& str) {
+    DUID d = DUID::fromText(str);
+    duid_.reset(new DUID(d));
+}
+
+void
 Dhcp6Client::modifyDUID() {
     if (!duid_) {
         duid_ = generateDUID(DUID::DUID_LLT);

+ 27 - 0
src/bin/dhcp6/tests/dhcp6_client.h

@@ -204,6 +204,21 @@ public:
     /// @todo Perform sanity checks on returned messages.
     void doSolicit();
 
+    /// @brief Sends a Renew to the server and receives the Reply.
+    ///
+    /// This function simulates sending the Renew message to the server and
+    /// receiving server's response (if any). The client uses existing leases
+    /// (either address or prefixes) and places them in the Renew message.
+    /// If the server responds to the Renew (and extends the lease lifetimes)
+    /// the current lease configuration is updated.
+    ///
+    /// @throw This function doesn't throw exceptions on its own, but it calls
+    /// functions that are not exception safe, so it may throw exceptions if
+    /// error occurs.
+    ///
+    /// @todo Perform sanity checks on returned messages.
+    void doRenew();
+
     /// @brief Sends a Rebind to the server and receives the Reply.
     ///
     /// This function simulates sending the Rebind message to the server and
@@ -324,6 +339,18 @@ public:
         return (srv_);
     }
 
+    /// @brief Sets the client's DUID from a string value
+    ///
+    /// Replaces the client's DUID with one constructed from the given
+    /// string.  The string is expected to contain hexadecimal digits with or
+    /// without ":" separators.
+    ///
+    /// @param str The string of digits from which to create the DUID
+    ///
+    /// The DUID modification affects the value returned by the
+    /// @c Dhcp6Client::getClientId
+    void setDUID(const std::string& duid_str);
+
     /// @brief Modifies the client's DUID (adds one to it).
     ///
     /// The DUID should be modified to test negative scenarios when the client

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

@@ -2354,4 +2354,5 @@ TEST_F(Dhcpv6SrvTest, rsooOverride) {
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 
+
 }   // end of anonymous namespace

+ 1 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -113,6 +113,7 @@ public:
     using Dhcpv6Srv::shutdown_;
     using Dhcpv6Srv::name_change_reqs_;
     using Dhcpv6Srv::VENDOR_CLASS_PREFIX;
+    using Dhcpv6Srv::createContext;
 
     /// @brief packets we pretend to receive
     ///

+ 189 - 5
src/bin/dhcp6/tests/fqdn_unittest.cc

@@ -24,6 +24,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option_int_array.h>
 #include <dhcpsrv/lease.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <boost/pointer_cast.hpp>
@@ -329,7 +330,8 @@ public:
         // Create three IAs, each having different address.
         addIA(1234, IOAddress("2001:db8:1::1"), answer);
 
-        ASSERT_NO_THROW(srv_->processClientFqdn(question, answer));
+        AllocEngine::ClientContext6 ctx;
+        ASSERT_NO_THROW(srv_->processClientFqdn(question, answer, ctx));
         Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
             Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
         ASSERT_TRUE(answ_fqdn);
@@ -393,6 +395,7 @@ public:
                             const std::string& exp_hostname,
                             const uint8_t client_flags =
                                 Option6ClientFqdn::FLAG_S,
+                            const IOAddress expected_address = IOAddress("2001:db8:1:1::dead:beef"),
                             const bool include_oro = true) {
         // Create a message of a specified type, add server id and
         // FQDN option.
@@ -440,7 +443,7 @@ public:
         ASSERT_TRUE(addr);
 
         // Check that we have got the address we requested.
-        checkIAAddr(addr, IOAddress("2001:db8:1:1::dead:beef"),
+        checkIAAddr(addr, expected_address,
                     Lease::TYPE_NA);
 
         if (msg_type != DHCPV6_SOLICIT) {
@@ -482,12 +485,15 @@ public:
     /// NameChangeRequest expires.
     /// @param len A valid lifetime of the lease associated with the
     /// NameChangeRequest.
+    /// @param fqdn The expected string value of the FQDN, if blank the
+    /// check is skipped
     void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
                                  const bool reverse, const bool forward,
                                  const std::string& addr,
                                  const std::string& dhcid,
                                  const uint16_t expires,
-                                 const uint16_t len) {
+                                 const uint16_t len,
+                                 const std::string& fqdn="") {
         NameChangeRequestPtr ncr;
         ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
         ASSERT_TRUE(ncr);
@@ -501,10 +507,40 @@ public:
         EXPECT_EQ(len, ncr->getLeaseLength());
         EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
 
+        if (! fqdn.empty()) {
+           EXPECT_EQ(fqdn, ncr->getFqdn());
+        }
+
+
         // Process the message off the queue
         ASSERT_NO_THROW(d2_mgr_.runReadyIO());
     }
 
+    /// @brief Updates inherited subnet and pool members
+    ///
+    /// Hack added to set subnet_ and pool_ members that are buried into lower
+    /// level tests such as checkLease(), so one can use "configure" functionality
+    /// rather than hand-building configured objects
+    /// @param subnet_idx Element index of the desired subnet
+    /// @param pool_idx Element index of the desired pool within the desired subnet
+    /// @param type lease type of desired pool
+    ///
+    void setSubnetAndPool(int subnet_idx, int pool_idx, Lease::Type type) {
+        ConstCfgSubnets6Ptr subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
+        ASSERT_TRUE(subnets);
+        const Subnet6Collection* subnet_col = subnets->getAll();
+        ASSERT_EQ(subnet_idx + 1, subnet_col->size());
+        subnet_ = subnet_col->at(subnet_idx);
+        ASSERT_TRUE(subnet_);
+
+        const PoolCollection& pool_col = subnet_->getPools(type);
+        ASSERT_EQ(pool_idx + 1, pool_col.size());
+        PoolPtr pool  = (subnet_->getPools(type)).at(pool_idx);
+        ASSERT_TRUE(pool);
+        pool_ = boost::dynamic_pointer_cast<Pool6>(pool);
+        ASSERT_TRUE(pool);
+    }
+
     // Holds a lease used by a test.
     Lease6Ptr lease_;
 
@@ -943,7 +979,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
     // In this case, we expect that the FQDN option will not be included
     // in the server's response. The testProcessMessage will check that.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", Option6ClientFqdn::FLAG_S, false);
+                       "myhost.example.com.", Option6ClientFqdn::FLAG_S,
+                       IOAddress("2001:db8:1:1::dead:beef"), false);
     ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -957,7 +994,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
 TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
     testProcessMessage(DHCPV6_REQUEST, "",
                        "myhost-2001-db8-1-1--dead-beef.example.com.",
-                       Option6ClientFqdn::FLAG_S, false);
+                       Option6ClientFqdn::FLAG_S,
+                       IOAddress("2001:db8:1:1::dead:beef"), false);
     ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -1050,5 +1088,151 @@ TEST_F(FqdnDhcpv6SrvTest, processClientDelegation) {
                             0, 4000);
 }
 
+// Verify that the host reservation is found and used. Lease host name and
+// FQDN should be the reservation hostname suffixed by the qualifying suffix.
+TEST_F(FqdnDhcpv6SrvTest, hostnameReservationSuffix) {
+    isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+    string config_str = "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+        "    \"interface\" : \"eth0\" , "
+        "   \"reservations\": ["
+        "    {"
+        "        \"duid\": \"" + duid_->toText() + "\","
+        "        \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+        "        \"hostname\": \"alice\""
+        "    }"
+        "    ]"
+        " } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : true, "
+        "     \"qualifying-suffix\" : \"example.com\" }"
+        "}";
+
+    configure(config_str);
+
+    // Update subnet_ and pool_ members after config
+    setSubnetAndPool(0, 0, Lease::TYPE_NA);
+
+    ASSERT_NO_THROW(srv_->startD2());
+
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+    // Verify that the host reservation is found and lease name/FQDN are
+    // formed properly from the host name and qualifying suffix.
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "alice.example.com.", 1, IOAddress("2001:db8:1:1::babe"));
+
+    // Verify that NameChangeRequest is correct.
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::babe",
+                            "000201E2EB74FB53A5778E74AFD43870ECA5"
+                            "4150B1F52B0CFED434802DA1259D6D3CA4",
+                            0, 4000, "alice.example.com.");
+}
+
+// Verify that the host reservation is found and used, rather than dynamic
+// Address.  Lease host name and FQDN should be the reservation hostname
+// without a qualifying suffix.
+TEST_F(FqdnDhcpv6SrvTest, hostnameReservationNoSuffix) {
+    isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+    string config_str = "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+        "    \"interface\" : \"eth0\" , "
+        "   \"reservations\": ["
+        "    {"
+        "        \"duid\": \"" + duid_->toText() + "\","
+        "        \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+        "        \"hostname\": \"alice.example.com\""
+        "    }"
+        "    ]"
+        " } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : true, "
+        "     \"qualifying-suffix\" : \"\" }"
+        "}";
+
+    configure(config_str);
+    // Update subnet_ and pool_ members after config
+    setSubnetAndPool(0, 0, Lease::TYPE_NA);
+
+    ASSERT_NO_THROW(srv_->startD2());
+
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "alice.example.com.", 1,
+                       IOAddress("2001:db8:1:1::babe"));
+
+    // Verify that NameChangeRequest is correct.
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::babe",
+                            "000201E2EB74FB53A5778E74AFD43870ECA5"
+                            "4150B1F52B0CFED434802DA1259D6D3CA4",
+                            0, 4000, "alice.example.com.");
+
+}
+
+TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) {
+    isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+    string config_str = "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+        "    \"interface\" : \"eth0\" , "
+        "    \"reservations\": ["
+        "    {"
+        "        \"duid\": \"" + duid_->toText() + "\","
+        "        \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+        "        \"hostname\": \"alice\""
+        "    }"
+        "    ]"
+        " } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : false, "
+        "     \"qualifying-suffix\" : \"disabled.example.com\" }"
+        "}";
+
+    configure(config_str);
+
+    // Update subnet_ and pool_ members after config
+    setSubnetAndPool(0, 0, Lease::TYPE_NA);
+
+    ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "alice.disabled.example.com.", 0,
+                       IOAddress("2001:db8:1:1::babe"));
+}
 
 }   // end of anonymous namespace

+ 258 - 0
src/bin/dhcp6/tests/host_unittest.cc

@@ -0,0 +1,258 @@
+// 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/tests/iface_mgr_test_config.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used by the Host reservation unit tests.
+///
+/// - Configuration 0:
+///   Single subnet with two reservations, one with a hostname, one without
+const char* CONFIGS[] = {
+    // Configuration 0:
+    "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+        "    \"interface\" : \"eth0\" , "
+        "    \"reservations\": ["
+        "    {"
+        "        \"duid\": \"01:02:03:04\","
+        "        \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+        "        \"hostname\": \"alice\""
+        "    },"
+        "    {"
+        "        \"duid\": \"01:02:03:05\","
+        "        \"ip-addresses\": [ \"2001:db8:1:1::babf\" ]"
+        "    } ]"
+        " } ]"
+    "}"
+};
+
+/// @brief Test fixture class for testing host reservations
+class HostTest : public Dhcpv6SrvTest {
+public:
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    HostTest()
+        : Dhcpv6SrvTest(),
+          iface_mgr_test_config_(true) {
+    }
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+// Test basic SARR scenarios against a server configured with one subnet
+// containing two reservations.  One reservation with a hostname, one
+// without a hostname. Scenarios:
+//
+// - Verify that a client when matched to a host reservation with a hostname
+// gets that reservation and the lease hostname matches the reserved hostname
+//
+// - Verify that a client when matched to a host reservation without a hostname
+// gets that reservation and the lease hostname is blank
+//
+// - Verify that a client that does not match a host reservation gets a dynamic
+// lease and the hostname for the lease is blank.
+//
+TEST_F(HostTest, basicSarrs) {
+    Dhcp6Client client;
+    configure(CONFIGS[0], *client.getServer());
+
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(1, subnets->size());
+
+    // Configure client to request IA_NA and aAppend IA_NA option
+    //  to the client's message.
+    client.setDUID("01:02:03:04");
+    client.useNA();
+    ASSERT_NO_THROW(client.useHint(100, 200, 64, "2001:db8:1:1::dead:beef"));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Verify that the client we got the reserved address
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client = client.getLease(0);
+    EXPECT_EQ("2001:db8:1:1::babe", lease_client.addr_.toText());
+
+    // Check that the server recorded the lease.
+    // and lease has reserved hostname
+    Lease6Ptr lease_server = checkLease(lease_client);
+    ASSERT_TRUE(lease_server);
+    EXPECT_EQ("alice", lease_server->hostname_);
+
+    // Now redo the client, adding one to the DUID
+    client.clearConfig();
+    client.modifyDUID();
+    client.useNA();
+    ASSERT_NO_THROW(client.useHint(100, 200, 64, "2001:db8:1:1::dead:beef"));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Verify that the client we got the reserved address
+    ASSERT_EQ(1, client.getLeaseNum());
+    lease_client = client.getLease(0);
+    EXPECT_EQ("2001:db8:1:1::babf", lease_client.addr_.toText());
+
+    // Check that the server recorded the lease.
+    // and that the server lease has NO hostname
+    lease_server = checkLease(lease_client);
+    ASSERT_TRUE(lease_server);
+    EXPECT_EQ("", lease_server->hostname_);
+
+    // Now redo the client with yet another DUID and verify that
+    // we get a dynamic address.
+    client.clearConfig();
+    client.modifyDUID();
+    client.useNA();
+    ASSERT_NO_THROW(client.useHint(100, 200, 64, "2001:db8:1:1::dead:beef"));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Verify that the client got a dynamic address
+    ASSERT_EQ(1, client.getLeaseNum());
+    lease_client = client.getLease(0);
+    EXPECT_EQ("2001:db8:1:1::", lease_client.addr_.toText());
+
+    // Check that the server recorded the lease.
+    // and that the server lease has NO hostname
+    lease_server = checkLease(lease_client);
+    ASSERT_TRUE(lease_server);
+    EXPECT_EQ("", lease_server->hostname_);
+}
+
+// Test basic SARR and renew situation with a client that matches a host
+// reservation
+TEST_F(HostTest, sarrAndRenew) {
+    Dhcp6Client client;
+
+    configure(CONFIGS[0], *client.getServer());
+
+    // Configure client to request IA_NA.
+    client.useNA();
+
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(1, subnets->size());
+
+    // Configure client to request IA_NA and aAppend IA_NA option
+    //  to the client's message.
+    client.setDUID("01:02:03:04");
+    client.useNA();
+    ASSERT_NO_THROW(client.useHint(100, 200, 64, "2001:db8:1:1::dead:beef"));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Now play with time
+    client.fastFwdTime(1000);
+
+    // Verify that the client we got the reserved address
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client = client.getLease(0);
+    EXPECT_EQ("2001:db8:1:1::babe", lease_client.addr_.toText());
+
+    // Send Renew message to the server.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // Verify that we got an extended lease back
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    EXPECT_EQ("2001:db8:1:1::babe", lease_client2.addr_.toText());
+
+    // The client's lease should have been extended. The client will
+    // update the cltt to current time when the lease gets extended.
+    ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+
+    // Make sure, that the client's lease matches the lease held by the
+    // server and that we have the reserved host name.
+    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+    EXPECT_EQ("alice", lease_server2->hostname_);
+}
+
+// Test basic SARR and rebind situation with a client that matches a host
+// reservation.
+TEST_F(HostTest, sarrAndRebind) {
+    Dhcp6Client client;
+
+    configure(CONFIGS[0], *client.getServer());
+
+    // Configure client to request IA_NA.
+    client.useNA();
+
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
+    ASSERT_EQ(1, subnets->size());
+
+    // Configure client to request IA_NA and aAppend IA_NA option
+    //  to the client's message.
+    client.setDUID("01:02:03:04");
+    client.useNA();
+    ASSERT_NO_THROW(client.useHint(100, 200, 64, "2001:db8:1:1::dead:beef"));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Now play with time
+    client.fastFwdTime(1000);
+
+    // Verify that the client we got the reserved address
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client = client.getLease(0);
+    EXPECT_EQ("2001:db8:1:1::babe", lease_client.addr_.toText());
+
+    // Send Rebind message to the server.
+    ASSERT_NO_THROW(client.doRebind());
+
+    // Verify that we got an extended lease back
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    EXPECT_EQ("2001:db8:1:1::babe", lease_client2.addr_.toText());
+
+    // The client's lease should have been extended. The client will
+    // update the cltt to current time when the lease gets extended.
+    ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+
+    // Make sure, that the client's lease matches the lease held by the
+    // server and that we have the reserved host name.
+    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+    EXPECT_EQ("alice", lease_server2->hostname_);
+}
+
+} // end of anonymous namespace

+ 19 - 28
src/lib/dhcpsrv/alloc_engine.cc

@@ -318,6 +318,25 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const Duid
 }
 
 
+void AllocEngine::findReservation(ClientContext6& ctx) const {
+    if (!ctx.subnet_ || !ctx.duid_) {
+        return;
+    }
+
+    // Check which host reservation mode is supported in this subnet.
+    Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
+
+    // Check if there's a host reservation for this client. Attempt to get
+    // host info only if reservations are not disabled.
+    if (hr_mode != Subnet::HR_DISABLED) {
+
+        ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
+                                             ctx.hwaddr_);
+        } else {
+        // Let's explicitly set it to NULL if reservations are disabled.
+        ctx.host_.reset();
+    }
+}
 
 Lease6Collection
 AllocEngine::allocateLeases6(ClientContext6& ctx) {
@@ -330,20 +349,6 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
             isc_throw(InvalidOperation, "DUID is mandatory for IPv6 lease allocation");
         }
 
-        // Check which host reservation mode is supported in this subnet.
-        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
-
-        // Check if there's a host reservation for this client. Attempt to get
-        // host info only if reservations are not disabled.
-        if (hr_mode != Subnet::HR_DISABLED) {
-
-            ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
-                                                 ctx.hwaddr_);
-        } else {
-            // Let's explicitly set it to NULL if reservations are disabled.
-            ctx.host_.reset();
-        }
-
         // Check if there are existing leases for that subnet/duid/iaid
         // combination.
         Lease6Collection leases =
@@ -1011,20 +1016,6 @@ AllocEngine::renewLeases6(ClientContext6& ctx) {
             isc_throw(InvalidOperation, "DUID is mandatory for allocation");
         }
 
-        // Check which host reservation mode is supported in this subnet.
-        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
-
-        // Check if there's a host reservation for this client. Attempt to get
-        // host info only if reservations are not disabled.
-        if (hr_mode != Subnet::HR_DISABLED) {
-
-            ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
-                                                 ctx.hwaddr_);
-        } else {
-            // Host reservations disabled? Then explicitly set host to NULL
-            ctx.host_.reset();
-        }
-
         // Check if there are any leases for this client.
         Lease6Collection leases = LeaseMgrFactory::instance()
             .getLeases6(ctx.type_, *ctx.duid_, ctx.iaid_, ctx.subnet_->getID());

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

@@ -406,7 +406,8 @@ public:
     /// it into LeaseMgr (if this allocation is not fake, i.e. this is not a
     /// response to SOLICIT).
     ///
-    /// This method uses host reservation if appropriate. The host reservation
+    /// This method uses host reservation if ctx.host_ is set. The easy way to
+    /// set it is to call @ref AllocEngine::findReservation(ctx). The host reservation
     /// is convenient, but incurs performance penalty, so it can be tweaked on
     /// a per subnet basis. There are three possible modes:
     /// 1. disabled (no host reservation at all). This is the most performant one
@@ -479,7 +480,6 @@ public:
     Lease6Collection
     allocateLeases6(ClientContext6& ctx);
 
-
     /// @brief Renews existing DHCPv6 leases for a given IA.
     ///
     /// This method updates the leases associated with a specified IA container.
@@ -499,8 +499,14 @@ public:
     /// contain removed leases in this case.
     ///
     /// @return Returns renewed lease.
-    Lease6Collection
-    renewLeases6(ClientContext6& ctx);
+    Lease6Collection renewLeases6(ClientContext6& ctx);
+
+    /// @brief Attempts to find appropriate host reservation.
+    ///
+    /// Attempts to find appropriate host reservation in HostMgr. If found, it
+    /// will be set in ctx.host_.
+    /// @param ctx Client context that contains all necessary information.
+    void findReservation(ClientContext6& ctx) const;
 
 private:
 

+ 8 - 0
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc

@@ -968,6 +968,7 @@ TEST_F(AllocEngine6Test, reservedAddress) {
     for (int i = 0; i < 30; i++) {
         AllocEngine::ClientContext6 ctx(subnet_, clients[i], iaid_, IOAddress("::"),
                                         Lease::TYPE_NA,  false, false, "", false);
+        findReservation(engine, ctx);
         Lease6Collection leases = engine.allocateLeases6(ctx);
         if (leases.empty()) {
             failure++;
@@ -990,6 +991,7 @@ TEST_F(AllocEngine6Test, reservedAddress) {
     // address reserved, will get it (despite the pool being depleted).
     AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
                                     Lease::TYPE_NA,  false, false, "", false);
+    findReservation(engine, ctx);
     Lease6Collection leases = engine.allocateLeases6(ctx);
     ASSERT_EQ(1, leases.size());
     EXPECT_EQ("2001:db8:1::12", leases[0]->addr_.toText());
@@ -1097,6 +1099,7 @@ TEST_F(AllocEngine6Test, DISABLED_reserved2AddressesSolicit) {
     AllocEngine::ClientContext6 ctx1(subnet_, duid_, iaid_, IOAddress("::"),
                                     pool_->getType(), false, false, "", true);
     Lease6Collection leases1;
+    findReservation(engine, ctx1);
     EXPECT_NO_THROW(leases1 = engine.allocateLeases6(ctx1));
     ASSERT_EQ(1, leases1.size());
     EXPECT_EQ("2001:db8:1::babe", leases1[0]->addr_.toText());
@@ -1106,6 +1109,7 @@ TEST_F(AllocEngine6Test, DISABLED_reserved2AddressesSolicit) {
     AllocEngine::ClientContext6 ctx2(subnet_, duid_, iaid_, IOAddress("::"),
                                     pool_->getType(), false, false, "", true);
     Lease6Collection leases2;
+    findReservation(engine, ctx2);
     EXPECT_NO_THROW(leases2 = engine.allocateLeases6(ctx2));
     EXPECT_EQ(1, leases2.size());
     EXPECT_EQ("2001:db8:1::babe", leases2[0]->addr_.toText());
@@ -1115,6 +1119,7 @@ TEST_F(AllocEngine6Test, DISABLED_reserved2AddressesSolicit) {
     AllocEngine::ClientContext6 ctx3(subnet_, duid_, iaid_ + 1, IOAddress("::"),
                                     pool_->getType(), false, false, "", true);
     Lease6Collection leases3;
+    findReservation(engine, ctx3);
     EXPECT_NO_THROW(leases3 = engine.allocateLeases6(ctx3));
     ASSERT_EQ(1, leases3.size());
     EXPECT_EQ("2001:db8:1::cafe", leases3[0]->addr_.toText());
@@ -1138,6 +1143,7 @@ TEST_F(AllocEngine6Test, reserved2Addresses) {
     AllocEngine::ClientContext6 ctx1(subnet_, duid_, iaid_, IOAddress("::"),
                                     pool_->getType(), false, false, "", false);
     Lease6Collection leases1;
+    findReservation(engine, ctx1);
     EXPECT_NO_THROW(leases1 = engine.allocateLeases6(ctx1));
     ASSERT_EQ(1, leases1.size());
     EXPECT_EQ("2001:db8:1::babe", leases1[0]->addr_.toText());
@@ -1147,6 +1153,7 @@ TEST_F(AllocEngine6Test, reserved2Addresses) {
     AllocEngine::ClientContext6 ctx2(subnet_, duid_, iaid_, IOAddress("::"),
                                     pool_->getType(), false, false, "", false);
     Lease6Collection leases2;
+    findReservation(engine, ctx2);
     EXPECT_NO_THROW(leases2 = engine.allocateLeases6(ctx2));
     EXPECT_EQ(1, leases2.size());
     EXPECT_EQ("2001:db8:1::babe", leases2[0]->addr_.toText());
@@ -1156,6 +1163,7 @@ TEST_F(AllocEngine6Test, reserved2Addresses) {
     AllocEngine::ClientContext6 ctx3(subnet_, duid_, iaid_ + 1, IOAddress("::"),
                                     pool_->getType(), false, false, "", false);
     Lease6Collection leases3;
+    findReservation(engine, ctx3);
     EXPECT_NO_THROW(leases3 = engine.allocateLeases6(ctx3));
     ASSERT_EQ(1, leases3.size());
     EXPECT_EQ("2001:db8:1::cafe", leases3[0]->addr_.toText());

+ 18 - 1
src/lib/dhcpsrv/tests/alloc_engine_utils.cc

@@ -82,6 +82,20 @@ AllocEngine6Test::initSubnet(const asiolink::IOAddress& subnet,
     cfg_mgr.commit();
 }
 
+void
+AllocEngine6Test::findReservation(AllocEngine& engine,
+    AllocEngine::ClientContext6& ctx) {
+    engine.findReservation(ctx);
+    // Let's check whether there's a hostname specified in the reservation
+    if (ctx.host_) {
+        std::string hostname = ctx.host_->getHostname();
+        // If there is, let's use it
+        if (!hostname.empty()) {
+            ctx.hostname_ = hostname;
+        }
+    }
+}
+
 HostPtr
 AllocEngine6Test::createHost6HWAddr(bool add_to_host_mgr, IPv6Resrv::Type type,
                                     HWAddrPtr& hwaddr, const asiolink::IOAddress& addr,
@@ -110,6 +124,8 @@ AllocEngine6Test::allocateTest(AllocEngine& engine, const Pool6Ptr& pool,
                                     false, false, "", fake);
 
     Lease6Collection leases;
+
+    findReservation(engine, ctx);
     EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
 
     for (Lease6Collection::iterator it = leases.begin(); it != leases.end(); ++it) {
@@ -165,7 +181,7 @@ AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
     AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type,
                                     false, false, "", fake);
     ctx.hwaddr_ = hwaddr_;
-
+    findReservation(*engine, ctx);
     EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
 
     // Check that we got a lease
@@ -214,6 +230,7 @@ AllocEngine6Test::renewTest(AllocEngine& engine, const Pool6Ptr& pool,
     ctx.query_.reset(new Pkt6(DHCPV6_RENEW, 123));
     ctx.allow_new_leases_in_renewals_ = allow_new_leases_in_renewal;
 
+    findReservation(engine, ctx);
     Lease6Collection leases = engine.renewLeases6(ctx);
 
     for (Lease6Collection::iterator it = leases.begin(); it != leases.end(); ++it) {

+ 9 - 0
src/lib/dhcpsrv/tests/alloc_engine_utils.h

@@ -111,6 +111,15 @@ public:
         fqdn_rev_ = fqdn_rev;
     }
 
+    /// @brief Wrapper around call to AllocEngine6::findRervation
+    ///
+    /// If a reservation is found by the engine, the function sets
+    /// ctx.hostname_ accordingly.
+    ///
+    /// @param engine allocation engine to use
+    /// @param ctx client context to pass into engine's findReservation method
+    void findReservation(AllocEngine& engine, AllocEngine::ClientContext6& ctx);
+
     /// @brief attempts to convert leases collection to a single lease
     ///
     /// This operation makes sense if there is at most one lease in the