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) {
appendRequestedOptions(request, reply, co_list);
appendRequestedVendorOptions(request, reply, ctx, co_list);
+ updateReservedFqdn(ctx, reply);
generateFqdn(reply);
createNameChangeRequests(reply, ctx);
appendRequestedOptions(renew, reply, co_list);
appendRequestedVendorOptions(renew, reply, ctx, co_list);
+ updateReservedFqdn(ctx, reply);
generateFqdn(reply);
createNameChangeRequests(reply, ctx);
appendRequestedOptions(rebind, reply, co_list);
appendRequestedVendorOptions(rebind, reply, ctx, co_list);
+ updateReservedFqdn(ctx, reply);
generateFqdn(reply);
createNameChangeRequests(reply, ctx);
}
}
+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) {
// 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())
/// @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.
///
#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>
" \"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\" ]"
" }"
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"));
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_() {
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);
}
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,
.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)) {
// 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;
}
// 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())
/// @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_;
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;