Browse Source

[3282] Added FQDN processing logic to D2ClientMgr

Methods were added to D2ClientMgr which work for v4 and v6 to
determine the server values for FQDN flags and domain names based
upon the client FQDN and configuration options.
Thomas Markwalder 11 years ago
parent
commit
7f2ef41080
3 changed files with 730 additions and 6 deletions
  1. 87 5
      src/lib/dhcpsrv/d2_client.cc
  2. 120 1
      src/lib/dhcpsrv/d2_client.h
  3. 523 0
      src/lib/dhcpsrv/tests/d2_client_unittest.cc

+ 87 - 5
src/lib/dhcpsrv/d2_client.cc

@@ -62,8 +62,8 @@ D2ClientConfig::D2ClientConfig()
       override_no_update_(false),
       override_client_update_(false),
       replace_client_name_(false),
-      generated_prefix_(""),
-      qualifying_suffix_("") {
+      generated_prefix_("myhost"),
+      qualifying_suffix_("example.com") {
     validateContents();
 }
 
@@ -142,7 +142,7 @@ operator<<(std::ostream& os, const D2ClientConfig& config) {
 }
 
 D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) {
-    // Default contstructor initializes with a disabled config.
+    // Default constructor initializes with a disabled configuration.
 }
 
 D2ClientMgr::~D2ClientMgr(){
@@ -159,8 +159,8 @@ D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
     // scenarios:
     // 1. D2 was enabled but now it is disabled
     //     - 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.
     //     - create sender
     //
@@ -181,5 +181,87 @@ D2ClientMgr::getD2ClientConfig() const {
     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 (secion 3.2)
+    //    0       1      client wants server to do forward updates (secion 3.3)
+    //    1       0      client wants no one to do updates (section 3.4)
+    //    1       1      invalid combination
+    //
+    // Make a bit mask from the client's flags and use it to set the response
+    // flags accordingly.
+    //
+    /// @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.
+    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:
+        // In theory we cannot get here because the fqdn option class defends
+        // against this combination.
+        isc_throw(isc::Unexpected,
+                  "Invalid client FQDN - N and S cannot both be 1");
+        break;
+    }
+
+    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;
+    std::string tmp = gen_name.str();
+    return (qualifyName(tmp));
+}
+
+std::string
+D2ClientMgr::qualifyName(const std::string& partial_name) const {
+    std::ostringstream gen_name;
+    gen_name << partial_name << "." << d2_client_config_->getQualifyingSuffix();
+    if (gen_name.str().back() != '.') {
+        gen_name << ".";
+    }
+
+    return (gen_name.str());
+}
+
+
+
 };  // namespace dhcp
 };  // namespace isc

+ 120 - 1
src/lib/dhcpsrv/d2_client.h

@@ -189,7 +189,7 @@ private:
     size_t server_port_;
 
     /// @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_;
 
     /// @brief Format of the b10-dhcp-ddns requests.
@@ -263,11 +263,130 @@ public:
     /// @return a reference to the current configuration pointer.
     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.
+    ///
+    /// @param client_s  S Flag from the client's FQDN
+    /// @param client_n  N Flag from the client's FQDN
+    /// @param server_s  S Flag for the server's FQDN  (output)
+    /// @param server_n  N Flag for the server's FQDN (output)
+    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.
+    ///
+    /// @param fqdn FQDN option from which to read client (inbound) flags
+    /// @param fqdn_resp FQDN option to update with the server (outbound) flags
+    template <class OPT>
+    void adjustFqdnFlags(OPT& fqdn, OPT& 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
+    template <class OPT>
+    void adjustDomainName(OPT& fqdn, OPT& fqdn_resp);
+
 private:
     /// @brief Container class for DHCP-DDNS configuration parameters.
     D2ClientConfigPtr d2_client_config_;
 };
 
+template <class OPT>
+void
+D2ClientMgr::adjustFqdnFlags(OPT& fqdn, OPT& fqdn_resp) {
+    bool server_s = false;
+    bool server_n = false;
+    analyzeFqdn(fqdn.getFlag(OPT::FLAG_S), fqdn.getFlag(OPT::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(OPT::FLAG_S, server_s);
+    fqdn_resp.setFlag(OPT::FLAG_N, server_n);
+
+    // Set O flag true if server S overrides client S.
+    fqdn_resp.setFlag(OPT::FLAG_O, (fqdn.getFlag(OPT::FLAG_S) != server_s));
+}
+
+
+template <class OPT>
+void
+D2ClientMgr::adjustDomainName(OPT& fqdn, OPT& 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("", OPT::PARTIAL);
+    } else {
+        // If the supplied name is partial, qualify it by adding the suffix.
+        if (fqdn.getDomainNameType() == OPT::PARTIAL) {
+            std::ostringstream name;
+            name << fqdn.getDomainName();
+            name << "." << (d2_client_config_->getQualifyingSuffix());
+            if (d2_client_config_->getQualifyingSuffix().back() != '.') {
+                name << ".";
+            }
+
+            fqdn_resp.setDomainName(name.str(), OPT::FULL);
+        }
+    }
+}
+
 /// @brief Defines a pointer for D2ClientMgr instances.
 typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
 

+ 523 - 0
src/lib/dhcpsrv/tests/d2_client_unittest.cc

@@ -13,6 +13,8 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcpsrv/d2_client.h>
 #include <exceptions/exceptions.h>
 
@@ -290,5 +292,526 @@ TEST(D2ClientMgr, validConfig) {
     EXPECT_NE(*original_config, *updated_config);
 }
 
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are disabled.
+TEST(D2ClientMgr, analyzeFqdnUpdatesDisabled) {
+    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());
+    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 0 (server is not doing forward updates)
+    // and server N should be 1 (server doing no updates)
+    mgr.analyzeFqdn(true, false, server_s, server_n);
+    EXPECT_FALSE(server_s);
+    EXPECT_TRUE(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 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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