]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1518] extended unittests for v6
authorRazvan Becheriu <razvan@isc.org>
Fri, 13 Jan 2023 14:11:30 +0000 (16:11 +0200)
committerRazvan Becheriu <razvan@isc.org>
Fri, 10 Mar 2023 15:38:55 +0000 (17:38 +0200)
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/tests/config_parser_unittest.cc
src/bin/dhcp6/tests/dhcp6_client.cc
src/bin/dhcp6/tests/host_unittest.cc
src/bin/dhcp6/tests/rebind_unittest.cc
src/bin/dhcp6/tests/renew_unittest.cc
src/bin/dhcp6/tests/vendor_opts_unittest.cc
src/lib/dhcpsrv/dhcp4o6_ipc.cc
src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc

index 20189f09cf547e5044b0ea67b0aa1e279737df73..b978c979ce437aefa8f3d31a7bd007a8ebfcf3bf 100644 (file)
@@ -1424,15 +1424,13 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
                 co_list.push_back(pool->getCfgOption());
             }
         }
-    };
 
-    if (ctx.subnet_) {
-        // Next, subnet configured options.
+        // Thirdly, subnet configured options.
         if (!ctx.subnet_->getCfgOption()->empty()) {
             co_list.push_back(ctx.subnet_->getCfgOption());
         }
 
-        // Then, shared network specific options.
+        // Fourthly, shared network specific options.
         SharedNetwork6Ptr network;
         ctx.subnet_->getSharedNetwork(network);
         if (network && !network->getCfgOption()->empty()) {
@@ -1475,7 +1473,6 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
 void
 Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
                                   const CfgOptionList& co_list) {
-
     // Unlikely short cut
     if (co_list.empty()) {
         return;
@@ -1485,23 +1482,19 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
 
     // Client requests some options using ORO option. Try to
     // get this option from client's message.
-    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
-        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
-        (question->getOption(D6O_ORO));
+    OptionUint16ArrayPtr option_oro = boost::dynamic_pointer_cast<
+        OptionUint16Array>(question->getOption(D6O_ORO));
 
     // Get the list of options that client requested.
     if (option_oro) {
-        set<uint16_t> oro_req_opts;
         for (uint16_t code : option_oro->getValues()) {
-            static_cast<void>(oro_req_opts.insert(code));
+            static_cast<void>(requested_opts.insert(code));
         }
-        requested_opts = oro_req_opts;
     }
 
     // Iterate on the configured option list to add persistent options
-    for (CfgOptionList::const_iterator copts = co_list.begin();
-         copts != co_list.end(); ++copts) {
-        const OptionContainerPtr& opts = (*copts)->getAll(DHCP6_OPTION_SPACE);
+    for (auto const& copts : co_list) {
+        const OptionContainerPtr& opts = copts->getAll(DHCP6_OPTION_SPACE);
         if (!opts) {
             continue;
         }
@@ -1512,21 +1505,23 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
              desc != range.second; ++desc) {
             // Add the persistent option code to requested options
             if (desc->option_) {
-                uint16_t code = desc->option_->getType();
-                static_cast<void>(requested_opts.insert(code));
+                static_cast<void>(requested_opts.insert(desc->option_->getType()));
             }
         }
     }
 
     // For each requested option code get the first instance of the option
     // to be returned to the client.
-    for (uint16_t opt : requested_opts) {
+    for (auto const& opt : requested_opts) {
         // Add nothing when it is already there.
+        // Skip special cases: D6O_VENDOR_CLASS and D6O_VENDOR_OPTS
+        if (opt == D6O_VENDOR_CLASS || opt == D6O_VENDOR_OPTS) {
+            continue;
+        }
         if (!answer->getOption(opt)) {
             // Iterate on the configured option list
-            for (CfgOptionList::const_iterator copts = co_list.begin();
-                 copts != co_list.end(); ++copts) {
-                OptionDescriptor desc = (*copts)->get(DHCP6_OPTION_SPACE, opt);
+            for (auto const& copts : co_list) {
+                OptionDescriptor desc = copts->get(DHCP6_OPTION_SPACE, opt);
                 // Got it: add it and jump to the outer loop
                 if (desc.option_) {
                     answer->addOption(desc.option_);
@@ -1545,15 +1540,13 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
             OptionVendorClassPtr vendor_class;
             vendor_class = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
             if (vendor_class) {
-                uint32_t vendor_id = vendor_class->getVendorId();
-                static_cast<void>(vendor_ids.insert(vendor_id));
+                static_cast<void>(vendor_ids.insert(vendor_class->getVendorId()));
             }
         }
         // Iterate on the configured option list
-        for (CfgOptionList::const_iterator copts = co_list.begin();
-             copts != co_list.end(); ++copts) {
-            for (OptionDescriptor desc : (*copts)->getList(DHCP6_OPTION_SPACE,
-                                                           D6O_VENDOR_CLASS)) {
+        for (auto const& copts : co_list) {
+            for (OptionDescriptor desc : copts->getList(DHCP6_OPTION_SPACE,
+                                                        D6O_VENDOR_CLASS)) {
                 if (!desc.option_) {
                     continue;
                 }
@@ -1581,15 +1574,13 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
             OptionVendorPtr vendor_opts;
             vendor_opts = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
             if (vendor_opts) {
-                uint32_t vendor_id = vendor_opts->getVendorId();
-                static_cast<void>(vendor_ids.insert(vendor_id));
+                static_cast<void>(vendor_ids.insert(vendor_opts->getVendorId()));
             }
         }
         // Iterate on the configured option list
-        for (CfgOptionList::const_iterator copts = co_list.begin();
-             copts != co_list.end(); ++copts) {
-            for (OptionDescriptor desc : (*copts)->getList(DHCP6_OPTION_SPACE,
-                                                           D6O_VENDOR_OPTS)) {
+        for (auto const& copts : co_list) {
+            for (OptionDescriptor desc : copts->getList(DHCP6_OPTION_SPACE,
+                                                        D6O_VENDOR_OPTS)) {
                 if (!desc.option_) {
                     continue;
                 }
@@ -1662,8 +1653,7 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
         OptionVendorClassPtr vendor_class;
         vendor_class = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
         if (vendor_class) {
-            uint32_t vendor_id = vendor_class->getVendorId();
-            static_cast<void>(vendor_ids.insert(vendor_id));
+            static_cast<void>(vendor_ids.insert(vendor_class->getVendorId()));
         }
     }
 
@@ -1700,9 +1690,8 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
 
     // Iterate on the configured option list to add persistent options
     for (uint32_t vendor_id : vendor_ids) {
-        for (CfgOptionList::const_iterator copts = co_list.begin();
-             copts != co_list.end(); ++copts) {
-            const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
+        for (auto const& copts : co_list) {
+            const OptionContainerPtr& opts = copts->getAll(vendor_id);
             if (!opts) {
                 continue;
             }
@@ -1715,12 +1704,15 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
                     continue;
                 }
                 // Add the persistent option code to requested options
-                uint16_t code = desc->option_->getType();
-                static_cast<void>(requested_opts[vendor_id].insert(code));
+                static_cast<void>(requested_opts[vendor_id].insert(desc->option_->getType()));
             }
         }
 
-        // If there is nothing to add don't do anything then with this vendor.
+        // If there is nothing to add don't do anything with this vendor.
+        // This will explicitly not echo back vendor options from the request
+        // that either correspond to a vendor not known to Kea even if the
+        // option encapsulates data or there are no persistent options
+        // configured for this vendor so Kea does not send any option back.
         if (requested_opts[vendor_id].empty()) {
             continue;
         }
@@ -1739,9 +1731,8 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
 
         for (uint16_t opt : requested_opts[vendor_id]) {
             if (!vendor_rsp->getOption(opt)) {
-                for (CfgOptionList::const_iterator copts = co_list.begin();
-                     copts != co_list.end(); ++copts) {
-                    OptionDescriptor desc = (*copts)->get(vendor_id, opt);
+                for (auto const& copts : co_list) {
+                    OptionDescriptor desc = copts->get(vendor_id, opt);
                     if (desc.option_) {
                         vendor_rsp->addOption(desc.option_);
                         added = true;
index 71b92a5f4e60177a952097d25b34449a2179f961..a48589547dbf418d8860522428affe11f7fe9bab 100644 (file)
@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <cc/command_interpreter.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/iface_mgr.h>
@@ -4150,7 +4151,7 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
     // Options should be now available
     // Try to get the option from the vendor space 4491
     OptionDescriptor desc1 =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(4491, 100);
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100);
     ASSERT_TRUE(desc1.option_);
     EXPECT_EQ(100, desc1.option_->getType());
     // Try to get the option from the vendor space 1234
@@ -4209,7 +4210,7 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
     // Options should be now available.
     // Try to get the option from the vendor space 4491
     OptionDescriptor desc1 =
-        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(4491, 100);
+        CfgMgr::instance().getStagingCfg()->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100);
     ASSERT_TRUE(desc1.option_);
     EXPECT_EQ(100, desc1.option_->getType());
 
index 487cad8f861fecbcacc53f513aa5f9ec67c8b360..fce882aae0eff71cc7cf7d68d2e1fbd6fa0843ab 100644 (file)
@@ -407,7 +407,7 @@ Dhcp6Client::createMsg(const uint8_t msg_type) {
         OptionUint16ArrayPtr vendor_oro(new OptionUint16Array(Option::V6,
                                                               DOCSIS3_V6_ORO));
         vendor_oro->setValues(docsis_oro_);
-        OptionPtr vendor(new OptionVendor(Option::V6, 4491));
+        OptionVendorPtr vendor(new OptionVendor(Option::V6, VENDOR_ID_CABLE_LABS));
         vendor->addOption(vendor_oro);
         msg->addOption(vendor);
     }
index 4f9415b423a0965fb9fd07daf32bf304d1745f50..1424826cb367b392fd3a555656d73c09f99533e3 100644 (file)
@@ -1380,8 +1380,7 @@ HostTest::testOverrideVendorOptions(const uint16_t msg_type) {
     // Client needs to include Vendor Specific Information option
     // with ORO suboption, which the server will use to determine
     // which suboptions should be returned to the client.
-    OptionVendorPtr opt_vendor(new OptionVendor(Option::V6,
-                                                VENDOR_ID_CABLE_LABS));
+    OptionVendorPtr opt_vendor(new OptionVendor(Option::V6, VENDOR_ID_CABLE_LABS));
     // Include ORO with TFTP servers suboption code being requested.
     opt_vendor->addOption(OptionPtr(new OptionUint16(Option::V6, DOCSIS3_V6_ORO,
                                                      DOCSIS3_V6_TFTP_SERVERS)));
index 164098ee04a8a4a0db3450eebb91756326bd6d38..a58b3b9883c9fe20be5e835010df7c2babc69f34 100644 (file)
@@ -1068,8 +1068,7 @@ TEST_F(RebindTest, docsisORO) {
     opt = client.config_.findOption(D6O_VENDOR_OPTS);
     ASSERT_TRUE(opt);
     // The vendor option must be a OptionVendor object.
-    boost::shared_ptr<OptionVendor> vendor =
-        boost::dynamic_pointer_cast<OptionVendor>(opt);
+    OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
     ASSERT_TRUE(vendor);
     // The vendor-id should be DOCSIS.
     EXPECT_EQ(VENDOR_ID_CABLE_LABS, vendor->getVendorId());
index 29f9c39e05e87125acb2c8a83f699df86cd2ee86..ff7a5833bf15c1fbf7477bf87515d072fac3568f 100644 (file)
@@ -649,8 +649,7 @@ TEST_F(RenewTest, docsisORO) {
     ASSERT_TRUE(opt);
 
     // The vendor option must be a OptionVendor object.
-    boost::shared_ptr<OptionVendor> vendor =
-        boost::dynamic_pointer_cast<OptionVendor>(opt);
+    OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
     ASSERT_TRUE(vendor);
 
     // The vendor-id should be DOCSIS.
index 0e241f887e7b145e08cd62237c336eb256519b88..e6b7cce283f540a7d31d9199a5809e94dcfe2898 100644 (file)
@@ -7,12 +7,12 @@
 // This file is dedicated to testing vendor options in DHCPv6. There
 // are several related options:
 //
-// client-class (15) - this specifies (as a plain string) what kind of
-//                     device this is
-// vendor-class (16) - contains an enterprise-id followed by zero or
-//                     more of vendor-class data.
-// vendor-option (17) - contains an enterprise-id followed by zero or
-//                     more vendor suboptions
+// client-class (15) - this specifies (as a plain string) what kind of device
+//                     this is.
+// vendor-class (16) - contains an enterprise-id followed by zero or more of
+//                     vendor-class data.
+// vendor-option (17) - contains an enterprise-id followed by zero or more
+//                      vendor suboptions.
 
 #include <config.h>
 
@@ -57,48 +57,113 @@ public:
         IfaceMgr::instance().closeSockets();
     }
 
-    void testVendorOptionsORO(int vendor_id) {
-        // Create a config with a custom option for Cable Labs.
+    /// @brief Checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+    /// vendor options is parsed correctly and the requested options are
+    /// actually assigned. Also covers negative tests that options are not
+    /// provided when a different vendor ID is given.
+    ///
+    /// @note  Kea only knows how to process VENDOR_ID_CABLE_LABS DOCSIS3_V4_ORO
+    /// (suboption 1).
+    ///
+    /// @param configured_vendor_ids The vendor IDs that are configured in the
+    /// server: 4491 or both 4491 and 3561.
+    /// @param requested_vendor_ids Then vendor IDs that are present in ORO.
+    /// @param requested_options The requested options in ORO.
+    void testVendorOptionsORO(std::vector<uint32_t> configured_vendor_ids,
+                              std::vector<uint32_t> requested_vendor_ids,
+                              std::vector<uint32_t> requested_options) {
+        std::vector<uint32_t> result_vendor_ids;
+        ASSERT_TRUE(configured_vendor_ids.size());
+        ASSERT_EQ(configured_vendor_ids[0], VENDOR_ID_CABLE_LABS);
+        for (const auto& req : requested_vendor_ids) {
+            if (req == VENDOR_ID_CABLE_LABS) {
+                result_vendor_ids.push_back(req);
+            }
+        }
+        // Create a config with custom options.
         string config = R"(
             {
-            "interfaces-config": {
-                "interfaces": [ "*" ]
-            },
-            "option-data": [
-                {
-                    "data": "normal_erouter_v6.cm",
-                    "name": "config-file",
-                    "space": "vendor-4491"
-                }
-            ],
-            "option-def": [
-                {
-                    "code": 33,
-                    "name": "config-file",
-                    "space": "vendor-4491",
-                    "type": "string"
-                }
-            ],
-            "preferred-lifetime": 3000,
-            "rebind-timer": 2000,
-            "renew-timer": 1000,
-            "subnet6": [
-                {
-                    "interface": "eth0",
-                    "interface-id": "",
-                    "pools": [
-                        {
-                            "pool": "2001:db8:1::/64"
-                        }
-                    ],
-                    "preferred-lifetime": 3000,
-                    "rebind-timer": 1000,
-                    "renew-timer": 1000,
-                    "subnet": "2001:db8:1::/48",
-                    "valid-lifetime": 4000
-                }
-            ],
-            "valid-lifetime": 4000
+                "interfaces-config": {
+                    "interfaces": [ "*" ]
+                },
+                "option-data": [
+                    {
+                        "code": 33,
+                        "data": "normal_erouter_v6.cm",
+                        "name": "config-file",
+                        "space": "vendor-4491"
+                    },
+                    {
+                        "code": 12,
+                        "data": "first",
+                        "name": "payload",
+                        "space": "vendor-4491"
+        )";
+        if (configured_vendor_ids.size() > 1) {
+            config += R"(
+                    },
+                    {
+                        "code": 33,
+                        "data": "special_erouter_v6.cm",
+                        "name": "custom",
+                        "space": "vendor-3561",
+                    },
+                    {
+                        "code": 12,
+                        "data": "last",
+                        "name": "special",
+                        "space": "vendor-3561"
+            )";
+        }
+        config += R"(
+                    }
+                ],
+                "option-def": [
+                    {
+                        "code": 12,
+                        "name": "payload",
+                        "space": "vendor-4491",
+                        "type": "string"
+        )";
+        if (configured_vendor_ids.size() > 1) {
+            config += R"(
+                    },
+                    {
+                        "code": 33,
+                        "name": "custom",
+                        "space": "vendor-3561",
+                        "type": "string"
+                    },
+                    {
+                        "code": 12,
+                        "name": "special",
+                        "space": "vendor-3561",
+                        "type": "string"
+            )";
+        }
+        config += R"(
+                    }
+                ],
+                "preferred-lifetime": 3000,
+                "rebind-timer": 2000,
+                "renew-timer": 1000,
+                "subnet6": [
+                    {
+                        "interface": "eth0",
+                        "interface-id": "",
+                        "pools": [
+                            {
+                                "pool": "2001:db8:1::/64"
+                            }
+                        ],
+                        "preferred-lifetime": 3000,
+                        "rebind-timer": 1000,
+                        "renew-timer": 1000,
+                        "subnet": "2001:db8:1::/48",
+                        "valid-lifetime": 4000
+                    }
+                ],
+                "valid-lifetime": 4000
             }
         )";
 
@@ -132,11 +197,16 @@ public:
         // Let's add a vendor-option (vendor-id=4491) with a single sub-option.
         // That suboption has code 1 and is a docsis ORO option.
         boost::shared_ptr<OptionUint16Array> vendor_oro(new OptionUint16Array(Option::V6,
-                                                                            DOCSIS3_V6_ORO));
-        vendor_oro->addValue(DOCSIS3_V6_CONFIG_FILE); // Request option 33
-        OptionPtr vendor(new OptionVendor(Option::V6, vendor_id));
-        vendor->addOption(vendor_oro);
-        sol->addOption(vendor);
+                                                                              DOCSIS3_V6_ORO));
+        for (auto const& option : requested_options) {
+            vendor_oro->addValue(option);
+        }
+
+        for (auto const& vendor_id : requested_vendor_ids) {
+            OptionVendorPtr vendor(new OptionVendor(Option::V6, vendor_id));
+            vendor->addOption(vendor_oro);
+            sol->addOption(vendor);
+        }
 
         // Need to process SOLICIT again after requesting new option.
         AllocEngine::ClientContext6 ctx2;
@@ -147,27 +217,314 @@ public:
         adv = srv_.processSolicit(ctx2);
         ASSERT_TRUE(adv);
 
-        // Check if there is (or not) a vendor option in the response.
-        OptionPtr tmp = adv->getOption(D6O_VENDOR_OPTS);
-        if (vendor_id != VENDOR_ID_CABLE_LABS) {
-            EXPECT_FALSE(tmp);
+        // Check if there is a vendor option in the response, if the Cable Labs
+        // vendor ID was provided in the request. Otherwise, check that there is
+        // no vendor and stop processing since the following checks are built on
+        // top of the now-absent options.
+        OptionCollection tmp = adv->getOptions(D6O_VENDOR_OPTS);
+        ASSERT_EQ(tmp.size(), result_vendor_ids.size());
+        if (!result_vendor_ids.size()) {
             return;
         }
-        ASSERT_TRUE(tmp);
 
-        // The response should be an OptionVendor.
-        boost::shared_ptr<OptionVendor> vendor_resp =
-            boost::dynamic_pointer_cast<OptionVendor>(tmp);
-        ASSERT_TRUE(vendor_resp);
+        for (const auto& opt : tmp) {
+            // The response should be an OptionVendor.
+            OptionVendorPtr vendor_resp;
+
+            for (auto const& vendor_id : result_vendor_ids) {
+                vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+                ASSERT_TRUE(vendor_resp);
+                if (vendor_resp->getVendorId() == vendor_id) {
+                    break;
+                }
+                vendor_resp.reset();
+            }
+            ASSERT_TRUE(vendor_resp);
+            if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+                for (auto const& option : requested_options) {
+                    if (option == DOCSIS3_V6_CONFIG_FILE) {
+                        // Option 33 should be present.
+                        OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
+                        ASSERT_TRUE(docsis33);
+
+                        // Check that the provided content match the one in configuration.
+                        OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+                        ASSERT_TRUE(config_file);
+                        EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+                    }
+
+                    if (option == 12) {
+                        // Option 12 should be present.
+                        OptionPtr custom = vendor_resp->getOption(12);
+                        ASSERT_TRUE(custom);
+
+                        // It should be an OptionString.
+                        OptionStringPtr tag = boost::dynamic_pointer_cast<OptionString>(custom);
+                        ASSERT_TRUE(tag);
+
+                        // Check that the provided value match the ones in configuration.
+                        EXPECT_EQ(tag->getValue(), "first");
+                    }
+                }
+            } else {
+                // If explicitly sending OptionVendor and the vendor is not
+                // requested, options should not be present. Kea only knows how
+                // to process VENDOR_ID_CABLE_LABS DOCSIS3_V6_ORO (suboption 1).
+                // Option 33 should not be present.
+                OptionPtr docsis33 = vendor_resp->getOption(33);
+                ASSERT_FALSE(docsis33);
+
+                // Option 12 should not be present.
+                OptionPtr custom = vendor_resp->getOption(12);
+                ASSERT_FALSE(custom);
+            }
+        }
+    }
+
+    /// @brief Checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+    /// vendor options is parsed correctly and the configured options are
+    /// actually assigned.
+    ///
+    /// @param configured_vendor_ids The vendor IDs that are configured in the
+    /// server: 4491 or both 4491 and 3561.
+    /// @param requested_vendor_ids Then vendor IDs that are present in ORO.
+    /// @param requested_options The requested options in ORO.
+    /// @param add_vendor_option The flag which indicates if the request should
+    /// contain a OptionVendor option or should the server always send all the
+    /// OptionVendor options and suboptions.
+    void testVendorOptionsPersistent(std::vector<uint32_t> configured_vendor_ids,
+                                     std::vector<uint32_t> requested_vendor_ids,
+                                     std::vector<uint32_t> configured_options,
+                                     bool add_vendor_option) {
+        std::vector<uint32_t> result_vendor_ids;
+        ASSERT_TRUE(configured_vendor_ids.size());
+        ASSERT_EQ(configured_vendor_ids[0], VENDOR_ID_CABLE_LABS);
+        if (add_vendor_option) {
+            for (const auto& req : requested_vendor_ids) {
+                if (std::find(configured_vendor_ids.begin(), configured_vendor_ids.end(), req) != configured_vendor_ids.end()) {
+                    result_vendor_ids.push_back(req);
+                }
+            }
+        } else {
+            result_vendor_ids = configured_vendor_ids;
+        }
+        ASSERT_TRUE(configured_options.size());
+        ASSERT_EQ(configured_options[0], DOCSIS3_V6_CONFIG_FILE);
+        // Create a config with a custom options.
+        string config = R"(
+            {
+                "interfaces-config": {
+                    "interfaces": [ "*" ]
+                },
+                "preferred-lifetime": 3000,
+                "rebind-timer": 2000,
+                "renew-timer": 1000,
+                "valid-lifetime": 4000,
+                "option-data": [
+                    {
+                        "always-send": true,
+                        "code": 33,
+                        "data": "normal_erouter_v6.cm",
+                        "name": "config-file",
+                        "space": "vendor-4491"
+            )";
+        if (configured_options.size() > 1) {
+            config += R"(
+                    },
+                    {
+                        "always-send": true,
+                        "code": 12,
+                        "data": "first",
+                        "name": "payload",
+                        "space": "vendor-4491"
+            )";
+        }
+        if (!add_vendor_option) {
+            config += R"(
+                    },
+                    {
+                        "always-send": true,
+                        "name": "vendor-opts",
+                        "data": "4491",
+                        "space": "dhcp6"
+            )";
+        }
+        if (configured_vendor_ids.size() > 1) {
+            config += R"(
+                    },
+                    {
+                        "always-send": true,
+                        "code": 33,
+                        "data": "special_erouter_v6.cm",
+                        "name": "custom",
+                        "space": "vendor-3561"
+            )";
+            if (configured_options.size() > 1) {
+                config += R"(
+                    },
+                    {
+                        "always-send": true,
+                        "code": 12,
+                        "data": "last",
+                        "name": "special",
+                        "space": "vendor-3561"
+                )";
+            }
+            if (!add_vendor_option) {
+                config += R"(
+                    },
+                    {
+                        "always-send": true,
+                        "name": "vendor-opts",
+                        "data": "3561",
+                        "space": "dhcp6"
+                )";
+            }
+        }
+        config += R"(
+                    }
+                ],
+                "option-def": [
+                    {
+                        "code": 12,
+                        "name": "payload",
+                        "space": "vendor-4491",
+                        "type": "string"
+        )";
+        if (configured_vendor_ids.size() > 1) {
+            config += R"(
+                    },
+                    {
+                        "code": 33,
+                        "name": "custom",
+                        "space": "vendor-3561",
+                        "type": "string"
+                    },
+                    {
+                        "code": 12,
+                        "name": "special",
+                        "space": "vendor-3561",
+                        "type": "string"
+            )";
+        }
+        config += R"(
+                    }
+                ],
+                "subnet6": [
+                    {
+                        "interface": "eth0",
+                        "pools": [
+                            {
+                                "pool": "2001:db8:1::/64"
+                            }
+                        ],
+                        "subnet": "2001:db8:1::/48",
+                        "interface-id": ""
+                    }
+                ]
+            }
+        )";
 
-        // Option 33 should be present.
-        OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
-        ASSERT_TRUE(docsis33);
+        ASSERT_NO_THROW(configure(config));
 
-        // Check that the provided content match the one in configuration.
-        OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
-        ASSERT_TRUE(config_file);
-        EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+        Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+        sol->setRemoteAddr(IOAddress("fe80::abcd"));
+        sol->setIface("eth0");
+        sol->setIndex(ETH0_INDEX);
+        sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+        OptionPtr clientid = generateClientId();
+        sol->addOption(clientid);
+
+        if (add_vendor_option) {
+            for (auto const& vendor_id : requested_vendor_ids) {
+                // Let's add a vendor-option (vendor-id=4491).
+                OptionVendorPtr vendor(new OptionVendor(Option::V6, vendor_id));
+
+                sol->addOption(vendor);
+            }
+        }
+
+        // Pass it to the server and get an advertise
+        AllocEngine::ClientContext6 ctx;
+        bool drop = !srv_.earlyGHRLookup(sol, ctx);
+        ASSERT_FALSE(drop);
+        srv_.initContext(sol, ctx, drop);
+        ASSERT_FALSE(drop);
+        Pkt6Ptr adv = srv_.processSolicit(ctx);
+
+        // check if we get response at all
+        ASSERT_TRUE(adv);
+
+        // Check if there is a vendor option response
+        OptionCollection tmp = adv->getOptions(D6O_VENDOR_OPTS);
+        ASSERT_EQ(tmp.size(), result_vendor_ids.size());
+
+        for (const auto& opt : tmp) {
+            // The response should be an OptionVendor.
+            OptionVendorPtr vendor_resp;
+
+            for (auto const& vendor_id : result_vendor_ids) {
+                vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+                ASSERT_TRUE(vendor_resp);
+                if (vendor_resp->getVendorId() == vendor_id) {
+                    break;
+                }
+            }
+            ASSERT_TRUE(vendor_resp);
+
+            for (auto const& option : configured_options) {
+                if (add_vendor_option &&
+                    std::find(requested_vendor_ids.begin(), requested_vendor_ids.end(),
+                              vendor_resp->getVendorId()) == requested_vendor_ids.end()) {
+                    // If explicitly sending OptionVendor and the vendor is not
+                    // requested, options should not be present.
+                    if (option == DOCSIS3_V6_CONFIG_FILE) {
+                        // Option 33 should not be present.
+                        OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
+                        ASSERT_FALSE(docsis33);
+                    }
+                    if (option == 12) {
+                        // Option 12 should not be present.
+                        OptionPtr custom = vendor_resp->getOption(12);
+                        ASSERT_FALSE(custom);
+                    }
+                } else {
+                    if (option == DOCSIS3_V6_CONFIG_FILE) {
+                        // Option 33 should be present.
+                        OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
+                        ASSERT_TRUE(docsis33);
+
+                        OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+                        ASSERT_TRUE(config_file);
+                        if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+                            EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+                        } else {
+                            EXPECT_EQ("special_erouter_v6.cm", config_file->getValue());
+                        }
+                    }
+
+                    if (option == 12) {
+                        // Option 12 should be present.
+                        OptionPtr custom = vendor_resp->getOption(12);
+                        ASSERT_TRUE(custom);
+
+                        // It should be an OptionString.
+                        // The option is serialized as Option so it needs to be converted to
+                        // OptionString.
+                        auto const& buffer = custom->toBinary();
+                        OptionStringPtr tag(new OptionString(Option::V6, 12, buffer.begin(), buffer.end()));
+                        ASSERT_TRUE(tag);
+
+                        // Check that the provided value match the ones in configuration.
+                        if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+                            EXPECT_EQ(tag->getValue(), "first");
+                        } else {
+                            EXPECT_EQ(tag->getValue(), "last");
+                        }
+                    }
+                }
+            }
+        }
     }
 
     /// @brief Test what options a client can use to request vendor options.
@@ -192,8 +549,7 @@ public:
                                                           vendor_id_);
                 should_yield_response = true;
             } else if (i == D6O_VENDOR_OPTS) {
-                vendor_option = boost::make_shared<OptionVendor>(Option::V6,
-                                                                 vendor_id_);
+                vendor_option.reset(new OptionVendor(Option::V6, vendor_id_));
                 should_yield_response = true;
             } else {
                 continue;
@@ -224,8 +580,7 @@ public:
         EXPECT_EQ(vendor_id_, response_vendor_options->getVendorId());
 
         // Check that it contains requested option with the appropriate content.
-        OptionPtr suboption(
-            response_vendor_options->getOption(option_));
+        OptionPtr suboption(response_vendor_options->getOption(option_));
         ASSERT_TRUE(suboption);
         vector<uint8_t> binary_suboption = suboption->toBinary(false);
         string text(binary_suboption.begin(), binary_suboption.end());
@@ -240,7 +595,7 @@ private:
     int32_t option_ = 32;
 
     /// @brief Configured and requested vendor ID
-    int32_t vendor_id_ = 32768;
+    uint32_t vendor_id_ = 32768;
 
     /// @brief Server configuration
     string config_ = R"(
@@ -317,6 +672,7 @@ TEST_F(VendorOptsTest, docsisVendorOptionsParse) {
 
     boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
     ASSERT_TRUE(vendor);
+    ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
 
     EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_ORO));
     EXPECT_TRUE(vendor->getOption(36));
