Skip to content

Commit 5ad77b2

Browse files
committed
NTPv3/4 queries can request if NTPv5 is supported
1 parent e51a24b commit 5ad77b2

3 files changed

Lines changed: 65 additions & 11 deletions

File tree

ntp.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,12 @@ type QueryOptions struct {
178178
RequestReferenceID ReferenceIDRequest
179179

180180
// RequestSupportedVersions indicates whether to request which versions of
181-
// the NTP protocol are supported by the server in its response. Used only
182-
// in NTPv5.
181+
// the NTP protocol are supported by the server. When used with NTPv5, the
182+
// response will list all supported versions. When used with NTPv3 or
183+
// NTPv4, the response's supported versions will include the version used
184+
// in the query as well as 5 if the server supports it. The response's
185+
// ReferenceTime field will be invalid in NTPv3 and NTPv4 if this option
186+
// is set.
183187
RequestSupportedVersions bool
184188

185189
// RequestCorrection indicates whether to request delay corrections from
@@ -484,6 +488,7 @@ func (r *Response) Log(w io.Writer) {
484488
fmt.Fprintf(w, " RootDelay: %s\n", r.RootDelay)
485489
fmt.Fprintf(w, " RootDisp: %s\n", r.RootDispersion)
486490
fmt.Fprintf(w, " RootDist: %s\n", r.RootDistance)
491+
fmt.Fprintf(w, " Supported: %v\n", r.SupportedVersions)
487492
fmt.Fprintf(w, " MinError: %s\n", r.MinError)
488493
fmt.Fprintf(w, " RefTime: %s\n", fmtTime(r.ReferenceTime))
489494
if r.Version == 5 {
@@ -492,7 +497,6 @@ func (r *Response) Log(w io.Writer) {
492497
fmt.Fprintf(w, " Offsets: %s\n", fmtTimescaleOffsets(r.TimescaleOffsets))
493498
fmt.Fprintf(w, " MonoOffset: %s\n", r.MonotonicOffset)
494499
fmt.Fprintf(w, " MonoEpoch: %s\n", fmtEpoch(r.MonotonicEpochID))
495-
fmt.Fprintf(w, " Supported: %v\n", r.SupportedVersions)
496500
fmt.Fprintf(w, " SrvCookie: %s", fmtCookie(r.ServerCookie))
497501
} else {
498502
fmt.Fprintf(w, " RefID: %s (0x%08x)\n", r.ReferenceString(), r.ReferenceID)
@@ -559,9 +563,9 @@ func (r *Response) Validate() error {
559563
return ErrInvalidStratum
560564
}
561565

562-
// Estimate the "freshness" of the time. If it exceeds the maximum
563-
// polling interval (~36 hours), then it cannot be considered "fresh".
564-
if r.Version < 5 || (r.Version == 5 && !r.ReferenceTime.IsZero()) {
566+
// Estimate the "freshness" of the time. If it exceeds the maximum polling
567+
// interval (~36 hours), then it cannot be considered "fresh".
568+
if (r.Version < 5 && r.ReferenceTime != ntpEra0) || (r.Version == 5 && !r.ReferenceTime.IsZero()) {
565569
if freshness := r.Time.Sub(r.ReferenceTime); freshness > maxPollInterval {
566570
return ErrServerClockFreshness
567571
}
@@ -1009,8 +1013,8 @@ func fmtFlags(flags ResponseFlags) string {
10091013
}
10101014

10111015
func fmtTime(value time.Time) string {
1012-
if value.IsZero() {
1013-
return "<zero>"
1016+
if value.Equal(ntpEra0) {
1017+
return "<invalid>"
10141018
}
10151019
return value.Format(timeFormat)
10161020
}

ntp4.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ type modeV4 uint8
2121
const (
2222
clientMode modeV4 = 3
2323
serverMode modeV4 = 4
24+
25+
// Sentinel value sent in the ReferenceTime field to request whether the
26+
// server supports NTPv5. If server responds with the same value, then it
27+
// also supports NTPv5. This value is the ASCII representation of
28+
// "NTP5DRFT". Once the NTPv5 protocol is finalized, the value will be
29+
// changed to "NTP5NTP5".
30+
v5sentinel = 0x4e54503544524654
2431
)
2532

2633
// timeShortV4 is a 32-bit fixed-point (Q16.16) representation of the number
@@ -191,6 +198,18 @@ func queryV4(conn net.Conn, opt *QueryOptions) (*Response, error) {
191198
Time: m.TransmitTime.TimeV4(),
192199
}
193200

201+
// If NTPv5 support was requested, check for it.
202+
if opt.RequestSupportedVersions {
203+
r.SupportedVersions = []int{opt.Version}
204+
205+
// If the server responded to the NTPv5 support request in its
206+
// ReferenceTime field, add version 5 and clear the ReferenceTime.
207+
if m.ReferenceTime == v5sentinel {
208+
r.SupportedVersions = append(r.SupportedVersions, 5)
209+
r.ReferenceTime = ntpEra0
210+
}
211+
}
212+
194213
// Calculate root distance.
195214
r.RootDistance = rootDistance(r.RTT, r.RootDelay, r.RootDispersion)
196215

@@ -221,6 +240,12 @@ func buildV4Request(opt *QueryOptions) (*bytes.Buffer, error) {
221240
m.setMode(clientMode)
222241
m.setLeap(LeapNoWarning)
223242

243+
// To request whether the server supports NTPv5, set the reference time to
244+
// "NTP5DRFT". If the server supports it, it will echo this value back.
245+
if opt.RequestSupportedVersions {
246+
m.ReferenceTime = v5sentinel
247+
}
248+
224249
// Write the message to a buffer.
225250
buf := new(bytes.Buffer)
226251
binary.Write(buf, binary.BigEndian, m.LiVnMode)

ntp4_test.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ func TestOnlineBadServerPort(t *testing.T) {
2323
assert.NotNil(t, err)
2424
}
2525

26-
func TestOnlineV3Query(t *testing.T) {
27-
r, err := QueryWithOptions(host, QueryOptions{Version: 3})
26+
func TestOnlineV3QueryWithSupportedVersions(t *testing.T) {
27+
opt := QueryOptions{
28+
Version: 3,
29+
RequestSupportedVersions: true,
30+
}
31+
r, err := QueryWithOptions(host, opt)
2832
if isError(t, host, err) {
2933
return
3034
}
@@ -37,7 +41,28 @@ func TestOnlineV3Query(t *testing.T) {
3741
}
3842

3943
func TestOnlineV4Query(t *testing.T) {
40-
r, err := QueryWithOptions(host, QueryOptions{Version: 4})
44+
opt := QueryOptions{
45+
Version: 4,
46+
RequestSupportedVersions: false,
47+
}
48+
r, err := QueryWithOptions(host, opt)
49+
if isError(t, host, err) {
50+
return
51+
}
52+
assertValid(t, r)
53+
54+
var buf bytes.Buffer
55+
fmt.Fprintf(&buf, "\n Address: %s\n", host)
56+
r.Log(&buf)
57+
t.Log(buf.String())
58+
}
59+
60+
func TestOnlineV4QueryWithSupportedVersions(t *testing.T) {
61+
opt := QueryOptions{
62+
Version: 4,
63+
RequestSupportedVersions: true,
64+
}
65+
r, err := QueryWithOptions(host, opt)
4166
if isError(t, host, err) {
4267
return
4368
}

0 commit comments

Comments
 (0)