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