1#!@PERL@ -w 2# 3# (c)1999 Ian Cass, Knowledge Matters Ltd. 4# Read the GNU copyright stuff for all the legalese 5# 6# Check NTP time servers plugin. This plugin requires the ntpdate utility to 7# be installed on the system, however since it's part of the ntp suite, you 8# should already have it installed. 9# 10# 11# Nothing clever done in this program - its a very simple bare basics hack to 12# get the job done. 13# 14# Things to do... 15# check @words[9] for time differences greater than +/- x secs & return a 16# warning. 17# 18# (c) 1999 Mark Jewiss, Knowledge Matters Limited 19# 22-9-1999, 12:45 20# 21# Modified script to accept 2 parameters or set defaults. 22# Now issues warning or critical alert is time difference is greater than the 23# time passed. 24# 25# These changes have not been tested completely due to the unavailability of a 26# server with the incorrect time. 27# 28# (c) 1999 Bo Kersey, VirCIO - Managed Server Solutions <bo@vircio.com> 29# 22-10-99, 12:17 30# 31# Modified the script to give usage if no parameters are input. 32# 33# Modified the script to check for negative as well as positive 34# time differences. 35# 36# Modified the script to work with ntpdate 3-5.93e Wed Apr 14 20:23:03 EDT 1999 37# 38# Modified the script to work with ntpdate's that return adjust or offset... 39# 40# 41# Script modified 2000 June 01 by William Pietri <william@bianca.com> 42# 43# Modified script to handle weird cases: 44# o NTP server doesn't respond (e.g., has died) 45# o Server has correct time but isn't suitable synchronization 46# source. This happens while starting up and if contact 47# with master has been lost. 48# 49# Modified to run under Embedded Perl (sghosh@users.sf.net) 50# - combined logic some blocks together.. 51# 52# Added ntpdate check for stratum 16 desynch peer (James Fidell) Feb 03, 2003 53# 54# ntpdate - offset is in seconds 55# changed ntpdc to ntpq - jitter/dispersion is in milliseconds 56# 57# Patch for for regex for stratum1 refid. 58 59require 5.004; 60use POSIX; 61use strict; 62use Getopt::Long; 63use vars qw($opt_V $opt_h $opt_H $opt_t $opt_w $opt_c $opt_O $opt_j $opt_k $verbose $PROGNAME $def_jitter $ipv4 $ipv6); 64use FindBin; 65use lib "$FindBin::Bin"; 66use utils qw($TIMEOUT %ERRORS &print_revision &support); 67 68$PROGNAME="check_ntp"; 69 70sub print_help (); 71sub print_usage (); 72 73$ENV{'PATH'}='@TRUSTED_PATH@'; 74$ENV{'BASH_ENV'}=''; 75$ENV{'ENV'}=''; 76 77# defaults in sec 78my $DEFAULT_OFFSET_WARN = 60; # 1 minute 79my $DEFAULT_OFFSET_CRIT = 120; # 2 minutes 80# default in millisec 81my $DEFAULT_JITTER_WARN = 5000; # 5 sec 82my $DEFAULT_JITTER_CRIT = 10000; # 10 sec 83 84Getopt::Long::Configure('bundling'); 85GetOptions 86 ("V" => \$opt_V, "version" => \$opt_V, 87 "h" => \$opt_h, "help" => \$opt_h, 88 "v" => \$verbose, "verbose" => \$verbose, 89 "4" => \$ipv4, "use-ipv4" => \$ipv4, 90 "6" => \$ipv6, "use-ipv6" => \$ipv6, 91 "w=f" => \$opt_w, "warning=f" => \$opt_w, # offset|adjust warning if above this number 92 "c=f" => \$opt_c, "critical=f" => \$opt_c, # offset|adjust critical if above this number 93 "O" => \$opt_O, "zero-offset" => \$opt_O, # zero-offset bad 94 "j=s" => \$opt_j, "jwarn=i" => \$opt_j, # jitter warning if above this number 95 "k=s" => \$opt_k, "jcrit=i" => \$opt_k, # jitter critical if above this number 96 "t=s" => \$opt_t, "timeout=i" => \$opt_t, 97 "H=s" => \$opt_H, "hostname=s" => \$opt_H); 98 99if ($opt_V) { 100 print_revision($PROGNAME,'@NP_VERSION@'); 101 exit $ERRORS{'OK'}; 102} 103 104if ($opt_h) { 105 print_help(); 106 exit $ERRORS{'OK'}; 107} 108 109# jitter test params specified 110if (defined $opt_j || defined $opt_k ) { 111 $def_jitter = 1; 112} 113 114$opt_H = shift unless ($opt_H); 115my $host = $1 if ($opt_H && $opt_H =~ m/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z][-a-zA-Z0-9]+(\.[a-zA-Z][-a-zA-Z0-9]+)*)$/); 116unless ($host) { 117 print "No target host specified\n"; 118 print_usage(); 119 exit $ERRORS{'UNKNOWN'}; 120} 121 122my ($timeout, $owarn, $ocrit, $jwarn, $jcrit); 123 124$timeout = $TIMEOUT; 125($opt_t) && ($opt_t =~ /^([0-9]+)$/) && ($timeout = $1); 126 127$owarn = $DEFAULT_OFFSET_WARN; 128($opt_w) && ($opt_w =~ /^([0-9.]+)$/) && ($owarn = $1); 129 130$ocrit = $DEFAULT_OFFSET_CRIT; 131($opt_c) && ($opt_c =~ /^([0-9.]+)$/) && ($ocrit = $1); 132 133$jwarn = $DEFAULT_JITTER_WARN; 134($opt_j) && ($opt_j =~ /^([0-9]+)$/) && ($jwarn = $1); 135 136$jcrit = $DEFAULT_JITTER_CRIT; 137($opt_k) && ($opt_k =~ /^([0-9]+)$/) && ($jcrit = $1); 138 139if ($ocrit < $owarn ) { 140 print "Critical offset should be larger than warning offset\n"; 141 print_usage(); 142 exit $ERRORS{"UNKNOWN"}; 143} 144 145if ($def_jitter) { 146 if ($opt_k < $opt_j) { 147 print "Critical jitter should be larger than warning jitter\n"; 148 print_usage(); 149 exit $ERRORS{'UNKNOWN'}; 150 } 151} 152 153 154my $stratum = -1; 155my $ignoreret = 0; 156my $answer = undef; 157my $offset = undef; 158my $jitter = undef; 159my $syspeer = undef; 160my $candidate = 0; 161my @candidates; 162my $msg; # first line of output to print if format is invalid 163 164my $state = $ERRORS{'UNKNOWN'}; 165my $ntpdate_error = $ERRORS{'UNKNOWN'}; 166my $jitter_error = $ERRORS{'UNKNOWN'}; 167 168# some systems don't have a proper ntpq (migrated from ntpdc) 169my $have_ntpq = undef; 170if ($utils::PATH_TO_NTPQ && -x $utils::PATH_TO_NTPQ ) { 171 $have_ntpq = 1; 172}else{ 173 $have_ntpq = 0; 174} 175 176# Just in case of problems, let's not hang Nagios 177$SIG{'ALRM'} = sub { 178 print ("ERROR: No response from ntp server (alarm)\n"); 179 exit $ERRORS{"UNKNOWN"}; 180}; 181alarm($timeout); 182 183# Determine protocol to be used for ntpdate and ntpq 184my $ntpdate = $utils::PATH_TO_NTPDATE; 185my $ntpq = $utils::PATH_TO_NTPQ; 186if ($ipv4) { 187 $ntpdate .= " -4"; 188 $ntpq .= " -4"; 189} 190elsif ($ipv6) { 191 $ntpdate .= " -6"; 192 $ntpq .= " -6"; 193} 194# else don't use any flags 195 196### 197### 198### First, check ntpdate 199### 200### 201 202if (!open (NTPDATE, $ntpdate . " -q $host 2>&1 |")) { 203 print "Could not open $ntpdate: $!\n"; 204 exit $ERRORS{"UNKNOWN"}; 205} 206 207my $out; 208while (<NTPDATE>) { 209 #print if ($verbose); # noop 210 $msg = $_ unless ($msg); 211 $out .= "$_ "; 212 213 if (/stratum\s(\d+)/) { 214 $stratum = $1; 215 } 216 217 if (/(offset|adjust)\s+([-.\d]+)/i) { 218 $offset = $2; 219 220 # An offset of 0.000000 with an error is probably bogus. Actually, 221 # it's probably always bogus, but let's be paranoid here. 222 # Has been reported that 0.0000 happens in a production environment 223 # on Solaris 8 so this check should be taken out - SF tracker 1150777 224 if (defined $opt_O ) { 225 if ($offset == 0) { undef $offset;} 226 } 227 228 $ntpdate_error = defined ($offset) ? $ERRORS{"OK"} : $ERRORS{"CRITICAL"}; 229 print "ntperr = $ntpdate_error \n" if $verbose; 230 231 } 232 233 if (/no server suitable for synchronization found/) { 234 if ($stratum == 16) { 235 $ntpdate_error = $ERRORS{"WARNING"}; 236 $msg = "Desynchronized peer server found"; 237 $ignoreret=1; 238 } 239 else { 240 $ntpdate_error = $ERRORS{"CRITICAL"}; 241 $msg = "No suitable peer server found - "; 242 } 243 } 244 245} 246$out =~ s/\n//g; 247close (NTPDATE) || 248 die $! ? "$out - Error closing $ntpdate pipe: $!" 249 : "$out - Exit status: $? from $ntpdate\n"; 250 251# declare an error if we also get a non-zero return code from ntpdate 252# unless already set to critical 253if ( $? && !$ignoreret ) { 254 print "stderr = $? : $! \n" if $verbose; 255 $ntpdate_error = $ntpdate_error == $ERRORS{"CRITICAL"} ? $ERRORS{"CRITICAL"} : $ERRORS{"UNKNOWN"} ; 256 print "ntperr = $ntpdate_error : $!\n" if $verbose; 257} 258 259### 260### 261### Then scan xntpq/ntpq if it exists 262### and look in the 11th column for jitter 263### 264# Field 1: Tally Code ( Space, 'x','.','-','+','#','*','o') 265# Only match for '*' which implies sys.peer 266# or 'o' which implies pps.peer 267# If both exist, the last one is picked. 268# Field 2: address of the remote peer 269# Field 3: Refid of the clock (0.0.0.0 if unknown, WWWV/PPS/GPS/ACTS/USNO/PCS/... if Stratum1) 270# Field 4: stratum (0-15) 271# Field 5: Type of the peer: local (l), unicast (u), multicast (m) 272# broadcast (b); not sure about multicast/broadcast 273# Field 6: last packet receive (in seconds) 274# Field 7: polling interval 275# Field 8: reachability register (octal) 276# Field 9: delay 277# Field 10: offset 278# Field 11: dispersion/jitter 279# 280# According to bug 773588 Some solaris xntpd implementations seemto match on 281# "#" even though the docs say it exceeds maximum distance. Providing patch 282# here which will generate a warining. 283 284if ($have_ntpq) { 285 286 if ( open(NTPQ, $ntpq . " -np $host 2>&1 |") ) { 287 while (<NTPQ>) { 288 print $_ if ($verbose); 289 if ( /timed out/ ){ 290 $have_ntpq = 0 ; 291 last ; 292 } 293 # number of candidates on <host> for sys.peer 294 if (/^(\*|\+|\#|o])/) { 295 ++$candidate; 296 push (@candidates, $_); 297 print "Candidate count= $candidate\n" if ($verbose); 298 } 299 300 # match sys.peer or pps.peer 301 if (/^(\*|o)(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) { 302 $syspeer = $2; 303 $stratum = $4; 304 $jitter = $11; 305 print "match $_ \n" if $verbose; 306 if ($jitter > $jcrit) { 307 print "Jitter_crit = $11 :$jcrit\n" if ($verbose); 308 $jitter_error = $ERRORS{'CRITICAL'}; 309 } elsif ($jitter > $jwarn ) { 310 print "Jitter_warn = $11 :$jwarn\n" if ($verbose); 311 $jitter_error = $ERRORS{'WARNING'}; 312 } else { 313 $jitter_error = $ERRORS{'OK'}; 314 } 315 } else { 316 print "No match!\n" if $verbose; 317 } 318 319 } 320 close NTPQ || 321 die $! ? "Error closing $ntpq pipe: $!" 322 : "Exit status: $? from $ntpq\n"; 323 324 # if we did not match sys.peer or pps.peer but matched # candidates only 325 # generate a warning 326 # based on bug id 773588 327 unless (defined $syspeer) { 328 if ($#candidates >=0) { 329 foreach my $c (@candidates) { 330 $c =~ /^(#)([-0-9.\s]+)\s+([-0-9A-Za-z_().]+)\s+([-0-9.]+)\s+([lumb-]+)\s+([-0-9m.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)/; 331 $syspeer = $2; 332 $stratum = $4; 333 $jitter = $11; 334 print "candidate match $c \n" if $verbose; 335 if ($jitter > $jcrit) { 336 print "Candidate match - Jitter_crit = $11 :$jcrit\n" if ($verbose); 337 $jitter_error = $ERRORS{'CRITICAL'}; 338 }elsif ($jitter > $jwarn ) { 339 print "Candidate match - Jitter_warn = $11 :$jwarn \n" if ($verbose); 340 $jitter_error = $ERRORS{'WARNING'}; 341 } else { 342 $jitter_error = $ERRORS{'WARNING'}; 343 } 344 } 345 346 } 347 } 348 } 349} 350 351 352if ($ntpdate_error != $ERRORS{'OK'}) { 353 $state = $ntpdate_error; 354 if ($ntpdate_error == $ERRORS{'WARNING'} ) { 355 $answer = $msg; 356 } 357 else { 358 $answer = $msg . "Server for ntp probably down"; 359 } 360 361 if (defined($offset) && abs($offset) > $ocrit) { 362 $state = $ERRORS{'CRITICAL'}; 363 $answer = "Server Error and offset $offset sec > +/- $ocrit sec"; 364 } elsif (defined($offset) && abs($offset) > $owarn) { 365 $answer = "Server error and offset $offset sec > +/- $owarn sec"; 366 } elsif (defined($jitter) && abs($jitter) > $jcrit) { 367 $answer = "Server error and jitter $jitter msec > +/- $jcrit msec"; 368 } elsif (defined($jitter) && abs($jitter) > $jwarn) { 369 $answer = "Server error and jitter $jitter msec > +/- $jwarn msec"; 370 } 371 372} elsif ($have_ntpq && $jitter_error != $ERRORS{'OK'}) { 373 $state = $jitter_error; 374 $answer = "Jitter $jitter too high"; 375 if (defined($offset) && abs($offset) > $ocrit) { 376 $state = $ERRORS{'CRITICAL'}; 377 $answer = "Jitter error and offset $offset sec > +/- $ocrit sec"; 378 } elsif (defined($offset) && abs($offset) > $owarn) { 379 $answer = "Jitter error and offset $offset sec > +/- $owarn sec"; 380 } elsif (defined($jitter) && abs($jitter) > $jcrit) { 381 $answer = "Jitter error and jitter $jitter msec > +/- $jcrit msec"; 382 } elsif (defined($jitter) && abs($jitter) > $jwarn) { 383 $answer = "Jitter error and jitter $jitter msec > +/- $jwarn msec"; 384 } 385 386} elsif( !$have_ntpq ) { # no errors from ntpdate and no ntpq or ntpq timed out 387 if (abs($offset) > $ocrit) { 388 $state = $ERRORS{'CRITICAL'}; 389 $answer = "Offset $offset sec > +/- $ocrit sec"; 390 } elsif (abs($offset) > $owarn) { 391 $state = $ERRORS{'WARNING'}; 392 $answer = "Offset $offset sec > +/- $owarn sec"; 393 } elsif (( abs($offset) > $owarn) && $def_jitter ) { 394 $state = $ERRORS{'WARNING'}; 395 $answer = "Offset $offset sec > +/- $owarn sec, ntpq timed out"; 396 } elsif ( $def_jitter ) { 397 $state = $ERRORS{'WARNING'}; 398 $answer = "Offset $offset secs, ntpq timed out"; 399 } else{ 400 $state = $ERRORS{'OK'}; 401 $answer = "Offset $offset secs"; 402 } 403 404 405 406} else { # no errors from ntpdate or ntpq 407 if (abs($offset) > $ocrit) { 408 $state = $ERRORS{'CRITICAL'}; 409 $answer = "Offset $offset sec > +/- $ocrit sec, jitter $jitter msec"; 410 } elsif (abs($jitter) > $jcrit ) { 411 $state = $ERRORS{'CRITICAL'}; 412 $answer = "Jitter $jitter msec> +/- $jcrit msec, offset $offset sec"; 413 } elsif (abs($offset) > $owarn) { 414 $state = $ERRORS{'WARNING'}; 415 $answer = "Offset $offset sec > +/- $owarn sec, jitter $jitter msec"; 416 } elsif (abs($jitter) > $jwarn ) { 417 $state = $ERRORS{'WARNING'}; 418 $answer = "Jitter $jitter msec> +/- $jwarn msec, offset $offset sec"; 419 420 } else { 421 $state = $ERRORS{'OK'}; 422 $answer = "Offset $offset secs, jitter $jitter msec, peer is stratum $stratum"; 423 } 424 425} 426 427foreach my $key (keys %ERRORS) { 428 if ($state==$ERRORS{$key}) { 429 print ("NTP $key: $answer|offset=$offset"); 430 if ($have_ntpq) { 431 print (", jitter=" . ($jitter || 0)/1000 . ",peer_stratum=$stratum"); 432 } 433 print ("\n"); 434 last; 435 } 436} 437exit $state; 438 439 440#### 441#### subs 442 443sub print_usage () { 444 print "Usage: $PROGNAME -H <host> [-46] [-O] [-w <warn>] [-c <crit>] [-j <warn>] [-k <crit>] [-v verbose]\n"; 445} 446 447sub print_help () { 448 print_revision($PROGNAME,'@NP_VERSION@'); 449 print "Copyright (c) 2003 Bo Kersey/Karl DeBisschop\n"; 450 print "\n"; 451 print_usage(); 452 print " 453Checks the local timestamp offset versus <host> with ntpdate 454Checks the jitter/dispersion of clock signal between <host> and its sys.peer with ntpq\n 455-O (--zero-offset) 456 A zero offset on \"ntpdate\" will generate a CRITICAL.\n 457-w (--warning) 458 Clock offset in seconds at which a warning message will be generated.\n Defaults to $DEFAULT_OFFSET_WARN. 459-c (--critical) 460 Clock offset in seconds at which a critical message will be generated.\n Defaults to $DEFAULT_OFFSET_CRIT. 461-j (--jwarn) 462 Clock jitter in milliseconds at which a warning message will be generated.\n Defaults to $DEFAULT_JITTER_WARN. 463-k (--jcrit) 464 Clock jitter in milliseconds at which a critical message will be generated.\n Defaults to $DEFAULT_JITTER_CRIT. 465 466 If jitter/dispersion is specified with -j or -k and ntpq times out, then a 467 warning is returned.\n 468-4 (--use-ipv4) 469 Use IPv4 connection 470-6 (--use-ipv6) 471 Use IPv6 connection 472\n"; 473support(); 474} 475