case isc::dhcp::Parser4Context::POOLS:
case isc::dhcp::Parser4Context::RESERVATIONS:
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
- case isc::dhcp::Parser4Context::CLIENT_CLASS:
return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_);
case isc::dhcp::Parser4Context::OPTION_DEF:
case isc::dhcp::Parser4Context::OPTION_DATA:
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
- case isc::dhcp::Parser4Context::CLIENT_CLASS:
case isc::dhcp::Parser4Context::SHARED_NETWORK:
case isc::dhcp::Parser4Context::LOGGERS:
return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_);
}
}
+\"eval-client-classes\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_EVAL_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("eval-client-classes", driver.loc_);
+ }
+}
+
\"client-class\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::SUBNET4:
\"test\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
- case isc::dhcp::Parser4Context::CLIENT_CLASS:
return isc::dhcp::Dhcp4Parser::make_TEST(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("test", driver.loc_);
}
}
+\"eval-on-demand\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_EVAL_ON_DEMAND(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("eval-on-demand", driver.loc_);
+ }
+}
+
\"reservations\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::SUBNET4:
HOST_RESERVATION_IDENTIFIERS "host-reservation-identifiers"
CLIENT_CLASSES "client-classes"
+ EVAL_CLIENT_CLASSES "eval-client-classes"
TEST "test"
+ EVAL_ON_DEMAND "eval-on-demand"
CLIENT_CLASS "client-class"
RESERVATIONS "reservations"
| id
| rapid_commit
| client_class
+ | eval_client_classes
| reservations
| reservation_mode
| relay
};
client_class: CLIENT_CLASS {
- ctx.enter(ctx.CLIENT_CLASS);
+ ctx.enter(ctx.NO_KEYWORD);
} COLON STRING {
ElementPtr cls(new StringElement($4, ctx.loc2pos(@4)));
ctx.stack_.back()->set("client-class", cls);
ctx.leave();
};
+eval_client_classes: EVAL_CLIENT_CLASSES {
+ ElementPtr c(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("eval-client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
reservation_mode: RESERVATION_MODE {
ctx.enter(ctx.RESERVATION_MODE);
} COLON hr_mode {
| relay
| reservation_mode
| client_class
+ | eval_client_classes
| valid_lifetime
| unknown_map_entry
;
pool_param: pool_entry
| option_data_list
| client_class
+ | eval_client_classes
| user_context
| known_clients
| unknown_map_entry
client_class_param: client_class_name
| client_class_test
+ | eval_on_demand
| option_def_list
| option_data_list
| next_server
ctx.leave();
};
+eval_on_demand: EVAL_ON_DEMAND COLON BOOLEAN {
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("eval-on-demand");
+};
+
// --- end of client classes ---------------------------------
// was server-id but in is DHCPv6-only
return (Pkt4Ptr());
}
- // Assign reserved classes.
- ex.setReservedClientClasses();
-
// Adding any other options makes sense only when we got the lease.
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
+ // Assign reserved classes.
+ ex.setReservedClientClasses();
+ // Late classification
+ lateClassify(ex);
+
buildCfgOptionList(ex);
appendRequestedOptions(ex);
appendRequestedVendorOptions(ex);
return (Pkt4Ptr());
}
- // Assign reserved classes.
- ex.setReservedClientClasses();
-
// Adding any other options makes sense only when we got the lease.
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
+ // Assign reserved classes.
+ ex.setReservedClientClasses();
+ // Late classification
+ lateClassify(ex);
+
buildCfgOptionList(ex);
appendRequestedOptions(ex);
appendRequestedVendorOptions(ex);
Pkt4Ptr ack = ex.getResponse();
ex.setReservedClientClasses();
+ lateClassify(ex);
buildCfgOptionList(ex);
appendRequestedOptions(ex);
if (!expr_ptr) {
continue;
}
+ // Not the right time if on demand
+ if ((*it)->getOnDemand()) {
+ continue;
+ }
// Evaluate the expression which can return false (no match),
// true (match) or raise an exception (error)
try {
}
}
+void Dhcpv4Srv::lateClassify(Dhcpv4Exchange& ex) {
+ // First collect on-demand classes
+ Pkt4Ptr query = ex.getQuery();
+ ClientClasses classes = query->getClasses(true);
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+
+ if (subnet) {
+ // Begin by the shared-network
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ const ClientClasses& to_add = network->getOnDemandClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+
+ // Followed by the subnet
+ const ClientClasses& to_add = subnet->getOnDemandClasses();
+ for(ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+
+ // And finish by the pool
+ Pkt4Ptr resp = ex.getResponse();
+ IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
+ if (resp) {
+ addr = resp->getYiaddr();
+ }
+ if (!addr.isV4Zero()) {
+ PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
+ if (pool) {
+ const ClientClasses& to_add = pool->getOnDemandClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+ }
+
+ // host reservation???
+ }
+
+ // Run match expressions
+ // Note getClientClassDictionary() cannot be null
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ const ClientClassDefPtr class_def = dict->findClass(*cclass);
+ // Todo: log unknown classes
+ if (!class_def) {
+ continue;
+ }
+ const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
+ // Nothing to do without an expression to evaluate
+ if (!expr_ptr) {
+ continue;
+ }
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *query);
+ if (status) {
+ LOG_INFO(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ // Matching: add the class
+ query->addClass(*cclass);
+ } else {
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg("get exception?");
+ }
+ }
+}
+
void
Dhcpv4Srv::deferredUnpack(Pkt4Ptr& query)
{
/// @param pkt packet to be classified
void classifyPacket(const Pkt4Ptr& pkt);
+ /// @brief Assigns incoming packet to zero or more classes (late pass).
+ ///
+ /// @note This late classification evaluates all classes which
+ /// were marked for this deferred/on-demand pass. Classes are
+ /// collected in the reversed order than output option processing.
+ ///
+ /// @note The eval-on-demand flag is related because it avoids
+ /// double evaluation (which is not forbidden).
+ ///
+ /// @param ex The exchange holding needed informations.
+ void lateClassify(Dhcpv4Exchange& ex);
+
/// @brief Perform deferred option unpacking.
///
/// @note Options 43 and 224-254 are processed after classification.
return ("reservations");
case RELAY:
return ("relay");
- case CLIENT_CLASS:
- return ("client-class");
case LOGGERS:
return ("loggers");
case OUTPUT_OPTIONS:
/// Used while parsing Dhcp4/subnet4relay structures.
RELAY,
- /// Used while parsing Dhcp4/client-classes structures.
- CLIENT_CLASS,
-
/// Used while parsing Logging/loggers structures.
LOGGERS,
}
}
+\"eval-client-classes\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser6Context::SUBNET6:
+ case isc::dhcp::Parser6Context::POOLS:
+ case isc::dhcp::Parser6Context::PD_POOLS:
+ case isc::dhcp::Parser6Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp6Parser::make_EVAL_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("eval-client-classes", driver.loc_);
+ }
+}
+
\"client-class\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::SUBNET6:
}
}
+\"eval-on-demand\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser6Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp6Parser::make_EVAL_ON_DEMAND(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp6Parser::make_STRING("eval-on-demand", driver.loc_);
+ }
+}
+
\"reservations\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::SUBNET6:
HOST_RESERVATION_IDENTIFIERS "host-reservation-identifiers"
CLIENT_CLASSES "client-classes"
+ EVAL_CLIENT_CLASSES "eval-client-classes"
TEST "test"
+ EVAL_ON_DEMAND "eval-on-demand"
CLIENT_CLASS "client-class"
RESERVATIONS "reservations"
| id
| rapid_commit
| client_class
+ | eval_client_classes
| reservations
| reservation_mode
| relay
};
client_class: CLIENT_CLASS {
- ctx.enter(ctx.CLIENT_CLASS);
+ ctx.enter(ctx.NO_KEYWORD);
} COLON STRING {
ElementPtr cls(new StringElement($4, ctx.loc2pos(@4)));
ctx.stack_.back()->set("client-class", cls);
ctx.leave();
};
++eval_client_classes: EVAL_CLIENT_CLASSES {
+ ElementPtr c(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("eval-client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
reservation_mode: RESERVATION_MODE {
ctx.enter(ctx.RESERVATION_MODE);
} COLON hr_mode {
| relay
| reservation_mode
| client_class
+ | eval_client_classes
| preferred_lifetime
| rapid_commit
| valid_lifetime
pool_param: pool_entry
| option_data_list
| client_class
+ | eval_client_classes
| user_context
| known_clients
| unknown_map_entry
| pd_delegated_len
| option_data_list
| client_class
+ | eval_client_classes
| excluded_prefix
| excluded_prefix_len
| user_context
client_class_param: client_class_name
| client_class_test
+ | eval_on_demand
| option_data_list
| unknown_map_entry
;
ctx.leave();
};
+eval_on_demand: EVAL_ON_DEMAND COLON BOOLEAN {
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("eval-on-demand");
+};
+
// --- end of client classes ---------------------------------
// --- server-id ---------------------------------------------
assignLeases(solicit, response, ctx);
setReservedClientClasses(solicit, ctx);
+ lateClassify(solicit, ctx);
copyClientOptions(solicit, response);
CfgOptionList co_list;
assignLeases(request, reply, ctx);
setReservedClientClasses(request, ctx);
+ lateClassify(request, ctx);
copyClientOptions(request, reply);
CfgOptionList co_list;
extendLeases(renew, reply, ctx);
setReservedClientClasses(renew, ctx);
+ lateClassify(renew, ctx);
copyClientOptions(renew, reply);
CfgOptionList co_list;
extendLeases(rebind, reply, ctx);
setReservedClientClasses(rebind, ctx);
+ lateClassify(rebind, ctx);
copyClientOptions(rebind, reply);
CfgOptionList co_list;
AllocEngine::ClientContext6 ctx;
initContext(confirm, ctx);
setReservedClientClasses(confirm, ctx);
+ lateClassify(confirm, ctx);
// Get IA_NAs from the Confirm. If there are none, the message is
// invalid and must be discarded. There is nothing more to do.
AllocEngine::ClientContext6 ctx;
initContext(release, ctx);
setReservedClientClasses(release, ctx);
+ lateClassify(release, ctx);
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
AllocEngine::ClientContext6 ctx;
initContext(decline, ctx);
setReservedClientClasses(decline, ctx);
+ lateClassify(decline, ctx);
// Copy client options (client-id, also relay information if present)
copyClientOptions(decline, reply);
AllocEngine::ClientContext6 ctx;
initContext(inf_request, ctx);
setReservedClientClasses(inf_request, ctx);
+ lateClassify(inf_request, ctx);
// Create a Reply packet, with the same trans-id as the client's.
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid()));
if (!expr_ptr) {
continue;
}
+ // Not the right time if on demand
+ if ((*it)->getOnDemand()) {
+ continue;
+ }
// Evaluate the expression which can return false (no match),
// true (match) or raise an exception (error)
try {
}
}
+void
+Dhcpv6Srv::lateClassify(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx) {
+ // First collect on-demand classes
+ ClientClasses classes = pkt->getClasses(true);
+ Subnet6Ptr subnet = ctx.subnet_;
+
+ if (subnet) {
+ // Begin by the shared-network
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ const ClientClasses& to_add = network->getOnDemandClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+
+ // Followed by the subnet
+ const ClientClasses& to_add = subnet->getOnDemandClasses();
+ for(ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+
+ // And finish by pools
+ BOOST_FOREACH(const AllocEngine::ResourceType& resource,
+ ctx.allocated_resources_) {
+ PoolPtr pool = ctx.subnet_->getPool(resource.second == 128 ?
+ Lease::TYPE_NA :
+ Lease::TYPE_PD,
+ resource.first,
+ false);
+ if (pool) {
+ const ClientClasses& to_add = pool->getOnDemandClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+ }
+
+ // host reservation???
+ }
+
+ // Run match expressions
+ // Note getClientClassDictionary() cannot be null
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ const ClientClassDefPtr class_def = dict->findClass(*cclass);
+ // Todo: log unknown classes
+ if (!class_def) {
+ continue;
+ }
+ const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
+ // Nothing to do without an expression to evaluate
+ if (!expr_ptr) {
+ continue;
+ }
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *pkt);
+ if (status) {
+ LOG_INFO(dhcp6_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ // Matching: add the class
+ pkt->addClass(*cclass);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcp6_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(dhcp6_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg("get exception?");
+ }
+ }
+}
+
void
Dhcpv6Srv::updateReservedFqdn(const AllocEngine::ClientContext6& ctx,
const Pkt6Ptr& answer) {
void setReservedClientClasses(const Pkt6Ptr& pkt,
const AllocEngine::ClientContext6& ctx);
+ /// @brief Assigns incoming packet to zero or more classes (late pass).
+ ///
+ /// @note This late classification evaluates all classes which
+ /// were marked for this deferred/on-demand pass. Classes are
+ /// collected in the reversed order than output option processing.
+ ///
+ /// @note The eval-on-demand flag is related because it avoids
+ /// double evaluation (which is not forbidden).
+ ///
+ /// @param pkt packet to be classified
+ /// @param ctx allocation context where to get informations
+ void lateClassify(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx);
+
/// @brief Attempts to get a MAC/hardware address using configured sources
///
/// Tries to extract MAC/hardware address information from the packet
return ("reservations");
case RELAY:
return ("relay");
- case CLIENT_CLASS:
- return ("client-class");
case LOGGERS:
return ("loggers");
case OUTPUT_OPTIONS:
/// Used while parsing Dhcp6/subnet6/relay structures.
RELAY,
- /// Used while parsing Dhcp6/client-classes structures.
- CLIENT_CLASS,
-
/// Used while parsing Logging/loggers structures.
LOGGERS,
}
void
-Pkt::addClass(const std::string& client_class) {
- if (!classes_.contains(client_class)) {
- classes_.insert(client_class);
+Pkt::addClass(const std::string& client_class, bool deferred) {
+ ClientClasses& classes = !deferred ? classes_ : on_demand_classes_;
+ if (!classes.contains(client_class)) {
+ classes.insert(client_class);
}
}
/// so I decided to stick with addClass().
///
/// @param client_class name of the class to be added
- void addClass(const isc::dhcp::ClientClass& client_class);
+ /// @param deferred the class is marked for late evaluation
+ void addClass(const isc::dhcp::ClientClass& client_class,
+ bool deferred = false);
/// @brief Returns the class set
///
/// @note This should be used only to iterate over the class set.
- /// @return
- const ClientClasses& getClasses() const { return (classes_); }
+ /// @param deferred return classes or to be evaluated classes.
+ /// @return if deferred is false (the default) the claases the
+ /// packet belongs to else the classes which will be evaluated later.
+ const ClientClasses& getClasses(bool deferred = false) const {
+ return (!deferred ? classes_ : on_demand_classes_);
+ }
/// @brief Unparsed data (in received packets).
///
/// @ref addClass should be used to operate on this field.
ClientClasses classes_;
+ /// @brief Classes which will be evaluated later.
+ ///
+ /// The comment on @ref classes_ applies here.
+ ///
+ /// Before output option processing these classes will be evaluated
+ /// and if evaluation status is true added to the previous collection.
+ ClientClasses on_demand_classes_;
+
/// @brief Collection of options present in this message.
///
/// @warning This public member is accessed by derived
// Default values (do not belong to any class)
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- EXPECT_TRUE(pkt.classes_.empty());
+ EXPECT_TRUE(pkt.getClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER);
EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- ASSERT_FALSE(pkt.classes_.empty());
+ ASSERT_FALSE(pkt.getClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM);
EXPECT_TRUE(pkt.inClass("foo"));
}
+// Tests whether a packet can be marked to evaluate later a class and
+// after check if a given class is in the collection
+TEST_F(Pkt4Test, deferredClientClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_TRUE(pkt.getClasses(true).empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
+ EXPECT_EQ(1, pkt.getClasses(true).size());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM, true);
+ EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
+ EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
+}
+
// Tests whether MAC can be obtained and that MAC sources are not
// confused.
TEST_F(Pkt4Test, getMAC) {
// Default values (do not belong to any class)
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- EXPECT_TRUE(pkt.classes_.empty());
+ EXPECT_TRUE(pkt.getClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER);
EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- ASSERT_FALSE(pkt.classes_.empty());
+ ASSERT_FALSE(pkt.getClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM);
EXPECT_TRUE(pkt.inClass("foo"));
}
+// Tests whether a packet can be marked to evaluate later a class and
+// after check if a given class is in the collection
+TEST_F(Pkt6Test, deferredClientClasses) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_TRUE(pkt.getClasses(true).empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
+ EXPECT_EQ(1, pkt.getClasses(true).size());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM, true);
+ EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
+ EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
+}
+
// Tests whether MAC can be obtained and that MAC sources are not
// confused.
TEST_F(Pkt6Test, getMAC) {
ClientClassDef::ClientClassDef(const std::string& name,
const ExpressionPtr& match_expr,
const CfgOptionPtr& cfg_option)
- : name_(name), match_expr_(match_expr), cfg_option_(cfg_option),
+ : name_(name), match_expr_(match_expr), on_demand_(false),
+ cfg_option_(cfg_option),
next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()) {
// Name can't be blank
ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
: name_(rhs.name_), match_expr_(ExpressionPtr()),
- cfg_option_(new CfgOption()),
+ on_demand_(false), cfg_option_(new CfgOption()),
next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()) {
if (rhs.match_expr_) {
rhs.cfg_option_->copyTo(*cfg_option_);
}
+ on_demand_ = rhs.on_demand_;
next_server_ = rhs.next_server_;
sname_ = rhs.sname_;
filename_ = rhs.filename_;
test_ = test;
}
+bool
+ClientClassDef::getOnDemand() const {
+ return (on_demand_);
+}
+
+void
+ClientClassDef::setOnDemand(bool on_demand) {
+ on_demand_ = on_demand;
+}
+
const CfgOptionDefPtr&
ClientClassDef::getCfgOptionDef() const {
return (cfg_option_def_);
((!cfg_option_def_ && !other.cfg_option_def_) ||
(cfg_option_def_ && other.cfg_option_def_ &&
(*cfg_option_def_ == *other.cfg_option_def_))) &&
+ (on_demand_ == other.on_demand_) &&
(next_server_ == other.next_server_) &&
(sname_ == other.sname_) &&
(filename_ == other.filename_));
if (!test_.empty()) {
result->set("test", Element::create(test_));
}
+ // Set eval-on-demand
+ if (on_demand_) {
+ result->set("eval-on-demand", Element::create(on_demand_));
+ }
// Set option-def (used only by DHCPv4)
if (cfg_option_def_ && (family == AF_INET)) {
result->set("option-def", cfg_option_def_->toElement());
ClientClassDictionary::addClass(const std::string& name,
const ExpressionPtr& match_expr,
const std::string& test,
+ bool on_demand,
const CfgOptionPtr& cfg_option,
CfgOptionDefPtr cfg_option_def,
asiolink::IOAddress next_server,
const std::string& filename) {
ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
cclass->setTest(test);
+ cclass->setOnDemand(on_demand);
cclass->setCfgOptionDef(cfg_option_def);
cclass->setNextServer(next_server);
cclass->setSname(sname);
/// @param test the original expression to assign the class
void setTest(const std::string& test);
+ /// @brief Fetches the on demand flag
+ bool getOnDemand() const;
+
+ /// @brief Sets the on demand flag
+ void setOnDemand(bool on_demand);
+
/// @brief Fetches the class's option definitions
const CfgOptionDefPtr& getCfgOptionDef() const;
/// this class.
std::string test_;
+ /// @brief The on demand flag: when false (the defaul) membership
+ /// is determined during classification so is for instance
+ /// available for subnet selection, when true membership is evaluated
+ /// only if asked for and is usable only for option configuration.
+ bool on_demand_;
+
/// @brief The option definition configuration for this class
CfgOptionDefPtr cfg_option_def_;
/// @param name Name to assign to this class
/// @param match_expr Expression the class will use to determine membership
/// @param test Original version of match_expr
+ /// @param on_demand Original value of the on demand flag
/// @param options Collection of options members should be given
/// @param defs Option definitions (optional)
/// @param next_server next-server value for this class (optional)
/// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for
/// others.
void addClass(const std::string& name, const ExpressionPtr& match_expr,
- const std::string& test, const CfgOptionPtr& options,
+ const std::string& test, bool on_demand,
+ const CfgOptionPtr& options,
CfgOptionDefPtr defs = CfgOptionDefPtr(),
asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
const std::string& sname = std::string(),
client_class_ = class_name;
}
+void
+Network::deferClientClass(const isc::dhcp::ClientClass& class_name) {
+ if (!on_demand_classes_.contains(class_name)) {
+ on_demand_classes_.insert(class_name);
+ }
+}
+
+const ClientClasses&
+Network::getOnDemandClasses() const {
+ return (on_demand_classes_);
+}
+
ElementPtr
Network::toElement() const {
ElementPtr map = Element::createMap();
map->set("client-class", Element::create(cclass));
}
+ // Set eval-client-classes
+ const ClientClasses& classes = getOnDemandClasses();
+ if (!classes.empty()) {
+ ElementPtr class_list = Element::createList();
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
+ class_list->add(Element::create(*it));
+ }
+ map->set("eval-client-classes", class_list);
+ }
+
// Set renew-timer
map->set("renew-timer",
Element::create(static_cast<long long>
/// @brief Sets the supported class to class class_name
///
- /// @param class_name client class to be supported by this subnet
+ /// @param class_name client class to be supported by this network
void allowClientClass(const isc::dhcp::ClientClass& class_name);
+ /// @brief Adds class class_name to classes to be evaluated later
+ ///
+ /// @param class_name client class to be evaluated later
+ void deferClientClass(const isc::dhcp::ClientClass& class_name);
+
+ /// @brief Returns classes which will be evaluated later
+ const isc::dhcp::ClientClasses& getOnDemandClasses() const;
+
/// @brief returns the client class
///
/// @note The returned reference is only valid as long as the object
/// which means that any client is allowed, regardless of its class.
ClientClass client_class_;
+ /// @brief On demand classes
+ ///
+ /// If the network is selected these classes will be added to the
+ /// incoming packet and evaluated later.
+ ClientClasses on_demand_classes_;
+
/// @brief a Triplet (min/default/max) holding allowed renew timer values
Triplet<uint32_t> t1_;
opts_parser.parse(options, option_data);
}
+ // Let's try to parse the eval-on-demand flag
+ bool on_demand = false;
+ if (class_def_cfg->contains("eval-on-demand")) {
+ on_demand = getBoolean(class_def_cfg, "eval-on-demand");
+ }
+
// Let's try to parse the next-server field
IOAddress next_server("0.0.0.0");
if (class_def_cfg->contains("next-server")) {
// Add the client class definition
try {
- class_dictionary->addClass(name, match_expr, test, options,
+ class_dictionary->addClass(name, match_expr, test, on_demand, options,
defs, next_server, sname, filename);
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, "Can't add class: " << ex.what()
}
}
+ // Try setting up on demand client classes.
+ ConstElementPtr class_list = pool_structure->get("eval-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool->deferClientClass((*cclass)->stringValue());
+ }
+ }
+
// Known-clients.
ConstElementPtr known_clients = pool_structure->get("known-clients");
if (known_clients) {
subnet4->allowClientClass(client_class);
}
+ // Try setting up on demand client classes.
+ ConstElementPtr class_list = params->get("eval-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet4->deferClientClass((*cclass)->stringValue());
+ }
+ }
+
// 4o6 specific parameter: 4o6-interface. If not explicitly specified,
// it will have the default value of "".
string iface4o6 = getString(params, "4o6-interface");
known_clients_ = known_clients;
}
+ ConstElementPtr class_list = pd_pool_->get("eval-client-classes");
+
// Check the pool parameters. It will throw an exception if any
// of the required parameters are invalid.
try {
<< " (" << known_clients_->getPosition() << ")");
}
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool_->deferClientClass((*cclass)->stringValue());
+ }
+ }
+
+
// Add the local pool to the external storage ptr.
pools->push_back(pool_);
}
subnet6->allowClientClass(client_class);
}
+ // Try setting up on demand client classes.
+ ConstElementPtr class_list = params->get("eval-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet6->deferClientClass((*cclass)->stringValue());
+ }
+ }
+
/// client-class processing is now generic and handled in the common
/// code (see isc::data::SubnetConfigParser::createSubnet)
}
}
+ if (shared_network_data->contains("eval-client-classes")) {
+ const std::vector<data::ElementPtr>& class_list =
+ shared_network_data->get("eval-client-classes")->listValue();
+ for (auto cclass = class_list.cbegin();
+ cclass != class_list.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ shared_network->deferClientClass((*cclass)->stringValue());
+ }
+ }
+
} catch (const DhcpConfigError&) {
// Position was already added
throw;
}
}
+ if (shared_network_data->contains("eval-client-classes")) {
+ const std::vector<data::ElementPtr>& class_list =
+ shared_network_data->get("eval-client-classes")->listValue();
+ for (auto cclass = class_list.cbegin();
+ cclass != class_list.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ shared_network->deferClientClass((*cclass)->stringValue());
+ }
+ }
+
if (shared_network_data->contains("subnet6")) {
auto json = shared_network_data->get("subnet6");
Element::create(kc == SERVE_KNOWN ? "only" : "never"));
}
+ // Set eval-client-classes
+ const ClientClasses& classes = getOnDemandClasses();
+ if (!classes.empty()) {
+ ElementPtr class_list =Element::createList();
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
+ class_list->add(Element::create(*it));
+ }
+ map->set("eval-client-classes", class_list);
+ }
+
return (map);
}
isc_throw(ToElementError, "invalid prefix range "
<< prefix.toText() << "-" << last.toText());
}
+ map->set("prefix-len", Element::create(prefix_len));
// Set delegated-len
uint8_t len = getLength();
uint8_t xlen = xopt->getExcludedPrefixLength();
map->set("excluded-prefix-len",
Element::create(static_cast<int>(xlen)));
- } else {
- map->set("excluded-prefix", Element::create(std::string("::")));
- map->set("excluded-prefix-len", Element::create(0));
}
break;
known_clients_ = known_clients;
}
+ /// @brief Adds class class_name to classes to be evaluated later
+ ///
+ /// @param class_name client class to be evaluated later
+ void deferClientClass(const ClientClass& class_name) {
+ if (!on_demand_classes_.contains(class_name)) {
+ on_demand_classes_.insert(class_name);
+ }
+ }
+
+ /// @brief Returns classes which will be evaluated later
+ const ClientClasses& getOnDemandClasses() const {
+ return (on_demand_classes_);
+ }
+
/// @brief returns the last address that was tried from this pool
///
/// @return address/prefix that was last tried from this pool
/// @brief Value of known clients
KnownClients known_clients_;
+ /// @brief On demand classes
+ ///
+ /// @ref isc::dhcp::Network::on_demand_classes
+ ClientClasses on_demand_classes_;
+
/// @brief Pointer to the user context (may be NULL)
data::ConstElementPtr user_context_;
ElementPtr pool_list = Element::createList();
for (PoolCollection::const_iterator pool = pools.cbegin();
pool != pools.cend(); ++pool) {
- // Prepare the map for a pool (@todo move this code to pool.cc)
- ElementPtr pool_map = Element::createMap();
- // Set pool
- const IOAddress& first = (*pool)->getFirstAddress();
- const IOAddress& last = (*pool)->getLastAddress();
- std::string range = first.toText() + "-" + last.toText();
- // Try to output a prefix (vs a range)
- int prefix_len = prefixLengthFromRange(first, last);
- if (prefix_len >= 0) {
- std::ostringstream oss;
- oss << first.toText() << "/" << prefix_len;
- range = oss.str();
- }
- pool_map->set("pool", Element::create(range));
- // Set user-context
- ConstElementPtr context = (*pool)->getContext();
- if (!isNull(context)) {
- pool_map->set("user-context", context);
- }
- // Set pool options
- ConstCfgOptionPtr opts = (*pool)->getCfgOption();
- pool_map->set("option-data", opts->toElement());
- // Set client-class
- const ClientClass& cclass = (*pool)->getClientClass();
- if (!cclass.empty()) {
- pool_map->set("client-class", Element::create(cclass));
- }
- // Set known-clients
- Pool::KnownClients kc = (*pool)->getKnownClients();
- if (kc != Pool::SERVE_BOTH) {
- pool_map->set("known-clients",
- Element::create(kc == Pool::SERVE_KNOWN ?
- "only" : "never"));
- }
- // Push on the pool list
- pool_list->add(pool_map);
+ // Add the elementized pool to the list
+ pool_list->add((*pool)->toElement());
}
map->set("pools", pool_list);
+
// Set pd-pools
const PoolCollection& pdpools = getPools(Lease::TYPE_PD);
ElementPtr pdpool_list = Element::createList();
for (PoolCollection::const_iterator pool = pdpools.cbegin();
pool != pdpools.cend(); ++pool) {
- // Get it as a Pool6 (@todo move this code to pool.cc)
- const Pool6* pdpool = dynamic_cast<Pool6*>(pool->get());
- if (!pdpool) {
- isc_throw(ToElementError, "invalid pd-pool pointer");
- }
- // Prepare the map for a pd-pool
- ElementPtr pool_map = Element::createMap();
- // Set prefix
- const IOAddress& prefix = pdpool->getFirstAddress();
- pool_map->set("prefix", Element::create(prefix.toText()));
- // Set prefix-len (get it from min - max)
- const IOAddress& last = pdpool->getLastAddress();
- int prefix_len = prefixLengthFromRange(prefix, last);
- if (prefix_len < 0) {
- // The pool is bad: give up
- isc_throw(ToElementError, "invalid prefix range "
- << prefix.toText() << "-" << last.toText());
- }
- pool_map->set("prefix-len", Element::create(prefix_len));
- // Set delegated-len
- uint8_t len = pdpool->getLength();
- pool_map->set("delegated-len",
- Element::create(static_cast<int>(len)));
-
- // Set excluded prefix
- const Option6PDExcludePtr& xopt =
- pdpool->getPrefixExcludeOption();
- if (xopt) {
- const IOAddress& xprefix =
- xopt->getExcludedPrefix(prefix, len);
- pool_map->set("excluded-prefix",
- Element::create(xprefix.toText()));
- uint8_t xlen = xopt->getExcludedPrefixLength();
- pool_map->set("excluded-prefix-len",
- Element::create(static_cast<int>(xlen)));
- }
-
- // Set user-context
- ConstElementPtr context = pdpool->getContext();
- if (!isNull(context)) {
- pool_map->set("user-context", context);
- }
- // Set pool options
- ConstCfgOptionPtr opts = pdpool->getCfgOption();
- pool_map->set("option-data", opts->toElement());
- // Set client-class
- const ClientClass& cclass = pdpool->getClientClass();
- if (!cclass.empty()) {
- pool_map->set("client-class", Element::create(cclass));
- }
- // Set known-clients
- Pool::KnownClients kc = pdpool->getKnownClients();
- if (kc != Pool::SERVE_BOTH) {
- pool_map->set("known-clients",
- Element::create(kc == Pool::SERVE_KNOWN ?
- "only" : "never"));
- }
- // Push on the pool list
- pdpool_list->add(pool_map);
+ // Add the elementized pool to the list
+ pdpool_list->add((*pool)->toElement());
}
map->set("pd-pools", pdpool_list);
subnet2->setIface("lo");
subnet2->setRelayInfo(IOAddress("10.0.0.1"));
subnet3->setIface("eth1");
+ subnet3->deferClientClass("foo");
+ subnet3->deferClientClass("bar");
cfg.add(subnet1);
cfg.add(subnet2);
" \"4o6-subnet\": \"\",\n"
" \"reservation-mode\": \"all\",\n"
" \"option-data\": [ ],\n"
- " \"pools\": [ ]\n"
+ " \"pools\": [ ]\n,"
+ " \"eval-client-classes\": [ \"foo\", \"bar\" ]\n"
"} ]\n";
runToElementTest<CfgSubnets4>(expected, cfg);
}
Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.64"), 26));
pool2->allowClientClass("bar");
pool2->setKnownClients(Pool::SERVE_KNOWN);
+ pool2->deferClientClass("foo");
subnet->addPool(pool1);
subnet->addPool(pool2);
" \"option-data\": [ ],\n"
" \"pool\": \"192.0.2.64/26\",\n"
" \"client-class\": \"bar\",\n"
- " \"known-clients\": \"only\"\n"
+ " \"known-clients\": \"only\",\n"
+ " \"eval-client-classes\": [ \"foo\" ]\n"
" }\n"
" ]\n"
"} ]\n";
subnet2->setIface("lo");
subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
subnet3->setIface("eth1");
+ subnet3->deferClientClass("foo");
+ subnet3->deferClientClass("bar");
cfg.add(subnet1);
cfg.add(subnet2);
" \"reservation-mode\": \"all\",\n"
" \"pools\": [ ],\n"
" \"pd-pools\": [ ],\n"
- " \"option-data\": [ ]\n"
+ " \"option-data\": [ ],\n"
+ " \"eval-client-classes\": [ \"foo\", \"bar\" ]\n"
"} ]\n";
runToElementTest<CfgSubnets6>(expected, cfg);
}
Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
pool2->allowClientClass("bar");
pool2->setKnownClients(Pool::SERVE_UNKNOWN);
+ pool2->deferClientClass("foo");
subnet->addPool(pool1);
subnet->addPool(pool2);
" \"pool\": \"2001:db8:1:1::/64\",\n"
" \"option-data\": [ ],\n"
" \"client-class\": \"bar\",\n"
- " \"known-clients\": \"never\"\n"
+ " \"known-clients\": \"never\",\n"
+ " \"eval-client-classes\": [ \"foo\" ]\n"
" }\n"
" ],\n"
" \"pd-pools\": [ ],\n"
IOAddress("2001:db8:2::"), 48, 64));
Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56,
IOAddress("2001:db8:3::"), 64));
+ pdpool1->deferClientClass("bar");
pdpool2->allowClientClass("bar");
pdpool2->setKnownClients(Pool::SERVE_KNOWN);
+
subnet->addPool(pdpool1);
subnet->addPool(pdpool2);
cfg.add(subnet);
" \"prefix\": \"2001:db8:2::\",\n"
" \"prefix-len\": 48,\n"
" \"delegated-len\": 64,\n"
- " \"option-data\": [ ]\n"
+ " \"option-data\": [ ],\n"
+ " \"eval-client-classes\": [ \"bar\" ]\n"
" },{\n"
" \"prefix\": \"2001:db8:3::\",\n"
" \"prefix-len\": 48,\n"
EXPECT_TRUE(*cclass == *cclass2);
EXPECT_FALSE(*cclass != *cclass2);
+ // Verify the on_demand flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getOnDemand());
+ cclass2->setOnDemand(true);
+ EXPECT_TRUE(cclass2->getOnDemand());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
// Make a class that differs from the first class only by name and
// verify that the equality tools reflect that the classes are not equal.
ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_two", expr,
// Verify that we can add classes with both addClass variants
// First addClass(name, expression, cfg_option)
- ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", cfg_option));
- ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", false, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", false, cfg_option));
// Verify duplicate add attempt throws
- ASSERT_THROW(dictionary->addClass("cc2", expr, "", cfg_option),
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false, cfg_option),
DuplicateClientClassDef);
// Verify that you cannot add a class with no name.
- ASSERT_THROW(dictionary->addClass("", expr, "", cfg_option), BadValue);
+ ASSERT_THROW(dictionary->addClass("", expr, "", false, cfg_option),
+ BadValue);
// Now with addClass(class pointer)
ASSERT_NO_THROW(cclass.reset(new ClientClassDef("cc3", expr, cfg_option)));
CfgOptionPtr options;
dictionary.reset(new ClientClassDictionary());
- ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false, options));
// Copy constructor should succeed.
ASSERT_NO_THROW(dictionary2.reset(new ClientClassDictionary(*dictionary)));
ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
// Let's checks that it doesn't return any nonsense
+ EXPECT_FALSE(cclass->getOnDemand());
EXPECT_FALSE(cclass->getCfgOptionDef());
string empty;
ASSERT_EQ(IOAddress("0.0.0.0"), cclass->getNextServer());
// Verify we can create a class with a name, expression, and no cfg_option
ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+ cclass->setOnDemand(true);
string sname = "This is a very long string that can be a server name";
string filename = "this-is-a-slightly-longish-name-of-a-file.txt";
cclass->setFilename(filename);
// Let's checks that it doesn't return any nonsense
- ASSERT_EQ(IOAddress("1.2.3.4"), cclass->getNextServer());
+ EXPECT_TRUE(cclass->getOnDemand());
+ EXPECT_EQ(IOAddress("1.2.3.4"), cclass->getNextServer());
EXPECT_EQ(sname, cclass->getSname());
EXPECT_EQ(filename, cclass->getFilename());
}
ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
std::string test = "option[12].text == 'foo'";
cclass->setTest(test);
+ cclass->setOnDemand(true);
std::string next_server = "1.2.3.4";
cclass->setNextServer(IOAddress(next_server));
std::string sname = "my-server.example.com";
std::string expected = "{\n"
"\"name\": \"" + name + "\",\n"
"\"test\": \"" + test + "\",\n"
+ "\"eval-on-demand\": true,\n"
"\"next-server\": \"" + next_server + "\",\n"
"\"server-hostname\": \"" + sname + "\",\n"
"\"boot-file-name\": \"" + filename + "\",\n"
// Get a client class dictionary and fill it
dictionary.reset(new ClientClassDictionary());
- ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false, options));
// Unparse it
auto add_defaults =
EXPECT_EQ(Pool::SERVE_KNOWN,pool->getKnownClients());
}
+// This test checks that handling for eval-client-classes is valid.
+TEST(Pool4Test, onDemandClasses) {
+ // Create a pool.
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+
+ // This client starts with no deferred classes.
+ EXPECT_TRUE(pool->getOnDemandClasses().empty());
+
+ // Add the first class
+ pool->deferClientClass("router");
+ EXPECT_EQ(1, pool->getOnDemandClasses().size());
+
+ // Add a second class
+ pool->deferClientClass("modem");
+ EXPECT_EQ(2, pool->getOnDemandClasses().size());
+ EXPECT_TRUE(pool->getOnDemandClasses().contains("router"));
+ EXPECT_TRUE(pool->getOnDemandClasses().contains("modem"));
+ EXPECT_FALSE(pool->getOnDemandClasses().contains("foo"));
+
+ // Check that it's ok to add the same class repeatedly
+ EXPECT_NO_THROW(pool->deferClientClass("foo"));
+ EXPECT_NO_THROW(pool->deferClientClass("foo"));
+ EXPECT_NO_THROW(pool->deferClientClass("foo"));
+
+ // Check that 'foo' is marked for late evaluation
+ EXPECT_TRUE(pool->getOnDemandClasses().contains("foo"));
+}
+
// This test checks that handling for last allocated address/prefix is valid.
TEST(Pool4Test, lastAllocated) {
// Create a pool.
EXPECT_EQ(Pool::SERVE_KNOWN,pool.getKnownClients());
}
+// This test checks that handling for eval-client-classes is valid.
+TEST(Pool6Test, onDemandClasses) {
+ // Create a pool.
+ Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+
+ // This client starts with no deferred classes.
+ EXPECT_TRUE(pool.getOnDemandClasses().empty());
+
+ // Add the first class
+ pool.deferClientClass("router");
+ EXPECT_EQ(1, pool.getOnDemandClasses().size());
+
+ // Add a second class
+ pool.deferClientClass("modem");
+ EXPECT_EQ(2, pool.getOnDemandClasses().size());
+ EXPECT_TRUE(pool.getOnDemandClasses().contains("router"));
+ EXPECT_TRUE(pool.getOnDemandClasses().contains("modem"));
+ EXPECT_FALSE(pool.getOnDemandClasses().contains("foo"));
+
+ // Check that it's ok to add the same class repeatedly
+ EXPECT_NO_THROW(pool.deferClientClass("foo"));
+ EXPECT_NO_THROW(pool.deferClientClass("foo"));
+ EXPECT_NO_THROW(pool.deferClientClass("foo"));
+
+ // Check that 'foo' is marked for late evaluation
+ EXPECT_TRUE(pool.getOnDemandClasses().contains("foo"));
+}
+
// This test checks that handling for last allocated address/prefix is valid.
TEST(Pool6Test, lastAllocated) {
// Create a pool.
" \"server-hostname\": \"\","
" \"boot-file-name\": \"\","
" \"client-class\": \"\","
+ " \"eval-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"4o6-interface\": \"\","
" \"4o6-interface-id\": \"\","
" \"server-hostname\": \"\","
" \"boot-file-name\": \"\","
" \"client-class\": \"\","
+ " \"eval-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"4o6-interface\": \"\","
" \"4o6-interface-id\": \"\","
" \"preferred-lifetime\": 300,"
" \"valid-lifetime\": 400,"
" \"client-class\": \"\","
+ " \"eval-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"decline-probation-period\": 86400,"
" \"dhcp4o6-port\": 0,"
" \"preferred-lifetime\": 30,"
" \"valid-lifetime\": 40,"
" \"client-class\": \"\","
+ " \"eval-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"decline-probation-period\": 86400,"
" \"dhcp4o6-port\": 0,"
EXPECT_EQ("alpha", network->getClientClass());
}
+// This test verifies that it's possible to specify eval-client-classes
+// on shared-network level.
+TEST_F(SharedNetwork6ParserTest, evalClientClasses) {
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ ElementPtr class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create("beta"));
+ config_element->set("eval-client-classes", class_list);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ network = parser.parse(config_element);
+ ASSERT_TRUE(network);
+
+ const ClientClasses& classes = network->getOnDemandClasses();
+ EXPECT_EQ(2, classes.size());
+ EXPECT_EQ("alpha, beta", classes.toText());
+}
+
+// This test verifies that bad eval-client-classes configs raise
+// expected errors.
+TEST_F(SharedNetwork6ParserTest, badEvalClientClasses) {
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Element of the list must be strings.
+ ElementPtr class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create(1234));
+ config_element->set("eval-client-classes", class_list);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+
+ // Empty class name is forbidden.
+ class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create(""));
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+
+ // And of course the list must be a list even the parser can only
+ // trigger the previous error case...
+ class_list = Element::createMap();
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+}
+
} // end of anonymous namespace
network->setValid(200);
network->setMatchClientId(false);
+ network->deferClientClass("foo");
+
// Add several subnets.
Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
SubnetID(1)));
" \"valid-lifetime\": 30\n"
" }\n"
" ],\n"
+ " \"eval-client-classes\": [ \"foo\" ],\n"
" \"valid-lifetime\": 200\n"
"}\n";
network->setPreferred(200);
network->setValid(300);
network->setRapidCommit(true);
+ network->deferClientClass("foo");
// Add several subnets.
Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
" \"valid-lifetime\": 40\n"
" }\n"
" ],\n"
+ " \"eval-client-classes\": [ \"foo\" ],\n"
" \"valid-lifetime\": 300\n"
"}\n";
}
// Build our reference dictionary of client classes
- ref_dictionary_->addClass("cc1", ExpressionPtr(), "", CfgOptionPtr());
- ref_dictionary_->addClass("cc2", ExpressionPtr(), "", CfgOptionPtr());
- ref_dictionary_->addClass("cc3", ExpressionPtr(), "", CfgOptionPtr());
+ ref_dictionary_->addClass("cc1", ExpressionPtr(),
+ "", false, CfgOptionPtr());
+ ref_dictionary_->addClass("cc2", ExpressionPtr(),
+ "", false, CfgOptionPtr());
+ ref_dictionary_->addClass("cc3", ExpressionPtr(),
+ "", false, CfgOptionPtr());
}
// Tests whether Subnet4 object is able to store and process properly
// information about allowed client class (a single class).
-TEST(Subnet4Test, clientClasses) {
+TEST(Subnet4Test, clientClass) {
// Create the V4 subnet.
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
// Tests whether Subnet6 object is able to store and process properly
// information about allowed client class (a single class).
-TEST(Subnet6Test, clientClasses) {
+TEST(Subnet6Test, clientClass) {
// Create the V6 subnet.
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));