Annotation of nagios/check_pf_limits/check_pf_limits, Revision 1.3
1.1 andrew 1: #!/usr/bin/perl -T
1.3 ! andrew 2: # $AFresh1: check_pf_limits,v 1.2 2010/01/14 22:37:40 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.3 ! andrew 32: our ($VERSION) = '$Revision: 1.2 $' =~ 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;
253: my %matched;
254: STATE: foreach my $k ( keys %known_limits ) {
255: $matched{$k} = 1;
256: if ( my $c = $C->{$k} || $C->{_UNKNOWN} ) {
257: CODE: foreach my $code ( 'CRITICAL', 'WARNING' ) {
258: next CODE if ( ref $c->{$code} ne 'HASH' );
259: my $result = check_item( $known_limits{$k}, $c->{$code} );
260:
261: if ($result) {
262: push @{ $states{$code} }, "[$k] $result";
263: next STATE;
264: }
265: }
266: }
267: else {
268: push @{ $states{CRITICAL} }, '[' . $k . '] Unhandled Limit';
269: next STATE;
270: }
271:
272: push @{ $states{OK} }, $k;
273: }
274:
275: foreach my $k ( keys %{$C} ) {
1.2 andrew 276: next if 0 == index $k, '_';
1.1 andrew 277: if ( !exists $matched{$k} ) {
278: push @{ $states{CRITICAL} }, '[' . $k . '] Unsupported Limit';
279: }
280: }
281:
282: return %states;
283: }
284:
285: sub check_item {
286: my ( $d, $c ) = @_;
287:
288: my $result;
289: if ( $c->{match} && @{ $c->{match} } ) {
290: foreach my $m ( @{ $c->{match} } ) {
291: return if $m eq $d->{current};
292: }
293: $result
294: = 'State (' . $d->{current} . ') is outside of acceptable values';
295: }
296:
297: if ( $c->{low} || $c->{high} ) {
298: $result = undef;
299: my $num = $d->{current};
300: $num =~ s/[^-\d\.]//gxms;
301:
302: if ( !length $num ) {
303: return 'State (' . $d . ') is not numeric';
304: }
305:
306: DIRECTION: foreach my $dir qw( low high ) {
307: if ( !$c->{$dir} ) { next DIRECTION; }
308:
309: my $check = $c->{$dir};
310: my $cnum = $num;
311:
312: my $max = $d->{limit};
313: if ( $check =~ s/\%$//xms ) {
314: if ( !defined $max ) {
315: return 'max-prefix not specified and % check requested';
316: }
317: $num = "$num/$max";
318:
319: # convert to percent
320: $cnum = 100 * $cnum / $max;
321: }
322:
323: my @nums = ( $cnum, $check );
324: my $abovebelow = 'below';
325: my $symbol = '<';
326: if ( $dir eq 'high' ) {
327: @nums = ( $check, $cnum );
328: $abovebelow = 'above';
329: $symbol = '>';
330: }
331:
332: if ( $nums[0] < $nums[1] ) {
333: return join q{ }, 'is', $abovebelow,
334: 'threshold (' . $num,
335: $symbol, $c->{$dir} . ')';
336: }
337: }
338: }
339:
340: return $result;
341: }
342:
343: sub getopt {
344: my (@argv) = @_;
345:
1.2 andrew 346: my ( %checks, $w, $c );
1.1 andrew 347: while (@argv) {
348: my $opt = shift @argv;
349: given ($opt) {
350: when ( '-V' || '--version' ) {
1.3 ! andrew 351: print_revision( $PROGNAME, '$Revision: 1.2 $ ' );
1.1 andrew 352: exit $ERRORS{'OK'}
353: }
354: when (/^-?-h(?:elp)?/xms) { print_help(); exit $ERRORS{'OK'} }
355: when (/^-?-p(?:ath)?/xms) { $checks{_DEVICE} = shift @argv }
356: when (/^-?-w(?:arning)?/xms) {
357: $w = parse_check( shift @argv )
358: }
359: when (/^-?-c(?:ritical)?/xms) {
360: $c = parse_check( shift @argv )
361: }
362: when (/^-?-l(?:limit)?/xms) {
363: while ( @argv && $argv[0] !~ /^-/xms ) {
364: $checks{ shift @argv } = {
365: WARNING => $w,
366: CRITICAL => $c,
367: }
368: }
369: }
370: default { print_help(); exit $ERRORS{'UNKNOWN'} }
371: }
372: }
1.2 andrew 373:
1.3 ! andrew 374: if (defined $w) {
! 375: $checks{_UNKNOWN}{WARNING} = $w;
! 376: }
! 377: if (defined $c) {
! 378: $checks{_UNKNOWN}{CRITICAL} = $c;
1.2 andrew 379: };
1.3 ! andrew 380:
1.1 andrew 381: return %checks;
382: }
383:
384: sub print_help {
385: print <<"EOL";
386: $PROGNAME - checks status of pf limits
387: $PROGNAME [ -d device ][ -w ENTRY ][ -c ENTRY ][ -l limit ]
388:
389: Usage:
390: -d, --path Path to pf device
391: Path to pf device to use. See -p in pfctl(8).
392: -w, --warning RANGE or single ENTRY
393: Exit with WARNING status if outside of RANGE or if != ENTRY
394: May be entered multiple times.
395: -c, --critical RANGE or single ENTRY
396: Exit with CRITICAL status if outside of RANGE or if != ENTRY
397: May be entered multiple times.
398: -l, --limit LIMIT
399: The name of the limit, can be a space separated list of limits
400: May be entered multiple times.
401:
402: ENTRY is a comma separated list of items to match against. Each item can be
403: a RANGE or it will just be matched against the status.
404:
405: RANGE is specified as two optional numbers separated with a colon (:). The
406: check is that the value is between the two numbers. If either number is left
407: off, that check is ignored.
408:
409: If either number in a RANGE is specified as a percent, check is that
410: max-prefix is specified and that the number is within the specified percent.
411:
412: LIMIT is the name that shows when running 'pfctl -s memory'. Currently
413: supported limits are 'states' and 'src-nodes'.
414:
415: Any time a LIMIT is specified on the command line that is NOT a supported
416: limit causes a CRITICAL result.
417:
418: Any known limits that are unspecified are checked with the last specified
419: warning and criticical ENTRY.
420:
421: Example:
422:
423: $PROGNAME -c :90% -l src-nodes -w 10:75% -l states
424:
425: CRITICAL
426: If states or src-nodes are above 90% of their limit
427:
428: WARNING
429: If states is below 10 or above 75% of its limit
430:
431: EOL
432:
1.3 ! andrew 433: print_revision( $PROGNAME, '$Revision: 1.2 $' );
1.1 andrew 434:
435: print $LICENSE;
436:
437: return;
438: }
439:
440: sub print_revision {
441: my ( $prog, $rev ) = @_;
442: $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
443:
444: say $prog, q{ }, $rev;
445:
446: return;
447: }
448:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>