From: Marcin Siodelski Date: Wed, 20 Sep 2017 16:27:55 +0000 (+0200) Subject: [5307] Assign reserved hostnames for shared networks. X-Git-Tag: trac5363_base~11^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dd3e3a9101e06a5bb80ac1a55b45fcaa6827b22c;p=thirdparty%2Fkea.git [5307] Assign reserved hostnames for shared networks. --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 0b9e705343..9dde04b4d5 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/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); @@ -3098,6 +3103,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 + (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) { @@ -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()) diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 6e469f9142..ed7a103005 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/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. /// diff --git a/src/bin/dhcp6/tests/shared_network_unittest.cc b/src/bin/dhcp6/tests/shared_network_unittest.cc index 496fd33303..771278ef39 100644 --- a/src/bin/dhcp6/tests/shared_network_unittest.cc +++ b/src/bin/dhcp6/tests/shared_network_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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")); diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 593ea6ec1b..b58de9f9ed 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/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(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(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()) diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 32dd51c08f..be78db245d 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/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;