#!/usr/local/bin/perl -w # # check_remote_interfaces # # by ATonns Mon Sep 9 14:15:23 EDT 2002 # # monitor the interfaces OID in net-snmpd # # $Id: check_remote_interfaces,v 1.13 2003/09/15 17:39:16 atonns Exp atonns $ # # check_remote_interfaces - monitor the interfaces OID in net-snmpd # Copyright (C) 2003 - iVillage.com, Anthony Tonns # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # perl setup use strict; use Getopt::Long; use Net::SNMP; use RRDs; use lib "/usr/local/nagios/libexec"; use utils qw($TIMEOUT %ERRORS &print_revision &support); delete @ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; # where to store all RRD files my $rrd_dir = "/www/apps/nagios/data/ifErrors"; # static variables my $PROGNAME = "check_remote_interfaces"; my $version = '$Revision: 1.13 $'; # OIDs for the net-snmpd host resources mib # # iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry my $baseoid = "1.3.6.1.2.1.2.2.1"; my $baselen = length($baseoid) + 1; # ifDescr my $descrkey = "2"; # ifAdminStatus my $adminstatuskey = "7"; # ifAdminStatus linkage from /usr/local/share/snmp/mibs/IF-MIB.txt my %adminstatuslinkage = ( 1 => "up", 2 => "down", 3 => "testing", ); # ifOperStatus my $operstatuskey = "8"; # ifOperStatus linkage from /usr/local/share/snmp/mibs/IF-MIB.txt my %operstatuslinkage = ( 1 => "up", 2 => "down", 3 => "testing", 4 => "unknown", 5 => "dormant", 6 => "notPresent", 7 => "lowerLayerDown", ); # ifInErrors my $inerrorskey = "14"; # ifOutErrors my $outerrorskey = "20"; # skip these interfaces my %skipif = ( 'lo0' => 1, ); # auth config stuff my $username = "XxXxXxXxXxXx"; my $authpass = "XxXxXxXxXxXx"; my $privpass = "XxXxXxXxXxXx"; my $community = "XxXxXxXxXxXx"; ################################################################################ # parse args my ($opt_V,$opt_h,$opt_H,$opt_i,$opt_v,$opt_w,$opt_c); Getopt::Long::Configure('bundling'); GetOptions( "V" => \$opt_V, "version" => \$opt_V, "h" => \$opt_h, "help" => \$opt_h, "v+" => \$opt_v, "verbose+" => \$opt_v, "H=s" => \$opt_H, "hostname=s" => \$opt_H, "i=s" => \$opt_i, "interval=s" => \$opt_i, "c=s" => \$opt_c, "critical=s" => \$opt_c, "w=s" => \$opt_w, "warning=s" => \$opt_w, ); # check args if ( $opt_h ) { print_usage(""); } if ( $opt_V ) { print_revision($PROGNAME,$version); clean_exit($ERRORS{OK}); } if ( ! $opt_H ) { print_usage("$PROGNAME: must specify hostname with -H option."); } my $hostname = $opt_H; # my $interval = $1 if ($opt_i =~ /([0-9]+)/); ($interval) || print_usage("$PROGNAME: Invalid interval: $opt_i"); # my ($w_count,$w_time) = split(',',$opt_w,2); my ($c_rate,$c_time) = split(',',$opt_c,2); map { my $input = $_; my $output = $1 if ($input =~ /([0-9]+)/); ($output) || print_usage("$PROGNAME: Invalid threshold: '$input' "); } ($w_count,$w_time,$c_rate,$c_time); my $warning_count = $w_count; my $warning_time = $w_time; my $critical_rate = $c_rate; my $critical_time = $c_time; # set a timeout w/error message $SIG{'ALRM'} = sub { print STDOUT ("$PROGNAME: ERROR: alarm timeout\n"); clean_exit($ERRORS{UNKNOWN}); }; alarm($TIMEOUT); # establish a session my ($session,$error) = Net::SNMP->session( -hostname => $hostname, -version => "3", -username => $username, -authprotocol => "md5", -authpassword => $authpass, -privpassword => $privpass, -maxmsgsize => 1048576, -timeout => $TIMEOUT, -retries => 3, ); if ( $error ) { print STDOUT "$PROGNAME: session error: $error\n"; clean_exit($ERRORS{UNKNOWN}); } # retreive the entire ifTable my $result = $session->get_table( -baseoid => $baseoid, ); if ( $session->error ) { print STDOUT "$PROGNAME: get_table error: ".$session->error."\n"; $session->close; clean_exit($ERRORS{UNKNOWN}); } $session->close; my %map; # from ifDescr to oid instance my %data; # data table re-hashed by ifDescr # create a map based on the final oid for the column with $descrkey foreach (sort keys %{$result}) { my $key = $_; my ($column,$instance) = split(/\./, substr($key,$baselen)); if ( $column eq $descrkey ) { $map{$instance} = ${$result}{$key}; } } # NOW reorder all the data in the table based on what was $descrkey's value foreach (sort keys %{$result}) { my $key = $_; my ($column,$instance) = split(/\./, substr($key,$baselen)); if ( $column ne $descrkey ) { $data{$map{$instance}}->{$column} = ${$result}{$key}; } } ## DEBUG ## let's see it in order now #foreach (sort keys %data) { # my $key = $_; # print STDERR "$key "; # print STDERR " " if length($key) < 8; # foreach (sort keys %{$data{$key}}) { # my $subkey = $_; # my $val = $data{$key}->{$subkey}; # print STDERR " $subkey:$val, "; # } # print STDERR "\n"; #} # since we've checked all the sanity beforehand, # start off assuming all is well my $state = $ERRORS{OK}; # BUT just in case we'll declare a variable my @badinterfaces; # check all the interfaces INT: foreach (sort keys %data) { my $ifDescr = $_; if ( exists($skipif{$ifDescr}) ) { next INT; } my $mystate = $ERRORS{OK}; # get the current data my $ifAdminStatus = $data{$ifDescr}->{$adminstatuskey}; my $ifOperStatus = $data{$ifDescr}->{$operstatuskey}; my $ifInErrors = $data{$ifDescr}->{$inerrorskey}; my $ifOutErrors = $data{$ifDescr}->{$outerrorskey}; # DEBUG # print STDERR "$ifDescr: $ifAdminStatus - $ifOperStatus - $ifInErrors - $ifOutErrors\n"; # if it's not OK, signal critical if ( $ifOperStatus != $ifAdminStatus ) { $mystate = $ERRORS{CRITICAL}; } my ($warnstart,$warnstep,$warnnames,$warndata, $critstart,$critstep,$critnames,$critdata) = get_data($hostname,$ifDescr,$ifInErrors,$ifOutErrors); # DEBUG # use Dumpvalue; # my $dumper = new Dumpvalue; # $dumper->set(globPrint => 1); # # Example code from RRDs perldoc # # my ($start,$step,$names,$data) = RRDs::fetch ... # print "Start: ", scalar localtime($start), " ($start)\n"; # print "Step size: $step seconds\n"; # print "DS names: ", join (", ", @$names)."\n"; # print "Data points: ", $#$data + 1, "\n"; # print "Data:\n"; # foreach my $line (@$data) { # print " ", scalar localtime($start), " ($start) "; # $start += $step; # foreach my $val (@$line) { # printf "%12.1f ", $val; # } # print "\n"; # } # DEBUG # print STDERR "DS names: ", join (", ", @$warnnames)."\n" if $ifDescr eq "hme0"; # print STDERR "warning data: " if $ifDescr eq "hme0"; # $dumper->dumpValue($warndata) if $ifDescr eq "hme0"; # warning is based on pure count my $warnifInErrors = 0; my $warnifOutErrors = 0; foreach (@$warndata) { my $line = $_; # DEBUG # print STDERR "time: ". scalar localtime($warnstart). " ($warnstart) \n" if $ifDescr eq "hme0"; # $warnstart += $warnstep; if ( ${$line}[0] ) { my $in = ${$line}[0] * $warnstep; # DEBUG # print STDERR "in: $in\n" if $ifDescr eq "hme0"; $warnifInErrors += $in; } if ( ${$line}[1] ) { my $out = ${$line}[1] * $warnstep; # DEBUG # print STDERR "out: $out\n"if $ifDescr eq "hme0"; $warnifOutErrors += $out; } } # if any errors, signal warning if ( $warnifInErrors > $warning_count || $warnifOutErrors > $warning_count ) { $mystate = $ERRORS{WARNING} if $mystate == $ERRORS{OK}; } # DEBUG # print STDERR "DS names: ", join (", ", @$critnames)."\n" if $ifDescr eq "hme0"; # print STDERR "critical data: " if $ifDescr eq "hme0"; # $dumper->dumpValue($critdata) if $ifDescr eq "hme0"; my $critifInErrors_cnt = 0; my $critifOutErrors_cnt = 0; foreach (@$critdata) { my $line = $_; # DEBUG # print STDERR "time: ". scalar localtime($critstart). " ($critstart) \n" if $ifDescr eq "hme0"; # $critstart += $critstep; if ( ${$line}[0] ) { my $in = ${$line}[0]; $in *= $critstep; # DEBUG # print STDERR "in: $in\n" if $ifDescr eq "hme0"; $critifInErrors_cnt += $in; } if ( ${$line}[1] ) { my $out = ${$line}[1]; $out *= $critstep; # DEBUG # print STDERR "out: $out\n" if $ifDescr eq "hme0"; $critifOutErrors_cnt += $out; } } # express in errors per minute my $critifInErrors = $critifInErrors_cnt / $critical_time; my $critifOutErrors = $critifOutErrors_cnt / $critical_time; # if any errors, signal warning if ( $critifInErrors > $critical_rate || $critifOutErrors > $critical_rate ) { $mystate = $ERRORS{CRITICAL}; } # DEBUG # print STDERR "interface => $ifDescr, adminstatus => $ifAdminStatus, operstatus => $ifOperStatus, inerrors => $warnifInErrors, outerrors => $warnifOutErrors, inerrorsrate => $critifInErrors, outerrorsrate => $critifOutErrors\n"; if ( $mystate != $ERRORS{OK} ) { push( @badinterfaces, { interface => $ifDescr, adminstatus => $ifAdminStatus, operstatus => $ifOperStatus, inerrors => $warnifInErrors, outerrors => $warnifOutErrors, inerrorsrate => $critifInErrors, outerrorsrate => $critifOutErrors, } ); # if it's OK, it can be changed to show error if ( $state == $ERRORS{OK} ) { $state = $mystate; } # if it's WARNING, it can be escalated to CRITICAL elsif ( $state == $ERRORS{WARNING} && $mystate == $ERRORS{CRITICAL} ) { $state = $mystate; } } } # print text for the humans my $statetxt; foreach (keys(%ERRORS)) { my $key = $_; $statetxt=$key if ( $state == $ERRORS{$key} ); } # the almighty output print STDOUT "Interfaces $statetxt"; if ( $state != $ERRORS{OK} ) { print STDOUT " - "; while($#badinterfaces != -1) { my $int = shift(@badinterfaces); print STDOUT "$$int{interface}: " . "AdminStatus: ". $adminstatuslinkage{${$int}{adminstatus}} . ", " . "OperStatus: ". $operstatuslinkage{${$int}{operstatus}} . ", " . "InErrCnt: ". sprintf("%4.2f",${$int}{inerrors}) . ", " . "OutErrCnt: ". sprintf("%4.2f",${$int}{outerrors}) . ", ", "InErrRate: ". sprintf("%4.2f",${$int}{inerrorsrate}) . ", " . "OutErrRate: ". sprintf("%4.2f",${$int}{outerrorsrate}) . ", "; } } print STDOUT "\n"; clean_exit($state); ################################################################################ # how does this work again? sub print_usage { my ($msg) = @_; if ( $msg ) { print STDOUT "$msg\n\n"; } print_revision($PROGNAME,$version); print STDOUT "Usage: $PROGNAME -H hostname " . " -i seconds -w count,seconds -c rate,minutes\n"; print STDOUT "Usage: $PROGNAME --hostname=hostname " . " --interval=seconds --warning=count,seconds --critical=rate,minutes\n"; print STDOUT " ".' ' x length($PROGNAME) . " [-v|--verbose -V|--version -h|--help]\n"; clean_exit($ERRORS{UNKNOWN}); } sub clean_exit { my($state)=@_; alarm(0); exit($state); } sub get_data { my ($hostname,$ifDescr,$ifInErrors,$ifOutErrors) = @_; my $ERR; my $filename = "$rrd_dir/$hostname-$ifDescr.rrd"; if ( ! -f $filename ) { my $oneday = 60 * 60 * 24; my $howmanyperday = $oneday / $interval; RRDs::create ( $filename, "--step", "$interval", "DS:ifInErrors:DERIVE:$interval:0:U", "DS:ifOutErrors:DERIVE:$interval:0:U", "RRA:AVERAGE:0.5:1:$howmanyperday", "RRA:AVERAGE:0.5:1:$howmanyperday", ); $ERR=RRDs::error; die "ERROR while creating $filename: $ERR\n" if $ERR; } my $now = time; my $rrd_data = "$now:$ifInErrors:$ifOutErrors"; # DEBUG RRDs::update ( $filename, $rrd_data ); $ERR=RRDs::error; die "ERROR while updating $filename: $ERR\n" if $ERR; # $warning_time is in minutes my $warning_start = $now - $warning_time; # $critical_time is in minutes my $critical_start = $now - ($critical_time*60); my ($warnstart,$warnstep,$warnnames,$warndata) = RRDs::fetch ( $filename, "AVERAGE", "--start", $warning_start, ); $ERR=RRDs::error; die "ERROR while fetching from $filename: $ERR\n" if $ERR; my ($critstart,$critstep,$critnames,$critdata) = RRDs::fetch ( $filename, "AVERAGE", "--start", $critical_start, ); $ERR=RRDs::error; die "ERROR while fetching from $filename: $ERR\n" if $ERR; return ($warnstart,$warnstep,$warnnames,$warndata,$critstart,$critstep,$critnames,$critdata) }