#!/usr/bin/perl -T # $RedRiver$ ######################################################################## # 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 = 0; 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.1 $ ' ); 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 - monitors sysctl hw.bgpstatus on OpenBSD $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: $PROGNAME -w 10000:300000 -c 1000:500000 -n eBGP1 eBGP2 -n eBGP3 Checks that peers eBGP1, eBGP2 and eBGP2 are within the ranges specified $PROGNAME -c 1:1 -n eBGP1 -w 200000:300000 -c 100: -n iBGP1 Checks that 1 and only 1 prefix is received from from eBGP1, warns if less than 200000 or more than 300000 prefixes are recieved from peer iBGP1 and is critical if less than 100 prefixes are recieved from peer iBGP1 $PROGNAME -c Idle -n iBGP1 -w EOL print_revision( $PROGNAME, '$Revision: 1.1 $' ); print $LICENSE; return; } sub print_revision { my ( $prog, $rev ) = @_; $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms; say $prog, q{ }, $rev; return; }