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