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