dns: fix Windows SRV ECONNREFUSED regression by correcting c-ares fallback detection by NotVivek12 · Pull Request #61453 · nodejs/node · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/cares_wrap.cc
10 changes: 10 additions & 0 deletions test/common/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const types = {
PTR: 12,
MX: 15,
TXT: 16,
SRV: 33,
ANY: 255,
CAA: 257,
};
Expand Down Expand Up @@ -279,6 +280,15 @@ function writeDNSPacket(parsed) {
buffers.push(Buffer.from('issue' + rr.issue));
break;
}
case 'SRV':
{
// SRV record format: priority (2) + weight (2) + port (2) + target
const target = writeDomainName(rr.name);
rdLengthBuf[0] = 6 + target.length;
buffers.push(new Uint16Array([rr.priority, rr.weight, rr.port]));
buffers.push(target);
break;
}
default:
throw new Error(`Unknown RR type ${rr.type}`);
}
Expand Down
147 changes: 147 additions & 0 deletions test/parallel/test-dns-resolvesrv-econnrefused.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
'use strict';
// Regression test for SRV record resolution returning ECONNREFUSED.
//
// This test verifies that dns.resolveSrv() properly handles SRV queries
// and doesn't incorrectly return ECONNREFUSED errors when DNS servers
// are reachable but the query format or handling has issues.
//
// Background: In certain Node.js versions, SRV queries could fail with
// ECONNREFUSED even when the DNS server was accessible, affecting
// applications using MongoDB Atlas (mongodb+srv://) and other services
// that rely on SRV record discovery.

const common = require('../common');
const dnstools = require('../common/dns');
const dns = require('dns');
const dnsPromises = dns.promises;
const assert = require('assert');
const dgram = require('dgram');

// Test 1: Basic SRV resolution should succeed, not return ECONNREFUSED
{
const server = dgram.createSocket('udp4');
const srvRecord = {
type: 'SRV',
name: 'mongodb-server.cluster0.example.net',
port: 27017,
priority: 0,
weight: 1,
ttl: 60,
};

server.on('message', common.mustCall((msg, { address, port }) => {
const parsed = dnstools.parseDNSPacket(msg);
const domain = parsed.questions[0].domain;

server.send(dnstools.writeDNSPacket({
id: parsed.id,
questions: parsed.questions,
answers: [Object.assign({ domain }, srvRecord)],
}), port, address);
}));

server.bind(0, common.mustCall(async () => {
const { port } = server.address();
const resolver = new dnsPromises.Resolver();
resolver.setServers([`127.0.0.1:${port}`]);

try {
const result = await resolver.resolveSrv(
'_mongodb._tcp.cluster0.example.net'
);

// Should NOT throw ECONNREFUSED
assert.ok(Array.isArray(result));
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].name, 'mongodb-server.cluster0.example.net');
assert.strictEqual(result[0].port, 27017);
assert.strictEqual(result[0].priority, 0);
assert.strictEqual(result[0].weight, 1);
} catch (err) {
// This is the regression: should NOT get ECONNREFUSED
assert.notStrictEqual(err.code, 'ECONNREFUSED');
throw err;
} finally {
server.close();
}
}));
}

// Test 2: Multiple SRV records (common for MongoDB Atlas clusters)
{
const server = dgram.createSocket('udp4');
const srvRecords = [
{ type: 'SRV', name: 'shard-00-00.cluster.mongodb.net', port: 27017, priority: 0, weight: 1, ttl: 60 },
{ type: 'SRV', name: 'shard-00-01.cluster.mongodb.net', port: 27017, priority: 0, weight: 1, ttl: 60 },
{ type: 'SRV', name: 'shard-00-02.cluster.mongodb.net', port: 27017, priority: 0, weight: 1, ttl: 60 },
];

server.on('message', common.mustCall((msg, { address, port }) => {
const parsed = dnstools.parseDNSPacket(msg);
const domain = parsed.questions[0].domain;

server.send(dnstools.writeDNSPacket({
id: parsed.id,
questions: parsed.questions,
answers: srvRecords.map((r) => Object.assign({ domain }, r)),
}), port, address);
}));

server.bind(0, common.mustCall(async () => {
const { port } = server.address();
const resolver = new dnsPromises.Resolver();
resolver.setServers([`127.0.0.1:${port}`]);

const result = await resolver.resolveSrv('_mongodb._tcp.cluster.mongodb.net');

assert.strictEqual(result.length, 3);

const names = result.map((r) => r.name).sort();
assert.deepStrictEqual(names, [
'shard-00-00.cluster.mongodb.net',
'shard-00-01.cluster.mongodb.net',
'shard-00-02.cluster.mongodb.net',
]);

server.close();
}));
}

// Test 3: Callback-based API should also work
{
const server = dgram.createSocket('udp4');

server.on('message', common.mustCall((msg, { address, port }) => {
const parsed = dnstools.parseDNSPacket(msg);
const domain = parsed.questions[0].domain;

server.send(dnstools.writeDNSPacket({
id: parsed.id,
questions: parsed.questions,
answers: [{
domain,
type: 'SRV',
name: 'service.example.com',
port: 443,
priority: 10,
weight: 5,
ttl: 120,
}],
}), port, address);
}));

server.bind(0, common.mustCall(() => {
const { port } = server.address();
const resolver = new dns.Resolver();
resolver.setServers([`127.0.0.1:${port}`]);

resolver.resolveSrv('_https._tcp.example.com', common.mustSucceed((result) => {
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].name, 'service.example.com');
assert.strictEqual(result[0].port, 443);
assert.strictEqual(result[0].priority, 10);
assert.strictEqual(result[0].weight, 5);
server.close();
}));
}));
}
102 changes: 102 additions & 0 deletions test/parallel/test-dns-resolvesrv.js
Loading