Browse Source

[master] Merge branch 'trac3282'

Implements use of dhcp-ddns configuration parameters in b10-dhcp4.
Thomas Markwalder 11 years ago
parent
commit
42b1f1e4c4

+ 0 - 7
src/bin/dhcp4/dhcp4.spec

@@ -336,13 +336,6 @@
                 "item_description" : "Format of the update request packet"
                 "item_description" : "Format of the update request packet"
             },
             },
             {
             {
-                "item_name": "remove-on-renew",
-                "item_type": "boolean",
-                "item_optional": true,
-                "item_default": false,
-                "item_description": "Enable requesting a DNS Remove, before a DNS Update on renewals"
-            },
-            {
 
 
                 "item_name": "always-include-fqdn",
                 "item_name": "always-include-fqdn",
                 "item_type": "boolean",
                 "item_type": "boolean",

+ 61 - 144
src/bin/dhcp4/dhcp4_srv.cc

@@ -80,36 +80,6 @@ Dhcp4Hooks Hooks;
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-namespace {
-
-// @todo The following constants describe server's behavior with respect to the
-// DHCPv4 Client FQDN Option sent by a client. They will be removed
-// when DDNS parameters for DHCPv4 are implemented with the ticket #3033.
-
-// @todo Additional configuration parameter which we may consider is the one
-// that controls whether the DHCP server sends the removal NameChangeRequest
-// if it discovers that the entry for the particular client exists or that
-// it always updates the DNS.
-
-// Should server always include the FQDN option in its response, regardless
-// if it has been requested in Parameter Request List Option (Disabled).
-const bool FQDN_ALWAYS_INCLUDE = false;
-// Enable A RR update delegation to the client (Disabled).
-const bool FQDN_ALLOW_CLIENT_UPDATE = false;
-// Globally enable updates (Enabled).
-const bool FQDN_ENABLE_UPDATE = true;
-// Do update, even if client requested no updates with N flag (Disabled).
-const bool FQDN_OVERRIDE_NO_UPDATE = false;
-// Server performs an update when client requested delegation (Enabled).
-const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
-// The fully qualified domain-name suffix if partial name provided by
-// a client.
-const char* FQDN_PARTIAL_SUFFIX = "example.com";
-// Should server replace the domain-name supplied by the client (Disabled).
-const bool FQDN_REPLACE_CLIENT_NAME = false;
-
-}
-
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
                      const bool direct_response_desired)
                      const bool direct_response_desired)
 : shutdown_(true), alloc_engine_(), port_(port),
 : shutdown_(true), alloc_engine_(), port_(port),
@@ -596,8 +566,8 @@ Dhcpv4Srv::appendServerID(const Pkt4Ptr& response) {
     // The source address for the outbound message should have been set already.
     // The source address for the outbound message should have been set already.
     // This is the address that to the best of the server's knowledge will be
     // This is the address that to the best of the server's knowledge will be
     // available from the client.
     // available from the client.
-    // @todo: perhaps we should consider some more sophisticated server id
-    // generation, but for the current use cases, it should be ok.
+    /// @todo: perhaps we should consider some more sophisticated server id
+    /// generation, but for the current use cases, it should be ok.
     response->addOption(OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
     response->addOption(OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
                                                      response->getLocalAddr()))
                                                      response->getLocalAddr()))
                         );
                         );
@@ -666,8 +636,8 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer
     uint32_t vendor_id = vendor_req->getVendorId();
     uint32_t vendor_id = vendor_req->getVendorId();
 
 
     // Let's try to get ORO within that vendor-option
     // Let's try to get ORO within that vendor-option
-    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
-    /// may have different policies.
+    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
+    /// vendors may have different policies.
     OptionUint8ArrayPtr oro =
     OptionUint8ArrayPtr oro =
         boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
         boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
 
 
@@ -773,68 +743,19 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
     // response to a client.
     // response to a client.
     Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
     Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
 
 
-    // RFC4702, section 4 - set 'NOS' flags to 0.
-    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, 0);
-    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, 0);
-    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, 0);
-
-    // Conditions when N flag has to be set to indicate that server will not
-    // perform DNS updates:
-    // 1. Updates are globally disabled,
-    // 2. Client requested no update and server respects it,
-    // 3. Client requested that the forward DNS update is delegated to the
-    //    client but server neither respects requests for forward update
-    //    delegation nor it is configured to send update on its own when
-    //    client requested delegation.
-    if (!FQDN_ENABLE_UPDATE ||
-        (fqdn->getFlag(Option4ClientFqdn::FLAG_N) &&
-         !FQDN_OVERRIDE_NO_UPDATE) ||
-        (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
-         !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
-        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, true);
-
-    // Conditions when S flag is set to indicate that server will perform DNS
-    // update on its own:
-    // 1. Client requested that server performs DNS update and DNS updates are
-    //    globally enabled.
-    // 2. Client requested that server delegates forward update to the client
-    //    but server doesn't respect requests for delegation and it is
-    // configured to perform an update on its own when client requested the
-    // delegation.
-    } else  if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) ||
-                (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
-                 !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
-        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, true);
-    }
-
-    // Server MUST set the O flag if it has overriden the client's setting
-    // of S flag.
-    if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) !=
-        fqdn_resp->getFlag(Option4ClientFqdn::FLAG_S)) {
-        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, true);
-    }
+    // Set the server S, N, and O flags based on client's flags and
+    // current configuration.
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp);
 
 
-    // If client suppled partial or empty domain-name, server should generate
-    // one.
-    if (fqdn->getDomainNameType() == Option4ClientFqdn::PARTIAL) {
-        std::ostringstream name;
-        if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) {
-            fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL);
+    // Carry over the client's E flag.
+    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
+                       fqdn->getFlag(Option4ClientFqdn::FLAG_E));
 
 
-        } else {
-            name << fqdn->getDomainName();
-            name << "." << FQDN_PARTIAL_SUFFIX;
-            fqdn_resp->setDomainName(name.str(), Option4ClientFqdn::FULL);
 
 
-        }
-
-    // Server may be configured to replace a name supplied by a client, even if
-    // client supplied fully qualified domain-name. The empty domain-name is
-    // is set to indicate that the name must be generated when the new lease
-    // is acquired.
-    } else if(FQDN_REPLACE_CLIENT_NAME) {
-        fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL);
-    }
+    // Adjust the domain name based on domain name value and type sent by the
+    // client and current configuration.
+    d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp);
 
 
     // Add FQDN option to the response message. Note that, there may be some
     // Add FQDN option to the response message. Note that, there may be some
     // cases when server may choose not to include the FQDN option in a
     // cases when server may choose not to include the FQDN option in a
@@ -854,15 +775,20 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
 void
 void
 Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
 Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
                                  Pkt4Ptr& answer) {
                                  Pkt4Ptr& answer) {
+    // Fetch D2 configuration.
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+
     // Do nothing if the DNS updates are disabled.
     // Do nothing if the DNS updates are disabled.
-    if (!FQDN_ENABLE_UPDATE) {
+    if (!d2_mgr.ddnsEnabled()) {
         return;
         return;
     }
     }
 
 
     std::string hostname = isc::util::str::trim(opt_hostname->readString());
     std::string hostname = isc::util::str::trim(opt_hostname->readString());
     unsigned int label_count = OptionDataTypeUtil::getLabelCount(hostname);
     unsigned int label_count = OptionDataTypeUtil::getLabelCount(hostname);
     // The hostname option sent by the client should be at least 1 octet long.
     // The hostname option sent by the client should be at least 1 octet long.
-    // If it isn't we ignore this option.
+    // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
+    /// @todo It would be more liberal to accept this and let it fall into
+    /// the case  of replace or less than two below.
     if (label_count == 0) {
     if (label_count == 0) {
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_EMPTY_HOSTNAME);
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_EMPTY_HOSTNAME);
         return;
         return;
@@ -880,21 +806,20 @@ Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
     // By checking the number of labels present in the hostname we may infer
     // By checking the number of labels present in the hostname we may infer
     // whether client has sent the fully qualified or unqualified hostname.
     // whether client has sent the fully qualified or unqualified hostname.
 
 
-    // @todo We may want to reconsider whether it is appropriate for the
-    // client to send a root domain name as a Hostname. There are
-    // also extensions to the auto generation of the client's name,
-    // e.g. conversion to the puny code which may be considered at some point.
-    // For now, we just remain liberal and expect that the DNS will handle
-    // conversion if needed and possible.
-    if (FQDN_REPLACE_CLIENT_NAME || (label_count < 2)) {
+    /// @todo We may want to reconsider whether it is appropriate for the
+    /// client to send a root domain name as a Hostname. There are
+    /// also extensions to the auto generation of the client's name,
+    /// e.g. conversion to the puny code which may be considered at some point.
+    /// For now, we just remain liberal and expect that the DNS will handle
+    /// conversion if needed and possible.
+    if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
+        (label_count < 2)) {
         opt_hostname_resp->writeString("");
         opt_hostname_resp->writeString("");
-    // If there are two labels, it means that the client has specified
-    // the unqualified name. We have to concatenate the unqalified name
-    // with the domain name.
     } else if (label_count == 2) {
     } else if (label_count == 2) {
-        std::ostringstream resp_hostname;
-        resp_hostname << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
-        opt_hostname_resp->writeString(resp_hostname.str());
+        // If there are two labels, it means that the client has specified
+        // the unqualified name. We have to concatenate the unqalified name
+        // with the domain name.
+        opt_hostname_resp->writeString(d2_mgr.qualifyName(hostname));
     }
     }
 
 
     answer->addOption(opt_hostname_resp);
     answer->addOption(opt_hostname_resp);
@@ -985,9 +910,9 @@ queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type,
 void
 void
 Dhcpv4Srv::sendNameChangeRequests() {
 Dhcpv4Srv::sendNameChangeRequests() {
     while (!name_change_reqs_.empty()) {
     while (!name_change_reqs_.empty()) {
-        // @todo Once next NameChangeRequest is picked from the queue
-        // we should send it to the b10-dhcp_ddns module. Currently we
-        // just drop it.
+        /// @todo Once next NameChangeRequest is picked from the queue
+        /// we should send it to the b10-dhcp_ddns module. Currently we
+        /// just drop it.
         name_change_reqs_.pop();
         name_change_reqs_.pop();
     }
     }
 }
 }
@@ -1006,8 +931,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         // thing this client can get is some global information (like DNS
         // thing this client can get is some global information (like DNS
         // servers).
         // servers).
 
 
-        // perhaps this should be logged on some higher level? This is most likely
-        // configuration bug.
+        // perhaps this should be logged on some higher level? This is most
+        // likely configuration bug.
         LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
         LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
             .arg(question->getRemoteAddr().toText())
             .arg(question->getRemoteAddr().toText())
             .arg(serverReceivedPacketName(question->getType()));
             .arg(serverReceivedPacketName(question->getType()));
@@ -1020,7 +945,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // as siaddr has nothing to do with a lease, but otherwise we would have
     // as siaddr has nothing to do with a lease, but otherwise we would have
     // to select subnet twice (performance hit) or update too many functions
     // to select subnet twice (performance hit) or update too many functions
     // at once.
     // at once.
