Using TCP monitors to call a simple perl remote command server

Problem this snippet solves:

Many times you need to do some complex things in a monitor. If F5 doesn't have a canned EAV it can be easier to do them on another system besides the BIG-IP. In many cases you can simply use the HTTP monitor to poll an existing monitoring web page within your organization. Other times you would like to run a command on a remote system and base the monitor status on the information returned by this command. It's not that hard to use a BIG-IP TCP monitor and use a remote TCP socket server to accomplish this task.

This was the case for one of my customers who had some customer libraries on a box that would not install on the BIG-IP. However they had a local command that would exercise the system through those libraries and return status. For them the following solution worked like a charm.

Here is an example perl socket server which calls a command on its local system and returns it's status to a remote sockets client (your monitor!). It uses standard system socket calls so it should work on even older versions of perl.

The follow example monitor can be used with the attached perl server. It will mark a monitored object up if www.yahoo.com returns 3 correct pings to a the system the perl socket server is installed on (in this example 192.168.0.2). Edit the attached perl socket server script to bind to an appropriate IP address and port. (again in this example the perl server was listening on 192.168.0.2 on port 9090)

monitor remote_cmd {
  defaults from tcp
  interval 10
  timeout 31
  dest 192.168.0.2:9090
  recv "3 packets transmitted, 3 received"
  send "www.yahoo.com"
}

This could be expanded to pull back status from any command on a remote machine and parse its output. Change the command to 'ls -l $arg' and check for a certain file date on a remote machine. Change it to 'ps -ef|grep $arg' and make sure a remote service is still running. Change it to 'mysql < tablevaluecheck.sql' and get results from a remote mysql database. The possibilities are endless!

Please make sure to add some security on the system which you install the perl socket server. Simple packet filters or TCP wrappers can make sure it only takes connections from your BIG-IPs.

This was originally posted in the monitoring forum in January.. sorry to wait so long to put it in Code Share.

Code :

#!/usr/bin/perl
#
# Simple perl TCP socket server
# This server uses the standard lib Socket in perl and does not require the
# more recent IO::Socket module.  This should work on perl 5.6+
#
# This examples reads the first line from connected client and executes a 
# a command using the fist line entry as a argument to the command
#
# The client is a ECV monitor on LTM. 
#
# To test just telnet to this server and input what is in the Send field for your
# monitor and watch for what you should recieve as output. Be sure to note the time
# it takes for the command to execute and setup your monitor timers accordingly.
#
use strict;
use Socket;

# Global variables
# Turn logging off = 0 on = 1
my $debug = 0;

# predeclare a simple loging sub
sub logmsg { if($debug) { print "$0 $$: @_ at ", scalar localtime, "\n" } }

# Read the port to listen on as the first command line argument or use a preconfigured default
my $svcPort = shift || 9090;

# Open a TCP listener on that listening port for all address on the host machine
my $proto = getprotobyname('tcp');
# make sure what was input was a interger for a port number
$svcPort = $1 if $svcPort =~ /(\d+)/; 
socket(SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or die "socket failed: $!";
setsockopt(SERVER,SOL_SOCKET,SO_REUSEADDR,1) or die "setsock failed: $!";
my $port_hdl = sockaddr_in($svcPort,INADDR_ANY);
bind(SERVER,$port_hdl) or die "bind failed: $!";
listen(SERVER, SOMAXCONN) or die "listen failed: $!";
logmsg "server started on port $svcPort";

# Create a SIGCHLD handler for terminating forked child processes. You need
# this to clean up your forked child processes. Assumes SYSV signaling.. which 
# is good for Linux, Solaris,... most *nixes.  Not Windows. I'll have to do a 
# .Net service similar example at a later date. 
my $waitedpid = 0;
sub REAPER {
   $waitedpid = wait;
   $SIG{CHLD} = \&REAPER; 
   logmsg "reaped child process";
}
$SIG{CHLD} = \&REAPER;

# Loop waiting for a client connection
my $paddr;
for ( $waitedpid = 0; ($paddr = accept(CLIENT,SERVER)) || $waitedpid; $waitedpid = 0, close CLIENT)
{
    next if $waitedpid and not $paddr;
    my($port,$iaddr) = sockaddr_in($paddr);
    my $name = gethostbyaddr($iaddr,AF_INET);
    # Got a client connection fork a client thread
    logmsg "connection from $name [",inet_ntoa($iaddr), "] at port $port";
    spawn();
}

sub spawn {
    # fork() a client process - Heavy weight fork
    my $pid;
    if (!defined($pid = fork)) {
            return; # fork system call failed -- SIG
    } elsif ($pid) {
            return; # I'm the parent
    }
    # I'm the forked client now
    my $line; 
    # Read in from the client socket a line at a time until EOF
    while (defined ($line = ))
    {
# In this example we only deal with the first line
# Using the splite command would allow you to separate multiple 
#   arguments with spaces and just use the split array in your command
# In our example we will simply read in a host to PING. 
        my @clientinput = split(/ /,$line);        
my $hostname = $clientinput[0];
        #If they only sent one command on the line, then remove the last \n character
        chomp($hostname);
#Setup the ping command
        my $cmd = "/bin/ping -c 3 $hostname";
logmsg " client executing $cmd";
#Run the command and echo back the command output
        my $cmdout = `$cmd`;
print CLIENT $cmdout;
close CLIENT;
exit;
    }     
}
Published Mar 12, 2015
Version 1.0
No CommentsBe the first to comment