2013-07-11

Detecting Rogue DHCP servers

I don't know why there aren't more simple ways to detect rogue DHCP servers.  It can have nasty effects on a network and can be difficult to diagnose and it's easy for it to happen. Someone can plug in a machine set to share its internet connection and bam.

I wrote a perl script to detect rogue DHCP servers in nagios.  I found one on the web here but I wanted it a bit more flexible so the hosts it was testing were in the nagios config files rather than in the script itself.  It's a debian policy thing I like.

So I adjusted the script.  It requires the check_dhcp plugin which for me was part of my ubuntu nagios install.  It takes options "-v" and a list of servers.   In your nagios-plugins/configure file the command looks like:

/usr/lib/nagios/plugins/check_rogue_dhcp.pl!$ARG1$!$ARG3$!$ARG3$
  
The script can be downloaded from here. (Sorry the link is corrected now!)

Post on new version can be found here.

-----check_rogue_dhcp.pl-------------

#!/usr/bin/perl -w
# nagios: -epn
# the above makes nagios run the script separately.
use POSIX;
use lib "/usr/lib/nagios/plugins";
use utils qw(%ERRORS);

sub fail_usage {
  if (scalar @_) {
    print "$0: error: \n";
    map { print "   $_\n"; } @_;
  }
  print "$0: Usage: \n";
  print "$0 [-v [-v [-v]]] (server-ip) [(server-ip) (server-ip) ....] \n";
  print "$0 [-v [-v [-v]]] [-s] (server-ip) [[-s] (server-ip) (server-ip)....] \n";
  print " \n";
  exit 3 ;
}

my $verbose = 0;
my %servers=();

# examine commandline args
while ($ARGV=$ARGV[0]) {
  my $myarg = $ARGV;
  if ($ARGV eq '-s') {
    shift @ARGV;
    if (!($ARGV = $ARGV[0])) { fail_usage ("$myarg needs an argument"); }
    if ($ARGV =~ /^-/) { fail_usage ("$myarg must be followed by an argument"); }
    if (!defined($servers{$ARGV})) { $servers{$ARGV}=1; }
  }
  elsif ($ARGV eq '-v' ) { $verbose++; }
  elsif ($ARGV eq '-h' or $ARGV eq '--help' ) { fail_usage ; }
  elsif ($ARGV =~ /^-/ ) { fail_usage " invalid option ($ARGV)"; }
  elsif ($ARGV =~ /^\d+\.\d+\.\d+\.\d+$/)
# servers should be ip addresses.  I'm not doing detailed checks for this.
    { if (!defined($servers{$ARGV})) { $servers{$ARGV}=1; } }
  else { last; }
  shift @ARGV;
}

# for some reason I can't test for empty ARGs in the while loop
@ARGV = grep {!/^\s*$/} @ARGV;
if (scalar @ARGV) { fail_usage "didn't understand arguments: (".join (" ",@ARGV).")"; }  

my $serversn = scalar keys %servers;

if ($verbose > 2) {
  print "verbosity=($verbose)\n";
  print "servers = ($serversn)\n";
  if ($serversn) { for my $i (keys %servers) { print "server ($i)\n"; } }
}

if (!$serversn) { fail_usage "no servers"; }
my $responses=0;
my $responders="";
my @check_dhcp = qx{/usr/lib/nagios/plugins/check_dhcp -v};
foreach my $value (@check_dhcp) {
  if ($value =~ /Added offer from server \@ /i){
    $value =~ m/(\d+\.\d+\.\d+\.\d+)/i;
    my $host = $1;
    # we find a server in our list
    if (defined($servers{$host})) { $responses++; $responders.="$host "; }
    # we find a rogue DHCP server.  Danger Will Robinson!
    else {
      print "SERVICE STATUS:CRITICAL: DHCP service running on $host";
      exit $ERRORS{'CRITICAL'}
    }
  }
}
# we saw all the servers in our list.  All is good.
if ($responses == $serversn) {
  print "SERVICE STATUS:OK: $responses of $serversn Expected Responses to DHCP Broadcast";
  exit $ERRORS{'OK'};
}
# we found no DHCP responses.
if ($responses == 0) {
  print "SERVICE STATUS:CRITICAL: no DHCP service responded";
  exit $ERRORS{'CRITICAL'}
}
# we found less DHCP servers than we should have. Oh Nos!
$responders =~ s/ $//;
print "SERVICE STATUS:WARNING: $responses of $serversn Responses to DHCP Broadcast. Only ($responders) responded. ";
exit $ERRORS{'WARNING'};