1'use strict'; 2 3const common = require('../common'); 4 5if (!common.hasCrypto) { 6 common.skip('missing crypto'); 7} 8 9const { parseDNSPacket, writeDNSPacket } = require('../common/dns'); 10const fixtures = require('../common/fixtures'); 11 12const assert = require('assert'); 13const dgram = require('dgram'); 14const { Resolver } = require('dns'); 15const { request, createServer } = require('https'); 16const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net'); 17 18if (!common.hasCrypto) 19 common.skip('missing crypto'); 20 21const options = { 22 key: fixtures.readKey('agent1-key.pem'), 23 cert: fixtures.readKey('agent1-cert.pem') 24}; 25 26// Test that happy eyeballs algorithm is properly implemented when using HTTP. 27 28// Some of the windows machines in the CI need more time to establish connection 29setDefaultAutoSelectFamilyAttemptTimeout(common.platformTimeout(common.isWindows ? 1500 : 250)); 30 31function _lookup(resolver, hostname, options, cb) { 32 resolver.resolve(hostname, 'ANY', (err, replies) => { 33 assert.notStrictEqual(options.family, 4); 34 35 if (err) { 36 return cb(err); 37 } 38 39 const hosts = replies 40 .map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 })) 41 .sort((a, b) => b.family - a.family); 42 43 if (options.all === true) { 44 return cb(null, hosts); 45 } 46 47 return cb(null, hosts[0].address, hosts[0].family); 48 }); 49} 50 51function createDnsServer(ipv6Addr, ipv4Addr, cb) { 52 // Create a DNS server which replies with a AAAA and a A record for the same host 53 const socket = dgram.createSocket('udp4'); 54 55 socket.on('message', common.mustCall((msg, { address, port }) => { 56 const parsed = parseDNSPacket(msg); 57 const domain = parsed.questions[0].domain; 58 assert.strictEqual(domain, 'example.org'); 59 60 socket.send(writeDNSPacket({ 61 id: parsed.id, 62 questions: parsed.questions, 63 answers: [ 64 { type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' }, 65 { type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' }, 66 ] 67 }), port, address); 68 })); 69 70 socket.bind(0, () => { 71 const resolver = new Resolver(); 72 resolver.setServers([`127.0.0.1:${socket.address().port}`]); 73 74 cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) }); 75 }); 76} 77 78// Test that IPV4 is reached if IPV6 is not reachable 79{ 80 createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) { 81 const ipv4Server = createServer(options, common.mustCall((req, res) => { 82 assert.strictEqual(req.socket.servername, 'example.org'); 83 res.writeHead(200, { Connection: 'close' }); 84 res.end('response-ipv4'); 85 })); 86 87 ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { 88 request( 89 `https://example.org:${ipv4Server.address().port}/`, 90 { 91 lookup, 92 rejectUnauthorized: false, 93 autoSelectFamily: true, 94 }, 95 (res) => { 96 assert.strictEqual(res.statusCode, 200); 97 res.setEncoding('utf-8'); 98 99 let response = ''; 100 101 res.on('data', (chunk) => { 102 response += chunk; 103 }); 104 105 res.on('end', common.mustCall(() => { 106 assert.strictEqual(response, 'response-ipv4'); 107 ipv4Server.close(); 108 dnsServer.close(); 109 })); 110 } 111 ).end(); 112 })); 113 })); 114} 115 116// Test that IPV4 is NOT reached if IPV6 is reachable 117if (common.hasIPv6) { 118 createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) { 119 const ipv4Server = createServer(options, common.mustNotCall((req, res) => { 120 assert.strictEqual(req.socket.servername, 'example.org'); 121 res.writeHead(200, { Connection: 'close' }); 122 res.end('response-ipv4'); 123 })); 124 125 const ipv6Server = createServer(options, common.mustCall((req, res) => { 126 assert.strictEqual(req.socket.servername, 'example.org'); 127 res.writeHead(200, { Connection: 'close' }); 128 res.end('response-ipv6'); 129 })); 130 131 ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { 132 const port = ipv4Server.address().port; 133 134 ipv6Server.listen(port, '::1', common.mustCall(() => { 135 request( 136 `https://example.org:${ipv4Server.address().port}/`, 137 { 138 lookup, 139 rejectUnauthorized: false, 140 autoSelectFamily: true, 141 }, 142 (res) => { 143 assert.strictEqual(res.statusCode, 200); 144 res.setEncoding('utf-8'); 145 146 let response = ''; 147 148 res.on('data', (chunk) => { 149 response += chunk; 150 }); 151 152 res.on('end', common.mustCall(() => { 153 assert.strictEqual(response, 'response-ipv6'); 154 ipv4Server.close(); 155 ipv6Server.close(); 156 dnsServer.close(); 157 })); 158 } 159 ).end(); 160 })); 161 })); 162 })); 163} 164