diff options
Diffstat (limited to 'tests/node_compat/test/parallel/test-net-autoselectfamily.js')
-rw-r--r-- | tests/node_compat/test/parallel/test-net-autoselectfamily.js | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/tests/node_compat/test/parallel/test-net-autoselectfamily.js b/tests/node_compat/test/parallel/test-net-autoselectfamily.js new file mode 100644 index 000000000..3b520e6c8 --- /dev/null +++ b/tests/node_compat/test/parallel/test-net-autoselectfamily.js @@ -0,0 +1,312 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +const common = require('../common'); +const { parseDNSPacket, writeDNSPacket } = 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'); + + // TODO(kt3k): We use common.mustCallAtLeast instead of common.mustCall + // because Deno sends multiple requests to the DNS server. + // This can be addressed if Deno.resolveDns supports ANY record type. + // See https://github.com/denoland/deno/issues/14492 + socket.on('message', common.mustCallAtLeast((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(); + })); + }); + + 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, [`::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 only the last successful connection is established. +{ + createDnsServer( + ['2606:4700::6810:85e5', '2606:4700::6810:84e5', "::1"], + // TODO(kt3k): Comment out ipv4 addresses to make the test pass faster. + // Enable this when Deno.connect() call becomes cancellable. + // See https://github.com/denoland/deno/issues/26819 + // ['104.20.22.46', '104.20.23.46', '127.0.0.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(); + })); + }); + + 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, + }); + + 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')); + + if (common.hasIPv6) { + assert.ok(errors.includes('connect ECONNREFUSED ::1:10')); + } + + dnsServer.close(); + })); + })); +} + +// 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(); + })); + }); + + ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { + const port = ipv4Server.address().port; + + const connection = createConnection({ + host: 'example.org', + port, + lookup, + autoSelectFamily: false, + }); + + 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(); + })); + })); + })); +} |