github.com/kelleygo/clashcore@v1.0.2/ntp/service.go (about)

     1  package ntp
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/kelleygo/clashcore/component/dialer"
     9  	"github.com/kelleygo/clashcore/component/proxydialer"
    10  	"github.com/kelleygo/clashcore/log"
    11  
    12  	M "github.com/sagernet/sing/common/metadata"
    13  	"github.com/sagernet/sing/common/ntp"
    14  )
    15  
    16  var offset time.Duration
    17  var service *Service
    18  
    19  type Service struct {
    20  	server         M.Socksaddr
    21  	dialer         proxydialer.SingDialer
    22  	ticker         *time.Ticker
    23  	ctx            context.Context
    24  	cancel         context.CancelFunc
    25  	mu             sync.Mutex
    26  	syncSystemTime bool
    27  	running        bool
    28  }
    29  
    30  func ReCreateNTPService(server string, interval time.Duration, dialerProxy string, syncSystemTime bool) {
    31  	if service != nil {
    32  		service.Stop()
    33  	}
    34  	ctx, cancel := context.WithCancel(context.Background())
    35  	service = &Service{
    36  		server:         M.ParseSocksaddr(server),
    37  		dialer:         proxydialer.NewByNameSingDialer(dialerProxy, dialer.NewDialer()),
    38  		ticker:         time.NewTicker(interval * time.Minute),
    39  		ctx:            ctx,
    40  		cancel:         cancel,
    41  		syncSystemTime: syncSystemTime,
    42  	}
    43  	service.Start()
    44  }
    45  
    46  func (srv *Service) Start() {
    47  	srv.mu.Lock()
    48  	defer srv.mu.Unlock()
    49  	log.Infoln("NTP service start, sync system time is %t", srv.syncSystemTime)
    50  	err := srv.update()
    51  	if err != nil {
    52  		log.Errorln("Initialize NTP time failed: %s", err)
    53  		return
    54  	}
    55  	service.running = true
    56  	go srv.loopUpdate()
    57  }
    58  
    59  func (srv *Service) Stop() {
    60  	srv.mu.Lock()
    61  	defer srv.mu.Unlock()
    62  	if service.running {
    63  		srv.ticker.Stop()
    64  		srv.cancel()
    65  		service.running = false
    66  	}
    67  }
    68  
    69  func (srv *Service) Running() bool {
    70  	if srv == nil {
    71  		return false
    72  	}
    73  	srv.mu.Lock()
    74  	defer srv.mu.Unlock()
    75  	return srv.running
    76  }
    77  
    78  func (srv *Service) update() error {
    79  	var response *ntp.Response
    80  	var err error
    81  	for i := 0; i < 3; i++ {
    82  		if response, err = ntp.Exchange(context.Background(), srv.dialer, srv.server); err == nil {
    83  			break
    84  		}
    85  		if i == 2 {
    86  			return err
    87  		}
    88  	}
    89  	offset = response.ClockOffset
    90  	if offset > time.Duration(0) {
    91  		log.Infoln("System clock is ahead of NTP time by %s", offset)
    92  	} else if offset < time.Duration(0) {
    93  		log.Infoln("System clock is behind NTP time by %s", -offset)
    94  	}
    95  	if srv.syncSystemTime {
    96  		timeNow := response.Time
    97  		syncErr := setSystemTime(timeNow)
    98  		if syncErr == nil {
    99  			log.Infoln("Sync system time success: %s", timeNow.Local().Format(ntp.TimeLayout))
   100  		} else {
   101  			log.Errorln("Write time to system: %s", syncErr)
   102  			srv.syncSystemTime = false
   103  		}
   104  	}
   105  	return nil
   106  }
   107  
   108  func (srv *Service) loopUpdate() {
   109  	for {
   110  		select {
   111  		case <-srv.ctx.Done():
   112  			return
   113  		case <-srv.ticker.C:
   114  		}
   115  		err := srv.update()
   116  		if err != nil {
   117  			log.Warnln("Sync time failed: %s", err)
   118  		}
   119  	}
   120  }
   121  
   122  func Now() time.Time {
   123  	now := time.Now()
   124  	if service.Running() && offset.Abs() > 0 {
   125  		now = now.Add(offset)
   126  	}
   127  	return now
   128  }