Blame local/checkbandwidth

Packit fcad23
#!/usr/bin/perl
Packit fcad23
Packit fcad23
=pod
Packit fcad23
Packit fcad23
=head1 NAME
Packit fcad23
Packit fcad23
checkbandwidth - Alert on bandwidth high/low levels for a host/interface
Packit fcad23
Packit fcad23
=head1 DESCRIPTION
Packit fcad23
Packit fcad23
The checkbandwidth script uses the perl SNMP module to collect, store
Packit fcad23
and analyze date from the IF-MIB::ifXTable for the ifHCInOctets and
Packit fcad23
ifHCOutOctets counters.  It can be configured with high/low levels and
Packit fcad23
issue warnings, either through email or via output text and exit
Packit fcad23
codes.  Specifically, the exit codes (1 = warning, 2 = error) and text
Packit fcad23
output are designed to be used with Nagios.  It calculates bandwidth
Packit fcad23
by comparing the values from the last run against the values from the
Packit fcad23
current run and the amonut of time that has passeed between the two
Packit fcad23
runs [i.e. bandwidth = (this_run - last_run)/time].  Because we need
Packit fcad23
data from two runs, the first time it is run after configuration will
Packit fcad23
never detect an error until the second run.
Packit fcad23
Packit fcad23
Data is stored in ~/.snmp/checkbandwidth.json, which can be
Packit fcad23
removed/unlinked to reset the command's data store if needed.  Or the
Packit fcad23
-r switch can be used to just reset the data without wiping the
Packit fcad23
configuration.
Packit fcad23
Packit fcad23
=head1 SYNOPSIS
Packit fcad23
Packit fcad23
1) Setup your snmp.conf file with authentication parameters for your system:
Packit fcad23
Packit fcad23
    # snmpconf -g basic_setup
Packit fcad23
Packit fcad23
2) Add the interface on the host you want monitor, along with an optional
Packit fcad23
"threshold" parameters that you want to be warned about:
Packit fcad23
Packit fcad23
    # checkbandwidth --add-interface eth0 \
Packit fcad23
                     --max-in-bandwidth 300000=admin@example.com \
Packit fcad23
                     foo.example.com
Packit fcad23
Packit fcad23
3A) Then run the tool against the host on a regular basis (eg, every 5
Packit fcad23
minutes in cron):
Packit fcad23
Packit fcad23
    # checkbandwidth foo.example.com
Packit fcad23
    foo.example.com  wlan1    49200.1426 in   5538.7522 out (B/s) IO
Packit fcad23
Packit fcad23
The numbers reported are the measured bandwidth during this run.  The
Packit fcad23
"IO" letters are flags letting you know that the (I)nput or (O)utput
Packit fcad23
values are out of expected range.
Packit fcad23
Packit fcad23
3B) Run the tool inside of nagios:
Packit fcad23
Packit fcad23
    define command {
Packit fcad23
           command_name checkbandwidth
Packit fcad23
           # specific path to vendor_perl is to fix vir's /usr/local installation
Packit fcad23
           command_line /usr/bin/perl /usr/local/bin/checkbandwidth -L /var/spool/nagios/.snmp/checkbandwidth.json.lock -N -s $ARG1$
Packit fcad23
    }
Packit fcad23
Packit fcad23
=head1 OPTIONS
Packit fcad23
Packit fcad23
=head2 Configuration and setup:
Packit fcad23
Packit fcad23
=over
Packit fcad23
Packit fcad23
=item  -i STRING
Packit fcad23
Packit fcad23
=item  --add-interface=STRING
Packit fcad23
Packit fcad23
Add this interface to the list of things to report data for.
Packit fcad23
Packit fcad23
=item  -I INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
=item  --max-in-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
=item  -O INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
=item  --max-out-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
These setup options specify an maximum input and/or output bandwidth that will:
Packit fcad23
Packit fcad23
1) When running as a nagios script (using the --nagios flag), the
Packit fcad23
   script will display an appropriate message and exit with a return
Packit fcad23
   code based on whether the string passed to the flag is 'nagioswarn'
Packit fcad23
   or 'nagioscritical'.
Packit fcad23
Packit fcad23
2) When run manually or via cron, then the checkbandwidth will send an
Packit fcad23
   email address to ADDRESS about the error.
