| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -314,6 +314,15 @@ added: v6.0.0 | |||
| 314 | 314 | Enable FIPS-compliant crypto at startup. (Requires Node.js to be built | |
| 315 | 315 | against FIPS-compatible OpenSSL.) | |
| 316 | 316 | ||
| 317 | + ### `--enable-network-family-autoselection` | ||
| 318 | + | ||
| 319 | + <!-- YAML | ||
| 320 | + added: REPLACEME | ||
| 321 | + --> | ||
| 322 | + | ||
| 323 | + Enables the family autoselection algorithm unless connection options explicitly | ||
| 324 | + disables it. | ||
| 325 | + | ||
| 317 | 326 | ### `--enable-source-maps` | |
| 318 | 327 | ||
| 319 | 328 | <!-- YAML | |
@@ -1861,6 +1870,7 @@ Node.js options that are allowed are: | |||
| 1861 | 1870 | * `--disable-proto` | |
| 1862 | 1871 | * `--dns-result-order` | |
| 1863 | 1872 | * `--enable-fips` | |
| 1873 | + * `--enable-network-family-autoselection` | ||
| 1864 | 1874 | * `--enable-source-maps` | |
| 1865 | 1875 | * `--experimental-abortcontroller` | |
| 1866 | 1876 | * `--experimental-global-customevent` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -778,6 +778,20 @@ Returns the bound `address`, the address `family` name and `port` of the | |||
| 778 | 778 | socket as reported by the operating system: | |
| 779 | 779 | `{ port: 12346, family: 'IPv4', address: '127.0.0.1' }` | |
| 780 | 780 | ||
| 781 | + ### `socket.autoSelectFamilyAttemptedAddresses` | ||
| 782 | + | ||
| 783 | + <!-- YAML | ||
| 784 | + added: REPLACEME | ||
| 785 | + --> | ||
| 786 | + | ||
| 787 | + * {string\[]} | ||
| 788 | + | ||
| 789 | + This property is only present if the family autoselection algorithm is enabled in | ||
| 790 | + [`socket.connect(options)`][] and it is an array of the addresses that have been attempted. | ||
| 791 | + | ||
| 792 | + Each address is a string in the form of `$IP:$PORT`. If the connection was successful, | ||
| 793 | + then the last address is the one that the socket is currently connected to. | ||
| 794 | + | ||
| 781 | 795 | ### `socket.bufferSize` | |
| 782 | 796 | ||
| 783 | 797 | <!-- YAML | |
@@ -854,6 +868,11 @@ behavior. | |||
| 854 | 868 | <!-- YAML | |
| 855 | 869 | added: v0.1.90 | |
| 856 | 870 | changes: | |
| 871 | + - version: REPLACEME | ||
| 872 | + pr-url: https://github.com/nodejs/node/pull/45777 | ||
| 873 | + description: The default value for autoSelectFamily option can be changed | ||
| 874 | + at runtime using `setDefaultAutoSelectFamily` or via the | ||
| 875 | + command line option `--enable-network-family-autoselection`. | ||
| 857 | 876 | - version: v18.13.0 | |
| 858 | 877 | pr-url: https://github.com/nodejs/node/pull/44731 | |
| 859 | 878 | description: Added the `autoSelectFamily` option. | |
@@ -905,12 +924,14 @@ For TCP connections, available `options` are: | |||
| 905 | 924 | that loosely implements section 5 of [RFC 8305][]. | |
| 906 | 925 | The `all` option passed to lookup is set to `true` and the sockets attempts to connect to all | |
| 907 | 926 | obtained IPv6 and IPv4 addresses, in sequence, until a connection is established. | |
| 908 | - The first returned AAAA address is tried first, then the first returned A address and so on. | ||
| 927 | + The first returned AAAA address is tried first, then the first returned A address, | ||
| 928 | + then the second returned AAAA address and so on. | ||
| 909 | 929 | Each connection attempt is given the amount of time specified by the `autoSelectFamilyAttemptTimeout` | |
| 910 | 930 | option before timing out and trying the next address. | |
| 911 | 931 | Ignored if the `family` option is not `0` or if `localAddress` is set. | |
| 912 | 932 | Connection errors are not emitted if at least one connection succeeds. | |
| 913 | - **Default:** `false`. | ||
| 933 | + **Default:** initially `false`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamily(value)`][] | ||
| 934 | + or via the command line option `--enable-network-family-autoselection`. | ||
| 914 | 935 | * `autoSelectFamilyAttemptTimeout` {number}: The amount of time in milliseconds to wait | |
| 915 | 936 | for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. | |
| 916 | 937 | If set to a positive integer less than `10`, then the value `10` will be used instead. | |
@@ -1499,6 +1520,26 @@ immediately initiates connection with | |||
| 1499 | 1520 | [`socket.connect(port[, host][, connectListener])`][`socket.connect(port)`], | |
| 1500 | 1521 | then returns the `net.Socket` that starts the connection. | |
| 1501 | 1522 | ||
| 1523 | + ## `net.setDefaultAutoSelectFamily(value)` | ||
| 1524 | + | ||
| 1525 | + <!-- YAML | ||
| 1526 | + added: REPLACEME | ||
| 1527 | + --> | ||
| 1528 | + | ||
| 1529 | + Sets the default value of the `autoSelectFamily` option of [`socket.connect(options)`][]. | ||
| 1530 | + | ||
| 1531 | + * `value` {boolean} The new default value. The initial default value is `false`. | ||
| 1532 | + | ||
| 1533 | + ## `net.getDefaultAutoSelectFamily()` | ||
| 1534 | + | ||
| 1535 | + <!-- YAML | ||
| 1536 | + added: REPLACEME | ||
| 1537 | + --> | ||
| 1538 | + | ||
| 1539 | + Gets the current default value of the `autoSelectFamily` option of [`socket.connect(options)`][]. | ||
| 1540 | + | ||
| 1541 | + * Returns: {boolean} The current default value of the `autoSelectFamily` option. | ||
| 1542 | + | ||
| 1502 | 1543 | ## `net.createServer([options][, connectionListener])` | |
| 1503 | 1544 | ||
| 1504 | 1545 | <!-- YAML | |
@@ -1683,6 +1724,7 @@ net.isIPv6('fhqwhgads'); // returns false | |||
| 1683 | 1724 | [`net.createConnection(path)`]: #netcreateconnectionpath-connectlistener | |
| 1684 | 1725 | [`net.createConnection(port, host)`]: #netcreateconnectionport-host-connectlistener | |
| 1685 | 1726 | [`net.createServer()`]: #netcreateserveroptions-connectionlistener | |
| 1727 | + [`net.setDefaultAutoSelectFamily(value)`]: #netsetdefaultautoselectfamilyvalue | ||
| 1686 | 1728 | [`new net.Socket(options)`]: #new-netsocketoptions | |
| 1687 | 1729 | [`readable.setEncoding()`]: stream.md#readablesetencodingencoding | |
| 1688 | 1730 | [`server.close()`]: #serverclosecallback | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -124,12 +124,14 @@ const { | |||
| 124 | 124 | DTRACE_NET_SERVER_CONNECTION, | |
| 125 | 125 | DTRACE_NET_STREAM_END, | |
| 126 | 126 | } = require('internal/dtrace'); | |
| 127 | + const { getOptionValue } = require('internal/options'); | ||
| 127 | 128 | ||
| 128 | 129 | // Lazy loaded to improve startup performance. | |
| 129 | 130 | let cluster; | |
| 130 | 131 | let dns; | |
| 131 | 132 | let BlockList; | |
| 132 | 133 | let SocketAddress; | |
| 134 | + let autoSelectFamilyDefault = getOptionValue('--enable-network-family-autoselection'); | ||
| 133 | 135 | ||
| 134 | 136 | const { clearTimeout, setTimeout } = require('timers'); | |
| 135 | 137 | const { kTimeout } = require('internal/timers'); | |
@@ -244,6 +246,14 @@ function connect(...args) { | |||
| 244 | 246 | return socket.connect(normalized); | |
| 245 | 247 | } | |
| 246 | 248 | ||
| 249 | + function getDefaultAutoSelectFamily() { | ||
| 250 | + return autoSelectFamilyDefault; | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + function setDefaultAutoSelectFamily(value) { | ||
| 254 | + validateBoolean(value, 'value'); | ||
| 255 | + autoSelectFamilyDefault = value; | ||
| 256 | + } | ||
| 247 | 257 | ||
| 248 | 258 | // Returns an array [options, cb], where options is an object, | |
| 249 | 259 | // cb is either a function or null. | |
@@ -1116,6 +1126,8 @@ function internalConnectMultiple(context) { | |||
| 1116 | 1126 | req.localAddress = localAddress; | |
| 1117 | 1127 | req.localPort = localPort; | |
| 1118 | 1128 | ||
| 1129 | + ArrayPrototypePush(self.autoSelectFamilyAttemptedAddresses, `${address}:${port}`); | ||
| 1130 | + | ||
| 1119 | 1131 | if (addressType === 4) { | |
| 1120 | 1132 | err = handle.connect(req, address, port); | |
| 1121 | 1133 | } else { | |
@@ -1208,9 +1220,9 @@ function socketToDnsFamily(family) { | |||
| 1208 | 1220 | } | |
| 1209 | 1221 | ||
| 1210 | 1222 | function lookupAndConnect(self, options) { | |
| 1211 | - const { localAddress, localPort, autoSelectFamily } = options; | ||
| 1223 | + const { localAddress, localPort } = options; | ||
| 1212 | 1224 | const host = options.host || 'localhost'; | |
| 1213 | - let { port, autoSelectFamilyAttemptTimeout } = options; | ||
| 1225 | + let { port, autoSelectFamilyAttemptTimeout, autoSelectFamily } = options; | ||
| 1214 | 1226 | ||
| 1215 | 1227 | if (localAddress && !isIP(localAddress)) { | |
| 1216 | 1228 | throw new ERR_INVALID_IP_ADDRESS(localAddress); | |
@@ -1229,11 +1241,14 @@ function lookupAndConnect(self, options) { | |||
| 1229 | 1241 | } | |
| 1230 | 1242 | port |= 0; | |
| 1231 | 1243 | ||
| 1232 | - if (autoSelectFamily !== undefined) { | ||
| 1233 | - validateBoolean(autoSelectFamily); | ||
| 1244 | + | ||
| 1245 | + if (autoSelectFamily != null) { | ||
| 1246 | + validateBoolean(autoSelectFamily, 'options.autoSelectFamily'); | ||
| 1247 | + } else { | ||
| 1248 | + autoSelectFamily = autoSelectFamilyDefault; | ||
| 1234 | 1249 | } | |
| 1235 | 1250 | ||
| 1236 | - if (autoSelectFamilyAttemptTimeout !== undefined) { | ||
| 1251 | + if (autoSelectFamilyAttemptTimeout != null) { | ||
| 1237 | 1252 | validateInt32(autoSelectFamilyAttemptTimeout, 'options.autoSelectFamilyAttemptTimeout', 1); | |
| 1238 | 1253 | ||
| 1239 | 1254 | if (autoSelectFamilyAttemptTimeout < 10) { | |
@@ -1257,7 +1272,7 @@ function lookupAndConnect(self, options) { | |||
| 1257 | 1272 | return; | |
| 1258 | 1273 | } | |
| 1259 | 1274 | ||
| 1260 | - if (options.lookup !== undefined) | ||
| 1275 | + if (options.lookup != null) | ||
| 1261 | 1276 | validateFunction(options.lookup, 'options.lookup'); | |
| 1262 | 1277 | ||
| 1263 | 1278 | if (dns === undefined) dns = require('dns'); | |
@@ -1394,6 +1409,8 @@ function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options, | |||
| 1394 | 1409 | } | |
| 1395 | 1410 | } | |
| 1396 | 1411 | ||
| 1412 | + self.autoSelectFamilyAttemptedAddresses = []; | ||
| 1413 | + | ||
| 1397 | 1414 | const context = { | |
| 1398 | 1415 | socket: self, | |
| 1399 | 1416 | addresses, | |
@@ -2262,4 +2279,6 @@ module.exports = { | |||
| 2262 | 2279 | Server, | |
| 2263 | 2280 | Socket, | |
| 2264 | 2281 | Stream: Socket, // Legacy naming | |
| 2282 | + getDefaultAutoSelectFamily, | ||
| 2283 | + setDefaultAutoSelectFamily, | ||
| 2265 | 2284 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -359,6 +359,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |||
| 359 | 359 | "returned)", | |
| 360 | 360 | &EnvironmentOptions::dns_result_order, | |
| 361 | 361 | kAllowedInEnvvar); | |
| 362 | + AddOption("--enable-network-family-autoselection", | ||
| 363 | + "Enable network address family autodetection algorithm", | ||
| 364 | + &EnvironmentOptions::enable_network_family_autoselection, | ||
| 365 | + kAllowedInEnvvar); | ||
| 362 | 366 | AddOption("--enable-source-maps", | |
| 363 | 367 | "Source Map V3 support for stack traces", | |
| 364 | 368 | &EnvironmentOptions::enable_source_maps, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -128,6 +128,7 @@ class EnvironmentOptions : public Options { | |||
| 128 | 128 | bool frozen_intrinsics = false; | |
| 129 | 129 | int64_t heap_snapshot_near_heap_limit = 0; | |
| 130 | 130 | std::string heap_snapshot_signal; | |
| 131 | + bool enable_network_family_autoselection = false; | ||
| 131 | 132 | uint64_t max_http_header_size = 16 * 1024; | |
| 132 | 133 | bool deprecation = true; | |
| 133 | 134 | bool force_async_hooks_checks = true; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,108 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // Flags: --enable-network-family-autoselection | ||
| 4 | + | ||
| 5 | + const common = require('../common'); | ||
| 6 | + const { parseDNSPacket, writeDNSPacket } = require('../common/dns'); | ||
| 7 | + | ||
| 8 | + const assert = require('assert'); | ||
| 9 | + const dgram = require('dgram'); | ||
| 10 | + const { Resolver } = require('dns'); | ||
| 11 | + const { createConnection, createServer } = require('net'); | ||
| 12 | + | ||
| 13 | + // Test that happy eyeballs algorithm can be enable from command line. | ||
| 14 | + | ||
| 15 | + let autoSelectFamilyAttemptTimeout = common.platformTimeout(250); | ||
| 16 | + if (common.isWindows) { | ||
| 17 | + // Some of the windows machines in the CI need more time to establish connection | ||
| 18 | + autoSelectFamilyAttemptTimeout = common.platformTimeout(1500); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + function _lookup(resolver, hostname, options, cb) { | ||
| 22 | + resolver.resolve(hostname, 'ANY', (err, replies) => { | ||
| 23 | + assert.notStrictEqual(options.family, 4); | ||
| 24 | + | ||
| 25 | + if (err) { | ||
| 26 | + return cb(err); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + const hosts = replies | ||
| 30 | + .map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 })) | ||
| 31 | + .sort((a, b) => b.family - a.family); | ||
| 32 | + | ||
| 33 | + if (options.all === true) { | ||
| 34 | + return cb(null, hosts); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + return cb(null, hosts[0].address, hosts[0].family); | ||
| 38 | + }); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + function createDnsServer(ipv6Addr, ipv4Addr, cb) { | ||
| 42 | + // Create a DNS server which replies with a AAAA and a A record for the same host | ||
| 43 | + const socket = dgram.createSocket('udp4'); | ||
| 44 | + | ||
| 45 | + socket.on('message', common.mustCall((msg, { address, port }) => { | ||
| 46 | + const parsed = parseDNSPacket(msg); | ||
| 47 | + const domain = parsed.questions[0].domain; | ||
| 48 | + assert.strictEqual(domain, 'example.org'); | ||
| 49 | + | ||
| 50 | + socket.send(writeDNSPacket({ | ||
| 51 | + id: parsed.id, | ||
| 52 | + questions: parsed.questions, | ||
| 53 | + answers: [ | ||
| 54 | + { type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' }, | ||
| 55 | + { type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' }, | ||
| 56 | + ] | ||
| 57 | + }), port, address); | ||
| 58 | + })); | ||
| 59 | + | ||
| 60 | + socket.bind(0, () => { | ||
| 61 | + const resolver = new Resolver(); | ||
| 62 | + resolver.setServers([`127.0.0.1:${socket.address().port}`]); | ||
| 63 | + | ||
| 64 | + cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) }); | ||
| 65 | + }); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + // Test that IPV4 is reached if IPV6 is not reachable | ||
| 69 | + { | ||
| 70 | + createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) { | ||
| 71 | + const ipv4Server = createServer((socket) => { | ||
| 72 | + socket.on('data', common.mustCall(() => { | ||
| 73 | + socket.write('response-ipv4'); | ||
| 74 | + socket.end(); | ||
| 75 | + })); | ||
| 76 | + }); | ||
| 77 | + | ||
| 78 | + ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { | ||
| 79 | + const port = ipv4Server.address().port; | ||
| 80 | + | ||
| 81 | + const connection = createConnection({ | ||
| 82 | + host: 'example.org', | ||
| 83 | + port: port, | ||
| 84 | + lookup, | ||
| 85 | + autoSelectFamilyAttemptTimeout, | ||
| 86 | + }); | ||
| 87 | + | ||
| 88 | + let response = ''; | ||
| 89 | + connection.setEncoding('utf-8'); | ||
| 90 | + | ||
| 91 | + connection.on('ready', common.mustCall(() => { | ||
| 92 | + assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]); | ||
| 93 | + })); | ||
| 94 | + | ||
| 95 | + connection.on('data', (chunk) => { | ||
| 96 | + response += chunk; | ||
| 97 | + }); | ||
| 98 | + | ||
| 99 | + connection.on('end', common.mustCall(() => { | ||
| 100 | + assert.strictEqual(response, 'response-ipv4'); | ||
| 101 | + ipv4Server.close(); | ||
| 102 | + dnsServer.close(); | ||
| 103 | + })); | ||
| 104 | + | ||
| 105 | + connection.write('request'); | ||
| 106 | + })); | ||
| 107 | + })); | ||
| 108 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -74,9 +74,11 @@ function createDnsServer(ipv6Addr, ipv4Addr, cb) { | |||
| 74 | 74 | }); | |
| 75 | 75 | ||
| 76 | 76 | ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { | |
| 77 | + const port = ipv4Server.address().port; | ||
| 78 | + | ||
| 77 | 79 | const connection = createConnection({ | |
| 78 | 80 | host: 'example.org', | |
| 79 | - port: ipv4Server.address().port, | ||
| 81 | + port: port, | ||
| 80 | 82 | lookup, | |
| 81 | 83 | autoSelectFamily: true, | |
| 82 | 84 | autoSelectFamilyAttemptTimeout, | |
@@ -85,6 +87,10 @@ function createDnsServer(ipv6Addr, ipv4Addr, cb) { | |||
| 85 | 87 | let response = ''; | |
| 86 | 88 | connection.setEncoding('utf-8'); | |
| 87 | 89 | ||
| 90 | + connection.on('ready', common.mustCall(() => { | ||
| 91 | + assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]); | ||
| 92 | + })); | ||
| 93 | + | ||
| 88 | 94 | connection.on('data', (chunk) => { | |
| 89 | 95 | response += chunk; | |
| 90 | 96 | }); | |
@@ -132,6 +138,10 @@ if (common.hasIPv6) { | |||
| 132 | 138 | let response = ''; | |
| 133 | 139 | connection.setEncoding('utf-8'); | |
| 134 | 140 | ||
| 141 | + connection.on('ready', common.mustCall(() => { | ||
| 142 | + assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`]); | ||
| 143 | + })); | ||
| 144 | + | ||
| 135 | 145 | connection.on('data', (chunk) => { | |
| 136 | 146 | response += chunk; | |
| 137 | 147 | }); | |
@@ -162,6 +172,7 @@ if (common.hasIPv6) { | |||
| 162 | 172 | ||
| 163 | 173 | connection.on('ready', common.mustNotCall()); | |
| 164 | 174 | connection.on('error', common.mustCall((error) => { | |
| 175 | + assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, ['::1:10', '127.0.0.1:10']); | ||
| 165 | 176 | assert.strictEqual(error.constructor.name, 'AggregateError'); | |
| 166 | 177 | assert.strictEqual(error.errors.length, 2); | |
| 167 | 178 | ||
@@ -199,6 +210,8 @@ if (common.hasIPv6) { | |||
| 199 | 210 | ||
| 200 | 211 | connection.on('ready', common.mustNotCall()); | |
| 201 | 212 | connection.on('error', common.mustCall((error) => { | |
| 213 | + assert.strictEqual(connection.autoSelectFamilyAttemptedAddresses, undefined); | ||
| 214 | + | ||
| 202 | 215 | if (common.hasIPv6) { | |
| 203 | 216 | assert.strictEqual(error.code, 'ECONNREFUSED'); | |
| 204 | 217 | assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments