mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
2fca7ea2be
When establishing a TLS connection to a server with `autoSelectFamily` set to `true`, the `net.Socket` will call `[kWrapConnectedHandle]()` to reinitialize the socket (in case if it got broken during previous connect attempts). Unfortunately, prior to this patch this resulted in a brand new `TLSWrap` instance being created for the socket. While most of the configuration of `TLSWrap` is restored, the `servername` was sadly dropped and not reinitalized. With this patch `servername` will be reinitialized if there are `tls.connect` options present on the `TLSSocket` instance, making it possible to connect with "Happy Eyeballs" to TLS servers that require the servername extension. PR-URL: https://github.com/nodejs/node/pull/48255 Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
162 lines
4.6 KiB
JavaScript
162 lines
4.6 KiB
JavaScript
'use strict';
|
|
|
|
const common = require('../common');
|
|
|
|
if (!common.hasCrypto) {
|
|
common.skip('missing crypto');
|
|
}
|
|
|
|
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
|
|
const fixtures = require('../common/fixtures');
|
|
|
|
const assert = require('assert');
|
|
const dgram = require('dgram');
|
|
const { Resolver } = require('dns');
|
|
const { request, createServer } = require('https');
|
|
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
|
|
const options = {
|
|
key: fixtures.readKey('agent1-key.pem'),
|
|
cert: fixtures.readKey('agent1-cert.pem')
|
|
};
|
|
|
|
// Test that happy eyeballs algorithm is properly implemented when using HTTP.
|
|
|
|
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(options, common.mustCall((req, res) => {
|
|
assert.strictEqual(req.socket.servername, 'example.org');
|
|
res.writeHead(200, { Connection: 'close' });
|
|
res.end('response-ipv4');
|
|
}));
|
|
|
|
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
|
request(
|
|
`https://example.org:${ipv4Server.address().port}/`,
|
|
{
|
|
lookup,
|
|
rejectUnauthorized: false,
|
|
autoSelectFamily: true,
|
|
servername: 'example.org',
|
|
},
|
|
(res) => {
|
|
assert.strictEqual(res.statusCode, 200);
|
|
res.setEncoding('utf-8');
|
|
|
|
let response = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
response += chunk;
|
|
});
|
|
|
|
res.on('end', common.mustCall(() => {
|
|
assert.strictEqual(response, 'response-ipv4');
|
|
ipv4Server.close();
|
|
dnsServer.close();
|
|
}));
|
|
}
|
|
).end();
|
|
}));
|
|
}));
|
|
}
|
|
|
|
// 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(options, common.mustNotCall((req, res) => {
|
|
assert.strictEqual(req.socket.servername, 'example.org');
|
|
res.writeHead(200, { Connection: 'close' });
|
|
res.end('response-ipv4');
|
|
}));
|
|
|
|
const ipv6Server = createServer(options, common.mustCall((req, res) => {
|
|
assert.strictEqual(req.socket.servername, 'example.org');
|
|
res.writeHead(200, { Connection: 'close' });
|
|
res.end('response-ipv6');
|
|
}));
|
|
|
|
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
|
const port = ipv4Server.address().port;
|
|
|
|
ipv6Server.listen(port, '::1', common.mustCall(() => {
|
|
request(
|
|
`https://example.org:${ipv4Server.address().port}/`,
|
|
{
|
|
lookup,
|
|
rejectUnauthorized: false,
|
|
autoSelectFamily: true,
|
|
servername: 'example.org',
|
|
},
|
|
(res) => {
|
|
assert.strictEqual(res.statusCode, 200);
|
|
res.setEncoding('utf-8');
|
|
|
|
let response = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
response += chunk;
|
|
});
|
|
|
|
res.on('end', common.mustCall(() => {
|
|
assert.strictEqual(response, 'response-ipv6');
|
|
ipv4Server.close();
|
|
ipv6Server.close();
|
|
dnsServer.close();
|
|
}));
|
|
}
|
|
).end();
|
|
}));
|
|
}));
|
|
}));
|
|
}
|