Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
code share

NTP Monitor

Problem this snippet solves:

Version 9 (tested on v9.2.4)

Based on F5's example monitor, with the NTP check added. We make sure that the node gives us an NTP response and that the node hasn't become stratum 16 (ie, it's lost it's source). We use the CPAN Net::NTP module to make things easy.

Just use it like a normal external monitor, it only uses the default IP and port.

Tested on Version:
Comments on this Snippet
Comment made 05-Jun-2015 by Rich R 0
Where is the code?
Comment made 08-Jul-2015 by Jason Cohen
The old page... https://devcentral.f5.com/wiki/AdvDesignConfig.Print.aspx?Page=AdvDesignConfig.NTP_Monitor
Comment made 04-Jan-2016 by Rao 53
For v11.x my $node = $ENV{"NODE_IP"}; my $port = $ENV{"NODE_PORT"}; and add: my $node = $ARGV[0]; my $node = substr $node,7;; my $port = $ARGV[1]; $node =~ s/::ffff//; --> Remove this
Comment made 07-Nov-2016 by Godfrey Bennett 0

Rao, I don't quite understand your suggestion for modification - would it be possible to modify the source file to show exactly what needs to be done? I am interested in testing this on v12.x, which I think should be very close to v11. Thanks

Comment made 25-Apr-2017 by Mark 8

I updated the code block on this page to include the monitor here, rather than needing to follow the link to the old location.

I included Rao's changes to make the monitor compatible with v11 and later. Rather than "$node = substr $node,7;;", I did "$node =~ s/^::ffff://;" to hopefully not break v6 node addresses. (I don't have any v6 to test, but Rao's use use of substr was sure to break them)

I also moved Net::NTP from /usr/bin/monitors/CPAN to /shared/lib/perl5 because I don't want the monitor to break when I upgrade Big IP.

Comment made 08-May-2017 by WeaverJK 177

Thanks to philh, Mark S., Rao, and Mark for their contributions.

Looking for assistance determining where my use of the above code may have gone wrong.

Steps taken:

A) Within /shared/lib, created "perl5" directory with default permissions.

B) Per this page: https://devcentral.f5.com/codeshare/ntp-monitor-for-11x-with-complete-instructions-999?lc=1 Downloaded and extracted ntp.zip contents. TFTPed NTP.pm to /shared/lib/perl5 and saved it with 755 permissions.

C) Used the code above in a file named "ntp_mon."

D) Used the F5 File Management External Monitors section to upload "ntp_mon" as "MON_EXT_NTP."

E) Created a Monitor called "MON_NTP" that used "MON_EXT_NTP" as an external monitor.

F) Modified the Pool to use the "MON_NTP" monitor.

Results: Pool Members showed as red diamond down.

I did not use Arguments or Variables when creating the external monitor configuration as neither article mentioned the configuration of arguments or variables.

I just re-re-read one of the articles and noticed that it uses only the "default" IP and Port. I wonder what the author means by this. I presumed that the monitor would send an NTP request to the IP address of the Member over its IP and Port. I also assumed that the BIG-IP would be intelligent enough to use a static or floating self IP on the same subnet as Member in order to attempt to perform the NTP query.

Thanks for your input,


Comment made 08-May-2017 by Mark 8

If I read what you did correctly, you created /shared/lib/perl5/NTP.pm. Unfortunately, that isn't how Perl manages it's modules. The way Perl typically stores/accesses it's module files, the :: is translated into a file system directory break.

So, for Net::NTP above, stored in /shared/lib/perl5, the full path to NTP.pm that Perl expects would be /shared/lib/perl5/Net/NTP.pm.

Comment made 08-May-2017 by WeaverJK 177

Mark, thanks for your input.

Mark, thank you for explaining the Net::NTP section. I Googled it earlier but did not make the connection to the file structure as you explained it. I have no experience with "perl" at this time.

I changed the actual directory structure as suggested -> /shared/lib/perl5/Net/NTP.pm. I left the "use lib" statement as per the code in this article (use lib "/shared/lib/perl5";). I used the Configuration Utility to examine the external monitor. I discovered about six instances of invalid characters (was a question mark inside a black diamond). I removed these characters.

