• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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