| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843 |
- // Copyright © 2015-2023 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
- // RFC 5905 (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 (
- "bytes"
- "crypto/rand"
- "encoding/binary"
- "errors"
- "fmt"
- "net"
- "strconv"
- "strings"
- "time"
- "golang.org/x/net/ipv4"
- )
- var (
- ErrAuthFailed = errors.New("authentication failed")
- ErrInvalidAuthKey = errors.New("invalid authentication key")
- ErrInvalidDispersion = errors.New("invalid dispersion in response")
- ErrInvalidLeapSecond = errors.New("invalid leap second in response")
- ErrInvalidMode = errors.New("invalid mode in response")
- ErrInvalidProtocolVersion = errors.New("invalid protocol version requested")
- ErrInvalidStratum = errors.New("invalid stratum in response")
- ErrInvalidTime = errors.New("invalid time reported")
- ErrInvalidTransmitTime = errors.New("invalid transmit time in response")
- ErrKissOfDeath = errors.New("kiss of death received")
- ErrServerClockFreshness = errors.New("server clock not fresh")
- ErrServerResponseMismatch = errors.New("server response didn't match request")
- ErrServerTickedBackwards = errors.New("server clock ticked backwards")
- )
- // 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
- defaultNtpPort = 123
- nanoPerSec = 1000000000
- maxStratum = 16
- defaultTimeout = 5 * time.Second
- maxPollInterval = (1 << 17) * time.Second
- maxDispersion = 16 * time.Second
- )
- // Internal variables
- var (
- ntpEra0 = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
- ntpEra1 = time.Date(2036, 2, 7, 6, 28, 16, 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
- nsec := frac >> 32
- if uint32(frac) >= 0x80000000 {
- nsec++
- }
- return time.Duration(sec + nsec)
- }
- // Time interprets the fixed-point ntpTime as an absolute time and returns
- // the corresponding time.Time value.
- func (t ntpTime) Time() time.Time {
- // Assume NTP era 1 (year 2036+) if the raw timestamp suggests a year
- // before 1970. Otherwise assume NTP era 0. This allows the function to
- // report an accurate time value both before and after the 0-to-1 era
- // rollover.
- const t1970 = 0x83aa7e8000000000
- if uint64(t) < t1970 {
- return ntpEra1.Add(t.Duration())
- }
- return ntpEra0.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(ntpEra0))
- sec := nsec / nanoPerSec
- nsec = uint64(nsec-sec*nanoPerSec) << 32
- frac := uint64(nsec / nanoPerSec)
- if nsec%nanoPerSec >= nanoPerSec/2 {
- frac++
- }
- 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 {
- sec := uint64(t>>16) * nanoPerSec
- frac := uint64(t&0xffff) * nanoPerSec
- nsec := frac >> 16
- if uint16(frac) >= 0x8000 {
- nsec++
- }
- return time.Duration(sec + nsec)
- }
- // header is an internal representation of an NTP packet header.
- type header struct {
- LiVnMode uint8 // Leap Indicator (2) + Version (3) + Mode (3)
- Stratum uint8
- Poll int8
- Precision int8
- RootDelay ntpTimeShort
- RootDispersion ntpTimeShort
- ReferenceID uint32 // KoD code if Stratum == 0
- ReferenceTime ntpTime
- OriginTime ntpTime
- ReceiveTime ntpTime
- TransmitTime ntpTime
- }
- // setVersion sets the NTP protocol version on the header.
- func (h *header) setVersion(v int) {
- h.LiVnMode = (h.LiVnMode & 0xc7) | uint8(v)<<3
- }
- // setMode sets the NTP protocol mode on the header.
- func (h *header) setMode(md mode) {
- h.LiVnMode = (h.LiVnMode & 0xf8) | uint8(md)
- }
- // setLeap modifies the leap indicator on the header.
- func (h *header) setLeap(li LeapIndicator) {
- h.LiVnMode = (h.LiVnMode & 0x3f) | uint8(li)<<6
- }
- // getVersion returns the version value in the header.
- func (h *header) getVersion() int {
- return int((h.LiVnMode >> 3) & 0x7)
- }
- // getMode returns the mode value in the header.
- func (h *header) getMode() mode {
- return mode(h.LiVnMode & 0x07)
- }
- // getLeap returns the leap indicator on the header.
- func (h *header) getLeap() LeapIndicator {
- return LeapIndicator((h.LiVnMode >> 6) & 0x03)
- }
- // An Extension adds custom behaviors capable of modifying NTP packets before
- // being sent to the server and processing packets after being received by the
- // server.
- type Extension interface {
- // ProcessQuery is called when the client is about to send a query to the
- // NTP server. The buffer contains the NTP header. It may also contain
- // extension fields added by extensions processed prior to this one.
- ProcessQuery(buf *bytes.Buffer) error
- // ProcessResponse is called after the client has received the server's
- // NTP response. The buffer contains the entire message returned by the
- // server.
- ProcessResponse(buf []byte) error
- }
- // QueryOptions contains configurable options used by the QueryWithOptions
- // function.
- type QueryOptions struct {
- // Timeout determines how long the client waits for a response from the
- // server before failing with a timeout error. Defaults to 5 seconds.
- Timeout time.Duration
- // Version of the NTP protocol to use. Defaults to 4.
- Version int
- // LocalAddress contains the local IP address to use when creating a
- // connection to the remote NTP server. This may be useful when the local
- // system has more than one IP address. This address should not contain
- // a port number.
- LocalAddress string
- // TTL specifies the maximum number of IP hops before the query datagram
- // is dropped by the network. Defaults to the local system's default value.
- TTL int
- // Auth contains the settings used to configure NTP symmetric key
- // authentication. See RFC 5905 for further details.
- Auth AuthOptions
- // Extensions may be added to modify NTP queries before they are
- // transmitted and to process NTP responses after they arrive.
- Extensions []Extension
- // GetSystemTime is a callback used to override the default method of
- // obtaining the local system time during time synchronization. If not
- // specified, time.Now is used.
- GetSystemTime func() time.Time
- // Dialer is a callback used to override the default UDP network dialer.
- // The localAddress is directly copied from the LocalAddress field
- // specified in QueryOptions. It may be the empty string or a host address
- // (without port number). The remoteAddress is the "host:port" string
- // derived from the first parameter to QueryWithOptions. The
- // remoteAddress is guaranteed to include a port number.
- Dialer func(localAddress, remoteAddress string) (net.Conn, error)
- // Dial is a callback used to override the default UDP network dialer.
- //
- // DEPRECATED. Use Dialer instead.
- Dial func(laddr string, lport int, raddr string, rport int) (net.Conn, error)
- // Port indicates the port used to reach the remote NTP server.
- //
- // DEPRECATED. Embed the port number in the query address string instead.
- Port int
- }
- // A Response contains time data, some of which is returned by the NTP server
- // and some of which is calculated by this client.
- type Response struct {
- // ClockOffset is the estimated offset of the local system clock relative
- // to the server's clock. Add this value to subsequent local system clock
- // times in order to obtain a time that is synchronized to the server's
- // clock.
- ClockOffset time.Duration
- // Time is the time the server transmitted this response, measured using
- // its own clock. You should not use this value for time synchronization
- // purposes. Add ClockOffset to your system clock instead.
- Time time.Time
- // 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
- // Version is the NTP protocol version number reported by the server.
- Version int
- // 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 integer identifying the server or reference
- // clock. For stratum 1 servers, this is typically a meaningful
- // zero-padded ASCII-encoded string assigned to the clock. For stratum 2+
- // servers, this is a reference identifier for the server and is either
- // the server's IPv4 address or a hash of its IPv6 address. For
- // kiss-of-death responses (stratum 0), this is the ASCII-encoded "kiss
- // code".
- ReferenceID uint32
- // ReferenceTime is the time the server last updated its local clock.
- 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 query messages to
- // the server.
- Poll time.Duration
- authErr error
- }
- // IsKissOfDeath returns true if the response is a "kiss of death" from the
- // remote server. If this function returns true, you may examine the
- // response's KissCode value to determine the reason for the kiss of death.
- func (r *Response) IsKissOfDeath() bool {
- return r.Stratum == 0
- }
- // ReferenceString returns the response's ReferenceID value formatted as a
- // string. If the response's stratum is zero, then the "kiss o' death" string
- // is returned. If stratum is one, then the server is a reference clock and
- // the reference clock's name is returned. If stratum is two or greater, then
- // the ID is either an IPv4 address or an MD5 hash of the IPv6 address; in
- // either case the reference string is reported as 4 dot-separated
- // decimal-based integers.
- func (r *Response) ReferenceString() string {
- if r.Stratum == 0 {
- return kissCode(r.ReferenceID)
- }
- var b [4]byte
- binary.BigEndian.PutUint32(b[:], r.ReferenceID)
- if r.Stratum == 1 {
- const dot = rune(0x22c5)
- var r []rune
- for i := range b {
- if b[i] == 0 {
- break
- }
- if b[i] >= 32 && b[i] <= 126 {
- r = append(r, rune(b[i]))
- } else {
- r = append(r, dot)
- }
- }
- return fmt.Sprintf(".%s.", string(r))
- }
- return fmt.Sprintf("%d.%d.%d.%d", b[0], b[1], b[2], b[3])
- }
- // Validate checks if the response is valid for the purposes of time
- // synchronization.
- func (r *Response) Validate() error {
- // Forward authentication errors.
- if r.authErr != nil {
- return r.authErr
- }
- // Handle invalid stratum values.
- if r.Stratum == 0 {
- return ErrKissOfDeath
- }
- if r.Stratum >= maxStratum {
- return ErrInvalidStratum
- }
- // 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 ErrServerClockFreshness
- }
- // 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 ErrInvalidDispersion
- }
- // If the server's transmit time is before its reference time, the
- // response is invalid.
- if r.Time.Before(r.ReferenceTime) {
- return ErrInvalidTime
- }
- // Handle invalid leap second indicator.
- if r.Leap == LeapNotInSync {
- return ErrInvalidLeapSecond
- }
- // nil means the response is valid.
- return nil
- }
- // Query requests time data from a remote NTP server. The response contains
- // information from which a more accurate local time can be inferred.
- //
- // The server address is of the form "host", "host:port", "host%zone:port",
- // "[host]:port" or "[host%zone]:port". The host may contain an IPv4, IPv6 or
- // domain name address. When specifying both a port and an IPv6 address, one
- // of the bracket formats must be used. If no port is included, NTP default
- // port 123 is used.
- func Query(address string) (*Response, error) {
- return QueryWithOptions(address, QueryOptions{})
- }
- // QueryWithOptions performs the same function as Query but allows for the
- // customization of certain query behaviors. See the comments for Query and
- // QueryOptions for further details.
- func QueryWithOptions(address string, opt QueryOptions) (*Response, error) {
- h, now, err := getTime(address, &opt)
- if err != nil && err != ErrAuthFailed {
- return nil, err
- }
- return generateResponse(h, now, err), nil
- }
- // Time returns the current, corrected local time using information returned
- // from the remote NTP server. On error, Time returns the uncorrected local
- // system time.
- //
- // The server address is of the form "host", "host:port", "host%zone:port",
- // "[host]:port" or "[host%zone]:port". The host may contain an IPv4, IPv6 or
- // domain name address. When specifying both a port and an IPv6 address, one
- // of the bracket formats must be used. If no port is included, NTP default
- // port 123 is used.
- func Time(address string) (time.Time, error) {
- r, err := Query(address)
- if err != nil {
- return time.Now(), err
- }
- err = r.Validate()
- if err != nil {
- return time.Now(), err
- }
- // Use the response's clock offset to calculate an accurate time.
- return time.Now().Add(r.ClockOffset), nil
- }
- // getTime performs the NTP server query and returns the response header
- // along with the local system time it was received.
- func getTime(address string, opt *QueryOptions) (*header, ntpTime, error) {
- if opt.Timeout == 0 {
- opt.Timeout = defaultTimeout
- }
- if opt.Version == 0 {
- opt.Version = defaultNtpVersion
- }
- if opt.Version < 2 || opt.Version > 4 {
- return nil, 0, ErrInvalidProtocolVersion
- }
- if opt.Port == 0 {
- opt.Port = defaultNtpPort
- }
- if opt.Dial != nil {
- // wrapper for the deprecated Dial callback.
- opt.Dialer = func(la, ra string) (net.Conn, error) {
- return dialWrapper(la, ra, opt.Dial)
- }
- }
- if opt.Dialer == nil {
- opt.Dialer = defaultDialer
- }
- if opt.GetSystemTime == nil {
- opt.GetSystemTime = time.Now
- }
- // Compose a conforming host:port remote address string if the address
- // string doesn't already contain a port.
- remoteAddress, err := fixHostPort(address, opt.Port)
- if err != nil {
- return nil, 0, err
- }
- // Connect to the remote server.
- con, err := opt.Dialer(opt.LocalAddress, remoteAddress)
- 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.
- con.SetDeadline(time.Now().Add(opt.Timeout))
- // Allocate a buffer big enough to hold an entire response datagram.
- recvBuf := make([]byte, 8192)
- recvHdr := new(header)
- // Allocate the query message header.
- xmitHdr := new(header)
- xmitHdr.setMode(client)
- xmitHdr.setVersion(opt.Version)
- xmitHdr.setLeap(LeapNoWarning)
- xmitHdr.Precision = 0x20
- // To help prevent spoofing and client fingerprinting, use a
- // cryptographically random 64-bit value for the TransmitTime. See:
- // https://www.ietf.org/archive/id/draft-ietf-ntp-data-minimization-04.txt
- bits := make([]byte, 8)
- _, err = rand.Read(bits)
- if err != nil {
- return nil, 0, err
- }
- xmitHdr.TransmitTime = ntpTime(binary.BigEndian.Uint64(bits))
- // Write the query header to a transmit buffer.
- var xmitBuf bytes.Buffer
- binary.Write(&xmitBuf, binary.BigEndian, xmitHdr)
- // Allow extensions to process the query and add to the transmit buffer.
- for _, e := range opt.Extensions {
- err = e.ProcessQuery(&xmitBuf)
- if err != nil {
- return nil, 0, err
- }
- }
- // If using symmetric key authentication, decode and validate the auth key
- // string.
- authKey, err := decodeAuthKey(opt.Auth)
- if err != nil {
- return nil, 0, err
- }
- // Append a MAC if authentication is being used.
- appendMAC(&xmitBuf, opt.Auth, authKey)
- // Transmit the query and keep track of when it was transmitted.
- xmitTime := opt.GetSystemTime()
- _, err = con.Write(xmitBuf.Bytes())
- if err != nil {
- return nil, 0, err
- }
- // Receive the response.
- recvBytes, err := con.Read(recvBuf)
- if err != nil {
- return nil, 0, err
- }
- // Keep track of the time the response was received. As of go 1.9, the
- // time package uses a monotonic clock, so delta will never be less than
- // zero for go version 1.9 or higher.
- recvTime := opt.GetSystemTime()
- if recvTime.Sub(xmitTime) < 0 {
- recvTime = xmitTime
- }
- // Parse the response header.
- recvBuf = recvBuf[:recvBytes]
- recvReader := bytes.NewReader(recvBuf)
- err = binary.Read(recvReader, binary.BigEndian, recvHdr)
- if err != nil {
- return nil, 0, err
- }
- // Allow extensions to process the response.
- for i := len(opt.Extensions) - 1; i >= 0; i-- {
- err = opt.Extensions[i].ProcessResponse(recvBuf)
- if err != nil {
- return nil, 0, err
- }
- }
- // Check for invalid fields.
- if recvHdr.getMode() != server {
- return nil, 0, ErrInvalidMode
- }
- if recvHdr.TransmitTime == ntpTime(0) {
- return nil, 0, ErrInvalidTransmitTime
- }
- if recvHdr.OriginTime != xmitHdr.TransmitTime {
- return nil, 0, ErrServerResponseMismatch
- }
- if recvHdr.ReceiveTime > recvHdr.TransmitTime {
- return nil, 0, ErrServerTickedBackwards
- }
- // Correct the received message's origin time using the actual
- // transmit time.
- recvHdr.OriginTime = toNtpTime(xmitTime)
- // Perform authentication of the server response.
- authErr := verifyMAC(recvBuf, opt.Auth, authKey)
- return recvHdr, toNtpTime(recvTime), authErr
- }
- // defaultDialer provides a UDP dialer based on Go's built-in net stack.
- func defaultDialer(localAddress, remoteAddress string) (net.Conn, error) {
- var laddr *net.UDPAddr
- if localAddress != "" {
- var err error
- laddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(localAddress, "0"))
- if err != nil {
- return nil, err
- }
- }
- raddr, err := net.ResolveUDPAddr("udp", remoteAddress)
- if err != nil {
- return nil, err
- }
- return net.DialUDP("udp", laddr, raddr)
- }
- // dialWrapper is used to wrap the deprecated Dial callback in QueryOptions.
- func dialWrapper(la, ra string,
- dial func(la string, lp int, ra string, rp int) (net.Conn, error)) (net.Conn, error) {
- rhost, rport, err := net.SplitHostPort(ra)
- if err != nil {
- return nil, err
- }
- rportValue, err := strconv.Atoi(rport)
- if err != nil {
- return nil, err
- }
- return dial(la, 0, rhost, rportValue)
- }
- // fixHostPort examines an address in one of the accepted forms and fixes it
- // to include a port number if necessary.
- func fixHostPort(address string, defaultPort int) (fixed string, err error) {
- if len(address) == 0 {
- return "", errors.New("address string is empty")
- }
- // If the address is wrapped in brackets, append a port if necessary.
- if address[0] == '[' {
- end := strings.IndexByte(address, ']')
- switch {
- case end < 0:
- return "", errors.New("missing ']' in address")
- case end+1 == len(address):
- return fmt.Sprintf("%s:%d", address, defaultPort), nil
- case address[end+1] == ':':
- return address, nil
- default:
- return "", errors.New("unexpected character following ']' in address")
- }
- }
- // No colons? Must be a port-less IPv4 or domain address.
- last := strings.LastIndexByte(address, ':')
- if last < 0 {
- return fmt.Sprintf("%s:%d", address, defaultPort), nil
- }
- // Exactly one colon? A port have been included along with an IPv4 or
- // domain address. (IPv6 addresses are guaranteed to have more than one
- // colon.)
- prev := strings.LastIndexByte(address[:last], ':')
- if prev < 0 {
- return address, nil
- }
- // Two or more colons means we must have an IPv6 address without a port.
- return fmt.Sprintf("[%s]:%d", address, defaultPort), nil
- }
- // generateResponse processes NTP header fields along with the its receive
- // time to generate a Response record.
- func generateResponse(h *header, recvTime ntpTime, authErr error) *Response {
- r := &Response{
- Time: h.TransmitTime.Time(),
- ClockOffset: offset(h.OriginTime, h.ReceiveTime, h.TransmitTime, recvTime),
- RTT: rtt(h.OriginTime, h.ReceiveTime, h.TransmitTime, recvTime),
- Precision: toInterval(h.Precision),
- Version: h.getVersion(),
- Stratum: h.Stratum,
- ReferenceID: h.ReferenceID,
- ReferenceTime: h.ReferenceTime.Time(),
- RootDelay: h.RootDelay.Duration(),
- RootDispersion: h.RootDispersion.Duration(),
- Leap: h.getLeap(),
- MinError: minError(h.OriginTime, h.ReceiveTime, h.TransmitTime, recvTime),
- Poll: toInterval(h.Poll),
- authErr: authErr,
- }
- // 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 {
- a := int64(dst - org)
- b := int64(xmt - rec)
- rtt := a - b
- if rtt < 0 {
- rtt = 0
- }
- return ntpTime(rtt).Duration()
- }
- func offset(org, rec, xmt, dst ntpTime) time.Duration {
- // The inputs are 64-bit unsigned integer timestamps. These timestamps can
- // "roll over" at the end of an NTP era, which occurs approximately every
- // 136 years starting from the year 1900. To ensure an accurate offset
- // calculation when an era boundary is crossed, we need to take care that
- // the difference between two 64-bit timestamp values is accurately
- // calculated even when they are in neighboring eras.
- //
- // See: https://www.eecis.udel.edu/~mills/y2k.html
- a := int64(rec - org)
- b := int64(xmt - dst)
- offset := a + (b-a)/2
- if offset < 0 {
- return -ntpTime(-offset).Duration()
- }
- return ntpTime(offset).Duration()
- }
- 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 := [4]byte{
- byte(id >> 24),
- byte(id >> 16),
- byte(id >> 8),
- byte(id),
- }
- for _, ch := range b {
- if !isPrintable(ch) {
- return ""
- }
- }
- return string(b[:])
- }
|