package Net::Ping;
-# $Id: Ping.pm,v 1.11 2001/12/04 02:41:51 rob Exp $
+# $Id: Ping.pm,v 1.46 2002/12/02 19:17:09 rob Exp $
require 5.002;
require Exporter;
use strict;
use vars qw(@ISA @EXPORT $VERSION
- $def_timeout $def_proto $max_datasize $pingstring);
+ $def_timeout $def_proto $max_datasize $pingstring $hires $source_verify $syn_forking);
+use Fcntl qw( F_GETFL F_SETFL O_NONBLOCK );
+use Socket qw( SOCK_DGRAM SOCK_STREAM SOCK_RAW PF_INET SOL_SOCKET
+ inet_aton inet_ntoa sockaddr_in );
+use POSIX qw( ECONNREFUSED EINPROGRESS EAGAIN WNOHANG );
use FileHandle;
-use Socket qw( SOCK_DGRAM SOCK_STREAM SOCK_RAW PF_INET
- inet_aton sockaddr_in );
use Carp;
@ISA = qw(Exporter);
@EXPORT = qw(pingecho);
-$VERSION = 2.07;
+$VERSION = "2.26";
# Constants
$def_timeout = 5; # Default timeout to wait for a reply
-$def_proto = "udp"; # Default protocol to use for pinging
+$def_proto = "tcp"; # Default protocol to use for pinging
$max_datasize = 1024; # Maximum data bytes in a packet
# The data we exchange with the server for the stream protocol
$pingstring = "pingschwingping!\n";
+$source_verify = 1; # Default is to verify source endpoint
+$syn_forking = 0;
+
+if ($^O =~ /Win32/i) {
+ # Hack to avoid this Win32 spewage:
+ # Your vendor has not defined POSIX macro ECONNREFUSED
+ *ECONNREFUSED = sub {10061;}; # "Unknown Error" Special Win32 Response?
+ $syn_forking = 1;
+};
+
+# h2ph "asm/socket.h"
+# require "asm/socket.ph";
+sub SO_BINDTODEVICE {25;}
# Description: The pingecho() subroutine is provided for backward
# compatibility with the original Net::Ping. It accepts a host
sub pingecho
{
- my ($host, # Name or IP number of host to ping
- $timeout # Optional timeout in seconds
- ) = @_;
- my ($p); # A ping object
+ my ($host, # Name or IP number of host to ping
+ $timeout # Optional timeout in seconds
+ ) = @_;
+ my ($p); # A ping object
- $p = Net::Ping->new("tcp", $timeout);
- $p->ping($host); # Going out of scope closes the connection
+ $p = Net::Ping->new("tcp", $timeout);
+ $p->ping($host); # Going out of scope closes the connection
}
# Description: The new() method creates a new ping object. Optional
sub new
{
- my ($this,
- $proto, # Optional protocol to use for pinging
- $timeout, # Optional timeout in seconds
- $data_size # Optional additional bytes of data
- ) = @_;
- my $class = ref($this) || $this;
- my $self = {};
- my ($cnt, # Count through data bytes
- $min_datasize # Minimum data bytes required
- );
-
- bless($self, $class);
-
- $proto = $def_proto unless $proto; # Determine the protocol
- croak('Protocol for ping must be "icmp", "udp", "tcp", "stream", or "external"')
- unless $proto =~ m/^(icmp|udp|tcp|stream|external)$/;
- $self->{"proto"} = $proto;
-
- $timeout = $def_timeout unless $timeout; # Determine the timeout
- croak("Default timeout for ping must be greater than 0 seconds")
- if $timeout <= 0;
- $self->{"timeout"} = $timeout;
-
- $min_datasize = ($proto eq "udp") ? 1 : 0; # Determine data size
- $data_size = $min_datasize unless defined($data_size) && $proto ne "tcp";
- croak("Data for ping must be from $min_datasize to $max_datasize bytes")
- if ($data_size < $min_datasize) || ($data_size > $max_datasize);
- $data_size-- if $self->{"proto"} eq "udp"; # We provide the first byte
- $self->{"data_size"} = $data_size;
-
- $self->{"data"} = ""; # Construct data bytes
- for ($cnt = 0; $cnt < $self->{"data_size"}; $cnt++)
- {
- $self->{"data"} .= chr($cnt % 256);
- }
-
- $self->{"seq"} = 0; # For counting packets
- if ($self->{"proto"} eq "udp") # Open a socket
- {
- $self->{"proto_num"} = (getprotobyname('udp'))[2] ||
- croak("Can't udp protocol by name");
- $self->{"port_num"} = (getservbyname('echo', 'udp'))[2] ||
- croak("Can't get udp echo port by name");
- $self->{"fh"} = FileHandle->new();
- socket($self->{"fh"}, &PF_INET(), &SOCK_DGRAM(),
- $self->{"proto_num"}) ||
- croak("udp socket error - $!");
+ my ($this,
+ $proto, # Optional protocol to use for pinging
+ $timeout, # Optional timeout in seconds
+ $data_size, # Optional additional bytes of data
+ $device, # Optional device to use
+ ) = @_;
+ my $class = ref($this) || $this;
+ my $self = {};
+ my ($cnt, # Count through data bytes
+ $min_datasize # Minimum data bytes required
+ );
+
+ bless($self, $class);
+
+ $proto = $def_proto unless $proto; # Determine the protocol
+ croak('Protocol for ping must be "icmp", "udp", "tcp", "syn", "stream", or "external"')
+ unless $proto =~ m/^(icmp|udp|tcp|syn|stream|external)$/;
+ $self->{"proto"} = $proto;
+
+ $timeout = $def_timeout unless $timeout; # Determine the timeout
+ croak("Default timeout for ping must be greater than 0 seconds")
+ if $timeout <= 0;
+ $self->{"timeout"} = $timeout;
+
+ $self->{"device"} = $device;
+
+ $min_datasize = ($proto eq "udp") ? 1 : 0; # Determine data size
+ $data_size = $min_datasize unless defined($data_size) && $proto ne "tcp";
+ croak("Data for ping must be from $min_datasize to $max_datasize bytes")
+ if ($data_size < $min_datasize) || ($data_size > $max_datasize);
+ $data_size-- if $self->{"proto"} eq "udp"; # We provide the first byte
+ $self->{"data_size"} = $data_size;
+
+ $self->{"data"} = ""; # Construct data bytes
+ for ($cnt = 0; $cnt < $self->{"data_size"}; $cnt++)
+ {
+ $self->{"data"} .= chr($cnt % 256);
+ }
+
+ $self->{"local_addr"} = undef; # Don't bind by default
+
+ $self->{"tcp_econnrefused"} = undef; # Default Connection refused behavior
+
+ $self->{"seq"} = 0; # For counting packets
+ if ($self->{"proto"} eq "udp") # Open a socket
+ {
+ $self->{"proto_num"} = (getprotobyname('udp'))[2] ||
+ croak("Can't udp protocol by name");
+ $self->{"port_num"} = (getservbyname('echo', 'udp'))[2] ||
+ croak("Can't get udp echo port by name");
+ $self->{"fh"} = FileHandle->new();
+ socket($self->{"fh"}, PF_INET, SOCK_DGRAM,
+ $self->{"proto_num"}) ||
+ croak("udp socket error - $!");
+ if ($self->{'device'}) {
+ setsockopt($self->{"fh"}, SOL_SOCKET, SO_BINDTODEVICE(), pack("Z*", $self->{'device'}))
+ or croak "error binding to device $self->{'device'} $!";
}
- elsif ($self->{"proto"} eq "icmp")
- {
- croak("icmp ping requires root privilege") if ($> and $^O ne 'VMS');
- $self->{"proto_num"} = (getprotobyname('icmp'))[2] ||
- croak("Can't get icmp protocol by name");
- $self->{"pid"} = $$ & 0xffff; # Save lower 16 bits of pid
- $self->{"fh"} = FileHandle->new();
- socket($self->{"fh"}, &PF_INET(), &SOCK_RAW(), $self->{"proto_num"}) ||
- croak("icmp socket error - $!");
+ }
+ elsif ($self->{"proto"} eq "icmp")
+ {
+ croak("icmp ping requires root privilege") if ($> and $^O ne 'VMS' and $^O ne 'cygwin');
+ $self->{"proto_num"} = (getprotobyname('icmp'))[2] ||
+ croak("Can't get icmp protocol by name");
+ $self->{"pid"} = $$ & 0xffff; # Save lower 16 bits of pid
+ $self->{"fh"} = FileHandle->new();
+ socket($self->{"fh"}, PF_INET, SOCK_RAW, $self->{"proto_num"}) ||
+ croak("icmp socket error - $!");
+ if ($self->{'device'}) {
+ setsockopt($self->{"fh"}, SOL_SOCKET, SO_BINDTODEVICE(), pack("Z*", $self->{'device'}))
+ or croak "error binding to device $self->{'device'} $!";
}
- elsif ($self->{"proto"} eq "tcp" || $self->{"proto"} eq "stream")
- {
- $self->{"proto_num"} = (getprotobyname('tcp'))[2] ||
- croak("Can't get tcp protocol by name");
- $self->{"port_num"} = (getservbyname('echo', 'tcp'))[2] ||
- croak("Can't get tcp echo port by name");
- $self->{"fh"} = FileHandle->new();
+ }
+ elsif ($self->{"proto"} eq "tcp" || $self->{"proto"} eq "stream")
+ {
+ $self->{"proto_num"} = (getprotobyname('tcp'))[2] ||
+ croak("Can't get tcp protocol by name");
+ $self->{"port_num"} = (getservbyname('echo', 'tcp'))[2] ||
+ croak("Can't get tcp echo port by name");
+ $self->{"fh"} = FileHandle->new();
+ }
+ elsif ($self->{"proto"} eq "syn")
+ {
+ $self->{"proto_num"} = (getprotobyname('tcp'))[2] ||
+ croak("Can't get tcp protocol by name");
+ $self->{"port_num"} = (getservbyname('echo', 'tcp'))[2] ||
+ croak("Can't get tcp echo port by name");
+ if ($syn_forking) {
+ $self->{"fork_rd"} = FileHandle->new();
+ $self->{"fork_wr"} = FileHandle->new();
+ pipe($self->{"fork_rd"}, $self->{"fork_wr"});
+ $self->{"fh"} = FileHandle->new();
+ $self->{"good"} = {};
+ $self->{"bad"} = {};
+ } else {
+ $self->{"wbits"} = "";
+ $self->{"bad"} = {};
}
+ $self->{"syn"} = {};
+ $self->{"stop_time"} = 0;
+ }
+ elsif ($self->{"proto"} eq "external")
+ {
+ # No preliminary work needs to be done.
+ }
+
+ return($self);
+}
+
+# Description: Set the local IP address from which pings will be sent.
+# For ICMP and UDP pings, this calls bind() on the already-opened socket;
+# for TCP pings, just saves the address to be used when the socket is
+# opened. Returns non-zero if successful; croaks on error.
+sub bind
+{
+ my ($self,
+ $local_addr # Name or IP number of local interface
+ ) = @_;
+ my ($ip # Packed IP number of $local_addr
+ );
+
+ croak("Usage: \$p->bind(\$local_addr)") unless @_ == 2;
+ croak("already bound") if defined($self->{"local_addr"}) &&
+ ($self->{"proto"} eq "udp" || $self->{"proto"} eq "icmp");
+
+ $ip = inet_aton($local_addr);
+ croak("nonexistent local address $local_addr") unless defined($ip);
+ $self->{"local_addr"} = $ip; # Only used if proto is tcp
+
+ if ($self->{"proto"} eq "udp" || $self->{"proto"} eq "icmp")
+ {
+ CORE::bind($self->{"fh"}, sockaddr_in(0, $ip)) ||
+ croak("$self->{'proto'} bind error - $!");
+ }
+ elsif ($self->{"proto"} ne "tcp")
+ {
+ croak("Unknown protocol \"$self->{proto}\" in bind()");
+ }
+
+ return 1;
+}
+
+
+# Description: Allow UDP source endpoint comparision to be
+# skipped for those remote interfaces that do
+# not response from the same endpoint.
+
+sub source_verify
+{
+ my $self = shift;
+ $source_verify = 1 unless defined
+ ($source_verify = ((defined $self) && (ref $self)) ? shift() : $self);
+}
+# Description: Set whether or not the tcp connect
+# behavior should enforce remote service availability
+# as well as reachability.
- return($self);
+sub tcp_service_check
+{
+ my $self = shift;
+ $self->{"tcp_econnrefused"} = 1 unless defined
+ ($self->{"tcp_econnrefused"} = shift());
+}
+
+# Description: allows the module to use milliseconds as returned by
+# the Time::HiRes module
+
+$hires = 0;
+sub hires
+{
+ my $self = shift;
+ $hires = 1 unless defined
+ ($hires = ((defined $self) && (ref $self)) ? shift() : $self);
+ require Time::HiRes if $hires;
+}
+
+sub time
+{
+ return $hires ? Time::HiRes::time() : CORE::time();
}
# Description: Ping a host name or IP number with an optional timeout.
sub ping
{
- my ($self,
- $host, # Name or IP number of host to ping
- $timeout # Seconds after which ping times out
- ) = @_;
- my ($ip, # Packed IP number of $host
- $ret # The return value
- );
-
- croak("Usage: \$p->ping(\$host [, \$timeout])") unless @_ == 2 || @_ == 3;
- $timeout = $self->{"timeout"} unless $timeout;
- croak("Timeout must be greater than 0 seconds") if $timeout <= 0;
-
- $ip = inet_aton($host);
- return(undef) unless defined($ip); # Does host exist?
-
- # Dispatch to the appropriate routine.
- return $self->ping_external($ip, $timeout) if $self->{"proto"} eq "external";
- return $self->ping_udp($ip, $timeout) if $self->{"proto"} eq "udp";
- return $self->ping_icmp($ip, $timeout) if $self->{"proto"} eq "icmp";
- return $self->ping_tcp($ip, $timeout) if $self->{"proto"} eq "tcp";
- return $self->ping_stream($ip, $timeout) if $self->{"proto"} eq "stream";
-
+ my ($self,
+ $host, # Name or IP number of host to ping
+ $timeout, # Seconds after which ping times out
+ ) = @_;
+ my ($ip, # Packed IP number of $host
+ $ret, # The return value
+ $ping_time, # When ping began
+ );
+
+ croak("Usage: \$p->ping(\$host [, \$timeout])") unless @_ == 2 || @_ == 3;
+ $timeout = $self->{"timeout"} unless $timeout;
+ croak("Timeout must be greater than 0 seconds") if $timeout <= 0;
+
+ $ip = inet_aton($host);
+ return(undef) unless defined($ip); # Does host exist?
+
+ # Dispatch to the appropriate routine.
+ $ping_time = &time();
+ if ($self->{"proto"} eq "external") {
+ $ret = $self->ping_external($ip, $timeout);
+ }
+ elsif ($self->{"proto"} eq "udp") {
+ $ret = $self->ping_udp($ip, $timeout);
+ }
+ elsif ($self->{"proto"} eq "icmp") {
+ $ret = $self->ping_icmp($ip, $timeout);
+ }
+ elsif ($self->{"proto"} eq "tcp") {
+ $ret = $self->ping_tcp($ip, $timeout);
+ }
+ elsif ($self->{"proto"} eq "stream") {
+ $ret = $self->ping_stream($ip, $timeout);
+ }
+ elsif ($self->{"proto"} eq "syn") {
+ $ret = $self->ping_syn($host, $ip, $ping_time, $ping_time+$timeout);
+ } else {
croak("Unknown protocol \"$self->{proto}\" in ping()");
+ }
+
+ return wantarray ? ($ret, &time() - $ping_time, inet_ntoa($ip)) : $ret;
}
# Uses Net::Ping::External to do an external ping.
return Net::Ping::External::ping(ip => $ip, timeout => $timeout);
}
+use constant ICMP_ECHOREPLY => 0; # ICMP packet types
+use constant ICMP_ECHO => 8;
+use constant ICMP_STRUCT => "C2 S3 A"; # Structure of a minimal ICMP packet
+use constant SUBCODE => 0; # No ICMP subcode for ECHO and ECHOREPLY
+use constant ICMP_FLAGS => 0; # No special flags for send or recv
+use constant ICMP_PORT => 0; # No port with ICMP
+
sub ping_icmp
{
- my ($self,
- $ip, # Packed IP number of the host
- $timeout # Seconds after which ping times out
- ) = @_;
-
- my $ICMP_ECHOREPLY = 0; # ICMP packet types
- my $ICMP_ECHO = 8;
- my $icmp_struct = "C2 S3 A"; # Structure of a minimal ICMP packet
- my $subcode = 0; # No ICMP subcode for ECHO and ECHOREPLY
- my $flags = 0; # No special flags when opening a socket
- my $port = 0; # No port with ICMP
-
- my ($saddr, # sockaddr_in with port and ip
- $checksum, # Checksum of ICMP packet
- $msg, # ICMP packet to send
- $len_msg, # Length of $msg
- $rbits, # Read bits, filehandles for reading
- $nfound, # Number of ready filehandles found
- $finish_time, # Time ping should be finished
- $done, # set to 1 when we are done
- $ret, # Return value
- $recv_msg, # Received message including IP header
- $from_saddr, # sockaddr_in of sender
- $from_port, # Port packet was sent from
- $from_ip, # Packed IP of sender
- $from_type, # ICMP type
- $from_subcode, # ICMP subcode
- $from_chk, # ICMP packet checksum
- $from_pid, # ICMP packet id
- $from_seq, # ICMP packet sequence
- $from_msg # ICMP message
- );
-
- $self->{"seq"} = ($self->{"seq"} + 1) % 65536; # Increment sequence
- $checksum = 0; # No checksum for starters
- $msg = pack($icmp_struct . $self->{"data_size"}, $ICMP_ECHO, $subcode,
- $checksum, $self->{"pid"}, $self->{"seq"}, $self->{"data"});
- $checksum = Net::Ping->checksum($msg);
- $msg = pack($icmp_struct . $self->{"data_size"}, $ICMP_ECHO, $subcode,
- $checksum, $self->{"pid"}, $self->{"seq"}, $self->{"data"});
- $len_msg = length($msg);
- $saddr = sockaddr_in($port, $ip);
- send($self->{"fh"}, $msg, $flags, $saddr); # Send the message
-
- $rbits = "";
- vec($rbits, $self->{"fh"}->fileno(), 1) = 1;
- $ret = 0;
- $done = 0;
- $finish_time = time() + $timeout; # Must be done by this time
- while (!$done && $timeout > 0) # Keep trying if we have time
+ my ($self,
+ $ip, # Packed IP number of the host
+ $timeout # Seconds after which ping times out
+ ) = @_;
+
+ my ($saddr, # sockaddr_in with port and ip
+ $checksum, # Checksum of ICMP packet
+ $msg, # ICMP packet to send
+ $len_msg, # Length of $msg
+ $rbits, # Read bits, filehandles for reading
+ $nfound, # Number of ready filehandles found
+ $finish_time, # Time ping should be finished
+ $done, # set to 1 when we are done
+ $ret, # Return value
+ $recv_msg, # Received message including IP header
+ $from_saddr, # sockaddr_in of sender
+ $from_port, # Port packet was sent from
+ $from_ip, # Packed IP of sender
+ $from_type, # ICMP type
+ $from_subcode, # ICMP subcode
+ $from_chk, # ICMP packet checksum
+ $from_pid, # ICMP packet id
+ $from_seq, # ICMP packet sequence
+ $from_msg # ICMP message
+ );
+
+ $self->{"seq"} = ($self->{"seq"} + 1) % 65536; # Increment sequence
+ $checksum = 0; # No checksum for starters
+ $msg = pack(ICMP_STRUCT . $self->{"data_size"}, ICMP_ECHO, SUBCODE,
+ $checksum, $self->{"pid"}, $self->{"seq"}, $self->{"data"});
+ $checksum = Net::Ping->checksum($msg);
+ $msg = pack(ICMP_STRUCT . $self->{"data_size"}, ICMP_ECHO, SUBCODE,
+ $checksum, $self->{"pid"}, $self->{"seq"}, $self->{"data"});
+ $len_msg = length($msg);
+ $saddr = sockaddr_in(ICMP_PORT, $ip);
+ send($self->{"fh"}, $msg, ICMP_FLAGS, $saddr); # Send the message
+
+ $rbits = "";
+ vec($rbits, $self->{"fh"}->fileno(), 1) = 1;
+ $ret = 0;
+ $done = 0;
+ $finish_time = &time() + $timeout; # Must be done by this time
+ while (!$done && $timeout > 0) # Keep trying if we have time
+ {
+ $nfound = select($rbits, undef, undef, $timeout); # Wait for packet
+ $timeout = $finish_time - &time(); # Get remaining time
+ if (!defined($nfound)) # Hmm, a strange error
{
- $nfound = select($rbits, undef, undef, $timeout); # Wait for packet
- $timeout = $finish_time - time(); # Get remaining time
- if (!defined($nfound)) # Hmm, a strange error
- {
- $ret = undef;
- $done = 1;
- }
- elsif ($nfound) # Got a packet from somewhere
- {
- $recv_msg = "";
- $from_saddr = recv($self->{"fh"}, $recv_msg, 1500, $flags);
- ($from_port, $from_ip) = sockaddr_in($from_saddr);
- ($from_type, $from_subcode, $from_chk,
- $from_pid, $from_seq, $from_msg) =
- unpack($icmp_struct . $self->{"data_size"},
- substr($recv_msg, length($recv_msg) - $len_msg,
- $len_msg));
- if (($from_type == $ICMP_ECHOREPLY) &&
- ($from_ip eq $ip) &&
- ($from_pid == $self->{"pid"}) && # Does the packet check out?
- ($from_seq == $self->{"seq"}))
- {
- $ret = 1; # It's a winner
- $done = 1;
- }
- }
- else # Oops, timed out
- {
- $done = 1;
- }
+ $ret = undef;
+ $done = 1;
+ }
+ elsif ($nfound) # Got a packet from somewhere
+ {
+ $recv_msg = "";
+ $from_saddr = recv($self->{"fh"}, $recv_msg, 1500, ICMP_FLAGS);
+ ($from_port, $from_ip) = sockaddr_in($from_saddr);
+ ($from_type, $from_subcode, $from_chk,
+ $from_pid, $from_seq, $from_msg) =
+ unpack(ICMP_STRUCT . $self->{"data_size"},
+ substr($recv_msg, length($recv_msg) - $len_msg,
+ $len_msg));
+ if (($from_type == ICMP_ECHOREPLY) &&
+ (!$source_verify || $from_ip eq $ip) &&
+ ($from_pid == $self->{"pid"}) && # Does the packet check out?
+ ($from_seq == $self->{"seq"}))
+ {
+ $ret = 1; # It's a winner
+ $done = 1;
+ }
+ }
+ else # Oops, timed out
+ {
+ $done = 1;
}
- return($ret)
+ }
+ return $ret;
}
# Description: Do a checksum on the message. Basically sum all of
sub checksum
{
- my ($class,
- $msg # The message to checksum
- ) = @_;
- my ($len_msg, # Length of the message
- $num_short, # The number of short words in the message
- $short, # One short word
- $chk # The checksum
- );
-
- $len_msg = length($msg);
- $num_short = int($len_msg / 2);
- $chk = 0;
- foreach $short (unpack("S$num_short", $msg))
- {
- $chk += $short;
- } # Add the odd byte in
- $chk += (unpack("C", substr($msg, $len_msg - 1, 1)) << 8) if $len_msg % 2;
- $chk = ($chk >> 16) + ($chk & 0xffff); # Fold high into low
- return(~(($chk >> 16) + $chk) & 0xffff); # Again and complement
+ my ($class,
+ $msg # The message to checksum
+ ) = @_;
+ my ($len_msg, # Length of the message
+ $num_short, # The number of short words in the message
+ $short, # One short word
+ $chk # The checksum
+ );
+
+ $len_msg = length($msg);
+ $num_short = int($len_msg / 2);
+ $chk = 0;
+ foreach $short (unpack("S$num_short", $msg))
+ {
+ $chk += $short;
+ } # Add the odd byte in
+ $chk += (unpack("C", substr($msg, $len_msg - 1, 1)) << 8) if $len_msg % 2;
+ $chk = ($chk >> 16) + ($chk & 0xffff); # Fold high into low
+ return(~(($chk >> 16) + $chk) & 0xffff); # Again and complement
}
sub ping_tcp
{
- my ($self,
- $ip, # Packed IP number of the host
- $timeout # Seconds after which ping times out
- ) = @_;
- my ($ret # The return value
- );
-
- $@ = "";
- $ret = $self -> tcp_connect( $ip, $timeout);
- $ret = 1 if $@ =~ /(Connection Refused|Unknown Error)/i;
- $self->{"fh"}->close();
- return($ret);
+ my ($self,
+ $ip, # Packed IP number of the host
+ $timeout # Seconds after which ping times out
+ ) = @_;
+ my ($ret # The return value
+ );
+
+ $@ = ""; $! = 0;
+ $ret = $self -> tcp_connect( $ip, $timeout);
+ if (!$self->{"tcp_econnrefused"} &&
+ $! == ECONNREFUSED) {
+ $ret = 1; # "Connection refused" means reachable
+ }
+ $self->{"fh"}->close();
+ return $ret;
}
sub tcp_connect
{
- my ($self,
- $ip, # Packed IP number of the host
- $timeout # Seconds after which connect times out
- ) = @_;
- my ($saddr); # Packed IP and Port
+ my ($self,
+ $ip, # Packed IP number of the host
+ $timeout # Seconds after which connect times out
+ ) = @_;
+ my ($saddr); # Packed IP and Port
- $saddr = sockaddr_in($self->{"port_num"}, $ip);
+ $saddr = sockaddr_in($self->{"port_num"}, $ip);
- my $ret = 0; # Default to unreachable
+ my $ret = 0; # Default to unreachable
- my $do_socket = sub {
- socket($self->{"fh"}, &PF_INET(), &SOCK_STREAM(), $self->{"proto_num"}) ||
- croak("tcp socket error - $!");
- };
- my $do_connect = sub {
- eval {
- die $! unless connect($self->{"fh"}, $saddr);
- $self->{"ip"} = $ip;
- $ret = 1;
- };
- $ret;
+ my $do_socket = sub {
+ socket($self->{"fh"}, PF_INET, SOCK_STREAM, $self->{"proto_num"}) ||
+ croak("tcp socket error - $!");
+ if (defined $self->{"local_addr"} &&
+ !CORE::bind($self->{"fh"}, sockaddr_in(0, $self->{"local_addr"}))) {
+ croak("tcp bind error - $!");
+ }
+ if ($self->{'device'}) {
+ setsockopt($self->{"fh"}, SOL_SOCKET, SO_BINDTODEVICE(), pack("Z*", $self->{'device'}))
+ or croak("error binding to device $self->{'device'} $!");
+ }
+ };
+ my $do_connect = sub {
+ eval {
+ die $! unless connect($self->{"fh"}, $saddr);
+ $self->{"ip"} = $ip;
+ $ret = 1;
};
-
- if ($^O =~ /Win32/i) {
-
- # Buggy Winsock API doesn't allow us to use alarm() calls.
- # Hence, if our OS is Windows, we need to create a separate
- # process to do the blocking connect attempt.
-
- $| = 1; # Clear buffer prior to fork to prevent duplicate flushing.
- my $pid = fork;
- if (!$pid) {
- if (!defined $pid) {
- # Fork did not work
- warn "Win32 Fork error: $!";
- return 0;
- }
- &{ $do_socket }();
-
- # Try a slow blocking connect() call
- # and report the status to the pipe.
- if ( &{ $do_connect }() ) {
- $self->{"fh"}->close();
- # No error
- exit 0;
- } else {
- # Pass the error status to the parent
- exit $!;
- }
+ $ret;
+ };
+
+ if ($^O =~ /Win32/i) {
+
+ # Buggy Winsock API doesn't allow us to use alarm() calls.
+ # Hence, if our OS is Windows, we need to create a separate
+ # process to do the blocking connect attempt.
+
+ $| = 1; # Clear buffer prior to fork to prevent duplicate flushing.
+ my $pid = fork;
+ if (!$pid) {
+ if (!defined $pid) {
+ # Fork did not work
+ warn "Win32 Fork error: $!";
+ return 0;
}
-
&{ $do_socket }();
- my $patience = time + $timeout;
-
- require POSIX;
- my ($child);
- $? = 0;
- # Wait up to the timeout
- # And clean off the zombie
- do {
- $child = waitpid($pid, &POSIX::WNOHANG);
- $! = $? >> 8;
- $@ = $!;
- sleep 1;
- } while time < $patience && $child != $pid;
-
- if ($child == $pid) {
- # Since she finished within the timeout,
- # it is probably safe for me to try it too
- &{ $do_connect }();
+ # Try a slow blocking connect() call
+ # and report the status to the pipe.
+ if ( &{ $do_connect }() ) {
+ $self->{"fh"}->close();
+ # No error
+ exit 0;
} else {
- # Time must have run out.
- $@ = "Timed out!";
- # Put that choking client out of its misery
- kill "KILL", $pid;
- # Clean off the zombie
- waitpid($pid, 0);
- $ret = 0;
+ # Pass the error status to the parent
+ exit $!;
}
- } else { # Win32
- # Otherwise don't waste the resources to fork
+ }
- &{ $do_socket }();
+ &{ $do_socket }();
+
+ my $patience = &time() + $timeout;
- $SIG{'ALRM'} = sub { die "Timed out!"; };
- alarm($timeout); # Interrupt connect() if we have to
+ my ($child);
+ $? = 0;
+ # Wait up to the timeout
+ # And clean off the zombie
+ do {
+ $child = waitpid($pid, &WNOHANG());
+ $! = $? >> 8;
+ $@ = $!;
+ select(undef, undef, undef, 0.1);
+ } while &time() < $patience && $child != $pid;
+ if ($child == $pid) {
+ # Since she finished within the timeout,
+ # it is probably safe for me to try it too
&{ $do_connect }();
- alarm(0);
+ } else {
+ # Time must have run out.
+ $@ = "Timed out!";
+ # Put that choking client out of its misery
+ kill "KILL", $pid;
+ # Clean off the zombie
+ waitpid($pid, 0);
+ $ret = 0;
}
+ } else { # Win32
+ # Otherwise don't waste the resources to fork
+
+ &{ $do_socket }();
- return $ret;
+ local $SIG{'ALRM'} = sub { die "Timed out!"; };
+ my $old = alarm($timeout); # Interrupt connect() if we have to
+
+ &{ $do_connect }();
+ alarm($old);
+ }
+
+ return $ret;
}
# This writes the given string to the socket and then reads it
# back. It returns 1 on success, 0 on failure.
sub tcp_echo
{
- my $self = shift;
- my $timeout = shift;
- my $pingstring = shift;
-
- my $ret = undef;
- my $time = time;
- my $wrstr = $pingstring;
- my $rdstr = "";
-
- eval <<'EOM';
- do {
- my $rin = "";
- vec($rin, $self->{"fh"}->fileno(), 1) = 1;
-
- my $rout = undef;
- if($wrstr) {
- $rout = "";
- vec($rout, $self->{"fh"}->fileno(), 1) = 1;
- }
-
- if(select($rin, $rout, undef, ($time + $timeout) - time())) {
-
- if($rout && vec($rout,$self->{"fh"}->fileno(),1)) {
- my $num = syswrite($self->{"fh"}, $wrstr);
- if($num) {
- # If it was a partial write, update and try again.
- $wrstr = substr($wrstr,$num);
- } else {
- # There was an error.
- $ret = 0;
- }
- }
-
- if(vec($rin,$self->{"fh"}->fileno(),1)) {
- my $reply;
- if(sysread($self->{"fh"},$reply,length($pingstring)-length($rdstr))) {
- $rdstr .= $reply;
- $ret = 1 if $rdstr eq $pingstring;
- } else {
- # There was an error.
- $ret = 0;
- }
- }
-
- }
- } until time() > ($time + $timeout) || defined($ret);
+ my $self = shift;
+ my $timeout = shift;
+ my $pingstring = shift;
+
+ my $ret = undef;
+ my $time = &time();
+ my $wrstr = $pingstring;
+ my $rdstr = "";
+
+ eval <<'EOM';
+ do {
+ my $rin = "";
+ vec($rin, $self->{"fh"}->fileno(), 1) = 1;
+
+ my $rout = undef;
+ if($wrstr) {
+ $rout = "";
+ vec($rout, $self->{"fh"}->fileno(), 1) = 1;
+ }
+
+ if(select($rin, $rout, undef, ($time + $timeout) - &time())) {
+
+ if($rout && vec($rout,$self->{"fh"}->fileno(),1)) {
+ my $num = syswrite($self->{"fh"}, $wrstr);
+ if($num) {
+ # If it was a partial write, update and try again.
+ $wrstr = substr($wrstr,$num);
+ } else {
+ # There was an error.
+ $ret = 0;
+ }
+ }
+
+ if(vec($rin,$self->{"fh"}->fileno(),1)) {
+ my $reply;
+ if(sysread($self->{"fh"},$reply,length($pingstring)-length($rdstr))) {
+ $rdstr .= $reply;
+ $ret = 1 if $rdstr eq $pingstring;
+ } else {
+ # There was an error.
+ $ret = 0;
+ }
+ }
+
+ }
+ } until &time() > ($time + $timeout) || defined($ret);
EOM
- return $ret;
+ return $ret;
}
sub ping_stream
{
- my ($self,
- $ip, # Packed IP number of the host
- $timeout # Seconds after which ping times out
- ) = @_;
-
- # Open the stream if it's not already open
- if(!defined $self->{"fh"}->fileno()) {
- $self->tcp_connect($ip, $timeout) or return 0;
- }
+ my ($self,
+ $ip, # Packed IP number of the host
+ $timeout # Seconds after which ping times out
+ ) = @_;
+
+ # Open the stream if it's not already open
+ if(!defined $self->{"fh"}->fileno()) {
+ $self->tcp_connect($ip, $timeout) or return 0;
+ }
- croak "tried to switch servers while stream pinging"
- if $self->{"ip"} ne $ip;
+ croak "tried to switch servers while stream pinging"
+ if $self->{"ip"} ne $ip;
- return $self->tcp_echo($timeout, $pingstring);
+ return $self->tcp_echo($timeout, $pingstring);
}
# Description: opens the stream. You would do this if you want to
sub open
{
- my ($self,
- $host, # Host or IP address
- $timeout # Seconds after which open times out
- ) = @_;
-
- my ($ip); # Packed IP number of the host
- $ip = inet_aton($host);
- $timeout = $self->{"timeout"} unless $timeout;
-
- if($self->{"proto"} eq "stream") {
- if(defined($self->{"fh"}->fileno())) {
- croak("socket is already open");
- } else {
- $self->tcp_connect($ip, $timeout);
- }
+ my ($self,
+ $host, # Host or IP address
+ $timeout # Seconds after which open times out
+ ) = @_;
+
+ my ($ip); # Packed IP number of the host
+ $ip = inet_aton($host);
+ $timeout = $self->{"timeout"} unless $timeout;
+
+ if($self->{"proto"} eq "stream") {
+ if(defined($self->{"fh"}->fileno())) {
+ croak("socket is already open");
+ } else {
+ $self->tcp_connect($ip, $timeout);
}
+ }
}
# done. Otherwise go back and wait for the message until we run out
# of time. Return the result of our efforts.
+use constant UDP_FLAGS => 0; # Nothing special on send or recv
+# XXX - Use concept by rdw @ perlmonks
+# http://perlmonks.thepen.com/42898.html
sub ping_udp
{
- my ($self,
- $ip, # Packed IP number of the host
- $timeout # Seconds after which ping times out
- ) = @_;
-
- my $flags = 0; # Nothing special on open
-
- my ($saddr, # sockaddr_in with port and ip
- $ret, # The return value
- $msg, # Message to be echoed
- $finish_time, # Time ping should be finished
- $done, # Set to 1 when we are done pinging
- $rbits, # Read bits, filehandles for reading
- $nfound, # Number of ready filehandles found
- $from_saddr, # sockaddr_in of sender
- $from_msg, # Characters echoed by $host
- $from_port, # Port message was echoed from
- $from_ip # Packed IP number of sender
- );
-
- $saddr = sockaddr_in($self->{"port_num"}, $ip);
- $self->{"seq"} = ($self->{"seq"} + 1) % 256; # Increment sequence
- $msg = chr($self->{"seq"}) . $self->{"data"}; # Add data if any
- send($self->{"fh"}, $msg, $flags, $saddr); # Send it
-
- $rbits = "";
- vec($rbits, $self->{"fh"}->fileno(), 1) = 1;
- $ret = 0; # Default to unreachable
- $done = 0;
- $finish_time = time() + $timeout; # Ping needs to be done by then
- while (!$done && $timeout > 0)
+ my ($self,
+ $ip, # Packed IP number of the host
+ $timeout # Seconds after which ping times out
+ ) = @_;
+
+ my ($saddr, # sockaddr_in with port and ip
+ $ret, # The return value
+ $msg, # Message to be echoed
+ $finish_time, # Time ping should be finished
+ $done, # Set to 1 when we are done pinging
+ $rbits, # Read bits, filehandles for reading
+ $nfound, # Number of ready filehandles found
+ $from_saddr, # sockaddr_in of sender
+ $from_msg, # Characters echoed by $host
+ $from_port, # Port message was echoed from
+ $from_ip # Packed IP number of sender
+ );
+
+ $saddr = sockaddr_in($self->{"port_num"}, $ip);
+ $self->{"seq"} = ($self->{"seq"} + 1) % 256; # Increment sequence
+ $msg = chr($self->{"seq"}) . $self->{"data"}; # Add data if any
+ send($self->{"fh"}, $msg, UDP_FLAGS, $saddr); # Send it
+
+ $rbits = "";
+ vec($rbits, $self->{"fh"}->fileno(), 1) = 1;
+ $ret = 0; # Default to unreachable
+ $done = 0;
+ $finish_time = &time() + $timeout; # Ping needs to be done by then
+ while (!$done && $timeout > 0)
+ {
+ $nfound = select($rbits, undef, undef, $timeout); # Wait for response
+ $timeout = $finish_time - &time(); # Get remaining time
+
+ if (!defined($nfound)) # Hmm, a strange error
{
- $nfound = select($rbits, undef, undef, $timeout); # Wait for response
- $timeout = $finish_time - time(); # Get remaining time
+ $ret = undef;
+ $done = 1;
+ }
+ elsif ($nfound) # A packet is waiting
+ {
+ $from_msg = "";
+ $from_saddr = recv($self->{"fh"}, $from_msg, 1500, UDP_FLAGS)
+ or last; # For example an unreachable host will make recv() fail.
+ ($from_port, $from_ip) = sockaddr_in($from_saddr);
+ if (!$source_verify ||
+ (($from_ip eq $ip) && # Does the packet check out?
+ ($from_port == $self->{"port_num"}) &&
+ ($from_msg eq $msg)))
+ {
+ $ret = 1; # It's a winner
+ $done = 1;
+ }
+ }
+ else # Oops, timed out
+ {
+ $done = 1;
+ }
+ }
+ return $ret;
+}
+
+# Description: Send a TCP SYN packet to host specified.
+sub ping_syn
+{
+ my $self = shift;
+ my $host = shift;
+ my $ip = shift;
+ my $start_time = shift;
+ my $stop_time = shift;
+
+ if ($syn_forking) {
+ return $self->ping_syn_fork($host, $ip, $start_time, $stop_time);
+ }
+
+ my $fh = FileHandle->new();
+ my $saddr = sockaddr_in($self->{"port_num"}, $ip);
+
+ # Create TCP socket
+ if (!socket ($fh, PF_INET, SOCK_STREAM, $self->{"proto_num"})) {
+ croak("tcp socket error - $!");
+ }
+
+ if (defined $self->{"local_addr"} &&
+ !CORE::bind($fh, sockaddr_in(0, $self->{"local_addr"}))) {
+ croak("tcp bind error - $!");
+ }
+
+ if ($self->{'device'}) {
+ setsockopt($fh, SOL_SOCKET, SO_BINDTODEVICE(), pack("Z*", $self->{'device'}))
+ or croak("error binding to device $self->{'device'} $!");
+ }
+
+ # Set O_NONBLOCK property on filehandle
+ my $flags = 0;
+ if (fcntl($fh, F_GETFL, $flags)) {
+ if (!fcntl($fh, F_SETFL, $flags | O_NONBLOCK)) {
+ croak("fcntl F_SETFL: $!");
+ }
+ } else {
+ croak("fcntl F_GETFL: $!");
+ }
+
+ # Attempt the non-blocking connect
+ # by just sending the TCP SYN packet
+ if (connect($fh, $saddr)) {
+ # Non-blocking, yet still connected?
+ # Must have connected very quickly,
+ # or else it wasn't very non-blocking.
+ #warn "WARNING: Nonblocking connect connected anyway? ($^O)";
+ } else {
+ # Error occurred connecting.
+ if ($! == EINPROGRESS) {
+ # The connection is just still in progress.
+ # This is the expected condition.
+ } else {
+ # Just save the error and continue on.
+ # The ack() can check the status later.
+ $self->{"bad"}->{$host} = $!;
+ }
+ }
+
+ my $entry = [ $host, $ip, $fh, $start_time, $stop_time ];
+ $self->{"syn"}->{$fh->fileno} = $entry;
+ if ($self->{"stop_time"} < $stop_time) {
+ $self->{"stop_time"} = $stop_time;
+ }
+ vec($self->{"wbits"}, $fh->fileno, 1) = 1;
+
+ return 1;
+}
+
+sub ping_syn_fork {
+ my ($self, $host, $ip, $start_time, $stop_time) = @_;
+
+ # Buggy Winsock API doesn't allow nonblocking connect.
+ # Hence, if our OS is Windows, we need to create a separate
+ # process to do the blocking connect attempt.
+ my $pid = fork();
+ if (defined $pid) {
+ if ($pid) {
+ # Parent process
+ my $entry = [ $host, $ip, $pid, $start_time, $stop_time ];
+ $self->{"syn"}->{$pid} = $entry;
+ if ($self->{"stop_time"} < $stop_time) {
+ $self->{"stop_time"} = $stop_time;
+ }
+ } else {
+ # Child process
+ my $saddr = sockaddr_in($self->{"port_num"}, $ip);
+
+ # Create TCP socket
+ if (!socket ($self->{"fh"}, PF_INET, SOCK_STREAM, $self->{"proto_num"})) {
+ croak("tcp socket error - $!");
+ }
+
+ if (defined $self->{"local_addr"} &&
+ !CORE::bind($self->{"fh"}, sockaddr_in(0, $self->{"local_addr"}))) {
+ croak("tcp bind error - $!");
+ }
+
+ if ($self->{'device'}) {
+ setsockopt($self->{"fh"}, SOL_SOCKET, SO_BINDTODEVICE(), pack("Z*", $self->{'device'}))
+ or croak("error binding to device $self->{'device'} $!");
+ }
+
+ $!=0;
+ # Try to connect (could take a long time)
+ connect($self->{"fh"}, $saddr);
+ # Notify parent of connect error status
+ my $err = $!+0;
+ my $wrstr = "$$ $err";
+ # Force to 10 chars including \n
+ $wrstr .= " "x(9 - length $wrstr). "\n";
+ syswrite($self->{"fork_wr"}, $wrstr);
+ exit;
+ }
+ } else {
+ # fork() failed?
+ die "fork: $!";
+ }
+ return 1;
+}
- if (!defined($nfound)) # Hmm, a strange error
- {
- $ret = undef;
- $done = 1;
+# Description: Wait for TCP ACK from host specified
+# from ping_syn above. If no host is specified, wait
+# for TCP ACK from any of the hosts in the SYN queue.
+sub ack
+{
+ my $self = shift;
+
+ if ($self->{"proto"} eq "syn") {
+ if ($syn_forking) {
+ my @answer = $self->ack_unfork(shift);
+ return wantarray ? @answer : $answer[0];
+ }
+ my $wbits = "";
+ my $stop_time = 0;
+ if (my $host = shift) {
+ # Host passed as arg
+ if (exists $self->{"bad"}->{$host}) {
+ if (!$self->{"tcp_econnrefused"} &&
+ $self->{"bad"}->{ $host } &&
+ (($! = ECONNREFUSED)>0) &&
+ $self->{"bad"}->{ $host } eq "$!") {
+ # "Connection refused" means reachable
+ # Good, continue
+ } else {
+ # ECONNREFUSED means no good
+ return ();
}
- elsif ($nfound) # A packet is waiting
- {
- $from_msg = "";
- $from_saddr = recv($self->{"fh"}, $from_msg, 1500, $flags)
- or last; # For example an unreachable host will make recv() fail.
- ($from_port, $from_ip) = sockaddr_in($from_saddr);
- if (($from_ip eq $ip) && # Does the packet check out?
- ($from_port == $self->{"port_num"}) &&
- ($from_msg eq $msg))
- {
- $ret = 1; # It's a winner
- $done = 1;
- }
- }
- else # Oops, timed out
- {
- $done = 1;
+ }
+ my $host_fd = undef;
+ foreach my $fd (keys %{ $self->{"syn"} }) {
+ my $entry = $self->{"syn"}->{$fd};
+ if ($entry->[0] eq $host) {
+ $host_fd = $fd;
+ $stop_time = $entry->[4]
+ || croak("Corrupted SYN entry for [$host]");
+ last;
}
+ }
+ croak("ack called on [$host] without calling ping first!")
+ unless defined $host_fd;
+ vec($wbits, $host_fd, 1) = 1;
+ } else {
+ # No $host passed so scan all hosts
+ # Use the latest stop_time
+ $stop_time = $self->{"stop_time"};
+ # Use all the bits
+ $wbits = $self->{"wbits"};
}
- return($ret);
+
+ while ($wbits !~ /^\0*$/) {
+ my $timeout = $stop_time - &time();
+ # Force a minimum of 10 ms timeout.
+ $timeout = 0.01 if $timeout <= 0.01;
+
+ my $winner_fd = undef;
+ my $wout = $wbits;
+ my $fd = 0;
+ # Do "bad" fds from $wbits first
+ while ($wout !~ /^\0*$/) {
+ if (vec($wout, $fd, 1)) {
+ # Wipe it from future scanning.
+ vec($wout, $fd, 1) = 0;
+ if (my $entry = $self->{"syn"}->{$fd}) {
+ if ($self->{"bad"}->{ $entry->[0] }) {
+ $winner_fd = $fd;
+ last;
+ }
+ }
+ }
+ $fd++;
+ }
+
+ if (defined($winner_fd) or my $nfound = select(undef, ($wout=$wbits), undef, $timeout)) {
+ if (defined $winner_fd) {
+ $fd = $winner_fd;
+ } else {
+ # Done waiting for one of the ACKs
+ $fd = 0;
+ # Determine which one
+ while ($wout !~ /^\0*$/ &&
+ !vec($wout, $fd, 1)) {
+ $fd++;
+ }
+ }
+ if (my $entry = $self->{"syn"}->{$fd}) {
+ # Wipe it from future scanning.
+ delete $self->{"syn"}->{$fd};
+ vec($self->{"wbits"}, $fd, 1) = 0;
+ vec($wbits, $fd, 1) = 0;
+ if (!$self->{"tcp_econnrefused"} &&
+ $self->{"bad"}->{ $entry->[0] } &&
+ (($! = ECONNREFUSED)>0) &&
+ $self->{"bad"}->{ $entry->[0] } eq "$!") {
+ # "Connection refused" means reachable
+ # Good, continue
+ } elsif (getpeername($entry->[2])) {
+ # Connection established to remote host
+ # Good, continue
+ } else {
+ # TCP ACK will never come from this host
+ # because there was an error connecting.
+
+ # This should set $! to the correct error.
+ my $char;
+ read($entry->[2],$char,1);
+ # Store the excuse why the connection failed.
+ $self->{"bad"}->{$entry->[0]} = $!;
+ if (!$self->{"tcp_econnrefused"} &&
+ (($! == ECONNREFUSED) ||
+ ($! == EAGAIN && $^O =~ /cygwin/i))) {
+ # "Connection refused" means reachable
+ # Good, continue
+ } else {
+ # No good, try the next socket...
+ next;
+ }
+ }
+ # Everything passed okay, return the answer
+ return wantarray ?
+ ($entry->[0], &time() - $entry->[3], inet_ntoa($entry->[1]))
+ : $entry->[0];
+ } else {
+ warn "Corrupted SYN entry: unknown fd [$fd] ready!";
+ vec($wbits, $fd, 1) = 0;
+ vec($self->{"wbits"}, $fd, 1) = 0;
+ }
+ } elsif (defined $nfound) {
+ # Timed out waiting for ACK
+ foreach my $fd (keys %{ $self->{"syn"} }) {
+ if (vec($wbits, $fd, 1)) {
+ my $entry = $self->{"syn"}->{$fd};
+ $self->{"bad"}->{$entry->[0]} = "Timed out";
+ vec($wbits, $fd, 1) = 0;
+ vec($self->{"wbits"}, $fd, 1) = 0;
+ delete $self->{"syn"}->{$fd};
+ }
+ }
+ } else {
+ # Weird error occurred with select()
+ warn("select: $!");
+ $self->{"syn"} = {};
+ $wbits = "";
+ }
+ }
+ }
+ return ();
}
-# Description: Close the connection unless we are using the tcp
-# protocol, since it will already be closed.
+sub ack_unfork {
+ my ($self,$host) = @_;
+ my $stop_time = $self->{"stop_time"};
+ if ($host) {
+ # Host passed as arg
+ if (my $entry = $self->{"good"}->{$host}) {
+ delete $self->{"good"}->{$host};
+ return ($entry->[0], &time() - $entry->[3], inet_ntoa($entry->[1]));
+ }
+ }
+
+ my $rbits = "";
+ my $timeout;
+
+ if (keys %{ $self->{"syn"} }) {
+ # Scan all hosts that are left
+ vec($rbits, fileno($self->{"fork_rd"}), 1) = 1;
+ $timeout = $stop_time - &time();
+ # Force a minimum of 10 ms timeout.
+ $timeout = 0.01 if $timeout < 0.01;
+ } else {
+ # No hosts left to wait for
+ $timeout = 0;
+ }
+
+ if ($timeout > 0) {
+ my $nfound;
+ while ( keys %{ $self->{"syn"} } and
+ $nfound = select((my $rout=$rbits), undef, undef, $timeout)) {
+ # Done waiting for one of the ACKs
+ if (!sysread($self->{"fork_rd"}, $_, 10)) {
+ # Socket closed, which means all children are done.
+ return ();
+ }
+ my ($pid, $how) = split;
+ if ($pid) {
+ # Flush the zombie
+ waitpid($pid, 0);
+ if (my $entry = $self->{"syn"}->{$pid}) {
+ # Connection attempt to remote host is done
+ delete $self->{"syn"}->{$pid};
+ if (!$how || # If there was no error connecting
+ (!$self->{"tcp_econnrefused"} &&
+ $how == ECONNREFUSED)) { # "Connection refused" means reachable
+ if ($host && $entry->[0] ne $host) {
+ # A good connection, but not the host we need.
+ # Move it from the "syn" hash to the "good" hash.
+ $self->{"good"}->{$entry->[0]} = $entry;
+ # And wait for the next winner
+ next;
+ }
+ return ($entry->[0], &time() - $entry->[3], inet_ntoa($entry->[1]));
+ }
+ } else {
+ # Should never happen
+ die "Unknown ping from pid [$pid]";
+ }
+ } else {
+ die "Empty response from status socket?";
+ }
+ }
+ if (defined $nfound) {
+ # Timed out waiting for ACK status
+ } else {
+ # Weird error occurred with select()
+ warn("select: $!");
+ }
+ }
+ if (my @synners = keys %{ $self->{"syn"} }) {
+ # Kill all the synners
+ kill 9, @synners;
+ foreach my $pid (@synners) {
+ # Wait for the deaths to finish
+ # Then flush off the zombie
+ waitpid($pid, 0);
+ }
+ }
+ $self->{"syn"} = {};
+ return ();
+}
+
+# Description: Tell why the ack() failed
+sub nack {
+ my $self = shift;
+ my $host = shift || croak('Usage> nack($failed_ack_host)');
+ return $self->{"bad"}->{$host} || undef;
+}
+
+# Description: Close the connection.
sub close
{
- my ($self) = @_;
+ my ($self) = @_;
- $self->{"fh"}->close() unless $self->{"proto"} eq "tcp";
+ if ($self->{"proto"} eq "syn") {
+ delete $self->{"syn"};
+ } elsif ($self->{"proto"} eq "tcp") {
+ # The connection will already be closed
+ } else {
+ $self->{"fh"}->close();
+ }
}
Net::Ping - check a remote host for reachability
-$Id: Ping.pm,v 1.11 2001/12/04 02:41:51 rob Exp $
+$Id: Ping.pm,v 1.46 2002/12/02 19:17:09 rob Exp $
=head1 SYNOPSIS
$p->close();
$p = Net::Ping->new("icmp");
+ $p->bind($my_addr); # Specify source interface of pings
foreach $host (@host_array)
{
print "$host is ";
}
undef($p);
+ # Like tcp protocol, but with many hosts
+ $p = Net::Ping->new("syn");
+ $p->{port_num} = getservbyname("http", "tcp");
+ foreach $host (@host_array) {
+ $p->ping($host);
+ }
+ while (($host,$rtt,$ip) = $p->ack) {
+ print "HOST: $host [$ip] ACKed in $rtt seconds.\n";
+ }
+
+ # High precision syntax (requires Time::HiRes)
+ $p = Net::Ping->new();
+ $p->hires();
+ ($ret, $duration, $ip) = $p->ping($host, 5.5);
+ printf("$host [ip: $ip] is alive (packet return time: %.2f ms)\n", 1000 * $duration)
+ if $ret;
+ $p->close();
+
# For backward compatibility
print "$host is alive.\n" if pingecho($host);
parameters, a variable number of hosts may be pinged multiple
times and then the connection is closed.
-You may choose one of four different protocols to use for the
-ping. The "udp" protocol is the default. Note that a live remote host
+You may choose one of six different protocols to use for the
+ping. The "tcp" protocol is the default. Note that a live remote host
may still fail to be pingable by one or more of these protocols. For
-example, www.microsoft.com is generally alive but not pingable.
+example, www.microsoft.com is generally alive but not "icmp" pingable.
With the "tcp" protocol the ping() method attempts to establish a
connection to the remote host's echo port. If the connection is
accurate results. If C<Net::Ping::External> if not installed on your
system, specifying the "external" protocol will result in an error.
+If the "syn" protocol is specified, the ping() method will only
+send a TCP SYN packet to the remote host then immediately return.
+If the syn packet was sent successfully, it will return a true value,
+otherwise it will return false. NOTE: Unlike the other protocols,
+the return value does NOT determine if the remote host is alive or
+not since the full TCP three-way handshake may not have completed
+yet. The remote host is only considered reachable if it receives
+a TCP ACK within the timeout specifed. To begin waiting for the
+ACK packets, use the ack() method as explained below. Use the
+"syn" protocol instead the "tcp" protocol to determine reachability
+of multiple destinations simultaneously by sending parallel TCP
+SYN packets. It will not block while testing each remote host.
+demo/fping is provided in this distribution to demonstrate the
+"syn" protocol as an example.
+This protocol does not require any special privileges.
+
=head2 Functions
=over 4
-=item Net::Ping->new([$proto [, $def_timeout [, $bytes]]]);
+=item Net::Ping->new([$proto [, $def_timeout [, $bytes [, $device ]]]]);
Create a new ping object. All of the parameters are optional. $proto
specifies the protocol to use when doing a ping. The current choices
-are "tcp", "udp" or "icmp". The default is "udp".
+are "tcp", "udp", "icmp", "stream", "syn", or "external".
+The default is "tcp".
If a default timeout ($def_timeout) in seconds is provided, it is used
when a timeout is not given to the ping() method (below). The timeout
otherwise. The maximum number of data bytes that can be specified is
1024.
+If $device is given, this device is used to bind the source endpoint
+before sending the ping packet. I beleive this only works with
+superuser privileges and with udp and icmp protocols at this time.
+
=item $p->ping($host [, $timeout]);
Ping the remote host and wait for a response. $host can be either the
hostname or the IP number of the remote host. The optional timeout
must be greater than 0 seconds and defaults to whatever was specified
-when the ping object was created. If the hostname cannot be found or
-there is a problem with the IP number, undef is returned. Otherwise,
-1 is returned if the host is reachable and 0 if it is not. For all
-practical purposes, undef and 0 and can be treated as the same case.
+when the ping object was created. Returns a success flag. If the
+hostname cannot be found or there is a problem with the IP number, the
+success flag returned will be undef. Otherwise, the success flag will
+be 1 if the host is reachable and 0 if it is not. For most practical
+purposes, undef and 0 and can be treated as the same case. In array
+context, the elapsed time as well as the string form of the ip the
+host resolved to are also returned. The elapsed time value will
+be a float, as retuned by the Time::HiRes::time() function, if hires()
+has been previously called, otherwise it is returned as an integer.
+
+=item $p->source_verify( { 0 | 1 } );
+
+Allows source endpoint verification to be enabled or disabled.
+This is useful for those remote destinations with multiples
+interfaces where the response may not originate from the same
+endpoint that the original destination endpoint was sent to.
+This only affects udp and icmp protocol pings.
+
+This is enabled by default.
+
+=item $p->tcp_service_check( { 0 | 1 } );
+
+Set whether or not the tcp connect behavior should enforce
+remote service availability as well as reachability. Normally,
+if the remote server reported ECONNREFUSED, it must have been
+reachable because of the status packet that it reported.
+With this option enabled, the full three-way tcp handshake
+must have been established successfully before it will
+claim it is reachable. NOTE: It still does nothing more
+than connect and disconnect. It does not speak any protocol
+(i.e., HTTP or FTP) to ensure the remote server is sane in
+any way. The remote server CPU could be grinding to a halt
+and unresponsive to any clients connecting, but if the kernel
+throws the ACK packet, it is considered alive anyway. To
+really determine if the server is responding well would be
+application specific and is beyond the scope of Net::Ping.
+
+This only affects "tcp" and "syn" protocols.
+
+This is disabled by default.
+
+=item $p->hires( { 0 | 1 } );
+
+Causes this module to use Time::HiRes module, allowing milliseconds
+to be returned by subsequent calls to ping().
+
+This is disabled by default.
+
+=item $p->bind($local_addr);
+
+Sets the source address from which pings will be sent. This must be
+the address of one of the interfaces on the local host. $local_addr
+may be specified as a hostname or as a text IP address such as
+"192.168.1.1".
+
+If the protocol is set to "tcp", this method may be called any
+number of times, and each call to the ping() method (below) will use
+the most recent $local_addr. If the protocol is "icmp" or "udp",
+then bind() must be called at most once per object, and (if it is
+called at all) must be called before the first call to ping() for that
+object.
=item $p->open($host);
-When you are using the stream protocol, this call pre-opens the
+When you are using the "stream" protocol, this call pre-opens the
tcp socket. It's only necessary to do this if you want to
provide a different timeout when creating the connection, or
remove the overhead of establishing the connection from the
This call simply does nothing if you are using any protocol other
than stream.
-=item $p->open($host);
-
-When you are using the stream protocol, this call pre-opens the
-tcp socket. It's only necessary to do this if you want to
-provide a different timeout when creating the connection, or
-remove the overhead of establishing the connection from the
-first ping. If you don't call C<open()>, the connection is
-automatically opened the first time C<ping()> is called.
-This call simply does nothing if you are using any protocol other
-than stream.
+=item $p->ack( [ $host ] );
+
+When using the "syn" protocol, use this method to determine
+the reachability of the remote host. This method is meant
+to be called up to as many times as ping() was called. Each
+call returns the host (as passed to ping()) that came back
+with the TCP ACK. The order in which the hosts are returned
+may not necessarily be the same order in which they were
+SYN queued using the ping() method. If the timeout is
+reached before the TCP ACK is received, or if the remote
+host is not listening on the port attempted, then the TCP
+connection will not be established and ack() will return
+undef. In list context, the host, the ack time, and the
+dotted ip string will be returned instead of just the host.
+If the optional $host argument is specified, the return
+value will be partaining to that host only.
+This call simply does nothing if you are using any protocol
+other than syn.
+
+=item $p->nack( $failed_ack_host );
+
+The reason that host $failed_ack_host did not receive a
+valid ACK. Useful to find out why when ack( $fail_ack_host )
+returns a false value.
=item $p->close();
separate module to be written which understands all of the different
kinds of ICMP packets.
-=head1 AUTHOR(S)
+=head1 INSTALL
+
+The latest source tree is available via cvs:
+
+ cvs -z3 -q -d :pserver:anonymous@cvs.roobik.com.:/usr/local/cvsroot/freeware checkout Net-Ping
+ cd Net-Ping
+
+The tarball can be created as follows:
+
+ perl Makefile.PL ; make ; make dist
+
+The latest Net::Ping release can be found at CPAN:
+
+ $CPAN/modules/by-module/Net/
+
+1) Extract the tarball
+
+ gtar -zxvf Net-Ping-xxxx.tar.gz
+ cd Net-Ping-xxxx
+
+2) Build:
+
+ make realclean
+ perl Makefile.PL
+ make
+ make test
- Current maintainer Net::Ping base code:
+3) Install
+
+ make install
+
+Or install it RPM Style:
+
+ rpm -ta SOURCES/Net-Ping-xxxx.tar.gz
+
+ rpm -ih RPMS/noarch/perl-Net-Ping-xxxx.rpm
+
+=head1 AUTHORS
+
+ Current maintainer:
+ bbb@cpan.org (Rob Brown)
+
+ External protocol:
colinm@cpan.org (Colin McMillen)
Stream protocol:
Original Net::Ping author:
mose@ns.ccsn.edu (Russell Mosemann)
- Compatibility porting:
- bbb@cpan.org (Rob Brown)
-
=head1 COPYRIGHT
+Copyright (c) 2002, Rob Brown. All rights reserved.
+
Copyright (c) 2001, Colin McMillen. All rights reserved.
-Copyright (c) 2001, Rob Brown. All rights reserved.
This program is free software; you may redistribute it and/or
modify it under the same terms as Perl itself.