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:
RT: CPAN's request tracker (report bugs here)
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
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.