#!/usr/bin/perl

# V1.0
# check_snmp_counter2, nagios-plugin to read SNMP-values and calculate 
# the delta between two readings. Requires Net::SNMP.
# 
#
# Copyright (C) 2004  Carl Bingel / Dacom Svenska
#
# 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.

use strict;
use Net::SNMP qw/ticks_to_time/;
use Getopt::Long;

my $OID_SYSUPTIME = ".1.3.6.1.2.1.1.3.0";

## Where to store the database with readings (one file)
my $DEFAULT_DB_LOCATION = "/tmp/check_snmp_counter.dat";

&main();

sub main {
   
   ##
   ##   **Parse command-line options
   ##
   my( $oids, $snmp_community, $snmp_hostname, $snmp_timeout, $counter_db, $counter_isoctets, $counter_inkilos, $counter_inmegas, $help);
   my( $critical, @critical, $warning, @warning, $descr, @descr, $opt_printuptime);
   my $exit_level = 0;
   GetOptions( "oids|o=s" => \$oids,
               "community|C=s" => \$snmp_community,
               "hostname|H=s" => \$snmp_hostname,
               "timeout|t=i" => \$snmp_timeout,
               "counterdb=s" => \$counter_db,
               "isoctets" => \$counter_isoctets,
               "inkilos" => \$counter_inkilos,
               "inmegas" => \$counter_inmegas,
               "help" => \$help,
               "critical|c=s", \$critical,
               "warning|w=s", \$warning,
               "descr=s", \$descr,
               "printuptime", \$opt_printuptime
               );
   ##  Display help
   display_help() if $help;
   
   ##-o|--oids
   my( @oids) = split( /,/, $oids);
   abort( "ERROR: No OIDs specified. Use the -o|--oids parameter.") if( @oids < 1);
   ##-C|--community
   $snmp_community = "public" if( $snmp_community eq "");
   ##-H|--hostname
   abort( "ERROR: You must specify hostname. Use the -H|--hostname parameter.") if( $snmp_hostname eq "");
   ##-t|--timeout
   $snmp_timeout = 120 if( $snmp_timeout eq "");
   ##--counterdb
   $counter_db = $DEFAULT_DB_LOCATION if( $counter_db eq "");
   ##-w|--warning
   @warning = split( /,/, $warning);
   abort( "ERROR: You must specify a warning level/range for each oid. Use the -w|--warning parameter.") if( (scalar @warning) != (scalar @oids));
   ##-c|--critical
   @critical = split( /,/, $critical);
   abort( "ERROR: You must specify a critical level/range for each oid. Use the -c|--critical parameter.") if( (scalar @critical) != (scalar @oids));
   ##--descr
   @descr = split( /,/, $descr);

   ##
   ## Create new SNMP-session-object
   ##
   my( $snmp_session, $error) = Net::SNMP->session( -hostname => $snmp_hostname, 
                                                    -community => $snmp_community,
                                                    -translate => [ -timeticks => 0]         ## return uptime in numeric format
                                                    );   
   if( ! defined $snmp_session) {
     abort( "ERROR: Couldn't create snmp session: $error");
   }
   ## set timeout
   $snmp_session->timeout( $snmp_timeout);

   ##
   ## Request the actual counters
   ##
   my $snmp_result = $snmp_session->get_request( -varbindlist => [ @oids, $OID_SYSUPTIME]);
   if( ! defined $snmp_result) {
      abort( "ERROR during get-request: ".$snmp_session->error());
   }
   
   ## Read counter-db
   my $db = &read_counter_db( $counter_db);
   if( ! exists $db->{$snmp_hostname}) {
      $db->{$snmp_hostname} = {};
   }
   my $counters = $db->{$snmp_hostname};

   ## Check system uptime
   my $sysUpTime = $snmp_result->{$OID_SYSUPTIME};
   
   ##
   ## Loop through each OID and calculate rate
   ##
   my $oid_counter=0;
   my( $result_string, $perfdata);
   foreach my $oid (@oids) {
      my $since_last_check=0;
      
      ## time-delta, calculate seconds since last poll
      my $time_delta = time() - $counters->{$oid}->{'timestamp'};

      ## Check if system has been reset since last poll
      if( $sysUpTime < $counters->{$oid}->{'uptime'}) {                    ## system has been reset since last check!
         $since_last_check = undef;

      ## Check if counter has wrapped since last poll
      } elsif( $counters->{$oid}->{'value'} > $snmp_result->{$oid}) {      ## INT counter has probably wrapped
         if( $counters->{$oid}->{'value'} > 4294967295) {   ## counter-64 has wrapped
            $since_last_check = 18446744073709551616 - $counters->{$oid}->{'value'} + $snmp_result->{$oid};
         } elsif( $counters->{$oid}->{'value'} > 65535) {   ## counter-32 has wrapped
            $since_last_check = 4294967295 - $counters->{$oid}->{'value'} + $snmp_result->{$oid};
         } elsif( $counters->{$oid}->{'value'} > 256) {     ## counter-16 has wrapped
            $since_last_check = 65535 - $counters->{$oid}->{'value'} + $snmp_result->{$oid};
         }
      } else {
         $since_last_check = $snmp_result->{$oid} - $counters->{$oid}->{'value'};
      }
      
      ## Update database-fields
      $counters->{$oid}->{'uptime'} = $sysUpTime;
      $counters->{$oid}->{'timestamp'} = time();
      $counters->{$oid}->{'value'} = $snmp_result->{$oid};

      ##
      ##  Modify rate whether counter is in octets/bits and if value should be in kilos or megas
      ## 
      my $unit_c="byte";
      if( $counter_isoctets) {
         $since_last_check *= 8;
         $unit_c="bit";
      }
      my $rate = int( $since_last_check / $time_delta);
      my $unit_cm;
      if( $counter_inkilos) {
         $rate = int( $rate / 1024);
         $unit_cm="k";
      } elsif( $counter_inmegas) {
         $rate = $rate / (1024 * 1024);
         $unit_cm="m";
      }

      ##
      ##  Check warning/critical levels
      ##
      my $status = "OK";
      if( ! is_in_range( $rate, $critical[$oid_counter])) {       ## State = critical
         $exit_level = 2;
         $status = "CRITICAL";
      } elsif( ! is_in_range( $rate, $warning[$oid_counter])) {   ## State = warning
         $exit_level = 1 if( $exit_level == 0);    ## only set to warning if state is OK before
         $status = "WARNING";
      }

      ##
      ##  Rate-label is the text within the square-brackets in the textual output rate[<text>]=<rate>, default is 0, 1, 2 and so on, may be changed with the --descr-switch
      ##
      my $rate_label = $oid_counter;
      $rate_label = $descr[$oid_counter] if( $descr[$oid_counter] ne "");
            
      ## Append to textual output
      $result_string.=$status.": rate[".$rate_label."]=".$rate." ".$unit_cm.$unit_c."/s  ";

      $oid_counter++;
   }
   
   ## print system uptime in textual output
   $result_string.="  sysUpTime=".ticks_to_time( $sysUpTime) if( $opt_printuptime);

   ## Output textual output
   print $result_string."\n";

   ## close SNMP-session
   $snmp_session->close();
   
   ## Write counter-db
   &write_counter_db( $db, $counter_db);

   ## exit
   exit( $exit_level);
}

