#!/usr/bin/perl 
# -*- perl -*-

#  Copyright (c) 2005 by Kevin Kadow
#
#	The code in this file is made freely available for any purpose
#	whatsoever. The author does not take any responsibility for the
#	correctness or suitability of the code.
#
# ICP Test Script 'test-icp.pl'
# Version 0.2
#
#	Simple script for testing Internet Cache Protocol Servers,
#	suitable for interactive use, or to be executed from a
#	response time graphing tool such as Cricket, Hobbit, etc.
#
# Usage:
#
#	test-icp.pl host[:port] [uri]
#
#	Sends an ICP query for uri to the specified host and port.
#	See below for default values for port and uri.
#

#
# Debugging
#
#	Remove the "#" in front of $debug=1 for verbose output.
#
#$debug=1;

#
# Required modules:
#	You will almost certainly need to download and install the
#	 Time::HiRes and WebCache::ICP modules from CPAN.
#
use Time::HiRes qw(gettimeofday);
use WebCache::ICP;
use Socket;


#
# Defaults:
#	Seldom any reason to change these.
#
$DEFAULT_PORT=3130;
$DEFAULT_QUERY="http://www.w3.org/";
$TIMEOUT=10;


#
# Pick up the host and (optional) query from the command line.
#
$host=shift;
$query=shift;
$query=$DEFAULT_QUERY unless($query);

$icp = new WebCache::ICP;
$icp->opcode("OP_QUERY");
$icp->payload($query);
warn "Will query via ICP for \'$query\'\n" if($debug);


#  In case of failure, print 'U' results for Cricket to parse.
#
sub death {
	local($why)=@_;
        print "U\nU\n";
	die "\nDied on SIG$why";
        }

$SIG{'INT'}='death';
$SIG{'QUIT'}='death';
$SIG{'PIPE'}='death';
$SIG{'ALRM'}='death';

$port=$DEFAULT_PORT;
die("Missing URL on command line.\n") unless($host);

if($host=~m/^([^:]+):(\d+)$/) {
	$host=$1;
	$port=$2;
	}

$host=&name2address($host);
die "Invalid IP address $host\n" unless($host);


#
# Construct our socket for the ICP query.
#
warn "Sending packet to $host:$port" if($debug);

socket(SOCK, PF_INET, SOCK_DGRAM, getprotobyname('udp'))
	or die "socket: $!";

my $sin = sockaddr_in($port, inet_aton($host));
die "bad sin" unless($sin);


#
# Send the packet.
#
alarm($TIMEOUT);
$start = now();
$icp->send(fd => \*SOCK, sin => $sin);

#
# Get an answer (or timeout due to SIGALRM)
#
$response= $icp->recv(fd =>\*SOCK);
$stop = now();

alarm(0);

warn "Back from recv()\n" if($debug);
close(SOCK);


#
# Calculate the elapsed time since we sent the query. 
#
$delta = $stop - $start;
$SCALE=1000000;
$delta = (int(0.9+($delta * $SCALE)))/$SCALE;

#
# Cricket wants a number as the first field of the first line of output.
#
print $delta," Seconds\n";


#
# Process our answer, print as necessary.
#
$answer = new WebCache::ICP($response);
$c=$answer->opcode;

print $c,"\t",&code2name( $c ),"\n";

$answer->dump if($debug);

exit(0);

#########################################################
#
# Subroutines follow
#
#########################################################


sub now {
	my(@t) = gettimeofday();
	return $t[0] + ($t[1] / 1000000.0);
}


#
# Convert an ICP opcode to a human-readable form.
#
sub code2name {
my($code)=(@_);

%OPCODENAMES = (
     0 => "OP_INVALID",
     1, "OP_QUERY",
     2, "OP_HIT",
     3, "OP_MISS",
     4, "OP_ERR",
    10, "OP_SECHO",
    11, "OP_DECHO",
    21, "OP_MISS_NOFETCH",
    22, "OP_DENIED",
    23, "OP_HIT_OBJ",
);
return($OPCODENAMES{$code}) if($OPCODENAMES{$code});
return "UNDEF $code";
}

#
# Convert a hostname to a machine-readable form.  Die if DNS fails.
#
sub name2address {
my($name)=@_;
my $address;

eval {$address=&n2a($name) };

if($@ || !$address) {
        print "U bad dns\nU cannot resolve dns\n";
        die("DNS lookup failed, fatal error");
        }
return($address);
}


sub n2a {
my($hostname)=@_;
return($hostname) if($hostname=~m/^\d[\d.]+\d$/ );

my($name, $aliases, $addrtype, $length, @addrs,@result);
alarm(7);
@result = gethostbyname($hostname);
alarm(0);

unless(@result) {
        warn "name2address($hostname) Cannot resolve\n" if($debug);
        return undef;
        }

($name, $aliases, $addrtype, $length, @addrs) =@result;
($a, $b, $c, $d) = unpack('C4', $addrs[0]);

my($ip)="$a.$b.$c.$d";

warn "$hostname resolves to $ip\n" if($debug);

return($ip);
}

###EOF###

