diff --git a/collector/ntp.go b/collector/ntp.go index f889699d..2580353c 100644 --- a/collector/ntp.go +++ b/collector/ntp.go @@ -152,7 +152,7 @@ func (c *ntpCollector) Update(ch chan<- prometheus.Metric) error { maxerr += time.Second } - if resp.Validate() && resp.RootDistance <= *ntpMaxDistance && resp.CausalityViolation <= maxerr { + if resp.Validate() == nil && resp.RootDistance <= *ntpMaxDistance && resp.MinError <= maxerr { ch <- c.sanity.mustNewConstMetric(1) } else { ch <- c.sanity.mustNewConstMetric(0) diff --git a/vendor/github.com/beevik/ntp/CONTRIBUTORS b/vendor/github.com/beevik/ntp/CONTRIBUTORS index 5533ecd3..626c12eb 100644 --- a/vendor/github.com/beevik/ntp/CONTRIBUTORS +++ b/vendor/github.com/beevik/ntp/CONTRIBUTORS @@ -4,3 +4,4 @@ Anton Tolchanov (knyar) Christopher Batey (chbatey) Meng Zhuo (mengzhuo) Leonid Evdokimov (darkk) +Ask Bjørn Hansen (abh) \ No newline at end of file diff --git a/vendor/github.com/beevik/ntp/README.md b/vendor/github.com/beevik/ntp/README.md index 5ca98ba8..7d0d23fb 100644 --- a/vendor/github.com/beevik/ntp/README.md +++ b/vendor/github.com/beevik/ntp/README.md @@ -4,21 +4,22 @@ ntp === -The ntp package is an implementation of a simple NTP client. It allows you -to connect to a remote NTP server and request the current time. +The ntp package is an implementation of a Simple NTP (SNTP) client based on +[RFC5905](https://tools.ietf.org/html/rfc5905). It allows you to connect to +a remote NTP server and request the current time. -To request the current time, simply do the following: +If all you care about is the current time according to a known remote NTP +server, simply use the `Time` function: ```go time, err := ntp.Time("0.beevik-ntp.pool.ntp.org") ``` -To request the current time along with additional metadata, use the Query -function: +If you want the time as well as additional metadata about the time, use the +`Query` function instead: ```go response, err := ntp.Query("0.beevik-ntp.pool.ntp.org") ``` -NB: if you want to use the NTP Pool in your software you should request your -own [vendor zone](http://www.pool.ntp.org/en/vendors.html). You **must -absolutely not use the default pool.ntp.org zone names** as the default -configuration in your application or appliance. +To use the NTP pool in your application, please request your own +[vendor zone](http://www.pool.ntp.org/en/vendors.html). Avoid using +the `[number].pool.ntp.org` zone names in your applications. diff --git a/vendor/github.com/beevik/ntp/RELEASE_NOTES.md b/vendor/github.com/beevik/ntp/RELEASE_NOTES.md new file mode 100644 index 00000000..4266ee77 --- /dev/null +++ b/vendor/github.com/beevik/ntp/RELEASE_NOTES.md @@ -0,0 +1,44 @@ +Release v0.1.1 +============== + +**Breaking changes** + +* Removed the `MaxStratum` constant. + +**Deprecations** + +* Officially deprecated the `TimeV` function. + +**Internal changes** + +* Removed `minDispersion` from the `RootDistance` calculation, since the value + was arbitrary. +* Moved some validation into main code path so that invalid `TransmitTime` and + `mode` responses trigger an error even when `Response.Validate` is not + called. + + +Release v0.1.0 +============== + +This is the initial release of the `ntp` package. Currently it supports the following features: +* `Time()` to query the current time according to a remote NTP server. +* `Query()` to query multiple pieces of time-related information from a remote NTP server. +* `QueryWithOptions()`, which is like `Query()` but with the ability to override default query options. + +Time-related information returned by the `Query` functions includes: +* `Time`: the time the server transmitted its response, according to the server's clock. +* `ClockOffset`: the estimated offset of the client's clock relative to the server's clock. You may apply this offset to any local system clock reading once the query is complete. +* `RTT`: an estimate of the round-trip-time delay between the client and the server. +* `Precision`: the precision of the server's clock reading. +* `Stratum`: the "stratum" level of the server, where 1 indicates a server directly connected to a reference clock, and values greater than 1 indicating the number of hops from the reference clock. +* `ReferenceID`: A unique identifier for the NTP server that was contacted. +* `ReferenceTime`: The time at which the server last updated its local clock setting. +* `RootDelay`: The server's round-trip delay to the reference clock. +* `RootDispersion`: The server's total dispersion to the referenced clock. +* `RootDistance`: An estimate of the root synchronization distance. +* `Leap`: The leap second indicator. +* `MinError`: A lower bound on the clock error between the client and the server. +* `Poll`: the maximum polling interval between successive messages on the server. + +The `Response` structure returned by the `Query` functions also contains a `Response.Validate()` function that returns an error if any of the fields returned by the server are invalid. diff --git a/vendor/github.com/beevik/ntp/ntp.go b/vendor/github.com/beevik/ntp/ntp.go index f97c1bc6..558b2e2d 100644 --- a/vendor/github.com/beevik/ntp/ntp.go +++ b/vendor/github.com/beevik/ntp/ntp.go @@ -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 an 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 an absolute time and returns +// Time interprets the fixed-point ntpTime as an 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) } -// setLeapIndicator modifies the leap indicator on the message. -func (m *msg) setLeapIndicator(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<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 time - // 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 the + // 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 { diff --git a/vendor/vendor.json b/vendor/vendor.json index b7fbcade..dd9fdc46 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2,12 +2,6 @@ "comment": "", "ignore": "test", "package": [ - { - "checksumSHA1": "0Tugz8gj9KqqVj6JLkXUA7BXas4=", - "path": "github.com/sirupsen/logrus", - "revision": "0208149b40d863d2c1a2f8fe5753096a9cf2cc8b", - "revisionTime": "2017-02-27T12:44:09Z" - }, { "checksumSHA1": "KmjnydoAbofMieIWm+it5OWERaM=", "path": "github.com/alecthomas/template", @@ -27,10 +21,12 @@ "revisionTime": "2015-10-22T06:55:26Z" }, { - "checksumSHA1": "X73SC/5YR0afYD/tq802Shxb0UI=", + "checksumSHA1": "OXcULIfKv/Xcsa4o1By1lY0s+AI=", "path": "github.com/beevik/ntp", - "revision": "74e5133786235ede462c83c17fa3b34653901719", - "revisionTime": "2017-08-25T09:16:23Z" + "revision": "802074b1b037c59dbdbee7e196ad91b51d0ec844", + "revisionTime": "2017-10-03T23:10:51Z", + "version": "v0.1.1", + "versionExact": "v0.1.1" }, { "checksumSHA1": "spyv5/YFBjYyZLZa1U2LBfDR8PM=", @@ -182,6 +178,12 @@ "revision": "a66a2f8b6f3fe82a95a1bed0bb3705bac8031717", "revisionTime": "2017-06-07T19:36:46Z" }, + { + "checksumSHA1": "0Tugz8gj9KqqVj6JLkXUA7BXas4=", + "path": "github.com/sirupsen/logrus", + "revision": "0208149b40d863d2c1a2f8fe5753096a9cf2cc8b", + "revisionTime": "2017-02-27T12:44:09Z" + }, { "checksumSHA1": "uozMgPjB4AggpuuJkGq3FgAs4CA=", "path": "github.com/soundcloud/go-runit/runit",