JamesNC has asked for the wisdom of the Perl Monks concerning the following question:

I wrote a threaded app to ping lots of servers using the system command (Net::Ping failed when script was not executed as root, and Tye's post about uncommenting several lines of code where Net::Ping checks OS did not change Net-Ping's behavior either) The threaded version worked great for a small list of 10-30 servers. When I pushed it up to 200, it would sometimes just hang the entire system, and grew to about 157,000kb of memory (WinXP). I decided to time the two methods ( ie just using a single system command and reiterating over it vs the threaded version ) The threaded version failed during the Benchmark with a "Scalar's leaked: 1" when iteration were greater than 1. I went on to read on CPAN that certain subs may indeed have a leak, but it hadn't been resolved. *sigh* I wish I knew enough to help with that effort ) So, I decided to hack the Net::Ping module to see if could just send a simple ICMP raw echo myself. I just wanted a simple sub that would send a ICMP raw packet and then look for a return. At first, I used the IO::Socket::INET, I thought it would be a *simple* matter. Since I had built simple client servers using the examples in here and in serveral other books. NOT! Merlyn had a Suggestion on here last week about trying to do this. I tried to compile that links module (Net::RawIP), but it failed to compile :( . Below is the hacked version of Ping. It works fine when run as Administrator... but fails when run as "User" in Win32. I think the error is occuring in the send or the bind, but I can't determine which. I have tried debug statements everywhere... Can anyone tell me if there is a FIX to this?
#!/perl/bin/perl -w # myping.pl # Useage: myping.pl www.someplace.com use IO::Socket::INET; use strict; use Socket qw( SOCK_DGRAM SOCK_STREAM SOCK_RAW PF_INET inet_aton inet_ntoa sockaddr_in ); use Carp; use POSIX qw(ECONNREFUSED); use Time::HiRes qw(time); use FileHandle; use constant ICMP_ECHOREPLY => 0; # ICMP packet types use constant ICMP_ECHO => 8; use constant ICMP_STRUCT => "C2 S3 A"; # Structure of a minimal IC +MP packet use constant SUBCODE => 0; # No ICMP subcode for ECHO and ECHOR +EPLY use constant ICMP_FLAGS => 0; # No special flags for send or recv use constant ICMP_PORT => 0; # No port with ICMP my $cnt; my $pid; my $fh; my $seq = 0; my $proto_num; my $data_size = 4; my $data=""; my $wait = 500; # in milliseconds my $wait_time = ($wait/1000); for (1..$data_size){ $data .= chr($_ % 256); } 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 Respon +se? }; &ping($ARGV[0]); sub ping { my $to_ip = shift; my ($name) = gethostbyname("localhost"); my $ip = gethostbyname("$name"); my $timeout = 1; 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 ); $to_ip = inet_aton("$to_ip"); $proto_num = (getprotobyname('icmp'))[2] || croak("Can't get icmp pro +tocol by name"); $pid = $$ & 0xffff; # Save lower 16 bits of pid $fh = FileHandle->new(); socket($fh, PF_INET, SOCK_RAW, $proto_num) ||croak("icmp socket error + - $!"); ### COMPLAINS HERE WHEN NOT RUN AS ADMIIN ### bind($fh, sockaddr_in(0,$ip)) || croak ("can't bind"); $seq = ($seq + 1) % 65536; # Increment sequence $checksum = 0; $msg = pack(ICMP_STRUCT . $data_size, ICMP_ECHO, SUBCODE, $checksum, $pid, $seq, $data); $checksum = &checksum($msg); $msg = pack(ICMP_STRUCT . $data_size, ICMP_ECHO, SUBCODE, $checksum, $pid, $seq, $data ); $len_msg = length($msg); ### I GET A VALID RETURN FOR $saddr ### $saddr = sockaddr_in(ICMP_PORT, $to_ip); ### BUT I THINK THIS IS WHERE THERE ERROR IS OCCURING ### send($fh, $msg, ICMP_FLAGS,$saddr) || die "can't send! $!\n"; # Send + the message $rbits = ""; vec($rbits, $fh->fileno(), 1) = 1; # Set up Bits $ret = 0; $done = 0; $finish_time = &time() + $timeout; # Must be done by this time while (!$done && $timeout > 0) # Keep trying if we have tim +e { #select the socket and look for info... time out if you don't see +it #I set the timeout to 500 ms for local tests... $nfound = select($rbits, undef, undef, $wait_time ); # LOOK for RB +ITS $timeout = $finish_time - &time(); if (!defined($nfound)) #we didn't see anything on that socket + { $ret = undef; $done = 1; } elsif ($nfound) # Got a packet - open to see i +f it is ours { $recv_msg = ""; $from_saddr = recv($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 . $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 == $pid) && # Does the packet check out? ($from_seq == $seq)) # clos enough then.. { $ret = 1; $done = 1; $from_ip = inet_ntoa($from_ip); print STDOUT "Recieved reply from: $from_ip"; } } else # Or, we timed out { $done = 1; print "Timed out"; } } return $ret; } ## ALMOST WORD FOR WORD OUT OF STEVEN's BOOK sub checksum { my ( $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_ms +g % 2; $chk = ($chk >> 16) + ($chk & 0xffff); # Fold high into low return(~(($chk >> 16) + $chk) & 0xffff); # Again and complement }
and here is the timed code where the threads die
#!/perl/bin/perl -w use threads qw(yield); use strict; use Benchmark; my @childs; my $child; my (@rv, @RV); my @list; my $list; @list = qw ( www.perl.org www.ibm.com www.coke.com www.oracle.com www.hp.com ); #Dies if you iterate more than once :( timethese (1 , { 'threaded' => sub { foreach $list (@list){ push @childs, threads->create( "ping" , "$list" ); } foreach $child (@childs){ $child->join(); } undef @childs; }, 'non-threaded' => sub { foreach (@list){ ping($_); } } }); sub ping { my $ip = shift; @rv = `ping -n 1 -w 500 $ip`; foreach(@rv){ return "$ip up\n" if /\(0% loss\)/g; return "$ip down\n" if /\(100% loss\)/g; } }
Thanks for taking a look! James

