Annotation of embedaddon/ntp/scripts/monitoring/ntploopstat, revision 1.1

1.1     ! misho       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: 
        !            33: require "ctime.pl";
        !            34: ;# handle bug in early ctime distributions
        !            35: $ENV{'TZ'} = 'MET' unless defined($ENV{'TZ'}) || $] > 4.010;
        !            36: 
        !            37: if (defined(@ctime'MoY))
        !            38: {
        !            39:     *MonthName = *ctime'MoY;
        !            40: }
        !            41: else
        !            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
        !            48: sub 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: 
        !            61: usage:
        !            62:   $0 [-d<delay>] [-t<timeout>] [-l <logfile>] [-v] [ntpserver]
        !            63: E-O-S
        !            64: 
        !            65: while($_ = 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: 
        !            99: if (@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: 
        !           117: if (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: }
        !           147: else
        !           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
        !           204: eval 'sub INTEL {1;}' unless defined(&INTEL);
        !           205: eval 'sub ATT {1;}'  unless defined(&ATT);
        !           206: 
        !           207: require "sys/socket.ph";
        !           208: 
        !           209: require '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: 
        !           215: if ($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: }
        !           227: else
        !           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: ;# 
        !           246: 0 && &SOCK_DGRAM;              # satisfy perl -w ...
        !           247: socket(S, &AF_INET, &SOCK_DGRAM, $proto_udp) ||
        !           248:     die("Cannot open socket: $!\n");
        !           249: 
        !           250: bind(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: 
        !           267: undef($lasttime);
        !           268: $lostpacket = 0;
        !           269: 
        !           270: while(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: }
        !           378: continue
        !           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: 
        !           387: sub 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
        !           429: sub 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: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>