From 65189324665ab4b7285c79729a0b9c0e4c2d6fcb Mon Sep 17 00:00:00 2001
From: XadillaX <admin@xcoder.in>
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 <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
---
 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<Object> req_wrap_obj)
@@ -402,6 +465,13 @@ class QueryWrap : public AsyncWrap {
     return static_cast<void*>(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<uv_async_t*>(handle);
     auto data = static_cast<struct CaresAsyncData*>(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<Value>& 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<Object> 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<v8::Context> 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_;