@@ -346,12 +702,13 @@ TEST_F(VendorOptsTest, docsisVendorORO) {
     Pkt6Ptr sol = PktCaptures::captureDocsisRelayedSolicit();
     ASSERT_NO_THROW(sol->unpack());
 
-    // Check if the packet contains vendor options option
+    // Check if the packet contains vendor specific information option
     OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
     ASSERT_TRUE(opt);
 
     boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
     ASSERT_TRUE(vendor);
+    ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
 
     opt = vendor->getOption(DOCSIS3_V6_ORO);
     ASSERT_TRUE(opt);
@@ -362,121 +719,134 @@ TEST_F(VendorOptsTest, docsisVendorORO) {
 
 // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
 // vendor options is parsed correctly and the requested options are actually assigned.
-TEST_F(VendorOptsTest, vendorOptionsORO) {
-    testVendorOptionsORO(VENDOR_ID_CABLE_LABS);
+TEST_F(VendorOptsTest, vendorOptionsOROOneOption) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE});
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptions) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE, 12});
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionMultipleVendorsMatchOne) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE});
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionsMultipleVendorsMatchOne) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE, 12});
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionMultipleVendorsMatchAll) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V6_CONFIG_FILE});
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionsMultipleVendorsMatchAll) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V6_CONFIG_FILE, 12});
 }
 
 // Same as vendorOptionsORO except a different vendor ID than Cable Labs is
 // provided and vendor options are expected to not be present in the response.
