• 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 { 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