#!/usr/bin/perl # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use strict; use Getopt::Long; use Socket qw(inet_aton); use Time::Local qw(timegm); use DB_File; use Pod::Usage; #--------------------------------------------------------------------- # Global variables #--------------------------------------------------------------------- # Version information my $VERSION = '0.2'; # Two hashes were all info found in the dhcpd.conf and dhcpd.leases # files are stored my %subnet = (); my %lease = (); # Options with default values my %opt = ( 'config' => '/etc/dhcpd.conf', 'leases' => '/var/db/dhcpd.leases', 'munin' => 0, 'pool' => q{}, 'append' => q{}, 'nagios' => 0, 'verbose' => 0, 'help' => 0, 'version' => 0, 'cache-period' => 5, ); # IP address regexp (not really precise, but close enough) my $ip_regexp = qr{ \d{1,3} [.] \d{1,3} [.] \d{1,3} [.] \d{1,3} }xms; # Get options from command line GetOptions ( "c|config=s" => \$opt{'config'}, "leases=s" => \$opt{'leases'}, "m|munin" => \$opt{'munin'}, "pool=s" => \$opt{'pool'}, "append=s" => \$opt{'append'}, "nagios" => \$opt{'nagios'}, "snmp" => \$opt{'snmp'}, "v|verbose" => \$opt{'verbose'}, "help" => \$opt{'help'}, "man" => \$opt{'man'}, "version" => \$opt{'version'}, "cache-period=i" => \$opt{'cache_period'} ) or exit 1; #my $cache_dir = '/tmp/dhcpd-pool'; # handy for debugging my $cache_dir = '/var/cache/dhcpd-pool'; my $cache_file = $cache_dir . '/cache.db'; #===================================================================== # Main program #===================================================================== # If user requested help if ($opt{'help'}) { pod2usage(0); } # If user requested man page if ($opt{'man'}) { pod2usage(-exitstatus => 0, -verbose => 2); } # If user requested version info if ($opt{'version'}) { print "dhcpd-pool version $VERSION\n"; exit 0; } # If munin option is specified, set options the Munin way if ($opt{'munin'}) { $opt{'config'} = $ENV{'configfile'} ? $ENV{'configfile'} : $opt{'config'}; $opt{'leases'} = $ENV{'leasefile'} ? $ENV{'leasefile'} : $opt{'leases'}; } # Check and possibly create the cache dir if (! -d $cache_dir) { mkdir $cache_dir, 0700 or die "Couldn't create cache directory $cache_dir: $!\n"; } # Stat the cache file, and if mtime is less than the cache period in # the past, read the cache instead of the config and leases files my @cstat = stat($cache_file); if ( (time() - $cstat[9]) < ($opt{'cache_period'} * 60) ) { read_cache(); # read cache } else { read_config($opt{'config'}); # read config file read_leases(); # read leases file write_cache(); # write cache } # Behaviour depending on options if ($opt{'nagios'}) { # Act as Nagios plugin my $retval = nagios_plugin(); exit $retval; } elsif ($opt{'munin'}) { # Act as a Munin plugin my $retval = munin_plugin(); exit $retval; } else { # Default behaviour print_status(); } #--------------------------------------------------------------------- # Functions #--------------------------------------------------------------------- # Writes the cache file. Uses a Berkeley DB via DB_File sub write_cache { # Delete cache file unlink $cache_file; # The cache hash my %cache = (); # Open the cache file my $db = tie %cache, 'DB_File', $cache_file, O_CREAT|O_RDWR, 0600, $DB_HASH or die "Cannot open file '$cache_file': $!\n"; # Write config to cache foreach my $net (keys %subnet) { my $mask = $subnet{$net}{'mask'}; foreach my $pool (keys %{ $subnet{$net}{'pool'} }) { foreach my $key (qw(name warning critical monitor)) { my $ckey = join('__CaChEiD__', '0', join('__SuBnEt__', $net, $mask, $pool, $key)); $cache{$ckey} = $subnet{$net}{'pool'}{$pool}->{$key}; } } } # Write lease info to cache foreach my $ip (keys %lease) { foreach my $key (qw(pool state)) { my $ckey = join('__CaChEiD__', '1', join('__LeAsE__', $ip, $key)); $cache{$ckey} = $lease{$ip}{$key}; } } # Cleanup undef $db; untie %cache; } # Reads the cache file. Uses a Berkeley DB via DB_File sub read_cache { # The cache hash my %cache = (); # Open the cache file my $db = tie %cache, 'DB_File', $cache_file, O_RDONLY, 0600, $DB_HASH or die "Cannot tie '$cache_file': $!\n"; # Read config and leases from cache foreach my $key (keys %cache) { my ($id, $rest) = split(/__CaChEiD__/, $key); if ($id == 0) { my ($net, $mask, $pool, $attr) = split(/__SuBnEt__/, $rest); $subnet{$net}{'mask'} = $mask; $subnet{$net}{'pool'}{$pool}{$attr} = $cache{$key}; } elsif ($id == 1) { my ($ip, $attr) = split(/__LeAsE__/, $rest); $lease{$ip}{$attr} = $cache{$key}; } } # Cleanup undef $db; untie %cache; } # Convert octal subnet mask to its decimal form # E.g. 255.255.255.0 = 24 sub convert_netmask { my $subnetmask = shift; my $mask = 0; foreach my $oct ( split('\.', $subnetmask) ) { for (my $i = 0; $i < 8; ++$i) { ++$mask if ($oct & 2**$i) == (2**$i); } } return $mask; } # # Read the DHCP configuration file. Whenever we find a pool # declaration, monitoring information (i.e. warning and critical limits), # the IP range and subnet information is stored. # # This function is recursive, to take into account "include" # statements in the configuration file. # sub read_config { my $cf = shift; # Limit declaration regexp (semi-evil and obscure) # Example: # monitor: 80% 90% Y My subnet my $limit_regexp = qr{ \# \s*? # Comment sign monitor: \s+? # monitor: (-{0,1}) # Optional minus sign (\d+) # WARNING limit (%{0,1}) \s+? # Optional percent sign (-{0,1}) # Optional minus sign (\d+) # CRITICAL limit (%{0,1}) \s+? # Optional percent sign ([YN]) \s+? # Y or N ([^\n]*) # Name of pool (optional) }ixms; # Subnet declaration regexp # Example: subnet 129.240.202.0 netmask 255.255.254.0 { my $subnet_regexp = qr{ \A \s* subnet \s+ ($ip_regexp) \s+ netmask \s+ ($ip_regexp) }xms; # Range declaration regexp # Examples: range 129.240.203.200 129.240.203.246; # range 129.240.203.187; my $range_regexp = qr{ \A \s* range \s+ ($ip_regexp) \s* (($ip_regexp){0,1}) \s* ; }xms; recursive_read_config($cf); # The recursive part sub recursive_read_config { $cf = shift; my $count = 0; my $scope = q{}; my $net = q{}; my $mask = q{}; my $name = q{}; my %warning = ('pool' => q{}, 'subnet' => q{}); my %critical = ('pool' => q{}, 'subnet' => q{}); my %monitor = ('pool' => q{}, 'subnet' => q{}); # Open and read the configuration file open my $CONF, '<', $cf or die "Couldn't open config file ($cf): $!\n"; while (<$CONF>) { # Found an include statement. Call ourself recursively if (m/\A\s* include \s+ ['"](.*?)['"];/xms) { my $newcf = $1; #$newcf =~ s{/etc/dhcpd.conf.d/}{}; # handy for debugging recursive_read_config($newcf); } # Found a subnet declaration elsif (m{$subnet_regexp}xms) { $net = $1; $mask = convert_netmask($2); # We're inside a subnet scope $scope = 'subnet'; # store subnet info $subnet{$net}{'mask'} = $mask; # reset pool count $count = 0; } # Found a pool declaration elsif (m/\A \s* pool \s* \{/xms) { # We're inside a pool scope $scope = 'pool'; # increase the pool count ++$count; } # Found a limit statement elsif (m{$limit_regexp}xms) { $warning{$scope} = $1 . $2 . $3; $critical{$scope} = $4 . $5 . $6; $monitor{$scope} = $7; $name = $8; chomp $name; } # Found a range declaration elsif (m{$range_regexp}xms) { # store pool info if ($scope eq 'pool' and $monitor{'pool'} ne q{}) { $subnet{$net}{'pool'}{$count}{'warning'} = $warning{'pool'}; $subnet{$net}{'pool'}{$count}{'critical'} = $critical{'pool'}; $subnet{$net}{'pool'}{$count}{'monitor'} = $monitor{'pool'}; } else { $subnet{$net}{'pool'}{$count}{'warning'} = $warning{'subnet'}; $subnet{$net}{'pool'}{$count}{'critical'} = $critical{'subnet'}; $subnet{$net}{'pool'}{$count}{'monitor'} = $monitor{'subnet'}; } $name = 'Anonymous' if $scope eq 'subnet'; $name = 'N/A' if $name eq q{}; $subnet{$net}{'pool'}{$count}{'name'} = $name; if ($2 eq q{}) { $lease{$1}->{'pool'} = "$net/$mask/$count"; } else { foreach my $ip ( @{ explode_range($1, $2) } ) { $lease{$ip}->{'pool'} = "$net/$mask/$count"; } } } # End of pool elsif ($scope eq 'pool' and m@\}@) { $scope = 'subnet'; #reset variables $name = q{}; $warning{'pool'} = q{}; $critical{'pool'} = q{}; $monitor{'pool'} = q{}; } # End of subnet elsif ($scope eq 'subnet' and m@\}@) { $scope = q{}; # reset variables $net = q{}; $mask = q{}; $name = q{}; $warning{'subnet'} = q{}; $critical{'subnet'} = q{}; $monitor{'subnet'} = q{}; } } close $CONF; } } # # Explode the range of IP addresses declared in the range # declaration. Arguments are the "to" and "from" in the range # declaration. Returns pointer to a list with all IP addresses in the # range. # sub explode_range { my $ipaddress1 = shift; my $ipaddress2 = shift; my @range = (); my @ip1 = split('\.', $ipaddress1); my @ip2 = split('\.', $ipaddress2); my @i = @ip1; while (@i[3] != @ip2[3] or @i[2] != @ip2[2] or @i[1] != @ip2[1] or @i[0] != @ip2[0]) { push @range, join('.', @i); if ($i[3] < 255) { $i[3]++; } elsif ($i[3] == 255 and $i[2] < 255) { $i[3] = 0; $i[2]++; } elsif ($i[2] == 255 and $i[1] < 255) { $i[3] = 0; $i[2] = 0; $i[1]++; } elsif ($i[1] == 255 and $i[0] < 255) { $i[3] = 0; $i[2] = 0; $i[1] = 0; $i[0]++; } else { die "Range error: IP out of range\n"; } } push @range, join('.', @ip2); return \@range; } # # Function that reads the dhcpd.leases file. End time for leases are # calculated and the lease is flagged as either free, expired or # active (i.e. in use). # sub read_leases { # Initialize leases foreach my $l (keys %lease) { $lease{$l}->{'state'} = '-'; } my $valid = 0; # flag: if a lease is found in a range my $pid = q{}; # pool ID my $now = time(); # current time my $ends = q{}; # lease end time my $ip = q{}; # lease IP number # ends regexp # Example: ends 5 2008/04/04 10:40:45; my $ends_regexp = qr{ \A \s+ ends \s \d \s (\d+)/(\d+)/(\d+) \s (\d+):(\d+):(\d+) ; }xms; # Open and read the dhcpd.leases file. Store the lease and # relevant information in the %lease hash. open my $LEASES, '<', $opt{'leases'} or die "Couldn't open leases file ($opt{leases}): $!\n"; while (<$LEASES>) { if (m/^lease ($ip_regexp) \{$/) { $ip = $1; POOL: foreach my $l (keys %lease) { if ($l eq $ip) { $valid = 1; # this is a valid lease $pid = $lease{$l}->{'pool'}; last POOL; } } } elsif ($valid and m{$ends_regexp}xms) { $ends = timegm($6, $5, $4, $3, $2-1, $1); } elsif ($valid and /^\s+ends never;$/) { $ends = -1; } elsif ($valid and /^\}$/) { if ($ends == -1 or $ends >= $now) { $lease{$ip}->{'state'} = 'active'; } else { # A lease can exist several places in the leases # file. If one of the entries is active, the others # should be ignored if ($lease{$ip}->{'state'} ne 'active') { $lease{$ip}->{'state'} = 'expired'; } } $valid = 0; $ends = q{}; } } close $LEASES; } # # Function that does the Nagios stuff # sub nagios_plugin { my %limit = (); my $lease_total = 0; my $lease_active = 0; SUBNET: foreach my $net (sort by_ip keys %subnet) { my $mask = $subnet{$net}{'mask'}; POOL: foreach my $pool (sort keys %{ $subnet{$net}{'pool'} }) { next POOL if $subnet{$net}{'pool'}{$pool}->{'monitor'} ne 'Y'; # Some helper variables my $monitor = $subnet{$net}{'pool'}{$pool}->{'monitor'}; my $warning = $subnet{$net}{'pool'}{$pool}->{'warning'}; my $critical = $subnet{$net}{'pool'}{$pool}->{'critical'}; my $name = $subnet{$net}{'pool'}{$pool}->{'name'}; # Summarize active/total leases in pool my $active = 0; my $range = 0; foreach my $l (keys %lease) { if ($lease{$l}->{'pool'} eq "$net/$mask/$pool") { ++$range; if ($lease{$l}->{'state'} eq 'active') { ++$active; } } } # Count leases and adresses $lease_total += $range; $lease_active += $active; # Handle the critical and warning limits foreach ( qw(critical warning) ) { my $treshold = $subnet{$net}{'pool'}{$pool}->{$_}; # If limit is given in percent if ($treshold =~ m/^(\d+)%$/) { my $lim = $1; my $percent = $active * 100 / $range; if ($percent > $lim) { my $line = sprintf("Pool \"%s\" in subnet %s/%s is %.1f%% full", $name, $net, $mask, $percent); push @{ $limit{$_} }, $line; next POOL; } } # If limit is given in number of free leases elsif ($treshold =~ m/^-(\d+)$/) { my $lim = $1; my $free = $range - $active; if ($free < $lim) { my $line = sprintf("Pool \"%s\" in subnet %s/%s has only %d free leases", $name, $net, $mask, $free); push @{ $limit{$_} }, $line; next POOL; } } # If limit is given in number of leases in use elsif ($treshold =~ m/^(\d+)$/) { my $lim = $1; if ($active > $lim) { my $line = sprintf("Pool \"%s\" in subnet %s/%s has %d active leases (of total %d)", $name, $net, $mask, $active, $range); push @{ $limit{$_} }, $line; next POOL; } } } } } # Print the criticals, if any foreach (@{ $limit{'critical'} }) { print "$_\n"; } # Print the warnings, if any foreach (@{ $limit{'warning'} }) { print "$_\n"; } # Determine proper return value # (critical = 2, warning = 1, normal = 0) my $retval = 0; if (scalar(@{ $limit{'critical'} }) > 0) { $retval = 2; } elsif (scalar(@{ $limit{'warning'} }) > 0) { $retval = 1; } if ($retval == 0) { printf ("All fine: %d / %d (%.1f%%) leases in use in all pools.\n", $lease_active, $lease_total, $lease_active * 100 / $lease_total); } return $retval; } # # Function that implements the Munin feature # sub munin_plugin { # Suggest option if ($opt{'append'} eq 'suggest') { foreach my $net (sort by_ip keys %subnet) { POOL: foreach my $pool (sort keys %{ $subnet{$net}{'pool'} }) { next POOL if $subnet{$net}{'pool'}{$pool}->{'monitor'} ne 'Y'; print join('_', $net, $subnet{$net}{'mask'}, $pool) . "\n"; } } return 0; } # Count number of active leases in each pool foreach my $net (keys %subnet) { foreach my $pool (keys %{ $subnet{$net}{'pool'} }) { # Initialize $subnet{$net}{'pool'}{$pool}{'active'} = 0; $subnet{$net}{'pool'}{$pool}{'range'} = 0; # Summarize foreach my $l (keys %lease) { if ($lease{$l}{'pool'} eq join('/', $net, $subnet{$net}{'mask'}, $pool)) { $subnet{$net}{'pool'}{$pool}{'range'}++; if ($lease{$l}->{'state'} eq 'active') { $subnet{$net}{'pool'}{$pool}{'active'}++; } } } } } # Graph with all pools if ($opt{'pool'} eq 'total') { # If config is requested if ($opt{'append'} eq 'config') { my %label = (); print "graph_title All DHCP pools\n"; print "graph_args --base 1000\n"; print "graph_vlabel % full\n"; print "graph_category DHCP\n"; print "graph_order"; foreach my $net (sort by_ip keys %subnet) { POOL: foreach my $pool (sort keys %{ $subnet{$net}{'pool'} }) { next POOL if $subnet{$net}{'pool'}{$pool}->{'monitor'} ne 'Y'; my $lab = $net; $lab =~ s/\./_/g; print " " . join('_', $lab, $subnet{$net}{'mask'}, $pool); $label{join('_', $lab, $subnet{$net}{'mask'}, $pool)} = $subnet{$net}{'pool'}{$pool}->{'name'}; } } print "\n"; foreach my $l (keys %label) { my $lab = $l; $lab =~ s/\./_/g; print "$lab.label $label{$l}\n"; print "$lab.min 0\n"; print "$lab.max 100\n"; } return 0; } # If values are requested else { foreach my $net (sort by_ip keys %subnet) { POOL: foreach my $pool (sort keys %{ $subnet{$net}{'pool'} }) { next POOL if $subnet{$net}{'pool'}{$pool}->{'monitor'} ne 'Y'; my $lab = $net; $lab =~ s/\./_/g; print join('_', $lab, $subnet{$net}{'mask'}, $pool) . '.value ' . $subnet{$net}{'pool'}{$pool}{'active'} * 100 / $subnet{$net}{'pool'}{$pool}{'range'} . "\n"; } } return 0; } } # Identify which pool the user wants to graph my ($net, $pool); SUBNET: foreach my $n (keys %subnet) { foreach my $p (keys %{ $subnet{$n}{'pool'} }) { if ($opt{'pool'} eq join('_', $n, $subnet{$n}{'mask'}, $p)) { $net = $n; $pool = $p; last SUBNET; } } } # If pool is monitored, get warning/critical values my %val = ('warning' => 0, 'critical' => 0); if ($subnet{$net}{'pool'}{$pool}{'monitor'} eq 'Y') { foreach (qw(warning critical)) { my $lim = $subnet{$net}{'pool'}{$pool}->{$_}; if ($lim =~ m/^(\d+)%$/) { $val{$_} = ($1 / 100) * $subnet{$net}{'pool'}{$pool}{'range'}; } elsif ($lim =~ m/^-(\d+)$/) { $val{$_} = $subnet{$net}{'pool'}{$pool}{'range'} - $1; } elsif ($lim =~ m/^(\d+)$/) { $val{$_} = $1; } } } # If config is requested if ($opt{'append'} eq 'config') { print "graph_title DHCP leases in \"" . $subnet{$net}{'pool'}{$pool}{'name'} . "\"\n"; print "graph_args --base 1000 -v leases -l 0\n"; print "graph_category DHCP\n"; print "active.info Number of active leases\n"; print "active.draw AREA\n"; print "active.min 0\n"; print "active.max $subnet{$net}{pool}{$pool}{range}\n"; print "active.label Active leases\n"; # If pool is monitored, include warning/critical tresholds if ($subnet{$net}{pool}{$pool}{'monitor'} eq 'Y') { print "warning.label Warning at $subnet{$net}{pool}{$pool}{warning}\n"; print "warning.min 0\n"; print "warning.max $subnet{$net}{pool}{$pool}{range}\n"; print "warning.info Warning treshold\n"; print "critical.label Critical at $subnet{$net}{pool}{$pool}{critical}\n"; print "critical.min 0\n"; print "critical.max $subnet{$net}{pool}{$pool}{range}\n"; print "critical.info Critical treshold\n"; } print "max.label Total leases\n"; print "max.min 0\n"; print "max.max $subnet{$net}{pool}{$pool}{range}\n"; print "max.info Total number of leases in range\n"; # If pool is monitored, include warning/critical tresholds if ($subnet{$net}{'pool'}{$pool}{'monitor'} eq 'Y') { printf ("active.warning %.1f\n", $val{'warning'}); printf ("active.critical %.1f\n", $val{'critical'}); } } # If values are requested else { print "active.value $subnet{$net}{pool}{$pool}{active}\n"; # If pool is monitored, include warning/critical tresholds if ($subnet{$net}{'pool'}{$pool}{'monitor'} eq 'Y') { printf ("warning.value %.1f\n", $val{'warning'}); printf ("critical.value %.1f\n", $val{'critical'}); } print "max.value $subnet{$net}{pool}{$pool}{range}\n"; } return 0; } # Sort by IP address sub by_ip { (inet_aton($a) || 0) cmp (inet_aton($b) || 0); } # # This function prints various status information # sub print_status { my $pools_monitored = 0; my $pools_unmonitored = 0; my $pools_total = 0; my $lease_total = 0; my $lease_active = 0; foreach my $net (sort by_ip keys %subnet) { my $mask = $subnet{$net}{'mask'}; if (defined $subnet{$net}{'pool'}) { print "\n"; print "Subnet $net/$mask\n"; print '-' x 50, "\n\n"; } foreach my $pool (sort keys %{ $subnet{$net}{'pool'} }) { # Some helper variables my $monitor = $subnet{$net}{'pool'}{$pool}->{'monitor'}; my $warning = $subnet{$net}{'pool'}{$pool}->{'warning'}; my $critical = $subnet{$net}{'pool'}{$pool}->{'critical'}; my $name = $subnet{$net}{'pool'}{$pool}->{'name'}; # Count values ++$pools_total; $monitor eq 'Y' ? ++$pools_monitored : ++$pools_unmonitored; # Sum active/free/total leases my $active = 0; my $range = 0; foreach my $l (keys %lease) { if ($lease{$l}->{'pool'} eq "$net/$mask/$pool") { ++$range; ++$lease_total; if ($lease{$l}->{'state'} eq 'active') { ++$active; ++$lease_active; } } } # Print information about pool if ($name eq 'Anonymous') { print " Anonymous pool:\n"; } else { print " $pool. Pool \"$name\":\n"; } print "\n"; print " Monitoring: " . ($monitor eq 'Y' ? "ON" : "OFF") . "\n"; if ($monitor eq 'Y') { print " Warning limit: " . ($warning =~ m/^-/ ? q{} : q{ }) . "$warning\n"; print " Critical limit: " . ($critical =~ m/^-/ ? q{} : q{ }) . "$critical\n"; } printf (" Active leases: %d/%d (%.1f\%)\n", $active, $range, ($active * 100 / $range) ); # Print IP range if verbose if ($opt{'verbose'}) { print " IP range ($range addresses):\n"; foreach my $l (sort by_ip keys %lease) { if ($lease{$l}->{'pool'} eq "$net/$mask/$pool") { print " $l\t" . $lease{$l}->{'state'} . "\n"; } } } print "\n"; } } # Print a short summary at the end print "\nSUMMARY\n"; print '=' x 50 . "\n\n"; print " Total pools: $pools_total\n"; print " Total pools monitored: $pools_monitored\n"; print " Total pools un-monitored: $pools_unmonitored\n"; print "\n"; print " Total leases: $lease_total\n"; printf (" Total active leases: %d (%.1f%%)\n", $lease_active, ($lease_active * 100 / $lease_total) ); print "\n"; } __END__ =head1 NAME dhcpd-pool - Monitor and report ISC dhcpd pool usage =head1 SYNOPSIS dhcpd-pool [-c|--config ] [-l|--leases ] [-m|--munin [-p|--pool ] [-a|--append ]] [-n|--nagios] [-v|--verbose] [-h|--help] =head1 DESCRIPTION This script will report pool usage on a ISC dhcpd server. Does also work on failover pairs, since each node will have identical config (when it comes to subnets and pools) and a complete leases file. Configuration is done in the DHCP config file, but the script will report usage on pools without configuration. Details below. The script can operate as a Nagios plugin, reporting pool usage above the treshold configured by the user. It can also act as a Munin plugin, creating one graph per pool and/or one graph with all pools. B uses a cache file (via Berkeley DB) to speed up runtime and decrease load impact on the DHCP server. The cache is updated if it is more than 5 minutes old. =head1 OPTIONS =over 4 =item B<-c>, B<--config> The ISC dhcpd config file. Normally F, which is the default. =item B<-l>, B<--leases> The ISC dhcpd leases file. Default is F =item B<-n>, B<--nagios> Act as a Nagios plugin. Notification limits for each pool, as well as which pools will be monitored, is configured in the DHCP config file. Details below. =item B<-m>, B<--munin> Act as a Munin plugin. One can create one graph per pool, or create one graph with all pools. In case of the latter percentage usage is graphed instead of absolute usage, since many different pools in one graph usually don't make sense and is pretty useless. =item B<-p>, B<--pool> Pool ID. This option is only used when acting as a Munin plugin. The pool ID has the form F where F is the pool number as found in the DHCP config file. Pools declared with "pool" starts on 1, while anonymous pools (without pool declaration) has number 0. Example: 129.240.202.0_23_1 =item B<-a>, B<--append> This is the regular Munin option, i.e. "config". Other possibilities are "suggest" and "autoconf". This option does only make sense when the script is used as a Munin plugin. =item B<-v>, B<--verbose> Be more verbose. This option has only effect when the script is used in its default form, i.e. not Nagios or Munin. When this option is given, the IP range for each pool is printed out. =item B<--cache-period> The default timeout (or TTL) for the cache, given in minutes. The default cache period is 5 minutes. You may want to increase this if you're polling less frequently than the default, in which case the cache has no effect. =item B<-h>, B<--help> Short help text. =item B<--man> Display the man page. =item B<--version> Display version information =back =head1 CONFIGURATION Configuration is done in the DHCP config file. For each pool you want to monitor, include a statement like this in the beginning of each pool scope: # monitor: [name] Note the comment sign. The limits configuration is a comment in the DHCP config, and is only recognized by this script. The B or B is simply a yes or no to monitoring. If monitoring is set to B, the Nagios plugin will ignore the pool. The name is optional, but it's encouraged to set a proper name for each pool. The warning and critical limits can each be given in three different forms. In the examples, if the pool holds 200 leases total, the limits are effectively identical. =over 4 =item B When a percent sign (%) follows the number, the limit is given in percent. E.g. if the limit is 80% and the pool range contains 200 leases, a notification will occur if the number of active leases is more than or equal to 160. Example: # monitor: 80% 90% Y My pool =item B When the limit is given as a positive integer, a notification will occur when the number of active leases is greater than or equal to the limit. Example: # monitor: 160 180 Y My pool =item B If a minus sign (-) precedes the number, a notification will occur if the number of free (not active) leases is less than or equal to the limit. Example: # monitor: -40 -20 Y My pool =back The limit configuration can be set in both the subnet scope and the pool scope. If set in the pool scope, that takes precedence for that particular pool. Setting the limits configuration per pool is recommended. =head1 FILES Cache file: F DHCP config: F DHCP leases: F =head1 SEE ALSO Complete documentation: L =head1 AUTHOR Trond H. Amundsen =head1 BUGS Probably. =cut