Browse Source

[3295] Generated hostname for a client who sent an empty FQDN.

Also, allow the server to generate FQDN if configured to do so, even if
client specified non-empty FQDN.
Marcin Siodelski 11 years ago
parent
commit
e91931226a

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

@@ -233,6 +233,11 @@ but the lease does not contain any client identification. This is most
 likely due to a software error: please raise a bug report. As a temporary
 workaround, manually remove the lease entry from the database.
 
+% DHCP6_NAME_GEN_UPDATE_FAIL failed to update the lease using address %1, after generating FQDN for a client, reason: %2
+This message indicates the failure when trying to update the lease and/or
+options in the server's response with the hostname generated by the server
+from the acquired address.
+
 % DHCP6_NOT_RUNNING IPv6 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
 IPv6 DHCP server but it is not running.

+ 70 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -2152,6 +2152,8 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     // perform DNS Updates for Solicit. Client must send Request to update
     // DNS.
 
+    generateFqdn(advertise);
+
     return (advertise);
 }
 
@@ -2169,6 +2171,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
 
     processClientFqdn(request, reply);
     assignLeases(request, reply);
+    generateFqdn(reply);
     createNameChangeRequests(reply);
 
     return (reply);
@@ -2187,6 +2190,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
 
     processClientFqdn(renew, reply);
     renewLeases(renew, reply);
+    generateFqdn(reply);
     createNameChangeRequests(reply);
 
     return (reply);
@@ -2391,5 +2395,71 @@ Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) {
     LOG_WARN(dhcp6_logger, DHCP6_OPEN_SOCKET_FAIL).arg(errmsg);
 }
 
+void
+Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
+    if (!answer) {
+        isc_throw(isc::Unexpected, "an instance of the object encapsulating"
+                  " a message must not be NULL when generating FQDN");
+    }
+
+    // It is likely that client haven't included the FQDN option. In such case,
+    // FQDN option will be NULL. Also, there is nothing to do if the option
+    // is present and conveys the non-empty FQDN.
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+    if (!fqdn || !fqdn->getDomainName().empty()) {
+        return;
+    }
+
+    // Get the first IA_NA acquired for the client.
+    OptionPtr ia = answer->getOption(D6O_IA_NA);
+    if (!ia) {
+        return;
+    }
+
+    // If it has any IAAddr, use the first one to generate unique FQDN.
+    Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+        Option6IAAddr>(ia->getOption(D6O_IAADDR));
+    if (!iaaddr) {
+        return;
+    }
+    // Get the IPv6 address acquired by the client.
+    IOAddress addr = iaaddr->getAddress();
+    std::string hostname = addr.toText();
+    // Colons may not be ok for FQDNs so let's replace them with hyphens.
+    std::replace(hostname.begin(), hostname.end(), ':', '-');
+    std::ostringstream stream;
+    // The final FQDN consists of the partial domain name and the suffix.
+    // For example, if the acquired address is 2001:db8:1::2, the generated
+    // FQDN may be:
+    //     host-2001-db8:1--2.example.com.
+    // where prefix 'host' should be configurable. The domain name suffix
+    // should also be configurable.
+    stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
+    try {
+        // The lease has been acquired but the FQDN for this lease hasn't
+        // been updated in the lease database. We now have new FQDN
+        // generated, so the lease database has to be updated here.
+        // However, never update lease database for Advertise, just send
+        // our notion of client's FQDN in the Client FQDN option.
+        if (answer->getType() != DHCPV6_ADVERTISE) {
+            Lease6Ptr lease =
+                LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+            if (lease) {
+                lease->hostname_ = stream.str();
+            }
+            LeaseMgrFactory::instance().updateLease6(lease);
+        }
+
+        // Set the generated FQDN in the Client FQDN option.
+        fqdn->setDomainName(stream.str(), Option6ClientFqdn::FULL);
+
+    } catch (const Exception& ex) {
+        LOG_ERROR(dhcp6_logger, DHCP6_NAME_GEN_UPDATE_FAIL)
+            .arg(hostname)
+            .arg(ex.what());
+    }
+}
+
 };
 };

+ 42 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -527,6 +527,48 @@ private:
     /// @param errmsg An error message containing a cause of the failure.
     static void ifaceMgrSocket6ErrorHandler(const std::string& errmsg);
 
+    /// @brief Generate FQDN to be sent to a client if none exists.
+    ///
+    /// This function is meant to be called by the functions which process
+    /// client's messages. The function should be called after a function
+    /// which creates FQDN option for the client. This option must exist
+    /// in the answer message specified as an argument. It must also be
+    /// called after functions which assign leases for a client. The
+    /// IA options being a result of lease acquisition must be appended
+    /// to the message specified as a parameter.
+    ///
+    /// If the Client FQDN option being present in the message carries empty
+    /// hostname, this function will attempt to generate hostname from the
+    /// IPv6 address being acquired by the client. The IPv6 address is retrieved
+    /// from the IA_NA option carried in the specified message. If multiple
+    /// addresses are present in the particular IA_NA option or multiple IA_NA
+    /// options exist, the first address found is selected.
+    ///
+    /// The IPv6 address is converted to the hostname using the following
+    /// pattern:
+    /// @code
+    ///     prefix-converted-ip-address.domain-name-suffix.
+    /// @endcode
+    /// where:
+    /// - prefix is a configurable prefix string appended to all auto-generated
+    /// hostnames.
+    /// - converted-ip-address is created by replacing all colons from the IPv6
+    /// address with hyphens.
+    /// - domain-name-suffix is a suffix for a domain name that, together with
+    /// the other parts, constitute the fully qualified domain name.
+    ///
+    /// When hostname is successfully generated, it is either used to update
+    /// FQDN-related fields in a lease database or to update the Client FQDN
+    /// option being sent back to the client. The lease database update is
+    /// NOT performed if Advertise message is being processed.
+    ///
+    /// @param answer Message being sent to a client, which may hold IA_NA
+    /// and Client FQDN options to be used to generate name for a client.
+    ///
+    /// @throw isc::Unexpected if specified message is NULL. This is treated
+    /// as a programmatic error.
+    void generateFqdn(const Pkt6Ptr& answer);
+
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using
     /// It must be a pointer, because we will support changing engines

