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