]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2517] Revamped option sending
authorFrancis Dupont <fdupont@isc.org>
Sun, 31 Jul 2022 18:05:41 +0000 (20:05 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 3 Aug 2022 13:26:10 +0000 (15:26 +0200)
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/lib/dhcp/pkt6.cc
src/lib/dhcp/pkt6.h
src/lib/dhcpsrv/cfg_option.cc

index 1ad99099ea660e0a6172c3816405a5c5b5b000ad..118ba2128ebc19602ba03ddaafa4be13e29e2616 100644 (file)
@@ -76,6 +76,7 @@
 #include <iomanip>
 #include <fstream>
 #include <sstream>
+#include <map>
 #include <set>
 
 using namespace isc;
@@ -1469,7 +1470,7 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
         return;
     }
 
-    std::vector<uint16_t> requested_opts;
+    set<uint16_t> requested_opts;
 
     // Client requests some options using ORO option. Try to
     // get this option from client's message.
@@ -1479,8 +1480,13 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
 
     // Get the list of options that client requested.
     if (option_oro) {
-        requested_opts = option_oro->getValues();
+        set<uint16_t> oro_req_opts;
+        for (uint16_t code : option_oro->getValues()) {
+            static_cast<void>(oro_req_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) {
@@ -1495,7 +1501,8 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
              desc != range.second; ++desc) {
             // Add the persistent option code to requested options
             if (desc->option_) {
-                requested_opts.push_back(desc->option_->getType());
+                uint16_t code = desc->option_->getType();
+                static_cast<void>(requested_opts.insert(code));
             }
         }
     }
@@ -1515,6 +1522,43 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
             }
         }
     }
+
+    // Special cases for vendor class. Vendor options are done later.
+    if (requested_opts.count(D6O_VENDOR_CLASS) > 0) {
+        set<uint32_t> vendor_ids;
+        for (auto opt : answer->getOptions(D6O_VENDOR_CLASS)) {
+            if (opt.first != D6O_VENDOR_CLASS) {
+                continue;
+            }
+            OptionVendorClassPtr vendor_class;
+            vendor_class = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
+            if (vendor_class) {
+                int32_t vendor_id = vendor_class->getVendorId();
+                static_cast<void>(vendor_ids.insert(vendor_id));
+            }
+        }
+        // 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)) {
+                if (!desc.option_) {
+                    continue;
+                }
+                OptionVendorClassPtr vendor_class =
+                    boost::dynamic_pointer_cast<OptionVendorClass>(desc.option_);
+                if (!vendor_class) {
+                    continue;
+                }
+                // Is the vendor id already in the response?
+                if (vendor_ids.count(vendor_class->getVendorId()) > 0) {
+                    continue;
+                }
+                // Got it: add it.
+                answer->addOption(desc.option_);
+            }
+        }
+    }
 }
 
 void
@@ -1534,51 +1578,72 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
         return;
     }
 
-    uint32_t vendor_id = 0;
+    set<uint32_t> vendor_ids;
 
     // The server could have provided the option using client classification or
