Upgrade to Net::Ping 2.28, from Rob Brown.
Jarkko Hietaniemi [Sat, 8 Feb 2003 08:35:06 +0000 (08:35 +0000)]
p4raw-id: //depot/perl@18671

MANIFEST
lib/Net/Ping.pm
lib/Net/Ping/Changes
lib/Net/Ping/README
lib/Net/Ping/t/190_alarm.t [new file with mode: 0644]
lib/Net/Ping/t/300_ping_stream.t
lib/Net/Ping/t/400_ping_syn.t
lib/Net/Ping/t/410_syn_host.t
lib/Net/Ping/t/500_ping_icmp.t [new file with mode: 0644]

index 0488f7f..066d2ca 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -1353,12 +1353,14 @@ lib/Net/Ping/t/120_udp_inst.t   Ping Net::Ping
 lib/Net/Ping/t/130_tcp_inst.t  Ping Net::Ping
 lib/Net/Ping/t/140_stream_inst.t       Ping Net::Ping
 lib/Net/Ping/t/150_syn_inst.t  Ping Net::Ping
+lib/Net/Ping/t/190_alarm.t     Ping Net::Ping
 lib/Net/Ping/t/200_ping_tcp.t  Ping Net::Ping
 lib/Net/Ping/t/250_ping_hires.t        Ping Net::Ping
 lib/Net/Ping/t/300_ping_stream.t       Ping Net::Ping
 lib/Net/Ping/t/400_ping_syn.t  Ping Net::Ping
 lib/Net/Ping/t/410_syn_host.t  Ping Net::Ping
 lib/Net/Ping/t/450_service.t   Ping Net::Ping
+lib/Net/Ping/t/500_ping_icmp.t Ping Net::Ping
 lib/Net/POP3.pm                        libnet
 lib/Net/protoent.pm            By-name interface to Perl's builtin getproto*
 lib/Net/protoent.t             See if Net::protoent works
index e27692f..3312ea3 100644 (file)
@@ -1,7 +1,5 @@
 package Net::Ping;
 
-# $Id: Ping.pm,v 1.46 2002/12/02 19:17:09 rob Exp $
-
 require 5.002;
 require Exporter;
 
@@ -17,7 +15,7 @@ use Carp;
 
 @ISA = qw(Exporter);
 @EXPORT = qw(pingecho);
-$VERSION = "2.26";
+$VERSION = "2.28";
 
 # Constants
 
@@ -247,6 +245,26 @@ sub time
   return $hires ? Time::HiRes::time() : CORE::time();
 }
 
+# Description: Sets or clears the O_NONBLOCK flag on a file handle.
+sub socket_blocking_mode
+{
+  my ($self,
+      $fh,              # the file handle whose flags are to be modified
+      $block) = @_;     # if true then set the blocking
+                        # mode (clear O_NONBLOCK), otherwise
+                        # set the non-blocking mode (set O_NONBLOCK)
+
+  my $flags;
+  if ($flags = fcntl($fh, F_GETFL, 0)) {
+    $flags = $block ? ($flags & ~O_NONBLOCK) : ($flags | O_NONBLOCK);
+    if (!fcntl($fh, F_SETFL, $flags)) {
+      croak("fcntl F_SETFL: $!");
+    }
+  } else {
+    croak("fcntl F_GETFL: $!");
+  }
+}
+
 # Description: Ping a host name or IP number with an optional timeout.
 # First lookup the host, and return undef if it is not found.  Otherwise
 # perform the specific ping method based on the protocol.  Return the
@@ -310,7 +328,7 @@ sub ping_external {
 
 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 ICMP_STRUCT    => "C2 n3 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
@@ -352,6 +370,9 @@ sub ping_icmp
               $checksum, $self->{"pid"}, $self->{"seq"}, $self->{"data"});
   $len_msg = length($msg);
   $saddr = sockaddr_in(ICMP_PORT, $ip);
+  $self->{"from_ip"} = undef;
+  $self->{"from_type"} = undef;
+  $self->{"from_subcode"} = undef;
   send($self->{"fh"}, $msg, ICMP_FLAGS, $saddr); # Send the message
 
   $rbits = "";
@@ -373,28 +394,36 @@ sub ping_icmp
       $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
+      ($from_type, $from_subcode) = unpack("C2", substr($recv_msg, 20, 2));
+      if ($from_type == ICMP_ECHOREPLY){
+        ($from_pid, $from_seq) = unpack("n3", substr($recv_msg, 24, 4));
+      } else {
+        ($from_pid, $from_seq) = unpack("n3", substr($recv_msg, 52, 4));
+      }
+      $self->{"from_ip"} = $from_ip;
+      $self->{"from_type"} = $from_type;
+      $self->{"from_subcode"} = $from_subcode;
+      if (($from_pid == $self->{"pid"}) && # Does the packet check out?
+          ($from_seq == $self->{"seq"})) {
+        if ($from_type == ICMP_ECHOREPLY){
+          $ret = 1;
+        }
         $done = 1;
       }
-    }
-    else                                # Oops, timed out
-    {
+    } else {     # Oops, timed out
       $done = 1;
     }
   }
   return $ret;
 }
 
+sub icmp_result {
+  my ($self) = @_;
+  my $ip = $self->{"from_ip"} || "";
+  $ip = "\0\0\0\0" unless 4 == length $ip;
+  return (inet_ntoa($ip),($self->{"from_type"} || 0), ($self->{"from_subcode"} || 0));
+}
+
 # Description:  Do a checksum on the message.  Basically sum all of
 # the short words and fold the high order bits into the low order bits.
 
@@ -412,7 +441,7 @@ sub checksum
   $len_msg = length($msg);
   $num_short = int($len_msg / 2);
   $chk = 0;
-  foreach $short (unpack("S$num_short", $msg))
+  foreach $short (unpack("n$num_short", $msg))
   {
     $chk += $short;
   }                                           # Add the odd byte in
@@ -441,7 +470,7 @@ sub ping_tcp
   my ($ret                # The return value
       );
 
-  $@ = ""; $! = 0;
+  $! = 0;
   $ret = $self -> tcp_connect( $ip, $timeout);
   if (!$self->{"tcp_econnrefused"} &&
       $! == ECONNREFUSED) {
@@ -476,32 +505,82 @@ sub tcp_connect
     }
   };
   my $do_connect = sub {
-    eval {
-      die $! unless connect($self->{"fh"}, $saddr);
-      $self->{"ip"} = $ip;
-      $ret = 1;
-    };
-    $ret;
+    $self->{"ip"} = $ip;
+    return ($ret = connect($self->{"fh"}, $saddr));
   };
+  my $do_connect_nb = sub {
+    # Set O_NONBLOCK property on filehandle
+    $self->socket_blocking_mode($self->{"fh"}, 0);
+
+    # start the connection attempt
+    if (!connect($self->{"fh"}, $saddr)) {
+      if ($! == ECONNREFUSED) {
+        $ret = 1 unless $self->{"tcp_econnrefused"};
+      } else {
+        # EINPROGRESS is the expected error code after a connect()
+        # on a non-blocking socket
+        croak("tcp connect error - $!") if $! != EINPROGRESS;
+
+        # wait for connection completion
+        my ($wbits, $wout);
+        $wout = $wbits = "";
+        vec($wbits, $self->{"fh"}->fileno, 1) = 1;
+
+        my $nfound = select(undef, ($wout = $wbits), undef, $timeout);
+        warn("select: $!") unless defined $nfound;
+
+        if ($nfound && vec($wout, $self->{"fh"}->fileno, 1)) {
+          # the socket is ready for writing so the connection
+          # attempt completed. test whether the connection
+          # attempt was successful or not
+
+          if (getpeername($self->{"fh"})) {
+            # Connection established to remote host
+            $ret = 1;
+          } else {
+            # TCP ACK will never come from this host
+            # because there was an error connecting.
 
-  if ($^O =~ /Win32/i) {
+            # This should set $! to the correct error.
+            my $char;
+            sysread($self->{"fh"},$char,1);
+            $! = ECONNREFUSED if ($! == EAGAIN && $^O =~ /cygwin/i);
 
-    # Buggy Winsock API doesn't allow us to use alarm() calls.
+            $ret = 1 if (!$self->{"tcp_econnrefused"}
+                         && $! == ECONNREFUSED);
+          }
+        } else {
+          # the connection attempt timed out
+        }
+      }
+    } else {
+      # Connection established to remote host
+      $ret = 1;
+    }
+
+    # Unset O_NONBLOCK property on filehandle
+    $self->socket_blocking_mode($self->{"fh"}, 1);
+    $self->{"ip"} = $ip;
+    return $ret;
+  };
+
+  if ($syn_forking) {
+    # 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.
 
     $| = 1; # Clear buffer prior to fork to prevent duplicate flushing.
-    my $pid = fork;
-    if (!$pid) {
-      if (!defined $pid) {
+    $self->{'tcp_chld'} = fork;
+    if (!$self->{'tcp_chld'}) {
+      if (!defined $self->{'tcp_chld'}) {
         # Fork did not work
-        warn "Win32 Fork error: $!";
+        warn "Fork error: $!";
         return 0;
       }
       &{ $do_socket }();
 
       # Try a slow blocking connect() call
-      # and report the status to the pipe.
+      # and report the status to the parent.
       if ( &{ $do_connect }() ) {
         $self->{"fh"}->close();
         # No error
@@ -516,45 +595,55 @@ sub tcp_connect
 
     my $patience = &time() + $timeout;
 
-    my ($child);
-    $? = 0;
+    my ($child, $child_errno);
+    $? = 0; $child_errno = 0;
     # Wait up to the timeout
     # And clean off the zombie
     do {
-      $child = waitpid($pid, &WNOHANG());
-      $! = $? >> 8;
-      $@ = $!;
+      $child = waitpid($self->{'tcp_chld'}, &WNOHANG());
+      $child_errno = $? >> 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 }();
+    } while &time() < $patience && $child != $self->{'tcp_chld'};
+
+    if ($child == $self->{'tcp_chld'}) {
+      if ($self->{"proto"} eq "stream") {
+        # We need the socket connected here, in parent
+        # Should be safe to connect because the child finished
+        # within the timeout
+        &{ $do_connect }();
+      }
     } else {
       # Time must have run out.
-      $@ = "Timed out!";
       # Put that choking client out of its misery
-      kill "KILL", $pid;
+      kill "KILL", $self->{'tcp_chld'};
       # Clean off the zombie
-      waitpid($pid, 0);
+      waitpid($self->{'tcp_chld'}, 0);
       $ret = 0;
     }
-  } else { # Win32
+    delete $self->{'tcp_chld'};
+    $! = $child_errno;
+  } else {
     # Otherwise don't waste the resources to fork
 
     &{ $do_socket }();
 
-    local $SIG{'ALRM'} = sub { die "Timed out!"; };
-    my $old = alarm($timeout);   # Interrupt connect() if we have to
-
-    &{ $do_connect }();
-    alarm($old);
+    &{ $do_connect_nb }();
   }
 
   return $ret;
 }
 
+sub DESTROY {
+  my $self = shift;
+  if ($self->{'proto'} eq 'tcp' &&
+      $self->{'tcp_chld'}) {
+    # Put that choking client out of its misery
+    kill "KILL", $self->{'tcp_chld'};
+    # Clean off the zombie
+    waitpid($self->{'tcp_chld'}, 0);
+  }
+}
+
 # This writes the given string to the socket and then reads it
 # back.  It returns 1 on success, 0 on failure.
 sub tcp_echo
@@ -582,7 +671,7 @@ sub tcp_echo
       if(select($rin, $rout, undef, ($time + $timeout) - &time())) {
 
         if($rout && vec($rout,$self->{"fh"}->fileno(),1)) {
-          my $num = syswrite($self->{"fh"}, $wrstr);
+          my $num = syswrite($self->{"fh"}, $wrstr, length $wrstr);
           if($num) {
             # If it was a partial write, update and try again.
             $wrstr = substr($wrstr,$num);
@@ -764,14 +853,7 @@ sub ping_syn
   }
 
   # 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: $!");
-  }
+  $self->socket_blocking_mode($fh, 0);
 
   # Attempt the non-blocking connect
   # by just sending the TCP SYN packet
@@ -842,9 +924,9 @@ sub ping_syn_fork {
       # 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);
+      # Force to 16 chars including \n
+      $wrstr .= " "x(15 - length $wrstr). "\n";
+      syswrite($self->{"fork_wr"}, $wrstr, length $wrstr);
       exit;
     }
   } else {
@@ -903,7 +985,7 @@ sub ack
       $wbits = $self->{"wbits"};
     }
 
-    while ($wbits !~ /^\0*$/) {
+    while ($wbits !~ /^\0*\z/) {
       my $timeout = $stop_time - &time();
       # Force a minimum of 10 ms timeout.
       $timeout = 0.01 if $timeout <= 0.01;
@@ -912,7 +994,7 @@ sub ack
       my $wout = $wbits;
       my $fd = 0;
       # Do "bad" fds from $wbits first
-      while ($wout !~ /^\0*$/) {
+      while ($wout !~ /^\0*\z/) {
         if (vec($wout, $fd, 1)) {
           # Wipe it from future scanning.
           vec($wout, $fd, 1) = 0;
@@ -933,7 +1015,7 @@ sub ack
           # Done waiting for one of the ACKs
           $fd = 0;
           # Determine which one
-          while ($wout !~ /^\0*$/ &&
+          while ($wout !~ /^\0*\z/ &&
                  !vec($wout, $fd, 1)) {
             $fd++;
           }
@@ -958,7 +1040,7 @@ sub ack
 
             # This should set $! to the correct error.
             my $char;
-            read($entry->[2],$char,1);
+            sysread($entry->[2],$char,1);
             # Store the excuse why the connection failed.
             $self->{"bad"}->{$entry->[0]} = $!;
             if (!$self->{"tcp_econnrefused"} &&
@@ -1032,7 +1114,7 @@ sub ack_unfork {
     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)) {
+      if (!sysread($self->{"fork_rd"}, $_, 16)) {
         # Socket closed, which means all children are done.
         return ();
       }
@@ -1113,7 +1195,7 @@ __END__
 
 Net::Ping - check a remote host for reachability
 
-$Id: Ping.pm,v 1.46 2002/12/02 19:17:09 rob Exp $
+$Id: Ping.pm,v 1.69 2003/01/23 17:21:29 rob Exp $
 
 =head1 SYNOPSIS
 
@@ -1182,7 +1264,7 @@ With the "tcp" protocol the ping() method attempts to establish a
 connection to the remote host's echo port.  If the connection is
 successfully established, the remote host is considered reachable.  No
 data is actually echoed.  This protocol does not require any special
-privileges but has higher overhead than the other two protocols.
+privileges but has higher overhead than the "udp" and "icmp" protocols.
 
 Specifying the "udp" protocol causes the ping() method to send a udp
 packet to the remote host's echo port.  If the echoed packet is
@@ -1371,13 +1453,6 @@ version of Net::Ping.
 
 =back
 
-=head1 WARNING
-
-pingecho() or a ping object with the tcp protocol use alarm() to
-implement the timeout.  So, don't use alarm() in your program while
-you are using pingecho() or a ping object with the tcp protocol.  The
-udp and icmp protocols do not use alarm() to implement the timeout.
-
 =head1 NOTES
 
 There will be less network overhead (and some efficiency in your
@@ -1464,7 +1539,7 @@ Or install it RPM Style:
 
 =head1 COPYRIGHT
 
-Copyright (c) 2002, Rob Brown.  All rights reserved.
+Copyright (c) 2002-2003, Rob Brown.  All rights reserved.
 
 Copyright (c) 2001, Colin McMillen.  All rights reserved.
 
index d8dad16..01454ac 100644 (file)
@@ -1,6 +1,106 @@
 CHANGES
 -------
 
+2.28  Jan 23 18:00 2003
+       - No new features.  Bug fixes only.
+       - Fixed ICMP_STRUCT to work on Big Endian platforms.
+         Thanks to danb@thelittlemacshop.com (Dan Buettner)
+         for testing on Mac OS X 10.2.3 and many others
+         for testing on Big Endian boxes.
+       - Not do binmode(). Causes more problems than helps.
+       - Perl 5.004 compatibility fixes (Spot by Honza).
+
+2.27  Jan 15 23:00 2003
+       - Patch by slebedev@iwl.net (Sergey Lebedev):
+       - 1) Fixed response packet parsing offsets in ping_icmp.
+       - 2) Added icmp_result method.
+       - Patch by radu@netsoft.ro (Radu Greab):
+       - 1) Changed ping_tcp() to use non-blocking connect
+         instead of alarm() interface in order to avoid
+         conflicts with user applications.
+       - 2) Also get rid of all eval {} code in ping_tcp
+         in order to avoid catching SIGALRM trigger and
+         to avoid conflicts with other evals.
+       - 3) Avoid ioctl() syscall for more accurate error
+         detection on non-blocking tcp connects.
+       - 4) Fix fcntl() syntax usage.
+       - Patch by adelton@fi.muni.cz (Honza Pazdziora):
+       - 1) Fix icmp request pack code to be more platform
+         independent regardless of Big/Little Endian.
+       - 2) Use binmode for filehandle in case perl 5.8.0
+         tries to dink with the data stream.
+       - Other changes by Rob Brown:
+       - Fixed ack() failures under certain rare conditions.
+       - Use more appropriate \z instead of $ in regex.
+       - Resolved Cygwin "make test" problems reported by
+         h.m.brand@hccnet.nl (H.Merijn Brand).
+       - Add sending a real ICMP packet in the test suite.
+       - Add Socket to PREREQ_PM (missing on some boxes?)
+       - Adjust syn_forking IPC pipe for fatter Win32 pids.
+       - Better handling of alarm() in test suite for Win32.
+       - Add a DESTROY method to reduce chances of
+         lingering connect-choking children.
+
+2.26  Dec 02 12:00 2002
+       - More compatibility fixes.
+       - Thanks for Solaris bug reports:
+         Paul.Gaborit@enstimac.fr (Paul Gaborit)
+         Jost.Krieger@ruhr-uni-bochum.de (Jost Krieger)
+       - Thanks for Solaris testing box:
+         Gunther.Heintzen@rrze.uni-erlangen.de (Gunther Heintzen)
+       - Solaris ENOTCONN select() for write choke bug.
+       - Thanks for Cygwin bug reports:
+         h.m.brand@hccnet.nl (H.Merijn Brand)
+       - Cygwin "EAGAIN instead of ECONNREFUSED" buttwag.
+
+2.25  Nov 19 12:00 2002
+       - Handle condition where O_NONBLOCK tcp connects
+         immediately fail without EINPROGRESS
+         (certain platforms or SMP optimizations).
+
+2.24  Oct 21 22:00 2002
+       - Compatibility fixes.
+       - Avoid using 127.1.1.1 and 127.2.2.2 because
+         it breaks on some platforms (Irix).
+       - Handle condition where nonblocking tcp connects
+         immediately connect on some platforms
+         (solaris and freebsd) and to be SMP safer.
+       - Win32 $p->ack( $host ) method should now work.
+       - Add ack( $host ) test cases to test suite.
+
+2.23  Oct 18 22:00 2002
+       - Fix ack() fd "each" detection bug.
+       - Add nack() method for OO interface to the
+         reason why the ack() failed.
+       - Fix premature "Timed out" side effect when a
+         different specified ack( $host ) fails.
+       - IO::Socket::INET ephemeral port buttwag
+         hack for the t/450_service.t test.
+       - Documental changes.
+
+2.22  Oct 17 16:00 2002
+       - Add $p->tcp_service_check() method to enforce
+         remote tcp service availability checking.
+         Patch by jef@linuxbe.org (Jean-Francois Dive).
+       - Changed default behavior of "syn" protocol to
+         disabled tcp_service_check instead of enabled.
+       - Win32 compatibility changes ("syn" protocol).
+       - Increase timeouts for tests in case client or
+         server network(s) are busy.
+
+2.21  Oct 14 12:00 2002
+       - Preserve/restore ALRM settings for tcp mode pings.
+         Spot by d@niel-berlin.de (Daniel Berlin)
+       - Can now select device for udp and icmp protocols.
+         Patch by sarfata@altern.org (Thomas Sarlandie).
+       - Add new "syn" protocol to allow for mass parallel
+         (syncronous) TCP service reachability checking.
+       - Add ack() method to utilize non-blocking connect
+         (SYN/ACK) feature of the "syn" protocol.
+       - Add demo/fping script as a "syn" demonstration.
+       - Compatibiliy patches for cygwin.
+         Spot by frazee.23@osu.edu (Joseph Frazee)
+
 2.20  Jun 20 10:00 2002
        - Perl 5.8.0 compatibility stuff.
          Spot by dcd@tc.fluke.com (David Dyck).
index 2dc4b95..38300c1 100644 (file)
@@ -1,7 +1,7 @@
 NAME
     Net::Ping - check a remote host for reachability
 
-    $Id: Ping.pm,v 1.6 2002/06/19 15:23:48 rob Exp $
+    $Id: Ping.pm,v 1.69 2003/01/23 17:21:29 rob Exp $
 
 SYNOPSIS
         use Net::Ping;
@@ -32,6 +32,16 @@ SYNOPSIS
         }
         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();
@@ -49,16 +59,16 @@ DESCRIPTION
     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 may still
+    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.
+    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
     successfully established, the remote host is considered reachable. No
     data is actually echoed. This protocol does not require any special
-    privileges but has higher overhead than the other two protocols.
+    privileges but has higher overhead than the "udp" and "icmp" protocols.
 
     Specifying the "udp" protocol causes the ping() method to send a udp
     packet to the remote host's echo port. If the echoed packet is received
@@ -77,18 +87,34 @@ DESCRIPTION
     or that the program be setuid to root.
 
     If the "external" protocol is specified, the ping() method attempts to
-    use the `Net::Ping::External' module to ping the remote host.
-    `Net::Ping::External' interfaces with your system's default `ping'
+    use the "Net::Ping::External" module to ping the remote host.
+    "Net::Ping::External" interfaces with your system's default "ping"
     utility to perform the ping, and generally produces relatively accurate
-    results. If `Net::Ping::External' if not installed on your system,
+    results. If "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.
+
   Functions
 
-    Net::Ping->new([$proto [, $def_timeout [, $bytes]]]);
+    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
@@ -102,6 +128,10 @@ DESCRIPTION
         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.
+
     $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
@@ -111,10 +141,11 @@ DESCRIPTION
         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 is 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.
+        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.
 
     $p->source_verify( { 0 | 1 } );
         Allows source endpoint verification to be enabled or disabled. This
@@ -125,6 +156,25 @@ DESCRIPTION
 
         This is enabled by default.
 
+    $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.
+
     $p->hires( { 0 | 1 } );
         Causes this module to use Time::HiRes module, allowing milliseconds
         to be returned by subsequent calls to ping().
@@ -145,14 +195,35 @@ DESCRIPTION
         that object.
 
     $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
+        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 `open()', the connection is automatically opened the
-        first time `ping()' is called. This call simply does nothing if you
+        don't call "open()", the connection is automatically opened the
+        first time "ping()" is called. This call simply does nothing if you
         are using any protocol other than stream.
 
+    $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.
+
+    $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.
+
     $p->close();
         Close the network connection for this ping object. The network
         connection is also closed by "undef $p". The network connection is
@@ -167,12 +238,6 @@ DESCRIPTION
         ping() method. This subroutine is obsolete and may be removed in a
         future version of Net::Ping.
 
-WARNING
-    pingecho() or a ping object with the tcp protocol use alarm() to
-    implement the timeout. So, don't use alarm() in your program while you
-    are using pingecho() or a ping object with the tcp protocol. The udp and
-    icmp protocols do not use alarm() to implement the timeout.
-
 NOTES
     There will be less network overhead (and some efficiency in your
     program) if you specify either the udp or the icmp protocol. The tcp
@@ -204,7 +269,7 @@ NOTES
 INSTALL
     The latest source tree is available via cvs:
 
-      cvs -z3 -q -d :pserver:anonymous@cvs.roobik.com.:/usr/local/cvsroot/freeware co Net-Ping
+      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:
@@ -255,7 +320,7 @@ AUTHORS
         mose@ns.ccsn.edu (Russell Mosemann)
 
 COPYRIGHT
-    Copyright (c) 2002, Rob Brown. All rights reserved.
+    Copyright (c) 2002-2003, Rob Brown. All rights reserved.
 
     Copyright (c) 2001, Colin McMillen. All rights reserved.
 
diff --git a/lib/Net/Ping/t/190_alarm.t b/lib/Net/Ping/t/190_alarm.t
new file mode 100644 (file)
index 0000000..513d96e
--- /dev/null
@@ -0,0 +1,50 @@
+# Test to make sure alarm / SIGALM does not interfere
+# with Net::Ping.  (This test was derived to ensure
+# compatibility with the "spamassassin" utility.)
+# Based on code written by radu@netsoft.ro (Radu Greab).
+
+BEGIN {
+  unless (eval "require Socket") {
+    print "1..0 \# Skip: no Socket\n";
+    exit;
+  }
+  unless (eval {alarm 0; 1;}) {
+    print "1..0 \# Skip: alarm borks on $^O $^X $] ?\n";
+    exit;
+  }
+}
+
+use strict;
+use Test;
+use Net::Ping;
+
+plan tests => 6;
+
+# Everything compiled
+ok 1;
+
+eval {
+  my $timeout = 11;
+
+  ok 1; # In eval
+  local $SIG{ALRM} = sub { die "alarm works" };
+  ok 1; # SIGALRM can be set on this platform
+  alarm $timeout;
+  ok 1; # alarm() can be set on this platform
+
+  my $start = time;
+  while (1) {
+    my $ping = Net::Ping->new("tcp", 2);
+    # It does not matter if alive or not
+    $ping->ping("127.0.0.1");
+    $ping->ping("172.29.249.249");
+    die "alarm failed" if time > $start + $timeout + 1;
+  }
+};
+# Got out of "infinite loop" okay
+ok 1;
+
+# Make sure it died for a good excuse
+ok $@ =~ /alarm works/ or die $@;
+
+alarm 0; # Reset alarm
index 270650a..ddc36a2 100644 (file)
@@ -11,7 +11,14 @@ BEGIN {
     print "1..0 \# Skip: no Socket\n";
     exit;
   }
-  unless (getservbyname('echo', 'udp')) {
+  if (my $port = getservbyname('echo', 'tcp')) {
+    socket(*ECHO, &Socket::PF_INET(), &Socket::SOCK_STREAM(), (getprotobyname 'tcp')[2]);
+    unless (connect(*ECHO, scalar &Socket::sockaddr_in($port, &Socket::inet_aton("localhost")))) {
+      print "1..0 \# Skip: loopback echo service is off ($!)\n";
+      exit;
+    }
+    close (*ECHO);
+  } else {
     print "1..0 \# Skip: no echo port\n";
     exit;
   }
@@ -21,11 +28,12 @@ BEGIN {
 #
 # NOTE:
 #   The echo service must be enabled on localhost
-#   to really test the stream protocol ping.
+#   to really test the stream protocol ping.  See
+#   the end of this document on how to enable it.
 
 use Test;
 use Net::Ping;
-plan tests => 12;
+plan tests => 22;
 
 my $p = new Net::Ping "stream";
 
@@ -33,16 +41,12 @@ my $p = new Net::Ping "stream";
 ok !!$p;
 
 # Attempt to connect to the echo port
-if ($p -> ping("localhost")) {
-  ok 1;
-  # Try several pings while it is connected
-  for (1..10) {
-    ok $p -> ping("localhost");
-  }
-} else {
-  # Echo port is off, skip the tests
-  for (2..12) { skip "Local echo port is off", 1; }
-  exit;
+ok ($p -> ping("localhost"));
+
+# Try several pings while it is connected
+for (1..20) {
+  select (undef,undef,undef,0.1);
+  ok $p -> ping("localhost");
 }
 
 __END__
@@ -52,16 +56,19 @@ Just create the following file before restarting xinetd:
 
 /etc/xinetd.d/echo:
 
-# description: echo service
+# description: An echo server.
 service echo
 {
-        socket_type             = stream
-        wait                    = no
-        user                    = root
-        server                  = /bin/cat
-        disable                 = no
+        type            = INTERNAL
+        id              = echo-stream
+        socket_type     = stream
+        protocol        = tcp
+        user            = root
+        wait            = no
+        disable         = no
 }
 
+
 Or if you are using inetd, before restarting, add
 this line to your /etc/inetd.conf:
 
index 29022d2..ae89800 100644 (file)
@@ -55,7 +55,13 @@ plan tests => ((keys %{ $webs }) * 2 + 3);
 # Everything loaded fine
 ok 1;
 
-alarm(50);
+my $can_alarm = eval {alarm 0; 1;};
+
+sub Alarm {
+  alarm(shift) if $can_alarm;
+}
+
+Alarm(50);
 $SIG{ALRM} = sub {
   ok 0;
   die "TIMED OUT!";
@@ -73,13 +79,13 @@ ok ($p -> {port_num} = getservbyname("http", "tcp"));
 foreach my $host (keys %{ $webs }) {
   # ping() does dns resolution and
   # only sends the SYN at this point
-  alarm(50); # (Plenty for a DNS lookup)
+  Alarm(50); # (Plenty for a DNS lookup)
   if (!ok $p -> ping($host)) {
     print STDERR "CANNOT RESOLVE $host $p->{bad}->{$host}\n";
   }
 }
 
-alarm(20);
+Alarm(20);
 while (my $host = $p->ack()) {
   if (!ok $webs->{$host}) {
     print STDERR "SUPPOSED TO BE DOWN: http://$host/\n";
@@ -87,9 +93,9 @@ while (my $host = $p->ack()) {
   delete $webs->{$host};
 }
 
-alarm(0);
+Alarm(0);
 foreach my $host (keys %{ $webs }) {
   if (!ok !$webs->{$host}) {
-    print STDERR "DOWN: http://$host/ [$p->{bad}->{$host}]\n";
+    print STDERR "DOWN: http://$host/ [",($p->{bad}->{$host} || ""),"]\n";
   }
 }
index 38bc7f2..8374b20 100644 (file)
@@ -57,7 +57,13 @@ plan tests => ((keys %{ $webs }) * 2 + 3);
 # Everything loaded fine
 ok 1;
 
-alarm(50);
+my $can_alarm = eval {alarm 0; 1;};
+
+sub Alarm {
+  alarm(shift) if $can_alarm;
+}
+
+Alarm(50);
 $SIG{ALRM} = sub {
   ok 0;
   die "TIMED OUT!";
@@ -75,13 +81,13 @@ ok ($p -> {port_num} = getservbyname("http", "tcp"));
 foreach my $host (keys %{ $webs }) {
   # ping() does dns resolution and
   # only sends the SYN at this point
-  alarm(50); # (Plenty for a DNS lookup)
+  Alarm(50); # (Plenty for a DNS lookup)
   if (!ok($p -> ping($host))) {
     print STDERR "CANNOT RESOLVE $host $p->{bad}->{$host}\n";
   }
 }
 
-alarm(20);
+Alarm(20);
 foreach my $host (sort keys %{ $webs }) {
   my $on = $p->ack($host);
   if (!ok (($on && $webs->{$host}) ||
@@ -89,11 +95,11 @@ foreach my $host (sort keys %{ $webs }) {
     if ($on) {
       print STDERR "SUPPOSED TO BE DOWN: http://$host/\n";
     } else {
-      print STDERR "DOWN: http://$host/ $p->{bad}->{$host}\n";
+      print STDERR "DOWN: http://$host/ [",($p->{bad}->{$host} || ""),"]\n";
     }
   }
   delete $webs->{$host};
-  alarm(20);
+  Alarm(20);
 }
 
-alarm(0);
+Alarm(0);
diff --git a/lib/Net/Ping/t/500_ping_icmp.t b/lib/Net/Ping/t/500_ping_icmp.t
new file mode 100644 (file)
index 0000000..6b6c3ef
--- /dev/null
@@ -0,0 +1,29 @@
+# Test to perform icmp protocol testing.
+# Root access is required.
+
+BEGIN {
+  unless (eval "require Socket") {
+    print "1..0 \# Skip: no Socket\n";
+    exit;
+  }
+}
+
+use Test;
+use Net::Ping;
+plan tests => 2;
+
+# Everything loaded fine
+ok 1;
+
+if (($> and $^O ne 'VMS' and $^O ne 'cygwin')
+    or ($^O eq 'MSWin32'
+        and Win32::IsWinNT())
+    or ($^O eq 'VMS'
+        and (`write sys\$output f\$privilege("SYSPRV")` =~ m/FALSE/))) {
+  skip "icmp ping requires root privileges.", 1;
+} elsif ($^O eq 'MacOS') {
+  skip "icmp protocol not supported.", 1;
+} else {
+  my $p = new Net::Ping "icmp";
+  ok $p->ping("127.0.0.1");
+}