2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-29 13:07:50 +00:00

[#1710] Added lifetimes to v6 client classes

kea-dhcp6 client classes now support valid and preferred
lifetime triplets.  Need to update ARM and CB yet.

/doc/examples/kea6/all-keys.json
    Updated

/src/bin/dhcp6/dhcp6_lexer.ll b/src/bin/dhcp6/dhcp6_lexer.ll
    Added preferred and valid lifetime parameters to client class

/src/bin/dhcp6/dhcp6_parser.yy
    Added preferred and valid lifetime parameters to client class

src/bin/dhcp6/tests/config_parser_unittest.cc
    TEST_F(Dhcp6ParserTest, clientClassValidPreferredLifetime)  - new test

/src/bin/dhcp6/tests/parser_unittest.cc
    Added use of EXPECT_NO_THROW_LOG to ease finding JSON errors

/src/lib/dhcpsrv/alloc_engine.*
    AllocEngine::getLifetimes6() - new function
    Lease6Ptr AllocEngine::createLease6() - calls new function

/src/lib/dhcpsrv/client_class_def.*
/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
    ClientClassDef - added support for preferred lifetime triplet

/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
    TEST_F(AllocEngine6Test, getValidLifetime)
    TEST_F(AllocEngine6Test, getPreferredLifetime) - new tests

/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
    TEST_F(ClientClassDefParserTest, preferredLifetimeTests) - new test

/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
    updated tests
This commit is contained in:
Thomas Markwalder 2021-08-05 14:05:32 -04:00
parent 5400f23dea
commit d98770b09c
17 changed files with 1539 additions and 973 deletions

View File

@ -22,7 +22,25 @@
// Class selection expression. The DHCP packet is assigned to this // Class selection expression. The DHCP packet is assigned to this
// class when the given expression evaluates to true. // class when the given expression evaluates to true.
"test": "member('HA_server1')" "test": "member('HA_server1')",
// Class valid lifetime.
"valid-lifetime": 6000,
// Class min valid lifetime.
"min-valid-lifetime": 4000,
// Class max valid lifetime.
"max-valid-lifetime": 8000,
// Class preferred lifetime.
"preferred-lifetime": 7000,
// Class min preferred lifetime.
"min-preferred-lifetime": 5000,
// Class max preferred lifetime.
"max-preferred-lifetime": 9000
}, },
{ {
// Second class name. // Second class name.

File diff suppressed because it is too large Load Diff

View File

@ -832,6 +832,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
case isc::dhcp::Parser6Context::DHCP6: case isc::dhcp::Parser6Context::DHCP6:
case isc::dhcp::Parser6Context::SUBNET6: case isc::dhcp::Parser6Context::SUBNET6:
case isc::dhcp::Parser6Context::SHARED_NETWORK: case isc::dhcp::Parser6Context::SHARED_NETWORK:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
return isc::dhcp::Dhcp6Parser::make_PREFERRED_LIFETIME(driver.loc_); return isc::dhcp::Dhcp6Parser::make_PREFERRED_LIFETIME(driver.loc_);
default: default:
return isc::dhcp::Dhcp6Parser::make_STRING("preferred-lifetime", driver.loc_); return isc::dhcp::Dhcp6Parser::make_STRING("preferred-lifetime", driver.loc_);
@ -843,6 +844,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
case isc::dhcp::Parser6Context::DHCP6: case isc::dhcp::Parser6Context::DHCP6:
case isc::dhcp::Parser6Context::SUBNET6: case isc::dhcp::Parser6Context::SUBNET6:
case isc::dhcp::Parser6Context::SHARED_NETWORK: case isc::dhcp::Parser6Context::SHARED_NETWORK:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
return isc::dhcp::Dhcp6Parser::make_MIN_PREFERRED_LIFETIME(driver.loc_); return isc::dhcp::Dhcp6Parser::make_MIN_PREFERRED_LIFETIME(driver.loc_);
default: default:
return isc::dhcp::Dhcp6Parser::make_STRING("min-preferred-lifetime", driver.loc_); return isc::dhcp::Dhcp6Parser::make_STRING("min-preferred-lifetime", driver.loc_);
@ -854,6 +856,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
case isc::dhcp::Parser6Context::DHCP6: case isc::dhcp::Parser6Context::DHCP6:
case isc::dhcp::Parser6Context::SUBNET6: case isc::dhcp::Parser6Context::SUBNET6:
case isc::dhcp::Parser6Context::SHARED_NETWORK: case isc::dhcp::Parser6Context::SHARED_NETWORK:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
return isc::dhcp::Dhcp6Parser::make_MAX_PREFERRED_LIFETIME(driver.loc_); return isc::dhcp::Dhcp6Parser::make_MAX_PREFERRED_LIFETIME(driver.loc_);
default: default:
return isc::dhcp::Dhcp6Parser::make_STRING("max-preferred-lifetime", driver.loc_); return isc::dhcp::Dhcp6Parser::make_STRING("max-preferred-lifetime", driver.loc_);
@ -865,6 +868,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
case isc::dhcp::Parser6Context::DHCP6: case isc::dhcp::Parser6Context::DHCP6:
case isc::dhcp::Parser6Context::SUBNET6: case isc::dhcp::Parser6Context::SUBNET6:
case isc::dhcp::Parser6Context::SHARED_NETWORK: case isc::dhcp::Parser6Context::SHARED_NETWORK:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
return isc::dhcp::Dhcp6Parser::make_VALID_LIFETIME(driver.loc_); return isc::dhcp::Dhcp6Parser::make_VALID_LIFETIME(driver.loc_);
default: default:
return isc::dhcp::Dhcp6Parser::make_STRING("valid-lifetime", driver.loc_); return isc::dhcp::Dhcp6Parser::make_STRING("valid-lifetime", driver.loc_);
@ -876,6 +880,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
case isc::dhcp::Parser6Context::DHCP6: case isc::dhcp::Parser6Context::DHCP6:
case isc::dhcp::Parser6Context::SUBNET6: case isc::dhcp::Parser6Context::SUBNET6:
case isc::dhcp::Parser6Context::SHARED_NETWORK: case isc::dhcp::Parser6Context::SHARED_NETWORK:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
return isc::dhcp::Dhcp6Parser::make_MIN_VALID_LIFETIME(driver.loc_); return isc::dhcp::Dhcp6Parser::make_MIN_VALID_LIFETIME(driver.loc_);
default: default:
return isc::dhcp::Dhcp6Parser::make_STRING("min-valid-lifetime", driver.loc_); return isc::dhcp::Dhcp6Parser::make_STRING("min-valid-lifetime", driver.loc_);
@ -887,6 +892,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
case isc::dhcp::Parser6Context::DHCP6: case isc::dhcp::Parser6Context::DHCP6:
case isc::dhcp::Parser6Context::SUBNET6: case isc::dhcp::Parser6Context::SUBNET6:
case isc::dhcp::Parser6Context::SHARED_NETWORK: case isc::dhcp::Parser6Context::SHARED_NETWORK:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
return isc::dhcp::Dhcp6Parser::make_MAX_VALID_LIFETIME(driver.loc_); return isc::dhcp::Dhcp6Parser::make_MAX_VALID_LIFETIME(driver.loc_);
default: default:
return isc::dhcp::Dhcp6Parser::make_STRING("max-valid-lifetime", driver.loc_); return isc::dhcp::Dhcp6Parser::make_STRING("max-valid-lifetime", driver.loc_);

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -2271,6 +2271,12 @@ client_class_param: client_class_name
| option_data_list | option_data_list
| user_context | user_context
| comment | comment
| preferred_lifetime
| min_preferred_lifetime
| max_preferred_lifetime
| valid_lifetime
| min_valid_lifetime
| max_valid_lifetime
| unknown_map_entry | unknown_map_entry
; ;

View File

@ -1,8 +1,8 @@
// A Bison parser, made by GNU Bison 3.7.6. // A Bison parser, made by GNU Bison 3.7.2.
// Locations for Bison parsers in C++ // Locations for Bison parsers in C++
// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc. // Copyright (C) 2002-2015, 2018-2020 Free Software Foundation, Inc.
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
@ -15,7 +15,7 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// As a special exception, you may create a larger work that contains // As a special exception, you may create a larger work that contains
// part or all of the Bison parser skeleton and distribute that work // part or all of the Bison parser skeleton and distribute that work

View File

@ -29,6 +29,7 @@
#include <hooks/hooks_manager.h> #include <hooks/hooks_manager.h>
#include <process/config_ctl_info.h> #include <process/config_ctl_info.h>
#include <stats/stats_mgr.h> #include <stats/stats_mgr.h>
#include <testutils/gtest_utils.h>
#include <testutils/log_utils.h> #include <testutils/log_utils.h>
#include <util/chrono_time_utils.h> #include <util/chrono_time_utils.h>
@ -8090,4 +8091,66 @@ TEST_F(Dhcp6ParserTest, multiThreadingSettings) {
<< " actual: " << *(cfg) << std::endl; << " actual: " << *(cfg) << std::endl;
} }
// Verifies that client class definitions may specify
// valid and preferred lifetime triplets.
TEST_F(Dhcp6ParserTest, clientClassValidPreferredLifetime) {
string config = "{ " + genIfaceConfig() + "," +
"\"client-classes\" : [ \n"
" { \n"
" \"name\": \"one\", \n"
" \"min-valid-lifetime\": 1000, \n"
" \"valid-lifetime\": 2000, \n"
" \"max-valid-lifetime\": 3000, \n"
" \"min-preferred-lifetime\": 4000, \n"
" \"preferred-lifetime\": 5000, \n"
" \"max-preferred-lifetime\": 6000 \n"
" }, \n"
" { \n"
" \"name\": \"two\" \n"
" } \n"
"], \n"
"\"subnet6\": [ { \n"
" \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
" \"subnet\": \"2001:db8:1::/64\""
" } ] \n"
"} \n";
ConstElementPtr json;
ASSERT_NO_THROW_LOG(json = parseDHCP6(config));
extractConfig(config);
ConstElementPtr status;
ASSERT_NO_THROW_LOG(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
// We check staging config because CfgMgr::commit hasn't been executed.
ClientClassDictionaryPtr dictionary;
dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ASSERT_TRUE(dictionary);
EXPECT_EQ(2, dictionary->getClasses()->size());
// Execute the commit
ASSERT_NO_THROW(CfgMgr::instance().commit());
// Verify that after commit, the current config has the correct dictionary
dictionary = CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
ASSERT_TRUE(dictionary);
EXPECT_EQ(2, dictionary->getClasses()->size());
ClientClassDefPtr class_def = dictionary->findClass("one");
ASSERT_TRUE(class_def);
EXPECT_EQ(class_def->getValid().getMin(), 1000);
EXPECT_EQ(class_def->getValid().get(), 2000);
EXPECT_EQ(class_def->getValid().getMax(), 3000);
EXPECT_EQ(class_def->getPreferred().getMin(), 4000);
EXPECT_EQ(class_def->getPreferred().get(), 5000);
EXPECT_EQ(class_def->getPreferred().getMax(), 6000);
class_def = dictionary->findClass("two");
ASSERT_TRUE(class_def);
EXPECT_TRUE(class_def->getValid().unspecified());
}
} // namespace } // namespace

View File

@ -10,6 +10,7 @@
#include <dhcpsrv/parsers/simple_parser6.h> #include <dhcpsrv/parsers/simple_parser6.h>
#include <testutils/io_utils.h> #include <testutils/io_utils.h>
#include <testutils/user_context_utils.h> #include <testutils/user_context_utils.h>
#include <testutils/gtest_utils.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <fstream> #include <fstream>
#include <set> #include <set>
@ -245,7 +246,7 @@ void testFile(const std::string& fname) {
cout << "Parsing file " << fname << "(" << decommented << ")" << endl; cout << "Parsing file " << fname << "(" << decommented << ")" << endl;
EXPECT_NO_THROW(json = Element::fromJSONFile(decommented, true)); EXPECT_NO_THROW_LOG(json = Element::fromJSONFile(decommented, true));
reference_json = moveComments(json); reference_json = moveComments(json);
// remove the temporary file // remove the temporary file

View File

@ -1906,6 +1906,64 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
return (expired); return (expired);
} }
void
AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& valid) {
// If the triplets are specified in one of our classes use it.
// We use the first one we find for each lifetime.
Triplet<uint32_t> candidate_preferred;
Triplet<uint32_t> candidate_valid;
const ClientClasses classes = ctx.query_->getClasses();
if (!classes.empty()) {
// Let's get class definitions
const ClientClassDictionaryPtr& dict =
CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
// Iterate over the assigned class defintions.
int cnt = 0;
for (ClientClasses::const_iterator name = classes.cbegin();
name != classes.cend() && cnt < 2; ++name) {
ClientClassDefPtr cl = dict->findClass(*name);
if (candidate_preferred.unspecified() &&
(cl && (!cl->getPreferred().unspecified()))) {
candidate_preferred = cl->getPreferred();
++cnt;
}
if (candidate_valid.unspecified() &&
(cl && (!cl->getValid().unspecified()))) {
candidate_valid = cl->getValid();
++cnt;
}
}
}
// If no classes specified preferred lifetime, get it from the subnet.
if (!candidate_preferred) {
candidate_preferred = ctx.subnet_->getPreferred();
}
// If no classes specified valid lifetime, get it from the subnet.
if (!candidate_valid) {
candidate_valid = ctx.subnet_->getValid();
}
// Set the outbound parameters to the values we have so far.
preferred = candidate_preferred;
valid = candidate_valid;
// If client requested either value, use the requested value(s) bounded by
// the candidate triplet(s).
if (!ctx.currentIA().hints_.empty()) {
if (ctx.currentIA().hints_[0].getPreferred()) {
preferred = candidate_preferred.get(ctx.currentIA().hints_[0].getPreferred());
}
if (ctx.currentIA().hints_[0].getValid()) {
valid = candidate_valid.get(ctx.currentIA().hints_[0].getValid());
}
}
}
Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx, Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
const IOAddress& addr, const IOAddress& addr,
uint8_t prefix_len, uint8_t prefix_len,
@ -1915,18 +1973,10 @@ Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
prefix_len = 128; // non-PD lease types must be always /128 prefix_len = 128; // non-PD lease types must be always /128
} }
uint32_t preferred = ctx.subnet_->getPreferred(); uint32_t preferred = 0;
if (!ctx.currentIA().hints_.empty() && uint32_t valid = 0;
ctx.currentIA().hints_[0].getPreferred()) { getLifetimes6(ctx, preferred, valid);
preferred = ctx.currentIA().hints_[0].getPreferred();
preferred = ctx.subnet_->getPreferred().get(preferred);
}
uint32_t valid = ctx.subnet_->getValid();
if (!ctx.currentIA().hints_.empty() &&
ctx.currentIA().hints_[0].getValid()) {
valid = ctx.currentIA().hints_[0].getValid();
valid = ctx.subnet_->getValid().get(valid);
}
Lease6Ptr lease(new Lease6(ctx.currentIA().type_, addr, ctx.duid_, Lease6Ptr lease(new Lease6(ctx.currentIA().type_, addr, ctx.duid_,
ctx.currentIA().iaid_, preferred, ctx.currentIA().iaid_, preferred,
valid, ctx.subnet_->getID(), valid, ctx.subnet_->getID(),

View File

@ -996,6 +996,25 @@ public:
return (IPv6Resrv(IPv6Resrv::TYPE_PD, lease.addr_, lease.prefixlen_)); return (IPv6Resrv(IPv6Resrv::TYPE_PD, lease.addr_, lease.prefixlen_));
} }
public:
/// @brief Determines the preferred and valid v6 lease lifetimes.
///
/// A candidate triplet for both preferred and valid lifetimes will be
/// selected from the first class matched to the query which defines the
/// value or from the subnet if none do. Classes are searched in the order
/// they are assigned to the query.
///
/// If the client requested a lifetime IA hint, then the
/// lifetime values returned will be the requested values bounded by
/// the candidate triplets. If the client did not request a value, then
/// it simply returns the candidate triplet's default value.
///
/// @param ctx client context that passes all necessary information. See
/// @ref ClientContext6 for details.
/// @param [out] preferred set to the preferred lifetime that should be used.
/// @param [out] valid set to the valid lifetime that should be used.
static void getLifetimes6(ClientContext6& ctx, uint32_t& preferred,
uint32_t& valid);
private: private:
/// @brief Creates a lease and inserts it in LeaseMgr if necessary /// @brief Creates a lease and inserts it in LeaseMgr if necessary

View File

@ -24,7 +24,7 @@ ClientClassDef::ClientClassDef(const std::string& name,
: UserContext(), CfgToElement(), StampedElement(), name_(name), : UserContext(), CfgToElement(), StampedElement(), name_(name),
match_expr_(match_expr), required_(false), depend_on_known_(false), match_expr_(match_expr), required_(false), depend_on_known_(false),
cfg_option_(cfg_option), next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()), cfg_option_(cfg_option), next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
valid_() { valid_(), preferred_() {
// Name can't be blank // Name can't be blank
if (name_.empty()) { if (name_.empty()) {
@ -44,7 +44,7 @@ ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
match_expr_(ExpressionPtr()), test_(rhs.test_), required_(rhs.required_), match_expr_(ExpressionPtr()), test_(rhs.test_), required_(rhs.required_),
depend_on_known_(rhs.depend_on_known_), cfg_option_(new CfgOption()), depend_on_known_(rhs.depend_on_known_), cfg_option_(new CfgOption()),
next_server_(rhs.next_server_), sname_(rhs.sname_), next_server_(rhs.next_server_), sname_(rhs.sname_),
filename_(rhs.filename_), valid_(rhs.valid_) { filename_(rhs.filename_), valid_(rhs.valid_), preferred_(rhs.preferred_) {
if (rhs.match_expr_) { if (rhs.match_expr_) {
match_expr_.reset(new Expression()); match_expr_.reset(new Expression());
@ -180,16 +180,33 @@ ClientClassDef:: toElement() const {
} }
// Set option-data // Set option-data
result->set("option-data", cfg_option_->toElement()); result->set("option-data", cfg_option_->toElement());
if (family != AF_INET) {
// Other parameters are DHCPv4 specific if (family == AF_INET) {
return (result); // V4 only
// Set next-server
result->set("next-server", Element::create(next_server_.toText()));
// Set server-hostname
result->set("server-hostname", Element::create(sname_));
// Set boot-file-name
result->set("boot-file-name", Element::create(filename_));
} else {
// V6 only
// Set preferred-lifetime
if (!preferred_.unspecified()) {
result->set("preferred-lifetime",
Element::create(static_cast<long long>(preferred_.get())));
}
if (preferred_.getMin() < preferred_.get()) {
result->set("min-preferred-lifetime",
Element::create(static_cast<long long>(preferred_.getMin())));
}
if (preferred_.getMax() > preferred_.get()) {
result->set("max-preferred-lifetime",
Element::create(static_cast<long long>(preferred_.getMax())));
}
} }
// Set next-server
result->set("next-server", Element::create(next_server_.toText()));
// Set server-hostname
result->set("server-hostname", Element::create(sname_));
// Set boot-file-name
result->set("boot-file-name", Element::create(filename_));
// Set valid-lifetime // Set valid-lifetime
if (!valid_.unspecified()) { if (!valid_.unspecified()) {
@ -244,7 +261,8 @@ ClientClassDictionary::addClass(const std::string& name,
asiolink::IOAddress next_server, asiolink::IOAddress next_server,
const std::string& sname, const std::string& sname,
const std::string& filename, const std::string& filename,
const Triplet<uint32_t>& valid) { const Triplet<uint32_t>& valid,
const Triplet<uint32_t>& preferred) {
ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option)); ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
cclass->setTest(test); cclass->setTest(test);
cclass->setRequired(required); cclass->setRequired(required);
@ -255,6 +273,7 @@ ClientClassDictionary::addClass(const std::string& name,
cclass->setSname(sname); cclass->setSname(sname);
cclass->setFilename(filename); cclass->setFilename(filename);
cclass->setValid(valid); cclass->setValid(valid);
cclass->setPreferred(preferred);
addClass(cclass); addClass(cclass);
} }

View File

@ -208,6 +208,20 @@ public:
valid_ = valid; valid_ = valid;
} }
/// @brief Return preferred-lifetime value
///
/// @return a triplet containing the preferred lifetime.
Triplet<uint32_t> getPreferred() const {
return (preferred_);
}
/// @brief Sets new preferred lifetime
///
/// @param preferred New valid lifetime in seconds.
void setPreferred(const Triplet<uint32_t>& preferred) {
preferred_ = preferred;
}
/// @brief Unparse a configuration object /// @brief Unparse a configuration object
/// ///
/// @return a pointer to unparsed configuration /// @return a pointer to unparsed configuration
@ -266,6 +280,9 @@ private:
/// @brief a Triplet (min/default/max) holding allowed valid lifetime values /// @brief a Triplet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_; Triplet<uint32_t> valid_;
/// @brief a Triplet (min/default/max) holding allowed preferred lifetime values
Triplet<uint32_t> preferred_;
}; };
/// @brief a pointer to an ClientClassDef /// @brief a pointer to an ClientClassDef
@ -309,6 +326,7 @@ public:
/// @param sname server-name value for this class (optional) /// @param sname server-name value for this class (optional)
/// @param filename boot-file-name value for this class (optional) /// @param filename boot-file-name value for this class (optional)
/// @param valid valid-lifetime triplet (optional) /// @param valid valid-lifetime triplet (optional)
/// @param preferred preferred-lifetime triplet (optional)
/// ///
/// @throw DuplicateClientClassDef if class already exists within the /// @throw DuplicateClientClassDef if class already exists within the
/// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for /// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for
@ -321,7 +339,8 @@ public:
asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"), asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
const std::string& sname = std::string(), const std::string& sname = std::string(),
const std::string& filename = std::string(), const std::string& filename = std::string(),
const Triplet<uint32_t>&valid = Triplet<uint32_t>()); const Triplet<uint32_t>&valid = Triplet<uint32_t>(),
const Triplet<uint32_t>&preferred = Triplet<uint32_t>());
/// @brief Adds a new class to the list /// @brief Adds a new class to the list
/// ///

View File

@ -205,6 +205,12 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
// Parse valid lifetime triplet. // Parse valid lifetime triplet.
Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime"); Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime");
Triplet<uint32_t> preferred_lft;
if (family != AF_INET) {
// Parse preferred lifetime triplet.
preferred_lft = parseIntTriplet(class_def_cfg, "preferred-lifetime");
}
// Sanity checks on built-in classes // Sanity checks on built-in classes
for (auto bn : builtinNames) { for (auto bn : builtinNames) {
if (name == bn) { if (name == bn) {
@ -232,7 +238,8 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
try { try {
class_dictionary->addClass(name, match_expr, test, required, class_dictionary->addClass(name, match_expr, test, required,
depend_on_known, options, defs, depend_on_known, options, defs,
user_context, next_server, sname, filename, valid_lft); user_context, next_server, sname, filename,
valid_lft, preferred_lft);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
std::ostringstream s; std::ostringstream s;
s << "Can't add class: " << ex.what(); s << "Can't add class: " << ex.what();
@ -257,7 +264,11 @@ ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_
"test", "test",
"option-data", "option-data",
"user-context", "user-context",
"only-if-required" }; "only-if-required",
"valid-lifetime",
"min-valid-lifetime",
"max-valid-lifetime" };
// The v4 client class supports additional parameters. // The v4 client class supports additional parameters.
static std::set<std::string> supported_params_v4 = { "option-def", static std::set<std::string> supported_params_v4 = { "option-def",
@ -265,12 +276,17 @@ ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_
"server-hostname", "server-hostname",
"boot-file-name" }; "boot-file-name" };
// The v6 client class supports additional parameters.
static std::set<std::string> supported_params_v6 = { "preferred-lifetime",
"min-preferred-lifetime",
"max-preferred-lifetime" };
// Iterate over the specified parameters and check if they are all supported. // Iterate over the specified parameters and check if they are all supported.
for (auto name_value_pair : class_def_cfg->mapValue()) { for (auto name_value_pair : class_def_cfg->mapValue()) {
if ((supported_params.count(name_value_pair.first) > 0) || if ((supported_params.count(name_value_pair.first) > 0) ||
((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0))) { ((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0)) ||
((family != AF_INET) && (supported_params_v6.count(name_value_pair.first) > 0))) {
continue; continue;
} else { } else {
isc_throw(DhcpConfigError, "unsupported client class parameter '" isc_throw(DhcpConfigError, "unsupported client class parameter '"
<< name_value_pair.first << "'"); << name_value_pair.first << "'");

View File

@ -5238,6 +5238,262 @@ TEST_F(AllocEngine6Test, renewCacheHostname6) {
detailCompareLease(lease, from_mgr); detailCompareLease(lease, from_mgr);
} }
// Verifies that AllocEngine::getLifetimes6() returns the appropriate
// valid lifetime value based on the context content.
TEST_F(AllocEngine6Test, getValidLifetime) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
// Let's make three classes, two with valid-lifetime and one without,
// and add them to the dictionary.
ClientClassDictionaryPtr dictionary =
CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
Triplet<uint32_t> valid_one(50, 100, 150);
class_def->setValid(valid_one);
dictionary->addClass(class_def);
class_def.reset(new ClientClassDef("valid_two", ExpressionPtr()));
Triplet<uint32_t>valid_two(200, 250, 300);
class_def->setValid(valid_two);
dictionary->addClass(class_def);
class_def.reset(new ClientClassDef("valid_unspec", ExpressionPtr()));
dictionary->addClass(class_def);
// Commit our class changes.
CfgMgr::instance().commit();
// Update the subnet's triplet to something more useful.
subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
// Describes a test scenario.
struct Scenario {
std::string desc_; // descriptive text for logging
std::vector<std::string> classes_; // class list of assigned classes
uint32_t requested_lft_; // use as option 51 is > 0
uint32_t exp_valid_; // expected lifetime
};
// Scenarios to test.
std::vector<Scenario> scenarios = {
{
"no classes, no hint",
{},
0,
subnet_->getValid()
},
{
"no classes, hint",
{},
subnet_->getValid().getMin() + 50,
subnet_->getValid().getMin() + 50
},
{
"no classes, hint too small",
{},
subnet_->getValid().getMin() - 50,
subnet_->getValid().getMin()
},
{
"no classes, hint too big",
{},
subnet_->getValid().getMax() + 50,
subnet_->getValid().getMax()
},
{
"class unspecified, no hint",
{ "valid_unspec" },
0,
subnet_->getValid()
},
{
"from last class, no hint",
{ "valid_unspec", "valid_one" },
0,
valid_one.get()
},
{
"from first class, no hint",
{ "valid_two", "valid_one" },
0,
valid_two.get()
},
{
"class plus hint",
{ "valid_one" },
valid_one.getMin() + 25,
valid_one.getMin() + 25
},
{
"class plus hint too small",
{ "valid_one" },
valid_one.getMin() - 25,
valid_one.getMin()
},
{
"class plus hint too big",
{ "valid_one" },
valid_one.getMax() + 25,
valid_one.getMax()
}
};
// Iterate over the scenarios and verify the correct outcome.
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.desc_); {
// Create a context;
AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
// Add client classes (if any)
for (auto class_name : scenario.classes_) {
ctx.query_->addClass(class_name);
}
// Add hint
ctx.currentIA().iaid_ = iaid_;
// prefix,prefixlen, preferred, valid
ctx.currentIA().addHint(IOAddress("::"), 128, 0, scenario.requested_lft_);
Lease6Ptr lease;
ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
ASSERT_TRUE(lease);
EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
}
}
}
// Verifies that AllocEngine::getLifetimes6() returns the appropriate
// preferred lifetime value based on the context content.
TEST_F(AllocEngine6Test, getPreferredLifetime) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
// Let's make three classes, two with preferred-lifetime and one without,
// and add them to the dictionary.
ClientClassDictionaryPtr dictionary =
CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ClientClassDefPtr class_def(new ClientClassDef("preferred_one", ExpressionPtr()));
Triplet<uint32_t> preferred_one(50, 100, 150);
class_def->setPreferred(preferred_one);
dictionary->addClass(class_def);
class_def.reset(new ClientClassDef("preferred_two", ExpressionPtr()));
Triplet<uint32_t>preferred_two(200, 250, 300);
class_def->setPreferred(preferred_two);
dictionary->addClass(class_def);
class_def.reset(new ClientClassDef("preferred_unspec", ExpressionPtr()));
dictionary->addClass(class_def);
// Commit our class changes.
CfgMgr::instance().commit();
// Update the subnet's triplet to something more useful.
subnet_->setPreferred(Triplet<uint32_t>(500, 1000, 1500));
// Describes a test scenario.
struct Scenario {
std::string desc_; // descriptive text for logging
std::vector<std::string> classes_; // class list of assigned classes
uint32_t requested_lft_; // use as option 51 is > 0
uint32_t exp_preferred_; // expected lifetime
};
// Scenarios to test.
std::vector<Scenario> scenarios = {
{
"no classes, no hint",
{},
0,
subnet_->getPreferred()
},
{
"no classes, hint",
{},
subnet_->getPreferred().getMin() + 50,
subnet_->getPreferred().getMin() + 50
},
{
"no classes, hint too small",
{},
subnet_->getPreferred().getMin() - 50,
subnet_->getPreferred().getMin()
},
{
"no classes, hint too big",
{},
subnet_->getPreferred().getMax() + 50,
subnet_->getPreferred().getMax()
},
{
"class unspecified, no hint",
{ "preferred_unspec" },
0,
subnet_->getPreferred()
},
{
"from last class, no hint",
{ "preferred_unspec", "preferred_one" },
0,
preferred_one.get()
},
{
"from first class, no hint",
{ "preferred_two", "preferred_one" },
0,
preferred_two.get()
},
{
"class plus hint",
{ "preferred_one" },
preferred_one.getMin() + 25,
preferred_one.getMin() + 25
},
{
"class plus hint too small",
{ "preferred_one" },
preferred_one.getMin() - 25,
preferred_one.getMin()
},
{
"class plus hint too big",
{ "preferred_one" },
preferred_one.getMax() + 25,
preferred_one.getMax()
}
};
// Iterate over the scenarios and verify the correct outcome.
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.desc_); {
// Create a context;
AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
// Add client classes (if any)
for (auto class_name : scenario.classes_) {
ctx.query_->addClass(class_name);
}
// Add hint
ctx.currentIA().iaid_ = iaid_;
// prefix,prefixlen, preferred, preferred
ctx.currentIA().addHint(IOAddress("::"), 128, scenario.requested_lft_, 0);
Lease6Ptr lease;
ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
ASSERT_TRUE(lease);
EXPECT_EQ(lease->preferred_lft_, scenario.exp_preferred_);
}
}
}
} // namespace test } // namespace test
} // namespace dhcp } // namespace dhcp
} // namespace isc } // namespace isc