-    // @todo: move subnet selection to a common code
+    /// @todo: move subnet selection to a common code
     answer->setSiaddr(subnet->getSiaddr());
     answer->setSiaddr(subnet->getSiaddr());
 
 
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
@@ -1063,8 +988,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
             (answer->getOption(DHO_HOST_NAME));
             (answer->getOption(DHO_HOST_NAME));
         if (opt_hostname) {
         if (opt_hostname) {
             hostname = opt_hostname->readString();
             hostname = opt_hostname->readString();
-            // @todo It could be configurable what sort of updates the server
-            // is doing when Hostname option was sent.
+            /// @todo It could be configurable what sort of updates the
+            /// server is doing when Hostname option was sent.
             fqdn_fwd = true;
             fqdn_fwd = true;
             fqdn_rev = true;
             fqdn_rev = true;
         }
         }
@@ -1074,7 +999,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // 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
     // may be used instead. If fake_allocation is set to false, the lease will
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
     // be inserted into the LeaseMgr as well.
-    // @todo pass the actual FQDN data.
+    /// @todo pass the actual FQDN data.
     Lease4Ptr old_lease;
     Lease4Ptr old_lease;
     Lease4Ptr lease = alloc_engine_->allocateLease4(subnet, client_id, hwaddr,
     Lease4Ptr lease = alloc_engine_->allocateLease4(subnet, client_id, hwaddr,
                                                       hint, fqdn_fwd, fqdn_rev,
                                                       hint, fqdn_fwd, fqdn_rev,
@@ -1099,14 +1024,9 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         // generating the entire hostname for the client. The example of the
         // generating the entire hostname for the client. The example of the
         // client's name, generated from the IP address is: host-192-0-2-3.
         // client's name, generated from the IP address is: host-192-0-2-3.
         if ((fqdn || opt_hostname) && lease->hostname_.empty()) {
         if ((fqdn || opt_hostname) && lease->hostname_.empty()) {
-            hostname = lease->addr_.toText();
-            // Replace dots with hyphens.
-            std::replace(hostname.begin(), hostname.end(), '.', '-');
-            ostringstream stream;
-            // The partial suffix will need to be replaced with the actual
-            // domain-name for the client when configuration is implemented.
-            stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
-            lease->hostname_ = stream.str();
+            lease->hostname_ = CfgMgr::instance()
+                               .getD2ClientMgr().generateFqdn(lease->addr_);
+
             // The operations below are rather safe, but we want to catch
             // The operations below are rather safe, but we want to catch
             // any potential exceptions (e.g. invalid lease database backend
             // any potential exceptions (e.g. invalid lease database backend
             // implementation) and log an error.
             // implementation) and log an error.
@@ -1138,22 +1058,18 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         // Subnet mask (type 1)
         // Subnet mask (type 1)
         answer->addOption(getNetmaskOption(subnet));
         answer->addOption(getNetmaskOption(subnet));
 
 
-        // @todo: send renew timer option (T1, option 58)
-        // @todo: send rebind timer option (T2, option 59)
+        /// @todo: send renew timer option (T1, option 58)
+        /// @todo: send rebind timer option (T2, option 59)
 
 
-        // @todo Currently the NameChangeRequests are always generated if
-        // real (not fake) allocation is being performed. Should we have
-        // control switch to enable/disable NameChangeRequest creation?
-        // Perhaps we need a way to detect whether the b10-dhcp-ddns module
-        // is up an running?
-        if (!fake_allocation) {
+        // Create NameChangeRequests if DDNS is enabled and this is a
+        // real allocation.
+        if (!fake_allocation && CfgMgr::instance().ddnsEnabled()) {
             try {
             try {
                 createNameChangeRequests(lease, old_lease);
                 createNameChangeRequests(lease, old_lease);
             } catch (const Exception& ex) {
             } catch (const Exception& ex) {
                 LOG_ERROR(dhcp4_logger, DHCP4_NCR_CREATION_FAILED)
                 LOG_ERROR(dhcp4_logger, DHCP4_NCR_CREATION_FAILED)
                     .arg(ex.what());
                     .arg(ex.what());
             }
             }
-
         }
         }
 
 
     } else {
     } else {
@@ -1194,8 +1110,8 @@ Dhcpv4Srv::adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response) {
     // address for the response. Instead, we have to check what address our
     // address for the response. Instead, we have to check what address our
     // socket is bound to and use it as a source address. This operation
     // socket is bound to and use it as a source address. This operation
     // may throw if for some reason the socket is closed.
     // may throw if for some reason the socket is closed.
-    // @todo Consider an optimization that we use local address from
-    // the query if this address is not broadcast.
+    /// @todo Consider an optimization that we use local address from
+    /// the query if this address is not broadcast.
     SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
     SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
     // Set local adddress, port and interface.
     // Set local adddress, port and interface.
     response->setLocalAddr(sock_info.addr_);
     response->setLocalAddr(sock_info.addr_);
@@ -1310,7 +1226,7 @@ Pkt4Ptr
 Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
 Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
 
 
     /// @todo Uncomment this (see ticket #3116)
     /// @todo Uncomment this (see ticket #3116)
-    // sanityCheck(request, MANDATORY);
+    /// sanityCheck(request, MANDATORY);
 
 
     Pkt4Ptr ack = Pkt4Ptr
     Pkt4Ptr ack = Pkt4Ptr
         (new Pkt4(DHCPACK, request->getTransid()));
         (new Pkt4(DHCPACK, request->getTransid()));
@@ -1352,7 +1268,7 @@ void
 Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 
 
     /// @todo Uncomment this (see ticket #3116)
     /// @todo Uncomment this (see ticket #3116)
-    // sanityCheck(release, MANDATORY);
+    /// sanityCheck(release, MANDATORY);
 
 
     // Try to find client-id
     // Try to find client-id
     ClientIdPtr client_id;
     ClientIdPtr client_id;
@@ -1377,7 +1293,7 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
         // Does the hardware address match? We don't want one client releasing
         // Does the hardware address match? We don't want one client releasing
         // second client's leases.
         // second client's leases.
         if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
         if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
-            // @todo: Print hwaddr from lease as part of ticket #2589
+            /// @todo: Print hwaddr from lease as part of ticket #2589
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
                 .arg(release->getCiaddr().toText())
                 .arg(release->getCiaddr().toText())
                 .arg(client_id ? client_id->toText() : "(no client-id)")
                 .arg(client_id ? client_id->toText() : "(no client-id)")
@@ -1435,9 +1351,10 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
                     .arg(client_id ? client_id->toText() : "(no client-id)")
                     .arg(client_id ? client_id->toText() : "(no client-id)")
                     .arg(release->getHWAddr()->toText());
                     .arg(release->getHWAddr()->toText());
 
 
-                // Remove existing DNS entries for the lease, if any.
-                queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease);
-
+                if (CfgMgr::instance().ddnsEnabled()) {
+                    // Remove existing DNS entries for the lease, if any.
+                    queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease);
+                }
             } else {
             } else {
                 // Release failed -
                 // Release failed -
                 LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
                 LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
@@ -1678,8 +1595,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
     }
     }
     // Let's reopen active sockets. openSockets4 will check internally whether
     // Let's reopen active sockets. openSockets4 will check internally whether
     // sockets are marked active or inactive.
     // sockets are marked active or inactive.
-    // @todo Optimization: we should not reopen all sockets but rather select
-    // those that have been affected by the new configuration.
+    /// @todo Optimization: we should not reopen all sockets but rather select
+    /// those that have been affected by the new configuration.
     isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
     isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
         boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
         boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
     if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) {
     if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) {

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

@@ -2712,7 +2712,6 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
         "     \"server-port\" : 777, "
         "     \"server-port\" : 777, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"allow-client-update\" : true, "
         "     \"allow-client-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
@@ -2745,7 +2744,6 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
     EXPECT_EQ(777, d2_client_config->getServerPort());
     EXPECT_EQ(777, d2_client_config->getServerPort());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
-    EXPECT_TRUE(d2_client_config->getRemoveOnRenew());
     EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
     EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
     EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
     EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
     EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
     EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
@@ -2772,7 +2770,6 @@ TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
         "     \"server-port\" : 5301, "
         "     \"server-port\" : 5301, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"allow-client-update\" : true, "
         "     \"allow-client-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "

+ 287 - 69
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/cfgmgr.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -32,13 +33,53 @@ namespace {
 
 
 class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
 class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
 public:
 public:
+
+    // Bit Constants for turning on and off DDNS configuration options.
+    static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
+    static const uint16_t OVERRIDE_NO_UPDATE = 2;
+    static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
+    static const uint16_t REPLACE_CLIENT_NAME = 8;
+
     NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
     NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
         srv_ = new NakedDhcpv4Srv(0);
         srv_ = new NakedDhcpv4Srv(0);
+        // Config DDNS to be enabled, all controls off
+        enableD2();
     }
     }
+
     virtual ~NameDhcpv4SrvTest() {
     virtual ~NameDhcpv4SrvTest() {
         delete srv_;
         delete srv_;
     }
     }
 
 
+    /// @brief Sets the server's DDNS configuration to ddns updates disabled.
+    void disableD2() {
+        // Default constructor creates a config with DHCP-DDNS updates
+        // disabled.
+        D2ClientConfigPtr cfg(new D2ClientConfig());
+        CfgMgr::instance().setD2ClientConfig(cfg);
+    }
+
+    /// @brief Enables DHCP-DDNS updates with the given options enabled.
+    ///
+    /// Replaces the current D2ClientConfiguration with a configuration
+    /// which as updates enabled and the control options set based upon
+    /// the bit mask of options.
+    ///
+    /// @param mask Bit mask of configuration options that should be enabled.
+    void enableD2(const uint16_t mask = 0) {
+        D2ClientConfigPtr cfg;
+
+        ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("192.0.2.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  (mask & ALWAYS_INCLUDE_FQDN),
+                                  (mask & OVERRIDE_NO_UPDATE),
+                                  (mask & OVERRIDE_CLIENT_UPDATE),
+                                  (mask & REPLACE_CLIENT_NAME),
+                                  "myhost", "example.com")));
+
+        CfgMgr::instance().setD2ClientConfig(cfg);
+    }
+
     // Create a lease to be used by various tests.
     // Create a lease to be used by various tests.
     Lease4Ptr createLease(const isc::asiolink::IOAddress& addr,
     Lease4Ptr createLease(const isc::asiolink::IOAddress& addr,
                           const std::string& hostname,
                           const std::string& hostname,
@@ -78,15 +119,18 @@ public:
         return (opt_hostname);
         return (opt_hostname);
     }
     }
 
 
-    // Generates partial hostname from the address. The format of the
-    // generated address is: host-A-B-C-D, where A.B.C.D is an IP
-    // address.
+    /// @brief Convenience method for generating an FQDN from an IP address.
+    ///
+    /// This is just a wrapper method around the D2ClientMgr's method for
+    /// generating domain names from the configured prefix, suffix, and a
+    /// given IP address.  This is useful for verifying that fully generated
+    /// names are correct.
+    ///
+    /// @param addr IP address used in the lease.
+    ///
+    /// @return An std::string contained the generated FQDN.
     std::string generatedNameFromAddress(const IOAddress& addr) {
     std::string generatedNameFromAddress(const IOAddress& addr) {
-        std::string gen_name = addr.toText();
-        std::replace(gen_name.begin(), gen_name.end(), '.', '-');
-        std::ostringstream hostname;
-        hostname << "host-" << gen_name;
-        return (hostname.str());
+        return(CfgMgr::instance().getD2ClientMgr().generateFqdn(addr));
     }
     }
 
 
     // Get the Client FQDN Option from the given message.
     // Get the Client FQDN Option from the given message.
@@ -182,6 +226,21 @@ public:
         Option4ClientFqdnPtr fqdn = getClientFqdnOption(answer);
         Option4ClientFqdnPtr fqdn = getClientFqdnOption(answer);
         ASSERT_TRUE(fqdn);
         ASSERT_TRUE(fqdn);
 
 
+        checkFqdnFlags(answer, exp_flags);
+
+        EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
+        EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
+
+    }
+
+    /// @brief Checks the packet's FQDN option flags against a given mask
+    ///
+    /// @param pkt IPv4 packet whose FQDN flags should be checked.
+    /// @param exp_flags Bit mask of flags that are expected to be true.
+    void checkFqdnFlags(const Pkt4Ptr& pkt, const uint8_t exp_flags) {
+        Option4ClientFqdnPtr fqdn = getClientFqdnOption(pkt);
+        ASSERT_TRUE(fqdn);
+
         const bool flag_n = (exp_flags & Option4ClientFqdn::FLAG_N) != 0;
         const bool flag_n = (exp_flags & Option4ClientFqdn::FLAG_N) != 0;
         const bool flag_s = (exp_flags & Option4ClientFqdn::FLAG_S) != 0;
         const bool flag_s = (exp_flags & Option4ClientFqdn::FLAG_S) != 0;
         const bool flag_o = (exp_flags & Option4ClientFqdn::FLAG_O) != 0;
         const bool flag_o = (exp_flags & Option4ClientFqdn::FLAG_O) != 0;
@@ -191,12 +250,9 @@ public:
         EXPECT_EQ(flag_s, fqdn->getFlag(Option4ClientFqdn::FLAG_S));
         EXPECT_EQ(flag_s, fqdn->getFlag(Option4ClientFqdn::FLAG_S));
         EXPECT_EQ(flag_o, fqdn->getFlag(Option4ClientFqdn::FLAG_O));
         EXPECT_EQ(flag_o, fqdn->getFlag(Option4ClientFqdn::FLAG_O));
         EXPECT_EQ(flag_e, fqdn->getFlag(Option4ClientFqdn::FLAG_E));
         EXPECT_EQ(flag_e, fqdn->getFlag(Option4ClientFqdn::FLAG_E));
-
-        EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
-        EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
-
     }
     }
 
 
+
     // Processes the Hostname option in the client's message and returns
     // Processes the Hostname option in the client's message and returns
     // the hostname option which would be sent to the client. It will
     // the hostname option which would be sent to the client. It will
     // throw NULL pointer if the hostname option is not to be included
     // throw NULL pointer if the hostname option is not to be included
@@ -221,8 +277,8 @@ public:
 
 
     }
     }
 
 
-    // Test that the client message holding an FQDN is processed and the
-    // NameChangeRequests are generated.
+    // Test that the client message holding an FQDN is processed and
+    // that the response packet is as expected.
     void testProcessMessageWithFqdn(const uint8_t msg_type,
     void testProcessMessageWithFqdn(const uint8_t msg_type,
                             const std::string& hostname) {
                             const std::string& hostname) {
         Pkt4Ptr req = generatePktWithFqdn(msg_type, Option4ClientFqdn::FLAG_S |
         Pkt4Ptr req = generatePktWithFqdn(msg_type, Option4ClientFqdn::FLAG_S |
@@ -287,6 +343,50 @@ public:
         srv_->name_change_reqs_.pop();
         srv_->name_change_reqs_.pop();
     }
     }
 
 
+
+    /// @brief Tests processing a request with the given client flags
+    ///
+    /// This method creates a request with its FQDN flags set to the given
+    /// value and submits it to the server for processing.  It then checks
+    /// the following:
+    /// 1. Did the server generate an ACK with the correct FQDN flags
+    /// 2. If the server should have generated an NCR, did it? and If
+    /// so was it correct?
+    ///
+    /// @param client_flags Mask of client FQDN flags which are true
+    /// @param response_flags Mask of expected FQDN flags in the response
+    void flagVsConfigScenario(const uint8_t client_flags,
+                       const uint8_t response_flags) {
+        Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
+                                          "myhost.example.com.",
+                                          Option4ClientFqdn::FULL, true);
+
+        // Process the request.
+        Pkt4Ptr reply;
+        ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+        // Verify the response and flags.
+        checkResponse(reply, DHCPACK, 1234);
+        checkFqdnFlags(reply, response_flags);
+
+        // There should be an NCR only if response S flag is 1.
+        /// @todo This logic will need to change if forward and reverse
+        /// updates are ever controlled independently.
+        if ((response_flags & Option4ClientFqdn::FLAG_S) == 0) {
+            ASSERT_EQ(0, srv_->name_change_reqs_.size());
+        } else {
+            // Verify that there is one NameChangeRequest generated as expected.
+            ASSERT_EQ(1, srv_->name_change_reqs_.size());
+            verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                                    reply->getYiaddr().toText(),
+                                    "myhost.example.com.",
+                                    "", // empty DHCID means don't check it
+                                    time(NULL) + subnet_->getValid(),
+                                    subnet_->getValid(), true);
+        }
+    }
+
+
     NakedDhcpv4Srv* srv_;
     NakedDhcpv4Srv* srv_;
 
 
 };
 };
@@ -348,21 +448,101 @@ TEST_F(NameDhcpv4SrvTest, dhcidComputeFromHWAddr) {
     EXPECT_EQ(dhcid_ref, dhcid.toStr());
     EXPECT_EQ(dhcid_ref, dhcid.toStr());
 }
 }
 
 
+// Tests the following scenario:
+//  - Updates are enabled
+//  - All overrides are off
+//  - Client requests forward update  (N = 0, S = 1)
+//
+//  Server should perform the update:
+//  - Reponse flags should N = 0, S = 1, O = 0
+//  - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, updatesEnabled) {
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_S),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_S));
+}
 
 
-// Test that server confirms to perform the forward and reverse DNS update,
-// when client asks for it.
-TEST_F(NameDhcpv4SrvTest, serverUpdateForwardFqdn) {
-    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
-                                        Option4ClientFqdn::FLAG_E |
-                                        Option4ClientFqdn::FLAG_S,
-                                        "myhost.example.com.",
-                                        Option4ClientFqdn::FULL,
-                                        true);
+// Tests the following scenario
+//  - Updates are disabled
+//  - Client requests forward update  (N = 0, S = 1)
+//
+//  Server should NOT perform updates:
+//   - Response flags should N = 1, S = 0, O = 1
+//   - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, updatesDisabled) {
+    disableD2();
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_S),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_N |
+                           Option4ClientFqdn::FLAG_O));
+}
 
 
-    testProcessFqdn(query,
-                    Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S,
-                    "myhost.example.com.");
+// Tests the following scenario:
+//  - Updates are enabled
+//  - All overrides are off.
+//  - Client requests no updates  (N = 1, S = 0)
+//
+//  Server should NOT perform updates:
+//  - Response flags should N = 1, S = 0, O = 0
+//  - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectNoUpdate) {
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_N),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_N));
+}
 
 
+// Tests the following scenario:
+//  - Updates are enabled
+//  - override-no-update is on
+//  - Client requests no updates  (N = 1, S = 0)
+//
+// Server should override "no update" request and perform updates:
+// - Response flags should be  N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideNoUpdate) {
+    enableD2(OVERRIDE_NO_UPDATE);
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_N),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_S |
+                           Option4ClientFqdn::FLAG_O));
+}
+
+// Tests the following scenario:
+//  - Updates are enabled
+//  - All overrides are off.
+//  - Client requests delegation  (N = 0, S = 0)
+//
+// Server should respect client's delegation request and NOT do updates:
+
+// - Response flags should be  N = 1, S = 0, O = 0
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectClientDelegation) {
+
+    flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+                         (Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_N));
+}
+
+// Tests the following scenario:
+//  - Updates are enabled
+//  - override-client-update is on.
+//  - Client requests delegation  (N = 0, S = 0)
+//
+// Server should override client's delegation request and do updates:
+// - Response flags should be  N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideClientDelegation) {
+    // Turn on override-client-update.
+    enableD2(OVERRIDE_CLIENT_UPDATE);
+
+    flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+                         (Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_S |
+                          Option4ClientFqdn::FLAG_O));
 }
 }
 
 
 // Test that server processes the Hostname option sent by a client and
 // Test that server processes the Hostname option sent by a client and
@@ -447,34 +627,6 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateForwardNoNameFqdn) {
 
 
 }
 }
 
 
-// Test server's response when client requests no DNS update.
-TEST_F(NameDhcpv4SrvTest, noUpdateFqdn) {
-    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
-                                        Option4ClientFqdn::FLAG_E |
-                                        Option4ClientFqdn::FLAG_N,
-                                        "myhost.example.com.",
-                                        Option4ClientFqdn::FULL,
-                                        true);
-    testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
-                    Option4ClientFqdn::FLAG_N,
-                    "myhost.example.com.");
-}
-
-// Test that server does not accept delegation of the forward DNS update
-// to a client.
-TEST_F(NameDhcpv4SrvTest, clientUpdateNotAllowedFqdn) {
-    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
-                                        Option4ClientFqdn::FLAG_E,
-                                        "myhost.example.com.",
-                                        Option4ClientFqdn::FULL,
-                                        true);
-
-    testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
-                    Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_O,
-                    "myhost.example.com.");
-
-}
-
 // Test that exactly one NameChangeRequest is generated when the new lease
 // Test that exactly one NameChangeRequest is generated when the new lease
 // has been acquired (old lease is NULL).
 // has been acquired (old lease is NULL).
 TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
 TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
@@ -600,18 +752,42 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
 
 
     // Verify that there is one NameChangeRequest generated.
     // Verify that there is one NameChangeRequest generated.
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
+
     // The hostname is generated from the IP address acquired (yiaddr).
     // The hostname is generated from the IP address acquired (yiaddr).
-    std::ostringstream hostname;
-    hostname << generatedNameFromAddress(reply->getYiaddr())
-             << ".example.com.";
+    std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
-                            reply->getYiaddr().toText(), hostname.str(),
+                            reply->getYiaddr().toText(), hostname,
                             "", // empty DHCID forces that it is not checked
                             "", // empty DHCID forces that it is not checked
                             time(NULL) + subnet_->getValid(),
                             time(NULL) + subnet_->getValid(),
                             subnet_->getValid(), true);
                             subnet_->getValid(), true);
 }
 }
 
 
 // Test that server generates client's hostname from the IP address assigned
 // Test that server generates client's hostname from the IP address assigned
