#!/usr/bin/perl -w
# nagios: +epn
#
############################## sgichk_san_iftraffic ##############
my $Version='1.0';
# Date : Oct 18 2011
# Author  : Brent Bice (bbice@sgi.com)
# TODO : 
#################################################################
#
# Help : ./sgichk_san_iftraffic.pl -h
#
use strict;
use Net::SNMP;
use Getopt::Long;
use Cache::Memcached;

############### BASE DIRECTORY FOR TEMP FILE ########

# Nagios specific

my $TIMEOUT = 15;
my %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);

# SNMP Datas

my $stats_table = '.1.3.6.1.3.94.4.5.1';
my $tx_table = $stats_table . '.6';
my $rx_table = $stats_table . '.7';
my $port_table = '.1.3.6.1.3.94.1.10.1';
my $port_speeds = $port_table . '.15';
my $port_names = $port_table . '.17';

# Globals


# Standard options
my $o_host = 		undef; 	# hostname
my $o_port = 		161; 	# port
my $o_descr =		'.*'; 	# description filter
my $o_help=		undef; 	# wan't some help ?
my $o_admin=		undef;	# admin status instead of oper
my $o_chkadmin=		undef;	# admin status instead of oper
my $o_inverse=  	undef;	# Critical when up
my $o_dormant=        	undef;  # Dormant state is OK
my $verbose=		undef;	# verbose mode
my $o_version=		undef;	# print version
my $o_maxspeed=		undef;	# Do not use Fixed port speed
my $o_warn = 85;
my $o_crit = 98;

my $o_timeout=  undef; 		# Timeout (Default 5)
# SNMP Message size parameter (Makina Corpus contrib)
my $o_octetlength=undef;
# Login options specific
my $o_community = 	undef; 	# community
my $o_version2	= undef;	#use snmp v2c
my $o_login=	undef;		# Login for snmpv3
my $o_passwd=	undef;		# Pass for snmpv3
my $v3protocols=undef;	# V3 protocol list.
my $o_authproto='md5';		# Auth protocol
my $o_privproto='des';		# Priv protocol
my $o_privpass= undef;		# priv password

# Readable names for counters (M. Berger contrib)
my @countername = ( "in=" , "out=" , "errors-in=" , "errors-out=" , "discard-in=" , "discard-out=" );
my $checkperf_out_desc;

# functions

sub p_version { print "sgichk_san_iftraffic version : $Version\n"; }

sub print_usage {
    print "Usage: $0 [-v] -H <host> -C <snmp_community> [-2] | (-l login -x passwd [-X pass -L <authp>,<privp>)  [-p <port>] [-n <name in desc_oid>] [-r] [-A] [-o <octet_length>] [-t <timeout>] [-V]\n";
}

sub isnnum { # Return true if arg is not a number
  my $num = shift;
  if ( $num =~ /^(\d+\.?\d*)|(^\.\d+)$/ ) { return 0 ;}
  return 1;
}

sub help {
   print "\nSNMP Network Interface Monitor for Nagios version ",$Version,"\n";
   print "GPL licence, (c)2004-2007 Patrick Proy\n\n";
   print_usage();
   print <<EOT;
-v, --verbose
   print extra debugging information (including interface list on the system)
-h, --help
   print this help message
-H, --hostname=HOST
   name or IP address of host to check
-A
   Check the operational status of an interface only if the admin status
   is up
-C, --community=COMMUNITY NAME
   community name for the host's SNMP agent (implies v1 protocol)
-l, --login=LOGIN ; -x, --passwd=PASSWD, -2, --v2c
   Login and auth password for snmpv3 authentication 
   If no priv password exists, implies AuthNoPriv 
   -2 : use snmp v2c
-X, --privpass=PASSWD
   Priv password for snmpv3 (AuthPriv protocol)
-L, --protocols=<authproto>,<privproto>
   <authproto> : Authentication protocol (md5|sha : default md5)
   <privproto> : Priv protocole (des|aes : default des) 
-P, --port=PORT
   SNMP port (Default 161)
-n, --name=NAME
   Port Name in description
   Test it before, because there are known bugs (ex : trailling /)
-i, --inverse
   Make critical when up
-A, --admin
   Use administrative status instead of operational
-o, --octetlength=INTEGER
  max-size of the SNMP message, usefull in case of Too Long responses.
  Be carefull with network filters. Range 484 - 65535, default are
  usually 1472,1452,1460 or 1440.     
-t, --timeout=INTEGER
   timeout for SNMP in seconds (Default: 5)   
-V, --version
   prints version number
EOT
}

# For verbose output
sub verb { my $t=shift; print $t,"\n" if defined($verbose) ; }

