/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 |