From 65189324665ab4b7285c79729a0b9c0e4c2d6fcb Mon Sep 17 00:00:00 2001 From: XadillaX Date: Thu, 10 Aug 2017 14:47:14 +0800 Subject: [PATCH] dns: fix `resolve` failed starts without network Fix the bug that you start process without network at first, but it connected lately, `dns.resolve` will stay failed with ECONNREFUSED because c-ares servers fallback to 127.0.0.1 at the very beginning. If c-ares servers "127.0.0.1" is detected and its not set by user self, and last query is not OK, recreating `ares_channel` operation will be triggered to reload servers. Fixes: https://github.com/nodejs/node/issues/1644 Backport-PR-URL: https://github.com/nodejs/node/pull/14434 PR-URL: https://github.com/nodejs/node/pull/13076 Reviewed-By: Anna Henningsen Reviewed-By: Ben Noordhuis --- src/cares_wrap.cc | 160 +++++++++++++++++++++++++--------------------- src/env-inl.h | 18 ++++++ src/env.h | 6 ++ 3 files changed, 110 insertions(+), 74 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 44d0926ba6b178..d40d4b3256f193 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -372,6 +372,69 @@ struct CaresAsyncData { uv_async_t async_handle; }; +void SetupCaresChannel(Environment* env) { + struct ares_options options; + memset(&options, 0, sizeof(options)); + options.flags = ARES_FLAG_NOCHECKRESP; + options.sock_state_cb = ares_sockstate_cb; + options.sock_state_cb_data = env; + + /* We do the call to ares_init_option for caller. */ + int r = ares_init_options(env->cares_channel_ptr(), + &options, + ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB); + + if (r != ARES_SUCCESS) { + ares_library_cleanup(); + return env->ThrowError(ToErrorCodeString(r)); + } +} + + +/** + * This function is to check whether current servers are fallback servers + * when cares initialized. + * + * The fallback servers of cares is [ "127.0.0.1" ] with no user additional + * setting. + */ +void AresEnsureServers(Environment* env) { + /* if last query is OK or servers are set by user self, do not check */ + if (env->cares_query_last_ok() || !env->cares_is_servers_default()) { + return; + } + + ares_channel channel = env->cares_channel(); + ares_addr_node* servers = nullptr; + + ares_get_servers(channel, &servers); + + /* if no server or multi-servers, ignore */ + if (servers == nullptr) return; + if (servers->next != nullptr) { + ares_free_data(servers); + env->set_cares_is_servers_default(false); + return; + } + + /* if the only server is not 127.0.0.1, ignore */ + if (servers[0].family != AF_INET || + servers[0].addr.addr4.s_addr != htonl(INADDR_LOOPBACK)) { + ares_free_data(servers); + env->set_cares_is_servers_default(false); + return; + } + + ares_free_data(servers); + servers = nullptr; + + /* destroy channel and reset channel */ + ares_destroy(channel); + + SetupCaresChannel(env); +} + + class QueryWrap : public AsyncWrap { public: QueryWrap(Environment* env, Local req_wrap_obj) @@ -402,6 +465,13 @@ class QueryWrap : public AsyncWrap { return static_cast(this); } + static void AresQuery(Environment* env, const char* name, + int dnsclass, int type, ares_callback callback, + void* arg) { + AresEnsureServers(env); + ares_query(env->cares_channel(), name, dnsclass, type, callback, arg); + } + static void CaresAsyncClose(uv_handle_t* handle) { uv_async_t* async = reinterpret_cast(handle); auto data = static_cast(async->data); @@ -453,6 +523,7 @@ class QueryWrap : public AsyncWrap { async_handle, CaresAsyncCb)); + wrap->env()->set_cares_query_last_ok(status != ARES_ECONNREFUSED); async_handle->data = data; uv_async_send(async_handle); } @@ -478,6 +549,7 @@ class QueryWrap : public AsyncWrap { async_handle, CaresAsyncCb)); + wrap->env()->set_cares_query_last_ok(status != ARES_ECONNREFUSED); async_handle->data = data; uv_async_send(async_handle); } @@ -522,12 +594,7 @@ class QueryAWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_a, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_a, Callback, GetQueryArg()); return 0; } @@ -570,12 +637,7 @@ class QueryAaaaWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_aaaa, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_aaaa, Callback, GetQueryArg()); return 0; } @@ -618,12 +680,7 @@ class QueryCnameWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_cname, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_cname, Callback, GetQueryArg()); return 0; } @@ -659,12 +716,7 @@ class QueryMxWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_mx, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_mx, Callback, GetQueryArg()); return 0; } @@ -710,12 +762,7 @@ class QueryNsWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_ns, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_ns, Callback, GetQueryArg()); return 0; } @@ -748,12 +795,7 @@ class QueryTxtWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_txt, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_txt, Callback, GetQueryArg()); return 0; } @@ -805,12 +847,7 @@ class QuerySrvWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_srv, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_srv, Callback, GetQueryArg()); return 0; } @@ -861,12 +898,7 @@ class QueryPtrWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_ptr, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_ptr, Callback, GetQueryArg()); return 0; } @@ -904,12 +936,7 @@ class QueryNaptrWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_naptr, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_naptr, Callback, GetQueryArg()); return 0; } @@ -968,12 +995,7 @@ class QuerySoaWrap: public QueryWrap { } int Send(const char* name) override { - ares_query(env()->cares_channel(), - name, - ns_c_in, - ns_t_soa, - Callback, - GetQueryArg()); + AresQuery(env(), name, ns_c_in, ns_t_soa, Callback, GetQueryArg()); return 0; } @@ -1434,6 +1456,9 @@ static void SetServers(const FunctionCallbackInfo& args) { delete[] servers; + if (err == ARES_SUCCESS) + env->set_cares_is_servers_default(false); + args.GetReturnValue().Set(err); } @@ -1468,20 +1493,7 @@ static void Initialize(Local target, if (r != ARES_SUCCESS) return env->ThrowError(ToErrorCodeString(r)); - struct ares_options options; - memset(&options, 0, sizeof(options)); - options.flags = ARES_FLAG_NOCHECKRESP; - options.sock_state_cb = ares_sockstate_cb; - options.sock_state_cb_data = env; - - /* We do the call to ares_init_option for caller. */ - r = ares_init_options(env->cares_channel_ptr(), - &options, - ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB); - if (r != ARES_SUCCESS) { - ares_library_cleanup(); - return env->ThrowError(ToErrorCodeString(r)); - } + SetupCaresChannel(env); /* Initialize the timeout timer. The timer won't be started until the */ /* first socket is opened. */ diff --git a/src/env-inl.h b/src/env-inl.h index 735dbca6857fa1..e2876de44a9390 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -219,6 +219,8 @@ inline Environment::Environment(v8::Local context, : isolate_(context->GetIsolate()), isolate_data_(IsolateData::GetOrCreate(context->GetIsolate(), loop)), timer_base_(uv_now(loop)), + cares_query_last_ok_(true), + cares_is_servers_default_(true), using_domains_(false), printed_error_(false), trace_sync_io_(false), @@ -453,6 +455,22 @@ inline ares_channel* Environment::cares_channel_ptr() { return &cares_channel_; } +inline bool Environment::cares_query_last_ok() { + return cares_query_last_ok_; +} + +inline void Environment::set_cares_query_last_ok(bool ok) { + cares_query_last_ok_ = ok; +} + +inline bool Environment::cares_is_servers_default() { + return cares_is_servers_default_; +} + +inline void Environment::set_cares_is_servers_default(bool is_default) { + cares_is_servers_default_ = is_default; +} + inline node_ares_task_list* Environment::cares_task_list() { return &cares_task_list_; } diff --git a/src/env.h b/src/env.h index bb4dab070606a8..940c97fe4ddbf1 100644 --- a/src/env.h +++ b/src/env.h @@ -437,6 +437,10 @@ class Environment { inline uv_timer_t* cares_timer_handle(); inline ares_channel cares_channel(); inline ares_channel* cares_channel_ptr(); + inline bool cares_query_last_ok(); + inline void set_cares_query_last_ok(bool ok); + inline bool cares_is_servers_default(); + inline void set_cares_is_servers_default(bool is_default); inline node_ares_task_list* cares_task_list(); inline bool using_domains() const; @@ -555,6 +559,8 @@ class Environment { const uint64_t timer_base_; uv_timer_t cares_timer_handle_; ares_channel cares_channel_; + bool cares_query_last_ok_; + bool cares_is_servers_default_; node_ares_task_list cares_task_list_; bool using_domains_; bool printed_error_;