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
|