]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3440] Checkpoint: added code, UT to update
authorFrancis Dupont <fdupont@isc.org>
Wed, 26 Jun 2024 10:39:55 +0000 (12:39 +0200)
committerFrancis Dupont <fdupont@isc.org>
Fri, 19 Jul 2024 15:48:57 +0000 (17:48 +0200)
src/lib/dhcp/libdhcp++.cc
src/lib/dhcp/libdhcp++.h
src/lib/dhcp/pkt4.cc
src/lib/dhcp/tests/libdhcp++_unittest.cc

index fd790a6ebf8a6d2f57ad32626a95c3780650ca4f..87660b0515f9a6eedb02b4052e5f6b1e43ac7685 100644 (file)
@@ -30,6 +30,8 @@
 
 #include <limits>
 #include <list>
+#include <unordered_map>
+#include <vector>
 
 using namespace std;
 using namespace isc::dhcp;
@@ -482,6 +484,69 @@ LibDHCP::unpackOptions4(const OptionBuffer& buf, const string& option_space,
 
     // The buffer being read comprises a set of options, each starting with
     // a one-byte type code and a one-byte length field.
+
+    // Track seen options in a first pass.
+    vector<bool> seen(256, false);
+    unordered_map<uint8_t, size_t> counts;
+    while (offset < buf.size()) {
+        // Get the option type
+        uint8_t opt_type = buf[offset++];
+
+        // DHO_END is a special, one octet long option
+        // Valid in dhcp4 space or when check is true and
+        // there is a sub-option configured for this code.
+        if ((opt_type == DHO_END) && (space_is_dhcp4 || flex_end)) {
+            // Done.
+            break;
+        }
+
+        // DHO_PAD is just a padding after DHO_END. Let's continue parsing
+        // in case we receive a message without DHO_END.
+        // Valid in dhcp4 space or when check is true and
+        // there is a sub-option configured for this code.
+        if ((opt_type == DHO_PAD) && (space_is_dhcp4 || flex_pad)) {
+            continue;
+        }
+
+        if (offset + 1 > buf.size()) {
+            // Error case.
+            break;
+        }
+
+        uint8_t opt_len = buf[offset++];
+        if (offset + opt_len > buf.size()) {
+            // Error case.
+            break;
+        }
+
+        // See below for this special case.
+        if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) {
+            continue;
+        }
+
+        offset += opt_len;
+
+        // Mark as seen.
+        if (!seen[opt_type]) {
+            seen[opt_type] = true;
+            continue;
+        }
+
+        // Already seen.
+        size_t& count = counts[opt_type];
+        if (count == 0) {
+            // Default value for size_t is 0 but this option was already seen.
+            count = 2;
+        } else {
+            ++count;
+        }
+    }
+
+    // Fusing option buffers.
+    unordered_map<uint8_t, OptionBuffer> fused;
+
+    // Second pass.
+    offset = 0;
     while (offset < buf.size()) {
         // Save the current offset for backtracking
         last_offset = offset;
@@ -536,6 +601,25 @@ LibDHCP::unpackOptions4(const OptionBuffer& buf, const string& option_space,
             continue;
         }
 
+        OptionBuffer obuf(buf.begin() + offset, buf.begin() + offset + opt_len);
+        offset += opt_len;
+
+        // Concatenate multiple instance of an option.
+        try {
+            size_t count = counts.at(opt_type);
+            OptionBuffer& previous = fused[opt_type];
+            previous.insert(previous.end(), obuf.begin(), obuf.end());
+            if (count <= 1) {
+                // last occurrence: build the option.
+                obuf = previous;
+            } else {
+                counts[opt_type] = count - 1;
+                continue;
+            }
+        } catch (const std::out_of_range&) {
+            // Regular case.
+        }
+
         // Get all definitions with the particular option code. Note
         // that option code is non-unique within this container
         // however at this point we expect to get one option
@@ -592,9 +676,7 @@ LibDHCP::unpackOptions4(const OptionBuffer& buf, const string& option_space,
                       " This will be supported once support for option spaces"
                       " is implemented");
         } else if (num_defs == 0) {
-            opt = OptionPtr(new Option(Option::V4, opt_type,
-                                       buf.begin() + offset,
-                                       buf.begin() + offset + opt_len));
+            opt = OptionPtr(new Option(Option::V4, opt_type, obuf));
             opt->setEncapsulatedSpace(DHCP4_OPTION_SPACE);
         } else {
             try {
@@ -602,9 +684,7 @@ LibDHCP::unpackOptions4(const OptionBuffer& buf, const string& option_space,
                 // the option instance from the provided buffer chunk.
                 const OptionDefinitionPtr& def = *(range.first);
                 isc_throw_assert(def);
-                opt = def->optionFactory(Option::V4, opt_type,
-                                         buf.begin() + offset,
-                                         buf.begin() + offset + opt_len);
+                opt = def->optionFactory(Option::V4, opt_type, obuf);
             } catch (const SkipThisOptionError&) {
                 opt.reset();
             }
@@ -614,79 +694,11 @@ LibDHCP::unpackOptions4(const OptionBuffer& buf, const string& option_space,
         if (opt) {
             options.insert(std::make_pair(opt_type, opt));
         }
-
-        offset += opt_len;
     }
     last_offset = offset;
     return (last_offset);
 }
 
-bool
-LibDHCP::fuseOptions4(OptionCollection& options) {
-    bool result = false;
-    // We need to loop until all options have been fused.
-    for (;;) {
-        uint32_t found = 0;
-        bool found_suboptions = false;
-        // Iterate over all options in the container.
-        for (auto const& option : options) {
-            OptionPtr candidate = option.second;
-            OptionCollection& sub_options = candidate->getMutableOptions();
-            // Fuse suboptions recursively, if any.
-            if (sub_options.size()) {
-                // Fusing suboptions might result in new options with multiple
-                // options having the same code, so we need to iterate again
-                // until no option needs fusing.
-                found_suboptions = LibDHCP::fuseOptions4(sub_options);
-                if (found_suboptions) {
-                    result = true;
-                }
-            }
-            OptionBuffer data;
-            OptionCollection suboptions;
-            // Make a copy of the options so we can safely iterate over the
-            // old container.
-            OptionCollection copy = options;
-            for (auto const& old_option : copy) {
-                if (old_option.first == option.first) {
-                    // Copy the option data to the buffer.
-                    data.insert(data.end(), old_option.second->getData().begin(),
-                                old_option.second->getData().end());
-                    suboptions.insert(old_option.second->getOptions().begin(),
-                                      old_option.second->getOptions().end());
-                    // Other options might need fusing, so we need to iterate
-                    // again until no options needs fusing.
-                    found++;
-                }
-            }
-            if (found > 1) {
-                result = true;
-                // Erase the old options from the new container so that only the
-                // new option is present.
-                copy.erase(option.first);
-                // Create new option with entire data.
-                OptionPtr new_option(new Option(candidate->getUniverse(),
-                                                candidate->getType(), data));
-                // Recreate suboptions container.
-                new_option->getMutableOptions() = suboptions;
-                // Add the new option to the new container.
-                copy.insert(make_pair(candidate->getType(), new_option));
-                // After all options have been fused and new option added,
-                // update the option container with the new container.
-                options = copy;
-                break;
-            } else {
-                found = 0;
-            }
-        }
-        // No option needs fusing, so we can exit the loop.
-        if ((found <= 1) && !found_suboptions) {
-            break;
-        }
-    }
-    return (result);
-}
-
 namespace { // Anonymous namespace.
 
 // VIVCO part of extendVendorOptions4.
index 3de55167ceac00e33bd3cca09ad9978f25be8bd2..89a02be275fd71fce5a9bd7a6b544346d22103f7 100644 (file)
@@ -291,14 +291,6 @@ public:
                                  size_t* relay_msg_offset = 0,
                                  size_t* relay_msg_len = 0);
 
-    /// @brief Fuse multiple options with the same option code in long options
-    /// (RFC3396).
-    ///
-    /// @param options The option container which needs to be updated with fused
-    /// options.
-    /// @return True if any option has been fused, false otherwise.
-    static bool fuseOptions4(isc::dhcp::OptionCollection& options);
-
     /// @brief Extend vendor options from fused options in multiple OptionVendor
     /// or OptionVendorClass options and add respective suboptions or values.
     ///
index d552eead5613c92223e5002954a67fe04495a38d..88bbbce5d21187d1b73a3e9a5873304ee7e98510 100644 (file)
@@ -222,14 +222,6 @@ Pkt4::unpack() {
     // }
     (void)offset;
 
-    // The RFC3396 adds support for multiple options using the same code fused
-    // into long options.
-    // All instances of the same option are fused together, including merging
-    // the suboption lists and fusing suboptions. As a result, the options will
-    // store more than 255 bytes of data and the regex parsers can effectively
-    // access the entire data.
-    LibDHCP::fuseOptions4(options_);
-
     // Kea supports multiple vendor options so it needs to split received and
     // fused options in multiple OptionVendor instances.
     LibDHCP::extendVendorOptions4(options_);
index 948a12963de849929a2095d02c22274fa4452e3d..ae5dc18976470fa014c6d9b836c3b78d4c1e881f 100644 (file)
@@ -1548,7 +1548,7 @@ TEST_F(LibDhcpTest, fuseLongOption) {
         col.insert(std::make_pair(231, option));
     }
     ASSERT_EQ(256, col.size());
-    LibDHCP::fuseOptions4(col);
+    //// LibDHCP::fuseOptions4(col);
 
     ASSERT_EQ(1, col.size());
     uint8_t index = 0;
@@ -1588,7 +1588,7 @@ TEST_F(LibDhcpTest, fuseLongOptionWithLongSuboption) {
     col.insert(std::make_pair(213, rai));
     ASSERT_EQ(1, col.size());
     ASSERT_EQ(256, col.begin()->second->getOptions().size());
-    LibDHCP::fuseOptions4(col);
+    //// LibDHCP::fuseOptions4(col);
 
     ASSERT_EQ(1, col.size());
     ASSERT_EQ(1, col.begin()->second->getOptions().size());
@@ -1644,7 +1644,7 @@ TEST_F(LibDhcpTest, extendVivco) {
     options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt2));
     options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt3));
     EXPECT_EQ(options.size(), 3);
-    EXPECT_NO_THROW(LibDHCP::fuseOptions4(options));
+    //// LibDHCP::fuseOptions4(options);
     EXPECT_EQ(options.size(), 1);
     EXPECT_NO_THROW(LibDHCP::extendVendorOptions4(options));
     EXPECT_EQ(options.size(), 2);
@@ -1712,7 +1712,7 @@ TEST_F(LibDhcpTest, extendVivso) {
                                                       option.second->getType(),
                                                       buffer))));
     }
-    ASSERT_NO_THROW(LibDHCP::fuseOptions4(options));
+    //// LibDHCP::fuseOptions4(options);
     ASSERT_EQ(options.size(), 1);
     ASSERT_EQ(options.count(DHO_VIVSO_SUBOPTIONS), 1);
     ASSERT_EQ(options.find(DHO_VIVSO_SUBOPTIONS)->second->getType(), DHO_VIVSO_SUBOPTIONS);