+// to it when DHCPv4 Client FQDN option specifies an empty domain-name  AND
+// ddns updates are disabled.
+TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
+    disableD2();
+    Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+                                      Option4ClientFqdn::FLAG_E,
+                                      "", Option4ClientFqdn::PARTIAL, true);
+    Pkt4Ptr reply;
+    ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+    checkResponse(reply, DHCPACK, 1234);
+
+    Option4ClientFqdnPtr fqdn = getClientFqdnOption(reply);
+    ASSERT_TRUE(fqdn);
+
+    // The hostname is generated from the IP address acquired (yiaddr).
+    std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
+    EXPECT_EQ(hostname, fqdn->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, fqdn->getDomainNameType());
+}
+
+
+// Test that server generates client's hostname from the IP address assigned
 // to it when Hostname option carries the top level domain-name.
 // to it when Hostname option carries the top level domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
@@ -626,11 +802,12 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
 
 
     // Verify that there is one NameChangeRequest generated.
     // Verify that there is one NameChangeRequest generated.
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
+
     // The hostname is generated from the IP address acquired (yiaddr).
     // The hostname is generated from the IP address acquired (yiaddr).
-    std::ostringstream hostname;
-    hostname << generatedNameFromAddress(reply->getYiaddr()) << ".example.com.";
+    std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
-                            reply->getYiaddr().toText(), hostname.str(),
+                            reply->getYiaddr().toText(), hostname,
                             "", // empty DHCID forces that it is not checked
                             "", // empty DHCID forces that it is not checked
                             time(NULL), subnet_->getValid(), true);
                             time(NULL), subnet_->getValid(), true);
 }
 }
@@ -742,21 +919,23 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
                             time(NULL), subnet_->getValid(), true);
                             time(NULL), subnet_->getValid(), true);
 }
 }
 
 
-// Test that when the Release message is sent for the previously acquired
-// lease, then server genenerates a NameChangeRequest to remove the entries
-// corresponding to the lease being released.
+// Test that when a release message is sent for a previously acquired lease,
+// DDNS updates are enabled that the server genenerates a NameChangeRequest
+// to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
 TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
+    // Verify the updates are enabled.
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+    // Create and process a lease request so we have a lease to release.
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       Option4ClientFqdn::FLAG_E,
                                       "myhost.example.com.",
                                       "myhost.example.com.",
                                       Option4ClientFqdn::FULL, true);
                                       Option4ClientFqdn::FULL, true);
-
     Pkt4Ptr reply;
     Pkt4Ptr reply;
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
-
     checkResponse(reply, DHCPACK, 1234);
     checkResponse(reply, DHCPACK, 1234);
 
 
-    // Verify that there is one NameChangeRequest generated.
+    // Verify that there is one NameChangeRequest generated for lease.
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             reply->getYiaddr().toText(), "myhost.example.com.",
@@ -764,18 +943,57 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
                             "965B68B6D438D98E680BF10B09F3BCF",
                             "965B68B6D438D98E680BF10B09F3BCF",
                             time(NULL), subnet_->getValid(), true);
                             time(NULL), subnet_->getValid(), true);
 
 
-    // Create a Release message.
+    // Create and process the Release message.
     Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
     Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
     rel->setCiaddr(reply->getYiaddr());
     rel->setCiaddr(reply->getYiaddr());
     rel->setRemoteAddr(IOAddress("192.0.2.3"));
     rel->setRemoteAddr(IOAddress("192.0.2.3"));
     rel->addOption(generateClientId());
     rel->addOption(generateClientId());
     rel->addOption(srv_->getServerID());
     rel->addOption(srv_->getServerID());
-
     ASSERT_NO_THROW(srv_->processRelease(rel));
     ASSERT_NO_THROW(srv_->processRelease(rel));
 
 
     // The lease has been removed, so there should be a NameChangeRequest to
     // The lease has been removed, so there should be a NameChangeRequest to
     // remove corresponding DNS entries.
     // remove corresponding DNS entries.
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            reply->getYiaddr().toText(), "myhost.example.com.",
+                            "00010132E91AA355CFBB753C0F0497A5A940436"
+                            "965B68B6D438D98E680BF10B09F3BCF",
+                            time(NULL), subnet_->getValid(), true);
+}
+
+// Test that when the Release message is sent for a previously acquired lease
+// and DDNS updates are disabled that server does NOT generate a
+// NameChangeRequest to remove entries corresponding to the released lease.
+TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+    // Disable DDNS.
+    disableD2();
+    ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+    // Create and process a lease request so we have a lease to release.
+    Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+                                      Option4ClientFqdn::FLAG_E,
+                                      "myhost.example.com.",
+                                      Option4ClientFqdn::FULL, true);
+    Pkt4Ptr reply;
+    ASSERT_NO_THROW(reply = srv_->processRequest(req));
+    checkResponse(reply, DHCPACK, 1234);
+
+    // With DDNS updates disabled, there should be not be a NameChangeRequest
+    // for the add.
+    ASSERT_EQ(0, srv_->name_change_reqs_.size());
+
+    // Create and process the Release message.
+    Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+    rel->setCiaddr(reply->getYiaddr());
+    rel->setRemoteAddr(IOAddress("192.0.2.3"));
+    rel->addOption(generateClientId());
+    rel->addOption(srv_->getServerID());
+    ASSERT_NO_THROW(srv_->processRelease(rel));
+
+    // With DDNS updates disabled, there should be not be a NameChangeRequest
+    // for the remove.
+    ASSERT_EQ(0, srv_->name_change_reqs_.size());
 }
 }
 
 
+
 } // end of anonymous namespace
 } // end of anonymous namespace

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -217,7 +217,6 @@ setDomainName(const std::string& domain_name,
     } else {
     } else {
         try {
         try {
             domain_name_.reset(new isc::dns::Name(name));
             domain_name_.reset(new isc::dns::Name(name));
-            domain_name_type_ = name_type;
 
 
         } catch (const Exception& ex) {
         } catch (const Exception& ex) {
             isc_throw(InvalidOption4FqdnDomainName,
             isc_throw(InvalidOption4FqdnDomainName,
@@ -227,6 +226,8 @@ setDomainName(const std::string& domain_name,
 
 
         }
         }
     }
     }
+
+    domain_name_type_ = name_type;
 }
 }
 
 
 void
 void

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -188,7 +188,6 @@ setDomainName(const std::string& domain_name,
     } else {
     } else {
         try {
         try {
             domain_name_.reset(new isc::dns::Name(name, true));
             domain_name_.reset(new isc::dns::Name(name, true));
-            domain_name_type_ = name_type;
 
 
         } catch (const Exception& ex) {
         } catch (const Exception& ex) {
             isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
             isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
@@ -197,6 +196,8 @@ setDomainName(const std::string& domain_name,
 
 
         }
         }
     }
     }
+
+    domain_name_type_ = name_type;
 }
 }
 
 
 void
 void

+ 2 - 1
src/lib/dhcp/tests/option4_client_fqdn_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -615,6 +615,7 @@ TEST(Option4ClientFqdnTest, setDomainName) {
     // Empty domain name (partial). This should be successful.
     // Empty domain name (partial). This should be successful.
     ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
     ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
     EXPECT_TRUE(option->getDomainName().empty());
     EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
 
 
     // Fully qualified domain-names must not be empty.
     // Fully qualified domain-names must not be empty.
     EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),
     EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),

+ 2 - 1
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -537,6 +537,7 @@ TEST(Option6ClientFqdnTest, setDomainName) {
     // Empty domain name (partial). This should be successful.
     // Empty domain name (partial). This should be successful.
     ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
     ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
     EXPECT_TRUE(option->getDomainName().empty());
     EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
 
 
     // Fully qualified domain-names must not be empty.
     // Fully qualified domain-names must not be empty.
     EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
     EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),

+ 5 - 1
src/lib/dhcpsrv/cfgmgr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -363,6 +363,10 @@ CfgMgr::getD2ClientConfig() const {
     return (d2_client_mgr_.getD2ClientConfig());
     return (d2_client_mgr_.getD2ClientConfig());
 }
 }
 
 
+D2ClientMgr&
+CfgMgr::getD2ClientMgr() {
+    return (d2_client_mgr_);
+}
 
 
 CfgMgr::CfgMgr()
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR),
     : datadir_(DHCP_DATA_DIR),

+ 7 - 2
src/lib/dhcpsrv/cfgmgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -341,7 +341,7 @@ public:
     /// pointer.
     /// pointer.
     void setD2ClientConfig(D2ClientConfigPtr& new_config);
     void setD2ClientConfig(D2ClientConfigPtr& new_config);
 
 
-    /// @param Convenience method for checking if DHCP-DDNS updates are enabled.
+    /// @brief Convenience method for checking if DHCP-DDNS updates are enabled.
     ///
     ///
     /// @return True if the D2 configuration is enabled.
     /// @return True if the D2 configuration is enabled.
     bool ddnsEnabled();
     bool ddnsEnabled();
@@ -351,6 +351,11 @@ public:
     /// @return a reference to the current configuration pointer.
     /// @return a reference to the current configuration pointer.
     const D2ClientConfigPtr& getD2ClientConfig() const;
     const D2ClientConfigPtr& getD2ClientConfig() const;
 
 
+    /// @brief Fetches the DHCP-DDNS manager.
+    ///
+    /// @return a reference to the DHCP-DDNS manager.
+    D2ClientMgr& getD2ClientMgr();
+
 protected:
 protected:
 
 
     /// @brief Protected constructor.
     /// @brief Protected constructor.

+ 91 - 13
src/lib/dhcpsrv/d2_client.cc

@@ -29,7 +29,6 @@ D2ClientConfig::D2ClientConfig(const  bool enable_updates,
                                      NameChangeProtocol& ncr_protocol,
                                      NameChangeProtocol& ncr_protocol,
                                const dhcp_ddns::
                                const dhcp_ddns::
                                      NameChangeFormat& ncr_format,
                                      NameChangeFormat& ncr_format,
-                               const bool remove_on_renew,
                                const bool always_include_fqdn,
                                const bool always_include_fqdn,
                                const bool override_no_update,
                                const bool override_no_update,
                                const bool override_client_update,
                                const bool override_client_update,
@@ -41,7 +40,6 @@ D2ClientConfig::D2ClientConfig(const  bool enable_updates,
     server_port_(server_port),
     server_port_(server_port),
     ncr_protocol_(ncr_protocol),
     ncr_protocol_(ncr_protocol),
     ncr_format_(ncr_format),
     ncr_format_(ncr_format),
-    remove_on_renew_(remove_on_renew),
     always_include_fqdn_(always_include_fqdn),
     always_include_fqdn_(always_include_fqdn),
     override_no_update_(override_no_update),
     override_no_update_(override_no_update),
     override_client_update_(override_client_update),
     override_client_update_(override_client_update),
@@ -57,13 +55,12 @@ D2ClientConfig::D2ClientConfig()
       server_port_(0),
       server_port_(0),
       ncr_protocol_(dhcp_ddns::NCR_UDP),
       ncr_protocol_(dhcp_ddns::NCR_UDP),
       ncr_format_(dhcp_ddns::FMT_JSON),
       ncr_format_(dhcp_ddns::FMT_JSON),
-      remove_on_renew_(false),
       always_include_fqdn_(false),
       always_include_fqdn_(false),
       override_no_update_(false),
       override_no_update_(false),
       override_client_update_(false),
       override_client_update_(false),
       replace_client_name_(false),
       replace_client_name_(false),
-      generated_prefix_(""),
-      qualifying_suffix_("") {
+      generated_prefix_("myhost"),
+      qualifying_suffix_("example.com") {
     validateContents();
     validateContents();
 }
 }
 
 
@@ -83,9 +80,8 @@ D2ClientConfig::validateContents() {
                     << " is not yet supported");
                     << " is not yet supported");
     }
     }
 
 
-    // @todo perhaps more validation we should do yet?
-    // Are there any invalid combinations of options we need to test against?
-    // Also do we care about validating contents if it's disabled?
+    /// @todo perhaps more validation we should do yet?
+    /// Are there any invalid combinations of options we need to test against?
 }
 }
 
 
 bool
 bool
@@ -95,7 +91,6 @@ D2ClientConfig::operator == (const D2ClientConfig& other) const {
             (server_port_ == other.server_port_) &&
             (server_port_ == other.server_port_) &&
             (ncr_protocol_ == other.ncr_protocol_) &&
             (ncr_protocol_ == other.ncr_protocol_) &&
             (ncr_format_ == other.ncr_format_) &&
             (ncr_format_ == other.ncr_format_) &&
-            (remove_on_renew_ == other.remove_on_renew_) &&
             (always_include_fqdn_ == other.always_include_fqdn_) &&
             (always_include_fqdn_ == other.always_include_fqdn_) &&
             (override_no_update_ == other.override_no_update_) &&
             (override_no_update_ == other.override_no_update_) &&
             (override_client_update_ == other.override_client_update_) &&
             (override_client_update_ == other.override_client_update_) &&
@@ -119,7 +114,6 @@ D2ClientConfig::toText() const {
                << ", server_port: " << server_port_
                << ", server_port: " << server_port_
                << ", ncr_protocol: " << ncr_protocol_
                << ", ncr_protocol: " << ncr_protocol_
                << ", ncr_format: " << ncr_format_
                << ", ncr_format: " << ncr_format_
-               << ", remove_on_renew: " << (remove_on_renew_ ? "yes" : "no")
                << ", always_include_fqdn: " << (always_include_fqdn_ ?
                << ", always_include_fqdn: " << (always_include_fqdn_ ?
                                                 "yes" : "no")
                                                 "yes" : "no")
                << ", override_no_update: " << (override_no_update_ ?
                << ", override_no_update: " << (override_no_update_ ?
@@ -142,7 +136,7 @@ operator<<(std::ostream& os, const D2ClientConfig& config) {
 }
 }
 
 
 D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) {
 D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) {
-    // Default contstructor initializes with a disabled config.
+    // Default constructor initializes with a disabled configuration.
 }
 }
 
 
 D2ClientMgr::~D2ClientMgr(){
 D2ClientMgr::~D2ClientMgr(){
@@ -159,8 +153,8 @@ D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
     // scenarios:
     // scenarios:
     // 1. D2 was enabled but now it is disabled
     // 1. D2 was enabled but now it is disabled
     //     - destroy the sender, flush any queued
     //     - destroy the sender, flush any queued
-    // 2. D2 is still enabled but server params have changed
-    //     - preserve any queued,  reconnect based on sender params
+    // 2. D2 is still enabled but server parameters have changed
+    //     - preserve any queued,  reconnect based on sender parameters
     // 3. D2 was was disabled now it is enabled.
     // 3. D2 was was disabled now it is enabled.
     //     - create sender
     //     - create sender
     //
     //
@@ -181,5 +175,89 @@ D2ClientMgr::getD2ClientConfig() const {
     return (d2_client_config_);
     return (d2_client_config_);
 }
 }
 
 
+void
+D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
+                         bool& server_s, bool& server_n) const {
+    // Per RFC 4702 & 4704, the client N and S flags allow the client to
+    // request one of three options:
+    //
+    //  N flag  S flag   Option
+    // ------------------------------------------------------------------
+    //    0       0      client wants to do forward updates (section 3.2)
+    //    0       1      client wants server to do forward updates (section 3.3)
+    //    1       0      client wants no one to do updates (section 3.4)
+    //    1       1      invalid combination
+    // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3)
+    //
+    // Make a bit mask from the client's flags and use it to set the response
+    // flags accordingly.
+    const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));
+
+    switch (mask) {
+    case 0:
+        // If updates are enabled and we are overriding client delegation
+        // then S flag should be true.
+        server_s = (d2_client_config_->getEnableUpdates() &&
+                    d2_client_config_->getOverrideClientUpdate());
+        break;
+
+    case 1:
+        server_s = d2_client_config_->getEnableUpdates();
+        break;
+
+    case 2:
+        // If updates are enabled and we are overriding "no updates" then
+        // S flag should be true.
+        server_s = (d2_client_config_->getEnableUpdates() &&
+                    d2_client_config_->getOverrideNoUpdate());
+        break;
+
+    default:
+        // RFCs declare this an invalid combination.
+        isc_throw(isc::BadValue,
+                  "Invalid client FQDN - N and S cannot both be 1");
+        break;
+    }
+
+    /// @todo Currently we are operating under the premise that N should be 1
+    /// if the server is not doing updates nor do we have configuration
+    /// controls to govern forward and reverse updates independently.
+    /// In addition, the client FQDN flags cannot explicitly suggest what to
+    /// do with reverse updates. They request either forward updates or no
+    /// updates.  In other words, the client cannot request the server do or
+    /// not do reverse updates.  For now, we are either going to do updates in
+    /// both directions or none at all.  If and when additional configuration
+    /// parameters are added this logic will have to be reassessed.
+    server_n = !server_s;
+}
+
+std::string
+D2ClientMgr::generateFqdn(const asiolink::IOAddress& address) const {
+    std::string hostname = address.toText();
+    std::replace(hostname.begin(), hostname.end(),
+                 (address.isV4() ? '.' : ':'), '-');
+
+    std::ostringstream gen_name;
+    gen_name << d2_client_config_->getGeneratedPrefix() << "-" << hostname;
+    return (qualifyName(gen_name.str()));
+}
+
+std::string
+D2ClientMgr::qualifyName(const std::string& partial_name) const {
+    std::ostringstream gen_name;
+    gen_name << partial_name << "." << d2_client_config_->getQualifyingSuffix();
+
+    // Tack on a trailing dot in case suffix doesn't have one.
+    std::string str = gen_name.str();
+    size_t len = str.length();
+    if ((len > 0) && (str[len - 1] != '.')) {
+        gen_name << ".";
+    }
+
+    return (gen_name.str());
+}
+
+
+
 };  // namespace dhcp
 };  // namespace dhcp
 };  // namespace isc
 };  // namespace isc

+ 123 - 19
src/lib/dhcpsrv/d2_client.h

@@ -64,11 +64,6 @@ public:
     /// Currently only UDP is supported.
     /// Currently only UDP is supported.
     /// @param ncr_format Format of the b10-dhcp-ddns requests.
     /// @param ncr_format Format of the b10-dhcp-ddns requests.
     /// Currently only JSON format is supported.
     /// Currently only JSON format is supported.
-    /// @param remove_on_renew Enables DNS Removes when renewing a lease
-    /// If true, Kea should request an explicit DNS remove prior to requesting
-    /// a DNS update when renewing a lease.
-    /// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove
-    /// is unnecessary).
     /// @param always_include_fqdn Enables always including the FQDN option in
     /// @param always_include_fqdn Enables always including the FQDN option in
     /// DHCP responses.
     /// DHCP responses.
     /// @param override_no_update Enables updates, even if clients request no
     /// @param override_no_update Enables updates, even if clients request no
@@ -86,7 +81,6 @@ public:
                    const size_t server_port,
                    const size_t server_port,
                    const dhcp_ddns::NameChangeProtocol& ncr_protocol,
                    const dhcp_ddns::NameChangeProtocol& ncr_protocol,
                    const dhcp_ddns::NameChangeFormat& ncr_format,
                    const dhcp_ddns::NameChangeFormat& ncr_format,
-                   const bool remove_on_renew,
                    const bool always_include_fqdn,
                    const bool always_include_fqdn,
                    const bool override_no_update,
                    const bool override_no_update,
                    const bool override_client_update,
                    const bool override_client_update,
@@ -126,11 +120,6 @@ public:
         return(ncr_format_);
         return(ncr_format_);
     }
     }
 
 