-    // hooks. If there's a vendor info option in the response already, use that.
-    OptionVendorPtr vendor_rsp(boost::dynamic_pointer_cast<OptionVendor>(
-        answer->getOption(D6O_VENDOR_OPTS)));
-    if (vendor_rsp) {
-        vendor_id = vendor_rsp->getVendorId();
+    // hooks. If there're vendor info options in the response already, use them.
+    map<uint32_t, OptionVendorPtr> vendor_rsps;
+    for (auto opt : answer->getOptions(D6O_VENDOR_OPTS)) {
+        if (opt.first != D6O_VENDOR_OPTS) {
+            continue;
+        }
+        OptionVendorPtr vendor_rsp;
+        vendor_rsp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+        if (vendor_rsp) {
+            uint32_t vendor_id = vendor_rsp->getVendorId();
+            vendor_rsps[vendor_id] = vendor_rsp;
+            static_cast<void>(vendor_ids.insert(vendor_id));
+        }
     }
 
     // Otherwise, try to get the vendor-id from the client packet's
     // vendor-specific information option (17).
-    OptionVendorPtr vendor_req;
-    if (vendor_id == 0) {
-        vendor_req = boost::dynamic_pointer_cast<OptionVendor>(
-            question->getOption(D6O_VENDOR_OPTS));
-        if (vendor_req) {
-            vendor_id = vendor_req->getVendorId();
+    map<uint32_t, OptionVendorPtr> vendor_reqs;
+    if (vendor_ids.empty()) {
+        for (auto opt : question->getOptions(D6O_VENDOR_OPTS)) {
+            if (opt.first != D6O_VENDOR_OPTS) {
+                continue;
+            }
+            OptionVendorPtr vendor_req;
+            vendor_req = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+            if (vendor_req) {
+                uint32_t vendor_id = vendor_req->getVendorId();
+                vendor_reqs[vendor_id] = vendor_req;
+                static_cast<void>(vendor_ids.insert(vendor_id));
+            }
         }
     }
 
     // Finally, try to get the vendor-id from the client packet's vendor-class
     // option (16).
-    if (vendor_id == 0) {
-        OptionVendorClassPtr vendor_class(
-            boost::dynamic_pointer_cast<OptionVendorClass>(
-                question->getOption(D6O_VENDOR_CLASS)));
-        if (vendor_class) {
-            vendor_id = vendor_class->getVendorId();
+    if (vendor_ids.empty()) {
+        for (auto opt : question->getOptions(D6O_VENDOR_CLASS)) {
+            if (opt.first != D6O_VENDOR_CLASS) {
+                continue;
+            }
+            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));
+            }
         }
     }
 
     // If there's no vendor option in either request or response, then there's no way
-    // to figure out what the vendor-id value is and we give up.
-    if (vendor_id == 0) {
+    // to figure out what the vendor-id values are and we give up.
+    if (vendor_ids.empty()) {
         return;
     }
 
-    std::vector<uint16_t> requested_opts;
+    map<uint32_t, set<uint16_t> > requested_opts;
 
     // Let's try to get ORO within that vendor-option.
     // This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
     // different policies.
     OptionUint16ArrayPtr oro;
-    if (vendor_id == VENDOR_ID_CABLE_LABS && vendor_req) {
+    if (vendor_reqs.count(VENDOR_ID_CABLE_LABS) > 0) {
+        OptionVendorPtr vendor_req = vendor_reqs[VENDOR_ID_CABLE_LABS];
         OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V6_ORO);
         if (oro_generic) {
             // Vendor ID 4491 makes Kea look at DOCSIS3_V6_OPTION_DEFINITIONS
@@ -1586,63 +1651,74 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
             // created as an OptionUint16Array, but might not be for other
             // vendor IDs.
             oro = boost::dynamic_pointer_cast<OptionUint16Array>(oro_generic);
-            if (oro) {
-                requested_opts = oro->getValues();
+        }
+        if (oro) {
+            set<uint16_t> oro_req_opts;
+            for (uint16_t code : oro->getValues()) {
+                static_cast<void>(oro_req_opts.insert(code));
             }
+            requested_opts[VENDOR_ID_CABLE_LABS] = 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(vendor_id);
-        if (!opts) {
-            continue;
-        }
-        // Get persistent options
-        const OptionContainerPersistIndex& idx = opts->get<2>();
-        const OptionContainerPersistRange& range = idx.equal_range(true);
-        for (OptionContainerPersistIndex::const_iterator desc = range.first;
-             desc != range.second; ++desc) {
-            // Add the persistent option code to requested options
-            if (desc->option_) {
-                requested_opts.push_back(desc->option_->getType());
+    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);
+            if (!opts) {
+                continue;
+            }
+            // Get persistent options
+            const OptionContainerPersistIndex& idx = opts->get<2>();
+            const OptionContainerPersistRange& range = idx.equal_range(true);
+            for (OptionContainerPersistIndex::const_iterator desc = range.first;
+                 desc != range.second; ++desc) {
+                if (!desc->option_) {
+                    continue;
+                }
+                // Add the persistent option code to requested options
+                uint16_t code = desc->option_->getType();
+                static_cast<void>(requested_opts[vendor_id].insert(code));
             }
         }
-    }
 
-    // If there is nothing to add don't do anything then.
-    if (requested_opts.empty()) {
-        return;
-    }
+        // If there is nothing to add don't do anything then with this vendor.
+        if (requested_opts[vendor_id].empty()) {
+            continue;
+        }
 
-    if (!vendor_rsp) {
         // It's possible that the vendor opts option was inserted already
         // by client class or a hook. If that is so, let's use it.
-        vendor_rsp.reset(new OptionVendor(Option::V6, vendor_id));
-    }
-
-    // Get the list of options that client requested.
-    bool added = false;
+        OptionVendorPtr vendor_rsp;
+        if (vendor_rsps.count(vendor_id) > 0) {
+            vendor_rsp = vendor_rsps[vendor_id];
+        } else {
+            vendor_rsp.reset(new OptionVendor(Option::V6, vendor_id));
+        }
 
-    for (uint16_t opt : requested_opts) {
-        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);
-                if (desc.option_) {
-                    vendor_rsp->addOption(desc.option_);
-                    added = true;
-                    break;
+        // Get the list of options that client requested.
+        bool added = false;
+
+        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);
+                    if (desc.option_) {
+                        vendor_rsp->addOption(desc.option_);
+                        added = true;
+                        break;
+                    }
                 }
             }
         }
-    }
 
-    // If we added some sub-options and the vendor opts option is not in
-    // the response already, then add it.
-    if (added && !answer->getOption(D6O_VENDOR_OPTS)) {
-        answer->addOption(vendor_rsp);
+        // If we added some sub-options and the vendor opts option is not in
+        // the response already, then add it.
+        if (added && (vendor_rsps.count(vendor_id) == 0)) {
+            answer->addOption(vendor_rsp);
+        }
     }
 }
 
