#!/usr/bin/perl -T # $RedRiver: check_openbgpd,v 1.1 2009/11/13 02:05:20 andrew Exp $ ######################################################################## # check_openbgpd *** A nagios check for OpenBSD bgpd # # 2009.11.12 #*#*# andrew fresh ######################################################################## use strict; use warnings; use 5.010; local %ENV = (); my $NAGIOS_OUTPUT = 1; my $LICENSE = <<'EOL'; Copyright (c) 2009 Andrew Fresh 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; BEGIN { ## no critic 'warnings' no warnings 'uninitialized'; $PREFIX = "${PREFIX}" || '/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(); 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 '
'; print map {" - $_
"} @{ $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 ($status) = @_; my @S; #open my $fh, '<', 'output' # XXX open my $fh, '-|', $BGPCTL, 'show', 'summary' or die "Couldn't open bgpctl: $!\n"; while (<$fh>) { chomp; push @S, parse_line($_); } ## no critic 'die' close $fh 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; STATE: foreach my $s ( @{$S} ) { my $n = $s->{neighbor}; $neighbors{$n} = $s; my $result; if ( my $c = $C->{$n} ) { 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 = $d; $num =~ s/[^-\d\.]//gxms; if ( !length $num ) { return 'State (' . $d . ') is not numeric'; } if ( $c->{low} && $num < $c->{low} ) { return 'is below threshold (' . $d . ' < ' . $c->{low} . ')'; } if ( $c->{high} && $num > $c->{high} ) { return 'is above threshold (' . $d . ' > ' . $c->{high} . ')'; } } return $result; } sub getopt { my (@argv) = @_; my %checks; while (@argv) { state( $w, $c ); my $opt = shift @argv; given ($opt) { when ( '-V' || '--version' ) { print_revision( $PROGNAME, '$Revision: 1.2 $ ' ); exit $ERRORS{'OK'} } when (/^-?-h(?:elp)?/xms) { print_help(); exit $ERRORS{'OK'} } when (/^-?-w(?:arning)?/xms) { $w = parse_check( shift @argv ) } when (/^-?-c(?:ritical)?/xms) { $c = parse_check( shift @argv ) } 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 [ -w ENTRY ][ -c ENTRY ][ -n NEIGHBOR [ NEIGHBOR2 ] ] Usage: -w, --warning RANGE or single ENTRY Exit with WARNING status if outside of RANGE or if != ENTRY -c, --critical RANGE or single ENTRY Exit with CRITICAL status if outside of RANGE or if != ENTRY -n, --neighbor NEIGHBOR The name of the Neighbor 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 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. $PROGNAME -w 10:300 -c 10:500 -n P1 P2 -n P3 CRITICAL If any of P1, P2, P3 are below 10, above 500 or any non-numeric value. WARNING If any of P1, P2, P3 are above 300. $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. EOL print_revision( $PROGNAME, '$Revision: 1.2 $' ); print $LICENSE; return; } sub print_revision { my ( $prog, $rev ) = @_; $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms; say $prog, q{ }, $rev; return; }