diff --git a/config/config.go b/config/config.go index 62fef1bd..cb030530 100644 --- a/config/config.go +++ b/config/config.go @@ -91,10 +91,11 @@ type Controller struct { // NTP config type NTP struct { - Enable bool `yaml:"enable"` - Server string `yaml:"server"` - Port int `yaml:"port"` - Interval int `yaml:"interval"` + Enable bool `yaml:"enable"` + WriteToSystem bool `yaml:"write-to-system"` + Server string `yaml:"server"` + Port int `yaml:"port"` + Interval int `yaml:"interval"` } // DNS config @@ -179,10 +180,11 @@ type Config struct { } type RawNTP struct { - Enable bool `yaml:"enable"` - Server string `yaml:"server"` - ServerPort int `yaml:"server-port"` - Interval int `yaml:"interval"` + Enable bool `yaml:"enable"` + WriteToSystem bool `yaml:"write-to-system"` + Server string `yaml:"server"` + ServerPort int `yaml:"server-port"` + Interval int `yaml:"interval"` } type RawDNS struct { @@ -399,6 +401,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { InboundInterface: "lo", Bypass: []string{}, }, + NTP: RawNTP{ + Enable: false, + WriteToSystem: false, + Server: "time.apple.com", + ServerPort: 123, + Interval: 30, + }, DNS: RawDNS{ Enable: false, IPv6: false, @@ -1162,24 +1171,13 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM } func paresNTP(rawCfg *RawConfig) *NTP { - var server = "time.apple.com" - var port = 123 - var interval = 30 cfg := rawCfg.NTP - if len(cfg.Server) != 0 { - server = cfg.Server - } - if cfg.ServerPort != 0 { - port = cfg.ServerPort - } - if cfg.Interval != 0 { - interval = cfg.Interval - } ntpCfg := &NTP{ - Enable: cfg.Enable, - Server: server, - Port: port, - Interval: interval, + Enable: cfg.Enable, + Server: cfg.Server, + Port: cfg.ServerPort, + Interval: cfg.Interval, + WriteToSystem: cfg.WriteToSystem, } return ntpCfg } diff --git a/go.mod b/go.mod index a57d9a2b..aa37eff2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.20 require ( github.com/3andne/restls-client-go v0.1.6 github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da - github.com/beevik/ntp v1.3.0 github.com/cilium/ebpf v0.11.0 github.com/coreos/go-iptables v0.7.0 github.com/dlclark/regexp2 v1.10.0 diff --git a/go.sum b/go.sum index e718b9e4..530b615d 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/beevik/ntp v1.3.0 h1:/w5VhpW5BGKS37vFm1p9oVk/t4HnnkKZAZIubHM6F7Q= -github.com/beevik/ntp v1.3.0/go.mod h1:vD6h1um4kzXpqmLTuu0cCLcC+NfvC0IC+ltmEDA8E78= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -197,7 +195,6 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1 github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig= @@ -210,8 +207,6 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= @@ -219,24 +214,15 @@ golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N0 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -250,30 +236,17 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -282,8 +255,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index b7111c14..f4364b7b 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -186,7 +186,7 @@ func updateExperimental(c *config.Config) { func updateNTP(c *config.NTP) { if c.Enable { ntp.ReCreateNTPService(net.JoinHostPort(c.Server, strconv.Itoa(c.Port)), - time.Duration(c.Interval)) + time.Duration(c.Interval), c.WriteToSystem) } } diff --git a/ntp/service.go b/ntp/service.go index af06a571..726ac636 100644 --- a/ntp/service.go +++ b/ntp/service.go @@ -3,7 +3,9 @@ package ntp import ( "context" "github.com/Dreamacro/clash/log" - "github.com/beevik/ntp" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" "sync" "time" ) @@ -12,43 +14,37 @@ var offset time.Duration var service *Service type Service struct { - addr string - interval time.Duration - ticker *time.Ticker - ctx context.Context - cancel context.CancelFunc - mu sync.Mutex - running bool + server M.Socksaddr + ticker *time.Ticker + ctx context.Context + cancel context.CancelFunc + mu sync.Mutex + syncSystemTime bool + running bool } -func ReCreateNTPService(addr string, interval time.Duration) { +func ReCreateNTPService(server string, interval time.Duration, syncSystemTime bool) { if service != nil { service.Stop() } ctx, cancel := context.WithCancel(context.Background()) - service = &Service{addr: addr, interval: interval, ctx: ctx, cancel: cancel} + service = &Service{ + ctx: ctx, + cancel: cancel, + server: M.ParseSocksaddr(server), + ticker: time.NewTicker(interval * time.Minute), + syncSystemTime: syncSystemTime, + } service.Start() } func (srv *Service) Start() { srv.mu.Lock() defer srv.mu.Unlock() - log.Infoln("NTP service start") - srv.ticker = time.NewTicker(srv.interval * time.Minute) + log.Infoln("NTP service start, sync system time is %t", srv.syncSystemTime) service.running = true - go func() { - for { - err := srv.updateTime(srv.addr) - if err != nil { - log.Warnln("updateTime failed: %s", err) - } - select { - case <-srv.ticker.C: - case <-srv.ctx.Done(): - return - } - } - }() + srv.update() + go srv.loopUpdate() } func (srv *Service) Stop() { @@ -70,20 +66,38 @@ func (srv *Service) Running() bool { return srv.running } -func (srv *Service) updateTime(addr string) error { - response, err := ntp.Query(addr) - if err != nil { - return err +func (srv *Service) update() { + response, err := ntp.Exchange(context.Background(), N.SystemDialer, srv.server) + if err != nil || response == nil { + log.Errorln("initialize time: %s", err) } - localTime := time.Now() - ntpTime := response.Time - offset = localTime.Sub(ntpTime) + offset = response.ClockOffset if offset > time.Duration(0) { - log.Warnln("System clock is ahead of NTP time by %s", offset) + log.Infoln("System clock is ahead of NTP time by %s", offset) } else if offset < time.Duration(0) { - log.Warnln("System clock is behind NTP time by %s", -offset) + log.Infoln("System clock is behind NTP time by %s", -offset) + } + if srv.syncSystemTime { + timeNow := response.Time + err = setSystemTime(timeNow) + if err == nil { + log.Infoln("sync system time success: %s", timeNow.Local().Format(ntp.TimeLayout)) + } else { + log.Errorln("write time to system: %s", err) + srv.syncSystemTime = false + } + } +} + +func (srv *Service) loopUpdate() { + for { + select { + case <-srv.ctx.Done(): + return + case <-srv.ticker.C: + } + srv.update() } - return nil } func Now() time.Time { diff --git a/ntp/time_stub.go b/ntp/time_stub.go new file mode 100644 index 00000000..12050983 --- /dev/null +++ b/ntp/time_stub.go @@ -0,0 +1,12 @@ +//go:build !(windows || linux || darwin) + +package ntp + +import ( + "os" + "time" +) + +func setSystemTime(nowTime time.Time) error { + return os.ErrInvalid +} diff --git a/ntp/time_unix.go b/ntp/time_unix.go new file mode 100644 index 00000000..9e819473 --- /dev/null +++ b/ntp/time_unix.go @@ -0,0 +1,14 @@ +//go:build linux || darwin + +package ntp + +import ( + "time" + + "golang.org/x/sys/unix" +) + +func setSystemTime(nowTime time.Time) error { + timeVal := unix.NsecToTimeval(nowTime.UnixNano()) + return unix.Settimeofday(&timeVal) +} diff --git a/ntp/time_windows.go b/ntp/time_windows.go new file mode 100644 index 00000000..8ef29b1b --- /dev/null +++ b/ntp/time_windows.go @@ -0,0 +1,32 @@ +package ntp + +import ( + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +func setSystemTime(nowTime time.Time) error { + var systemTime windows.Systemtime + systemTime.Year = uint16(nowTime.Year()) + systemTime.Month = uint16(nowTime.Month()) + systemTime.Day = uint16(nowTime.Day()) + systemTime.Hour = uint16(nowTime.Hour()) + systemTime.Minute = uint16(nowTime.Minute()) + systemTime.Second = uint16(nowTime.Second()) + systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000) + + dllKernel32 := windows.NewLazySystemDLL("kernel32.dll") + proc := dllKernel32.NewProc("SetSystemTime") + + _, _, err := proc.Call( + uintptr(unsafe.Pointer(&systemTime)), + ) + + if err != nil && err.Error() != "The operation completed successfully." { + return err + } + + return nil +}