Updated vendored ntp package (#681)

The github.com/beevik/ntp package was recently updated with some
API changes that broke node_exporter. This commit fetches the
latest version of the ntp package and brings node_exporter in
line with the latest API.
pull/683/head
Brett Vickers 7 years ago committed by Ben Kochie
parent 859a825bb8
commit b62c7bc0ad

@ -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)

@ -4,3 +4,4 @@ Anton Tolchanov (knyar)
Christopher Batey (chbatey)
Meng Zhuo (mengzhuo)
Leonid Evdokimov (darkk)
Ask Bjørn Hansen (abh)

@ -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.

@ -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.

553
vendor/github.com/beevik/ntp/ntp.go generated vendored

@ -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
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
}
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 lambda > maxDispersion {
return errors.New("invalid dispersion")
}
if violation < 0 {
return -violation
// 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")
}
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()
r := parseTime(m, recvTime)
err = r.Validate()
if err != nil {
return time.Now(), err
}
// https://tools.ietf.org/html/rfc5905#section-7.3
if r.Stratum == 0 {
r.Stratum = MaxStratum
// Use the clock offset to calculate the time.
return time.Now().Add(r.ClockOffset), nil
}
return r
// 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 returns SNTP packet & DestinationTime timestamp.
// 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
// Resolve the remote NTP server address.
raddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(host, "123"))
if err != nil {
return nil, 0, err
}
raddr, err := net.ResolveUDPAddr("udp", host+":123")
// 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))
// 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))
// 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?
// Check for invalid fields.
if recvMsg.getMode() != server {
return nil, 0, errors.New("invalid mode in response")
}
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")
}
// Correct the received message's origin time using the actual send
// time.
recvMsg.OriginTime = toNtpTime(sendTime)
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")
return recvMsg, recvTime, nil
}
if m.ReceiveTime > m.TransmitTime {
return nil, 0, errors.New("server clock tick backwards")
// 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),
}
return m, dst, nil
// Calculate values depending on other calculated values
r.RootDistance = rootDistance(r.RTT, r.RootDelay, r.RootDispersion)
return r
}
// 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
// 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
}
r := parseTime(m, dst)
if !r.Validate() {
return time.Now(), errors.New("invalid SNTP reply")
return rtt
}
// 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
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)
}
// 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 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 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 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 {

20
vendor/vendor.json vendored

@ -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",

Loading…
Cancel
Save