Several weeks ago my friend Madars was in an airport in the Netherlands and he wanted to login into his server via ssh. It turned out that their public internet had only ports 80 and 443 open so he couldn't do that. He asked me if I could proxy either port 80 or 443 to his server. Surely, I had a solution. I modified the tcp proxy server that I had written for my Turn any Linux computer into SOCKS5 proxy in one command article and did:

sudo ./tcp-proxy2.pl 443 madars-server.com:22

This proxied the port 443 on my server to madars-server.com ssh port. Now Madars could do

ssh -p 443 catonmat.net

and he got connected to his server. Mission accomplished.

Here is the code of tcp-proxy2.pl,

use warnings;
use strict;

use IO::Socket::INET;
use IO::Select;

my @allowed_ips = ('all', '10.10.10.5');
my $ioset = IO::Select->new;
my %socket_map;

my $debug = 1;

sub new_conn {
    my ($host, $port) = @_;
    return IO::Socket::INET->new(
        PeerAddr => $host,
        PeerPort => $port
    ) || die "Unable to connect to $host:$port: $!";
}

sub new_server {
    my ($host, $port) = @_;
    my $server = IO::Socket::INET->new(
        LocalAddr => $host,
        LocalPort => $port,
        ReuseAddr => 1,
        Listen    => 100
    ) || die "Unable to listen on $host:$port: $!";
}

sub new_connection {
    my $server = shift;
    my $remote_host = shift;
    my $remote_port = shift;

    my $client = $server->accept;
    my $client_ip = client_ip($client);

    unless (client_allowed($client)) {
        print "Connection from $client_ip denied.\n" if $debug;
        $client->close;
        return;
    }
    print "Connection from $client_ip accepted.\n" if $debug;

    my $remote = new_conn($remote_host, $remote_port);
    $ioset->add($client);
    $ioset->add($remote);

    $socket_map{$client} = $remote;
    $socket_map{$remote} = $client;
}

sub close_connection {
    my $client = shift;
    my $client_ip = client_ip($client);
    my $remote = $socket_map{$client};
    
    $ioset->remove($client);
    $ioset->remove($remote);

    delete $socket_map{$client};
    delete $socket_map{$remote};

    $client->close;
    $remote->close;

    print "Connection from $client_ip closed.\n" if $debug;
}

sub client_ip {
    my $client = shift;
    return inet_ntoa($client->sockaddr);
}

sub client_allowed {
    my $client = shift;
    my $client_ip = client_ip($client);
    return grep { $_ eq $client_ip || $_ eq 'all' } @allowed_ips;
}

die "Usage: $0 <local port> <remote_host:remote_port>" unless @ARGV == 2;

my $local_port = shift;
my ($remote_host, $remote_port) = split ':', shift();


print "Starting a server on 0.0.0.0:$local_port\n";
my $server = new_server('0.0.0.0', $local_port);
$ioset->add($server);

while (1) {
    for my $socket ($ioset->can_read) {
        if ($socket == $server) {
            new_connection($server, $remote_host, $remote_port);
        }
        else {
            next unless exists $socket_map{$socket};
            my $remote = $socket_map{$socket};
            my $buffer;
            my $read = $socket->sysread($buffer, 4096);
            if ($read) {
                $remote->syswrite($buffer);
            }
            else {
                close_connection($socket);
            }
        }
    }
}

Download tcp-proxy2.pl

Download link: tcp proxy 2 (tcp-proxy2.pl)
Download URL: http://www.catonmat.net/download/tcp-proxy2.pl
Downloaded: 3799 times

I also pushed the tcp-proxy2.pl to a new GitHub repository: tcp-proxy2.pl on GitHub.

Enjoy!

Comments

March 31, 2011, 16:45

Simple and elegant!

I wrote a TCP proxy in Python a few weeks ago. It was on a whiteboard for an interview though, assuming that there are already functions for establishing new connections and close them later. May be I can write a complete program, and see how it really performs.

March 31, 2011, 16:49

F*#@!g firewalls!!! :-D

I encountered the same problem, but my 443 port was already in use by my HTTP server, so I did a similar proxy with a simple dispatching technique to detect HTTP from SSH on a single port...

Have a look at my solution, I just posted it on our blog.

Best regards

Dirk Permalink
April 01, 2011, 06:07

AnyEvent++

September 06, 2012, 12:00

I would like to see this code. Where I can found it?

April 02, 2011, 01:42

Have you heard of SSLH? Not something your friend could have done at the airport, but it's a great way to get access.

What's even more fun is tossing OpenVPN into the mix. Since OpenVPN is smart enough to proxy port 443 already, you can combo SSLH & OpenVPN. Have sslh sitting on 0.0.0.0:443, and let it redirect to ssh & openvpn. Then OpenVPN will sniff the traffic, and if it's really HTTPS traffic, send it along to the web server. Bit more complex, and does add some latency, but really really fun & silly to do. :-)

user Permalink
April 04, 2011, 13:19

Such proxy also can be done with great program named "socat". It can connect almost everything-to-everything.
Also, as I understand You proxy uses only non-blocking read, but write is blocking. This can cause problems on some applications. I have seen them when using rsync over nc (netcat seems to use blocking write too). socat have no this problem.

April 06, 2011, 02:41

kit kit

mindoula Permalink
September 25, 2012, 11:32

Hi! Proxy very useful and very convenient, but it does not transfer header for http - HTTP_X_REAL_IP, HTTP_X_FORWARDED_FOR.
I did not programing on perl. And please help correct this problem. Thanks a lot.

Adam vonNieda Permalink
October 09, 2012, 14:02

This proxy works great, and really helped me out in a pinch. Thanks!

-Adam

November 01, 2012, 03:02

Hi! Thanks for sharing your Perl TCP. I forked you on the GitHub. This seed script also inspired me to write an asynchronous Python TCP proxy tool with injection capability. You may view & fork at:

https://github.com/vietlq/pyproxy

ryne Permalink
February 01, 2013, 15:33

why note
local.com > ssh user@127.0.0.1 -L 80:127.0.0.1:22 -g

remote.com > ssh -p 80 use@local.com

Leave a new comment

(why do I need your e-mail?)

(Your twitter name, if you have one. (I'm @pkrumins, btw.))

Type the word "server": (just to make sure you're a human)

Please preview the comment before submitting to make sure it's OK.

Advertisements