1 //
2 // Copyright 2021 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include <grpc/support/port_platform.h>
18
19 #include "src/core/ext/filters/client_channel/resolver_registry.h"
20 #include "src/core/ext/xds/xds_client.h"
21 #include "src/core/lib/gpr/env.h"
22 #include "src/core/lib/gpr/string.h"
23 #include "src/core/lib/http/httpcli.h"
24 #include "src/core/lib/iomgr/polling_entity.h"
25 #include "src/core/lib/security/credentials/alts/check_gcp_environment.h"
26
27 namespace grpc_core {
28
29 namespace {
30
31 class GoogleCloud2ProdResolver : public Resolver {
32 public:
33 explicit GoogleCloud2ProdResolver(ResolverArgs args);
34
35 void StartLocked() override;
36 void RequestReresolutionLocked() override;
37 void ResetBackoffLocked() override;
38 void ShutdownLocked() override;
39
40 private:
41 // Represents an HTTP request to the metadata server.
42 class MetadataQuery : public InternallyRefCounted<MetadataQuery> {
43 public:
44 MetadataQuery(RefCountedPtr<GoogleCloud2ProdResolver> resolver,
45 const char* path, grpc_polling_entity* pollent);
46 ~MetadataQuery() override;
47
48 void Orphan() override;
49
50 private:
51 static void OnHttpRequestDone(void* arg, grpc_error_handle error);
52
53 // Calls OnDone() if not already called. Releases a ref.
54 void MaybeCallOnDone(grpc_error_handle error);
55
56 // If error is not GRPC_ERROR_NONE, then it's not safe to look at response.
57 virtual void OnDone(GoogleCloud2ProdResolver* resolver,
58 const grpc_http_response* response,
59 grpc_error_handle error) = 0;
60
61 RefCountedPtr<GoogleCloud2ProdResolver> resolver_;
62 grpc_httpcli_context context_;
63 grpc_httpcli_response response_;
64 grpc_closure on_done_;
65 Atomic<bool> on_done_called_{false};
66 };
67
68 // A metadata server query to get the zone.
69 class ZoneQuery : public MetadataQuery {
70 public:
71 ZoneQuery(RefCountedPtr<GoogleCloud2ProdResolver> resolver,
72 grpc_polling_entity* pollent);
73
74 private:
75 void OnDone(GoogleCloud2ProdResolver* resolver,
76 const grpc_http_response* response,
77 grpc_error_handle error) override;
78 };
79
80 // A metadata server query to get the IPv6 address.
81 class IPv6Query : public MetadataQuery {
82 public:
83 IPv6Query(RefCountedPtr<GoogleCloud2ProdResolver> resolver,
84 grpc_polling_entity* pollent);
85
86 private:
87 void OnDone(GoogleCloud2ProdResolver* resolver,
88 const grpc_http_response* response,
89 grpc_error_handle error) override;
90 };
91
92 void ZoneQueryDone(std::string zone);
93 void IPv6QueryDone(bool ipv6_supported);
94 void StartXdsResolver();
95
96 std::shared_ptr<WorkSerializer> work_serializer_;
97 grpc_polling_entity pollent_;
98 bool using_dns_ = false;
99 OrphanablePtr<Resolver> child_resolver_;
100
101 OrphanablePtr<ZoneQuery> zone_query_;
102 absl::optional<std::string> zone_;
103
104 OrphanablePtr<IPv6Query> ipv6_query_;
105 absl::optional<bool> supports_ipv6_;
106 };
107
108 //
109 // GoogleCloud2ProdResolver::MetadataQuery
110 //
111
MetadataQuery(RefCountedPtr<GoogleCloud2ProdResolver> resolver,const char * path,grpc_polling_entity * pollent)112 GoogleCloud2ProdResolver::MetadataQuery::MetadataQuery(
113 RefCountedPtr<GoogleCloud2ProdResolver> resolver, const char* path,
114 grpc_polling_entity* pollent)
115 : resolver_(std::move(resolver)) {
116 grpc_httpcli_context_init(&context_);
117 // Start HTTP request.
118 GRPC_CLOSURE_INIT(&on_done_, OnHttpRequestDone, this, nullptr);
119 Ref().release(); // Ref held by callback.
120 grpc_httpcli_request request;
121 memset(&request, 0, sizeof(grpc_httpcli_request));
122 grpc_http_header header = {const_cast<char*>("Metadata-Flavor"),
123 const_cast<char*>("Google")};
124 request.host = const_cast<char*>("metadata.google.internal");
125 request.http.path = const_cast<char*>(path);
126 request.http.hdr_count = 1;
127 request.http.hdrs = &header;
128 grpc_resource_quota* resource_quota =
129 grpc_resource_quota_create("c2p_resolver");
130 grpc_httpcli_get(&context_, pollent, resource_quota, &request,
131 ExecCtx::Get()->Now() + 10000, // 10s timeout
132 &on_done_, &response_);
133 grpc_resource_quota_unref_internal(resource_quota);
134 }
135
~MetadataQuery()136 GoogleCloud2ProdResolver::MetadataQuery::~MetadataQuery() {
137 grpc_httpcli_context_destroy(&context_);
138 grpc_http_response_destroy(&response_);
139 }
140
Orphan()141 void GoogleCloud2ProdResolver::MetadataQuery::Orphan() {
142 // TODO(roth): Once the HTTP client library supports cancellation,
143 // use that here.
144 MaybeCallOnDone(GRPC_ERROR_CANCELLED);
145 }
146
OnHttpRequestDone(void * arg,grpc_error_handle error)147 void GoogleCloud2ProdResolver::MetadataQuery::OnHttpRequestDone(
148 void* arg, grpc_error_handle error) {
149 auto* self = static_cast<MetadataQuery*>(arg);
150 self->MaybeCallOnDone(GRPC_ERROR_REF(error));
151 }
152
MaybeCallOnDone(grpc_error_handle error)153 void GoogleCloud2ProdResolver::MetadataQuery::MaybeCallOnDone(
154 grpc_error_handle error) {
155 bool expected = false;
156 if (!on_done_called_.CompareExchangeStrong(
157 &expected, true, MemoryOrder::RELAXED, MemoryOrder::RELAXED)) {
158 // We've already called OnDone(), so just clean up.
159 GRPC_ERROR_UNREF(error);
160 Unref();
161 return;
162 }
163 // Hop back into WorkSerializer to call OnDone().
164 // Note: We implicitly pass our ref to the callback here.
165 resolver_->work_serializer_->Run(
166 [this, error]() {
167 OnDone(resolver_.get(), &response_, error);
168 Unref();
169 },
170 DEBUG_LOCATION);
171 }
172
173 //
174 // GoogleCloud2ProdResolver::ZoneQuery
175 //
176
ZoneQuery(RefCountedPtr<GoogleCloud2ProdResolver> resolver,grpc_polling_entity * pollent)177 GoogleCloud2ProdResolver::ZoneQuery::ZoneQuery(
178 RefCountedPtr<GoogleCloud2ProdResolver> resolver,
179 grpc_polling_entity* pollent)
180 : MetadataQuery(std::move(resolver), "/computeMetadata/v1/instance/zone",
181 pollent) {}
182
OnDone(GoogleCloud2ProdResolver * resolver,const grpc_http_response * response,grpc_error_handle error)183 void GoogleCloud2ProdResolver::ZoneQuery::OnDone(
184 GoogleCloud2ProdResolver* resolver, const grpc_http_response* response,
185 grpc_error_handle error) {
186 if (error != GRPC_ERROR_NONE) {
187 gpr_log(GPR_ERROR, "error fetching zone from metadata server: %s",
188 grpc_error_std_string(error).c_str());
189 }
190 std::string zone;
191 if (error == GRPC_ERROR_NONE && response->status == 200) {
192 absl::string_view body(response->body, response->body_length);
193 size_t i = body.find_last_of('/');
194 if (i == body.npos) {
195 gpr_log(GPR_ERROR, "could not parse zone from metadata server: %s",
196 std::string(body).c_str());
197 } else {
198 zone = std::string(body.substr(i));
199 }
200 }
201 resolver->ZoneQueryDone(std::move(zone));
202 GRPC_ERROR_UNREF(error);
203 }
204
205 //
206 // GoogleCloud2ProdResolver::IPv6Query
207 //
208
IPv6Query(RefCountedPtr<GoogleCloud2ProdResolver> resolver,grpc_polling_entity * pollent)209 GoogleCloud2ProdResolver::IPv6Query::IPv6Query(
210 RefCountedPtr<GoogleCloud2ProdResolver> resolver,
211 grpc_polling_entity* pollent)
212 : MetadataQuery(std::move(resolver),
213 "/computeMetadata/v1/instance/network-interfaces/0/ipv6s",
214 pollent) {}
215
OnDone(GoogleCloud2ProdResolver * resolver,const grpc_http_response * response,grpc_error_handle error)216 void GoogleCloud2ProdResolver::IPv6Query::OnDone(
217 GoogleCloud2ProdResolver* resolver, const grpc_http_response* response,
218 grpc_error_handle error) {
219 if (error != GRPC_ERROR_NONE) {
220 gpr_log(GPR_ERROR, "error fetching IPv6 address from metadata server: %s",
221 grpc_error_std_string(error).c_str());
222 }
223 resolver->IPv6QueryDone(error == GRPC_ERROR_NONE && response->status == 200);
224 GRPC_ERROR_UNREF(error);
225 }
226
227 //
228 // GoogleCloud2ProdResolver
229 //
230
GoogleCloud2ProdResolver(ResolverArgs args)231 GoogleCloud2ProdResolver::GoogleCloud2ProdResolver(ResolverArgs args)
232 : work_serializer_(std::move(args.work_serializer)),
233 pollent_(grpc_polling_entity_create_from_pollset_set(args.pollset_set)) {
234 absl::string_view name_to_resolve = absl::StripPrefix(args.uri.path(), "/");
235 // If we're not running on GCP, we can't use DirectPath, so delegate
236 // to the DNS resolver.
237 if (!grpc_alts_is_running_on_gcp() ||
238 // If the client is already using xDS, we can't use it here, because
239 // they may be talking to a completely different xDS server than we
240 // want to.
241 // TODO(roth): When we implement xDS federation, remove this constraint.
242 UniquePtr<char>(gpr_getenv("GRPC_XDS_BOOTSTRAP")) != nullptr ||
243 UniquePtr<char>(gpr_getenv("GRPC_XDS_BOOTSTRAP_CONFIG")) != nullptr) {
244 using_dns_ = true;
245 child_resolver_ = ResolverRegistry::CreateResolver(
246 absl::StrCat("dns:", name_to_resolve).c_str(), args.args,
247 args.pollset_set, work_serializer_, std::move(args.result_handler));
248 GPR_ASSERT(child_resolver_ != nullptr);
249 return;
250 }
251 // Create xds resolver.
252 child_resolver_ = ResolverRegistry::CreateResolver(
253 absl::StrCat("xds:", name_to_resolve).c_str(), args.args,
254 args.pollset_set, work_serializer_, std::move(args.result_handler));
255 GPR_ASSERT(child_resolver_ != nullptr);
256 }
257
StartLocked()258 void GoogleCloud2ProdResolver::StartLocked() {
259 if (using_dns_) {
260 child_resolver_->StartLocked();
261 return;
262 }
263 // Using xDS. Start metadata server queries.
264 zone_query_ = MakeOrphanable<ZoneQuery>(Ref(), &pollent_);
265 ipv6_query_ = MakeOrphanable<IPv6Query>(Ref(), &pollent_);
266 }
267
RequestReresolutionLocked()268 void GoogleCloud2ProdResolver::RequestReresolutionLocked() {
269 if (child_resolver_ != nullptr) {
270 child_resolver_->RequestReresolutionLocked();
271 }
272 }
273
ResetBackoffLocked()274 void GoogleCloud2ProdResolver::ResetBackoffLocked() {
275 if (child_resolver_ != nullptr) {
276 child_resolver_->ResetBackoffLocked();
277 }
278 }
279
ShutdownLocked()280 void GoogleCloud2ProdResolver::ShutdownLocked() {
281 zone_query_.reset();
282 ipv6_query_.reset();
283 child_resolver_.reset();
284 }
285
ZoneQueryDone(std::string zone)286 void GoogleCloud2ProdResolver::ZoneQueryDone(std::string zone) {
287 zone_query_.reset();
288 zone_ = std::move(zone);
289 if (supports_ipv6_.has_value()) StartXdsResolver();
290 }
291
IPv6QueryDone(bool ipv6_supported)292 void GoogleCloud2ProdResolver::IPv6QueryDone(bool ipv6_supported) {
293 ipv6_query_.reset();
294 supports_ipv6_ = ipv6_supported;
295 if (zone_.has_value()) StartXdsResolver();
296 }
297
StartXdsResolver()298 void GoogleCloud2ProdResolver::StartXdsResolver() {
299 // Construct bootstrap JSON.
300 Json::Object node = {
301 {"id", "C2P"},
302 };
303 if (!zone_->empty()) {
304 node["locality"] = Json::Object{
305 {"zone", *zone_},
306 };
307 };
308 if (*supports_ipv6_) {
309 node["metadata"] = Json::Object{
310 {"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true},
311 };
312 }
313 // Allow the TD server uri to be overridden for testing purposes.
314 UniquePtr<char> override_server(
315 gpr_getenv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI"));
316 const char* server_uri =
317 override_server != nullptr && strlen(override_server.get()) > 0
318 ? override_server.get()
319 : "directpath-trafficdirector.googleapis.com";
320 Json bootstrap = Json::Object{
321 {"xds_servers",
322 Json::Array{
323 Json::Object{
324 {"server_uri", server_uri},
325 {"channel_creds",
326 Json::Array{
327 Json::Object{
328 {"type", "google_default"},
329 },
330 }},
331 {"server_features", Json::Array{"xds_v3"}},
332 },
333 }},
334 {"node", std::move(node)},
335 };
336 // Inject bootstrap JSON as fallback config.
337 internal::SetXdsFallbackBootstrapConfig(bootstrap.Dump().c_str());
338 // Now start xDS resolver.
339 child_resolver_->StartLocked();
340 }
341
342 //
343 // Factory
344 //
345
346 class GoogleCloud2ProdResolverFactory : public ResolverFactory {
347 public:
IsValidUri(const URI & uri) const348 bool IsValidUri(const URI& uri) const override {
349 if (GPR_UNLIKELY(!uri.authority().empty())) {
350 gpr_log(GPR_ERROR, "google-c2p URI scheme does not support authorities");
351 return false;
352 }
353 return true;
354 }
355
CreateResolver(ResolverArgs args) const356 OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override {
357 if (!IsValidUri(args.uri)) return nullptr;
358 return MakeOrphanable<GoogleCloud2ProdResolver>(std::move(args));
359 }
360
scheme() const361 const char* scheme() const override { return "google-c2p"; }
362 };
363
364 } // namespace
365
GoogleCloud2ProdResolverInit()366 void GoogleCloud2ProdResolverInit() {
367 // TODO(roth): Remove env var protection once this code is proven stable.
368 UniquePtr<char> value(gpr_getenv("GRPC_EXPERIMENTAL_GOOGLE_C2P_RESOLVER"));
369 bool parsed_value;
370 bool parse_succeeded = gpr_parse_bool_value(value.get(), &parsed_value);
371 if (parse_succeeded && parsed_value) {
372 ResolverRegistry::Builder::RegisterResolverFactory(
373 absl::make_unique<GoogleCloud2ProdResolverFactory>());
374 }
375 }
376
GoogleCloud2ProdResolverShutdown()377 void GoogleCloud2ProdResolverShutdown() {}
378
379 } // namespace grpc_core
380