File Coverage

blib/lib/IO/Stream/Proxy/SOCKSv5.pm
Criterion Covered Total %
statement 62 125 49.6
branch 0 38 0.0
condition 0 9 0.0
subroutine 21 26 80.7
pod 1 3 33.3
total 84 201 41.7


line stmt bran cond sub pod time code
1             package IO::Stream::Proxy::SOCKSv5;
2 2     2   166730 use 5.010001;
  2         14  
3 2     2   9 use warnings;
  2         4  
  2         44  
4 2     2   8 use strict;
  2         2  
  2         27  
5 2     2   448 use utf8;
  2         12  
  2         9  
6 2     2   39 use Carp;
  2         4  
  2         113  
7              
8             our $VERSION = 'v2.0.1';
9              
10 2     2   380 use IO::Stream::const;
  2         6556  
  2         16  
11 2     2   602 use IO::Stream::EV;
  2         25888  
  2         66  
12 2     2   12 use Scalar::Util qw( weaken );
  2         4  
  2         82  
13              
14 2     2   11 use constant HANDSHAKE => 1;
  2         2  
  2         91  
15 2     2   17 use constant CONNECTING => 2;
  2         4  
  2         73  
16              
17             ### SOCKS protocol constants:
18 2     2   8 use constant VN => 0x05;# version number (5)
  2         2  
  2         75  
19 2     2   9 use constant AUTH_NO => 0x00;# authentication method id
  2         2  
  2         71  
20 2     2   9 use constant CD => 0x01;# command code (CONNECT)
  2         4  
  2         70  
21             ## no critic (Capitalization)
22 2     2   9 use constant ADDR_IPv4 => 0x01;# address type (IPv4)
  2         4  
  2         89  
23 2     2   8 use constant ADDR_DOMAIN => 0x03;# address type (DOMAIN)
  2         5  
  2         89  
24 2     2   10 use constant ADDR_IPv6 => 0x04;# address type (IPv6)
  2         3  
  2         82  
25 2     2   10 use constant LEN_IPv4 => 4;
  2         2  
  2         63  
26 2     2   7 use constant LEN_IPv6 => 16;
  2         4  
  2         75  
27             ## use critic
28 2     2   9 use constant REPLY_LEN_HANDSHAKE=> 2; # reply length for handshake (bytes)
  2         3  
  2         73  
29 2     2   8 use constant REPLY_LEN_CONNECT => 4; # reply length for connect header (bytes)
  2         2  
  2         74  
30 2     2   8 use constant REPLY_CD => 0x00;# reply code 'request granted'
  2         3  
  2         1602  
31              
32              
33             sub new {
34 0     0 1   my ($class, $opt) = @_;
35             croak '{host}+{port} required'
36             if !defined $opt->{host}
37             || !defined $opt->{port}
38 0 0 0       ;
39             my $self = bless {
40             host => undef,
41             port => undef,
42             # user => q{}, # TODO
43             # pass => q{}, # TODO
44 0           %{$opt},
  0            
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 0           return $self;
57             }
58              
59             sub PREPARE {
60 0     0 0   my ($self, $fh, $host, $port) = @_;
61 0 0         croak '{fh} already connected'
62             if !defined $host;
63 0           $self->{_port} = $port;
64 0           $self->{_slave}->PREPARE($fh, $self->{host}, $self->{port});
65             IO::Stream::EV::resolve($host, $self, sub {
66 0     0     my ($self, $ip) = @_;
67 0           $self->{_master}{ip} = $ip;
68 0           $self->{_state} = HANDSHAKE;
69 0           my @auth = ( AUTH_NO );
70 0           $self->{out_buf} = pack 'C C C*', VN, 0+@auth, @auth;
71 0           $self->{_slave}->WRITE();
72 0           });
73 0           return;
74             }
75              
76             sub WRITE {
77 0     0     my ($self) = @_;
78 0           $self->{_want_write} = 1;
79 0           return;
80             }
81              
82             sub EVENT { ## no critic (ProhibitExcessComplexity)
83             ## no critic (ProhibitDeepNests)
84 0     0 0   my ($self, $e, $err) = @_;
85 0           my $m = $self->{_master};
86 0 0         if ($err) {
87 0           $m->EVENT(0, $err);
88             }
89 0 0         if ($e & IN) {
90 0 0         if ($self->{_state} == HANDSHAKE) {
    0          
91 0 0         if (length $self->{in_buf} < REPLY_LEN_HANDSHAKE) {
92 0           $m->EVENT(0, 'socks v5 proxy: protocol error');
93             } else {
94 0           my ($vn, $auth) = unpack 'CC', $self->{in_buf};
95 0           substr $self->{in_buf}, 0, REPLY_LEN_HANDSHAKE, q{};
96 0 0         if ($vn != VN) {
    0          
97 0           $m->EVENT(0, 'socks v5 proxy: unknown version of reply code');
98             }
99             elsif ($auth != AUTH_NO) {
100 0           $m->EVENT(0, 'socks v5 proxy: auth method handshake error');
101             }
102             else {
103 0           $self->{_state} = CONNECTING;
104             $self->{out_buf} = pack 'C C C C CCCC n',
105             VN, CD, 0, ADDR_IPv4,
106 0           split(/[.]/xms, $self->{_master}{ip}), $self->{_port};
107 0           $self->{_slave}->WRITE();
108             }
109             }
110             }
111             elsif ($self->{_state} == CONNECTING) {
112 0 0         if (length $self->{in_buf} < REPLY_LEN_CONNECT) {
113 0           $m->EVENT(0, 'socks v5 proxy: protocol error');
114             } else {
115 0           my ($vn, $cd, $atype) = unpack 'CCxC', $self->{in_buf};
116 0           substr $self->{in_buf}, 0, REPLY_LEN_CONNECT, q{};
117 0 0 0       if ($vn != VN) {
    0 0        
    0          
118 0           $m->EVENT(0, 'socks v5 proxy: unknown version of reply code');
119             }
120             elsif ($cd != REPLY_CD) {
121 0           $m->EVENT(0, 'socks v5 proxy: error '.$cd);
122             }
123             elsif ($atype != ADDR_IPv4 && $atype != ADDR_DOMAIN && $atype != ADDR_IPv6) {
124 0           $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 0 0         : $atype == ADDR_DOMAIN ? 1+unpack('C', $self->{in_buf})+2
    0          
130             : LEN_IPv6+2
131             ;
132 0 0         if (length $self->{in_buf} < $tail_len) {
133 0           $m->EVENT(0, 'socks v5 proxy: protocol error');
134             } else {
135 0           substr $self->{in_buf}, 0, $tail_len, q{};
136             # SOCKS v5 protocol done
137 0           $e = CONNECTED;
138 0 0         if (my $l = length $self->{in_buf}) {
139 0           $e |= IN;
140 0           $m->{in_buf} .= $self->{in_buf};
141 0           $m->{in_bytes} += $l;
142             }
143 0           $m->EVENT($e);
144 0           $self->{_slave}->{_master} = $m;
145 0           weaken($self->{_slave}->{_master});
146 0           $m->{_slave} = $self->{_slave};
147 0 0         if ($self->{_want_write}) {
148 0           $self->{_slave}->WRITE();
149             }
150             }
151             }
152             }
153             }
154             }
155 0 0         if ($e & EOF) {
156 0           $m->{is_eof} = $self->{is_eof};
157 0           $m->EVENT(0, 'socks v5 proxy: unexpected EOF');
158             }
159 0           return;
160             }
161              
162              
163             1; # Magic true value required at end of module
164             __END__