4 ;# ntploopstat,v 3.1 1993/07/06 01:09:11 jbj Exp
6 ;# Poll NTP server using NTP mode 7 loopinfo request.
7 ;# Log info and timestamp to file for processing by ntploopwatch.
11 ;# Rainer Pruy Friedrich-Alexander Universitaet Erlangen-Nuernberg
13 ;#################################################################
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.
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()..)
26 $0 =~ s!^.*/([^/]+)$!$1!; # beautify script name
28 $ntpserver = 'localhost'; # default host to poll
29 $delay = 60; # default sampling rate
30 ;# keep it shorter than minpoll (=64)
34 ;# handle bug in early ctime distributions
35 $ENV{'TZ'} = 'MET' unless defined($ENV{'TZ'}) || $] > 4.010;
37 if (defined(@ctime'MoY))
39 *MonthName = *ctime'MoY
;
43 @MonthName = ('Jan','Feb','Mar','Apr','May','Jun',
44 'Jul','Aug','Sep','Oct','Nov','Dec');
47 ;# this routine can be redefined to point to syslog if necessary
50 return unless $verbose;
56 ;#############################################################
58 ;# process command line
62 $0 [-d<delay>] [-t<timeout>] [-l <logfile>] [-v] [ntpserver]
67 /^-v(\d*)$/ && ($verbose=($1 eq '') ?
1 : $1,1) && next;
70 ($1 ne '') && ($delay = $1,1) && next;
71 @ARGV || die("$0: delay value missing after -d\n$usage");
73 ($delay >= 0) || die("$0: bad delay value \"$delay\"\n$usage");
78 @ARGV || die("$0: logfile missing after -l\n$usage");
82 /^-t(\d*(\.\d*)?)$/ &&
84 ($1 ne '') && ($timeout = $1,1) && next;
85 @ARGV || die("$0: timeout value missing after -t\n$usage\n");
88 die("$0: bad timeout value \"$timeout\"\n$usage");
92 /^-/ && die("$0: unknown option \"$_\"\n$usage");
94 ;# any other argument is server to poll
101 warn("unexpected arguments: ".join(" ",@ARGV).".\n");
102 die("$0: too many servers specified\n$usage");
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.
109 $logfile = "loopstats:$ntpserver." unless defined($logfile);
110 $timeout = 12.0 unless defined($timeout); # wait $timeout seconds for reply
112 $MAX_FAIL = 60; # give up after $MAX_FAIL failed polls
117 if (eval 'require "syscall.ph";')
119 if (defined(&SYS_gettimeofday
))
122 ;# gettimeofday(struct timeval *tp,struct timezone *tzp)
123 ;# syntax for gettimeofday syscall
124 ;# tzp = NULL -> undef
126 eval 'sub time { local($tz) = pack("LL",0,0);
127 (&msg("gettimeofday failed: $!\n"),
129 unless syscall(&SYS_gettimeofday,$tz,undef) == 0;
130 local($s,$us) = unpack("LL",$tz);
131 return $s + $us/1000000; }';
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");
143 warn("No gettimeofday syscall found - using time builtin for timestamps\n");
144 eval 'sub time { return time; }';
149 warn("No syscall.ph file found - using time builtin for timestamps\n");
150 eval 'sub time { return time; }';
154 ;#------------------+
155 ;# from ntp_request.h
156 ;#------------------+
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
164 ;# Short 1: Err(3bit) NumItems(12bit)
165 ;# Short 2: MBZ(3bit)=0 DataItemSize(12bit)
167 ;# if AuthBit is set:
176 ;# request packet for REQ_LOOP_INFO:
177 ;# B1: RB=0 MB=0 V=2 M=7
179 ;# B3: I# = IMPL_XNTPD
180 ;# B4: RC = REQ_LOOP_INFO
183 ;# data: 32 byte 0 padding
184 ;# 8byte timestamp if encryption, 0 padding otherwise
186 pack("CCCC nn x32 x8", 0x17, 0, $IMPL_XNTPD, $REQ_LOOP_INFO, 0, 0);
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
196 $loopinfo_response_fmt = "C4n2N2N2NN";
197 $loopinfo_response_fmt_v2 = "C4n2N2N2N2N";
200 ;# prepare connection to server
203 ;# workaround for broken socket.ph on dynix_ptx
204 eval 'sub INTEL {1;}' unless defined(&INTEL
);
205 eval 'sub ATT {1;}' unless defined(&ATT
);
207 require "sys/socket.ph";
209 require 'netinet/in.ph';
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);
215 if ($ntpserver =~ /^((0x?)?\w+)\.((0x?)?\w+)\.((0x?)?\w+)\.((0x?)?\w+)$/)
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);
225 = (gethostbyaddr($server_addr,&AF_INET
))[$[] || $ntpserver;
229 ($server_mainname,$server_addr)
230 = (gethostbyname($ntpserver))[$[,$[+4];
232 die("$0: host \"$ntpserver\" is unknown\n")
233 unless defined($server_addr);
235 &msg
("Address of server \"$ntpserver\" is \"%d.%d.%d.%d\"\n",
236 unpack("C4",$server_addr));
238 $proto_udp = (getprotobyname('udp'))[$[+2] || &IPPROTO_UDP
;
241 (getservbyname('ntp','udp'))[$[+2] ||
242 (warn "Could not get port number for service \"ntp/udp\" using 123\n"),
246 0 && &SOCK_DGRAM
; # satisfy perl -w ...
247 socket(S
, &AF_INET
, &SOCK_DGRAM
, $proto_udp) ||
248 die("Cannot open socket: $!\n");
250 bind(S
, pack("S n N x8", &AF_INET
, 0, &INADDR_ANY
)) ||
251 die("Cannot bind: $!\n");
253 ($my_port, $my_addr) = (unpack("S n a4 x8",getsockname(S
)))[$[+1,$[+2];
255 &msg
("Listening at address %d.%d.%d.%d port %d\n",
256 unpack("C4",$my_addr), $my_port);
258 $server_inaddr = pack("Sna4x8", &AF_INET
, $ntp_port, $server_addr);
260 ;############################################################
265 ;# wait til next sample time
274 &msg
("Sending request $stime...\n");
276 $ret = send(S
,$loopinfo_reqpkt,0,$server_inaddr);
278 if (! defined($ret) || $ret < length($loopinfo_reqpkt))
280 warn("$0: send failed ret=($ret): $!\n");
285 &msg
("Waiting for reply...\n");
287 $mask = ""; vec($mask,fileno(S
),1) = 1;
288 $ret = select($mask,undef,undef,$timeout);
292 warn("$0: select failed: $!\n");
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;
306 &msg
("Receiving reply...\n");
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);
314 warn("$0: recv failed: $!\n");
320 &msg
("Received at\t$etime\n");
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
328 &msg
(sprintf("Reply from %d.%d.%d.%d took %f seconds\n",
329 (unpack("SnC4",$ret))[$[+2 .. $[+5], ($etime - $stime)));
331 if ($len < $loopinfo_response_size)
333 warn("$0: short packet ($len bytes) received ($loopinfo_response_size bytes expected\n");
338 ($b1,$b2,$b3,$b4,$s1,$s2,
339 $offset_i,$offset_f,$drift_i,$drift_f,$compl,$watchdog)
340 = unpack($loopinfo_response_fmt,$reply);
343 if (($s1 >> 12) != 0) # error !
345 die("$0: got error reply ".($s1>>12)."\n");
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 ?
352 ($s2 != 24 && $s2 != 28) #
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));
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);
373 $offset = &lfptoa
($offset_i, $offset_f);
374 $drift = &lfptoa
($drift_i, $drift_f);
376 &log($time,$offset,$drift,$compl) && ($fail = 0);;
380 die("$0: Too many failures - terminating\n") if $fail > $MAX_FAIL;
381 &msg
("Sleeping " . ($lostpacket ?
($delay / 2) : $delay) . " seconds...\n");
383 sleep($lostpacket ?
($delay / 2) : $delay);
389 local($time,$offs,$freq,$cmpl) = @_;
391 local($fname,$suff) = ($logfile);
394 ;# silently drop sample if distance to last sample is too low
395 if (defined($lasttime) && ($lasttime + 2) >= $time)
397 &msg
("Dropped packet - old sample\n");
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...
405 ($d,$m,$y) = (localtime($time))[$[+3 .. $[+5];
406 $suff = sprintf("%04d%02d%02d",$y+1900,$m+1,$d);
408 if (!open(LOG
,">>$fname"))
410 warn("$0: open($fname) failed: $!\n");
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,
428 ;# see ntp_fp.h to understand this
445 $i += 1; # 2s complement
448 ;#print "NEG: $i $f\n";
452 ;#print "POS: $i $f\n";
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);