For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

DHCP Monitor

Problem this snippet solves:

This monitor uses the perl IO::socket module (included in the LTM build) to send a DHCPINFORM message to verify an expected response from load balanced DHCP servers.

It requires a single command-line argument: For a redundant pair, enter a space delimited list containing the server-facing non-floating Self-IPs of BOTH LTMs (the script will parse the correct response address for each unit in the redundant pair.); for a single unit, enter the server-facing Self-IP.

How to use this snippet:

Implementation

  1. Create a new file containing the code below on the LTM filesystem. Recommended location is /config/eav. Permissions on the file must be 700 or better, giving root rwx access to the file. 2. Create a monitor profile of type "External" with the following values:

    • External Program: . . the name of the script file created in step 1
    • Arguments: . . . . . .
    • Adjust the interval and timeout as appropriate for your application

Code :

#!/usr/bin/perl -w 
use strict;

#############################################################################
# When you configure this as an EAV you MUST specify *both* SELF-IPs on the
# VLAN containing the servers to be monitored for both BIG-IPs in an HA pair 
# as arguments, separated by a space.  If you only have a standalone BIG-IP 
# then just specify one...

# Note that the default timeout below is 5 seconds so I'd recommend a monitor
# frequency of interval=10, timeout=31.
#############################################################################

#############################################################################
# Configuration Section
# How long to wait for an answer
my $TimeOut = 5;

# Message type of 8 is a DHCP Inform -- 
# your DHCP server must support DHCPINFORM to use this monitor
my $MsgType = 8;
#############################################################################

package Net::DHCP::Watch;
# This is a slightly-modified version of the CPAN module
# by Evilio José del Río Silván.

use Carp;
use Config;
use Socket;
use Net::hostent;
use IO::Socket;

#
# new
#
sub new {
    my $proto  = shift;
    my $params = shift; 
    my $class  = ref($proto) || $proto;
    my $self   = {};
    bless($self, $class);
    $self->init($params);
    return $self;
}
#
# init: initalize parameters.
#
sub init {
    my $self   = shift;
    my $params = shift;
    my $h;

    # test if server hostname given is known (name or IP)
    $self->{Server} = $params->{server};

    # test if client hostname given is known (name or IP)
    # and keep only the first IP address.
    $self->{Client} = $params->{client};
    $self->{Client} = pack('CCCC', split(/\./, $params->{client}, 4));

    # test if ethernet address is either an array of six bytes or
    # a string of hex bytes separated by ':'
    $self->{Ether}  = $params->{ether};

    if ( $self->{Ether} =~ m/^([0-9a-f]{1,2}:)+[0-9a-f]{1,2}$/i ) {
my @eth = map( hex, split(':', $self->{Ether}) );
$self->{Ether} = \@eth;
    }
    elsif ( scalar($self->{Ether}) != 6 ) {
croak "Not a good ethernet addres: ",$params->{ether};
    }

    # can we use alarm() ?
    if ( $Config{d_alarm} eq 'define' ) {
$self->{_Alarm} = 1;
    }
    else {
carp "No alarm() function, network operation may hang";
$self->{_Alarm} = 0;
    }

    # set the timeout (alarm)
    $self->{TimeOut} = $params->{timeout} || 5;

    # set the timeout (alarm)
    $self->{MsgType} = $params->{msgtype} || 8;

    # initialize status result to zero
    $self->{Last} = {
Ok   => 0,
Bad  => 0,
Time => '0000-00-00 00:00:00 GMT'
    };
    return;
}

#
# watch: opens the udp socket to the server
#
sub watch {
    my $self = shift;
    if ( $self->{Watcher} ) {
carp "Already watching.";
    }
    else {
$self->{Watcher} = new IO::Socket::INET(
PeerAddr  => $self->{Server},
PeerPort  => 'bootps(67)',
#LocalAddr => inet_ntoa($self->{Client}),
LocalPort => 'bootpc(68)',
Proto     => 'udp',
Timeout   => $self->{TimeOut}
)
    or carp "Can not watch: $!";
    }
    return $self->{Watcher};
}

#
# status: returns the present status
#
sub status {
    my $self = shift;
    # now the watch/unwatch cycle is carried by status.
    $self->watch unless( $self->{Watcher} );
    $self->dhcp_query or return;
    $self->unwatch;
    return $self->{Last};
}