Tried the monitor again and the members went to red diamond down.

Any other thoughts? Thanks!

Comment made 08-May-2017 by Mark 8

'use lib "/shared/lib/perl5";' is correct, so leaving it as-is was the right thing to do.

First thing I would do is make sure that your pool config is correct and that your NTP servers really are up and configured correctly. (e.g. responding to remote queries, not stratum 16). I don't know where those invalid characters came from when you downloaded it, everything looks OK when I look at the code on this page. I suspect that you may have broken the code when you deleted the invalid character markers.

Comment made 08-May-2017 by WeaverJK 177

Great point, Mark. I believe I addressed that question in my initial post, which DevCentral trashed as I was not logged in at the time. I had to retype the post and left out the bit about the NTP services functioning.

As a temporary measure, when I first configured the monitor, I set up a monitor to check port 123. The Nodes were all Active. The Printers which are using the VS/VIP received their NTP configurations without a problem.

The issue is when I remove the "MON_NTP_123" monitor and replace it with the "MON_EXT_NTP" monitor.

Other systems using the Nodes directly (not going through the VS/VIP/F5) are also obtaining time synchronization appropriately.

I can try deleting and importing the monitor file again, without modifying it, but I truly would expect the program to fail if it contained characters that the F5 was not happy with. Not to say that the code on this page is that way, but with the copying and pasting I had to do on a Mac OS, who knows what characters were thrown into the mix. ;)

Comment made 09-May-2017 by WeaverJK 177

The script above uses arguments instead of environment variables.
Are these arguments the IP and Port passed from the Monitor itself? Seems reasonable.

Script hasn't worked with or without the invalid characters mentioned above.

Thanks for any additional input.

Comment made 09-May-2017 by Mark 8

External monitor input handling changed between version 10 and version 11. In version 10 and earlier, they used environment variables to pass the IP and port. Starting in version 11, the IP and port are passed as argv[0] and argv[1].

I don't know what to tell you about the script not working for you. The script above is what I currently have in service.

Comment made 09-May-2017 by WeaverJK 177

Thanks, Mark, for the background info.

If you don't mind, let's just walk through the steps again...

Should this work: ?

SSH or Console to the F5. Navigate to /shared/lib. Create a directory called "perl5" with 755 permissions. (Note: I created the directory and allowed it to inherit permissions). TFTP the NTP.pm file (from the ntp.zip file from the other post) to /shared/lib/perl5/Net/NTP.pm and set the permissions to 755. Copy the text above and paste it into a notepad.exe text file on a Windows computer. Use that Windows computer to connect to the Configuration Utility (CU). Use CU. Go to System > File Management > External Monitor File List (or something close). Import the contents of the text file while providing the external monitor a name.

Edit the external monitor to verify that it does not contain invalid characters (such as the question mark inside the diamond). Create a Monitor. Define the type as External. Select the External Monitor. Assign the monitor to a Pool. Pool Members are Nodes that are NTP servers.

Did I miss something or should this work?



Comment made 10-May-2017 by Mark 8

That should work.

Comment made 10-May-2017 by WeaverJK 177

Thanks, Mark, for your time and patience. I appreciate the explanations.

If/When the monitor works on our systems, I'll follow-up here.

Comment made 11-May-2017 by WeaverJK 177

Is there anything that needs to be done to ensure perl is working? Can the script (external monitor and NTP.pm) be tested from the command line to directly test functionality of the script between the F5 and the NTP server? If so, how?

At a quick glance (being new to perl), it looks like I'd have to copy the external monitor file over to the F5 as a perl script (such as TFTPing mon_ntp to /var/tmp/test/mon_ntp.pl), perhaps modify the script so that the arguments (IP and Port) are hard-coded, and call the script (/usr/bin/perl /var/tmp/test/mon_ntp.pl). Sound about right? Is there an easier way to test this and see the results/errors?

Thanks in advance!


Comment made 11-May-2017 by WeaverJK 177

