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  }