Replies are listed 'Best First'.
Re: Socket problem - when running Win32 app as someone different than Admin
by pg (Canon) on Feb 03, 2003 at 15:59 UTC

    On most of the client side win32 OS, SOCK_RAW is not supported at all; On server side, you have to be super.

    I never did this in Perl, but did this in c++ before, and what I said is what I got then. However for this particular problem, it does not matter what language is being used, the OS determines this.

      Thanks, ++ for each of you.. if it is a port problem as you suggest. It is ugly because, I don't get a value for $! or $@ back.

        In the case of WinSock errors, $! gets set to an error code that has no text description programmatically available. So check 0+$! instead of $! and then look up that error number in winsock.h.

                        - tye
Re: Socket problem - when running Win32 app as someone different than Admin
by derby (Abbot) on Feb 03, 2003 at 15:26 UTC
    In *Nix-land, you have to be a super-user to bind to a socket (I'm just guessing the same is true in Win32 land). If you look at the *Nix command ping, it is suid root and that's why Joe User can use it. I'm guessing but if you look at the perms for your NT version of ping and give your perl script the same perms, it should work.

    -derby

    update: that should be you may have to be super-user to bind to a socket (depends on the port number). If you do not have access, bind should return an EACCES error.

      Thanks for your reply - derby... On Win32 - Ping.exe gives Read/Execute and Read permissions to group Users, which is the same permission that myPing script fails under. I can ping.exe even as Guest. I already know that if I change the script to run as someone with admin rights that that will work. I was hoping to get the EACCES error so that I could confirm that NiX is not allowing me to write (send) to to ICMPECHO port... I can bind to an arbitrary socket. I tested that with another client/server with permissions User.