-    /// @brief Return whether or not removes should be sent for lease renewals.
-    bool getRemoveOnRenew() const {
-        return(remove_on_renew_);
-    }
-
     /// @brief Return whether or not FQDN is always included in DHCP responses.
     /// @brief Return whether or not FQDN is always included in DHCP responses.
     bool getAlwaysIncludeFqdn() const {
     bool getAlwaysIncludeFqdn() const {
         return(always_include_fqdn_);
         return(always_include_fqdn_);
@@ -189,20 +178,13 @@ private:
     size_t server_port_;
     size_t server_port_;
 
 
     /// @brief The socket protocol to use with b10-dhcp-ddns.
     /// @brief The socket protocol to use with b10-dhcp-ddns.
-    /// Currently only UPD is supported.
+    /// Currently only UDP is supported.
     dhcp_ddns::NameChangeProtocol ncr_protocol_;
     dhcp_ddns::NameChangeProtocol ncr_protocol_;
 
 
     /// @brief Format of the b10-dhcp-ddns requests.
     /// @brief Format of the b10-dhcp-ddns requests.
     /// Currently only JSON format is supported.
     /// Currently only JSON format is supported.
     dhcp_ddns::NameChangeFormat ncr_format_;
     dhcp_ddns::NameChangeFormat ncr_format_;
 
 
-    /// @brief Should Kea request a DNS Remove when renewing a lease.
-    /// If true, Kea should request an explicit DNS remove prior to requesting
-    /// a DNS update when renewing a lease.
-    /// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove
-    /// is unnecessary).
-    bool remove_on_renew_;
-
     /// @brief Should Kea always include the FQDN option in its response.
     /// @brief Should Kea always include the FQDN option in its response.
     bool always_include_fqdn_;
     bool always_include_fqdn_;
 
 
@@ -263,11 +245,133 @@ public:
     /// @return a reference to the current configuration pointer.
     /// @return a reference to the current configuration pointer.
     const D2ClientConfigPtr& getD2ClientConfig() const;
     const D2ClientConfigPtr& getD2ClientConfig() const;
 
 
+    /// @brief Determines server flags based on configuration and  client flags.
+    ///
+    /// This method uses input values for the client's FQDN S and N flags, in
+    /// conjunction with the configuration parameters updates-enabled, override-
+    /// no-updates, and override-client-updates to determine the values that
+    /// should be used for the server's FQDN S and N flags.
+    /// The logic in this method is based upon RFCs 4702 and 4704.
+    ///
+    /// @param client_s  S Flag from the client's FQDN
+    /// @param client_n  N Flag from the client's FQDN
+    /// @param server_s [out] S Flag for the server's FQDN
+    /// @param server_n [out] N Flag for the server's FQDN
+    ///
+    /// @throw isc::BadValue if client_s and client_n are both 1 as this is
+    /// an invalid combination per RFCs.
+    void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
+                     bool& server_n) const;
+
+    /// @brief Builds a FQDN based on the configuration and given IP address.
+    ///
+    /// Using the current values for generated-prefix, qualifying-suffix and
+    /// an IP address, this method constructs a fully qualified domain name.
+    /// It supports both IPv4 and IPv6 addresses.  The format of the name
+    /// is as follows:
+    ///
+    ///     <generated-prefix>-<ip address>.<qualifying-suffix>.
+    ///
+    /// <ip-address> is the result of IOAddress.toText() with the delimiters
+    /// ('.' for IPv4 or ':' for IPv6) replaced with a hyphen, '-'.
+    ///
+    /// @param address IP address from which to derive the name (IPv4 or IPv6)
+    ///
+    /// @return std::string containing the generated name.
+    std::string generateFqdn(const asiolink::IOAddress& address) const;
+
+    /// @brief Adds a qualifying suffix to a given domain name
+    ///
+    /// Constructs a FQDN based on the configured qualifying-suffix and
+    /// a partial domain name as follows:
+    ///
+    ///     <partial_name>.<qualifying-suffix>.
+    /// Note it will add a trailing '.' should qualifying-suffix not end with
+    /// one.
+    ///
+    /// @param partial_name domain name to qualify
+    ///
+    /// @return std::string containing the qualified name.
+    std::string qualifyName(const std::string& partial_name) const;
+
+    /// @brief Set server FQDN flags based on configuration and a given FQDN
+    ///
+    /// Templated wrapper around the analyzeFqdn() allowing that method to
+    /// be used for either IPv4 or IPv6 processing.  This methods resets all
+    /// of the flags in the response to zero and then sets the S,N, and O
+    /// flags.  Any other flags are the responsiblity of the invoking layer.
+    ///
+    /// @param fqdn FQDN option from which to read client (inbound) flags
+    /// @param fqdn_resp FQDN option to update with the server (outbound) flags
+    /// @tparam T FQDN Option class containing the FQDN data such as
+    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+    template <class T>
+    void adjustFqdnFlags(const T& fqdn, T& fqdn_resp);
+
+    /// @brief Set server FQDN name based on configuration and a given FQDN
+    ///
+    /// Templated method which adjusts the domain name value and type in
+    /// a server FQDN from a client (inbound) FQDN and the current
+    /// configuration.  The logic is as follows:
+    ///
+    /// If replace-client-name is true or the supplied name is empty, the
+    /// server FQDN is set to ""/PARTIAL.
+    ///
+    /// If replace-client-name is false and the supplied name is a partial
+    /// name the server FQDN is set to the supplied name qualified by
+    /// appending the qualifying-suffix.
+    ///
+    /// If replace-client-name is false and the supplied name is a fully
+    /// qualified name, set the server FQDN to the supplied name.
+    ///
+    /// @param fqdn FQDN option from which to get client (inbound) name
+    /// @param fqdn_resp FQDN option to update with the adjusted name
+    /// @tparam T  FQDN Option class containing the FQDN data such as
+    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+    template <class T>
+    void adjustDomainName(const T& fqdn, T& fqdn_resp);
+
 private:
 private:
     /// @brief Container class for DHCP-DDNS configuration parameters.
     /// @brief Container class for DHCP-DDNS configuration parameters.
     D2ClientConfigPtr d2_client_config_;
     D2ClientConfigPtr d2_client_config_;
 };
 };
 
 
+template <class T>
+void
+D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
+    bool server_s = false;
+    bool server_n = false;
+    analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
+                server_s, server_n);
+
+    // Reset the flags to zero to avoid triggering N and S both 1 check.
+    fqdn_resp.resetFlags();
+
+    // Set S and N flags.
+    fqdn_resp.setFlag(T::FLAG_S, server_s);
+    fqdn_resp.setFlag(T::FLAG_N, server_n);
+
+    // Set O flag true if server S overrides client S.
+    fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
+}
+
+
+template <class T>
+void
+D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
+    // If we're configured to replace it or the supplied name is blank
+    // set the response name to blank.
+    if (d2_client_config_->getReplaceClientName() ||
+        fqdn.getDomainName().empty()) {
+        fqdn_resp.setDomainName("", T::PARTIAL);
+    } else {
+        // If the supplied name is partial, qualify it by adding the suffix.
+        if (fqdn.getDomainNameType() == T::PARTIAL) {
+            fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName()), T::FULL);
+        }
+    }
+}
+
 /// @brief Defines a pointer for D2ClientMgr instances.
 /// @brief Defines a pointer for D2ClientMgr instances.
 typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
 typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
 
 

+ 6 - 7
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -1200,10 +1200,11 @@ D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
     }
     }
 
 
     bool enable_updates = boolean_values_->getParam("enable-updates");
     bool enable_updates = boolean_values_->getParam("enable-updates");
-    if (!enable_updates) {
-        // If it's not enabled, don't bother validating the rest.  This
-        // allows for an abbreviated config entry that only contains
-        // the flag.  The default constructor creates a disabled instance.
+    if (!enable_updates && (client_config->mapValue().size() == 1)) {
+        // If enable-updates is the only parameter and it is false then
+        // we're done.  This allows for an abbreviated configuration entry
+        // that only contains that flag.  Use the default D2ClientConfig
+        // constructor to a create a disabled instance.
         local_client_config_.reset(new D2ClientConfig());
         local_client_config_.reset(new D2ClientConfig());
         return;
         return;
     }
     }
@@ -1225,7 +1226,6 @@ D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
     std::string qualifying_suffix = string_values_->
     std::string qualifying_suffix = string_values_->
                                     getParam("qualifying-suffix");
                                     getParam("qualifying-suffix");
 
 
-    bool remove_on_renew = boolean_values_->getParam("remove-on-renew");
     bool always_include_fqdn = boolean_values_->getParam("always-include-fqdn");
     bool always_include_fqdn = boolean_values_->getParam("always-include-fqdn");
     bool override_no_update = boolean_values_->getParam("override-no-update");
     bool override_no_update = boolean_values_->getParam("override-no-update");
     bool override_client_update = boolean_values_->
     bool override_client_update = boolean_values_->
@@ -1235,7 +1235,7 @@ D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
     // Attempt to create the new client config.
     // Attempt to create the new client config.
     local_client_config_.reset(new D2ClientConfig(enable_updates, server_ip,
     local_client_config_.reset(new D2ClientConfig(enable_updates, server_ip,
                                                   server_port, ncr_protocol,
                                                   server_port, ncr_protocol,
-                                                  ncr_format, remove_on_renew,
+                                                  ncr_format,
                                                   always_include_fqdn,
                                                   always_include_fqdn,
                                                   override_no_update,
                                                   override_no_update,
                                                   override_client_update,
                                                   override_client_update,
@@ -1256,7 +1256,6 @@ D2ClientConfigParser::createConfigParser(const std::string& config_id) {
         (config_id.compare("qualifying-suffix") == 0)) {
         (config_id.compare("qualifying-suffix") == 0)) {
         parser = new StringParser(config_id, string_values_);
         parser = new StringParser(config_id, string_values_);
     } else if ((config_id.compare("enable-updates") == 0) ||
     } else if ((config_id.compare("enable-updates") == 0) ||
-        (config_id.compare("remove-on-renew") == 0) ||
         (config_id.compare("always-include-fqdn") == 0) ||
         (config_id.compare("always-include-fqdn") == 0) ||
         (config_id.compare("allow-client-update") == 0) ||
         (config_id.compare("allow-client-update") == 0) ||
         (config_id.compare("override-no-update") == 0) ||
         (config_id.compare("override-no-update") == 0) ||

+ 8 - 6
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -672,15 +672,17 @@ TEST_F(CfgMgrTest, echoClientId) {
 
 
 // This test checks the D2ClientMgr wrapper methods.
 // This test checks the D2ClientMgr wrapper methods.
 TEST_F(CfgMgrTest, d2ClientConfig) {
 TEST_F(CfgMgrTest, d2ClientConfig) {
-    // After CfgMgr construction, D2 configuration should be disabled.
-    // Fetch it and verify this is the case.
+    // After CfgMgr construction, D2ClientMgr member should be initialized
+    // with a D2 configuration that is disabled.
+    // Verify we can Fetch the mgr.
+    D2ClientMgr d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    EXPECT_FALSE(d2_mgr.ddnsEnabled());
+
+    // Make sure the convenience method fetches the config correctly.
     D2ClientConfigPtr original_config = CfgMgr::instance().getD2ClientConfig();
     D2ClientConfigPtr original_config = CfgMgr::instance().getD2ClientConfig();
     ASSERT_TRUE(original_config);
     ASSERT_TRUE(original_config);
     EXPECT_FALSE(original_config->getEnableUpdates());
     EXPECT_FALSE(original_config->getEnableUpdates());
 
 
-    // Make sure convenience method agrees.
-    EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
-
     // Verify that we cannot set the configuration to an empty pointer.
     // Verify that we cannot set the configuration to an empty pointer.
     D2ClientConfigPtr new_cfg;
     D2ClientConfigPtr new_cfg;
     ASSERT_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg), D2ClientError);
     ASSERT_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg), D2ClientError);
@@ -689,7 +691,7 @@ TEST_F(CfgMgrTest, d2ClientConfig) {
     ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                                  true, true, true, true, true,
+                                  true, true, true, true,
                                   "pre-fix", "suf-fix")));
                                   "pre-fix", "suf-fix")));
 
 
     // Verify that we can assign a new, non-empty configuration.
     // Verify that we can assign a new, non-empty configuration.

+ 530 - 26
src/lib/dhcpsrv/tests/d2_client_unittest.cc

@@ -13,6 +13,8 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcpsrv/d2_client.h>
 #include <dhcpsrv/d2_client.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -41,7 +43,6 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
     size_t server_port = 477;
     size_t server_port = 477;
     dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
     dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
     dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
     dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
-    bool remove_on_renew = true;
     bool always_include_fqdn = true;
     bool always_include_fqdn = true;
     bool override_no_update = true;
     bool override_no_update = true;
     bool override_client_update = true;
     bool override_client_update = true;
@@ -56,7 +57,6 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
                                                           server_port,
                                                           server_port,
                                                           ncr_protocol,
                                                           ncr_protocol,
                                                           ncr_format,
                                                           ncr_format,
-                                                          remove_on_renew,
                                                           always_include_fqdn,
                                                           always_include_fqdn,
                                                           override_no_update,
                                                           override_no_update,
                                                          override_client_update,
                                                          override_client_update,
@@ -73,7 +73,6 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
     EXPECT_EQ(d2_client_config->getServerPort(), server_port);
     EXPECT_EQ(d2_client_config->getServerPort(), server_port);
     EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol);
     EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol);
     EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format);
     EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format);
