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

File: [local] / nagios / check_pf_limits / check_pf_limits (download)

Revision 1.4, Thu Jan 14 22:57:15 2010 UTC (14 years, 3 months ago) by andrew
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +15 -4 lines

Also check if PF is enabled

#!/usr/bin/perl -T
# $AFresh1: check_pf_limits,v 1.4 2010/01/14 22:57:15 andrew Exp $
########################################################################
# check_openbgpd *** A nagios check for OpenBSD bgpd
#
# 2009.11.12 #*#*# andrew fresh <andrew@afresh1.com>
########################################################################
use strict;
use warnings;

use 5.010;

local %ENV = ();

my $NAGIOS_OUTPUT = 1;

my $LICENSE = <<'EOL';
Copyright (c) 2009 Andrew Fresh <andrew@afresh1.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
EOL

our ($VERSION) = '$Revision: 1.4 $' =~ m{ \$Revision: \s+ (\S+) }xms;
my $PROGNAME = 'check_pf_limits';
my $PFCTL    = '/sbin/pfctl';

#use POSIX;
#use Config;
my $PREFIX;

BEGIN {
    $PREFIX = q{};
    $PREFIX = "${PREFIX}" || '/usr/local';    # Magic for OpenBSD ports tree
}
use lib $PREFIX . '/libexec/nagios';
use utils qw($TIMEOUT %ERRORS &support);

$SIG{'ALRM'} = sub {
    print("ERROR: $PROGNAME timeout\n");
    exit $ERRORS{'UNKNOWN'};
};
alarm($TIMEOUT);

my %CHECKS = getopt(@ARGV);
if ( !%CHECKS ) {
    print_help();
    exit $ERRORS{'OK'};
}

my %STATUS = read_status( $CHECKS{_SOCKET} );
my %STATES = check_status( \%STATUS, \%CHECKS );

my $have_results = 0;
my $state        = 'OK';
foreach
    my $error ( reverse sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS )
{
    if ( exists $STATES{$error} ) {
        $have_results++;
        $state = $error if $ERRORS{$state} < $ERRORS{$error};

        if ($NAGIOS_OUTPUT) {
            print $error . ' (' . scalar( @{ $STATES{$error} } ) . ')';
            if ( $error ne 'OK' ) {
                print '<br>';
                print map {" - $_<br>"} @{ $STATES{$error} };
            }
        }
        else {
            print $error . ' (' . scalar( @{ $STATES{$error} } ) . "):\n";
            foreach ( @{ $STATES{$error} } ) {
                print "   $_\n";
            }
        }
    }
}
if ( $have_results == 0 ) {
    print "No results found\n";
}
exit $ERRORS{$state};

