NAME

TCPIP::Tap - connects a client and a server via a 'tap', giving visibility of and control over messages passed.

VERSION

Version 0.01_01

SYNOPSIS

TCPIP::Tap is designed to be inserted between a client and a server. It proxies all traffic through verbatum, and also copies that same data to a log file and/or a callback function, allowing a data session to be monitored, recorded and/or altered on the fly.

Tap acts as a 'man in the middle', sitting between the client and server. To the client, Tap looks like the server. To the server, Tap looks like the client.

There is an (as yet unreleased) sister module TCPIP::Replay that allows a 'tapped' session to be replayed.

When started, Tap opens a socket and listens for connections. When that socket is connected to, Tap opens another connection to the server. Messages from either client or server are passed to the other, and a copy of each message is, potentially, logged. Alternately, callback methods may be used to add business logic, including potentially altering the messages being passed.

Tap cannot be used to covertly tap unsuspecting client/server sessions - it requires that you control either the client or the server. If you control the client, you can tell it to connect via your tap. If you control the server, you can move it to a different port, and put a tap in its place.

Tap can also be used to allow two processes on machines that cannot 'see' each other to communicate via an intermediary machine that is visible to both.

Usage

Assume the following script is running on the local machine:

use TCPIP::Tap;
my $tap = TCPIP::Tap->new("cpan.org", 80, 10080);
$tap->log_file("tap.log");
$tap->go();

A browser connecting to http://localhost:10080 will be connected to cpan.org - and all communications back and forwards will be logged to tap.log.

Modifying messages on the fly.

However you deploy Tap, it will be virtually identical to having the client and server talk directly. The difference will be that either the client and/or server will be at an address other than the one its counterpart believes it to be at. Most programs ignore this, but sometimes it matters.

For example, HTTP servers often return URLs containing the address of the server. If the browser is told to navigate to the returned URL, it will from that point onwards connect directly to the server instead of communicating via Tap.

Further, HTTP browsers pass a number of parameters, one of which is the Host to which the browser believes it is connecting. Often, this does not matter. But sometimes, a single HTTP server will be serving content for more than one website. Such a server generally relies on the Host parameter to know what it is to return.

These two problems can be worked around by modifying the messages being passed.

For example, assume the following script is running on the local machine:

use TCPIP::Tap;
sub send_($) {$_[0] =~ s/Host: .*:\d+/Host: cpan.org/;}
sub receive($) {$_[0] =~ s/cpan.org:\d+/localhost:10080/g;}
my $tap = TCPIP::Tap->new("cpan.org", 80, 10080);
$tap->send_callback(\&send);
$tap->receive_callback(\&receive);
$tap->log_file("http_tap.log");
$tap->go();

The send callback tells the server that it is to serve cpan.org pages, instead of some other set of pages, while the receive callback tells the browser to access cpan.org URLs via the tap process, not directly. Server will now respond properly, even though the browser has sent the wrong hostname, and the browser will now behave as desired and direct future requests through the tap.

A more difficult problem is security aware processes, such as those that use HTTPS based protocols. They are actively hostname aware. Precisely to defend against a man-in-the-middle attack, they check their counterpart's reported hostname (but not normally the port) against the actual hostname. Unless client, server and tap are all on the same host, either the client or the server will notice that the remote hostname is not what it should be, and will abort the connection. There is no good workaround for this, unless you can run an instance of tap on the server, and another on the client - but even if you do, you still have to deal with the communication being encrypted.

SUBROUTINES/METHODS

new( remote_ip_address, local_port_num, remote_port_num )

Creates a new Tap

Parameters

  • remote_ip_address - the remote hostname/IP address of the server

  • remote_port_num - the remote port number of the server

  • local_port_num - the port number to listen on

Usage

To keep a record of all messages sent:

use TCPIP::Tap;
my $tap = TCPIP::Tap->new("www.cpan.org", 80, 10080);
$tap->log_file("tap.log");
$tap->go();

go()

Listen on local_port, accept incoming connections, and forwards messages back and forth.

Parameters

  • --none--

Usage