sub check_options {
    Getopt::Long::Configure ("bundling");
	GetOptions(
   	'v'	=> \$verbose,		'verbose'	=> \$verbose,
        'h'     => \$o_help,    	'help'        	=> \$o_help,
        'H:s'   => \$o_host,		'hostname:s'	=> \$o_host,
        'p:i'   => \$o_port,   		'port:i'	=> \$o_port,
	'n:s'   => \$o_descr,           'name:s'        => \$o_descr,
	'A'	=> \$o_chkadmin,	'1'		=> \$o_chkadmin,		
        'C:s'   => \$o_community,	'community:s'	=> \$o_community,
	'2'	=> \$o_version2,	'v2c'		=> \$o_version2,		
	'l:s'	=> \$o_login,		'login:s'	=> \$o_login,
	'x:s'	=> \$o_passwd,		'passwd:s'	=> \$o_passwd,
	'X:s'	=> \$o_privpass,	'privpass:s'	=> \$o_privpass,
	'L:s'	=> \$v3protocols,	'protocols:s'	=> \$v3protocols,   
        't:i'   => \$o_timeout,    	'timeout:i'	=> \$o_timeout,
        'I:i'   => \$o_maxspeed,
	'o:i'   => \$o_octetlength,    	'octetlength:i' => \$o_octetlength
    );
    if (defined ($o_help) ) { help(); exit $ERRORS{"UNKNOWN"}};
    if (defined($o_version)) { p_version(); exit $ERRORS{"UNKNOWN"}};
    if ( ! defined($o_host) ) # check host
	{ print_usage(); exit $ERRORS{"UNKNOWN"}}
    if (defined($o_descr)) { #substitute commas for pipes
       $o_descr =~ s/,/|/g;
    }

    # check snmp information
    if ( !defined($o_community) && (!defined($o_login) || !defined($o_passwd)) )
	{ print "Put snmp login info!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
	if ((defined($o_login) || defined($o_passwd)) && (defined($o_community) || defined($o_version2)) )
	{ print "Can't mix snmp v1,2c,3 protocols!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
	if (defined ($v3protocols)) {
	  if (!defined($o_login)) { print "Put snmp V3 login info with protocols!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
	  my @v3proto=split(/,/,$v3protocols);
	  if ((defined ($v3proto[0])) && ($v3proto[0] ne "")) {$o_authproto=$v3proto[0];	}	# Auth protocol
	  if (defined ($v3proto[1])) {$o_privproto=$v3proto[1];	}	# Priv  protocol
	  if ((defined ($v3proto[1])) && (!defined($o_privpass))) {
	    print "Put snmp V3 priv login info with priv protocols!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
	}
	if (defined($o_timeout) && (isnnum($o_timeout) || ($o_timeout < 2) || ($o_timeout > 60))) 
	  { print "Timeout must be >1 and <60 !\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
	if (!defined($o_timeout)) {$o_timeout=5;}

    #### octet length checks
    if (defined ($o_octetlength) && (isnnum($o_octetlength) || $o_octetlength > 65535 || $o_octetlength < 484 )) {
		print "octet lenght must be < 65535 and > 484\n";print_usage(); exit $ERRORS{"UNKNOWN"};
    }	
}
    
########## MAIN #######

check_options();

# Check gobal timeout if snmp screws up
if (defined($TIMEOUT)) {
  verb("Alarm at $TIMEOUT + 5");
  alarm($TIMEOUT+5);
} else {
  verb("no timeout defined : $o_timeout + 10");
  alarm ($o_timeout+10);
}

$SIG{'ALRM'} = sub {
 print "No answer from host\n";
 exit $ERRORS{"UNKNOWN"};
};

# Connect to host
my ($session,$error);
if ( defined($o_login) && defined($o_passwd)) {
  # SNMPv3 login
  if (!defined ($o_privpass)) {
  verb("SNMPv3 AuthNoPriv login : $o_login, $o_authproto");
    ($session, $error) = Net::SNMP->session(
      -hostname   	=> $o_host,
      -version		=> '3',
      -port      	=> $o_port,
      -username		=> $o_login,
      -authpassword	=> $o_passwd,
      -authprotocol	=> $o_authproto,
      -timeout          => $o_timeout
    );  
  } else {
    verb("SNMPv3 AuthPriv login : $o_login, $o_authproto, $o_privproto");
    ($session, $error) = Net::SNMP->session(
      -hostname   	=> $o_host,
      -version		=> '3',
      -username		=> $o_login,
      -port      	=> $o_port,
      -authpassword	=> $o_passwd,
      -authprotocol	=> $o_authproto,
      -privpassword	=> $o_privpass,
	  -privprotocol => $o_privproto,
      -timeout          => $o_timeout
    );
  }
} else {
  if (defined ($o_version2)) {
    # SNMPv2c Login
	verb("SNMP v2c login");
	($session, $error) = Net::SNMP->session(
       -hostname  => $o_host,
	   -version   => 2,
       -community => $o_community,
       -port      => $o_port,
       -timeout   => $o_timeout
    );
  } else {
    # SNMPV1 login
	verb("SNMP v1 login");
    ($session, $error) = Net::SNMP->session(
       -hostname  => $o_host,
       -community => $o_community,
       -port      => $o_port,
       -timeout   => $o_timeout
    );
  }
}
if (!defined($session)) {
   printf("ERROR opening session: %s.\n", $error);
   exit $ERRORS{"UNKNOWN"};
}

if (defined($o_octetlength)) {
	my $oct_resultat=undef;
	my $oct_test= $session->max_msg_size();
	verb(" actual max octets:: $oct_test");
	$oct_resultat = $session->max_msg_size($o_octetlength);
	if (!defined($oct_resultat)) {
		 printf("ERROR: Session settings : %s.\n", $session->error);
		 $session->close;
		 exit $ERRORS{"UNKNOWN"};
	}
	$oct_test= $session->max_msg_size();
	verb(" new max octets:: $oct_test");
}

# fetching the whole ports table is slooooow.
#print "Getting ports table\n" if ($verbose);
## Get Ports table
#my $ports = $session->get_table( 
#	Baseoid => $port_table 
#);
#
#if (!defined($ports)) {
#   printf("ERROR: Ports table : %s.\n", $session->error);
#   $session->close;
#   exit $ERRORS{"UNKNOWN"};
#}
#print "Got ports table\n" if ($verbose);

my $cache = new Cache::Memcached {
   'servers' => [
       '127.0.0.1:11211',
    ],
};

my $pnames = ();
my $pspeeds = ();
my $pnames_delta = 9999999;
my $oldpnametime = $cache->get("$o_host.pnames.time");
if (defined($oldpnametime)) {
   $pnames_delta = (time - $oldpnametime);
}
if ( $pnames_delta < 300) {
   #print "Fetching portnames cache from memcached\n";
   $pnames = $cache->get("$o_host.pnames");
   $pspeeds = $cache->get("$o_host.pspeeds");
} else {   # otherwise fetch it via SNMP
   # Get Port Names table
   #print "Fetching portnames from SNMP\n";
   $pnames = $session->get_table( 
	   Baseoid => $port_names 
   );
   if (!defined($pnames)) {
      printf("ERROR: Port Names table : %s.\n", $session->error);
      $session->close;
      exit $ERRORS{"UNKNOWN"};
   }

   # Get Port Speeds table
   $pspeeds = $session->get_table( 
   	Baseoid => $port_speeds 
   );
   
   if (!defined($pspeeds)) {
      printf("ERROR: Port Speeds table : %s.\n", $session->error);
      $session->close;
      exit $ERRORS{"UNKNOWN"};
   }

   $cache->set($o_host.".pnames.time",time,500);
   $cache->set($o_host.".pnames", $pnames, 500);
   $cache->set($o_host.".pspeeds", $pspeeds, 500);
}

# Get RX stats table
my $rxstats = $session->get_table( 
	Baseoid => $rx_table 
);

if (!defined($rxstats)) {
   printf("ERROR: RX Stats table : %s.\n", $session->error);
   $session->close;
   exit $ERRORS{"UNKNOWN"};
}

# Get TX stats table
my $txstats = $session->get_table( 
	Baseoid => $tx_table 
);

if (!defined($txstats)) {
   printf("ERROR: TX Stats table : %s.\n", $session->error);
   $session->close;
   exit $ERRORS{"UNKNOWN"};
}

#use bigint;
use Math::BigInt;
my $low = 0;
my $high = 0;
my $part = qr/[0-9a-fA-F]{8}/;

# Only a few ms left...
alarm(0);

my $perfstr = "\|";
my @outstr = ();
my $result = $ERRORS{'OK'};
my $resstr = "OK";

foreach my $key ( keys %$pnames) {
   if ($key =~ /$port_names\.(.+)/) {
      my $port = $1;
      my $descr = $$pnames{$port_names . "." . $port};
      my $speed = $$pspeeds{$port_speeds . "." . $port};
      $speed = $speed * 1024;   # convert to max bytes per second
      if (defined($o_maxspeed)) {
         $speed = $o_maxspeed * 1024 * 1024; # Set to arbitrary max speed
      }
      if ($descr =~ /$o_descr/) {
         my $rxcount = -1;
         my $txcount = -1;
         my $rxkey = $rx_table . "." . $port;
         my $txkey = $tx_table . "." . $port;

         # For some reason, sometimes we don't get valid traffic counters
         # Check, if if they're bogus, make one more try
         if (!($$rxstats{$rxkey} =~ /0x[0-9a-fA-F]{16}/)) {
            sleep(1);
            $rxstats = $session->get_table( Baseoid => $rx_table );
         }
         if (!($$txstats{$txkey} =~ /0x[0-9a-fA-F]{16}/)) {
            sleep(1);
            $txstats = $session->get_table( Baseoid => $tx_table );
         }

         # fetch the old counters
         my $oldtxcount = $cache->get("$o_host.$port.txcount");
         my $oldrxcount = $cache->get("$o_host.$port.rxcount");

         if ($$rxstats{$rxkey} =~ /0x[0-9a-fA-F]{16}/) {
            ($high, $low) = $$rxstats{$rxkey} =~ /^0x($part)($part)$/;
            $rxcount = (hex("0x$high") << 32) + (hex("0x$low"));
         } else { # still can't get a valid counter so assume no traffic
            if (defined($oldrxcount)) { $rxcount = $oldrxcount; }
         }
         if ($$txstats{$txkey} =~ /0x[0-9a-fA-F]{16}/) {
            ($high, $low) = $$txstats{$txkey} =~ /^0x($part)($part)$/;
            $txcount = (hex("0x$high") << 32) + (hex("0x$low"));
         } else { # still can't get a valid counter so assume no traffic
            if (defined($oldtxcount)) { $txcount = $oldtxcount; }
         }

         if (($rxcount > 0) && ($txcount > 0)) {
            my $oldtime = $cache->get("$o_host.$port.time");
            print "$descr: max_speed=$speed rx=$rxcount tx=$txcount\n" if($verbose);
            $cache->set("$o_host.$port.time", time,1800);
            $cache->set("$o_host.$port.txcount", $txcount,1800);
            $cache->set("$o_host.$port.rxcount", $rxcount,1800);
   
            if (defined($oldtxcount) && defined($oldrxcount) &&
                defined($oldtime)) {
               my $rxrate = ($rxcount-$oldrxcount) / (time - $oldtime) / 1048576;
               my $txrate = ($txcount-$oldtxcount) / (time - $oldtime) / 1048576;
               my $speedMB = $speed / 1048576;
               my $rxp = $rxrate / ($speed/1048576) * 100;
               my $txp = $txrate / ($speed/1048576) * 100;
               print "$descr: RX=$rxrate MBps   TX=$txrate MBps\n" if ($verbose);
               my $tstr = sprintf ("$descr: IN: %.2f MBps (%.2f%%), OUT: %.2f MBps (%.2f%%)", $rxrate, $rxp, $txrate, $txp);
               push (@outstr, $tstr);
#push (@outstr, "oldrxcount = $oldrxcount, rxcount = $rxcount");
#push (@outstr, "oldtxcount = $oldtxcount, txcount = $txcount");
#push (@outstr, "speed = $speed");
   
               # Check stats
               if (($rxp>$o_crit) || ($txp>$o_crit)) {
                  $result = $ERRORS{'CRITICAL'};
                  $resstr = "CRITICAL: $tstr";
               } elsif ((($rxp>$o_warn) || ($txp>$o_warn)) &&
                        ($result != $ERRORS{'CRITICAL'})) {
                  $result = $ERRORS{'WARNING'};
                  $resstr = "WARNING: $tstr";
               }
   
               # Append performance data
               $descr =~ s/ /_/g;
               $tstr = sprintf ("inUsage=%.2f%%;$o_warn;$o_crit", $rxp);
               $perfstr .= " $tstr";
               $tstr = sprintf ("outUsage=%.2f%%;$o_warn;$o_crit", $txp);
               $perfstr .= " $tstr";
               $tstr = sprintf ("inBandwidth=%.2fMBs", $rxrate);
               $perfstr .= " $tstr";
               $tstr = sprintf ("outBandwidth=%.2fMBs", $txrate);
               $perfstr .= " $tstr";
            }
         } else {
            push (@outstr, "Couldn't get valid traffic counters");
            push (@outstr, "rxstats = $$rxstats{$rxkey}");
            push (@outstr, "txstats = $$txstats{$txkey}");
            $resstr = "UNKNOWN: Couldn't get valid traffic counters";
            $result = $ERRORS{'UNKNOWN'};
         }
      }
   }
}

print "$resstr $perfstr\n";
foreach (@outstr) { print "$_\n"; }
exit $result;

