1'use strict';
2
3const common = require('../common');
4const fixtures = require('../common/fixtures');
5if (!common.hasCrypto)
6  common.skip('missing crypto');
7const assert = require('assert');
8const net = require('net');
9const tls = require('tls');
10const h2 = require('http2');
11
12// This test sets up an H2 proxy server, and tunnels a request over one of its streams
13// back to itself, via TLS, and then closes the TLS connection. On some Node versions
14// (v18 & v20 up to 20.5.1) the resulting JS Stream Socket fails to shutdown correctly
15// in this case, and crashes due to a null pointer in finishShutdown.
16
17const tlsOptions = {
18  key: fixtures.readKey('agent1-key.pem'),
19  cert: fixtures.readKey('agent1-cert.pem'),
20  ALPNProtocols: ['h2']
21};
22
23const netServer = net.createServer((socket) => {
24  socket.allowHalfOpen = false;
25  // ^ This allows us to trigger this reliably, but it's not strictly required
26  // for the bug and crash to happen, skipping this just fails elsewhere later.
27
28  h2Server.emit('connection', socket);
29});
30
31const h2Server = h2.createSecureServer(tlsOptions, (req, res) => {
32  res.writeHead(200);
33  res.end();
34});
35
36h2Server.on('connect', (req, res) => {
37  res.writeHead(200, {});
38  netServer.emit('connection', res.stream);
39});
40
41netServer.listen(0, common.mustCall(() => {
42  const proxyClient = h2.connect(`https://localhost:${netServer.address().port}`, {
43    rejectUnauthorized: false
44  });
45
46  const proxyReq = proxyClient.request({
47    ':method': 'CONNECT',
48    ':authority': 'example.com:443'
49  });
50
51  proxyReq.on('response', common.mustCall((response) => {
52    assert.strictEqual(response[':status'], 200);
53
54    // Create a TLS socket within the tunnel, and start sending a request:
55    const tlsSocket = tls.connect({
56      socket: proxyReq,
57      ALPNProtocols: ['h2'],
58      rejectUnauthorized: false
59    });
60
61    proxyReq.on('close', common.mustCall(() => {
62      proxyClient.close();
63      netServer.close();
64    }));
65
66    // Forcibly kill the TLS socket
67    tlsSocket.destroy();
68
69    // This results in an async error in affected Node versions, before the 'close' event
70  }));
71}));
72