]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5374] Checkpoint before updting syntax
authorFrancis Dupont <fdupont@isc.org>
Mon, 27 Nov 2017 01:59:59 +0000 (02:59 +0100)
committerFrancis Dupont <fdupont@isc.org>
Mon, 27 Nov 2017 01:59:59 +0000 (02:59 +0100)
34 files changed:
src/bin/dhcp4/dhcp4_lexer.ll
src/bin/dhcp4/dhcp4_parser.yy
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/dhcp4_srv.h
src/bin/dhcp4/parser_context.cc
src/bin/dhcp4/parser_context.h
src/bin/dhcp6/dhcp6_lexer.ll
src/bin/dhcp6/dhcp6_parser.yy
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/bin/dhcp6/parser_context.cc
src/bin/dhcp6/parser_context.h
src/lib/dhcp/pkt.cc
src/lib/dhcp/pkt.h
src/lib/dhcp/tests/pkt4_unittest.cc
src/lib/dhcp/tests/pkt6_unittest.cc
src/lib/dhcpsrv/client_class_def.cc
src/lib/dhcpsrv/client_class_def.h
src/lib/dhcpsrv/network.cc
src/lib/dhcpsrv/network.h
src/lib/dhcpsrv/parsers/client_class_def_parser.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/shared_network_parser.cc
src/lib/dhcpsrv/pool.cc
src/lib/dhcpsrv/pool.h
src/lib/dhcpsrv/subnet.cc
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
src/lib/dhcpsrv/tests/client_class_def_unittest.cc
src/lib/dhcpsrv/tests/pool_unittest.cc
src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
src/lib/dhcpsrv/tests/shared_network_unittest.cc
src/lib/dhcpsrv/tests/srv_config_unittest.cc
src/lib/dhcpsrv/tests/subnet_unittest.cc

index 2434eb3caee1c643a74bae947e1dc8ab7a534a59..23ae51162919e311b031c22e0d484a6c92ade310 100644 (file)
@@ -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:
index 247a240855023b96d968199cf9ced5051f031d3e..2b83ced450550ad8a95b1cc8adbf1471d33ce7ea 100644 (file)
@@ -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
index 09f1eab5764a2b8260666490b86d207262caa332..e5f39d21bb91a0572e8453e133dc397deea70461 100644 (file)
@@ -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)
 {
index 2fa90f127e3da6782f16cd6a578ab68d80d1fced..536ef263daf5e44757c943c662c2c801ee25cbba 100644 (file)
@@ -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.
index bbafcc2f2e86ebbadb154f277c4aaabd15e016ee..dca316b398986709ed242c982bf83d27caa72ded 100644 (file)
@@ -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:
index 83e1f0a13a34aeb8d3d028f397b572d12fcb6fd8..4b112c651f330c3db43b65815b9dd3c0287aa590 100644 (file)
@@ -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,
 
index fba59d48e099614a733eeafea3da0cffb3eebeb2..0ea4191f49b07c7256aa06fa5ff500f2041982d0 100644 (file)
@@ -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:
index 34c346503d45bc27555af73847ebb0c30165e103..0747d12c48af45ff4c0d6828b015a75bbc709c15 100644 (file)
@@ -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 ---------------------------------------------
index 628b5c49d5a7405572220fe031197d79eacfd53b..18cbcb03febb4c04718fad8dc55b098284875f0d 100644 (file)
@@ -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) {
index ed7a10300583c614f1661d64eea886ba83d2aefb..a63691f8ce5b7d0f189e6e9ce93571519f51cb9f 100644 (file)
@@ -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
index 1ff28141ea1e5144f809a374d6c770619e4921d2..5bd52e4b1084e09bcde5864f27e687f340ecd55c 100644 (file)
@@ -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:
index 568cd995b5b75911d80905c378b3049f75959f9f..e6a1388318343c205c6489b6a63a33341e5e895e 100644 (file)
@@ -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,
 
index ac633f078cf2d773b74009b6fdde044c0af3043b..a6261802d576c01d4525af260d6247541ec8e52a 100644 (file)
@@ -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);
     }
 }
 
