diff options
author | Yoshiya Hinosawa <stibium121@gmail.com> | 2024-11-12 19:54:47 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-12 19:54:47 +0900 |
commit | c3c2b379669b17e5fdcbe5e62662404ca22c71c6 (patch) | |
tree | 91aae17a3c9dbad8aad35d41d52767b6df4dd24b /tests | |
parent | 90236d67c591d4344a9ca0e5d23a4906d08308e5 (diff) |
fix(ext/node): add autoSelectFamily option to net.createConnection (#26661)
Diffstat (limited to 'tests')
-rw-r--r-- | tests/node_compat/config.jsonc | 2 | ||||
-rw-r--r-- | tests/node_compat/runner/TODO.md | 1 | ||||
-rw-r--r-- | tests/node_compat/test/common/index.js | 1 | ||||
-rw-r--r-- | tests/node_compat/test/parallel/test-net-autoselectfamily.js | 312 | ||||
-rw-r--r-- | tests/unit_node/http2_test.ts | 5 |
5 files changed, 320 insertions, 1 deletions
diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index 16951d9ed..664adaedf 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -77,6 +77,7 @@ "test-fs-rmdir-recursive.js", "test-fs-write-file.js", "test-http-url.parse-https.request.js", + "test-net-autoselectfamily.js", "test-net-better-error-messages-path.js", "test-net-connect-buffer.js", "test-net-connect-buffer2.js", @@ -404,6 +405,7 @@ "test-http-url.parse-only-support-http-https-protocol.js", "test-icu-transcode.js", "test-net-access-byteswritten.js", + "test-net-autoselectfamily.js", "test-net-better-error-messages-listen-path.js", "test-net-better-error-messages-path.js", "test-net-better-error-messages-port-hostname.js", diff --git a/tests/node_compat/runner/TODO.md b/tests/node_compat/runner/TODO.md index 231a4f62c..27c2ef3e7 100644 --- a/tests/node_compat/runner/TODO.md +++ b/tests/node_compat/runner/TODO.md @@ -1767,7 +1767,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-net-autoselectfamily-commandline-option.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-commandline-option.js) - [parallel/test-net-autoselectfamily-default.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-default.js) - [parallel/test-net-autoselectfamily-ipv4first.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-ipv4first.js) -- [parallel/test-net-autoselectfamily.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily.js) - [parallel/test-net-better-error-messages-listen.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-better-error-messages-listen.js) - [parallel/test-net-binary.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-binary.js) - [parallel/test-net-bind-twice.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-bind-twice.js) diff --git a/tests/node_compat/test/common/index.js b/tests/node_compat/test/common/index.js index d2165aecd..d358ffce5 100644 --- a/tests/node_compat/test/common/index.js +++ b/tests/node_compat/test/common/index.js @@ -473,6 +473,7 @@ const pwdCommand = isWindows ? module.exports = { allowGlobals, + defaultAutoSelectFamilyAttemptTimeout: 2500, expectsError, expectWarning, getArrayBufferViews, 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(); + })); + })); + })); +} diff --git a/tests/unit_node/http2_test.ts b/tests/unit_node/http2_test.ts index 7473a487a..c540c90f7 100644 --- a/tests/unit_node/http2_test.ts +++ b/tests/unit_node/http2_test.ts @@ -10,6 +10,11 @@ import * as net from "node:net"; import { assert, assertEquals } from "@std/assert"; import { curlRequest } from "../unit/test_util.ts"; +// Increase the timeout for the auto select family to avoid flakiness +net.setDefaultAutoSelectFamilyAttemptTimeout( + net.getDefaultAutoSelectFamilyAttemptTimeout() * 30, +); + for (const url of ["http://localhost:4246", "https://localhost:4247"]) { Deno.test(`[node/http2 client] ${url}`, { ignore: Deno.build.os === "windows", |