Files
EdgeNode/internal/utils/clock/manager.go
GoEdgeLab c19be78e0d v1.4.1
2024-07-27 15:42:50 +08:00

183 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2022 GoEdge goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cloud .
package clock
import (
"encoding/binary"
"fmt"
"net"
"runtime"
"time"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
timeutil "github.com/iwind/TeaGo/utils/time"
)
var hasSynced = false
var sharedClockManager = NewClockManager()
func init() {
if !teaconst.IsMain {
return
}
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.Warn("CLOCK", "sync clock failed: "+err.Error())
}
})
}
})
}
type ClockManager struct {
lastFailAt int64
}
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 {
var currentTimestamp = time.Now().Unix()
// 每天只提醒一次错误
if currentTimestamp-this.lastFailAt > 86400 {
remotelogs.Warn("CLOCK", "sync clock failed: "+err.Error())
this.lastFailAt = currentTimestamp
}
}
}
}
// 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
}
// check chrony
if config.CheckChrony {
chronycExe, err := executils.LookPath("chronyc")
if err == nil && len(chronycExe) > 0 {
var chronyCmd = executils.NewTimeoutCmd(3*time.Second, chronycExe, "tracking")
err = chronyCmd.Run()
if err == nil {
return nil
}
}
}
var server = config.Server
if len(server) == 0 {
server = "pool.ntp.org"
}
ntpdate, err := executils.LookPath("ntpdate")
if err != nil {
// 使用 date 命令设置
// date --set TIME
dateExe, err := executils.LookPath("date")
if err == nil {
currentTime, err := this.ReadServer(server)
if err != nil {
return fmt.Errorf("read server failed: %w", err)
}
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 fmt.Errorf("%w: %s", err, cmd.Stderr())
}
return nil
}
// ReadServer 参考自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{}, fmt.Errorf("connect to server failed: %w", err)
}
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{}, fmt.Errorf("write request failed: %w", err)
}
var resp = &NTPPacket{}
err = binary.Read(conn, binary.BigEndian, resp)
if err != nil {
return time.Time{}, fmt.Errorf("write server response failed: %w", err)
}
const ntpEpochOffset = 2208988800
var secs = float64(resp.TxTimeSec) - ntpEpochOffset
var nanos = (int64(resp.TxTimeFrac) * 1e9) >> 32
return time.Unix(int64(secs), nanos), nil
}