func ntpDate()

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
}