github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/p2p/discover/dht/ntp.go (about) 1 // Contains the NTP time drift detection via the SNTP protocol: 2 // https://tools.ietf.org/html/rfc4330 3 4 package dht 5 6 import ( 7 "fmt" 8 "net" 9 "sort" 10 "strings" 11 "time" 12 13 log "github.com/sirupsen/logrus" 14 ) 15 16 const ( 17 ntpPool = "pool.ntp.org" // ntpPool is the NTP server to query for the current time 18 ntpChecks = 3 // Number of measurements to do against the NTP server 19 ) 20 21 // durationSlice attaches the methods of sort.Interface to []time.Duration, 22 // sorting in increasing order. 23 type durationSlice []time.Duration 24 25 func (s durationSlice) Len() int { return len(s) } 26 func (s durationSlice) Less(i, j int) bool { return s[i] < s[j] } 27 func (s durationSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 28 29 // checkClockDrift queries an NTP server for clock drifts and warns the user if 30 // one large enough is detected. 31 func checkClockDrift() { 32 drift, err := sntpDrift(ntpChecks) 33 if err != nil { 34 return 35 } 36 if drift < -driftThreshold || drift > driftThreshold { 37 warning := fmt.Sprintf("System clock seems off by %v, which can prevent network connectivity", drift) 38 howtofix := fmt.Sprintf("Please enable network time synchronisation in system settings") 39 separator := strings.Repeat("-", len(warning)) 40 41 log.WithFields(log.Fields{"module": logModule}).Warn(separator) 42 log.WithFields(log.Fields{"module": logModule}).Warn(warning) 43 log.WithFields(log.Fields{"module": logModule}).Warn(howtofix) 44 log.WithFields(log.Fields{"module": logModule}).Warn(separator) 45 } else { 46 log.WithFields(log.Fields{"module": logModule, "drift": drift}).Debug(fmt.Sprintf("Sanity NTP check reported all ok")) 47 } 48 } 49 50 // sntpDrift does a naive time resolution against an NTP server and returns the 51 // measured drift. This method uses the simple version of NTP. It's not precise 52 // but should be fine for these purposes. 53 // 54 // Note, it executes two extra measurements compared to the number of requested 55 // ones to be able to discard the two extremes as outliers. 56 func sntpDrift(measurements int) (time.Duration, error) { 57 // Resolve the address of the NTP server 58 addr, err := net.ResolveUDPAddr("udp", ntpPool+":123") 59 if err != nil { 60 return 0, err 61 } 62 // Construct the time request (empty package with only 2 fields set): 63 // Bits 3-5: Protocol version, 3 64 // Bits 6-8: Mode of operation, client, 3 65 request := make([]byte, 48) 66 request[0] = 3<<3 | 3 67 68 // Execute each of the measurements 69 drifts := []time.Duration{} 70 for i := 0; i < measurements+2; i++ { 71 // Dial the NTP server and send the time retrieval request 72 conn, err := net.DialUDP("udp", nil, addr) 73 if err != nil { 74 return 0, err 75 } 76 defer conn.Close() 77 78 sent := time.Now() 79 if _, err = conn.Write(request); err != nil { 80 return 0, err 81 } 82 // Retrieve the reply and calculate the elapsed time 83 conn.SetDeadline(time.Now().Add(5 * time.Second)) 84 85 reply := make([]byte, 48) 86 if _, err = conn.Read(reply); err != nil { 87 return 0, err 88 } 89 elapsed := time.Since(sent) 90 91 // Reconstruct the time from the reply data 92 sec := uint64(reply[43]) | uint64(reply[42])<<8 | uint64(reply[41])<<16 | uint64(reply[40])<<24 93 frac := uint64(reply[47]) | uint64(reply[46])<<8 | uint64(reply[45])<<16 | uint64(reply[44])<<24 94 95 nanosec := sec*1e9 + (frac*1e9)>>32 96 97 t := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nanosec)).Local() 98 99 // Calculate the drift based on an assumed answer time of RRT/2 100 drifts = append(drifts, sent.Sub(t)+elapsed/2) 101 } 102 // Calculate average drif (drop two extremities to avoid outliers) 103 sort.Sort(durationSlice(drifts)) 104 105 drift := time.Duration(0) 106 for i := 1; i < len(drifts)-1; i++ { 107 drift += drifts[i] 108 } 109 return drift / time.Duration(measurements), nil 110 }