-    EXPECT_EQ(d2_client_config->getRemoveOnRenew(), remove_on_renew);
     EXPECT_EQ(d2_client_config->getAlwaysIncludeFqdn(), always_include_fqdn);
     EXPECT_EQ(d2_client_config->getAlwaysIncludeFqdn(), always_include_fqdn);
     EXPECT_EQ(d2_client_config->getOverrideNoUpdate(), override_no_update);
     EXPECT_EQ(d2_client_config->getOverrideNoUpdate(), override_no_update);
     EXPECT_EQ(d2_client_config->getOverrideClientUpdate(),
     EXPECT_EQ(d2_client_config->getOverrideClientUpdate(),
@@ -94,7 +93,6 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
                                                        server_port,
                                                        server_port,
                                                        dhcp_ddns::NCR_TCP,
                                                        dhcp_ddns::NCR_TCP,
                                                        ncr_format,
                                                        ncr_format,
-                                                       remove_on_renew,
                                                        always_include_fqdn,
                                                        always_include_fqdn,
                                                        override_no_update,
                                                        override_no_update,
                                                        override_client_update,
                                                        override_client_update,
@@ -119,7 +117,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, true,
+                    true, true, true, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(ref_config);
     ASSERT_TRUE(ref_config);
 
 
@@ -127,7 +125,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, true,
+                    true, true, true, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_TRUE(*ref_config == *test_config);
     EXPECT_TRUE(*ref_config == *test_config);
@@ -137,7 +135,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, true,
+                    true, true, true, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -147,7 +145,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     test_address, 477,
                     test_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, true,
+                    true, true, true, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -157,17 +155,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 333,
                     ref_address, 333,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, true,
-                    "pre-fix", "suf-fix")));
-    ASSERT_TRUE(test_config);
-    EXPECT_FALSE(*ref_config == *test_config);
-    EXPECT_TRUE(*ref_config != *test_config);
-
-    // Check a configuration that differs only by remove_on_renew.
-    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
-                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    false, true, true, true, true,
+                    true, true, true, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -177,7 +165,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, false, true, true, true,
+                    false, true, true, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -187,7 +175,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, false, true, true,
+                    true, false, true, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -197,7 +185,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, false, true,
+                    true, true, false, true,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -207,7 +195,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, false,
+                    true, true, true, false,
                     "pre-fix", "suf-fix")));
                     "pre-fix", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -217,7 +205,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, true,
+                    true, true, true, true,
                     "bogus", "suf-fix")));
                     "bogus", "suf-fix")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -227,7 +215,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
                     ref_address, 477,
                     ref_address, 477,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                    true, true, true, true, true,
+                    true, true, true, true,
                     "pre-fix", "bogus")));
                     "pre-fix", "bogus")));
     ASSERT_TRUE(test_config);
     ASSERT_TRUE(test_config);
     EXPECT_FALSE(*ref_config == *test_config);
     EXPECT_FALSE(*ref_config == *test_config);
@@ -270,7 +258,7 @@ TEST(D2ClientMgr, validConfig) {
     ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
     ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
-                                  true, true, true, true, true,
+                                  true, true, true, true,
                                   "pre-fix", "suf-fix")));
                                   "pre-fix", "suf-fix")));
 
 
     // Verify that we can assign a new, non-empty configuration.
     // Verify that we can assign a new, non-empty configuration.
@@ -291,4 +279,520 @@ TEST(D2ClientMgr, validConfig) {
 }
 }
 
 
 
 
+/// @brief Tests that analyzeFqdn detects invalid combination of both the
+/// client S and N flags set to true.
+TEST(D2ClientMgr, analyzeFqdnInvalidCombination) {
+    D2ClientMgr mgr;
+    bool server_s = false;
+    bool server_n = false;
+
+    // Create disabled configuration.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig()));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // client S=1 N=1 is invalid.  analyzeFqdn should throw.
+    ASSERT_THROW(mgr.analyzeFqdn(true, true, server_s, server_n),
+                 isc::BadValue);
+
+    // Create enabled configuration with all controls off (no overrides).
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, false, false,
+                                  "pre-fix", "suf-fix")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+
+    // client S=1 N=1 is invalid.  analyzeFqdn should throw.
+    ASSERT_THROW(mgr.analyzeFqdn(true, true, server_s, server_n),
+                 isc::BadValue);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and all overrides are off.
+TEST(D2ClientMgr, analyzeFqdnEnabledNoOverrides) {
+    D2ClientMgr mgr;
+    bool server_s = false;
+    bool server_n = false;
+
+    // Create enabled configuration with all controls off (no overrides).
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, false, false,
+                                  "pre-fix", "suf-fix")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(cfg->getOverrideClientUpdate());
+    ASSERT_FALSE(cfg->getOverrideNoUpdate());
+
+    // client S=0 N=0 means client wants to do forward update.
+    // server S should be 0 (server is not doing forward updates)
+    // and server N should be 1 (server doing no updates)
+    mgr.analyzeFqdn(false, false, server_s, server_n);
+    EXPECT_FALSE(server_s);
+    EXPECT_TRUE(server_n);
+
+    // client S=1 N=0 means client wants server to do forward update.
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    mgr.analyzeFqdn(true, false, server_s, server_n);
+    EXPECT_TRUE(server_s);
+    EXPECT_FALSE(server_n);
+
+
+    // client S=0 N=1 means client wants no one to do forward updates.
+    // server S should be 0 (server is  not forward updates)
+    // and server N should be 1 (server is not doing any updates)
+    mgr.analyzeFqdn(false, true, server_s, server_n);
+    EXPECT_FALSE(server_s);
+    EXPECT_TRUE(server_n);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and override-no-update is on.
+TEST(D2ClientMgr, analyzeFqdnEnabledOverrideNoUpdate) {
+    D2ClientMgr mgr;
+    bool server_s = false;
+    bool server_n = false;
+
+    // Create enabled configuration with OVERRIDE_NO_UPDATE on.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, true, false, false,
+                                  "pre-fix", "suf-fix")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(cfg->getOverrideNoUpdate());
+    ASSERT_FALSE(cfg->getOverrideClientUpdate());
+
+    // client S=0 N=0 means client wants to do forward update.
+    // server S should be 0 (server is not doing forward updates)
+    // and server N should be 1 (server is not doing any updates)
+    mgr.analyzeFqdn(false, false, server_s, server_n);
+    EXPECT_FALSE(server_s);
+    EXPECT_TRUE(server_n);
+
+    // client S=1 N=0 means client wants server to do forward update.
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    mgr.analyzeFqdn(true, false, server_s, server_n);
+    EXPECT_TRUE(server_s);
+    EXPECT_FALSE(server_n);
+
+    // client S=0 N=1 means client wants no one to do forward updates.
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server is doing updates)
+    mgr.analyzeFqdn(false, true, server_s, server_n);
+    EXPECT_TRUE(server_s);
+    EXPECT_FALSE(server_n);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and override-client-update is on.
+TEST(D2ClientMgr, analyzeFqdnEnabledOverrideClientUpdate) {
+    D2ClientMgr mgr;
+    bool server_s = false;
+    bool server_n = false;
+
+    // Create enabled configuration with OVERRIDE_CLIENT_UPDATE on.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, true, false,
+                                  "pre-fix", "suf-fix")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(cfg->getOverrideNoUpdate());
+    ASSERT_TRUE(cfg->getOverrideClientUpdate());
+
+    // client S=0 N=0 means client wants to do forward update.
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    mgr.analyzeFqdn(false, false, server_s, server_n);
+    EXPECT_TRUE(server_s);
+    EXPECT_FALSE(server_n);
+
+    // client S=1 N=0 means client wants server to do forward update.
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    mgr.analyzeFqdn(true, false, server_s, server_n);
+    EXPECT_TRUE(server_s);
+    EXPECT_FALSE(server_n);
+
+    // client S=0 N=1 means client wants no one to do forward updates.
+    // server S should be 0 (server is  not forward updates)
+    // and server N should be 1 (server is not doing any updates)
+    mgr.analyzeFqdn(false, true, server_s, server_n);
+    EXPECT_FALSE(server_s);
+    EXPECT_TRUE(server_n);
+}
+
+/// @brief Verifies the adustFqdnFlags template with Option4ClientFqdn objects.
+/// Ensures that the method can set the N, S, and O flags properly.
+/// Other permutations are covered by analyzeFqdnFlag tests.
+TEST(D2ClientMgr, adjustFqdnFlagsV4) {
+    D2ClientMgr mgr;
+    Option4ClientFqdnPtr request;
+    Option4ClientFqdnPtr response;
+
+    // Create enabled configuration and override-no-update on.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, true, false, false,
+                                  "pre-fix", "suf-fix")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(cfg->getOverrideNoUpdate());
+    ASSERT_FALSE(cfg->getOverrideClientUpdate());
+
+    // client S=0 N=0 means client wants to do forward update.
+    // server S should be 0 (server is not doing forward updates)
+    // and server N should be 1 (server doing no updates)
+    // and server O should be 0
+    request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                        "", Option4ClientFqdn::PARTIAL));
+    response.reset(new Option4ClientFqdn(*request));
+    response->resetFlags();
+
+    mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response);
+    EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_O));
+
+    // client S=1 N=0 means client wants server to do forward update.
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    // and server O should be 0
+    request.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                        Option4ClientFqdn::RCODE_CLIENT(),
+                                        "", Option4ClientFqdn::PARTIAL));
+    response.reset(new Option4ClientFqdn(*request));
+    response->resetFlags();
+
+    mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response);
+    EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_O));
+
+    // client S=0 N=1 means client wants no one to do updates
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    // and O should be 1 (overriding client S)
+    request.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_N,
+                                        Option4ClientFqdn::RCODE_CLIENT(),
+                                        "", Option4ClientFqdn::PARTIAL));
+    response.reset(new Option4ClientFqdn(*request));
+    response->resetFlags();
+
+    mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response);
+    EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_O));
+}
+
+/// @brief Tests the qualifyName method's ability to construct FQDNs
+TEST(D2ClientMgr, qualifyName) {
+    D2ClientMgr mgr;
+
+    // Create enabled configuration.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, true, false,
+                                  "prefix", "suffix.com")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+    // Verify that the qualifying suffix gets appended with trailing dot added.
+    std::string partial_name = "somehost";
+    std::string qualified_name = mgr.qualifyName(partial_name);
+    EXPECT_EQ("somehost.suffix.com.", qualified_name);
+
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, true, false,
+                                  "prefix", "hasdot.com.")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+    // Verify that the qualifying suffix gets appended without dot added.
+    qualified_name = mgr.qualifyName(partial_name);
+    EXPECT_EQ("somehost.hasdot.com.", qualified_name);
+}
+
+
+/// @brief Tests the generateFdqn method's ability to construct FQDNs
+TEST(D2ClientMgr, generateFqdn) {
+    D2ClientMgr mgr;
+
+    // Create enabled configuration.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, true, false,
+                                  "prefix", "suffix.com")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+    // Verify that it works with an IPv4 address.
+    asiolink::IOAddress v4address("192.0.2.75");
+    EXPECT_EQ("prefix-192-0-2-75.suffix.com.", mgr.generateFqdn(v4address));
+
+    // Verify that it works with an IPv6 address.
+    asiolink::IOAddress v6address("2001:db8::2");
+    EXPECT_EQ("prefix-2001-db8--2.suffix.com.", mgr.generateFqdn(v6address));
+
+    // Create a disabled config.
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig()));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+    // Verify names generate properly with a disabled configuration.
+    EXPECT_EQ("myhost-192-0-2-75.example.com.", mgr.generateFqdn(v4address));
+    EXPECT_EQ("myhost-2001-db8--2.example.com.", mgr.generateFqdn(v6address));
+}
+
+/// @brief Tests adjustDomainName template method with Option4ClientFqdn
+TEST(D2ClientMgr, adjustDomainNameV4) {
+    D2ClientMgr mgr;
+    Option4ClientFqdnPtr request;
+    Option4ClientFqdnPtr response;
+
+    // Create enabled configuration.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, false, false,
+                                  "prefix", "suffix.com")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_FALSE(cfg->getReplaceClientName());
+
+    // replace-client-name is false, client passes in empty fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                        "", Option4ClientFqdn::PARTIAL));
+    response.reset(new Option4ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+
+
+    // replace-client-name is false, client passes in a partial fqdn
+    // response should contain client's name plus the qualifying suffix.
+    request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                        "myhost", Option4ClientFqdn::PARTIAL));
+    response.reset(new Option4ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+    EXPECT_EQ("myhost.suffix.com.", response->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, response->getDomainNameType());
+
+
+    // replace-client-name is false, client passes in a full fqdn
+    // response domain should not be altered.
+    request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                        "myhost.example.com.",
+                                         Option4ClientFqdn::FULL));
+    response.reset(new Option4ClientFqdn(*request));
+    mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+    EXPECT_EQ("myhost.example.com.", response->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, response->getDomainNameType());
+
+    // Create enabled configuration.
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, false, true,
+                                  "prefix", "suffix.com")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(cfg->getReplaceClientName());
+
+    // replace-client-name is true, client passes in empty fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                        "", Option4ClientFqdn::PARTIAL));
+    response.reset(new Option4ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+
+    // replace-client-name is true, client passes in a partial fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                        "myhost", Option4ClientFqdn::PARTIAL));
+    response.reset(new Option4ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+
+
+    // replace-client-name is true, client passes in a full fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                        "myhost.example.com.",
+                                         Option4ClientFqdn::FULL));
+    response.reset(new Option4ClientFqdn(*request));
+    mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+}
+
+/// @brief Tests adjustDomainName template method with Option6ClientFqdn
+TEST(D2ClientMgr, adjustDomainNameV6) {
+    D2ClientMgr mgr;
+    Option6ClientFqdnPtr request;
+    Option6ClientFqdnPtr response;
+
+    // Create enabled configuration.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, false, false,
+                                  "prefix", "suffix.com")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_FALSE(cfg->getReplaceClientName());
+
+    // replace-client-name is false, client passes in empty fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
+    response.reset(new Option6ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+
+    // replace-client-name is false, client passes in a partial fqdn
+    // response should contain client's name plus the qualifying suffix.
+    request.reset(new Option6ClientFqdn(0, "myhost",
+                                        Option6ClientFqdn::PARTIAL));
+    response.reset(new Option6ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+    EXPECT_EQ("myhost.suffix.com.", response->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, response->getDomainNameType());
+
+
+    // replace-client-name is false, client passes in a full fqdn
+    // response domain should not be altered.
+    request.reset(new Option6ClientFqdn(0, "myhost.example.com.",
+                                        Option6ClientFqdn::FULL));
+    response.reset(new Option6ClientFqdn(*request));
+    mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+    EXPECT_EQ("myhost.example.com.", response->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, response->getDomainNameType());
+
+    // Create enabled configuration.
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, false, false, true,
+                                  "prefix", "suffix.com")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(cfg->getReplaceClientName());
+
+    // replace-client-name is true, client passes in empty fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
+    response.reset(new Option6ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+
+    // replace-client-name is true, client passes in a partial fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option6ClientFqdn(0, "myhost",
+                                        Option6ClientFqdn::PARTIAL));
+    response.reset(new Option6ClientFqdn(*request));
+
+    mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+
+
+    // replace-client-name is true, client passes in a full fqdn
+    // reponse domain should be empty/partial.
+    request.reset(new Option6ClientFqdn(0, "myhost.example.com.",
+                                        Option6ClientFqdn::FULL));
+    response.reset(new Option6ClientFqdn(*request));
+    mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+    EXPECT_EQ("", response->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+}
+
+/// @brief Verifies the adustFqdnFlags template with Option6ClientFqdn objects.
+/// Ensures that the method can set the N, S, and O flags properly.
+/// Other permutations are covered by analyzeFqdnFlags tests.
+TEST(D2ClientMgr, adjustFqdnFlagsV6) {
+    D2ClientMgr mgr;
+    Option6ClientFqdnPtr request;
+    Option6ClientFqdnPtr response;
+
+    // Create enabled configuration and override-no-update on.
+    D2ClientConfigPtr cfg;
+    ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  false, true, false, false,
+                                  "pre-fix", "suf-fix")));
+    ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(cfg->getOverrideNoUpdate());
+    ASSERT_FALSE(cfg->getOverrideClientUpdate());
+
+    // client S=0 N=0 means client wants to do forward update.
+    // server S should be 0 (server is not doing forward updates)
+    // and server N should be 1 (server doing no updates)
+    // and server O should be 0
+    request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
+    response.reset(new Option6ClientFqdn(*request));
+    response->resetFlags();
+
+    mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response);
+    EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_O));
+
+    // client S=1 N=0 means client wants server to do forward update.
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    // and server O should be 0
+    request.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                        "", Option6ClientFqdn::PARTIAL));
+    response.reset(new Option6ClientFqdn(*request));
+    response->resetFlags();
+
+    mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response);
+    EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_O));
+
+    // client S=0 N=1 means client wants no one to do updates
+    // server S should be 1 (server is doing forward updates)
+    // and server N should be 0 (server doing updates)
+    // and O should be 1 (overriding client S)
+    request.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+                                        "", Option6ClientFqdn::PARTIAL));
+    response.reset(new Option6ClientFqdn(*request));
+    response->resetFlags();
+
+    mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response);
+    EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_O));
+}
+
 } // end of anonymous namespace
 } // end of anonymous namespace

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