-TEST_F(VendorOptsTest, vendorOptionsORODifferentVendorID) {
-    testVendorOptionsORO(32768);
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionDifferentVendorID) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {32768}, {DOCSIS3_V6_CONFIG_FILE});
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionsDifferentVendorID) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {32768}, {DOCSIS3_V6_CONFIG_FILE, 12});
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionDifferentVendorIDMultipleVendorsMatchNone) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {32768, 16384}, {DOCSIS3_V6_CONFIG_FILE});
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionDifferentVendorIDMultipleVendorsMatchNone) {
+    testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {32768, 16384}, {DOCSIS3_V6_CONFIG_FILE, 12});
 }
 
 // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
-// vendor options is parsed correctly and the persistent options are actually assigned.
-TEST_F(VendorOptsTest, vendorPersistentOptions) {
-    string config = "{ \"interfaces-config\": {"
-        "  \"interfaces\": [ \"*\" ]"
-        "},"
-        "\"preferred-lifetime\": 3000,"
-        "\"rebind-timer\": 2000, "
-        "\"renew-timer\": 1000, "
-        "    \"option-def\": [ {"
-        "        \"name\": \"config-file\","
-        "        \"code\": 33,"
-        "        \"type\": \"string\","
-        "        \"space\": \"vendor-4491\""
-        "     } ],"
-        "    \"option-data\": [ {"
-        "          \"name\": \"config-file\","
-        "          \"space\": \"vendor-4491\","
-        "          \"data\": \"normal_erouter_v6.cm\","
-        "          \"always-send\": true"
-        "        }],"
-        "\"subnet6\": [ { "
-        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
-        "    \"subnet\": \"2001:db8:1::/48\", "
-        "    \"renew-timer\": 1000, "
-        "    \"rebind-timer\": 1000, "
-        "    \"preferred-lifetime\": 3000,"
-        "    \"valid-lifetime\": 4000,"
-        "    \"interface-id\": \"\","
-        "    \"interface\": \"eth0\""
-        " } ],"
-        "\"valid-lifetime\": 4000 }";
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE}, false);
+}
 
-    ASSERT_NO_THROW(configure(config));
-
-    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
-    sol->setRemoteAddr(IOAddress("fe80::abcd"));
-    sol->setIface("eth0");
-    sol->setIndex(ETH0_INDEX);
-    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
-    OptionPtr clientid = generateClientId();
-    sol->addOption(clientid);
-
-    // Let's add a vendor-option (vendor-id=4491).
-    OptionPtr vendor(new OptionVendor(Option::V6, 4491));
-    sol->addOption(vendor);
-
-    // Pass it to the server and get an advertise
-    AllocEngine::ClientContext6 ctx;
-    bool drop = !srv_.earlyGHRLookup(sol, ctx);
-    ASSERT_FALSE(drop);
-    srv_.initContext(sol, ctx, drop);
-    ASSERT_FALSE(drop);
-    Pkt6Ptr adv = srv_.processSolicit(ctx);
-
-    // check if we get response at all
-    ASSERT_TRUE(adv);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE, 12}, false);
+}
 
-    // Check if there is vendor option response
-    OptionPtr tmp = adv->getOption(D6O_VENDOR_OPTS);
-    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);
-
-    OptionPtr docsis33 = vendor_resp->getOption(33);
-    ASSERT_TRUE(docsis33);
-
-    OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
-    ASSERT_TRUE(config_file);
-    EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
-
-    // Let's add a vendor-option (vendor-id=4491) with a single sub-option.
-    // That suboption has code 1 and is a docsis ORO option.
-    sol->delOption(D6O_VENDOR_OPTS);
-    boost::shared_ptr<OptionUint16Array> vendor_oro(new OptionUint16Array(Option::V6,
-                                                                          DOCSIS3_V6_ORO));
-    vendor_oro->addValue(DOCSIS3_V6_CONFIG_FILE); // Request option 33
-    OptionPtr vendor2(new OptionVendor(Option::V6, 4491));
-    vendor2->addOption(vendor_oro);
-    sol->addOption(vendor2);
-
-    // Need to process SOLICIT again after requesting new option.
-    AllocEngine::ClientContext6 ctx2;
-    drop = !srv_.earlyGHRLookup(sol, ctx2);
-    ASSERT_FALSE(drop);
-    srv_.initContext(sol, ctx2, drop);
-    ASSERT_FALSE(drop);
-    adv = srv_.processSolicit(ctx2);
-    ASSERT_TRUE(adv);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchOne) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE}, false);
+}
 
