github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/clock/manager.go (about) 1 // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package clock 4 5 import ( 6 "encoding/binary" 7 "fmt" 8 "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" 9 teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 10 "github.com/TeaOSLab/EdgeNode/internal/events" 11 "github.com/TeaOSLab/EdgeNode/internal/goman" 12 "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 13 executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec" 14 timeutil "github.com/iwind/TeaGo/utils/time" 15 "net" 16 "runtime" 17 "time" 18 ) 19 20 var hasSynced = false 21 var sharedClockManager = NewClockManager() 22 23 func init() { 24 if !teaconst.IsMain { 25 return 26 } 27 28 events.On(events.EventLoaded, func() { 29 goman.New(sharedClockManager.Start) 30 }) 31 events.On(events.EventReload, func() { 32 if !hasSynced { 33 hasSynced = true 34 35 goman.New(func() { 36 err := sharedClockManager.Sync() 37 if err != nil { 38 remotelogs.Warn("CLOCK", "sync clock failed: "+err.Error()) 39 } 40 }) 41 } 42 }) 43 } 44 45 type ClockManager struct { 46 lastFailAt int64 47 } 48 49 func NewClockManager() *ClockManager { 50 return &ClockManager{} 51 } 52 53 // Start 启动 54 func (this *ClockManager) Start() { 55 var ticker = time.NewTicker(1 * time.Hour) 56 for range ticker.C { 57 err := this.Sync() 58 if err != nil { 59 var currentTimestamp = time.Now().Unix() 60 61 // 每天只提醒一次错误 62 if currentTimestamp-this.lastFailAt > 86400 { 63 remotelogs.Warn("CLOCK", "sync clock failed: "+err.Error()) 64 this.lastFailAt = currentTimestamp 65 } 66 } 67 } 68 } 69 70 // Sync 自动校对时间 71 func (this *ClockManager) Sync() error { 72 if runtime.GOOS != "linux" { 73 return nil 74 } 75 76 nodeConfig, _ := nodeconfigs.SharedNodeConfig() 77 if nodeConfig == nil { 78 return nil 79 } 80 81 var config = nodeConfig.Clock 82 if config == nil || !config.AutoSync { 83 return nil 84 } 85 86 // check chrony 87 if config.CheckChrony { 88 chronycExe, err := executils.LookPath("chronyc") 89 if err == nil && len(chronycExe) > 0 { 90 var chronyCmd = executils.NewTimeoutCmd(3*time.Second, chronycExe, "tracking") 91 err = chronyCmd.Run() 92 if err == nil { 93 return nil 94 } 95 } 96 } 97 98 var server = config.Server 99 if len(server) == 0 { 100 server = "pool.ntp.org" 101 } 102 103 ntpdate, err := executils.LookPath("ntpdate") 104 if err != nil { 105 // 使用 date 命令设置 106 // date --set TIME 107 dateExe, err := executils.LookPath("date") 108 if err == nil { 109 currentTime, err := this.ReadServer(server) 110 if err != nil { 111 return fmt.Errorf("read server failed: %w", err) 112 } 113 114 var delta = time.Now().Unix() - currentTime.Unix() 115 if delta > 1 || delta < -1 { // 相差比较大的时候才会同步 116 var err = executils.NewTimeoutCmd(3*time.Second, dateExe, "--set", timeutil.Format("Y-m-d H:i:s+P", currentTime)). 117 Run() 118 if err != nil { 119 return err 120 } 121 } 122 } 123 124 return nil 125 } 126 if len(ntpdate) > 0 { 127 return this.syncNtpdate(ntpdate, server) 128 } 129 130 return nil 131 } 132 133 func (this *ClockManager) syncNtpdate(ntpdate string, server string) error { 134 var cmd = executils.NewTimeoutCmd(30*time.Second, ntpdate, server) 135 cmd.WithStderr() 136 err := cmd.Run() 137 if err != nil { 138 return fmt.Errorf("%w: %s", err, cmd.Stderr()) 139 } 140 141 return nil 142 } 143 144 // ReadServer 参考自:https://medium.com/learning-the-go-programming-language/lets-make-an-ntp-client-in-go-287c4b9a969f 145 func (this *ClockManager) ReadServer(server string) (time.Time, error) { 146 conn, err := net.Dial("udp", server+":123") 147 if err != nil { 148 return time.Time{}, fmt.Errorf("connect to server failed: %w", err) 149 } 150 defer func() { 151 _ = conn.Close() 152 }() 153 err = conn.SetDeadline(time.Now().Add(5 * time.Second)) 154 if err != nil { 155 return time.Time{}, err 156 } 157 158 // configure request settings by specifying the first byte as 159 // 00 011 011 (or 0x1B) 160 // | | +-- client mode (3) 161 // | + ----- version (3) 162 // + -------- leap year indicator, 0 no warning 163 164 var req = &NTPPacket{Settings: 0x1B} 165 err = binary.Write(conn, binary.BigEndian, req) 166 if err != nil { 167 return time.Time{}, fmt.Errorf("write request failed: %w", err) 168 } 169 170 var resp = &NTPPacket{} 171 err = binary.Read(conn, binary.BigEndian, resp) 172 if err != nil { 173 return time.Time{}, fmt.Errorf("write server response failed: %w", err) 174 } 175 176 const ntpEpochOffset = 2208988800 177 178 var secs = float64(resp.TxTimeSec) - ntpEpochOffset 179 var nanos = (int64(resp.TxTimeFrac) * 1e9) >> 32 180 return time.Unix(int64(secs), nanos), nil 181 }