EvoBSD/roles/nagios-nrpe/files/plugins_bsd/check_openbgpd
2020-07-08 17:28:12 +02:00

402 lines
11 KiB
Perl
Executable file

#!/usr/bin/perl -T
# $AFresh1: check_openbgpd,v 1.10 2015/03/26 03:44:15 andrew Exp $
########################################################################
# check_openbgpd *** A nagios check for OpenBSD bgpd
#
# 2009.11.12 #*#*# andrew fresh <andrew@afresh1.com>
########################################################################
#
# MODIFIED VERSION FOR THE NEEDS OF EVOLIX
# By Jérémy Dubois <jdubois@evolix.fr>
#
# Line 51 :
# added « open STDERR, '>&STDOUT'; »
#
# Lines 123 to 126 :
# added « or exit 2; »
# commented « or die $! » and the 2 lines below
#
########################################################################
use strict;
use warnings;
use 5.010;
use if $] >= 5.016, experimental => 'switch';
local %ENV = ();
my $NAGIOS_OUTPUT = 1;
my $LICENSE = <<'EOL';
Copyright (c) 2009-2015 Andrew Fresh <andrew@afresh1.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
EOL
my $PROGNAME = 'check_openbgpd';
my $BGPCTL = '/usr/sbin/bgpctl';
use POSIX;
use Config;
my $PREFIX;
open STDERR, '>&STDOUT';
BEGIN {
## no critic 'warnings'
no warnings 'uninitialized';
$PREFIX = "/usr/local" || '/usr/local'; # Magic for OpenBSD ports tree
}
use lib $PREFIX . '/libexec/nagios';
use utils qw($TIMEOUT %ERRORS &support);
$SIG{'ALRM'} = sub {
print("ERROR: $PROGNAME timeout\n");
exit $ERRORS{'UNKNOWN'};
};
alarm($TIMEOUT);
my %CHECKS = getopt(@ARGV);
if ( !%CHECKS ) {
print_help();
exit $ERRORS{'OK'};
}
my @STATUS = read_status( $CHECKS{_SOCKET} );
my %STATES = check_status( \@STATUS, \%CHECKS );
my $have_results = 0;
my $state = 'OK';
foreach
my $error ( reverse sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS )
{
if ( exists $STATES{$error} ) {
$have_results++;
$state = $error if $ERRORS{$state} < $ERRORS{$error};
if ($NAGIOS_OUTPUT) {
print $error . ' (' . scalar( @{ $STATES{$error} } ) . ')';
if ( $error ne 'OK' ) {
print '<br>';
print map {" - $_<br>"} @{ $STATES{$error} };
}
}
else {
print $error . ' (' . scalar( @{ $STATES{$error} } ) . "):\n";
foreach ( @{ $STATES{$error} } ) {
print " $_\n";
}
}
}
}
if ( $have_results == 0 ) {
print "No results found\n";
}
exit $ERRORS{$state};
sub read_status {
my ($socket) = @_;
my @S;
my @cmd = ($BGPCTL);
if ($socket) {
push @cmd, '-s', $socket;
}
push @cmd, 'show', 'summary';
#open my $fh, '<', 'output' # XXX
open my $fh, '-|', @cmd or die "Couldn't open bgpctl: $!\n";
while (<$fh>) {
chomp;
push @S, parse_line($_);
}
## no critic 'die'
close $fh
or exit 2;
# or die $!
# ? "Error closing sysctl pipe: $!\n"
# : "Exit status $? from sysctl\n";
return grep { exists $_->{neighbor} && $_->{as} ne 'AS' } @S;
}
sub parse_line {
my ($c) = @_;
my ( $neighbor, $as, $rcvd, $sent, $outq, $updown, $state, )
= $c
=~ /^(.*?)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/xms;
return {
neighbor => $neighbor,
as => $as,
rcvd => $rcvd,
sent => $sent,
outq => $outq,
updown => $updown,
state => $state,
line => $c,
};
}
sub parse_check {
my $check = shift;
return { match => [] } unless $check;
my @values = split /,\s*/xms, $check;
my %c = ( match => [] );
foreach my $v (@values) {
if ( $v =~ /:/xms ) {
( $c{low}, $c{high} ) = split /:/xms, $v;
}
else {
push @{ $c{match} }, $v;
}
}
foreach my $d ( 'low', 'high' ) {
if ( defined $c{$d} ) {
$c{$d} =~ s/[^-\d\.\%]//gxms;
if ( !length $c{$d} ) {
delete $c{$d};
}
}
}
return \%c;
}
sub check_status {
my ( $S, $C ) = @_;
my %states;
my %neighbors = map { $_ => $C->{$_} } qw( _SOCKET _UNKNOWN );
STATE: foreach my $s ( @{$S} ) {
my $n = $s->{neighbor};
$neighbors{$n} = $s;
my $result;
if ( my $c = $C->{$n} || $C->{_UNKNOWN} ) {
CODE: foreach my $code ( 'CRITICAL', 'WARNING' ) {
next CODE if ( ref $c->{$code} ne 'HASH' );
my $data = $s->{state};
my $result = check_item( $data, $c->{$code} );
if ($result) {
push @{ $states{$code} }, "[$n] $result";
next STATE;
}
}
}
else {
push @{ $states{CRITICAL} }, '[' . $n . '] Unknown Neighbor';
next STATE;
}
push @{ $states{OK} }, $n;
}
foreach my $n ( keys %{$C} ) {
if ( !exists $neighbors{$n} ) {
push @{ $states{CRITICAL} }, '[' . $n . '] Missing Neighbor';
}
}
return %states;
}
sub check_item {
my ( $d, $c ) = @_;
my $result;
if ( $c->{match} && @{ $c->{match} } ) {
foreach my $m ( @{ $c->{match} } ) {
return if $m eq $d;
}
$result = 'State (' . $d . ') is outside of acceptable values';
}
if ( $c->{low} || $c->{high} ) {
$result = undef;
my ( $num, $max ) = split m{/}xms, $d;
$num =~ s/[^-\d\.]//gxms;
if ( !length $num ) {
return 'State (' . $d . ') is not numeric';
}
DIRECTION: foreach my $dir (qw( low high )) {
if ( !$c->{$dir} ) { next DIRECTION; }
my $check = $c->{$dir};
my $cnum = $num;
if ( $check =~ s/\%$//xms ) {
if ( !defined $max ) {
return 'max-prefix not specified and % check requested';
}
# convert to percent
$cnum = 100 * $cnum / $max;
}
my @nums = ( $cnum, $check );
my $abovebelow = 'below';
my $symbol = '<';
if ( $dir eq 'high' ) {
@nums = ( $check, $cnum );
$abovebelow = 'above';
$symbol = '>';
}
if ( $nums[0] < $nums[1] ) {
return join q{ }, 'is', $abovebelow,
'threshold (' . $d,
$symbol, $c->{$dir} . ')';
}
}
}
return $result;
}
sub getopt {
my (@argv) = @_;
my %checks;
while (@argv) {
state( $w, $c );
my $opt = shift @argv;
for ($opt) {
when ( '-V' || '--version' ) {
print_revision( $PROGNAME, '$Revision: 1.10 $ ' );
exit $ERRORS{'OK'}
}
when (/^-?-h(?:elp)?/xms) { print_help(); exit $ERRORS{'OK'} }
when (/^-?-s(?:ocket)?/xms) { $checks{_SOCKET} = shift @argv }
when (/^-?-w(?:arning)?/xms) { $w = parse_check( shift @argv ) }
when (/^-?-c(?:ritical)?/xms) { $c = parse_check( shift @argv ) }
when (/^-?-u(?:nknown)?/xms) {
$checks{_UNKNOWN} = {
WARNING => $w,
CRITICAL => $c,
};
}
when (/^-?-n(?:eighbor)?/xms) {
while ( @argv && $argv[0] !~ /^-/xms ) {
$checks{ shift @argv } = {
WARNING => $w,
CRITICAL => $c,
};
}
}
default { print_help(); exit $ERRORS{'UNKNOWN'} }
}
}
return %checks;
}
sub print_help {
print <<"EOL";
$PROGNAME - checks status of OpenBGPd peers
$PROGNAME [ -s SOCKET ][ -w ENTRY ][ -c ENTRY ]( -u | -n NEIGHBOR )
Usage:
-s, --socket SOCKET
Path to bgpd socket to use. See -r in bgpd(8).
-w, --warning RANGE or single ENTRY
Exit with WARNING status if outside of RANGE or if != ENTRY
May be entered multiple times.
-c, --critical RANGE or single ENTRY
Exit with CRITICAL status if outside of RANGE or if != ENTRY
May be entered multiple times.
-n, --neighbor NEIGHBOR
The name of the Neighbor, can be a space separated list of neighbors.
May be entered multiple times.
-u, --unknown
As if you specified -n for all unknown neighbors
ENTRY is a comma separated list of items to match against. Each item can be
a RANGE or it will just be matched against the status.
RANGE is specified as two optional numbers separated with a colon (:). The
check is that the value is between the two numbers. If either number is left
off, that check is ignored.
If either number in a RANGE is specified as a percent, check is that
max-prefix is specified and that the number is within the specified percent.
NEIGHBOR is the name that shows when running "bgpctl show summary"
Examples:
(where many of the numbers would probably have to be multiplied by 1000)
Any time a NEIGHBOR is specified on the command line but does NOT show up in
the output causes a CRITICAL result.
Any time a NEIGHBOR that is NOT specified on the command line shows up in the
output causes a CRITICAL result. If -u is specified, it treats NEIGHBOR as if
it were specified at that position.
$PROGNAME -c Idle -n P1 -c 1:1 -n P2 -w 200:300 -c Active,10: -n P3
CRITICAL
If P1 is any value but Idle.
If P2 is any value but 1.
If P3 is below 10 or any non-numeric value other than "Active".
WARNING
If P3 is above 10 and below 200 or above 300.
$PROGNAME -u -w 50%:70% -c 10%:90% -n P2 P3
No checks of unknown neighbors.
CRITICAL
If P2 or P3 do not have max-prefix set or if they do but learned prefixes
are below 10% or above 90% of max-prefix or any non-numeric value.
WARNING
If P2 or P3 have learned prefixes below 50% or above 70% of max-prefix.
$PROGNAME -w 50%:70% -c 10%:90% -u
CRITICAL
If any neighbor does not have max-prefix set or if they do but learned
prefixes are below 10% or above 90% of max-prefix or any non-numeric value.
WARNING
If any neighbor have learned prefixes below 50% or above 70% of max-prefix.
EOL
print_revision( $PROGNAME, '$Revision: 1.10 $' );
print $LICENSE;
return;
}
sub print_revision {
my ( $prog, $rev ) = @_;
$rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
say $prog, q{ }, $rev;
return;
}