Browse Source

[4259] kea-dhcp6 now supports replace-client-name modes

    src/bin/dhcp6/dhcp6_messages.mes
        - Added new log message, DHCP6_DDNS_SUPPLY_FQDN

    src/bin/dhcp6/dhcp6_srv.cc
        - Dhcpv6Srv::processClientFqdn() - modified to support the name
        replacement modes

    src/bin/dhcp6/tests/fqdn_unittest.cc
        - FqdnDhcpv6SrvTest::testReplaceClientNameMode() new method which tests
        a server's handling of a single client packet for a given
        replace-client-name mode.

        - TEST_F(FqdnDhcpv6SrvTest, replaceClientNameModeTest) - new test which
        exercises the permutations of client packets and replace-client-name
        modes.
Thomas Markwalder 9 years ago
parent
commit
b3f483e537

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

@@ -183,6 +183,11 @@ the lease is acquired for the client.
 This debug message is logged when server includes an DHCPv6 Client FQDN Option
 in its response to the client.
 
+% DHCP6_DDNS_SUPPLY_FQDN %1: client did not send a FQDN option, will supply one for them
+This debug message is issued when the server did not receive a FQDN option
+from the client and client name replacement is enabled.  This provides a means
+to create DNS entries for unsophisticated clients.
+
 % DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
 This message is printed when DHCPv6 server disables an interface from being
 used to receive DHCPv6 traffic. Sockets on this interface will not be opened

+ 22 - 7
src/bin/dhcp6/dhcp6_srv.cc

@@ -1063,17 +1063,34 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
 void
 Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
                              AllocEngine::ClientContext6& ctx) {
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+
     // Get Client FQDN Option from the client's message. If this option hasn't
     // been included, do nothing.
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
     if (!fqdn) {
-        // No FQDN so lease hostname comes from host reservation if one
-        if (ctx.host_) {
-            ctx.hostname_ = ctx.host_->getHostname();
-        }
+        D2ClientConfig::ReplaceClientNameMode replace_name_mode =
+            d2_mgr.getD2ClientConfig()->getReplaceClientNameMode();
+        if (d2_mgr.ddnsEnabled() &&
+            (replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
+             replace_name_mode == D2ClientConfig::RCM_WHEN_NOT_PRESENT)) {
+            // Fabricate an empty "client" FQDN with flags requesting
+            // the server do all the updates.  The flags will get modified
+            // below according the configuration options, the name will
+            // be supplied later on.
+            fqdn.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+                                             Option6ClientFqdn::PARTIAL));
+            LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_SUPPLY_FQDN)
+                .arg(question->getLabel());
+        } else {
+            // No FQDN so lease hostname comes from host reservation if one
+            if (ctx.host_) {
+                ctx.hostname_ = ctx.host_->getHostname();
+            }
 
-        return;
+            return;
+        }
     }
 
     LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_RECEIVE_FQDN)
@@ -1086,12 +1103,10 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
 
     // 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<Option6ClientFqdn>(*fqdn, *fqdn_resp);
 
     // If there's a reservation and it has a hostname specified, use it!
     if (ctx.host_ && !ctx.host_->getHostname().empty()) {
-        D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
         // Add the qualifying suffix.
         // After #3765, this will only occur if the suffix is not empty.
         fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(),

+ 134 - 1
src/bin/dhcp6/tests/fqdn_unittest.cc

@@ -53,6 +53,20 @@ public:
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
 
+    // Enum used to specify whether a client (packet) should include
+    // the hostname option
+    enum ClientNameFlag {
+        CLIENT_NAME_PRESENT,
+        CLIENT_NAME_NOT_PRESENT
+    };
+
+    // Enum used to specify whether the server should replace/supply
+    // the hostname or not
+    enum ReplacementFlag {
+        NAME_REPLACED,
+        NAME_NOT_REPLACED
+    };
+
     // Type used to indicate whether or not forward updates are expected
     struct ExpFwd {
         ExpFwd(bool exp_fwd) : value_(exp_fwd){};
@@ -112,7 +126,8 @@ public:
                                   (mask & ALWAYS_INCLUDE_FQDN),
                                   (mask & OVERRIDE_NO_UPDATE),
                                   (mask & OVERRIDE_CLIENT_UPDATE),
-                                  ((mask & REPLACE_CLIENT_NAME) ? D2ClientConfig::RCM_WHEN_PRESENT
+                                  ((mask & REPLACE_CLIENT_NAME) ?
+                                   D2ClientConfig::RCM_WHEN_PRESENT
                                    : D2ClientConfig::RCM_NEVER),
                                   "myhost", "example.com")));
         ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
@@ -368,6 +383,84 @@ public:
         }
     }
 
