@ -1,15 +1,17 @@
// Copyright 2015 Brett Vickers.
// Copyright 2015 -2017 Brett Vickers.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ntp provides a simple mechanism for querying the current time from
// a remote NTP server. See RFC 5905. Approach inspired by go-nuts post by
// Michael Hofmann:
// Package ntp provides a n implementation of a Simple NTP (SNTP) client
// capable of querying the current time from a remote NTP server. See
// RFC5905 (https://tools.ietf.org/html/rfc5905) for more details.
//
// This approach grew out of a go-nuts post by Michael Hofmann:
// https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/FlcdMU5fkLQ
package ntp
import (
"crypto/rand"
"encoding/binary"
"errors"
"net"
@ -18,57 +20,55 @@ import (
"golang.org/x/net/ipv4"
)
type mode uint8
const (
reserved mode = 0 + iota
symmetricActive
symmetricPassive
client
server
broadcast
controlMessage
reservedPrivate
)
// The LeapIndicator is used to warn if a leap second should be inserted
// or deleted in the last minute of the current month.
type LeapIndicator uint8
const (
// LeapNoWarning indicates no impending leap second
// LeapNoWarning indicates no impending leap second.
LeapNoWarning LeapIndicator = 0
// LeapAddSecond indicates the last minute of the day has 61 seconds
// LeapAddSecond indicates the last minute of the day has 61 seconds.
LeapAddSecond = 1
// LeapDelSecond indicates the last minute of the day has 59 seconds
// LeapDelSecond indicates the last minute of the day has 59 seconds.
LeapDelSecond = 2
// LeapNotInSync indicates an unsynchronized leap second.
LeapNotInSync = 3
)
// Internal constants
const (
// MaxStratum is the largest allowable NTP stratum value
MaxStratum = 16
nanoPerSec = 1000000000
defaultNtpVersion = 4
maxPoll = 17 // log2 max poll interval (~36 h)
maxDispersion = 16 // aka MAXDISP
nanoPerSec = 1000000000
maxStratum = 16
defaultTimeout = 5 * time . Second
maxPollInterval = ( 1 << 17 ) * time . Second
maxDispersion = 16 * time . Second
)
// Internal variables
var (
defaultTimeout = 5 * time . Second
ntpEpoch = time . Date ( 1900 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC )
)
type mode uint8
// NTP modes. This package uses only client mode.
const (
reserved mode = 0 + iota
symmetricActive
symmetricPassive
client
server
broadcast
controlMessage
reservedPrivate
)
// An ntpTime is a 64-bit fixed-point (Q32.32) representation of the number of
// seconds elapsed since the NTP epoch.
// seconds elapsed .
type ntpTime uint64
// Duration interprets the fixed-point ntpTime as a number of elapsed seconds
@ -79,7 +79,7 @@ func (t ntpTime) Duration() time.Duration {
return time . Duration ( sec + frac )
}
// Time interprets the fixed-point ntpTime as a a n absolute time and returns
// Time interprets the fixed-point ntpTime as a n absolute time and returns
// the corresponding time.Time value.
func ( t ntpTime ) Time ( ) time . Time {
return ntpEpoch . Add ( t . Duration ( ) )
@ -90,12 +90,15 @@ func (t ntpTime) Time() time.Time {
func toNtpTime ( t time . Time ) ntpTime {
nsec := uint64 ( t . Sub ( ntpEpoch ) )
sec := nsec / nanoPerSec
frac := ( nsec - sec * nanoPerSec ) << 32 / nanoPerSec
// Round up the fractional component so that repeated conversions
// between time.Time and ntpTime do not yield continually decreasing
// results.
frac := ( ( ( nsec - sec * nanoPerSec ) << 32 ) + nanoPerSec - 1 ) / nanoPerSec
return ntpTime ( sec << 32 | frac )
}
// An ntpTimeShort is a 32-bit fixed-point (Q16.16) representation of the
// number of seconds elapsed since the NTP epoch .
// number of seconds elapsed .
type ntpTimeShort uint32
// Duration interprets the fixed-point ntpTimeShort as a number of elapsed
@ -132,134 +135,151 @@ func (m *msg) setMode(md mode) {
m . LiVnMode = ( m . LiVnMode & 0xf8 ) | uint8 ( md )
}
// setLeap Indicator modifies the leap indicator on the message.
func ( m * msg ) setLeap Indicator ( li LeapIndicator ) {
// setLeap modifies the leap indicator on the message.
func ( m * msg ) setLeap ( li LeapIndicator ) {
m . LiVnMode = ( m . LiVnMode & 0x3f ) | uint8 ( li ) << 6
}
// getLeapIndicator returns the leap indicator on the message.
func ( m * msg ) getLeapIndicator ( ) LeapIndicator {
// getVersion returns the version value in the message.
func ( m * msg ) getVersion ( ) int {
return int ( ( m . LiVnMode >> 3 ) & 0x07 )
}
// getMode returns the mode value in the message.
func ( m * msg ) getMode ( ) mode {
return mode ( m . LiVnMode & 0x07 )
}
// getLeap returns the leap indicator on the message.
func ( m * msg ) getLeap ( ) LeapIndicator {
return LeapIndicator ( ( m . LiVnMode >> 6 ) & 0x03 )
}
// QueryOptions contains the list of configurable options that may be used with
// the QueryWithOptions function.
// QueryOptions contains the list of configurable options that may be used
// with the QueryWithOptions function.
type QueryOptions struct {
Timeout time . Duration // defaults to 5 seconds
Version int // NTP protocol version, defaults to 4
Port int // NTP Server port for UDPAddr.Port, defaults to 123
TTL int // IP TTL to use for outgoing UDP packets, defaults to system default
Timeout time . Duration // defaults to 5 seconds
Version int // NTP protocol version, defaults to 4
LocalAddress string // IP address to use for the client address
Port int // Server port, defaults to 123
TTL int // IP TTL to use, defaults to system default
}
// A Response contains time data, some of which is returned by the NTP server
// and some of which is calculated by the client.
type Response struct {
Time time . Time // receive time reported by the server
RTT time . Duration // round-trip time between client and server
ClockOffset time . Duration // local clock offset relative to server
Poll time . Duration // maximum polling interval
Precision time . Duration // precision of server's system clock
Stratum uint8 // stratum level of NTP server's clock
ReferenceID uint32 // server's reference ID
ReferenceTime time . Time // server's time of last clock update
RootDelay time . Duration // server's RTT to the reference clock
RootDispersion time . Duration // server's dispersion to the reference clock
Leap LeapIndicator // server's leap second indicator; see RFC 5905
// RootDistance is the single-packet estimate of the root synchronization
// distance. Some SNTP clients limit-check this value before using the
// response. For example, systemd-timesyncd uses 5.0s as an upper bound. See
// https://tools.ietf.org/html/rfc5905#appendix-A.5.5.2
// Time is the transmit time reported by the server just before it
// responded to the client's NTP query.
Time time . Time
// ClockOffset is the estimated offset of the client clock relative to
// the server. Add this to the client's system clock time to obtain a
// more accurate time.
ClockOffset time . Duration
// RTT is the measured round-trip-time delay estimate between the client
// and the server.
RTT time . Duration
// Precision is the reported precision of the server's clock.
Precision time . Duration
// Stratum is the "stratum level" of the server. The smaller the number,
// the closer the server is to the reference clock. Stratum 1 servers are
// attached directly to the reference clock. A stratum value of 0
// indicates the "kiss of death," which typically occurs when the client
// issues too many requests to the server in a short period of time.
Stratum uint8
// ReferenceID is a 32-bit identifier identifying the server or
// reference clock.
ReferenceID uint32
// ReferenceTime is the time when the server's system clock was last
// set or corrected.
ReferenceTime time . Time
// RootDelay is the server's estimated aggregate round-trip-time delay to
// the stratum 1 server.
RootDelay time . Duration
// RootDispersion is the server's estimated maximum measurement error
// relative to the stratum 1 server.
RootDispersion time . Duration
// RootDistance is an estimate of the total synchronization distance
// between the client and the stratum 1 server.
RootDistance time . Duration
// CausalityViolation is a time duration representing the amount of
// causality violation between two sets of timestamps. It may be used as a
// lower bound on current time synchronization error betwen local and NTP
// clock. A leap second may contribute as much as 1 second of causality violation.
CausalityViolation time . Duration
// Leap indicates whether a leap second should be added or removed from
// the current month's last minute.
Leap LeapIndicator
// MinError is a lower bound on the error between the client and server
// clocks. When the client and server are not synchronized to the same
// clock, the reported timestamps may appear to violate the principle of
// causality. In other words, the NTP server's response may indicate
// that a message was received before it was sent. In such cases, the
// minimum error may be useful.
MinError time . Duration
// Poll is the maximum interval between successive NTP polling messages.
// It is not relevant for simple NTP clients like this one.
Poll time . Duration
}
// Validate checks if the response is valid for the purposes of time
// synchronization.
func ( r * Response ) Validate ( ) bool {
// Reference Timestamp: Time when the system clock was last set or
// corrected. Semantics of this value seems to vary across NTP server
// implementations: it may be both NTP-clock time and system wall-clock
// time of this event. :-( So (T3 - ReferenceTime) is not true
// "freshness" as it may be actually NEGATIVE sometimes.
func ( r * Response ) Validate ( ) error {
// Handle invalid stratum values.
if r . Stratum == 0 {
return errors . New ( "kiss of death received" )
}
if r . Stratum >= maxStratum {
return errors . New ( "invalid stratum in response" )
}
// Handle invalid leap second indicator.
if r . Leap == LeapNotInSync {
return errors . New ( "invalid leap second" )
}
// Estimate the "freshness" of the time. If it exceeds the maximum
// polling interval (~36 hours), then it cannot be considered "fresh".
freshness := r . Time . Sub ( r . ReferenceTime )
if freshness > maxPollInterval {
return errors . New ( "server clock not fresh" )
}
// (Lambda := RootDelay/2 + RootDispersion) check against MAXDISP (16s)
// is required as ntp.org ntpd may report sane other fields while
// giving quite erratic clock. The check is declared in packet() at
// Calculate the peer synchronization distance, lambda:
// lambda := RootDelay/2 + RootDispersion
// If this value exceeds MAXDISP (16s), then the time is not suitable
// for synchronization purposes.
// https://tools.ietf.org/html/rfc5905#appendix-A.5.1.1.
lambda := r . RootDelay / 2 + r . RootDispersion
// `r.RTT > 0` check is not included as it does not depend on the
// packet itself, but also depends on clock _speed_. It's indicator
// that local clock run faster than remote one, so (T4-T1) < (T3-T2),
// but it may be local clock issue.
// E.g. T1/T2/T3/T4 = 0/10/20/1 leads to RTT = -9s.
return r . Leap != LeapNotInSync && // RFC5905, packet()
0 < r . Stratum && r . Stratum < MaxStratum && // RFC5905, packet()
lambda < maxDispersion * time . Second && // RFC5905, packet()
! r . Time . Before ( r . ReferenceTime ) && // RFC5905, packet(), reftime <= xmt ~~ !(xmt < reftime)
freshness <= ( 1 << maxPoll ) * time . Second && // ntpdate uses 24h as a heuristics instead of ~36h derived from MAXPOLL
ntpEpoch . Before ( r . Time ) && // sanity
ntpEpoch . Before ( r . ReferenceTime ) // sanity
}
func ( r * Response ) rootDistance ( ) time . Duration {
// RFC5905 suggests more strict check against _peer_ in fit(), that
// root_dist should be less than MAXDIST + PHI * LOG2D(s.poll).
// MAXPOLL is 17, so it is approximately at most (1s + 15e-6 * 2**17) =
// 2.96608 s, but MAXDIST and MAXPOLL are confugurable values in the
// reference implementation, so only MAXDISP check has hardcoded value
// in Validate().
//
// root_dist should also have following summands
// + Dispersion towards the peer
// + jitter of the link to the peer
// + PHI * (current_uptime - peer->uptime_of_last_update)
// but all these values are 0 if only single NTP packet was sent.
rtt := r . RTT
if rtt < 0 {
rtt = 0
if lambda > maxDispersion {
return errors . New ( "invalid dispersion" )
}
return ( rtt + r . RootDelay ) / 2 + r . RootDispersion
}
func ( r * Response ) causalityViolation ( ) time . Duration {
// SNTP query has four timestamps for consecutive events: T1, T2, T3
// and T4. T1 and T4 use local clock, T2 and T3 use NTP clock.
// RTT = (T4 - T1) - (T3 - T2) = T4 - T3 + T2 - T1
// Offset = (T2 + T3)/2 - (T4 + T1)/2 = (-T4 + T3 + T2 - T1) / 2
// => T2 - T1 = RTT/2 + Offset && T4 - T3 = RTT/2 - Offset
// If system wall-clock is synced to NTP-clock then T2 >= T1 && T4 >= T3.
// This check may be useful against chrony NTP daemon as it starts
// relaying sane NTP clock before system wall-clock is actually adjusted.
violation := r . RTT / 2
if r . ClockOffset > 0 {
violation -= r . ClockOffset
} else {
violation += r . ClockOffset
// If the server's transmit time is before its reference time, the
// response is invalid.
if r . Time . Before ( r . ReferenceTime ) {
return errors . New ( "invalid time reported" )
}
if violation < 0 {
return - violation
}
return time . Duration ( 0 )
// nil means the response is valid.
return nil
}
// Query returns the current time from the remote server host. It also returns
// additional information about the exchanged time information.
// Query returns a response from the remote NTP server host. It contains
// the time at which the server transmitted the response as well as other
// useful information about the time and the remote server.
func Query ( host string ) ( * Response , error ) {
return QueryWithOptions ( host , QueryOptions { } )
}
// QueryWithOptions returns the current time from the remote server host.
// It also returns additional information about the exchanged time
// information. It allows the specification of additional query options.
// QueryWithOptions performs the same function as Query but allows for the
// customization of several query options.
func QueryWithOptions ( host string , opt QueryOptions ) ( * Response , error ) {
m , now , err := getTime ( host , opt )
if err != nil {
@ -268,64 +288,71 @@ func QueryWithOptions(host string, opt QueryOptions) (*Response, error) {
return parseTime ( m , now ) , nil
}
// parseTime parses SNTP packet paired with the packet arrival time (dst) and
// returns Response having SNTP packet data converted to go types.
func parseTime ( m * msg , dst ntpTime ) * Response {
r := & Response {
Time : m . TransmitTime . Time ( ) ,
RTT : rtt ( m . OriginTime , m . ReceiveTime , m . TransmitTime , dst ) ,
ClockOffset : offset ( m . OriginTime , m . ReceiveTime , m . TransmitTime , dst ) ,
Poll : toInterval ( m . Poll ) ,
Precision : toInterval ( m . Precision ) ,
Stratum : m . Stratum ,
ReferenceID : m . ReferenceID ,
ReferenceTime : m . ReferenceTime . Time ( ) ,
RootDelay : m . RootDelay . Duration ( ) ,
RootDispersion : m . RootDispersion . Duration ( ) ,
Leap : m . getLeapIndicator ( ) ,
// TimeV returns the current time using information from a remote NTP server.
// On error, it returns the local system time. The version may be 2, 3, or 4.
//
// Deprecated: TimeV is deprecated. Use QueryWithOptions instead.
func TimeV ( host string , version int ) ( time . Time , error ) {
m , recvTime , err := getTime ( host , QueryOptions { Version : version } )
if err != nil {
return time . Now ( ) , err
}
// these are exported as values to preserve API style consistency
r . RootDistance = r . rootDistance ( )
r . CausalityViolation = r . causalityViolation ( )
// https://tools.ietf.org/html/rfc5905#section-7.3
if r . Stratum == 0 {
r . Stratum = MaxStratum
r := parseTime ( m , recvTime )
err = r . Validate ( )
if err != nil {
return time . Now ( ) , err
}
return r
// Use the clock offset to calculate the time.
return time . Now ( ) . Add ( r . ClockOffset ) , nil
}
// getTime returns SNTP packet & DestinationTime timestamp.
// Time returns the current time using information from a remote NTP server.
// It uses version 4 of the NTP protocol. On error, it returns the local
// system time.
func Time ( host string ) ( time . Time , error ) {
return TimeV ( host , defaultNtpVersion )
}
// getTime performs the NTP server query and returns the response message
// along with the local system time it was received.
func getTime ( host string , opt QueryOptions ) ( * msg , ntpTime , error ) {
if opt . Version == 0 {
opt . Version = defaultNtpVersion
}
if opt . Version < 2 || opt . Version > 4 {
panic ( "ntp: invalid version number" )
return nil , 0 , errors . New ( "invalid protocol version requested ")
}
if opt . Timeout == 0 {
opt . Timeout = defaultTimeout
}
raddr , err := net . ResolveUDPAddr ( "udp" , host + ":123" )
// Resolve the remote NTP server address.
raddr , err := net . ResolveUDPAddr ( "udp" , net . JoinHostPort ( host , "123" ) )
if err != nil {
return nil , 0 , err
}
// Resolve the local address if specified as an option.
var laddr * net . UDPAddr
if opt . LocalAddress != "" {
laddr , err = net . ResolveUDPAddr ( "udp" , net . JoinHostPort ( opt . LocalAddress , "0" ) )
if err != nil {
return nil , 0 , err
}
}
// Override the port if requested.
if opt . Port != 0 {
raddr . Port = opt . Port
}
con , err := net . DialUDP ( "udp" , nil , raddr )
// Prepare a "connection" to the remote server.
con , err := net . DialUDP ( "udp" , laddr , raddr )
if err != nil {
return nil , 0 , err
}
defer con . Close ( )
// Set a TTL for the packet if requested.
if opt . TTL != 0 {
ipcon := ipv4 . NewConn ( con )
err = ipcon . SetTTL ( opt . TTL )
@ -334,108 +361,166 @@ func getTime(host string, opt QueryOptions) (*msg, ntpTime, error) {
}
}
// Set a timeout on the connection.
if opt . Timeout == 0 {
opt . Timeout = defaultTimeout
}
con . SetDeadline ( time . Now ( ) . Add ( opt . Timeout ) )
m := new ( msg )
m . setMode ( client )
m . setVersion ( opt . Version )
m . setLeapIndicator ( LeapNotInSync )
xmtTime := time . Now ( )
xmt := toNtpTime ( xmtTime )
m . TransmitTime = xmt
// Allocate a message to hold the response.
recvMsg := new ( msg )
// Allocate a message to hold the query.
xmitMsg := new ( msg )
xmitMsg . setMode ( client )
xmitMsg . setVersion ( opt . Version )
xmitMsg . setLeap ( LeapNotInSync )
// To ensure privacy and prevent spoofing, try to use a random 64-bit
// value for the TransmitTime. If crypto/rand couldn't generate a
// random value, fall back to using the system clock. Keep track of
// when the messsage was actually sent.
r := make ( [ ] byte , 8 )
_ , err = rand . Read ( r )
var sendTime time . Time
if err == nil {
xmitMsg . TransmitTime = ntpTime ( binary . BigEndian . Uint64 ( r ) )
sendTime = time . Now ( )
} else {
sendTime = time . Now ( )
xmitMsg . TransmitTime = toNtpTime ( sendTime )
}
err = binary . Write ( con , binary . BigEndian , m )
// Transmit the query.
err = binary . Write ( con , binary . BigEndian , xmitMsg )
if err != nil {
return nil , 0 , err
}
err = binary . Read ( con , binary . BigEndian , m )
// Receive the response.
err = binary . Read ( con , binary . BigEndian , recvMsg )
if err != nil {
return nil , 0 , err
}
delta := time . Since ( xmtTime ) // uses monotonic clock @ Go 1.9+, NB: delta != RTT
dst := toNtpTime ( xmtTime . Add ( delta ) )
// It's possible to use random uint64 as client's `TransmitTime` field,
// it has better privacy (clock of the node is not disclosed in
// plain-text), better UDP packet spoofing resistance (blind attacker
// has to guess both port and the uint64 value), and OpenNTPD behaves
// like that. But math/rand is not secure enough for the purpose,
// crypto/rand takes 64 bits of entropy for every outgoing packet and
// CSPRNG from crypto/rand/rand_unix is not available: see
// https://github.com/golang/go/issues/13820
// A packet is bogus if the origin timestamp t1 in the packet does not
// match the xmt state variable T1.
// -- https://tools.ietf.org/html/rfc5905#section-8
if m . OriginTime != xmt {
return nil , 0 , errors . New ( "response OriginTime != query TransmitTime" ) // spoofed packet?
// Keep track of the time the response was received.
delta := time . Since ( sendTime )
if delta < 0 {
// The system clock may have been set backwards since the packet was
// transmitted. In go 1.9 and later, time.Since ensures that a
// monotonic clock is used, and delta can never be less than zero.
// In versions before 1.9, we have to check.
return nil , 0 , errors . New ( "client clock ticked backwards" )
}
recvTime := toNtpTime ( sendTime . Add ( delta ) )
if m . OriginTime > dst { // Go 1.9 has monotonic clock preventing that, but 1.8 has not, so it's not panic()
return nil , 0 , errors . New ( "client clock tick backwards" )
// Check for invalid fields.
if recvMsg . getMode ( ) != server {
return nil , 0 , errors . New ( "invalid mode in response" )
}
if m . ReceiveTime > m . TransmitTime {
return nil , 0 , errors . New ( "server clock tick backwards" )
if recvMsg . TransmitTime == ntpTime ( 0 ) {
return nil , 0 , errors . New ( "invalid transmit time in response" )
}
if recvMsg . OriginTime != xmitMsg . TransmitTime {
return nil , 0 , errors . New ( "server response mismatch" )
}
if recvMsg . ReceiveTime > recvMsg . TransmitTime {
return nil , 0 , errors . New ( "server clock ticked backwards" )
}
return m , dst , nil
// Correct the received message's origin time using the actual send
// time.
recvMsg . OriginTime = toNtpTime ( sendTime )
return recvMsg , recvTime , nil
}
// TimeV returns the current time from the remote server host using the
// requested version of the NTP protocol. On error, it returns the local time.
// The version may be 2, 3, or 4.
func TimeV ( host string , version int ) ( time . Time , error ) {
m , dst , err := getTime ( host , QueryOptions { Version : version } )
if err != nil {
return time . Now ( ) , err
// parseTime parses the NTP packet along with the packet receive time to
// generate a Response record.
func parseTime ( m * msg , recvTime ntpTime ) * Response {
r := & Response {
Time : m . TransmitTime . Time ( ) ,
ClockOffset : offset ( m . OriginTime , m . ReceiveTime , m . TransmitTime , recvTime ) ,
RTT : rtt ( m . OriginTime , m . ReceiveTime , m . TransmitTime , recvTime ) ,
Precision : toInterval ( m . Precision ) ,
Stratum : m . Stratum ,
ReferenceID : m . ReferenceID ,
ReferenceTime : m . ReferenceTime . Time ( ) ,
RootDelay : m . RootDelay . Duration ( ) ,
RootDispersion : m . RootDispersion . Duration ( ) ,
Leap : m . getLeap ( ) ,
MinError : minError ( m . OriginTime , m . ReceiveTime , m . TransmitTime , recvTime ) ,
Poll : toInterval ( m . Poll ) ,
}
r := parseTime ( m , dst )
if ! r . Validate ( ) {
return time . Now ( ) , errors . New ( "invalid SNTP reply" )
// Calculate values depending on other calculated values
r . RootDistance = rootDistance ( r . RTT , r . RootDelay , r . RootDispersion )
return r
}
// The following helper functions calculate additional metadata about the
// timestamps received from an NTP server. The timestamps returned by
// the server are given the following variable names:
//
// org = Origin Timestamp (client send time)
// rec = Receive Timestamp (server receive time)
// xmt = Transmit Timestamp (server reply time)
// dst = Destination Timestamp (client receive time)
func rtt ( org , rec , xmt , dst ntpTime ) time . Duration {
// round trip delay time
// rtt = (dst-org) - (xmt-rec)
a := dst . Time ( ) . Sub ( org . Time ( ) )
b := xmt . Time ( ) . Sub ( rec . Time ( ) )
rtt := a - b
if rtt < 0 {
rtt = 0
}
// An SNTP client implementing the on-wire protocol has a single server
// and no dependent clients. It can operate with any subset of the NTP
// on-wire protocol, the simplest approach using only the transmit
// timestamp of the server packet and ignoring all other fields.
// -- https://tools.ietf.org/html/rfc5905#section-14
return time . Now ( ) . Add ( r . ClockOffset ) , nil
return rtt
}
// Time returns the current time from the remote server host using version 4 of
// the NTP protocol. On error, it returns the local time.
func Time ( host string ) ( time . Time , error ) {
return TimeV ( host , defaultNtpVersion )
func offset ( org , rec , xmt , dst ntpTime ) time . Duration {
// local clock offset
// offset = ((rec-org) + (xmt-dst)) / 2
a := rec . Time ( ) . Sub ( org . Time ( ) )
b := xmt . Time ( ) . Sub ( dst . Time ( ) )
return ( a + b ) / time . Duration ( 2 )
}
func rtt ( t1 , t2 , t3 , t4 ntpTime ) time . Duration {
// round trip delay time (https://tools.ietf.org/html/rfc5905#section-8)
// T1 = client send time
// T2 = server receive time
// T3 = server reply time
// T4 = client receive time
//
// RTT d:
// d = (T4-T1) - (T3-T2)
a := t4 . Time ( ) . Sub ( t1 . Time ( ) )
b := t3 . Time ( ) . Sub ( t2 . Time ( ) )
return a - b
func minError ( org , rec , xmt , dst ntpTime ) time . Duration {
// Each NTP response contains two pairs of send/receive timestamps.
// When either pair indicates a "causality violation", we calculate the
// error as the difference in time between them. The minimum error is
// the greater of the two causality violations.
var error0 , error1 ntpTime
if org >= rec {
error0 = org - rec
}
if xmt >= dst {
error1 = xmt - dst
}
if error0 > error1 {
return error0 . Duration ( )
}
return error1 . Duration ( )
}
func offset ( t1 , t2 , t3 , t4 ntpTime ) time . Duration {
// local offset equation (https://tools.ietf.org/html/rfc5905#section-8)
// T1 = client send time
// T2 = server receive tim e
// T3 = server reply time
// T4 = client receive time
func rootDistance( rtt , rootDelay , rootDisp time . Duration ) time . Duration {
// The root distance is:
// the maximum error due to all causes of the local clock
// relative to the primary server. It is defined as half th e
// total delay plus total dispersion plus peer jitter.
// (https://tools.ietf.org/html/rfc5905#appendix-A.5.5.2)
//
// Local clock offset t:
// t = ((T2-T1) + (T3-T4)) / 2
a := t2 . Time ( ) . Sub ( t1 . Time ( ) )
b := t3 . Time ( ) . Sub ( t4 . Time ( ) )
return ( a + b ) / time . Duration ( 2 )
// In the reference implementation, it is calculated as follows:
// rootDist = max(MINDISP, rootDelay + rtt)/2 + rootDisp
// + peerDisp + PHI * (uptime - peerUptime)
// + peerJitter
// For an SNTP client which sends only a single packet, most of these
// terms are irrelevant and become 0.
totalDelay := rtt + rootDelay
return totalDelay / 2 + rootDisp
}
func toInterval ( t int8 ) time . Duration {