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 e71e950

Browse filesBrowse files
NotVivek12aduh95
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 e90eb1d commit e71e950
Copy full SHA for e71e950

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
@@ -983,19 +983,31 @@ void ChannelWrap::EnsureServers() {
983983

984984
ares_get_servers_ports(channel_, &servers);
985985

986-
/* if no server or multi-servers, ignore */
986+
/* if no server, ignore */
987987
if (servers == nullptr) return;
988+
989+
/* if multi-servers, mark as non-default and ignore */
988990
if (servers->next != nullptr) {
989991
ares_free_data(servers);
990992
is_servers_default_ = false;
991993
return;
992994
}
993995

994-
/* if the only server is not 127.0.0.1, ignore */
995-
if (servers[0].family != AF_INET ||
996-
servers[0].addr.addr4.s_addr != htonl(INADDR_LOOPBACK) ||
997-
servers[0].tcp_port != 0 ||
998-
servers[0].udp_port != 0) {
996+
/* Check if the only server is a loopback address (IPv4 127.0.0.1 or IPv6
997+
* ::1). Newer c-ares versions may set tcp_port/udp_port to 53 instead of 0,
998+
* so we no longer check port values. */
999+
bool is_loopback = false;
1000+
if (servers[0].family == AF_INET) {
1001+
is_loopback = (servers[0].addr.addr4.s_addr == htonl(INADDR_LOOPBACK));
1002+
} else if (servers[0].family == AF_INET6) {
1003+
static const unsigned char kIPv6Loopback[16] = {
1004+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
1005+
is_loopback =
1006+
(memcmp(&servers[0].addr.addr6, kIPv6Loopback, sizeof(kIPv6Loopback)) ==
1007+
0);
1008+
}
1009+
1010+
if (!is_loopback) {
9991011
ares_free_data(servers);
10001012
is_servers_default_ = false;
10011013
return;
@@ -1769,6 +1781,10 @@ static void Query(const FunctionCallbackInfo<Value>& args) {
17691781
node::Utf8Value utf8name(args.GetIsolate(), string);
17701782
auto plain_name = utf8name.ToStringView();
17711783
std::string name = ada::idna::to_ascii(plain_name);
1784+
1785+
// Ensure c-ares did not fall back to loopback resolver.
1786+
channel->EnsureServers();
1787+
17721788
channel->ModifyActivityQueryCount(1);
17731789
int err = wrap->Send(name.c_str());
17741790
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.