use
Socket
qw(IPPROTO_TCP SOCK_STREAM TCP_NODELAY)
;
? 0
:
eval
'use Net::DNS::Native 0.15 (); 1'
;
my
$NDN
= NDN ? Net::DNS::Native->new(
pool
=> 5,
extra_thread
=> 1) :
undef
;
? 0
:
eval
'use IO::Socket::SSL 1.94 (); 1'
;
use
constant
TLS_READ
=> TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use
constant
TLS_WRITE
=> TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;
use
constant
SOCKS
=>
$ENV
{MOJO_NO_SOCKS}
? 0
:
eval
'use IO::Socket::Socks 0.64 (); 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 }
sub
connect
{
my
(
$self
,
$args
) = (
shift
,
ref
$_
[0] ?
$_
[0] : {
@_
});
weaken
$self
;
my
$reactor
=
$self
->reactor;
$self
->{timer} =
$reactor
->timer(
$args
->{timeout} || 10,
sub
{
$self
->emit(
error
=>
'Connect timeout'
) });
$_
&& s/[[\]]//g
for
@$args
{
qw(address socks_address)
};
my
$address
=
$args
->{socks_address} || (
$args
->{address} ||=
'127.0.0.1'
);
return
$reactor
->next_tick(
sub
{
$self
&&
$self
->_connect(
$args
) })
if
!NDN ||
$args
->{handle};
my
$handle
=
$self
->{dns} =
$NDN
->getaddrinfo(
$address
, _port(
$args
),
{
protocol
=> IPPROTO_TCP,
socktype
=> SOCK_STREAM});
$reactor
->io(
$handle
=>
sub
{
my
$reactor
=
shift
;
$reactor
->remove(
$self
->{dns});
my
(
$err
,
@res
) =
$NDN
->get_result(
delete
$self
->{dns});
return
$self
->emit(
error
=>
"Can't resolve: $err"
)
if
$err
;
$args
->{addr_info} = \
@res
;
$self
->_connect(
$args
);
}
)->watch(
$handle
, 1, 0);
}
sub
_cleanup {
my
$self
=
shift
;
$NDN
->timedout(
$self
->{dns})
if
$self
->{dns};
my
$reactor
=
$self
->reactor;
$self
->{
$_
} &&
$reactor
->remove(
delete
$self
->{
$_
})
for
qw(dns timer handle)
;
return
$self
;
}
sub
_connect {
my
(
$self
,
$args
) =
@_
;
my
$handle
;
my
$address
=
$args
->{socks_address} ||
$args
->{address};
unless
(
$handle
=
$self
->{handle} =
$args
->{handle}) {
my
%options
= (
PeerAddr
=>
$address
,
PeerPort
=> _port(
$args
));
%options
= (
PeerAddrInfo
=>
$args
->{addr_info})
if
$args
->{addr_info};
$options
{Blocking} = 0;
$options
{LocalAddr} =
$args
->{local_address}
if
$args
->{local_address};
return
$self
->emit(
error
=>
"Can't connect: $@"
)
unless
$self
->{handle} =
$handle
= IO::Socket::IP->new(
%options
);
}
$handle
->blocking(0);
weaken
$self
;
$self
->reactor->io(
$handle
=>
sub
{
$self
->_ready(
$args
) })
->watch(
$handle
, 0, 1);
}
sub
_port {
$_
[0]{socks_port} ||
$_
[0]{port} || (
$_
[0]{tls} ? 443 : 80) }
sub
_ready {
my
(
$self
,
$args
) =
@_
;
my
$handle
=
$self
->{handle};
return
$! == EINPROGRESS ?
undef
:
$self
->emit(
error
=> $!)
if
$handle
->isa(
'IO::Socket::IP'
) && !
$handle
->
connect
;
return
$self
->emit(
error
=> $! ||
'Not connected'
)
unless
$handle
->connected;
setsockopt
$handle
, IPPROTO_TCP, TCP_NODELAY, 1;
$self
->_try_socks(
$args
);
}
sub
_socks {
my
(
$self
,
$args
) =
@_
;
my
$handle
=
$self
->{handle};
return
$self
->_try_tls(
$args
)
if
$handle
->ready;
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 {
my
$self
=
shift
;
my
$handle
=
$self
->{handle};
return
$self
->_cleanup->emit(
connect
=>
$handle
)
if
$handle
->connect_SSL;
my
$err
=
$IO::Socket::SSL::SSL_ERROR
;
if
(
$err
== TLS_READ) {
$self
->reactor->watch(
$handle
, 1, 0) }
elsif
(
$err
== TLS_WRITE) {
$self
->reactor->watch(
$handle
, 1, 1) }
}
sub
_try_socks {
my
(
$self
,
$args
) =
@_
;
my
$handle
=
$self
->{handle};
return
$self
->_try_tls(
$args
)
unless
$args
->{socks_address};
return
$self
->emit(
error
=>
'IO::Socket::Socks 0.64+ 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
);
weaken
$self
;
$reactor
->io(
$handle
=>
sub
{
$self
->_socks(
$args
) })->watch(
$handle
, 0, 1);
}
sub
_try_tls {
my
(
$self
,
$args
) =
@_
;
my
$handle
=
$self
->{handle};
return
$self
->_cleanup->emit(
connect
=>
$handle
)
unless
$args
->{tls};
return
$self
->emit(
error
=>
'IO::Socket::SSL 1.94+ required for TLS support'
)
unless
TLS;
weaken
$self
;
my
%options
= (
SSL_ca_file
=>
$args
->{tls_ca}
&& -T
$args
->{tls_ca} ?
$args
->{tls_ca} :
undef
,
SSL_cert_file
=>
$args
->{tls_cert},
SSL_error_trap
=>
sub
{
$self
->emit(
error
=>
$_
[1]) },
SSL_hostname
=> IO::Socket::SSL->can_client_sni ?
$args
->{address} :
''
,
SSL_key_file
=>
$args
->{tls_key},
SSL_startHandshake
=> 0,
SSL_verify_mode
=>
$args
->{tls_ca} ? 0x01 : 0x00,
SSL_verifycn_name
=>
$args
->{address},
SSL_verifycn_scheme
=>
$args
->{tls_ca} ?
'http'
:
undef
);
my
$reactor
=
$self
->reactor;
$reactor
->remove(
$handle
);
return
$self
->emit(
error
=>
'TLS upgrade failed'
)
unless
IO::Socket::SSL->start_SSL(
$handle
,
%options
);
$reactor
->io(
$handle
=>
sub
{
$self
->_tls })->watch(
$handle
, 0, 1);
}
1;