3d5ebe88dd4206a839d03b32c9e95903af16b3c5
[safeq] / tcp-proxy.pl
1 #!/usr/bin/perl
2 #
3 # Peteris Krumins (peter@catonmat.net)
4 # http://www.catonmat.net  --  good coders code, great reuse
5 #
6 # A simple TCP proxy that implements IP-based access control
7 # Currently the ports are hard-coded, and it proxies
8 # 0.0.0.0:1080 to localhost:55555.
9 #
10 # Written for the article "Turn any Linux computer into SOCKS5
11 # proxy in one command," which can be read here:
12 #
13 # http://www.catonmat.net/blog/linux-socks5-proxy
14 #
15
16 use warnings;
17 use strict;
18
19 use IO::Socket;
20 use IO::Select;
21
22 my @allowed_ips = ('1.2.3.4', '5.6.7.8', '127.0.0.1', '192.168.1.2');
23 my $ioset = IO::Select->new;
24 my %socket_map;
25
26 my $debug = 1;
27
28 sub new_conn {
29     my ($host, $port) = @_;
30     return IO::Socket::INET->new(
31         PeerAddr => $host,
32         PeerPort => $port
33     ) || die "Unable to connect to $host:$port: $!";
34 }
35
36 sub new_server {
37     my ($host, $port) = @_;
38     my $server = IO::Socket::INET->new(
39         LocalAddr => $host,
40         LocalPort => $port,
41         ReuseAddr => 1,
42         Listen    => 100
43     ) || die "Unable to listen on $host:$port: $!";
44 }
45
46 sub new_connection {
47     my $server = shift;
48     my $client = $server->accept;
49     my $client_ip = client_ip($client);
50
51     unless (client_allowed($client)) {
52         print "Connection from $client_ip denied.\n" if $debug;
53         $client->close;
54         return;
55     }
56     print "Connection from $client_ip accepted.\n" if $debug;
57
58     my $remote = new_conn('localhost', 55555);
59     $ioset->add($client);
60     $ioset->add($remote);
61
62     $socket_map{$client} = $remote;
63     $socket_map{$remote} = $client;
64 }
65
66 sub close_connection {
67     my $client = shift;
68     my $client_ip = client_ip($client);
69     my $remote = $socket_map{$client};
70     
71     $ioset->remove($client);
72     $ioset->remove($remote);
73
74     delete $socket_map{$client};
75     delete $socket_map{$remote};
76
77     $client->close;
78     $remote->close;
79
80     print "Connection from $client_ip closed.\n" if $debug;
81 }
82
83 sub client_ip {
84     my $client = shift;
85     return inet_ntoa($client->sockaddr);
86 }
87
88 sub client_allowed {
89     my $client = shift;
90     my $client_ip = client_ip($client);
91     return grep { $_ eq $client_ip } @allowed_ips;
92 }
93
94 print "Starting a server on 0.0.0.0:1080\n";
95 my $server = new_server('0.0.0.0', 1080);
96 $ioset->add($server);
97
98 while (1) {
99     for my $socket ($ioset->can_read) {
100         if ($socket == $server) {
101             new_connection($server);
102         }
103         else {
104             next unless exists $socket_map{$socket};
105             my $remote = $socket_map{$socket};
106             my $buffer;
107             my $read = $socket->sysread($buffer, 4096);
108             if ($read) {
109                 $remote->syswrite($buffer);
110             }
111             else {
112                 close_connection($socket);
113             }
114         }
115     }
116 }
117