/contrib/ntp/scripts/monitoring/ntploopstat

https://bitbucket.org/freebsd/freebsd-head/ · Perl · 458 lines · 387 code · 69 blank · 2 comment · 61 complexity · cc0f57c03a5094566d1ee02bf9c0f480 MD5 · raw file

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