Coverage Report

Created: 2023-11-12 09:30

/proc/self/cwd/source/common/http/hash_policy.cc
Line
Count
Source (jump to first uncovered line)
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
17.2k
  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
6.35k
      : HashMethodImplBase(terminal), header_name_(header.header_name()) {
33
6.35k
    if (header.has_regex_rewrite()) {
34
1.90k
      const auto& rewrite_spec = header.regex_rewrite();
35
1.90k
      regex_rewrite_ = Regex::Utility::parseRegex(rewrite_spec.pattern());
36
1.90k
      regex_rewrite_substitution_ = rewrite_spec.substitution();
37
1.90k
    }
38
6.35k
  }
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
2.04k
      : 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
2.42k
  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
2.67k
      : 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
3.71k
      : 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
8.27k
    absl::Span<const envoy::config::route::v3::RouteAction::HashPolicy* const> hash_policies) {
180
181
8.27k
  hash_impls_.reserve(hash_policies.size());
182
22.1k
  for (auto* hash_policy : hash_policies) {
183
22.1k
    switch (hash_policy->policy_specifier_case()) {
184
6.35k
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kHeader:
185
6.35k
      hash_impls_.emplace_back(
186
6.35k
          new HeaderHashMethod(hash_policy->header(), hash_policy->terminal()));
187
6.35k
      break;
188
2.04k
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kCookie: {
189
2.04k
      absl::optional<std::chrono::seconds> ttl;
190
2.04k
      if (hash_policy->cookie().has_ttl()) {
191
952
        ttl = std::chrono::seconds(hash_policy->cookie().ttl().seconds());
192
952
      }
193
2.04k
      std::vector<CookieAttribute> attributes;
194
2.04k
      for (const auto& attribute : hash_policy->cookie().attributes()) {
195
670
        attributes.push_back({attribute.name(), attribute.value()});
196
670
      }
197
2.04k
      CookieAttributeRefVector ref_attributes;
198
2.04k
      for (const auto& attribute : attributes) {
199
670
        ref_attributes.push_back(attribute);
200
670
      }
201
2.04k
      hash_impls_.emplace_back(new CookieHashMethod(hash_policy->cookie().name(),
202
2.04k
                                                    hash_policy->cookie().path(), ttl,
203
2.04k
                                                    hash_policy->terminal(), ref_attributes));
204
2.04k
      break;
205
0
    }
206
7.35k
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::
207
7.35k
        kConnectionProperties:
208
7.35k
      if (hash_policy->connection_properties().source_ip()) {
209
2.42k
        hash_impls_.emplace_back(new IpHashMethod(hash_policy->terminal()));
210
2.42k
      }
211
7.35k
      break;
212
2.67k
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kQueryParameter:
213
2.67k
      hash_impls_.emplace_back(new QueryParameterHashMethod(hash_policy->query_parameter().name(),
214
2.67k
                                                            hash_policy->terminal()));
215
2.67k
      break;
216
3.71k
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kFilterState:
217
3.71k
      hash_impls_.emplace_back(
218
3.71k
          new FilterStateHashMethod(hash_policy->filter_state().key(), hash_policy->terminal()));
219
3.71k
      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
22.1k
    }
224
22.1k
  }
225
8.27k
}
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