Version 14 of Network Time Protocol - NTP

Updated 2002-05-26 00:38:51

Purpose - to discuss what NTP is, and to discuss coding issues relating to this protocol.

[hopefully one of my comrades in crime will fill in the blanks here as to RFC number, point to standard implementations of say an ntp daemon, etc.]

[Here's a start:-

Firstly, the protocol implemented in the code below is the "daytime" protocol, not NTP. The daytime protocol is described in RFC867 [L1 ]. Also likely to be of interest is the "time" protocol RFC 868 [L2 ] describes.

NTP is a far more sophisticated protocol created by Dave Mills. Version 3 of NTP is described in RFC1305 [L3 ]. More information including links to Dave Mills' ntpd implementation is available at http://www.ntp.org

Documentation and other links are at http://www.eecis.udel.edu/~ntp/documentation.html

NTP is implemented via UDP over port 123, and can operate in broadcast and multicast modes, or by direct queries. NTP can synchronise to multiple time sources, making allowances for the network delays and apparent reliability of the clocks it synchronises to.

(The assigned ports list includes 123/tcp as NTP as well as 123/udp but the tcp port is not used by ntpd)

Another good reason that UDP should be built into Tcl (like TCP is)...

]

The following code was recently posted to comp.lang.tcl :

 ##
 ## good-time - for the correct time, ...
 ##
 ## Placed into the public domain by [email protected], January 1999.
 ##
 ## usage: good-time [host [port [timezone]]]
 ##     host - defaults to time.nist.gov.
 ##     port - defaults to 13.
 ##     timezone - defaults to nothing, not necessary for NIST format.
 ##
 ## After wading through a complicated linux time synchronization
 ## package, it turned out that the key was to telnet to time.nist.gov
 ## on port 13.  You can do that in tcl, so here it is.
 ##
 ## It also turns out that a lot of machines return some kind of time
 ## service on port 13, so I recognize a few variations in the format
 ## returned.
 ##
 ## See http://www.boulder.nist.gov/timefreq/ for more information and
 ## links to yet more information.
 ##
 # 23Jan02RT - add -set flag
 set doset 0
 if {$argc >= 1 && [string equal [lindex $argv 0] -set]} {
        incr argc -1
        set argv [lrange $argv 1 end]
        set doset 1
 }

 ##
 ## choose the host
 ##
        # 11Jan02RT TODO use host list
        # numeric values come from "Atomic Clock Sync v2.5" server menu
 set hlist {
        129.6.15.28         
        129.6.15.29         
        132.163.4.101       
        132.163.4.102       
        132.163.4.103       
        128.138.140.44      
        time-nw.nist.gov
        time-a.timefreq.bldrdoc.gov
        time.nist.gov
        allhoststried
 }
 # set host time.nist.gov
 if {$argc > 0} {
     set hlist [concat[lindex $argv 0] $hlist]
 }

 ##
 ## choose the port
 ##
 set port 13
 if {$argc > 1} {
     set port [lindex $argv 1];
 }

 ##
 ## choose the timezone
 ##
 set timezone {}
 if {$argc > 2} {
     set timezone [lindex $argv 2];
 }

 ##
 ## set a time out
 ##
 after [expr 30*1000] {
     close $sock
     puts stderr "timed out";
     if {[info exists lines]} {
        puts stderr "no time found in:\n$lines"
     }
     exit 1;
 }

 ##
 ## open a socket
 ##
 foreach h $hlist {
        if {[catch {socket $h $port} sock]} {
                #puts stderr "socket: $sock"
                #exit 2;
                puts stderr "host $h - FAILED"
                continue
        }
        break
 }

 ##
 ## read what comes back
 ##
 while {[gets $sock line] >= 0} {

     #
     # typical daytime response is unix date format without timezone indication
     #
     #  Mon Sep 16 21:59:22 1996
     #
     set seconds 0
     if {[regexp \
        {^... ([A-Z][a-z][a-z]) ([ \d]\d) (\d\d:\d\d:\d\d) (\d\d\d\d)$} \
         $line all month mday time year]} {
        set seconds [clock scan "$month $mday, $year $time $timezone"]
     }

     #
     # NIST time service format returned by:
     #
     #  time.nist.gov
     #  time-nw.nist.gov
     #  time-a.timefreq.bldrdoc.gov
     #
     # 50343 96-09-17 05:02:07 50 0 0  50.0 UTC(NIST) * 
     #
     if {!$seconds &&
       [regexp \
      {^..... (\d\d)-(\d\d)-(\d\d) (\d\d:\d\d:\d\d) .. . . ..... UTC\(NIST\)} \
      $line all year month day time]} {
        set seconds [clock scan "$month/$day/$year $time UTC"]
     }

     #
     # USNO master clock format returned by:
     #
     #  tick.usno.navy.mil
     #  tock.usno.navy.mil
     #
     # 50343 261 044932 UTC
     #
     if {!$seconds && [regexp {^..... ([ \d][ \d]\d) (\d\d)(\d\d)(\d\d) UTC$} \
             $line all doy hour minute second]} {
        set seconds [clock scan $hour:$minute:$second -gmt true]
     }
     if {$seconds} {
        puts "FETCHED: [clock format $seconds]"
        puts "  LOCAL: [clock format [clock seconds]]"
        if {$doset} {
            set newtime [clock format $seconds -format %H%M.%S]
            exec date.exe $newtime
            puts "set date to $newtime"
        }
        close $sock;
        exit 0;
    }

     #
     # save unrecognized stuff for final error message
     #
     append lines $line\n
 }

 ##
 ## give up
 ##
 close $sock;
 puts stderr "no time found in:\n$lines";
 exit 4

TIME protocol client

See RFC 868 [L4 ] for the background. The client connects to a TIME server on port 37, the server returns a 32-bit unsigned integer of seconds since 1900-01-01 00:00.00 UTC. The protocol takes no account of network delays, the time you get is the time when sent, in seconds - but this is good enough to keep the average desktop machine's clock up to date (and don't ask me how you do that in Tcl).

This little client proc adjusts the return value to the Tcl [clock seconds] standard, by subtracting the number of seconds between 1900 (TIME epoch) and 1970 (Tcl epoch). The arithmetic plays horrible games with Tcl's 32-bit signed integers, but works nonetheless.

From the UK at least, ntp1.fau.de makes a good TIME server.

 proc TIMEseconds {host} {
     set c -2208988800        ;# epoch-shifter: 1970-1900 in seconds
     set s [socket $host 37]  ;# 37 is TIME
     fconfigure $s -translation binary
     set r [read $s]
     close $s
     for {set t 0;set i 0} {$i<4} {incr i} {
         set t [expr {($t<<8)|[scan [string index $r $i] %c]}]
     }
     incr t $c
 }

 puts [clock format [TIMEseconds ntp1.fau.de]]

Bob Clark


Category Acronym Category Internet Category Application