Skip to content

Commit

Permalink
added SOCKS5 support
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Jul 29, 2014
1 parent 7547c79 commit 82a7d45
Show file tree
Hide file tree
Showing 15 changed files with 296 additions and 79 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -22,9 +22,9 @@
magic and no requirements besides Perl 5.10.1 (although 5.18+ is
recommended, and optional CPAN modules will be used to provide advanced
functionality if they are installed).
* Full stack HTTP and WebSocket client/server implementation with IPv6, TLS,
SNI, IDNA, Comet (long polling), keep-alive, connection pooling, timeout,
cookie, multipart, proxy, and gzip compression support.
* Full stack HTTP and WebSocket client/server implementation with IPv6,
SOCKS5, TLS, SNI, IDNA, Comet (long polling), keep-alive, connection
pooling, timeout, cookie, multipart, proxy, and gzip compression support.
* Built-in non-blocking I/O web server, supporting multiple event loops as
well as optional preforking and hot deployment, perfect for embedding.
* Automatic CGI and [PSGI](http://plackperl.org) detection.
Expand Down
11 changes: 6 additions & 5 deletions lib/Mojo/IOLoop.pm
Expand Up @@ -320,11 +320,12 @@ right in, to make writing test servers as easy as possible. Also note that for
convenience the C<PIPE> signal will be set to C<IGNORE> when L<Mojo::IOLoop>
is loaded.
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
Individual features can also be disabled with the C<MOJO_NO_IPV6> and
C<MOJO_NO_TLS> environment variables.
For better scalability (epoll, kqueue) and to provide IPv6, SOCKS5 as well as
TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
automatically if they are installed. Individual features can also be disabled
with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
variables.
See L<Mojolicious::Guides::Cookbook/"REAL-TIME WEB"> for more.
Expand Down
98 changes: 86 additions & 12 deletions lib/Mojo/IOLoop/Client.pm
Expand Up @@ -19,6 +19,13 @@ use constant TLS => $ENV{MOJO_NO_TLS}
use constant TLS_READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use constant TLS_WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;

# SOCKS support requires IO::Socket::Socks
use constant SOCKS => $ENV{MOJO_NO_SOCKS}
? 0
: eval 'use IO::Socket::Socks 0.63 (); 1';
use constant SOCKS_READ => SOCKS ? IO::Socket::Socks::SOCKS_WANT_READ() : 0;
use constant SOCKS_WRITE => SOCKS ? IO::Socket::Socks::SOCKS_WANT_WRITE() : 0;

has reactor => sub { Mojo::IOLoop->singleton->reactor };

sub DESTROY { shift->_cleanup }
Expand All @@ -42,12 +49,13 @@ sub _connect {

my $handle;
my $reactor = $self->reactor;
my $address = $args->{address} ||= 'localhost';
my $address = $args->{socks_address} || ($args->{address} ||= 'localhost');
my $port = $args->{socks_port} || $args->{port} || ($args->{tls} ? 443 : 80);
unless ($handle = $self->{handle} = $args->{handle}) {
my %options = (
Blocking => 0,
PeerAddr => $address eq 'localhost' ? '127.0.0.1' : $address,
PeerPort => $args->{port} || ($args->{tls} ? 443 : 80)
PeerPort => $port
);
$options{LocalAddr} = $args->{local_address} if $args->{local_address};
$options{PeerAddr} =~ s/[\[\]]//g if $options{PeerAddr};
Expand All @@ -63,7 +71,37 @@ sub _connect {

# Wait for handle to become writable
weaken $self;
$reactor->io($handle => sub { $self->_try($args) })->watch($handle, 0, 1);
$reactor->io($handle => sub { $self->_ready($args) })->watch($handle, 0, 1);
}

sub _ready {
my ($self, $args) = @_;

# Retry or handle exceptions
my $handle = $self->{handle};
return $! == EINPROGRESS ? undef : $self->emit(error => $!)
if $handle->isa('IO::Socket::IP') && !$handle->connect;
return $self->emit(error => $! = $handle->sockopt(SO_ERROR))
unless $handle->connected;

# Disable Nagle's algorithm
setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;

$self->_try_socks($args);
}

sub _socks {
my ($self, $args) = @_;

# Connected
my $handle = $self->{handle};
return $self->_try_tls($args) if $handle->ready;

# Switch between reading and writing
my $err = $IO::Socket::Socks::SOCKS_ERROR;
if ($err == SOCKS_READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == SOCKS_WRITE) { $self->reactor->watch($handle, 1, 1) }
else { $self->emit(error => $err) }
}

sub _tls {
Expand All @@ -80,19 +118,31 @@ sub _tls {
elsif ($err == TLS_WRITE) { $self->reactor->watch($handle, 1, 1) }
}

sub _try {
sub _try_socks {
my ($self, $args) = @_;

# Retry or handle exceptions
my $handle = $self->{handle};
return $! == EINPROGRESS ? undef : $self->emit(error => $!)
if $handle->isa('IO::Socket::IP') && !$handle->connect;
return $self->emit(error => $! = $handle->sockopt(SO_ERROR))
unless $handle->connected;
return $self->_try_tls($args) unless $args->{socks_address};
return $self->emit(
error => 'IO::Socket::Socks 0.63 required for SOCKS support')
unless SOCKS;

my %options
= (ConnectAddr => $args->{address}, ConnectPort => $args->{port});
@options{qw(AuthType Username Password)}
= ('userpass', @$args{qw(socks_user socks_pass)})
if $args->{socks_user};
my $reactor = $self->reactor;
$reactor->remove($handle);
return $self->emit(error => 'SOCKS upgrade failed')
unless IO::Socket::Socks->start_SOCKS($handle, %options);
$reactor->io($handle => sub { $self->_socks($args) })->watch($handle, 0, 1);
}

# Disable Nagle's algorithm
setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
sub _try_tls {
my ($self, $args) = @_;

my $handle = $self->{handle};
return $self->_cleanup->emit_safe(connect => $handle)
if !$args->{tls} || $handle->isa('IO::Socket::SSL');
return $self->emit(error => 'IO::Socket::SSL 1.84 required for TLS support')
Expand All @@ -115,7 +165,7 @@ sub _try {
my $reactor = $self->reactor;
$reactor->remove($handle);
return $self->emit(error => 'TLS upgrade failed')
unless $handle = IO::Socket::SSL->start_SSL($handle, %options);
unless IO::Socket::SSL->start_SSL($handle, %options);
$reactor->io($handle => sub { $self->_tls })->watch($handle, 0, 1);
}

Expand Down Expand Up @@ -225,6 +275,30 @@ Local address to bind to.
Port to connect to, defaults to C<80> or C<443> with C<tls> option.
=item socks_address
socks_address => '127.0.0.1'
Address or host name of SOCKS5 proxy server to use for connection.
=item socks_pass
socks_pass => 'secr3t'
Password to use for SOCKS5 authentication.
=item socks_port
socks_port => 9050
Port of SOCKS5 proxy server to use for connection.
=item socks_user
socks_user => 'sri'
Username to use for SOCKS5 authentication.
=item timeout
timeout => 15
Expand Down
6 changes: 3 additions & 3 deletions lib/Mojo/Message/Request.pm
Expand Up @@ -101,15 +101,15 @@ sub get_start_line_chunk {

# CONNECT
my $method = uc $self->method;
my $proxy = $self->proxy;
if ($method eq 'CONNECT') {
my $port = $url->port || ($url->protocol eq 'https' ? '443' : '80');
$path = $url->ihost . ":$port";
}

# Proxy
elsif ($self->proxy) {
$path = $url->clone->userinfo(undef)
unless $self->is_handshake || $url->protocol eq 'https';
elsif ($proxy && $proxy->protocol ne 'socks' && !$self->is_handshake) {
$path = $url->clone->userinfo(undef) unless $url->protocol eq 'https';
}

$self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
Expand Down
11 changes: 6 additions & 5 deletions lib/Mojo/Server/Daemon.pm
Expand Up @@ -265,11 +265,12 @@ HTTP and WebSocket server, with IPv6, TLS, Comet (long polling), keep-alive,
connection pooling, timeout, cookie, multipart and multiple event loop
support.
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
they are installed. Individual features can also be disabled with the
C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
For better scalability (epoll, kqueue) and to provide IPv6, SOCKS5 as well as
TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
automatically if they are installed. Individual features can also be disabled
with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
Expand Down
11 changes: 6 additions & 5 deletions lib/Mojo/Server/Hypnotoad.pm
Expand Up @@ -172,11 +172,12 @@ You can run the same command again for automatic hot deployment.
This second invocation will load the application again, detect the process id
file with it, and send a L</"USR2"> signal to the already running server.
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
they are installed. Individual features can also be disabled with the
C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
For better scalability (epoll, kqueue) and to provide IPv6, SOCKS5 as well as
TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
automatically if they are installed. Individual features can also be disabled
with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
Expand Down
11 changes: 6 additions & 5 deletions lib/Mojo/Server/Morbo.pm
Expand Up @@ -130,11 +130,12 @@ To start applications with it you can use the L<morbo> script.
$ morbo myapp.pl
Server available at http://127.0.0.1:3000.
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
they are installed. Individual features can also be disabled with the
C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
For better scalability (epoll, kqueue) and to provide IPv6, SOCKS5 as well as
TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
automatically if they are installed. Individual features can also be disabled
with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
Expand Down
11 changes: 6 additions & 5 deletions lib/Mojo/Server/Prefork.pm
Expand Up @@ -261,11 +261,12 @@ keep-alive, connection pooling, timeout, cookie, multipart and multiple event
loop support. Note that the server uses signals for process management, so you
should avoid modifying signal handlers in your applications.
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
they are installed. Individual features can also be disabled with the
C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
For better scalability (epoll, kqueue) and to provide IPv6, SOCKS5 as well as
TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
automatically if they are installed. Individual features can also be disabled
with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
Expand Down
55 changes: 34 additions & 21 deletions lib/Mojo/UserAgent.pm
Expand Up @@ -85,21 +85,30 @@ sub _cleanup {
}

sub _connect {
my ($self, $nb, $proto, $host, $port, $handle, $cb) = @_;
my ($self, $nb, $tx, $proto, $host, $port, $handle, $cb) = @_;

my $options
= {address => $host, port => $port, timeout => $self->connect_timeout};
if (my $local = $self->local_address) { $options->{local_address} = $local }
$options->{handle} = $handle if $handle;

# SOCKS
if ($proto eq 'socks') {
@$options{qw(socks_address socks_port)} = @$options{qw(address port)};
($proto, @$options{qw(address port)}) = $self->transactor->endpoint($tx);
my $userinfo = $tx->req->proxy->userinfo;
@$options{qw(socks_user socks_pass)} = split ':', $userinfo if $userinfo;
}

# TLS
if ($options->{tls} = $proto eq 'https') {
$options->{"tls_$_"} = $self->$_ for qw(ca cert key);
}

weaken $self;
my $id;
return $id = $self->_loop($nb)->client(
address => $host,
handle => $handle,
local_address => $self->local_address,
port => $port,
timeout => $self->connect_timeout,
tls => $proto eq 'https',
tls_ca => $self->ca,
tls_cert => $self->cert,
tls_key => $self->key,
sub {
$options => sub {
my ($loop, $err, $stream) = @_;

# Connection error
Expand Down Expand Up @@ -143,7 +152,7 @@ sub _connect_proxy {
my $handle = $loop->stream($id)->steal_handle;
my $c = delete $self->{connections}{$id};
$loop->remove($id);
$id = $self->_connect($nb, $self->transactor->endpoint($old),
$id = $self->_connect($nb, $old, $self->transactor->endpoint($old),
$handle, sub { shift->_start($nb, $old->connection($id), $cb) });
$self->{connections}{$id} = $c;
}
Expand Down Expand Up @@ -191,7 +200,7 @@ sub _connection {
# Connect
warn "-- Connect ($proto:$host:$port)\n" if DEBUG;
($proto, $host, $port) = $self->transactor->peer($tx);
$id = $self->_connect(($nb, $proto, $host, $port, $id) => \&_connected);
$id = $self->_connect(($nb, $tx, $proto, $host, $port, $id) => \&_connected);
$self->{connections}{$id} = {cb => $cb, nb => $nb, tx => $tx};

return $id;
Expand Down Expand Up @@ -424,19 +433,20 @@ Mojo::UserAgent - Non-blocking I/O HTTP and WebSocket user agent
=head1 DESCRIPTION
L<Mojo::UserAgent> is a full featured non-blocking I/O HTTP and WebSocket user
agent, with IPv6, TLS, SNI, IDNA, Comet (long polling), keep-alive, connection
pooling, timeout, cookie, multipart, proxy, gzip compression and multiple
event loop support.
agent, with IPv6, SOCKS5, TLS, SNI, IDNA, Comet (long polling), keep-alive,
connection pooling, timeout, cookie, multipart, proxy, gzip compression and
multiple event loop support.
All connections will be reset automatically if a new process has been forked,
this allows multiple processes to share the same L<Mojo::UserAgent> object
safely.
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+) and
L<IO::Socket::SSL> (1.84+) will be used automatically by L<Mojo::IOLoop> if
they are installed. Individual features can also be disabled with the
C<MOJO_NO_IPV6> and C<MOJO_NO_TLS> environment variables.
For better scalability (epoll, kqueue) and to provide IPv6, SOCKS5 as well as
TLS support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.20+),
L<IO::Socket::Socks> (0.63+) and L<IO::Socket::SSL> (1.84+) will be used
automatically if they are installed. Individual features can also be disabled
with the C<MOJO_NO_IPV6>, C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment
variables.
See L<Mojolicious::Guides::Cookbook/"USER AGENT"> for more.
Expand Down Expand Up @@ -580,6 +590,9 @@ Proxy manager, defaults to a L<Mojo::UserAgent::Proxy> object.
# Detect proxy servers from environment
$ua->proxy->detect;
# Manually configure Tor
$ua->proxy->http('socks://127.0.0.1:9050')->https('socks://127.0.0.1:9050');
=head2 request_timeout
my $timeout = $ua->request_timeout;
Expand Down
5 changes: 4 additions & 1 deletion lib/Mojo/UserAgent/Transactor.pm
Expand Up @@ -33,8 +33,10 @@ sub endpoint {
my $port = $url->port || ($proto eq 'https' ? 443 : 80);

# Proxy for normal HTTP requests
my $socks;
if (my $proxy = $req->proxy) { $socks = $proxy->protocol eq 'socks' }
return $self->_proxy($tx, $proto, $host, $port)
if $proto eq 'http' && !$req->is_handshake;
if $proto eq 'http' && !$req->is_handshake && !$socks;

return $proto, $host, $port;
}
Expand All @@ -50,6 +52,7 @@ sub proxy_connect {

# No proxy
return undef unless my $proxy = $req->proxy;
return undef if $proxy->protocol eq 'socks';

# WebSocket and/or HTTPS
my $url = $req->url;
Expand Down

0 comments on commit 82a7d45

Please sign in to comment.