Annotation of nagios/check_pf_limits/check_pf_limits, Revision 1.4
1.1 andrew 1: #!/usr/bin/perl -T
1.4 ! andrew 2: # $AFresh1: check_pf_limits,v 1.3 2010/01/14 22:42:12 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.4 ! andrew 32: our ($VERSION) = '$Revision: 1.3 $' =~ 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;
158: given ($line) {
159: when (/^\w+:/xms) {
160: while (
161: $line =~ s{ \s*
162: (\w+): \s+
163: ( (?:[^:]|:\S)+ )
164: \s*$ }{}xms
165: )
166: {
167: $result->{$1} = $2;
168: }
169: }
170: when (/^\S/xms) { ($section) = unpack 'A27', $line }
171: when (/^\s\s/xms) {
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.2 andrew 181: default {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:
196: return { match => [] } if !$check;
197: my @values = split /,\s*/xms, $check;
198:
199: my %c = ( match => [] );
200: foreach my $v (@values) {
201: if ( $v =~ /:/xms ) {
202: ( $c{low}, $c{high} ) = split /:/xms, $v;
203: }
204: else {
205: push @{ $c{match} }, $v;
206: }
207: }
208:
209: foreach my $d ( 'low', 'high' ) {
210: if ( defined $c{$d} ) {
211: $c{$d} =~ s/[^-\d\.\%]//gxms;
212: if ( !length $c{$d} ) {
213: delete $c{$d};
214: }
215: }
216: }
217:
218: return \%c;
219: }
220:
221: sub check_status {
222: my ( $S, $C ) = @_;
223:
224: my %known_limits = (
225: states => {
226: limit => $S->{memory}{states}{limit},
227: current => $S->{info}{'State Table'}{'current entries'}{total},
228:
229: },
230: 'src-nodes' => {
231: limit => $S->{memory}{'src-nodes'}{limit},
232: current =>
233: $S->{info}{'Source Tracking Table'}{'current entries'}{total},
234: },
235:
236: # XXX Can't check frags, don't know where to read current
237: #frags => {
238: # limit => $S->{memory}{frags}{limit},
239: # current => $S->{info}{Counters}{fragment},
240: #},
241:
242: # XXX It takes an additional call to pfctl and could be long
243: #Tables => {
244: # limit => $S->{memory}{tables}{limit},
245: # current => $S->{Tables}{count},
246: #},
247:
248: # XXX Can't check table-entries, don't know where to read current
249: #table-entries => {},
250: );
251:
252: my %states;
1.4 ! andrew 253: my $status = $S->{info}{Status};
! 254: if (0 == index $status, 'Enabled') {
! 255: push @{ $states{OK} }, $status;
! 256: }
! 257: else {
! 258: push @{ $states{CRITICAL} }, $status;
! 259: }
! 260:
! 261:
1.1 andrew 262: my %matched;
263: STATE: foreach my $k ( keys %known_limits ) {
264: $matched{$k} = 1;
265: if ( my $c = $C->{$k} || $C->{_UNKNOWN} ) {
266: CODE: foreach my $code ( 'CRITICAL', 'WARNING' ) {
267: next CODE if ( ref $c->{$code} ne 'HASH' );
268: my $result = check_item( $known_limits{$k}, $c->{$code} );
269:
270: if ($result) {
271: push @{ $states{$code} }, "[$k] $result";
272: next STATE;
273: }
274: }
275: }
276: else {
277: push @{ $states{CRITICAL} }, '[' . $k . '] Unhandled Limit';
278: next STATE;
279: }
280:
281: push @{ $states{OK} }, $k;
282: }
283:
284: foreach my $k ( keys %{$C} ) {
1.2 andrew 285: next if 0 == index $k, '_';
1.1 andrew 286: if ( !exists $matched{$k} ) {
287: push @{ $states{CRITICAL} }, '[' . $k . '] Unsupported Limit';
288: }
289: }
290:
291: return %states;
292: }
293:
294: sub check_item {
295: my ( $d, $c ) = @_;
296:
297: my $result;
298: if ( $c->{match} && @{ $c->{match} } ) {
299: foreach my $m ( @{ $c->{match} } ) {
300: return if $m eq $d->{current};
301: }
302: $result
303: = 'State (' . $d->{current} . ') is outside of acceptable values';
304: }
305:
306: if ( $c->{low} || $c->{high} ) {
307: $result = undef;
308: my $num = $d->{current};
309: $num =~ s/[^-\d\.]//gxms;
310:
311: if ( !length $num ) {
312: return 'State (' . $d . ') is not numeric';
313: }
314:
315: DIRECTION: foreach my $dir qw( low high ) {
316: if ( !$c->{$dir} ) { next DIRECTION; }
317:
318: my $check = $c->{$dir};
319: my $cnum = $num;
320:
321: my $max = $d->{limit};
322: if ( $check =~ s/\%$//xms ) {
323: if ( !defined $max ) {
324: return 'max-prefix not specified and % check requested';
325: }
326: $num = "$num/$max";
327:
328: # convert to percent
329: $cnum = 100 * $cnum / $max;
330: }
331:
332: my @nums = ( $cnum, $check );
333: my $abovebelow = 'below';
334: my $symbol = '<';
335: if ( $dir eq 'high' ) {
336: @nums = ( $check, $cnum );
337: $abovebelow = 'above';
338: $symbol = '>';
339: }
340:
341: if ( $nums[0] < $nums[1] ) {
342: return join q{ }, 'is', $abovebelow,
343: 'threshold (' . $num,
344: $symbol, $c->{$dir} . ')';
345: }
346: }
347: }
348:
349: return $result;
350: }
351:
352: sub getopt {
353: my (@argv) = @_;
354:
1.2 andrew 355: my ( %checks, $w, $c );
1.1 andrew 356: while (@argv) {
357: my $opt = shift @argv;
358: given ($opt) {
359: when ( '-V' || '--version' ) {
1.4 ! andrew 360: print_revision( $PROGNAME, '$Revision: 1.3 $ ' );
1.1 andrew 361: exit $ERRORS{'OK'}
362: }
363: when (/^-?-h(?:elp)?/xms) { print_help(); exit $ERRORS{'OK'} }
364: when (/^-?-p(?:ath)?/xms) { $checks{_DEVICE} = shift @argv }
365: when (/^-?-w(?:arning)?/xms) {
366: $w = parse_check( shift @argv )
367: }
368: when (/^-?-c(?:ritical)?/xms) {
369: $c = parse_check( shift @argv )
370: }
371: when (/^-?-l(?:limit)?/xms) {
372: while ( @argv && $argv[0] !~ /^-/xms ) {
373: $checks{ shift @argv } = {
374: WARNING => $w,
375: CRITICAL => $c,
376: }
377: }
378: }
379: default { print_help(); exit $ERRORS{'UNKNOWN'} }
380: }
381: }
1.2 andrew 382:
1.3 andrew 383: if (defined $w) {
384: $checks{_UNKNOWN}{WARNING} = $w;
385: }
386: if (defined $c) {
387: $checks{_UNKNOWN}{CRITICAL} = $c;
1.2 andrew 388: };
1.3 andrew 389:
1.1 andrew 390: return %checks;
391: }
392:
393: sub print_help {
394: print <<"EOL";
395: $PROGNAME - checks status of pf limits
396: $PROGNAME [ -d device ][ -w ENTRY ][ -c ENTRY ][ -l limit ]
397:
398: Usage:
399: -d, --path Path to pf device
400: Path to pf device to use. See -p in pfctl(8).
401: -w, --warning RANGE or single ENTRY
402: Exit with WARNING status if outside of RANGE or if != ENTRY
403: May be entered multiple times.
404: -c, --critical RANGE or single ENTRY
405: Exit with CRITICAL status if outside of RANGE or if != ENTRY
406: May be entered multiple times.
407: -l, --limit LIMIT
408: The name of the limit, can be a space separated list of limits
409: May be entered multiple times.
410:
411: ENTRY is a comma separated list of items to match against. Each item can be
412: a RANGE or it will just be matched against the status.
413:
414: RANGE is specified as two optional numbers separated with a colon (:). The
415: check is that the value is between the two numbers. If either number is left
416: off, that check is ignored.
417:
418: If either number in a RANGE is specified as a percent, check is that
419: max-prefix is specified and that the number is within the specified percent.
420:
421: LIMIT is the name that shows when running 'pfctl -s memory'. Currently
422: supported limits are 'states' and 'src-nodes'.
423:
424: Any time a LIMIT is specified on the command line that is NOT a supported
425: limit causes a CRITICAL result.
426:
427: Any known limits that are unspecified are checked with the last specified
428: warning and criticical ENTRY.
429:
1.4 ! andrew 430: Also checks if pf is enabled and is CRITICAL if not.
! 431:
1.1 andrew 432: Example:
433:
434: $PROGNAME -c :90% -l src-nodes -w 10:75% -l states
435:
436: CRITICAL
437: If states or src-nodes are above 90% of their limit
438:
439: WARNING
440: If states is below 10 or above 75% of its limit
441:
442: EOL
443:
1.4 ! andrew 444: print_revision( $PROGNAME, '$Revision: 1.3 $' );
1.1 andrew 445:
446: print $LICENSE;
447:
448: return;
449: }
450:
451: sub print_revision {
452: my ( $prog, $rev ) = @_;
453: $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
454:
455: say $prog, q{ }, $rev;
456:
457: return;
458: }
459:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>