github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/ntp/service.go (about)

     1  package ntp
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/inazumav/sing-box/adapter"
     9  	"github.com/inazumav/sing-box/common/dialer"
    10  	"github.com/inazumav/sing-box/common/settings"
    11  	C "github.com/inazumav/sing-box/constant"
    12  	"github.com/inazumav/sing-box/option"
    13  	"github.com/sagernet/sing/common"
    14  	E "github.com/sagernet/sing/common/exceptions"
    15  	"github.com/sagernet/sing/common/logger"
    16  	M "github.com/sagernet/sing/common/metadata"
    17  	N "github.com/sagernet/sing/common/network"
    18  	"github.com/sagernet/sing/common/ntp"
    19  )
    20  
    21  var _ ntp.TimeService = (*Service)(nil)
    22  
    23  type Service struct {
    24  	ctx           context.Context
    25  	cancel        common.ContextCancelCauseFunc
    26  	server        M.Socksaddr
    27  	writeToSystem bool
    28  	dialer        N.Dialer
    29  	logger        logger.Logger
    30  	ticker        *time.Ticker
    31  	clockOffset   time.Duration
    32  }
    33  
    34  func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) (*Service, error) {
    35  	ctx, cancel := common.ContextWithCancelCause(ctx)
    36  	server := options.ServerOptions.Build()
    37  	if server.Port == 0 {
    38  		server.Port = 123
    39  	}
    40  	var interval time.Duration
    41  	if options.Interval > 0 {
    42  		interval = time.Duration(options.Interval)
    43  	} else {
    44  		interval = 30 * time.Minute
    45  	}
    46  	outboundDialer, err := dialer.New(router, options.DialerOptions)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	return &Service{
    51  		ctx:           ctx,
    52  		cancel:        cancel,
    53  		server:        server,
    54  		writeToSystem: options.WriteToSystem,
    55  		dialer:        outboundDialer,
    56  		logger:        logger,
    57  		ticker:        time.NewTicker(interval),
    58  	}, nil
    59  }
    60  
    61  func (s *Service) Start() error {
    62  	err := s.update()
    63  	if err != nil {
    64  		return E.Cause(err, "initialize time")
    65  	}
    66  	s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
    67  	go s.loopUpdate()
    68  	return nil
    69  }
    70  
    71  func (s *Service) Close() error {
    72  	s.ticker.Stop()
    73  	s.cancel(os.ErrClosed)
    74  	return nil
    75  }
    76  
    77  func (s *Service) TimeFunc() func() time.Time {
    78  	return func() time.Time {
    79  		return time.Now().Add(s.clockOffset)
    80  	}
    81  }
    82  
    83  func (s *Service) loopUpdate() {
    84  	for {
    85  		select {
    86  		case <-s.ctx.Done():
    87  			return
    88  		case <-s.ticker.C:
    89  		}
    90  		err := s.update()
    91  		if err == nil {
    92  			s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
    93  		} else {
    94  			s.logger.Warn("update time: ", err)
    95  		}
    96  	}
    97  }
    98  
    99  func (s *Service) update() error {
   100  	response, err := ntp.Exchange(s.ctx, s.dialer, s.server)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	s.clockOffset = response.ClockOffset
   105  	if s.writeToSystem {
   106  		writeErr := settings.SetSystemTime(s.TimeFunc()())
   107  		if writeErr != nil {
   108  			s.logger.Warn("write time to system: ", writeErr)
   109  		}
   110  	}
   111  	return nil
   112  }