/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 | 11.9k | 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, Regex::Engine& regex_engine) |
32 | 3.61k | : HashMethodImplBase(terminal), header_name_(header.header_name()) { |
33 | 3.61k | if (header.has_regex_rewrite()) { |
34 | 2.32k | const auto& rewrite_spec = header.regex_rewrite(); |
35 | 2.32k | regex_rewrite_ = Regex::Utility::parseRegex(rewrite_spec.pattern(), regex_engine); |
36 | 2.32k | regex_rewrite_substitution_ = rewrite_spec.substitution(); |
37 | 2.32k | } |
38 | 3.61k | } |
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) { |
86 | 3.51k | for (const auto& attribute : attributes) { |
87 | 3.51k | attributes_.push_back(attribute); |
88 | 3.51k | } |
89 | 2.04k | } |
90 | | |
91 | | absl::optional<uint64_t> evaluate(const Network::Address::Instance*, |
92 | | const RequestHeaderMap& headers, |
93 | | const HashPolicy::AddCookieCallback add_cookie, |
94 | 0 | const StreamInfo::FilterStateSharedPtr) const override { |
95 | 0 | absl::optional<uint64_t> hash; |
96 | 0 | std::string value = Utility::parseCookieValue(headers, key_); |
97 | 0 | if (value.empty() && ttl_.has_value()) { |
98 | 0 | CookieAttributeRefVector attributes; |
99 | 0 | for (const auto& attribute : attributes_) { |
100 | 0 | attributes.push_back(attribute); |
101 | 0 | } |
102 | 0 | value = add_cookie(key_, path_, ttl_.value(), attributes); |
103 | 0 | hash = HashUtil::xxHash64(value); |
104 | |
|
105 | 0 | } else if (!value.empty()) { |
106 | 0 | hash = HashUtil::xxHash64(value); |
107 | 0 | } |
108 | 0 | return hash; |
109 | 0 | } |
110 | | |
111 | | private: |
112 | | const std::string key_; |
113 | | const std::string path_; |
114 | | const absl::optional<std::chrono::seconds> ttl_; |
115 | | std::vector<CookieAttribute> attributes_; |
116 | | }; |
117 | | |
118 | | class IpHashMethod : public HashMethodImplBase { |
119 | | public: |
120 | 1.44k | IpHashMethod(bool terminal) : HashMethodImplBase(terminal) {} |
121 | | |
122 | | absl::optional<uint64_t> evaluate(const Network::Address::Instance* downstream_addr, |
123 | | const RequestHeaderMap&, const HashPolicy::AddCookieCallback, |
124 | 0 | const StreamInfo::FilterStateSharedPtr) const override { |
125 | 0 | if (downstream_addr == nullptr) { |
126 | 0 | return absl::nullopt; |
127 | 0 | } |
128 | 0 | auto* downstream_ip = downstream_addr->ip(); |
129 | 0 | if (downstream_ip == nullptr) { |
130 | 0 | return absl::nullopt; |
131 | 0 | } |
132 | 0 | const auto& downstream_addr_str = downstream_ip->addressAsString(); |
133 | 0 | if (downstream_addr_str.empty()) { |
134 | 0 | return absl::nullopt; |
135 | 0 | } |
136 | 0 | return HashUtil::xxHash64(downstream_addr_str); |
137 | 0 | } |
138 | | }; |
139 | | |
140 | | class QueryParameterHashMethod : public HashMethodImplBase { |
141 | | public: |
142 | | QueryParameterHashMethod(const std::string& parameter_name, bool terminal) |
143 | 2.34k | : HashMethodImplBase(terminal), parameter_name_(parameter_name) {} |
144 | | |
145 | | absl::optional<uint64_t> evaluate(const Network::Address::Instance*, |
146 | | const RequestHeaderMap& headers, |
147 | | const HashPolicy::AddCookieCallback, |
148 | 0 | const StreamInfo::FilterStateSharedPtr) const override { |
149 | 0 | absl::optional<uint64_t> hash; |
150 | |
|
151 | 0 | const HeaderEntry* header = headers.Path(); |
152 | 0 | if (header) { |
153 | 0 | Http::Utility::QueryParamsMulti query_parameters = |
154 | 0 | Http::Utility::QueryParamsMulti::parseQueryString(header->value().getStringView()); |
155 | 0 | const auto val = query_parameters.getFirstValue(parameter_name_); |
156 | 0 | if (val.has_value()) { |
157 | 0 | hash = HashUtil::xxHash64(val.value()); |
158 | 0 | } |
159 | 0 | } |
160 | 0 | return hash; |
161 | 0 | } |
162 | | |
163 | | private: |
164 | | const std::string parameter_name_; |
165 | | }; |
166 | | |
167 | | class FilterStateHashMethod : public HashMethodImplBase { |
168 | | public: |
169 | | FilterStateHashMethod(const std::string& key, bool terminal) |
170 | 2.53k | : HashMethodImplBase(terminal), key_(key) {} |
171 | | |
172 | | absl::optional<uint64_t> |
173 | | evaluate(const Network::Address::Instance*, const RequestHeaderMap&, |
174 | | const HashPolicy::AddCookieCallback, |
175 | 0 | const StreamInfo::FilterStateSharedPtr filter_state) const override { |
176 | 0 | if (auto typed_state = filter_state->getDataReadOnly<Hashable>(key_); typed_state != nullptr) { |
177 | 0 | return typed_state->hash(); |
178 | 0 | } |
179 | 0 | return absl::nullopt; |
180 | 0 | } |
181 | | |
182 | | private: |
183 | | const std::string key_; |
184 | | }; |
185 | | |
186 | | HashPolicyImpl::HashPolicyImpl( |
187 | | absl::Span<const envoy::config::route::v3::RouteAction::HashPolicy* const> hash_policies, |
188 | 7.36k | Regex::Engine& regex_engine) { |
189 | | |
190 | 7.36k | hash_impls_.reserve(hash_policies.size()); |
191 | 14.0k | for (auto* hash_policy : hash_policies) { |
192 | 14.0k | switch (hash_policy->policy_specifier_case()) { |
193 | 3.61k | case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kHeader: |
194 | 3.61k | hash_impls_.emplace_back( |
195 | 3.61k | new HeaderHashMethod(hash_policy->header(), hash_policy->terminal(), regex_engine)); |
196 | 3.61k | break; |
197 | 2.04k | case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kCookie: { |
198 | 2.04k | absl::optional<std::chrono::seconds> ttl; |
199 | 2.04k | if (hash_policy->cookie().has_ttl()) { |
200 | 215 | ttl = std::chrono::seconds(hash_policy->cookie().ttl().seconds()); |
201 | 215 | } |
202 | 2.04k | std::vector<CookieAttribute> attributes; |
203 | 3.51k | for (const auto& attribute : hash_policy->cookie().attributes()) { |
204 | 3.51k | attributes.push_back({attribute.name(), attribute.value()}); |
205 | 3.51k | } |
206 | 2.04k | CookieAttributeRefVector ref_attributes; |
207 | 3.51k | for (const auto& attribute : attributes) { |
208 | 3.51k | ref_attributes.push_back(attribute); |
209 | 3.51k | } |
210 | 2.04k | hash_impls_.emplace_back(new CookieHashMethod(hash_policy->cookie().name(), |
211 | 2.04k | hash_policy->cookie().path(), ttl, |
212 | 2.04k | hash_policy->terminal(), ref_attributes)); |
213 | 2.04k | break; |
214 | 0 | } |
215 | 3.47k | case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase:: |
216 | 3.47k | kConnectionProperties: |
217 | 3.47k | if (hash_policy->connection_properties().source_ip()) { |
218 | 1.44k | hash_impls_.emplace_back(new IpHashMethod(hash_policy->terminal())); |
219 | 1.44k | } |
220 | 3.47k | break; |
221 | 2.34k | case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kQueryParameter: |
222 | 2.34k | hash_impls_.emplace_back(new QueryParameterHashMethod(hash_policy->query_parameter().name(), |
223 | 2.34k | hash_policy->terminal())); |
224 | 2.34k | break; |
225 | 2.53k | case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase::kFilterState: |
226 | 2.53k | hash_impls_.emplace_back( |
227 | 2.53k | new FilterStateHashMethod(hash_policy->filter_state().key(), hash_policy->terminal())); |
228 | 2.53k | break; |
229 | 0 | case envoy::config::route::v3::RouteAction::HashPolicy::PolicySpecifierCase:: |
230 | 0 | POLICY_SPECIFIER_NOT_SET: |
231 | 0 | PANIC("hash policy not set"); |
232 | 14.0k | } |
233 | 14.0k | } |
234 | 7.36k | } |
235 | | |
236 | | absl::optional<uint64_t> |
237 | | HashPolicyImpl::generateHash(const Network::Address::Instance* downstream_addr, |
238 | | const RequestHeaderMap& headers, const AddCookieCallback add_cookie, |
239 | 0 | const StreamInfo::FilterStateSharedPtr filter_state) const { |
240 | 0 | absl::optional<uint64_t> hash; |
241 | 0 | for (const HashMethodPtr& hash_impl : hash_impls_) { |
242 | 0 | const absl::optional<uint64_t> new_hash = |
243 | 0 | hash_impl->evaluate(downstream_addr, headers, add_cookie, filter_state); |
244 | 0 | if (new_hash) { |
245 | | // Rotating the old value prevents duplicate hash rules from cancelling each other out |
246 | | // and preserves all of the entropy |
247 | 0 | const uint64_t old_value = hash ? ((hash.value() << 1) | (hash.value() >> 63)) : 0; |
248 | 0 | hash = old_value ^ new_hash.value(); |
249 | 0 | } |
250 | | // If the policy is a terminal policy and a hash has been generated, ignore |
251 | | // the rest of the hash policies. |
252 | 0 | if (hash_impl->terminal() && hash) { |
253 | 0 | break; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | return hash; |
257 | 0 | } |
258 | | |
259 | | } // namespace Http |
260 | | } // namespace Envoy |