@@ -720,7 +720,6 @@ TEST_F(ParseConfigTest, validD2Config) {
         "     \"server-port\" : 3432, "
         "     \"server-port\" : 3432, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-client-update\" : true, "
         "     \"override-client-update\" : true, "
@@ -746,7 +745,6 @@ TEST_F(ParseConfigTest, validD2Config) {
     EXPECT_EQ(3432, d2_client_config->getServerPort());
     EXPECT_EQ(3432, d2_client_config->getServerPort());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
-    EXPECT_TRUE(d2_client_config->getRemoveOnRenew());
     EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
     EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
     EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
     EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
     EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
     EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
@@ -755,17 +753,16 @@ TEST_F(ParseConfigTest, validD2Config) {
     EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
     EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
 
 
     // Another valid Configuration string.
     // Another valid Configuration string.
-    // This one has IPV6 server ip, control flags false,
+    // This one is disabled, has IPV6 server ip, control flags false,
     // empty prefix/suffix
     // empty prefix/suffix
     std::string config_str2 =
     std::string config_str2 =
         "{ \"dhcp-ddns\" :"
         "{ \"dhcp-ddns\" :"
         "    {"
         "    {"
-        "     \"enable-updates\" : true, "
+        "     \"enable-updates\" : false, "
         "     \"server-ip\" : \"2001:db8::\", "
         "     \"server-ip\" : \"2001:db8::\", "
         "     \"server-port\" : 43567, "
         "     \"server-port\" : 43567, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : false, "
         "     \"always-include-fqdn\" : false, "
         "     \"always-include-fqdn\" : false, "
         "     \"override-no-update\" : false, "
         "     \"override-no-update\" : false, "
         "     \"override-client-update\" : false, "
         "     \"override-client-update\" : false, "
@@ -779,18 +776,17 @@ TEST_F(ParseConfigTest, validD2Config) {
     rcode = parseConfiguration(config_str2);
     rcode = parseConfiguration(config_str2);
     ASSERT_TRUE(rcode == 0) << error_text_;
     ASSERT_TRUE(rcode == 0) << error_text_;
 
 
-    // Verify that DHCP-DDNS is enabled and we can fetch the configuration.
-    EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+    // Verify that DHCP-DDNS is disabled and we can fetch the configuration.
+    EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
     ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
     ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
     ASSERT_TRUE(d2_client_config);
     ASSERT_TRUE(d2_client_config);
 
 
     // Verify that the configuration values are as expected.
     // Verify that the configuration values are as expected.
-    EXPECT_TRUE(d2_client_config->getEnableUpdates());
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
     EXPECT_EQ("2001:db8::", d2_client_config->getServerIp().toText());
     EXPECT_EQ("2001:db8::", d2_client_config->getServerIp().toText());
     EXPECT_EQ(43567, d2_client_config->getServerPort());
     EXPECT_EQ(43567, d2_client_config->getServerPort());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
-    EXPECT_FALSE(d2_client_config->getRemoveOnRenew());
     EXPECT_FALSE(d2_client_config->getAlwaysIncludeFqdn());
     EXPECT_FALSE(d2_client_config->getAlwaysIncludeFqdn());
     EXPECT_FALSE(d2_client_config->getOverrideNoUpdate());
     EXPECT_FALSE(d2_client_config->getOverrideNoUpdate());
     EXPECT_FALSE(d2_client_config->getOverrideClientUpdate());
     EXPECT_FALSE(d2_client_config->getOverrideClientUpdate());
@@ -842,7 +838,6 @@ TEST_F(ParseConfigTest, invalidD2Config) {
         "     \"server-port\" : 53001, "
         "     \"server-port\" : 53001, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-client-update\" : true, "
         "     \"override-client-update\" : true, "
@@ -859,7 +854,6 @@ TEST_F(ParseConfigTest, invalidD2Config) {
         "     \"server-port\" : 53001, "
         "     \"server-port\" : 53001, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-client-update\" : true, "
         "     \"override-client-update\" : true, "
@@ -876,7 +870,6 @@ TEST_F(ParseConfigTest, invalidD2Config) {
         "     \"server-port\" : 53001, "
         "     \"server-port\" : 53001, "
         "     \"ncr-protocol\" : \"Bogus\", "
         "     \"ncr-protocol\" : \"Bogus\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-client-update\" : true, "
         "     \"override-client-update\" : true, "
@@ -893,7 +886,6 @@ TEST_F(ParseConfigTest, invalidD2Config) {
         "     \"server-port\" : 53001, "
         "     \"server-port\" : 53001, "
         "     \"ncr-protocol\" : \"TCP\", "
         "     \"ncr-protocol\" : \"TCP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-client-update\" : true, "
         "     \"override-client-update\" : true, "
@@ -910,7 +902,6 @@ TEST_F(ParseConfigTest, invalidD2Config) {
         "     \"server-port\" : 53001, "
         "     \"server-port\" : 53001, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"Bogus\", "
         "     \"ncr-format\" : \"Bogus\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-client-update\" : true, "
         "     \"override-client-update\" : true, "
@@ -927,7 +918,6 @@ TEST_F(ParseConfigTest, invalidD2Config) {
         // "     \"server-port\" : 53001, "
         // "     \"server-port\" : 53001, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-no-update\" : true, "
         "     \"override-client-update\" : true, "
         "     \"override-client-update\" : true, "