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