mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
net: add connection attempt events
PR-URL: https://github.com/nodejs/node/pull/51045 Fixes: https://github.com/nodejs/node/issues/48763 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
This commit is contained in:
parent
abbdc3efaa
commit
2cb94240f9
@ -691,6 +691,47 @@ added: v0.1.90
|
||||
Emitted when a socket connection is successfully established.
|
||||
See [`net.createConnection()`][].
|
||||
|
||||
### Event: `'connectionAttempt'`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `ip` {number} The IP which the socket is attempting to connect to.
|
||||
* `port` {number} The port which the socket is attempting to connect to.
|
||||
* `family` {number} The family of the IP. It can be `6` for IPv6 or `4` for IPv4.
|
||||
|
||||
Emitted when a new connection attempt is started. This may be emitted multiple times
|
||||
if the family autoselection algorithm is enabled in [`socket.connect(options)`][].
|
||||
|
||||
### Event: `'connectionAttemptFailed'`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `ip` {number} The IP which the socket attempted to connect to.
|
||||
* `port` {number} The port which the socket attempted to connect to.
|
||||
* `family` {number} The family of the IP. It can be `6` for IPv6 or `4` for IPv4.
|
||||
\* `error` {Error} The error associated with the failure.
|
||||
|
||||
Emitted when a connection attempt failed. This may be emitted multiple times
|
||||
if the family autoselection algorithm is enabled in [`socket.connect(options)`][].
|
||||
|
||||
### Event: `'connectionAttemptTimeout'`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `ip` {number} The IP which the socket attempted to connect to.
|
||||
* `port` {number} The port which the socket attempted to connect to.
|
||||
* `family` {number} The family of the IP. It can be `6` for IPv6 or `4` for IPv4.
|
||||
|
||||
Emitted when a connection attempt timed out. This is only emitted (and may be
|
||||
emitted multiple times) if the family autoselection algorithm is enabled
|
||||
in [`socket.connect(options)`][].
|
||||
|
||||
### Event: `'data'`
|
||||
|
||||
<!-- YAML
|
||||
@ -963,8 +1004,7 @@ For TCP connections, available `options` are:
|
||||
obtained IPv6 and IPv4 addresses, in sequence, until a connection is established.
|
||||
The first returned AAAA address is tried first, then the first returned A address,
|
||||
then the second returned AAAA address and so on.
|
||||
Each connection attempt is given the amount of time specified by the `autoSelectFamilyAttemptTimeout`
|
||||
option before timing out and trying the next address.
|
||||
Each connection attempt (but the last one) is given the amount of time specified by the `autoSelectFamilyAttemptTimeout` option before timing out and trying the next address.
|
||||
Ignored if the `family` option is not `0` or if `localAddress` is set.
|
||||
Connection errors are not emitted if at least one connection succeeds.
|
||||
If all connections attempts fails, a single `AggregateError` with all failed attempts is emitted.
|
||||
|
31
lib/net.js
31
lib/net.js
@ -1058,6 +1058,7 @@ function internalConnect(
|
||||
}
|
||||
|
||||
debug('connect: attempting to connect to %s:%d (addressType: %d)', address, port, addressType);
|
||||
self.emit('connectionAttempt', address, port, addressType);
|
||||
|
||||
if (addressType === 6 || addressType === 4) {
|
||||
const req = new TCPConnectWrap();
|
||||
@ -1066,6 +1067,7 @@ function internalConnect(
|
||||
req.port = port;
|
||||
req.localAddress = localAddress;
|
||||
req.localPort = localPort;
|
||||
req.addressType = addressType;
|
||||
|
||||
if (addressType === 4)
|
||||
err = self._handle.connect(req, address, port);
|
||||
@ -1149,6 +1151,7 @@ function internalConnectMultiple(context, canceled) {
|
||||
}
|
||||
|
||||
debug('connect/multiple: attempting to connect to %s:%d (addressType: %d)', address, port, addressType);
|
||||
self.emit('connectionAttempt', address, port, addressType);
|
||||
|
||||
const req = new TCPConnectWrap();
|
||||
req.oncomplete = FunctionPrototypeBind(afterConnectMultiple, undefined, context, current);
|
||||
@ -1156,6 +1159,7 @@ function internalConnectMultiple(context, canceled) {
|
||||
req.port = port;
|
||||
req.localAddress = localAddress;
|
||||
req.localPort = localPort;
|
||||
req.addressType = addressType;
|
||||
|
||||
ArrayPrototypePush(self.autoSelectFamilyAttemptedAddresses, `${address}:${port}`);
|
||||
|
||||
@ -1173,7 +1177,10 @@ function internalConnectMultiple(context, canceled) {
|
||||
details = sockname.address + ':' + sockname.port;
|
||||
}
|
||||
|
||||
ArrayPrototypePush(context.errors, new ExceptionWithHostPort(err, 'connect', address, port, details));
|
||||
const ex = new ExceptionWithHostPort(err, 'connect', address, port, details);
|
||||
ArrayPrototypePush(context.errors, ex);
|
||||
|
||||
self.emit('connectionAttemptFailed', address, port, addressType, ex);
|
||||
internalConnectMultiple(context);
|
||||
return;
|
||||
}
|
||||
@ -1601,6 +1608,8 @@ function afterConnect(status, handle, req, readable, writable) {
|
||||
ex.localAddress = req.localAddress;
|
||||
ex.localPort = req.localPort;
|
||||
}
|
||||
|
||||
self.emit('connectionAttemptFailed', req.address, req.port, req.addressType, ex);
|
||||
self.destroy(ex);
|
||||
}
|
||||
}
|
||||
@ -1661,10 +1670,16 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
|
||||
|
||||
// Some error occurred, add to the list of exceptions
|
||||
if (status !== 0) {
|
||||
ArrayPrototypePush(context.errors, createConnectionError(req, status));
|
||||
const ex = createConnectionError(req, status);
|
||||
ArrayPrototypePush(context.errors, ex);
|
||||
|
||||
self.emit('connectionAttemptFailed', req.address, req.port, req.addressType, ex);
|
||||
|
||||
// Try the next address, unless we were aborted
|
||||
if (context.socket.connecting) {
|
||||
internalConnectMultiple(context, status === UV_ECANCELED);
|
||||
}
|
||||
|
||||
// Try the next address
|
||||
internalConnectMultiple(context, status === UV_ECANCELED);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1681,10 +1696,16 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
|
||||
|
||||
function internalConnectMultipleTimeout(context, req, handle) {
|
||||
debug('connect/multiple: connection to %s:%s timed out', req.address, req.port);
|
||||
context.socket.emit('connectionAttemptTimeout', req.address, req.port, req.addressType);
|
||||
|
||||
req.oncomplete = undefined;
|
||||
ArrayPrototypePush(context.errors, createConnectionError(req, UV_ETIMEDOUT));
|
||||
handle.close();
|
||||
internalConnectMultiple(context);
|
||||
|
||||
// Try the next address, unless we were aborted
|
||||
if (context.socket.connecting) {
|
||||
internalConnectMultiple(context);
|
||||
}
|
||||
}
|
||||
|
||||
function addServerAbortSignalOption(self, options) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
const assert = require('assert');
|
||||
const os = require('os');
|
||||
const { isIP } = require('net');
|
||||
|
||||
const types = {
|
||||
A: 1,
|
||||
@ -309,6 +310,25 @@ function errorLookupMock(code = mockedErrorCode, syscall = mockedSysCall) {
|
||||
};
|
||||
}
|
||||
|
||||
function createMockedLookup(...addresses) {
|
||||
addresses = addresses.map((address) => ({ address: address, family: isIP(address) }));
|
||||
|
||||
// Create a DNS server which replies with a AAAA and a A record for the same host
|
||||
return function lookup(hostname, options, cb) {
|
||||
if (options.all === true) {
|
||||
process.nextTick(() => {
|
||||
cb(null, addresses);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
process.nextTick(() => {
|
||||
cb(null, addresses[0].address, addresses[0].family);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
types,
|
||||
classes,
|
||||
@ -317,4 +337,5 @@ module.exports = {
|
||||
errorLookupMock,
|
||||
mockedErrorCode,
|
||||
mockedSysCall,
|
||||
createMockedLookup,
|
||||
};
|
||||
|
128
test/internet/test-net-autoselectfamily-events-failure.js
Normal file
128
test/internet/test-net-autoselectfamily-events-failure.js
Normal file
@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { addresses: { INET6_IP, INET4_IP } } = require('../common/internet');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const { createConnection } = require('net');
|
||||
|
||||
//
|
||||
// When testing this is MacOS, remember that the last connection will have no timeout at Node.js
|
||||
// level but only at operating system one.
|
||||
//
|
||||
// The default for MacOS is 75 seconds. It can be changed by doing:
|
||||
//
|
||||
// sudo sysctl net.inet.tcp.keepinit=VALUE_IN_MS
|
||||
//
|
||||
|
||||
// Test that all failure events are emitted when trying a single IP (which means autoselectfamily is bypassed)
|
||||
{
|
||||
const pass = common.mustCallAtLeast(1);
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: 10,
|
||||
lookup: createMockedLookup(INET4_IP),
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 10,
|
||||
});
|
||||
|
||||
connection.on('connectionAttempt', (address, port, family) => {
|
||||
assert.strictEqual(address, INET4_IP);
|
||||
assert.strictEqual(port, 10);
|
||||
assert.strictEqual(family, 4);
|
||||
|
||||
pass();
|
||||
});
|
||||
|
||||
connection.on('connectionAttemptFailed', (address, port, family, error) => {
|
||||
assert.strictEqual(address, INET4_IP);
|
||||
assert.strictEqual(port, 10);
|
||||
assert.strictEqual(family, 4);
|
||||
|
||||
assert.ok(
|
||||
error.code.match(/ECONNREFUSED|ENETUNREACH|EHOSTUNREACH|ETIMEDOUT/),
|
||||
`Received unexpected error code ${error.code}`,
|
||||
);
|
||||
|
||||
pass();
|
||||
});
|
||||
|
||||
connection.on('ready', () => {
|
||||
pass();
|
||||
connection.destroy();
|
||||
});
|
||||
|
||||
connection.on('error', () => {
|
||||
pass();
|
||||
connection.destroy();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
pass();
|
||||
process.exit(0);
|
||||
}, 5000).unref();
|
||||
|
||||
}
|
||||
|
||||
// Test that all events are emitted when trying multiple IPs
|
||||
{
|
||||
const pass = common.mustCallAtLeast(1);
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: 10,
|
||||
lookup: createMockedLookup(INET6_IP, INET4_IP),
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 10,
|
||||
});
|
||||
|
||||
const addresses = [
|
||||
{ address: INET6_IP, port: 10, family: 6 },
|
||||
{ address: INET6_IP, port: 10, family: 6 },
|
||||
{ address: INET4_IP, port: 10, family: 4 },
|
||||
{ address: INET4_IP, port: 10, family: 4 },
|
||||
];
|
||||
|
||||
connection.on('connectionAttempt', (address, port, family) => {
|
||||
const expected = addresses.shift();
|
||||
|
||||
assert.strictEqual(address, expected.address);
|
||||
assert.strictEqual(port, expected.port);
|
||||
assert.strictEqual(family, expected.family);
|
||||
|
||||
pass();
|
||||
});
|
||||
|
||||
connection.on('connectionAttemptFailed', (address, port, family, error) => {
|
||||
const expected = addresses.shift();
|
||||
|
||||
assert.strictEqual(address, expected.address);
|
||||
assert.strictEqual(port, expected.port);
|
||||
assert.strictEqual(family, expected.family);
|
||||
|
||||
assert.ok(
|
||||
error.code.match(/ECONNREFUSED|ENETUNREACH|EHOSTUNREACH|ETIMEDOUT/),
|
||||
`Received unexpected error code ${error.code}`,
|
||||
);
|
||||
|
||||
pass();
|
||||
});
|
||||
|
||||
connection.on('ready', () => {
|
||||
pass();
|
||||
connection.destroy();
|
||||
});
|
||||
|
||||
connection.on('error', () => {
|
||||
pass();
|
||||
connection.destroy();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
pass();
|
||||
process.exit(0);
|
||||
}, 5000).unref();
|
||||
|
||||
}
|
54
test/internet/test-net-autoselectfamily-events-timeout.js
Normal file
54
test/internet/test-net-autoselectfamily-events-timeout.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { addresses: { INET6_IP, INET4_IP } } = require('../common/internet');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const { createConnection } = require('net');
|
||||
|
||||
//
|
||||
// When testing this is MacOS, remember that the last connection will have no timeout at Node.js
|
||||
// level but only at operating system one.
|
||||
//
|
||||
// The default for MacOS is 75 seconds. It can be changed by doing:
|
||||
//
|
||||
// sudo sysctl net.inet.tcp.keepinit=VALUE_IN_MS
|
||||
//
|
||||
// Depending on the network, it might be impossible to obtain a timeout in 10ms,
|
||||
// which is the minimum value allowed by network family autoselection.
|
||||
// At the time of writing (Dec 2023), the network times out on local machine and in the Node CI,
|
||||
// but it does not on GitHub actions runner.
|
||||
// Therefore, after five seconds we just consider this test as passed.
|
||||
|
||||
// Test that if a connection attempt times out and the socket is destroyed before the
|
||||
// next attempt starts then the process does not crash
|
||||
{
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: 443,
|
||||
lookup: createMockedLookup(INET4_IP, INET6_IP),
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 10,
|
||||
});
|
||||
|
||||
const pass = common.mustCall();
|
||||
|
||||
connection.on('connectionAttemptTimeout', (address, port, family) => {
|
||||
assert.strictEqual(address, INET4_IP);
|
||||
assert.strictEqual(port, 443);
|
||||
assert.strictEqual(family, 4);
|
||||
connection.destroy();
|
||||
pass();
|
||||
});
|
||||
|
||||
connection.on('ready', () => {
|
||||
pass();
|
||||
connection.destroy();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
pass();
|
||||
process.exit(0);
|
||||
}, 5000).unref();
|
||||
}
|
@ -6,6 +6,15 @@ const { addresses } = require('../common/internet');
|
||||
const assert = require('assert');
|
||||
const { connect } = require('net');
|
||||
|
||||
//
|
||||
// When testing this is MacOS, remember that the last connection will have no timeout at Node.js
|
||||
// level but only at operating system one.
|
||||
//
|
||||
// The default for MacOS is 75 seconds. It can be changed by doing:
|
||||
//
|
||||
// sudo sysctl net.inet.tcp.keepinit=VALUE_IN_MS
|
||||
//
|
||||
|
||||
// Test that when all errors are returned when no connections succeeded and that the close event is emitted
|
||||
{
|
||||
const connection = connect({
|
||||
|
@ -3,100 +3,46 @@
|
||||
// Flags: --no-network-family-autoselection
|
||||
|
||||
const common = require('../common');
|
||||
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
const { Resolver } = require('dns');
|
||||
const { createConnection, createServer } = require('net');
|
||||
|
||||
// Test that happy eyeballs algorithm can be enable from command line.
|
||||
|
||||
function _lookup(resolver, hostname, options, cb) {
|
||||
resolver.resolve(hostname, 'ANY', (err, replies) => {
|
||||
assert.notStrictEqual(options.family, 4);
|
||||
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const hosts = replies
|
||||
.map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
|
||||
.sort((a, b) => b.family - a.family);
|
||||
|
||||
if (options.all === true) {
|
||||
return cb(null, hosts);
|
||||
}
|
||||
|
||||
return cb(null, hosts[0].address, hosts[0].family);
|
||||
});
|
||||
}
|
||||
|
||||
function createDnsServer(ipv6Addr, ipv4Addr, cb) {
|
||||
// Create a DNS server which replies with a AAAA and a A record for the same host
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
socket.on('message', common.mustCall((msg, { address, port }) => {
|
||||
const parsed = parseDNSPacket(msg);
|
||||
const domain = parsed.questions[0].domain;
|
||||
assert.strictEqual(domain, 'example.org');
|
||||
|
||||
socket.send(writeDNSPacket({
|
||||
id: parsed.id,
|
||||
questions: parsed.questions,
|
||||
answers: [
|
||||
{ type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' },
|
||||
{ type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' },
|
||||
]
|
||||
}), port, address);
|
||||
}));
|
||||
|
||||
socket.bind(0, () => {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
|
||||
|
||||
cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
|
||||
});
|
||||
}
|
||||
|
||||
// Test that IPV4 is NOT reached if IPV6 is not reachable and the option has been disabled via command line
|
||||
{
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
assert.strictEqual(connection.autoSelectFamilyAttemptedAddresses, undefined);
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup,
|
||||
});
|
||||
if (common.hasIPv6) {
|
||||
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);
|
||||
} else if (error.code === 'EAFNOSUPPORT') {
|
||||
assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`);
|
||||
} else if (error.code === 'EUNATCH') {
|
||||
assert.strictEqual(error.message, `connect EUNATCH ::1:${port} - Local (:::0)`);
|
||||
} else {
|
||||
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||
assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`);
|
||||
}
|
||||
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
assert.strictEqual(connection.autoSelectFamilyAttemptedAddresses, undefined);
|
||||
|
||||
if (common.hasIPv6) {
|
||||
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);
|
||||
} else if (error.code === 'EAFNOSUPPORT') {
|
||||
assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`);
|
||||
} else if (error.code === 'EUNATCH') {
|
||||
assert.strictEqual(error.message, `connect EUNATCH ::1:${port} - Local (:::0)`);
|
||||
} else {
|
||||
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||
assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`);
|
||||
}
|
||||
|
||||
ipv4Server.close();
|
||||
dnsServer.close();
|
||||
}));
|
||||
ipv4Server.close();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
@ -1,140 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
const { Resolver } = require('dns');
|
||||
const { createConnection, createServer, setDefaultAutoSelectFamily } = require('net');
|
||||
|
||||
// Test that the default for happy eyeballs algorithm is properly respected.
|
||||
|
||||
const autoSelectFamilyAttemptTimeout = common.defaultAutoSelectFamilyAttemptTimeout;
|
||||
|
||||
function _lookup(resolver, hostname, options, cb) {
|
||||
resolver.resolve(hostname, 'ANY', (err, replies) => {
|
||||
assert.notStrictEqual(options.family, 4);
|
||||
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const hosts = replies
|
||||
.map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
|
||||
.sort((a, b) => b.family - a.family);
|
||||
|
||||
if (options.all === true) {
|
||||
return cb(null, hosts);
|
||||
}
|
||||
|
||||
return cb(null, hosts[0].address, hosts[0].family);
|
||||
});
|
||||
}
|
||||
|
||||
function createDnsServer(ipv6Addr, ipv4Addr, cb) {
|
||||
// Create a DNS server which replies with a AAAA and a A record for the same host
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
socket.on('message', common.mustCall((msg, { address, port }) => {
|
||||
const parsed = parseDNSPacket(msg);
|
||||
const domain = parsed.questions[0].domain;
|
||||
assert.strictEqual(domain, 'example.org');
|
||||
|
||||
socket.send(writeDNSPacket({
|
||||
id: parsed.id,
|
||||
questions: parsed.questions,
|
||||
answers: [
|
||||
{ type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' },
|
||||
{ type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' },
|
||||
]
|
||||
}), port, address);
|
||||
}));
|
||||
|
||||
socket.bind(0, () => {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
|
||||
|
||||
cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
|
||||
});
|
||||
}
|
||||
|
||||
// Test that IPV4 is reached by default if IPV6 is not reachable and the default is enabled
|
||||
{
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(true);
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: ipv4Server.address().port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(true);
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: ipv4Server.address().port,
|
||||
lookup,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
dnsServer.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
}));
|
||||
}
|
||||
|
||||
// Test that IPV4 is not reached by default if IPV6 is not reachable and the default is disabled
|
||||
{
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(false);
|
||||
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(false);
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
if (common.hasIPv6) {
|
||||
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);
|
||||
} else if (error.code === 'EAFNOSUPPORT') {
|
||||
assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`);
|
||||
} else if (error.code === 'EUNATCH') {
|
||||
assert.strictEqual(error.message, `connect EUNATCH ::1:${port} - Local (:::0)`);
|
||||
} else {
|
||||
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||
assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`);
|
||||
}
|
||||
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup,
|
||||
});
|
||||
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
if (common.hasIPv6) {
|
||||
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);
|
||||
} else if (error.code === 'EAFNOSUPPORT') {
|
||||
assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`);
|
||||
} else if (error.code === 'EUNATCH') {
|
||||
assert.strictEqual(error.message, `connect EUNATCH ::1:${port} - Local (:::0)`);
|
||||
} else {
|
||||
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||
assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`);
|
||||
}
|
||||
|
||||
ipv4Server.close();
|
||||
dnsServer.close();
|
||||
}));
|
||||
ipv4Server.close();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
@ -1,105 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
const { Resolver } = require('dns');
|
||||
const { createConnection, createServer } = require('net');
|
||||
|
||||
// Test that happy eyeballs algorithm is properly implemented when a A record is returned first.
|
||||
|
||||
function _lookup(resolver, hostname, options, cb) {
|
||||
resolver.resolve(hostname, 'ANY', (err, replies) => {
|
||||
assert.notStrictEqual(options.family, 4);
|
||||
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const hosts = replies
|
||||
.map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }));
|
||||
|
||||
if (options.all === true) {
|
||||
return cb(null, hosts);
|
||||
}
|
||||
|
||||
return cb(null, hosts[0].address, hosts[0].family);
|
||||
});
|
||||
}
|
||||
|
||||
function createDnsServer(ipv6Addr, ipv4Addr, cb) {
|
||||
// Create a DNS server which replies with a AAAA and a A record for the same host
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
socket.on('message', common.mustCall((msg, { address, port }) => {
|
||||
const parsed = parseDNSPacket(msg);
|
||||
const domain = parsed.questions[0].domain;
|
||||
assert.strictEqual(domain, 'example.org');
|
||||
|
||||
socket.send(writeDNSPacket({
|
||||
id: parsed.id,
|
||||
questions: parsed.questions,
|
||||
answers: [
|
||||
{ type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' },
|
||||
{ type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' },
|
||||
]
|
||||
}), port, address);
|
||||
}));
|
||||
|
||||
socket.bind(0, () => {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
|
||||
|
||||
cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
|
||||
});
|
||||
}
|
||||
|
||||
// Test that IPV6 is NOT reached if IPV4 is sorted first
|
||||
if (common.hasIPv6) {
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
const ipv6Server = createServer((socket) => {
|
||||
socket.on('data', common.mustNotCall(() => {
|
||||
socket.write('response-ipv6');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
ipv6Server.listen(port, '::1', common.mustCall(() => {
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup: createMockedLookup('127.0.0.1', '::1'),
|
||||
autoSelectFamily: true,
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
ipv6Server.close();
|
||||
}));
|
||||
});
|
||||
|
||||
const ipv6Server = createServer((socket) => {
|
||||
socket.on('data', common.mustNotCall(() => {
|
||||
socket.write('response-ipv6');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
ipv6Server.listen(port, '::1', common.mustCall(() => {
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup,
|
||||
autoSelectFamily: true,
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
ipv6Server.close();
|
||||
dnsServer.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
}));
|
||||
connection.write('request');
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
@ -1,93 +1,136 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
const { Resolver } = require('dns');
|
||||
const { createConnection, createServer } = require('net');
|
||||
|
||||
// Test that happy eyeballs algorithm is properly implemented.
|
||||
|
||||
// Purposely not using setDefaultAutoSelectFamilyAttemptTimeout here to test the
|
||||
// parameter is correctly used in options.
|
||||
//
|
||||
|
||||
// Some of the machines in the CI need more time to establish connection
|
||||
const autoSelectFamilyAttemptTimeout = common.defaultAutoSelectFamilyAttemptTimeout;
|
||||
|
||||
function _lookup(resolver, hostname, options, cb) {
|
||||
resolver.resolve(hostname, 'ANY', (err, replies) => {
|
||||
assert.notStrictEqual(options.family, 4);
|
||||
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const hosts = replies
|
||||
.map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
|
||||
.sort((a, b) => b.family - a.family);
|
||||
|
||||
if (options.all === true) {
|
||||
return cb(null, hosts);
|
||||
}
|
||||
|
||||
return cb(null, hosts[0].address, hosts[0].family);
|
||||
});
|
||||
}
|
||||
|
||||
function createDnsServer(ipv6Addrs, ipv4Addrs, cb) {
|
||||
if (!Array.isArray(ipv6Addrs)) {
|
||||
ipv6Addrs = [ipv6Addrs];
|
||||
}
|
||||
|
||||
if (!Array.isArray(ipv4Addrs)) {
|
||||
ipv4Addrs = [ipv4Addrs];
|
||||
}
|
||||
|
||||
// Create a DNS server which replies with a AAAA and a A record for the same host
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
socket.on('message', common.mustCall((msg, { address, port }) => {
|
||||
const parsed = parseDNSPacket(msg);
|
||||
const domain = parsed.questions[0].domain;
|
||||
assert.strictEqual(domain, 'example.org');
|
||||
|
||||
socket.send(writeDNSPacket({
|
||||
id: parsed.id,
|
||||
questions: parsed.questions,
|
||||
answers: [
|
||||
...ipv6Addrs.map((address) => ({ type: 'AAAA', address, ttl: 123, domain: 'example.org' })),
|
||||
...ipv4Addrs.map((address) => ({ type: 'A', address, ttl: 123, domain: 'example.org' })),
|
||||
]
|
||||
}), port, address);
|
||||
}));
|
||||
|
||||
socket.bind(0, () => {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
|
||||
|
||||
cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
|
||||
});
|
||||
}
|
||||
|
||||
// Test that IPV4 is reached if IPV6 is not reachable
|
||||
{
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('ready', common.mustCall(() => {
|
||||
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]);
|
||||
}));
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
}));
|
||||
}
|
||||
|
||||
// Test that only the last successful connection is established.
|
||||
{
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: port,
|
||||
lookup: createMockedLookup(
|
||||
'2606:4700::6810:85e5', '2606:4700::6810:84e5', '::1',
|
||||
'104.20.22.46', '104.20.23.46', '127.0.0.1',
|
||||
),
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('ready', common.mustCall(() => {
|
||||
assert.deepStrictEqual(
|
||||
connection.autoSelectFamilyAttemptedAddresses,
|
||||
[
|
||||
`2606:4700::6810:85e5:${port}`,
|
||||
`104.20.22.46:${port}`,
|
||||
`2606:4700::6810:84e5:${port}`,
|
||||
`104.20.23.46:${port}`,
|
||||
`::1:${port}`,
|
||||
`127.0.0.1:${port}`,
|
||||
]
|
||||
);
|
||||
}));
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
}));
|
||||
}
|
||||
|
||||
// Test that IPV4 is NOT reached if IPV6 is reachable
|
||||
if (common.hasIPv6) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustNotCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
const ipv6Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv6');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
ipv6Server.listen(port, '::1', common.mustCall(() => {
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: port,
|
||||
lookup,
|
||||
port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
@ -96,7 +139,7 @@ function createDnsServer(ipv6Addrs, ipv4Addrs, cb) {
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('ready', common.mustCall(() => {
|
||||
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]);
|
||||
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`]);
|
||||
}));
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
@ -104,9 +147,9 @@ function createDnsServer(ipv6Addrs, ipv4Addrs, cb) {
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
assert.strictEqual(response, 'response-ipv6');
|
||||
ipv4Server.close();
|
||||
dnsServer.close();
|
||||
ipv6Server.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
@ -114,184 +157,67 @@ function createDnsServer(ipv6Addrs, ipv4Addrs, cb) {
|
||||
}));
|
||||
}
|
||||
|
||||
// Test that only the last successful connection is established.
|
||||
{
|
||||
createDnsServer(
|
||||
['2606:4700::6810:85e5', '2606:4700::6810:84e5', '::1'],
|
||||
['104.20.22.46', '104.20.23.46', '127.0.0.1'],
|
||||
common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: port,
|
||||
lookup,
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('ready', common.mustCall(() => {
|
||||
assert.deepStrictEqual(
|
||||
connection.autoSelectFamilyAttemptedAddresses,
|
||||
[
|
||||
`2606:4700::6810:85e5:${port}`,
|
||||
`104.20.22.46:${port}`,
|
||||
`2606:4700::6810:84e5:${port}`,
|
||||
`104.20.23.46:${port}`,
|
||||
`::1:${port}`,
|
||||
`127.0.0.1:${port}`,
|
||||
]
|
||||
);
|
||||
}));
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
dnsServer.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Test that IPV4 is NOT reached if IPV6 is reachable
|
||||
if (common.hasIPv6) {
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustNotCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
const ipv6Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv6');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
ipv6Server.listen(port, '::1', common.mustCall(() => {
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup,
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('ready', common.mustCall(() => {
|
||||
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`]);
|
||||
}));
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv6');
|
||||
ipv4Server.close();
|
||||
ipv6Server.close();
|
||||
dnsServer.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
// Test that when all errors are returned when no connections succeeded
|
||||
{
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: 10,
|
||||
lookup,
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: 10,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, ['::1:10', '127.0.0.1:10']);
|
||||
assert.strictEqual(error.constructor.name, 'AggregateError');
|
||||
assert.strictEqual(error.errors.length, 2);
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, ['::1:10', '127.0.0.1:10']);
|
||||
assert.strictEqual(error.constructor.name, 'AggregateError');
|
||||
assert.strictEqual(error.errors.length, 2);
|
||||
|
||||
const errors = error.errors.map((e) => e.message);
|
||||
assert.ok(errors.includes('connect ECONNREFUSED 127.0.0.1:10'));
|
||||
const errors = error.errors.map((e) => e.message);
|
||||
assert.ok(errors.includes('connect ECONNREFUSED 127.0.0.1:10'));
|
||||
|
||||
if (common.hasIPv6) {
|
||||
assert.ok(errors.includes('connect ECONNREFUSED ::1:10'));
|
||||
}
|
||||
|
||||
dnsServer.close();
|
||||
}));
|
||||
if (common.hasIPv6) {
|
||||
assert.ok(errors.includes('connect ECONNREFUSED ::1:10'));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Test that the option can be disabled
|
||||
{
|
||||
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
autoSelectFamily: false,
|
||||
});
|
||||
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
const port = ipv4Server.address().port;
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
assert.strictEqual(connection.autoSelectFamilyAttemptedAddresses, undefined);
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup,
|
||||
autoSelectFamily: false,
|
||||
});
|
||||
if (common.hasIPv6) {
|
||||
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);
|
||||
} else if (error.code === 'EAFNOSUPPORT') {
|
||||
assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`);
|
||||
} else if (error.code === 'EUNATCH') {
|
||||
assert.strictEqual(error.message, `connect EUNATCH ::1:${port} - Local (:::0)`);
|
||||
} else {
|
||||
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||
assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`);
|
||||
}
|
||||
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on('error', common.mustCall((error) => {
|
||||
assert.strictEqual(connection.autoSelectFamilyAttemptedAddresses, undefined);
|
||||
|
||||
if (common.hasIPv6) {
|
||||
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);
|
||||
} else if (error.code === 'EAFNOSUPPORT') {
|
||||
assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`);
|
||||
} else if (error.code === 'EUNATCH') {
|
||||
assert.strictEqual(error.message, `connect EUNATCH ::1:${port} - Local (:::0)`);
|
||||
} else {
|
||||
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||
assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`);
|
||||
}
|
||||
|
||||
ipv4Server.close();
|
||||
dnsServer.close();
|
||||
}));
|
||||
ipv4Server.close();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user