I'm trying to create a reverse proxy to a CONNECT-based HTTP proxy. The user who wants to use the proxy just treats machine A
as an HTTP proxy. It works the following way:
machine B
opens a TCP socket tomachine A
.- On
machine A
, a TCP socket is exposed on a port and all the incoming data is tunneled tomachine B
(io.Copy). - On
machine B
, all the data is tunneled to the local HTTP server and the socket tomachine A
.
Essentially this is a reverse-proxy behind an HTTP proxy. The reason it's this complex is because the HTTP proxy is behind NAT (on machine B
) and therefore not accessible directly. The use case is being able to host an HTTP proxy behind a NAT.
Machine A tunnel (Go):
package main
import (
"log"
"net"
)
func Conn(c *net.TCPConn) string {
return c.RemoteAddr().String() + " (" + c.LocalAddr().String() + ")"
}
func ProxifyConns(recipientConn, donorConn *net.TCPConn) {
log.Println("Proxying", ConnrecipientConn), "and", Conn(donorConn))
go func() {
_, err := io.Copy(recipientConn, donorConn)
if err != nil {
utils.StacktraceError(err)
}
recipientConn.Close()
}()
go func() {
_, err := io.Copy(donorConn, recipientConn)
if err != nil {
utils.StacktraceError(err)
}
recipientConn.Close()
}()
}
func main() {
// Open the donor listener
donorsAddr, err := net.ResolveTCPAddr("tcp4", ":11000")
if err != nil {
utils.StacktraceErrorAndExit(err)
}
listenerDonors, err := net.ListenTCP("tcp", donorsAddr)
if err != nil {
utils.StacktraceErrorAndExit(err)
}
defer listenerDonors.Close()
log.Println("Listening for donors on", listenerDonors.Addr())
// Open the recipient listener
recipientsAddr, err := net.ResolveTCPAddr("tcp4", ":10000")
if err != nil {
utils.StacktraceErrorAndExit(err)
}
listenerRecipients, err := net.ListenTCP("tcp", recipientsAddr)
if err != nil {
utils.StacktraceErrorAndExit(err)
}
defer listenerRecipients.Close()
log.Println("Listening for recipients on", listenerRecipients.Addr())
// Handle donor connections
donorConns := make(chan *net.TCPConn)
go func() {
for {
donorConn, err := listenerDonors.AcceptTCP()
donorConn.SetKeepAlive(true)
if err != nil {
utils.StacktraceErrorAndExit(err)
return
}
log.Println("New donor connection from", Conn(donorConn))
donorConns <- donorConn
}
}()
// Handle recipient connections
for {
recipientConn, err := listenerRecipients.AcceptTCP()
recipientConn.SetKeepAlive(true)
if err != nil {
utils.StacktraceErrorAndExit(err)
return
}
log.Println("New recipient connection from", Conn(recipientConn))
donorConn := <-donorConns
proxy.ProxifyConns(recipientConn, donorConn)
}
}
Machine B tunnel (Node.js):
import net, { AddressInfo } from 'net';
import http from 'http';
import golgi from 'golgi';
export const startHttpProxy = () => {
const server = http.createServer();
let proxyServer: http.Server = golgi(server);
// Listening to 0 assigns a random OS-assigned port
proxyServer = proxyServer.listen(0);
return proxyServer;
};
export const startDonorSocket = () => {
const proxyServer = startHttpProxy();
const proxyServerSocket = new net.Socket();
proxyServerSocket.connect(
(proxyServer.address() as AddressInfo).port,
'127.0.0.1'
);
const donorSocket = new net.Socket();
donorSocket.setKeepAlive(true);
donorSocket.connect(11000, '2.226.102.14', () => {
proxyServerSocket.pipe(donorSocket);
donorSocket.pipe(proxyServerSocket);
});
};
Unfortunately this works when tunneling to one TCP address but not when tunneling to more. If I open many Machine B tunnels (Node.js code), it works. What I mean is that a donor connection (Node.js) is "consumed" ever time it is taken by a recipient (HTTP proxy user) because a persistent TCP tunnel is made on it.
I wonder is there is a way to make this work for any amount of TCP connections, not just one. My only idea right now is to create more TCP donor connections every time a connection is consumed but I wonder if there is a simpler solution.
When you do
You start processing the first TCP connection. This code blocks on
donorConns <- donorConn
. Until this send to channel finishes the loop won't go into the second iteration (and the next TCP connection won't be accepted).You do a very similar second loop
which requires
donorConn := <-donorConns
to complete (from the first loop) and requiresproxy.ProxifyConns(recipientConn, donorConn)
to complete.I'm not sure how you intend the whole thing to work, but, most likely, you need a very minor change:
go proxy.ProxifyConns(recipientConn, donorConn)