@@ -3871,24 +3947,28 @@ Dhcpv6Srv::processDhcp4Query(const Pkt6Ptr& dhcp4_query) {
 }
 
 void Dhcpv6Srv::classifyByVendor(const Pkt6Ptr& pkt, std::string& classes) {
-    OptionVendorClassPtr vclass = boost::dynamic_pointer_cast<
-        OptionVendorClass>(pkt->getOption(D6O_VENDOR_CLASS));
-
-    if (!vclass || vclass->getTuplesNum() == 0) {
-        return;
-    }
+    OptionVendorClassPtr vclass;
+    for (auto opt : pkt->getOptions(D6O_VENDOR_CLASS)) {
+        if (opt.first != D6O_VENDOR_CLASS) {
+            continue;
+        }
+        vclass = boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
+        if (!vclass || vclass->getTuplesNum() == 0) {
+            continue;
+        }
 
-    if (vclass->hasTuple(DOCSIS3_CLASS_MODEM)) {
-        pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM);
-        classes += VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM + " ";
+        if (vclass->hasTuple(DOCSIS3_CLASS_MODEM)) {
+            pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM);
+            classes += VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM + " ";
 
-    } else if (vclass->hasTuple(DOCSIS3_CLASS_EROUTER)) {
-        pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER);
-        classes += VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER + " ";
+        } else if (vclass->hasTuple(DOCSIS3_CLASS_EROUTER)) {
+            pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER);
+            classes += VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER + " ";
 
-    } else {
-        pkt->addClass(VENDOR_CLASS_PREFIX + vclass->getTuple(0).getText());
-        classes + VENDOR_CLASS_PREFIX + vclass->getTuple(0).getText() + " ";
+        } else {
+            pkt->addClass(VENDOR_CLASS_PREFIX + vclass->getTuple(0).getText());
+            classes + VENDOR_CLASS_PREFIX + vclass->getTuple(0).getText() + " ";
+        }
     }
 }
 
