Sometimes you'd just like to listen in on the conversation between a browser and a server, like a gossip-hungry neighbor on a party line. Perhaps you're dealing with browsers whose caching behavior is questionable, and you don't want to guess what's really being fetched. You could use a connection-sniffing tool like tcpdump to monitor the traffic between the client and the server, but this generally requires superuser access to put the network interface into “promiscuous” mode. So try instead our proxylog tool:
#!/usr/bin/perl -w use strict; use Getopt::Std; use IO::Socket; use IO::Select; sub usage { <<"EndUsage"; $0 lets you snoop on the conversation between a client and server. $0 -i clientport -o serverhost:port [-1] [-v] -i incoming port to listen to -o outgoing port to make connection to -1 only process one client request, then exit -v verbose Caveats: o Does not handle multiple simultaneous connections. o Reads are asynchronous, writes are synchronous. o Messages larger than 64K will be segmented. EndUsage } use vars qw($opt_i $opt_o $opt_1 $opt_v); getopts('i:o:1:v') and $opt_o or die usage; my $proxy_port = $opt_i or die usage; my ($server_host, $server_port) = $opt_o =~ /^(.+):(d+)$/ or die usage; my $verbose = 1 if $opt_v; my $only_one = 1 if $opt_1; $SIG{TERM} = $SIG{INT} = $SIG{HUP} = &shutdown; my $proxy = IO::Socket::INET->new(LocalPort => $proxy_port, Type => SOCK_STREAM, Proto => 'tcp', Reuse => 1, Listen => 1) or die "Can't listen on port $proxy_port: $! "; print "[listening on port $proxy_port] " if $verbose; my ($client, $server); OUTER: while ($client = $proxy->accept) { my ($client_host, $client_port) = (gethostbyaddr($client->peeraddr, AF_INET) || $client->peerhost, $client->peerport); print "[connection from $client_host on $client_port] " if $verbose; $server = IO::Socket::INET->new(PeerAddr => $server_host, PeerPort => $server_port, Proto => 'tcp', Type => SOCK_STREAM) or die "Can't connect to $server_host:$server_port "; print "[connected to server $server_host:$server_port] " if $verbose; my $selector = IO::Select->new($client, $server); CONNECTION: while (my @ready = $selector->can_read) { for my $sock (@ready) { my ($who, $target) = ($sock == $client ? ('client', $server) : ('server', $client)); my $msgbuf; unless ($sock->sysread($msgbuf, 64 * 1024)) { print "[$who closed connection] " if $verbose; last CONNECTION; } (my $safebuf = $msgbuf) =~ tr/ 11 12 15 40-176240-376/./cs; print " ===== U$whoE: $safebuf"; print " " unless substr($safebuf, -1, 1) eq " "; $target->syswrite($msgbuf, length $msgbuf); } last OUTER if $only_one; } print "[closing connection] " if $verbose; $client->shutdown(2) if $client; $server->shutdown(2) if $server; } sub shutdown { print "[shutting down] " if $verbose; $client->shutdown(2) if $client; $server->shutdown(2) if $server; $proxy ->shutdown(2); exit(0); }
Let's say that you want to see exactly what travels between the browser and server when you visit a URL like http://webhost/path/file.html. Run this script like so:
% proxylog -i 2222 -o webhost:80
Now the script is listening on port 2222. (You can pick any number you like—on a Unix system you need to be root to pick anything below 1024—but you want to avoid the numbers for any well-known services your computer might already use.)[2] Let's say that the machine on which you run proxylog is called listener.[3] Enter the URL http://listener:2222/path/file.html into your browser, and watch the transaction be printed out by proxylog:
[2] Like, for instance, 6667 (IRC), or 6699 (Napster).
[3] We've omitted domains like .com from the host names here, but you may need them when you run proxylog.
===== CLIENT: GET / HTTP/1.0 Connection: Keep-Alive User-Agent: Mozilla/4.72 [en] (Win98; I) Pragma: no-cache Host: 204.179.152.52:2222 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Encoding: gzip Accept-Language: en Accept-Charset: iso-8859-1,*,utf-8 ===== SERVER: HTTP/1.1 200 OK Date: Tue, 01 Aug 2000 19:54:37 GMT Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21 Last-Modified: Wed, 01 Mar 2000 18:37:44 GMT ETag: "32ae2-9cf-38bd6378" Accept-Ranges: bytes Content-Length: 2511 Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> [...]
Any anchors or embedded URLs (such as images) that do not specify the Web server will also go through proxylog when they are fetched.[4]
[4] proxylog can be downloaded from www.perldebugged.com.