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 Bopens 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 := <-donorConnsto 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)