index 2f095158a6b478a17b6bcfc6e6f04bf9a978de99..e6a0744d55887e8e498442739817303e7508060b 100644 (file)
@@ -821,7 +821,7 @@ protected:
     /// @brief Assigns incoming packet to zero or more classes.
     ///
     /// @note This is done in two phases: first the content of the
-    /// vendor-class-identifier option is used as a class, by
+    /// vendor-class-identifier options are used as classes, by
     /// calling @ref classifyByVendor(). Second, the classification match
     /// expressions are evaluated. The resulting classes will be stored
     /// in the packet (see @ref isc::dhcp::Pkt6::classes_ and
@@ -1042,7 +1042,7 @@ public:
 private:
 
     /// @public
-    /// @brief Assign class using vendor-class-identifier option
+    /// @brief Assign class using vendor-class-identifier options
     ///
     /// @note This is the first part of @ref classifyPacket
     ///
index 55d3b5f6bdd4feceb1343268aa4832902f2adc7d..701ea3916b22cf553651782e59d5792a2340df24 100644 (file)
@@ -168,6 +168,71 @@ Pkt6::getAnyRelayOption(const uint16_t option_code,
     return (OptionPtr());
 }
 
+OptionCollection
+Pkt6::getNonCopiedAnyRelayOptions(const uint16_t option_code,
+                                  const RelaySearchOrder& order) const {
+    if (relay_info_.empty()) {
+        // There's no relay info, this is a direct message
+        return (OptionCollection());
+    }
+
+    int start = 0; // First relay to check
+    int end = 0;   // Last relay to check
+    int direction = 0; // How we going to iterate: forward or backward?
+
+    prepareGetAnyRelayOption(order, start, end, direction);
+
+    // This is a tricky loop. It must go from start to end, but it must work in
+    // both directions (start > end; or start < end). We can't use regular
+    // exit condition, because we don't know whether to use i <= end or i >= end.
+    // That's why we check if in the next iteration we would go past the
+    // list (end + direction). It is similar to STL concept of end pointing
+    // to a place after the last element
+    for (int i = start; i != end + direction; i += direction) {
+        OptionCollection opts = getNonCopiedRelayOptions(option_code, i);
+        if (!opts.empty()) {
+            return (opts);
+        }
+    }
+
+    // We iterated over specified relays and haven't found what we were
+    // looking for
+    return (OptionCollection());
+}
+
+OptionCollection
+Pkt6::getAnyRelayOptions(const uint16_t option_code,
+                         const RelaySearchOrder& order) {
+
+    if (relay_info_.empty()) {
+        // There's no relay info, this is a direct message
+        return (OptionCollection());
+    }
+
+    int start = 0; // First relay to check
+    int end = 0;   // Last relay to check
+    int direction = 0; // How we going to iterate: forward or backward?
+
+    prepareGetAnyRelayOption(order, start, end, direction);
+
+    // This is a tricky loop. It must go from start to end, but it must work in
+    // both directions (start > end; or start < end). We can't use regular
+    // exit condition, because we don't know whether to use i <= end or i >= end.
+    // That's why we check if in the next iteration we would go past the
+    // list (end + direction). It is similar to STL concept of end pointing
+    // to a place after the last element
+    for (int i = start; i != end + direction; i += direction) {
+        OptionCollection opts = getRelayOptions(option_code, i);
+        if (!opts.empty()) {
+            return (opts);
+        }
+    }
+
+    // We iterated over specified relays and haven't found what we were
+    // looking for
+    return (OptionCollection());
+}
+
 OptionPtr
 Pkt6::getNonCopiedRelayOption(const uint16_t opt_type,
                               const uint8_t relay_level) const {
@@ -207,6 +272,51 @@ Pkt6::getRelayOption(const uint16_t opt_type, const uint8_t relay_level) {
     return (OptionPtr());
 }
 
+OptionCollection
+Pkt6::getNonCopiedRelayOptions(const uint16_t opt_type,
+                               const uint8_t relay_level) const {
+    if (relay_level >= relay_info_.size()) {
+        isc_throw(OutOfRange, "This message was relayed "
+                  << relay_info_.size() << " time(s)."
+                  << " There is no info about "
+                  << relay_level + 1 << " relay.");
+    }
+
+    std::pair<OptionCollection::const_iterator,
+              OptionCollection::const_iterator> range =
+        relay_info_[relay_level].options_.equal_range(opt_type);
+    return (OptionCollection(range.first, range.second));
+}
+
+OptionCollection
+Pkt6::getRelayOptions(const uint16_t opt_type,
+                      const uint8_t relay_level) {
+    if (relay_level >= relay_info_.size()) {
+        isc_throw(OutOfRange, "This message was relayed "
+                  << relay_info_.size() << " time(s)."
+                  << " There is no info about "
+                  << relay_level + 1 << " relay.");
+    }
+
+    OptionCollection options_copy;
+
+    std::pair<OptionCollection::iterator,
+              OptionCollection::iterator> range =
+        relay_info_[relay_level].options_.equal_range(opt_type);
+    // If options should be copied on retrieval, we should now iterate over
+    // matching options, copy them and replace the original ones with new
+    // instances.
+    if (copy_retrieved_options_) {
+        for (OptionCollection::iterator opt_it = range.first;
+             opt_it != range.second; ++opt_it) {
+            OptionPtr option_copy = opt_it->second->clone();
+            opt_it->second = option_copy;
+        }
+    }
+    // Finally, return updated options. This can also be empty in some cases.
+    return (OptionCollection(range.first, range.second));
+}
+
 const isc::asiolink::IOAddress&
 Pkt6::getRelay6LinkAddress(uint8_t relay_level) const {
     if (relay_level >= relay_info_.size()) {
@@ -846,11 +956,16 @@ Pkt6::getMACFromIPv6RelayOpt() {
 HWAddrPtr
 Pkt6::getMACFromDocsisModem() {
     HWAddrPtr mac;
-    OptionVendorPtr vendor = boost::dynamic_pointer_cast<
-        OptionVendor>(getNonCopiedOption(D6O_VENDOR_OPTS));
-
-    // Check if this is indeed DOCSIS3 environment
-    if (vendor && vendor->getVendorId() == VENDOR_ID_CABLE_LABS) {
+    OptionVendorPtr vendor;
+    for (auto opt : getNonCopiedOptions(D6O_VENDOR_OPTS)) {
+        if (opt.first != D6O_VENDOR_OPTS) {
+            continue;
+        }
+        vendor = boost::dynamic_pointer_cast< OptionVendor>(opt.second);
+        // Check if this is indeed DOCSIS3 environment
+        if (!vendor || vendor->getVendorId() != VENDOR_ID_CABLE_LABS) {
+            continue;
+        }
         // If it is, try to get device-id option
         OptionPtr device_id = vendor->getOption(DOCSIS3_V6_DEVICE_ID);
         if (device_id) {
@@ -858,6 +973,7 @@ Pkt6::getMACFromDocsisModem() {
             if (!device_id->getData().empty()) {
                 mac.reset(new HWAddr(device_id->getData(), HTYPE_DOCSIS));
                 mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_MODEM;
+                break;
             }
         }
     }
@@ -867,25 +983,32 @@ Pkt6::getMACFromDocsisModem() {
 
 HWAddrPtr
 Pkt6::getMACFromDocsisCMTS() {
-    HWAddrPtr mac;
+    if (relay_info_.empty()) {
+        return (HWAddrPtr());
+    }
 
     // If the message passed through a CMTS, there'll
     // CMTS-specific options in it.
-    if (!relay_info_.empty()) {
-        OptionVendorPtr vendor = boost::dynamic_pointer_cast<
-            OptionVendor>(getAnyRelayOption(D6O_VENDOR_OPTS,
-                                            RELAY_SEARCH_FROM_CLIENT));
-
+    HWAddrPtr mac;
+    OptionVendorPtr vendor;
+    for (auto opt : getAnyRelayOptions(D6O_VENDOR_OPTS,
+                                       RELAY_SEARCH_FROM_CLIENT)) {
+        if (opt.first != D6O_VENDOR_OPTS) {
+            continue;
+        }
+        vendor = boost::dynamic_pointer_cast< OptionVendor>(opt.second);
         // Check if this is indeed DOCSIS3 environment
-        if (vendor && vendor->getVendorId() == VENDOR_ID_CABLE_LABS) {
-            // Try to get cable modem mac
-            OptionPtr cm_mac = vendor->getOption(DOCSIS3_V6_CMTS_CM_MAC);
-
-            // If the option contains any data, use it as MAC address
-            if (cm_mac && !cm_mac->getData().empty()) {
-                mac.reset(new HWAddr(cm_mac->getData(), HTYPE_DOCSIS));
-                mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_CMTS;
-            }
+        if (!vendor || vendor->getVendorId() != VENDOR_ID_CABLE_LABS) {
+            continue;
+        }
+        // Try to get cable modem mac
+        OptionPtr cm_mac = vendor->getOption(DOCSIS3_V6_CMTS_CM_MAC);
+
+        // If the option contains any data, use it as MAC address
+        if (cm_mac && !cm_mac->getData().empty()) {
+            mac.reset(new HWAddr(cm_mac->getData(), HTYPE_DOCSIS));
+            mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_CMTS;
+            break;
         }
     }
 
index d7448b8b39ba7d65b0895aaa7b709e9694fd9b66..1aa02a870b91997832eb6e729002ddbb89535554 100644 (file)
@@ -246,10 +246,26 @@ protected:
     /// @param relay_level Nesting level as described for
     /// @ref Pkt6::getRelayOption.
     ///
-    /// @return Pointer to the option or NULL if such option doesn't exist.
+    /// @return Pointer to the option or null if such option doesn't exist.
     OptionPtr getNonCopiedRelayOption(const uint16_t opt_type,
                                       const uint8_t relay_level) const;
 
+    /// @brief Returns all option instances inserted by relay agent.
+    ///
+    /// This is a variant of the @ref Pkt6::getRelayOptions function which
+    /// never copies an option returned. This method should be only used by
+    /// the @ref Pkt6 class and derived classes. Any external callers should
+    /// use @ref getRelayOption which copies the option before returning it
+    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+    ///
+    /// @param opt_type Code of the requested option.
+    /// @param relay_level Nesting level as described for
+    /// @ref Pkt6::getRelayOption.
+    ///
+    /// @return Collection of options found.
+    OptionCollection getNonCopiedRelayOptions(const uint16_t opt_type,
+                                              const uint8_t relay_level) const;
+
 public:
 
     /// @brief Returns option inserted by relay
@@ -266,9 +282,27 @@ public:
     /// @param option_code code of the requested option
     /// @param nesting_level see description above
     ///
-    /// @return pointer to the option (or NULL if there is no such option)
+    /// @return pointer to the option (or null if there is no such option)
     OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
 
+    /// @brief Returns options inserted by relay
+    ///
+    /// Returns options from specified relay scope (inserted by a given relay
+    /// if this is received packet or to be decapsulated by a given relay if
+    /// this is a transmitted packet). nesting_level specifies which relay
+    /// scope is to be used. 0 is the outermost encapsulation (relay closest to
+    /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
+    /// (relay closest to the client).
+    ///
+    /// @throw isc::OutOfRange if nesting level has invalid value.
+    ///
+    /// @param option_code code of the requested option
+    /// @param nesting_level see description above
+    ///
+    /// @return Collection of options found.
+    OptionCollection getRelayOptions(uint16_t option_code,
+                                     uint8_t nesting_level);
+
 private:
 
     /// @brief Prepares parameters for loop used in @ref getAnyRelayOption
@@ -303,10 +337,25 @@ protected:
     /// @param option_code Searched option.
     /// @param order Option search order (see @ref RelaySearchOrder).
     ///
-    /// @return Option pointer or NULL, if no option matches specified criteria.
+    /// @return Option pointer or null, if no option matches specified criteria.
     OptionPtr getNonCopiedAnyRelayOption(const uint16_t option_code,
                                          const RelaySearchOrder& order) const;
 
+    /// @brief Returns pointers to instances of specified option.
+    ///
+    /// This is a variant of @ref getAnyRelayOptions but it never copies
+    /// an option returned. This method should be only used by
+    /// the @ref Pkt6 class and derived classes. Any external callers should
+    /// use @ref getAnyRelayOption which copies the option before returning it
+    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+    ///
+    /// @param option_code Searched option.
+    /// @param order Option search order (see @ref RelaySearchOrder).
+    ///
+    /// @return Collection of options found.
+    OptionCollection getNonCopiedAnyRelayOptions(const uint16_t option_code,
+                                                 const RelaySearchOrder& order) const;
+
 public:
 
     /// @brief Return first instance of a specified option
@@ -318,10 +367,23 @@ public:
     ///
     /// @param option_code searched option
     /// @param order option search order (see @ref RelaySearchOrder)
-    /// @return option pointer (or NULL if no option matches specified criteria)
+    /// @return option pointer (or null if no option matches specified criteria)
     OptionPtr getAnyRelayOption(const uint16_t option_code,
                                 const RelaySearchOrder& order);
 
+    /// @brief Return first instances of a specified option
+    ///
+    /// When a client's packet traverses multiple relays, each passing relay may
+    /// insert extra options. This method allows the specific instances of a given
+    /// option to be obtained (e.g. closest to the client, closest to the server,
+    /// etc.) See @ref RelaySearchOrder for a detailed description.
+    ///
+    /// @param option_code searched option
+    /// @param order option search order (see @ref RelaySearchOrder)
+    /// @return Collection of options found.
+    OptionCollection getAnyRelayOptions(const uint16_t option_code,
+                                        const RelaySearchOrder& order);
+
     /// @brief return the link address field from a relay option
     ///
     /// As with @c Pkt6::getRelayOption this returns information from the
index 00a087e579687fe73016b887c1978eb4c3440870..de1378586e0e412cabd574887de4879b1ad69124 100644 (file)
@@ -96,7 +96,7 @@ void
 CfgOption::replace(const OptionDescriptor& desc, const std::string& option_space) {
     if (!desc.option_) {
         isc_throw(isc::BadValue, "option being replaced must not be NULL");
-    } 
+    }
 
     // Check for presence of options.
     OptionContainerPtr options = getAll(option_space);
@@ -109,10 +109,10 @@ CfgOption::replace(const OptionDescriptor& desc, const std::string& option_space
     OptionContainerTypeIndex& idx = options->get<1>();
     auto const& od_itr = idx.find(desc.option_->getType());
     if (od_itr == idx.end()) {
-        isc_throw(isc::BadValue, "cannot replace option: " 
+        isc_throw(isc::BadValue, "cannot replace option: "
                   << option_space << ":" << desc.option_->getType()
                   << ", it does not exist");
-    } 
+    }
 
     idx.replace(od_itr, desc);
 }
@@ -154,7 +154,7 @@ CfgOption::createOptions(CfgOptionDefPtr cfg_def) {
     for (auto space : getOptionSpaceNames()) {
         for (auto opt_desc : *(getAll(space))) {
             if (createDescriptorOption(cfg_def, space, opt_desc)) {
-                // Option was recreated, let's replace the descriptor. 
+                // Option was recreated, let's replace the descriptor.
                 replace(opt_desc,space);
             }
         }