From: Francis Dupont Date: Mon, 27 Nov 2017 01:59:59 +0000 (+0100) Subject: [5374] Checkpoint before updting syntax X-Git-Tag: trac5458a_base~14^2~25 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=033b5a941e24765b8cdeea354f43d47975d38fb6;p=thirdparty%2Fkea.git [5374] Checkpoint before updting syntax --- diff --git a/src/bin/dhcp4/dhcp4_lexer.ll b/src/bin/dhcp4/dhcp4_lexer.ll index 2434eb3cae..23ae511629 100644 --- a/src/bin/dhcp4/dhcp4_lexer.ll +++ b/src/bin/dhcp4/dhcp4_lexer.ll @@ -517,7 +517,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} 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_); @@ -531,7 +530,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} 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_); @@ -816,6 +814,17 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } +\"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: @@ -831,13 +840,21 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} \"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: diff --git a/src/bin/dhcp4/dhcp4_parser.yy b/src/bin/dhcp4/dhcp4_parser.yy index 247a240855..2b83ced450 100644 --- a/src/bin/dhcp4/dhcp4_parser.yy +++ b/src/bin/dhcp4/dhcp4_parser.yy @@ -126,7 +126,9 @@ using namespace std; 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" @@ -912,6 +914,7 @@ subnet4_param: valid_lifetime | id | rapid_commit | client_class + | eval_client_classes | reservations | reservation_mode | relay @@ -975,13 +978,23 @@ interface_id: INTERFACE_ID { }; 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 { @@ -1051,6 +1064,7 @@ shared_network_param: name | relay | reservation_mode | client_class + | eval_client_classes | valid_lifetime | unknown_map_entry ; @@ -1333,6 +1347,7 @@ pool_params: pool_param pool_param: pool_entry | option_data_list | client_class + | eval_client_classes | user_context | known_clients | unknown_map_entry @@ -1576,6 +1591,7 @@ not_empty_client_class_params: client_class_param client_class_param: client_class_name | client_class_test + | eval_on_demand | option_def_list | option_data_list | next_server @@ -1594,6 +1610,11 @@ client_class_test: TEST { 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 diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 09f1eab576..e5f39d21bb 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -2325,11 +2325,13 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) { 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); @@ -2380,11 +2382,13 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) { 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); @@ -2668,6 +2672,7 @@ Dhcpv4Srv::processInform(Pkt4Ptr& inform) { Pkt4Ptr ack = ex.getResponse(); ex.setReservedClientClasses(); + lateClassify(ex); buildCfgOptionList(ex); appendRequestedOptions(ex); @@ -2970,6 +2975,10 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) { 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 { @@ -2997,6 +3006,94 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) { } } +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) { diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 2fa90f127e..536ef263da 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -803,6 +803,18 @@ protected: /// @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. diff --git a/src/bin/dhcp4/parser_context.cc b/src/bin/dhcp4/parser_context.cc index bbafcc2f2e..dca316b398 100644 --- a/src/bin/dhcp4/parser_context.cc +++ b/src/bin/dhcp4/parser_context.cc @@ -180,8 +180,6 @@ Parser4Context::contextName() return ("reservations"); case RELAY: return ("relay"); - case CLIENT_CLASS: - return ("client-class"); case LOGGERS: return ("loggers"); case OUTPUT_OPTIONS: diff --git a/src/bin/dhcp4/parser_context.h b/src/bin/dhcp4/parser_context.h index 83e1f0a13a..4b112c651f 100644 --- a/src/bin/dhcp4/parser_context.h +++ b/src/bin/dhcp4/parser_context.h @@ -273,9 +273,6 @@ public: /// Used while parsing Dhcp4/subnet4relay structures. RELAY, - /// Used while parsing Dhcp4/client-classes structures. - CLIENT_CLASS, - /// Used while parsing Logging/loggers structures. LOGGERS, diff --git a/src/bin/dhcp6/dhcp6_lexer.ll b/src/bin/dhcp6/dhcp6_lexer.ll index fba59d48e0..0ea4191f49 100644 --- a/src/bin/dhcp6/dhcp6_lexer.ll +++ b/src/bin/dhcp6/dhcp6_lexer.ll @@ -1069,6 +1069,18 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } +\"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: @@ -1092,6 +1104,15 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} } } +\"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: diff --git a/src/bin/dhcp6/dhcp6_parser.yy b/src/bin/dhcp6/dhcp6_parser.yy index 34c346503d..0747d12c48 100644 --- a/src/bin/dhcp6/dhcp6_parser.yy +++ b/src/bin/dhcp6/dhcp6_parser.yy @@ -119,7 +119,9 @@ using namespace std; 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" @@ -907,6 +909,7 @@ subnet6_param: preferred_lifetime | id | rapid_commit | client_class + | eval_client_classes | reservations | reservation_mode | relay @@ -939,13 +942,23 @@ interface_id: INTERFACE_ID { }; 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 { @@ -1013,6 +1026,7 @@ shared_network_param: name | relay | reservation_mode | client_class + | eval_client_classes | preferred_lifetime | rapid_commit | valid_lifetime @@ -1296,6 +1310,7 @@ pool_params: pool_param pool_param: pool_entry | option_data_list | client_class + | eval_client_classes | user_context | known_clients | unknown_map_entry @@ -1384,6 +1399,7 @@ pd_pool_param: pd_prefix | pd_delegated_len | option_data_list | client_class + | eval_client_classes | excluded_prefix | excluded_prefix_len | user_context @@ -1601,6 +1617,7 @@ not_empty_client_class_params: client_class_param client_class_param: client_class_name | client_class_test + | eval_on_demand | option_data_list | unknown_map_entry ; @@ -1615,6 +1632,11 @@ client_class_test: TEST { 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 --------------------------------------------- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 628b5c49d5..18cbcb03fe 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -2489,6 +2489,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { assignLeases(solicit, response, ctx); setReservedClientClasses(solicit, ctx); + lateClassify(solicit, ctx); copyClientOptions(solicit, response); CfgOptionList co_list; @@ -2523,6 +2524,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { assignLeases(request, reply, ctx); setReservedClientClasses(request, ctx); + lateClassify(request, ctx); copyClientOptions(request, reply); CfgOptionList co_list; @@ -2553,6 +2555,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { extendLeases(renew, reply, ctx); setReservedClientClasses(renew, ctx); + lateClassify(renew, ctx); copyClientOptions(renew, reply); CfgOptionList co_list; @@ -2583,6 +2586,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { extendLeases(rebind, reply, ctx); setReservedClientClasses(rebind, ctx); + lateClassify(rebind, ctx); copyClientOptions(rebind, reply); CfgOptionList co_list; @@ -2607,6 +2611,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) { 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. @@ -2700,6 +2705,7 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) { AllocEngine::ClientContext6 ctx; initContext(release, ctx); setReservedClientClasses(release, ctx); + lateClassify(release, ctx); Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid())); @@ -2729,6 +2735,7 @@ Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) { 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); @@ -3010,6 +3017,7 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) { 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())); @@ -3096,6 +3104,10 @@ void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) { 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 { @@ -3143,6 +3155,94 @@ Dhcpv6Srv::setReservedClientClasses(const Pkt6Ptr& pkt, } } +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) { diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index ed7a103005..a63691f8ce 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -649,6 +649,19 @@ protected: 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 diff --git a/src/bin/dhcp6/parser_context.cc b/src/bin/dhcp6/parser_context.cc index 1ff28141ea..5bd52e4b10 100644 --- a/src/bin/dhcp6/parser_context.cc +++ b/src/bin/dhcp6/parser_context.cc @@ -182,8 +182,6 @@ Parser6Context::contextName() return ("reservations"); case RELAY: return ("relay"); - case CLIENT_CLASS: - return ("client-class"); case LOGGERS: return ("loggers"); case OUTPUT_OPTIONS: diff --git a/src/bin/dhcp6/parser_context.h b/src/bin/dhcp6/parser_context.h index 568cd995b5..e6a1388318 100644 --- a/src/bin/dhcp6/parser_context.h +++ b/src/bin/dhcp6/parser_context.h @@ -279,9 +279,6 @@ public: /// Used while parsing Dhcp6/subnet6/relay structures. RELAY, - /// Used while parsing Dhcp6/client-classes structures. - CLIENT_CLASS, - /// Used while parsing Logging/loggers structures. LOGGERS, diff --git a/src/lib/dhcp/pkt.cc b/src/lib/dhcp/pkt.cc index ac633f078c..a6261802d5 100644 --- a/src/lib/dhcp/pkt.cc +++ b/src/lib/dhcp/pkt.cc @@ -97,9 +97,10 @@ Pkt::inClass(const std::string& client_class) { } 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); } } diff --git a/src/lib/dhcp/pkt.h b/src/lib/dhcp/pkt.h index 078584b1d1..bde7a8500e 100644 --- a/src/lib/dhcp/pkt.h +++ b/src/lib/dhcp/pkt.h @@ -285,13 +285,19 @@ public: /// 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). /// @@ -579,6 +585,14 @@ public: /// @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 diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc index d02c2cb28e..3939662911 100644 --- a/src/lib/dhcp/tests/pkt4_unittest.cc +++ b/src/lib/dhcp/tests/pkt4_unittest.cc @@ -916,13 +916,13 @@ TEST_F(Pkt4Test, clientClasses) { // 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); @@ -938,6 +938,34 @@ TEST_F(Pkt4Test, clientClasses) { 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) { diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc index f45b937c81..adb84dbb05 100644 --- a/src/lib/dhcp/tests/pkt6_unittest.cc +++ b/src/lib/dhcp/tests/pkt6_unittest.cc @@ -1089,13 +1089,13 @@ TEST_F(Pkt6Test, clientClasses) { // 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); @@ -1111,6 +1111,34 @@ TEST_F(Pkt6Test, clientClasses) { 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) { diff --git a/src/lib/dhcpsrv/client_class_def.cc b/src/lib/dhcpsrv/client_class_def.cc index d6e5dfe657..9b5b153704 100644 --- a/src/lib/dhcpsrv/client_class_def.cc +++ b/src/lib/dhcpsrv/client_class_def.cc @@ -18,7 +18,8 @@ namespace dhcp { 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 @@ -37,7 +38,7 @@ ClientClassDef::ClientClassDef(const std::string& name, 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_) { @@ -53,6 +54,7 @@ ClientClassDef::ClientClassDef(const ClientClassDef& rhs) rhs.cfg_option_->copyTo(*cfg_option_); } + on_demand_ = rhs.on_demand_; next_server_ = rhs.next_server_; sname_ = rhs.sname_; filename_ = rhs.filename_; @@ -91,6 +93,16 @@ ClientClassDef::setTest(const std::string& test) { 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_); @@ -123,6 +135,7 @@ ClientClassDef::equals(const ClientClassDef& other) const { ((!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_)); @@ -138,6 +151,10 @@ ClientClassDef:: toElement() const { 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()); @@ -183,6 +200,7 @@ void 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, @@ -190,6 +208,7 @@ ClientClassDictionary::addClass(const std::string& name, 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); diff --git a/src/lib/dhcpsrv/client_class_def.h b/src/lib/dhcpsrv/client_class_def.h index 63313ec790..d12d2b6f80 100644 --- a/src/lib/dhcpsrv/client_class_def.h +++ b/src/lib/dhcpsrv/client_class_def.h @@ -82,6 +82,12 @@ public: /// @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; @@ -182,6 +188,12 @@ private: /// 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_; @@ -239,6 +251,7 @@ public: /// @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) @@ -249,7 +262,8 @@ public: /// 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(), diff --git a/src/lib/dhcpsrv/network.cc b/src/lib/dhcpsrv/network.cc index 1297f2d38f..9ba52a8598 100644 --- a/src/lib/dhcpsrv/network.cc +++ b/src/lib/dhcpsrv/network.cc @@ -36,6 +36,18 @@ Network::allowClientClass(const isc::dhcp::ClientClass& class_name) { 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(); @@ -58,6 +70,17 @@ Network::toElement() const { 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 diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h index ab4a8d687f..b34eb6e424 100644 --- a/src/lib/dhcpsrv/network.h +++ b/src/lib/dhcpsrv/network.h @@ -169,9 +169,17 @@ public: /// @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 @@ -273,6 +281,12 @@ protected: /// 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 t1_; diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc index 6f1cfa1927..e0d11ec957 100644 --- a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc @@ -132,6 +132,12 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary, 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")) { @@ -187,7 +193,7 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary, // 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() diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index a384dbf135..3c549c520e 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -388,6 +388,21 @@ PoolParser::parse(PoolStoragePtr pools, } } + // Try setting up on demand client classes. + ConstElementPtr class_list = pool_structure->get("eval-client-classes"); + if (class_list) { + const std::vector& 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) { @@ -709,6 +724,21 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, 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& 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"); @@ -879,6 +909,8 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { 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 { @@ -921,6 +953,20 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { << " (" << known_clients_->getPosition() << ")"); } + if (class_list) { + const std::vector& 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_); } @@ -1095,6 +1141,21 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, 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& 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) diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc index 65f201e439..91c104fdef 100644 --- a/src/lib/dhcpsrv/parsers/shared_network_parser.cc +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc @@ -70,6 +70,20 @@ SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) { } } + if (shared_network_data->contains("eval-client-classes")) { + const std::vector& 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; @@ -111,6 +125,20 @@ SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) { } } + if (shared_network_data->contains("eval-client-classes")) { + const std::vector& 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"); diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc index 89cbd607df..b632949d7d 100644 --- a/src/lib/dhcpsrv/pool.cc +++ b/src/lib/dhcpsrv/pool.cc @@ -137,6 +137,17 @@ Pool::toElement() const { 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); } @@ -359,6 +370,7 @@ Pool6::toElement() const { 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(); @@ -373,9 +385,6 @@ Pool6::toElement() const { uint8_t xlen = xopt->getExcludedPrefixLength(); map->set("excluded-prefix-len", Element::create(static_cast(xlen))); - } else { - map->set("excluded-prefix", Element::create(std::string("::"))); - map->set("excluded-prefix-len", Element::create(0)); } break; diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h index 7ef00d59d5..df0cc5fce0 100644 --- a/src/lib/dhcpsrv/pool.h +++ b/src/lib/dhcpsrv/pool.h @@ -149,6 +149,20 @@ public: 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 @@ -241,6 +255,11 @@ protected: /// @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_; diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index 3b44a6707a..841592f789 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -717,108 +717,18 @@ Subnet6::toElement() const { 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(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(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(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); diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc index c97c7bee84..3acfe13070 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc @@ -739,6 +739,8 @@ TEST(CfgSubnets4Test, unparseSubnet) { 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); @@ -799,7 +801,8 @@ TEST(CfgSubnets4Test, unparseSubnet) { " \"4o6-subnet\": \"\",\n" " \"reservation-mode\": \"all\",\n" " \"option-data\": [ ],\n" - " \"pools\": [ ]\n" + " \"pools\": [ ]\n," + " \"eval-client-classes\": [ \"foo\", \"bar\" ]\n" "} ]\n"; runToElementTest(expected, cfg); } @@ -814,6 +817,7 @@ TEST(CfgSubnets4Test, unparsePool) { 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); @@ -845,7 +849,8 @@ TEST(CfgSubnets4Test, unparsePool) { " \"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"; diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc index d12cada956..bedaff84e8 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc @@ -438,6 +438,8 @@ TEST(CfgSubnets6Test, unparseSubnet) { 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); @@ -487,7 +489,8 @@ TEST(CfgSubnets6Test, unparseSubnet) { " \"reservation-mode\": \"all\",\n" " \"pools\": [ ],\n" " \"pd-pools\": [ ],\n" - " \"option-data\": [ ]\n" + " \"option-data\": [ ],\n" + " \"eval-client-classes\": [ \"foo\", \"bar\" ]\n" "} ]\n"; runToElementTest(expected, cfg); } @@ -505,6 +508,7 @@ TEST(CfgSubnets6Test, unparsePool) { 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); @@ -530,7 +534,8 @@ TEST(CfgSubnets6Test, unparsePool) { " \"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" @@ -551,9 +556,11 @@ TEST(CfgSubnets6Test, unparsePdPool) { 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); @@ -576,7 +583,8 @@ TEST(CfgSubnets6Test, unparsePdPool) { " \"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" diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc index a8e740e8d0..816d100c96 100644 --- a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc +++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc @@ -142,6 +142,13 @@ TEST(ClientClassDef, copyAndEquality) { 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, @@ -226,15 +233,16 @@ TEST(ClientClassDictionary, basics) { // 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))); @@ -290,9 +298,9 @@ TEST(ClientClassDictionary, copyAndEquality) { 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))); @@ -338,6 +346,7 @@ TEST(ClientClassDef, fixedFieldsDefaults) { 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()); @@ -360,6 +369,7 @@ TEST(ClientClassDef, fixedFieldsBasics) { // 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"; @@ -369,7 +379,8 @@ TEST(ClientClassDef, fixedFieldsBasics) { 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()); } @@ -386,6 +397,7 @@ TEST(ClientClassDef, unparseDef) { 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"; @@ -397,6 +409,7 @@ TEST(ClientClassDef, unparseDef) { 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" @@ -413,9 +426,9 @@ TEST(ClientClassDictionary, unparseDict) { // 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 = diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc index d1b8b462b6..ce2a1a8565 100644 --- a/src/lib/dhcpsrv/tests/pool_unittest.cc +++ b/src/lib/dhcpsrv/tests/pool_unittest.cc @@ -244,6 +244,35 @@ TEST(Pool4Test, knownClients) { 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. @@ -624,6 +653,35 @@ TEST(Pool6Test, knownClients) { 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. diff --git a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc index 10a60ddb97..dee9300e6a 100644 --- a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc @@ -52,6 +52,7 @@ public: " \"server-hostname\": \"\"," " \"boot-file-name\": \"\"," " \"client-class\": \"\"," + " \"eval-client-classes\": []\n," " \"reservation-mode\": \"all\"," " \"4o6-interface\": \"\"," " \"4o6-interface-id\": \"\"," @@ -72,6 +73,7 @@ public: " \"server-hostname\": \"\"," " \"boot-file-name\": \"\"," " \"client-class\": \"\"," + " \"eval-client-classes\": []\n," " \"reservation-mode\": \"all\"," " \"4o6-interface\": \"\"," " \"4o6-interface-id\": \"\"," @@ -192,6 +194,7 @@ public: " \"preferred-lifetime\": 300," " \"valid-lifetime\": 400," " \"client-class\": \"\"," + " \"eval-client-classes\": []\n," " \"reservation-mode\": \"all\"," " \"decline-probation-period\": 86400," " \"dhcp4o6-port\": 0," @@ -207,6 +210,7 @@ public: " \"preferred-lifetime\": 30," " \"valid-lifetime\": 40," " \"client-class\": \"\"," + " \"eval-client-classes\": []\n," " \"reservation-mode\": \"all\"," " \"decline-probation-period\": 86400," " \"dhcp4o6-port\": 0," @@ -279,4 +283,55 @@ TEST_F(SharedNetwork6ParserTest, clientClass) { 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 diff --git a/src/lib/dhcpsrv/tests/shared_network_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_unittest.cc index 63508ec44e..58df64673e 100644 --- a/src/lib/dhcpsrv/tests/shared_network_unittest.cc +++ b/src/lib/dhcpsrv/tests/shared_network_unittest.cc @@ -195,6 +195,8 @@ TEST(SharedNetwork4Test, unparse) { 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))); @@ -256,6 +258,7 @@ TEST(SharedNetwork4Test, unparse) { " \"valid-lifetime\": 30\n" " }\n" " ],\n" + " \"eval-client-classes\": [ \"foo\" ],\n" " \"valid-lifetime\": 200\n" "}\n"; @@ -479,6 +482,7 @@ TEST(SharedNetwork6Test, unparse) { 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, @@ -534,6 +538,7 @@ TEST(SharedNetwork6Test, unparse) { " \"valid-lifetime\": 40\n" " }\n" " ],\n" + " \"eval-client-classes\": [ \"foo\" ],\n" " \"valid-lifetime\": 300\n" "}\n"; diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc index acf7806e59..b38a9783f9 100644 --- a/src/lib/dhcpsrv/tests/srv_config_unittest.cc +++ b/src/lib/dhcpsrv/tests/srv_config_unittest.cc @@ -65,9 +65,12 @@ public: } // 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()); } diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index a53107ca53..ef34893d7b 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -440,7 +440,7 @@ TEST(Subnet4Test, pool4Checks) { // 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)); @@ -1026,7 +1026,7 @@ TEST(Subnet6Test, poolTypes) { // 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));