-    // Check if there is vendor option response
-    tmp = adv->getOption(D6O_VENDOR_OPTS);
-    ASSERT_TRUE(tmp);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchOne) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE, 12}, false);
+}
 
-    // The response should be OptionVendor object
-    vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(tmp);
-    ASSERT_TRUE(vendor_resp);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchAll) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V6_CONFIG_FILE}, false);
+}
 
-    // There should be only one suboption despite config-file is both
-    // requested and has the always-send flag.
-    const OptionCollection& opts = vendor_resp->getOptions();
-    ASSERT_EQ(1, opts.size());
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAll) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V6_CONFIG_FILE, 12}, false);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionAddVendorOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE}, true);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionAddVendorOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE, 12}, true);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchOneAddVendorOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE}, true);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchOneAddVendorOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V6_CONFIG_FILE, 12}, true);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchAllAddVendorOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V6_CONFIG_FILE}, true);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAllAddVendorOption) {
+    testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V6_CONFIG_FILE, 12}, true);
 }
 
 // Test checks whether it is possible to use option definitions defined in
@@ -877,7 +1247,7 @@ TEST_F(VendorOptsTest, threeVendors) {
     // Add a DOCSIS vendor-opts with an ORO.
     OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, DOCSIS3_V6_ORO));
     oro->addValue(DOCSIS3_V6_CONFIG_FILE); // Request option 33.