sub read_status {
    my ($device) = @_;

    my %commands = (
        memory => { parser => \&parse_memory },
        info   => { parser => \&parse_info, args => ['-v'] },

        # XXX Not important enough to be supported
        #Tables => { parser => \&parse_tables },
    );

    my %status;
    foreach my $command ( keys %commands ) {
        my @cmd = ($PFCTL);
        if ($device) {
            push @cmd, '-p', $device;
        }

        push @cmd, '-s', $command;

        if ( exists $commands{$command}{args} ) {
            push @cmd, @{ $commands{$command}{args} };
        }

        my %S;
        if ( $command eq 'Tables' ) {
            $S{count} = 0;
        }

        #open my $fh, '<', 'output'    # XXX
        open my $fh, q{-|}, @cmd or die qq{Couldn't open pfctl: $!\n};
        while (<$fh>) {
            $commands{$command}{parser}->( $_, \%S );
        }
        ## no critic 'die'
        close $fh
            or die $!
            ? "Error closing pfctl pipe: $!\n"
            : "Exit status $? from pfctl\n";

        $status{$command} = \%S;
    }

    return %status;
}

sub parse_memory {
    my ( $line, $result ) = @_;

    my ( $name, $type, $limit ) = unpack "A14 A10 A*", $line;

    $limit =~ s/^\s+//xms;

    $result->{$name} = {
        type  => $type,
        limit => $limit,
    };

    return 1;
}

sub parse_info {
    my ( $line, $result ) = @_;

    state $section = 'Unknown';

    chomp $line;
    given ($line) {
        when (/^\w+:/xms) {
            while (
                $line =~ s{ \s*
                (\w+): \s+ 
                ( (?:[^:]|:\S)+ )
                \s*$ }{}xms
                )
            {
                $result->{$1} = $2;
            }
        }
        when (/^\S/xms) { ($section) = unpack 'A27', $line }
        when (/^\s\s/xms) {
            my ( $name, $total, $rate ) = unpack 'x2 A25 x A14 A*';
            foreach ( $total, $rate ) {
                s/^\s+//xms;
            }
            $result->{$section}->{$name} = {
                total => $total,
                rate  => $rate,
            };
        }
        default {return}
    }

    return 1;
}

sub parse_tables {
    my ( $line, $result ) = @_;
    $result->{count}++;
    return 1;
}

sub parse_check {
    my $check = shift;

    return { match => [] } if !$check;
    my @values = split /,\s*/xms, $check;

    my %c = ( match => [] );
    foreach my $v (@values) {
        if ( $v =~ /:/xms ) {
            ( $c{low}, $c{high} ) = split /:/xms, $v;
        }
        else {
            push @{ $c{match} }, $v;
        }
    }

    foreach my $d ( 'low', 'high' ) {
        if ( defined $c{$d} ) {
            $c{$d} =~ s/[^-\d\.\%]//gxms;
            if ( !length $c{$d} ) {
                delete $c{$d};
            }
        }
    }

    return \%c;
}

sub check_status {
    my ( $S, $C ) = @_;

    my %known_limits = (
        states => {
            limit   => $S->{memory}{states}{limit},
            current => $S->{info}{'State Table'}{'current entries'}{total},

        },
        'src-nodes' => {
            limit => $S->{memory}{'src-nodes'}{limit},
            current =>
                $S->{info}{'Source Tracking Table'}{'current entries'}{total},
        },

        # XXX Can't check frags, don't know where to read current
        #frags => {
        #    limit   => $S->{memory}{frags}{limit},
        #    current => $S->{info}{Counters}{fragment},
        #},

        # XXX It takes an additional call to pfctl and could be long
        #Tables => {
        #    limit   => $S->{memory}{tables}{limit},
        #    current => $S->{Tables}{count},
        #},

        # XXX Can't check table-entries, don't know where to read current
        #table-entries => {},
    );

    my %states;
    my $status = $S->{info}{Status};
    if (0 == index $status, 'Enabled') {
        push @{ $states{OK} }, $status;
    }
    else {
        push @{ $states{CRITICAL} }, $status;
    }


    my %matched;
STATE: foreach my $k ( keys %known_limits ) {
        $matched{$k} = 1;
        if ( my $c = $C->{$k} || $C->{_UNKNOWN} ) {
        CODE: foreach my $code ( 'CRITICAL', 'WARNING' ) {
                next CODE if ( ref $c->{$code} ne 'HASH' );
                my $result = check_item( $known_limits{$k}, $c->{$code} );

                if ($result) {
                    push @{ $states{$code} }, "[$k] $result";
                    next STATE;
                }
            }
        }
        else {
            push @{ $states{CRITICAL} }, '[' . $k . '] Unhandled Limit';
            next STATE;
        }

        push @{ $states{OK} }, $k;
    }

    foreach my $k ( keys %{$C} ) {
        next if 0 == index $k, '_';
        if ( !exists $matched{$k} ) {
            push @{ $states{CRITICAL} }, '[' . $k . '] Unsupported Limit';
        }
    }

    return %states;
}

sub check_item {
    my ( $d, $c ) = @_;

    my $result;
    if ( $c->{match} && @{ $c->{match} } ) {
        foreach my $m ( @{ $c->{match} } ) {
            return if $m eq $d->{current};
        }
        $result
            = 'State (' . $d->{current} . ') is outside of acceptable values';
    }

    if ( $c->{low} || $c->{high} ) {
        $result = undef;
        my $num = $d->{current};
        $num =~ s/[^-\d\.]//gxms;

        if ( !length $num ) {
            return 'State (' . $d . ') is not numeric';
        }

    DIRECTION: foreach my $dir qw( low high ) {
            if ( !$c->{$dir} ) { next DIRECTION; }

            my $check = $c->{$dir};
            my $cnum  = $num;

            my $max = $d->{limit};
            if ( $check =~ s/\%$//xms ) {
                if ( !defined $max ) {
                    return 'max-prefix not specified and % check requested';
                }
                $num = "$num/$max";

                # convert to percent
                $cnum = 100 * $cnum / $max;
            }

            my @nums       = ( $cnum, $check );
            my $abovebelow = 'below';
            my $symbol     = '<';
            if ( $dir eq 'high' ) {
                @nums       = ( $check, $cnum );
                $abovebelow = 'above';
                $symbol     = '>';
            }

            if ( $nums[0] < $nums[1] ) {
                return join q{ }, 'is', $abovebelow,
                    'threshold (' . $num,
                    $symbol, $c->{$dir} . ')';
            }
        }
    }

    return $result;
}

sub getopt {
    my (@argv) = @_;

    my ( %checks, $w, $c );
    while (@argv) {
        my $opt = shift @argv;
        given ($opt) {
            when ( '-V' || '--version' ) {
                print_revision( $PROGNAME, '$Revision: 1.4 $ ' );
                exit $ERRORS{'OK'}
            }
            when (/^-?-h(?:elp)?/xms) { print_help(); exit $ERRORS{'OK'} }
            when (/^-?-p(?:ath)?/xms) { $checks{_DEVICE} = shift @argv }
            when (/^-?-w(?:arning)?/xms) {
                $w = parse_check( shift @argv )
            }
            when (/^-?-c(?:ritical)?/xms) {
                $c = parse_check( shift @argv )
            }
            when (/^-?-l(?:limit)?/xms) {
                while ( @argv && $argv[0] !~ /^-/xms ) {
                    $checks{ shift @argv } = {
                        WARNING      => $w,
                            CRITICAL => $c,
                    }
                }
            }
            default { print_help(); exit $ERRORS{'UNKNOWN'} }
        }
    }

    if (defined $w) {
        $checks{_UNKNOWN}{WARNING} = $w;
    }
    if (defined $c) {
        $checks{_UNKNOWN}{CRITICAL} = $c;
    };

    return %checks;
}

sub print_help {
    print <<"EOL";
$PROGNAME - checks status of pf limits
    $PROGNAME [ -d device ][ -w ENTRY ][ -c ENTRY ][ -l limit ]

Usage:
    -d, --path Path to pf device
        Path to pf device to use. See -p in pfctl(8).
    -w, --warning RANGE or single ENTRY
        Exit with WARNING status if outside of RANGE or if != ENTRY
        May be entered multiple times.
    -c, --critical RANGE or single ENTRY
        Exit with CRITICAL status if outside of RANGE or if != ENTRY
        May be entered multiple times.
    -l, --limit LIMIT
        The name of the limit, can be a space separated list of limits
        May be entered multiple times.

ENTRY is a comma separated list of items to match against.  Each item can be
a RANGE or it will just be matched against the status.

RANGE is specified as two optional numbers separated with a colon (:).  The
check is that the value is between the two numbers.  If either number is left
off, that check is ignored.

If either number in a RANGE is specified as a percent, check is that
max-prefix is specified and that the number is within the specified percent.

LIMIT is the name that shows when running 'pfctl -s memory'. Currently
supported limits are 'states' and 'src-nodes'.

Any time a LIMIT is specified on the command line that is NOT a supported
limit causes a CRITICAL result.

Any known limits that are unspecified are checked with the last specified
warning and criticical ENTRY.

Also checks if pf is enabled and is CRITICAL if not.

Example:

$PROGNAME -c :90% -l src-nodes -w 10:75% -l states

CRITICAL
    If states or src-nodes are above 90% of their limit

WARNING
    If states is below 10 or above 75% of its limit

EOL

    print_revision( $PROGNAME, '$Revision: 1.4 $' );

    print $LICENSE;

    return;
}

sub print_revision {
    my ( $prog, $rev ) = @_;
    $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;

    say $prog, q{ }, $rev;

    return;
}