IO-Socket-Forwarder
view release on metacpan or search on metacpan
lib/IO/Socket/Forwarder.pm view on Meta::CPAN
package IO::Socket::Forwarder;
our $VERSION = '0.02';
use warnings;
use strict;
use Carp;
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(forward_sockets);
use constant _default_io_buffer_size => 64 * 1024;
use constant _default_io_chunk_size => 16 * 1024;
sub _debug {
require Time::HiRes;
my $time = Time::HiRes::time();
my @date = localtime $time;
my $out = "@_";
$out =~ s/([^ -~])/sprintf("\\x%02x", ord $1)/ge;
warn sprintf("%02d:%02d:%02d.%03d: %s\n",
@date[2, 1, 0], 1000 * ($time - int $time), $out);
}
# lazy accessors to IO::Socket::SSL
# we use it but don't depend on it!
sub _ssl_error { $IO::Socket::SSL::SSL_ERROR }
sub _ssl_want_read { IO::Socket::SSL::SSL_WANT_READ() }
sub _ssl_want_write { IO::Socket::SSL::SSL_WANT_WRITE() }
sub _min { $_[0] < $_[1] ? $_[0] : $_[1] }
sub forward_sockets {
my ($s1, $s2, %opts) = @_;
my $debug = delete $opts{debug};
$debug = $IO::Socket::Forwarder::debug unless defined $debug;
my $io_buffer_size = delete $opts{io_buffer_size} || _default_io_buffer_size;
my $io_chunk_size = delete $opts{io_chunk_size} || _default_io_chunk_size;
my $fn1 = fileno $s1;
defined $fn1 or croak "socket 1 is not a valid file handle";
my $fn2 = fileno $s2;
defined $fn1 or croak "socket 2 is not a valid file handle";
my $ssl1 = $s1->isa('IO::Socket::SSL');
my $ssl2 = $s2->isa('IO::Socket::SSL');
$debug and _debug "s1 fn=$fn1, ssl=$ssl1";
$debug and _debug "s2 fn=$fn2, ssl=$ssl2";
my $b1to2 = delete $opts{buffer_1to2} // '';
my $b2to1 = delete $opts{buffer_2to1} // '';
if ($debug) {
_debug "b1to2: $b1to2";
_debug "b2to1: $b2to1";
}
my ($write_chunk_size1, $write_chunk_size2) = ($io_chunk_size, $io_chunk_size);
my ($s1_in_closed, $s2_in_closed,
$s1_out_closed, $s2_out_closed,
$ssl_wtr1, $ssl_wtw1, $ssl_wtr2, $ssl_wtw2,
%close);
unless ($^O =~ /Win32/) {
fcntl($s1, F_SETFL, fcntl($s1, F_GETFL, 0) | O_NONBLOCK)
or croak "unable to make socket 1 non-blocking";
fcntl($s2, F_SETFL, fcntl($s2, F_GETFL, 0) | O_NONBLOCK)
or croak "unable to make socket 2 non-blocking";
}
if (0 and $debug) {
_debug "delaying...";
sleep 5;
_debug "starting...";
}
while (1) {
my $wtr1 = (not $s1_in_closed and length $b1to2 < $io_buffer_size);
if ($ssl1 and $wtr1 and $s1->pending) {
sysread($s1, $b1to2, _min($s1->pending, $io_buffer_size), length $b1to2)
and redo;
}
my $wtr2 = (not $s2_in_closed and length $b2to1 < $io_buffer_size);
if ($ssl2 and $wtr2 and $s2->pending) {
sysread($s2, $b2to1, _min($s2->pending, $io_buffer_size), length $b2to1)
and redo;
}
my $wtw1 = (not $s1_out_closed and length $b2to1);
my $wtw2 = (not $s2_out_closed and length $b1to2);
$debug and _debug "wtr1: $wtr1, wtr2: $wtr2, wtw1: $wtw1, wtw2: $wtw2";
unless ($wtr1 or $wtr2 or $wtw1 or $wtw2) {
$debug and _debug "nothing else to do, exiting...";
last;
}
my $bitsr = '';
vec($bitsr, $fn1, 1) = 1 if (($wtr1 && !$ssl_wtw1) || $ssl_wtr1);
vec($bitsr, $fn2, 1) = 1 if (($wtr2 && !$ssl_wtw2) || $ssl_wtr2);
lib/IO/Socket/Forwarder.pm view on Meta::CPAN
}
if ($ssl2 and ($close{s2in} or $close{s2out})) {
$close{s2in} = $close{s2out} = 1;
}
if ($close{s1in} and !length $b1to2) {
$close{s2out} = 1;
}
if ($close{s2in} and !length $b2to1) {
$close{s1out} = 1;
}
}
if ($close{s1in}) {
$debug and _debug "shutdown s1 in";
shutdown($s1, 0);
$s1_in_closed = 1;
}
if ($close{s2in}) {
$debug and _debug "shutdown s2 in";
shutdown($s2, 0);
$s2_in_closed = 1;
}
if ($close{s1out}) {
$debug and _debug "shutdown s1 out";
shutdown($s1, 1);
$s1_out_closed = 1;
}
if ($close{s2out}) {
$debug and _debug "shutdown s1 out";
shutdown($s2, 1);
$s2_out_closed = 1;
}
}
%close = ();
}
}
shutdown($s1, 2);
shutdown($s2, 2);
}
1;
__END__
=head1 NAME
IO::Socket::Forwarder - bidirectionally forward data between two sockets
=head1 SYNOPSIS
use IO::Socket::Forwarder qw(foward_sockets);
forward_sockets($sock1, $sock2);
forward_sockets($sock3, $sock4, debug => 1);
=head1 DESCRIPTION
This module allows to forward data between two sockets bidirectionally.
IO::Socket::SSL sockets are also supported.
=head2 FUNCTIONS
=over 4
=item forward_sockets($sock1, $sock2, %opts)
Reads and writes data from both sockets simultaneously forwarding it.
On return both sockets will be closed.
This function automatically detects if any of the sockets is of type
L<IO::Socket::SSL> and doesn't require any extra configuration to
handle them.
The following options are accepted:
=over 4
=item debug => 1
turn on debugging. I
=item io_chunk_size => $size
maximun number of bytes allowed in IO operations
=item io_buffer_size => $size
size of the buffers used internally to transfer data between both sockets
=item buffer_1to2 => $data
=item buffer_2to1 => $data
these options allow to preload some data into the buffers used for
transferring data between the two sockets.
=back
=back
=head1 SEE ALSO
L<IO::Socket>, L<IO::Socket::SSL>.
The samples directory contains a couple of scripts showing how to use
this module.
=head1 BUGS AND SUPPORT
Please report any bugs or feature requests through the web interface
at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=IO-Socket-Forwarder>
or just send my an email with the details.
=head1 AUTHOR
Salvador FandiE<ntilde>o (sfandino@yahoo.com).
=head1 COPYRIGHT
Copyright 2009-2010 by Qindel Formacion y Servicios S.L.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.
( run in 0.832 second using v1.01-cache-2.11-cpan-39bf76dae61 )