Packit fcad23
Packit fcad23
The INT field should be the bandwidth above which the error or warning
Packit fcad23
should occur.  If the :COUNT field is included, no messages will be
Packit fcad23
sent/generated until at least the COUNT'th time is seen (default: 1).
Packit fcad23
The frequency of the count is dependent on often often the script gets
Packit fcad23
called or the loop frequency occurs (--loop).
Packit fcad23
Packit fcad23
Note that the --add-interface (-i) flag is required for this flag to be useful.
Packit fcad23
Packit fcad23
See the EXAMPLES section below for more usage.
Packit fcad23
Packit fcad23
=item  -J INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
=item  --min-in-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
=item  -Q INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
=item  --min-out-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
Packit fcad23
Packit fcad23
These two are similar to --max-in-bandwidth and --max-out-bandwidth,
Packit fcad23
but set the low-water mark thresholds for an interface.
Packit fcad23
Packit fcad23
=back
Packit fcad23
Packit fcad23
=head2 Operation:
Packit fcad23
Packit fcad23
=over
Packit fcad23
Packit fcad23
=item  -c STRING
Packit fcad23
Packit fcad23
=item  --cache-file=STRING
Packit fcad23
Packit fcad23
Location to store persistent data file, which is a JSON encoded structure.
Packit fcad23
This defaults to ~/.snmp/checkbandwidth.json.
Packit fcad23
Packit fcad23
=item  -r
Packit fcad23
Packit fcad23
=item  --reset
Packit fcad23
Packit fcad23
Ignore all past data and restart the statists collection from scratch
Packit fcad23
for all hosts/interfaces.  This wipes the collected data without
Packit fcad23
wiping the configuration min/max warning/critical levels.
Packit fcad23
Packit fcad23
Packit fcad23
Packit fcad23
=item  -l INTEGER
Packit fcad23
Packit fcad23
=item  --loop=INTEGER
Packit fcad23
Packit fcad23
Run as a daemon and loop forever, displaying bandwidth performing
Packit fcad23
checks every INTEGER seconds and displaying an output summary line.
Packit fcad23
Packit fcad23
=back
Packit fcad23
Packit fcad23
=head2 Nagios mode:
Packit fcad23
Packit fcad23
The following options only make sense when acting as an interface with
Packit fcad23
nagios.  An example nagios command definition is as follows:
Packit fcad23
Packit fcad23
    define command {
Packit fcad23
           command_name checkbandwidth
Packit fcad23
           command_line /usr/local/bin/checkbandwidth -c /var/spool/nagios/.snmp/checkbandwidth.nagios.json -L /var/spool/nagios/.snmp/checkbandwidth.nagios.json.lock -N $ARG1
Packit fcad23
    }
Packit fcad23
Packit fcad23
Note that if you need to specify perl module paths, you'll need to call it as:
Packit fcad23
Packit fcad23
    /usr/bin/perl -I /usr/lib64/perl5/vendor_perl/ /usr/local/bin/checkbandwidth
Packit fcad23
Packit fcad23
You can then use a service definition such as this one:
Packit fcad23
Packit fcad23
    define service {
Packit fcad23
           service_description  bandwidth
Packit fcad23
           host_name            localhost
Packit fcad23
           check_command        checkbandwidth!localhost
Packit fcad23
           use                  ...
Packit fcad23
Packit fcad23
    }
Packit fcad23
Packit fcad23
Recommendation: Use a different configuration file for nagios hosts
Packit fcad23
than other command line usage.
Packit fcad23
Packit fcad23
Recommendation: Use a lock file (-L) to ensure that data from two
Packit fcad23
parallel calls in nagios won't be written to at once, causing the file
Packit fcad23
to be corrupted.
Packit fcad23
Packit fcad23
Recommendation: If you're monitoring a large number of hosts, use a
Packit fcad23
sepearate configuration file (and lock file) for each host by passing
Packit fcad23
an argument like -c /var/spool/nagios/.snmp/checkbandwidth.nagios.$ARG1.json
Packit fcad23
Packit fcad23
=over
Packit fcad23
Packit fcad23
=item  -N
Packit fcad23
Packit fcad23
=item  --nagios
Packit fcad23
Packit fcad23
Operate in nagios mode, which will display appropriate nagios display
Packit fcad23
error codes and exit with an exit code that nagios will process.
Packit fcad23
Packit fcad23
=item  -s 
Packit fcad23
Packit fcad23
=item --nagios-summary
Packit fcad23
Packit fcad23
Place a basic summary line of bandwidth in the output even when no
Packit fcad23
errors are currently present.  Otherwise, the output will simply be
Packit fcad23
"All interfaces ok"
Packit fcad23
Packit fcad23
Packit fcad23
Packit fcad23
=item  -L LOCKFILE
Packit fcad23
Packit fcad23
=item  --lock-file LOCKFILE
Packit fcad23
Packit fcad23
Use this file as a lockfile to ensure multiple checkbandwidth's
Packit fcad23
running won't try to write to the file at the same time.
Packit fcad23
Packit fcad23
=back
Packit fcad23
Packit fcad23
=head2 Sending mail on errors from cron
Packit fcad23
Packit fcad23
When not in nagios mode, checkbandwidth will send mail if an error
Packit fcad23
condition is reached.  The following options specify the SMTP server
Packit fcad23
parameters used to send mail.
Packit fcad23
Packit fcad23
=over
Packit fcad23
Packit fcad23
=item  -S STRING
Packit fcad23
Packit fcad23
=item  --smtp-server=STRING
Packit fcad23
Packit fcad23
The hostname or IP address of the SMTP serve to use.
Packit fcad23
Packit fcad23
Packit fcad23
Packit fcad23
=item  -P INTEGER
Packit fcad23
Packit fcad23
=item  --port=INTEGER
Packit fcad23
Packit fcad23
The port number of the SMTP server to use.
Packit fcad23
Packit fcad23
Packit fcad23
Packit fcad23
=item  -F STRING
Packit fcad23
Packit fcad23
=item  --from=STRING
Packit fcad23
Packit fcad23
The email address to put in the From: line.
Packit fcad23
Packit fcad23
Packit fcad23
=item  -q
Packit fcad23
Packit fcad23
=item  --quiet
Packit fcad23
Packit fcad23
Don't output data to the terminal (i.e., email only).
Packit fcad23
Packit fcad23
=back
Packit fcad23
Packit fcad23
=head2 Debugging:
Packit fcad23
Packit fcad23
=over
Packit fcad23
Packit fcad23
=item  -d
Packit fcad23
Packit fcad23
=item  --dump
Packit fcad23
Packit fcad23
Dump the raw JSON data when finished.
Packit fcad23
Packit fcad23
=item  -v
Packit fcad23
Packit fcad23
=item  --verbose
Packit fcad23
Packit fcad23
Log (debugging) messages to stderr about what is being done
Packit fcad23
Packit fcad23
=item  -n
Packit fcad23
Packit fcad23
=item  --noop
Packit fcad23
Packit fcad23
Don't store the collected information from the SNMP agent to the
Packit fcad23
persistent data storage file.  I.E., only compare the results to last time.
Packit fcad23
Packit fcad23
[This is highly useful when you just want to check the server from the
Packit fcad23
command line using a data store configuration cache that is being used
Packit fcad23
by another daemon (--loop mode) or by nagios], without affecting the
Packit fcad23
timing between runs]
Packit fcad23
Packit fcad23
=back
Packit fcad23
Packit fcad23
=head1 EXAMPLES
Packit fcad23
Packit fcad23
=head2 Example Setup
Packit fcad23
Packit fcad23
With these goals:
Packit fcad23
Packit fcad23
* monitor eth0
Packit fcad23
* send a message to root@localhost when the input bandwidth goes above 50000 B/s
Packit fcad23
* send a message to ops@localhost when the input bandwidth goes above 100000 B/s
Packit fcad23
Packit fcad23
This can be configured as following:
Packit fcad23
Packit fcad23
    # checkbandwidth -i eth0 -I 50000=root@localhost localhost