View File

@ -1313,7 +1313,7 @@ TEST_F(ClientClassDefListParserTest, dropCheckError) {
EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6)); EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
} }
// Verify the ability to configure lease lifetime triplet. // Verify the ability to configure valid lifetime triplet.
TEST_F(ClientClassDefParserTest, validLifetimeTests) { TEST_F(ClientClassDefParserTest, validLifetimeTests) {
struct Scenario { struct Scenario {
@ -1374,4 +1374,68 @@ TEST_F(ClientClassDefParserTest, validLifetimeTests) {
} }
} }
// Verify the ability to configure lease preferred lifetime triplet.
TEST_F(ClientClassDefParserTest, preferredLifetimeTests) {
struct Scenario {
std::string desc_;
std::string cfg_txt_;
Triplet<uint32_t> exp_triplet_;
};
std::vector<Scenario> scenarios = {
{
"unspecified",
"",
Triplet<uint32_t>()
},
{
"preferred only",
"\"preferred-lifetime\": 100",
Triplet<uint32_t>(100)
},
{
"min only",
"\"min-preferred-lifetime\": 50",
Triplet<uint32_t>(50, 50, 50)
},
{
"max only",
"\"max-preferred-lifetime\": 75",
Triplet<uint32_t>(75, 75, 75)
},
{
"all three",
"\"min-preferred-lifetime\": 25,"
"\"preferred-lifetime\": 50,"
"\"max-preferred-lifetime\": 75",
Triplet<uint32_t>(25, 50, 75)
}
};
for (auto scenario : scenarios) {
SCOPED_TRACE(scenario.desc_); {
std::stringstream oss;
oss << "{ \"name\": \"foo\"";
if (!scenario.cfg_txt_.empty()) {
oss << ",\n" << scenario.cfg_txt_;
}
oss << "\n}\n";
ClientClassDefPtr class_def;
ASSERT_NO_THROW_LOG(class_def = parseClientClassDef(oss.str(), AF_INET6));
ASSERT_TRUE(class_def);
if (scenario.exp_triplet_.unspecified()) {
EXPECT_TRUE(class_def->getPreferred().unspecified());
} else {
EXPECT_EQ(class_def->getPreferred(), scenario.exp_triplet_);
EXPECT_EQ(class_def->getPreferred().getMin(), scenario.exp_triplet_.getMin());
EXPECT_EQ(class_def->getPreferred().get(), scenario.exp_triplet_.get());
EXPECT_EQ(class_def->getPreferred().getMax(), scenario.exp_triplet_.getMax());
}
}
}
}
} // end of anonymous namespace } // end of anonymous namespace

