dns: fix crash while setting server during query

Fix this issue follow these two points:

1. Keep track of how many queries are currently open. If `setServers()`
   is called while there are open queries, error out.
2. For `Resolver` instances, use option 1. For dns.setServers(), just
   create a fresh new default channel every time it is called, and then
   set its servers list.

PR-URL: https://github.com/nodejs/node/pull/14891
Fixes: https://github.com/nodejs/node/issues/14734
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
XadillaX 2017-11-15 15:09:33 +08:00 committed by Anna Henningsen
parent b3e53673b9
commit b3678dff52
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
4 changed files with 94 additions and 21 deletions

View File

@ -380,28 +380,44 @@ function setServers(servers) {
} }
} }
const defaultResolver = new Resolver(); let defaultResolver = new Resolver();
const resolverKeys = [
'getServers',
'resolve',
'resolveAny',
'resolve4',
'resolve6',
'resolveCname',
'resolveMx',
'resolveNs',
'resolveTxt',
'resolveSrv',
'resolvePtr',
'resolveNaptr',
'resolveSoa',
'reverse'
];
function setExportsFunctions() {
resolverKeys.forEach((key) => {
module.exports[key] = defaultResolver[key].bind(defaultResolver);
});
}
function defaultResolverSetServers(servers) {
const resolver = new Resolver();
resolver.setServers(servers);
defaultResolver = resolver;
setExportsFunctions();
}
module.exports = { module.exports = {
lookup, lookup,
lookupService, lookupService,
Resolver, Resolver,
getServers: defaultResolver.getServers.bind(defaultResolver), setServers: defaultResolverSetServers,
setServers: defaultResolver.setServers.bind(defaultResolver),
resolve: defaultResolver.resolve.bind(defaultResolver),
resolveAny: defaultResolver.resolveAny.bind(defaultResolver),
resolve4: defaultResolver.resolve4.bind(defaultResolver),
resolve6: defaultResolver.resolve6.bind(defaultResolver),
resolveCname: defaultResolver.resolveCname.bind(defaultResolver),
resolveMx: defaultResolver.resolveMx.bind(defaultResolver),
resolveNs: defaultResolver.resolveNs.bind(defaultResolver),
resolveTxt: defaultResolver.resolveTxt.bind(defaultResolver),
resolveSrv: defaultResolver.resolveSrv.bind(defaultResolver),
resolvePtr: defaultResolver.resolvePtr.bind(defaultResolver),
resolveNaptr: defaultResolver.resolveNaptr.bind(defaultResolver),
resolveSoa: defaultResolver.resolveSoa.bind(defaultResolver),
reverse: defaultResolver.reverse.bind(defaultResolver),
// uv_getaddrinfo flags // uv_getaddrinfo flags
ADDRCONFIG: cares.AI_ADDRCONFIG, ADDRCONFIG: cares.AI_ADDRCONFIG,
@ -433,3 +449,5 @@ module.exports = {
ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS', ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS',
CANCELLED: 'ECANCELLED' CANCELLED: 'ECANCELLED'
}; };
setExportsFunctions();

View File

@ -83,6 +83,7 @@ inline uint32_t cares_get_32bit(const unsigned char* p) {
const int ns_t_cname_or_a = -1; const int ns_t_cname_or_a = -1;
#define DNS_ESETSRVPENDING -1000
inline const char* ToErrorCodeString(int status) { inline const char* ToErrorCodeString(int status) {
switch (status) { switch (status) {
#define V(code) case ARES_##code: return #code; #define V(code) case ARES_##code: return #code;
@ -150,6 +151,8 @@ class ChannelWrap : public AsyncWrap {
void EnsureServers(); void EnsureServers();
void CleanupTimer(); void CleanupTimer();
void ModifyActivityQueryCount(int count);
inline uv_timer_t* timer_handle() { return timer_handle_; } inline uv_timer_t* timer_handle() { return timer_handle_; }
inline ares_channel cares_channel() { return channel_; } inline ares_channel cares_channel() { return channel_; }
inline bool query_last_ok() const { return query_last_ok_; } inline bool query_last_ok() const { return query_last_ok_; }
@ -158,6 +161,7 @@ class ChannelWrap : public AsyncWrap {
inline void set_is_servers_default(bool is_default) { inline void set_is_servers_default(bool is_default) {
is_servers_default_ = is_default; is_servers_default_ = is_default;
} }
inline int active_query_count() { return active_query_count_; }
inline node_ares_task_list* task_list() { return &task_list_; } inline node_ares_task_list* task_list() { return &task_list_; }
size_t self_size() const override { return sizeof(*this); } size_t self_size() const override { return sizeof(*this); }
@ -170,6 +174,7 @@ class ChannelWrap : public AsyncWrap {
bool query_last_ok_; bool query_last_ok_;
bool is_servers_default_; bool is_servers_default_;
bool library_inited_; bool library_inited_;
int active_query_count_;
node_ares_task_list task_list_; node_ares_task_list task_list_;
}; };
@ -180,7 +185,8 @@ ChannelWrap::ChannelWrap(Environment* env,
channel_(nullptr), channel_(nullptr),
query_last_ok_(true), query_last_ok_(true),
is_servers_default_(true), is_servers_default_(true),
library_inited_(false) { library_inited_(false),
active_query_count_(0) {
MakeWeak<ChannelWrap>(this); MakeWeak<ChannelWrap>(this);
Setup(); Setup();
@ -545,6 +551,11 @@ void ChannelWrap::CleanupTimer() {
timer_handle_ = nullptr; timer_handle_ = nullptr;
} }
void ChannelWrap::ModifyActivityQueryCount(int count) {
active_query_count_ += count;
if (active_query_count_ < 0) active_query_count_ = 0;
}
/** /**
* This function is to check whether current servers are fallback servers * This function is to check whether current servers are fallback servers
@ -682,6 +693,7 @@ class QueryWrap : public AsyncWrap {
CaresAsyncCb)); CaresAsyncCb));
wrap->channel_->set_query_last_ok(status != ARES_ECONNREFUSED); wrap->channel_->set_query_last_ok(status != ARES_ECONNREFUSED);
wrap->channel_->ModifyActivityQueryCount(-1);
async_handle->data = data; async_handle->data = data;
uv_async_send(async_handle); uv_async_send(async_handle);
} }
@ -1802,9 +1814,12 @@ static void Query(const FunctionCallbackInfo<Value>& args) {
Wrap* wrap = new Wrap(channel, req_wrap_obj); Wrap* wrap = new Wrap(channel, req_wrap_obj);
node::Utf8Value name(env->isolate(), string); node::Utf8Value name(env->isolate(), string);
channel->ModifyActivityQueryCount(1);
int err = wrap->Send(*name); int err = wrap->Send(*name);
if (err) if (err) {
channel->ModifyActivityQueryCount(-1);
delete wrap; delete wrap;
}
args.GetReturnValue().Set(err); args.GetReturnValue().Set(err);
} }
@ -2081,6 +2096,10 @@ void SetServers(const FunctionCallbackInfo<Value>& args) {
ChannelWrap* channel; ChannelWrap* channel;
ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());
if (channel->active_query_count()) {
return args.GetReturnValue().Set(DNS_ESETSRVPENDING);
}
CHECK(args[0]->IsArray()); CHECK(args[0]->IsArray());
Local<Array> arr = Local<Array>::Cast(args[0]); Local<Array> arr = Local<Array>::Cast(args[0]);
@ -2161,11 +2180,13 @@ void Cancel(const FunctionCallbackInfo<Value>& args) {
ares_cancel(channel->cares_channel()); ares_cancel(channel->cares_channel());
} }
const char EMSG_ESETSRVPENDING[] = "There are pending queries.";
void StrError(const FunctionCallbackInfo<Value>& args) { void StrError(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
const char* errmsg = ares_strerror(args[0]->Int32Value(env->context()) int code = args[0]->Int32Value(env->context()).FromJust();
.FromJust()); const char* errmsg = (code == DNS_ESETSRVPENDING) ?
EMSG_ESETSRVPENDING :
ares_strerror(code);
args.GetReturnValue().Set(OneByteString(env->isolate(), errmsg)); args.GetReturnValue().Set(OneByteString(env->isolate(), errmsg));
} }

View File

@ -13,3 +13,6 @@ dns.resolve4(
common.mustCall(function(/* err, nameServers */) { common.mustCall(function(/* err, nameServers */) {
dns.setServers([ addresses.DNS4_SERVER ]); dns.setServers([ addresses.DNS4_SERVER ]);
})); }));
// Test https://github.com/nodejs/node/issues/14734
dns.resolve4(addresses.INET4_HOST, common.mustCall());

View File

@ -0,0 +1,31 @@
'use strict';
const common = require('../common');
const dns = require('dns');
const goog = [
'8.8.8.8',
'8.8.4.4',
];
{
// Fix https://github.com/nodejs/node/issues/14734
{
const resolver = new dns.Resolver();
resolver.resolve('localhost', common.mustCall());
common.expectsError(resolver.setServers.bind(resolver, goog), {
code: 'ERR_DNS_SET_SERVERS_FAILED',
message: /^c-ares failed to set servers: "There are pending queries\." \[.+\]$/g
});
}
{
dns.resolve('localhost', common.mustCall());
// should not throw
dns.setServers(goog);
}
}