-    OptionPtr vendor(new OptionVendor(Option::V6, 4491));
+    OptionVendorPtr vendor(new OptionVendor(Option::V6, VENDOR_ID_CABLE_LABS));
     vendor->addOption(oro);
     client.addExtraOption(vendor);
 
@@ -903,7 +1273,7 @@ TEST_F(VendorOptsTest, threeVendors) {
             opt_opts1234 = opt_opts;
             continue;
         }
-        if (vendor_id == 4491) {
+        if (vendor_id == VENDOR_ID_CABLE_LABS) {
             ASSERT_FALSE(opt_docsis);
             opt_docsis = opt_opts;
             continue;
index 742b9541c6d2d0881b99121f9bd8f2b1b22270fd..62c54f1de953e0e8618199ee51e659c2993165be 100644 (file)
@@ -238,28 +238,39 @@ void Dhcp4o6IpcBase::send(const Pkt6Ptr& pkt) {
     }
 
     // Check if vendor option exists.
-    OptionVendorPtr option_vendor = boost::dynamic_pointer_cast<
-        OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS));
+    // Vendor option is initially NULL. If we find the instance of the vendor
+    // option with the ISC enterprise id this pointer will point to it.
+    OptionVendorPtr option_vendor;
+
+    // Get all vendor option and look for the one with the ISC enterprise id.
+    OptionCollection vendor_options = pkt->getOptions(D6O_VENDOR_OPTS);
+    for (OptionCollection::const_iterator opt = vendor_options.begin();
+         opt != vendor_options.end(); ++opt) {
+        option_vendor = boost::dynamic_pointer_cast<OptionVendor>(opt->second);
+        if (option_vendor) {
+            if (option_vendor->getVendorId() == ENTERPRISE_ID_ISC) {
+                break;
+            }
+            option_vendor.reset();
+        }
+    }
 
     // If vendor option doesn't exist or its enterprise id is not ISC's
     // enterprise id, let's create it.
