]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3583] kea-dhcp4 now supports option class tagging
authorThomas Markwalder <tmark@isc.org>
Thu, 26 Sep 2024 20:05:17 +0000 (16:05 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 15 Oct 2024 17:51:57 +0000 (13:51 -0400)
/src/bin/dhcp4/dhcp4_srv.cc
    Dhcpv4Srv::appendServerID() - modified to use the standard
    option defintion when appending a generated server id.

    Dhcpv4Srv::appendRequestedOptions()
    Dhcpv4Srv::appendRequestedVendorOptions()
    Dhcpv4Srv::appendBasicOptions()
    - modified to OptionDescriptor::allowedForClientClasses()

/src/bin/dhcp4/tests/classify_unittest.cc
    TEST_F(ClassifyTest, requestedOptionClassTagTest)
    TEST_F(ClassifyTest, vendorOptionClassTagTest)
    TEST_F(ClassifyTest, vivcoOptionClassTagTest)
    TEST_F(ClassifyTest, vivsoOptionClassTagTest)
    TEST_F(ClassifyTest, basicOptionClassTagTest)
    - new tests

/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
    TEST_F(Dhcpv4SrvTest, appendServerID) - updated test

/src/bin/dhcp4/tests/dhcp4_test_utils.*
    Dhcpv4SrvTest::checkServerIdentifier() - new function

/src/lib/dhcpsrv/cfg_option.*
    OptionDescriptor::allowedForClientClasses() - new function

/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
    TEST(OptionDescriptorTest, allowedForClientClassesTest) - new test

src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/classify_unittest.cc
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/dhcp4_test_utils.cc
src/bin/dhcp4/tests/dhcp4_test_utils.h
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/cfg_option.h
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

index edba3b68b1affb00eec2e42e4fcd63b266b819c4..99cad8b16fd719d161a05f6c91d9f1fdc5740f0d 100644 (file)
@@ -2000,8 +2000,9 @@ Dhcpv4Srv::appendServerID(Dhcpv4Exchange& ex) {
         local_addr = IfaceMgr::instance().getSocket(query).addr_;
     }
 
-    OptionPtr opt_srvid(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
-                                           local_addr));
+    static const OptionDefinition& server_id_def = LibDHCP::DHO_DHCP_SERVER_IDENTIFIER_DEF();
+    OptionCustomPtr opt_srvid(new OptionCustom(server_id_def, Option::V4));
+    opt_srvid->writeAddress(local_addr);
     ex.getResponse()->addOption(opt_srvid);
 }
 
@@ -2115,6 +2116,7 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
     }
 
     std::set<uint8_t> cancelled_opts;
+    const auto& cclasses = query->getClasses();
 
     // Iterate on the configured option list to add persistent and
     // cancelled options.
@@ -2161,7 +2163,7 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
             for (auto const& copts : co_list) {
                 OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, opt);
                 // Got it: add it and jump to the outer loop