View File

@ -83,6 +83,7 @@ TEST(ClientClassDef, copyConstruction) {
cclass->setSname("ufo"); cclass->setSname("ufo");
cclass->setFilename("ufo.efi"); cclass->setFilename("ufo.efi");
cclass->setValid(Triplet<uint32_t>(10, 20, 30)); cclass->setValid(Triplet<uint32_t>(10, 20, 30));
cclass->setPreferred(Triplet<uint32_t>(11, 21, 31));
// Copy the client class. // Copy the client class.
boost::scoped_ptr<ClientClassDef> cclass_copy; boost::scoped_ptr<ClientClassDef> cclass_copy;
@ -105,6 +106,9 @@ TEST(ClientClassDef, copyConstruction) {
EXPECT_EQ(cclass->getValid().get(), cclass_copy->getValid().get()); EXPECT_EQ(cclass->getValid().get(), cclass_copy->getValid().get());
EXPECT_EQ(cclass->getValid().getMin(), cclass_copy->getValid().getMin()); EXPECT_EQ(cclass->getValid().getMin(), cclass_copy->getValid().getMin());
EXPECT_EQ(cclass->getValid().getMax(), cclass_copy->getValid().getMax()); EXPECT_EQ(cclass->getValid().getMax(), cclass_copy->getValid().getMax());
EXPECT_EQ(cclass->getPreferred().get(), cclass_copy->getPreferred().get());
EXPECT_EQ(cclass->getPreferred().getMin(), cclass_copy->getPreferred().getMin());
EXPECT_EQ(cclass->getPreferred().getMax(), cclass_copy->getPreferred().getMax());
// Ensure that the option was copied into a new structure. // Ensure that the option was copied into a new structure.
ASSERT_TRUE(cclass_copy->getCfgOption()); ASSERT_TRUE(cclass_copy->getCfgOption());