-    if (!option_vendor ||
-        (option_vendor->getVendorId() != ENTERPRISE_ID_ISC)) {
+    if (!option_vendor) {
         option_vendor.reset(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
         pkt->addOption(option_vendor);
-
     }
 
     // Push interface name and source address in it
     option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
-                                                       ISC_V6_4O6_INTERFACE,
-                                                       pkt->getIface())));
-    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(
-                                                       ISC_V6_4O6_SRC_ADDRESS,
-                                                       pkt->getRemoteAddr())));
+                                                              ISC_V6_4O6_INTERFACE,
+                                                              pkt->getIface())));
+    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                                                  pkt->getRemoteAddr())));
     option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
-                                                       ISC_V6_4O6_SRC_PORT,
-                                                       pkt->getRemotePort())));
+                                                              ISC_V6_4O6_SRC_PORT,
+                                                              pkt->getRemotePort())));
     // Get packet content
     OutputBuffer& buf = pkt->getBuffer();
     buf.clear();
@@ -273,6 +284,5 @@ void Dhcp4o6IpcBase::send(const Pkt6Ptr& pkt) {
    }
 }
 
-};  // namespace dhcp
-
-};  // namespace isc
+}  // namespace dhcp
+}  // namespace isc
index 4fbb2a4dab0b26b91dcf0e225ab08bf187e0873b..7a0f54ab415f38c267dfbe8e35b954c980c44d14 100644 (file)
@@ -466,13 +466,11 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutVendorOption) {
 TEST_F(Dhcp4o6IpcBaseTest, receiveInvalidEnterpriseId) {
     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
     OptionVendorPtr option_vendor(new OptionVendor(Option::V6, 1));
-    option_vendor->addOption(
-        OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE,
-                                         "eth0")));
-    option_vendor->addOption(
-        Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
-                                             IOAddress("2001:db8:1::1")))
-    );
+    option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+                                                              ISC_V6_4O6_INTERFACE,
+                                                              "eth0")));
+    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                                                  IOAddress("2001:db8:1::1"))));
 
     pkt->addOption(option_vendor);
     testReceiveError(pkt);
