Go 语言创建一个 NTP 客户端

来自https://github.com/beevik/ntp/blob/master/ntp.go

// 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 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"

    "fmt"

    "net"

    "time"

 

    "golang.org/x/net/ipv4"

)

 

// 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 LeapIndicator = 0

 

    // 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 = 2

 

    // LeapNotInSync indicates an unsynchronized leap second.

    LeapNotInSync = 3

)

 

// Internal constants

const (

    defaultNtpVersion = 4

    nanoPerSec = 1000000000

    maxStratum = 16

    defaultTimeout = 5 * time.Second

    maxPollInterval = (1 << 17) * time.Second

    maxDispersion = 16 * time.Second

)

 

// Internal variables

var (

    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.

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

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

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 {

    t64 := uint64(t)

    sec := (t64 >> 16) * nanoPerSec

    frac := (t64 & 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)

}

 

// setLeap modifies the leap indicator on the message.

func (m *msg) setLeap(li LeapIndicator) {

    m.LiVnMode = (m.LiVnMode & 0x3f) | uint8(li)<<6

}

 

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

type QueryOptions struct {

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

 

    // 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

 

    // KissCode is a 4-character string describing the reason for a

    // "kiss of death" response (stratum = 0). For a list of standard kiss

    // codes, see https://tools.ietf.org/html/rfc5905#section-7.4.

    KissCode string

 

    // 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() error {

    // Handle invalid stratum values.

    if r.Stratum == 0 {

        return fmt.Errorf("kiss of death received: %s", r.KissCode)

    }

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

    }

 

    // 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

    if lambda > maxDispersion {

        return errors.New("invalid dispersion")

    }

 

    // 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")

    }

 

    // nil means the response is valid.

    return nil

}

 

// 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 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 {

        return nil, err

    }

    return parseTime(m, now), nil

}

 

// 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

    }

 

    r := parseTime(m, recvTime)

    err = r.Validate()

    if err != nil {

        return time.Now(), err

    }

 

    // Use the clock offset to calculate the time.

    return time.Now().Add(r.ClockOffset), nil

}

 

// 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 {

        return nil, 0, errors.New("invalid protocol version requested")

    }

 

    // 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

    }

 

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

        if err != nil {

            return nil, 0, err

        }

    }

 

    // Set a timeout on the connection.

    if opt.Timeout == 0 {

        opt.Timeout = defaultTimeout

    }

    con.SetDeadline(time.Now().Add(opt.Timeout))

 

    // 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 transmitted.

    bits := make([]byte, 8)

    _, err = rand.Read(bits)

    var xmitTime time.Time

    if err == nil {

        xmitMsg.TransmitTime = ntpTime(binary.BigEndian.Uint64(bits))

        xmitTime = time.Now()

    } else {

        xmitTime = time.Now()

        xmitMsg.TransmitTime = toNtpTime(xmitTime)

    }

 

    // Transmit the query.

    err = binary.Write(con, binary.BigEndian, xmitMsg)

    if err != nil {

        return nil, 0, err

    }

 

    // Receive the response.

    err = binary.Read(con, binary.BigEndian, recvMsg)

    if err != nil {

        return nil, 0, err

    }

 

    // Keep track of the time the response was received.

    delta := time.Since(xmitTime)

    if delta < 0 {

        // The local system may have had its clock adjusted since it

        // sent the query. In go 1.9 and later, time.Since ensures

        // that a monotonic clock is used, so delta can never be less

        // than zero. In versions before 1.9, a monotonic clock is

        // not used, so we have to check.

        return nil, 0, errors.New("client clock ticked backwards")

    }

    recvTime := toNtpTime(xmitTime.Add(delta))

 

    // 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

    // transmit time.

    recvMsg.OriginTime = toNtpTime(xmitTime)

 

    return recvMsg, recvTime, nil

}

 

// 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),

    }

 

    // Calculate values depending on other calculated values

    r.RootDistance = rootDistance(r.RTT, r.RootDelay, r.RootDispersion)

 

    // If a kiss of death was received, interpret the reference ID as

    // a kiss code.

    if r.Stratum == 0 {

        r.KissCode = kissCode(r.ReferenceID)

    }

 

    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

    }

    return rtt

}

 

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

    //

    // 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 {

    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

    }

}

 

func kissCode(id uint32) string {

    isPrintable := func(ch byte) bool { return ch >= 32 && ch <= 126 }

 

    b := []byte{

        byte(id >> 24),

        byte(id >> 16),

        byte(id >> 8),

        byte(id),

    }

    for _, ch := range b {

        if !isPrintable(ch) {

            return ""

        }

    }

    return string(b)

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值