1#!/usr/bin/perl -w 2# --*-perl-*- 3;# 4;# ntploopstat,v 3.1 1993/07/06 01:09:11 jbj Exp 5;# 6;# Poll NTP server using NTP mode 7 loopinfo request. 7;# Log info and timestamp to file for processing by ntploopwatch. 8;# 9;# 10;# Copyright (c) 1992 11;# Rainer Pruy Friedrich-Alexander Universitaet Erlangen-Nuernberg 12;# 13;################################################################# 14;# 15;# The format written to the logfile is the same as used by xntpd 16;# for the loopstats file. 17;# This script however allows to gather loop filter statistics from 18;# remote servers where you do not have access to the loopstats logfile. 19;# 20;# Please note: Communication delays affect the accuracy of the 21;# timestamps recorded. Effects from these delays will probably 22;# not show up, as timestamps are recorded to the second only. 23;# (Should have implemented &gettimeofday()..) 24;# 25 26$0 =~ s!^.*/([^/]+)$!$1!; # beautify script name 27 28$ntpserver = 'localhost'; # default host to poll 29$delay = 60; # default sampling rate 30 ;# keep it shorter than minpoll (=64) 31 ;# to get all values 32 33require "ctime.pl"; 34;# handle bug in early ctime distributions 35$ENV{'TZ'} = 'MET' unless defined($ENV{'TZ'}) || $] > 4.010; 36 37if (defined(@ctime'MoY)) 38{ 39 *MonthName = *ctime'MoY; 40} 41else 42{ 43 @MonthName = ('Jan','Feb','Mar','Apr','May','Jun', 44 'Jul','Aug','Sep','Oct','Nov','Dec'); 45} 46 47;# this routine can be redefined to point to syslog if necessary 48sub msg 49{ 50 return unless $verbose; 51 52 print STDERR "$0: "; 53 printf STDERR @_; 54} 55 56;############################################################# 57;# 58;# process command line 59$usage = <<"E-O-S"; 60 61usage: 62 $0 [-d<delay>] [-t<timeout>] [-l <logfile>] [-v] [ntpserver] 63E-O-S 64 65while($_ = shift) 66{ 67 /^-v(\d*)$/ && ($verbose=($1 eq '') ? 1 : $1,1) && next; 68 /^-d(\d*)$/ && 69 do { 70 ($1 ne '') && ($delay = $1,1) && next; 71 @ARGV || die("$0: delay value missing after -d\n$usage"); 72 $delay = shift; 73 ($delay >= 0) || die("$0: bad delay value \"$delay\"\n$usage"); 74 next; 75 }; 76 /^-l$/ && 77 do { 78 @ARGV || die("$0: logfile missing after -l\n$usage"); 79 $logfile = shift; 80 next; 81 }; 82 /^-t(\d*(\.\d*)?)$/ && 83 do { 84 ($1 ne '') && ($timeout = $1,1) && next; 85 @ARGV || die("$0: timeout value missing after -t\n$usage\n"); 86 $timeout = shift; 87 ($timeout > 0) || 88 die("$0: bad timeout value \"$timeout\"\n$usage"); 89 next; 90 }; 91 92 /^-/ && die("$0: unknown option \"$_\"\n$usage"); 93 94 ;# any other argument is server to poll 95 $ntpserver = $_; 96 last; 97} 98 99if (@ARGV) 100{ 101 warn("unexpected arguments: ".join(" ",@ARGV).".\n"); 102 die("$0: too many servers specified\n$usage"); 103} 104 105;# logfile defaults to include server name 106;# The name of the current month is appended and 107;# the file is opened and closed for each sample. 108;# 109$logfile = "loopstats:$ntpserver." unless defined($logfile); 110$timeout = 12.0 unless defined($timeout); # wait $timeout seconds for reply 111 112$MAX_FAIL = 60; # give up after $MAX_FAIL failed polls 113 114 115$MJD_1970 = 40587; 116 117if (eval 'require "syscall.ph";') 118{ 119 if (defined(&SYS_gettimeofday)) 120 { 121 ;# assume standard 122 ;# gettimeofday(struct timeval *tp,struct timezone *tzp) 123 ;# syntax for gettimeofday syscall 124 ;# tzp = NULL -> undef 125 ;# tp = (long,long) 126 eval 'sub time { local($tz) = pack("LL",0,0); 127 (&msg("gettimeofday failed: $!\n"), 128 return (time)) 129 unless syscall(&SYS_gettimeofday,$tz,undef) == 0; 130 local($s,$us) = unpack("LL",$tz); 131 return $s + $us/1000000; }'; 132 local($t1,$t2,$t3); 133 $t1 = time; 134 eval '$t2 = &time;'; 135 $t3 = time; 136 die("$0: gettimeofday failed: $@.\n") if defined($@) && $@; 137 die("$0: gettimeofday inconsistency time=$t1,gettimeofday=$t2,time=$t2\n") 138 if (int($t1) != int($t2) && int($t3) != int($t2)); 139 &msg("Using gettimeofday for timestamps\n"); 140 } 141 else 142 { 143 warn("No gettimeofday syscall found - using time builtin for timestamps\n"); 144 eval 'sub time { return time; }'; 145 } 146} 147else 148{ 149 warn("No syscall.ph file found - using time builtin for timestamps\n"); 150 eval 'sub time { return time; }'; 151} 152 153 154;#------------------+ 155;# from ntp_request.h 156;#------------------+ 157 158;# NTP mode 7 packet format: 159;# Byte 1: ResponseBit MoreBit Version(3bit) Mode(3bit)==7 160;# Byte 2: AuthBit Sequence # - 0 - 127 see MoreBit 161;# Byte 3: Implementation # 162;# Byte 4: Request Code 163;# 164;# Short 1: Err(3bit) NumItems(12bit) 165;# Short 2: MBZ(3bit)=0 DataItemSize(12bit) 166;# 0 - 500 byte Data 167;# if AuthBit is set: 168;# Long: KeyId 169;# 2xLong: AuthCode 170 171;# 172$IMPL_XNTPD = 2; 173$REQ_LOOP_INFO = 8; 174 175 176;# request packet for REQ_LOOP_INFO: 177;# B1: RB=0 MB=0 V=2 M=7 178;# B2: S# = 0 179;# B3: I# = IMPL_XNTPD 180;# B4: RC = REQ_LOOP_INFO 181;# S1: E=0 NI=0 182;# S2: MBZ=0 DIS=0 183;# data: 32 byte 0 padding 184;# 8byte timestamp if encryption, 0 padding otherwise 185$loopinfo_reqpkt = 186 pack("CCCC nn x32 x8", 0x17, 0, $IMPL_XNTPD, $REQ_LOOP_INFO, 0, 0); 187 188;# ignore any auth data in packets 189$loopinfo_response_size = 190 1+1+1+1+2+2 # header size like request pkt 191 + 8 # l_fp last_offset 192 + 8 # l_fp drift_comp 193 + 4 # u_long compliance 194 + 4 # u_long watchdog_timer 195 ; 196$loopinfo_response_fmt = "C4n2N2N2NN"; 197$loopinfo_response_fmt_v2 = "C4n2N2N2N2N"; 198 199;# 200;# prepare connection to server 201;# 202 203;# workaround for broken socket.ph on dynix_ptx 204eval 'sub INTEL {1;}' unless defined(&INTEL); 205eval 'sub ATT {1;}' unless defined(&ATT); 206 207require "sys/socket.ph"; 208 209require 'netinet/in.ph'; 210 211;# if you do not have netinet/in.ph enable the following lines 212;#eval 'sub INADDR_ANY { 0x00000000; }' unless defined(&INADDR_ANY); 213;#eval 'sub IPPRORO_UDP { 17; }' unless defined(&IPPROTO_UDP); 214 215if ($ntpserver =~ /^((0x?)?\w+)\.((0x?)?\w+)\.((0x?)?\w+)\.((0x?)?\w+)$/) 216{ 217 local($a,$b,$c,$d) = ($1,$3,$5,$7); 218 $a = oct($a) if defined($2); 219 $b = oct($b) if defined($4); 220 $c = oct($c) if defined($6); 221 $d = oct($d) if defined($8); 222 $server_addr = pack("C4", $a,$b,$c,$d); 223 224 $server_mainname 225 = (gethostbyaddr($server_addr,&AF_INET))[$[] || $ntpserver; 226} 227else 228{ 229 ($server_mainname,$server_addr) 230 = (gethostbyname($ntpserver))[$[,$[+4]; 231 232 die("$0: host \"$ntpserver\" is unknown\n") 233 unless defined($server_addr); 234} 235&msg ("Address of server \"$ntpserver\" is \"%d.%d.%d.%d\"\n", 236 unpack("C4",$server_addr)); 237 238$proto_udp = (getprotobyname('udp'))[$[+2] || &IPPROTO_UDP; 239 240$ntp_port = 241 (getservbyname('ntp','udp'))[$[+2] || 242 (warn "Could not get port number for service \"ntp/udp\" using 123\n"), 243 ($ntp_port=123); 244 245;# 2460 && &SOCK_DGRAM; # satisfy perl -w ... 247socket(S, &AF_INET, &SOCK_DGRAM, $proto_udp) || 248 die("Cannot open socket: $!\n"); 249 250bind(S, pack("S n N x8", &AF_INET, 0, &INADDR_ANY)) || 251 die("Cannot bind: $!\n"); 252 253($my_port, $my_addr) = (unpack("S n a4 x8",getsockname(S)))[$[+1,$[+2]; 254 255&msg("Listening at address %d.%d.%d.%d port %d\n", 256 unpack("C4",$my_addr), $my_port); 257 258$server_inaddr = pack("Sna4x8", &AF_INET, $ntp_port, $server_addr); 259 260;############################################################ 261;# 262;# the main loop: 263;# send request 264;# get reply 265;# wait til next sample time 266 267undef($lasttime); 268$lostpacket = 0; 269 270while(1) 271{ 272 $stime = &time; 273 274 &msg("Sending request $stime...\n"); 275 276 $ret = send(S,$loopinfo_reqpkt,0,$server_inaddr); 277 278 if (! defined($ret) || $ret < length($loopinfo_reqpkt)) 279 { 280 warn("$0: send failed ret=($ret): $!\n"); 281 $fail++; 282 next; 283 } 284 285 &msg("Waiting for reply...\n"); 286 287 $mask = ""; vec($mask,fileno(S),1) = 1; 288 $ret = select($mask,undef,undef,$timeout); 289 290 if (! defined($ret)) 291 { 292 warn("$0: select failed: $!\n"); 293 $fail++; 294 next; 295 } 296 elsif ($ret == 0) 297 { 298 warn("$0: request to $ntpserver timed out ($timeout seconds)\n"); 299 ;# do not count this event as failure 300 ;# it usually this happens due to dropped udp packets on noisy and 301 ;# havily loaded lines, so just try again; 302 $lostpacket = 1; 303 next; 304 } 305 306 &msg("Receiving reply...\n"); 307 308 $len = 520; # max size of a mode 7 packet 309 $reply = ""; # just make it defined for -w 310 $ret = recv(S,$reply,$len,0); 311 312 if (!defined($ret)) 313 { 314 warn("$0: recv failed: $!\n"); 315 $fail++; 316 next; 317 } 318 319 $etime = &time; 320 &msg("Received at\t$etime\n"); 321 322 ;#$time = ($stime + $etime) / 2; # symmetric delay assumed 323 $time = $etime; # the above assumption breaks for X25 324 ;# so taking etime makes timestamps be a 325 ;# little late, but keeps them increasing 326 ;# monotonously 327 328 &msg(sprintf("Reply from %d.%d.%d.%d took %f seconds\n", 329 (unpack("SnC4",$ret))[$[+2 .. $[+5], ($etime - $stime))); 330 331 if ($len < $loopinfo_response_size) 332 { 333 warn("$0: short packet ($len bytes) received ($loopinfo_response_size bytes expected\n"); 334 $fail++; 335 next; 336 } 337 338 ($b1,$b2,$b3,$b4,$s1,$s2, 339 $offset_i,$offset_f,$drift_i,$drift_f,$compl,$watchdog) 340 = unpack($loopinfo_response_fmt,$reply); 341 342 ;# check reply 343 if (($s1 >> 12) != 0) # error ! 344 { 345 die("$0: got error reply ".($s1>>12)."\n"); 346 } 347 if (($b1 != 0x97 && $b1 != 0x9f) || # Reply NotMore V=2 M=7 348 ($b2 != 0 && $b2 != 0x80) || # S=0 Auth no/yes 349 $b3 != $IMPL_XNTPD || # ! IMPL_XNTPD 350 $b4 != $REQ_LOOP_INFO || # Ehh.. not loopinfo reply ? 351 $s1 != 1 || # ???? 352 ($s2 != 24 && $s2 != 28) # 353 ) 354 { 355 warn("$0: Bad/unexpected reply from server:\n"); 356 warn(" \"".unpack("H*",$reply)."\"\n"); 357 warn(" ".sprintf("b1=%x b2=%x b3=%x b4=%x s1=%d s2=%d\n", 358 $b1,$b2,$b3,$b4,$s1,$s2)); 359 $fail++; 360 next; 361 } 362 elsif ($s2 == 28) 363 { 364 ;# seems to be a version 2 xntpd 365 ($b1,$b2,$b3,$b4,$s1,$s2, 366 $offset_i,$offset_f,$drift_i,$drift_f,$compl_i,$compl_f,$watchdog) 367 = unpack($loopinfo_response_fmt_v2,$reply); 368 $compl = &lfptoa($compl_i, $compl_f); 369 } 370 371 $time -= $watchdog; 372 373 $offset = &lfptoa($offset_i, $offset_f); 374 $drift = &lfptoa($drift_i, $drift_f); 375 376 &log($time,$offset,$drift,$compl) && ($fail = 0);; 377} 378continue 379{ 380 die("$0: Too many failures - terminating\n") if $fail > $MAX_FAIL; 381 &msg("Sleeping " . ($lostpacket ? ($delay / 2) : $delay) . " seconds...\n"); 382 383 sleep($lostpacket ? ($delay / 2) : $delay); 384 $lostpacket = 0; 385} 386 387sub log 388{ 389 local($time,$offs,$freq,$cmpl) = @_; 390 local($y,$m,$d); 391 local($fname,$suff) = ($logfile); 392 393 394 ;# silently drop sample if distance to last sample is too low 395 if (defined($lasttime) && ($lasttime + 2) >= $time) 396 { 397 &msg("Dropped packet - old sample\n"); 398 return 1; 399 } 400 401 ;# $suff determines which samples end up in the same file 402 ;# could have used $year (;-) or WeekOfYear, DayOfYear,.... 403 ;# Change it to your suit... 404 405 ($d,$m,$y) = (localtime($time))[$[+3 .. $[+5]; 406 $suff = sprintf("%04d%02d%02d",$y+1900,$m+1,$d); 407 $fname .= $suff; 408 if (!open(LOG,">>$fname")) 409 { 410 warn("$0: open($fname) failed: $!\n"); 411 $fail++; 412 return 0; 413 } 414 else 415 { 416 ;# file format 417 ;# MJD seconds offset drift compliance 418 printf LOG ("%d %.3lf %.8lf %.7lf %d\n", 419 int($time/86400)+$MJD_1970, 420 $time - int($time/86400) * 86400, 421 $offs,$freq,$cmpl); 422 close(LOG); 423 $lasttime = $time; 424 } 425 return 1; 426} 427 428;# see ntp_fp.h to understand this 429sub lfptoa 430{ 431 local($i,$f) = @_; 432 local($sign) = 1; 433 434 435 if ($i & 0x80000000) 436 { 437 if ($f == 0) 438 { 439 $i = -$i; 440 } 441 else 442 { 443 $f = -$f; 444 $i = ~$i; 445 $i += 1; # 2s complement 446 } 447 $sign = -1; 448 ;#print "NEG: $i $f\n"; 449 } 450 else 451 { 452 ;#print "POS: $i $f\n"; 453 } 454 ;# unlike xntpd I have perl do the dirty work. 455 ;# Using floats here may affect precision, but 456 ;# currently these bits aren't significant anyway 457 return $sign * ($i + $f/2**32); 458} 459