index 078584b1d16888afe9984579d7d983ef0091bc25..bde7a8500eab980d6ce7597ff93266bca53e7418 100644 (file)
@@ -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
index d02c2cb28eed754fa5372ed2913effb13426691a..3939662911820d16823d9fa94ad77f390c2f8f7c 100644 (file)
@@ -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) {
index f45b937c81412bbc41ee0490ae00f6f7f4e62bd7..adb84dbb057ccf4dbe2ce5e30dc5de4dc63d017e 100644 (file)
@@ -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) {
index d6e5dfe657553a8a1d216cc1c84d088a062995e5..9b5b1537042c8f5075d8ac67d00b90f52906aa46 100644 (file)
@@ -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);
index 63313ec790f52c076ca1634aca268aed6c6b2f13..d12d2b6f80127490495d84ad799424e79d6b8b4d 100644 (file)
@@ -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(),
index 1297f2d38f85571b28e087fbb0f4264dba0daf29..9ba52a8598f791123a60ddc2894951fd7390290d 100644 (file)
@@ -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<long long>
index ab4a8d687fffd6b6a5a5a9e5fe9b0498783b28e9..b34eb6e4246462ce9799dfec6c68f2c7b3824b1b 100644 (file)
@@ -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<uint32_t> t1_;
 
index 6f1cfa1927e0997adcc4b2df5fba2bcad54902ed..e0d11ec9573d7584446d628260f862f6326ca260 100644 (file)
@@ -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()
index a384dbf135b49fb47a91f55ccaa3daf89bef0d12..3c549c520e027cb25487c13d0bb50c633e22b045 100644 (file)
@@ -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<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) {
@@ -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<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");
@@ -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<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_);
 }
@@ -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<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)
 
index 65f201e43972ee9b5fa67d80697d8d32e33f1ba4..91c104fdef9110ca41688bab04b7f5245236152f 100644 (file)
@@ -70,6 +70,20 @@ SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) {
             }
         }
 
+        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;
@@ -111,6 +125,20 @@ SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) {
             }
         }
 
+        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");
 
index 89cbd607df132f9404e1c814504eec6c489c9b0a..b632949d7d5ea8f2c930d4ca317676baede5a578 100644 (file)
@@ -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<int>(xlen)));
-            } else {
-                map->set("excluded-prefix", Element::create(std::string("::")));
-                map->set("excluded-prefix-len", Element::create(0));
             }
 
             break;
index 7ef00d59d5d2e3b99f7a13e0666fdaed10b3c490..df0cc5fce0200d66f06ae800678a6fab85425d7b 100644 (file)
@@ -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_;
 
index 3b44a6707a61ed5691da2d186b0c4c00fa3a01c4..841592f789d880c9675ad214fb2707585ee5440e 100644 (file)
@@ -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<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);
 
index c97c7bee84050ffdd669b37dbc5c5730fe10b9f6..3acfe130700ddb55686076f487c6bcf97dc77e78 100644 (file)
@@ -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<CfgSubnets4>(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";
index d12cada9569224c8266d3e7d1eeb0f58c46305e2..bedaff84e8633f0e421583fc56e3c2d96b37c1ca 100644 (file)
@@ -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<CfgSubnets6>(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"
index a8e740e8d0f10d99722e55d897069c0bd9b258e9..816d100c968ad33a9985b7873fc4c783b8e37ffd 100644 (file)
@@ -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 =
index d1b8b462b6f6a9c91f349700077437e4c11cd9d2..ce2a1a8565721b64b28d4da98db8467c195f48c5 100644 (file)
@@ -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.
index 10a60ddb97d8ccb11d0eb28332ef0b236d68d4b0..dee9300e6a97b9dae5d5518415fb3eb7bab0fbe3 100644 (file)
@@ -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
index 63508ec44ec92b718f8294c6d34d9487eab59ec3..58df64673ed4cb550ce1ccaa580adf87ce8b54e3 100644 (file)
@@ -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";
 
index acf7806e597f570d31f6c31ab26c2d3b4017d4cf..b38a9783f9ac04eadc97e85dcb1d27ac31d94095 100644 (file)
@@ -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());
     }
 
 
index a53107ca53821062724b6a4527586bc9dd4661d0..ef34893d7b99bf122401ec0fe055e7b920f433b9 100644 (file)
@@ -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));