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/hex.h"
9
#include "source/common/common/matchers.h"
10
#include "source/common/common/regex.h"
11
#include "source/common/http/utility.h"
12
#include "source/common/runtime/runtime_features.h"
13

            
14
#include "absl/strings/str_cat.h"
15

            
16
namespace Envoy {
17
namespace Http {
18

            
19
class HashMethodImplBase : public HashPolicyImpl::HashMethod {
20
public:
21
99
  explicit HashMethodImplBase(bool terminal) : terminal_(terminal) {}
22

            
23
1581
  bool terminal() const override { return terminal_; }
24

            
25
private:
26
  const bool terminal_;
27
};
28

            
29
class HeaderHashMethod : public HashMethodImplBase {
30
public:
31
  HeaderHashMethod(const envoy::config::route::v3::RouteAction::HashPolicy::Header& header,
32
                   bool terminal, Regex::Engine& regex_engine, absl::Status& creation_status)
33
48
      : HashMethodImplBase(terminal), header_name_(header.header_name()),
34
48
        regex_rewrite_substitution_(header.regex_rewrite().substitution()) {
35
48
    if (header.has_regex_rewrite()) {
36
2
      auto regex_or_error =
37
2
          Regex::Utility::parseRegex(header.regex_rewrite().pattern(), regex_engine);
38
2
      SET_AND_RETURN_IF_NOT_OK(regex_or_error.status(), creation_status);
39
2
      regex_rewrite_ = std::move(*regex_or_error);
40
2
    }
41
48
  }
42

            
43
  absl::optional<uint64_t> evaluate(OptRef<const RequestHeaderMap> headers,
44
                                    OptRef<const StreamInfo::StreamInfo>,
45
593
                                    HashPolicy::AddCookieCallback) const override {
46
593
    if (!headers.has_value()) {
47
1
      return absl::nullopt;
48
1
    }
49

            
50
592
    const auto header = headers->get(header_name_);
51
592
    if (header.empty()) {
52
9
      return absl::nullopt;
53
9
    }
54

            
55
583
    absl::InlinedVector<absl::string_view, 1> header_values;
56
583
    const size_t num_headers_to_hash = header.size();
57
583
    header_values.reserve(num_headers_to_hash);
58

            
59
1183
    for (size_t i = 0; i < num_headers_to_hash; i++) {
60
600
      header_values.push_back(header[i]->value().getStringView());
61
600
    }
62

            
63
583
    absl::InlinedVector<std::string, 1> rewritten_header_values;
64
583
    if (regex_rewrite_ != nullptr) {
65
9
      rewritten_header_values.reserve(num_headers_to_hash);
66
16
      for (absl::string_view& value : header_values) {
67
16
        rewritten_header_values.push_back(
68
16
            regex_rewrite_->replaceAll(value, regex_rewrite_substitution_));
69
16
        value = rewritten_header_values.back();
70
16
      }
71
9
    }
72

            
73
    // Ensure generating same hash value for different order header values.
74
    // For example, generates the same hash value for {"foo","bar"} and {"bar","foo"}
75
583
    std::sort(header_values.begin(), header_values.end());
76
583
    return HashUtil::xxHash64(absl::MakeSpan(header_values));
77
592
  }
78

            
79
private:
80
  const LowerCaseString header_name_;
81
  Regex::CompiledMatcherPtr regex_rewrite_;
82
  const std::string regex_rewrite_substitution_;
83
};
84

            
85
class CookieHashMethod : public HashMethodImplBase {
86
public:
87
  CookieHashMethod(const envoy::config::route::v3::RouteAction::HashPolicy::Cookie& cookie,
88
                   bool terminal)
89
32
      : HashMethodImplBase(terminal), name_(cookie.name()), path_(cookie.path()),
90
32
        ttl_(cookie.has_ttl() ? absl::optional<std::chrono::seconds>(cookie.ttl().seconds())
91
32
                              : absl::nullopt) {
92
32
    attributes_.reserve(cookie.attributes().size());
93
33
    for (const auto& attribute : cookie.attributes()) {
94
6
      attributes_.push_back(CookieAttribute{attribute.name(), attribute.value()});
95
6
    }
96
32
  }
97

            
98
  absl::optional<uint64_t> evaluate(OptRef<const RequestHeaderMap> headers,
99
                                    OptRef<const StreamInfo::StreamInfo>,
100
951
                                    HashPolicy::AddCookieCallback add_cookie) const override {
101
951
    if (!headers.has_value()) {
102
      return absl::nullopt;
103
    }
104

            
105
951
    const std::string exist_value = Utility::parseCookieValue(*headers, name_);
106
951
    if (!exist_value.empty()) {
107
315
      return HashUtil::xxHash64(exist_value);
108
315
    }
109

            
110
    // If the cookie is not found, try to generate a new cookie.
111

            
112
    // If one of the conditions happens, skip generating a new cookie:
113
    // 1. The cookie has no TTL.
114
    // 2. The cookie generation callback is null.
115
636
    if (!ttl_.has_value() || add_cookie == nullptr) {
116
155
      return absl::nullopt;
117
155
    }
118

            
119
481
    const std::string new_value = add_cookie(name_, path_, ttl_.value(), attributes_);
120
481
    return new_value.empty() ? absl::nullopt
121
481
                             : absl::optional<uint64_t>(HashUtil::xxHash64(new_value));
122
636
  }
123

            
124
private:
125
  const std::string name_;
126
  const std::string path_;
127
  const absl::optional<std::chrono::seconds> ttl_;
128
  std::vector<CookieAttribute> attributes_;
129
};
130

            
131
class IpHashMethod : public HashMethodImplBase {
132
public:
133
8
  IpHashMethod(bool terminal) : HashMethodImplBase(terminal) {}
134

            
135
  absl::optional<uint64_t> evaluate(OptRef<const RequestHeaderMap>,
136
                                    OptRef<const StreamInfo::StreamInfo> info,
137
24
                                    HashPolicy::AddCookieCallback) const override {
138
24
    if (!info.has_value()) {
139
3
      return absl::nullopt;
140
3
    }
141

            
142
21
    const auto& conn = info->downstreamAddressProvider();
143
21
    const auto& downstream_addr = conn.remoteAddress();
144

            
145
21
    if (downstream_addr == nullptr) {
146
1
      return absl::nullopt;
147
1
    }
148
20
    auto* downstream_ip = downstream_addr->ip();
149
20
    if (downstream_ip == nullptr) {
150
1
      return absl::nullopt;
151
1
    }
152
19
    const auto& downstream_addr_str = downstream_ip->addressAsString();
153
19
    if (downstream_addr_str.empty()) {
154
1
      return absl::nullopt;
155
1
    }
156
18
    return HashUtil::xxHash64(downstream_addr_str);
157
19
  }
158
};
159

            
160
class QueryParameterHashMethod : public HashMethodImplBase {
161
public:
162
  QueryParameterHashMethod(const std::string& parameter_name, bool terminal)
163
4
      : HashMethodImplBase(terminal), parameter_name_(parameter_name) {}
164

            
165
  absl::optional<uint64_t> evaluate(OptRef<const RequestHeaderMap> headers,
166
                                    OptRef<const StreamInfo::StreamInfo>,
167
6
                                    HashPolicy::AddCookieCallback) const override {
168
6
    if (!headers.has_value()) {
169
      return absl::nullopt;
170
    }
171

            
172
6
    const Utility::QueryParamsMulti query_parameters =
173
6
        Utility::QueryParamsMulti::parseQueryString(headers->getPathValue());
174
6
    const auto val = query_parameters.getFirstValue(parameter_name_);
175
6
    if (val.has_value()) {
176
3
      return HashUtil::xxHash64(val.value());
177
3
    }
178
3
    return absl::nullopt;
179
6
  }
180

            
181
private:
182
  const std::string parameter_name_;
183
};
184

            
185
class FilterStateHashMethod : public HashMethodImplBase {
186
public:
187
  FilterStateHashMethod(const std::string& key, bool terminal)
188
7
      : HashMethodImplBase(terminal), key_(key) {}
189

            
190
  absl::optional<uint64_t> evaluate(OptRef<const RequestHeaderMap>,
191
                                    OptRef<const StreamInfo::StreamInfo> info,
192
7
                                    HashPolicy::AddCookieCallback) const override {
193
7
    if (!info.has_value()) {
194
      return absl::nullopt;
195
    }
196

            
197
7
    auto filter_state = info->filterState().getDataReadOnly<Hashable>(key_);
198
7
    return filter_state != nullptr ? filter_state->hash() : absl::nullopt;
199
7
  }
200

            
201
private:
202
  const std::string key_;
203
};
204

            
205
absl::StatusOr<std::unique_ptr<HashPolicyImpl>> HashPolicyImpl::create(
206
    absl::Span<const envoy::config::route::v3::RouteAction::HashPolicy* const> hash_policy,
207
95
    Regex::Engine& regex_engine) {
208
95
  absl::Status creation_status = absl::OkStatus();
209
95
  std::unique_ptr<HashPolicyImpl> ret = std::unique_ptr<HashPolicyImpl>(
210
95
      new HashPolicyImpl(hash_policy, regex_engine, creation_status));
211
95
  RETURN_IF_NOT_OK(creation_status);
212
95
  return ret;
213
95
}
214

            
215
HashPolicyImpl::HashPolicyImpl(
216
    absl::Span<const envoy::config::route::v3::RouteAction::HashPolicy* const> hash_policies,
217
95
    Regex::Engine& regex_engine, absl::Status& creation_status) {
218

            
219
95
  hash_impls_.reserve(hash_policies.size());
220
99
  for (auto* hash_policy : hash_policies) {
221
99
    switch (hash_policy->policy_specifier_case()) {
222
48
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kHeader:
223
48
      hash_impls_.emplace_back(new HeaderHashMethod(hash_policy->header(), hash_policy->terminal(),
224
48
                                                    regex_engine, creation_status));
225
48
      if (!creation_status.ok()) {
226
        return;
227
      }
228
48
      break;
229
73
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kCookie: {
230
32
      hash_impls_.emplace_back(
231
32
          new CookieHashMethod(hash_policy->cookie(), hash_policy->terminal()));
232
32
      break;
233
48
    }
234
8
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::
235
8
        kConnectionProperties:
236
8
      if (hash_policy->connection_properties().source_ip()) {
237
8
        hash_impls_.emplace_back(new IpHashMethod(hash_policy->terminal()));
238
8
      }
239
8
      break;
240
4
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kQueryParameter:
241
4
      hash_impls_.emplace_back(new QueryParameterHashMethod(hash_policy->query_parameter().name(),
242
4
                                                            hash_policy->terminal()));
243
4
      break;
244
7
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kFilterState:
245
7
      hash_impls_.emplace_back(
246
7
          new FilterStateHashMethod(hash_policy->filter_state().key(), hash_policy->terminal()));
247
7
      break;
248
    case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::
249
        POLICY_SPECIFIER_NOT_SET:
250
      PANIC("hash policy not set");
251
99
    }
252
99
  }
253
95
}
254

            
255
absl::optional<uint64_t>
256
HashPolicyImpl::generateHash(OptRef<const RequestHeaderMap> headers,
257
                             OptRef<const StreamInfo::StreamInfo> info,
258
1568
                             HashPolicy::AddCookieCallback add_cookie) const {
259
1568
  absl::optional<uint64_t> hash;
260
1581
  for (const HashMethodPtr& hash_impl : hash_impls_) {
261
1581
    const absl::optional<uint64_t> new_hash = hash_impl->evaluate(headers, info, add_cookie);
262
1581
    if (new_hash) {
263
      // Rotating the old value prevents duplicate hash rules from cancelling each other out
264
      // and preserves all of the entropy
265
1385
      const uint64_t old_value = hash ? ((hash.value() << 1) | (hash.value() >> 63)) : 0;
266
1385
      hash = old_value ^ new_hash.value();
267
1385
    }
268
    // If the policy is a terminal policy and a hash has been generated, ignore
269
    // the rest of the hash policies.
270
1581
    if (hash_impl->terminal() && hash) {
271
2
      break;
272
2
    }
273
1581
  }
274
1568
  return hash;
275
1568
}
276

            
277
} // namespace Http
278
} // namespace Envoy