Net::Proxy - Framework for proxying network connections in many ways


Net-Proxy documentation Contained in the Net-Proxy distribution.

Index


Code Index:

NAME

Top

Net::Proxy - Framework for proxying network connections in many ways

SYNOPSIS

Top

    use Net::Proxy;

    # proxy connections from localhost:6789 to remotehost:9876
    # using standard TCP connections
    my $proxy = Net::Proxy->new(
        {   in  => { type => 'tcp', port => '6789' },
            out => { type => 'tcp', host => 'remotehost', port => '9876' },
        }
    );

    # register the proxy object
    $proxy->register();

    # and you can setup multiple proxies

    # and now proxy connections indefinitely
    Net::Proxy->mainloop();

DESCRIPTION

Top

A Net::Proxy object represents a proxy that accepts connections and then relays the data transfered between the source and the destination.

The goal of this module is to abstract the different methods used to connect from the proxy to the destination.

A proxy is a program that transfer data across a network boundary between a client and a server. Net::Proxy introduces the concept of "connectors" (implemented as Net::Proxy::Connector subclasses), which abstract the server part (connected to the client) and the client part (connected to the server) of the proxy.

This architecture makes it easy to implement specific techniques to cross a given network boundary, possibly by using a proxy on one side of the network fence, and a reverse-proxy on the other side of the fence.

See AVAILABLE CONNECTORS for details about the existing connectors.

METHODS

Top

If you only intend to use Net::Proxy and not write new connectors, you only need to know about new(), register() and mainloop().

Class methods