@@ -482,16 +480,12 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveInvalidEnterpriseId) {
 // interface option is not present.
 TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutInterfaceOption) {
     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
-    OptionVendorPtr option_vendor(new OptionVendor(Option::V6,
-                                                   ENTERPRISE_ID_ISC));
-    option_vendor->addOption(
-        Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
-                                             IOAddress("2001:db8:1::1")))
-    );
-    option_vendor->addOption(
-        OptionUint16Ptr(new OptionUint16(Option::V6,
-                                         ISC_V6_4O6_SRC_PORT,
-                                         TEST_PORT)));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                                                  IOAddress("2001:db8:1::1"))));
+    option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
+                                                              ISC_V6_4O6_SRC_PORT,
+                                                              TEST_PORT)));
 
     pkt->addOption(option_vendor);
     testReceiveError(pkt);
@@ -502,19 +496,15 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutInterfaceOption) {
 // system.
 TEST_F(Dhcp4o6IpcBaseTest, receiveWithInvalidInterface) {
     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
-    OptionVendorPtr option_vendor(new OptionVendor(Option::V6,
-                                                   ENTERPRISE_ID_ISC));
-    option_vendor->addOption(
-        OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE,
-                                         "ethX")));
-    option_vendor->addOption(
-        Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
-                                             IOAddress("2001:db8:1::1")))
-    );
-    option_vendor->addOption(
-        OptionUint16Ptr(new OptionUint16(Option::V6,
-                                         ISC_V6_4O6_SRC_PORT,
-                                         TEST_PORT)));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+    option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+                                                              ISC_V6_4O6_INTERFACE,
+                                                              "ethX")));
+    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                                                  IOAddress("2001:db8:1::1"))));
+    option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
+                                                              ISC_V6_4O6_SRC_PORT,
+                                                              TEST_PORT)));
 
     pkt->addOption(option_vendor);
     testReceiveError(pkt);
@@ -525,15 +515,13 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithInvalidInterface) {
 // source address option is not present.
 TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourceAddressOption) {
     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
-    OptionVendorPtr option_vendor(new OptionVendor(Option::V6,
-                                                   ENTERPRISE_ID_ISC));
-    option_vendor->addOption(
-        OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE,
-                                         "eth0")));
-    option_vendor->addOption(
-        OptionUint16Ptr(new OptionUint16(Option::V6,
-                                         ISC_V6_4O6_SRC_PORT,
-                                         TEST_PORT)));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+    option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+                                             ISC_V6_4O6_INTERFACE,
+                                             "eth0")));
+    option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
+                                                              ISC_V6_4O6_SRC_PORT,
+                                                              TEST_PORT)));
 
     pkt->addOption(option_vendor);
     testReceiveError(pkt);
@@ -543,15 +531,12 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourceAddressOption) {
 // source port option is not present.
 TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourcePortOption) {
     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
-    OptionVendorPtr option_vendor(new OptionVendor(Option::V6,
-                                                   ENTERPRISE_ID_ISC));
-    option_vendor->addOption(
-        OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE,
-                                         "eth0")));
-    option_vendor->addOption(
-        Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
-                                             IOAddress("2001:db8:1::1")))
-    );
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+    option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+                                                              ISC_V6_4O6_INTERFACE,
+                                                              "eth0")));
+    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                                                  IOAddress("2001:db8:1::1"))));
 
     pkt->addOption(option_vendor);
     testReceiveError(pkt);