#
# dhcp_query: sends an udp packet containig a DHCP message 
# of type DHCPDISCOVER and listens to the reply. The random transaction id
# must match.
#
sub dhcp_query {
    my $self = shift;
    my $reply; # holdspace for udp reply
    #
    # Test if socket is ok
    #
    unless ( $self->{Watcher} ) {
carp "Not watching yet!";
return;
    };
    #
    # Transaction ID
    #
    my $xid = int(rand(2**32-1));
    #
    # DHCP Message: Fixed-Format + Options 
    # (see Droms & Lemon, 1999, Apendixes C and D).
    #
    my @fields = (
                  # op
  1,
                  # htype
  1,
                  # hlen
  6,
                  # hops
  0,
                  # xid
  $xid,
                  # secs
  0,
                  # flags
  0,
                  # ciaddr
  $self->{Client},
                  # yiaddr
  0,
                  # siaddr
  0,
                  # giaddr
  0,
                  # chaddr
  @{ $self->{Ether} },
  0,  0,  0,  0,  0,  0, 
  0,  0,  0,  0,
                  # sname
  "\0",
                  # file
  "\0",
                  # Magic cookie (RFC)
  99,130,83,99,
                  # option1 = DHCP-Message
  53,
                  # length1 = 1
  1,
                  # value1  
  $self->{MsgType}
  );
    my $query = pack(
     # It's horrible, but it works
     'CCCCNnna4NNNCCCCCCCCCCCCCCCCa64a128C*',
     @fields
     );
    my $serv_address;
    # I/O eval block
    eval {
# SIG handling for alarm()
local $SIG{ALRM} = sub { die "Alarm timeout\n" };
# Send query
alarm($self->{TimeOut})
    if $self->{_Alarm}; 
$self->{Watcher}->send($query, 0);
alarm(0)
    if $self->{_Alarm};
# Get reply
alarm($self->{TimeOut})
    if $self->{_Alarm};
$serv_address = $self->{Watcher}->recv($reply, 1024,  0);
alarm(0)
    if $self->{_Alarm};
    };
    # Die if not alarm
    if($@) {
carp $@ unless $@ =~ /alarm/i;
    }
    # Verify
    # be sure $ret_xid is not equal to $xid
    my $ret_xid = !$xid;
    if ( $reply ) {
$ret_xid = unpack('x4N',$reply);
    }
    # only if we've got a reply and the reply was correct all is ok.
    if ( $ret_xid == $xid ) {
# Increment Ok count (max: 2**31-1)
$self->{Last}->{Ok} %= 2147483647;
$self->{Last}->{Ok}++;
# Zero Bad
$self->{Last}->{Bad} = 0;
    }
    else {
# Zero ok
$self->{Last}->{Ok} = 0;
# Increment Bad count (max: 2**31-1)
$self->{Last}->{Bad} %= 2147483647;
$self->{Last}->{Bad}++;
    }
    # Get present time (GMT)
    $self->{Last}->{Time} = gmtime;
}
#
# close: just closes socket.
#
sub unwatch {
    my $self = shift;
    delete $self->{Watcher};
}
#
# Cleanup
# 
sub DESTROY {
    my $self = shift;
    $self->unwatch;
}

package main;

# Read arguments
my $Server = $ARGV[0];
$Server =~ s/::ffff://;
# Port not used
my $port = $ARGV[1];
shift @ARGV;
shift @ARGV;
my $IP = $ARGV[0];
# We need to figure out which SELF-IP is our own...
foreach (@ARGV) {
   if (system("b self $_ show >/dev/null 2>&1") == 0) {
      $IP = $_;
   }
}

# Check for and create PID file
my $monname = $ENV{MON_TMPL_NAME};
$monname = $0 unless $monname;
$monname =~ s/^.*\///;
my $pidfile = "/var/run/$monname.$Server.pid";
if (-r $pidfile) {
   open(PIDFILE, $pidfile);
   my $oldpid = ;
   close PIDFILE;
   chomp $oldpid;
   system("kill -9 $oldpid");
}
open(PIDFILE, ">$pidfile");
print PIDFILE "$$\n";
close PIDFILE;

# Net::DHCP::Watch object
my $dhcpw = new Net::DHCP::Watch({
              server => $Server,
              client => $IP,
              ether  => $Ether,
              timeout  => $TimeOut,
              msgtype  => $MsgType,
      });

# Open network
$dhcpw->watch();
# Get status
my $stat = $dhcpw->status;
# Remove PIDFILE before echoing anything to STDOUT
unlink $pidfile;
# print results
print STDERR "Down" if ( $stat->{Bad} );
print "UP" if ( $stat->{Ok}  );
Published Mar 12, 2015
Version 1.0

2 Comments

  • hi i'm tommy lee korea. nowaday DHCP server monitoring on BIGIP 5000 set for PyeongChang winter olympic. but i cannot anyway F5 TAC cannot support because i use external monitor but fail during 3 week. also i cannot search any document just only your comment.

     

    can you help me for DHCP server monitoring? i want more detail set configuration guide. this is urgency project. regard tommy@itian.co.kr

     

  • This code doesn't work on out bigip with 13.1.0.8 and windows dhcp server.

     

    The bigip send traffic and get a DHCP ACK, but the script doesn't see anything.