+ 52 - 16
src/bin/dhcp6/tests/fqdn_unittest.cc

@@ -294,15 +294,18 @@ public:
     /// that the server doesn't respond with the FQDN.
     void testProcessMessage(const uint8_t msg_type,
                             const std::string& hostname,
+                            const std::string& exp_hostname,
                             NakedDhcpv6Srv& srv,
                             const bool include_oro = true) {
         // Create a message of a specified type, add server id and
         // FQDN option.
         OptionPtr srvid = srv.getServerID();
+        // Set the appropriate FQDN type. It must be partial if hostname is
+        // empty.
+        Option6ClientFqdn::DomainNameType fqdn_type = hostname.empty() ?
+            Option6ClientFqdn::PARTIAL : Option6ClientFqdn::FULL;
         Pkt6Ptr req = generateMessage(msg_type, Option6ClientFqdn::FLAG_S,
-                                      hostname,
-                                      Option6ClientFqdn::FULL,
-                                      include_oro, srvid);
+                                      hostname, fqdn_type, include_oro, srvid);
 
         // For different client's message types we have to invoke different
         // functions to generate response.
@@ -348,11 +351,15 @@ public:
             Lease6Ptr lease =
                 checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
             ASSERT_TRUE(lease);
+            EXPECT_EQ(exp_hostname, lease->hostname_);
         }
 
         // The Client FQDN option should be always present in the server's
         // response, regardless if requested using ORO or not.
-        ASSERT_TRUE(reply->getOption(D6O_CLIENT_FQDN));
+        Option6ClientFqdnPtr fqdn;
+        ASSERT_TRUE(fqdn = boost::dynamic_pointer_cast<
+                        Option6ClientFqdn>(reply->getOption(D6O_CLIENT_FQDN)));
+        EXPECT_EQ(exp_hostname, fqdn->getDomainName());
     }
 
     /// @brief Verify that NameChangeRequest holds valid values.
@@ -634,7 +641,8 @@ TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
 
     // Create a Solicit message with FQDN option and generate server's
     // response using processSolicit function.
-    testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com",
+                       "myhost.example.com.", srv);
     EXPECT_TRUE(srv.name_change_reqs_.empty());
 }
 
@@ -649,7 +657,8 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -664,7 +673,8 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // entries should be removed and the entries for the new domain-name
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
-    testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
     ASSERT_EQ(2, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -691,7 +701,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -703,7 +714,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
     // NameChangeRequest and preserve existing DNS entries for the client.
     // The NameChangeRequest should only be generated when a client sends
     // Request or Renew.
-    testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com", srv);
+    testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
     ASSERT_TRUE(srv.name_change_reqs_.empty());
 
 }
@@ -721,7 +733,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -736,7 +749,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // entries should be removed and the entries for the new domain-name
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
-    testProcessMessage(DHCPV6_RENEW, "otherhost.example.com", srv);
+    testProcessMessage(DHCPV6_RENEW, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
     ASSERT_EQ(2, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -758,7 +772,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -770,7 +785,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // removed and all existing DNS entries for this lease should be
     // also removed. Therefore, we expect that single NameChangeRequest to
     // remove DNS entries is generated.
-    testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
+    testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -788,7 +804,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
     // The last parameter disables use of the ORO to request FQDN option
     // In this case, we expect that the FQDN option will not be included
     // in the server's response. The testProcessMessage will check that.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv, false);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv, false);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -797,6 +814,23 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
                             0, 4000);
 }
 
+// Checks that FQDN is generated from an ip address, when client sends an empty
+// FQDN.
+TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
+    NakedDhcpv6Srv srv(0);
+
+    testProcessMessage(DHCPV6_REQUEST, "",
+                       "host-2001-db8-1-1--dead-beef.example.com.",
+                       srv, false);
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "0002018D6874B105A5C92DBBD6E4F6C80A93161"
+                            "BC03996F0CD0EB75800DEF997C29961",
+                            0, 4000);
+
+}
+
 // Checks that when the server reuses expired lease, the NameChangeRequest
 // is generated to remove the DNS mapping for the expired lease and second
 // NameChangeRequest to add a DNS mapping for a new lease.
@@ -815,7 +849,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
 
     // Allocate a lease.
     NakedDhcpv6Srv srv(0);
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     // Test that the appropriate NameChangeRequest has been generated.
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
@@ -847,7 +882,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
     // exactly one address and this address is used by the lease in the
     // lease database, it is guaranteed that the allocation engine will
     // reuse this lease.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(2, srv.name_change_reqs_.size());
     // The first name change request generated, should remove a DNS
     // mapping for an expired lease.