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 { createConnection, createServer, setDefaultAutoSelectFamily } = require('net'); 10 11// Test that the default for happy eyeballs algorithm is properly respected. 12 13let autoSelectFamilyAttemptTimeout = common.platformTimeout(250); 14if (common.isWindows) { 15 // Some of the windows machines in the CI need more time to establish connection 16 autoSelectFamilyAttemptTimeout = common.platformTimeout(1500); 17} 18 19function _lookup(resolver, hostname, options, cb) { 20 resolver.resolve(hostname, 'ANY', (err, replies) => { 21 assert.notStrictEqual(options.family, 4); 22 23 if (err) { 24 return cb(err); 25 } 26 27 const hosts = replies 28 .map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 })) 29 .sort((a, b) => b.family - a.family); 30 31 if (options.all === true) { 32 return cb(null, hosts); 33 } 34 35 return cb(null, hosts[0].address, hosts[0].family); 36 }); 37} 38 39function createDnsServer(ipv6Addr, ipv4Addr, cb) { 40 // Create a DNS server which replies with a AAAA and a A record for the same host 41 const socket = dgram.createSocket('udp4'); 42 43 socket.on('message', common.mustCall((msg, { address, port }) => { 44 const parsed = parseDNSPacket(msg); 45 const domain = parsed.questions[0].domain; 46 assert.strictEqual(domain, 'example.org'); 47 48 socket.send(writeDNSPacket({ 49 id: parsed.id, 50 questions: parsed.questions, 51 answers: [ 52 { type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' }, 53 { type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' }, 54 ] 55 }), port, address); 56 })); 57 58 socket.bind(0, () => { 59 const resolver = new Resolver(); 60 resolver.setServers([`127.0.0.1:${socket.address().port}`]); 61 62 cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) }); 63 }); 64} 65 66// Test that IPV4 is reached by default if IPV6 is not reachable and the default is enabled 67{ 68 createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) { 69 const ipv4Server = createServer((socket) => { 70 socket.on('data', common.mustCall(() => { 71 socket.write('response-ipv4'); 72 socket.end(); 73 })); 74 }); 75 76 ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { 77 setDefaultAutoSelectFamily(true); 78 79 const connection = createConnection({ 80 host: 'example.org', 81 port: ipv4Server.address().port, 82 lookup, 83 autoSelectFamilyAttemptTimeout, 84 }); 85 86 let response = ''; 87 connection.setEncoding('utf-8'); 88 89 connection.on('data', (chunk) => { 90 response += chunk; 91 }); 92 93 connection.on('end', common.mustCall(() => { 94 assert.strictEqual(response, 'response-ipv4'); 95 ipv4Server.close(); 96 dnsServer.close(); 97 })); 98 99 connection.write('request'); 100 })); 101 })); 102} 103 104// Test that IPV4 is not reached by default if IPV6 is not reachable and the default is disabled 105{ 106 createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) { 107 const ipv4Server = createServer((socket) => { 108 socket.on('data', common.mustCall(() => { 109 socket.write('response-ipv4'); 110 socket.end(); 111 })); 112 }); 113 114 ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => { 115 setDefaultAutoSelectFamily(false); 116 117 const port = ipv4Server.address().port; 118 119 const connection = createConnection({ 120 host: 'example.org', 121 port, 122 lookup, 123 }); 124 125 connection.on('ready', common.mustNotCall()); 126 connection.on('error', common.mustCall((error) => { 127 if (common.hasIPv6) { 128 assert.strictEqual(error.code, 'ECONNREFUSED'); 129 assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`); 130 } else if (error.code === 'EAFNOSUPPORT') { 131 assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`); 132 } else if (common.isIBMi) { 133 // IBMi returns EUNATCH (ERRNO 42) when IPv6 is disabled 134 // keep this errno assertion until EUNATCH is recognized by libuv 135 assert.strictEqual(error.errno, -42); 136 } else { 137 assert.strictEqual(error.code, 'EADDRNOTAVAIL'); 138 assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`); 139 } 140 141 ipv4Server.close(); 142 dnsServer.close(); 143 })); 144 })); 145 })); 146} 147