dns: fix Windows SRV ECONNREFUSED by adjusting c-ares fallback detection · nodejs/node@e71e950 · GitHub
Skip to content

Commit e71e950

Browse 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

4 files changed

Lines changed: 281 additions & 6 deletions

File tree

src/cares_wrap.cc

Lines changed: 22 additions & 6 deletions

test/common/dns.js

Lines 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
}
Lines 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+
}
Lines changed: 102 additions & 0 deletions

0 commit comments

Comments
 (0)