Parcourir la source

[4097a] Rebased and updated trac4097 code (still some comments to address)

Francis Dupont il y a 9 ans
Parent
commit
ac0de4134f

+ 1 - 0
src/bin/dhcp4/Makefile.am

@@ -75,6 +75,7 @@ kea_dhcp4_SOURCES  = main.cc
 kea_dhcp4_LDADD  = libdhcp4.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la

+ 76 - 4
src/bin/dhcp4/dhcp4_srv.cc

@@ -38,6 +38,8 @@
 #include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/utils.h>
+#include <eval/evaluate.h>
+#include <eval/eval_messages.h>
 #include <hooks/callout_handle.h>
 #include <hooks/hooks_log.h>
 #include <hooks/hooks_manager.h>
@@ -2242,13 +2244,14 @@ Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
 }
 
 void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
+    string classes = "";
+
+    // Built-in vendor class processing
     boost::shared_ptr<OptionString> vendor_class =
         boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
 
-    string classes = "";
-
     if (!vendor_class) {
-        return;
+        goto vendor_class_done;
     }
 
     // DOCSIS specific section
@@ -2275,8 +2278,49 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
         pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER);
         classes += string(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER) + " ";
     } else {
-        classes += VENDOR_CLASS_PREFIX + vendor_class->getValue();
         pkt->addClass(VENDOR_CLASS_PREFIX + vendor_class->getValue());
+        classes += VENDOR_CLASS_PREFIX + vendor_class->getValue();
+    }
+
+vendor_class_done:
+
+    // Run match expressions
+    // Note getClientClassDictionary() cannot be null
+    const ClientClassDefMapPtr& defs_ptr = CfgMgr::instance().getCurrentCfg()->
+        getClientClassDictionary()->getClasses();
+    for (ClientClassDefMap::const_iterator it = defs_ptr->begin();
+         it != defs_ptr->end(); ++it) {
+        // Note second cannot be null
+        const ExpressionPtr& expr_ptr = it->second->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 = evaluate(*expr_ptr, *pkt);
+            if (status) {
+                LOG_INFO(options4_logger, EVAL_RESULT)
+                    .arg(it->first)
+                    .arg(status);
+                // Matching: add the class
+                pkt->addClass(it->first);
+                classes += it->first + " ";
+            } else {
+                LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
+                    .arg(it->first)
+                    .arg(status);
+            }
+        } catch (const Exception& ex) {
+            LOG_ERROR(options4_logger, EVAL_RESULT)
+                .arg(it->first)
+                .arg(ex.what());
+        } catch (...) {
+            LOG_ERROR(options4_logger, EVAL_RESULT)
+                .arg(it->first)
+                .arg("get exception?");
+        }
     }
 
     if (!classes.empty()) {
@@ -2298,6 +2342,7 @@ Dhcpv4Srv::classSpecificProcessing(const Dhcpv4Exchange& ex) {
         return (true);
     }
 
+    // DOCSIS3 class modem specific processing
     if (query->inClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM)) {
 
         // Set next-server. This is TFTP server address. Cable modems will
@@ -2319,12 +2364,39 @@ Dhcpv4Srv::classSpecificProcessing(const Dhcpv4Exchange& ex) {
         }
     }
 
+    // DOCSIS3 class erouter specific processing
     if (query->inClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER)) {
 
         // Do not set TFTP server address for eRouter devices.
         rsp->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
     }
 
+    // Process each class
+    const ClientClassDefMapPtr& defs_ptr = CfgMgr::instance().getCurrentCfg()->
+        getClientClassDictionary()->getClasses();
+    for (ClientClassDefMap::const_iterator it = defs_ptr->begin();
+         it != defs_ptr->end(); ++it) {
+        // Is the query in this class?
+        if (!it->second || !query->inClass(it->first)) {
+            continue;
+        }
+        // Get the configured options of this class
+        const OptionContainerPtr& options =
+            it->second->getCfgOption()->getAll("dhcp4");
+        if (!options || options->empty()) {
+            continue;
+        }
+        // Go through each OptionDescriptor
+        for (OptionContainer::const_iterator desc = options->begin();
+             desc != options->end(); ++desc) {
+            OptionPtr opt = desc->option_;
+            // Add the option if it doesn't exist yet
+            if (!rsp->getOption(opt->getType())) {
+                rsp->addOption(opt);
+            }
+        }
+    }
+
     return (true);
 }
 

+ 5 - 4
src/bin/dhcp4/dhcp4_srv.h

@@ -706,10 +706,11 @@ protected:
 
     /// @brief Assigns incoming packet to zero or more classes.
     ///
