1'use strict';
2
3// Flags: --enable-network-family-autoselection
4
5const common = require('../common');
6const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
7
8const assert = require('assert');
9const dgram = require('dgram');
10const { Resolver } = require('dns');
11const { createConnection, createServer, setDefaultAutoSelectFamilyAttemptTimeout } = require('net');
12
13// Test that happy eyeballs algorithm can be enable from command line.
14
15// Some of the windows machines in the CI need more time to establish connection
16setDefaultAutoSelectFamilyAttemptTimeout(common.platformTimeout(common.isWindows ? 1500 : 250));
17
18function _lookup(resolver, hostname, options, cb) {
19  resolver.resolve(hostname, 'ANY', (err, replies) => {
20    assert.notStrictEqual(options.family, 4);
21
22    if (err) {
23      return cb(err);
24    }
25
26    const hosts = replies
27      .map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
28      .sort((a, b) => b.family - a.family);
29
30    if (options.all === true) {
31      return cb(null, hosts);
32    }
33
34    return cb(null, hosts[0].address, hosts[0].family);
35  });
36}
37
38function createDnsServer(ipv6Addr, ipv4Addr, cb) {
39  // Create a DNS server which replies with a AAAA and a A record for the same host
40  const socket = dgram.createSocket('udp4');
41
42  socket.on('message', common.mustCall((msg, { address, port }) => {
43    const parsed = parseDNSPacket(msg);
44    const domain = parsed.questions[0].domain;
45    assert.strictEqual(domain, 'example.org');
46
47    socket.send(writeDNSPacket({
48      id: parsed.id,
49      questions: parsed.questions,
50      answers: [
51        { type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' },
52        { type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' },
53      ]
54    }), port, address);
55  }));
56
57  socket.bind(0, () => {
58    const resolver = new Resolver();
59    resolver.setServers([`127.0.0.1:${socket.address().port}`]);
60
61    cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
62  });
63}
64
65// Test that IPV4 is reached if IPV6 is not reachable
66{
67  createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
68    const ipv4Server = createServer((socket) => {
69      socket.on('data', common.mustCall(() => {
70        socket.write('response-ipv4');
71        socket.end();
72      }));
73    });
74
75    ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
76      const port = ipv4Server.address().port;
77
78      const connection = createConnection({
79        host: 'example.org',
80        port: port,
81        lookup,
82      });
83
84      let response = '';
85      connection.setEncoding('utf-8');
86
87      connection.on('ready', common.mustCall(() => {
88        assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]);
89      }));
90
91      connection.on('data', (chunk) => {
92        response += chunk;
93      });
94
95      connection.on('end', common.mustCall(() => {
96        assert.strictEqual(response, 'response-ipv4');
97        ipv4Server.close();
98        dnsServer.close();
99      }));
100
101      connection.write('request');
102    }));
103  }));
104}
105