diff --git a/internal/nodes/node.go b/internal/nodes/node.go index 1f2bea7..73c0b56 100644 --- a/internal/nodes/node.go +++ b/internal/nodes/node.go @@ -23,7 +23,7 @@ import ( "github.com/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/trackers" "github.com/TeaOSLab/EdgeNode/internal/utils" - "github.com/TeaOSLab/EdgeNode/internal/utils/clock" + _ "github.com/TeaOSLab/EdgeNode/internal/utils/clock" // 触发时钟更新 "github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/andybalholm/brotli" "github.com/iwind/TeaGo/Tea" @@ -170,11 +170,6 @@ func (this *Node) Start() { NewNodeStatusExecutor().Listen() }) - // 同步时间 - goman.New(func() { - clock.Start() - }) - // 读取配置 nodeConfig, err := nodeconfigs.SharedNodeConfig() if err != nil { diff --git a/internal/utils/clock/manager.go b/internal/utils/clock/manager.go new file mode 100644 index 0000000..e96e3e5 --- /dev/null +++ b/internal/utils/clock/manager.go @@ -0,0 +1,158 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package clock + +import ( + "encoding/binary" + "errors" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeNode/internal/events" + "github.com/TeaOSLab/EdgeNode/internal/goman" + "github.com/TeaOSLab/EdgeNode/internal/remotelogs" + executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec" + timeutil "github.com/iwind/TeaGo/utils/time" + "net" + "os/exec" + "runtime" + "time" +) + +var hasSynced = false +var sharedClockManager = NewClockManager() + +func init() { + events.On(events.EventLoaded, func() { + goman.New(sharedClockManager.Start) + }) + events.On(events.EventReload, func() { + if !hasSynced { + hasSynced = true + + goman.New(func() { + err := sharedClockManager.Sync() + if err != nil { + remotelogs.Error("CLOCK", "sync clock failed: "+err.Error()) + } + }) + } + }) +} + +type ClockManager struct { +} + +func NewClockManager() *ClockManager { + return &ClockManager{} +} + +// Start 启动 +func (this *ClockManager) Start() { + var ticker = time.NewTicker(1 * time.Hour) + for range ticker.C { + err := this.Sync() + if err != nil { + remotelogs.Error("CLOCK", "sync clock failed: "+err.Error()) + } + } +} + +// Sync 自动校对时间 +func (this *ClockManager) Sync() error { + if runtime.GOOS != "linux" { + return nil + } + + nodeConfig, _ := nodeconfigs.SharedNodeConfig() + if nodeConfig == nil { + return nil + } + + var config = nodeConfig.Clock + if config == nil || !config.AutoSync { + return nil + } + + var server = config.Server + if len(server) == 0 { + server = "pool.ntp.org" + } + + ntpdate, err := exec.LookPath("ntpdate") + if err != nil { + // 使用 date 命令设置 + // date --set TIME + dateExe, err := exec.LookPath("date") + if err == nil { + currentTime, err := this.readServer(server) + if err != nil { + return errors.New("read server failed: " + err.Error()) + } + + var delta = time.Now().Unix() - currentTime.Unix() + if delta > 1 || delta < -1 { // 相差比较大的时候才会同步 + var err = executils.NewTimeoutCmd(3*time.Second, dateExe, "--set", timeutil.Format("Y-m-d H:i:s+P", currentTime)). + Run() + if err != nil { + return err + } + } + } + + return nil + } + if len(ntpdate) > 0 { + return this.syncNtpdate(ntpdate, server) + } + + return nil +} + +func (this *ClockManager) syncNtpdate(ntpdate string, server string) error { + var cmd = executils.NewTimeoutCmd(30*time.Second, ntpdate, server) + cmd.WithStderr() + err := cmd.Run() + if err != nil { + return errors.New(err.Error() + ": " + cmd.Stderr()) + } + + return nil +} + +// 参考自:https://medium.com/learning-the-go-programming-language/lets-make-an-ntp-client-in-go-287c4b9a969f +func (this *ClockManager) readServer(server string) (time.Time, error) { + conn, err := net.Dial("udp", server+":123") + if err != nil { + return time.Time{}, errors.New("connect to server failed: " + err.Error()) + } + defer func() { + _ = conn.Close() + }() + err = conn.SetDeadline(time.Now().Add(5 * time.Second)) + if err != nil { + return time.Time{}, err + } + + // configure request settings by specifying the first byte as + // 00 011 011 (or 0x1B) + // | | +-- client mode (3) + // | + ----- version (3) + // + -------- leap year indicator, 0 no warning + + var req = &NTPPacket{Settings: 0x1B} + err = binary.Write(conn, binary.BigEndian, req) + if err != nil { + return time.Time{}, errors.New("write request failed: " + err.Error()) + } + + var resp = &NTPPacket{} + err = binary.Read(conn, binary.BigEndian, resp) + if err != nil { + return time.Time{}, errors.New("write server response failed: " + err.Error()) + } + + const ntpEpochOffset = 2208988800 + + var secs = float64(resp.TxTimeSec) - ntpEpochOffset + var nanos = (int64(resp.TxTimeFrac) * 1e9) >> 32 + return time.Unix(int64(secs), nanos), nil +} diff --git a/internal/utils/clock/manager_test.go b/internal/utils/clock/manager_test.go new file mode 100644 index 0000000..38e1fcb --- /dev/null +++ b/internal/utils/clock/manager_test.go @@ -0,0 +1,11 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package clock_test + +import ( + "testing" +) + +func TestReadServer(t *testing.T) { + +} diff --git a/internal/utils/clock/ntp_packet.go b/internal/utils/clock/ntp_packet.go new file mode 100644 index 0000000..d9d34f8 --- /dev/null +++ b/internal/utils/clock/ntp_packet.go @@ -0,0 +1,21 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package clock + +type NTPPacket struct { + Settings uint8 // leap yr indicator, ver number, and mode + Stratum uint8 // stratum of local clock + Poll int8 // poll exponent + Precision int8 // precision exponent + RootDelay uint32 // root delay + RootDispersion uint32 // root dispersion + ReferenceID uint32 // reference id + RefTimeSec uint32 // reference timestamp sec + RefTimeFrac uint32 // reference timestamp fractional + OrigTimeSec uint32 // origin time secs + OrigTimeFrac uint32 // origin time fractional + RxTimeSec uint32 // receive time secs + RxTimeFrac uint32 // receive time frac + TxTimeSec uint32 // transmit time secs + TxTimeFrac uint32 // transmit time frac +} diff --git a/internal/utils/clock/utils.go b/internal/utils/clock/utils.go deleted file mode 100644 index ac40b99..0000000 --- a/internal/utils/clock/utils.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . - -package clock - -import ( - "errors" - "github.com/TeaOSLab/EdgeNode/internal/remotelogs" - executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec" - "os/exec" - "runtime" - "time" -) - -// Start TODO 需要可以在集群中配置 -func Start() { - // sync once - err := Sync() - if err != nil { - remotelogs.Warn("CLOCK", "sync time clock failed: "+err.Error()) - } - - var ticker = time.NewTicker(1 * time.Hour) - for range ticker.C { - err := Sync() - if err != nil { - // ignore error - } - } -} - -// Sync 自动校对时间 -func Sync() error { - if runtime.GOOS != "linux" { - return nil - } - - ntpdate, err := exec.LookPath("ntpdate") - if err != nil { - return nil - } - if len(ntpdate) > 0 { - return syncNtpdate(ntpdate) - } - - return nil -} - -func syncNtpdate(ntpdate string) error { - var cmd = executils.NewTimeoutCmd(30*time.Second, ntpdate, "pool.ntp.org") - cmd.WithStderr() - err := cmd.Run() - if err != nil { - return errors.New(err.Error() + ": " + cmd.Stderr()) - } - - return nil -}