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
 This debug message is logged when server includes an DHCPv6 Client FQDN Option
 in its response to the client.
 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
 % DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
 This message is printed when DHCPv6 server disables an interface from being
 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
 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
 void
 Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
 Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
                              AllocEngine::ClientContext6& ctx) {
                              AllocEngine::ClientContext6& ctx) {
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+
     // Get Client FQDN Option from the client's message. If this option hasn't
     // Get Client FQDN Option from the client's message. If this option hasn't
     // been included, do nothing.
     // been included, do nothing.
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
         Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
     if (!fqdn) {
     if (!fqdn) {
-        // No FQDN so lease hostname comes from host reservation if one
+        D2ClientConfig::ReplaceClientNameMode replace_name_mode =
-        if (ctx.host_) {
+            d2_mgr.getD2ClientConfig()->getReplaceClientNameMode();
-            ctx.hostname_ = ctx.host_->getHostname();
+        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)
     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
     // Set the server S, N, and O flags based on client's flags and
     // current configuration.
     // current configuration.
-    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
     d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
     d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
 
 
     // If there's a reservation and it has a hostname specified, use it!
     // If there's a reservation and it has a hostname specified, use it!
     if (ctx.host_ && !ctx.host_->getHostname().empty()) {
     if (ctx.host_ && !ctx.host_->getHostname().empty()) {
-        D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
         // Add the qualifying suffix.
         // Add the qualifying suffix.
         // After #3765, this will only occur if the suffix is not empty.
         // After #3765, this will only occur if the suffix is not empty.
         fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(),
         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 OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
     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
     // Type used to indicate whether or not forward updates are expected
     struct ExpFwd {
     struct ExpFwd {
         ExpFwd(bool exp_fwd) : value_(exp_fwd){};
         ExpFwd(bool exp_fwd) : value_(exp_fwd){};
@@ -112,7 +126,8 @@ public:
                                   (mask & ALWAYS_INCLUDE_FQDN),
                                   (mask & ALWAYS_INCLUDE_FQDN),
                                   (mask & OVERRIDE_NO_UPDATE),
                                   (mask & OVERRIDE_NO_UPDATE),
                                   (mask & OVERRIDE_CLIENT_UPDATE),
                                   (mask & OVERRIDE_CLIENT_UPDATE),
-                                  ((mask & REPLACE_CLIENT_NAME) ? D2ClientConfig::RCM_WHEN_PRESENT
+                                  ((mask & REPLACE_CLIENT_NAME) ?
+                                   D2ClientConfig::RCM_WHEN_PRESENT
                                    : D2ClientConfig::RCM_NEVER),
                                    : D2ClientConfig::RCM_NEVER),
                                   "myhost", "example.com")));
                                   "myhost", "example.com")));
         ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
         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
     /// @brief Tests that the client's message holding an FQDN is processed
     /// and that lease is acquired.
     /// and that lease is acquired.
     ///
     ///
@@ -1234,4 +1327,44 @@ TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) {
                        IOAddress("2001:db8:1:1::babe"));
                        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
 }   // 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) {
 D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
     // If we're configured to replace it or the supplied name is blank
     // If we're configured to replace it or the supplied name is blank
     // set the response name to 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.getDomainName().empty()) {
         fqdn_resp.setDomainName("", T::PARTIAL);
         fqdn_resp.setDomainName("", T::PARTIAL);
     } else {
     } else {