AllocEngine::ClientContext6::currentHost() const {
Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
if (subnet) {
- auto host = hosts_.find(subnet->getID());
+ SubnetID id = (subnet_->getHostReservationMode() == Network::HR_GLOBAL ?
+ SUBNET_ID_GLOBAL : subnet->getID());
+
+ auto host = hosts_.find(id);
+ if (host != hosts_.cend()) {
+ return (host->second);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+AllocEngine::ClientContext6::globalHost() const {
+ Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
+ if (subnet && subnet_->getHostReservationMode() == Network::HR_GLOBAL) {
+ auto host = hosts_.find(SUBNET_ID_GLOBAL);
if (host != hosts_.cend()) {
return (host->second);
}
}
+
return (ConstHostPtr());
}
+bool
+AllocEngine::ClientContext6::hasGlobalReservation(const IPv6Resrv& resv) const {
+ ConstHostPtr ghost = globalHost();
+ return (ghost && ghost->hasReservation(resv));
+}
+
void AllocEngine::findReservation(ClientContext6& ctx) {
ctx.hosts_.clear();
SharedNetwork6Ptr network;
subnet->getSharedNetwork(network);
+ if (subnet->getHostReservationMode() == Network::HR_GLOBAL) {
+ ConstHostPtr ghost = findGlobalReservation(ctx);
+ if (ghost) {
+ ctx.hosts_[SUBNET_ID_GLOBAL] = ghost;
+
+ // @todo In theory, to support global as part of HR_ALL,
+ // we would just keep going, instead of returning.
+ return;
+ }
+ }
+
// If the subnet belongs to a shared network it is usually going to be
// more efficient to make a query for all reservations for a particular
// client rather than a query for each subnet within this shared network.
}
}
+ConstHostPtr
+AllocEngine::findGlobalReservation(ClientContext6& ctx) {
+ ConstHostPtr host;
+ BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) {
+ // Attempt to find a host using a specified identifier.
+ host = HostMgr::instance().get6(SUBNET_ID_GLOBAL, id_pair.first,
+ &id_pair.second[0], id_pair.second.size());
+
+ // If we found matching global host we're done.
+ if (host) {
+ break;
+ }
+ }
+
+ return (host);
+}
+
+
Lease6Collection
AllocEngine::allocateLeases6(ClientContext6& ctx) {
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,
return;
}
+ if (allocateGlobalReservedLeases6(ctx, existing_leases)) {
+ // global reservation provided the lease, we're done
+ return;
+ }
+
// Let's convert this from Lease::Type to IPv6Reserv::Type
IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ?
IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
if ((lease->valid_lft_ != 0)) {
if ((ctx.hosts_.count(lease->subnet_id_) > 0) &&
- ctx.hosts_[lease->subnet_id_]->hasReservation(IPv6Resrv(type, lease->addr_,
- lease->prefixlen_))) {
+ ctx.hosts_[lease->subnet_id_]->hasReservation(makeIPv6Resrv(*lease))) {
// We found existing lease for a reserved address or prefix.
// We'll simply extend the lifetime of the lease.
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
}
}
+bool
+AllocEngine::allocateGlobalReservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases) {
+ // Get the global host
+ ConstHostPtr ghost = ctx.globalHost();
+ if (!ghost) {
+ return (false);
+ }
+
+ // We want to avoid allocating new lease for an IA if there is already
+ // a valid lease for which client has reservation. So, we first check if
+ // we already have a lease for a reserved address or prefix.
+ BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
+ if ((lease->valid_lft_ != 0) &&
+ (ghost->hasReservation(makeIPv6Resrv(*lease)))) {
+ // We found existing lease for a reserved address or prefix.
+ // We'll simply extend the lifetime of the lease.
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS)
+ .arg(ctx.query_->getLabel())
+ .arg(lease->typeToText(lease->type_))
+ .arg(lease->addr_.toText());
+
+ // 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 (!ghost->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(ghost->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)) {
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+
+ return(true);
+ }
+ }
+
+ // There is no lease for a reservation in this IA. So, let's now iterate
+ // over reservations specified and try to allocate one of them for the IA.
+
+ // Let's convert this from Lease::Type to IPv6Reserv::Type
+ IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ?
+ IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
+
+ const IPv6ResrvRange& reservs = ghost->getIPv6Reservations(type);
+ BOOST_FOREACH(IPv6ResrvTuple type_lease_tuple, reservs) {
+ // We do have a reservation for address or prefix.
+ const IOAddress& addr = type_lease_tuple.second.getPrefix();
+ uint8_t prefix_len = type_lease_tuple.second.getPrefixLen();
+
+ // 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)) {
+ continue;
+ }
+
+ // If there's a lease for this address, let's not create it.
+ // It doesn't matter whether it is for this client or for someone else.
+ if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, addr)) {
+
+ if (!ghost->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(ghost->getHostname(), static_cast<bool>(fqdn));
+ }
+
+ // Ok, let's create a new lease...
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+ Lease6Ptr lease = createLease6(ctx, addr, prefix_len, callout_status);
+
+ // ... 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())
+ .arg(ctx.query_->getLabel());
+ } else {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED)
+ .arg(addr.toText())
+ .arg(static_cast<int>(prefix_len))
+ .arg(ctx.query_->getLabel());
+ }
+
+ // We found a lease for this client and this IA. Let's return.
+ // Returning after the first lease was assigned is useful if we
+ // have multiple reservations for the same client. If the client
+ // sends 2 IAs, the first time we call allocateReservedLeases6 will
+ // use the first reservation and return. The second time, we'll
+ // go over the first reservation, but will discover that there's
+ // a lease corresponding to it and will skip it and then pick
+ // the second reservation and turn it into the lease. This approach
+ // would work for any number of reservations.
+ return (true);
+ }
+ }
+
+ return(false);
+}
+
void
AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
Lease6Collection& existing_leases) {
BOOST_FOREACH(const Lease6Ptr& candidate, copy) {
// If we have reservation we should check if the reservation is for
// the candidate lease. If so, we simply accept the lease.
- if (ctx.hosts_.count(candidate->subnet_id_) > 0) {
- if (candidate->type_ == Lease6::TYPE_NA) {
- if (ctx.hosts_[candidate->subnet_id_]->hasReservation(
- IPv6Resrv(IPv6Resrv::TYPE_NA, candidate->addr_))) {
- continue;
- }
- } else {
- if (ctx.hosts_[candidate->subnet_id_]->hasReservation(
- IPv6Resrv(IPv6Resrv::TYPE_PD,candidate->addr_,
- candidate->prefixlen_))) {
- continue;
- }
- }
+ IPv6Resrv resv = makeIPv6Resrv(*candidate);
+ if ((ctx.hasGlobalReservation(resv)) ||
+ ((ctx.hosts_.count(candidate->subnet_id_) > 0) &&
+ (ctx.hosts_[candidate->subnet_id_]->hasReservation(resv)))) {
+ // We have a subnet reservation
+ continue;
}
// The candidate address doesn't appear to be reserved for us.
for (Lease6Collection::iterator lease = existing_leases.begin();
lease != existing_leases.end(); ++lease) {
- IPv6Resrv resv(ctx.currentIA().type_ == Lease::TYPE_NA ?
- IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD,
- (*lease)->addr_, (*lease)->prefixlen_);
-
- // If there is no reservation for this subnet.
- if ((ctx.hosts_.count((*lease)->subnet_id_) > 0) &&
- (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv))) {
+ // If there is reservation for this keep it.
+ IPv6Resrv resv = makeIPv6Resrv(*(*lease));
+ if (ctx.hasGlobalReservation(resv) ||
+ ((ctx.hosts_.count((*lease)->subnet_id_) > 0) &&
+ (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv)))) {
continue;
+ }
- } else {
-
- // We have reservations, but not for this lease. Release it.
-
- // Remove this lease from LeaseMgr
- LeaseMgrFactory::instance().deleteLease((*lease)->addr_);
+ // We have reservations, but not for this lease. Release it.
+ // Remove this lease from LeaseMgr
+ LeaseMgrFactory::instance().deleteLease((*lease)->addr_);
- // Update DNS if required.
- queueNCR(CHG_REMOVE, *lease);
+ // Update DNS if required.
+ queueNCR(CHG_REMOVE, *lease);
- // Need to decrease statistic for assigned addresses.
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", (*lease)->subnet_id_,
- ctx.currentIA().type_ == Lease::TYPE_NA ?
- "assigned-nas" : "assigned-pds"),
- static_cast<int64_t>(-1));
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", (*lease)->subnet_id_,
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(-1));
- /// @todo: Probably trigger a hook here
+ /// @todo: Probably trigger a hook here
- // Add this to the list of removed leases.
- ctx.currentIA().old_leases_.push_back(*lease);
+ // Add this to the list of removed leases.
+ ctx.currentIA().old_leases_.push_back(*lease);
- // Set this pointer to NULL. The pointer is still valid. We're just
- // setting the Lease6Ptr to NULL value. We'll remove all NULL
- // pointers once the loop is finished.
- lease->reset();
+ // Set this pointer to NULL. The pointer is still valid. We're just
+ // setting the Lease6Ptr to NULL value. We'll remove all NULL
+ // pointers once the loop is finished.
+ lease->reset();
- if (--total == 1) {
- // If there's only one lease left, break the loop.
- break;
- }
+ if (--total == 1) {
+ // If there's only one lease left, break the loop.
+ break;
}
}
}
}
- // Check if the lease still belongs to the subnet and that the use of this subnet
- // is allowed per client classification. If not, remove this lease.
- if (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) ||
- !ctx.subnet_->clientSupported(ctx.query_->getClasses())) {
+ // If the lease is not global and it is either out of range (NAs only) or it
+ // is not permitted by subnet client classification, delete it.
+ if (!(ctx.hasGlobalReservation(makeIPv6Resrv(*lease))) &&
+ (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) ||
+ !ctx.subnet_->clientSupported(ctx.query_->getClasses()))) {
// Oh dear, the lease is no longer valid. We need to get rid of it.
// Remove this lease from LeaseMgr
// Create a subnet with a pool that has one address.
initSubnet(IOAddress("2001:db8:1::"), addr, addr);
-
+
// Initialize FQDN for a lease.
initFqdn("myhost.example.com", true, true);
EXPECT_TRUE(leases.empty());
}
+// Verifies that client with a global hostname reservation can get and
+// renew a dynamic lease from their selected subnet.
+TEST_F(AllocEngine6Test, globalHostDynamicAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated a dynamic address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::10", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ // We verify the "time" change in case the lease returned to us
+ // by expectOneLease ever becomes a copy and not what is in the lease mgr.
+ --lease->cltt_;
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(from_mgr->cltt_, lease->cltt_);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(make_pair(IOAddress("2001:db8:1::10"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, true);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a global address reservation can get and
+// renew a lease for an arbitrary address.
+TEST_F(AllocEngine6Test, globalHostReservedAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("3001::1", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ // We verify the "time" change in case the lease returned to us
+ // by expectOneLease ever becomes a copy and not what is in the lease mgr.
+ --lease->cltt_;
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(from_mgr->cltt_, lease->cltt_);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(make_pair(IOAddress("3001::1"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, false);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a global prefix reservation can get and
+// renew a lease for an arbitrary prefix.
+TEST_F(AllocEngine6Test, globalHostReservedPrefix) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("3001::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ // We verify the "time" change in case the lease returned to us
+ // by expectOneLease ever becomes a copy and not what is in the lease mgr.
+ --lease->cltt_;
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(from_mgr->cltt_, lease->cltt_);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(make_pair(IOAddress("3001::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, false);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
}; // namespace test
}; // namespace dhcp
}; // namespace isc