+    // Test that the server processes the FQDN option (or lack thereof)
+    // in a client request correctly, according to the replace-client-name
+    // mode configuration parameter.
+    //
+    // @param mode - value to use client-name-replacment parameter - for
+    // mode labels such as NEVER and ALWAYS must incluce enclosing quotes:
+    // "\"NEVER\"".  This allows us to also pass in boolean literals which
+    // are unquoted.
+    // @param client_name_flag - specifies whether or not the client request
+    // should contain a hostname option
+    // @param exp_replacement_flag - specifies whether or not the server is
+    // expected to replace (or supply) the FQDN/name in its response
+    void testReplaceClientNameMode(const char* mode,
+                                   enum ClientNameFlag client_name_flag,
+                                   enum ReplacementFlag exp_replacement_flag) {
+        // Configuration "template" with a replaceable mode parameter
+        const char* config_template =
+            "{ \"interfaces-config\": { \n"
+            "  \"interfaces\": [ \"eth0\" ] \n"
+            "}, \n"
+            "\"valid-lifetime\": 4000,  \n"
+            "\"preferred-lifetime\": 3000, \n"
+            "\"rebind-timer\": 2000,  \n"
+            "\"renew-timer\": 1000,  \n"
+            "\"subnet6\": [ {  \n"
+            "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+            "    \"subnet\": \"2001:db8:1::/48\",  \n"
+            "    \"interface-id\": \"\", \n"
+            "    \"interface\": \"eth0\" \n"
+            " } ], \n"
+            "\"dhcp-ddns\": { \n"
+            "\"enable-updates\": true, \n"
+            "\"qualifying-suffix\": \"fake-suffix.isc.org.\", \n"
+            "\"replace-client-name\": %s \n"
+            "}} \n";
+
+        // Create the configuration and configure the server
+        char config_buf[1024];
+        sprintf(config_buf, config_template, mode);
+        configure(config_buf, *srv_);
+
+        // Build our client packet
+        Pkt6Ptr query;
+        if (client_name_flag == CLIENT_NAME_PRESENT) {
+            query = generateMessage(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+                                    "my.example.com.", Option6ClientFqdn::FULL,
+                                    true);
+        } else {
+            query = generateMessageWithIds(DHCPV6_SOLICIT);
+        }
+
+        Pkt6Ptr answer = generateMessageWithIds(DHCPV6_ADVERTISE);
+
+        AllocEngine::ClientContext6 ctx;
+        ASSERT_NO_THROW(srv_->processClientFqdn(query, answer, ctx));
+
+        Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
+            Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+
+        // Verify the contents (or lack thereof) of the FQDN
+        if (exp_replacement_flag == NAME_REPLACED) {
+            ASSERT_TRUE(answ_fqdn);
+            EXPECT_TRUE(answ_fqdn->getDomainName().empty());
+            EXPECT_EQ(Option6ClientFqdn::PARTIAL,
+                      answ_fqdn->getDomainNameType());
+        } else {
+            if (client_name_flag == CLIENT_NAME_PRESENT) {
+                ASSERT_TRUE(answ_fqdn);
+                EXPECT_EQ("my.example.com.", answ_fqdn->getDomainName());
+                EXPECT_EQ(Option6ClientFqdn::FULL,
+                          answ_fqdn->getDomainNameType());
+            } else {
+                ASSERT_FALSE(answ_fqdn);
+            }
+        }
+    }
+
+
     /// @brief Tests that the client's message holding an FQDN is processed
     /// and that lease is acquired.
     ///
@@ -1234,4 +1327,44 @@ TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) {
                        IOAddress("2001:db8:1:1::babe"));
 }
 
+// Verifies that the replace-client-name behavior is correct for each of
+// the supported modes.
+TEST_F(FqdnDhcpv6SrvTest, replaceClientNameModeTest) {
+    isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+    // We pass mode labels in with enclosing quotes so we can also test
+    // unquoted boolean literals true/false
+    testReplaceClientNameMode("\"NEVER\"",
+                              CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
+    testReplaceClientNameMode("\"NEVER\"",
+                              CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
+
+    testReplaceClientNameMode("\"ALWAYS\"",
+                              CLIENT_NAME_NOT_PRESENT, NAME_REPLACED);
+    testReplaceClientNameMode("\"ALWAYS\"",
+                              CLIENT_NAME_PRESENT, NAME_REPLACED);
+
+    testReplaceClientNameMode("\"WHEN_PRESENT\"",
+                              CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
+    testReplaceClientNameMode("\"WHEN_PRESENT\"",
+                              CLIENT_NAME_PRESENT, NAME_REPLACED);
+
+    testReplaceClientNameMode("\"WHEN_NOT_PRESENT\"",
+                              CLIENT_NAME_NOT_PRESENT, NAME_REPLACED);
+    testReplaceClientNameMode("\"WHEN_NOT_PRESENT\"",
+                              CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
+
+    // Verify that boolean false produces the same result as RCM_NEVER
+    testReplaceClientNameMode("false",
+                              CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
+    testReplaceClientNameMode("false",
+                              CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
+
+    // Verify that boolean true produces the same result as RCM_WHEN_PRESENT
+    testReplaceClientNameMode("true",
+                              CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
+    testReplaceClientNameMode("true",
+                              CLIENT_NAME_PRESENT, NAME_REPLACED);
+}
+
 }   // end of anonymous namespace

+ 2 - 1
src/lib/dhcpsrv/d2_client_mgr.h

@@ -457,7 +457,8 @@ 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_->getReplaceClientNameMode() != D2ClientConfig::RCM_NEVER) ||
+    if ((d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_ALWAYS ||
+         d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_WHEN_PRESENT) ||
         fqdn.getDomainName().empty()) {
         fqdn_resp.setDomainName("", T::PARTIAL);
     } else {