#!/usr/bin/perl -w # --*-perl-*- ;# ;# ntploopstat,v 3.1 1993/07/06 01:09:11 jbj Exp ;# ;# Poll NTP server using NTP mode 7 loopinfo request. ;# Log info and timestamp to file for processing by ntploopwatch. ;# ;# ;# Copyright (c) 1992 ;# Rainer Pruy Friedrich-Alexander Universitaet Erlangen-Nuernberg ;# ;################################################################# ;# ;# The format written to the logfile is the same as used by xntpd ;# for the loopstats file. ;# This script however allows to gather loop filter statistics from ;# remote servers where you do not have access to the loopstats logfile. ;# ;# Please note: Communication delays affect the accuracy of the ;# timestamps recorded. Effects from these delays will probably ;# not show up, as timestamps are recorded to the second only. ;# (Should have implemented &gettimeofday()..) ;# $0 =~ s!^.*/([^/]+)$!$1!; # beautify script name $ntpserver = 'localhost'; # default host to poll $delay = 60; # default sampling rate ;# keep it shorter than minpoll (=64) ;# to get all values require "ctime.pl"; ;# handle bug in early ctime distributions $ENV{'TZ'} = 'MET' unless defined($ENV{'TZ'}) || $] > 4.010; if (defined(@ctime'MoY)) { *MonthName = *ctime'MoY; } else { @MonthName = ('Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'); } ;# this routine can be redefined to point to syslog if necessary sub msg { return unless $verbose; print STDERR "$0: "; printf STDERR @_; } ;############################################################# ;# ;# process command line $usage = <<"E-O-S"; usage: $0 [-d] [-t] [-l ] [-v] [ntpserver] E-O-S while($_ = shift) { /^-v(\d*)$/ && ($verbose=($1 eq '') ? 1 : $1,1) && next; /^-d(\d*)$/ && do { ($1 ne '') && ($delay = $1,1) && next; @ARGV || die("$0: delay value missing after -d\n$usage"); $delay = shift; ($delay >= 0) || die("$0: bad delay value \"$delay\"\n$usage"); next; }; /^-l$/ && do { @ARGV || die("$0: logfile missing after -l\n$usage"); $logfile = shift; next; }; /^-t(\d*(\.\d*)?)$/ && do { ($1 ne '') && ($timeout = $1,1) && next; @ARGV || die("$0: timeout value missing after -t\n$usage\n"); $timeout = shift; ($timeout > 0) || die("$0: bad timeout value \"$timeout\"\n$usage"); next; }; /^-/ && die("$0: unknown option \"$_\"\n$usage"); ;# any other argument is server to poll $ntpserver = $_; last; } if (@ARGV) { warn("unexpected arguments: ".join(" ",@ARGV).".\n"); die("$0: too many servers specified\n$usage"); } ;# logfile defaults to include server name ;# The name of the current month is appended and ;# the file is opened and closed for each sample. ;# $logfile = "loopstats:$ntpserver." unless defined($logfile); $timeout = 12.0 unless defined($timeout); # wait $timeout seconds for reply $MAX_FAIL = 60; # give up after $MAX_FAIL failed polls $MJD_1970 = 40587; if (eval 'require "syscall.ph";') { if (defined(&SYS_gettimeofday)) { ;# assume standard ;# gettimeofday(struct timeval *tp,struct timezone *tzp) ;# syntax for gettimeofday syscall ;# tzp = NULL -> undef ;# tp = (long,long) eval 'sub time { local($tz) = pack("LL",0,0); (&msg("gettimeofday failed: $!\n"), return (time)) unless syscall(&SYS_gettimeofday,$tz,undef) == 0; local($s,$us) = unpack("LL",$tz); return $s + $us/1000000; }'; local($t1,$t2,$t3); $t1 = time; eval '$t2 = &time;'; $t3 = time; die("$0: gettimeofday failed: $@.\n") if defined($@) && $@; die("$0: gettimeofday inconsistency time=$t1,gettimeofday=$t2,time=$t2\n") if (int($t1) != int($t2) && int($t3) != int($t2)); &msg("Using gettimeofday for timestamps\n"); } else { warn("No gettimeofday syscall found - using time builtin for timestamps\n"); eval 'sub time { return time; }'; } } else { warn("No syscall.ph file found - using time builtin for timestamps\n"); eval 'sub time { return time; }'; } ;#------------------+ ;# from ntp_request.h ;#------------------+ ;# NTP mode 7 packet format: ;# Byte 1: ResponseBit MoreBit Version(3bit) Mode(3bit)==7 ;# Byte 2: AuthBit Sequence # - 0 - 127 see MoreBit ;# Byte 3: Implementation # ;# Byte 4: Request Code ;# ;# Short 1: Err(3bit) NumItems(12bit) ;# Short 2: MBZ(3bit)=0 DataItemSize(12bit) ;# 0 - 500 byte Data ;# if AuthBit is set: ;# Long: KeyId ;# 2xLong: AuthCode ;# $IMPL_XNTPD = 2; $REQ_LOOP_INFO = 8; ;# request packet for REQ_LOOP_INFO: ;# B1: RB=0 MB=0 V=2 M=7 ;# B2: S# = 0 ;# B3: I# = IMPL_XNTPD ;# B4: RC = REQ_LOOP_INFO ;# S1: E=0 NI=0 ;# S2: MBZ=0 DIS=0 ;# data: 32 byte 0 padding ;# 8byte timestamp if encryption, 0 padding otherwise $loopinfo_reqpkt = pack("CCCC nn x32 x8", 0x17, 0, $IMPL_XNTPD, $REQ_LOOP_INFO, 0, 0); ;# ignore any auth data in packets $loopinfo_response_size = 1+1+1+1+2+2 # header size like request pkt + 8 # l_fp last_offset + 8 # l_fp drift_comp + 4 # u_long compliance + 4 # u_long watchdog_timer ; $loopinfo_response_fmt = "C4n2N2N2NN"; $loopinfo_response_fmt_v2 = "C4n2N2N2N2N"; ;# ;# prepare connection to server ;# ;# workaround for broken socket.ph on dynix_ptx eval 'sub INTEL {1;}' unless defined(&INTEL); eval 'sub ATT {1;}' unless defined(&ATT); require "sys/socket.ph"; require 'netinet/in.ph'; ;# if you do not have netinet/in.ph enable the following lines ;#eval 'sub INADDR_ANY { 0x00000000; }' unless defined(&INADDR_ANY); ;#eval 'sub IPPRORO_UDP { 17; }' unless defined(&IPPROTO_UDP); if ($ntpserver =~ /^((0x?)?\w+)\.((0x?)?\w+)\.((0x?)?\w+)\.((0x?)?\w+)$/) { local($a,$b,$c,$d) = ($1,$3,$5,$7); $a = oct($a) if defined($2); $b = oct($b) if defined($4); $c = oct($c) if defined($6); $d = oct($d) if defined($8); $server_addr = pack("C4", $a,$b,$c,$d); $server_mainname = (gethostbyaddr($server_addr,&AF_INET))[$[] || $ntpserver; } else { ($server_mainname,$server_addr) = (gethostbyname($ntpserver))[$[,$[+4]; die("$0: host \"$ntpserver\" is unknown\n") unless defined($server_addr); } &msg ("Address of server \"$ntpserver\" is \"%d.%d.%d.%d\"\n", unpack("C4",$server_addr)); $proto_udp = (getprotobyname('udp'))[$[+2] || &IPPROTO_UDP; $ntp_port = (getservbyname('ntp','udp'))[$[+2] || (warn "Could not get port number for service \"ntp/udp\" using 123\n"), ($ntp_port=123); ;# 0 && &SOCK_DGRAM; # satisfy perl -w ... socket(S, &AF_INET, &SOCK_DGRAM, $proto_udp) || die("Cannot open socket: $!\n"); bind(S, pack("S n N x8", &AF_INET, 0, &INADDR_ANY)) || die("Cannot bind: $!\n"); ($my_port, $my_addr) = (unpack("S n a4 x8",getsockname(S)))[$[+1,$[+2]; &msg("Listening at address %d.%d.%d.%d port %d\n", unpack("C4",$my_addr), $my_port); $server_inaddr = pack("Sna4x8", &AF_INET, $ntp_port, $server_addr); ;############################################################ ;# ;# the main loop: ;# send request ;# get reply ;# wait til next sample time undef($lasttime); $lostpacket = 0; while(1) { $stime = &time; &msg("Sending request $stime...\n"); $ret = send(S,$loopinfo_reqpkt,0,$server_inaddr); if (! defined($ret) || $ret < length($loopinfo_reqpkt)) { warn("$0: send failed ret=($ret): $!\n"); $fail++; next; } &msg("Waiting for reply...\n"); $mask = ""; vec($mask,fileno(S),1) = 1; $ret = select($mask,undef,undef,$timeout); if (! defined($ret)) { warn("$0: select failed: $!\n"); $fail++; next; } elsif ($ret == 0) { warn("$0: request to $ntpserver timed out ($timeout seconds)\n"); ;# do not count this event as failure ;# it usually this happens due to dropped udp packets on noisy and ;# havily loaded lines, so just try again; $lostpacket = 1; next; } &msg("Receiving reply...\n"); $len = 520; # max size of a mode 7 packet $reply = ""; # just make it defined for -w $ret = recv(S,$reply,$len,0); if (!defined($ret)) { warn("$0: recv failed: $!\n"); $fail++; next; } $etime = &time; &msg("Received at\t$etime\n"); ;#$time = ($stime + $etime) / 2; # symmetric delay assumed $time = $etime; # the above assumption breaks for X25 ;# so taking etime makes timestamps be a ;# little late, but keeps them increasing ;# monotonously &msg(sprintf("Reply from %d.%d.%d.%d took %f seconds\n", (unpack("SnC4",$ret))[$[+2 .. $[+5], ($etime - $stime))); if ($len < $loopinfo_response_size) { warn("$0: short packet ($len bytes) received ($loopinfo_response_size bytes expected\n"); $fail++; next; } ($b1,$b2,$b3,$b4,$s1,$s2, $offset_i,$offset_f,$drift_i,$drift_f,$compl,$watchdog) = unpack($loopinfo_response_fmt,$reply); ;# check reply if (($s1 >> 12) != 0) # error ! { die("$0: got error reply ".($s1>>12)."\n"); } if (($b1 != 0x97 && $b1 != 0x9f) || # Reply NotMore V=2 M=7 ($b2 != 0 && $b2 != 0x80) || # S=0 Auth no/yes $b3 != $IMPL_XNTPD || # ! IMPL_XNTPD $b4 != $REQ_LOOP_INFO || # Ehh.. not loopinfo reply ? $s1 != 1 || # ???? ($s2 != 24 && $s2 != 28) # ) { warn("$0: Bad/unexpected reply from server:\n"); warn(" \"".unpack("H*",$reply)."\"\n"); warn(" ".sprintf("b1=%x b2=%x b3=%x b4=%x s1=%d s2=%d\n", $b1,$b2,$b3,$b4,$s1,$s2)); $fail++; next; } elsif ($s2 == 28) { ;# seems to be a version 2 xntpd ($b1,$b2,$b3,$b4,$s1,$s2, $offset_i,$offset_f,$drift_i,$drift_f,$compl_i,$compl_f,$watchdog) = unpack($loopinfo_response_fmt_v2,$reply); $compl = &lfptoa($compl_i, $compl_f); } $time -= $watchdog; $offset = &lfptoa($offset_i, $offset_f); $drift = &lfptoa($drift_i, $drift_f); &log($time,$offset,$drift,$compl) && ($fail = 0);; } continue { die("$0: Too many failures - terminating\n") if $fail > $MAX_FAIL; &msg("Sleeping " . ($lostpacket ? ($delay / 2) : $delay) . " seconds...\n"); sleep($lostpacket ? ($delay / 2) : $delay); $lostpacket = 0; } sub log { local($time,$offs,$freq,$cmpl) = @_; local($y,$m,$d); local($fname,$suff) = ($logfile); ;# silently drop sample if distance to last sample is too low if (defined($lasttime) && ($lasttime + 2) >= $time) { &msg("Dropped packet - old sample\n"); return 1; } ;# $suff determines which samples end up in the same file ;# could have used $year (;-) or WeekOfYear, DayOfYear,.... ;# Change it to your suit... ($d,$m,$y) = (localtime($time))[$[+3 .. $[+5]; $suff = sprintf("%04d%02d%02d",$y+1900,$m+1,$d); $fname .= $suff; if (!open(LOG,">>$fname")) { warn("$0: open($fname) failed: $!\n"); $fail++; return 0; } else { ;# file format ;# MJD seconds offset drift compliance printf LOG ("%d %.3lf %.8lf %.7lf %d\n", int($time/86400)+$MJD_1970, $time - int($time/86400) * 86400, $offs,$freq,$cmpl); close(LOG); $lasttime = $time; } return 1; } ;# see ntp_fp.h to understand this sub lfptoa { local($i,$f) = @_; local($sign) = 1; if ($i & 0x80000000) { if ($f == 0) { $i = -$i; } else { $f = -$f; $i = ~$i; $i += 1; # 2s complement } $sign = -1; ;#print "NEG: $i $f\n"; } else { ;#print "POS: $i $f\n"; } ;# unlike xntpd I have perl do the dirty work. ;# Using floats here may affect precision, but ;# currently these bits aren't significant anyway return $sign * ($i + $f/2**32); }