• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const common = require('../common');
4
5if (!common.hasCrypto) {
6  common.skip('missing crypto');
7}
8
9const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
10const fixtures = require('../common/fixtures');
11
12const assert = require('assert');
13const dgram = require('dgram');
14const { Resolver } = require('dns');
15const { request, createServer } = require('https');
16const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net');
17
18if (!common.hasCrypto)
19  common.skip('missing crypto');
20
21const options = {
22  key: fixtures.readKey('agent1-key.pem'),
23  cert: fixtures.readKey('agent1-cert.pem')
24};
25
26// Test that happy eyeballs algorithm is properly implemented when using HTTP.
27
28// Some of the windows machines in the CI need more time to establish connection
29setDefaultAutoSelectFamilyAttemptTimeout(common.platformTimeout(common.isWindows ? 1500 : 250));
30
31function _lookup(resolver, hostname, options, cb) {
32  resolver.resolve(hostname, 'ANY', (err, replies) => {
33    assert.notStrictEqual(options.family, 4);
34
35    if (err) {
36      return cb(err);
37    }
38
39    const hosts = replies
40      .map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
41      .sort((a, b) => b.family - a.family);
42
43    if (options.all === true) {
44      return cb(null, hosts);
45    }
46
47    return cb(null, hosts[0].address, hosts[0].family);
48  });
49}
50
51function createDnsServer(ipv6Addr, ipv4Addr, cb) {
52  // Create a DNS server which replies with a AAAA and a A record for the same host
53  const socket = dgram.createSocket('udp4');
54
55  socket.on('message', common.mustCall((msg, { address, port }) => {
56    const parsed = parseDNSPacket(msg);
57    const domain = parsed.questions[0].domain;
58    assert.strictEqual(domain, 'example.org');
59
60    socket.send(writeDNSPacket({
61      id: parsed.id,
62      questions: parsed.questions,
63      answers: [
64        { type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' },
65        { type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' },
66      ]
67    }), port, address);
68  }));
69
70  socket.bind(0, () => {
71    const resolver = new Resolver();
72    resolver.setServers([`127.0.0.1:${socket.address().port}`]);
73
74    cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
75  });
76}
77
78// Test that IPV4 is reached if IPV6 is not reachable
79{
80  createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
81    const ipv4Server = createServer(options, common.mustCall((req, res) => {
82      assert.strictEqual(req.socket.servername, 'example.org');
83      res.writeHead(200, { Connection: 'close' });
84      res.end('response-ipv4');
85    }));
86
87    ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
88      request(
89        `https://example.org:${ipv4Server.address().port}/`,
90        {
91          lookup,
92          rejectUnauthorized: false,
93          autoSelectFamily: true,
94        },
95        (res) => {
96          assert.strictEqual(res.statusCode, 200);
97          res.setEncoding('utf-8');
98
99          let response = '';
100
101          res.on('data', (chunk) => {
102            response += chunk;
103          });
104
105          res.on('end', common.mustCall(() => {
106            assert.strictEqual(response, 'response-ipv4');
107            ipv4Server.close();
108            dnsServer.close();
109          }));
110        }
111      ).end();
112    }));
113  }));
114}
115
116// Test that IPV4 is NOT reached if IPV6 is reachable
117if (common.hasIPv6) {
118  createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
119    const ipv4Server = createServer(options, common.mustNotCall((req, res) => {
120      assert.strictEqual(req.socket.servername, 'example.org');
121      res.writeHead(200, { Connection: 'close' });
122      res.end('response-ipv4');
123    }));
124
125    const ipv6Server = createServer(options, common.mustCall((req, res) => {
126      assert.strictEqual(req.socket.servername, 'example.org');
127      res.writeHead(200, { Connection: 'close' });
128      res.end('response-ipv6');
129    }));
130
131    ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
132      const port = ipv4Server.address().port;
133
134      ipv6Server.listen(port, '::1', common.mustCall(() => {
135        request(
136          `https://example.org:${ipv4Server.address().port}/`,
137          {
138            lookup,
139            rejectUnauthorized: false,
140            autoSelectFamily: true,
141          },
142          (res) => {
143            assert.strictEqual(res.statusCode, 200);
144            res.setEncoding('utf-8');
145
146            let response = '';
147
148            res.on('data', (chunk) => {
149              response += chunk;
150            });
151
152            res.on('end', common.mustCall(() => {
153              assert.strictEqual(response, 'response-ipv6');
154              ipv4Server.close();
155              ipv6Server.close();
156              dnsServer.close();
157            }));
158          }
159        ).end();
160      }));
161    }));
162  }));
163}
164