github.com/sagernet/sing@v0.2.6/common/ntp/service.go (about) 1 package ntp 2 3 import ( 4 "context" 5 "os" 6 "time" 7 8 "github.com/sagernet/sing/common" 9 E "github.com/sagernet/sing/common/exceptions" 10 "github.com/sagernet/sing/common/logger" 11 M "github.com/sagernet/sing/common/metadata" 12 N "github.com/sagernet/sing/common/network" 13 ) 14 15 const TimeLayout = "2006-01-02 15:04:05 -0700" 16 17 type TimeService interface { 18 TimeFunc() func() time.Time 19 } 20 21 type Options struct { 22 Context context.Context 23 Server M.Socksaddr 24 Interval time.Duration 25 Dialer N.Dialer 26 Logger logger.Logger 27 } 28 29 var _ TimeService = (*Service)(nil) 30 31 type Service struct { 32 ctx context.Context 33 cancel common.ContextCancelCauseFunc 34 server M.Socksaddr 35 dialer N.Dialer 36 logger logger.Logger 37 ticker *time.Ticker 38 clockOffset time.Duration 39 } 40 41 func NewService(options Options) *Service { 42 ctx := options.Context 43 if ctx == nil { 44 ctx = context.Background() 45 } 46 ctx, cancel := common.ContextWithCancelCause(ctx) 47 destination := options.Server 48 if !destination.IsValid() { 49 destination = M.Socksaddr{ 50 Fqdn: "time.google.com", 51 } 52 } 53 if destination.Port == 0 { 54 destination.Port = 123 55 } 56 var interval time.Duration 57 if options.Interval > 0 { 58 interval = options.Interval 59 } else { 60 interval = 30 * time.Minute 61 } 62 var dialer N.Dialer 63 if options.Dialer != nil { 64 dialer = options.Dialer 65 } else { 66 dialer = N.SystemDialer 67 } 68 return &Service{ 69 ctx: ctx, 70 cancel: cancel, 71 server: destination, 72 dialer: dialer, 73 logger: options.Logger, 74 ticker: time.NewTicker(interval), 75 } 76 } 77 78 func (s *Service) Start() error { 79 err := s.update() 80 if err != nil { 81 return E.Cause(err, "initialize time") 82 } 83 s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(TimeLayout)) 84 go s.loopUpdate() 85 return nil 86 } 87 88 func (s *Service) Close() error { 89 s.ticker.Stop() 90 s.cancel(os.ErrClosed) 91 return nil 92 } 93 94 func (s *Service) TimeFunc() func() time.Time { 95 return func() time.Time { 96 return time.Now().Add(s.clockOffset) 97 } 98 } 99 100 func (s *Service) loopUpdate() { 101 for { 102 select { 103 case <-s.ctx.Done(): 104 return 105 case <-s.ticker.C: 106 } 107 err := s.update() 108 if err == nil { 109 s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(TimeLayout)) 110 } else { 111 s.logger.Warn("update time: ", err) 112 } 113 } 114 } 115 116 func (s *Service) update() error { 117 response, err := Exchange(s.ctx, s.dialer, s.server) 118 if err != nil { 119 return err 120 } 121 s.clockOffset = response.ClockOffset 122 return nil 123 }