Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On bad response, the socket is destroyed only when the proxy server closes the connection #86

Closed
kadler15 opened this issue Aug 4, 2022 · 0 comments · Fixed by #98
Closed

Comments

@kadler15
Copy link

kadler15 commented Aug 4, 2022

Summary

hpagent receives a socket on proxy server response to the CONNECT request. On non-200 response, hpagent passes an Error to the createConnection callback without first destroying the socket.

While it's highly unlikely the proxy server will fail to close the connection immediately after responding with a bad status code, I'm nervous about the complete lack of client control over socket cleanup.

Environment

  • Node v16.15.1
  • hpagent v1.0.0
  • Linux kernel 5.13

Steps to reproduce

Run a simple web server on port 80 that returns 403 in response to any request:

#!/bin/bash
while true; do
  echo -e "HTTP/1.1 403 FORBIDDEN\r\n$(date)\r\n\r\n<h1>hello world from $(hostname) on $(date)</h1>" | nc -vl -p 80
done

Run the following test script:

const https = require('https');
const {HttpsProxyAgent} = require('hpagent');

const agent = new HttpsProxyAgent({
  proxy: 'http://127.0.0.1:80',
  timeout: 2000
});

const req = https.get('https://www.google.com', {agent})
  .on('error', console.log)
  .on('response', console.log)
  .on('socket', (sock) => {
    console.log(`Got TLSSocket`);
    sock.on('close', () => console.log(`TLSSocket closed`));
    sock.on('connect', () => console.log(`TLSSocket connected`));
    sock.on('timeout', () => {
      console.log(`TLSSocket timeout`);
      sock.destroy();
    });
  })
  .on('timeout', () => console.log(`timeout`))
  .end();

// Uncomment the following line to print active handles after 5 seconds
//setTimeout(() => console.log(process._getActiveHandles()), 5000);

Note that Node doesn't exit until the server is killed, because the proxy socket remains connected, with no ability to destroy it on the client side.

A potential fix

Call socket.destroy() before passing a bad response error to the createConnection callback. E.g.:

      if (response.statusCode === 200) {
        const secureSocket = super.createConnection({ ...options, socket })
        callback(null, secureSocket)
      } else {
        socket.destroy();
        callback(new Error(`Bad response: ${response.statusCode}`), null)
      }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant