• 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/json.h>
18 #include <grpc/support/port_platform.h>
19 
20 #include <cstdint>
21 #include <memory>
22 #include <random>
23 #include <string>
24 #include <type_traits>
25 #include <utility>
26 
27 #include "absl/log/check.h"
28 #include "absl/log/log.h"
29 #include "absl/status/statusor.h"
30 #include "absl/strings/str_cat.h"
31 #include "absl/strings/string_view.h"
32 #include "absl/strings/strip.h"
33 #include "absl/types/optional.h"
34 #include "src/core/config/core_configuration.h"
35 #include "src/core/lib/channel/channel_args.h"
36 #include "src/core/lib/iomgr/polling_entity.h"
37 #include "src/core/lib/resource_quota/resource_quota.h"
38 #include "src/core/lib/security/credentials/alts/check_gcp_environment.h"
39 #include "src/core/resolver/resolver.h"
40 #include "src/core/resolver/resolver_factory.h"
41 #include "src/core/resolver/resolver_registry.h"
42 #include "src/core/util/debug_location.h"
43 #include "src/core/util/env.h"
44 #include "src/core/util/gcp_metadata_query.h"
45 #include "src/core/util/json/json.h"
46 #include "src/core/util/json/json_writer.h"
47 #include "src/core/util/orphanable.h"
48 #include "src/core/util/ref_counted_ptr.h"
49 #include "src/core/util/time.h"
50 #include "src/core/util/uri.h"
51 #include "src/core/util/work_serializer.h"
52 #include "src/core/xds/grpc/xds_client_grpc.h"
53 #include "src/core/xds/xds_client/xds_bootstrap.h"
54 
55 namespace grpc_core {
56 
57 namespace {
58 
59 const char* kC2PAuthority = "traffic-director-c2p.xds.googleapis.com";
60 
61 class GoogleCloud2ProdResolver final : public Resolver {
62  public:
63   explicit GoogleCloud2ProdResolver(ResolverArgs args);
64 
65   void StartLocked() override;
66   void RequestReresolutionLocked() override;
67   void ResetBackoffLocked() override;
68   void ShutdownLocked() override;
69 
70  private:
71   void ZoneQueryDone(std::string zone);
72   void IPv6QueryDone(bool ipv6_supported);
73   void StartXdsResolver();
74 
75   ResourceQuotaRefPtr resource_quota_;
76   std::shared_ptr<WorkSerializer> work_serializer_;
77   grpc_polling_entity pollent_;
78   bool using_dns_ = false;
79   OrphanablePtr<Resolver> child_resolver_;
80   std::string metadata_server_name_ = "metadata.google.internal.";
81   bool shutdown_ = false;
82 
83   OrphanablePtr<GcpMetadataQuery> zone_query_;
84   absl::optional<std::string> zone_;
85 
86   OrphanablePtr<GcpMetadataQuery> ipv6_query_;
87   absl::optional<bool> supports_ipv6_;
88 };
89 
90 //
91 // GoogleCloud2ProdResolver
92 //
93 
XdsBootstrapConfigured()94 bool XdsBootstrapConfigured() {
95   return GetEnv("GRPC_XDS_BOOTSTRAP").has_value() ||
96          GetEnv("GRPC_XDS_BOOTSTRAP_CONFIG").has_value();
97 }
98 
GoogleCloud2ProdResolver(ResolverArgs args)99 GoogleCloud2ProdResolver::GoogleCloud2ProdResolver(ResolverArgs args)
100     : resource_quota_(args.args.GetObjectRef<ResourceQuota>()),
101       work_serializer_(std::move(args.work_serializer)),
102       pollent_(grpc_polling_entity_create_from_pollset_set(args.pollset_set)) {
103   absl::string_view name_to_resolve = absl::StripPrefix(args.uri.path(), "/");
104   // If we're not running on GCP, we can't use DirectPath, so delegate
105   // to the DNS resolver.
106   const bool test_only_pretend_running_on_gcp =
107       args.args
108           .GetBool("grpc.testing.google_c2p_resolver_pretend_running_on_gcp")
109           .value_or(false);
110   const bool running_on_gcp =
111       test_only_pretend_running_on_gcp || grpc_alts_is_running_on_gcp();
112   const bool federation_enabled = XdsFederationEnabled();
113   if (!running_on_gcp ||
114       // If the client is already using xDS and federation is not enabled,
115       // we can't use it here, because they may be talking to a completely
116       // different xDS server than we want to.
117       // TODO(roth): When we remove xDS federation env var protection,
118       // remove this constraint.
119       (!federation_enabled && XdsBootstrapConfigured())) {
120     using_dns_ = true;
121     child_resolver_ =
122         CoreConfiguration::Get().resolver_registry().CreateResolver(
123             absl::StrCat("dns:", name_to_resolve), args.args, args.pollset_set,
124             work_serializer_, std::move(args.result_handler));
125     CHECK(child_resolver_ != nullptr);
126     return;
127   }
128   // Maybe override metadata server name for testing
129   absl::optional<std::string> test_only_metadata_server_override =
130       args.args.GetOwnedString(
131           "grpc.testing.google_c2p_resolver_metadata_server_override");
132   if (test_only_metadata_server_override.has_value() &&
133       !test_only_metadata_server_override->empty()) {
134     metadata_server_name_ = std::move(*test_only_metadata_server_override);
135   }
136   // Create xds resolver.
137   std::string xds_uri =
138       federation_enabled
139           ? absl::StrCat("xds://", kC2PAuthority, "/", name_to_resolve)
140           : absl::StrCat("xds:", name_to_resolve);
141   child_resolver_ = CoreConfiguration::Get().resolver_registry().CreateResolver(
142       xds_uri, args.args, args.pollset_set, work_serializer_,
143       std::move(args.result_handler));
144   CHECK(child_resolver_ != nullptr);
145 }
146 
StartLocked()147 void GoogleCloud2ProdResolver::StartLocked() {
148   if (using_dns_) {
149     child_resolver_->StartLocked();
150     return;
151   }
152   // Using xDS.  Start metadata server queries.
153   zone_query_ = MakeOrphanable<GcpMetadataQuery>(
154       metadata_server_name_, std::string(GcpMetadataQuery::kZoneAttribute),
155       &pollent_,
156       [resolver = RefAsSubclass<GoogleCloud2ProdResolver>()](
157           std::string /* attribute */,
158           absl::StatusOr<std::string> result) mutable {
159         resolver->work_serializer_->Run(
160             [resolver, result = std::move(result)]() mutable {
161               resolver->ZoneQueryDone(result.ok() ? std::move(result).value()
162                                                   : "");
163             },
164             DEBUG_LOCATION);
165       },
166       Duration::Seconds(10));
167   ipv6_query_ = MakeOrphanable<GcpMetadataQuery>(
168       metadata_server_name_, std::string(GcpMetadataQuery::kIPv6Attribute),
169       &pollent_,
170       [resolver = RefAsSubclass<GoogleCloud2ProdResolver>()](
171           std::string /* attribute */,
172           absl::StatusOr<std::string> result) mutable {
173         resolver->work_serializer_->Run(
174             [resolver, result = std::move(result)]() {
175               // Check that the payload is non-empty in order to work around
176               // the fact that there are buggy implementations of metadata
177               // servers in the wild, which can in some cases return 200
178               // plus an empty result when they should have returned 404.
179               resolver->IPv6QueryDone(result.ok() && !result->empty());
180             },
181             DEBUG_LOCATION);
182       },
183       Duration::Seconds(10));
184 }
185 
RequestReresolutionLocked()186 void GoogleCloud2ProdResolver::RequestReresolutionLocked() {
187   if (child_resolver_ != nullptr) {
188     child_resolver_->RequestReresolutionLocked();
189   }
190 }
191 
ResetBackoffLocked()192 void GoogleCloud2ProdResolver::ResetBackoffLocked() {
193   if (child_resolver_ != nullptr) {
194     child_resolver_->ResetBackoffLocked();
195   }
196 }
197 
ShutdownLocked()198 void GoogleCloud2ProdResolver::ShutdownLocked() {
199   shutdown_ = true;
200   zone_query_.reset();
201   ipv6_query_.reset();
202   child_resolver_.reset();
203 }
204 
ZoneQueryDone(std::string zone)205 void GoogleCloud2ProdResolver::ZoneQueryDone(std::string zone) {
206   zone_query_.reset();
207   zone_ = std::move(zone);
208   if (supports_ipv6_.has_value()) StartXdsResolver();
209 }
210 
IPv6QueryDone(bool ipv6_supported)211 void GoogleCloud2ProdResolver::IPv6QueryDone(bool ipv6_supported) {
212   ipv6_query_.reset();
213   supports_ipv6_ = ipv6_supported;
214   if (zone_.has_value()) StartXdsResolver();
215 }
216 
StartXdsResolver()217 void GoogleCloud2ProdResolver::StartXdsResolver() {
218   if (shutdown_) {
219     return;
220   }
221   // Construct bootstrap JSON.
222   std::random_device rd;
223   std::mt19937 mt(rd());
224   std::uniform_int_distribution<uint64_t> dist(1, UINT64_MAX);
225   Json::Object node = {
226       {"id", Json::FromString(absl::StrCat("C2P-", dist(mt)))},
227   };
228   if (!zone_->empty()) {
229     node["locality"] = Json::FromObject({
230         {"zone", Json::FromString(*zone_)},
231     });
232   };
233   if (*supports_ipv6_) {
234     node["metadata"] = Json::FromObject({
235         {"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", Json::FromBool(true)},
236     });
237   }
238   // Allow the TD server uri to be overridden for testing purposes.
239   auto override_server =
240       GetEnv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI");
241   const char* server_uri =
242       override_server.has_value() && !override_server->empty()
243           ? override_server->c_str()
244           : "directpath-pa.googleapis.com";
245   Json xds_server = Json::FromArray({
246       Json::FromObject({
247           {"server_uri", Json::FromString(server_uri)},
248           {"channel_creds",
249            Json::FromArray({
250                Json::FromObject({
251                    {"type", Json::FromString("google_default")},
252                }),
253            })},
254           {"server_features",
255            Json::FromArray({Json::FromString("ignore_resource_deletion")})},
256       }),
257   });
258   Json bootstrap = Json::FromObject({
259       {"xds_servers", xds_server},
260       {"authorities",
261        Json::FromObject({
262            {kC2PAuthority, Json::FromObject({
263                                {"xds_servers", std::move(xds_server)},
264                            })},
265        })},
266       {"node", Json::FromObject(std::move(node))},
267   });
268   // Inject bootstrap JSON as fallback config.
269   internal::SetXdsFallbackBootstrapConfig(JsonDump(bootstrap).c_str());
270   // Now start xDS resolver.
271   child_resolver_->StartLocked();
272 }
273 
274 //
275 // Factory
276 //
277 
278 class GoogleCloud2ProdResolverFactory final : public ResolverFactory {
279  public:
scheme() const280   absl::string_view scheme() const override { return "google-c2p"; }
281 
IsValidUri(const URI & uri) const282   bool IsValidUri(const URI& uri) const override {
283     if (GPR_UNLIKELY(!uri.authority().empty())) {
284       LOG(ERROR) << "google-c2p URI scheme does not support authorities";
285       return false;
286     }
287     return true;
288   }
289 
CreateResolver(ResolverArgs args) const290   OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override {
291     if (!IsValidUri(args.uri)) return nullptr;
292     return MakeOrphanable<GoogleCloud2ProdResolver>(std::move(args));
293   }
294 };
295 
296 // TODO(apolcyn): remove this class after user code has updated to the
297 // stable "google-c2p" URI scheme.
298 class ExperimentalGoogleCloud2ProdResolverFactory final
299     : public ResolverFactory {
300  public:
scheme() const301   absl::string_view scheme() const override {
302     return "google-c2p-experimental";
303   }
304 
IsValidUri(const URI & uri) const305   bool IsValidUri(const URI& uri) const override {
306     if (GPR_UNLIKELY(!uri.authority().empty())) {
307       LOG(ERROR) << "google-c2p-experimental URI scheme does not support "
308                     "authorities";
309       return false;
310     }
311     return true;
312   }
313 
CreateResolver(ResolverArgs args) const314   OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override {
315     if (!IsValidUri(args.uri)) return nullptr;
316     return MakeOrphanable<GoogleCloud2ProdResolver>(std::move(args));
317   }
318 };
319 
320 }  // namespace
321 
RegisterCloud2ProdResolver(CoreConfiguration::Builder * builder)322 void RegisterCloud2ProdResolver(CoreConfiguration::Builder* builder) {
323   builder->resolver_registry()->RegisterResolverFactory(
324       std::make_unique<GoogleCloud2ProdResolverFactory>());
325   builder->resolver_registry()->RegisterResolverFactory(
326       std::make_unique<ExperimentalGoogleCloud2ProdResolverFactory>());
327 }
328 
329 }  // namespace grpc_core
330