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