Skip to content

Commit e372bf7

Browse files
committed
net: do not attempt new connections when aborted
1 parent d4bcdd8 commit e372bf7

File tree

2 files changed

+110
-3
lines changed

2 files changed

+110
-3
lines changed

lib/net.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,8 +1663,11 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
16631663
if (status !== 0) {
16641664
ArrayPrototypePush(context.errors, createConnectionError(req, status));
16651665

1666-
// Try the next address
1667-
internalConnectMultiple(context, status === UV_ECANCELED);
1666+
// Try the next address, unless we were aborted
1667+
if (context.socket.connecting) {
1668+
internalConnectMultiple(context, status === UV_ECANCELED);
1669+
}
1670+
16681671
return;
16691672
}
16701673

@@ -1684,7 +1687,11 @@ function internalConnectMultipleTimeout(context, req, handle) {
16841687
req.oncomplete = undefined;
16851688
ArrayPrototypePush(context.errors, createConnectionError(req, UV_ETIMEDOUT));
16861689
handle.close();
1687-
internalConnectMultiple(context);
1690+
1691+
// Try the next address, unless we were aborted
1692+
if (context.socket.connecting) {
1693+
internalConnectMultiple(context);
1694+
}
16881695
}
16891696

16901697
function addServerAbortSignalOption(self, options) {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { addresses: { INET4_IP } } = require('../common/internet');
5+
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
6+
7+
const assert = require('assert');
8+
const dgram = require('dgram');
9+
const { Resolver } = require('dns');
10+
const { createConnection } = require('net');
11+
12+
// Test that happy eyeballs algorithm properly handles cancellations.
13+
14+
// Purposely set this to a low value because we want all connection but the last to fail
15+
const autoSelectFamilyAttemptTimeout = 15;
16+
17+
function _lookup(resolver, hostname, options, cb) {
18+
resolver.resolve(hostname, 'ANY', (err, replies) => {
19+
assert.notStrictEqual(options.family, 4);
20+
21+
if (err) {
22+
return cb(err);
23+
}
24+
25+
const hosts = replies
26+
.map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
27+
.sort((a, b) => b.family - a.family);
28+
29+
if (options.all === true) {
30+
return cb(null, hosts);
31+
}
32+
33+
return cb(null, hosts[0].address, hosts[0].family);
34+
});
35+
}
36+
37+
function createDnsServer(ipv6Addrs, ipv4Addrs, cb) {
38+
if (!Array.isArray(ipv6Addrs)) {
39+
ipv6Addrs = [ipv6Addrs];
40+
}
41+
42+
if (!Array.isArray(ipv4Addrs)) {
43+
ipv4Addrs = [ipv4Addrs];
44+
}
45+
46+
// Create a DNS server which replies with a AAAA and a A record for the same host
47+
const socket = dgram.createSocket('udp4');
48+
49+
socket.on('message', common.mustCall((msg, { address, port }) => {
50+
const parsed = parseDNSPacket(msg);
51+
const domain = parsed.questions[0].domain;
52+
assert.strictEqual(domain, 'example.org');
53+
54+
socket.send(writeDNSPacket({
55+
id: parsed.id,
56+
questions: parsed.questions,
57+
answers: [
58+
...ipv6Addrs.map((address) => ({ type: 'AAAA', address, ttl: 123, domain: 'example.org' })),
59+
...ipv4Addrs.map((address) => ({ type: 'A', address, ttl: 123, domain: 'example.org' })),
60+
]
61+
}), port, address);
62+
}));
63+
64+
socket.bind(0, () => {
65+
const resolver = new Resolver();
66+
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
67+
68+
cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
69+
});
70+
}
71+
72+
// Test that if a connection attempt is aborted before finishing the connection, then the process does not crash
73+
{
74+
createDnsServer([], [INET4_IP, '127.0.0.1', INET4_IP], common.mustCall(function({ dnsServer, lookup }) {
75+
const connection = createConnection({
76+
host: 'example.org',
77+
port: 443,
78+
lookup,
79+
autoSelectFamily: true,
80+
autoSelectFamilyAttemptTimeout,
81+
});
82+
83+
let destroyTimeoutSet = false;
84+
connection.on('lookup', common.mustCall(() => {
85+
if (destroyTimeoutSet) {
86+
return;
87+
}
88+
89+
destroyTimeoutSet = true;
90+
setTimeout(() => {
91+
connection.destroy();
92+
}, autoSelectFamilyAttemptTimeout);
93+
}, 3));
94+
95+
connection.on('ready', common.mustNotCall());
96+
connection.on('close', common.mustCall(() => {
97+
dnsServer.close();
98+
}));
99+
}));
100+
}

0 commit comments

Comments
 (0)