When a new connection on local_port is received a child process is spawned (unless this is disabled using parallel() and messages from the client are passed to the server and vice-versa. (If new_server() was used instead of new(), messages from client are instead passed to the server callback function.)

If any callback functions have been set, they will be called before each message is passed. If logging is on, messages will be logged.

There is no way to send an unprompted message.

go() does not return. You may want to fork before calling it. There is no way to stop it from outside except using a signal to interrupt it.

verbose( [level] )

Turns on/off reporting to stdout. Default is on.

Parameters

  • level - how verbose to be. 0=nothing, 1=normal, 2=debug

  • Returns: - the current or new setting

Usage

send_callback( send_callback )

set a callback function to monitor/modify each message sent to server

Parameters

  • send_callback - the method to be called for message sent to server

  • Returns: - the current or new setting

Usage

If send_callback is set, it will be called with a copy of each message to the server before it is sent. Whatever the callback returns will be sent.

If the callback is readonly, it should either return a copy of the original message, or undef. Be careful not to accidentally return something else - remember that perl methods implicitly returns the value of the last command executed.

For example, to write messages to a log:

sub peek($) {my $_ = shift; print LOG; return $_;}
my $tap = TCPIP::Tap->new("www.cpan.org", 80, 10080);
$tap->send_callback(\&peek);
$tap->receive_callback(\&peek);
$tap->go();

For example, to modify messages:

use TCPIP::Tap;
sub send_($) {$_[0] =~ s/Host: .*:\d+/Host: cpan.org/;}
sub receive($) {$_[0] =~ s/www.cpan.org(:\d+)?/localhost:10080/g;}
my $tap = TCPIP::Tap->new("www.cpan.org", 80, 10080);
$tap->send_callback(\&send);
$tap->receive_callback(\&receive);
$tap->go();

receive_callback( [receive_callback] )

set a callback function to monitor/modify each message recieved from server

Parameters

  • receive_callback - the method to be called for each inward message

  • Returns: - the current or new setting

Usage

If receive_callback is set, it will be called with a copy of each message received from the server before it is sent to the client. Whatever the callback returns will be sent.

If the callback is readonly, it should either return a copy of the original message, or undef. Be careful not to accidentally return something else - remember that perl methods implicitly returns the value of the last command executed.

parallel( [level] )

Turns on/off running in parallel. Default is on.

Parameters

  • level - 0=serial, 1=parallel

  • Returns: - the current or new setting

Usage

If running in parallel, Tap support multiple concurrent connections by starting a new process for each new connection using fork.

If running in serial, any additional clients must wait for the current client to finish.

Turning off parallel can be very helpful for debugging, as long as it doesn't matter if your client sessions have to wait for each other.

log_file( [log_file_name] ] )

log_file() sets, or clears, a log file.

Parameters

  • log_file_name - the name of the log file to be appended to. Passing "" disables logging. Passing nothing, or undef, returns the current log filename without change.

  • Returns: log file name

Usage

The log file contains a record of connects and disconnects and messages as sent back and forwards. Log entries are timestamped. If the log file already exists, it is appended to.

defrag_delay( [delay] )

Use a small delay to defragment messages

Parameters

  • Delay - seconds to wait - fractions of a second are OK

  • Returns: the current setting.

Usage

Under TCPIP, there is always a risk that large messages will be fragmented in transit, and that messages sent close together may be concatenated.

Client/Server programs have to know how to turn a stream of bytes into the messages they care about, either by repeatedly reading until they see an end-of-message (defragmenting), or by splitting the bytes read into multiple messages (deconcatenating).

For our purposes, fragmentation and concatenation can make our logs harder to read.

Without knowning the protocol, it's not possible to tell for sure if a message has been fragmented or concatenated.

A small delay can be used as a way of defragmenting messages, although it increases the risk that separate messages may be concatenated.

Eg: $tap->defrag_delay( 0.1 );

SUPPORTING SUBROUTINES/METHODS

The remaining functions are supplimentary. new_server and new_client provide a simple client and a simple server that may be useful in some limited circumstances, such as testing your own programs. The other methods are only likely to be useful if you choose to bypass go() in order to, for example, have more control over messages being received and sent.

new_server( local_port_num, callback_function )

new_server returns a very simple server, adequate for simple tasks

Parameters

  • local_port_num - the Port number to listen on

  • callback_function - a function to be called when a message arrives - must return a response which will be returned to the client

Usage

 sub do_something($){
   my $in = shift;
   my $out = ...
   return $out;
 }

 my $server = TCPIP::Tap::new_server(8080,\&do_something) || die;
 $server->go();

The server returned by new_server has a method, go(), which tells it to start receiving messages (arbitrary strings). Each string is passed to the callback_function, which is expected to return a single string, being the response to be returned to caller. If the callback returns undef, the original message will be echoed back to the client.

go() does not return. You may want to fork before calling it.

new_client( remote_host, local_port_num )

new client returns a very simple client, adequate for simple tasks

The server returned has a single method, send_and_receive(), which sends a message and receives a response.

Alternately, sendToServer() may be used to send a message, and readFromServer() may be used to receive a message.

Parameters

  • remote_ip_address - the hostname/IP address of the server

  • remote_port_num - the Port number of the server

Usage

my $client = TCPIP::Tap::new_client("localhost", 8080) || die("failed to start test client: $!");
$client->connectToServer();
my $resp = $client->send_and_receive("hello");
...

log( string )

log is a convenience function that prefixes output with a timestamp and PID information then writes to log_file.

Parameters

  • string(s) - one or more strings to be logged

Usage

log is a convenience function that prefixes output with a timestamp and PID information then writes to log_file.

log() does nothing unless log_file is set, which by default, it is not.

echo( string(s) )

echo prints to stdout, or not

Parameters

  • string(s) - one or more strings to be echoed (printed)

Usage

echo() is a convenience function that prefixes output with a timestamp and PID information and prints it to standard out if verbose is set and, if log_file is set, logs it.

sendToServer( string(s) )

sendToServer() sends a message to the server

Parameters

  • string(s) - one or more strings to be sent

Usage

If a callback is set, it will be called before the message is sent.

sendToClient( string(s) )

sendToClient() sends a message to the client

Parameters

  • string(s) - one or more strings to be sent

  • Return: true if successful

Usage

If a callback is set, it will be called before the message is sent.

readFromServer()

readFromServer() reads a message from the server

Parameters

  • --none--

  • Returns: the message read, or undef if the server disconnected.

Usage

Blocks until a message is received.

send_and_receive()

send_and_receive() sends a message to the server and receives a response

Parameters

  • the message(s) to be sent

  • Returns: message read, or undef if the server disconnected.

Usage

Blocks until a message is received.

connectToServer()

Connects to the server

Parameters

  • --none--

Usage

This method is called by go(). It only needs to be called directly if go() is being bypassed for some reason.

disconnectFromServer()

Disconnects from the server

Parameters

  • --none--

Usage

Disconnection is normally triggered by the other party disconnecting, not by us. disconnectFromServer() is potentially useful with new_client, but is not otherwise supported.

sendAndReceiveLoop()

Passes messages between client and server.

Parameters

  • --none--

Usage

This method is called by go(). It only needs to be called directly if go() is being bypassed for some reason.

It does not return until either client or server disconnects.

hhmmss()

The default timestamp function - returns locatime in hh:mm:ss format

Parameters

  • --none--

Usage

This function is, by default, called when a message is written to the log file.

It may be overridden by calling mydate().

mydate()

Override the standard hh:mm:ss datestamp

Parameters

  • datestamp_callback - a function that returns a datestamp

Usage

For example:

sub ymdhms {
  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
  return sprintf "%02d/%02d/%02d %02d:%02d:%02d", 
    $year+1900,$mon+1,$mday,$hour,$min,$sec;
}
mydate(\&ymdhms);

listen()

Listen on local_port and prepare to accept incoming connections

Parameters

  • --none--

Usage

This method is called by go(). It only needs to be called directly if go() is being bypassed for some reason.

AUTHOR

Ben AVELING, <bena.aveling at optusnet.com.au>

BUGS

Please report any bugs or feature requests to bug-tcpip-tap at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=TCPIP-Tap. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

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

perldoc TCPIP::Tap

You can also look for information at:

ACKNOWLEDGEMENTS

I'd like to acknowledge W. Richard Steven's and his fantastic introduction to TCPIP: "TCP/IP Illustrated, Volume 1: The Protocols", Addison-Wesley, 1994. (http://www.kohala.com/start/tcpipiv1.html).

It got me started. Recommend. RIP.

The Blue Camel Book is pretty useful too.

Langworth & chromatic's "Perl Testing, A Developer's Notebook" also has its place.

ALTERNATIVES

If what you want is a pure proxy, especially if you want an ssh proxy or support for firewalls, you might want to evaluate Philippe "BooK" Bruhat's Net::Proxy.

If you want a full "portable multitasking and networking framework for any event loop", you may be looking for POE.

LICENSE AND COPYRIGHT

Copyleft 2013 Ben AVELING.

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

http://www.perlfoundation.org/artistic_license_2_0

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, have, hold and cherish, use, offer to use, sell, offer to sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SO THERE.