mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 00:21:30 +00:00
ntp: Add write_to_system service option
This commit is contained in:
parent
99b2ab5526
commit
0558b3fc5c
|
@ -4,19 +4,21 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
box "github.com/sagernet/sing-box"
|
box "github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var commandToolsFlagOutbound string
|
||||||
|
|
||||||
var commandTools = &cobra.Command{
|
var commandTools = &cobra.Command{
|
||||||
Use: "tools",
|
Use: "tools",
|
||||||
Short: "Experimental tools",
|
Short: "Experimental tools",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
|
||||||
mainCommand.AddCommand(commandTools)
|
mainCommand.AddCommand(commandTools)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,10 +27,6 @@ func createPreStartedClient() (*box.Box, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.Log == nil {
|
|
||||||
options.Log = &option.LogOptions{}
|
|
||||||
}
|
|
||||||
options.Log.Disabled = true
|
|
||||||
instance, err := box.New(context.Background(), options, nil)
|
instance, err := box.New(context.Background(), options, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create service")
|
return nil, E.Cause(err, "create service")
|
||||||
|
@ -42,7 +40,7 @@ func createPreStartedClient() (*box.Box, error) {
|
||||||
|
|
||||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
||||||
if outboundTag == "" {
|
if outboundTag == "" {
|
||||||
outbound := instance.Router().DefaultOutbound(network)
|
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||||
if outbound == nil {
|
if outbound == nil {
|
||||||
return nil, E.New("missing default outbound")
|
return nil, E.New("missing default outbound")
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var commandConnectFlagNetwork string
|
||||||
commandConnectFlagNetwork string
|
|
||||||
commandConnectFlagOutbound string
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandConnect = &cobra.Command{
|
var commandConnect = &cobra.Command{
|
||||||
Use: "connect [address]",
|
Use: "connect [address]",
|
||||||
|
@ -33,8 +30,7 @@ var commandConnect = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
commandConnect.Flags().StringVar(&commandConnectFlagNetwork, "network", "tcp", "network type")
|
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
|
||||||
commandConnect.Flags().StringVar(&commandConnectFlagOutbound, "outbound", "", "outbound tag")
|
|
||||||
commandTools.AddCommand(commandConnect)
|
commandTools.AddCommand(commandConnect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +45,7 @@ func connect(address string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer instance.Close()
|
defer instance.Close()
|
||||||
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandConnectFlagOutbound)
|
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,10 @@ import (
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var commandFetchFlagOutbound string
|
|
||||||
|
|
||||||
var commandFetch = &cobra.Command{
|
var commandFetch = &cobra.Command{
|
||||||
Use: "fetch",
|
Use: "fetch",
|
||||||
Short: "Fetch an URL",
|
Short: "Fetch an URL",
|
||||||
|
@ -32,7 +29,6 @@ var commandFetch = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
commandFetch.Flags().StringVar(&commandFetchFlagOutbound, "outbound", "", "outbound tag")
|
|
||||||
commandTools.AddCommand(commandFetch)
|
commandTools.AddCommand(commandFetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +43,7 @@ func fetch(args []string) error {
|
||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
dialer, err := createDialer(instance, N.NetworkTCP, commandFetchFlagOutbound)
|
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
69
cmd/sing-box/cmd_tools_synctime.go
Normal file
69
cmd/sing-box/cmd_tools_synctime.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commandSyncTimeFlagServer string
|
||||||
|
commandSyncTimeOutputFormat string
|
||||||
|
commandSyncTimeWrite bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandSyncTime = &cobra.Command{
|
||||||
|
Use: "synctime",
|
||||||
|
Short: "Sync time using the NTP protocol",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := syncTime()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, "server", "s", "time.apple.com", "Set NTP server")
|
||||||
|
commandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, "format", "f", C.TimeLayout, "Set output format")
|
||||||
|
commandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, "write", "w", false, "Write time to system")
|
||||||
|
commandTools.AddCommand(commandSyncTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncTime() error {
|
||||||
|
instance, err := createPreStartedClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer instance.Close()
|
||||||
|
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
||||||
|
if serverAddress.Port == 0 {
|
||||||
|
serverAddress.Port = 123
|
||||||
|
}
|
||||||
|
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if commandSyncTimeWrite {
|
||||||
|
err = settings.SetSystemTime(response.Time)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write time to system")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat))
|
||||||
|
return nil
|
||||||
|
}
|
12
common/settings/time_stub.go
Normal file
12
common/settings/time_stub.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build !(windows || linux || darwin)
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
14
common/settings/time_unix.go
Normal file
14
common/settings/time_unix.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
||||||
|
return unix.Settimeofday(&timeVal)
|
||||||
|
}
|
32
common/settings/time_windows.go
Normal file
32
common/settings/time_windows.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package settings
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
3
constant/time.go
Normal file
3
constant/time.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const TimeLayout = "2006-01-02 15:04:05 -0700"
|
|
@ -6,6 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
@ -14,17 +16,15 @@ import (
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const timeLayout = "2006-01-02 15:04:05 -0700"
|
|
||||||
|
|
||||||
var _ adapter.TimeService = (*Service)(nil)
|
var _ adapter.TimeService = (*Service)(nil)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
server M.Socksaddr
|
server M.Socksaddr
|
||||||
|
writeToSystem bool
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
|
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
clockOffset time.Duration
|
clockOffset time.Duration
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ func NewService(ctx context.Context, router adapter.Router, logger logger.Logger
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
server: server,
|
server: server,
|
||||||
|
writeToSystem: options.WriteToSystem,
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
ticker: time.NewTicker(interval),
|
ticker: time.NewTicker(interval),
|
||||||
|
@ -56,7 +57,7 @@ func (s *Service) Start() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "initialize time")
|
return E.Cause(err, "initialize time")
|
||||||
}
|
}
|
||||||
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
|
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
|
||||||
go s.loopUpdate()
|
go s.loopUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -82,7 +83,7 @@ func (s *Service) loopUpdate() {
|
||||||
}
|
}
|
||||||
err := s.update()
|
err := s.update()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
|
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
|
||||||
} else {
|
} else {
|
||||||
s.logger.Warn("update time: ", err)
|
s.logger.Warn("update time: ", err)
|
||||||
}
|
}
|
||||||
|
@ -95,5 +96,11 @@ func (s *Service) update() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.clockOffset = response.ClockOffset
|
s.clockOffset = response.ClockOffset
|
||||||
|
if s.writeToSystem {
|
||||||
|
writeErr := settings.SetSystemTime(s.TimeFunc()())
|
||||||
|
if writeErr != nil {
|
||||||
|
s.logger.Warn("write time to system: ", writeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package option
|
||||||
type NTPOptions struct {
|
type NTPOptions struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Interval Duration `json:"interval,omitempty"`
|
Interval Duration `json:"interval,omitempty"`
|
||||||
|
WriteToSystem bool `json:"write_to_system,omitempty"`
|
||||||
ServerOptions
|
ServerOptions
|
||||||
DialerOptions
|
DialerOptions
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue