1 //
2 //
3 // Copyright 2021 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18
19 #include "src/core/xds/grpc/xds_routing.h"
20
21 #include <grpc/support/port_platform.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24
25 #include <algorithm>
26 #include <cctype>
27 #include <utility>
28
29 #include "absl/log/check.h"
30 #include "absl/status/status.h"
31 #include "absl/status/statusor.h"
32 #include "absl/strings/match.h"
33 #include "absl/strings/str_cat.h"
34 #include "src/core/lib/channel/channel_args.h"
35 #include "src/core/util/matchers.h"
36 #include "src/core/xds/grpc/xds_http_filter.h"
37
38 namespace grpc_core {
39
40 namespace {
41 enum MatchType {
42 EXACT_MATCH,
43 SUFFIX_MATCH,
44 PREFIX_MATCH,
45 UNIVERSE_MATCH,
46 INVALID_MATCH,
47 };
48
49 // Returns true if match succeeds.
DomainMatch(MatchType match_type,absl::string_view domain_pattern_in,absl::string_view expected_host_name_in)50 bool DomainMatch(MatchType match_type, absl::string_view domain_pattern_in,
51 absl::string_view expected_host_name_in) {
52 // Normalize the args to lower-case. Domain matching is case-insensitive.
53 std::string domain_pattern = std::string(domain_pattern_in);
54 std::string expected_host_name = std::string(expected_host_name_in);
55 std::transform(domain_pattern.begin(), domain_pattern.end(),
56 domain_pattern.begin(),
57 [](unsigned char c) { return std::tolower(c); });
58 std::transform(expected_host_name.begin(), expected_host_name.end(),
59 expected_host_name.begin(),
60 [](unsigned char c) { return std::tolower(c); });
61 if (match_type == EXACT_MATCH) {
62 return domain_pattern == expected_host_name;
63 } else if (match_type == SUFFIX_MATCH) {
64 // Asterisk must match at least one char.
65 if (expected_host_name.size() < domain_pattern.size()) return false;
66 absl::string_view pattern_suffix(domain_pattern.c_str() + 1);
67 absl::string_view host_suffix(expected_host_name.c_str() +
68 expected_host_name.size() -
69 pattern_suffix.size());
70 return pattern_suffix == host_suffix;
71 } else if (match_type == PREFIX_MATCH) {
72 // Asterisk must match at least one char.
73 if (expected_host_name.size() < domain_pattern.size()) return false;
74 absl::string_view pattern_prefix(domain_pattern.c_str(),
75 domain_pattern.size() - 1);
76 absl::string_view host_prefix(expected_host_name.c_str(),
77 pattern_prefix.size());
78 return pattern_prefix == host_prefix;
79 } else {
80 return match_type == UNIVERSE_MATCH;
81 }
82 }
83
DomainPatternMatchType(absl::string_view domain_pattern)84 MatchType DomainPatternMatchType(absl::string_view domain_pattern) {
85 if (domain_pattern.empty()) return INVALID_MATCH;
86 if (!absl::StrContains(domain_pattern, '*')) return EXACT_MATCH;
87 if (domain_pattern == "*") return UNIVERSE_MATCH;
88 if (domain_pattern[0] == '*') return SUFFIX_MATCH;
89 if (domain_pattern[domain_pattern.size() - 1] == '*') return PREFIX_MATCH;
90 return INVALID_MATCH;
91 }
92
93 } // namespace
94
FindVirtualHostForDomain(const VirtualHostListIterator & vhost_iterator,absl::string_view domain)95 absl::optional<size_t> XdsRouting::FindVirtualHostForDomain(
96 const VirtualHostListIterator& vhost_iterator, absl::string_view domain) {
97 // Find the best matched virtual host.
98 // The search order for 4 groups of domain patterns:
99 // 1. Exact match.
100 // 2. Suffix match (e.g., "*ABC").
101 // 3. Prefix match (e.g., "ABC*").
102 // 4. Universe match (i.e., "*").
103 // Within each group, longest match wins.
104 // If the same best matched domain pattern appears in multiple virtual
105 // hosts, the first matched virtual host wins.
106 absl::optional<size_t> target_index;
107 MatchType best_match_type = INVALID_MATCH;
108 size_t longest_match = 0;
109 // Check each domain pattern in each virtual host to determine the best
110 // matched virtual host.
111 for (size_t i = 0; i < vhost_iterator.Size(); ++i) {
112 const auto& domains = vhost_iterator.GetDomainsForVirtualHost(i);
113 for (const std::string& domain_pattern : domains) {
114 // Check the match type first. Skip the pattern if it's not better
115 // than current match.
116 const MatchType match_type = DomainPatternMatchType(domain_pattern);
117 // This should be caught by RouteConfigParse().
118 CHECK(match_type != INVALID_MATCH);
119 if (match_type > best_match_type) continue;
120 if (match_type == best_match_type &&
121 domain_pattern.size() <= longest_match) {
122 continue;
123 }
124 // Skip if match fails.
125 if (!DomainMatch(match_type, domain_pattern, domain)) continue;
126 // Choose this match.
127 target_index = i;
128 best_match_type = match_type;
129 longest_match = domain_pattern.size();
130 if (best_match_type == EXACT_MATCH) break;
131 }
132 if (best_match_type == EXACT_MATCH) break;
133 }
134 return target_index;
135 }
136
137 namespace {
138
HeadersMatch(const std::vector<HeaderMatcher> & header_matchers,grpc_metadata_batch * initial_metadata)139 bool HeadersMatch(const std::vector<HeaderMatcher>& header_matchers,
140 grpc_metadata_batch* initial_metadata) {
141 for (const auto& header_matcher : header_matchers) {
142 std::string concatenated_value;
143 if (!header_matcher.Match(XdsRouting::GetHeaderValue(
144 initial_metadata, header_matcher.name(), &concatenated_value))) {
145 return false;
146 }
147 }
148 return true;
149 }
150
UnderFraction(const uint32_t fraction_per_million)151 bool UnderFraction(const uint32_t fraction_per_million) {
152 // Generate a random number in [0, 1000000).
153 const uint32_t random_number = rand() % 1000000;
154 return random_number < fraction_per_million;
155 }
156
157 } // namespace
158
GetRouteForRequest(const RouteListIterator & route_list_iterator,absl::string_view path,grpc_metadata_batch * initial_metadata)159 absl::optional<size_t> XdsRouting::GetRouteForRequest(
160 const RouteListIterator& route_list_iterator, absl::string_view path,
161 grpc_metadata_batch* initial_metadata) {
162 for (size_t i = 0; i < route_list_iterator.Size(); ++i) {
163 const XdsRouteConfigResource::Route::Matchers& matchers =
164 route_list_iterator.GetMatchersForRoute(i);
165 if (matchers.path_matcher.Match(path) &&
166 HeadersMatch(matchers.header_matchers, initial_metadata) &&
167 (!matchers.fraction_per_million.has_value() ||
168 UnderFraction(*matchers.fraction_per_million))) {
169 return i;
170 }
171 }
172 return absl::nullopt;
173 }
174
IsValidDomainPattern(absl::string_view domain_pattern)175 bool XdsRouting::IsValidDomainPattern(absl::string_view domain_pattern) {
176 return DomainPatternMatchType(domain_pattern) != INVALID_MATCH;
177 }
178
GetHeaderValue(grpc_metadata_batch * initial_metadata,absl::string_view header_name,std::string * concatenated_value)179 absl::optional<absl::string_view> XdsRouting::GetHeaderValue(
180 grpc_metadata_batch* initial_metadata, absl::string_view header_name,
181 std::string* concatenated_value) {
182 // Note: If we ever allow binary headers here, we still need to
183 // special-case ignore "grpc-tags-bin" and "grpc-trace-bin", since
184 // they are not visible to the LB policy in grpc-go.
185 if (absl::EndsWith(header_name, "-bin")) {
186 return absl::nullopt;
187 } else if (header_name == "content-type") {
188 return "application/grpc";
189 }
190 return initial_metadata->GetStringValue(header_name, concatenated_value);
191 }
192
193 namespace {
194
FindFilterConfigOverride(const std::string & instance_name,const XdsRouteConfigResource::VirtualHost & vhost,const XdsRouteConfigResource::Route & route,const XdsRouteConfigResource::Route::RouteAction::ClusterWeight * cluster_weight)195 const XdsHttpFilterImpl::FilterConfig* FindFilterConfigOverride(
196 const std::string& instance_name,
197 const XdsRouteConfigResource::VirtualHost& vhost,
198 const XdsRouteConfigResource::Route& route,
199 const XdsRouteConfigResource::Route::RouteAction::ClusterWeight*
200 cluster_weight) {
201 // Check ClusterWeight, if any.
202 if (cluster_weight != nullptr) {
203 auto it = cluster_weight->typed_per_filter_config.find(instance_name);
204 if (it != cluster_weight->typed_per_filter_config.end()) return &it->second;
205 }
206 // Check Route.
207 auto it = route.typed_per_filter_config.find(instance_name);
208 if (it != route.typed_per_filter_config.end()) return &it->second;
209 // Check VirtualHost.
210 it = vhost.typed_per_filter_config.find(instance_name);
211 if (it != vhost.typed_per_filter_config.end()) return &it->second;
212 // Not found.
213 return nullptr;
214 }
215
216 absl::StatusOr<XdsRouting::GeneratePerHttpFilterConfigsResult>
GeneratePerHTTPFilterConfigs(const XdsHttpFilterRegistry & http_filter_registry,const std::vector<XdsListenerResource::HttpConnectionManager::HttpFilter> & http_filters,const ChannelArgs & args,absl::FunctionRef<absl::StatusOr<XdsHttpFilterImpl::ServiceConfigJsonEntry> (const XdsHttpFilterImpl &,const XdsListenerResource::HttpConnectionManager::HttpFilter &)> generate_service_config)217 GeneratePerHTTPFilterConfigs(
218 const XdsHttpFilterRegistry& http_filter_registry,
219 const std::vector<XdsListenerResource::HttpConnectionManager::HttpFilter>&
220 http_filters,
221 const ChannelArgs& args,
222 absl::FunctionRef<absl::StatusOr<XdsHttpFilterImpl::ServiceConfigJsonEntry>(
223 const XdsHttpFilterImpl&,
224 const XdsListenerResource::HttpConnectionManager::HttpFilter&)>
225 generate_service_config) {
226 XdsRouting::GeneratePerHttpFilterConfigsResult result;
227 result.args = args;
228 for (const auto& http_filter : http_filters) {
229 // Find filter. This is guaranteed to succeed, because it's checked
230 // at config validation time in the listener parsing code.
231 const XdsHttpFilterImpl* filter_impl =
232 http_filter_registry.GetFilterForType(
233 http_filter.config.config_proto_type_name);
234 CHECK_NE(filter_impl, nullptr);
235 // If there is not actually any C-core filter associated with this
236 // xDS filter, then it won't need any config, so skip it.
237 if (filter_impl->channel_filter() == nullptr) continue;
238 // Allow filter to add channel args that may affect service config
239 // parsing.
240 result.args = filter_impl->ModifyChannelArgs(result.args);
241 // Generate service config for filter.
242 auto service_config_field =
243 generate_service_config(*filter_impl, http_filter);
244 if (!service_config_field.ok()) {
245 return absl::FailedPreconditionError(absl::StrCat(
246 "failed to generate service config for HTTP filter ",
247 http_filter.name, ": ", service_config_field.status().ToString()));
248 }
249 if (service_config_field->service_config_field_name.empty()) continue;
250 result.per_filter_configs[service_config_field->service_config_field_name]
251 .push_back(service_config_field->element);
252 }
253 return result;
254 }
255
256 } // namespace
257
258 absl::StatusOr<XdsRouting::GeneratePerHttpFilterConfigsResult>
GeneratePerHTTPFilterConfigsForMethodConfig(const XdsHttpFilterRegistry & http_filter_registry,const std::vector<XdsListenerResource::HttpConnectionManager::HttpFilter> & http_filters,const XdsRouteConfigResource::VirtualHost & vhost,const XdsRouteConfigResource::Route & route,const XdsRouteConfigResource::Route::RouteAction::ClusterWeight * cluster_weight,const ChannelArgs & args)259 XdsRouting::GeneratePerHTTPFilterConfigsForMethodConfig(
260 const XdsHttpFilterRegistry& http_filter_registry,
261 const std::vector<XdsListenerResource::HttpConnectionManager::HttpFilter>&
262 http_filters,
263 const XdsRouteConfigResource::VirtualHost& vhost,
264 const XdsRouteConfigResource::Route& route,
265 const XdsRouteConfigResource::Route::RouteAction::ClusterWeight*
266 cluster_weight,
267 const ChannelArgs& args) {
268 return GeneratePerHTTPFilterConfigs(
269 http_filter_registry, http_filters, args,
270 [&](const XdsHttpFilterImpl& filter_impl,
271 const XdsListenerResource::HttpConnectionManager::HttpFilter&
272 http_filter) {
273 const XdsHttpFilterImpl::FilterConfig* config_override =
274 FindFilterConfigOverride(http_filter.name, vhost, route,
275 cluster_weight);
276 // Generate service config for filter.
277 return filter_impl.GenerateMethodConfig(http_filter.config,
278 config_override);
279 });
280 }
281
282 absl::StatusOr<XdsRouting::GeneratePerHttpFilterConfigsResult>
GeneratePerHTTPFilterConfigsForServiceConfig(const XdsHttpFilterRegistry & http_filter_registry,const std::vector<XdsListenerResource::HttpConnectionManager::HttpFilter> & http_filters,const ChannelArgs & args)283 XdsRouting::GeneratePerHTTPFilterConfigsForServiceConfig(
284 const XdsHttpFilterRegistry& http_filter_registry,
285 const std::vector<XdsListenerResource::HttpConnectionManager::HttpFilter>&
286 http_filters,
287 const ChannelArgs& args) {
288 return GeneratePerHTTPFilterConfigs(
289 http_filter_registry, http_filters, args,
290 [&](const XdsHttpFilterImpl& filter_impl,
291 const XdsListenerResource::HttpConnectionManager::HttpFilter&
292 http_filter) {
293 return filter_impl.GenerateServiceConfig(http_filter.config);
294 });
295 }
296
297 } // namespace grpc_core
298