Packit fcad23
    # checkbandwidth -i eth0 -I 100000=ops@localhost localhost
Packit fcad23
Packit fcad23
If we want to add new configuration in the future, say for the output
Packit fcad23
interface at 50000 but with a minimum number of 3 "times seen", we can do so:
Packit fcad23
Packit fcad23
    # checkbandwidth -i eth0 -O 50000:3=root@localhost localhost
Packit fcad23
Packit fcad23
Testing it on a regalur basis is as simple as running:
Packit fcad23
Packit fcad23
    # checkbandwidth
Packit fcad23
    localhost       eth0           3907.2973 in        1638.1081 out (B/s)
Packit fcad23
Packit fcad23
=head2 Nagios usage example
Packit fcad23
Packit fcad23
* monitor eth0
Packit fcad23
* Alert at nagios 'warning' when the input bandwidth goes above 50000 B/s
Packit fcad23
* Alert at nagios 'critical' when the input bandwidth goes above 100000 B/s
Packit fcad23
Packit fcad23
    # checkbandwidth -i eth0 -I 50000=nagioswarning -O 50000=nagioswarning localhost
Packit fcad23
    # checkbandwidth -i eth0 -I 50000=nagioscritical -O 50000=nagioscritical localhost
Packit fcad23
Packit fcad23
(and you may or may not want to add -s to always include summary information)
Packit fcad23
Packit fcad23
=head1 BUGS / TODO
Packit fcad23
Packit fcad23
* There is no way to automatically remove a configured option.  IE,
Packit fcad23
  once an delivery address is designated, there is no way to remove it
Packit fcad23
  without manually editing the json file.
Packit fcad23
Packit fcad23
=head1 AUTHOR
Packit fcad23
Packit fcad23
Wes Hardaker <hardaker@users.sourceforge.net>
Packit fcad23
USC/ISI
Packit fcad23
Packit fcad23
=head1 COPYRIGHT and LICENSING
Packit fcad23
 
Packit fcad23
See the Net-SNMP COPYING file for licensing information for this script.
Packit fcad23
Packit fcad23
=cut
Packit fcad23
Packit fcad23
use JSON;
Packit fcad23
use Data::Dumper;
Packit fcad23
use Mail::Sender;
Packit fcad23
use SNMP;
Packit fcad23
use Fcntl ':flock';
Packit fcad23
Packit fcad23
use strict;
Packit fcad23
Packit fcad23
my %opts = (
Packit fcad23
            c => "$ENV{HOME}/.snmp/checkbandwidth.json",
Packit fcad23
            S => 'localhost',
Packit fcad23
            F => 'root',
Packit fcad23
            P => 25,
Packit fcad23
            );
