nodejs / help

:sparkles: Need help with Node.js? File an Issue here. :rocket:
1.47k stars 282 forks source link

Connect a net.Socket to an existing socket #4189

Closed chrmarti closed 4 months ago

chrmarti commented 1 year ago

Details

Is there a way to connect a new net.Socket() to an existing socket later? There is Socket.connect() that can connect the socket to a port or path. Is there a way to connect it to a socket?

Background: I'm looking into adding network proxy support in VS Code to existing 3rd-party code (VS Code extensions) that uses the http2 module. The code synchronously expects a net.Socket, so I need to return one immediately and then set up the connection through the proxy resulting in another socket. Once the proxied socket is ready I need a way to make the first socket take over the connection as if the connection was just established.

Thanks!

Node.js version

16.17.1

Example code

const http = require('http'); const http2 = require('http2'); const net = require('net'); const tls = require('tls');

const lazySocket = new net.Socket();

const client = http2.connect(https://example.com, { createConnection: () => tls.connect({ socket: lazySocket }, { ALPNProtocols: ['h2'] }), }); const req = client.request({

}); req.on('response', (headers, _flags) => { console.log(headers[':status']); client.close(); }); req.end();

const proxyReq = http.request({ method: 'CONNECT', host: 'http-proxy-mitmproxy', port: 8080, path: 'example.com:443', }); proxyReq.on('connect', (_res, proxySocket) => { console.log('connecting'); lazySocket.connect({ socket: proxySocket }); // TODO: Does not work. Is there a way to do this? }); proxyReq.on('error', console.error); proxyReq.end();

Operating system

Developing on Linux, Mac and Windows.

Scope

http2/tls/net API

Module and version

Not applicable.

preveen-stack commented 1 year ago

pls check this code


const http = require('http');

// Create a server that acts as a transparent HTTP proxy
const server = http.createServer((clientReq, clientRes) => {
  // Extract the destination host and port from the client's request
  const { headers, method, url } = clientReq;
  const { host, port } = new URL(`http://${headers.host}`);

  // Create a new request to forward to the destination server
  const options = {
    host,
    port: port || 443,
    path: url,
    method,
    headers: {
      ...headers,
      host,
    },
  };

  const proxyReq = http.request(options, (proxyRes) => {
    // Forward the response headers from the destination server to the client
    clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);

    // Forward the response body from the destination server to the client
    proxyRes.pipe(clientRes);
  });

  // Forward the request body from the client to the destination server
  clientReq.pipe(proxyReq);
});

// Start the server on port 443
server.listen(443, () => {
  console.log('HTTP proxy listening on port 443');
});
chrmarti commented 1 year ago

Thanks for your reply. What I'm trying to do is all in the same Node process, no proxy server involved, my code is all network client code.

I'm looking for a way to return one socket to my caller while using a second socket to prepare a tunnel through an external proxy server. Once the tunnel connection is set up with the second socket, I want to hand over the connection to the first socket.

Having read some of the Node.js code, I think I need to transfer the _handle from the second socket to the first. I found I can use the following to do this and am now looking for a cleaner and more correct way to do this:

// Connect handle.
net.Socket.call(firstSocket, { handle: secondSocket._handle });

// Copy over state.
for (const key of Object.keys(secondSocket)) {
    if (key === '_events' || key === '_eventsCount') {
        continue;
    }
    if (firstSocket[key] !== secondSocket[key]) {
        firstSocket[key] = secondSocket[key];
    }
}
prettydiff commented 1 year ago

I do not believe you can merge sockets together, which is what it sounds like when you mention connecting one socket to another. Each socket will connect to a port at each destination and that port will be owned by the socket for the life of the socket.

If you want to stream through a proxy then you will connect a socket from the origination to the proxy and there must be a socket service running on the proxy to set up a second separate socket between the proxy location and the destination. The proxy node instance will then pipe between those two sockets.

More specifically socket connections feature a TCP/UDP connection handshake that Node abstracts away during the socket's connection event. In the handler for that event you need to notify the Proxy of the destination address and port. Then the proxy must open a second socket to the destination machine. Now there are two sockets on the proxy, because the proxy is in the middle between the two terminal points. On the data event handler for those two sockets you need to pipe from one socket to the other. It could look as simple as:

primary.on("data", function (chunk) {
    secondary.write(chunk);
});
secondary.on("data", function (chunk) {
    primary.write(chunk);
});
github-actions[bot] commented 5 months ago

It seems there has been no activity on this issue for a while, and it is being closed in 30 days. If you believe this issue should remain open, please leave a comment. If you need further assistance or have questions, you can also search for similar issues on Stack Overflow. Make sure to look at the README file for the most updated links.

github-actions[bot] commented 4 months ago

It seems there has been no activity on this issue for a while, and it is being closed. If you believe this issue should remain open, please leave a comment. If you need further assistance or have questions, you can also search for similar issues on Stack Overflow. Make sure to look at the README file for the most updated links.