new( { in => { ... }, { out => { ... } } )

Return a new Net::Proxy object, with two connectors configured as described in the hashref.

The connector parameters are described in the table below, as well as in each connector documentation.

mainloop( $max_connections )

This method initialises all the registered Net::Proxy objects and then loops on all the sockets ready for reading, passing the data through the various Net::Proxy::Connector objets to handle the specifics of each connection.

If $max_connections is given, the proxy will stop after having fully processed that many connections. Otherwise, this method does not return.

add_listeners( @sockets )

Add the given sockets to the list of listening sockets.

watch_reader_sockets( @sockets )

Add the given sockets to the readers watch list.

watch_writer_sockets( @sockets )

Add the given sockets to the writers watch list.

remove_writer_sockets( @sockets )

Remove the given sockets from the writers watch list.

close_sockets( @sockets )

Close the given sockets and cleanup the related internal structures.

set_verbosity( $level )

Set the logging level. 0 means not messages except warnings and errors.

error( $message )

Log $message to STDERR, always.

notice( $message )

Log $message to STDERR if verbosity level is equal to 1 or more.

info( $message )

Log $message to STDERR if verbosity level is equal to 2 or more.

debug( $message )

Log $message to STDERR if verbosity level is equal to 3 or more.

(Note: throughout the Net::Proxy source code, calls to debug() are commented with ##.)

Some of the class methods are related to the socket objects that handle the actual connections.

get_peer( $socket )
set_peer( $socket, $peer )

Get or set the socket peer.

get_connector( $socket )
set_connector( $socket, $connector )

Get or set the socket connector (a Net::Proxy::Connector object).

get_state( $socket )
set_state( $socket, $state )

Get or set the socket state. Some Net::Proxy::Connector subclasses may wish to use this to store some internal information about the socket or the connection.

get_nick( $socket )
set_nick( $socket, $nickname )

Get or set the socket nickname. Typically used by Net::Proxy::Connector to give informative names to socket (used in the log messages).

get_buffer( $socket )
set_buffer( $socket, $data )

Get or set the content of the writing buffer for the socket. Used by Net::Proxy::Connector in raw_read_from() and ranw_write_to().

get_callback( $socket )
set_callback( $socket, $coderef )

Get or set the callback currently associated with the socket.

add_to_buffer( $socket, $data )

Add data to the writing buffer of the socket.

Instance methods

register()

Register a Net::Proxy object so that it will be included in the mainloop() processing.

unregister()

Unregister the Net::Proxy object.

in_connector()

Return the Net::Proxy::Connector objet that handles the incoming connection and handles the data coming from the "client" side.

out_connector()

Return the Net::Proxy::Connector objet that creates the outgoing connection and handles the data coming from the "server" side.

Statistical methods

The following methods manage some statistical information about the individual proxies:

stat_inc_opened()
stat_inc_closed()

Increment the "opened" or "closed" connection counter for this proxy.

stat_opened()
stat_closed()

Return the count of "opened" or "closed" connections for this proxy.

stat_total_opened()
stat_total_closed()

Return the total count of "opened" or "closed" connection across all proxy objects.

CONNECTORS

Top

All connection types are provided with the help of specialised classes. The logic for protocol xxx is provided by the Net::Proxy::Connector::xxx class.

Connector hooks

There is a single parameter that all connectors accept: hook. Given a code reference, the code reference will be called when data is received on the corresponding socket.

The code reference should have the following signature:

    sub callback {
        my ($dataref, $sock, $connector) = @_;
        ...
    }

$dataref is a reference to the chunk of data received, $sock is a reference to the socket that received the data, and $connector is the Net::Proxy::Connector object that created the socket. This allows someone to eventually store data in a stash stored in the connector, so as to share data between sockets.

Available connectors

* tcp (Net::Proxy::Connector::tcp)

This is the simplest possible proxy connector. On the "in" side, it sits waiting for incoming connections, and on the "out" side, it connects to the configured host/port.

* connect (Net::Proxy::Connector::connect)

This proxy connector can connect to a TCP server though a web proxy that accepts HTTP CONNECT requests.

* dual (Net::Proxy::Connector::dual)

This proxy connector is a Y-shaped connector: depending on the client behaviour right after the connection is established, it connects it to one of two services, handled by two distinct connectors.

* dummy (Net::Proxy::Connector::dummy)

This proxy connector does nothing. You can use it as a template for writing new Net::Proxy::Connector classes.

Summary

This table summarises all the available Net::Proxy::Connector classes and the parameters their constructors recognise.

N/A means that the given Net::Proxy::Connector cannot be used in that position (either in or out).

     Connector  | in parameters   | out parameters
    ------------+-----------------+-----------------
     tcp        | host            | host
                | port            | port
    ------------+-----------------+-----------------
     connect    | N/A             | host
                |                 | port
                |                 | proxy_host
                |                 | proxy_port
                |                 | proxy_user
                |                 | proxy_pass
                |                 | proxy_agent
    ------------+-----------------+-----------------
     dual       | host            | N/A
                | port            |
                | timeout         |
                | server_first    |
                | client_first    |
    ------------+-----------------+-----------------
     dummy      | N/A             | N/A
    ------------+-----------------+-----------------
     ssl        | host            | host
                | port            | port
                | start_cleartext | start_cleartext
    ------------+-----------------+-----------------
     connect_ssl| N/A             | host
                |                 | port
                |                 | proxy_host
                |                 | proxy_port
                |                 | proxy_user
                |                 | proxy_pass
                |                 | proxy_agent

Net::Proxy::Connector::dummy is used as the out parameter for a Net::Proxy::Connector::dual, since the later is linked to two different connector objects.

AUTHOR

Top

Philippe 'BooK' Bruhat, <book@cpan.org>.

BUGS

Top

Please report any bugs or feature requests to bug-net-proxy@rt.cpan.org, or through the web interface at http://rt.cpan.org/. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

TODO

Top

Here's my own wishlist:

SUPPORT

Top

You can find documentation for this module with the perldoc command.

    perldoc Net::Proxy

You can also look for information at:

* The Net::Proxy mailing-list

http://listes.mongueurs.net/mailman/listinfo/net-proxy/

This list receive an email for each commit

* The public source repository

svn://svn.mongueurs.net/Net-Proxy/trunk/

Also available through a web interface at http://svnweb.mongueurs.net/Net-Proxy

* AnnoCPAN: Annotated CPAN documentation

http://annocpan.org/dist/Net-Proxy

* CPAN Ratings

http://cpanratings.perl.org/d/Net-Proxy

* RT: CPAN's request tracker

http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-Proxy

* Search CPAN

http://search.cpan.org/dist/Net-Proxy

COPYRIGHT

Top

LICENSE

Top

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


Net-Proxy documentation Contained in the Net-Proxy distribution.

package Net::Proxy;
use strict;
use warnings;
use Carp;
use Scalar::Util qw( refaddr reftype );
use IO::Select;
use POSIX 'strftime';

our $VERSION = '0.12';

# interal socket information table
my %SOCK_INFO;
my %LISTENER;
my %CLOSING;
my $READERS;
my $WRITERS;
my %PROXY;
my %STATS;

# Net::Proxy attributes
my %CONNECTOR = (
    in  => {},
    out => {},
);
my $VERBOSITY = 0; # be silent by default
my $BUFFSIZE  = 16384;

#
# some logging-related methods
#
sub set_verbosity { $VERBOSITY = $_[1]; }
{
    my $i;
    for my $meth (qw( error notice info debug )) {
        no strict 'refs';
        my $level = $i++;
        *$meth = sub {
            return if $VERBOSITY < $level;
            print STDERR strftime "%Y-%m-%d %H:%M:%S $_[1]\n", localtime;
        };
    }
}

#
# constructor
#
sub new {
    my ( $class, $args ) = @_;
    my $self = bless \do { my $anon }, $class;

    croak "Argument to new() must be a HASHREF" if ref $args ne 'HASH';

    for my $conn (qw( in out )) {

        # check arguments
        croak "'$conn' connector required" if !exists $args->{$conn};

        croak "'$conn' connector must be a HASHREF"
            if ref $args->{$conn} ne 'HASH';

        croak "'type' key required for '$conn' connector"
            if !exists $args->{$conn}{type};

        croak "'hook' key is not a CODE reference for '$conn' connector"
            if $args->{$conn}{hook}
            && reftype( $args->{$conn}{hook} ) ne 'CODE';
 
        # load the class
        my $class = 'Net::Proxy::Connector::' . $args->{$conn}{type};
        eval "require $class";
        croak "Couldn't load $class for '$conn' connector: $@" if $@;

        # create and store the Connector object
        $args->{$conn}{_proxy_} = $self;
        $CONNECTOR{$conn}{ refaddr $self} = $class->new( $args->{$conn} );
        $CONNECTOR{$conn}{ refaddr $self}->set_proxy($self);
    }

    return $self;
}

sub register { $PROXY{ refaddr $_[0] } = $_[0]; }
sub unregister { delete $PROXY{ refaddr $_[0] }; }

#
# The Net::Proxy attributes
#
sub in_connector  { return $CONNECTOR{in}{ refaddr $_[0] }; }
sub out_connector { return $CONNECTOR{out}{ refaddr $_[0] }; }

#
# create the socket setter/getter methods
# these are actually Net::Proxy clas methods
#
BEGIN {
    my $n = 0;
    my $buffer_id;
    for my $attr (qw( peer connector state nick buffer callback )) {
        no strict 'refs';
        my $i = $n;
        *{"get_$attr"} = sub { $SOCK_INFO{ refaddr $_[1] }[$i]; };
        *{"set_$attr"} = sub { $SOCK_INFO{ refaddr $_[1] }[$i] = $_[2]; };
        $buffer_id = $n if $attr eq 'buffer';
        $n++;
    }
    # special shortcut
    sub add_to_buffer { $SOCK_INFO{ refaddr $_[1] }[$buffer_id] .= $_[2]; }
}

#
# create statistical methods
#
for my $info (qw( opened closed )) {
    no strict 'refs';
    *{"stat_inc_$info"} = sub {
        $STATS{ refaddr $_[0]}{$info}++;
        $STATS{total}{$info}++;
    };
    *{"stat_$info"}       = sub { $STATS{ refaddr $_[0]}{$info} || 0; };
    *{"stat_total_$info"} = sub { $STATS{total}{$info} || 0; };
}

#
# socket-related methods
#
sub add_listeners {
    my ( $class, @socks ) = @_;
    for my $sock (@socks) {
        Net::Proxy->notice( 'Add ' . Net::Proxy->get_nick($sock) );
        $LISTENER{ refaddr $sock} = $sock;
    }
    return;
}

sub close_sockets {
    my ( $class, @socks ) = @_;

  SOCKET:
    for my $sock (@socks) {
        if( my $data = Net::Proxy->get_buffer( $sock ) ) {
            ## Net::Proxy->debug( length($data) . ' bytes left to write on ' . Net::Proxy->get_nick( $sock ) );
            $CLOSING{ refaddr $sock} = $sock;
            next SOCKET;
        }

        Net::Proxy->notice( 'Closing ' . Net::Proxy->get_nick( $sock ) );

        # clean up connector
        if ( my $conn = Net::Proxy->get_connector($sock) ) {
            $conn->close($sock) if $conn->can('close');

            # count connections to the proxy "in connectors" only
            my $proxy = $conn->get_proxy();
            if ( refaddr $conn == refaddr $proxy->in_connector()
                && !_is_listener($sock) )
            {
                $proxy->stat_inc_closed();
            }
        }

        # clean up internal structures
        delete $SOCK_INFO{ refaddr $sock};
        delete $LISTENER{ refaddr $sock};
        delete $CLOSING{ refaddr $sock};

        # clean up sockets
        $READERS->remove($sock);
        $WRITERS->remove($sock);
        $sock->close();
    }

    return;
}

#
# select() stuff
#
sub watch_reader_sockets {
    my ( $class, @socks ) = @_;
    $READERS->add(@socks);
    return;
}

sub watch_writer_sockets {
    my ( $class, @socks ) = @_;
    $WRITERS->add(@socks);
    return;
}

sub remove_writer_sockets {
    my ( $class, @socks ) = @_;
    $WRITERS->remove(@socks);
    return;
}

#
# destructor
#
sub DESTROY {
    my ($self) = @_;
    delete $CONNECTOR{in}{ refaddr $self};
    delete $CONNECTOR{out}{ refaddr $self};
}

#
# the mainloop itself
#
sub mainloop {
    my ( $class, $max_connections ) = @_;
    $max_connections ||= 0;

    # initialise the loop
    $READERS = IO::Select->new();
    $WRITERS = IO::Select->new();

    # initialise all proxies
    for my $proxy ( values %PROXY ) {
        my $in    = $proxy->in_connector();
        my @socks = $in->listen();
        Net::Proxy->add_listeners(@socks);
        Net::Proxy->watch_reader_sockets(@socks);
        Net::Proxy->set_connector( $_, $in ) for @socks;
    }

    my $continue = 1;
    for my $signal (qw( INT HUP )) {
        $SIG{$signal} = sub {
            Net::Proxy->notice("Caught $signal signal");
            $continue = 0;
        };
    }

    # loop indefinitely
    while ( $continue and my @ready = IO::Select->select( $READERS, $WRITERS ) ) {

        ## Net::Proxy->debug( 0+@{$ready[0]} . " sockets ready for reading" );
        ## Net::Proxy->debug( join "\n  ", "Readers:", map { Net::Proxy->get_nick($_) } $READERS->handles() );
        ## Net::Proxy->debug( 0+@{$ready[1]} . " sockets ready for writing" );
        ## Net::Proxy->debug( join "\n  ", "Writers:", map { Net::Proxy->get_nick($_) } $WRITERS->handles() );

        # first read
    READER:
        for my $sock (@{$ready[0]}) {
            if ( _is_listener($sock) ) {

                # accept the new connection and connect to the destination
                Net::Proxy->get_connector($sock)->new_connection_on($sock);
            }
            else {

                # have we read too much?
                my $peer = Net::Proxy->get_peer($sock);
                next READER
                    if !$peer
                    || length( Net::Proxy->get_buffer($peer) ) >= $BUFFSIZE;

                # read the data
                if ( my $conn = Net::Proxy->get_connector($sock) ) {
                    my $data = $conn->read_from($sock);
                    next READER if !defined $data;

                    if ($peer) {

                        # run the hook on incoming data
                        my $callback = Net::Proxy->get_callback( $sock );
                        $callback->( \$data, $sock, $conn )
                            if $callback && defined $data;

                        Net::Proxy->add_to_buffer( $peer, $data );
                        Net::Proxy->watch_writer_sockets($peer);

                        ## Net::Proxy->debug( "Will write " . length( Net::Proxy->get_buffer($peer)). " bytes to " .  Net::Proxy->get_nick( $peer ));
                    }
                }
            }
        }

        # then write
        for my $sock (@{$ready[1]}) {
            my $conn = Net::Proxy->get_connector($sock);
            $conn->write_to($sock);
        }

    }
    continue {
        if( %CLOSING ) {
            Net::Proxy->close_sockets( values %CLOSING );
        }
        if( $max_connections ) {

            # stop after that many connections
            last if Net::Proxy->stat_total_closed() == $max_connections;

            # prevent new connections
            if ( %LISTENER
                && Net::Proxy->stat_total_opened() == $max_connections )
            {
                Net::Proxy->close_sockets( values %LISTENER );
            }
        }
    }

    # close all remaining sockets
    Net::Proxy->close_sockets( $READERS->handles(), $WRITERS->handles() );
}

#
# helper private FUNCTIONS
#
sub _is_listener { return exists $LISTENER{ refaddr $_[0] }; }

1;

__END__