Annotation of nagios/check_pf_limits/check_pf_limits, Revision 1.5
1.1 andrew 1: #!/usr/bin/perl -T
1.5 ! afresh1 2: # $AFresh1: check_pf_limits,v 1.4 2010/01/14 22:57:15 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;
12:
13: local %ENV = ();
14:
15: my $NAGIOS_OUTPUT = 1;
16:
17: my $LICENSE = <<'EOL';
18: Copyright (c) 2009 Andrew Fresh <andrew@afresh1.com>
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:
1.5 ! afresh1 32: our ($VERSION) = '$Revision: 1.4 $' =~ m{ \$Revision: \s+ (\S+) }xms;
1.1 andrew 33: my $PROGNAME = 'check_pf_limits';
34: my $PFCTL = '/sbin/pfctl';
35:
36: #use POSIX;
37: #use Config;
38: my $PREFIX;
39:
40: BEGIN {
41: $PREFIX = q{};
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:
59: my %STATUS = read_status( $CHECKS{_SOCKET} );
60: my %STATES = check_status( \%STATUS, \%CHECKS );
61:
62: my $have_results = 0;
63: my $state = 'OK';
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 {
92: my ($device) = @_;
93:
94: my %commands = (
95: memory => { parser => \&parse_memory },
96: info => { parser => \&parse_info, args => ['-v'] },
97:
98: # XXX Not important enough to be supported
99: #Tables => { parser => \&parse_tables },
100: );
101:
102: my %status;
103: foreach my $command ( keys %commands ) {
104: my @cmd = ($PFCTL);
105: if ($device) {
106: push @cmd, '-p', $device;
107: }
108:
109: push @cmd, '-s', $command;
110:
111: if ( exists $commands{$command}{args} ) {
112: push @cmd, @{ $commands{$command}{args} };
113: }
114:
115: my %S;
116: if ( $command eq 'Tables' ) {
117: $S{count} = 0;
118: }
119:
120: #open my $fh, '<', 'output' # XXX
121: open my $fh, q{-|}, @cmd or die qq{Couldn't open pfctl: $!\n};
122: while (<$fh>) {
123: $commands{$command}{parser}->( $_, \%S );
124: }
125: ## no critic 'die'
126: close $fh
127: or die $!
128: ? "Error closing pfctl pipe: $!\n"
129: : "Exit status $? from pfctl\n";
130:
131: $status{$command} = \%S;
132: }
133:
134: return %status;
135: }
136:
137: sub parse_memory {
138: my ( $line, $result ) = @_;
139:
140: my ( $name, $type, $limit ) = unpack "A14 A10 A*", $line;
141:
142: $limit =~ s/^\s+//xms;
143:
144: $result->{$name} = {
145: type => $type,
146: limit => $limit,
147: };
148:
149: return 1;
150: }
151:
152: sub parse_info {
153: my ( $line, $result ) = @_;
154:
155: state $section = 'Unknown';
156:
157: chomp $line;
1.5 ! afresh1 158: for ($line) {
! 159: if (/^\w+:/xms) {
1.1 andrew 160: while (
161: $line =~ s{ \s*
162: (\w+): \s+
163: ( (?:[^:]|:\S)+ )
164: \s*$ }{}xms
165: )
166: {
167: $result->{$1} = $2;
168: }
169: }
1.5 ! afresh1 170: elsif (/^\S/xms) { ($section) = unpack 'A27', $line }
! 171: elsif (/^\s\s/xms) {
1.1 andrew 172: my ( $name, $total, $rate ) = unpack 'x2 A25 x A14 A*';
173: foreach ( $total, $rate ) {
174: s/^\s+//xms;
175: }
176: $result->{$section}->{$name} = {
177: total => $total,
178: rate => $rate,
179: };
180: }
1.5 ! afresh1 181: else {return}
1.1 andrew 182: }
183:
184: return 1;
185: }
186:
187: sub parse_tables {
188: my ( $line, $result ) = @_;
189: $result->{count}++;
190: return 1;
191: }
192:
193: sub parse_check {
194: my $check = shift;
195:
1.5 ! afresh1 196: if (!$check) {
! 197: print_help();
! 198: exit $ERRORS{'OK'};
! 199: };
! 200:
1.1 andrew 201: my @values = split /,\s*/xms, $check;
202:
203: my %c = ( match => [] );
204: foreach my $v (@values) {
205: if ( $v =~ /:/xms ) {
206: ( $c{low}, $c{high} ) = split /:/xms, $v;
207: }
208: else {
209: push @{ $c{match} }, $v;
210: }
211: }
212:
213: foreach my $d ( 'low', 'high' ) {
214: if ( defined $c{$d} ) {
215: $c{$d} =~ s/[^-\d\.\%]//gxms;
216: if ( !length $c{$d} ) {
217: delete $c{$d};
218: }
219: }
220: }
221:
222: return \%c;
223: }
224:
225: sub check_status {
226: my ( $S, $C ) = @_;
227:
228: my %known_limits = (
229: states => {
230: limit => $S->{memory}{states}{limit},
231: current => $S->{info}{'State Table'}{'current entries'}{total},
232:
233: },
234: 'src-nodes' => {
235: limit => $S->{memory}{'src-nodes'}{limit},
236: current =>
237: $S->{info}{'Source Tracking Table'}{'current entries'}{total},
238: },
239:
240: # XXX Can't check frags, don't know where to read current
241: #frags => {
242: # limit => $S->{memory}{frags}{limit},
243: # current => $S->{info}{Counters}{fragment},
244: #},
245:
246: # XXX It takes an additional call to pfctl and could be long
247: #Tables => {
248: # limit => $S->{memory}{tables}{limit},
249: # current => $S->{Tables}{count},
250: #},
251:
252: # XXX Can't check table-entries, don't know where to read current
253: #table-entries => {},
254: );
255:
256: my %states;
1.4 andrew 257: my $status = $S->{info}{Status};
258: if (0 == index $status, 'Enabled') {
259: push @{ $states{OK} }, $status;
260: }
261: else {
262: push @{ $states{CRITICAL} }, $status;
263: }
264:
265:
1.1 andrew 266: my %matched;
267: STATE: foreach my $k ( keys %known_limits ) {
268: $matched{$k} = 1;
269: if ( my $c = $C->{$k} || $C->{_UNKNOWN} ) {
270: CODE: foreach my $code ( 'CRITICAL', 'WARNING' ) {
271: next CODE if ( ref $c->{$code} ne 'HASH' );
272: my $result = check_item( $known_limits{$k}, $c->{$code} );
273:
274: if ($result) {
275: push @{ $states{$code} }, "[$k] $result";
276: next STATE;
277: }
278: }
279: }
280: else {
281: push @{ $states{CRITICAL} }, '[' . $k . '] Unhandled Limit';
282: next STATE;
283: }
284:
285: push @{ $states{OK} }, $k;
286: }
287:
288: foreach my $k ( keys %{$C} ) {
1.2 andrew 289: next if 0 == index $k, '_';
1.1 andrew 290: if ( !exists $matched{$k} ) {
291: push @{ $states{CRITICAL} }, '[' . $k . '] Unsupported Limit';
292: }
293: }
294:
295: return %states;
296: }
297:
298: sub check_item {
299: my ( $d, $c ) = @_;
300:
301: my $result;
302: if ( $c->{match} && @{ $c->{match} } ) {
303: foreach my $m ( @{ $c->{match} } ) {
304: return if $m eq $d->{current};
305: }
306: $result
307: = 'State (' . $d->{current} . ') is outside of acceptable values';
308: }
309:
310: if ( $c->{low} || $c->{high} ) {
311: $result = undef;
312: my $num = $d->{current};
313: $num =~ s/[^-\d\.]//gxms;
314:
315: if ( !length $num ) {
316: return 'State (' . $d . ') is not numeric';
317: }
318:
1.5 ! afresh1 319: DIRECTION: foreach my $dir (qw< low high >) {
1.1 andrew 320: if ( !$c->{$dir} ) { next DIRECTION; }
321:
322: my $check = $c->{$dir};
323: my $cnum = $num;
324:
325: my $max = $d->{limit};
326: if ( $check =~ s/\%$//xms ) {
327: if ( !defined $max ) {
328: return 'max-prefix not specified and % check requested';
329: }
330: $num = "$num/$max";
331:
332: # convert to percent
333: $cnum = 100 * $cnum / $max;
334: }
335:
336: my @nums = ( $cnum, $check );
337: my $abovebelow = 'below';
338: my $symbol = '<';
339: if ( $dir eq 'high' ) {
340: @nums = ( $check, $cnum );
341: $abovebelow = 'above';
342: $symbol = '>';
343: }
344:
345: if ( $nums[0] < $nums[1] ) {
346: return join q{ }, 'is', $abovebelow,
347: 'threshold (' . $num,
348: $symbol, $c->{$dir} . ')';
349: }
350: }
351: }
352:
353: return $result;
354: }
355:
356: sub getopt {
357: my (@argv) = @_;
358:
1.2 andrew 359: my ( %checks, $w, $c );
1.1 andrew 360: while (@argv) {
1.5 ! afresh1 361: local $_ = shift @argv;
! 362:
! 363: if ( $_ eq '-V' || $_ eq '--version' ) {
! 364: print_revision( $PROGNAME, '$Revision: 1.4 $ ' );
! 365: exit $ERRORS{'OK'}
! 366: }
! 367: elsif (/^-?-h(?:elp)?\Z/xms) { print_help(); exit $ERRORS{'OK'} }
! 368: elsif (/^-?-p(?:ath)?\Z/xms) {
! 369: $checks{_DEVICE} = shift @argv
! 370: || do { print_help(); exit $ERRORS{'OK'} };
! 371: }
! 372: elsif (/^-?-w(?:arning)?\Z/xms) {
! 373: $w = parse_check( shift @argv )
! 374: }
! 375: elsif (/^-?-c(?:ritical)?\Z/xms) {
! 376: $c = parse_check( shift @argv )
! 377: }
! 378: elsif (/^-?-l(?:limit)?\Z/xms) {
! 379: while ( @argv && $argv[0] !~ /^-/xms ) {
! 380: $checks{ shift @argv } = {
! 381: WARNING => $w,
! 382: CRITICAL => $c,
1.1 andrew 383: }
384: }
385: }
1.5 ! afresh1 386: else { print_help(); exit $ERRORS{'UNKNOWN'} }
1.1 andrew 387: }
1.2 andrew 388:
1.3 andrew 389: if (defined $w) {
390: $checks{_UNKNOWN}{WARNING} = $w;
391: }
392: if (defined $c) {
393: $checks{_UNKNOWN}{CRITICAL} = $c;
1.2 andrew 394: };
1.3 andrew 395:
1.1 andrew 396: return %checks;
397: }
398:
399: sub print_help {
400: print <<"EOL";
401: $PROGNAME - checks status of pf limits
402: $PROGNAME [ -d device ][ -w ENTRY ][ -c ENTRY ][ -l limit ]
403:
404: Usage:
405: -d, --path Path to pf device
406: Path to pf device to use. See -p in pfctl(8).
407: -w, --warning RANGE or single ENTRY
408: Exit with WARNING status if outside of RANGE or if != ENTRY
409: May be entered multiple times.
410: -c, --critical RANGE or single ENTRY
411: Exit with CRITICAL status if outside of RANGE or if != ENTRY
412: May be entered multiple times.
413: -l, --limit LIMIT
414: The name of the limit, can be a space separated list of limits
415: May be entered multiple times.
416:
417: ENTRY is a comma separated list of items to match against. Each item can be
418: a RANGE or it will just be matched against the status.
419:
420: RANGE is specified as two optional numbers separated with a colon (:). The
421: check is that the value is between the two numbers. If either number is left
422: off, that check is ignored.
423:
424: If either number in a RANGE is specified as a percent, check is that
425: max-prefix is specified and that the number is within the specified percent.
426:
427: LIMIT is the name that shows when running 'pfctl -s memory'. Currently
428: supported limits are 'states' and 'src-nodes'.
429:
430: Any time a LIMIT is specified on the command line that is NOT a supported
431: limit causes a CRITICAL result.
432:
433: Any known limits that are unspecified are checked with the last specified
434: warning and criticical ENTRY.
435:
1.4 andrew 436: Also checks if pf is enabled and is CRITICAL if not.
437:
1.1 andrew 438: Example:
439:
440: $PROGNAME -c :90% -l src-nodes -w 10:75% -l states
441:
442: CRITICAL
443: If states or src-nodes are above 90% of their limit
444:
445: WARNING
446: If states is below 10 or above 75% of its limit
447:
448: EOL
449:
1.5 ! afresh1 450: print_revision( $PROGNAME, '$Revision: 1.4 $' );
1.1 andrew 451:
452: print $LICENSE;
453:
454: return;
455: }
456:
457: sub print_revision {
458: my ( $prog, $rev ) = @_;
459: $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
460:
461: say $prog, q{ }, $rev;
462:
463: return;
464: }
465:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>