-    /// @note For now, the client classification is very simple. It just uses
-    /// content of the vendor-class-identifier option as a class. The resulting
-    /// class will be stored in packet (see @ref isc::dhcp::Pkt4::classes_ and
-    /// @ref isc::dhcp::Pkt4::inClass).
+    /// @note It is done in two phases: first the content of the
+    /// vendor-class-identifier option is used as a class. Second
+    /// classification match expressions are evaluated. The resulting
+    /// class will be stored in packet (see @ref isc::dhcp::Pkt4::classes_
+    /// and @ref isc::dhcp::Pkt4::inClass).
     ///
     /// @param pkt packet to be classified
     void classifyPacket(const Pkt4Ptr& pkt);

+ 1 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -109,6 +109,7 @@ dhcp4_unittests_LDADD = $(top_builddir)/src/bin/dhcp4/libdhcp4.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la

+ 72 - 5
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -410,7 +410,7 @@ TEST_F(Dhcpv4SrvTest, initResponse) {
     OptionPtr resp_sbnsel = response->getOption(DHO_SUBNET_SELECTION);
     ASSERT_TRUE(resp_sbnsel);
     OptionCustomPtr resp_custom =
-	boost::dynamic_pointer_cast<OptionCustom>(resp_sbnsel);
+        boost::dynamic_pointer_cast<OptionCustom>(resp_sbnsel);
     ASSERT_TRUE(resp_custom);
     IOAddress subnet_addr("0.0.0.0");
     ASSERT_NO_THROW(subnet_addr = resp_custom->readAddress());
@@ -1646,8 +1646,8 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
     ASSERT_EQ(0, rcode_);
 }
 
-// Checks if client packets are classified properly
-TEST_F(Dhcpv4SrvTest, clientClassification) {
+// Checks if DOCSIS client packets are classified properly
+TEST_F(Dhcpv4SrvTest, docsisClientClassification) {
 
     NakedDhcpv4Srv srv(0);
 
@@ -1674,10 +1674,77 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
     EXPECT_FALSE(dis2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
 }
 
+// Checks if client packets are classified properly using match expressions.
+TEST_F(Dhcpv4SrvTest, matchClassification) {
+    NakedDhcpv4Srv srv(0);
+
+    // The router class matches incoming packets with foo in a host-name
+    // option (code 12) and sets an ip-forwarding option in the response
+    string config = "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ] }, "
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000, "
+        "\"subnet4\": [ "
+        "{   \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ], "
+        "\"client-classes\": [ "
+        "{   \"name\": \"router\","
+        "    \"option-data\": ["
+        "        {    \"name\": \"ip-forwarding\","
+        "             \"data\": \"true\" } ],"
+        "    \"test\": \"option[12] == 'foo'\" } ] }";
+
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    CfgMgr::instance().commit();
+
+    // Create packets with enough to select the subnet
+    Pkt4Ptr query1(new Pkt4(DHCPDISCOVER, 1234));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    Pkt4Ptr query2(new Pkt4(DHCPDISCOVER, 1234));
+    query2->setRemoteAddr(IOAddress("192.0.2.1"));
+
+    // Create and add a host-name option to the first query
+    OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
+    ASSERT_TRUE(hostname);
+    query1->addOption(hostname);
+
+    // Classify packets
+    srv.classifyPacket(query1);
+    srv.classifyPacket(query2);
+
+    // The first packet (and only the first) should be in the router class
+    EXPECT_TRUE(query1->inClass("router"));
+    EXPECT_FALSE(query2->inClass("router"));
+
+    Dhcpv4Exchange ex1 = createExchange(query1);
+    Pkt4Ptr response1 = ex1.getResponse();
+    Dhcpv4Exchange ex2 = createExchange(query2);
+    Pkt4Ptr response2 = ex2.getResponse();
+
+    // Classification processing should add an ip-forwarding option
+    srv.classSpecificProcessing(ex1);
+    OptionPtr opt1 = response1->getOption(DHO_IP_FORWARDING);
+    EXPECT_TRUE(opt1);
+
+    // But only for the first exchange
+    srv.classSpecificProcessing(ex2);
+    OptionPtr opt2 = response2->getOption(DHO_IP_FORWARDING);
+    EXPECT_FALSE(opt2);
+}
+
 // Checks if the client-class field is indeed used for subnet selection.
 // Note that packet classification is already checked in Dhcpv4SrvTest
-// .clientClassification above.
-TEST_F(Dhcpv4SrvTest, clientClassify2) {
+// .*clientClassification above.
+TEST_F(Dhcpv4SrvTest, clientClassify) {
 
     // This test configures 2 subnets. We actually only need the
     // first one, but since there's still this ugly hack that picks

+ 1 - 0
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -204,6 +204,7 @@ public:
     using Dhcpv4Srv::srvidToString;
     using Dhcpv4Srv::unpackOptions;
     using Dhcpv4Srv::classifyPacket;
+    using Dhcpv4Srv::classSpecificProcessing;
     using Dhcpv4Srv::accept;
     using Dhcpv4Srv::acceptMessageType;
     using Dhcpv4Srv::selectSubnet;