##  Test wether a value is within a specified range
##
sub is_in_range {
   my( $value, $range) = @_;
   my( $min, $max) = ( 0, 0);
   if( $range =~ m/(\d+):(\d+)/) {
      ($min, $max) = ( $1, $2);
   } elsif( $range =~ m/(\d+)/) {
      $max = $1;
   }
   if(($value >= $min) && ($value <= $max)) { 
     return( 1);
   }
   return( 0);
}

##
##  Display help
##
sub display_help {
      print "check_snmp_counter by Carl Bingel / Svensk IT konsult AB 2003. Distributed under Gnu Public License.
usage: check_snmp_counter -H <hostname> -o <oid>[,<oid>]
   -o | --oids <oid>[,<oid>]     Which oid[s] to poll for counter-data. Specified in 
                                 dotted-decimal-format (ex .1.3.6.1.2.1.2.2.1.10.2). If you want to poll
                                 several OIDS (from the same host), separate the OIDS with a comma. (mandatory)
   -H | --hostname <hostname>    Host to poll for SNMP data (mandatory)
   -C | --community <community>  Specify which community-string to use
   -t | --timeout <secs>         Specify the time (in seconds) after when a SNMP-request times out
   -w|--warning <range>          When to issue a warning. (mandatory parameter). <range> may be specified as a
                                 <min>:<max>-range. If just specified as a lone integer, the range
                                 0:<integer> is assumed. The warning is issued when the monitored
                                 rate falls OUT of the range specified. If youre polling several OIDs, you
                                 have to specify one range for each OID, separated with comma (example -w 0:1000,0:1500).
   -c|--critical <range>         When to issue a critical state. (Mandatory parameter). <range> has same syntax as in --warning.
                                 When the rate is out of both ranges for warning and critical, critical
                                 is always issued. The same goes if youre polling several OIDs.
   --descr <descr>               Short description of OID. This text is displayed in the rate[<descr>]=27kbit/s of
                                 the textual output for better readability in the Nagios user interface. 
                                 Example --descr \"Outbound traffic:Inbound traffic\".
   --counterdb <filename>        Location of file containing the database with counter values. This
                                 file must be writable by the nagios-user! Default=$DEFAULT_DB_LOCATION
   --isoctets                    The counter counts octets (usually bytes) of data and not bits
   --inkilos                     Rate gets divided by 1024 (kilo)
   --inmegas                     Rate gets divided by 1048576 (mega)
   --printuptime                 Print system uptime in textual output
   --help                        Display this help-page

";
     exit;
}

##
##  Read counter-db
##
sub read_counter_db {
   my( $filename) = @_;
   my $counter_db;
   open( CF, $filename);
   while( <CF>) {
      chomp;
      my( $hostname, $oid, $uptime, $timestamp, $value) = split( /;/);
      $counter_db->{$hostname}->{$oid} = {
            'uptime' => $uptime,
            'timestamp' => $timestamp,
            'value' => $value
            };
   }
   close( CF);
   return( $counter_db);
}

##
##  Write counter-db
##
sub write_counter_db {
   my( $db, $filename) = @_;
   open( CF, ">".$filename);
   foreach my $hostname (keys %{$db}) {
      foreach my $oid (keys %{$db->{$hostname}}) {
         print CF join( ";", ($hostname,$oid,$db->{$hostname}->{$oid}->{'uptime'}, $db->{$hostname}->{$oid}->{'timestamp'}, $db->{$hostname}->{$oid}->{'value'}))."\n";
      }
   }
   close( CF);
}


##
##  ABORT PROGRAM W/ ERROR-TEXT
##
sub abort {
   my( $msg) = @_;
   print STDERR "check_snmp_counter: ".$msg."\n";
   print "check_snmp_counter: ".$msg."\n";
   exit( 3); ## unknown
}



