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