Annotation of nagios/check_openbgpd/check_openbgpd, Revision 1.12
1.1 andrew 1: #!/usr/bin/perl -T
1.12 ! afresh1 2: # $AFresh1: check_openbgpd,v 1.11 2024/06/09 22:34:58 afresh1 Exp $
1.1 andrew 3: ########################################################################
4: # check_openbgpd *** A nagios check for OpenBSD bgpd
5: #
6: # 2009.11.12 #*#*# andrew fresh <andrew@afresh1.com>
7: ########################################################################
8: use strict;
9: use warnings;
10:
11: use 5.010;
1.9 andrew 12: use if $] >= 5.016, experimental => 'switch';
1.1 andrew 13:
14: local %ENV = ();
15:
1.2 andrew 16: my $NAGIOS_OUTPUT = 1;
1.1 andrew 17:
18: my $LICENSE = <<'EOL';
1.7 andrew 19: Copyright (c) 2009-2015 Andrew Fresh <andrew@afresh1.com>
1.1 andrew 20: Permission to use, copy, modify, and distribute this software for any
21: purpose with or without fee is hereby granted, provided that the above
22: copyright notice and this permission notice appear in all copies.
23:
24: THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
25: WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
26: MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
27: ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
28: WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
29: ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
30: OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
31: EOL
32:
33: my $PROGNAME = 'check_openbgpd';
34: my $BGPCTL = '/usr/sbin/bgpctl';
35:
36: use POSIX;
37: use Config;
38: my $PREFIX;
39:
40: BEGIN {
41: ## no critic 'warnings'
42: no warnings 'uninitialized';
43: $PREFIX = "${PREFIX}" || '/usr/local'; # Magic for OpenBSD ports tree
44: }
45: use lib $PREFIX . '/libexec/nagios';
46: use utils qw($TIMEOUT %ERRORS &support);
47:
48: $SIG{'ALRM'} = sub {
49: print("ERROR: $PROGNAME timeout\n");
50: exit $ERRORS{'UNKNOWN'};
51: };
52: alarm($TIMEOUT);
53:
54: my %CHECKS = getopt(@ARGV);
55: if ( !%CHECKS ) {
56: print_help();
57: exit $ERRORS{'OK'};
58: }
59:
1.5 andrew 60: my @STATUS = read_status( $CHECKS{_SOCKET} );
1.1 andrew 61: my %STATES = check_status( \@STATUS, \%CHECKS );
62:
63: my $have_results = 0;
1.2 andrew 64: my $state = 'OK';
1.1 andrew 65: foreach
66: my $error ( reverse sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS )
67: {
68: if ( exists $STATES{$error} ) {
69: $have_results++;
70: $state = $error if $ERRORS{$state} < $ERRORS{$error};
71:
72: if ($NAGIOS_OUTPUT) {
73: print $error . ' (' . scalar( @{ $STATES{$error} } ) . ')';
74: if ( $error ne 'OK' ) {
75: print '<br>';
76: print map {" - $_<br>"} @{ $STATES{$error} };
77: }
78: }
79: else {
80: print $error . ' (' . scalar( @{ $STATES{$error} } ) . "):\n";
81: foreach ( @{ $STATES{$error} } ) {
82: print " $_\n";
83: }
84: }
85: }
86: }
87: if ( $have_results == 0 ) {
88: print "No results found\n";
89: }
90: exit $ERRORS{$state};
91:
92: sub read_status {
1.5 andrew 93: my ($socket) = @_;
1.1 andrew 94: my @S;
95:
1.5 andrew 96: my @cmd = ($BGPCTL);
97: if ($socket) {
98: push @cmd, '-s', $socket;
99: }
100: push @cmd, 'show', 'summary';
101:
1.12 ! afresh1 102: #open my $fh, '<', 'output' or die $!; # XXX
1.5 andrew 103: open my $fh, '-|', @cmd or die "Couldn't open bgpctl: $!\n";
1.1 andrew 104: while (<$fh>) {
105: chomp;
106: push @S, parse_line($_);
107: }
108: ## no critic 'die'
109: close $fh
110: or die $!
111: ? "Error closing sysctl pipe: $!\n"
112: : "Exit status $? from sysctl\n";
113:
114: return grep { exists $_->{neighbor} && $_->{as} ne 'AS' } @S;
115: }
116:
117: sub parse_line {
118: my ($c) = @_;
119: my ( $neighbor, $as, $rcvd, $sent, $outq, $updown, $state, )
120: = $c
121: =~ /^(.*?)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/xms;
122: return {
123: neighbor => $neighbor,
124: as => $as,
125: rcvd => $rcvd,
126: sent => $sent,
127: outq => $outq,
128: updown => $updown,
129: state => $state,
130: line => $c,
131: };
132: }
133:
134: sub parse_check {
135: my $check = shift;
136:
1.11 afresh1 137: unless ($check) {
138: print_help();
139: exit $ERRORS{'OK'}
140: }
141:
1.1 andrew 142: my @values = split /,\s*/xms, $check;
143:
144: my %c = ( match => [] );
145: foreach my $v (@values) {
146: if ( $v =~ /:/xms ) {
147: ( $c{low}, $c{high} ) = split /:/xms, $v;
148: }
149: else {
150: push @{ $c{match} }, $v;
151: }
152: }
153:
154: foreach my $d ( 'low', 'high' ) {
155: if ( defined $c{$d} ) {
1.3 andrew 156: $c{$d} =~ s/[^-\d\.\%]//gxms;
1.1 andrew 157: if ( !length $c{$d} ) {
158: delete $c{$d};
159: }
160: }
161: }
162:
163: return \%c;
164: }
165:
166: sub check_status {
167: my ( $S, $C ) = @_;
168:
169: my %states;
1.5 andrew 170: my %neighbors = map { $_ => $C->{$_} } qw( _SOCKET _UNKNOWN );
1.1 andrew 171: STATE: foreach my $s ( @{$S} ) {
172: my $n = $s->{neighbor};
173: $neighbors{$n} = $s;
174:
175: my $result;
176:
1.5 andrew 177: if ( my $c = $C->{$n} || $C->{_UNKNOWN} ) {
1.1 andrew 178: CODE: foreach my $code ( 'CRITICAL', 'WARNING' ) {
179: next CODE if ( ref $c->{$code} ne 'HASH' );
180: my $data = $s->{state};
181:
182: my $result = check_item( $data, $c->{$code} );
183:
184: if ($result) {
185: push @{ $states{$code} }, "[$n] $result";
186: next STATE;
187: }
188: }
189: }
190: else {
191: push @{ $states{CRITICAL} }, '[' . $n . '] Unknown Neighbor';
192: next STATE;
193: }
194:
195: push @{ $states{OK} }, $n;
196: }
197:
198: foreach my $n ( keys %{$C} ) {
199: if ( !exists $neighbors{$n} ) {
200: push @{ $states{CRITICAL} }, '[' . $n . '] Missing Neighbor';
201: }
202: }
203:
204: return %states;
205: }
206:
207: sub check_item {
208: my ( $d, $c ) = @_;
209:
210: my $result;
211:
212: if ( $c->{match} && @{ $c->{match} } ) {
213: foreach my $m ( @{ $c->{match} } ) {
214: return if $m eq $d;
215: }
216: $result = 'State (' . $d . ') is outside of acceptable values';
217: }
218:
219: if ( $c->{low} || $c->{high} ) {
220: $result = undef;
1.3 andrew 221: my ( $num, $max ) = split m{/}xms, $d;
1.1 andrew 222: $num =~ s/[^-\d\.]//gxms;
223:
224: if ( !length $num ) {
225: return 'State (' . $d . ') is not numeric';
226: }
227:
1.6 andrew 228: DIRECTION: foreach my $dir (qw( low high )) {
1.3 andrew 229: if ( !$c->{$dir} ) { next DIRECTION; }
230:
231: my $check = $c->{$dir};
232: my $cnum = $num;
233:
234: if ( $check =~ s/\%$//xms ) {
235: if ( !defined $max ) {
236: return 'max-prefix not specified and % check requested';
237: }
238:
239: # convert to percent
240: $cnum = 100 * $cnum / $max;
241: }
242:
243: my @nums = ( $cnum, $check );
244: my $abovebelow = 'below';
245: my $symbol = '<';
246: if ( $dir eq 'high' ) {
247: @nums = ( $check, $cnum );
248: $abovebelow = 'above';
249: $symbol = '>';
250: }
1.1 andrew 251:
1.3 andrew 252: if ( $nums[0] < $nums[1] ) {
253: return join q{ }, 'is', $abovebelow,
254: 'threshold (' . $d,
255: $symbol, $c->{$dir} . ')';
256: }
1.1 andrew 257: }
258: }
259:
260: return $result;
261: }
262:
263: sub getopt {
264: my (@argv) = @_;
265:
266: my %checks;
267: while (@argv) {
268: state( $w, $c );
269:
270: my $opt = shift @argv;
1.9 andrew 271: for ($opt) {
1.11 afresh1 272: if ( $_ eq '-V' || $_ eq '--version' ) {
1.12 ! afresh1 273: print_revision( $PROGNAME, '$Revision: 1.11 $ ' );
1.1 andrew 274: exit $ERRORS{'OK'}
275: }
1.11 afresh1 276: elsif (/^-?-h(?:elp)?\Z/xms) { print_help(); exit $ERRORS{'OK'} }
277: elsif (/^-?-s(?:ocket)?\Z/xms) {
278: $checks{_SOCKET} = shift @argv
279: || do { print_help(); exit $ERRORS{'OK'} }
280: }
281: elsif (/^-?-w(?:arning)?\Z/xms) { $w = parse_check( shift @argv ) }
282: elsif (/^-?-c(?:ritical)?\Z/xms) { $c = parse_check( shift @argv ) }
283: elsif (/^-?-u(?:nknown)?\Z/xms) {
1.5 andrew 284: $checks{_UNKNOWN} = {
1.10 andrew 285: WARNING => $w,
286: CRITICAL => $c,
287: };
1.4 andrew 288: }
1.11 afresh1 289: elsif (/^-?-n(?:eighbor)?\Z/xms) {
1.1 andrew 290: while ( @argv && $argv[0] !~ /^-/xms ) {
291: $checks{ shift @argv } = {
1.10 andrew 292: WARNING => $w,
293: CRITICAL => $c,
294: };
1.1 andrew 295: }
296: }
1.11 afresh1 297: else { print_help(); exit $ERRORS{'UNKNOWN'} }
1.1 andrew 298: }
299: }
300: return %checks;
301: }
302:
303: sub print_help {
304: print <<"EOL";
1.2 andrew 305: $PROGNAME - checks status of OpenBGPd peers
1.5 andrew 306: $PROGNAME [ -s SOCKET ][ -w ENTRY ][ -c ENTRY ]( -u | -n NEIGHBOR )
1.1 andrew 307:
308: Usage:
1.5 andrew 309: -s, --socket SOCKET
310: Path to bgpd socket to use. See -r in bgpd(8).
1.1 andrew 311: -w, --warning RANGE or single ENTRY
312: Exit with WARNING status if outside of RANGE or if != ENTRY
1.4 andrew 313: May be entered multiple times.
1.1 andrew 314: -c, --critical RANGE or single ENTRY
315: Exit with CRITICAL status if outside of RANGE or if != ENTRY
1.4 andrew 316: May be entered multiple times.
1.1 andrew 317: -n, --neighbor NEIGHBOR
1.4 andrew 318: The name of the Neighbor, can be a space separated list of neighbors.
319: May be entered multiple times.
320: -u, --unknown
321: As if you specified -n for all unknown neighbors
1.1 andrew 322:
323: ENTRY is a comma separated list of items to match against. Each item can be
324: a RANGE or it will just be matched against the status.
325:
326: RANGE is specified as two optional numbers separated with a colon (:). The
327: check is that the value is between the two numbers. If either number is left
1.3 andrew 328: off, that check is ignored.
329:
330: If either number in a RANGE is specified as a percent, check is that
331: max-prefix is specified and that the number is within the specified percent.
1.1 andrew 332:
333: NEIGHBOR is the name that shows when running "bgpctl show summary"
334:
335: Examples:
1.2 andrew 336: (where many of the numbers would probably have to be multiplied by 1000)
1.1 andrew 337:
1.2 andrew 338: Any time a NEIGHBOR is specified on the command line but does NOT show up in
339: the output causes a CRITICAL result.
1.1 andrew 340:
1.2 andrew 341: Any time a NEIGHBOR that is NOT specified on the command line shows up in the
1.4 andrew 342: output causes a CRITICAL result. If -u is specified, it treats NEIGHBOR as if
343: it were specified at that position.
1.2 andrew 344:
345:
346: $PROGNAME -c Idle -n P1 -c 1:1 -n P2 -w 200:300 -c Active,10: -n P3
347:
348: CRITICAL
349: If P1 is any value but Idle.
350: If P2 is any value but 1.
351: If P3 is below 10 or any non-numeric value other than "Active".
352:
353: WARNING
354: If P3 is above 10 and below 200 or above 300.
1.1 andrew 355:
1.3 andrew 356:
1.4 andrew 357: $PROGNAME -u -w 50%:70% -c 10%:90% -n P2 P3
1.3 andrew 358:
1.4 andrew 359: No checks of unknown neighbors.
1.3 andrew 360:
361: CRITICAL
362: If P2 or P3 do not have max-prefix set or if they do but learned prefixes
1.4 andrew 363: are below 10% or above 90% of max-prefix or any non-numeric value.
364:
365: WARNING
366: If P2 or P3 have learned prefixes below 50% or above 70% of max-prefix.
367:
368:
369: $PROGNAME -w 50%:70% -c 10%:90% -u
370:
371: CRITICAL
372: If any neighbor does not have max-prefix set or if they do but learned
373: prefixes are below 10% or above 90% of max-prefix or any non-numeric value.
1.3 andrew 374:
375: WARNING
1.4 andrew 376: If any neighbor have learned prefixes below 50% or above 70% of max-prefix.
1.3 andrew 377:
1.1 andrew 378: EOL
379:
1.12 ! afresh1 380: print_revision( $PROGNAME, '$Revision: 1.11 $' );
1.1 andrew 381:
382: print $LICENSE;
383:
384: return;
385: }
386:
387: sub print_revision {
388: my ( $prog, $rev ) = @_;
389: $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
390:
391: say $prog, q{ }, $rev;
392:
393: return;
394: }
395:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>