Annotation of nagios/check_openbgpd/check_openbgpd, Revision 1.9
1.1 andrew 1: #!/usr/bin/perl -T
1.9 ! andrew 2: # $AFresh1: check_openbgpd,v 1.8 2015/03/25 02:20:06 andrew 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.1 andrew 102: #open my $fh, '<', 'output' # 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:
137: return { match => [] } unless $check;
138: my @values = split /,\s*/xms, $check;
139:
140: my %c = ( match => [] );
141: foreach my $v (@values) {
142: if ( $v =~ /:/xms ) {
143: ( $c{low}, $c{high} ) = split /:/xms, $v;
144: }
145: else {
146: push @{ $c{match} }, $v;
147: }
148: }
149:
150: foreach my $d ( 'low', 'high' ) {
151: if ( defined $c{$d} ) {
1.3 andrew 152: $c{$d} =~ s/[^-\d\.\%]//gxms;
1.1 andrew 153: if ( !length $c{$d} ) {
154: delete $c{$d};
155: }
156: }
157: }
158:
159: return \%c;
160: }
161:
162: sub check_status {
163: my ( $S, $C ) = @_;
164:
165: my %states;
1.5 andrew 166: my %neighbors = map { $_ => $C->{$_} } qw( _SOCKET _UNKNOWN );
1.1 andrew 167: STATE: foreach my $s ( @{$S} ) {
168: my $n = $s->{neighbor};
169: $neighbors{$n} = $s;
170:
171: my $result;
172:
1.5 andrew 173: if ( my $c = $C->{$n} || $C->{_UNKNOWN} ) {
1.1 andrew 174: CODE: foreach my $code ( 'CRITICAL', 'WARNING' ) {
175: next CODE if ( ref $c->{$code} ne 'HASH' );
176: my $data = $s->{state};
177:
178: my $result = check_item( $data, $c->{$code} );
179:
180: if ($result) {
181: push @{ $states{$code} }, "[$n] $result";
182: next STATE;
183: }
184: }
185: }
186: else {
187: push @{ $states{CRITICAL} }, '[' . $n . '] Unknown Neighbor';
188: next STATE;
189: }
190:
191: push @{ $states{OK} }, $n;
192: }
193:
194: foreach my $n ( keys %{$C} ) {
195: if ( !exists $neighbors{$n} ) {
196: push @{ $states{CRITICAL} }, '[' . $n . '] Missing Neighbor';
197: }
198: }
199:
200: return %states;
201: }
202:
203: sub check_item {
204: my ( $d, $c ) = @_;
205:
206: my $result;
207:
208: if ( $c->{match} && @{ $c->{match} } ) {
209: foreach my $m ( @{ $c->{match} } ) {
210: return if $m eq $d;
211: }
212: $result = 'State (' . $d . ') is outside of acceptable values';
213: }
214:
215: if ( $c->{low} || $c->{high} ) {
216: $result = undef;
1.3 andrew 217: my ( $num, $max ) = split m{/}xms, $d;
1.1 andrew 218: $num =~ s/[^-\d\.]//gxms;
219:
220: if ( !length $num ) {
221: return 'State (' . $d . ') is not numeric';
222: }
223:
1.6 andrew 224: DIRECTION: foreach my $dir (qw( low high )) {
1.3 andrew 225: if ( !$c->{$dir} ) { next DIRECTION; }
226:
227: my $check = $c->{$dir};
228: my $cnum = $num;
229:
230: if ( $check =~ s/\%$//xms ) {
231: if ( !defined $max ) {
232: return 'max-prefix not specified and % check requested';
233: }
234:
235: # convert to percent
236: $cnum = 100 * $cnum / $max;
237: }
238:
239: my @nums = ( $cnum, $check );
240: my $abovebelow = 'below';
241: my $symbol = '<';
242: if ( $dir eq 'high' ) {
243: @nums = ( $check, $cnum );
244: $abovebelow = 'above';
245: $symbol = '>';
246: }
1.1 andrew 247:
1.3 andrew 248: if ( $nums[0] < $nums[1] ) {
249: return join q{ }, 'is', $abovebelow,
250: 'threshold (' . $d,
251: $symbol, $c->{$dir} . ')';
252: }
1.1 andrew 253: }
254: }
255:
256: return $result;
257: }
258:
259: sub getopt {
260: my (@argv) = @_;
261:
262: my %checks;
263: while (@argv) {
264: state( $w, $c );
265:
266: my $opt = shift @argv;
1.9 ! andrew 267: for ($opt) {
1.1 andrew 268: when ( '-V' || '--version' ) {
1.9 ! andrew 269: print_revision( $PROGNAME, '$Revision: 1.8 $ ' );
1.1 andrew 270: exit $ERRORS{'OK'}
271: }
1.4 andrew 272: when (/^-?-h(?:elp)?/xms) { print_help(); exit $ERRORS{'OK'} }
1.5 andrew 273: when (/^-?-s(?:ocket)?/xms) { $checks{_SOCKET} = shift @argv }
1.2 andrew 274: when (/^-?-w(?:arning)?/xms) { $w = parse_check( shift @argv ) }
275: when (/^-?-c(?:ritical)?/xms) { $c = parse_check( shift @argv ) }
1.4 andrew 276: when (/^-?-u(?:nknown)?/xms) {
1.5 andrew 277: $checks{_UNKNOWN} = {
1.4 andrew 278: WARNING => $w,
279: CRITICAL => $c,
280: }
281: }
1.2 andrew 282: when (/^-?-n(?:eighbor)?/xms) {
1.1 andrew 283: while ( @argv && $argv[0] !~ /^-/xms ) {
284: $checks{ shift @argv } = {
285: WARNING => $w,
1.4 andrew 286: CRITICAL => $c,
1.1 andrew 287: }
288: }
289: }
290: default { print_help(); exit $ERRORS{'UNKNOWN'} }
291: }
292: }
293: return %checks;
294: }
295:
296: sub print_help {
297: print <<"EOL";
1.2 andrew 298: $PROGNAME - checks status of OpenBGPd peers
1.5 andrew 299: $PROGNAME [ -s SOCKET ][ -w ENTRY ][ -c ENTRY ]( -u | -n NEIGHBOR )
1.1 andrew 300:
301: Usage:
1.5 andrew 302: -s, --socket SOCKET
303: Path to bgpd socket to use. See -r in bgpd(8).
1.1 andrew 304: -w, --warning RANGE or single ENTRY
305: Exit with WARNING status if outside of RANGE or if != ENTRY
1.4 andrew 306: May be entered multiple times.
1.1 andrew 307: -c, --critical RANGE or single ENTRY
308: Exit with CRITICAL status if outside of RANGE or if != ENTRY
1.4 andrew 309: May be entered multiple times.
1.1 andrew 310: -n, --neighbor NEIGHBOR
1.4 andrew 311: The name of the Neighbor, can be a space separated list of neighbors.
312: May be entered multiple times.
313: -u, --unknown
314: As if you specified -n for all unknown neighbors
1.1 andrew 315:
316: ENTRY is a comma separated list of items to match against. Each item can be
317: a RANGE or it will just be matched against the status.
318:
319: RANGE is specified as two optional numbers separated with a colon (:). The
320: check is that the value is between the two numbers. If either number is left
1.3 andrew 321: off, that check is ignored.
322:
323: If either number in a RANGE is specified as a percent, check is that
324: max-prefix is specified and that the number is within the specified percent.
1.1 andrew 325:
326: NEIGHBOR is the name that shows when running "bgpctl show summary"
327:
328: Examples:
1.2 andrew 329: (where many of the numbers would probably have to be multiplied by 1000)
1.1 andrew 330:
1.2 andrew 331: Any time a NEIGHBOR is specified on the command line but does NOT show up in
332: the output causes a CRITICAL result.
1.1 andrew 333:
1.2 andrew 334: Any time a NEIGHBOR that is NOT specified on the command line shows up in the
1.4 andrew 335: output causes a CRITICAL result. If -u is specified, it treats NEIGHBOR as if
336: it were specified at that position.
1.2 andrew 337:
338:
339: $PROGNAME -c Idle -n P1 -c 1:1 -n P2 -w 200:300 -c Active,10: -n P3
340:
341: CRITICAL
342: If P1 is any value but Idle.
343: If P2 is any value but 1.
344: If P3 is below 10 or any non-numeric value other than "Active".
345:
346: WARNING
347: If P3 is above 10 and below 200 or above 300.
1.1 andrew 348:
1.3 andrew 349:
1.4 andrew 350: $PROGNAME -u -w 50%:70% -c 10%:90% -n P2 P3
1.3 andrew 351:
1.4 andrew 352: No checks of unknown neighbors.
1.3 andrew 353:
354: CRITICAL
355: If P2 or P3 do not have max-prefix set or if they do but learned prefixes
1.4 andrew 356: are below 10% or above 90% of max-prefix or any non-numeric value.
357:
358: WARNING
359: If P2 or P3 have learned prefixes below 50% or above 70% of max-prefix.
360:
361:
362: $PROGNAME -w 50%:70% -c 10%:90% -u
363:
364: CRITICAL
365: If any neighbor does not have max-prefix set or if they do but learned
366: prefixes are below 10% or above 90% of max-prefix or any non-numeric value.
1.3 andrew 367:
368: WARNING
1.4 andrew 369: If any neighbor have learned prefixes below 50% or above 70% of max-prefix.
1.3 andrew 370:
1.1 andrew 371: EOL
372:
1.9 ! andrew 373: print_revision( $PROGNAME, '$Revision: 1.8 $' );
1.1 andrew 374:
375: print $LICENSE;
376:
377: return;
378: }
379:
380: sub print_revision {
381: my ( $prog, $rev ) = @_;
382: $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
383:
384: say $prog, q{ }, $rev;
385:
386: return;
387: }
388:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>