Skip to content

Commit

Permalink
http: add --enable-network-family-autoselection command line option
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogunPanda committed Dec 8, 2022
1 parent bb0c56f commit 2efd797
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 3 deletions.
10 changes: 10 additions & 0 deletions doc/api/cli.md
Expand Up @@ -314,6 +314,15 @@ added: v6.0.0
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built
against FIPS-compatible OpenSSL.)

### `--enable-network-family-autoselection`

<!-- YAML
added: REPLACEME
-->

Enables the family autoselection algorithm unless connection options explicitly
disables it.

### `--enable-source-maps`

<!-- YAML
Expand Down Expand Up @@ -1840,6 +1849,7 @@ Node.js options that are allowed are:
* `--disable-proto`
* `--dns-result-order`
* `--enable-fips`
* `--enable-network-family-autoselection`
* `--enable-source-maps`
* `--experimental-abortcontroller`
* `--experimental-import-meta-resolve`
Expand Down
6 changes: 4 additions & 2 deletions doc/api/net.md
Expand Up @@ -873,7 +873,8 @@ changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/45777
description: The default value for autoSelectFamily option can be changed
at runtime using `setDefaultAutoSelectFamily`.
at runtime using `setDefaultAutoSelectFamily` or via the
command line option `--enable-network-family-autoselection`.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44731
description: Added the `autoSelectFamily` option.
Expand Down Expand Up @@ -933,7 +934,8 @@ For TCP connections, available `options` are:
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.
**Default:** initially `false`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamily(value)`][].
**Default:** initially `false`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamily(value)`][]
or via the command line option `--enable-network-family-autoselection`.
* `autoSelectFamilyAttemptTimeout` {number}: The amount of time in milliseconds to wait
for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option.
If set to a positive integer less than `10`, then the value `10` will be used instead.
Expand Down
3 changes: 2 additions & 1 deletion lib/net.js
Expand Up @@ -118,13 +118,14 @@ const {
validateString
} = require('internal/validators');
const kLastWriteQueueSize = Symbol('lastWriteQueueSize');
const { getOptionValue } = require('internal/options');

// Lazy loaded to improve startup performance.
let cluster;
let dns;
let BlockList;
let SocketAddress;
let autoSelectFamilyDefault = false;
let autoSelectFamilyDefault = getOptionValue('--enable-network-family-autoselection');

const { clearTimeout, setTimeout } = require('timers');
const { kTimeout } = require('internal/timers');
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Expand Up @@ -346,6 +346,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"returned)",
&EnvironmentOptions::dns_result_order,
kAllowedInEnvvar);
AddOption("--enable-network-family-autoselection",
"Enable network address family autodetection algorithm",
&EnvironmentOptions::enable_network_family_autoselection,
kAllowedInEnvvar);
AddOption("--enable-source-maps",
"Source Map V3 support for stack traces",
&EnvironmentOptions::enable_source_maps,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Expand Up @@ -127,6 +127,7 @@ class EnvironmentOptions : public Options {
bool frozen_intrinsics = false;
int64_t heap_snapshot_near_heap_limit = 0;
std::string heap_snapshot_signal;
bool enable_network_family_autoselection = false;
uint64_t max_http_header_size = 16 * 1024;
bool deprecation = true;
bool force_async_hooks_checks = true;
Expand Down
108 changes: 108 additions & 0 deletions test/parallel/test-net-autoselectfamily-commandline-option.js
@@ -0,0 +1,108 @@
'use strict';

// Flags: --enable-network-family-autoselection

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 can be enable from command line.

let autoSelectFamilyAttemptTimeout = common.platformTimeout(250);
if (common.isWindows) {
// Some of the windows machines in the CI need more time to establish connection
autoSelectFamilyAttemptTimeout = common.platformTimeout(1500);
}

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 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,
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');
}));
}));
}

0 comments on commit 2efd797

Please sign in to comment.