#!/usr/bin/perl -w # # check_ospf_counter - nagios plugin # # by Frank Bulk <frnkblk@iname.com> # inspiration by Douglas E. Warner <silfreed@silfreed.net> # inspiration by Christoph Kron <ck@zet.net> - check_ifstatus.pl # with code for SNMPv3 contributed by Duncan Ferguson # # This plugin is a sister to check_bgp_counters. # # For those that have no SNMP trap manager, this poll-based approach will still catch short OSPF # outages because certain counters will increment or change on OSPF session failure and re-establishment. # There's no need to know or enumerate the IP addresses of the router's OSPF peers - the # code handles that automatically. # # This code does not work with OSPFv3 at this time. # # Here is a suggested command definition: # # 'check_ospf_counters command definition # define command{ # command_name check_ospf_counters # command_line perl $USER1$/check_ospf_counters -H $HOSTADDRESS$ -C $ARG1$ -f /tmp/ # } # # Here is a suggested service configuration: # define service{ # use generic-service # host_name router # service_description OSPF # contact_groups router-admins # notification_interval 15 # normal_check_interval 2 # max_check_attempts 1 # notification_options w,c,r # check_command check_ospf_counters!community_string # } # # Several notes: # - increasing the max_check_attempts could result in missing counter changes, # so it's recommended to leave it at '1' # - the host definition should use an IP address rather than a host name or FQDN # - make sure the directory and filename is writable by the NAGIOS process. # If you first test using 'root' or another user, the cached files may not be # overwritable # - you can change the error level of a counter by modifying the definition in # the code below. In some environments prefixes may change all the time and # so those OIDs could be commented out altogether. Another option is to change # the notification option to just 'c,r' # - if you silence notifications due to an issue with one OSPF session, you won't be # notified if another OSPF session on that host goes awry. This is potentially a # feature request: to narrow checking to per OSPF session basis, which would naturally # require setting up a separate service check for each BGP session. # # To test from the command-line, try something like this: # ./check_ospf_counters -C community_string -H hostip -f /tmp # # Release notes # none use strict; use Net::SNMP; use Getopt::Long; use File::Basename; &Getopt::Long::config('auto_abbrev'); use IO::Socket; my $version = "0.8"; my $status; my $needhelp = ''; my $TIMEOUT = 30; my %ERRORS = ( 'OK' => '0', 'WARNING' => '1', 'CRITICAL' => '2', 'UNKNOWN' => '3', ); # default return value is UNKNOWN my $state = "UNKNOWN"; # time this script was run my $runtime = time(); # responses from script my $answer = ""; my $oidmsg = ""; my $error; my $oidwarn = 0; my $oidcrit = 0; # external variable declarations my $hostname; my $community = "public"; my $port = 161; my @ignorestring; my $counterFilePath; my $counterFile; my $warntmp; my @warning; my $crittmp; my @critical; my $snmpversion = "2c"; my $snmpv3_username = "initial"; # SNMPv3 username my $snmpv3_password = ""; # SNMPv3 password my $snmpv3_authprotocol = "md5"; # SNMPv3 hash algorithm (md5 / sha) my $snmpv3_privprotocol = "des"; # SNMPv3 encryption protocol (des / aes / aes128) my $snmpv3_privpassword = ""; my %snmpOID; # ospfIfEvents $snmpOID{"1.3.6.1.2.1.14.7.1.15"} = "The number of OSPF events on interface"; # ospfNbrState $snmpOID{"1.3.6.1.2.1.14.10.1.6"} = "The OSPF neighbor state for"; # ospfNbrEvents $snmpOID{"1.3.6.1.2.1.14.10.1.7"} = "The number of OSPF neighbor events for"; my %ospfNbrState; $ospfNbrState{1} = "down"; $ospfNbrState{2} = "attempt"; $ospfNbrState{3} = "init"; $ospfNbrState{4} = "twoWay"; $ospfNbrState{5} = "exchangeStart"; $ospfNbrState{6} = "exchange"; $ospfNbrState{7} = "loading"; $ospfNbrState{8} = "full"; # snmp related variables my $session; my $response; my $snmpkey; my $snmpoid; my $key; our %snmpIndexes; my $snmpSysUpTime = ".1.3.6.1.2.1.1.3.0"; my $snmpHostUptime; # file related variables my $fileRuntime; my $fileHostUptime; my %fileIndexes; ## main program # Just in case of problems, let's not hang NetSaint $SIG{'ALRM'} = sub { print ("ERROR: No snmp response from $hostname (alarm)\n"); exit $ERRORS{"UNKNOWN"}; }; alarm($TIMEOUT); # we must have -some- arguments if (scalar(@ARGV) == 0) { usage(); } # end if no options Getopt::Long::Configure("no_ignore_case"); $status = GetOptions( "h|help" => \$needhelp, "C|snmpcommunity=s" => \$community, "i|ignore-string=s" => \@ignorestring, "p|port=i" => \$port, "f|filepath=s" => \$counterFilePath, "H|hostname=s" => \$hostname, "U|username=s" => \$snmpv3_username, "P|password=s" => \$snmpv3_password, "a|authprotocol=s" => \$snmpv3_authprotocol, "e|privprotocol=s" => \$snmpv3_privprotocol, "v|snmpversion=s" => \$snmpversion, "x|privpassword=s" => \$snmpv3_privpassword, ); if ($status == 0 || $needhelp) { usage(); } # end if getting options fails or the user wants help if (!defined($counterFilePath)) { $state = "UNKNOWN"; $answer = "Filepath must be specified"; print "$state: $answer\n"; exit $ERRORS{$state}; } # end check for filepath if (!defined($hostname)) { $state = "UNKNOWN"; $answer = "Hostname must be specified"; print "$state: $answer\n"; exit $ERRORS{$state}; } # end check for hostname # setup counterFile now that we have hostname and oid $counterFile = "$counterFilePath/$hostname.check_ospf_counters.nagioscache"; readolddata(); getSysUpTime(); # ospfIfEvents foreach $key (keys %snmpOID) { getCounters($key); } checkIgnores(); outputdata(); # check to see if we pulled data from the cache file or not if (!defined($fileRuntime)) { $state = "OK"; $answer = "never cached - caching\n"; print "$state: $answer\n"; exit $ERRORS{$state}; } # end if cache file didn't exist # check host's uptime to see if it goes backward if ($fileHostUptime > $snmpHostUptime) { $state = "WARNING"; $answer = "uptime goes backward - recaching data\n"; print "$state: $answer\n"; exit $ERRORS{$state}; } # end if host uptime goes backward # check if number of indexes in file is different than our new data if (scalar(keys(%fileIndexes)) != scalar(keys(%snmpIndexes))) { $state = "WARNING"; $answer = "number of indexes changed - recaching data\n"; print "$state: $answer\n"; exit $ERRORS{$state}; } # end number of indexes different # check if there are no neighbors at all if (scalar(keys(%snmpIndexes)) eq 0) { $state = "WARNING"; $answer = "No neighbors\n"; print "$state: $answer\n"; exit $ERRORS{$state}; } # end number of indexes different # foreach snmp key (sorted numerically), figure stuff out foreach $key (sort numerically (keys %snmpIndexes)) { my $timeperiod = ($runtime-$fileRuntime); if ($snmpIndexes{$key} ne $fileIndexes{$key}) { (my @oid_array) = split (/\./, $key); my $int_ip = join(".", @oid_array[$#oid_array-4 .. $#oid_array-1]); my $oid = join(".", @oid_array[0 .. $#oid_array-5]); my $int_hostname = gethostbyaddr(inet_aton($int_ip), AF_INET); if ($fileIndexes{$key} eq "") { $fileIndexes{$key} = "null"; } $oidcrit++; if ($int_hostname) { $oidmsg .= "$snmpOID{$oid} $int_hostname [$int_ip] has changed from '$fileIndexes{$key}' to '$snmpIndexes{$key}'\n"; } else { $oidmsg .= "$snmpOID{$oid} $int_ip has changed from '$fileIndexes{$key}' to '$snmpIndexes{$key}'\n"; } } } # end foreach $key # figure out what state we're in if ($oidcrit > 0) { $state = "CRITICAL"; } elsif ($oidwarn > 0) { $state = "WARNING"; } else { $state = "OK"; } # end if we have warnings or not # setup final message $answer = "critical $oidcrit, warning $oidwarn\n$oidmsg"; print ("$state: $answer"); # foreach snmp key (sorted numerically), figure stuff out foreach $key (sort numerically (keys %snmpIndexes)) { if ($key =~ /^1\.3\.6\.1\.2\.1\.14\.10\.1\.6\./) { (my @oid_array) = split (/\./, $key); my $int_ip = join(".", @oid_array[$#oid_array-4 .. $#oid_array-1]); my $oid = join(".", @oid_array[0 .. $#oid_array-5]); my $int_hostname = gethostbyaddr(inet_aton($int_ip), AF_INET); if ($int_hostname) { print "OSPF state with $int_hostname [$int_ip]: $snmpIndexes{$key}\n"; } else { print "OSPF state with $int_ip: $snmpIndexes{$key}\n"; } } } # end foreach $key exit $ERRORS{$state}; ## subroutines ## # the usage of this program (duh) sub usage { print <<END; == check_ospf_counters v$version == Perl SNMP Check Counter plugin for Nagios Frank Bulk <frnkblk\@iname.com> checks a provided counter and verifies that it was within Usage: check_ospf_counters (-C|--snmpcommunity) <read_community> (-H|--hostname) <hostname> [-i|--ignore-string] <ignore_string> [-p|--port] <port> (-f|--filepath) <file> END exit $ERRORS{"UNKNOWN"}; } # for sorting things numerically sub numerically { (pack'C*',split/\./,$a) cmp (pack'C*',split/\./,$b); # $a <=> $b } # end numerically # read in the old data (if it exists) sub readolddata { if (-e $counterFile) { open(FILE, "$counterFile"); chomp($fileRuntime = <FILE>); chomp($fileHostUptime = <FILE>); while (my $line = <FILE>) { chomp($line); my @splitline = split(/ /, $line); $fileIndexes{$splitline[0]} = $splitline[1]; } # end while rest of file close(FILE); } # end if file exists } # end readolddata # output data for cache sub outputdata { if ((-w $counterFile) || (-w dirname($counterFile))) { open(FILE, ">$counterFile"); print FILE "$runtime\n"; print FILE "$snmpHostUptime\n"; foreach $key (sort numerically (keys %snmpIndexes)) { print FILE "$key $snmpIndexes{$key}\n"; } # end for each value to output close(FILE); } else { $state = "WARNING"; $answer = "file $counterFile is not writable\n"; print ("$state: $answer\n"); exit $ERRORS{$state}; } # end if file is writable } # end outputdata # get sysUpTime from host sub getSysUpTime { # get the uptime for the host given if($snmpversion eq "1" || $snmpversion eq "2c") { ($session, $error) = Net::SNMP->session( -hostname => $hostname, -community => $community, -port => $port ); } else { ($session, $error) = Net::SNMP->session( -hostname => $hostname, -port => $port, -username => $snmpv3_username, -authpassword => $snmpv3_password, -authprotocol => $snmpv3_authprotocol, -privpassword => $snmpv3_privpassword, -privprotocol => $snmpv3_privprotocol, -version => $snmpversion, -timeout => 30, ); } if (!defined($session)) { $state = "UNKNOWN"; $answer = $error; print "$state: $answer"; exit $ERRORS{$state}; } $session->translate( [-timeticks => 0x0] ); $response = $session->get_request( -varbindlist => [$snmpSysUpTime] ); if (!defined($response)) { $answer=$session->error; $session->close; $state = "WARNING"; print "$state: $answer,$community,$snmpSysUpTime"; exit $ERRORS{$state}; } $snmpHostUptime = $response->{$snmpSysUpTime}; $session->close; } # end getSysUpTime # get counters the user wants from host sub getCounters { my $temp_snmpCounter = shift; # get the value(s) for the oid given if($snmpversion eq "1" || $snmpversion eq "2c") { ($session, $error) = Net::SNMP->session( -hostname => $hostname, -community => $community, -port => $port ); } else { ($session, $error) = Net::SNMP->session( -hostname => $hostname, -port => $port, -username => $snmpv3_username, -authpassword => $snmpv3_password, -authprotocol => $snmpv3_authprotocol, -privpassword => $snmpv3_privpassword, -privprotocol => $snmpv3_privprotocol, -version => $snmpversion, -timeout => 30, ); } if (!defined($session)) { $state = "UNKNOWN"; $answer = $error; print "$state: $answer"; exit $ERRORS{$state}; } if ( !defined($response = $session->get_table($temp_snmpCounter)) && !defined($response = $session->get_request($temp_snmpCounter)) ) { $answer = $session->error; $session->close; $state = "WARNING"; print "$state: $answer,$community,$temp_snmpCounter\n"; exit $ERRORS{$state}; } foreach $snmpkey (keys %{$response}) { $key = $snmpkey; (my @oid_array) = split (/\./, $key); my $oid = join(".", @oid_array[0 .. $#oid_array-5]); if ($oid eq "1.3.6.1.2.1.14.10.1.6") { $response->{$snmpkey} = $ospfNbrState{$response->{$snmpkey}}; } # if the value this is isn't in @ignorestring, okay $snmpIndexes{$key} = $response->{$snmpkey}; } $session->close; } # end getCounters # check to see if we're supposed to be ignoring a key. if so, kill it sub checkIgnores { foreach my $key (keys(%snmpIndexes)) { if (arrayInScalar($key, @ignorestring)) { delete($snmpIndexes{$key}); } # end if ignore, nuke key } # end foreach key } # end checkIgnores # check to see if variables in @array match $scalar sub arrayInScalar { my $temp_key = shift; my @temp_ignorestring = @_; foreach (@temp_ignorestring) { if ($temp_key =~ /$_\.\d+$/) { return 1; #true } } return 0; #false } # end arrayInScalar