PERL pinger script

When I was working at WebTV we had a pinger script that monitored a variety of routers.  It was probably one of the few diagnostic tools we had that provided useful information and was reliable in its method of obtaining that information.  I decided I could use a script of that sort to monitor some of my personal machines, but the script we used there pinged broadcast addresses and just checked to see which machines responded and which did not.  This didn't particularly work for my application since I wanted to ping several different machines on the Internet existing in several different networks.  So, PERL book in hand, I set to writing my own.

The fruits of my labor are here for you to enjoy (or laugh at, whatever the case may be).  If this script is of use to you, feel free to copy it and play with it.  I'd appreciate an e-mail saying "Hey, I used your script and it's very helpful" but I'm not going to hold my breath.  I make no guarantees that this script won't break things in your network, or provide particularly useful monitoring abilities.  But, it might help someone out there by being a starting point for developing a more useful and customized pinger script.

There should be sufficient comments in the code for anyone to figure out how the script works and how to configure it for any given environment.  You need to have PERL 5 installed, and you need to have the Net::Ping module available.  This script was designed to run as a cron job.  Seems to work pretty well, too, believe it or not.


"pinger.perl" (click here to download)

#!/usr/local/bin/perl
#
# Bryan's cheesy-ass pinger script for monitoring the status of machines
# we all know and love.  I fully realize this script is completely worthless
# and unnecessary, but you must admit that it's handy having a seemingly
# reliable machine like tep12 monitoring the stability of other machines
# is pretty darn cool.
#
# Important note: this script needs to be run as root in order for the ICMP
# ping to work.  Only root has the access necessary to create an ICMP ping
# object.  You can, however, use an echo ping instead if you don't want
# to or cannot run this as root.  See down below for further details.
#
# Other important notice: hosts to be pinged reside in an external
# configuration file, which is specified on the command line.  Its contents
# should look something like this:
#
#            host1.some.com:0
#            host2.some.other.com:0
#            host3.yet.another.org:0
#
# Basically, one line for each host to be monitored.  Don't try putting
# comments or blank lines in the file or anything like that.  No extraneous
# spaces either.  At the end of the host name, place a colon, and then the
# number zero.  The pinger script will interpret a zero as an "unpingable"
# host, and replace it with a number one if the host was pingable and then
# notify whoever should be notified that it changed.  You could enter a one
# instead of a zero, but if you put in a zero it'll mail you the first time
# the script is run (assuming it can ping those hosts) so you'll see that
# the script is working.

# Needed includes to make this thing work.
use Net::Ping; # Make sure you have this installed; if not, head over to
               # http://www.cpan.org/ and get the latest version of the Net
               # library, it should have Net::Ping in it.

# Variable initialization.  Probably excessive in some cases.
#
# $timeout --- time (in seconds) it will wait before timing out on a ping.
#              This variable can be increased or decreased to compensate for
#              network stupidity.  If pinging across a WAN, a longer ping
#              time is good.  In a LAN, short ping times are best.  Keep in
#              mind that pinging hosts across a WAN isn't a reliable way of
#              keeping in touch with them, since things like ICMP filtering
#              and general network slowness can give false results.
#
# $target --- used for indicating which host it is going to ping.  This did
#             not need to be set, I only have it in there out of habit from
#             programming C code.
#
# $oldstate --- If this had been C code, I would have made these two
# $newstate --- variables into macros instead.  But, it's Perl, so I
#               used variables.  They're just values indicating different
#               elements in the array and what those elements mean.  Nothing
#               special or terribly important.  I did it this way for
#               clarity.
#
# $notify --- who gets e-mailed when the script detects a change in the
#             pingability of a host.  Set this to reflect the proper
#             recipient for your implementation.  Multiple recipients can be
#             specified in normal e-mail fashion with a comma seperating
#             each recipient.  And be sure to put a backslash in front of
#             each @ symbol... Perl 5 needs it there.
#
# $whosend --- set this to whoever the generated e-mail should be from.
#              Normally I would say that the recipient should also be used
#              as the sender.  I had to make two variables in case there are
#              going to be multiple recipients since there can only be one
#              sender.
#
# $sendmail --- the full path to the sendmail executable.  This can vary
#               from platform to platform.  In the case of SGI and Sun as
#               well (I think) it's /usr/lib/sendmail but with things like
#               FreeBSD and Linux it seems to be /usr/sbin/sendmail most of
#               the time.  Be sure to leave the -t option in there.
$timeout = 120;
$target = 0;
$oldstate = 1;
$newstate = 2;
$notify = "bryan\@badideas.webicommerce.com";
$whosend = "bryan\@badideas.webicommerce.com";
$sendmail = "/usr/sbin/sendmail -t";

# Set this to one if you want debugging mode on.
#$debug = 1;
 

# This block reads in the configuration file.  The configuration file should
# have been specified on the command line.
open(CONFFILE, "$ARGV[0]") or die "$0: error during open(): $!\n";
while (<CONFFILE>)
{
        ($hostname[$whichhost][$target], $hostname[$whichhost][$oldstate]) = split(/:/, $_);
        if ($debug) # If debugging is on, read what it interprets the
                    # previous host statuses to be.
        {
                print "$hostname[$whichhost][$target]:  host last marked as ";
                if ($hostname[$whichhost][$oldstate] == 1) {print "up.\n";}
                else {print "down.\n";}
        }
        ++$whichhost;
}
close(CONFFILE);
 

# This creates a ping object so we can actually do pinging.  Again, you
# have to be root to use ICMP pings.  Change the word "icmp" to "tcp" if
# you want to do echo pinging instead, but make sure the host to be echo
# pinged is able to echo.  This is specified in /etc/inetd.conf and carried
# on port 7 for both TCP and UDP.  For an easy way to check, telnet to port
# 7 on any given host and start typing at it.  It should echo what you type
# back at you.
#
# You can also do UDP echo pinging instead of TCP if you want.  Just change
# the "icmp" to "udp" instead of "tcp" and it should work, assuming UDP
# echoing is enabled.
$p = Net::Ping->new("icmp", $timeout);

# Begin the actual pinging process, set the state according to the result.
while ($whichhost)
{
        --$whichhost;
        if ($debug) {print "Pinging host $hostname[$whichhost][$target].\n";}
        if ($p->ping($hostname[$whichhost][$target]))
        {
                if ($debug) {print "Host is up.\n";}
                $hostname[$whichhost][$newstate] = 1;
                if ($hostname[$whichhost][$newstate] != $hostname[$whichhost][$oldstate]) {$statechange = 1;}
        }
        else
        {
                if ($debug) {print "Host is down.\n";}
                $hostname[$whichhost][$newstate] = 0;
                if ($hostname[$whichhost][$newstate] != $hostname[$whichhost][$oldstate]) {$statechange = 1;}
        }
}
 

# Now write back out the new version of the conf file, include
# the new status values.
open(CONFFILE, ">$ARGV[0]");
while ($whichhost <= $#hostname)
{
        if ($debug) {print "$hostname[$whichhost][$target]:$hostname[$whichhost][$newstate]\n";}
        print CONFFILE "$hostname[$whichhost][$target]:$hostname[$whichhost][$newstate]\n";
        ++$whichhost;
}
close(CONFFILE);
 

# Notify the individual(s) specified in the $notify variable if the
# states of the target hosts have changed.
if ($statechange)
{
        open(SENDMAIL, "|$sendmail") || die "$0: unable to open sendmail: $!\n";
        select(SENDMAIL);
        print "To: $notify\n";
        print "From: System Monitoring Script <$whosend>\n";
        print "Subject: Network host status change.\n\n";
        while ($whichhost)
        {
                --$whichhost;
                if ($hostname[$whichhost][$newstate] != $hostname[$whichhost][$oldstate])
                {
                        if ($hostname[$whichhost][$newstate]) {print "Host $hostname[$whichhost][$target] is up.\n"}
                        else {print "Host $hostname[$whichhost][$target] is down.\n"}
                }
        }
        print "\n.\n";
        select(STDOUT);
}


Back to the Bad Idea page