Skip to content

Commit

Permalink
net: do not attempt new connections when aborted
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogunPanda committed Dec 4, 2023
1 parent d4bcdd8 commit e372bf7
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 3 deletions.
13 changes: 10 additions & 3 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -1663,8 +1663,11 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
if (status !== 0) {
ArrayPrototypePush(context.errors, createConnectionError(req, status));

// Try the next address
internalConnectMultiple(context, status === UV_ECANCELED);
// Try the next address, unless we were aborted
if (context.socket.connecting) {
internalConnectMultiple(context, status === UV_ECANCELED);
}

return;
}

Expand All @@ -1684,7 +1687,11 @@ function internalConnectMultipleTimeout(context, req, handle) {
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) {
Expand Down
100 changes: 100 additions & 0 deletions test/parallel/test-net-autoselectfamily-timeout-cancelling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use strict';

Check failure on line 1 in test/parallel/test-net-autoselectfamily-timeout-cancelling.js

View workflow job for this annotation

GitHub Actions / test-macOS

--- stderr --- node:assert:173 throw err; ^ AssertionError [ERR_ASSERTION]: function should not have been called at /Users/runner/work/node/node/test/parallel/test-net-autoselectfamily-timeout-cancelling.js:95 at Socket.mustNotCall (/Users/runner/work/node/node/test/common/index.js:554:12) at Socket.emit (node:events:519:28) at afterConnect (node:net:1581:10) at afterConnectMultiple (node:net:1682:3) { generatedMessage: false, code: 'ERR_ASSERTION', actual: undefined, expected: undefined, operator: 'fail' } Node.js v22.0.0-pre Command: out/Release/node --test-reporter=spec --test-reporter-destination=stdout --test-reporter=./tools/github_reporter/index.js --test-reporter-destination=stdout /Users/runner/work/node/node/test/parallel/test-net-autoselectfamily-timeout-cancelling.js

Check failure on line 1 in test/parallel/test-net-autoselectfamily-timeout-cancelling.js

View workflow job for this annotation

GitHub Actions / test-asan

--- stderr --- node:assert:173 throw err; ^ AssertionError [ERR_ASSERTION]: function should not have been called at /home/runner/work/node/node/test/parallel/test-net-autoselectfamily-timeout-cancelling.js:95 at Socket.mustNotCall (/home/runner/work/node/node/test/common/index.js:554:12) at Socket.emit (node:events:519:28) at afterConnect (node:net:1581:10) at afterConnectMultiple (node:net:1682:3) { generatedMessage: false, code: 'ERR_ASSERTION', actual: undefined, expected: undefined, operator: 'fail' } Node.js v22.0.0-pre Command: out/Release/node --test-reporter=spec --test-reporter-destination=stdout --test-reporter=./tools/github_reporter/index.js --test-reporter-destination=stdout /home/runner/work/node/node/test/parallel/test-net-autoselectfamily-timeout-cancelling.js

Check failure on line 1 in test/parallel/test-net-autoselectfamily-timeout-cancelling.js

View workflow job for this annotation

GitHub Actions / test-linux

--- stderr --- node:assert:173 throw err; ^ AssertionError [ERR_ASSERTION]: function should not have been called at /home/runner/work/node/node/test/parallel/test-net-autoselectfamily-timeout-cancelling.js:95 at Socket.mustNotCall (/home/runner/work/node/node/test/common/index.js:554:12) at Socket.emit (node:events:519:28) at afterConnect (node:net:1581:10) at afterConnectMultiple (node:net:1682:3) { generatedMessage: false, code: 'ERR_ASSERTION', actual: undefined, expected: undefined, operator: 'fail' } Node.js v22.0.0-pre Command: out/Release/node --test-reporter=spec --test-reporter-destination=stdout --test-reporter=./tools/github_reporter/index.js --test-reporter-destination=stdout /home/runner/work/node/node/test/parallel/test-net-autoselectfamily-timeout-cancelling.js

const common = require('../common');
const { addresses: { INET4_IP } } = require('../common/internet');
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');

const assert = require('assert');
const dgram = require('dgram');
const { Resolver } = require('dns');
const { createConnection } = require('net');

// Test that happy eyeballs algorithm properly handles cancellations.

// Purposely set this to a low value because we want all connection but the last to fail
const autoSelectFamilyAttemptTimeout = 15;

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 if a connection attempt is aborted before finishing the connection, then the process does not crash
{
createDnsServer([], [INET4_IP, '127.0.0.1', INET4_IP], common.mustCall(function({ dnsServer, lookup }) {
const connection = createConnection({
host: 'example.org',
port: 443,
lookup,
autoSelectFamily: true,
autoSelectFamilyAttemptTimeout,
});

let destroyTimeoutSet = false;
connection.on('lookup', common.mustCall(() => {
if (destroyTimeoutSet) {
return;
}

destroyTimeoutSet = true;
setTimeout(() => {
connection.destroy();
}, autoSelectFamilyAttemptTimeout);
}, 3));

connection.on('ready', common.mustNotCall());
connection.on('close', common.mustCall(() => {
dnsServer.close();
}));
}));
}

0 comments on commit e372bf7

Please sign in to comment.