Skip to content

Commit c52aaac

Browse files
theanarkhtargos
authored andcommitted
dns: support max timeout
PR-URL: #58440 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent cb95e18 commit c52aaac

File tree

5 files changed

+118
-22
lines changed

5 files changed

+118
-22
lines changed

doc/api/dns.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ Create a new resolver.
157157
default timeout.
158158
* `tries` {integer} The number of tries the resolver will try contacting
159159
each name server before giving up. **Default:** `4`
160+
* `maxTimeout` {integer} The max retry timeout, in milliseconds.
161+
**Default:** `0`, disabled.
160162

161163
### `resolver.cancel()`
162164

lib/internal/dns/utils.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
validateInt32,
2626
validateOneOf,
2727
validateString,
28+
validateUint32,
2829
} = require('internal/validators');
2930
let binding;
3031
function lazyBinding() {
@@ -49,6 +50,12 @@ function validateTimeout(options) {
4950
return timeout;
5051
}
5152

53+
function validateMaxTimeout(options) {
54+
const { maxTimeout = 0 } = { ...options };
55+
validateUint32(maxTimeout, 'options.maxTimeout');
56+
return maxTimeout;
57+
}
58+
5259
function validateTries(options) {
5360
const { tries = 4 } = { ...options };
5461
validateInt32(tries, 'options.tries', 1);
@@ -67,17 +74,18 @@ class ResolverBase {
6774
constructor(options = undefined) {
6875
const timeout = validateTimeout(options);
6976
const tries = validateTries(options);
77+
const maxTimeout = validateMaxTimeout(options);
7078
// If we are building snapshot, save the states of the resolver along
7179
// the way.
7280
if (isBuildingSnapshot()) {
73-
this[kSnapshotStates] = { timeout, tries };
81+
this[kSnapshotStates] = { timeout, tries, maxTimeout };
7482
}
75-
this[kInitializeHandle](timeout, tries);
83+
this[kInitializeHandle](timeout, tries, maxTimeout);
7684
}
7785

78-
[kInitializeHandle](timeout, tries) {
86+
[kInitializeHandle](timeout, tries, maxTimeout) {
7987
const { ChannelWrap } = lazyBinding();
80-
this._handle = new ChannelWrap(timeout, tries);
88+
this._handle = new ChannelWrap(timeout, tries, maxTimeout);
8189
}
8290

8391
cancel() {
@@ -187,8 +195,8 @@ class ResolverBase {
187195
}
188196

189197
[kDeserializeResolver]() {
190-
const { timeout, tries, localAddress, servers } = this[kSnapshotStates];
191-
this[kInitializeHandle](timeout, tries);
198+
const { timeout, tries, maxTimeout, localAddress, servers } = this[kSnapshotStates];
199+
this[kInitializeHandle](timeout, tries, maxTimeout);
192200
if (localAddress) {
193201
const { ipv4, ipv6 } = localAddress;
194202
this._handle.setLocalAddress(ipv4, ipv6);

src/cares_wrap.cc

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -787,14 +787,15 @@ Maybe<int> ParseSoaReply(Environment* env,
787787
}
788788
} // anonymous namespace
789789

790-
ChannelWrap::ChannelWrap(
791-
Environment* env,
792-
Local<Object> object,
793-
int timeout,
794-
int tries)
790+
ChannelWrap::ChannelWrap(Environment* env,
791+
Local<Object> object,
792+
int timeout,
793+
int tries,
794+
int max_timeout)
795795
: AsyncWrap(env, object, PROVIDER_DNSCHANNEL),
796796
timeout_(timeout),
797-
tries_(tries) {
797+
tries_(tries),
798+
max_timeout_(max_timeout) {
798799
MakeWeak();
799800

800801
Setup();
@@ -808,13 +809,15 @@ void ChannelWrap::MemoryInfo(MemoryTracker* tracker) const {
808809

809810
void ChannelWrap::New(const FunctionCallbackInfo<Value>& args) {
810811
CHECK(args.IsConstructCall());
811-
CHECK_EQ(args.Length(), 2);
812+
CHECK_EQ(args.Length(), 3);
812813
CHECK(args[0]->IsInt32());
813814
CHECK(args[1]->IsInt32());
815+
CHECK(args[2]->IsInt32());
814816
const int timeout = args[0].As<Int32>()->Value();
815817
const int tries = args[1].As<Int32>()->Value();
818+
const int max_timeout = args[2].As<Int32>()->Value();
816819
Environment* env = Environment::GetCurrent(args);
817-
new ChannelWrap(env, args.This(), timeout, tries);
820+
new ChannelWrap(env, args.This(), timeout, tries, max_timeout);
818821
}
819822

820823
GetAddrInfoReqWrap::GetAddrInfoReqWrap(Environment* env,
@@ -879,9 +882,14 @@ void ChannelWrap::Setup() {
879882
}
880883

881884
/* We do the call to ares_init_option for caller. */
882-
const int optmask = ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS |
883-
ARES_OPT_SOCK_STATE_CB | ARES_OPT_TRIES |
884-
ARES_OPT_QUERY_CACHE;
885+
int optmask = ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | ARES_OPT_SOCK_STATE_CB |
886+
ARES_OPT_TRIES | ARES_OPT_QUERY_CACHE;
887+
888+
if (max_timeout_ > 0) {
889+
options.maxtimeout = max_timeout_;
890+
optmask |= ARES_OPT_MAXTIMEOUTMS;
891+
}
892+
885893
r = ares_init_options(&channel_, &options, optmask);
886894

887895
if (r != ARES_SUCCESS) {

src/cares_wrap.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ struct NodeAresTask final : public MemoryRetainer {
151151

152152
class ChannelWrap final : public AsyncWrap {
153153
public:
154-
ChannelWrap(
155-
Environment* env,
156-
v8::Local<v8::Object> object,
157-
int timeout,
158-
int tries);
154+
ChannelWrap(Environment* env,
155+
v8::Local<v8::Object> object,
156+
int timeout,
157+
int tries,
158+
int max_timeout);
159159
~ChannelWrap() override;
160160

161161
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -190,6 +190,7 @@ class ChannelWrap final : public AsyncWrap {
190190
bool library_inited_ = false;
191191
int timeout_;
192192
int tries_;
193+
int max_timeout_;
193194
int active_query_count_ = 0;
194195
NodeAresTask::List task_list_;
195196
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict';
2+
const common = require('../common');
3+
const dnstools = require('../common/dns');
4+
const dns = require('dns');
5+
const assert = require('assert');
6+
const dgram = require('dgram');
7+
8+
[
9+
-1,
10+
1.1,
11+
NaN,
12+
undefined,
13+
{},
14+
[],
15+
null,
16+
function() {},
17+
Symbol(),
18+
true,
19+
Infinity,
20+
].forEach((maxTimeout) => {
21+
try {
22+
new dns.Resolver({ maxTimeout });
23+
} catch (e) {
24+
assert.ok(/ERR_OUT_OF_RANGE|ERR_INVALID_ARG_TYPE/i.test(e.code));
25+
}
26+
});
27+
28+
const server = dgram.createSocket('udp4');
29+
const nxdomain = 'nxdomain.org';
30+
const domain = 'example.org';
31+
const answers = [{ type: 'A', address: '1.2.3.4', ttl: 123, domain }];
32+
33+
server.on('message', common.mustCallAtLeast((msg, { address, port }) => {
34+
const parsed = dnstools.parseDNSPacket(msg);
35+
if (parsed.questions[0].domain === nxdomain) {
36+
return;
37+
}
38+
assert.strictEqual(parsed.questions[0].domain, domain);
39+
server.send(dnstools.writeDNSPacket({
40+
id: parsed.id,
41+
questions: parsed.questions,
42+
answers: answers,
43+
}), port, address);
44+
}), 1);
45+
46+
server.bind(0, common.mustCall(async () => {
47+
const address = server.address();
48+
// Test if the Resolver works as before.
49+
const resolver = new dns.promises.Resolver({ timeout: 1000, tries: 1, maxTimeout: 1000 });
50+
resolver.setServers([`127.0.0.1:${address.port}`]);
51+
const res = await resolver.resolveAny('example.org');
52+
assert.strictEqual(res.length, 1);
53+
assert.strictEqual(res.length, answers.length);
54+
assert.strictEqual(res[0].address, answers[0].address);
55+
56+
// Test that maxTimeout is effective.
57+
// Without maxTimeout, the timeout will keep increasing when retrying.
58+
const timeout1 = await timeout(address, { timeout: 500, tries: 3 });
59+
// With maxTimeout, the timeout will always be 500 when retrying.
60+
const timeout2 = await timeout(address, { timeout: 500, tries: 3, maxTimeout: 500 });
61+
console.log(`timeout1: ${timeout1}, timeout2: ${timeout2}`);
62+
assert.strictEqual(timeout1 !== undefined && timeout2 !== undefined, true);
63+
assert.strictEqual(timeout1 > timeout2, true);
64+
server.close();
65+
}));
66+
67+
async function timeout(address, options) {
68+
const start = Date.now();
69+
const resolver = new dns.promises.Resolver(options);
70+
resolver.setServers([`127.0.0.1:${address.port}`]);
71+
try {
72+
await resolver.resolveAny(nxdomain);
73+
} catch (e) {
74+
assert.strictEqual(e.code, 'ETIMEOUT');
75+
return Date.now() - start;
76+
}
77+
}

0 commit comments

Comments
 (0)