1'use strict'; 2 3const common = require('../common'); 4const { parseDNSPacket, writeDNSPacket } = require('../common/dns'); 5 6const assert = require('assert'); 7const dgram = require('dgram'); 8const { Resolver } = require('dns'); 9const { request, createServer } = require('http'); 10const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net'); 11 12// Test that happy eyeballs algorithm is properly implemented when using HTTP. 13 14// Some of the windows machines in the CI need more time to establish connection 15setDefaultAutoSelectFamilyAttemptTimeout(common.platformTimeout(common.isWindows ? 1500 : 250)); 16 17function _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 37function createDnsServer(ipv6Addr, ipv4Addr, cb) { 38 // Create a DNS server which replies with a AAAA and a A record for the same host 39 const socket = dgram.createSocket('udp4'); 40 41 socket.on('message', common.mustCall((msg, { address, port }) => { 42 const parsed = parseDNSPacket(msg); 43 const domain = parsed.questions[0].domain; 44 assert.strictEqual(domain, 'example.org'); 45 46 socket.send(writeDNSPacket({ 47 id: parsed.id, 48 questions: parsed.questions, 49 answers: [ 50 { type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' }, 51 { type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' }, 52 ] 53 }), port, address); 54 })); 55 56 socket.bind(0, () => { 57 const resolver = new Resolver(); 58 resolver.setServers([`127.0.0.1:${socket.address().port}`]); 59 60 cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) }); 61 }); 62} 63 64// Test that IPV4 is reached if IPV6 is not reachable 65{ 66 createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) { 67 const ipv4Server = createServer(common.mustCall((_, res) => { 68 res.writeHead(200, { Connection: 'close' }); 69 res.end('response-ipv4'); 70 })); 71 72 ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { 73 request( 74 `http://example.org:${ipv4Server.address().port}/`, 75 { 76 lookup, 77 autoSelectFamily: true, 78 }, 79 (res) => { 80 assert.strictEqual(res.statusCode, 200); 81 res.setEncoding('utf-8'); 82 83 let response = ''; 84 85 res.on('data', (chunk) => { 86 response += chunk; 87 }); 88 89 res.on('end', common.mustCall(() => { 90 assert.strictEqual(response, 'response-ipv4'); 91 ipv4Server.close(); 92 dnsServer.close(); 93 })); 94 } 95 ).end(); 96 })); 97 })); 98} 99 100// Test that IPV4 is NOT reached if IPV6 is reachable 101if (common.hasIPv6) { 102 createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) { 103 const ipv4Server = createServer(common.mustNotCall((_, res) => { 104 res.writeHead(200, { Connection: 'close' }); 105 res.end('response-ipv4'); 106 })); 107 108 const ipv6Server = createServer(common.mustCall((_, res) => { 109 res.writeHead(200, { Connection: 'close' }); 110 res.end('response-ipv6'); 111 })); 112 113 ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { 114 const port = ipv4Server.address().port; 115 116 ipv6Server.listen(port, '::1', common.mustCall(() => { 117 request( 118 `http://example.org:${ipv4Server.address().port}/`, 119 { 120 lookup, 121 autoSelectFamily: true, 122 }, 123 (res) => { 124 assert.strictEqual(res.statusCode, 200); 125 res.setEncoding('utf-8'); 126 127 let response = ''; 128 129 res.on('data', (chunk) => { 130 response += chunk; 131 }); 132 133 res.on('end', common.mustCall(() => { 134 assert.strictEqual(response, 'response-ipv6'); 135 ipv4Server.close(); 136 ipv6Server.close(); 137 dnsServer.close(); 138 })); 139 } 140 ).end(); 141 })); 142 })); 143 })); 144} 145