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:

The script can be downloaded from here. (Sorry the link is corrected now!)

Post on new version can be found here.


#!/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";
# 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. ";



zachsis said...

Hey there, excellent script! I'm trying to implement it at my work and running into a small issue. When i define a known server in %servers, it still returns a "SERVICE STATUS:CRITICAL"

my %servers=( "undertow", "");

Any ideas?

Kim said...

Which critical do you get:

DHCP service running on $host
no DHCP service responded


Kim said...

Also as per the comment the servers have to be IP address.

Kim said...

Oh and you do have the nagios dhcp plugin?

Anonymous said...

Thanks for the link. I agree with your approach. Everything should be in nagios config files.

K said...

# ./check_rogue_dhcp.pl
Odd number of elements in hash assignment at ./check_rogue_dhcp.pl line 21.

I've added to servers variable. It is not working for me.
No matter IP is in "", '' or with out any quotation marks.

K said...

Odd number of elements in hash assignment at ./check_rogue_dhcp.pl line 21.

I've added that IP to servers variable. It is not working for me, no matter IP is in "", '' or with out any quotation marks.

Kim said...

Hi @K,

You don't change the script. The servers are added in the invocation line. There is one just above the script:


Each $ARGn$ is a server IP. This is put in the plugin config file.

Instructions here, given I don't know your setup :

Add the following lines to this file:


# 'check_rogue_dhcp' command definition
define command{
command_name check_rogue_dhcp
command_line /usr/lib/nagios/plugins/check_rogue_dhcp.pl '$ARG1$' '$ARG2$' '$ARG3$'

Then to (say) this file: /etc/nagios3/conf.d/services.cfg

Add something like:

# check that no rogue dhcp services are running
define service {
hostgroup_name rogue-dhcp
service_description rogue-dhcp
check_command check_rogue_dhcp!!
use generic-service
notification_interval 0 ; set > 0 if you want to be renotified

Does that help?

K said...

no, now I have:

Error: Could not find any hostgroup matching 'rogue-dhcp' (config file '/etc/nagios3/conf.d/020-services.cfg', starting on line 815)
Error: Could not expand hostgroups and/or hosts specified in service (config file '/etc/nagios3/conf.d/020-services.cfg', starting on line 815)
Error processing object config files!

how can I add service? I suppose, that there will be another problem with host and his IP address after that.

Kim said...

Hi @K,

That's an issue with your nagios config. I think you might need to define a hostgroup and maybe a host.

Nagios configuration is not simple. I am not sure I am the best person to advise you on this. Partly because it's very flexible and there are lots of ways to configure it and I'm not sure I have configured my system the best way.

I'll have a look and see if I can extract all the related bits and post them here if you're interested.