Commit 07ce52ee authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac5364'

# Conflicts:
#	src/bin/dhcp6/tests/config_parser_unittest.cc
parents 4cc8e6ec dfefff01
......@@ -3182,6 +3182,13 @@ If not specified, the default value is:
2001:db8:cafe::1 and no server unicast.
</para>
<para>Some parameters must be the same in all subnets in the same shared
network. This restriction applies to <command>interface</command> and
<command>rapid-commit</command> settings. The most convenient way is to
define them on shared network scope, but you may specify them for each
subnet. However, care should be taken for each subnet to have the same
value.</para>
<section>
<title>Local and relayed traffic in shared networks</title>
......
......@@ -663,6 +663,14 @@ the client. The first argument includes the client and the
transaction identification information. The second arguments
includes the subnet details.
% DHCP4_SUBNET_DYNAMICALLY_CHANGED %1: changed selected subnet %2 to subnet %3 from shared network %4 for client assignments
This debug message indicates that the server is using another subnet
than initially selected for client assignments. This newly selected
subnet belongs to the same shared network as the original subnet.
Some reasons why the new subnet was selected include: address pool
exhaustion in the original subnet or the fact that the new subnet
includes some static reservations for this client.
% DHCP4_SUBNET_SELECTED %1: the subnet with ID %2 was selected for client assignments
This is a debug message noting the selection of a subnet to be used for
address and option assignment. Subnet selection is one of the early
......
......@@ -1849,7 +1849,18 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
// Subnet may be modified by the allocation engine, if the initial subnet
// belongs to a shared network.
subnet = ctx->subnet_;
if (subnet->getID() != ctx->subnet_->getID()) {
SharedNetwork4Ptr network;
subnet->getSharedNetwork(network);
if (network) {
LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_DYNAMICALLY_CHANGED)
.arg(query->getLabel())
.arg(subnet->toText())
.arg(ctx->subnet_->toText())
.arg(network->getName());
}
subnet = ctx->subnet_;
}
if (lease) {
// We have a lease! Let's set it in the packet and send it back to
......
......@@ -300,6 +300,9 @@ Dhcp4Client::doInform(const bool set_ciaddr) {
void
Dhcp4Client::doRelease() {
// There is no response for Release message.
context_.response_.reset();
if (config_.lease_.addr_.isV4Zero()) {
isc_throw(Dhcp4ClientError, "failed to send the release"
" message because client doesn't have a lease");
......
......@@ -711,6 +711,14 @@ the client. The first argument includes the client and the
transaction identification information. The second argument
includes the subnet details.
% DHCP6_SUBNET_DYNAMICALLY_CHANGED %1: changed selected subnet %2 to subnet %3 from shared network %4 for client assignments
This debug message indicates that the server is using another subnet
than initially selected for client assignments. This newly selected
subnet belongs to the same shared network as the original subnet.
Some reasons why the new subnet was selected include: address pool
exhaustion in the original subnet or the fact that the new subnet
includes some static reservations for this client.
% DHCP6_SUBNET_SELECTED %1: the subnet with ID %2 was selected for client assignments
This is a debug message noting the selection of a subnet to be used for
address and option assignment. Subnet selection is one of the early
......
......@@ -1195,6 +1195,8 @@ void
Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
AllocEngine::ClientContext6& ctx) {
Subnet6Ptr subnet = ctx.subnet_;
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
// @todo add support for IA_TA
......@@ -1232,6 +1234,20 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
break;
}
}
// Subnet may be modified by the allocation engine, if the initial subnet
// belongs to a shared network.
if (ctx.subnet_ && subnet && (subnet->getID() != ctx.subnet_->getID())) {
SharedNetwork6Ptr network;
subnet->getSharedNetwork(network);
if (network) {
LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC_DATA, DHCP6_SUBNET_DYNAMICALLY_CHANGED)
.arg(question->getLabel())
.arg(subnet->toText())
.arg(ctx.subnet_->toText())
.arg(network->getName());
}
}
}
......
......@@ -245,8 +245,33 @@ public:
const Subnet6Collection* subnets = (*net)->getAllSubnets();
if (subnets) {
bool rapid_commit;
// For each subnet, add it to a list of regular subnets.
for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
// Rapid commit must either be enabled or disabled in all subnets
// in the shared network.
if (subnet == subnets->begin()) {
// If this is the first subnet, remember the value.
rapid_commit = (*subnet)->getRapidCommit();
} else {
// Ok, this is the second or following subnets. The value
// must match what was set in the first subnet.
if (rapid_commit != (*subnet)->getRapidCommit()) {
isc_throw(DhcpConfigError, "All subnets in a shared network "
"must have the same rapid-commit value. Subnet "
<< (*subnet)->toText()
<< " has specified rapid-commit "
<< ( (*subnet)->getRapidCommit() ? "true" : "false")
<< ", but earlier subnet in the same shared-network"
<< " or the shared-network itself used rapid-commit "
<< (rapid_commit ? "true" : "false"));
}
}
if (iface.empty()) {
iface = (*subnet)->getIface();
continue;
......@@ -284,8 +309,8 @@ public:
}
}
};
} // anonymous namespace
......@@ -562,7 +587,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
// it checks that there is no conflict between plain subnets and those
// defined as part of shared networks.
global_parser.sanityChecks(srv_config, mutable_cfg);
} catch (const isc::Exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
.arg(config_pair.first).arg(ex.what());
......@@ -597,7 +622,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
// Setup the command channel.
configureCommandChannel();
// No need to commit interface names as this is handled by the
// CfgMgr::commit() function.
......
......@@ -5676,7 +5676,7 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
" },\n"
" \"valid-lifetime\": 400, \n"
" \"interface-id\": \"twotwo\",\n"
" \"rapid-commit\": false,\n"
" \"rapid-commit\": true,\n"
" \"reservation-mode\": \"out-of-pool\"\n"
" }\n"
" ]\n"
......@@ -5732,7 +5732,7 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
ASSERT_TRUE(s->getInterfaceId());
EXPECT_TRUE(iface_id2.equals(s->getInterfaceId()));
EXPECT_EQ(IOAddress("2222::2"), s->getRelayInfo().addr_);
EXPECT_FALSE(s->getRapidCommit());
EXPECT_TRUE(s->getRapidCommit());
EXPECT_EQ(Network::HR_OUT_OF_POOL, s->getHostReservationMode());
// Ok, now check the second shared subnet.
......@@ -5837,6 +5837,38 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDeriveInterfaces) {
EXPECT_EQ("", s->getIface());
}
// It is not allowed to have different values for interfaces names is subnets
// in the same shared network.
TEST_F(Dhcp6ParserTest, sharedNetworksInterfacesMixed) {
// We need to fake the interfaces present, because we want to test
// interface names inheritance. However, there are sanity checks
// on subnet level that would refuse the value if the interface
// is not present.
IfaceMgrTestConfig iface_config(true);
string config = "{\n"
"\"shared-networks\": [ {\n"
" \"name\": \"foo\"\n,"
" \"subnet6\": [\n"
" { \n"
" \"subnet\": \"2001:db1::/48\",\n"
" \"interface\": \"eth0\"\n"
" },\n"
" { \n"
" \"subnet\": \"2001:db2::/48\",\n"
" \"interface\": \"eth1\"\n"
" }\n"
" ]\n"
" } ]\n"
"} \n";
configure(config, CONTROL_RESULT_ERROR, "Subnet 2001:db2::/48 has specified "
"interface eth1, but earlier subnet in the same shared-network "
"or the shared-network itself used eth0");
}
// This test checks if client-class is derived properly.
TEST_F(Dhcp6ParserTest, sharedNetworksDeriveClientClass) {
......@@ -5921,4 +5953,101 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDeriveClientClass) {
EXPECT_TRUE(classes.empty());
}
// Tests if rapid-commit is derived properly.
TEST_F(Dhcp6ParserTest, sharedNetworksRapidCommit) {
string config = "{\n"
"\"shared-networks\": [ {\n"
" \"name\": \"frog\"\n,"
" \"rapid-commit\": true,\n"
" \"subnet6\": [\n"
" { \n"
" \"subnet\": \"2001:db1::/48\",\n"
" \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
" },\n"
" { \n"
" \"subnet\": \"2001:db2::/48\",\n"
" \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
" \"client-class\": \"beta\"\n"
" }\n"
" ]\n"
" },\n"
"{ // second shared-network starts here\n"
" \"name\": \"bar\",\n"
" \"rapid-commit\": false,\n"
" \"subnet6\": [\n"
" {\n"
" \"subnet\": \"2001:db3::/48\",\n"
" \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
" }\n"
" ]\n"
"} ]\n"
"} \n";
configure(config, CONTROL_RESULT_SUCCESS, "");
// Now verify that the shared network was indeed configured.
CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
->getCfgSharedNetworks6();
// Two shared networks are expeced.
ASSERT_TRUE(cfg_net);
const SharedNetwork6Collection* nets = cfg_net->getAll();
ASSERT_TRUE(nets);
ASSERT_EQ(2, nets->size());
// Let's check the first one.
SharedNetwork6Ptr net = nets->at(0);
ASSERT_TRUE(net);
const Subnet6Collection * subs = net->getAllSubnets();
ASSERT_TRUE(subs);
ASSERT_EQ(2, subs->size());
EXPECT_TRUE(subs->at(0)->getRapidCommit());
EXPECT_TRUE(subs->at(1)->getRapidCommit());
// Ok, now check the second shared network. It doesn't have
// anything defined on shared-network or subnet level, so
// everything should have default values.
net = nets->at(1);
ASSERT_TRUE(net);
subs = net->getAllSubnets();
ASSERT_TRUE(subs);
EXPECT_EQ(1, subs->size());
// This subnet should derive its renew-timer from global scope.
EXPECT_FALSE(subs->at(0)->getRapidCommit());
}
// Tests that non-matching rapid-commit setting for subnets belonging to a
// shared network cause configuration error.
TEST_F(Dhcp6ParserTest, sharedNetworksRapidCommitMix) {
string config = "{\n"
"\"shared-networks\": [ {\n"
" \"name\": \"frog\"\n,"
" \"subnet6\": [\n"
" { \n"
" \"subnet\": \"2001:db1::/48\",\n"
" \"rapid-commit\": true,\n"
" \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
" },\n"
" { \n"
" \"subnet\": \"2001:db2::/48\",\n"
" \"rapid-commit\": false,\n"
" \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
" \"client-class\": \"beta\"\n"
" }\n"
" ]\n"
" } ]\n"
"} \n";
configure(config, CONTROL_RESULT_ERROR, "All subnets in a shared network "
"must have the same rapid-commit value. Subnet 2001:db2::/48 has "
"specified rapid-commit false, but earlier subnet in the same "
"shared-network or the shared-network itself used rapid-commit true");
}
};
......@@ -24,6 +24,7 @@
#include <algorithm>
#include <cstdlib>
#include <time.h>
#include <utility>
using namespace isc::asiolink;
using namespace isc::dhcp;
......@@ -108,7 +109,8 @@ Dhcp6Client::Dhcp6Client() :
use_client_id_(true),
use_rapid_commit_(false),
client_ias_(),
fqdn_() {
fqdn_(),
interface_id_() {
}
Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
......@@ -125,7 +127,8 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
use_client_id_(true),
use_rapid_commit_(false),
client_ias_(),
fqdn_() {
fqdn_(),
interface_id_() {
}
void
......@@ -899,7 +902,14 @@ Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
relay.peeraddr_ = asiolink::IOAddress("fe80::1");
relay.msg_type_ = DHCPV6_RELAY_FORW;
relay.hop_count_ = 1;
// Interface identifier, if specified.
if (interface_id_) {
relay.options_.insert(std::make_pair(D6O_INTERFACE_ID, interface_id_));
}
msg->relay_info_.push_back(relay);
} else {
// The test provided relay_info_, let's use that.
msg->relay_info_ = relay_info_;
......@@ -932,6 +942,12 @@ Dhcp6Client::requestPrefix(const uint32_t iaid,
client_ias_.push_back(ClientIA(Lease::TYPE_PD, iaid, prefix, prefix_len));
}
void
Dhcp6Client::useInterfaceId(const std::string& interface_id) {
OptionBuffer buf(interface_id.begin(), interface_id.end());
interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, buf));
}
void
Dhcp6Client::useFQDN(const uint8_t flags, const std::string& fqdn_name,
Option6ClientFqdn::DomainNameType fqdn_type) {
......@@ -943,6 +959,28 @@ Dhcp6Client::addExtraOption(const OptionPtr& opt) {
extra_options_.insert(std::make_pair(opt->getType(), opt));
}
void
Dhcp6Client::clearExtraOptions() {
extra_options_.clear();
}
void
Dhcp6Client::printConfiguration() const {
// Print DUID
std::cout << "Client " << (duid_ ? duid_->toText() : "(without duid)")
<< " got " << getLeaseNum() << " lease(s): ";
// Print leases
for (int i = 0; i < getLeaseNum(); i++) {
Lease6 lease = getLease(i);
std::cout << lease.addr_.toText() << " ";
}
/// @todo: Print many other parameters.
std::cout << std::endl;
}
} // end of namespace isc::dhcp::test
} // end of namespace isc::dhcp
} // end of namespace isc
......@@ -639,6 +639,11 @@ public:
relay_link_addr_ = link_addr;
}
/// @brief Sets interface id value to be inserted into relay agent option.
///
/// @param interface_id Value of the interface id as string.
void useInterfaceId(const std::string& interface_id);
/// @brief Controls whether the client should send a client-id or not
/// @param send should the client-id be sent?
void useClientId(const bool send) {
......@@ -741,6 +746,15 @@ public:
/// @param opt additional option to be sent
void addExtraOption(const OptionPtr& opt);
/// @brief Configures the client to not send any extra options.
void clearExtraOptions();
/// @brief Debugging method the prints currently held configuration
///
/// @todo: This is mostly useful when debugging tests. This method
/// is incomplete. Please extend it when needed.
void printConfiguration() const;
private:
/// @brief Applies the new leases for the client.
......@@ -906,6 +920,9 @@ private:
/// @brief FQDN requested by the client.
Option6ClientFqdnPtr fqdn_;
/// @brief Interface id.
OptionPtr interface_id_;
};
} // end of namespace isc::dhcp::test
......
......@@ -813,6 +813,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
// there's no existing lease for selected candidate, so it is
// free. Let's allocate it.
ctx.subnet_ = subnet;
Lease6Ptr lease = createLease6(ctx, candidate, prefix_len);
if (lease) {
// We are allocating a new lease (not renewing). So, the
......@@ -838,6 +839,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
Lease6Ptr old_lease(new Lease6(*existing));
ctx.currentIA().old_leases_.push_back(old_lease);
ctx.subnet_ = subnet;
existing = reuseExpiredLease(existing, ctx, prefix_len);
leases.push_back(existing);
......@@ -1508,9 +1510,25 @@ AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
return;
}
// Check if the lease still belongs to the subnet. If it doesn't,
// we'll need to remove it.
if ((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) {
// It is likely that the lease for which we're extending the lifetime doesn't
// belong to the current but a sibling subnet.
if (ctx.subnet_->getID() != lease->subnet_id_) {
SharedNetwork6Ptr network;
ctx.subnet_->getSharedNetwork(network);
if (network) {
Subnet6Ptr subnet = network->getSubnet(SubnetID(lease->subnet_id_));
// Found the actual subnet this lease belongs to. Stick to this
// subnet.
if (subnet) {
ctx.subnet_ = subnet;
}
}
}
// 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())) {
// Oh dear, the lease is no longer valid. We need to get rid of it.
// Remove this lease from LeaseMgr
......@@ -2859,7 +2877,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
// Need to decrease statistic for assigned addresses.
StatsMgr::instance().addValue(
StatsMgr::generateName("subnet", ctx.subnet_->getID(), "assigned-addresses"),
StatsMgr::generateName("subnet", client_lease->subnet_id_, "assigned-addresses"),
static_cast<int64_t>(-1));
}
......
......@@ -325,11 +325,7 @@ public:
///
/// The result set is populated by iterating over the IPv4 leases in
/// storage, in ascending order by address, accumulating the lease state
/// counts per subnet. Note that walking the leases by address should
/// inherently group them by subnet, and while this does not guarantee
/// ascending order of subnet id, it should be sufficient to accumulate
/// state counts per subnet. This avoids introducing an additional
/// subnet_id index.
/// counts per subnet.
/// At the completion of all entries for a given subnet, the counts are
/// used to create LeaseStatsRow instances which are appended to an
/// internal vector. The process results in a vector containing one entry
......@@ -340,8 +336,8 @@ public:
/// - Lease::STATE_DEFAULT (i.e. assigned)
/// - Lease::STATE_DECLINED
void start() {
const Lease4StorageAddressIndex& idx
= storage4_.get<AddressIndexTag>();
const Lease4StorageSubnetIdIndex& idx
= storage4_.get<SubnetIdIndexTag>();
// Iterate over the leases in order by subnet, accumulating per
// subnet counts for each state of interest. As we finish each
......@@ -349,7 +345,7 @@ public:
SubnetID cur_id = 0;
int64_t assigned = 0;
int64_t declined = 0;
for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
for(Lease4StorageSubnetIdIndex::const_iterator lease = idx.begin();
lease != idx.end(); ++lease) {
// If we've hit the next subnet, add rows for the current subnet
// and wipe the accumulators
......@@ -417,16 +413,11 @@ public:
/// @brief Creates the IPv6 lease statistical data result set
///
/// The result set is populated by iterating over the IPv6 leases in
/// storage, in ascending order by address, accumulating the lease state
/// counts per subnet. Note that walking the leases by address should
/// inherently group them by subnet, and while this does not guarantee
/// ascending order of subnet id, it should be sufficient to accumulate
/// state counts per subnet. This avoids introducing an additional
/// subnet_id index.
/// At the completion of all entries for a given subnet, the counts
/// are used to create LeaseStatsRow instances which are appended to an
/// internal vector. The process results in a vector containing one entry
/// per state per lease type per subnet.
/// storage, in ascending order by subnet id, accumulating the lease state
/// counts per subnet. At the completion of all entries for a given subnet,
/// the counts are used to create LeaseStatsRow instances which are appended
/// to an internal vector. The process results in a vector containing one
/// entry per state per lease type per subnet.
///
/// Currently the states counted are:
///
......@@ -434,8 +425,8 @@ public:
/// - Lease::STATE_DECLINED
virtual void start() {
// Get the subnet_id index
const Lease6StorageAddressIndex& idx
= storage6_.get<AddressIndexTag>();
const Lease6StorageSubnetIdIndex& idx
= storage6_.get<SubnetIdIndexTag>();
// Iterate over the leases in order by subnet, accumulating per
// subnet counts for each state of interest. As we finish each
......@@ -445,7 +436,7 @@ public:
int64_t declined = 0;
int64_t assigned_pds = 0;
for(Lease6StorageAddressIndex::const_iterator lease = idx.begin();
for(Lease6StorageSubnetIdIndex::const_iterator lease = idx.begin();
lease != idx.end(); ++lease) {
// If we've hit the next subnet, add rows for the current subnet
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment