File Coverage

blib/lib/IO/Socket/Forwarder.pm
Criterion Covered Total %
statement 18 180 10.0
branch 0 190 0.0
condition 0 121 0.0
subroutine 6 12 50.0
pod 1 1 100.0
total 25 504 4.9


line stmt bran cond sub pod time code
1             package IO::Socket::Forwarder;
2              
3             our $VERSION = '0.02';
4              
5 1     1   25325 use warnings;
  1         2  
  1         34  
6 1     1   6 use strict;
  1         2  
  1         38  
7 1     1   5 use Carp;
  1         5  
  1         83  
8              
9 1     1   6 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
  1         1  
  1         105  
10              
11             require Exporter;
12             our @ISA = qw(Exporter);
13             our @EXPORT_OK = qw(forward_sockets);
14              
15 1     1   11 use constant _default_io_buffer_size => 64 * 1024;
  1         2  
  1         70  
16 1     1   6 use constant _default_io_chunk_size => 16 * 1024;
  1         1  
  1         2346  
17              
18             sub _debug {
19 0     0     require Time::HiRes;
20 0           my $time = Time::HiRes::time();
21 0           my @date = localtime $time;
22 0           my $out = "@_";
23 0           $out =~ s/([^ -~])/sprintf("\\x%02x", ord $1)/ge;
  0            
24 0           warn sprintf("%02d:%02d:%02d.%03d: %s\n",
25             @date[2, 1, 0], 1000 * ($time - int $time), $out);
26             }
27              
28             # lazy accessors to IO::Socket::SSL
29             # we use it but don't depend on it!
30 0     0     sub _ssl_error { $IO::Socket::SSL::SSL_ERROR }
31 0     0     sub _ssl_want_read { IO::Socket::SSL::SSL_WANT_READ() }
32 0     0     sub _ssl_want_write { IO::Socket::SSL::SSL_WANT_WRITE() }
33              
34 0 0   0     sub _min { $_[0] < $_[1] ? $_[0] : $_[1] }
35              
36             sub forward_sockets {
37 0     0 1   my ($s1, $s2, %opts) = @_;
38              
39 0           my $debug = delete $opts{debug};
40 0 0         $debug = $IO::Socket::Forwarder::debug unless defined $debug;
41              
42 0   0       my $io_buffer_size = delete $opts{io_buffer_size} || _default_io_buffer_size;
43 0   0       my $io_chunk_size = delete $opts{io_chunk_size} || _default_io_chunk_size;
44              
45 0           my $fn1 = fileno $s1;
46 0 0         defined $fn1 or croak "socket 1 is not a valid file handle";
47 0           my $fn2 = fileno $s2;
48 0 0         defined $fn1 or croak "socket 2 is not a valid file handle";
49              
50 0           my $ssl1 = $s1->isa('IO::Socket::SSL');
51 0           my $ssl2 = $s2->isa('IO::Socket::SSL');
52              
53 0 0         $debug and _debug "s1 fn=$fn1, ssl=$ssl1";
54 0 0         $debug and _debug "s2 fn=$fn2, ssl=$ssl2";
55              
56 0   0       my $b1to2 = delete $opts{buffer_1to2} // '';
57 0   0       my $b2to1 = delete $opts{buffer_2to1} // '';
58              
59 0 0         if ($debug) {
60 0           _debug "b1to2: $b1to2";
61 0           _debug "b2to1: $b2to1";
62             }
63              
64 0           my ($write_chunk_size1, $write_chunk_size2) = ($io_chunk_size, $io_chunk_size);
65              
66 0           my ($s1_in_closed, $s2_in_closed,
67             $s1_out_closed, $s2_out_closed,
68             $ssl_wtr1, $ssl_wtw1, $ssl_wtr2, $ssl_wtw2,
69             %close);
70              
71 0 0         unless ($^O =~ /Win32/) {
72 0 0         fcntl($s1, F_SETFL, fcntl($s1, F_GETFL, 0) | O_NONBLOCK)
73             or croak "unable to make socket 1 non-blocking";
74 0 0         fcntl($s2, F_SETFL, fcntl($s2, F_GETFL, 0) | O_NONBLOCK)
75             or croak "unable to make socket 2 non-blocking";
76             }
77              
78 0           if (0 and $debug) {
79             _debug "delaying...";
80             sleep 5;
81             _debug "starting...";
82             }
83              
84 0           while (1) {
85 0   0       my $wtr1 = (not $s1_in_closed and length $b1to2 < $io_buffer_size);
86 0 0 0       if ($ssl1 and $wtr1 and $s1->pending) {
      0        
87 0 0         sysread($s1, $b1to2, _min($s1->pending, $io_buffer_size), length $b1to2)
88             and redo;
89             }
90              
91 0   0       my $wtr2 = (not $s2_in_closed and length $b2to1 < $io_buffer_size);
92 0 0 0       if ($ssl2 and $wtr2 and $s2->pending) {
      0        
93 0 0         sysread($s2, $b2to1, _min($s2->pending, $io_buffer_size), length $b2to1)
94             and redo;
95             }
96              
97 0   0       my $wtw1 = (not $s1_out_closed and length $b2to1);
98 0   0       my $wtw2 = (not $s2_out_closed and length $b1to2);
99              
100 0 0         $debug and _debug "wtr1: $wtr1, wtr2: $wtr2, wtw1: $wtw1, wtw2: $wtw2";
101              
102 0 0 0       unless ($wtr1 or $wtr2 or $wtw1 or $wtw2) {
      0        
      0        
103 0 0         $debug and _debug "nothing else to do, exiting...";
104 0           last;
105             }
106              
107              
108              
109 0           my $bitsr = '';
110 0 0 0       vec($bitsr, $fn1, 1) = 1 if (($wtr1 && !$ssl_wtw1) || $ssl_wtr1);
      0        
111 0 0 0       vec($bitsr, $fn2, 1) = 1 if (($wtr2 && !$ssl_wtw2) || $ssl_wtr2);
      0        
112 0           my $bitsw = '';
113 0 0 0       vec($bitsw, $fn1, 1) = 1 if (($wtw1 && !$ssl_wtr1) || $ssl_wtw1);
      0        
114 0 0 0       vec($bitsw, $fn2, 1) = 1 if (($wtw2 && !$ssl_wtr2) || $ssl_wtw2);
      0        
115              
116 0 0         $debug and _debug "calling select('$bitsr', '$bitsw')...";
117              
118 0           my $n = select($bitsr, $bitsw, undef, undef);
119              
120 0 0         $debug and _debug "select done, n: $n";
121              
122 0 0         if ($n > 0) {
123 0 0 0       if ($wtr1 and vec(($ssl_wtw1 ? $bitsw : $bitsr), $fn1, 1)) {
    0          
124 0 0         $debug and _debug "reading from s1...";
125 0           my $bytes = sysread($s1, $b1to2, $io_chunk_size, length $b1to2);
126 0 0 0       $debug and _debug "bytes: " . ($bytes // '');
127 0 0 0       if ($bytes) {
    0          
    0          
128 0           undef $ssl_wtw1;
129 0 0         $debug and _debug "s1 read: " . substr($b1to2, -$bytes);
130             }
131             elsif ($ssl1 and not defined $bytes) {
132 0 0         if (_ssl_error == _ssl_want_write) {
    0          
133 0           $ssl_wtw1 = 1;
134 0 0         $debug and _debug "s1 wants to write for SSL";
135             }
136             elsif (_ssl_error == _ssl_want_read) {
137 0 0         $debug and _debug "s1 wants to read more for SSL";
138             }
139             else {
140 0           _debug "unexpected SSL error " . _ssl_error;
141 0           $close{slin} = 1;
142             }
143             }
144             elsif ($ssl_wtw1) {
145 0           undef $ssl_wtw1;
146             }
147             else {
148 0 0         $debug and _debug "nothing read from s1, closing schedulled";
149 0           $close{s1in} = 1;
150             }
151             }
152 0 0 0       if ($wtr2 and vec(($ssl_wtw2 ? $bitsw : $bitsr), $fn2, 1)) {
    0          
153 0 0         $debug and _debug "reading from s2...";
154 0           my $bytes = sysread($s2, $b2to1, $io_chunk_size, length $b2to1);
155 0 0 0       $debug and _debug "bytes: " . ($bytes // '');
156 0 0 0       if ($bytes) {
    0          
    0          
157 0           undef $ssl_wtw2;
158 0 0         $debug and _debug "s2 read: " . substr($b2to1, -$bytes);
159             }
160             elsif ($ssl2 and not defined $bytes) {
161 0 0         if (_ssl_error == _ssl_want_write) {
    0          
162 0           $ssl_wtw2 = 1;
163 0 0         $debug and _debug "s2 wants to write for SSL";
164             }
165             elsif (_ssl_error == _ssl_want_read) {
166 0 0         $debug and _debug "s2 wants to read more for SSL";
167             }
168             else {
169 0           _debug "unexpected SSL error " . _ssl_error;
170             }
171             }
172             elsif ($ssl_wtw2) {
173 0           undef $ssl_wtw2;
174             }
175             else {
176 0 0         $debug and _debug "nothing read from s2, closing schedulled";
177 0           $close{s2in} = 1;
178             }
179             }
180 0 0 0       if ($wtw1 and vec(($ssl_wtr1 ? $bitsr : $bitsw), $fn1, 1)) {
    0          
181 0 0         $debug and _debug "writting to s1...";
182 0           my $bytes = syswrite($s1, $b2to1, $io_chunk_size);
183 0 0 0       $debug and _debug "bytes: " . ($bytes // '');
184 0 0 0       if ($bytes) {
    0          
185 0 0         $debug and _debug "s1 wrote: " . substr($b2to1, 0, $bytes);
186 0           substr($b2to1, 0, $bytes, "");
187 0 0 0       if ($s2_in_closed and !length $b2to1) {
188 0 0         $debug and _debug "buffer exhausted and s2-in is closed, shutting down s1-out";
189 0 0         shutdown($s1, 1) unless $ssl1;
190 0           $s1_out_closed = 1;
191             }
192 0           $write_chunk_size1 = $io_chunk_size;
193 0           undef $ssl_wtr1;
194             }
195             elsif ($ssl1 and not defined $bytes) {
196 0 0         if (_ssl_error == _ssl_want_read) {
    0          
197 0           $ssl_wtr1 = 1;
198 0 0         $debug and _debug "s1 wants to read for SSL";
199             }
200             elsif (_ssl_error == _ssl_want_write) {
201 0 0         $write_chunk_size1 = length $b2to1 if length $b2to1 < $write_chunk_size1;
202 0 0         $debug and _debug "s1 wants to write more for SSL, wcs1: $write_chunk_size1";
203             }
204             else {
205 0           _debug "unexpected SSL error " . _ssl_error;
206 0           $close{s1out} = 1;
207             }
208             }
209             else {
210 0 0         $debug and _debug "nothing written to s1, closing schedulled";
211 0           $close{s1out} = 1;
212             }
213             }
214 0 0 0       if ($wtw2 and vec(($ssl_wtr2 ? $bitsr : $bitsw), $fn2, 1)) {
    0          
215 0 0         $debug and _debug "writting to s2...";
216 0           my $bytes = syswrite($s2, $b1to2, $write_chunk_size2);
217 0 0 0       $debug and _debug "bytes: " . ($bytes // '');
218 0 0 0       if ($bytes) {
    0          
219 0 0         $debug and _debug "s2 wrote: " . substr($b1to2, 0, $bytes);
220 0           substr($b1to2, 0, $bytes, "");
221 0 0 0       if ($s1_in_closed and length $b1to2) {
222 0 0         $debug and _debug "buffer exhausted and s2-in is closed, shutting down s1-out";
223 0 0         shutdown($s2, 1) unless $ssl2;
224 0           $s2_out_closed = 1;
225             }
226 0           $write_chunk_size2 = $io_chunk_size;
227 0           undef $ssl_wtr2;
228             }
229             elsif ($ssl2 and not defined $bytes) {
230 0 0         if (_ssl_error == _ssl_want_read) {
    0          
231 0           $ssl_wtr2 = 1;
232 0 0         $debug and _debug "s2 wants to read for SSL";
233             }
234             elsif (_ssl_error == _ssl_want_write) {
235 0 0         $write_chunk_size2 = length $b1to2 if length $b1to2 < $write_chunk_size2;
236 0 0         $debug and _debug "s2 wants to write more for SSL, wcs2: $write_chunk_size2";
237             }
238             else {
239 0           _debug "unexpected SSL error " . _ssl_error;
240 0           $close{s2out} = 1;
241             }
242             }
243             else {
244 0 0         $debug and _debug "nothing written to s1, closing schedulled";
245 0           $close{s2out} = 1;
246             }
247             }
248 0 0         if (%close) {
249 0           for (1, 2, 3) { # propagate close flag to dependants
250 0 0 0       if ($ssl1 and ($close{s1in} or $close{s1out})) {
      0        
251 0           $close{s1in} = $close{s1out} = 1;
252             }
253 0 0 0       if ($ssl2 and ($close{s2in} or $close{s2out})) {
      0        
254 0           $close{s2in} = $close{s2out} = 1;
255             }
256 0 0 0       if ($close{s1in} and !length $b1to2) {
257 0           $close{s2out} = 1;
258             }
259 0 0 0       if ($close{s2in} and !length $b2to1) {
260 0           $close{s1out} = 1;
261             }
262             }
263 0 0         if ($close{s1in}) {
264 0 0         $debug and _debug "shutdown s1 in";
265 0           shutdown($s1, 0);
266 0           $s1_in_closed = 1;
267             }
268 0 0         if ($close{s2in}) {
269 0 0         $debug and _debug "shutdown s2 in";
270 0           shutdown($s2, 0);
271 0           $s2_in_closed = 1;
272             }
273 0 0         if ($close{s1out}) {
274 0 0         $debug and _debug "shutdown s1 out";
275 0           shutdown($s1, 1);
276 0           $s1_out_closed = 1;
277             }
278 0 0         if ($close{s2out}) {
279 0 0         $debug and _debug "shutdown s1 out";
280 0           shutdown($s2, 1);
281 0           $s2_out_closed = 1;
282             }
283             }
284 0           %close = ();
285             }
286             }
287 0           shutdown($s1, 2);
288 0           shutdown($s2, 2);
289             }
290              
291             1;
292              
293             __END__