I tried what I just suggested... F5_Prompt# perl mon_ntp.pl Unrecognized character \xC2 in column 1 at mon_ntp.pl line 27.

I removed the offending characters.

F5_Prompt# perl mon_ntp.pl syntax error at mon_ntp.pl line 59, near "= ;" Execution of mon_ntp.pl aborted due to compilation errors.

Perl appears to have a problem with something around this line:
my $pid = ;

Going through again to see what may have changed.

Comment made 11-May-2017 by WeaverJK 177

Comparing the code in this thread to the code in the original post (https://devcentral.f5.com/codeshare/ntp-monitor-for-11x-with-complete-instructions-999), I found this difference: Above, line 56: my $pid = ; Original: my $pid = <pid>;

When I changed the code back to the original, perl processed further (past line 56).

Yeah! This resolved the issue!!!

pats self on back

I also determined WHY this issue with the code appears above. The code that these F5 web pages is using to parse and display the text treated the "<pid>" as something special and this text did not display in the code lines above. I had to preface the less-than symbol with a backslash. Another 10 points!

Comment made 11-May-2017 by Mark 8

I just checked, and the <pid> is there if I try to edit the code on this page. DevCentral is probably blocking things that look like HTML tags to prevent HTML injection. I wonder how I fix that. I can't very well leave it broken.

Ah, that's how. By replacing the < and > with HTML entity strings in the code. sigh

Comment made 11-May-2017 by WeaverJK 177

Mark - the rest of my post may help. Preface the less-than symbol with a backslash.

No worries. I figured the code was there, just not visible.

Comment made 11-May-2017 by WeaverJK 177

Thanks again for your assistance, Mark.

Comment made 21-Jun-2018 by Ryan 498

I ain't got time for a Perl module.

Hacked together from the above and Perl Monks. Seems to work!


use IO::Socket;

my $programname = '/' . $0;
$programname =~ m/^.*\/([^\/]+)$/;
$programname = $1;
if ($programname eq '') {
    die "Bad data in program name\n"

my $pidfile = "/var/run/$programname.$node..$port.pid";
my $pid = "$$";

if (-f $pidfile ) {
    open(PID, "<$pidfile"); 
    my $pid = <pid>; 
    if ( $pid ) {
        chomp $pid; $pid =~ m/^(\d+)$/; $pid = $1;
        if ( $pid ) {
            kill 9, $pid;

open(PID, ">$pidfile");
print PID $pid, "\n";

if (GetNTPTime($ARGV[0],$ARGV[1])) {
    print "OK\n";
} else {
    print "DOWN\n";


sub GetNTPTime{
    my $host = shift;
    my $timeout = 2;
    my $sock = IO::Socket::INET->new(
        Proto => 'udp',
        PeerPort => $ARGV[1],
        PeerAddr => $host,
      Timeout => $timeout
    )   or do { return 0 };
    my $ntp_msg = pack("B8 C3 N11", '00100011', (0)x14);
    $sock->send($ntp_msg) or do { return 0 };
    my $rin='';
    vec($rin, fileno($sock), 1) = 1;
    select(my $rout=$rin, undef, my $eout=$rin, $timeout) or do { return 0 };
    $sock->recv($ntp_msg, length($ntp_msg))
        or do { return 0 };
    my ($LIVNMode, $Stratum, $Poll, $Precision,
            $RootDelay, $RootDispersion, $RefIdentifier,
            $Reference, $ReferenceF, $Original, $OriginalF,
            $Receive, $ReceiveF, $Transmit, $TransmitF
        )   = unpack('a C2 c1 N8 N N B32', $ntp_msg);
    $Receive -= 2208988800;
    my $NTPTime = $Receive + $ReceiveF / 65536 / 65536;
    $RefIdentifier = unpack('A4', $RefIdentifier);
    my $LI = vec($LIVNMode, 3, 2);
    my $VN = unpack("C", $LIVNMode & "\x38") >> 3;
    my $Mode = unpack("C", $LIVNMode & "\x07");
    if ($RefIdentifier ne '1280') {
        return 1
    } else {
        return 0