Skip to content

Commit d4442b1

Browse files
bnoordhuiscodebytere
authored andcommitted
dns: make dns.Resolver timeout configurable
PR-URL: #33472 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Jiawen Geng <[email protected]>
1 parent 15eb5a3 commit d4442b1

File tree

5 files changed

+101
-13
lines changed

5 files changed

+101
-13
lines changed

doc/api/dns.md

+16
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@ The following methods from the `dns` module are available:
9292
* [`resolver.reverse()`][`dns.reverse()`]
9393
* [`resolver.setServers()`][`dns.setServers()`]
9494

95+
### `Resolver([options])`
96+
<!-- YAML
97+
added: v8.3.0
98+
changes:
99+
- version: REPLACEME
100+
pr-url: https://github.com/nodejs/node/pull/33472
101+
description: The constructor now accepts an `options` object.
102+
The single supported option is `timeout`.
103+
-->
104+
105+
Create a new resolver.
106+
107+
* `options` {Object}
108+
* `timeout` {integer} Query timeout in milliseconds, or `-1` to use the
109+
default timeout.
110+
95111
### `resolver.cancel()`
96112
<!-- YAML
97113
added: v8.3.0

lib/internal/dns/promises.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
bindDefaultResolver,
1111
Resolver: CallbackResolver,
1212
validateHints,
13+
validateTimeout,
1314
emitInvalidHostnameWarning,
1415
} = require('internal/dns/utils');
1516
const { codes, dnsException } = require('internal/errors');
@@ -208,8 +209,9 @@ const resolveMap = ObjectCreate(null);
208209

209210
// Resolver instances correspond 1:1 to c-ares channels.
210211
class Resolver {
211-
constructor() {
212-
this._handle = new ChannelWrap();
212+
constructor(options = undefined) {
213+
const timeout = validateTimeout(options);
214+
this._handle = new ChannelWrap(timeout);
213215
}
214216
}
215217

lib/internal/dns/utils.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66

77
const errors = require('internal/errors');
88
const { isIP } = require('internal/net');
9+
const { validateInt32 } = require('internal/validators');
910
const {
1011
ChannelWrap,
1112
strerror,
@@ -23,10 +24,17 @@ const {
2324
ERR_INVALID_OPT_VALUE
2425
} = errors.codes;
2526

27+
function validateTimeout(options) {
28+
const { timeout = -1 } = { ...options };
29+
validateInt32(timeout, 'options.timeout', -1, 2 ** 31 - 1);
30+
return timeout;
31+
}
32+
2633
// Resolver instances correspond 1:1 to c-ares channels.
2734
class Resolver {
28-
constructor() {
29-
this._handle = new ChannelWrap();
35+
constructor(options = undefined) {
36+
const timeout = validateTimeout(options);
37+
this._handle = new ChannelWrap(timeout);
3038
}
3139

3240
cancel() {
@@ -162,6 +170,7 @@ module.exports = {
162170
getDefaultResolver,
163171
setDefaultResolver,
164172
validateHints,
173+
validateTimeout,
165174
Resolver,
166175
emitInvalidHostnameWarning,
167176
};

src/cares_wrap.cc

+17-9
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ using node_ares_task_list =
152152

153153
class ChannelWrap : public AsyncWrap {
154154
public:
155-
ChannelWrap(Environment* env, Local<Object> object);
155+
ChannelWrap(Environment* env, Local<Object> object, int timeout);
156156
~ChannelWrap() override;
157157

158158
static void New(const FunctionCallbackInfo<Value>& args);
@@ -190,18 +190,21 @@ class ChannelWrap : public AsyncWrap {
190190
bool query_last_ok_;
191191
bool is_servers_default_;
192192
bool library_inited_;
193+
int timeout_;
193194
int active_query_count_;
194195
node_ares_task_list task_list_;
195196
};
196197

197198
ChannelWrap::ChannelWrap(Environment* env,
198-
Local<Object> object)
199+
Local<Object> object,
200+
int timeout)
199201
: AsyncWrap(env, object, PROVIDER_DNSCHANNEL),
200202
timer_handle_(nullptr),
201203
channel_(nullptr),
202204
query_last_ok_(true),
203205
is_servers_default_(true),
204206
library_inited_(false),
207+
timeout_(timeout),
205208
active_query_count_(0) {
206209
MakeWeak();
207210

@@ -210,10 +213,11 @@ ChannelWrap::ChannelWrap(Environment* env,
210213

211214
void ChannelWrap::New(const FunctionCallbackInfo<Value>& args) {
212215
CHECK(args.IsConstructCall());
213-
CHECK_EQ(args.Length(), 0);
214-
216+
CHECK_EQ(args.Length(), 1);
217+
CHECK(args[0]->IsInt32());
218+
const int timeout = args[0].As<Int32>()->Value();
215219
Environment* env = Environment::GetCurrent(args);
216-
new ChannelWrap(env, args.This());
220+
new ChannelWrap(env, args.This(), timeout);
217221
}
218222

219223
class GetAddrInfoReqWrap : public ReqWrap<uv_getaddrinfo_t> {
@@ -462,6 +466,7 @@ void ChannelWrap::Setup() {
462466
options.flags = ARES_FLAG_NOCHECKRESP;
463467
options.sock_state_cb = ares_sockstate_cb;
464468
options.sock_state_cb_data = this;
469+
options.timeout = timeout_;
465470

466471
int r;
467472
if (!library_inited_) {
@@ -474,9 +479,9 @@ void ChannelWrap::Setup() {
474479
}
475480

476481
/* We do the call to ares_init_option for caller. */
477-
r = ares_init_options(&channel_,
478-
&options,
479-
ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB);
482+
const int optmask =
483+
ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | ARES_OPT_SOCK_STATE_CB;
484+
r = ares_init_options(&channel_, &options, optmask);
480485

481486
if (r != ARES_SUCCESS) {
482487
Mutex::ScopedLock lock(ares_library_mutex);
@@ -495,7 +500,10 @@ void ChannelWrap::StartTimer() {
495500
} else if (uv_is_active(reinterpret_cast<uv_handle_t*>(timer_handle_))) {
496501
return;
497502
}
498-
uv_timer_start(timer_handle_, AresTimeout, 1000, 1000);
503+
int timeout = timeout_;
504+
if (timeout == 0) timeout = 1;
505+
if (timeout < 0 || timeout > 1000) timeout = 1000;
506+
uv_timer_start(timer_handle_, AresTimeout, timeout, timeout);
499507
}
500508

501509
void ChannelWrap::CloseTimer() {
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const dgram = require('dgram');
5+
const dns = require('dns');
6+
7+
for (const ctor of [dns.Resolver, dns.promises.Resolver]) {
8+
for (const timeout of [null, true, false, '', '2']) {
9+
assert.throws(() => new ctor({ timeout }), {
10+
code: 'ERR_INVALID_ARG_TYPE',
11+
name: 'TypeError',
12+
});
13+
}
14+
15+
for (const timeout of [-2, 4.2, 2 ** 31]) {
16+
assert.throws(() => new ctor({ timeout }), {
17+
code: 'ERR_OUT_OF_RANGE',
18+
name: 'RangeError',
19+
});
20+
}
21+
22+
for (const timeout of [-1, 0, 1]) new ctor({ timeout }); // OK
23+
}
24+
25+
for (const timeout of [0, 1, 2]) {
26+
const server = dgram.createSocket('udp4');
27+
server.bind(0, '127.0.0.1', common.mustCall(() => {
28+
const resolver = new dns.Resolver({ timeout });
29+
resolver.setServers([`127.0.0.1:${server.address().port}`]);
30+
resolver.resolve4('nodejs.org', common.mustCall((err) => {
31+
assert.throws(() => { throw err; }, {
32+
code: 'ETIMEOUT',
33+
name: 'Error',
34+
});
35+
server.close();
36+
}));
37+
}));
38+
}
39+
40+
for (const timeout of [0, 1, 2]) {
41+
const server = dgram.createSocket('udp4');
42+
server.bind(0, '127.0.0.1', common.mustCall(() => {
43+
const resolver = new dns.promises.Resolver({ timeout });
44+
resolver.setServers([`127.0.0.1:${server.address().port}`]);
45+
resolver.resolve4('nodejs.org').catch(common.mustCall((err) => {
46+
assert.throws(() => { throw err; }, {
47+
code: 'ETIMEOUT',
48+
name: 'Error',
49+
});
50+
server.close();
51+
}));
52+
}));
53+
}

0 commit comments

Comments
 (0)