[BACK]Return to check_pf_limits CVS log [TXT][DIR] Up to [local] / nagios / check_pf_limits

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>