Skip to content
Merged
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
33 changes: 7 additions & 26 deletions lib/control-connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -895,40 +895,21 @@ class ControlConnection extends events.EventEmitter {
}

/**
* Uses the DNS protocol to resolve a IPv4 and IPv6 addresses (A and AAAA records) for the hostname.
* Uses the operating system resolver to resolve all IPv4 and IPv6 addresses for the hostname.
* It returns an Array of addresses that can be empty and logs the error.
* @private
* @param name
*/
async _resolveAll(name) {
const addresses = [];
const resolve4 = util.promisify(dns.resolve4);
const resolve6 = util.promisify(dns.resolve6);
const lookup = util.promisify(dns.lookup);

// Ignore errors for resolve calls
const ipv4Promise = resolve4(name).catch(() => {}).then(r => r || utils.emptyArray);
const ipv6Promise = resolve6(name).catch(() => {}).then(r => r || utils.emptyArray);

let arr;
arr = await ipv4Promise;
arr.forEach(address => addresses.push({address, isIPv6: false}));

arr = await ipv6Promise;
arr.forEach(address => addresses.push({address, isIPv6: true}));

if (addresses.length === 0) {
// In case dns.resolve*() methods don't yield a valid address for the host name
// Use system call getaddrinfo() that might resolve according to host system definitions
try {
arr = await lookup(name, { all: true });
arr.forEach(({address, family}) => addresses.push({address, isIPv6: family === 6}));
} catch (err) {
this.log('error', `Host with name ${name} could not be resolved`, err);
}
try {
const addresses = await lookup(name, { all: true });
Comment thread
finalchild marked this conversation as resolved.
Comment thread
SiyaoIsHiding marked this conversation as resolved.
return addresses.map(({address, family}) => ({address, isIPv6: family === 6}));
Comment thread
SiyaoIsHiding marked this conversation as resolved.
} catch (err) {
this.log('error', `Host with name ${name} could not be resolved`, err);
return utils.emptyArray;
}

return addresses;
}

/**
Expand Down
99 changes: 34 additions & 65 deletions test/unit/control-connection-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,12 @@ describe('ControlConnection', function () {

it('should resolve IPv4 and IPv6 addresses, default host (localhost) and port', () => {
const ControlConnectionMock = proxyquire('../../lib/control-connection', { dns: {
resolve4: function (name, cb) {
cb(null, ifLocalhost(name, ['127.0.0.1']));
},
resolve6: function (name, cb) {
cb(null, ifLocalhost(name, ['::1']));
},
lookup: function () {
throw new Error('dns.lookup() should not be used');
lookup: function (name, options, cb) {
assert.deepStrictEqual(options, { all: true });
cb(null, ifLocalhost(name, [
{ address: '127.0.0.1', family: 4 },
{ address: '::1', family: 6 }
]));
}
}});

Expand All @@ -99,14 +97,12 @@ describe('ControlConnection', function () {

it('should resolve IPv4 and IPv6 addresses with non default port', () => {
const ControlConnectionMock = proxyquire('../../lib/control-connection', { dns: {
resolve4: function (name, cb) {
cb(null, ifLocalhost(name, ['127.0.0.1']));
},
resolve6: function (name, cb) {
cb(null, ifLocalhost(name, ['::1']));
},
lookup: function () {
throw new Error('dns.lookup() should not be used');
lookup: function (name, options, cb) {
assert.deepStrictEqual(options, { all: true });
cb(null, ifLocalhost(name, [
{ address: '127.0.0.1', family: 4 },
{ address: '::1', family: 6 }
]));
}
}});

Expand All @@ -116,16 +112,23 @@ describe('ControlConnection', function () {
'localhost:9999');
});

it('should resolve all IPv4 and IPv6 addresses provided by dns.resolve()', () => {
it('should resolve all IPv4 and IPv6 addresses using the operating system resolver', () => {
const ControlConnectionMock = proxyquire('../../lib/control-connection', { dns: {
resolve4: function (name, cb) {
cb(null, ['1', '2']);
resolve4: function () {
throw new Error('dns.resolve4() should not be used');
},
resolve6: function (name, cb) {
cb(null, ['10', '20']);
resolve6: function () {
throw new Error('dns.resolve6() should not be used');
},
lookup: function () {
throw new Error('dns.lookup() should not be used');
lookup: function (name, options, cb) {
assert.strictEqual(name, 'localhost');
assert.deepStrictEqual(options, { all: true });
cb(null, [
{ address: '1', family: 4 },
{ address: '2', family: 4 },
{ address: '10', family: 6 },
{ address: '20', family: 6 }
]);
}
}});

Expand All @@ -134,52 +137,24 @@ describe('ControlConnection', function () {
[ '1:9042', '2:9042', '[10]:9042', '[20]:9042' ]);
});

it('should ignore IPv4 or IPv6 resolution errors', function () {
it('should handle system resolver errors', function () {
const ControlConnectionMock = proxyquire('../../lib/control-connection', { dns: {
resolve4: function (name, cb) {
cb(null, ['1', '2']);
},
resolve6: function (name, cb) {
lookup: function (name, options, cb) {
cb(new Error('Test error'));
},
lookup: function () {
throw new Error('dns.lookup() should not be used');
}
}});

return testResolution(ControlConnectionMock, [ '1:9042', '2:9042']);
return testResolution(ControlConnectionMock, [], utils.emptyArray);
});

it('should use dns.lookup() as failover', () => {
it('should handle an empty system resolver result', () => {
const ControlConnectionMock = proxyquire('../../lib/control-connection', { dns: {
resolve4: function (name, cb) {
cb(new Error('Test error'));
},
resolve6: function (name, cb) {
cb(new Error('Test error'));
},
lookup: function (name, options, cb) {
cb(null, [{ address: '123', family: 4 }]);
}
}});

return testResolution(ControlConnectionMock, [ '123:9042' ]);
});

it('should use dns.lookup() when no address was resolved', () => {
const ControlConnectionMock = proxyquire('../../lib/control-connection', { dns: {
resolve4: function (name, cb) {
cb(null);
},
resolve6: function (name, cb) {
cb(null, []);
},
lookup: function (name, options, cb) {
cb(null, [{ address: '1234', family: 4 }]);
}
}});

return testResolution(ControlConnectionMock, [ '1234:9042' ]);
return testResolution(ControlConnectionMock, [], utils.emptyArray);
});

it('should continue iterating through the hosts when borrowing a connection fails',async () => {
Expand Down Expand Up @@ -284,19 +259,13 @@ describe('ControlConnection', function () {
const resolvedAddresses = ['1','2'];

const ControlConnectionMock = proxyquire('../../lib/control-connection', { dns: {
resolve4: function (name, cb) {
lookup: function (name, options, cb) {
if (dnsWorks) {
cb(null, resolvedAddresses);
cb(null, resolvedAddresses.map(address => ({ address, family: 4 })));
}
else {
cb(null, []);
}
},
resolve6: function (name, cb) {
throw new Error('IPv6 resolution errors should be ignored');
},
lookup: function () {
throw new Error('dns.lookup() should not be used');
}
}});
const cc = new ControlConnectionMock(
Expand Down Expand Up @@ -590,4 +559,4 @@ function getContext(options) {
return state.connection = getFakeConnection(endpoint, options.queryResults);
}
};
}
}
Loading