-                if (desc.option_) {
+                if (desc.option_ && desc.allowedForClientClasses(cclasses)) {
                     resp->addOption(desc.option_);
                     break;
                 }
@@ -2189,7 +2191,7 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
         for (auto const& copts : co_list) {
             for (auto const& desc : copts->getList(DHCP4_OPTION_SPACE,
                                                         DHO_VIVCO_SUBOPTIONS)) {
-                if (!desc.option_) {
+                if (!desc.option_ || !desc.allowedForClientClasses(cclasses)) {
                     continue;
                 }
                 OptionVendorClassPtr vendor_opts =
@@ -2227,7 +2229,7 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
         for (auto const& copts : co_list) {
             for (auto const& desc : copts->getList(DHCP4_OPTION_SPACE,
                                                    DHO_VIVSO_SUBOPTIONS)) {
-                if (!desc.option_) {
+                if (!desc.option_ || !desc.allowedForClientClasses(cclasses)) {
                     continue;
                 }
                 OptionVendorPtr vendor_opts =
@@ -2404,7 +2406,7 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
             if (!vendor_rsp->getOption(opt)) {
                 for (auto const& copts : co_list) {
                     OptionDescriptor desc = copts->get(vendor_id, opt);
-                    if (desc.option_) {
+                    if (desc.option_ && desc.allowedForClientClasses(query->getClasses())) {
                         vendor_rsp->addOption(desc.option_);
                         added = true;
                         break;
@@ -2453,7 +2455,8 @@ Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
             // Check whether option has been configured.
             for (auto const& copts : co_list) {
                 OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, required);
-                if (desc.option_) {
+                /// @todo TKM - not sure if otion class-tagging should be allowed here?
+                if (desc.option_ && desc.allowedForClientClasses(ex.getQuery()->getClasses())) {
                     resp->addOption(desc.option_);
                     break;
                 }
index e9a39c27175bd15764d286e97394c544e6e4ba88..4c8d7c5c6a0e4ce8dfb0796a8edee0d03d885805 100644 (file)
@@ -8,8 +8,10 @@
 #include <asiolink/io_address.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/testutils/iface_mgr_test_config.h>
 #include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp/option_int.h>
 #include <stats/stats_mgr.h>
 #include <algorithm>
@@ -1605,4 +1607,538 @@ TEST_F(ClassifyTest, subClassPrecedence) {
     EXPECT_EQ(opt->toString(), "spawn two");
 }
 
+// Verifies that (non-vendor) requested options can be gated
+// by option class tagging.
+TEST_F(ClassifyTest, requestedOptionClassTagTest) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    string config = R"^(
+    {
+        "interfaces-config": {
+            "interfaces": [ "*" ]
+        },
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        "valid-lifetime": 4000,
+        "subnet4": [{
+            "id": 1,
+            "subnet": "192.0.2.0/24",
+            "pools": [{
+                "pool": "192.0.2.1 - 192.0.2.100"
+             }]
+        }],
+        "option-def": [{
+            "name": "no_classes",
+            "code": 249,
+            "type": "string"
+        },
+        {
+            "name": "wrong_class",
+            "code": 250,
+            "type": "string"
+        },
+        {
+            "name": "right_class",
+            "code": 251,
+            "type": "string"
+        }],
+        "option-data": [{
+            "name": "no_classes",
+            "data": "oompa"
+        },
+        {
+            "name": "wrong_class",
+            "data": "loompa",
+            "client-classes": [ "wrong" ]
+        },
+        {
+            "name": "right_class",
+            "data": "doompadee",
+            "client-classes": [ "right" ]
+        }],
+        "client-classes": [{
+            "name": "right",
+            "test": "substring(option[61].hex,0,3) == '111'"
+        }]
+    }
+    )^";
+
+    // Configure DHCP server.
+    configure(config, srv);
+
+    // Create packets with enough to select the subnet
+    auto id = ClientId::fromText("31:31:31");
+    OptionPtr clientid = (OptionPtr(new Option(Option::V4,
+                                               DHO_DHCP_CLIENT_IDENTIFIER,
+                                               id->getClientId())));
+
+    Pkt4Ptr query1(new Pkt4(DHCPDISCOVER, 1234));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+
+    // Create and add a PRL option to the first 2 queries
+    OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+                                                 DHO_DHCP_PARAMETER_REQUEST_LIST));
+    prl->addValue(249);
+    prl->addValue(250);
+    prl->addValue(251);
+    query1->addOption(prl);
+
+    // Classify packets
+    srv.classifyPacket(query1);
+
+    // Verify class membership is as expected.
+    EXPECT_TRUE(query1->inClass("right"));
+
+    // Process query
+    Pkt4Ptr response1 = srv.processDiscover(query1);
+
+    // Option without class tags should be included.
+    OptionPtr opt = response1->getOption(249);
+    ASSERT_TRUE(opt);
+
+    // Option with class tag that doesn't match should not included.
+    opt = response1->getOption(250);
+    ASSERT_FALSE(opt);
+
+    // Option with class tag that matches should be included.
+    opt = response1->getOption(251);
+    ASSERT_TRUE(opt);
+}
+
+// Verifies the requested vendor options can be gated by
+// option class tagging.
+TEST_F(ClassifyTest, vendorOptionClassTagTest) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    string config = R"^(
+    {
+        "interfaces-config": {
+            "interfaces": [ "*" ]
+        },
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        "valid-lifetime": 4000,
+        "subnet4": [{
+            "id": 1,
+            "subnet": "192.0.2.0/24",
+            "pools": [{
+                "pool": "192.0.2.1 - 192.0.2.100"
+             }]
+        }],
+        "option-def": [{
+            "space": "vendor-4491",
+            "name": "one",
+            "code": 101,
+            "type": "string"
+        },
+        {
+            "space": "vendor-4491",
+            "name": "two",
+            "code": 102,
+            "type": "string"
+        },
+        {
+            "space": "vendor-4491",
+            "name": "three",
+            "code": 103,
+            "type": "string"
+        }],
+        "option-data": [{ 
+            "space": "vendor-4491",
+            "code": 101,
+            "csv-format": true,
+            "data": "zippy-ah",
+            "client-classes": [ "melon" ]
+        },
+        {
+            "space": "vendor-4491",
+            "code": 102,
+            "csv-format": true,
+            "data": "dee",
+            "client-classes": [ "ball" ]
+        },
+        {
+            "space": "vendor-4491",
+            "code": 103,
+            "csv-format": true,
+            "data": "doo-dah"
+        }],
+        "client-classes": [{
+            "name": "melon",
+            "test": "substring(option[61].hex,0,3) == '111'"
+        }]
+    }
+    )^";
+
+    // Configure DHCP server.
+    configure(config, srv);
+
+    // Create packets with enough to select the subnet
+    auto id = ClientId::fromText("31:31:31");
+    OptionPtr clientid = (OptionPtr(new Option(Option::V4,
+                                               DHO_DHCP_CLIENT_IDENTIFIER,
+                                               id->getClientId())));
+
+    Pkt4Ptr query1(new Pkt4(DHCPDISCOVER, 1234));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+
+    // Let's add a vendor-option (vendor-id=4491).
+    OptionPtr vendor(new OptionVendor(Option::V4, 4491));
+    query1->addOption(vendor);
+
+    // Let's add a vendor-option (vendor-id=4491) with three sub-options.
+    boost::shared_ptr<OptionUint8Array> vendor_oro(new OptionUint8Array(Option::V4,
+                                                                        DOCSIS3_V4_ORO));
+    // Request the suboptions.
+    vendor_oro->addValue(101); 
+    vendor_oro->addValue(102);
+    vendor_oro->addValue(103);
+    vendor->addOption(vendor_oro);
+
+    // Classify packets
+    srv.classifyPacket(query1);
+
+    // Verify class membership is as expected.
+    EXPECT_TRUE(query1->inClass("melon"));
+    EXPECT_FALSE(query1->inClass("ball"));
+
+    // Process query
+    Pkt4Ptr response1 = srv.processDiscover(query1);
+
+    // Check if there is a vendor option response
+    OptionPtr tmp = response1->getOption(DHO_VIVSO_SUBOPTIONS);
+    ASSERT_TRUE(tmp);
+
+    // The response should be OptionVendor object
+    boost::shared_ptr<OptionVendor> vendor_resp =
+        boost::dynamic_pointer_cast<OptionVendor>(tmp);
+    ASSERT_TRUE(vendor_resp);
+
+    // Should have options 1 and 3.
+    EXPECT_EQ(2, vendor_resp->getOptions().size());
+    EXPECT_TRUE(vendor_resp->getOption(101));
+    EXPECT_FALSE(vendor_resp->getOption(102));
+    EXPECT_TRUE(vendor_resp->getOption(103));
+}
+
+// Verifies that requested VIVCO suboption can be gated by 
+// option class tagging.
+TEST_F(ClassifyTest, vivcoOptionClassTagTest) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    string config = R"^(
+    {
+        "interfaces-config": {
+            "interfaces": [ "*" ]
+        },
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        "valid-lifetime": 4000,
+        "subnet4": [{
+            "id": 1,
+            "subnet": "192.0.2.0/24",
+            "pools": [{
+                "pool": "192.0.2.1 - 192.0.2.100"
+             }]
+        }],
+        "option-data": [{
+            "name": "vivco-suboptions",
+            "data": "1234, 03666f6f",
+            "client-classes": [ "melon" ]
+        }],
+        "client-classes": [{
+            "name": "melon",
+            "test": "substring(option[61].hex,0,3) == '111'"
+        }]
+    }
+    )^";
+
+    // Configure DHCP server.
+    configure(config, srv);
+
+    // Create packet with enough to select the subnet
+    auto id = ClientId::fromText("31:31:31");
+    OptionPtr clientid = (OptionPtr(new Option(Option::V4,
+                                               DHO_DHCP_CLIENT_IDENTIFIER,
+                                               id->getClientId())));
+
+    Pkt4Ptr query1(new Pkt4(DHCPDISCOVER, 1234));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+
+    // Create and add a PRL option.
+    OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+                                                 DHO_DHCP_PARAMETER_REQUEST_LIST));
+    prl->addValue(DHO_VIVCO_SUBOPTIONS);
+    query1->addOption(prl);
+
+    // Classify packet
+    srv.classifyPacket(query1);
+
+    // Verify it is a member of "melon".
+    ASSERT_TRUE(query1->inClass("melon"));
+
+    // Process query
+    Pkt4Ptr response1 = srv.processDiscover(query1);
+
+    // Check if there is a vendor option response
+    OptionPtr tmp = response1->getOption(DHO_VIVCO_SUBOPTIONS);
+    EXPECT_TRUE(tmp);
+
+    // Try again with a different client id.
+    id = ClientId::fromText("31:31:32");
+    clientid = (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+                                                 id->getClientId())));
+    query1.reset(new Pkt4(DHCPDISCOVER, 1235));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+    query1->addOption(prl);
+
+    // Classify packet
+    srv.classifyPacket(query1);
+
+    // Verify it is not a member of "melon".
+    ASSERT_FALSE(query1->inClass("melon"));
+
+    // Process query
+    response1 = srv.processDiscover(query1);
+
+    // VIVCO suboption should not be present.
+    tmp = response1->getOption(DHO_VIVCO_SUBOPTIONS);
+    ASSERT_FALSE(tmp);
+}
+
+// Verifies that requested VIVSO suboption can be gated by 
+// option class tagging.
+TEST_F(ClassifyTest, vivsoOptionClassTagTest) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    string config = R"^(
+    {
+        "interfaces-config": {
+            "interfaces": [ "*" ]
+        },
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        "valid-lifetime": 4000,
+        "subnet4": [{
+            "id": 1,
+            "subnet": "192.0.2.0/24",
+            "pools": [{
+                "pool": "192.0.2.1 - 192.0.2.100"
+             }]
+        }],
+        "option-data": [{
+            "name": "vivso-suboptions",
+            "data": "1234",
+            "client-classes": [ "melon" ]
+        }],
+        "client-classes": [{
+            "name": "melon",
+            "test": "substring(option[61].hex,0,3) == '111'"
+        }]
+    }
+    )^";
+
+    // Configure DHCP server.
+    configure(config, srv);
+
+    // Create packet with enough to select the subnet
+    auto id = ClientId::fromText("31:31:31");
+    OptionPtr clientid = (OptionPtr(new Option(Option::V4,
+                                               DHO_DHCP_CLIENT_IDENTIFIER,
+                                               id->getClientId())));
+
+    Pkt4Ptr query1(new Pkt4(DHCPDISCOVER, 1234));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+
+    // Create and add a PRL option.
+    OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+                                                 DHO_DHCP_PARAMETER_REQUEST_LIST));
+    prl->addValue(DHO_VIVSO_SUBOPTIONS);
+    query1->addOption(prl);
+
+    // Classify packet
+    srv.classifyPacket(query1);
+
+    // Verify it is a member of "melon".
+    ASSERT_TRUE(query1->inClass("melon"));
+
+    // Process query
+    Pkt4Ptr response1 = srv.processDiscover(query1);
+
+    // Check if there is a vendor option response
+    OptionPtr tmp = response1->getOption(DHO_VIVSO_SUBOPTIONS);
+    EXPECT_TRUE(tmp);
+
+    // Try again with a different client id.
+    id = ClientId::fromText("31:31:32");
+    clientid = (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+                                                 id->getClientId())));
+    query1.reset(new Pkt4(DHCPDISCOVER, 1235));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+    query1->addOption(prl);
+
+    // Classify packet
+    srv.classifyPacket(query1);
+
+    // Verify it is not a member of "melon".
+    ASSERT_FALSE(query1->inClass("melon"));
+
+    // Process query
+    response1 = srv.processDiscover(query1);
+
+    // VIVCO suboption should not be present.
+    tmp = response1->getOption(DHO_VIVSO_SUBOPTIONS);
+    ASSERT_FALSE(tmp);
+}
+
+// Verifies that "basic" options can be gated by 
+// option class tagging.
+TEST_F(ClassifyTest, basicOptionClassTagTest) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    string config = R"^(
+    {
+        "interfaces-config": {
+            "interfaces": [ "*" ]
+        },
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        "valid-lifetime": 4000,
+        "subnet4": [{
+            "id": 1,
+            "subnet": "192.0.2.0/24",
+            "pools": [{
+                "pool": "192.0.2.1 - 192.0.2.100"
+             }]
+        }],
+        "option-data": [{
+            "name": "routers",
+            "data": "192.0.2.1",
+            "client-classes": [ "melon" ]
+        },
+        {
+            "name": "domain-name",
+            "data": "example.com",
+            "client-classes": [ "melon" ]
+        },
+        {
+            "name": "domain-name-servers",
+            "data": "192.0.2.3",
+            "client-classes": [ "melon" ]
+        },
+        {
+            "name": "dhcp-server-identifier",
+            "data": "192.0.2.0",
+            "client-classes": [ "melon" ]
+        }],
+        "client-classes": [{
+            "name": "melon",
+            "test": "substring(option[61].hex,0,3) == '111'"
+        }]
+    }
+    )^";
+
+    // Configure DHCP server.
+    configure(config, srv);
+
+    // Create packet with enough to select the subnet
+    auto id = ClientId::fromText("31:31:31");
+    OptionPtr clientid = (OptionPtr(new Option(Option::V4,
+                                               DHO_DHCP_CLIENT_IDENTIFIER,
+                                               id->getClientId())));
+
+    Pkt4Ptr query1(new Pkt4(DHCPDISCOVER, 1234));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+
+    // Configure DHCP server.
+    configure(config, srv);
+
+    // Classify packet
+    srv.classifyPacket(query1);
+
+    // Verify it is a member of "melon".
+    ASSERT_TRUE(query1->inClass("melon"));
+
+    // Process query
+    Pkt4Ptr response1 = srv.processDiscover(query1);
+
+    // Verify that routers, domain-name, and domain-name-servers are present.
+    OptionPtr tmp = response1->getOption(DHO_ROUTERS);
+    EXPECT_TRUE(tmp);
+    tmp = response1->getOption(DHO_DOMAIN_NAME);
+    EXPECT_TRUE(tmp);
+    tmp = response1->getOption(DHO_DOMAIN_NAME_SERVERS);
+    EXPECT_TRUE(tmp);
+
+    // Verify that server id is present and is the configured value. 
+    checkServerIdentifier(response1, "192.0.2.0");
+
+    // Try again with a different client id.
+    id = ClientId::fromText("31:31:32");
+    clientid = (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+                                                 id->getClientId())));
+    query1.reset(new Pkt4(DHCPDISCOVER, 1235));
+    query1->setRemoteAddr(IOAddress("192.0.2.1"));
+    query1->addOption(clientid);
+    query1->setIface("eth1");
+    query1->setIndex(ETH1_INDEX);
+
+    // Classify packet
+    srv.classifyPacket(query1);
+
+    // Verify it is not a member of "melon".
+    ASSERT_FALSE(query1->inClass("melon"));
+
+    // Process query
+    response1 = srv.processDiscover(query1);
+
+    // Verify that routers, domain-name, and domain-name-servers are not present.
+    tmp = response1->getOption(DHO_ROUTERS);
+    EXPECT_FALSE(tmp);
+    tmp = response1->getOption(DHO_DOMAIN_NAME);
+    EXPECT_FALSE(tmp);
+    tmp = response1->getOption(DHO_DOMAIN_NAME_SERVERS);
+    EXPECT_FALSE(tmp);
+
+    // Verify that server id is present and is the generated value. 
+    checkServerIdentifier(response1, "0.0.0.0");
+}
+
+
 } // end of anonymous namespace
index 3eaa2ab12116851280ce9409b8fbcbd74620baa9..79d4da46178203a21e86bcfaa38e88afd69b6204 100644 (file)
@@ -977,16 +977,9 @@ TEST_F(Dhcpv4SrvTest, appendServerID) {
     // Make sure that the option has been added.
     OptionPtr opt = response->getOption(DHO_DHCP_SERVER_IDENTIFIER);
     ASSERT_TRUE(opt);
-    Option4AddrLstPtr opt_server_id =
-        boost::dynamic_pointer_cast<Option4AddrLst>(opt);
-    ASSERT_TRUE(opt_server_id);
-
-    // The option is represented as a list of IPv4 addresses but with
-    // only one address added.
-    Option4AddrLst::AddressContainer addrs = opt_server_id->getAddresses();
-    ASSERT_EQ(1, addrs.size());
-    // This address should match the local address of the packet.
-    EXPECT_EQ("192.0.3.1", addrs[0].toText());
+    OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<OptionCustom>(opt);
+    ASSERT_TRUE(opt_serverid);
+    EXPECT_EQ("192.0.3.1", opt_serverid->readAddress().toText());
 }
 
 // Sanity check. Verifies that both Dhcpv4Srv and its derived
index f3db6b19121f0bfdd1e2fe768884cd4620fe6142..4824c56dbbd76294773ec8d2d076f6122b53f2b4 100644 (file)
@@ -1014,6 +1014,16 @@ Dhcpv4SrvTest::checkPktEvents(const PktPtr& msg,
     }
 }
 
+void
+Dhcpv4SrvTest::checkServerIdentifier(Pkt4Ptr msg, std::string exp_address) {
+    ASSERT_TRUE(msg);
+    OptionPtr opt = msg->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+    ASSERT_TRUE(opt);
+    OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<OptionCustom>(opt);
+    ASSERT_TRUE(opt_serverid);
+    EXPECT_EQ(exp_address, opt_serverid->readAddress().toText());
+}
+
 } // end of isc::dhcp::test namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
index 9988e3631cbf70e51acc2377dea59cdf331c60dd..8fb05776cc3e75f46568caf197cd2587b4b6e71a 100644 (file)
@@ -722,6 +722,12 @@ public:
     /// are expected to occur in the stack.
     void checkPktEvents(const PktPtr& msg, std::list<std::string> expected_events);
 
+    /// @brief Checks the message for an expected value of DHO_DHCP_SERVER_IDENTIFIER
+    ///
+    /// @param msg pointer to the packet under test.
+    /// @param exp_address expected ip address as a string
+    void checkServerIdentifier(Pkt4Ptr msg, std::string exp_address);
+
     /// @brief A subnet used in most tests.
     Subnet4Ptr subnet_;
 
index a25341ec8b4c6b9cbb770ec3229b1fb0d54304ff..83bbedf5287e858b91b3e18ef308e94236a84851 100644 (file)
@@ -59,6 +59,22 @@ OptionDescriptor::addClientClass(const std::string& class_name) {
     }
 }
 
+bool
+OptionDescriptor::allowedForClientClasses(const ClientClasses& cclasses) const {
+    if (client_classes_.empty()) {
+        return (true);
+    }
+    
+    for (const auto& cclass : client_classes_) {
+        if (cclasses.contains(cclass)) {
+            return (true);
+        }
+    }
+
+    return (false);
+}
+
+
 CfgOption::CfgOption()
     : encapsulated_(false) {
 }
index e3e9b52bb9dadbe39a515b19581caac89a27a8a6..50e82a3f20e506179a1828887944ff4bdeb62f9e 100644 (file)
@@ -210,6 +210,14 @@ public:
     ///
     /// @param class_name Class name.
     void addClientClass(const std::string& class_name);
+
+    /// @brief Validates an OptionDescriptor's client-classes against a list
+    /// of classes
+    ///
+    /// @param cclasses list of ClientClasses to validate against
+    /// @return True if descriptor's client-classes is empty or at least
+    /// one of its members is found in the validation list.
+    bool allowedForClientClasses(const ClientClasses& cclasses) const;
 };
 
 /// @brief Multi index container for DHCP option descriptors.
@@ -809,8 +817,11 @@ private:
     VendorOptionSpaceCollection vendor_options_;
 };
 
+class CfgOption; // forward declaration 
+
 /// @name Pointers to the @c CfgOption objects.
 //@{
+
 /// @brief Non-const pointer.
 typedef boost::shared_ptr<CfgOption> CfgOptionPtr;
 
index 09b43c9f296ddf44e297fbe83a32b12bcef3d778..4762bb1195709cfe5254bf4f33549ba30b586eff 100644 (file)
@@ -106,6 +106,37 @@ TEST(OptionDescriptorTest, assign) {
     EXPECT_EQ(context, desc->getContext());
 }
 
+// Exercise OptionDescriptor::allowedForClientClasses function.
+TEST(OptionDescriptorTest, allowedForClientClassesTest) {
+    ClientClasses filter_classes;
+
+    OptionPtr opt(new Option(Option::V6, 112));
+    OptionDescriptor desc(opt, false, false);
+
+    // Option should be allowed when both lists are empty.
+    EXPECT_TRUE(desc.allowedForClientClasses(filter_classes));
+
+    // Add some classes to the filter list.
+    filter_classes.insert("water");
+    filter_classes.insert("dog");
+
+    // Option should be allowed when option's client-classes is empty.
+    EXPECT_TRUE(desc.allowedForClientClasses(filter_classes));
+
+    //  Add classes to the option.
+    desc.addClientClass("avacado");
+    desc.addClientClass("cat");
+
+    // No intersection, option should not be allowed.
+    EXPECT_FALSE(desc.allowedForClientClasses(filter_classes));
+
+    // Add a matching class to the filter list.
+    filter_classes.insert("cat");
+
+    // Option should be allowed.
+    EXPECT_TRUE(desc.allowedForClientClasses(filter_classes));
+}
+
 /// This class fixture for testing @c CfgOption class, holding option
 /// configuration.
 class CfgOptionTest : public ::testing::Test {