Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2dc99d2

Browse filesBrowse files
NotVivek12marco-ippolito
authored andcommitted
dns: fix Windows SRV ECONNREFUSED by adjusting c-ares fallback detection
Newer c-ares versions set tcp_port/udp_port to 53 instead of 0, which caused the loopback detection to fail. This fix: - Removes the port check from loopback detection - Adds IPv6 loopback (::1) support - Calls EnsureServers() before each DNS query PR-URL: #61453 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 6dc2a99 commit 2dc99d2
Copy full SHA for 2dc99d2

4 files changed

+281-6Lines changed: 281 additions & 6 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/cares_wrap.cc‎

Copy file name to clipboardExpand all lines: src/cares_wrap.cc
+22-6Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -796,19 +796,31 @@ void ChannelWrap::EnsureServers() {
796796

797797
ares_get_servers_ports(channel_, &servers);
798798

799-
/* if no server or multi-servers, ignore */
799+
/* if no server, ignore */
800800
if (servers == nullptr) return;
801+
802+
/* if multi-servers, mark as non-default and ignore */
801803
if (servers->next != nullptr) {
802804
ares_free_data(servers);
803805
is_servers_default_ = false;
804806
return;
805807
}
806808

807-
/* if the only server is not 127.0.0.1, ignore */
808-
if (servers[0].family != AF_INET ||
809-
servers[0].addr.addr4.s_addr != htonl(INADDR_LOOPBACK) ||
810-
servers[0].tcp_port != 0 ||
811-
servers[0].udp_port != 0) {
809+
/* Check if the only server is a loopback address (IPv4 127.0.0.1 or IPv6
810+
* ::1). Newer c-ares versions may set tcp_port/udp_port to 53 instead of 0,
811+
* so we no longer check port values. */
812+
bool is_loopback = false;
813+
if (servers[0].family == AF_INET) {
814+
is_loopback = (servers[0].addr.addr4.s_addr == htonl(INADDR_LOOPBACK));
815+
} else if (servers[0].family == AF_INET6) {
816+
static const unsigned char kIPv6Loopback[16] = {
817+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
818+
is_loopback =
819+
(memcmp(&servers[0].addr.addr6, kIPv6Loopback, sizeof(kIPv6Loopback)) ==
820+
0);
821+
}
822+
823+
if (!is_loopback) {
812824
ares_free_data(servers);
813825
is_servers_default_ = false;
814826
return;
@@ -1413,6 +1425,10 @@ static void Query(const FunctionCallbackInfo<Value>& args) {
14131425
node::Utf8Value utf8name(env->isolate(), string);
14141426
auto plain_name = utf8name.ToStringView();
14151427
std::string name = ada::idna::to_ascii(plain_name);
1428+
1429+
// Ensure c-ares did not fall back to loopback resolver.
1430+
channel->EnsureServers();
1431+
14161432
channel->ModifyActivityQueryCount(1);
14171433
int err = wrap->Send(name.c_str());
14181434
if (err) {
Collapse file

‎test/common/dns.js‎

Copy file name to clipboardExpand all lines: test/common/dns.js
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const types = {
1313
PTR: 12,
1414
MX: 15,
1515
TXT: 16,
16+
SRV: 33,
1617
ANY: 255,
1718
CAA: 257,
1819
};
@@ -279,6 +280,15 @@ function writeDNSPacket(parsed) {
279280
buffers.push(Buffer.from('issue' + rr.issue));
280281
break;
281282
}
283+
case 'SRV':
284+
{
285+
// SRV record format: priority (2) + weight (2) + port (2) + target
286+
const target = writeDomainName(rr.name);
287+
rdLengthBuf[0] = 6 + target.length;
288+
buffers.push(new Uint16Array([rr.priority, rr.weight, rr.port]));
289+
buffers.push(target);
290+
break;
291+
}
282292
default:
283293
throw new Error(`Unknown RR type ${rr.type}`);
284294
}
Collapse file
+147Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
'use strict';
2+
// Regression test for SRV record resolution returning ECONNREFUSED.
3+
//
4+
// This test verifies that dns.resolveSrv() properly handles SRV queries
5+
// and doesn't incorrectly return ECONNREFUSED errors when DNS servers
6+
// are reachable but the query format or handling has issues.
7+
//
8+
// Background: In certain Node.js versions, SRV queries could fail with
9+
// ECONNREFUSED even when the DNS server was accessible, affecting
10+
// applications using MongoDB Atlas (mongodb+srv://) and other services
11+
// that rely on SRV record discovery.
12+
13+
const common = require('../common');
14+
const dnstools = require('../common/dns');
15+
const dns = require('dns');
16+
const dnsPromises = dns.promises;
17+
const assert = require('assert');
18+
const dgram = require('dgram');
19+
20+
// Test 1: Basic SRV resolution should succeed, not return ECONNREFUSED
21+
{
22+
const server = dgram.createSocket('udp4');
23+
const srvRecord = {
24+
type: 'SRV',
25+
name: 'mongodb-server.cluster0.example.net',
26+
port: 27017,
27+
priority: 0,
28+
weight: 1,
29+
ttl: 60,
30+
};
31+
32+
server.on('message', common.mustCall((msg, { address, port }) => {
33+
const parsed = dnstools.parseDNSPacket(msg);
34+
const domain = parsed.questions[0].domain;
35+
36+
server.send(dnstools.writeDNSPacket({
37+
id: parsed.id,
38+
questions: parsed.questions,
39+
answers: [Object.assign({ domain }, srvRecord)],
40+
}), port, address);
41+
}));
42+
43+
server.bind(0, common.mustCall(async () => {
44+
const { port } = server.address();
45+
const resolver = new dnsPromises.Resolver();
46+
resolver.setServers([`127.0.0.1:${port}`]);
47+
48+
try {
49+
const result = await resolver.resolveSrv(
50+
'_mongodb._tcp.cluster0.example.net'
51+
);
52+
53+
// Should NOT throw ECONNREFUSED
54+
assert.ok(Array.isArray(result));
55+
assert.strictEqual(result.length, 1);
56+
assert.strictEqual(result[0].name, 'mongodb-server.cluster0.example.net');
57+
assert.strictEqual(result[0].port, 27017);
58+
assert.strictEqual(result[0].priority, 0);
59+
assert.strictEqual(result[0].weight, 1);
60+
} catch (err) {
61+
// This is the regression: should NOT get ECONNREFUSED
62+
assert.notStrictEqual(err.code, 'ECONNREFUSED');
63+
throw err;
64+
} finally {
65+
server.close();
66+
}
67+
}));
68+
}
69+
70+
// Test 2: Multiple SRV records (common for MongoDB Atlas clusters)
71+
{
72+
const server = dgram.createSocket('udp4');
73+
const srvRecords = [
74+
{ type: 'SRV', name: 'shard-00-00.cluster.mongodb.net', port: 27017, priority: 0, weight: 1, ttl: 60 },
75+
{ type: 'SRV', name: 'shard-00-01.cluster.mongodb.net', port: 27017, priority: 0, weight: 1, ttl: 60 },
76+
{ type: 'SRV', name: 'shard-00-02.cluster.mongodb.net', port: 27017, priority: 0, weight: 1, ttl: 60 },
77+
];
78+
79+
server.on('message', common.mustCall((msg, { address, port }) => {
80+
const parsed = dnstools.parseDNSPacket(msg);
81+
const domain = parsed.questions[0].domain;
82+
83+
server.send(dnstools.writeDNSPacket({
84+
id: parsed.id,
85+
questions: parsed.questions,
86+
answers: srvRecords.map((r) => Object.assign({ domain }, r)),
87+
}), port, address);
88+
}));
89+
90+
server.bind(0, common.mustCall(async () => {
91+
const { port } = server.address();
92+
const resolver = new dnsPromises.Resolver();
93+
resolver.setServers([`127.0.0.1:${port}`]);
94+
95+
const result = await resolver.resolveSrv('_mongodb._tcp.cluster.mongodb.net');
96+
97+
assert.strictEqual(result.length, 3);
98+
99+
const names = result.map((r) => r.name).sort();
100+
assert.deepStrictEqual(names, [
101+
'shard-00-00.cluster.mongodb.net',
102+
'shard-00-01.cluster.mongodb.net',
103+
'shard-00-02.cluster.mongodb.net',
104+
]);
105+
106+
server.close();
107+
}));
108+
}
109+
110+
// Test 3: Callback-based API should also work
111+
{
112+
const server = dgram.createSocket('udp4');
113+
114+
server.on('message', common.mustCall((msg, { address, port }) => {
115+
const parsed = dnstools.parseDNSPacket(msg);
116+
const domain = parsed.questions[0].domain;
117+
118+
server.send(dnstools.writeDNSPacket({
119+
id: parsed.id,
120+
questions: parsed.questions,
121+
answers: [{
122+
domain,
123+
type: 'SRV',
124+
name: 'service.example.com',
125+
port: 443,
126+
priority: 10,
127+
weight: 5,
128+
ttl: 120,
129+
}],
130+
}), port, address);
131+
}));
132+
133+
server.bind(0, common.mustCall(() => {
134+
const { port } = server.address();
135+
const resolver = new dns.Resolver();
136+
resolver.setServers([`127.0.0.1:${port}`]);
137+
138+
resolver.resolveSrv('_https._tcp.example.com', common.mustSucceed((result) => {
139+
assert.strictEqual(result.length, 1);
140+
assert.strictEqual(result[0].name, 'service.example.com');
141+
assert.strictEqual(result[0].port, 443);
142+
assert.strictEqual(result[0].priority, 10);
143+
assert.strictEqual(result[0].weight, 5);
144+
server.close();
145+
}));
146+
}));
147+
}
Collapse file
+102Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict';
2+
// Regression test for dns.resolveSrv() functionality.
3+
// This test ensures SRV record resolution works correctly, which is
4+
// critical for services like MongoDB Atlas that use SRV records for
5+
// connection discovery (mongodb+srv:// URIs).
6+
//
7+
// Related issue: dns.resolveSrv() returning ECONNREFUSED instead of
8+
// properly resolving SRV records.
9+
10+
const common = require('../common');
11+
const dnstools = require('../common/dns');
12+
const dns = require('dns');
13+
const dnsPromises = dns.promises;
14+
const assert = require('assert');
15+
const dgram = require('dgram');
16+
17+
const srvRecords = [
18+
{
19+
type: 'SRV',
20+
name: 'server1.example.org',
21+
port: 27017,
22+
priority: 0,
23+
weight: 5,
24+
ttl: 300,
25+
},
26+
{
27+
type: 'SRV',
28+
name: 'server2.example.org',
29+
port: 27017,
30+
priority: 0,
31+
weight: 5,
32+
ttl: 300,
33+
},
34+
{
35+
type: 'SRV',
36+
name: 'server3.example.org',
37+
port: 27017,
38+
priority: 1,
39+
weight: 10,
40+
ttl: 300,
41+
},
42+
];
43+
44+
const server = dgram.createSocket('udp4');
45+
46+
server.on('message', common.mustCall((msg, { address, port }) => {
47+
const parsed = dnstools.parseDNSPacket(msg);
48+
const domain = parsed.questions[0].domain;
49+
assert.strictEqual(domain, '_mongodb._tcp.cluster0.example.org');
50+
51+
server.send(dnstools.writeDNSPacket({
52+
id: parsed.id,
53+
questions: parsed.questions,
54+
answers: srvRecords.map((record) => Object.assign({ domain }, record)),
55+
}), port, address);
56+
}, 2)); // Called twice: once for callback, once for promises
57+
58+
server.bind(0, common.mustCall(async () => {
59+
const address = server.address();
60+
const resolver = new dns.Resolver();
61+
const resolverPromises = new dnsPromises.Resolver();
62+
63+
resolver.setServers([`127.0.0.1:${address.port}`]);
64+
resolverPromises.setServers([`127.0.0.1:${address.port}`]);
65+
66+
function validateResult(result) {
67+
assert.ok(Array.isArray(result), 'Result should be an array');
68+
assert.strictEqual(result.length, 3);
69+
70+
for (const record of result) {
71+
assert.strictEqual(typeof record, 'object');
72+
assert.strictEqual(typeof record.name, 'string');
73+
assert.strictEqual(typeof record.port, 'number');
74+
assert.strictEqual(typeof record.priority, 'number');
75+
assert.strictEqual(typeof record.weight, 'number');
76+
assert.strictEqual(record.port, 27017);
77+
}
78+
79+
// Verify we got all expected server names
80+
const names = result.map((r) => r.name).sort();
81+
assert.deepStrictEqual(names, [
82+
'server1.example.org',
83+
'server2.example.org',
84+
'server3.example.org',
85+
]);
86+
}
87+
88+
// Test promises API
89+
const promiseResult = await resolverPromises.resolveSrv(
90+
'_mongodb._tcp.cluster0.example.org'
91+
);
92+
validateResult(promiseResult);
93+
94+
// Test callback API
95+
resolver.resolveSrv(
96+
'_mongodb._tcp.cluster0.example.org',
97+
common.mustSucceed((result) => {
98+
validateResult(result);
99+
server.close();
100+
})
101+
);
102+
}));

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.