diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index ee3cd4b2..755aaa3d 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -4,19 +4,21 @@ import ( "context" box "github.com/sagernet/sing-box" - "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" "github.com/spf13/cobra" ) +var commandToolsFlagOutbound string + var commandTools = &cobra.Command{ Use: "tools", Short: "Experimental tools", } func init() { + commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound") mainCommand.AddCommand(commandTools) } @@ -25,10 +27,6 @@ func createPreStartedClient() (*box.Box, error) { if err != nil { return nil, err } - if options.Log == nil { - options.Log = &option.LogOptions{} - } - options.Log.Disabled = true instance, err := box.New(context.Background(), options, nil) if err != nil { 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) { if outboundTag == "" { - outbound := instance.Router().DefaultOutbound(network) + outbound := instance.Router().DefaultOutbound(N.NetworkName(network)) if outbound == nil { return nil, E.New("missing default outbound") } diff --git a/cmd/sing-box/cmd_tools_connect.go b/cmd/sing-box/cmd_tools_connect.go index ab832786..b904ebc9 100644 --- a/cmd/sing-box/cmd_tools_connect.go +++ b/cmd/sing-box/cmd_tools_connect.go @@ -15,10 +15,7 @@ import ( "github.com/spf13/cobra" ) -var ( - commandConnectFlagNetwork string - commandConnectFlagOutbound string -) +var commandConnectFlagNetwork string var commandConnect = &cobra.Command{ Use: "connect [address]", @@ -33,8 +30,7 @@ var commandConnect = &cobra.Command{ } func init() { - commandConnect.Flags().StringVar(&commandConnectFlagNetwork, "network", "tcp", "network type") - commandConnect.Flags().StringVar(&commandConnectFlagOutbound, "outbound", "", "outbound tag") + commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type") commandTools.AddCommand(commandConnect) } @@ -49,7 +45,7 @@ func connect(address string) error { return err } defer instance.Close() - dialer, err := createDialer(instance, commandConnectFlagNetwork, commandConnectFlagOutbound) + dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound) if err != nil { return err } diff --git a/cmd/sing-box/cmd_tools_fetch.go b/cmd/sing-box/cmd_tools_fetch.go index 4669d5d4..256c3f42 100644 --- a/cmd/sing-box/cmd_tools_fetch.go +++ b/cmd/sing-box/cmd_tools_fetch.go @@ -12,13 +12,10 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/spf13/cobra" ) -var commandFetchFlagOutbound string - var commandFetch = &cobra.Command{ Use: "fetch", Short: "Fetch an URL", @@ -32,7 +29,6 @@ var commandFetch = &cobra.Command{ } func init() { - commandFetch.Flags().StringVar(&commandFetchFlagOutbound, "outbound", "", "outbound tag") commandTools.AddCommand(commandFetch) } @@ -47,7 +43,7 @@ func fetch(args []string) error { httpClient = &http.Client{ Transport: &http.Transport{ 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 { return nil, err } diff --git a/cmd/sing-box/cmd_tools_synctime.go b/cmd/sing-box/cmd_tools_synctime.go new file mode 100644 index 00000000..20d73a6d --- /dev/null +++ b/cmd/sing-box/cmd_tools_synctime.go @@ -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 +} diff --git a/common/settings/time_stub.go b/common/settings/time_stub.go new file mode 100644 index 00000000..06204913 --- /dev/null +++ b/common/settings/time_stub.go @@ -0,0 +1,12 @@ +//go:build !(windows || linux || darwin) + +package settings + +import ( + "os" + "time" +) + +func SetSystemTime(nowTime time.Time) error { + return os.ErrInvalid +} diff --git a/common/settings/time_unix.go b/common/settings/time_unix.go new file mode 100644 index 00000000..9eff244d --- /dev/null +++ b/common/settings/time_unix.go @@ -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) +} diff --git a/common/settings/time_windows.go b/common/settings/time_windows.go new file mode 100644 index 00000000..5d95e3ba --- /dev/null +++ b/common/settings/time_windows.go @@ -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 +} diff --git a/constant/time.go b/constant/time.go new file mode 100644 index 00000000..4249a72d --- /dev/null +++ b/constant/time.go @@ -0,0 +1,3 @@ +package constant + +const TimeLayout = "2006-01-02 15:04:05 -0700" diff --git a/ntp/service.go b/ntp/service.go index ed0fdc16..b7ef999b 100644 --- a/ntp/service.go +++ b/ntp/service.go @@ -6,6 +6,8 @@ import ( "github.com/sagernet/sing-box/adapter" "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" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -14,19 +16,17 @@ import ( "github.com/sagernet/sing/common/ntp" ) -const timeLayout = "2006-01-02 15:04:05 -0700" - var _ adapter.TimeService = (*Service)(nil) type Service struct { - ctx context.Context - cancel context.CancelFunc - server M.Socksaddr - dialer N.Dialer - logger logger.Logger - - ticker *time.Ticker - clockOffset time.Duration + ctx context.Context + cancel context.CancelFunc + server M.Socksaddr + writeToSystem bool + dialer N.Dialer + logger logger.Logger + ticker *time.Ticker + clockOffset time.Duration } func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service { @@ -42,12 +42,13 @@ func NewService(ctx context.Context, router adapter.Router, logger logger.Logger interval = 30 * time.Minute } return &Service{ - ctx: ctx, - cancel: cancel, - server: server, - dialer: dialer.New(router, options.DialerOptions), - logger: logger, - ticker: time.NewTicker(interval), + ctx: ctx, + cancel: cancel, + server: server, + writeToSystem: options.WriteToSystem, + dialer: dialer.New(router, options.DialerOptions), + logger: logger, + ticker: time.NewTicker(interval), } } @@ -56,7 +57,7 @@ func (s *Service) Start() error { if err != nil { 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() return nil } @@ -82,7 +83,7 @@ func (s *Service) loopUpdate() { } err := s.update() 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 { s.logger.Warn("update time: ", err) } @@ -95,5 +96,11 @@ func (s *Service) update() error { return err } 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 } diff --git a/option/ntp.go b/option/ntp.go index a1c6d65a..809056dd 100644 --- a/option/ntp.go +++ b/option/ntp.go @@ -1,8 +1,9 @@ package option type NTPOptions struct { - Enabled bool `json:"enabled"` - Interval Duration `json:"interval,omitempty"` + Enabled bool `json:"enabled"` + Interval Duration `json:"interval,omitempty"` + WriteToSystem bool `json:"write_to_system,omitempty"` ServerOptions DialerOptions }