File Coverage

blib/lib/IO/Stream/Proxy/SOCKSv5.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package IO::Stream::Proxy::SOCKSv5;
2              
3 2     2   29330 use warnings;
  2         5  
  2         53  
4 2     2   9 use strict;
  2         5  
  2         52  
5 2     2   9 use Carp;
  2         8  
  2         147  
6              
7 2     2   1470 use version; our $VERSION = qv('1.0.2'); # update POD & Changes & README
  2         4008  
  2         10  
8              
9             # update DEPENDENCIES in POD & Makefile.PL & README
10 2     2   870 use IO::Stream::const;
  0            
  0            
11             use IO::Stream::EV;
12             use Scalar::Util qw( weaken );
13              
14             use constant HANDSHAKE => 1;
15             use constant CONNECTING => 2;
16              
17             ### SOCKS protocol constants:
18             use constant VN => 0x05;# version number (5)
19             use constant AUTH_NO => 0x00;# authentication method id
20             use constant CD => 0x01;# command code (CONNECT)
21             ## no critic (Capitalization)
22             use constant ADDR_IPv4 => 0x01;# address type (IPv4)
23             use constant ADDR_DOMAIN => 0x03;# address type (DOMAIN)
24             use constant ADDR_IPv6 => 0x04;# address type (IPv6)
25             use constant LEN_IPv4 => 4;
26             use constant LEN_IPv6 => 16;
27             ## use critic
28             use constant REPLY_LEN_HANDSHAKE=> 2; # reply length for handshake (bytes)
29             use constant REPLY_LEN_CONNECT => 4; # reply length for connect header (bytes)
30             use constant REPLY_CD => 0x00;# reply code 'request granted'
31              
32              
33             sub new {
34             my ($class, $opt) = @_;
35             croak '{host}+{port} required'
36             if !defined $opt->{host}
37             || !defined $opt->{port}
38             ;
39             my $self = bless {
40             host => undef,
41             port => undef,
42             # user => q{}, # TODO
43             # pass => q{}, # TODO
44             %{$opt},
45             out_buf => q{}, # modified on: OUT
46             out_pos => undef, # modified on: OUT
47             out_bytes => 0, # modified on: OUT
48             in_buf => q{}, # modified on: IN
49             in_bytes => 0, # modified on: IN
50             ip => undef, # modified on: RESOLVED
51             is_eof => undef, # modified on: EOF
52             _want_write => undef,
53             _state => 0, # HANDSHAKE -> [AUTH] -> CONNECTING
54             _port => undef,
55             }, $class;
56             return $self;
57             }
58              
59             sub PREPARE {
60             my ($self, $fh, $host, $port) = @_;
61             croak '{fh} already connected'
62             if !defined $host;
63             $self->{_port} = $port;
64             $self->{_slave}->PREPARE($fh, $self->{host}, $self->{port});
65             IO::Stream::EV::resolve($host, $self, sub {
66             my ($self, $ip) = @_;
67             $self->{_master}{ip} = $ip;
68             $self->{_state} = HANDSHAKE;
69             my @auth = ( AUTH_NO );
70             $self->{out_buf} = pack 'C C C*', VN, 0+@auth, @auth;
71             $self->{_slave}->WRITE();
72             });
73             return;
74             }
75              
76             sub WRITE {
77             my ($self) = @_;
78             $self->{_want_write} = 1;
79             return;
80             }
81              
82             sub EVENT { ## no critic (ProhibitExcessComplexity)
83             ## no critic (ProhibitDeepNests)
84             my ($self, $e, $err) = @_;
85             my $m = $self->{_master};
86             if ($err) {
87             $m->EVENT(0, $err);
88             }
89             if ($e & IN) {
90             if ($self->{_state} == HANDSHAKE) {
91             if (length $self->{in_buf} < REPLY_LEN_HANDSHAKE) {
92             $m->EVENT(0, 'socks v5 proxy: protocol error');
93             } else {
94             my ($vn, $auth) = unpack 'CC', $self->{in_buf};
95             substr $self->{in_buf}, 0, REPLY_LEN_HANDSHAKE, q{};
96             if ($vn != VN) {
97             $m->EVENT(0, 'socks v5 proxy: unknown version of reply code');
98             }
99             elsif ($auth != AUTH_NO) {
100             $m->EVENT(0, 'socks v5 proxy: auth method handshake error');
101             }
102             else {
103             $self->{_state} = CONNECTING;
104             $self->{out_buf} = pack 'C C C C CCCC n',
105             VN, CD, 0, ADDR_IPv4,
106             split(/[.]/xms, $self->{_master}{ip}), $self->{_port};
107             $self->{_slave}->WRITE();
108             }
109             }
110             }
111             elsif ($self->{_state} == CONNECTING) {
112             if (length $self->{in_buf} < REPLY_LEN_CONNECT) {
113             $m->EVENT(0, 'socks v5 proxy: protocol error');
114             } else {
115             my ($vn, $cd, $atype) = unpack 'CCxC', $self->{in_buf};
116             substr $self->{in_buf}, 0, REPLY_LEN_CONNECT, q{};
117             if ($vn != VN) {
118             $m->EVENT(0, 'socks v5 proxy: unknown version of reply code');
119             }
120             elsif ($cd != REPLY_CD) {
121             $m->EVENT(0, 'socks v5 proxy: error '.$cd);
122             }
123             elsif ($atype != ADDR_IPv4 && $atype != ADDR_DOMAIN && $atype != ADDR_IPv6) {
124             $m->EVENT(0, 'socks v5 proxy: unknown address type '.$atype);
125             }
126             else {
127             my $tail_len
128             = $atype == ADDR_IPv4 ? LEN_IPv4+2
129             : $atype == ADDR_DOMAIN ? 1+unpack('C', $self->{in_buf})+2
130             : LEN_IPv6+2
131             ;
132             if (length $self->{in_buf} < $tail_len) {
133             $m->EVENT(0, 'socks v5 proxy: protocol error');
134             } else {
135             substr $self->{in_buf}, 0, $tail_len, q{};
136             # SOCKS v5 protocol done
137             $e = CONNECTED;
138             if (my $l = length $self->{in_buf}) {
139             $e |= IN;
140             $m->{in_buf} .= $self->{in_buf};
141             $m->{in_bytes} += $l;
142             }
143             $m->EVENT($e);
144             $self->{_slave}->{_master} = $m;
145             weaken($self->{_slave}->{_master});
146             $m->{_slave} = $self->{_slave};
147             if ($self->{_want_write}) {
148             $self->{_slave}->WRITE();
149             }
150             }
151             }
152             }
153             }
154             }
155             if ($e & EOF) {
156             $m->{is_eof} = $self->{is_eof};
157             $m->EVENT(0, 'socks v5 proxy: unexpected EOF');
158             }
159             return;
160             }
161              
162              
163             1; # Magic true value required at end of module
164             __END__
165              
166             =head1 NAME
167              
168             IO::Stream::Proxy::SOCKSv5 - SOCKSv5 proxy plugin for IO::Stream
169              
170              
171             =head1 VERSION
172              
173             This document describes IO::Stream::Proxy::SOCKSv5 version 1.0.2
174              
175              
176             =head1 SYNOPSIS
177              
178             use IO::Stream;
179             use IO::Stream::Proxy::SOCKSv5;
180              
181             IO::Stream->new({
182             ...
183             plugin => [
184             ...
185             proxy => IO::Stream::Proxy::SOCKSv5->new({
186             host => 'my.proxy.com',
187             port => 3128,
188             }),
189             ...
190             ],
191             });
192              
193              
194             =head1 DESCRIPTION
195              
196             This module is plugin for L<IO::Stream> which allow you to route stream
197             through SOCKSv5 proxy.
198              
199             You may use several IO::Stream::Proxy::SOCKSv5 plugins for single IO::Stream
200             object, effectively creating proxy chain (first proxy plugin will define
201             last proxy in a chain).
202              
203             =head2 SECURITY
204              
205             While version 5 of SOCKS protocol support domain name resolving by proxy,
206             it unable to report resolved IP address, which is required by IO::Stream
207             architecture, so resolving happens always on client side. This may result
208             in leaking client's DNS resolver IP address (usually it's client's address
209             or client's ISP address) and detecting the fact of using proxy.
210              
211             =head2 EVENTS
212              
213             When using this plugin event RESOLVED will never be delivered to user because
214             there may be two hosts to resolve (target host and proxy host) and it
215             isn't clear how to handle this case in right way.
216              
217             Event CONNECTED will be generated after SOCKS proxy successfully connects to
218             target {host} (and not when socket will connect to SOCKS proxy itself).
219              
220              
221             =head1 INTERFACE
222              
223             =over
224              
225             =item new({ host=>$host, port=>$port })
226              
227             Connect to proxy $host:$port.
228              
229             =back
230              
231              
232             =head1 DIAGNOSTICS
233              
234             =over
235              
236             =item C<< {host}+{port} required >>
237              
238             You must provide both {host} and {port} to IO::Stream::Proxy::SOCKSv5->new().
239              
240             =item C<< {fh} already connected >>
241              
242             You have provided {fh} to IO::Stream->new(), but this is not supported by
243             this plugin. Either don't use this plugin or provide {host}+{port} to
244             IO::Stream->new() instead.
245              
246             =back
247              
248              
249              
250             =head1 CONFIGURATION AND ENVIRONMENT
251              
252             IO::Stream::Proxy::SOCKSv5 requires no configuration files or environment variables.
253              
254              
255             =head1 DEPENDENCIES
256              
257             L<IO::Stream>.
258              
259              
260             =head1 INCOMPATIBILITIES
261              
262             None reported.
263              
264              
265             =head1 BUGS AND LIMITATIONS
266              
267             Only these authentication methods supported:
268              
269             - no authentication
270              
271             SOCKS "BIND" request doesn't supported.
272              
273             SOCKS "associate UDP" request doesn't supported.
274              
275             No bugs have been reported.
276              
277             Please report any bugs or feature requests to
278             C<bug-io-stream-proxy-socksv5@rt.cpan.org>, or through the web interface at
279             L<http://rt.cpan.org>.
280              
281              
282             =head1 AUTHOR
283              
284             Alex Efros C<< <powerman-asdf@ya.ru> >>
285              
286              
287             =head1 LICENSE AND COPYRIGHT
288              
289             Copyright (c) 2010, Alex Efros C<< <powerman-asdf@ya.ru> >>. All rights reserved.
290              
291             This module is free software; you can redistribute it and/or
292             modify it under the same terms as Perl itself. See L<perlartistic>.
293              
294              
295             =head1 DISCLAIMER OF WARRANTY
296              
297             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
298             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
299             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
300             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
301             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
302             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
303             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
304             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
305             NECESSARY SERVICING, REPAIR, OR CORRECTION.
306              
307             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
308             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
309             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
310             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
311             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
312             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
313             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
314             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
315             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
316             SUCH DAMAGES.