#!/usr/bin/perl # $Id: pingar.pl,v 1.6 2006/02/13 02:24:26 jcs Exp $ # # pingar.pl # tiny multi-host ping monitoring tool with dependencies and e-mail alerts # # Copyright (c) 2005-2006 joshua stein # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # use Net::Ping; use strict; $|++; # seconds to sleep between pings my $sleeptime = 30; # list of hosts to ping my %hosts = ( "router" => { "host" => "192.168.1.1", # no dependency here, it's right next to us }, "some host" => { "host" => "hostname.example.com", # dependency on "router" being up "dep" => "router", }, "wan router" => { "host" => "192.168.2.1", "dep" => "router", }, "google" => { "host" => "www.google.com", # depends on "wan router" being up, which depends on "router" being up "dep" => "wan router", }, ); # email address to send to (escape @ sign!) my $emailtarget = "root\@localhost"; my $debug = 0; while (@ARGV) { if ($ARGV[0] eq "-d") { $debug++; shift(@ARGV); } else { die "unknown option: " . $ARGV[0] . "\n" . "usage: " . $0 . " [-d]\n"; } } if ($<) { die $0 . ": must be run as root for ICMP privileges\n"; } my @hostorder; my %hostadded; my $hostcount = keys %hosts; # set each host to up foreach my $host (keys %hosts) { $hosts{$host}{"up"} = 1; } # order hosts so dependencies are pinged first while (($#hostorder + 1) < $hostcount) { my $t = $#hostorder; foreach my $host (keys %hosts) { if ($hostadded{$host}) { next; } if (exists($hosts{$host}{"dep"})) { # has a dependency, make sure it is added first foreach my $dhost (@hostorder) { if ($dhost eq $hosts{$host}{"dep"}) { # dependency exists, add it push @hostorder, $host; $hostadded{$host} = 1; } } } else { push @hostorder, $host; $hostadded{$host} = 1; } } if ($#hostorder eq $t) { # nothing got added, bah die "dependency loop?\n"; } } for (;;) { foreach my $host (@hostorder) { print "pinging " . $host . "... "; if ($hosts{$host}{"dep"}) { if ($hosts{$hosts{$host}{"dep"}}{"up"} < 1) { # dependency is down, skip this one print "dependency (" . $hosts{$host}{"dep"} . ") is down, " . "skipping\n"; $hosts{$host}{"up"} = -1; next; } } my $ret = 0; eval { local $SIG{"ALRM"} = sub { die "oh noes" }; alarm 1; my $p = Net::Ping->new("icmp"); $ret = $p->ping($hosts{$host}{"host"}); alarm 0; }; alarm 0; if ($ret) { print "returned " . $ret . "\n"; alert($host, 1); $hosts{$host}{"up"} = 1; } else { print "ping of " . $hosts{$host}{"host"} . " failed\n"; alert($host, 0); $hosts{$host}{"up"} = 0; } } naptime: sleep $sleeptime; } exit; sub alert { my $host = $_[0]; my $newup = $_[1]; if (($hosts{$host}{"up"} eq -1) && ($newup eq 1)) { # we're up but we were only down because of a dependency, so just # ignore this return; } if ($newup eq $hosts{$host}{"up"}) { # we already saw this # TODO: warn again after x amount of time? return; } print "sending alert for " . $host . " changing to " . $newup . "\n"; if (!$debug) { open(SENDMAIL, "|/usr/sbin/sendmail -t"); print SENDMAIL "To: " . $emailtarget . "\n"; print SENDMAIL "Subject: " . $host . " is " . ($newup ? "UP" : "DOWN") . "\n"; print SENDMAIL "\n"; print SENDMAIL "Host " . $host . " (" . $hosts{$host}{"host"} . ") " . ($newup ? "IS" : "IS NOT") . " reachable\n"; close(SENDMAIL); } }