| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6dc2a99 commit 2dc99d2
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -796,19 +796,31 @@ void ChannelWrap::EnsureServers() { | |||
| 796 | 796 | ||
| 797 | 797 | ares_get_servers_ports(channel_, &servers); | |
| 798 | 798 | ||
| 799 | - /* if no server or multi-servers, ignore */ | ||
| 799 | + /* if no server, ignore */ | ||
| 800 | 800 | if (servers == nullptr) return; | |
| 801 | + | ||
| 802 | + /* if multi-servers, mark as non-default and ignore */ | ||
| 801 | 803 | if (servers->next != nullptr) { | |
| 802 | 804 | ares_free_data(servers); | |
| 803 | 805 | is_servers_default_ = false; | |
| 804 | 806 | return; | |
| 805 | 807 | } | |
| 806 | 808 | ||
| 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) { | ||
| 812 | 824 | ares_free_data(servers); | |
| 813 | 825 | is_servers_default_ = false; | |
| 814 | 826 | return; | |
@@ -1413,6 +1425,10 @@ static void Query(const FunctionCallbackInfo<Value>& args) { | |||
| 1413 | 1425 | node::Utf8Value utf8name(env->isolate(), string); | |
| 1414 | 1426 | auto plain_name = utf8name.ToStringView(); | |
| 1415 | 1427 | 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 | + | ||
| 1416 | 1432 | channel->ModifyActivityQueryCount(1); | |
| 1417 | 1433 | int err = wrap->Send(name.c_str()); | |
| 1418 | 1434 | if (err) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,6 +13,7 @@ const types = { | |||
| 13 | 13 | PTR: 12, | |
| 14 | 14 | MX: 15, | |
| 15 | 15 | TXT: 16, | |
| 16 | + SRV: 33, | ||
| 16 | 17 | ANY: 255, | |
| 17 | 18 | CAA: 257, | |
| 18 | 19 | }; | |
@@ -279,6 +280,15 @@ function writeDNSPacket(parsed) { | |||
| 279 | 280 | buffers.push(Buffer.from('issue' + rr.issue)); | |
| 280 | 281 | break; | |
| 281 | 282 | } | |
| 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 | + } | ||
| 282 | 292 | default: | |
| 283 | 293 | throw new Error(`Unknown RR type ${rr.type}`); | |
| 284 | 294 | } | |
| Original file line number | Diff line number | Diff 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 | + } | ||
| Original file line number | Diff line number | Diff 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 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments