prometheusmetricshost-metricsmachine-metricsnode-metricsprocfsprometheus-exportersystem-informationsystem-metrics
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
6.6 KiB
240 lines
6.6 KiB
// Copyright 2015 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: |
|
// |
|
// https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/FlcdMU5fkLQ |
|
package ntp |
|
|
|
import ( |
|
"encoding/binary" |
|
"net" |
|
"time" |
|
) |
|
|
|
type mode uint8 |
|
|
|
const ( |
|
reserved mode = 0 + iota |
|
symmetricActive |
|
symmetricPassive |
|
client |
|
server |
|
broadcast |
|
controlMessage |
|
reservedPrivate |
|
) |
|
|
|
const ( |
|
maxStratum = 16 |
|
nanoPerSec = 1000000000 |
|
) |
|
|
|
var ( |
|
timeout = 5 * time.Second |
|
ntpEpoch = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC) |
|
) |
|
|
|
// An ntpTime is a 64-bit fixed-point (Q32.32) representation of the number of |
|
// seconds elapsed since the NTP epoch. |
|
type ntpTime uint64 |
|
|
|
// Duration interprets the fixed-point ntpTime as a number of elapsed seconds |
|
// and returns the corresponding time.Duration value. |
|
func (t ntpTime) Duration() time.Duration { |
|
sec := (t >> 32) * nanoPerSec |
|
frac := (t & 0xffffffff) * nanoPerSec >> 32 |
|
return time.Duration(sec + frac) |
|
} |
|
|
|
// Time interprets the fixed-point ntpTime as a an absolute time and returns |
|
// the corresponding time.Time value. |
|
func (t ntpTime) Time() time.Time { |
|
return ntpEpoch.Add(t.Duration()) |
|
} |
|
|
|
// toNtpTime converts the time.Time value t into its 64-bit fixed-point |
|
// ntpTime representation. |
|
func toNtpTime(t time.Time) ntpTime { |
|
nsec := uint64(t.Sub(ntpEpoch)) |
|
sec := nsec / nanoPerSec |
|
frac := (nsec - sec*nanoPerSec) << 32 / 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. |
|
type ntpTimeShort uint32 |
|
|
|
// Duration interprets the fixed-point ntpTimeShort as a number of elapsed |
|
// seconds and returns the corresponding time.Duration value. |
|
func (t ntpTimeShort) Duration() time.Duration { |
|
sec := (t >> 16) * nanoPerSec |
|
frac := (t & 0xffff) * nanoPerSec >> 16 |
|
return time.Duration(sec + frac) |
|
} |
|
|
|
// msg is an internal representation of an NTP packet. |
|
type msg struct { |
|
LiVnMode uint8 // Leap Indicator (2) + Version (3) + Mode (3) |
|
Stratum uint8 |
|
Poll int8 |
|
Precision int8 |
|
RootDelay ntpTimeShort |
|
RootDispersion ntpTimeShort |
|
ReferenceID uint32 |
|
ReferenceTime ntpTime |
|
OriginTime ntpTime |
|
ReceiveTime ntpTime |
|
TransmitTime ntpTime |
|
} |
|
|
|
// setVersion sets the NTP protocol version on the message. |
|
func (m *msg) setVersion(v int) { |
|
m.LiVnMode = (m.LiVnMode & 0xc7) | uint8(v)<<3 |
|
} |
|
|
|
// setMode sets the NTP protocol mode on the message. |
|
func (m *msg) setMode(md mode) { |
|
m.LiVnMode = (m.LiVnMode & 0xf8) | uint8(md) |
|
} |
|
|
|
// 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 |
|
RootDelay time.Duration // server's RTT to the reference clock |
|
RootDispersion time.Duration // server's dispersion to the reference clock |
|
} |
|
|
|
// Query returns the current time from the remote server host using the |
|
// requested version of the NTP protocol. It also returns additional |
|
// information about the exchanged time information. The version may be 2, 3, |
|
// or 4; although 4 is most typically used. |
|
func Query(host string, version int) (*Response, error) { |
|
m, err := getTime(host, version) |
|
now := toNtpTime(time.Now()) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
r := &Response{ |
|
Time: m.ReceiveTime.Time(), |
|
RTT: rtt(m.OriginTime, m.ReceiveTime, m.TransmitTime, now), |
|
ClockOffset: offset(m.OriginTime, m.ReceiveTime, m.TransmitTime, now), |
|
Poll: toInterval(m.Poll), |
|
Precision: toInterval(m.Precision), |
|
Stratum: m.Stratum, |
|
ReferenceID: m.ReferenceID, |
|
RootDelay: m.RootDelay.Duration(), |
|
RootDispersion: m.RootDispersion.Duration(), |
|
} |
|
|
|
// https://tools.ietf.org/html/rfc5905#section-7.3 |
|
if r.Stratum == 0 { |
|
r.Stratum = maxStratum |
|
} |
|
|
|
return r, nil |
|
} |
|
|
|
// getTime returns the "receive time" from the remote NTP server host. |
|
func getTime(host string, version int) (*msg, error) { |
|
if version < 2 || version > 4 { |
|
panic("ntp: invalid version number") |
|
} |
|
|
|
raddr, err := net.ResolveUDPAddr("udp", host+":123") |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
con, err := net.DialUDP("udp", nil, raddr) |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer con.Close() |
|
con.SetDeadline(time.Now().Add(timeout)) |
|
|
|
m := new(msg) |
|
m.setMode(client) |
|
m.setVersion(version) |
|
m.TransmitTime = toNtpTime(time.Now()) |
|
|
|
err = binary.Write(con, binary.BigEndian, m) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
err = binary.Read(con, binary.BigEndian, m) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return m, nil |
|
} |
|
|
|
// TimeV returns the current time from the remote server host using the |
|
// requested version of the NTP protocol. The version may be 2, 3, or 4; |
|
// although 4 is most typically used. |
|
func TimeV(host string, version int) (time.Time, error) { |
|
m, err := getTime(host, version) |
|
if err != nil { |
|
return time.Now(), err |
|
} |
|
return m.ReceiveTime.Time().Local(), nil |
|
} |
|
|
|
// Time returns the current time from the remote server host using version 4 |
|
// of the NTP protocol. |
|
func Time(host string) (time.Time, error) { |
|
return TimeV(host, 4) |
|
} |
|
|
|
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 |
|
// |
|
// 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) |
|
} |
|
|
|
func toInterval(t int8) time.Duration { |
|
switch { |
|
case t > 0: |
|
return time.Duration(uint64(time.Second) << uint(t)) |
|
case t < 0: |
|
return time.Duration(uint64(time.Second) >> uint(-t)) |
|
default: |
|
return time.Second |
|
} |
|
}
|
|
|