// // Copyright 2021 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include #include #include #include #include #include #include #include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" #include "absl/types/optional.h" #include #include #include #include "src/core/ext/xds/xds_bootstrap.h" #include "src/core/ext/xds/xds_client_grpc.h" #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/config/core_configuration.h" #include "src/core/lib/gprpp/debug_location.h" #include "src/core/lib/gprpp/env.h" #include "src/core/lib/gprpp/orphanable.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/gprpp/status_helper.h" #include "src/core/lib/gprpp/time.h" #include "src/core/lib/gprpp/work_serializer.h" #include "src/core/lib/http/httpcli.h" #include "src/core/lib/http/parser.h" #include "src/core/lib/iomgr/closure.h" #include "src/core/lib/iomgr/error.h" #include "src/core/lib/iomgr/polling_entity.h" #include "src/core/lib/json/json.h" #include "src/core/lib/resolver/resolver.h" #include "src/core/lib/resolver/resolver_factory.h" #include "src/core/lib/resolver/resolver_registry.h" #include "src/core/lib/resource_quota/resource_quota.h" #include "src/core/lib/security/credentials/alts/check_gcp_environment.h" #include "src/core/lib/security/credentials/credentials.h" #include "src/core/lib/uri/uri_parser.h" namespace grpc_core { namespace { const char* kC2PAuthority = "traffic-director-c2p.xds.googleapis.com"; class GoogleCloud2ProdResolver : public Resolver { public: explicit GoogleCloud2ProdResolver(ResolverArgs args); void StartLocked() override; void RequestReresolutionLocked() override; void ResetBackoffLocked() override; void ShutdownLocked() override; private: // Represents an HTTP request to the metadata server. class MetadataQuery : public InternallyRefCounted { public: MetadataQuery(RefCountedPtr resolver, const char* path, grpc_polling_entity* pollent); ~MetadataQuery() override; void Orphan() override; private: static void OnHttpRequestDone(void* arg, grpc_error_handle error); // If error is not absl::OkStatus(), then it's not safe to look at response. virtual void OnDone(GoogleCloud2ProdResolver* resolver, const grpc_http_response* response, grpc_error_handle error) = 0; RefCountedPtr resolver_; OrphanablePtr http_request_; grpc_http_response response_; grpc_closure on_done_; }; // A metadata server query to get the zone. class ZoneQuery : public MetadataQuery { public: ZoneQuery(RefCountedPtr resolver, grpc_polling_entity* pollent); private: void OnDone(GoogleCloud2ProdResolver* resolver, const grpc_http_response* response, grpc_error_handle error) override; }; // A metadata server query to get the IPv6 address. class IPv6Query : public MetadataQuery { public: IPv6Query(RefCountedPtr resolver, grpc_polling_entity* pollent); private: void OnDone(GoogleCloud2ProdResolver* resolver, const grpc_http_response* response, grpc_error_handle error) override; }; void ZoneQueryDone(std::string zone); void IPv6QueryDone(bool ipv6_supported); void StartXdsResolver(); ResourceQuotaRefPtr resource_quota_; std::shared_ptr work_serializer_; grpc_polling_entity pollent_; bool using_dns_ = false; OrphanablePtr child_resolver_; std::string metadata_server_name_ = "metadata.google.internal."; bool shutdown_ = false; OrphanablePtr zone_query_; absl::optional zone_; OrphanablePtr ipv6_query_; absl::optional supports_ipv6_; }; // // GoogleCloud2ProdResolver::MetadataQuery // GoogleCloud2ProdResolver::MetadataQuery::MetadataQuery( RefCountedPtr resolver, const char* path, grpc_polling_entity* pollent) : resolver_(std::move(resolver)) { // Start HTTP request. GRPC_CLOSURE_INIT(&on_done_, OnHttpRequestDone, this, nullptr); Ref().release(); // Ref held by callback. grpc_http_request request; memset(&request, 0, sizeof(grpc_http_request)); grpc_http_header header = {const_cast("Metadata-Flavor"), const_cast("Google")}; request.hdr_count = 1; request.hdrs = &header; auto uri = URI::Create("http", resolver_->metadata_server_name_, path, {} /* query params */, "" /* fragment */); GPR_ASSERT(uri.ok()); // params are hardcoded grpc_arg resource_quota_arg = grpc_channel_arg_pointer_create( const_cast(GRPC_ARG_RESOURCE_QUOTA), resolver_->resource_quota_.get(), grpc_resource_quota_arg_vtable()); grpc_channel_args args = {1, &resource_quota_arg}; http_request_ = HttpRequest::Get(std::move(*uri), &args, pollent, &request, Timestamp::Now() + Duration::Seconds(10), // 10s timeout &on_done_, &response_, RefCountedPtr( grpc_insecure_credentials_create())); http_request_->Start(); } GoogleCloud2ProdResolver::MetadataQuery::~MetadataQuery() { grpc_http_response_destroy(&response_); } void GoogleCloud2ProdResolver::MetadataQuery::Orphan() { http_request_.reset(); Unref(); } void GoogleCloud2ProdResolver::MetadataQuery::OnHttpRequestDone( void* arg, grpc_error_handle error) { auto* self = static_cast(arg); // Hop back into WorkSerializer to call OnDone(). // Note: We implicitly pass our ref to the callback here. self->resolver_->work_serializer_->Run( [self, error]() { self->OnDone(self->resolver_.get(), &self->response_, error); self->Unref(); }, DEBUG_LOCATION); } // // GoogleCloud2ProdResolver::ZoneQuery // GoogleCloud2ProdResolver::ZoneQuery::ZoneQuery( RefCountedPtr resolver, grpc_polling_entity* pollent) : MetadataQuery(std::move(resolver), "/computeMetadata/v1/instance/zone", pollent) {} void GoogleCloud2ProdResolver::ZoneQuery::OnDone( GoogleCloud2ProdResolver* resolver, const grpc_http_response* response, grpc_error_handle error) { absl::StatusOr zone; if (!error.ok()) { zone = absl::UnknownError(absl::StrCat( "error fetching zone from metadata server: ", StatusToString(error))); } else if (response->status != 200) { zone = absl::UnknownError(absl::StrFormat( "zone query received non-200 status: %d", response->status)); } else { absl::string_view body(response->body, response->body_length); size_t i = body.find_last_of('/'); if (i == body.npos) { zone = absl::UnknownError( absl::StrCat("could not parse zone from metadata server: ", body)); } else { zone = std::string(body.substr(i + 1)); } } if (!zone.ok()) { gpr_log(GPR_ERROR, "zone query failed: %s", zone.status().ToString().c_str()); resolver->ZoneQueryDone(""); } else { resolver->ZoneQueryDone(std::move(*zone)); } } // // GoogleCloud2ProdResolver::IPv6Query // GoogleCloud2ProdResolver::IPv6Query::IPv6Query( RefCountedPtr resolver, grpc_polling_entity* pollent) : MetadataQuery(std::move(resolver), "/computeMetadata/v1/instance/network-interfaces/0/ipv6s", pollent) {} void GoogleCloud2ProdResolver::IPv6Query::OnDone( GoogleCloud2ProdResolver* resolver, const grpc_http_response* response, grpc_error_handle error) { if (!error.ok()) { gpr_log(GPR_ERROR, "error fetching IPv6 address from metadata server: %s", StatusToString(error).c_str()); } resolver->IPv6QueryDone(error.ok() && response->status == 200); } // // GoogleCloud2ProdResolver // bool XdsBootstrapConfigured() { return GetEnv("GRPC_XDS_BOOTSTRAP").has_value() || GetEnv("GRPC_XDS_BOOTSTRAP_CONFIG").has_value(); } GoogleCloud2ProdResolver::GoogleCloud2ProdResolver(ResolverArgs args) : resource_quota_(args.args.GetObjectRef()), work_serializer_(std::move(args.work_serializer)), pollent_(grpc_polling_entity_create_from_pollset_set(args.pollset_set)) { absl::string_view name_to_resolve = absl::StripPrefix(args.uri.path(), "/"); // If we're not running on GCP, we can't use DirectPath, so delegate // to the DNS resolver. const bool test_only_pretend_running_on_gcp = args.args .GetBool("grpc.testing.google_c2p_resolver_pretend_running_on_gcp") .value_or(false); const bool running_on_gcp = test_only_pretend_running_on_gcp || grpc_alts_is_running_on_gcp(); const bool federation_enabled = XdsFederationEnabled(); if (!running_on_gcp || // If the client is already using xDS and federation is not enabled, // we can't use it here, because they may be talking to a completely // different xDS server than we want to. // TODO(roth): When we remove xDS federation env var protection, // remove this constraint. (!federation_enabled && XdsBootstrapConfigured())) { using_dns_ = true; child_resolver_ = CoreConfiguration::Get().resolver_registry().CreateResolver( absl::StrCat("dns:", name_to_resolve), args.args, args.pollset_set, work_serializer_, std::move(args.result_handler)); GPR_ASSERT(child_resolver_ != nullptr); return; } // Maybe override metadata server name for testing absl::optional test_only_metadata_server_override = args.args.GetOwnedString( "grpc.testing.google_c2p_resolver_metadata_server_override"); if (test_only_metadata_server_override.has_value() && !test_only_metadata_server_override->empty()) { metadata_server_name_ = std::move(*test_only_metadata_server_override); } // Create xds resolver. std::string xds_uri = federation_enabled ? absl::StrCat("xds://", kC2PAuthority, "/", name_to_resolve) : absl::StrCat("xds:", name_to_resolve); child_resolver_ = CoreConfiguration::Get().resolver_registry().CreateResolver( xds_uri, args.args, args.pollset_set, work_serializer_, std::move(args.result_handler)); GPR_ASSERT(child_resolver_ != nullptr); } void GoogleCloud2ProdResolver::StartLocked() { if (using_dns_) { child_resolver_->StartLocked(); return; } // Using xDS. Start metadata server queries. zone_query_ = MakeOrphanable(Ref(), &pollent_); ipv6_query_ = MakeOrphanable(Ref(), &pollent_); } void GoogleCloud2ProdResolver::RequestReresolutionLocked() { if (child_resolver_ != nullptr) { child_resolver_->RequestReresolutionLocked(); } } void GoogleCloud2ProdResolver::ResetBackoffLocked() { if (child_resolver_ != nullptr) { child_resolver_->ResetBackoffLocked(); } } void GoogleCloud2ProdResolver::ShutdownLocked() { shutdown_ = true; zone_query_.reset(); ipv6_query_.reset(); child_resolver_.reset(); } void GoogleCloud2ProdResolver::ZoneQueryDone(std::string zone) { zone_query_.reset(); zone_ = std::move(zone); if (supports_ipv6_.has_value()) StartXdsResolver(); } void GoogleCloud2ProdResolver::IPv6QueryDone(bool ipv6_supported) { ipv6_query_.reset(); supports_ipv6_ = ipv6_supported; if (zone_.has_value()) StartXdsResolver(); } void GoogleCloud2ProdResolver::StartXdsResolver() { if (shutdown_) { return; } // Construct bootstrap JSON. std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution dist(1, UINT64_MAX); Json::Object node = { {"id", absl::StrCat("C2P-", dist(mt))}, }; if (!zone_->empty()) { node["locality"] = Json::Object{ {"zone", *zone_}, }; }; if (*supports_ipv6_) { node["metadata"] = Json::Object{ {"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true}, }; } // Allow the TD server uri to be overridden for testing purposes. auto override_server = GetEnv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI"); const char* server_uri = override_server.has_value() && !override_server->empty() ? override_server->c_str() : "directpath-pa.googleapis.com"; Json xds_server = Json::Array{ Json::Object{ {"server_uri", server_uri}, {"channel_creds", Json::Array{ Json::Object{ {"type", "google_default"}, }, }}, {"server_features", Json::Array{"xds_v3"}}, }, }; Json bootstrap = Json::Object{ {"xds_servers", xds_server}, {"authorities", Json::Object{ {kC2PAuthority, Json::Object{ {"xds_servers", std::move(xds_server)}, }}, }}, {"node", std::move(node)}, }; // Inject bootstrap JSON as fallback config. internal::SetXdsFallbackBootstrapConfig(bootstrap.Dump().c_str()); // Now start xDS resolver. child_resolver_->StartLocked(); } // // Factory // class GoogleCloud2ProdResolverFactory : public ResolverFactory { public: absl::string_view scheme() const override { return "google-c2p"; } bool IsValidUri(const URI& uri) const override { if (GPR_UNLIKELY(!uri.authority().empty())) { gpr_log(GPR_ERROR, "google-c2p URI scheme does not support authorities"); return false; } return true; } OrphanablePtr CreateResolver(ResolverArgs args) const override { if (!IsValidUri(args.uri)) return nullptr; return MakeOrphanable(std::move(args)); } }; // TODO(apolcyn): remove this class after user code has updated to the // stable "google-c2p" URI scheme. class ExperimentalGoogleCloud2ProdResolverFactory : public ResolverFactory { public: absl::string_view scheme() const override { return "google-c2p-experimental"; } bool IsValidUri(const URI& uri) const override { if (GPR_UNLIKELY(!uri.authority().empty())) { gpr_log( GPR_ERROR, "google-c2p-experimental URI scheme does not support authorities"); return false; } return true; } OrphanablePtr CreateResolver(ResolverArgs args) const override { if (!IsValidUri(args.uri)) return nullptr; return MakeOrphanable(std::move(args)); } }; } // namespace void RegisterCloud2ProdResolver(CoreConfiguration::Builder* builder) { builder->resolver_registry()->RegisterResolverFactory( std::make_unique()); builder->resolver_registry()->RegisterResolverFactory( std::make_unique()); } } // namespace grpc_core