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
-
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} ); 2 Comments
- Tommy_lee_55826
Nimbostratus
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 - Torti
Cirrus
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.