LCOV - code coverage report
Current view: top level - source/common/http - hash_policy.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 50 142 35.2 %
Date: 2024-01-05 06:35:25 Functions: 7 14 50.0 %

          Line data    Source code
       1             : #include "source/common/http/hash_policy.h"
       2             : 
       3             : #include <string>
       4             : 
       5             : #include "envoy/common/hashable.h"
       6             : #include "envoy/config/route/v3/route_components.pb.h"
       7             : 
       8             : #include "source/common/common/matchers.h"
       9             : #include "source/common/common/regex.h"
      10             : #include "source/common/http/utility.h"
      11             : #include "source/common/runtime/runtime_features.h"
      12             : 
      13             : #include "absl/strings/str_cat.h"
      14             : 
      15             : namespace Envoy {
      16             : namespace Http {
      17             : 
      18             : class HashMethodImplBase : public HashPolicyImpl::HashMethod {
      19             : public:
      20         148 :   explicit HashMethodImplBase(bool terminal) : terminal_(terminal) {}
      21             : 
      22           0 :   bool terminal() const override { return terminal_; }
      23             : 
      24             : private:
      25             :   const bool terminal_;
      26             : };
      27             : 
      28             : class HeaderHashMethod : public HashMethodImplBase {
      29             : public:
      30             :   HeaderHashMethod(const envoy::config::route::v3::RouteAction::HashPolicy::Header& header,
      31             :                    bool terminal)
      32          56 :       : HashMethodImplBase(terminal), header_name_(header.header_name()) {
      33          56 :     if (header.has_regex_rewrite()) {
      34          18 :       const auto& rewrite_spec = header.regex_rewrite();
      35          18 :       regex_rewrite_ = Regex::Utility::parseRegex(rewrite_spec.pattern());
      36          18 :       regex_rewrite_substitution_ = rewrite_spec.substitution();
      37          18 :     }
      38          56 :   }
      39             : 
      40             :   absl::optional<uint64_t> evaluate(const Network::Address::Instance*,
      41             :                                     const RequestHeaderMap& headers,
      42             :                                     const HashPolicy::AddCookieCallback,
      43           0 :                                     const StreamInfo::FilterStateSharedPtr) const override {
      44           0 :     absl::optional<uint64_t> hash;
      45             : 
      46           0 :     const auto header = headers.get(header_name_);
      47           0 :     if (!header.empty()) {
      48           0 :       absl::InlinedVector<absl::string_view, 1> header_values;
      49           0 :       size_t num_headers_to_hash = header.size();
      50           0 :       header_values.reserve(num_headers_to_hash);
      51             : 
      52           0 :       for (size_t i = 0; i < num_headers_to_hash; i++) {
      53           0 :         header_values.push_back(header[i]->value().getStringView());
      54           0 :       }
      55             : 
      56           0 :       absl::InlinedVector<std::string, 1> rewritten_header_values;
      57           0 :       if (regex_rewrite_ != nullptr) {
      58           0 :         rewritten_header_values.reserve(num_headers_to_hash);
      59           0 :         for (auto& value : header_values) {
      60           0 :           rewritten_header_values.push_back(
      61           0 :               regex_rewrite_->replaceAll(value, regex_rewrite_substitution_));
      62           0 :           value = rewritten_header_values.back();
      63           0 :         }
      64           0 :       }
      65             : 
      66             :       // Ensure generating same hash value for different order header values.
      67             :       // For example, generates the same hash value for {"foo","bar"} and {"bar","foo"}
      68           0 :       std::sort(header_values.begin(), header_values.end());
      69           0 :       hash = HashUtil::xxHash64(absl::MakeSpan(header_values));
      70           0 :     }
      71           0 :     return hash;
      72           0 :   }
      73             : 
      74             : private:
      75             :   const LowerCaseString header_name_;
      76             :   Regex::CompiledMatcherPtr regex_rewrite_{};
      77             :   std::string regex_rewrite_substitution_{};
      78             : };
      79             : 
      80             : class CookieHashMethod : public HashMethodImplBase {
      81             : public:
      82             :   CookieHashMethod(const std::string& key, const std::string& path,
      83             :                    const absl::optional<std::chrono::seconds>& ttl, bool terminal,
      84             :                    const CookieAttributeRefVector attributes)
      85          36 :       : HashMethodImplBase(terminal), key_(key), path_(path), ttl_(ttl), attributes_(attributes) {}
      86             : 
      87             :   absl::optional<uint64_t> evaluate(const Network::Address::Instance*,
      88             :                                     const RequestHeaderMap& headers,
      89             :                                     const HashPolicy::AddCookieCallback add_cookie,
      90           0 :                                     const StreamInfo::FilterStateSharedPtr) const override {
      91           0 :     absl::optional<uint64_t> hash;
      92           0 :     std::string value = Utility::parseCookieValue(headers, key_);
      93           0 :     if (value.empty() && ttl_.has_value()) {
      94           0 :       value = add_cookie(key_, path_, ttl_.value(), attributes_);
      95           0 :       hash = HashUtil::xxHash64(value);
      96             : 
      97           0 :     } else if (!value.empty()) {
      98           0 :       hash = HashUtil::xxHash64(value);
      99           0 :     }
     100           0 :     return hash;
     101           0 :   }
     102             : 
     103             : private:
     104             :   const std::string key_;
     105             :   const std::string path_;
     106             :   const absl::optional<std::chrono::seconds> ttl_;
     107             :   const CookieAttributeRefVector attributes_;
     108             : };
     109             : 
     110             : class IpHashMethod : public HashMethodImplBase {
     111             : public:
     112          40 :   IpHashMethod(bool terminal) : HashMethodImplBase(terminal) {}
     113             : 
     114             :   absl::optional<uint64_t> evaluate(const Network::Address::Instance* downstream_addr,
     115             :                                     const RequestHeaderMap&, const HashPolicy::AddCookieCallback,
     116           0 :                                     const StreamInfo::FilterStateSharedPtr) const override {
     117           0 :     if (downstream_addr == nullptr) {
     118           0 :       return absl::nullopt;
     119           0 :     }
     120           0 :     auto* downstream_ip = downstream_addr->ip();
     121           0 :     if (downstream_ip == nullptr) {
     122           0 :       return absl::nullopt;
     123           0 :     }
     124           0 :     const auto& downstream_addr_str = downstream_ip->addressAsString();
     125           0 :     if (downstream_addr_str.empty()) {
     126           0 :       return absl::nullopt;
     127           0 :     }
     128           0 :     return HashUtil::xxHash64(downstream_addr_str);
     129           0 :   }
     130             : };
     131             : 
     132             : class QueryParameterHashMethod : public HashMethodImplBase {
     133             : public:
     134             :   QueryParameterHashMethod(const std::string& parameter_name, bool terminal)
     135           8 :       : HashMethodImplBase(terminal), parameter_name_(parameter_name) {}
     136             : 
     137             :   absl::optional<uint64_t> evaluate(const Network::Address::Instance*,
     138             :                                     const RequestHeaderMap& headers,
     139             :                                     const HashPolicy::AddCookieCallback,
     140           0 :                                     const StreamInfo::FilterStateSharedPtr) const override {
     141           0 :     absl::optional<uint64_t> hash;
     142             : 
     143           0 :     const HeaderEntry* header = headers.Path();
     144           0 :     if (header) {
     145           0 :       Http::Utility::QueryParamsMulti query_parameters =
     146           0 :           Http::Utility::QueryParamsMulti::parseQueryString(header->value().getStringView());
     147           0 :       const auto val = query_parameters.getFirstValue(parameter_name_);
     148           0 :       if (val.has_value()) {
     149           0 :         hash = HashUtil::xxHash64(val.value());
     150           0 :       }
     151           0 :     }
     152           0 :     return hash;
     153           0 :   }
     154             : 
     155             : private:
     156             :   const std::string parameter_name_;
     157             : };
     158             : 
     159             : class FilterStateHashMethod : public HashMethodImplBase {
     160             : public:
     161             :   FilterStateHashMethod(const std::string& key, bool terminal)
     162           8 :       : HashMethodImplBase(terminal), key_(key) {}
     163             : 
     164             :   absl::optional<uint64_t>
     165             :   evaluate(const Network::Address::Instance*, const RequestHeaderMap&,
     166             :            const HashPolicy::AddCookieCallback,
     167           0 :            const StreamInfo::FilterStateSharedPtr filter_state) const override {
     168           0 :     if (auto typed_state = filter_state->getDataReadOnly<Hashable>(key_); typed_state != nullptr) {
     169           0 :       return typed_state->hash();
     170           0 :     }
     171           0 :     return absl::nullopt;
     172           0 :   }
     173             : 
     174             : private:
     175             :   const std::string key_;
     176             : };
     177             : 
     178             : HashPolicyImpl::HashPolicyImpl(
     179         122 :     absl::Span<const envoy::config::route::v3::RouteAction::HashPolicy* const> hash_policies) {
     180             : 
     181         122 :   hash_impls_.reserve(hash_policies.size());
     182         148 :   for (auto* hash_policy : hash_policies) {
     183         148 :     switch (hash_policy->policy_specifier_case()) {
     184          56 :     case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kHeader:
     185          56 :       hash_impls_.emplace_back(
     186          56 :           new HeaderHashMethod(hash_policy->header(), hash_policy->terminal()));
     187          56 :       break;
     188          36 :     case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kCookie: {
     189          36 :       absl::optional<std::chrono::seconds> ttl;
     190          36 :       if (hash_policy->cookie().has_ttl()) {
     191          16 :         ttl = std::chrono::seconds(hash_policy->cookie().ttl().seconds());
     192          16 :       }
     193          36 :       std::vector<CookieAttribute> attributes;
     194          36 :       for (const auto& attribute : hash_policy->cookie().attributes()) {
     195           0 :         attributes.push_back({attribute.name(), attribute.value()});
     196           0 :       }
     197          36 :       CookieAttributeRefVector ref_attributes;
     198          36 :       for (const auto& attribute : attributes) {
     199           0 :         ref_attributes.push_back(attribute);
     200           0 :       }
     201          36 :       hash_impls_.emplace_back(new CookieHashMethod(hash_policy->cookie().name(),
     202          36 :                                                     hash_policy->cookie().path(), ttl,
     203          36 :                                                     hash_policy->terminal(), ref_attributes));
     204          36 :       break;
     205           0 :     }
     206          40 :     case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::
     207          40 :         kConnectionProperties:
     208          40 :       if (hash_policy->connection_properties().source_ip()) {
     209          40 :         hash_impls_.emplace_back(new IpHashMethod(hash_policy->terminal()));
     210          40 :       }
     211          40 :       break;
     212           8 :     case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kQueryParameter:
     213           8 :       hash_impls_.emplace_back(new QueryParameterHashMethod(hash_policy->query_parameter().name(),
     214           8 :                                                             hash_policy->terminal()));
     215           8 :       break;
     216           8 :     case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kFilterState:
     217           8 :       hash_impls_.emplace_back(
     218           8 :           new FilterStateHashMethod(hash_policy->filter_state().key(), hash_policy->terminal()));
     219           8 :       break;
     220           0 :     case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::
     221           0 :         POLICY_SPECIFIER_NOT_SET:
     222           0 :       PANIC("hash policy not set");
     223         148 :     }
     224         148 :   }
     225         122 : }
     226             : 
     227             : absl::optional<uint64_t>
     228             : HashPolicyImpl::generateHash(const Network::Address::Instance* downstream_addr,
     229             :                              const RequestHeaderMap& headers, const AddCookieCallback add_cookie,
     230           0 :                              const StreamInfo::FilterStateSharedPtr filter_state) const {
     231           0 :   absl::optional<uint64_t> hash;
     232           0 :   for (const HashMethodPtr& hash_impl : hash_impls_) {
     233           0 :     const absl::optional<uint64_t> new_hash =
     234           0 :         hash_impl->evaluate(downstream_addr, headers, add_cookie, filter_state);
     235           0 :     if (new_hash) {
     236             :       // Rotating the old value prevents duplicate hash rules from cancelling each other out
     237             :       // and preserves all of the entropy
     238           0 :       const uint64_t old_value = hash ? ((hash.value() << 1) | (hash.value() >> 63)) : 0;
     239           0 :       hash = old_value ^ new_hash.value();
     240           0 :     }
     241             :     // If the policy is a terminal policy and a hash has been generated, ignore
     242             :     // the rest of the hash policies.
     243           0 :     if (hash_impl->terminal() && hash) {
     244           0 :       break;
     245           0 :     }
     246           0 :   }
     247           0 :   return hash;
     248           0 : }
     249             : 
     250             : } // namespace Http
     251             : } // namespace Envoy

Generated by: LCOV version 1.15