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