in cmd/ntpcheck/cmd/utils.go [94:206]
func ntpDate(remoteServerAddr string, remoteServerPort string, requests int) error {
timeout := 5 * time.Second
addr := net.JoinHostPort(remoteServerAddr, remoteServerPort)
conn, err := net.DialTimeout("udp", addr, timeout)
if err != nil {
return fmt.Errorf("failed to connect to %s: %w", addr, err)
}
defer conn.Close()
// get connection file descriptor
connFd, err := timestamp.ConnFd(conn.(*net.UDPConn))
if err != nil {
return err
}
// Allow reading of kernel timestamps via socket
if err := timestamp.EnableSWTimestampsRx(connFd); err != nil {
return err
}
err = unix.SetNonblock(connFd, false)
if err != nil {
return err
}
var sumDelay int64
var sumOffset int64
for i := 0; i < requests; i++ {
clientTransmitTime := time.Now()
sec, frac := ntp.Time(clientTransmitTime)
clientWireTransmitTime := ntp.Unix(sec, frac)
log.Debugf("Client TX timestamp (ntp): %v\n", clientWireTransmitTime)
request := &ntp.Packet{
Settings: 0x1B,
TxTimeSec: sec,
TxTimeFrac: frac,
}
if err := binary.Write(conn, binary.BigEndian, request); err != nil {
return fmt.Errorf("failed to send request, %w", err)
}
var response *ntp.Packet
var clientReceiveTime time.Time
var buf []byte
blockingRead := make(chan bool, 1)
go func() {
// This calls unix.Recvmsg which has no timeout
buf, _, clientReceiveTime, err = timestamp.ReadPacketWithRXTimestamp(connFd)
if err != nil {
blockingRead <- true
return
}
response, err = ntp.BytesToPacket(buf)
blockingRead <- true
}()
select {
case <-blockingRead:
if err != nil {
return err
}
case <-time.After(timeout):
return fmt.Errorf("timeout waiting for reply from server for %v", timeout)
}
serverReceiveTime := ntp.Unix(response.RxTimeSec, response.RxTimeFrac)
serverTransmitTime := ntp.Unix(response.TxTimeSec, response.TxTimeFrac)
originTime := ntp.Unix(response.OrigTimeSec, response.OrigTimeFrac)
log.Debugf("Origin TX timestamp (T1): %v", originTime)
log.Debugf("Server RX timestamp (T2): %v", serverReceiveTime)
log.Debugf("Server TX timestamp (T3): %v", serverTransmitTime)
log.Debugf("Client RX timestamp (T4): %v", clientReceiveTime)
// sanity check: origin time must be same as client transmit time
if response.OrigTimeSec != sec || response.OrigTimeFrac != frac {
log.Errorf("Client TX timestamp %v not equal to Origin TX timestamp %v", clientTransmitTime, originTime)
}
delay := ntp.RoundTripDelay(originTime, serverReceiveTime, serverTransmitTime, clientReceiveTime)
offset := ntp.Offset(originTime, serverReceiveTime, serverTransmitTime, clientReceiveTime)
correctTime := ntp.CorrectTime(clientReceiveTime, offset)
sumDelay += delay
sumOffset += offset
if i == requests-1 {
fmt.Printf("\nServer: %s, Stratum: %d, Requests %d\n", addr, response.Stratum, requests)
fmt.Printf("Last Request:\n")
fmt.Printf("Offset: %fs (%sus) | Delay: %fs (%sus)\n",
float64(offset)/float64(time.Second.Nanoseconds()),
stripZeroes(math.Round(float64(offset)/float64(time.Microsecond.Nanoseconds()))),
float64(delay)/float64(time.Second.Nanoseconds()),
stripZeroes(math.Round(float64(delay)/float64(time.Microsecond.Nanoseconds()))))
fmt.Printf("Correct Time is %s\n\n", correctTime)
}
}
avgDelay := float64(sumDelay) / float64(requests)
avgOffset := float64(sumOffset) / float64(requests)
fmt.Printf("Average (%d requests):\n", requests)
fmt.Printf("Offset: %fs (%sus) | Delay: %fs (%sus)\n",
avgOffset/float64(time.Second.Nanoseconds()),
stripZeroes(math.Round(avgOffset/float64(time.Microsecond.Nanoseconds()))),
avgDelay/float64(time.Second.Nanoseconds()),
stripZeroes(math.Round(avgDelay/float64(time.Microsecond.Nanoseconds()))))
return nil
}