blob: 9e3a8c891ea6339407049f41f2a8f3b1755a903e [file] [log] [blame]
/*
*
* Copyright 2015 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 <grpc/support/port_platform.h>
#if GRPC_ARES == 1 && !defined(GRPC_UV)
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <grpc/support/alloc.h>
#include <grpc/support/string_util.h>
#include "src/core/ext/filters/client_channel/http_connect_handshaker.h"
#include "src/core/ext/filters/client_channel/lb_policy_registry.h"
#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
#include "src/core/ext/filters/client_channel/resolver_registry.h"
#include "src/core/lib/backoff/backoff.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/host_port.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gprpp/manual_constructor.h"
#include "src/core/lib/iomgr/combiner.h"
#include "src/core/lib/iomgr/gethostname.h"
#include "src/core/lib/iomgr/resolve_address.h"
#include "src/core/lib/iomgr/timer.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/transport/service_config.h"
#define GRPC_DNS_INITIAL_CONNECT_BACKOFF_SECONDS 1
#define GRPC_DNS_RECONNECT_BACKOFF_MULTIPLIER 1.6
#define GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS 120
#define GRPC_DNS_RECONNECT_JITTER 0.2
namespace grpc_core {
namespace {
const char kDefaultPort[] = "https";
class AresDnsResolver : public Resolver {
public:
explicit AresDnsResolver(const ResolverArgs& args);
void NextLocked(grpc_channel_args** result,
grpc_closure* on_complete) override;
void RequestReresolutionLocked() override;
void ShutdownLocked() override;
private:
virtual ~AresDnsResolver();
void MaybeStartResolvingLocked();
void StartResolvingLocked();
void MaybeFinishNextLocked();
static void OnNextResolutionLocked(void* arg, grpc_error* error);
static void OnResolvedLocked(void* arg, grpc_error* error);
/// DNS server to use (if not system default)
char* dns_server_;
/// name to resolve (usually the same as target_name)
char* name_to_resolve_;
/// channel args
grpc_channel_args* channel_args_;
/// whether to request the service config
bool request_service_config_;
/// pollset_set to drive the name resolution process
grpc_pollset_set* interested_parties_;
/// closures used by the combiner
grpc_closure on_next_resolution_;
grpc_closure on_resolved_;
/// are we currently resolving?
bool resolving_ = false;
/// the pending resolving request
grpc_ares_request* pending_request_ = nullptr;
/// which version of the result have we published?
int published_version_ = 0;
/// which version of the result is current?
int resolved_version_ = 0;
/// pending next completion, or NULL
grpc_closure* next_completion_ = nullptr;
/// target result address for next completion
grpc_channel_args** target_result_ = nullptr;
/// current (fully resolved) result
grpc_channel_args* resolved_result_ = nullptr;
/// next resolution timer
bool have_next_resolution_timer_ = false;
grpc_timer next_resolution_timer_;
/// min interval between DNS requests
grpc_millis min_time_between_resolutions_;
/// timestamp of last DNS request
grpc_millis last_resolution_timestamp_ = -1;
/// retry backoff state
BackOff backoff_;
/// currently resolving addresses
grpc_lb_addresses* lb_addresses_ = nullptr;
/// currently resolving service config
char* service_config_json_ = nullptr;
};
AresDnsResolver::AresDnsResolver(const ResolverArgs& args)
: Resolver(args.combiner),
backoff_(
BackOff::Options()
.set_initial_backoff(GRPC_DNS_INITIAL_CONNECT_BACKOFF_SECONDS *
1000)
.set_multiplier(GRPC_DNS_RECONNECT_BACKOFF_MULTIPLIER)
.set_jitter(GRPC_DNS_RECONNECT_JITTER)
.set_max_backoff(GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS * 1000)) {
// Get name to resolve from URI path.
const char* path = args.uri->path;
if (path[0] == '/') ++path;
name_to_resolve_ = gpr_strdup(path);
// Get DNS server from URI authority.
if (0 != strcmp(args.uri->authority, "")) {
dns_server_ = gpr_strdup(args.uri->authority);
}
channel_args_ = grpc_channel_args_copy(args.args);
const grpc_arg* arg = grpc_channel_args_find(
channel_args_, GRPC_ARG_SERVICE_CONFIG_DISABLE_RESOLUTION);
request_service_config_ = !grpc_channel_arg_get_integer(
arg, (grpc_integer_options){false, false, true});
arg = grpc_channel_args_find(channel_args_,
GRPC_ARG_DNS_MIN_TIME_BETWEEN_RESOLUTIONS_MS);
min_time_between_resolutions_ =
grpc_channel_arg_get_integer(arg, {1000, 0, INT_MAX});
interested_parties_ = grpc_pollset_set_create();
if (args.pollset_set != nullptr) {
grpc_pollset_set_add_pollset_set(interested_parties_, args.pollset_set);
}
GRPC_CLOSURE_INIT(&on_next_resolution_, OnNextResolutionLocked, this,
grpc_combiner_scheduler(combiner()));
GRPC_CLOSURE_INIT(&on_resolved_, OnResolvedLocked, this,
grpc_combiner_scheduler(combiner()));
}
AresDnsResolver::~AresDnsResolver() {
gpr_log(GPR_DEBUG, "destroying AresDnsResolver");
if (resolved_result_ != nullptr) {
grpc_channel_args_destroy(resolved_result_);
}
grpc_pollset_set_destroy(interested_parties_);
gpr_free(dns_server_);
gpr_free(name_to_resolve_);
grpc_channel_args_destroy(channel_args_);
}
void AresDnsResolver::NextLocked(grpc_channel_args** target_result,
grpc_closure* on_complete) {
gpr_log(GPR_DEBUG, "AresDnsResolver::NextLocked() is called.");
GPR_ASSERT(next_completion_ == nullptr);
next_completion_ = on_complete;
target_result_ = target_result;
if (resolved_version_ == 0 && !resolving_) {
MaybeStartResolvingLocked();
} else {
MaybeFinishNextLocked();
}
}
void AresDnsResolver::RequestReresolutionLocked() {
if (!resolving_) {
MaybeStartResolvingLocked();
}
}
void AresDnsResolver::ShutdownLocked() {
if (have_next_resolution_timer_) {
grpc_timer_cancel(&next_resolution_timer_);
}
if (pending_request_ != nullptr) {
grpc_cancel_ares_request(pending_request_);
}
if (next_completion_ != nullptr) {
*target_result_ = nullptr;
GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Resolver Shutdown"));
next_completion_ = nullptr;
}
}
void AresDnsResolver::OnNextResolutionLocked(void* arg, grpc_error* error) {
AresDnsResolver* r = static_cast<AresDnsResolver*>(arg);
r->have_next_resolution_timer_ = false;
if (error == GRPC_ERROR_NONE) {
if (!r->resolving_) {
r->StartResolvingLocked();
}
}
r->Unref(DEBUG_LOCATION, "next_resolution_timer");
}
bool ValueInJsonArray(grpc_json* array, const char* value) {
for (grpc_json* entry = array->child; entry != nullptr; entry = entry->next) {
if (entry->type == GRPC_JSON_STRING && strcmp(entry->value, value) == 0) {
return true;
}
}
return false;
}
char* ChooseServiceConfig(char* service_config_choice_json) {
grpc_json* choices_json = grpc_json_parse_string(service_config_choice_json);
if (choices_json == nullptr || choices_json->type != GRPC_JSON_ARRAY) {
gpr_log(GPR_ERROR, "cannot parse service config JSON string");
return nullptr;
}
char* service_config = nullptr;
for (grpc_json* choice = choices_json->child; choice != nullptr;
choice = choice->next) {
if (choice->type != GRPC_JSON_OBJECT) {
gpr_log(GPR_ERROR, "cannot parse service config JSON string");
break;
}
grpc_json* service_config_json = nullptr;
for (grpc_json* field = choice->child; field != nullptr;
field = field->next) {
// Check client language, if specified.
if (strcmp(field->key, "clientLanguage") == 0) {
if (field->type != GRPC_JSON_ARRAY || !ValueInJsonArray(field, "c++")) {
service_config_json = nullptr;
break;
}
}
// Check client hostname, if specified.
if (strcmp(field->key, "clientHostname") == 0) {
char* hostname = grpc_gethostname();
if (hostname == nullptr || field->type != GRPC_JSON_ARRAY ||
!ValueInJsonArray(field, hostname)) {
service_config_json = nullptr;
break;
}
}
// Check percentage, if specified.
if (strcmp(field->key, "percentage") == 0) {
if (field->type != GRPC_JSON_NUMBER) {
service_config_json = nullptr;
break;
}
int random_pct = rand() % 100;
int percentage;
if (sscanf(field->value, "%d", &percentage) != 1 ||
random_pct > percentage || percentage == 0) {
service_config_json = nullptr;
break;
}
}
// Save service config.
if (strcmp(field->key, "serviceConfig") == 0) {
if (field->type == GRPC_JSON_OBJECT) {
service_config_json = field;
}
}
}
if (service_config_json != nullptr) {
service_config = grpc_json_dump_to_string(service_config_json, 0);
break;
}
}
grpc_json_destroy(choices_json);
return service_config;
}
void AresDnsResolver::OnResolvedLocked(void* arg, grpc_error* error) {
AresDnsResolver* r = static_cast<AresDnsResolver*>(arg);
grpc_channel_args* result = nullptr;
GPR_ASSERT(r->resolving_);
r->resolving_ = false;
r->pending_request_ = nullptr;
if (r->lb_addresses_ != nullptr) {
static const char* args_to_remove[2];
size_t num_args_to_remove = 0;
grpc_arg new_args[3];
size_t num_args_to_add = 0;
new_args[num_args_to_add++] =
grpc_lb_addresses_create_channel_arg(r->lb_addresses_);
grpc_service_config* service_config = nullptr;
char* service_config_string = nullptr;
if (r->service_config_json_ != nullptr) {
service_config_string = ChooseServiceConfig(r->service_config_json_);
gpr_free(r->service_config_json_);
if (service_config_string != nullptr) {
gpr_log(GPR_INFO, "selected service config choice: %s",
service_config_string);
args_to_remove[num_args_to_remove++] = GRPC_ARG_SERVICE_CONFIG;
new_args[num_args_to_add++] = grpc_channel_arg_string_create(
(char*)GRPC_ARG_SERVICE_CONFIG, service_config_string);
service_config = grpc_service_config_create(service_config_string);
if (service_config != nullptr) {
const char* lb_policy_name =
grpc_service_config_get_lb_policy_name(service_config);
if (lb_policy_name != nullptr) {
args_to_remove[num_args_to_remove++] = GRPC_ARG_LB_POLICY_NAME;
new_args[num_args_to_add++] = grpc_channel_arg_string_create(
(char*)GRPC_ARG_LB_POLICY_NAME, const_cast<char*>(lb_policy_name));
}
}
}
}
result = grpc_channel_args_copy_and_add_and_remove(
r->channel_args_, args_to_remove, num_args_to_remove, new_args,
num_args_to_add);
if (service_config != nullptr) grpc_service_config_destroy(service_config);
gpr_free(service_config_string);
grpc_lb_addresses_destroy(r->lb_addresses_);
// Reset backoff state so that we start from the beginning when the
// next request gets triggered.
r->backoff_.Reset();
} else {
const char* msg = grpc_error_string(error);
gpr_log(GPR_DEBUG, "dns resolution failed: %s", msg);
grpc_millis next_try = r->backoff_.NextAttemptTime();
grpc_millis timeout = next_try - ExecCtx::Get()->Now();
gpr_log(GPR_INFO, "dns resolution failed (will retry): %s",
grpc_error_string(error));
GPR_ASSERT(!r->have_next_resolution_timer_);
r->have_next_resolution_timer_ = true;
// TODO(roth): We currently deal with this ref manually. Once the
// new closure API is done, find a way to track this ref with the timer
// callback as part of the type system.
RefCountedPtr<Resolver> self = r->Ref(DEBUG_LOCATION, "retry-timer");
self.release();
if (timeout > 0) {
gpr_log(GPR_DEBUG, "retrying in %" PRIdPTR " milliseconds", timeout);
} else {
gpr_log(GPR_DEBUG, "retrying immediately");
}
grpc_timer_init(&r->next_resolution_timer_, next_try,
&r->on_next_resolution_);
}
if (r->resolved_result_ != nullptr) {
grpc_channel_args_destroy(r->resolved_result_);
}
r->resolved_result_ = result;
++r->resolved_version_;
r->MaybeFinishNextLocked();
r->Unref(DEBUG_LOCATION, "dns-resolving");
}
void AresDnsResolver::MaybeStartResolvingLocked() {
if (last_resolution_timestamp_ >= 0) {
const grpc_millis earliest_next_resolution =
last_resolution_timestamp_ + min_time_between_resolutions_;
const grpc_millis ms_until_next_resolution =
earliest_next_resolution - grpc_core::ExecCtx::Get()->Now();
if (ms_until_next_resolution > 0) {
const grpc_millis last_resolution_ago =
grpc_core::ExecCtx::Get()->Now() - last_resolution_timestamp_;
gpr_log(GPR_DEBUG,
"In cooldown from last resolution (from %" PRIdPTR
" ms ago). Will resolve again in %" PRIdPTR " ms",
last_resolution_ago, ms_until_next_resolution);
if (!have_next_resolution_timer_) {
have_next_resolution_timer_ = true;
// TODO(roth): We currently deal with this ref manually. Once the
// new closure API is done, find a way to track this ref with the timer
// callback as part of the type system.
RefCountedPtr<Resolver> self =
Ref(DEBUG_LOCATION, "next_resolution_timer_cooldown");
self.release();
grpc_timer_init(&next_resolution_timer_, ms_until_next_resolution,
&on_next_resolution_);
}
// TODO(dgq): remove the following two lines once Pick First stops
// discarding subchannels after selecting.
++resolved_version_;
MaybeFinishNextLocked();
return;
}
}
StartResolvingLocked();
}
void AresDnsResolver::StartResolvingLocked() {
// TODO(roth): We currently deal with this ref manually. Once the
// new closure API is done, find a way to track this ref with the timer
// callback as part of the type system.
RefCountedPtr<Resolver> self = Ref(DEBUG_LOCATION, "dns-resolving");
self.release();
GPR_ASSERT(!resolving_);
resolving_ = true;
lb_addresses_ = nullptr;
service_config_json_ = nullptr;
pending_request_ = grpc_dns_lookup_ares(
dns_server_, name_to_resolve_, kDefaultPort, interested_parties_,
&on_resolved_, &lb_addresses_, true /* check_grpclb */,
request_service_config_ ? &service_config_json_ : nullptr);
last_resolution_timestamp_ = grpc_core::ExecCtx::Get()->Now();
}
void AresDnsResolver::MaybeFinishNextLocked() {
if (next_completion_ != nullptr && resolved_version_ != published_version_) {
*target_result_ = resolved_result_ == nullptr
? nullptr
: grpc_channel_args_copy(resolved_result_);
gpr_log(GPR_DEBUG, "AresDnsResolver::MaybeFinishNextLocked()");
GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_NONE);
next_completion_ = nullptr;
published_version_ = resolved_version_;
}
}
//
// Factory
//
class AresDnsResolverFactory : public ResolverFactory {
public:
OrphanablePtr<Resolver> CreateResolver(
const ResolverArgs& args) const override {
return OrphanablePtr<Resolver>(New<AresDnsResolver>(args));
}
const char* scheme() const override { return "dns"; }
};
} // namespace
} // namespace grpc_core
void grpc_resolver_dns_ares_init() {
char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
/* TODO(zyc): Turn on c-ares based resolver by default after the address
sorter and the CNAME support are added. */
if (resolver_env != nullptr && gpr_stricmp(resolver_env, "ares") == 0) {
grpc_error* error = grpc_ares_init();
if (error != GRPC_ERROR_NONE) {
GRPC_LOG_IF_ERROR("ares_library_init() failed", error);
return;
}
grpc_resolve_address = grpc_resolve_address_ares;
grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
grpc_core::UniquePtr<grpc_core::ResolverFactory>(
grpc_core::New<grpc_core::AresDnsResolverFactory>()));
}
gpr_free(resolver_env);
}
void grpc_resolver_dns_ares_shutdown() {
char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
if (resolver_env != nullptr && gpr_stricmp(resolver_env, "ares") == 0) {
grpc_ares_cleanup();
}
gpr_free(resolver_env);
}
#else /* GRPC_ARES == 1 && !defined(GRPC_UV) */
void grpc_resolver_dns_ares_init(void) {}
void grpc_resolver_dns_ares_shutdown(void) {}
#endif /* GRPC_ARES == 1 && !defined(GRPC_UV) */