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  }