Packit fcad23
my %storage;
Packit fcad23
my ($NAGIOS_NORMAL, $NAGIOS_WARNING, $NAGIOS_CRITICAL) = (0,1,2);
Packit fcad23
my $nagiosexit = $NAGIOS_NORMAL;
Packit fcad23
my $nagiosstr = "";
Packit fcad23
Packit fcad23
LocalGetOptions(\%opts,
Packit fcad23
           ["GUI:separator",     "Configuration and setup:"],
Packit fcad23
           ["i|add-interface=s", "Add this interface to the list of things to monitor"],
Packit fcad23
           ["I|max-in-bandwidth=s", "Alert to ADDRESS on bandwidth > INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
Packit fcad23
           ["O|max-out-bandwidth=s", "Alert to ADDRESS on bandwidth > INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
Packit fcad23
           ["J|min-in-bandwidth=s", "Alert to ADDRESS on bandwidth < INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
Packit fcad23
           ["Q|min-out-bandwidth=s", "Alert to ADDRESS on bandwidth < INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
Packit fcad23
Packit fcad23
           ["GUI:separator",     "Operation:"],
Packit fcad23
           ["c|cache-file=s", 	 "Location to store persistent data"],
Packit fcad23
           ["r|reset",         	 "Ignore all past data and start from scratch for all hosts/interfaces"],
Packit fcad23
           ["l|loop=i",          "Loop forever, displaying bandwidth every INTEGER seconds"],
Packit fcad23
Packit fcad23
           ["GUI:separator",     "Nagios mode:"],
Packit fcad23
           ["N|nagios",          "Operate in nagios mode, displaying errors/exit code for a given host"],
Packit fcad23
           ["s|nagios-summary",  "Always include summary information in the nagios output"],
Packit fcad23
           ["L|lock-file=s",     "Use a lockfile to ensure no simultaneous runs"],
Packit fcad23
Packit fcad23
           ["GUI:separator",     "Sending mail:"],
Packit fcad23
           ["S|smtp-server=s",   "SMTP server to use"],
Packit fcad23
           ["P|port=i",          "SMTP port to use"],
Packit fcad23
           ["F|from=s",          "From address to use"],
Packit fcad23
Packit fcad23
           ["GUI:separator",     "Debugging:"],
Packit fcad23
           ["q|quiet",           "Don't output data to the terminal (i.e., email only)"],
Packit fcad23
           ["d|dump",         	 "Dump the raw JSON data when finished"], 
Packit fcad23
           ["v|verbose",      	 "Log (debugging) messages to stderr about what is being done"],
Packit fcad23
           ["n|noop",         	 "Don\'t store persistent data ; compare only to last time"],
Packit fcad23
          ) || die "Illegal usage; see -h for help";
Packit fcad23
Packit fcad23
my @hosts = @ARGV; # remaining arguments should be hosts
Packit fcad23
Packit fcad23
my $cache = read_cache();
Packit fcad23
Packit fcad23
if ($#hosts == -1) {
Packit fcad23
	@hosts = keys(%{$cache->{'hosts'}});
Packit fcad23
	Verbose("loading hosts from cache: ", join(", ", @hosts));
Packit fcad23
}
Packit fcad23
Packit fcad23
my $lockfileh;
Packit fcad23
Packit fcad23
if ($opts{'d'}) {
Packit fcad23
	print to_json($cache, { ascii => 1, pretty => 1}),"\n";
Packit fcad23
} elsif ($opts{'i'}) {
Packit fcad23
	add_interface($cache, $opts{'i'}, $opts{'I'}, $opts{'O'}, $opts{'J'}, $opts{'Q'}, \@hosts);
Packit fcad23
} else {
Packit fcad23
	# just process per norm
Packit fcad23
	do {
Packit fcad23
		if ($opts{'L'}) {
Packit fcad23
			open($lockfileh, ">>$opts{L}");
Packit fcad23
			my $have_lock = flock($lockfileh, LOCK_EX | LOCK_NB);
Packit fcad23
			if (! $have_lock) {
Packit fcad23
				print STDERR "failed to get lock on $opts{L}\n";
Packit fcad23
				print "failed to get lock on $opts{L}\n";
Packit fcad23
				exit($NAGIOS_NORMAL);
Packit fcad23
			}
Packit fcad23
		}
Packit fcad23
		$cache = process_hosts($cache, \@hosts);
Packit fcad23
		if (defined($opts{'l'})) {
Packit fcad23
			Output("");
Packit fcad23
			save_cache($cache);
Packit fcad23
			sleep($opts{'l'});
Packit fcad23
		}
Packit fcad23
	} while ($opts{'l'});
Packit fcad23
}
Packit fcad23
Packit fcad23
save_cache($cache);
Packit fcad23
if ($lockfileh) {
Packit fcad23
	unlink($lockfileh);
Packit fcad23
	flock($lockfileh, LOCK_UN);  # it's ok if it fails
Packit fcad23
}
Packit fcad23
nagios_exit() if ($opts{'N'});
Packit fcad23
Packit fcad23
sub read_cache {
Packit fcad23
	# read in the json-based cache file
Packit fcad23
	my $cache = {};
Packit fcad23
	my ($buf, $content);
Packit fcad23
	
Packit fcad23
	if (-f $opts{'c'}) {
Packit fcad23
		Verbose("Reading cache file from '$opts{c}'");
Packit fcad23
Packit fcad23
		open(my $cacheh, "<", $opts{'c'});
Packit fcad23
		while (read($cacheh, $buf, 4096 * 16) > 0) {
Packit fcad23
			$content .= $buf;
Packit fcad23
		}
Packit fcad23
		close($cacheh);
Packit fcad23
			
Packit fcad23
		$cache = from_json($content); # cheap hack
Packit fcad23
	}
Packit fcad23
	return $cache;
Packit fcad23
}
Packit fcad23
Packit fcad23
sub process_hosts($$) {
Packit fcad23
	my ($cache, $hosts) = @_;
Packit fcad23
	
Packit fcad23
	# process hosts
Packit fcad23
	foreach my $host (sort @$hosts) {
Packit fcad23
		Verbose("Procesing host '$host'");
Packit fcad23
Packit fcad23
		my $sess = get_snmp_session($host);
Packit fcad23
Packit fcad23
		# we always want the uptime
Packit fcad23
		my @vars;
Packit fcad23
		push @vars, ['sysUpTime',0];
Packit fcad23
Packit fcad23
		foreach my $interface (sort keys(%{$cache->{'hosts'}{$host}{'interfaces'}})) {
Packit fcad23
			push @vars, ['ifHCInOctets', $cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'}];
Packit fcad23
			push @vars, ['ifHCOutOctets', $cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'}];
Packit fcad23
		}
Packit fcad23
		
Packit fcad23
		my $list = new SNMP::VarList(@vars);
Packit fcad23
		my $res = $sess->get($list);
Packit fcad23
Packit fcad23
		my $sysUpTime = shift @$list;
Packit fcad23
		my $sysUpTime = $sysUpTime->[2];
Packit fcad23
Packit fcad23
		my $sysUpTimeDiff = $sysUpTime - $cache->{'hosts'}{$host}{'sysUpTime'};
Packit fcad23
Packit fcad23
		foreach my $interface (sort keys(%{$cache->{'hosts'}{$host}{'interfaces'}})) {
Packit fcad23
			my $ifHCInOctets = shift @$list;
Packit fcad23
			my $ifHCOutOctets = shift @$list;
Packit fcad23
Packit fcad23
			my $ifData = $cache->{'hosts'}{$host}{'interfaces'}{$interface};
Packit fcad23
Packit fcad23
			if ($opts{'r'} ||
Packit fcad23
			    !exists($ifData->{'data'}{'ifHCInOctets'}) ||
Packit fcad23
			    !exists($cache->{'hosts'}{$host}{'sysUpTime'}) ||
Packit fcad23
			    $sysUpTimeDiff < 0) {
Packit fcad23
				# reset all past data or...
Packit fcad23
				# no data is collected yet -- just store the sysUpTime
Packit fcad23
				# XXX: should collect engine boots too
Packit fcad23
				$ifData->{'data'}{'ifHCInOctets'}  = $ifHCInOctets->[2];
Packit fcad23
				$ifData->{'data'}{'ifHCOutOctets'} = $ifHCOutOctets->[2];
Packit fcad23
				Verbose("interface $interface on $host needs to start with new data");
Packit fcad23
			} else {
Packit fcad23
				# the great analysis
Packit fcad23
Packit fcad23
				my $diffIn  = $ifHCInOctets->[2] - $ifData->{'data'}{'ifHCInOctets'};
Packit fcad23
				my $diffOut = $ifHCOutOctets->[2] - $ifData->{'data'}{'ifHCOutOctets'};
Packit fcad23
				
Packit fcad23
				Verbose("$interface   in: $ifHCInOctets->[2] - $ifData->{'data'}{'ifHCInOctets'}");
Packit fcad23
				Verbose("$interface diff: $diffIn/$sysUpTimeDiff = " . (100 * $diffIn/$sysUpTimeDiff) . " B/s");
Packit fcad23
Packit fcad23
				Verbose("$interface  out: $ifHCOutOctets->[2] - $ifData->{'data'}{'ifHCOutOctets'}");
Packit fcad23
				Verbose("$interface diff: $diffOut/$sysUpTimeDiff = " . (100 * $diffOut/$sysUpTimeDiff) . " B/s");
Packit fcad23
Packit fcad23
				my $inRate = (100 * $diffIn/$sysUpTimeDiff);
Packit fcad23
				my $outRate = (100 * $diffOut/$sysUpTimeDiff);
Packit fcad23
				
Packit fcad23
				my $inMarker = " ";
Packit fcad23
				my $outMarker = " ";
Packit fcad23
Packit fcad23
				# check maximum bandwidth limits
Packit fcad23
				if (defined($ifData->{'maxInBandwidth'})) {
Packit fcad23
					foreach my $limit (@{$ifData->{'maxInBandwidth'}}) {
Packit fcad23
						if ($inRate > 0 && $inRate > $limit->{'rate'}) {
Packit fcad23
							$inMarker = "I";
Packit fcad23
							$ifData->{'maxInBandwidthCount'} ++;
Packit fcad23
Packit fcad23
							if ($ifData->{'maxInBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
Packit fcad23
								Verbose("  input too high!!!  $inRate > $limit->{'rate'} ; emailing $limit->{'email'}");
Packit fcad23
								send_rate_message($limit->{'email'},
Packit fcad23
								                  $host, $interface, 'in', 'gt',
Packit fcad23
								                  $ifData->{'maxInBandwidthCount'},
Packit fcad23
								                  $inRate, $limit->{'rate'});
Packit fcad23
							}
Packit fcad23
						} else {
Packit fcad23
							$ifData->{'maxInBandwidthCount'} = 0;
Packit fcad23
						}
Packit fcad23
					}
Packit fcad23
				}
Packit fcad23
Packit fcad23
				if (defined($ifData->{'maxOutBandwidth'})) {
Packit fcad23
					foreach my $limit (@{$ifData->{'maxOutBandwidth'}}) {
Packit fcad23
						if ($outRate > 0 && $outRate > $limit->{'rate'}) {
Packit fcad23
							$outMarker = "O";
Packit fcad23
							$ifData->{'maxOutBandwidthCount'} ++;
Packit fcad23
							
Packit fcad23
							if ($ifData->{'maxOutBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
Packit fcad23
								Verbose("  output too high!!! $outRate > $limit->{'rate'} ; emailing $limit->{'email'}");
Packit fcad23
Packit fcad23
								send_rate_message($limit->{'email'},
Packit fcad23
								                  $host, $interface, 'out', 'gt',
Packit fcad23
								                  $ifData->{'maxOutBandwidthCount'},
Packit fcad23
								                  $outRate, $limit->{'rate'});
Packit fcad23
							}
Packit fcad23
						} else {
Packit fcad23
							$ifData->{'maxOutBandwidthCount'} = 0;
Packit fcad23
						}
Packit fcad23
					}
Packit fcad23
				}
Packit fcad23
Packit fcad23
				# check minimum bandwidth limits
Packit fcad23
				if (defined($ifData->{'minInBandwidth'})) {
Packit fcad23
					foreach my $limit (@{$ifData->{'minInBandwidth'}}) {
Packit fcad23
						if ($inRate > 0 && $inRate < $limit->{'rate'}) {
Packit fcad23
							$inMarker = "I";
Packit fcad23
							$ifData->{'minInBandwidthCount'} ++;
Packit fcad23
Packit fcad23
							if ($ifData->{'minInBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
Packit fcad23
								Verbose("  input too low!!!  $inRate < $limit->{'rate'} ; emailing $limit->{'email'}");
Packit fcad23
								send_rate_message($limit->{'email'},
Packit fcad23
								                  $host, $interface, 'in', 'lt',
Packit fcad23
								                  $ifData->{'minInBandwidthCount'},
Packit fcad23
								                  $inRate, $limit->{'rate'});
Packit fcad23
							}
Packit fcad23
						} else {
Packit fcad23
							$ifData->{'minInBandwidthCount'} = 0;
Packit fcad23
						}
Packit fcad23
					}
Packit fcad23
				}
Packit fcad23
Packit fcad23
				if (defined($ifData->{'minOutBandwidth'})) {
Packit fcad23
					foreach my $limit (@{$ifData->{'minOutBandwidth'}}) {
Packit fcad23
						if ($outRate > 0 && $outRate < $limit->{'rate'}) {
Packit fcad23
							$outMarker = "O";
Packit fcad23
							$ifData->{'minOutBandwidthCount'} ++;
Packit fcad23
							
Packit fcad23
							if ($ifData->{'minOutBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
Packit fcad23
								Verbose("  output too low!!! $outRate < $limit->{'rate'} ; emailing $limit->{'email'}");
Packit fcad23
Packit fcad23
								send_rate_message($limit->{'email'},
Packit fcad23
								                  $host, $interface, 'out', 'lt',
Packit fcad23
								                  $ifData->{'minOutBandwidthCount'},
Packit fcad23
								                  $outRate, $limit->{'rate'});
Packit fcad23
							}
Packit fcad23
						} else {
Packit fcad23
							$ifData->{'minOutBandwidthCount'} = 0;
Packit fcad23
						}
Packit fcad23
					}
Packit fcad23
				}
Packit fcad23
				
Packit fcad23
				Output(sprintf("%-15s %-8s %16.0f in %16.0f out (B/s) %s%s",
Packit fcad23
				               $host, $interface,
Packit fcad23
				               $inRate, $outRate,
Packit fcad23
				               $inMarker, $outMarker));
Packit fcad23
Packit fcad23
				# Update the cache data.  Maybe.
Packit fcad23
				if (! $opts{'n'}) {
Packit fcad23
					$ifData->{'data'}{'ifHCInOctets'}  = $ifHCInOctets->[2];
Packit fcad23
					$ifData->{'data'}{'ifHCOutOctets'} = $ifHCOutOctets->[2];
Packit fcad23
				}
Packit fcad23
			}
Packit fcad23
		}
Packit fcad23
Packit fcad23
		# store the sysUpTime
Packit fcad23
		$cache->{'hosts'}{$host}{'sysUpTime'} = $sysUpTime unless ($opts{'n'});
Packit fcad23
	}
Packit fcad23
Packit fcad23
	return $cache;
Packit fcad23
}
Packit fcad23
Packit fcad23
sub add_interface($$$$$) {
Packit fcad23
	my ($cache, $interface, $maxInBandwidth, $maxOutBandwidth, $minInBandwidth, $minOutBandwidth, $hosts) = @_;
Packit fcad23
	foreach my $host (@$hosts) {
Packit fcad23
		Verbose("adding interface $interface to $host");
Packit fcad23
Packit fcad23
		my $session = get_snmp_session($host);
Packit fcad23
		my $tabledata = $session->gettable('ifTable');
Packit fcad23
		my $found = 0;
Packit fcad23
Packit fcad23
		foreach my $key (keys(%$tabledata)) {
Packit fcad23
			# each key is an interface number; find the matching interface
Packit fcad23
			if ($tabledata->{$key}{'ifDescr'} eq $interface) {
Packit fcad23
				# found it!
Packit fcad23
Packit fcad23
				Verbose("found $interface on $host at $key; adding it");
Packit fcad23
				
Packit fcad23
				$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'} = $key;
Packit fcad23
				if ($maxInBandwidth) {
Packit fcad23
					my ($ratespec, $email) = split(/=/, $maxInBandwidth);
Packit fcad23
					my ($rate, $count)     = split(/:/, $ratespec);
Packit fcad23
					if (!defined($count)) {
Packit fcad23
						$rate = $ratespec;
Packit fcad23
						$count = 1;
Packit fcad23
					}
Packit fcad23
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'maxInBandwidth'}},
Packit fcad23
					  { rate => $rate,
Packit fcad23
					    email => $email,
Packit fcad23
					    maxcount => $count };
Packit fcad23
				}
Packit fcad23
				if ($maxOutBandwidth) {
Packit fcad23
					my ($ratespec, $email) = split(/=/, $maxOutBandwidth);
Packit fcad23
					my ($rate, $count)     = split(/:/, $ratespec);
Packit fcad23
					if (!defined($count)) {
Packit fcad23
						$rate = $ratespec;
Packit fcad23
						$count = 1;
Packit fcad23
					}
Packit fcad23
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'maxOutBandwidth'}},
Packit fcad23
					  { rate => $rate,
Packit fcad23
					    email => $email,
Packit fcad23
					    maxcount => $count};
Packit fcad23
				}
Packit fcad23
				if ($minInBandwidth) {
Packit fcad23
					my ($ratespec, $email) = split(/=/, $minInBandwidth);
Packit fcad23
					my ($rate, $count)     = split(/:/, $ratespec);
Packit fcad23
					if (!defined($count)) {
Packit fcad23
						$rate = $ratespec;
Packit fcad23
						$count = 1;
Packit fcad23
					}
Packit fcad23
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'minInBandwidth'}},
Packit fcad23
					  { rate => $rate,
Packit fcad23
					    email => $email,
Packit fcad23
					    maxcount => $count };
Packit fcad23
				}
Packit fcad23
				if ($minOutBandwidth) {
Packit fcad23
					my ($ratespec, $email) = split(/=/, $minOutBandwidth);
Packit fcad23
					my ($rate, $count)     = split(/:/, $ratespec);
Packit fcad23
					if (!defined($count)) {
Packit fcad23
						$rate = $ratespec;
Packit fcad23
						$count = 1;
Packit fcad23
					}
Packit fcad23
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'minOutBandwidth'}},
Packit fcad23
					  { rate => $rate,
Packit fcad23
					    email => $email,
Packit fcad23
					    maxcount => $count};
Packit fcad23
				}
Packit fcad23
				$found = 1;
Packit fcad23
				Output("Configured $interface for $host");
Packit fcad23
			}
Packit fcad23
		}
Packit fcad23
Packit fcad23
		if (!$found) {
Packit fcad23
			Log("  failed to find interface '$interface' on '$host'");
Packit fcad23
		}
Packit fcad23
	}
Packit fcad23
}
Packit fcad23
Packit fcad23
my %session_cache;
Packit fcad23
sub get_snmp_session($) {
Packit fcad23
	my ($host) = @_;
Packit fcad23
	if (exists($session_cache{$host})) {
Packit fcad23
		return $session_cache{$host};
Packit fcad23
	}
Packit fcad23
Packit fcad23
	my @args;
Packit fcad23
	
Packit fcad23
	if (-f "/etc/snmp/perl/$host.conf") {
Packit fcad23
		open(CC, "/etc/snmp/perl/$host.conf");
Packit fcad23
		while (<CC>) {
Packit fcad23
			next if (/^\s*#/);
Packit fcad23
			chomp;
Packit fcad23
			push @args, split();
Packit fcad23
		}
Packit fcad23
	}
Packit fcad23
Packit fcad23
	# hack to work around perl not handling per-host files correctly
Packit fcad23
	$session_cache{$host} = new SNMP::Session(DestHost => $host, @args);
Packit fcad23
	return	$session_cache{$host};
Packit fcad23
}
Packit fcad23
Packit fcad23
sub save_cache($) {
Packit fcad23
	my ($cache) = @_;
Packit fcad23
Packit fcad23
	# save data to the json cache (maybe)
Packit fcad23
	if (!$opts{'n'}) {
Packit fcad23
		my $dir = $opts{'c'};
Packit fcad23
		$dir =~ s/(.*)\/.*/$1/;
Packit fcad23
		if (! -d $dir) {
Packit fcad23
			mkdir($dir);
Packit fcad23
			if (! -d $dir) {
Packit fcad23
				Log("failed to create directory $dir");
Packit fcad23
			} else {
Packit fcad23
				Verbose("note: created directory $dir");
Packit fcad23
			}
Packit fcad23
		}
Packit fcad23
		# XXX: mkdir
Packit fcad23
		
Packit fcad23
		Verbose("Writing cache file to '$opts{c}'"); 
Packit fcad23
		
Packit fcad23
		my $jsondata = to_json($cache);
Packit fcad23
		open(my $outh, ">", $opts{'c'} . $$);
Packit fcad23
		print $outh $jsondata,"\n";
Packit fcad23
		$outh->close();
Packit fcad23
		rename($opts{'c'} . $$, $opts{'c'});
Packit fcad23
	}
Packit fcad23
}
Packit fcad23
Packit fcad23
sub send_rate_message($$$$$$) {
Packit fcad23
	my ($to, $host, $interface, $direction, $highlow, $count, $measured, $max) = @_;
Packit fcad23
Packit fcad23
	if ($opts{'N'} && ($to eq 'nagioswarn' || $to eq 'nagioscritical')) {
Packit fcad23
		# nagios mode ; collect simple output for later
Packit fcad23
		nagios_collect($to, $host, $interface, $direction, $highlow, $count, $measured, $max);
Packit fcad23
	} elsif($to ne 'nagioswarn' && $to ne 'nagioscritical') {
Packit fcad23
		# send email
Packit fcad23
		my $word = ($highlow eq '>' ? 'exceeded' : 'too low');
Packit fcad23
		send_message($to,
Packit fcad23
		             "rate $word: $host/$interface=$direction ($count times)",
Packit fcad23
		             "Rate limit $word for:\n\n" .
Packit fcad23
		             "\thost:\t\t$host\n" .
Packit fcad23
		             "\tinterface:\t$interface ($direction)\n" .
Packit fcad23
		             "\trate:\t\t$measured\n" .
Packit fcad23
		             "\tmax allowed:\t$max\n" .
Packit fcad23
		             "\tcount:\t\t$count times in a row\n");
Packit fcad23
	}
Packit fcad23
}
Packit fcad23
Packit fcad23
sub send_message($$$) {
Packit fcad23
	my ($to, $subject, $text) = @_;
Packit fcad23
Packit fcad23
	my $sender = new Mail::Sender { smtp => $opts{'S'} ,
Packit fcad23
	                                port => $opts{'P'},
Packit fcad23
	                                from => $opts{'F'},
Packit fcad23
                                  };
Packit fcad23
Packit fcad23
	my $status =
Packit fcad23
	  $sender->MailMsg({
Packit fcad23
	                    to      => $to,
Packit fcad23
	                    subject => $subject,
Packit fcad23
	                    msg     => $text
Packit fcad23
	                   });
Packit fcad23
	if ($status < 0) {
Packit fcad23
		Log("Failed to send mail with error code $status: $Mail::Sender::Error");
Packit fcad23
	}
Packit fcad23
}
Packit fcad23
Packit fcad23
sub nagios_collect($$$$$$) {
Packit fcad23
	my ($to, $host, $interface, $direction, $highlow, $count, $measured, $max) = @_;
Packit fcad23
Packit fcad23
	if ($to eq 'nagioscritical') {
Packit fcad23
		$nagiosexit = $NAGIOS_CRITICAL;
Packit fcad23
	} elsif ($to eq 'nagioswarn' && $nagiosexit < $NAGIOS_CRITICAL) {
Packit fcad23
		$nagiosexit = $NAGIOS_WARNING;
Packit fcad23
	}
Packit fcad23
Packit fcad23
	$nagiosstr .= sprintf(", %s $direction %16.0f B/s $highlow %s", $interface, $measured, $max);
Packit fcad23
}
Packit fcad23
Packit fcad23
sub nagios_exit() {
Packit fcad23
	if ($nagiosstr eq '') {
Packit fcad23
		$nagiosstr = "All interfaces ok: " . join(", ", keys(%{$cache->{'hosts'}{$hosts[0]}{'interfaces'}}));
Packit fcad23
	} else {
Packit fcad23
		$nagiosstr =~ s/^, //;
Packit fcad23
	}
Packit fcad23
Packit fcad23
	print $nagiosstr, "\n";
Packit fcad23
	exit $nagiosexit;
Packit fcad23
}
Packit fcad23
Packit fcad23
sub Verbose {
Packit fcad23
	if ($opts{'v'}) {
Packit fcad23
		print STDERR @_, "\n";
Packit fcad23
	}
Packit fcad23
}
Packit fcad23
Packit fcad23
sub Log {
Packit fcad23
	print STDERR @_,"\n";
Packit fcad23
}
Packit fcad23
Packit fcad23
sub Output {
Packit fcad23
	if (! $opts{'q'} && !$opts{'N'}) {
Packit fcad23
		print @_, "\n";
Packit fcad23
	}
Packit fcad23
	if ($opts{'N'} && $opts{'s'}) {
Packit fcad23
		$nagiosstr .= ", " . join(" ", @_);
Packit fcad23
	}
Packit fcad23
}
Packit fcad23
Packit fcad23
#### portability for not requiring Getopt::GUI::Long directly
Packit fcad23
sub LocalGetOptions {
Packit fcad23
	if (eval {require Getopt::GUI::Long;}) {
Packit fcad23
		import Getopt::GUI::Long;
Packit fcad23
		# optional configure call
Packit fcad23
		Getopt::GUI::Long::Configure(qw(display_help no_ignore_case allow_zero));
Packit fcad23
		return GetOptions(@_);
Packit fcad23
	}
Packit fcad23
	require Getopt::Long;
Packit fcad23
	import Getopt::Long;
Packit fcad23
	# optional configure call
Packit fcad23
	Getopt::Long::Configure(qw(auto_help no_ignore_case));
Packit fcad23
	GetOptions(LocalOptionsMap(@_));
Packit fcad23
}
Packit fcad23
Packit fcad23
sub LocalOptionsMap {
Packit fcad23
	my ($st, $cb, @opts) = ((ref($_[0]) eq 'HASH') 
Packit fcad23
	                        ? (1, 1, $_[0]) : (0, 2));
Packit fcad23
	for (my $i = $st; $i <= $#_; $i += $cb) {
Packit fcad23
		if ($_[$i]) {
Packit fcad23
			next if (ref($_[$i]) eq 'ARRAY' && $_[$i][0] =~ /^GUI:/);
Packit fcad23
			push @opts, ((ref($_[$i]) eq 'ARRAY') ? $_[$i][0] : $_[$i]);
Packit fcad23
			push @opts, $_[$i+1] if ($cb == 2);
Packit fcad23
		}
Packit fcad23
	}
Packit fcad23
	return @opts;
Packit fcad23
}
Packit fcad23
Packit fcad23
# Local Variables:
Packit fcad23
# tab-width: 4
Packit fcad23
# End: