Browse Source

[5307] Assign reserved hostnames for shared networks.

Marcin Siodelski 7 years ago
parent
commit
dd3e3a9101

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

@@ -2461,6 +2461,8 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     appendRequestedOptions(solicit, response, co_list);
     appendRequestedVendorOptions(solicit, response, ctx, co_list);
 
+    updateReservedFqdn(ctx, response);
+
     // Only generate name change requests if sending a Reply as a result
     // of receiving Rapid Commit option.
     if (response->getType() == DHCPV6_REPLY) {
@@ -2492,6 +2494,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     appendRequestedOptions(request, reply, co_list);
     appendRequestedVendorOptions(request, reply, ctx, co_list);
 
+    updateReservedFqdn(ctx, reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
 
@@ -2520,6 +2523,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     appendRequestedOptions(renew, reply, co_list);
     appendRequestedVendorOptions(renew, reply, ctx, co_list);
 
+    updateReservedFqdn(ctx, reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
 
@@ -2548,6 +2552,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
     appendRequestedOptions(rebind, reply, co_list);
     appendRequestedVendorOptions(rebind, reply, ctx, co_list);
 
+    updateReservedFqdn(ctx, reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
 
@@ -3099,6 +3104,44 @@ Dhcpv6Srv::setReservedClientClasses(const Pkt6Ptr& pkt,
 }
 
 void
+Dhcpv6Srv::updateReservedFqdn(const AllocEngine::ClientContext6& ctx,
+                              const Pkt6Ptr& answer) {
+    if (!answer) {
+        isc_throw(isc::Unexpected, "an instance of the object encapsulating"
+                  " a message must not be NULL when updating reserved FQDN");
+    }
+
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>
+        (answer->getOption(D6O_CLIENT_FQDN));
+
+   // If Client FQDN option is not included, there is nothing to do.
+   if (!fqdn) {
+       return;
+   }
+
+   std::string name = fqdn->getDomainName();
+
+   // If there is a host reservation for this client we have to check whether
+   // this reservation has the same hostname as the hostname currently
+   // present in the FQDN option. If not, it indicates that the allocation
+   // engine picked a different subnet (from within a shared network) for
+   // reservations and we have to send this new value to the client.
+   if (ctx.currentHost() &&
+       !ctx.currentHost()->getHostname().empty()) {
+       std::string new_name = CfgMgr::instance().getD2ClientMgr().
+           qualifyName(ctx.currentHost()->getHostname(), true);
+
+       if (new_name != name) {
+           fqdn->setDomainName(new_name, Option6ClientFqdn::FULL);
+
+           // Replace previous instance of Client FQDN option.
+           answer->delOption(D6O_CLIENT_FQDN);
+           answer->addOption(fqdn);
+       }
+   }
+}
+
+void
 Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
     if (!answer) {
         isc_throw(isc::Unexpected, "an instance of the object encapsulating"
@@ -3162,6 +3205,9 @@ Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
         // Set the generated FQDN in the Client FQDN option.
         fqdn->setDomainName(generated_name, Option6ClientFqdn::FULL);
 
+       answer->delOption(D6O_CLIENT_FQDN);
+       answer->addOption(fqdn);
+
     } catch (const Exception& ex) {
         LOG_ERROR(ddns6_logger, DHCP6_DDNS_GENERATED_FQDN_UPDATE_FAIL)
             .arg(answer->getLabel())

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

@@ -764,6 +764,25 @@ private:
     /// @param classes a reference to added class names for logging
     void classifyByVendor(const Pkt6Ptr& pkt, std::string& classes);
 
+    /// @brief Update FQDN based on the reservations in the current subnet.
+    ///
+    /// When shared networks are in use the allocation engine may switch to
+    /// a different subnet than originally selected. If this new subnet has
+    /// hostname reservations there is a need to update the FQDN option
+    /// value.
+    ///
+    /// This method should be called after lease assignments to perform
+    /// such update when required.
+    ///
+    /// @param ctx Client context.
+    /// @param answer Message being sent to a client, which may hold an FQDN
+    /// option to be updated.
+    ///
+    /// @throw isc::Unexpected if specified message is NULL. This is treated
+    /// as a programmatic error.
+    void updateReservedFqdn(const AllocEngine::ClientContext6& ctx,
+                            const Pkt6Ptr& answer);
+
     /// @private
     /// @brief Generate FQDN to be sent to a client if none exists.
     ///

+ 9 - 1
src/bin/dhcp6/tests/shared_network_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option6_client_fqdn.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <stats/stats_mgr.h>
@@ -599,12 +600,13 @@ const char* NETWORKS_CONFIG[] = {
     "                    \"id\": 100,"
     "                    \"pools\": ["
     "                        {"
-    "                            \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+    "                            \"pool\": \"2001:db8:2::20 - 2001:db8:2::30\""
     "                        }"
     "                    ],"
     "                    \"reservations\": ["
     "                        {"
     "                            \"duid\": \"00:03:00:01:11:22:33:44:55:66\","
+    "                            \"ip-addresses\": [ \"2001:db8:2::20\" ],"
     "                            \"hostname\": \"test.example.org\","
     "                            \"client-classes\": [ \"class-with-dns-servers\" ]"
     "                        }"
@@ -1119,6 +1121,12 @@ TEST_F(Dhcpv6SharedNetworkTest, variousFieldsInReservation) {
     ASSERT_TRUE(fqdn);
     ASSERT_EQ("test.example.org.", fqdn->getDomainName());
 
+    // Make sure that the correct hostname has been stored in the database.
+    Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+                                                            IOAddress("2001:db8:2::20"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("test.example.org.", lease->hostname_);
+
     // The DNS servers option should be derived from the client class based on the
     // static class reservations.
     ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "2001:db8:1::50"));

+ 60 - 4
src/lib/dhcpsrv/alloc_engine.cc

@@ -388,7 +388,7 @@ namespace isc {
 namespace dhcp {
 
 AllocEngine::ClientContext6::ClientContext6()
-    : query_(), fake_allocation_(false), subnet_(), duid_(),
+    : query_(), fake_allocation_(false), subnet_(), host_subnet_(), duid_(),
       hwaddr_(), host_identifiers_(), hosts_(), fwd_dns_update_(false),
       rev_dns_update_(false), hostname_(), callout_handle_(),
       ias_() {
@@ -443,8 +443,9 @@ isAllocated(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const {
 
 ConstHostPtr
 AllocEngine::ClientContext6::currentHost() const {
-    if (subnet_) {
-        auto host = hosts_.find(subnet_->getID());
+    Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
+    if (subnet) {
+        auto host = hosts_.find(subnet->getID());
         if (host != hosts_.cend()) {
             return (host->second);
         }
@@ -853,6 +854,7 @@ void
 AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
                                      Lease6Collection& existing_leases) {
 
+
     // If there are no reservations or the reservation is v4, there's nothing to do.
     if (ctx.hosts_.empty()) {
         LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
@@ -881,6 +883,37 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
                     .arg(lease->typeToText(lease->type_))
                     .arg(lease->addr_.toText());
 
+                // Besides IP reservations we're also going to return other reserved
+                // parameters, such as hostname. We want to hand out the hostname value
+                // from the same reservation entry as IP addresses. Thus, let's see if
+                // there is any hostname reservation.
+                if (!ctx.host_subnet_) {
+                    SharedNetwork6Ptr network;
+                    ctx.subnet_->getSharedNetwork(network);
+                    if (network) {
+                        // Remember the subnet that holds this preferred host
+                        // reservation. The server will use it to return appropriate
+                        // FQDN, classes etc.
+                        ctx.host_subnet_ = network->getSubnet(lease->subnet_id_);
+                        ConstHostPtr host = ctx.hosts_[lease->subnet_id_];
+                        // If there is a hostname reservation here we should stick
+                        // to this reservation. By updating the hostname in the
+                        // context we make sure that the database is updated with
+                        // this new value and the server doesn't need to do it and
+                        // its processing performance is not impacted by the hostname
+                        // updates.
+                        if (host && !host->getHostname().empty()) {
+                            // We have to determine whether the hostname is generated
+                            // in response to client's FQDN or not. If yes, we will
+                            // need to qualify the hostname. Otherwise, we just use
+                            // the hostname as it is specified for the reservation.
+                            OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+                            ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+                                qualifyName(host->getHostname(), static_cast<bool>(fqdn));
+                        }
+                    }
+                }
+
                 // If this is a real allocation, we may need to extend the lease
                 // lifetime.
                 if (!ctx.fake_allocation_ && conditionalExtendLifetime(*lease)) {
@@ -919,7 +952,6 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
             // We have allocated this address/prefix while processing one of the
             // previous IAs, so let's try another reservation.
             if (ctx.isAllocated(addr, prefix_len)) {
-                std::cout << "is allocated  " << addr << std::endl;
                 continue;
             }
 
@@ -931,11 +963,35 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
                 // Ok, let's create a new lease...
                 ctx.subnet_ = subnet;
 
+                // Let's remember the subnet from which the reserved address has been
+                // allocated. We'll use this subnet for allocating other reserved
+                // resources.
+                if (!ctx.host_subnet_) {
+                    ctx.host_subnet_ = subnet;
+                    if (!host->getHostname().empty()) {
+                        // If there is a hostname reservation here we should stick
+                        // to this reservation. By updating the hostname in the
+                        // context we make sure that the database is updated with
+                        // this new value and the server doesn't need to do it and
+                        // its processing performance is not impacted by the hostname
+                        // updates.
+
+                        // We have to determine whether the hostname is generated
+                        // in response to client's FQDN or not. If yes, we will
+                        // need to qualify the hostname. Otherwise, we just use
+                        // the hostname as it is specified for the reservation.
+                        OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+                        ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+                            qualifyName(host->getHostname(), static_cast<bool>(fqdn));
+                    }
+                }
+
                 Lease6Ptr lease = createLease6(ctx, addr, prefix_len);
 
                 // ... and add it to the existing leases list.
                 existing_leases.push_back(lease);
 
+
                 if (ctx.currentIA().type_ == Lease::TYPE_NA) {
                     LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED)
                         .arg(addr.toText())

+ 6 - 1
src/lib/dhcpsrv/alloc_engine.h

@@ -305,6 +305,11 @@ public:
         /// @brief Subnet selected for the client by the server.
         Subnet6Ptr subnet_;
 
+        /// @brief Subnet from which host reservations should be retrieved.
+        ///
+        /// It can be NULL, in which case @c subnet_ value is used.
+        Subnet6Ptr host_subnet_;
+
         /// @brief Client identifier
         DuidPtr duid_;
 
@@ -443,7 +448,7 @@ public:
             ias_.push_back(IAContext());
         };
 
-        /// @brief Returns host for currently selected subnet.
+        /// @brief Returns host from the most preferred subnet.
         ///
         /// @return Pointer to the host object.
         ConstHostPtr currentHost() const;